Compare commits

...

15 Commits

Author SHA1 Message Date
7a0cb13a1a MPD client: fix build on OpenIndiana 2023-07-24 08:33:45 +02:00
b6c54073cd Find ncursesw on OpenIndiana 2023-07-24 08:09:08 +02:00
62166f9679 lxdrgen-cpp-win32: fix return value handling 2023-07-10 09:34:42 +02:00
2edc9c6fd1 Add a C++ backend for LibertyXDR
Also change the C backend so that it also de/serializes
unions without any other fields besides the tag.
2023-07-07 16:43:52 +02:00
f78f8a70f1 lxdrgen-swift: fix prefix handling
"Any prefix will work, so long as it's 'Relay'."
2023-07-06 11:01:51 +02:00
be9a3e693e lxdrgen-swift: fix warnings with exhaustive unions 2023-07-06 06:54:23 +02:00
53197b51e5 Add a Swift backend for LibertyXDR 2023-07-06 06:54:22 +02:00
8466d0d850 CMakeLists.txt: link properly 2023-07-04 08:08:29 +02:00
4c2874649d liberty-xui: fix build on systems without A_ITALIC 2023-07-04 06:40:54 +02:00
717c301207 lxdrgen: fix decapitalization
decapitalize() is typically called on snaketocamel() output,
which always makes the first letter uppercase.
2023-06-28 16:24:59 +02:00
091f92bab3 liberty-xui: fix a build warning
On macOS, TIOCGWINSZ seems to be defined earlier.
2023-06-28 16:24:59 +02:00
556c25855e Fix a CMake warning 2023-06-20 01:24:29 +02:00
d01a1ff034 Turn liberty-tui into a terminal/X11 hybrid
Importing code from nncmpp, adjusting it to work with hex as well.
2023-06-19 13:06:12 +02:00
bd1013f16a Parse block attribute list lines
This code is of strategic importance, but its output is so far unused.
2023-06-11 10:02:16 +02:00
29bf109a51 asciiman: improve attribute handling 2022-10-09 18:43:37 +02:00
20 changed files with 3240 additions and 353 deletions

View File

@@ -1,5 +1,5 @@
project (liberty C)
cmake_minimum_required (VERSION 2.8.12)
project (liberty C CXX)
# Moar warnings
if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
@@ -29,7 +29,7 @@ link_directories (${libssl_LIBRARY_DIRS})
foreach (extra iconv rt)
find_library (extra_lib_${extra} ${extra})
if (extra_lib_${extra})
list (APPEND common_libraries ${extra})
list (APPEND common_libraries ${extra_lib_${extra}})
endif ()
endforeach ()
@@ -72,7 +72,7 @@ add_test (test-cmake-parser
# Test protocol code generation
set (lxdrgen_outputs)
set (lxdrgen_base "${PROJECT_BINARY_DIR}/lxdrgen.lxdr")
foreach (backend c go mjs)
foreach (backend c cpp go mjs swift)
list (APPEND lxdrgen_outputs ${lxdrgen_base}.${backend})
add_custom_command (OUTPUT ${lxdrgen_base}.${backend}
COMMAND env LC_ALL=C awk
@@ -91,9 +91,21 @@ add_custom_target (test-lxdrgen-outputs ALL DEPENDS ${lxdrgen_outputs})
set_source_files_properties (${lxdrgen_base}.c
PROPERTIES HEADER_FILE_ONLY TRUE)
add_executable (test-lxdrgen tests/lxdrgen.c ${lxdrgen_base}.c)
target_include_directories (test-lxdrgen PUBLIC ${PROJECT_BINARY_DIR})
add_test (NAME test-lxdrgen-c COMMAND test-lxdrgen)
add_executable (test-lxdrgen-c tests/lxdrgen.c ${lxdrgen_base}.c)
target_include_directories (test-lxdrgen-c PUBLIC ${PROJECT_BINARY_DIR})
add_test (NAME test-lxdrgen-c COMMAND test-lxdrgen-c)
set_source_files_properties (${lxdrgen_base}.cpp
PROPERTIES HEADER_FILE_ONLY TRUE)
if (WIN32)
add_executable (test-lxdrgen-cpp tests/lxdrgen.cpp
${lxdrgen_base}.cpp tools/lxdrgen-cpp-win32.cpp)
else ()
add_executable (test-lxdrgen-cpp tests/lxdrgen.cpp
${lxdrgen_base}.cpp tools/lxdrgen-cpp-posix.cpp)
endif ()
target_include_directories (test-lxdrgen-cpp PUBLIC ${PROJECT_BINARY_DIR})
add_test (NAME test-lxdrgen-cpp COMMAND test-lxdrgen-cpp)
find_program (GO_EXECUTABLE go)
if (GO_EXECUTABLE)
@@ -108,3 +120,11 @@ if (NODE_EXECUTABLE)
else ()
message (WARNING "Cannot test generated protocol code for Javascript")
endif ()
find_program (SWIFTC_EXECUTABLE swiftc)
if (SWIFTC_EXECUTABLE)
add_test (test-lxdrgen-swift
${SWIFTC_EXECUTABLE} -typecheck ${lxdrgen_base}.swift)
else ()
message (WARNING "Cannot test generated protocol code for Swift")
endif ()

View File

@@ -1,4 +1,4 @@
Copyright (c) 2014 - 2022, Přemysl Eric Janouch <p@janouch.name>
Copyright (c) 2014 - 2023, Přemysl Eric Janouch <p@janouch.name>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

View File

@@ -44,6 +44,12 @@ lxdrgen.awk::
lxdrgen-c.awk::
LibertyXDR backend that builds on top of the C pseudolibrary.
lxdrgen-cpp.awk::
lxdrgen-cpp-win32.cpp::
lxdrgen-cpp-posix.cpp::
LibertyXDR backend for C++, primarily targeting Win32 and its wide strings.
Link the result together with one of the accompanied source files.
lxdrgen-go.awk::
LibertyXDR backend for Go, supporting _encoding/json_ interfaces. It also
produces optimized JSON marshallers (however, note that the _json.Marshaler_
@@ -54,6 +60,9 @@ lxdrgen-mjs.awk::
LibertyXDR backend for Javascript, currently for decoding only.
It cuts a corner by not using BigInts, on par with `JSON.parse()`.
lxdrgen-swift.awk::
LibertyXDR backend for the Swift programming language.
Contributing and Support
------------------------
Use https://git.janouch.name/p/liberty to report any bugs, request features,

View File

@@ -7,7 +7,7 @@ pkg_check_modules (Ncursesw QUIET ncursesw)
set (required_vars Ncursesw_LIBRARIES)
if (NOT Ncursesw_FOUND)
find_library (Ncursesw_LIBRARIES NAMES ncursesw)
find_path (Ncursesw_INCLUDE_DIRS ncurses.h)
find_path (Ncursesw_INCLUDE_DIRS ncurses.h PATH_SUFFIXES ncurses)
list (APPEND required_vars Ncursesw_INCLUDE_DIRS)
endif (NOT Ncursesw_FOUND)

View File

@@ -1911,14 +1911,14 @@ mpd_client_connect_unix (struct mpd_client *self, const char *address,
// Expand tilde if needed
char *expanded = resolve_filename (address, xstrdup);
struct sockaddr_un sun;
sun.sun_family = AF_UNIX;
strncpy (sun.sun_path, expanded, sizeof sun.sun_path);
sun.sun_path[sizeof sun.sun_path - 1] = 0;
struct sockaddr_un sau;
sau.sun_family = AF_UNIX;
strncpy (sau.sun_path, expanded, sizeof sau.sun_path);
sau.sun_path[sizeof sau.sun_path - 1] = 0;
free (expanded);
if (connect (fd, (struct sockaddr *) &sun, sizeof sun))
if (connect (fd, (struct sockaddr *) &sau, sizeof sau))
{
error_set (e, "%s: %s", "connect", strerror (errno));
xclose (fd);

View File

@@ -1,270 +0,0 @@
/*
* liberty-tui.c: the ultimate C unlibrary: TUI
*
* Copyright (c) 2016 - 2017, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
// This file includes some common stuff to build TUI applications with
#include <ncurses.h>
// It is surprisingly hard to find a good library to handle Unicode shenanigans,
// and there's enough of those for it to be impractical to reimplement them.
//
// GLib ICU libunistring utf8proc
// Decently sized . . x x
// Grapheme breaks . x . x
// Character width x . x x
// Locale handling . . x .
// Liberal license . x . x
//
// Also note that the ICU API is icky and uses UTF-16 for its primary encoding.
//
// Currently we're chugging along with libunistring but utf8proc seems viable.
// Non-Unicode locales can mostly be handled with simple iconv like in sdtui.
// Similarly grapheme breaks can be guessed at using character width (a basic
// test here is Zalgo text).
//
// None of this is ever going to work too reliably anyway because terminals
// and Unicode don't go awfully well together. In particular, character cell
// devices have some problems with double-wide characters.
#include <unistr.h>
#include <uniwidth.h>
#include <uniconv.h>
#include <unicase.h>
// --- Configurable display attributes -----------------------------------------
struct attrs
{
short fg; ///< Foreground colour index
short bg; ///< Background colour index
chtype attrs; ///< Other attributes
};
/// Decode attributes in the value using a subset of the git config format,
/// ignoring all errors since it doesn't affect functionality
static struct attrs
attrs_decode (const char *value)
{
struct strv v = strv_make ();
cstr_split (value, " ", true, &v);
int colors = 0;
struct attrs attrs = { -1, -1, 0 };
for (char **it = v.vector; *it; it++)
{
char *end = NULL;
long n = strtol (*it, &end, 10);
if (*it != end && !*end && n >= SHRT_MIN && n <= SHRT_MAX)
{
if (colors == 0) attrs.fg = n;
if (colors == 1) attrs.bg = n;
colors++;
}
else if (!strcmp (*it, "bold")) attrs.attrs |= A_BOLD;
else if (!strcmp (*it, "dim")) attrs.attrs |= A_DIM;
else if (!strcmp (*it, "ul")) attrs.attrs |= A_UNDERLINE;
else if (!strcmp (*it, "blink")) attrs.attrs |= A_BLINK;
else if (!strcmp (*it, "reverse")) attrs.attrs |= A_REVERSE;
#ifdef A_ITALIC
else if (!strcmp (*it, "italic")) attrs.attrs |= A_ITALIC;
#endif // A_ITALIC
}
strv_free (&v);
return attrs;
}
// --- Terminal output ---------------------------------------------------------
// Necessary abstraction to simplify aligned, formatted character output
// This callback you need to implement in the application
static bool app_is_character_in_locale (ucs4_t ch);
struct row_char
{
ucs4_t c; ///< Unicode codepoint
chtype attrs; ///< Special attributes
int width; ///< How many cells this takes
};
struct row_buffer
{
ARRAY (struct row_char, chars) ///< Characters
int total_width; ///< Total width of all characters
};
static struct row_buffer
row_buffer_make (void)
{
struct row_buffer self = {};
ARRAY_INIT_SIZED (self.chars, 256);
return self;
}
static void
row_buffer_free (struct row_buffer *self)
{
free (self->chars);
}
/// Replace invalid chars and push all codepoints to the array w/ attributes.
static void
row_buffer_append (struct row_buffer *self, const char *str, chtype attrs)
{
// The encoding is only really used internally for some corner cases
const char *encoding = locale_charset ();
// Note that this function is a hotspot, try to keep it decently fast
struct row_char current = { .attrs = attrs };
struct row_char invalid = { .attrs = attrs, .c = '?', .width = 1 };
const uint8_t *next = (const uint8_t *) str;
while ((next = u8_next (&current.c, next)))
{
current.width = uc_width (current.c, encoding);
if (current.width < 0 || !app_is_character_in_locale (current.c))
current = invalid;
ARRAY_RESERVE (self->chars, 1);
self->chars[self->chars_len++] = current;
self->total_width += current.width;
}
}
static void
row_buffer_append_args (struct row_buffer *self, const char *s, ...)
ATTRIBUTE_SENTINEL;
static void
row_buffer_append_args (struct row_buffer *self, const char *s, ...)
{
va_list ap;
va_start (ap, s);
while (s)
{
row_buffer_append (self, s, va_arg (ap, chtype));
s = va_arg (ap, const char *);
}
va_end (ap);
}
static void
row_buffer_append_buffer (struct row_buffer *self, const struct row_buffer *rb)
{
ARRAY_RESERVE (self->chars, rb->chars_len);
memcpy (self->chars + self->chars_len, rb->chars,
rb->chars_len * sizeof *rb->chars);
self->chars_len += rb->chars_len;
self->total_width += rb->total_width;
}
/// Pop as many codepoints as needed to free up "space" character cells.
/// Given the suffix nature of combining marks, this should work pretty fine.
static int
row_buffer_pop_cells (struct row_buffer *self, int space)
{
int made = 0;
while (self->chars_len && made < space)
made += self->chars[--self->chars_len].width;
self->total_width -= made;
return made;
}
static void
row_buffer_space (struct row_buffer *self, int width, chtype attrs)
{
if (width < 0)
return;
ARRAY_RESERVE (self->chars, (size_t) width);
struct row_char space = { .attrs = attrs, .c = ' ', .width = 1 };
self->total_width += width;
while (width-- > 0)
self->chars[self->chars_len++] = space;
}
static void
row_buffer_ellipsis (struct row_buffer *self, int target)
{
if (self->total_width <= target
|| !row_buffer_pop_cells (self, self->total_width - target))
return;
// We use attributes from the last character we've removed,
// assuming that we don't shrink the array (and there's no real need)
ucs4_t ellipsis = 0x2026; // …
if (app_is_character_in_locale (ellipsis))
{
if (self->total_width >= target)
row_buffer_pop_cells (self, 1);
if (self->total_width + 1 <= target)
row_buffer_append (self, "", self->chars[self->chars_len].attrs);
}
else if (target >= 3)
{
if (self->total_width >= target)
row_buffer_pop_cells (self, 3);
if (self->total_width + 3 <= target)
row_buffer_append (self, "...", self->chars[self->chars_len].attrs);
}
}
static void
row_buffer_align (struct row_buffer *self, int target, chtype attrs)
{
row_buffer_ellipsis (self, target);
row_buffer_space (self, target - self->total_width, attrs);
}
static void
row_buffer_print (uint32_t *ucs4, chtype attrs)
{
// This assumes that we can reset the attribute set without consequences
char *str = u32_strconv_to_locale (ucs4);
if (str)
{
attrset (attrs);
addstr (str);
attrset (0);
free (str);
}
}
static void
row_buffer_flush (struct row_buffer *self)
{
if (!self->chars_len)
return;
// We only NUL-terminate the chunks because of the libunistring API
uint32_t chunk[self->chars_len + 1], *insertion_point = chunk;
for (size_t i = 0; i < self->chars_len; i++)
{
struct row_char *iter = self->chars + i;
if (i && iter[0].attrs != iter[-1].attrs)
{
row_buffer_print (chunk, iter[-1].attrs);
insertion_point = chunk;
}
*insertion_point++ = iter->c;
*insertion_point = 0;
}
row_buffer_print (chunk, self->chars[self->chars_len - 1].attrs);
}

2198
liberty-xui.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -32,13 +32,6 @@
#define LIBERTY_WANT_PROTO_MPD
#include "../liberty.c"
#include "../liberty-tui.c"
static bool
app_is_character_in_locale (ucs4_t ch)
{
return ch < 128;
}
// --- UTF-8 -------------------------------------------------------------------
@@ -211,19 +204,6 @@ test_config_item_parse (const uint8_t *data, size_t size)
config_item_destroy (item);
}
// --- TUI ---------------------------------------------------------------------
static void
test_attrs_decode (const uint8_t *data, size_t size)
{
struct str wrap = str_make ();
str_append_data (&wrap, data, size);
attrs_decode (wrap.str);
str_free (&wrap);
}
// --- MPD ---------------------------------------------------------------------
static void
@@ -266,7 +246,6 @@ LLVMFuzzerInitialize (int *argcp, char ***argvp)
REGISTER (fcgi_parser_push)
REGISTER (fcgi_nv_parser_push)
REGISTER (config_item_parse)
REGISTER (attrs_decode)
REGISTER (mpd_client_process_input)
char **argv = *argvp, *option = "-test=", *name = NULL;

View File

@@ -53,6 +53,10 @@ test_ser_deser_free (void)
u->others.bar = str_make ();
for (int i = rand () % 0x30; i > 0; i--)
str_append_c (&u->others.bar, 0x30 + i);
u->others.baz_len = rand () % 0x30;
u->others.baz = xcalloc (1, u->others.baz_len);
for (uint32_t i = 0; i < u->others.baz_len; i++)
u->others.baz[i] = 0x30 + i;
break;
case 2:
u->tag = PROTO_GEN_ENUM_NOTHING;
@@ -62,6 +66,8 @@ test_ser_deser_free (void)
}
}
a.o.tag = PROTO_GEN_ENUM_NOTHING;
struct str buf = str_make ();
hard_assert (proto_gen_struct_serialize (&a, &buf));
struct msg_unpacker r = msg_unpacker_make (buf.str, buf.len);
@@ -92,6 +98,9 @@ test_ser_deser_free (void)
hard_assert (ua->others.bar.len == ub->others.bar.len);
hard_assert (!memcmp (ua->others.bar.str, ub->others.bar.str,
ua->others.bar.len));
hard_assert (ua->others.baz_len == ub->others.baz_len);
hard_assert (!memcmp (ua->others.baz, ub->others.baz,
ua->others.baz_len));
break;
case PROTO_GEN_ENUM_NOTHING:
break;
@@ -100,6 +109,8 @@ test_ser_deser_free (void)
}
}
hard_assert (a.o.tag == b.o.tag);
// Emulate partially deserialized data to test disposal of that.
for (size_t i = b.u_len - CASES; i < b.u_len; i++)
{

132
tests/lxdrgen.cpp Normal file
View File

@@ -0,0 +1,132 @@
/*
* tests/lxdrgen.cpp
*
* Copyright (c) 2023, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "lxdrgen.lxdr.cpp"
#include <cstdlib>
static void
hard_assert (bool condition, const char *description)
{
if (!condition)
{
fprintf (stderr, "assertion failed: %s\n", description);
abort ();
}
}
#define hard_assert(condition) hard_assert (condition, #condition)
int
main (int argc, char *argv[])
{
hard_assert (ProtoGen::VERSION == 1);
enum { CASES = 3 };
ProtoGen::Struct a = {}, b = {};
a.u.resize (CASES + rand () % 100);
for (size_t i = 0; i < a.u.size (); i++)
{
std::unique_ptr<ProtoGen::Union> &u = a.u[i];
switch (i % CASES)
{
case 0:
{
auto numbers = new ProtoGen::Union_Numbers ();
numbers->a = rand () % UINT8_MAX;
numbers->b = rand () % UINT16_MAX;
numbers->c = rand () % UINT32_MAX;
numbers->d = rand () % UINT64_MAX;
numbers->e = rand () % UINT8_MAX;
numbers->f = rand () % UINT16_MAX;
numbers->g = rand () % UINT32_MAX;
numbers->h = rand () % UINT64_MAX;
u.reset (numbers);
break;
}
case 1:
{
auto others = new ProtoGen::Union_Others ();
others->foo = rand () % 2;
for (int i = rand () % 0x30; i > 0; i--)
others->bar += 0x30 + i;
for (int i = rand () % 0x30; i > 0; i--)
others->baz.push_back (0x30 + i);
u.reset (others);
break;
}
case 2:
u.reset (new ProtoGen::Union_Nothing ());
break;
default:
hard_assert (!"unhandled case");
}
}
a.o.reset (new ProtoGen::Onion_Nothing ());
LibertyXDR::Writer buf;
hard_assert (a.serialize (buf));
LibertyXDR::Reader r;
r.data = buf.data.data ();
r.length = buf.data.size ();
hard_assert (b.deserialize (r));
hard_assert (!r.length);
hard_assert (a.u.size () == b.u.size ());
for (size_t i = 0; i < a.u.size (); i++)
{
ProtoGen::Union *ua = a.u[i].get ();
ProtoGen::Union *ub = b.u[i].get ();
hard_assert (ua->tag == ub->tag);
switch (ua->tag)
{
case ProtoGen::Enum::NUMBERS:
{
auto a = dynamic_cast<ProtoGen::Union_Numbers *> (ua);
auto b = dynamic_cast<ProtoGen::Union_Numbers *> (ub);
hard_assert (a->a == b->a);
hard_assert (a->b == b->b);
hard_assert (a->c == b->c);
hard_assert (a->d == b->d);
hard_assert (a->e == b->e);
hard_assert (a->f == b->f);
hard_assert (a->g == b->g);
hard_assert (a->h == b->h);
break;
}
case ProtoGen::Enum::OTHERS:
{
auto a = dynamic_cast<ProtoGen::Union_Others *> (ua);
auto b = dynamic_cast<ProtoGen::Union_Others *> (ub);
hard_assert (a->foo == b->foo);
hard_assert (a->bar == b->bar);
hard_assert (a->baz == b->baz);
break;
}
case ProtoGen::Enum::NOTHING:
break;
default:
hard_assert (!"unexpected case");
}
}
hard_assert (a.o->tag == b.o->tag);
return 0;
}

View File

@@ -17,7 +17,13 @@ struct Struct {
case OTHERS:
bool foo;
string bar;
u8 baz<>;
case NOTHING:
void;
} u<>;
union Onion switch (Enum tag) {
case NOTHING:
void;
} o;
};

View File

@@ -1,6 +1,6 @@
# asciiman.awk: simplified AsciiDoc to manual page converter
#
# Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
# Copyright (c) 2022 - 2023, Přemysl Eric Janouch <p@janouch.name>
# SPDX-License-Identifier: 0BSD
#
# This is not intended to produce great output, merely useful output.
@@ -20,21 +20,17 @@ function fatal(message) {
exit 1
}
function haveattribute(name) {
return name in Attrs || ("asciidoc-" name) in ENVIRON
BEGIN {
for (name in ENVIRON)
if (match(name, /^asciidoc-/))
Attrs[substr(name, RSTART + RLENGTH)] = ENVIRON[name]
}
function getattribute(name) {
if (!(name in Attrs) && ("asciidoc-" name) in ENVIRON)
Attrs[name] = ENVIRON["asciidoc-" name]
return Attrs[name]
}
function expand(s, attr, v) {
while (match(s, /[{][^{}]*[}]/)) {
attr = substr(s, RSTART + 1, RLENGTH - 2)
if (haveattribute(attr))
v = v substr(s, 1, RSTART - 1) getattribute(attr)
function expand(s, attrname, v) {
while (match(s, /[{][^{}]+[}]/)) {
attrname = substr(s, RSTART + 1, RLENGTH - 2)
if (attrname in Attrs)
v = v substr(s, 1, RSTART - 1) Attrs[attrname]
else
v = v substr(s, 1, RSTART + RLENGTH - 1)
s = substr(s, RSTART + RLENGTH)
@@ -49,13 +45,20 @@ function escape(s) {
return s
}
function readattribute(line, attrname, attrvalue) {
if (match(line, /^:[^:]*: /)) {
function readattribute(line, attrname) {
if (match(line, /^:[^:]+:$/)) {
Attrs[substr(line, RSTART + 1, RLENGTH - 2)] = ""
} else if (match(line, /^:[^:]+!:$/)) {
delete Attrs[substr(line, RSTART + 1, RLENGTH - 3)]
} else if (match(line, /^:![^:]+:$/)) {
delete Attrs[substr(line, RSTART + 2, RLENGTH - 3)]
} else if (match(line, /^:[^:]+: /)) {
attrname = substr(line, RSTART + 1, RLENGTH - 3)
attrvalue = substr(line, RSTART + RLENGTH)
Attrs[attrname] = expand(attrvalue)
return 1
Attrs[attrname] = expand(substr(line, RSTART + RLENGTH))
} else {
return 0
}
return 1
}
NR == 1 {
@@ -80,15 +83,56 @@ NR == 1 {
# Requesting tbl(1), even though we currently do not support tables.
print "'\\\" t"
printf ".TH \"%s\" \"%s\" \"\" \"%s\"",
toupper(name), section, getattribute("mansource")
if (getattribute("manmanual"))
printf " \"%s\"", getattribute("manmanual")
toupper(name), section, Attrs["mansource"]
if ("manmanual" in Attrs)
printf " \"%s\"", Attrs["manmanual"]
print ""
# Hyphenation is indeed rather annoying, in particular with long links.
print ".nh"
}
function readattrlist(line, posattrs, namedattrs, name, value, n) {
if (!match(line, /^\[.*\]$/))
return 0
line = expand(substr(line, RSTART + 1, RLENGTH - 2))
while (line) {
name = ""
if (match(line, /^[[:alnum:]][[:alnum:]-]*/)) {
value = substr(line, RSTART, RLENGTH)
if (match(substr(line, RSTART + RLENGTH),
/^[[:space:]]*=[[:space:]]*/)) {
name = value
line = substr(line, 1 + length(name) + RLENGTH)
}
}
# The quoting syntax actually is awful like this.
if (match(line, /^"(\\.|[^"\\])*"/)) {
value = substr(line, RSTART + 1, RLENGTH - 2)
gsub(/\\"/, "\"", value)
} else if (match(line, /^'(\\.|[^'\\])*'/)) {
value = substr(line, RSTART + 1, RLENGTH - 2)
gsub(/\\'/, "'", value)
} else {
match(line, /^[^,]*/)
value = substr(line, RSTART, RLENGTH)
sub(/[[:space:]]*$/, "", value)
}
line = substr(line, RSTART + RLENGTH)
sub(/^[[:space:]]*,[[:space:]]*/, "", line)
if (!name)
posattrs[++n] = value
else if (value == "None")
delete namedattrs[name]
else
namedattrs[name] = value
}
return 1
}
function format(line, v) {
# Pass-through, otherwise useful for hacks, is a bit of a lie here,
# and formatting doesn't fully respect word boundaries.
@@ -151,7 +195,7 @@ function inline(line) {
}
# Returns 1 iff the left-over $0 should be processed further.
function process(firstline) {
function process(firstline, posattrs, namedattrs) {
if (readattribute(firstline))
return 0
if (getline <= 0) {
@@ -159,6 +203,17 @@ function process(firstline) {
return 0
}
# Block attribute list lines.
delete posattrs[0]
delete namedattrs[0]
while (readattrlist(firstline, posattrs, namedattrs)) {
firstline = $0
if (getline <= 0) {
inline(firstline)
return 0
}
}
# mandoc(1) automatically precedes section headers with blank lines.
if (length(firstline) == length($0) && /^-+$/) {
print ".SH \"" escape(toupper(expand(firstline))) "\""
@@ -196,7 +251,7 @@ function process(firstline) {
return 1
}
# We generally assume these block end with a blank line.
# We generally assume these blocks end with a blank line.
if (match(firstline, /^[[:space:]]*[*][[:space:]]+/)) {
flushspace()

View File

@@ -222,7 +222,7 @@ function codegen_struct(name, cg, ctype, funcname) {
delete cg[i]
}
function codegen_union_tag(d, cg) {
function codegen_union_tag(name, d, cg) {
cg["tagtype"] = d["type"]
cg["tagname"] = d["name"]
append(cg, "fields", "\t" CodegenCType[d["type"]] " " d["name"] ";\n")
@@ -259,7 +259,7 @@ function codegen_union_struct( \
"\t\tbreak;\n")
}
function codegen_union(name, cg, f, ctype, funcname) {
function codegen_union(name, cg, exhaustive, f, ctype, funcname) {
ctype = "union " PrefixLower cameltosnake(name)
print ""
print ctype " {"
@@ -281,7 +281,7 @@ function codegen_union(name, cg, f, ctype, funcname) {
CodegenDispose[name] = "\t" funcname "(&%s);\n"
}
if (cg["serialize"]) {
{
funcname = PrefixLower cameltosnake(name) "_serialize"
print ""
print "static bool\n" \
@@ -299,7 +299,7 @@ function codegen_union(name, cg, f, ctype, funcname) {
CodegenSerialize[name] = "\tif (!" funcname "(&%s, w))\n" \
"\t\treturn false;\n"
}
if (cg["deserialize"]) {
{
funcname = PrefixLower cameltosnake(name) "_deserialize"
print ""
print "static bool\n" \

View File

@@ -0,0 +1,67 @@
// lxdrgen-cpp-posix.cpp: POSIX support code for lxdrgen-cpp.awk.
//
// Copyright (c) 2023, Přemysl Eric Janouch <p@janouch.name>
// SPDX-License-Identifier: 0BSD
#include <iconv.h>
#include <cstdint>
#include <string>
// Various BSD derivatives may have a problem here.
// Linux defines __STDC_ISO_10646__, but also supports "WCHAR_T".
#ifdef APPLE
#define ICONV_WCHAR "UTF-32"
#else
#define ICONV_WCHAR "WCHAR_T"
#endif
namespace LibertyXDR {
bool utf8_to_wstring(const uint8_t *utf8, size_t length, std::wstring &wide) {
iconv_t conv = iconv_open(ICONV_WCHAR, "UTF-8");
if (conv == (iconv_t) -1)
return false;
wchar_t buffer[1024] = {};
char *start = (char *) buffer, *out = start, *in = (char *) utf8;
size_t available = sizeof buffer;
wide.clear();
while (iconv(conv, &in, &length, &out, &available) == (size_t) -1) {
if (errno != E2BIG) {
iconv_close(conv);
return false;
}
wide.append(buffer, (out - start) / sizeof *buffer);
out = start;
available = sizeof buffer;
}
wide.append(buffer, (out - start) / sizeof *buffer);
iconv_close(conv);
return true;
}
bool wstring_to_utf8(const std::wstring &wide, std::string &utf8) {
iconv_t conv = iconv_open("UTF-8", ICONV_WCHAR);
if (conv == (iconv_t) -1)
return false;
char buffer[1024] = {}, *out = buffer, *in = (char *) wide.data();
size_t available = sizeof buffer, length = wide.size() * sizeof wide[0];
utf8.clear();
while (iconv(conv, &in, &length, &out, &available) == (size_t) -1) {
if (errno != E2BIG) {
iconv_close(conv);
return false;
}
utf8.append(buffer, out - buffer);
out = buffer;
available = sizeof buffer;
}
utf8.append(buffer, out - buffer);
iconv_close(conv);
return true;
}
} // namespace LibertyXDR

View File

@@ -0,0 +1,47 @@
// lxdrgen-cpp-win32.cpp: Win32 support code for lxdrgen-cpp.awk.
//
// Copyright (c) 2023, Přemysl Eric Janouch <p@janouch.name>
// SPDX-License-Identifier: 0BSD
#include <windows.h>
#include <climits>
#include <cstdint>
#include <string>
namespace LibertyXDR {
bool utf8_to_wstring(const uint8_t *utf8, size_t length, std::wstring &wide) {
wide.clear();
if (!length)
return true;
if (length > INT_MAX)
return false;
int size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
(LPCCH) utf8, length, nullptr, 0);
if (size <= 0)
return false;
wide.resize(size);
return !!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
(LPCCH) utf8, length, wide.data(), size);
}
bool wstring_to_utf8(const std::wstring &wide, std::string &utf8) {
utf8.clear();
if (wide.empty())
return true;
if (wide.size() > INT_MAX)
return false;
int size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
(LPCWCH) wide.data(), wide.size(), nullptr, 0, NULL, NULL);
if (size <= 0)
return false;
utf8.resize(size);
return !!WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
(LPCWCH) wide.data(), wide.size(), utf8.data(), size, NULL, NULL);
}
} // namespace LibertyXDR

350
tools/lxdrgen-cpp.awk Normal file
View File

@@ -0,0 +1,350 @@
# lxdrgen-cpp.awk: C++ backend for lxdrgen.awk.
#
# This backend is intended for Windows, it just happens to have a fallback
# that will probably work on Unices, of which we make use in tests.
#
# Copyright (c) 2023, Přemysl Eric Janouch <p@janouch.name>
# SPDX-License-Identifier: 0BSD
function define_internal(name, ctype) {
Types[name] = "internal"
CodegenCType[name] = ctype
CodegenSerialize[name] = \
"\tw.append(%s);\n"
CodegenDeserialize[name] = \
"\tif (!r.read(%s))\n" \
"\t\treturn false;\n"
}
function define_int(shortname, ctype) {
define_internal(shortname, ctype)
}
function define_sint(size) { define_int("i" size, "int" size "_t") }
function define_uint(size) { define_int("u" size, "uint" size "_t") }
function codegen_begin() {
define_sint("8")
define_sint("16")
define_sint("32")
define_sint("64")
define_uint("8")
define_uint("16")
define_uint("32")
define_uint("64")
define_internal("string", "std::wstring")
define_internal("bool", "bool")
CodegenSerialize["string"] = \
"\tif (!w.append(%s))\n" \
"\t\treturn false;\n"
print "// Code generated from " FILENAME ". DO NOT EDIT."
print ""
print "#include <cstdint>"
print "#include <memory>"
print "#include <string>"
print "#include <vector>"
print ""
print "namespace LibertyXDR {"
print ""
print "bool utf8_to_wstring("
print "\tconst uint8_t *utf8, size_t length, std::wstring &wide);"
print "bool wstring_to_utf8("
print "\tconst std::wstring &wide, std::string &utf8);"
print ""
print "struct Reader {"
print "\tconst uint8_t *data = {};"
print "\tsize_t length = {};"
print ""
print "\ttemplate<typename T> bool read(T &number) {"
print "\t\tif (length < sizeof number)"
print "\t\t\treturn false;"
print ""
print "\t\tnumber = 0;"
print "\t\tfor (size_t i = 0; i < sizeof number; i++) {"
print "\t\t\tnumber = number << 8 | *data++;"
print "\t\t\tlength--;"
print "\t\t}"
print "\t\treturn true;"
print "\t}"
print ""
print "\tbool read(bool &boolean) {"
print "\t\tuint8_t number = 0;"
print "\t\tif (!read(number))"
print "\t\t\treturn false;"
print ""
print "\t\tboolean = number != 0;"
print "\t\treturn true;"
print "\t}"
print ""
print "\tbool read(std::wstring &string) {"
print "\t\tuint32_t size = 0;"
print "\t\tif (!read(size) || size > length)"
print "\t\t\treturn false;"
print "\t\tif (!utf8_to_wstring(data, size, string))"
print "\t\t\treturn false;"
print ""
print "\t\tdata += size;"
print "\t\tlength -= size;"
print "\t\treturn true;"
print "\t}"
print ""
print "\tbool read(std::vector<uint8_t> &vector) {"
print "\t\tuint32_t size = 0;"
print "\t\tif (!read(size) || size > length)"
print "\t\t\treturn false;"
print "\t\tvector.assign(data, data + size);"
print ""
print "\t\tdata += size;"
print "\t\tlength -= size;"
print "\t\treturn true;"
print "\t}"
print "};"
print ""
print "struct Writer {"
print "\tstd::vector<uint8_t> data;"
print ""
print "\ttemplate<typename T> bool append(T number) {"
print "\t\tuint8_t buffer[sizeof number], *p = buffer + sizeof buffer;"
print "\t\twhile (p != buffer) {"
print "\t\t\t*--p = number;"
print "\t\t\tnumber >>= 8;"
print "\t\t}"
print "\t\tdata.insert(data.end(), buffer, buffer + sizeof buffer);"
print "\t\treturn true;"
print "\t}"
print ""
print "\tbool append(int8_t number) {"
print "\t\tdata.push_back(number);"
print "\t\treturn true;"
print "\t}"
print ""
print "\tbool append(uint8_t number) {"
print "\t\tdata.push_back(number);"
print "\t\treturn true;"
print "\t}"
print ""
print "\tbool append(bool boolean) {"
print "\t\treturn append(uint8_t(boolean));"
print "\t}"
print ""
print "\tbool append(const std::wstring &string) {"
print "\t\tif (string.size() > UINT32_MAX)"
print "\t\t\treturn false;"
print ""
print "\t\tstd::string utf8;"
print "\t\tif (!wstring_to_utf8(string, utf8))"
print "\t\t\treturn false;"
print ""
print "\t\tappend<uint32_t>(utf8.size());"
print "\t\tdata.insert(data.end(), utf8.begin(), utf8.end());"
print "\t\treturn true;"
print "\t}"
print "};"
print ""
print "} // namespace LibertyXDR"
print "namespace " PrefixCamel " {"
}
END {
print ""
print "} // namespace " PrefixCamel
}
function codegen_constant(name, value) {
print ""
print "enum { " name " = " value " };"
}
function codegen_enum_value(name, subname, value, cg) {
append(cg, "fields", "\t" subname " = " value ",\n")
}
function codegen_enum(name, cg) {
print ""
print "enum struct " name " : int8_t {"
print cg["fields"] "};"
# XXX: This should also check if it isn't out-of-range for any reason,
# but our usage of sprintf() stands in the way a bit.
CodegenSerialize[name] = \
"\tw.append(static_cast<int8_t>(%s));\n"
CodegenDeserialize[name] = \
"\t{\n" \
"\t\tint8_t v = 0;\n" \
"\t\tif (!r.read(v) || !v)\n" \
"\t\t\treturn false;\n" \
"\t\t%s = static_cast<" name ">(v);\n" \
"\t}\n"
CodegenCType[name] = name
for (i in cg)
delete cg[i]
}
# Some identifiers do not pose a problem in C, but do in our C++.
function codegen_struct_sanitize(name) {
if (name ~ /^(serialize|deserialize)_*$/ ||
name ~ /^(catch|class|delete|except|finally|friend|new|operator)_*$/ ||
name ~ /^(private|protected|public|template|this|throw|try|virtual)_*$/)
return name "_"
return name
}
function codegen_struct_tag(d, cg, name, f) {
name = codegen_struct_sanitize(d["name"])
f = "this->" name
append(cg, "serialize", sprintf(CodegenSerialize[d["type"]], f))
# Do not deserialize here, that would be out of order.
}
function codegen_struct_field(d, cg, name, f, serialize, deserialize) {
name = codegen_struct_sanitize(d["name"])
f = "this->" name
serialize = CodegenSerialize[d["type"]]
deserialize = CodegenDeserialize[d["type"]]
if (!d["isarray"]) {
append(cg, "fields",
"\t" CodegenCType[d["type"]] " " name " = {};\n")
append(cg, "serialize", sprintf(serialize, f))
append(cg, "deserialize", sprintf(deserialize, f))
return
}
append(cg, "fields",
"\tstd::vector<" CodegenCType[d["type"]] "> " name ";\n")
# XXX: We should probably pedantically check for overflows.
append(cg, "serialize",
sprintf(CodegenSerialize["u32"], "uint32_t(" f ".size())") \
"\tfor (const auto &it : " f ")\n" \
indent(sprintf(serialize, "it")))
if (d["type"] == "u8") {
append(cg, "deserialize",
"\tif (!r.read(" f "))\n" \
"\t\treturn false;\n")
} else if (deserialize) {
append(cg, "deserialize",
"\t{\n" \
"\t\tuint32_t size = 0;\n" \
indent(sprintf(CodegenDeserialize["u32"], "size")) \
"\t\t" f ".resize(size);\n" \
"\t}\n" \
"\tfor (auto &it : " f ")\n" \
indent(sprintf(deserialize, "it")))
}
}
function codegen_struct(name, cg) {
print ""
print "struct " name " {"
print cg["fields"]
print "\tbool serialize(LibertyXDR::Writer &w) const {"
print indent(cg["serialize"]) "\t\treturn true;"
print "\t}"
print ""
print "\tbool deserialize([[maybe_unused]] LibertyXDR::Reader &r) {"
print indent(cg["deserialize"]) "\t\treturn true;"
print "\t}"
print "};"
CodegenSerialize[name] = "\tif (!%s->serialize(w))\n" \
"\t\treturn false;\n"
CodegenDeserialize[name] = "\tif (!%s->deserialize(r))\n" \
"\t\treturn false;\n"
CodegenCType[name] = name
for (i in cg)
delete cg[i]
}
function codegen_union_tag(name, d, cg, tagname) {
cg["tagtype"] = d["type"]
cg["tagname"] = tagname = codegen_struct_sanitize(d["name"])
print ""
print "struct " name " {"
print "\t" CodegenCType[d["type"]] " " tagname " = {};"
print "\tvirtual ~" name "() = 0;"
print "\tvirtual bool serialize(LibertyXDR::Writer &w) const = 0;"
print "\tvirtual bool deserialize(LibertyXDR::Reader &r) = 0;"
print "};"
print ""
print name "::~" name "() {}"
}
function codegen_union_struct(name, casename, cg, scg, structname) {
# And thus not all generated structs are present in Types.
structname = name "_" snaketocamel(casename)
print ""
print "struct " structname " : virtual public " name " {"
print scg["fields"]
print "\t" structname "() {"
print "\t\tthis->" cg["tagname"] " = " \
CodegenCType[cg["tagtype"]] "::" casename ";"
print "\t}"
print ""
print "\tvirtual bool serialize(LibertyXDR::Writer &w) const {"
print indent(scg["serialize"]) "\t\treturn true;"
print "\t}"
print ""
print "\tvirtual bool deserialize([[maybe_unused]] LibertyXDR::Reader &r) {"
print indent(scg["deserialize"]) "\t\treturn true;"
print "\t}"
print "};"
append(cg, "deserialize",
"\tcase " CodegenCType[cg["tagtype"]] "::" casename ":\n" \
"\t\treturn new " structname "();\n")
CodegenSerialize[structname] = "\tif (!%s->serialize(w))\n" \
"\t\treturn false;\n"
CodegenDeserialize[structname] = "\tif (!%s->deserialize(r))\n" \
"\t\treturn false;\n"
CodegenCType[structname] = structname
for (i in scg)
delete scg[i]
}
function codegen_union(name, cg, exhaustive, ctype) {
CodegenSerialize[name] = "\tif (!%s->serialize(w))\n" \
"\t\treturn false;\n"
ctype = "std::unique_ptr<" name ">"
if (cg["deserialize"]) {
print ""
print "static " name " *read" name "(" \
CodegenCType[cg["tagtype"]] " " cg["tagname"] ") {"
print "\tswitch (" cg["tagname"] ") {"
print cg["deserialize"] "\tdefault:"
print "\t\treturn nullptr;"
print "\t}"
print "}"
print ""
print "static " ctype " read" name "(LibertyXDR::Reader &r) {"
print "\tint8_t v = 0;"
print "\tif (!r.read(v) || !v)"
print "\t\treturn nullptr;"
print ""
print "\t" ctype " result(read" name "(static_cast<" \
CodegenCType[cg["tagtype"]] ">(v)));"
print "\tif (!result || !result->deserialize(r))"
print "\t\treturn nullptr;"
print "\treturn result;"
print "}"
CodegenDeserialize[name] = "\tif (!(%s = read" name "(r)))\n" \
"\t\treturn false;\n"
}
CodegenCType[name] = ctype
for (i in cg)
delete cg[i]
}

View File

@@ -439,9 +439,9 @@ function codegen_struct(name, cg, gotype) {
delete cg[i]
}
function codegen_union_tag(d, cg) {
function codegen_union_tag(name, d, cg) {
cg["tagtype"] = d["type"]
cg["tagname"] = d["name"]
cg["tagname"] = snaketocamel(d["name"])
# The tag is implied from the type of struct stored in the interface.
}
@@ -450,8 +450,8 @@ function codegen_union_struct(name, casename, cg, scg, structname, init) {
structname = name snaketocamel(casename)
codegen_struct(structname, scg)
init = CodegenGoType[structname] "{" snaketocamel(cg["tagname"]) \
": " decapitalize(snaketocamel(cg["tagname"])) "}"
init = CodegenGoType[structname] "{" cg["tagname"] \
": " decapitalize(cg["tagname"]) "}"
append(cg, "unmarshal",
"\tcase " CodegenGoType[cg["tagtype"]] snaketocamel(casename) ":\n" \
"\t\ts := " init "\n" \
@@ -467,7 +467,7 @@ function codegen_union_struct(name, casename, cg, scg, structname, init) {
"\t\tu.Interface = &s\n")
}
function codegen_union(name, cg, gotype, tagfield, tagvar) {
function codegen_union(name, cg, exhaustive, gotype, tagvar) {
gotype = PrefixCamel name
print "type " gotype " struct {"
print "\tInterface any"
@@ -481,18 +481,17 @@ function codegen_union(name, cg, gotype, tagfield, tagvar) {
print "}"
print ""
tagfield = snaketocamel(cg["tagname"])
tagvar = decapitalize(tagfield)
tagvar = decapitalize(cg["tagname"])
print "func (u *" gotype ") UnmarshalJSON(data []byte) (err error) {"
print "\tvar t struct {"
print "\t\t" tagfield " " CodegenGoType[cg["tagtype"]] \
print "\t\t" cg["tagname"] " " CodegenGoType[cg["tagtype"]] \
" `json:\"" tagvar "\"`"
print "\t}"
print "\tif err := json.Unmarshal(data, &t); err != nil {"
print "\t\treturn err"
print "\t}"
print ""
print "\tswitch " tagvar " := t." tagfield "; " tagvar " {"
print "\tswitch " tagvar " := t." cg["tagname"] "; " tagvar " {"
print cg["unmarshal"] "\tdefault:"
print "\t\terr = errors.New(`unsupported value: ` + " tagvar ".String())"
print "\t}"

View File

@@ -183,16 +183,16 @@ function codegen_struct(name, cg) {
delete cg[i]
}
function codegen_union_tag(d, cg) {
function codegen_union_tag(name, d, cg) {
cg["tagtype"] = d["type"]
cg["tagname"] = d["name"]
cg["tagname"] = decapitalize(snaketocamel(d["name"]))
}
function codegen_union_struct(name, casename, cg, scg, structname) {
append(scg, "methods",
"\n" \
"\tconstructor() {\n" \
"\t\tthis." decapitalize(snaketocamel(cg["tagname"])) \
"\t\tthis." cg["tagname"] \
" = " cg["tagtype"] "." snaketocamel(casename) "\n" \
"\t}\n")
@@ -208,8 +208,8 @@ function codegen_union_struct(name, casename, cg, scg, structname) {
"\t}\n")
}
function codegen_union(name, cg, tagvar) {
tagvar = decapitalize(snaketocamel(cg["tagname"]))
function codegen_union(name, cg, exhaustive, tagvar) {
tagvar = cg["tagname"]
print ""
print "export function deserialize" name "(r) {"

277
tools/lxdrgen-swift.awk Normal file
View File

@@ -0,0 +1,277 @@
# lxdrgen-swift.awk: Swift backend for lxdrgen.awk.
#
# Copyright (c) 2023, Přemysl Eric Janouch <p@janouch.name>
# SPDX-License-Identifier: 0BSD
function define_internal(name, swifttype) {
Types[name] = "internal"
CodegenSwiftType[name] = swifttype
CodegenDeserialize[name] = "%s.read()"
}
function define_sint(size, shortname, swifttype) {
shortname = "i" size
swifttype = "Int" size
define_internal(shortname, swifttype)
}
function define_uint(size, shortname, swifttype) {
shortname = "u" size
swifttype = "UInt" size
define_internal(shortname, swifttype)
}
function codegen_begin() {
define_sint("8")
define_sint("16")
define_sint("32")
define_sint("64")
define_uint("8")
define_uint("16")
define_uint("32")
define_uint("64")
define_internal("bool", "Bool")
define_internal("string", "String")
print "// Code generated from " FILENAME ". DO NOT EDIT."
print "import Foundation"
print ""
print "public struct " PrefixCamel "Reader {"
print "\tpublic var data: Data"
print ""
print "\tpublic enum ReadError: Error {"
print "\t\tcase unexpectedEOF"
print "\t\tcase invalidEncoding"
print "\t\tcase overflow"
print "\t\tcase unexpectedValue"
print "\t}"
print ""
print "\tpublic mutating func read<T: FixedWidthInteger>() throws -> T {"
print "\t\tlet size = MemoryLayout<T>.size"
print "\t\tguard data.count >= size else {"
print "\t\t\tthrow ReadError.unexpectedEOF"
print "\t\t}"
print "\t\tvar acc: T = 0"
print "\t\tdata.prefix(size).forEach { acc = acc << 8 | T($0) }"
print "\t\tdata = data.dropFirst(size)"
print "\t\treturn acc"
print "\t}"
print ""
print "\tpublic mutating func read() throws -> Bool {"
print "\t\ttry read() != UInt8(0)"
print "\t}"
print ""
print "\tpublic mutating func read() throws -> String {"
print "\t\tlet size: UInt32 = try self.read()"
print "\t\tguard let count = Int(exactly: size) else {"
print "\t\t\tthrow ReadError.overflow"
print "\t\t}"
print "\t\tguard data.count >= count else {"
print "\t\t\tthrow ReadError.unexpectedEOF"
print "\t\t}"
print "\t\tdefer {"
print "\t\t\tdata = data.dropFirst(count)"
print "\t\t}"
print "\t\tif let s = String(data: data.prefix(count), encoding: .utf8) {"
print "\t\t\treturn s"
print "\t\t} else {"
print "\t\t\tthrow ReadError.invalidEncoding"
print "\t\t}"
print "\t}"
print ""
print "\tpublic mutating func read<" \
"T: RawRepresentable<Int8>>() throws -> T {"
print "\t\tguard let value = T(rawValue: try read()) else {"
print "\t\t\tthrow ReadError.unexpectedValue"
print "\t\t}"
print "\t\treturn value"
print "\t}"
print ""
print "\tpublic mutating func read<T>("
print "\t\t\t_ read: (inout Self) throws -> T) throws -> [T] {"
print "\t\tlet size: UInt32 = try self.read()"
print "\t\tguard let count = Int(exactly: size) else {"
print "\t\t\tthrow ReadError.overflow"
print "\t\t}"
print "\t\tvar array = [T]()"
print "\t\tarray.reserveCapacity(count)"
print "\t\tfor _ in 0..<count {"
print "\t\t\tarray.append(try read(&self))"
print "\t\t}"
print "\t\treturn array"
print "\t}"
print "}"
print ""
print "public struct " PrefixCamel "Writer {"
print "\tpublic var data = Data()"
print ""
print "\tpublic mutating func append<T: FixedWidthInteger>(_ number: T) {"
print "\t\tvar n = number.byteSwapped"
print "\t\tfor _ in 0..<MemoryLayout<T>.size {"
print "\t\t\tdata.append(UInt8(truncatingIfNeeded: n))"
print "\t\t\tn >>= 8"
print "\t\t}"
print "\t}"
print ""
print "\tpublic mutating func append(_ bool: Bool) {"
print "\t\tappend(UInt8(bool ? 1 : 0))"
print "\t}"
print ""
print "\tpublic mutating func append(_ string: String) {"
print "\t\tlet bytes = string.data(using: .utf8)!"
print "\t\tappend(UInt32(bytes.count))"
print "\t\tdata.append(bytes)"
print "\t}"
print ""
print "\tpublic mutating func append<T: " \
"RawRepresentable<Int8>>(_ value: T) {"
print "\t\tappend(value.rawValue)"
print "\t}"
print ""
print "\tpublic mutating func append<T>("
print "\t\t\t_ array: Array<T>, _ write: (inout Self, T) -> ()) {"
print "\t\tappend(UInt32(array.count))"
print "\t\tfor i in 0..<array.count {"
print "\t\t\twrite(&self, array[i])"
print "\t\t}"
print "\t}"
print ""
print "\tpublic mutating func append<T: " \
PrefixCamel "Encodable>(_ value: T) {"
print "\t\tvalue.encode(to: &self)"
print "\t}"
print "}"
print ""
print "public protocol " PrefixCamel "Encodable { " \
"func encode(to: inout " PrefixCamel "Writer) }"
}
function codegen_constant(name, value) {
print ""
print "public let " decapitalize(PrefixCamel snaketocamel(name)) " = " value
}
function codegen_enum_value(name, subname, value, cg) {
append(cg, "fields",
"\tcase " decapitalize(snaketocamel(subname)) " = " value "\n")
}
function codegen_enum(name, cg, swifttype) {
swifttype = PrefixCamel name
print ""
print "public enum " swifttype ": Int8 {"
print cg["fields"] "}"
CodegenSwiftType[name] = swifttype
CodegenDeserialize[name] = "%s.read()"
for (i in cg)
delete cg[i]
}
function codegen_struct_field(d, cg, camel) {
camel = decapitalize(snaketocamel(d["name"]))
if (!d["isarray"]) {
append(cg, "fields",
"\tpublic var " camel ": " CodegenSwiftType[d["type"]] "\n")
append(cg, "deserialize",
"\t\tself." camel " = try " \
sprintf(CodegenDeserialize[d["type"]], "from") "\n")
append(cg, "serialize",
"\t\tto.append(self." camel ")\n")
return
}
append(cg, "fields",
"\tpublic var " camel ": [" CodegenSwiftType[d["type"]] "]\n")
append(cg, "deserialize",
"\t\tself." camel " = try from.read() { r in try " \
sprintf(CodegenDeserialize[d["type"]], "r") " }\n")
append(cg, "serialize",
"\t\tto.append(self." camel ") { (w, value) in w.append(value) }\n")
}
function codegen_struct_tag(d, cg, camel) {
camel = decapitalize(snaketocamel(d["name"]))
append(cg, "serialize",
"\t\tto.append(self." camel ")\n")
}
function codegen_struct(name, cg, swifttype) {
swifttype = PrefixCamel name
print ""
print "public struct " swifttype " {\n" cg["fields"] "}"
print ""
print "extension " swifttype ": " PrefixCamel "Encodable {"
print "\tpublic init(from: inout " PrefixCamel "Reader) throws {"
print cg["deserialize"] "\t}"
print ""
print "\tpublic func encode(to: inout " PrefixCamel "Writer) {"
print cg["serialize"] "\t}"
print "}"
CodegenSwiftType[name] = swifttype
CodegenDeserialize[name] = "%s.read()"
for (i in cg)
delete cg[i]
}
function codegen_union_tag(name, d, cg) {
cg["tagtype"] = d["type"]
cg["tagname"] = decapitalize(snaketocamel(d["name"]))
}
function codegen_union_struct(name, casename, cg, scg, swifttype) {
# And thus not all generated structs are present in Types.
swifttype = PrefixCamel name snaketocamel(casename)
casename = decapitalize(snaketocamel(casename))
print ""
print "public struct " swifttype ": " PrefixCamel name " {"
print "\tpublic var " cg["tagname"] \
": " CodegenSwiftType[cg["tagtype"]] " { ." casename " }"
print scg["fields"] "}"
print ""
print "extension " swifttype ": " PrefixCamel "Encodable {"
print "\tfileprivate init(from: inout " PrefixCamel "Reader) throws {"
print scg["deserialize"] "\t}"
print ""
print "\tpublic func encode(to: inout " PrefixCamel "Writer) {"
print scg["serialize"] "\t}"
print "}"
append(cg, "cases", "\tcase ." casename ":\n" \
"\t\treturn try " swifttype "(from: &from)\n")
CodegenSwiftType[name] = swifttype
CodegenDeserialize[name] = "%s.read()"
for (i in scg)
delete scg[i]
}
function codegen_union(name, cg, exhaustive, swifttype, init) {
# Classes don't have automatic member-wise initializers,
# thus using structs and protocols.
swifttype = PrefixCamel name
print ""
print "public protocol " swifttype ": " PrefixCamel "Encodable {"
print "\tvar " cg["tagname"] ": " CodegenSwiftType[cg["tagtype"]] " { get }"
print "}"
if (!exhaustive)
append(cg, "cases", "\tdefault:\n" \
"\t\tthrow " PrefixCamel "Reader.ReadError.unexpectedValue\n")
init = decapitalize(swifttype)
print ""
print "public func " init \
"(from: inout " PrefixCamel "Reader) throws -> " swifttype " {"
print "\tlet " cg["tagname"] ": " CodegenSwiftType[cg["tagtype"]] \
" = try from.read()"
print "\tswitch " cg["tagname"] " {"
print cg["cases"] "\t}"
print "}"
CodegenSwiftType[name] = swifttype
CodegenDeserialize[name] = init "(from: &%s)"
for (i in cg)
delete cg[i]
}

View File

@@ -1,6 +1,6 @@
# lxdrgen.awk: an XDR-derived code generator for network protocols.
#
# Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
# Copyright (c) 2022 - 2023, Přemysl Eric Janouch <p@janouch.name>
# SPDX-License-Identifier: 0BSD
#
# Usage: env LC_ALL=C awk -f lxdrgen.awk -f lxdrgen-{c,go,mjs}.awk \
@@ -28,9 +28,10 @@ function snaketocamel(s) {
}
function decapitalize(s) {
if (match(s, /[[:upper:]][[:lower:]]/)) {
if (match(s, /^[[:upper:]][[:lower:]]/))
return tolower(substr(s, 1, 1)) substr(s, 2)
}
if (match(s, /^[[:upper:]]$/))
return tolower(s)
return s
}
@@ -216,7 +217,8 @@ function defstruct( name, d, cg) {
return name
}
function defunion( name, tag, tagtype, tagvalue, cg, scg, d, a, i, unseen) {
function defunion( name, tag, tagtype, tagvalue, cg, scg, d, a, i,
unseen, exhaustive) {
delete cg[0]
delete scg[0]
delete d[0]
@@ -230,7 +232,7 @@ function defunion( name, tag, tagtype, tagvalue, cg, scg, d, a, i, unseen) {
if (Types[tagtype] != "enum")
fatal("not an enum type: " tagtype)
codegen_union_tag(tag, cg)
codegen_union_tag(name, tag, cg)
split(EnumValues[tagtype], a, SUBSEP)
for (i in a)
@@ -257,9 +259,14 @@ function defunion( name, tag, tagtype, tagvalue, cg, scg, d, a, i, unseen) {
if (tagvalue)
codegen_union_struct(name, tagvalue, cg, scg)
# What remains non-zero in unseen[2..] is simply not recognized/allowed.
# Unseen cases are simply not recognized/allowed.
exhaustive = 1
for (i in unseen)
if (i && unseen[i])
exhaustive = 0
Types[name] = "union"
codegen_union(name, cg)
codegen_union(name, cg, exhaustive)
return name
}