Compare commits

..

1 Commits
master ... c++

Author SHA1 Message Date
Přemysl Eric Janouch ddb3a60dcc
Experimental support for building as C++
So far with the following caveats:
 - Triggers -Wc99-designator
 - Compound literals are non-standard.
 - The setjmp/longjmp in the configuration parser might be an issue.
 - Perhaps others.

It does not seem to be a good idea to use this library for C++ at all.
Much of what it does is directly replaced by the STL.
2020-10-22 02:19:59 +02:00
37 changed files with 520 additions and 7215 deletions

View File

@ -1,32 +0,0 @@
# clang-format is fairly limited, and these rules are approximate:
# - array initializers can get terribly mangled with clang-format 12.0,
# - sometimes it still aligns with space characters,
# - struct name NL { NL ... NL } NL name; is unachievable.
BasedOnStyle: GNU
ColumnLimit: 80
IndentWidth: 4
TabWidth: 4
UseTab: ForContinuationAndIndentation
BreakBeforeBraces: Allman
SpaceAfterCStyleCast: true
AlignAfterOpenBracket: DontAlign
AlignOperands: DontAlign
AlignConsecutiveMacros: Consecutive
AllowAllArgumentsOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
IndentGotoLabels: false
# IncludeCategories has some potential, but it may also break the build.
# Note that the documentation says the value should be "Never".
SortIncludes: false
# This is a compromise, it generally works out aesthetically better.
BinPackArguments: false
# Unfortunately, this can't be told to align to column 40 or so.
SpacesBeforeTrailingComments: 2
# liberty-specific macro body wrappers.
MacroBlockBegin: "BLOCK_START"
MacroBlockEnd: "BLOCK_END"
ForEachMacros: ["LIST_FOR_EACH"]

2
.gitignore vendored
View File

@ -7,5 +7,3 @@
/liberty.files
/liberty.creator*
/liberty.includes
/liberty.cflags
/liberty.cxxflags

View File

@ -1,12 +1,12 @@
cmake_minimum_required (VERSION 3.0...3.27)
project (liberty C CXX)
project (liberty C)
cmake_minimum_required (VERSION 2.8.5)
# Moar warnings
if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
# -Wunused-function is pretty annoying here, as everything is static
set (wdisabled "-Wno-unused-function")
set (wdisabled "-Wno-unused-function -Wno-implicit-fallthrough")
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -Wall -Wextra ${wdisabled}")
endif ()
endif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
# Dependencies
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
@ -16,9 +16,11 @@ find_package (PkgConfig REQUIRED)
pkg_check_modules (libssl REQUIRED libssl libcrypto)
if ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD")
include_directories (/usr/local/include)
link_directories (/usr/local/lib)
# Our POSIX version macros make these undefined
add_definitions (-D__BSD_VISIBLE=1 -D_BSD_SOURCE=1)
endif ()
endif ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD")
set (common_libraries ${libssl_LIBRARIES})
include_directories (${libssl_INCLUDE_DIRS})
@ -29,102 +31,16 @@ link_directories (${libssl_LIBRARY_DIRS})
foreach (extra iconv rt)
find_library (extra_lib_${extra} ${extra})
if (extra_lib_${extra})
list (APPEND common_libraries ${extra_lib_${extra}})
endif ()
endforeach ()
list (APPEND common_libraries ${extra})
endif (extra_lib_${extra})
endforeach (extra)
# Build some unit tests
include_directories (${PROJECT_SOURCE_DIR})
enable_testing ()
set (tests liberty proto xdg)
pkg_check_modules (libpulse libpulse)
if (libpulse_FOUND)
list (APPEND tests pulse)
list (APPEND common_libraries ${libpulse_LIBRARIES})
include_directories (${libpulse_INCLUDE_DIRS})
link_directories (${libpulse_LIBRARY_DIRS})
endif ()
foreach (name ${tests})
foreach (name liberty proto)
add_executable (test-${name} tests/${name}.c ${common_sources})
add_threads (test-${name})
target_link_libraries (test-${name} ${common_libraries})
add_test (NAME test-${name} COMMAND test-${name})
endforeach ()
# --- Tools --------------------------------------------------------------------
# Test the AsciiDoc manual page generator for a successful parse
set (ASCIIMAN ${PROJECT_SOURCE_DIR}/tools/asciiman.awk)
add_custom_command (OUTPUT libertyxdr.7
COMMAND env LC_ALL=C awk -f ${ASCIIMAN}
"${PROJECT_SOURCE_DIR}/libertyxdr.adoc" > libertyxdr.7
DEPENDS libertyxdr.adoc ${ASCIIMAN}
COMMENT "Generating man page for libertyxdr" VERBATIM)
add_custom_target (docs ALL DEPENDS libertyxdr.7)
# Test CMake script parsing
add_test (test-cmake-parser
env LC_ALL=C awk -f ${PROJECT_SOURCE_DIR}/tools/cmake-parser.awk
-f ${PROJECT_SOURCE_DIR}/tools/cmake-dump.awk ${CMAKE_CURRENT_LIST_FILE})
# Test protocol code generation
set (lxdrgen_outputs)
set (lxdrgen_base "${PROJECT_BINARY_DIR}/lxdrgen.lxdr")
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
-f ${PROJECT_SOURCE_DIR}/tools/lxdrgen.awk
-f ${PROJECT_SOURCE_DIR}/tools/lxdrgen-${backend}.awk
-v PrefixCamel=ProtoGen
${PROJECT_SOURCE_DIR}/tests/lxdrgen.lxdr
> ${lxdrgen_base}.${backend}
DEPENDS
${PROJECT_SOURCE_DIR}/tools/lxdrgen.awk
${PROJECT_SOURCE_DIR}/tools/lxdrgen-${backend}.awk
${PROJECT_SOURCE_DIR}/tests/lxdrgen.lxdr
COMMENT "Generating test protocol code (${backend})" VERBATIM)
endforeach ()
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-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)
add_test (test-lxdrgen-go ${GO_EXECUTABLE} vet ${lxdrgen_base}.go)
else ()
message (WARNING "Cannot test generated protocol code for Go")
endif ()
find_program (NODE_EXECUTABLE node)
if (NODE_EXECUTABLE)
add_test (test-lxdrgen-mjs ${NODE_EXECUTABLE} -c ${lxdrgen_base}.mjs)
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 ()
endforeach (name)

View File

@ -1,4 +1,4 @@
Copyright (c) 2014 - 2024, Přemysl Eric Janouch <p@janouch.name>
Copyright (c) 2014 - 2020, 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

@ -1,67 +1,21 @@
liberty
=======
'liberty' is a pseudolibrary largely consisting of reusable C code for my
various projects. I used to copy-paste large swaths of it with minimal changes,
'liberty' is a pseudolibrary of all the common C code I have written for various
projects. I used to copy-paste large swaths of code with minimal changes to it
and it slowly became awfully painful to synchronize. The project can be thought
of as a successor to my other C library, libxtnd.
You are supposed to import it as a git submodule and include the main source
file directly everywhere you need it, setting feature flags as appropriate.
Everything is declared "static". I have come to the conclusion that this style
of C programming suits me the best, as it allows me to nearly forget about the
mess that are header files.
file directly everywhere you need it. Everything is declared "static". I have
come to the conclusion that this style of C programming suits me the best, as it
allows me to nearly forget about the mess that are header files.
The API is intentionally unstable, which allows for easy refactoring.
All development is done on Linux, but other POSIX-compatible operating systems
should be generally supported as well. They have an extremely low priority,
however, and I'm not testing them at all, perhaps with the exception of macOS.
Tools
-----
This project also hosts a number of supporting scripts written in portable AWK:
asciiman.awk::
A fallback manual page generator for AsciiDoc documents,
motivated by the hugeness of AsciiDoc's and Asciidoctor's dependency trees.
Just like them, it uses the _man_ macro package.
cmake-parser.awk::
Parses the CMake language to the extent that is necessary to reliably
extract project versions. Its greatest limitation is its inability
to expand variables, which would require a full interpreter.
cmake-dump.awk::
This can be used in conjunction with the previous script to dump CMake
scripts in a normalized format for further processing.
lxdrgen.awk::
Protocol code generator for a variant of XDR,
which is link:libertyxdr.adoc[documented separately].
Successfully employed in https://git.janouch.name/p/xK[xK].
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_
interface is bound to be underperforming, due to the amount of otherwise
avoidable memory allocations it necessitates).
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.
should be supported as well. They have an extremely low priority, however, and
I'm not testing them at all, with the exception of OpenBSD.
Contributing and Support
------------------------

View File

@ -9,15 +9,15 @@ find_package (Threads)
function (add_threads target)
if (NOT Threads_FOUND OR NOT CMAKE_USE_PTHREADS_INIT)
message (FATAL_ERROR "pthreads not found")
endif ()
endif (NOT Threads_FOUND OR NOT CMAKE_USE_PTHREADS_INIT)
if (THREADS_HAVE_PTHREAD_ARG)
set_property (TARGET ${target} PROPERTY
COMPILE_OPTIONS "-pthread")
set_property (TARGET ${target} PROPERTY
INTERFACE_COMPILE_OPTIONS "-pthread")
endif ()
endif (THREADS_HAVE_PTHREAD_ARG)
if (CMAKE_THREAD_LIBS_INIT)
target_link_libraries (${target} "${CMAKE_THREAD_LIBS_INIT}")
endif ()
endfunction ()
endif (CMAKE_THREAD_LIBS_INIT)
endfunction (add_threads)

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 PATH_SUFFIXES ncurses)
find_path (Ncursesw_INCLUDE_DIRS ncurses.h)
list (APPEND required_vars Ncursesw_INCLUDE_DIRS)
endif (NOT Ncursesw_FOUND)

View File

@ -1,40 +0,0 @@
# Public Domain
function (icon_to_png name svg size output_dir output)
set (_dimensions "${size}x${size}")
set (_png_path "${output_dir}/hicolor/${_dimensions}/apps")
set (_png "${_png_path}/${name}.png")
set (${output} "${_png}" PARENT_SCOPE)
set (_find_program_REQUIRE)
if (NOT ${CMAKE_VERSION} VERSION_LESS 3.18.0)
set (_find_program_REQUIRE REQUIRED)
endif ()
find_program (rsvg_convert_EXECUTABLE rsvg-convert ${_find_program_REQUIRE})
add_custom_command (OUTPUT "${_png}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${_png_path}"
COMMAND ${rsvg_convert_EXECUTABLE} "--output=${_png}"
"--width=${size}" "--height=${size}" -- "${svg}"
DEPENDS "${svg}"
COMMENT "Generating ${name} ${_dimensions} application icon" VERBATIM)
endfunction ()
# You should include a 256x256 icon--which takes less space as raw PNG.
function (icon_for_win32 ico pngs pngs_raw)
set (_raws)
foreach (png ${pngs_raw})
list (APPEND _raws "--raw=${png}")
endforeach ()
set (_find_program_REQUIRE)
if (NOT ${CMAKE_VERSION} VERSION_LESS 3.18.0)
set (_find_program_REQUIRE REQUIRED)
endif ()
find_program (icotool_EXECUTABLE icotool ${_find_program_REQUIRE})
add_custom_command (OUTPUT "${ico}"
COMMAND ${icotool_EXECUTABLE} -c -o "${ico}" ${_raws} -- ${pngs}
DEPENDS ${pngs} ${pngs_raw}
COMMENT "Generating Windows program icon" VERBATIM)
endfunction ()

View File

@ -1,15 +0,0 @@
set (CMAKE_SYSTEM_NAME "Windows")
set (CMAKE_SYSTEM_PROCESSOR "x86_64")
set (CMAKE_C_COMPILER "x86_64-w64-mingw32-gcc")
set (CMAKE_CXX_COMPILER "x86_64-w64-mingw32-g++")
set (CMAKE_RC_COMPILER "x86_64-w64-mingw32-windres")
# Remember to set WINEPATH for library dependencies
set (CMAKE_CROSSCOMPILING_EMULATOR "wine64")
set (CMAKE_FIND_ROOT_PATH "/usr/x86_64-w64-mingw32")
set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

View File

@ -1,15 +0,0 @@
set (CMAKE_SYSTEM_NAME "Windows")
set (CMAKE_SYSTEM_PROCESSOR "x86")
set (CMAKE_C_COMPILER "i686-w64-mingw32-gcc")
set (CMAKE_CXX_COMPILER "i686-w64-mingw32-g++")
set (CMAKE_RC_COMPILER "i686-w64-mingw32-windres")
# Remember to set WINEPATH for library dependencies
set (CMAKE_CROSSCOMPILING_EMULATOR "wine")
set (CMAKE_FIND_ROOT_PATH "/usr/i686-w64-mingw32")
set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

View File

@ -148,7 +148,7 @@ irc_free_message (struct irc_message *msg)
static void
irc_process_buffer (struct str *buf,
void (*callback) (const struct irc_message *, const char *, void *),
void (*callback)(const struct irc_message *, const char *, void *),
void *user_data)
{
char *start = buf->str, *end = start + buf->len;
@ -507,7 +507,7 @@ http_parse_upgrade (const char *upgrade, struct http_protocol **out)
case HTTP_T_WHITESPACE:
break;
case HTTP_T_TOKEN:
proto = xcalloc (1, sizeof *proto);
proto = (struct http_protocol *) xcalloc (1, sizeof *proto);
proto->name = xstrdup (t.string.str);
LIST_APPEND_WITH_TAIL (list, tail, proto);
state = STATE_SLASH;
@ -1002,7 +1002,7 @@ fcgi_nv_parser_push (struct fcgi_nv_parser *self, const void *data, size_t len)
if (self->input.len < self->name_len)
return;
self->name = xmalloc (self->name_len + 1);
self->name = (char *) xmalloc (self->name_len + 1);
self->name[self->name_len] = '\0';
memcpy (self->name, self->input.str, self->name_len);
str_remove_slice (&self->input, 0, self->name_len);
@ -1012,7 +1012,7 @@ fcgi_nv_parser_push (struct fcgi_nv_parser *self, const void *data, size_t len)
if (self->input.len < self->value_len)
return;
self->value = xmalloc (self->value_len + 1);
self->value = (char *) xmalloc (self->value_len + 1);
self->value[self->value_len] = '\0';
memcpy (self->value, self->input.str, self->value_len);
str_remove_slice (&self->input, 0, self->value_len);
@ -1050,7 +1050,7 @@ fcgi_nv_convert (struct str_map *map, struct str *output)
while (str_map_iter_next (&iter))
{
const char *name = iter.link->key;
const char *value = iter.link->data;
const char *value = (const char *) iter.link->data;
size_t name_len = iter.link->key_length;
size_t value_len = strlen (value);
@ -1227,7 +1227,7 @@ ws_parser_push (struct ws_parser *self, const void *data, size_t len)
self->reserved_1 = (u8 >> 6) & 1;
self->reserved_2 = (u8 >> 5) & 1;
self->reserved_3 = (u8 >> 4) & 1;
self->opcode = u8 & 15;
self->opcode = (enum ws_opcode) (u8 & 15);
(void) msg_unpacker_u8 (&unpacker, &u8);
self->is_masked = (u8 >> 7) & 1;
@ -1321,6 +1321,7 @@ enum mpd_subsystem
#define XX(a, b, c) MPD_SUBSYSTEM_ ## a = (1 << b),
MPD_SUBSYSTEM_TABLE (XX)
#undef XX
MPD_SUBSYSTEM_MAX
};
static const char *mpd_subsystem_names[] =
@ -1422,9 +1423,9 @@ mpd_client_make (struct poller *poller)
.socket = -1,
.read_buffer = str_make (),
.write_buffer = str_make (),
.data = strv_make (),
.socket_event = poller_fd_make (poller, -1),
.timeout_timer = poller_timer_make (poller),
.data = strv_make (),
};
}
@ -1463,7 +1464,7 @@ mpd_client_reset (struct mpd_client *self)
{
// Get rid of all pending tasks to release resources etc.
strv_reset (&self->data);
struct mpd_response aborted = { .message_text = "Disconnected" };
struct mpd_response aborted = { .message_text = (char *) "Disconnected" };
while (self->tasks)
mpd_client_dispatch (self, &aborted);
@ -1523,7 +1524,8 @@ mpd_client_parse_response (const char *p, struct mpd_response *response)
if (errno != 0 || end == p)
return false;
p = end;
if (*p++ != ']' || *p++ != ' ' || *p++ != '{' || !(end = strchr (p, '}')))
if (*p++ != ']' || *p++ != ' ' || *p++ != '{'
|| !(end = (char *) strchr (p, '}')))
return false;
response->current_command = xstrndup (p, end - p);
@ -1623,7 +1625,7 @@ mpd_client_on_ready (const struct pollfd *pfd, void *user_data)
{
(void) pfd;
struct mpd_client *self = user_data;
struct mpd_client *self = (struct mpd_client *) user_data;
if (socket_io_try_read (self->socket, &self->read_buffer) != SOCKET_IO_OK
|| !mpd_client_process_input (self)
|| socket_io_try_write (self->socket, &self->write_buffer) != SOCKET_IO_OK)
@ -1675,7 +1677,8 @@ mpd_client_add_task
// later flushed if an early ACK or OK arrives).
hard_assert (!self->in_list);
struct mpd_client_task *task = xcalloc (1, sizeof *self);
struct mpd_client_task *task =
(struct mpd_client_task *) xcalloc (1, sizeof *self);
task->callback = cb;
task->user_data = user_data;
LIST_APPEND_WITH_TAIL (self->tasks, self->tasks_tail, task);
@ -1793,7 +1796,7 @@ mpd_client_on_idle_return (const struct mpd_response *response,
{
(void) response;
struct mpd_client *self = user_data;
struct mpd_client *self = (struct mpd_client *) user_data;
unsigned subsystems = 0;
for (size_t i = 0; i < data->len; i++)
{
@ -1816,7 +1819,7 @@ static void mpd_client_idle (struct mpd_client *self, unsigned subsystems);
static void
mpd_client_on_timeout (void *user_data)
{
struct mpd_client *self = user_data;
struct mpd_client *self = (struct mpd_client *) user_data;
// Abort and immediately restore the current idle so that MPD doesn't
// disconnect us, even though the documentation says this won't happen.
@ -1884,7 +1887,7 @@ mpd_client_destroy_connector (struct mpd_client *self)
static void
mpd_client_on_connector_failure (void *user_data)
{
struct mpd_client *self = user_data;
struct mpd_client *self = (struct mpd_client *) user_data;
mpd_client_destroy_connector (self);
mpd_client_fail (self);
}
@ -1895,7 +1898,7 @@ mpd_client_on_connector_connected
{
(void) host;
struct mpd_client *self = user_data;
struct mpd_client *self = (struct mpd_client *) user_data;
mpd_client_destroy_connector (self);
mpd_client_finish_connection (self, socket);
}
@ -1911,14 +1914,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 sau;
sau.sun_family = AF_UNIX;
strncpy (sau.sun_path, expanded, sizeof sau.sun_path);
sau.sun_path[sizeof sau.sun_path - 1] = 0;
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;
free (expanded);
if (connect (fd, (struct sockaddr *) &sau, sizeof sau))
if (connect (fd, (struct sockaddr *) &sun, sizeof sun))
{
error_set (e, "%s: %s", "connect", strerror (errno));
xclose (fd);
@ -1939,7 +1942,8 @@ mpd_client_connect (struct mpd_client *self, const char *address,
if (strchr (address, '/'))
return mpd_client_connect_unix (self, address, e);
struct connector *connector = xmalloc (sizeof *connector);
struct connector *connector =
(struct connector *) xmalloc (sizeof *connector);
connector_init (connector, self->poller);
self->connector = connector;

View File

@ -1,348 +0,0 @@
/*
* liberty-pulse.c: PulseAudio mainloop abstraction
*
* Copyright (c) 2016 - 2021, 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 <pulse/mainloop.h>
// --- PulseAudio mainloop abstraction -----------------------------------------
struct pa_io_event
{
LIST_HEADER (pa_io_event)
pa_mainloop_api *api; ///< Parent structure
struct poller_fd fd; ///< Underlying FD event
pa_io_event_cb_t dispatch; ///< Dispatcher
pa_io_event_destroy_cb_t free; ///< Destroyer
void *user_data; ///< User data
};
struct pa_time_event
{
LIST_HEADER (pa_time_event)
pa_mainloop_api *api; ///< Parent structure
struct poller_timer timer; ///< Underlying timer event
pa_time_event_cb_t dispatch; ///< Dispatcher
pa_time_event_destroy_cb_t free; ///< Destroyer
void *user_data; ///< User data
};
struct pa_defer_event
{
LIST_HEADER (pa_defer_event)
pa_mainloop_api *api; ///< Parent structure
struct poller_idle idle; ///< Underlying idle event
pa_defer_event_cb_t dispatch; ///< Dispatcher
pa_defer_event_destroy_cb_t free; ///< Destroyer
void *user_data; ///< User data
};
struct poller_pa
{
struct poller *poller; ///< The underlying event loop
pa_io_event *io_list; ///< I/O events
pa_time_event *time_list; ///< Timer events
pa_defer_event *defer_list; ///< Deferred events
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static short
poller_pa_flags_to_events (pa_io_event_flags_t flags)
{
short result = 0;
if (flags & PA_IO_EVENT_ERROR) result |= POLLERR;
if (flags & PA_IO_EVENT_HANGUP) result |= POLLHUP;
if (flags & PA_IO_EVENT_INPUT) result |= POLLIN;
if (flags & PA_IO_EVENT_OUTPUT) result |= POLLOUT;
return result;
}
static pa_io_event_flags_t
poller_pa_events_to_flags (short events)
{
pa_io_event_flags_t result = 0;
if (events & POLLERR) result |= PA_IO_EVENT_ERROR;
if (events & POLLHUP) result |= PA_IO_EVENT_HANGUP;
if (events & POLLIN) result |= PA_IO_EVENT_INPUT;
if (events & POLLOUT) result |= PA_IO_EVENT_OUTPUT;
return result;
}
static struct timeval
poller_pa_get_current_time (void)
{
struct timeval tv;
#ifdef _POSIX_TIMERS
struct timespec tp;
hard_assert (clock_gettime (CLOCK_REALTIME, &tp) != -1);
tv.tv_sec = tp.tv_sec;
tv.tv_usec = tp.tv_nsec / 1000;
#else
gettimeofday (&tv, NULL);
#endif
return tv;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
poller_pa_io_dispatcher (const struct pollfd *pfd, void *user_data)
{
pa_io_event *self = user_data;
self->dispatch (self->api, self,
pfd->fd, poller_pa_events_to_flags (pfd->revents), self->user_data);
}
static void
poller_pa_io_enable (pa_io_event *self, pa_io_event_flags_t events)
{
struct poller_fd *fd = &self->fd;
if (events)
poller_fd_set (fd, poller_pa_flags_to_events (events));
else
poller_fd_reset (fd);
}
static pa_io_event *
poller_pa_io_new (pa_mainloop_api *api, int fd_, pa_io_event_flags_t events,
pa_io_event_cb_t cb, void *userdata)
{
pa_io_event *self = xcalloc (1, sizeof *self);
self->api = api;
self->dispatch = cb;
self->user_data = userdata;
struct poller_pa *data = api->userdata;
self->fd = poller_fd_make (data->poller, fd_);
self->fd.user_data = self;
self->fd.dispatcher = poller_pa_io_dispatcher;
// FIXME: under x2go PA tries to register twice for the same FD,
// which fails with our curent poller implementation;
// we could maintain a list of { poller_fd, listeners } structures;
// or maybe we're doing something wrong, which is yet to be determined
poller_pa_io_enable (self, events);
LIST_PREPEND (data->io_list, self);
return self;
}
static void
poller_pa_io_free (pa_io_event *self)
{
if (self->free)
self->free (self->api, self, self->user_data);
struct poller_pa *data = self->api->userdata;
poller_fd_reset (&self->fd);
LIST_UNLINK (data->io_list, self);
free (self);
}
static void
poller_pa_io_set_destroy (pa_io_event *self, pa_io_event_destroy_cb_t cb)
{
self->free = cb;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
poller_pa_time_dispatcher (void *user_data)
{
pa_time_event *self = user_data;
// XXX: the meaning of the time argument is undocumented,
// so let's just put current Unix time in there
struct timeval now = poller_pa_get_current_time ();
self->dispatch (self->api, self, &now, self->user_data);
}
static void
poller_pa_time_restart (pa_time_event *self, const struct timeval *tv)
{
struct poller_timer *timer = &self->timer;
if (tv)
{
struct timeval now = poller_pa_get_current_time ();
poller_timer_set (timer,
(tv->tv_sec - now.tv_sec) * 1000 +
(tv->tv_usec - now.tv_usec) / 1000);
}
else
poller_timer_reset (timer);
}
static pa_time_event *
poller_pa_time_new (pa_mainloop_api *api, const struct timeval *tv,
pa_time_event_cb_t cb, void *userdata)
{
pa_time_event *self = xcalloc (1, sizeof *self);
self->api = api;
self->dispatch = cb;
self->user_data = userdata;
struct poller_pa *data = api->userdata;
self->timer = poller_timer_make (data->poller);
self->timer.user_data = self;
self->timer.dispatcher = poller_pa_time_dispatcher;
poller_pa_time_restart (self, tv);
LIST_PREPEND (data->time_list, self);
return self;
}
static void
poller_pa_time_free (pa_time_event *self)
{
if (self->free)
self->free (self->api, self, self->user_data);
struct poller_pa *data = self->api->userdata;
poller_timer_reset (&self->timer);
LIST_UNLINK (data->time_list, self);
free (self);
}
static void
poller_pa_time_set_destroy (pa_time_event *self, pa_time_event_destroy_cb_t cb)
{
self->free = cb;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
poller_pa_defer_dispatcher (void *user_data)
{
pa_defer_event *self = user_data;
self->dispatch (self->api, self, self->user_data);
}
static pa_defer_event *
poller_pa_defer_new (pa_mainloop_api *api,
pa_defer_event_cb_t cb, void *userdata)
{
pa_defer_event *self = xcalloc (1, sizeof *self);
self->api = api;
self->dispatch = cb;
self->user_data = userdata;
struct poller_pa *data = api->userdata;
self->idle = poller_idle_make (data->poller);
self->idle.user_data = self;
self->idle.dispatcher = poller_pa_defer_dispatcher;
poller_idle_set (&self->idle);
LIST_PREPEND (data->defer_list, self);
return self;
}
static void
poller_pa_defer_enable (pa_defer_event *self, int enable)
{
struct poller_idle *idle = &self->idle;
if (enable)
poller_idle_set (idle);
else
poller_idle_reset (idle);
}
static void
poller_pa_defer_free (pa_defer_event *self)
{
if (self->free)
self->free (self->api, self, self->user_data);
struct poller_pa *data = self->api->userdata;
poller_idle_reset (&self->idle);
LIST_UNLINK (data->defer_list, self);
free (self);
}
static void
poller_pa_defer_set_destroy (pa_defer_event *self,
pa_defer_event_destroy_cb_t cb)
{
self->free = cb;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
poller_pa_quit (pa_mainloop_api *api, int retval)
{
(void) api;
(void) retval;
// This is not called from within libpulse
hard_assert (!"quitting the libpulse event loop is unimplemented");
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static struct pa_mainloop_api g_poller_pa_template =
{
.io_new = poller_pa_io_new,
.io_enable = poller_pa_io_enable,
.io_free = poller_pa_io_free,
.io_set_destroy = poller_pa_io_set_destroy,
.time_new = poller_pa_time_new,
.time_restart = poller_pa_time_restart,
.time_free = poller_pa_time_free,
.time_set_destroy = poller_pa_time_set_destroy,
.defer_new = poller_pa_defer_new,
.defer_enable = poller_pa_defer_enable,
.defer_free = poller_pa_defer_free,
.defer_set_destroy = poller_pa_defer_set_destroy,
.quit = poller_pa_quit,
};
static struct pa_mainloop_api *
poller_pa_new (struct poller *self)
{
struct poller_pa *data = xcalloc (1, sizeof *data);
data->poller = self;
struct pa_mainloop_api *api = xmalloc (sizeof *api);
*api = g_poller_pa_template;
api->userdata = data;
return api;
}
static void
poller_pa_destroy (struct pa_mainloop_api *api)
{
struct poller_pa *data = api->userdata;
LIST_FOR_EACH (pa_io_event, iter, data->io_list)
poller_pa_io_free (iter);
LIST_FOR_EACH (pa_time_event, iter, data->time_list)
poller_pa_time_free (iter);
LIST_FOR_EACH (pa_defer_event, iter, data->defer_list)
poller_pa_defer_free (iter);
free (data);
free (api);
}

270
liberty-tui.c Normal file
View File

@ -0,0 +1,270 @@
/*
* 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);
}

View File

@ -1,772 +0,0 @@
/*
* liberty-xdg.c: the ultimate C unlibrary: freedesktop.org specifications
*
* Copyright (c) 2023 - 2024, 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 files assumes you've already included liberty.c.
#ifdef LIBERTY_XDG_WANT_X11
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#endif
#ifdef LIBERTY_XDG_WANT_ICONS
#include <png.h>
#endif
// --- XSettings ---------------------------------------------------------------
#ifdef LIBERTY_XDG_WANT_X11
struct xdg_xsettings_setting
{
enum xdg_xsettings_type
{
XDG_XSETTINGS_INTEGER,
XDG_XSETTINGS_STRING,
XDG_XSETTINGS_COLOR,
}
type; ///< What's stored in the union
uint32_t serial; ///< Serial of the last change
union
{
int32_t integer;
struct str string;
struct { uint16_t red, green, blue, alpha; } color;
};
};
static void
xdg_xsettings_setting_destroy (struct xdg_xsettings_setting *self)
{
if (self->type == XDG_XSETTINGS_STRING)
str_free (&self->string);
free (self);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
struct xdg_xsettings
{
struct str_map settings; ///< Name -> xdg_xsettings_setting
};
static void
xdg_xsettings_free (struct xdg_xsettings *self)
{
str_map_free (&self->settings);
}
static struct xdg_xsettings
xdg_xsettings_make (void)
{
return (struct xdg_xsettings)
{
.settings =
str_map_make ((str_map_free_fn) xdg_xsettings_setting_destroy),
};
}
static void
xdg_xsettings_update (struct xdg_xsettings *self, Display *dpy)
{
// TODO: We're supposed to lock the server.
// TODO: We're supposed to trap X errors.
char *selection = xstrdup_printf ("_XSETTINGS_S%d", DefaultScreen (dpy));
Window owner
= XGetSelectionOwner (dpy, XInternAtom (dpy, selection, True));
free (selection);
if (!owner)
return;
Atom actual_type = None;
int actual_format = 0;
unsigned long nitems = 0, bytes_after = 0;
unsigned char *buffer = NULL;
Atom xsettings = XInternAtom (dpy, "_XSETTINGS_SETTINGS", True);
int status = XGetWindowProperty (dpy,
owner,
xsettings,
0L,
LONG_MAX,
False,
xsettings,
&actual_type,
&actual_format,
&nitems,
&bytes_after,
&buffer);
if (status != Success || !buffer)
return;
if (actual_type != xsettings
|| actual_format != 8
|| nitems < 12)
goto fail;
const struct peeker *peeker = NULL;
if (buffer[0] == LSBFirst)
peeker = &peeker_le;
else if (buffer[0] == MSBFirst)
peeker = &peeker_be;
else
goto fail;
// We're ignoring the serial for now.
uint32_t n_settings = peeker->u32 (buffer + 8);
size_t offset = 12;
struct str name = str_make ();
struct xdg_xsettings_setting *setting = xcalloc (1, sizeof *setting);
while (n_settings--)
{
if (nitems < offset + 4)
goto fail_item;
setting->type = buffer[offset];
uint16_t name_len = peeker->u16 (buffer + offset + 2);
offset += 4;
if (nitems < offset + name_len)
goto fail_item;
str_append_data (&name, buffer + offset, name_len);
offset += ((name_len + 3) & ~3);
if (nitems < offset + 4)
goto fail_item;
setting->serial = peeker->u32 (buffer + offset);
offset += 4;
switch (setting->type)
{
case XDG_XSETTINGS_INTEGER:
if (nitems < offset + 4)
goto fail_item;
setting->integer = (int32_t) peeker->u32 (buffer + offset);
offset += 4;
break;
case XDG_XSETTINGS_STRING:
{
if (nitems < offset + 4)
goto fail_item;
uint32_t value_len = peeker->u32 (buffer + offset);
offset += 4;
if (nitems < offset + value_len)
goto fail_item;
setting->string = str_make ();
str_append_data (&setting->string, buffer + offset, value_len);
offset += ((value_len + 3) & ~3);
break;
}
case XDG_XSETTINGS_COLOR:
if (nitems < offset + 8)
goto fail_item;
setting->color.red = peeker->u16 (buffer + offset);
setting->color.green = peeker->u16 (buffer + offset + 2);
setting->color.blue = peeker->u16 (buffer + offset + 4);
setting->color.alpha = peeker->u16 (buffer + offset + 6);
offset += 8;
break;
default:
goto fail_item;
}
// TODO(p): Change detection, by comparing existence and serials.
str_map_set (&self->settings, name.str, setting);
setting = xcalloc (1, sizeof *setting);
str_reset (&name);
}
fail_item:
xdg_xsettings_setting_destroy (setting);
str_free (&name);
fail:
XFree (buffer);
}
#endif // LIBERTY_XDG_WANT_X11
// --- Desktop file parser -----------------------------------------------------
// Useful for parsing desktop-entry-spec, icon-theme-spec, trash-spec,
// mime-apps-spec. This code is not designed for making changes to the files.
struct desktop_file
{
struct str_map groups; ///< Group name → Key → Value
};
static void
desktop_file_free_group (void *value)
{
str_map_free (value);
free (value);
}
static void
desktop_file_free (struct desktop_file *self)
{
str_map_free (&self->groups);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
desktop_file_parse_line (struct desktop_file *self,
char **group_name, const char *line, const char *end)
{
struct str_map *group = NULL;
if (*group_name)
group = str_map_find (&self->groups, *group_name);
if (*line == '[')
{
bool ok = *--end == ']';
for (const char *p = ++line; ok && p != end; p++)
ok = (unsigned char) *p >= 32 && (unsigned char) *p <= 127
&& *p != '[' && *p != ']';
if (!ok)
{
cstr_set (group_name, NULL);
print_debug ("invalid desktop file group header");
return;
}
cstr_set (group_name, xstrndup (line, end - line));
if (str_map_find (&self->groups, *group_name))
{
print_debug ("duplicate desktop file group: %s", *group_name);
return;
}
group = xcalloc (1, sizeof *group);
*group = str_map_make (free);
str_map_set (&self->groups, *group_name, group);
return;
}
if (!group)
{
print_debug ("unexpected desktop file entry outside of a group");
return;
}
const char *key_end = line;
while (key_end != end && (isalnum_ascii (*key_end) || *key_end == '-'))
key_end++;
// We could validate these further, but we just search in them anyway.
if (key_end != end && *key_end == '[')
{
while (++key_end != end && *key_end != ']')
;
if (key_end != end && *key_end == ']')
key_end++;
}
const char *value = key_end;
while (value != end && *value == ' ')
value++;
if (value == end || *value++ != '=')
{
print_debug ("invalid desktop file entry");
return;
}
while (value != end && *value == ' ')
value++;
char *key = xstrndup (line, key_end - line);
if (str_map_find (group, key))
print_debug ("duplicate desktop file entry for: %s", key);
else
str_map_set (group, key, xstrndup (value, end - value));
free (key);
}
static struct desktop_file
desktop_file_make (const char *data, size_t len)
{
struct desktop_file self = (struct desktop_file)
{ .groups = str_map_make (desktop_file_free_group) };
char *group_name = NULL;
const char *p = data, *data_end = p + len;
while (p != data_end)
{
const char *line = p, *line_end = line;
while (line_end != data_end && *line_end != '\n')
line_end++;
if ((p = line_end) != data_end && *p == '\n')
p++;
if (line != line_end && *line != '#')
desktop_file_parse_line (&self, &group_name, line, line_end);
}
free (group_name);
return self;
}
static const char *
desktop_file_get (struct desktop_file *self, const char *group, const char *key)
{
// TODO(p): Ideally, also implement localised keys.
struct str_map *group_map = str_map_find (&self->groups, group);
if (!group_map)
return NULL;
return str_map_find (group_map, key);
}
static struct strv
desktop_file_unescape (const char *value, bool is_list)
{
struct strv result = strv_make ();
struct str s = str_make ();
// XXX: The unescaping behaviour is underspecified.
// It might make sense to warn about unrecognised escape sequences.
bool escape = false;
for (const char *p = value; *p; p++)
{
if (escape)
{
switch (*p)
{
break; case 's': str_append_c (&s, ' ');
break; case 'n': str_append_c (&s, '\n');
break; case 't': str_append_c (&s, '\t');
break; case 'r': str_append_c (&s, '\r');
break; default: str_append_c (&s, *p);
}
escape = false;
}
else if (*p == '\\' && p[1])
escape = true;
else if (*p == ';' && is_list)
{
strv_append_owned (&result, str_steal (&s));
s = str_make ();
}
else
str_append_c (&s, *p);
}
if (!is_list || s.len != 0)
strv_append_owned (&result, str_steal (&s));
else
str_free (&s);
return result;
}
static char *
desktop_file_get_string (struct desktop_file *self,
const char *group, const char *key)
{
const char *value = desktop_file_get (self, group, key);
if (!value)
return NULL;
struct strv values = desktop_file_unescape (value, false /* is_list */);
char *unescaped = strv_steal (&values, 0);
strv_free (&values);
return unescaped;
}
static struct strv
desktop_file_get_stringv (struct desktop_file *self,
const char *group, const char *key)
{
const char *value = desktop_file_get (self, group, key);
if (!value)
return strv_make ();
return desktop_file_unescape (value, true /* is_list */);
}
static bool
desktop_file_get_bool (struct desktop_file *self,
const char *group, const char *key)
{
const char *value = desktop_file_get (self, group, key);
if (!value)
return false;
// Let's be compatible with pre-1.0 files when it costs us so little.
if (!strcmp (value, "true")
|| !strcmp (value, "1"))
return true;
if (!strcmp (value, "false")
|| !strcmp (value, "0"))
return false;
print_debug ("invalid desktop file boolean for '%s': %s", key, value);
return false;
}
// Nothing uses the "numeric" type.
// "icon-theme-spec" uses "integer" and doesn't say what it is.
static long
desktop_file_get_integer (struct desktop_file *self,
const char *group, const char *key)
{
const char *value = desktop_file_get (self, group, key);
if (!value)
return 0;
char *end = NULL;
long parsed = (errno = 0, strtol (value, &end, 10));
if (errno != 0 || *end)
print_debug ("invalid desktop file integer for '%s': %s", key, value);
return parsed;
}
// --- Icon themes -------------------------------------------------------------
// This implements part of the Icon Theme Specification.
#ifdef LIBERTY_XDG_WANT_ICONS
struct icon_theme_icon
{
uint32_t width; ///< Width of argb in pixels
uint32_t height; ///< Height of argb in pixels
uint32_t argb[]; ///< ARGB32 data, unassociated alpha
};
static void
icon_theme_open_on_error (png_structp pngp, const char *error)
{
print_debug ("%s: %s", (const char *) png_get_error_ptr (pngp), error);
png_longjmp (pngp, 1);
}
static void
icon_theme_open_on_warning (png_structp pngp, const char *warning)
{
(void) pngp;
(void) warning;
// Fuck your "gamma value does not match libpng estimate".
}
// For simplicity, only support PNG icons, using the most popular library.
static struct icon_theme_icon *
icon_theme_open (const char *path)
{
volatile png_bytep buffer = NULL;
volatile png_bytepp row_pointers = NULL;
struct icon_theme_icon *volatile result = NULL;
FILE *fp = fopen (path, "rb");
if (!fp)
{
if (errno != ENOENT)
print_debug ("%s: %s", path, strerror (errno));
return NULL;
}
// The simplified and high-level APIs aren't powerful enough.
png_structp pngp = png_create_read_struct (PNG_LIBPNG_VER_STRING,
(png_voidp) path, icon_theme_open_on_error, icon_theme_open_on_warning);
png_infop infop = png_create_info_struct (pngp);
if (!infop)
{
print_debug ("%s: %s", path, strerror (errno));
goto fail;
}
if (setjmp (png_jmpbuf (pngp)))
goto fail;
png_init_io (pngp, fp);
png_read_info (pngp, infop);
// Asking for at least 8-bit channels. This call is a superset of:
// - png_set_palette_to_rgb(),
// - png_set_tRNS_to_alpha(),
// - png_set_expand_gray_1_2_4_to_8().
png_set_expand (pngp);
// Reduce the possibilities further to RGB or RGBA...
png_set_gray_to_rgb (pngp);
// ...and /exactly/ 8-bit channels.
// Alternatively, use png_set_expand_16() above to obtain 16-bit channels.
png_set_scale_16 (pngp);
// PNG uses RGBA order, let's change that to ARGB (both in memory order).
// This doesn't change a row's `color_type` in png_do_read_filler(),
// and the following transformation thus ignores it.
png_set_add_alpha (pngp, 0xFFFF, PNG_FILLER_BEFORE);
png_set_swap_alpha (pngp);
(void) png_set_interlace_handling (pngp);
png_read_update_info (pngp, infop);
if (png_get_bit_depth (pngp, infop) != 8
|| png_get_channels (pngp, infop) != 4
|| png_get_color_type (pngp, infop) != PNG_COLOR_TYPE_RGB_ALPHA)
png_error (pngp, "result not A8R8G8B8");
size_t row_bytes = png_get_rowbytes (pngp, infop);
size_t height = png_get_image_height (pngp, infop);
buffer = xcalloc (row_bytes, height);
row_pointers = xcalloc (height, sizeof buffer);
for (size_t y = 0; y < height; y++)
row_pointers[y] = buffer + y * row_bytes;
png_read_image (pngp, row_pointers);
result = xcalloc (1, sizeof *result + row_bytes * height);
result->width = png_get_image_width (pngp, infop);
result->height = height;
uint32_t *dst = (uint32_t *) result->argb, *src = (uint32_t *) buffer;
for (size_t pixels = result->width * result->height; pixels--; )
*dst++ = ntohl (*src++);
fail:
free (buffer);
free (row_pointers);
png_destroy_read_struct (&pngp, &infop, NULL);
fclose (fp);
return result;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
struct icon_theme_find_context
{
struct strv base; ///< Base directories
struct str_map visited; ///< Cycle prevention
ARRAY (struct icon_theme_icon *, icons)
};
static void
icon_theme_find__fallback (struct icon_theme_find_context *ctx,
const char *name)
{
for (size_t i = 0; i < ctx->base.len; i++)
{
char *path = xstrdup_printf ("%s/%s.png", ctx->base.vector[i], name);
struct icon_theme_icon *icon = icon_theme_open (path);
free (path);
if (icon)
{
ARRAY_RESERVE (ctx->icons, 1);
ctx->icons[ctx->icons_len++] = icon;
return;
}
}
}
static struct desktop_file
icon_theme_find__index (struct icon_theme_find_context *ctx, const char *theme)
{
struct str data = str_make ();
for (size_t i = 0; i < ctx->base.len; i++)
{
struct error *e = NULL;
char *path = xstrdup_printf ("%s/%s/index.theme",
ctx->base.vector[i], theme);
read_file (path, &data, &e);
free (path);
if (!e)
break;
if (errno != ENOENT)
print_debug ("%s", e->message);
error_free (e);
}
struct desktop_file index = desktop_file_make (data.str, data.len);
str_free (&data);
return index;
}
static void
icon_theme_find__named (struct icon_theme_find_context *ctx,
const char *theme, const char *name)
{
// Either a cycle, or a common ancestor of inherited themes, which is valid.
if (str_map_find (&ctx->visited, theme))
return;
str_map_set (&ctx->visited, theme, (void *) (intptr_t) 1);
struct desktop_file index = icon_theme_find__index (ctx, theme);
char *directories =
desktop_file_get_string (&index, "Icon Theme", "Directories");
if (!directories)
goto out;
// NOTE: The sizes are not deduplicated, and priorities are uncertain.
struct strv dirs = strv_make ();
cstr_split (directories, ",", true, &dirs);
free (directories);
for (size_t d = 0; d < dirs.len; d++)
{
// The hicolor icon theme stuffs everything in Directories.
if (desktop_file_get (&index, dirs.vector[d], "Scale")
&& desktop_file_get_integer (&index, dirs.vector[d], "Scale") != 1)
continue;
for (size_t i = 0; i < ctx->base.len; i++)
{
char *path = xstrdup_printf ("%s/%s/%s/%s.png",
ctx->base.vector[i], theme, dirs.vector[d], name);
struct icon_theme_icon *icon = icon_theme_open (path);
free (path);
if (icon)
{
ARRAY_RESERVE (ctx->icons, 1);
ctx->icons[ctx->icons_len++] = icon;
break;
}
}
}
strv_free (&dirs);
if (ctx->icons_len)
goto out;
char *inherits =
desktop_file_get_string (&index, "Icon Theme", "Inherits");
if (inherits)
{
struct strv parents = strv_make ();
cstr_split (inherits, ",", true, &parents);
free (inherits);
for (size_t i = 0; i < parents.len; i++)
{
icon_theme_find__named (ctx, parents.vector[i], name);
if (ctx->icons_len)
break;
}
strv_free (&parents);
}
out:
desktop_file_free (&index);
}
/// Return all base directories appropriate for icon search.
static struct strv
icon_theme_get_base_directories (void)
{
struct strv dirs = strv_make ();
struct str icons = str_make ();
(void) str_append_env_path (&icons, "HOME", false);
str_append (&icons, "/.icons");
strv_append_owned (&dirs, str_steal (&icons));
// Note that we use XDG_CONFIG_HOME as well, which might be intended.
struct strv xdg = strv_make ();
get_xdg_data_dirs (&xdg);
for (size_t i = 0; i < xdg.len; i++)
strv_append_owned (&dirs, xstrdup_printf ("%s/icons", xdg.vector[i]));
strv_free (&xdg);
strv_append (&dirs, "/usr/share/pixmaps");
return dirs;
}
static int
icon_theme_find__compare (const void *a, const void *b)
{
const struct icon_theme_icon **ia = (const struct icon_theme_icon **) a;
const struct icon_theme_icon **ib = (const struct icon_theme_icon **) b;
double pa = (double) (*ia)->width * (*ia)->height;
double pb = (double) (*ib)->width * (*ib)->height;
return (pa > pb) - (pa < pb);
}
/// Return all sizes of the named icon. When the theme name is not NULL,
/// use it as the preferred theme. Always consult fallbacks locations.
/// Ignore icon scales other than 1.
static struct icon_theme_icon **
icon_theme_find (const char *theme, const char *name, size_t *len)
{
struct icon_theme_find_context ctx = {};
ctx.base = icon_theme_get_base_directories ();
ctx.visited = str_map_make (NULL);
ARRAY_INIT (ctx.icons);
if (theme)
icon_theme_find__named (&ctx, theme, name);
if (!ctx.icons_len)
icon_theme_find__named (&ctx, "hicolor", name);
if (!ctx.icons_len)
icon_theme_find__fallback (&ctx, name);
strv_free (&ctx.base);
str_map_free (&ctx.visited);
ARRAY_RESERVE (ctx.icons, 1);
ctx.icons[ctx.icons_len] = NULL;
if (!ctx.icons_len)
{
free (ctx.icons);
return NULL;
}
qsort (ctx.icons,
ctx.icons_len, sizeof *ctx.icons, icon_theme_find__compare);
*len = ctx.icons_len;
return ctx.icons;
}
static void
icon_theme_free (struct icon_theme_icon **icons)
{
for (struct icon_theme_icon **p = icons; *p; p++)
free (*p);
free (icons);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#ifdef LIBERTY_XDG_WANT_X11
static void
icon_theme_set_window_icon (Display *dpy,
Window window, const char *theme, const char *name)
{
size_t icons_len = 0;
struct icon_theme_icon **icons = icon_theme_find (theme, name, &icons_len);
if (!icons)
return;
size_t n = 0;
for (size_t i = 0; i < icons_len; i++)
n += 2 + icons[i]->width * icons[i]->height;
unsigned long *data = xcalloc (n, sizeof *data), *p = data;
for (size_t i = 0; i < icons_len; i++)
{
*p++ = icons[i]->width;
*p++ = icons[i]->height;
uint32_t *q = icons[i]->argb;
for (size_t k = icons[i]->width * icons[i]->height; k--; )
*p++ = *q++;
}
XChangeProperty (dpy, window, XInternAtom (dpy, "_NET_WM_ICON", False),
XA_CARDINAL, 32, PropModeReplace, (const unsigned char *) data, n);
free (data);
icon_theme_free (icons);
}
#endif // LIBERTY_XDG_WANT_X11
#endif // LIBERTY_XDG_WANT_ICONS

File diff suppressed because it is too large Load Diff

384
liberty.c
View File

@ -1,7 +1,7 @@
/*
* liberty.c: the ultimate C unlibrary
*
* Copyright (c) 2014 - 2024, Přemysl Eric Janouch <p@janouch.name>
* Copyright (c) 2014 - 2020, 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.
@ -16,13 +16,8 @@
*
*/
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200112L
#endif
#ifndef _XOPEN_SOURCE
#define _POSIX_C_SOURCE 199309L
#define _XOPEN_SOURCE 600
#endif
#include <stdio.h>
#include <stddef.h>
@ -122,7 +117,8 @@ extern char **environ;
#define CONTAINER_OF(pointer, type, member) \
((type *) ((char *) pointer - offsetof (type, member)))
char *liberty = "They who can give up essential liberty to obtain a little "
const char *liberty =
"They who can give up essential liberty to obtain a little "
"temporary safety deserve neither liberty nor safety.";
// --- Logging -----------------------------------------------------------------
@ -292,8 +288,7 @@ xreallocarray (void *o, size_t n, size_t m)
static char *
xstrdup (const char *s)
{
size_t len = strlen (s) + 1;
return memcpy (xmalloc (len), s, len);
return strcpy ((char *) xmalloc (strlen (s) + 1), s);
}
static char *
@ -303,7 +298,7 @@ xstrndup (const char *s, size_t n)
if (n > size)
n = size;
char *copy = xmalloc (n + 1);
char *copy = (char *) xmalloc (n + 1);
memcpy (copy, s, n);
copy[n] = '\0';
return copy;
@ -316,14 +311,15 @@ xstrndup (const char *s, size_t n)
#define ARRAY(type, name) type *name; size_t name ## _len, name ## _alloc;
#define ARRAY_INIT_SIZED(a, n) \
BLOCK_START \
(a) = xcalloc (sizeof *(a), (a ## _alloc) = (n)); \
(a) = (type *) xcalloc (sizeof *(a), (a ## _alloc) = (n)); \
(a ## _len) = 0; \
BLOCK_END
#define ARRAY_INIT(a) ARRAY_INIT_SIZED (a, 16)
#define ARRAY_RESERVE(a, n) \
BLOCK_START \
while ((a ## _alloc) - (a ## _len) < n) \
(a) = xreallocarray ((a), sizeof *(a), (a ## _alloc) <<= 1); \
(a) = (type *) xreallocarray ((a), \
sizeof *(a), (a ## _alloc) <<= 1); \
BLOCK_END
// --- Double-linked list helpers ----------------------------------------------
@ -398,7 +394,7 @@ strv_make (void)
struct strv self;
self.alloc = 4;
self.len = 0;
self.vector = xcalloc (sizeof *self.vector, self.alloc);
self.vector = (char **) xcalloc (sizeof *self.vector, self.alloc);
return self;
}
@ -425,7 +421,7 @@ strv_append_owned (struct strv *self, char *s)
{
self->vector[self->len] = s;
if (++self->len >= self->alloc)
self->vector = xreallocarray (self->vector,
self->vector = (char **) xreallocarray (self->vector,
sizeof *self->vector, (self->alloc <<= 1));
self->vector[self->len] = NULL;
}
@ -498,7 +494,7 @@ str_make (void)
struct str self;
self.alloc = 16;
self.len = 0;
self.str = strcpy (xmalloc (self.alloc), "");
self.str = strcpy ((char *) xmalloc (self.alloc), "");
return self;
}
@ -535,7 +531,7 @@ str_reserve (struct str *self, size_t n)
while (new_alloc <= self->len + n)
new_alloc <<= 1;
if (new_alloc != self->alloc)
self->str = xrealloc (self->str, (self->alloc = new_alloc));
self->str = (char *) xrealloc (self->str, (self->alloc = new_alloc));
}
static void
@ -614,7 +610,7 @@ str_remove_slice (struct str *self, size_t start, size_t length)
// Shrink the string if the allocation becomes way too large
if (self->alloc >= STR_SHRINK_THRESHOLD && self->len < (self->alloc >> 2))
self->str = xrealloc (self->str, self->alloc >>= 2);
self->str = (char *) xrealloc (self->str, self->alloc >>= 2);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -628,7 +624,11 @@ str_pack_u8 (struct str *self, uint8_t x)
static void
str_pack_u16 (struct str *self, uint16_t x)
{
uint8_t tmp[2] = { x >> 8, x };
uint8_t tmp[2] =
{
(uint8_t) (x >> 8),
(uint8_t) x
};
str_append_data (self, tmp, sizeof tmp);
}
@ -636,7 +636,13 @@ static void
str_pack_u32 (struct str *self, uint32_t x)
{
uint32_t u = x;
uint8_t tmp[4] = { u >> 24, u >> 16, u >> 8, u };
uint8_t tmp[4] =
{
(uint8_t) (u >> 24),
(uint8_t) (u >> 16),
(uint8_t) (u >> 8),
(uint8_t) u
};
str_append_data (self, tmp, sizeof tmp);
}
@ -644,7 +650,16 @@ static void
str_pack_u64 (struct str *self, uint64_t x)
{
uint8_t tmp[8] =
{ x >> 56, x >> 48, x >> 40, x >> 32, x >> 24, x >> 16, x >> 8, x };
{
(uint8_t) (x >> 56),
(uint8_t) (x >> 48),
(uint8_t) (x >> 40),
(uint8_t) (x >> 32),
(uint8_t) (x >> 24),
(uint8_t) (x >> 16),
(uint8_t) (x >> 8),
(uint8_t) x
};
str_append_data (self, tmp, sizeof tmp);
}
@ -653,60 +668,6 @@ str_pack_u64 (struct str *self, uint64_t x)
#define str_pack_i32(self, x) str_pack_u32 ((self), (uint32_t) (x))
#define str_pack_i64(self, x) str_pack_u64 ((self), (uint64_t) (x))
// --- Reading binary numbers --------------------------------------------------
// Doing this byte by byte prevents unaligned memory access issues.
static uint64_t
peek_u64be (const uint8_t *p)
{
return (uint64_t) p[0] << 56 | (uint64_t) p[1] << 48
| (uint64_t) p[2] << 40 | (uint64_t) p[3] << 32
| (uint64_t) p[4] << 24 | (uint64_t) p[5] << 16 | p[6] << 8 | p[7];
}
static uint32_t
peek_u32be (const uint8_t *p)
{
return (uint32_t) p[0] << 24 | (uint32_t) p[1] << 16 | p[2] << 8 | p[3];
}
static uint16_t
peek_u16be (const uint8_t *p)
{
return (uint16_t) p[0] << 8 | p[1];
}
static uint64_t
peek_u64le (const uint8_t *p)
{
return (uint64_t) p[7] << 56 | (uint64_t) p[6] << 48
| (uint64_t) p[5] << 40 | (uint64_t) p[4] << 32
| (uint64_t) p[3] << 24 | (uint64_t) p[2] << 16 | p[1] << 8 | p[0];
}
static uint32_t
peek_u32le (const uint8_t *p)
{
return (uint32_t) p[3] << 24 | (uint32_t) p[2] << 16 | p[1] << 8 | p[0];
}
static uint16_t
peek_u16le (const uint8_t *p)
{
return (uint16_t) p[1] << 8 | p[0];
}
struct peeker
{
uint64_t (*u64) (const uint8_t *);
uint32_t (*u32) (const uint8_t *);
uint16_t (*u16) (const uint8_t *);
};
static const struct peeker peeker_be = {peek_u64be, peek_u32be, peek_u16be};
static const struct peeker peeker_le = {peek_u64le, peek_u32le, peek_u16le};
// --- Errors ------------------------------------------------------------------
// Error reporting utilities. Inspired by GError, only much simpler.
@ -732,8 +693,8 @@ error_set (struct error **e, const char *message, ...)
hard_assert (size >= 0);
struct error *tmp = xmalloc (sizeof *tmp);
tmp->message = xmalloc (size + 1);
struct error *tmp = (struct error *) xmalloc (sizeof *tmp);
tmp->message = (char *) xmalloc (size + 1);
va_start (ap, message);
size = vsnprintf (tmp->message, size + 1, message, ap);
@ -838,7 +799,7 @@ random_bytes (void *output, size_t len, struct error **e)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static unsigned char g_siphash_key[16] = "SipHash 2-4 key!";
static unsigned char g_siphash_key[16] = "SipHash 2-4 key" /* \0 */;
static inline void
siphash_wrapper_randomize (void)
@ -850,7 +811,7 @@ siphash_wrapper_randomize (void)
static inline uint64_t
siphash_wrapper (const void *m, size_t len)
{
return siphash (g_siphash_key, m, len);
return siphash (g_siphash_key, (const unsigned char *) m, len);
}
// --- String hash map ---------------------------------------------------------
@ -892,7 +853,7 @@ str_map_make (str_map_free_fn free)
self.len = 0;
self.free = free;
self.key_xfrm = NULL;
self.map = xcalloc (self.alloc, sizeof *self.map);
self.map = (struct str_map_link **) xcalloc (self.alloc, sizeof *self.map);
self.shrink_lock = false;
return self;
}
@ -948,7 +909,8 @@ str_map_resize (struct str_map *self, size_t new_size)
size_t mask = new_size - 1;
self->alloc = new_size;
self->map = xcalloc (self->alloc, sizeof *self->map);
self->map =
(struct str_map_link **) xcalloc (self->alloc, sizeof *self->map);
for (i = 0; i < old_size; i++)
{
struct str_map_link *iter = old_map[i], *next_iter;
@ -1018,7 +980,8 @@ str_map_set_real (struct str_map *self, const char *key, void *value)
// Link in a new element for the given <key, value> pair
size_t key_length = strlen (key);
struct str_map_link *link = xmalloc (sizeof *link + key_length + 1);
struct str_map_link *link =
(struct str_map_link *) xmalloc (sizeof *link + key_length + 1);
link->data = value;
link->key_length = key_length;
memcpy (link->key, key, key_length + 1);
@ -1221,7 +1184,7 @@ async_cancel (struct async *self)
static void
async_cleanup (void *user_data)
{
struct async *self = user_data;
struct async *self = (struct async *) user_data;
hard_assert (!pthread_mutex_lock (&self->manager->lock));
LIST_UNLINK (self->manager->running, self);
@ -1237,7 +1200,7 @@ async_routine (void *user_data)
{
// Beware that we mustn't trigger any cancellation point before we set up
// the cleanup handler, otherwise we'd need to disable it first
struct async *self = user_data;
struct async *self = (struct async *) user_data;
pthread_cleanup_push (async_cleanup, self);
self->execute (self);
@ -1446,7 +1409,8 @@ poller_timers_make (void)
struct poller_timers self;
self.alloc = POLLER_MIN_ALLOC;
self.len = 0;
self.heap = xmalloc (self.alloc * sizeof *self.heap);
self.heap =
(struct poller_timer **) xmalloc (self.alloc * sizeof *self.heap);
return self;
}
@ -1556,7 +1520,7 @@ poller_timers_set (struct poller_timers *self, struct poller_timer *timer)
}
if (self->len == self->alloc)
self->heap = xreallocarray (self->heap,
self->heap = (struct poller_timer **) xreallocarray (self->heap,
self->alloc <<= 1, sizeof *self->heap);
self->heap[self->len] = timer;
timer->index = self->len;
@ -1633,9 +1597,10 @@ poller_init (struct poller *self)
self->len = 0;
self->alloc = POLLER_MIN_ALLOC;
self->fds = xcalloc (self->alloc, sizeof *self->fds);
self->dummy = xcalloc (self->alloc, sizeof *self->dummy);
self->revents = xcalloc (self->alloc, sizeof *self->revents);
self->fds = (struct poller_fd **) xcalloc (self->alloc, sizeof *self->fds);
self->dummy = (int *) xcalloc (self->alloc, sizeof *self->dummy);
self->revents = (struct epoll_event *)
xcalloc (self->alloc, sizeof *self->revents);
self->revents_len = 0;
poller_common_init (&self->common, self);
@ -1648,7 +1613,7 @@ poller_free (struct poller *self)
{
struct poller_fd *fd = self->fds[i];
hard_assert (epoll_ctl (self->epoll_fd,
EPOLL_CTL_DEL, fd->fd, (void *) "") != -1);
EPOLL_CTL_DEL, fd->fd, (struct epoll_event *) "") != -1);
}
poller_common_free (&self->common);
@ -1668,11 +1633,11 @@ poller_ensure_space (struct poller *self)
self->alloc <<= 1;
hard_assert (self->alloc != 0);
self->revents = xreallocarray
self->revents = (struct epoll_event *) xreallocarray
(self->revents, sizeof *self->revents, self->alloc);
self->fds = xreallocarray
self->fds = (struct poller_fd **) xreallocarray
(self->fds, sizeof *self->fds, self->alloc);
self->dummy = xreallocarray
self->dummy = (int *) xreallocarray
(self->dummy, sizeof *self->dummy, self->alloc);
}
@ -1724,8 +1689,12 @@ poller_set (struct poller *self, struct poller_fd *fd)
static int
poller_compare_fds (const void *ax, const void *bx)
{
const struct epoll_event *ay = ax, *by = bx;
struct poller_fd *a = ay->data.ptr, *b = by->data.ptr;
const struct epoll_event
*ay = (const struct epoll_event *) ax,
*by = (const struct epoll_event *) bx;
struct poller_fd
*a = (struct poller_fd *) ay->data.ptr,
*b = (struct poller_fd *) by->data.ptr;
return a->fd - b->fd;
}
@ -1736,7 +1705,7 @@ poller_remove_from_dispatch (struct poller *self, const struct poller_fd *fd)
return;
struct epoll_event key = { .data.ptr = (void *) fd }, *fd_event;
if ((fd_event = bsearch (&key, self->revents,
if ((fd_event = (struct epoll_event *) bsearch (&key, self->revents,
self->revents_len, sizeof *self->revents, poller_compare_fds)))
{
fd_event->events = -1;
@ -1759,7 +1728,7 @@ poller_remove_at_index (struct poller *self, size_t index)
poller_remove_from_dispatch (self, fd);
if (!fd->closed)
hard_assert (epoll_ctl (self->epoll_fd,
EPOLL_CTL_DEL, fd->fd, (void *) "") != -1);
EPOLL_CTL_DEL, fd->fd, (struct epoll_event *) "") != -1);
if (index != --self->len)
{
@ -1795,7 +1764,7 @@ poller_run (struct poller *self)
if (revents->events == (uint32_t) -1)
continue;
struct poller_fd *fd = revents->data.ptr;
struct poller_fd *fd = (struct poller_fd *) revents->data.ptr;
hard_assert (fd->index != -1);
struct pollfd pfd;
@ -1809,9 +1778,10 @@ poller_run (struct poller *self)
self->revents_len = 0;
}
// Sort of similar to the epoll version. Let's hope Darwin isn't broken,
// that'd mean reimplementing this in terms of select() just because of Crapple.
#elif defined (BSD) || defined (__APPLE__)
#elif defined (BSD)
// Mac OS X's kqueue is fatally broken, or so I've been told; leaving it out.
// Otherwise this is sort of similar to the epoll version.
#include <sys/types.h>
#include <sys/event.h>
@ -1837,8 +1807,9 @@ poller_init (struct poller *self)
self->len = 0;
self->alloc = POLLER_MIN_ALLOC;
self->fds = xcalloc (self->alloc, sizeof *self->fds);
self->revents = xcalloc (self->alloc, sizeof *self->revents);
self->fds = (struct poller_fd **) xcalloc (self->alloc, sizeof *self->fds);
self->revents = (struct kevent *)
xcalloc (self->alloc, sizeof *self->revents);
self->revents_len = 0;
poller_common_init (&self->common, self);
}
@ -1861,9 +1832,9 @@ poller_ensure_space (struct poller *self)
self->alloc <<= 1;
hard_assert (self->alloc != 0);
self->revents = xreallocarray
self->revents = (struct kevent *) xreallocarray
(self->revents, sizeof *self->revents, self->alloc);
self->fds = xreallocarray
self->fds = (struct poller_fd **) xreallocarray
(self->fds, sizeof *self->fds, self->alloc);
}
@ -2063,8 +2034,9 @@ poller_init (struct poller *self)
{
self->alloc = POLLER_MIN_ALLOC;
self->len = 0;
self->fds = xcalloc (self->alloc, sizeof *self->fds);
self->fds_data = xcalloc (self->alloc, sizeof *self->fds_data);
self->fds = (struct pollfd **) xcalloc (self->alloc, sizeof *self->fds);
self->fds_data = (struct poller_fd **)
xcalloc (self->alloc, sizeof *self->fds_data);
poller_common_init (&self->common, self);
self->dispatch_next = -1;
}
@ -2084,8 +2056,9 @@ poller_ensure_space (struct poller *self)
return;
self->alloc <<= 1;
self->fds = xreallocarray (self->fds, sizeof *self->fds, self->alloc);
self->fds_data = xreallocarray
self->fds = (struct pollfd *)
xreallocarray (self->fds, sizeof *self->fds, self->alloc);
self->fds_data = (struct poller_fd **) xreallocarray
(self->fds_data, sizeof *self->fds_data, self->alloc);
}
@ -2369,7 +2342,8 @@ static struct async_getaddrinfo *
async_getaddrinfo (struct async_manager *manager,
const char *host, const char *service, const struct addrinfo *hints)
{
struct async_getaddrinfo *self = xcalloc (1, sizeof *self);
struct async_getaddrinfo *self =
(struct async_getaddrinfo *) xcalloc (1, sizeof *self);
self->async = async_make (manager);
if (host) self->host = xstrdup (host);
@ -2433,10 +2407,11 @@ static struct async_getnameinfo *
async_getnameinfo (struct async_manager *manager,
const struct sockaddr *sa, socklen_t sa_len, int flags)
{
struct async_getnameinfo *self = xcalloc (1, sizeof *self);
struct async_getnameinfo *self =
(struct async_getnameinfo *) xcalloc (1, sizeof *self);
self->async = async_make (manager);
self->address = memcpy (xmalloc (sa_len), sa, sa_len);
self->address = (struct sockaddr *) memcpy (xmalloc (sa_len), sa, sa_len);
self->address_len = sa_len;
self->flags = flags;
@ -2510,7 +2485,7 @@ write_queue_processed (struct write_queue *self, size_t len)
}
static bool
write_queue_is_empty (const struct write_queue *self)
write_queue_is_empty (struct write_queue *self)
{
return self->head == NULL;
}
@ -2558,7 +2533,12 @@ msg_reader_get (struct msg_reader *self, size_t *len)
return NULL;
uint8_t *x = (uint8_t *) self->buf.str + self->offset;
uint64_t msg_len = peek_u64be (x);
uint64_t msg_len
= (uint64_t) x[0] << 56 | (uint64_t) x[1] << 48
| (uint64_t) x[2] << 40 | (uint64_t) x[3] << 32
| (uint64_t) x[4] << 24 | (uint64_t) x[5] << 16
| (uint64_t) x[6] << 8 | (uint64_t) x[7];
if (msg_len < sizeof msg_len)
{
// The message is shorter than its header
@ -2596,11 +2576,16 @@ struct msg_unpacker
static struct msg_unpacker
msg_unpacker_make (const void *data, size_t len)
{
return (struct msg_unpacker) { .data = data, .len = len, .offset = 0 };
return (struct msg_unpacker)
{
.data = (const char *) data,
.offset = 0,
.len = len
};
}
static size_t
msg_unpacker_get_available (const struct msg_unpacker *self)
msg_unpacker_get_available (struct msg_unpacker *self)
{
return self->len - self->offset;
}
@ -2623,7 +2608,8 @@ static bool
msg_unpacker_u16 (struct msg_unpacker *self, uint16_t *value)
{
UNPACKER_INT_BEGIN
*value = peek_u16be (x);
*value
= (uint16_t) x[0] << 8 | (uint16_t) x[1];
return true;
}
@ -2631,7 +2617,9 @@ static bool
msg_unpacker_u32 (struct msg_unpacker *self, uint32_t *value)
{
UNPACKER_INT_BEGIN
*value = peek_u32be (x);
*value
= (uint32_t) x[0] << 24 | (uint32_t) x[1] << 16
| (uint32_t) x[2] << 8 | (uint32_t) x[3];
return true;
}
@ -2639,7 +2627,11 @@ static bool
msg_unpacker_u64 (struct msg_unpacker *self, uint64_t *value)
{
UNPACKER_INT_BEGIN
*value = peek_u64be (x);
*value
= (uint64_t) x[0] << 56 | (uint64_t) x[1] << 48
| (uint64_t) x[2] << 40 | (uint64_t) x[3] << 32
| (uint64_t) x[4] << 24 | (uint64_t) x[5] << 16
| (uint64_t) x[6] << 8 | (uint64_t) x[7];
return true;
}
@ -2678,8 +2670,16 @@ msg_writer_flush (struct msg_writer *self, size_t *len)
{
// Update the message length
uint64_t x = self->buf.len;
uint8_t tmp[8] =
{ x >> 56, x >> 48, x >> 40, x >> 32, x >> 24, x >> 16, x >> 8, x };
uint8_t tmp[8] = {
(uint8_t) (x >> 56),
(uint8_t) (x >> 48),
(uint8_t) (x >> 40),
(uint8_t) (x >> 32),
(uint8_t) (x >> 24),
(uint8_t) (x >> 16),
(uint8_t) (x >> 8),
(uint8_t) x
};
memcpy (self->buf.str, tmp, sizeof tmp);
*len = x;
@ -2795,12 +2795,16 @@ utf8_decode (const char **s, size_t len)
}
// In the middle of a character
// or an overlong sequence (subset, possibly MUTF-8, not supported)
if (sequence_len == 1 || *p == 0xC0 || *p == 0xC1)
if (sequence_len == 1)
return -1;
// Check the rest of the sequence
uint32_t cp = *p++ & ~mask;
// Overlong sequence (possibly MUTF-8, not supported)
if (!cp && sequence_len)
return -1;
while (sequence_len && --sequence_len)
{
if (p == end)
@ -2954,7 +2958,7 @@ base64_encode (const void *data, size_t len, struct str *output)
const char *alphabet =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const uint8_t *p = data;
const uint8_t *p = (const uint8_t *) data;
size_t n_groups = len / 3;
size_t tail = len - n_groups * 3;
uint32_t group;
@ -2991,10 +2995,10 @@ base64_encode (const void *data, size_t len, struct str *output)
// --- Utilities ---------------------------------------------------------------
static void
cstr_set (char **s, char *new)
cstr_set (char **s, char *new_)
{
free (*s);
*s = new;
*s = new_;
}
static void
@ -3072,7 +3076,7 @@ iconv_xstrdup (iconv_t conv, char *in, size_t in_len, size_t *out_len)
char *buf, *buf_ptr;
size_t out_left, buf_alloc;
buf = buf_ptr = xmalloc (out_left = buf_alloc = 64);
buf = buf_ptr = (char *) xmalloc (out_left = buf_alloc = 64);
char *in_ptr = in;
if (in_len == (size_t) -1)
@ -3088,7 +3092,7 @@ iconv_xstrdup (iconv_t conv, char *in, size_t in_len, size_t *out_len)
return NULL;
}
out_left += buf_alloc;
char *new_buf = xrealloc (buf, buf_alloc <<= 1);
char *new_buf = (char *) xrealloc (buf, buf_alloc <<= 1);
buf_ptr += new_buf - buf;
buf = new_buf;
}
@ -3169,8 +3173,8 @@ lock_pid_file (const char *path, struct error **e)
struct flock lock =
{
.l_type = F_WRLCK,
.l_start = 0,
.l_whence = SEEK_SET,
.l_start = 0,
.l_len = 0,
};
if (fcntl (fd, F_SETLK, &lock))
@ -3294,7 +3298,7 @@ get_xdg_config_dirs (struct strv *out)
str_free (&config_home);
const char *xdg_config_dirs;
if (!(xdg_config_dirs = getenv ("XDG_CONFIG_DIRS")) || !*xdg_config_dirs)
if (!(xdg_config_dirs = getenv ("XDG_CONFIG_DIRS")))
xdg_config_dirs = "/etc/xdg";
cstr_split (xdg_config_dirs, ":", true, out);
}
@ -3319,7 +3323,7 @@ get_xdg_data_dirs (struct strv *out)
str_free (&data_home);
const char *xdg_data_dirs;
if (!(xdg_data_dirs = getenv ("XDG_DATA_DIRS")) || !*xdg_data_dirs)
if (!(xdg_data_dirs = getenv ("XDG_DATA_DIRS")))
xdg_data_dirs = "/usr/local/share/:/usr/share/";
cstr_split (xdg_data_dirs, ":", true, out);
}
@ -3369,7 +3373,7 @@ resolve_relative_runtime_filename (const char *filename)
/// path can reside in a system-wide directory with no risk of a conflict.
/// However, we have to take care about permissions. Do we even need this?
static char *
resolve_relative_runtime_template (const char *template)
resolve_relative_runtime_template (const char *template_)
{
struct str path = str_make ();
const char *runtime_dir = getenv ("XDG_RUNTIME_DIR");
@ -3381,7 +3385,7 @@ resolve_relative_runtime_template (const char *template)
else
str_append_printf (&path, "/tmp/%s.%d", PROGRAM_NAME, geteuid ());
str_append_printf (&path, "/%s", template);
str_append_printf (&path, "/%s", template_);
return resolve_relative_runtime_filename_finish (path);
}
@ -3403,9 +3407,9 @@ try_expand_tilde (const char *filename)
struct passwd pwd, *success = NULL;
char *user = xstrndup (filename, until_slash);
char *buf = xmalloc (buf_len);
char *buf = (char *) xmalloc (buf_len);
while (getpwnam_r (user, &pwd, buf, buf_len, &success) == ERANGE)
buf = xrealloc (buf, buf_len <<= 1);
buf = (char *) xrealloc (buf, buf_len <<= 1);
free (user);
char *result = NULL;
@ -3481,7 +3485,7 @@ xssl_get_error (SSL *ssl, int result, const char **error_info)
static regex_t *
regex_compile (const char *regex, int flags, struct error **e)
{
regex_t *re = xmalloc (sizeof *re);
regex_t *re = (regex_t *) xmalloc (sizeof *re);
int err = regcomp (re, regex, flags);
if (!err)
return re;
@ -3497,7 +3501,7 @@ regex_compile (const char *regex, int flags, struct error **e)
static void
regex_free (void *regex)
{
regfree (regex);
regfree ((regex_t *) regex);
free (regex);
}
@ -3516,7 +3520,7 @@ static bool
regex_cache_match (struct str_map *cache, const char *regex, int flags,
const char *s, struct error **e)
{
regex_t *re = str_map_find (cache, regex);
regex_t *re = (regex_t *) str_map_find (cache, regex);
if (!re)
{
re = regex_compile (regex, flags, e);
@ -3595,8 +3599,6 @@ write_file_safe (const char *filename, const void *data, size_t data_len,
{
// XXX: ideally we would also open the directory, use *at() versions
// of functions and call fsync() on the directory as appropriate
// FIXME: this should behave similarly to mkstemp(), just with 0666;
// as it is, this function is not particularly safe
char *temp = xstrdup_printf ("%s.new", filename);
bool success = write_file (temp, data, data_len, e);
if (success && !(success = !rename (temp, filename)))
@ -3788,7 +3790,7 @@ opt_handler_make (int argc, char **argv,
self.opts = opts;
self.opts_len = len;
self.options = xcalloc (len + 1, sizeof *self.options);
self.options = (struct option *) xcalloc (len + 1, sizeof *self.options);
struct str opt_string = str_make ();
for (size_t i = 0; i < len; i++)
@ -3966,7 +3968,7 @@ test_add_internal (struct test *self, const char *name, size_t fixture_size,
hard_assert (test != NULL);
hard_assert (name != NULL);
struct test_unit *unit = xcalloc (1, sizeof *unit);
struct test_unit *unit = (struct test_unit *) xcalloc (1, sizeof *unit);
unit->name = xstrdup (name);
unit->fixture_size = fixture_size;
unit->user_data = user_data;
@ -4119,7 +4121,8 @@ struct connector_target
static struct connector_target *
connector_target_new (void)
{
struct connector_target *self = xcalloc (1, sizeof *self);
struct connector_target *self =
(struct connector_target *) xcalloc (1, sizeof *self);
return self;
}
@ -4332,7 +4335,7 @@ connector_free (struct connector *self)
static void
connector_on_getaddrinfo (int err, struct addrinfo *results, void *user_data)
{
struct connector_target *self = user_data;
struct connector_target *self = (struct connector_target *) user_data;
if (err)
{
@ -4462,13 +4465,11 @@ socket_io_try_write (int socket_fd, struct str *wb)
// object = lws '{' entries endobj
// endobj = lws '}'
//
// quoted = lws '"' (!["\\] char / '\\' escape)* '"'
// / lws '`' (![`] char)* '`'
// string = (quoted)+
// string = lws '"' ('\\' escape / ![\\"] char)* '"'
// char = [\0-\177] # or any Unicode codepoint in the UTF-8 encoding
// escape = [\\"abfnrtv] / [xX][0-9A-Fa-f][0-9A-Fa-f]? / [0-7][0-7]?[0-7]?
//
// integer = lws [-+]? [0-9]+ # whatever strtoll() accepts on your system
// integer = lws '-'? [0-9]+ # whatever strtoll() accepts on your system
// null = lws 'null'
// boolean = lws 'yes' / lws 'YES' / lws 'no' / lws 'NO'
// / lws 'on' / lws 'ON' / lws 'off' / lws 'OFF'
@ -4587,7 +4588,8 @@ config_item_move (struct config_item *self, struct config_item *source)
static struct config_item *
config_item_new (enum config_item_type type)
{
struct config_item *self = xcalloc (1, sizeof *self);
struct config_item *self =
(struct config_item *) xcalloc (1, sizeof *self);
self->type = type;
return self;
}
@ -4727,7 +4729,8 @@ config_item_get (struct config_item *self, const char *path, struct error **e)
const char *key = v.vector[i];
if (!*key)
error_set (e, "empty path element");
else if (!(self = str_map_find (&self->value.object, key)))
else if (!(self = (struct config_item *)
str_map_find (&self->value.object, key)))
error_set (e, "`%s' not found in object", key);
else if (++i == v.len)
result = self;
@ -4759,15 +4762,13 @@ config_item_write_string (struct str *output, const struct str *s)
for (size_t i = 0; i < s->len; i++)
{
unsigned char c = s->str[i];
if (c == '\n') str_append (output, "\\n");
else if (c == '\r') str_append (output, "\\r");
else if (c == '\t') str_append (output, "\\t");
else if (c == '\\') str_append (output, "\\\\");
else if (c == '"') str_append (output, "\\\"");
else if (iscntrl_ascii (c))
str_append_printf (output, "\\x%02x", c);
else
str_append_c (output, c);
if (c == '\n') str_append (output, "\\n");
else if (c == '\r') str_append (output, "\\r");
else if (c == '\t') str_append (output, "\\t");
else if (c == '\\') str_append (output, "\\\\");
else if (c == '"') str_append (output, "\\\"");
else if (c < 32) str_append_printf (output, "\\x%02x", c);
else str_append_c (output, c);
}
str_append_c (output, '"');
}
@ -4860,7 +4861,7 @@ config_item_write_object_innards
struct str_map_iter iter = str_map_iter_make (&object->value.object);
struct config_item *value;
while ((value = str_map_iter_next (&iter)))
while ((value = (struct config_item *) str_map_iter_next (&iter)))
config_item_write_kv_pair (self, iter.link->key, value);
}
@ -5091,10 +5092,10 @@ config_tokenizer_escape_sequence
}
static bool
config_tokenizer_dq_string (struct config_tokenizer *self, struct str *output,
struct error **e)
config_tokenizer_string
(struct config_tokenizer *self, struct str *output, struct error **e)
{
unsigned char c = config_tokenizer_advance (self);
unsigned char c;
while (self->len)
{
if ((c = config_tokenizer_advance (self)) == '"')
@ -5108,44 +5109,6 @@ config_tokenizer_dq_string (struct config_tokenizer *self, struct str *output,
return false;
}
static bool
config_tokenizer_bt_string (struct config_tokenizer *self, struct str *output,
struct error **e)
{
unsigned char c = config_tokenizer_advance (self);
while (self->len)
{
if ((c = config_tokenizer_advance (self)) == '`')
return true;
str_append_c (output, c);
}
config_tokenizer_error (self, e, "premature end of string");
return false;
}
static bool
config_tokenizer_string (struct config_tokenizer *self, struct str *output,
struct error **e)
{
// Go-like strings, with C/AWK-like automatic concatenation
while (self->len)
{
bool ok = true;
if (isspace_ascii (*self->p) && *self->p != '\n')
config_tokenizer_advance (self);
else if (*self->p == '"')
ok = config_tokenizer_dq_string (self, output, e);
else if (*self->p == '`')
ok = config_tokenizer_bt_string (self, output, e);
else
break;
if (!ok)
return false;
}
return true;
}
static enum config_token
config_tokenizer_next (struct config_tokenizer *self, struct error **e)
{
@ -5170,7 +5133,7 @@ config_tokenizer_next (struct config_tokenizer *self, struct error **e)
return CONFIG_T_ABORT;
case '"':
case '`':
config_tokenizer_advance (self);
str_reset (&self->string);
if (!config_tokenizer_string (self, &self->string, e))
return CONFIG_T_ABORT;
@ -5464,11 +5427,11 @@ config_read_from_file (const char *filename, struct error **e)
{
struct config_item *root = NULL;
struct error *error = NULL;
struct str data = str_make ();
if (!read_file (filename, &data, e))
goto end;
struct error *error = NULL;
if (!(root = config_item_parse (data.str, data.len, false, &error)))
{
error_set (e, "parse error in `%s': %s", filename, error->message);
@ -5488,12 +5451,12 @@ config_schema_initialize_item (struct config_schema *schema,
struct error **e)
{
hard_assert (parent->type == CONFIG_ITEM_OBJECT);
struct config_item *item =
struct config_item *item = (struct config_item *)
str_map_find (&parent->value.object, schema->name);
struct error *error = NULL;
if (item)
{
struct error *error = NULL;
item->user_data = user_data;
if (config_item_validate_by_schema (item, schema, &error))
goto keep_current;
@ -5501,9 +5464,9 @@ config_schema_initialize_item (struct config_schema *schema,
error_set (warning, "resetting configuration item "
"`%s' to default: %s", schema->name, error->message);
error_free (error);
error = NULL;
}
struct error *error = NULL;
if (schema->default_)
item = config_item_parse
(schema->default_, strlen (schema->default_), true, &error);
@ -5566,7 +5529,7 @@ config_schema_call_changed (struct config_item *item)
{
struct str_map_iter iter = str_map_iter_make (&item->value.object);
struct config_item *child;
while ((child = str_map_iter_next (&iter)))
while ((child = (struct config_item *) str_map_iter_next (&iter)))
config_schema_call_changed (child);
}
else if (item->schema && item->schema->on_change)
@ -5619,7 +5582,8 @@ static void
config_register_module (struct config *self,
const char *name, config_module_load_fn loader, void *user_data)
{
struct config_module *module = xcalloc (1, sizeof *module);
struct config_module *module =
(struct config_module *) xcalloc (1, sizeof *module);
module->name = xstrdup (name);
module->loader = loader;
module->user_data = user_data;
@ -5637,9 +5601,9 @@ config_load (struct config *self, struct config_item *root)
struct str_map_iter iter = str_map_iter_make (&self->modules);
struct config_module *module;
while ((module = str_map_iter_next (&iter)))
while ((module = (struct config_module *) str_map_iter_next (&iter)))
{
struct config_item *subtree = str_map_find
struct config_item *subtree = (struct config_item *) str_map_find
(&root->value.object, module->name);
// Silently fix inputs that only a lunatic user could create
if (!subtree || subtree->type != CONFIG_ITEM_OBJECT)

View File

@ -1,108 +0,0 @@
libertyxdr(7)
=============
:doctype: manpage
Name
----
LibertyXDR - an XDR-derived IDL and data serialization format
Description
-----------
*LibertyXDR* is an interface description language, as well as a data
serialization format. It is largely derived from XDR, though notably
simplified.
Conventions
~~~~~~~~~~~
User-defined types should be named in *CamelCase*, field names in *snake_case*,
and constants in *SCREAMING_SNAKE_CASE*. Code generators will convert these to
whatever is appropriate in their target language.
Primitive data types
~~~~~~~~~~~~~~~~~~~~
Like in XDR, all data is serialized in the network byte order, i.e., big-endian.
* *void*: 0 bytes
+
This is a dummy type that cannot be assigned a field name.
* *bool*: 1 byte
+
This is a boolean value: 0 means _false_, any other value means _true_.
* *u8*, *u16*, *u32*, *u64*: 1, 2, 4, and 8 bytes respectively
+
These are unsigned integers.
* *i8*, *i16*, *i32*, *i64*: 1, 2, 4, and 8 bytes respectively
+
These are signed integers in two's complement.
* *string*: implicitly prefixed by its length as a *u32*,
then immediately followed by its contents, with no trailing NUL byte
+
This is a valid UTF-8 string without a byte order mark. Note that strings are
always unbounded, unlike in XDR.
Constants
~~~~~~~~~
At the top level of a document, outside other definitions, you can define
typeless integer constants:
const VERSION = 1;
The value can be either a name of another previously defined constant,
or an immediate decimal value, which may not contain leading zeros.
Enumerations
~~~~~~~~~~~~
An *enum* is an *i8* with uniquely named values, in their own namespace.
Values can be either specified explicitly, in the same way as with a constant,
or they can be left implicit, in which case names assume a value that is one
larger than their predecessor. Zero is reserved for internal use, thus
enumerations implicitly begin with a value of one. For example, these form
a sequence from one to three:
enum Vehicle { CAR, LORRY = 2, PLANE, };
Structures
~~~~~~~~~~
A *struct* is a sequence of fields, specified by their type, and their chosen
name. You can add a *<>* suffix to change a field to an array, in which case
it is implicitly preceded by a *u32* specifying its length in terms of its
elements.
Unlike in XDR, there is no padding between subsequent fields, and type
definitions can be arbitrarily syntactically nested, as in C.
struct StockReport {
u8 version; // Version of this report.
struct Item {
Vehicle kind; // The vehicle in question.
i32 count; // How many vehicle of that kind there are.
} items<>; // Reported items.
};
Unions
~~~~~~
A *union* is a kind of structure whose fields depend on the value of its first
and always-present field, which must be a tag *enum*:
union VehicleDetails switch (Vehicle kind) {
case CAR: void;
case LORRY: i8 axles;
case PLANE: i8 engines;
};
All possible enumeration values must be named, and there is no *case*
fall-through.
Framing
-------
Unless this role is already filled by, e.g., WebSocket, _LibertyXDR_ structures
should be prefixed by their byte length in the *u32* format, once serialized.
See also
--------
_XDR: External Data Representation Standard_, RFC 4506

View File

@ -1,21 +0,0 @@
" filetype.vim: au! BufNewFile,BufRead *.lxdr setf libertyxdr
if exists("b:current_syntax")
finish
endif
syn match libertyxdrError "[^[:space:]:;,(){}<>=]\+"
syn region libertyxdrBlockComment start=+/[*]+ end=+[*]/+
syn match libertyxdrComment "//.*"
syn match libertyxdrIdentifier "\<[[:alpha:]][[:alnum:]_]*\>"
syn match libertyxdrNumber "\<0\>\|\(-\|\<\)[1-9][[:digit:]]*\>"
syn keyword libertyxdrKeyword const enum struct union switch case
syn keyword libertyxdrType bool u8 u16 u32 u64 i8 i16 i32 i64 string void
let b:current_syntax = "libertyxdr"
hi def link libertyxdrError Error
hi def link libertyxdrBlockComment Comment
hi def link libertyxdrComment Comment
hi def link libertyxdrIdentifier Identifier
hi def link libertyxdrNumber Number
hi def link libertyxdrKeyword Statement
hi def link libertyxdrType Type

View File

@ -82,3 +82,4 @@ siphash (const unsigned char key[16], const unsigned char *m, size_t len)
return v0 ^ v1 ^ v2 ^ v3;
}

View File

@ -32,6 +32,13 @@
#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 -------------------------------------------------------------------
@ -204,6 +211,19 @@ 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
@ -246,6 +266,7 @@ 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

@ -1,7 +1,7 @@
/*
* tests/liberty.c
*
* Copyright (c) 2015 - 2022, Přemysl Eric Janouch <p@janouch.name>
* Copyright (c) 2015 - 2016, 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.
@ -331,12 +331,10 @@ test_utf8 (void)
soft_assert (utf8_decode (&partial, 1) == -2);
soft_assert (utf8_decode (&empty, 0) == -1);
const char valid_1[] = "2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm";
const char valid_2[] = "\xf0\x93\x82\xb9";
const char valid[] = "2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm";
const char invalid_1[] = "\xf0\x90\x28\xbc";
const char invalid_2[] = "\xc0\x80";
soft_assert ( utf8_validate (valid_1, sizeof valid_1));
soft_assert ( utf8_validate (valid_2, sizeof valid_2));
soft_assert ( utf8_validate (valid, sizeof valid));
soft_assert (!utf8_validate (invalid_1, sizeof invalid_1));
soft_assert (!utf8_validate (invalid_2, sizeof invalid_2));
@ -649,7 +647,7 @@ static struct config_schema g_config_test[] =
.default_ = "1" },
{ .name = "foobar",
.type = CONFIG_ITEM_STRING,
.default_ = "\"qux\\x01`\" \"\"`a`" },
.default_ = "\"qux\\x01\"" },
{}
};
@ -675,9 +673,6 @@ test_config (void)
"top.bar", NULL), invalid, NULL));
config_item_destroy (invalid);
hard_assert (!strcmp ("qux\001`a",
config_item_get (config.root, "top.foobar", NULL)->value.string.str));
struct str s = str_make ();
config_item_write (config.root, true, &s);
struct config_item *parsed = config_item_parse (s.str, s.len, false, NULL);

View File

@ -1,134 +0,0 @@
/*
* tests/lxdrgen.c
*
* Copyright (c) 2022, 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.
*
*/
#define PROGRAM_NAME "test"
#define PROGRAM_VERSION "0"
#include "../liberty.c"
#include "lxdrgen.lxdr.c"
static void
test_ser_deser_free (void)
{
hard_assert (PROTO_GEN_VERSION == 1);
enum { CASES = 3 };
struct proto_gen_struct a = {}, b = {};
a.u = xcalloc ((a.u_len = CASES + rand () % 100), sizeof *a.u);
for (size_t i = 0; i < a.u_len; i++)
{
union proto_gen_union *u = a.u + i;
switch (i % CASES)
{
case 0:
u->tag = PROTO_GEN_ENUM_NUMBERS;
u->numbers.a = rand () % UINT8_MAX;
u->numbers.b = rand () % UINT16_MAX;
u->numbers.c = rand () % UINT32_MAX;
u->numbers.d = rand () % UINT64_MAX;
u->numbers.e = rand () % UINT8_MAX;
u->numbers.f = rand () % UINT16_MAX;
u->numbers.g = rand () % UINT32_MAX;
u->numbers.h = rand () % UINT64_MAX;
break;
case 1:
u->tag = PROTO_GEN_ENUM_OTHERS;
u->others.foo = rand () % 2;
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;
break;
default:
hard_assert (!"unhandled case");
}
}
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);
hard_assert (proto_gen_struct_deserialize (&b, &r));
hard_assert (!msg_unpacker_get_available (&r));
str_free (&buf);
hard_assert (a.u_len == b.u_len);
for (size_t i = 0; i < a.u_len; i++)
{
union proto_gen_union *ua = a.u + i;
union proto_gen_union *ub = b.u + i;
hard_assert (ua->tag == ub->tag);
switch (ua->tag)
{
case PROTO_GEN_ENUM_NUMBERS:
hard_assert (ua->numbers.a == ub->numbers.a);
hard_assert (ua->numbers.b == ub->numbers.b);
hard_assert (ua->numbers.c == ub->numbers.c);
hard_assert (ua->numbers.d == ub->numbers.d);
hard_assert (ua->numbers.e == ub->numbers.e);
hard_assert (ua->numbers.f == ub->numbers.f);
hard_assert (ua->numbers.g == ub->numbers.g);
hard_assert (ua->numbers.h == ub->numbers.h);
break;
case PROTO_GEN_ENUM_OTHERS:
hard_assert (ua->others.foo == ub->others.foo);
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;
default:
hard_assert (!"unexpected case");
}
}
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++)
{
proto_gen_union_free (&b.u[i]);
memset (&b.u[i], 0, sizeof b.u[i]);
}
proto_gen_struct_free (&a);
proto_gen_struct_free (&b);
}
int
main (int argc, char *argv[])
{
struct test test;
test_init (&test, argc, argv);
test_add_simple (&test, "/ser-deser-free", NULL, test_ser_deser_free);
return test_run (&test);
}

View File

@ -1,132 +0,0 @@
/*
* 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

@ -1,29 +0,0 @@
/*
* tests/lxdrgen.lxdr: a test protocol for the generator
*/
const VERSION = 1;
const NOISREV = -1;
// TODO: Test failure paths, and in general go for full coverage.
struct Struct {
union Union switch (enum Enum {
NUMBERS = VERSION,
OTHERS = 2,
NOTHING,
} tag) {
case NUMBERS:
i8 a; i16 b; i32 c; i64 d;
u8 e; u16 f; u32 g; u64 h;
case OTHERS:
bool foo;
string bar;
u8 baz<>;
case NOTHING:
void;
} u<>;
union Onion switch (Enum tag) {
case NOTHING:
void;
} o;
};

View File

@ -1,127 +0,0 @@
/*
* tests/pulse.c
*
* Copyright (c) 2021, 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.
*
*/
#define PROGRAM_NAME "test"
#define PROGRAM_VERSION "0"
#define LIBERTY_WANT_POLLER
#include "../liberty.c"
#include "../liberty-pulse.c"
// --- Tests -------------------------------------------------------------------
enum
{
EVENT_IO = 1 << 0,
EVENT_TIME = 1 << 1,
EVENT_DEFER = 1 << 2,
EVENT_ALL = (1 << 3) - 1
};
static intptr_t g_events = 0;
static intptr_t g_destroys = 0;
static void
io_event_cb (pa_mainloop_api *a,
pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata)
{
(void) a; (void) e; (void) fd; (void) events;
g_events |= (intptr_t) userdata;
}
static void
io_event_destroy_cb (pa_mainloop_api *a, pa_io_event *e, void *userdata)
{
(void) a; (void) e;
g_destroys += (intptr_t) userdata;
}
static void
time_event_cb (pa_mainloop_api *a,
pa_time_event *e, const struct timeval *tv, void *userdata)
{
(void) a; (void) e; (void) tv;
g_events |= (intptr_t) userdata;
}
static void
time_event_destroy_cb (pa_mainloop_api *a, pa_time_event *e, void *userdata)
{
(void) a; (void) e;
g_destroys += (intptr_t) userdata;
}
static void
defer_event_cb (pa_mainloop_api *a, pa_defer_event *e, void *userdata)
{
(void) a; (void) e;
g_events |= (intptr_t) userdata;
}
static void
defer_event_destroy_cb (pa_mainloop_api *a, pa_defer_event *e, void *userdata)
{
(void) a; (void) e;
g_destroys += (intptr_t) userdata;
}
static void
test_pulse (void)
{
struct poller poller;
poller_init (&poller);
// Let's just get this over with, not aiming for high test coverage here
pa_mainloop_api *api = poller_pa_new (&poller);
pa_io_event *ie = api->io_new (api, STDOUT_FILENO, PA_IO_EVENT_OUTPUT,
io_event_cb, (void *) EVENT_IO);
api->io_set_destroy (ie, io_event_destroy_cb);
const struct timeval tv = poller_pa_get_current_time ();
pa_time_event *te = api->time_new (api, &tv,
time_event_cb, (void *) EVENT_TIME);
api->time_set_destroy (te, time_event_destroy_cb);
api->time_restart (te, &tv);
pa_defer_event *de = api->defer_new (api,
defer_event_cb, (void *) EVENT_DEFER);
api->defer_set_destroy (de, defer_event_destroy_cb);
api->defer_enable (api->defer_new (api,
defer_event_cb, (void *) EVENT_DEFER), false);
alarm (1);
while (g_events != EVENT_ALL)
poller_run (&poller);
poller_pa_destroy (api);
soft_assert (g_destroys == EVENT_ALL);
poller_free (&poller);
}
// --- Main --------------------------------------------------------------------
int
main (int argc, char *argv[])
{
struct test test;
test_init (&test, argc, argv);
test_add_simple (&test, "/pulse", NULL, test_pulse);
return test_run (&test);
}

View File

@ -1,67 +0,0 @@
/*
* tests/xdg.c
*
* Copyright (c) 2024, 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.
*
*/
#define PROGRAM_NAME "test"
#define PROGRAM_VERSION "0"
#include "../liberty.c"
#include "../liberty-xdg.c"
static const char file[] =
"# This only tests the happy paths\n"
"[Desktop Entry]\n"
"Version = 1.0\n"
"Name=\\s\\n\\t\\r\\\\\n"
"Name[fr]=Nom\n"
"Hidden=true\n"
"Categories=Utility;TextEditor;\n"
"Number=42";
static void
test_desktop_file (void)
{
struct desktop_file entry = desktop_file_make (file, sizeof file - 1);
const char *group = "Desktop Entry";
char *value = desktop_file_get_string (&entry, group, "Version");
hard_assert (!strcmp (value, "1.0"));
cstr_set (&value, desktop_file_get_string (&entry, group, "Name"));
hard_assert (!strcmp (value, " \n\t\r\\"));
free (value);
hard_assert (desktop_file_get_bool (&entry, group, "Hidden"));
struct strv values = desktop_file_get_stringv (&entry, group, "Categories");
hard_assert (values.len == 2);
hard_assert (!strcmp (values.vector[0], "Utility"));
hard_assert (!strcmp (values.vector[1], "TextEditor"));
strv_free (&values);
hard_assert (desktop_file_get_integer (&entry, group, "Number") == 42);
desktop_file_free (&entry);
}
int
main (int argc, char *argv[])
{
struct test test;
test_init (&test, argc, argv);
test_add_simple (&test, "/desktop-file", NULL, test_desktop_file);
return test_run (&test);
}

View File

@ -1,336 +0,0 @@
# asciiman.awk: simplified AsciiDoc to manual page converter
#
# Copyright (c) 2022 - 2024, Přemysl Eric Janouch <p@janouch.name>
# SPDX-License-Identifier: 0BSD
#
# This is not intended to produce great output, merely useful output.
# As such, input documents should restrict themselves as follows:
#
# - In-line formatting sequences must not overlap,
# cannot be escaped, and cannot span lines.
# - Heading underlines must match in byte length exactly.
# - Only a small subset of syntax is supported overall.
#
# Also beware that the output has only been tested with GNU troff and mandoc.
# Attributes can be passed via environment variables starting with "asciidoc-".
function fatal(message) {
print ".\\\" " FILENAME ":" FNR ": fatal error: " message
print FILENAME ":" FNR ": fatal error: " message > "/dev/stderr"
exit 1
}
BEGIN {
for (name in ENVIRON)
if (match(name, /^asciidoc-/))
Attrs[substr(name, RSTART + RLENGTH)] = ENVIRON[name]
}
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)
}
return v s
}
function escape(s) {
gsub(/\\/, "\\\\", s)
gsub(/-/, "\\-", s)
sub(/^[.']/, "\\\\\\&&", s)
return s
}
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)
Attrs[attrname] = expand(substr(line, RSTART + RLENGTH))
} else {
return 0
}
return 1
}
NR == 1 {
nameline = $0
if (match(nameline, /[(][[:digit:]][)]$/)) {
name = substr(nameline, 1, RSTART - 1)
section = substr(nameline, RSTART + 1, RLENGTH - 2)
} else {
fatal("invalid header line")
}
getline
if (length(nameline) != length($0) || /[^=]/)
fatal("invalid header underline")
getline
while (readattribute($0))
getline
if ($0)
fatal("expected an empty line after the header")
# Requesting tbl(1), even though we currently do not support tables.
print "'\\\" t"
printf ".TH \"%s\" \"%s\" \"\" \"%s\"",
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.
while (line) {
if (match(line, /^[+][+][+][^+]+[+][+][+]/)) {
v = v substr(line, RSTART + 3, RLENGTH - 6)
} else if (match(line, /^__[^_]+__/)) {
v = v "\\fI" substr(line, RSTART + 2, RLENGTH - 4) "\\fP"
} else if (match(line, /^[*][*][^*]+[*][*]/)) {
v = v "\\fB" substr(line, RSTART + 2, RLENGTH - 4) "\\fP"
} else if (match(line, /^_[^_]+_/) &&
substr(line, RSTART + RLENGTH) !~ /^[[:alnum:]]/) {
v = v "\\fI" substr(line, RSTART + 1, RLENGTH - 2) "\\fP"
} else if (match(line, /^[*][^*]+[*]/) &&
substr(line, RSTART + RLENGTH) !~ /^[[:alnum:]]/) {
v = v "\\fB" substr(line, RSTART + 1, RLENGTH - 2) "\\fP"
} else if (match(line, /^`[^`]+`/) &&
substr(line, RSTART + RLENGTH) !~ /^[[:alnum:]]/) {
# Manual pages are usually already rendered in monospace;
# follow others, and render this in boldface.
v = v "\\fB" substr(line, RSTART + 1, RLENGTH - 2) "\\fP"
} else {
v = v substr(line, 1, 1)
line = substr(line, 2)
continue
}
line = substr(line, RSTART + RLENGTH)
}
return v
}
function flushspace() {
if (NeedSpace) {
print ".sp"
NeedSpace = 0
}
}
function inline(line) {
if (!line) {
NeedSpace = 1
return
}
flushspace()
line = format(escape(expand(line)))
# Strip empty URL descriptions, otherwise useful for demarking the end.
while (match(line, /[^[:space:]]+\[\]/)) {
line = substr(line, 1, RSTART + RLENGTH - 3) \
substr(line, RSTART + RLENGTH)
}
# Enable double-spacing after the end of a sentence.
gsub(/[.][[:space:]]+/, ".\n", line)
gsub(/[!][[:space:]]+/, "!\n", line)
gsub(/[?][[:space:]]+/, "?\n", line)
# Quote commands resulting from that, as well as from expand().
gsub(/\n[.]/, "\n\\\\\\&.", line)
gsub(/\n[']/, "\n\\\\\\&'", line)
sub(/[[:space:]]+[+]$/, "\n.br", line)
print line
}
# Returns 1 iff the left-over $0 should be processed further.
function process(firstline, posattrs, namedattrs) {
if (readattribute(firstline))
return 0
if (getline <= 0) {
inline(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))) "\""
NeedSpace = 0
return 0
}
if (length(firstline) == length($0) && /^~+$/) {
print ".SS \"" escape(expand(firstline)) "\""
NeedSpace = 0
return 0
}
if (firstline ~ /^--$/) {
flushspace()
# For now, recognize, but do not process open block delimiters.
InOpenBlock = !InOpenBlock
return 1
}
if (firstline ~ /^(-{4,}|[.]{4,})$/) {
flushspace()
print ".if n .RS 4"
print ".nf"
print ".fam C"
do {
print escape($0)
} while (getline > 0 && $0 != firstline)
print ".fam"
print ".fi"
print ".if n .RE"
return 0
}
if (firstline ~ /^\/{4,}$/) {
do {
print ".\\\" " $0
} while (getline > 0 && $0 != firstline)
return 0
}
if (match(firstline, /^\/\//)) {
print ".\\\"" substr(firstline, RSTART + RLENGTH)
return 1
}
# We generally assume these blocks end with a blank line.
if (match(firstline, /^[[:space:]]*[*][[:space:]]+/)) {
flushspace()
# Bullet magic copied over from AsciiDoc/Asciidoctor generators.
print ".RS 4"
print ".ie n \\{\\"
print "\\h'-04'\\(bu\\h'+03'\\c"
print ".\\}"
print ".el \\{\\"
print ".sp -1"
print ".IP \\(bu 2.3"
print ".\\}"
inline(substr(firstline, RSTART + RLENGTH))
while ($0) {
sub(/^[[:space:]]+/, "")
sub(/^[+]$/, "")
if (!process($0) && getline <= 0)
fatal("unexpected EOF")
if (match($0, /^[[:space:]]*[*][[:space:]]+/))
break
}
print ".RE"
NeedSpace = 1
return !!$0
}
if (match(firstline, /^[[:space:]]+/)) {
flushspace()
print ".if n .RS 4"
print ".nf"
print ".fam C"
do {
print escape(substr(firstline, RLENGTH + 1))
firstline = $0
} while ($0 && getline > 0)
print ".fam"
print ".fi"
print ".if n .RE"
return 1
}
if (match(firstline, /::$/)) {
inline(substr(firstline, 1, RSTART - 1))
while (match($0, /::$/)) {
print ".br"
inline(substr($0, 1, RSTART - 1))
if (getline <= 0)
fatal("unexpected EOF")
}
print ".RS 4"
while ($0) {
sub(/^[[:space:]]+/, "")
sub(/^[+]$/, "")
if (!process($0) && getline <= 0)
fatal("unexpected EOF")
if (match($0, /::$/))
break
}
print ".RE"
NeedSpace = 1
return !!$0
}
inline(firstline)
return 1
}
{
while (process($0)) {}
}

View File

@ -1,24 +0,0 @@
# cmake-dump.awk: dump parsed CMake scripts as tables
#
# Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
# SPDX-License-Identifier: 0BSD
#
# Parsed scripts are output in a table, with commands separated using ASCII
# Record Separators, and arguments using Unit Separators.
#
# Example usage: awk -f cmake-parser.awk -f cmake-dump.awk CMakeLists.txt \
# | sed 'y/\x1F\x1E\t\n/\t\n /' \
# | sed -n '/^project\t\([^\t]*\).*\tVERSION\t\([^\t]*\).*/{s//\1 \2/p;q;}'
function sanitize(s) {
if (s ~ /[\x1E\x1F]/)
fatal("conflicting ASCII control characters found in source")
return s
}
Command {
out = sanitize(Command)
for (i in Args)
out = out "\x1F" sanitize(Args[i])
printf "%s\x1E", out
}

View File

@ -1,252 +0,0 @@
# cmake-parser.awk: rudimentary CMake script parser
#
# Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
# SPDX-License-Identifier: 0BSD
#
# Implemented roughly according to the grammar described in cmake-language(7),
# which is self-conflicting, and not an accurate description.
#
# The result of parsing is stored in the case-normalized Command variable,
# and the Args array. These can be used by subsequent scripts.
function warning(message) {
print FILENAME ":" FNR ": warning: " message > "/dev/stderr"
}
function fatal(message) {
print FILENAME ":" FNR ": fatal error: " message > "/dev/stderr"
exit 1
}
function expect(v) {
if (!v && v == 0)
fatal("broken expectations at `" $0 "'")
return v
}
function literal(v) {
if (substr($0, 1, length(v)) != v)
return 0
$0 = substr($0, length(v) + 1)
return 1
}
function regexp(re) {
if (!match($0, "^" re))
return 0
$0 = substr($0, RLENGTH + 1)
return 1
}
function space() {
return regexp("[ \t]+")
}
function unbracket(len, v) {
do {
if (match($0, "]={" len "}]")) {
v = v substr($0, 1, RSTART - 1)
$0 = substr($0, RSTART + RLENGTH)
return v
}
v = v $0 RS
} while (getline > 0)
fatal("unterminated bracket")
}
function bracket_comment() {
if (!match($0, /^#\[=*\[/))
return 0
$0 = substr($0, RSTART + RLENGTH)
unbracket(RLENGTH - 3)
return 1
}
function line_ending() {
while (space() || bracket_comment()) {}
if (/^#/)
$0 = ""
return !$0
}
# ------------------------------------------------------------------------------
# While elementary expansion of previously set variables is implementable,
# it doesn't seem to be worth the effort.
function expand(s, v) {
v = s
while (match(v, /\\*[$](|ENV|CACHE)[{]/)) {
if (index(substr(v, RSTART), "$") % 2 != 0) {
warning("variable expansion is not supported: " s)
return s
}
v = substr(v, RSTART + RLENGTH)
}
return s
}
function escape_sequence( v) {
if (!literal("\\"))
return 0
if (literal("t")) return "\t"
if (literal("r")) return "\r"
if (literal("n")) return "\n"
# escape_semicolon isn't treated any specially here.
if (regexp("[A-Za-z0-9]"))
fatal("unsupported escape sequence")
if ($0) {
v = substr($0, 1, 1)
$0 = substr($0, 2)
return v
}
if (getline > 0)
return ""
fatal("premature end of file")
}
function quoted_argument( v, unescaped) {
if (!literal("\""))
return 0
v = ""
while (!literal("\"")) {
if (!$0) {
if (getline <= 0)
fatal("premature end of file")
v = v RS
} else if ((unescaped = escape_sequence())) {
if (unescaped == "\\" || unescaped == "$")
v = v "\\"
else if (unescaped == ";")
v = v "\\\\"
v = v unescaped
} else if (unescaped == "") {
# quoted_continuation
} else {
v = v substr($0, 1, 1)
$0 = substr($0, 2)
}
}
return v
}
function finalize_quoted(expanded, v) {
while (match(expanded, /\\./)) {
v = v substr(expanded, 1, RSTART - 1) \
substr(expanded, RSTART + 1, 1)
expanded = substr(expanded, RSTART + RLENGTH)
}
Args[++N] = v expanded
}
function unquoted_argument( v, unescaped) {
while (1) {
if (match($0, /^[^[:space:]()#"\\]+/)) {
v = v substr($0, RSTART, RLENGTH)
$0 = substr($0, RSTART + RLENGTH)
} else if ((unescaped = escape_sequence())) {
if (unescaped == "\\" || unescaped == "$" || unescaped == ";")
v = v "\\"
v = v unescaped
} else if (unescaped == "") {
fatal("unexpected backslash in an unquoted argument")
} else {
# unquoted_legacy is not supported.
return v
}
}
}
function finalize_unquoted(expanded, v) {
while (expanded) {
if (expanded ~ /^;/) {
if (v)
Args[++N] = v
v = ""
expanded = substr(expanded, 2)
} else if (expanded ~ /^\\./) {
v = v substr(expanded, 2, 1)
expanded = substr(expanded, 3)
} else {
v = v substr(expanded, 1, 1)
expanded = substr(expanded, 2)
}
}
if (v)
Args[++N] = v
}
# We keep and reprocess some escape sequences in here.
function argument( arg, expanded, v) {
if (regexp("\\[=*\\["))
Args[++N] = unbracket(RLENGTH - 2)
else if ((arg = quoted_argument()) || arg == "")
finalize_quoted(expand(arg))
else if ((arg = unquoted_argument()))
finalize_unquoted(expand(arg))
else
return 0
return 1
}
# ------------------------------------------------------------------------------
function identifier( v) {
if (!match($0, /^[A-Za-z_][A-Za-z0-9_]*/))
return 0
v = substr($0, 1, RLENGTH)
$0 = substr($0, RLENGTH + 1)
return v
}
function separation() {
if (space() || bracket_comment())
return 1
if (!line_ending())
return 0
if (getline > 0)
return 1
fatal("premature end of file")
}
function command_invocation( level) {
while (space()) {}
Command = identifier()
if (!Command)
return 0
while (space()) {}
Command = tolower(Command)
for (N in Args)
delete Args[N]
N = 0
expect(literal("("))
while (1) {
while (separation()) {}
if (literal(")")) {
if (!level--)
break
Args[++N] = ")"
continue
}
if (literal("(")) {
level++
Args[++N] = "("
continue
}
expect(argument())
if (!/^[()]/)
expect(separation())
}
return 1
}
{
command_invocation()
expect(line_ending())
}

View File

@ -1,324 +0,0 @@
# lxdrgen-c.awk: C backend for lxdrgen.awk.
#
# Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
# SPDX-License-Identifier: 0BSD
#
# Neither *_new() nor *_destroy() functions are provided, because they'd only
# be useful for top-levels, and are merely extra malloc()/free() calls.
# Users are expected to reuse buffers.
#
# Similarly, no constructors are produced--those are easy to write manually.
#
# All arrays are deserialized zero-terminated, so u8<> and i8<> can be directly
# used as C strings.
#
# All types must be able to dispose partially zero values going from the back,
# i.e., in the reverse order of deserialization.
function define_internal(name, ctype) {
Types[name] = "internal"
CodegenCType[name] = ctype
}
function define_int(shortname, ctype) {
define_internal(shortname, ctype)
CodegenSerialize[shortname] = \
"\tstr_pack_" shortname "(w, %s);\n"
CodegenDeserialize[shortname] = \
"\tif (!msg_unpacker_" shortname "(r, &%s))\n" \
"\t\treturn false;\n"
}
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", "struct str")
CodegenDispose["string"] = "\tstr_free(&%s);\n"
CodegenSerialize["string"] = \
"\tif (!proto_string_serialize(&%s, w))\n" \
"\t\treturn false;\n"
CodegenDeserialize["string"] = \
"\tif (!proto_string_deserialize(&%s, r))\n" \
"\t\treturn false;\n"
define_internal("bool", "bool")
CodegenSerialize["bool"] = \
"\tstr_pack_u8(w, !!%s);\n"
CodegenDeserialize["bool"] = \
"\t{\n" \
"\t\tuint8_t v = 0;\n" \
"\t\tif (!msg_unpacker_u8(r, &v))\n" \
"\t\t\treturn false;\n" \
"\t\t%s = !!v;\n" \
"\t}\n"
print "// Code generated from " FILENAME ". DO NOT EDIT."
print "// This file directly depends on liberty.c, but doesn't include it."
print ""
print "static bool"
print "proto_string_serialize(const struct str *s, struct str *w) {"
print "\tif (s->len > UINT32_MAX)"
print "\t\treturn false;"
print "\tstr_pack_u32(w, s->len);"
print "\tstr_append_str(w, s);"
print "\treturn true;"
print "}"
print ""
print "static bool"
print "proto_string_deserialize(struct str *s, struct msg_unpacker *r) {"
print "\tuint32_t len = 0;"
print "\tif (!msg_unpacker_u32(r, &len))"
print "\t\treturn false;"
print "\tif (msg_unpacker_get_available(r) < len)"
print "\t\treturn false;"
print "\t*s = str_make();"
print "\tstr_append_data(s, r->data + r->offset, len);"
print "\tr->offset += len;"
print "\tif (!utf8_validate (s->str, s->len))"
print "\t\treturn false;"
print "\treturn true;"
print "}"
}
function codegen_constant(name, value) {
print ""
print "enum { " PrefixUpper name " = " value " };"
}
function codegen_enum_value(name, subname, value, cg) {
append(cg, "fields",
"\t" PrefixUpper toupper(cameltosnake(name)) "_" subname \
" = " value ",\n")
}
function codegen_enum(name, cg, ctype) {
ctype = "enum " PrefixLower cameltosnake(name)
print ""
print ctype " {"
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] = "\tstr_pack_i8(w, %s);\n"
CodegenDeserialize[name] = \
"\t{\n" \
"\t\tint8_t v = 0;\n" \
"\t\tif (!msg_unpacker_i8(r, &v) || !v)\n" \
"\t\t\treturn false;\n" \
"\t\t%s = v;\n" \
"\t}\n"
CodegenCType[name] = ctype
for (i in cg)
delete cg[i]
}
function codegen_struct_tag(d, cg, f) {
f = "self->" d["name"]
append(cg, "fields", "\t" CodegenCType[d["type"]] " " d["name"] ";\n")
append(cg, "dispose", sprintf(CodegenDispose[d["type"]], f))
append(cg, "serialize", sprintf(CodegenSerialize[d["type"]], f))
# Do not deserialize here, that would be out of order.
}
function codegen_struct_field(d, cg, f, dispose, serialize, deserialize) {
f = "self->" d["name"]
dispose = CodegenDispose[d["type"]]
serialize = CodegenSerialize[d["type"]]
deserialize = CodegenDeserialize[d["type"]]
if (!d["isarray"]) {
append(cg, "fields", "\t" CodegenCType[d["type"]] " " d["name"] ";\n")
append(cg, "dispose", sprintf(dispose, f))
append(cg, "serialize", sprintf(serialize, f))
append(cg, "deserialize", sprintf(deserialize, f))
return
}
append(cg, "fields",
"\t" CodegenCType["u32"] " " d["name"] "_len;\n" \
"\t" CodegenCType[d["type"]] " *" d["name"] ";\n")
if (dispose)
append(cg, "dispose", "\tif (" f ")\n" \
"\t\tfor (size_t i = 0; i < " f "_len; i++)\n" \
indent(indent(sprintf(dispose, f "[i]"))))
append(cg, "dispose", "\tfree(" f ");\n")
append(cg, "serialize", sprintf(CodegenSerialize["u32"], f "_len"))
if (d["type"] == "u8" || d["type"] == "i8") {
append(cg, "serialize",
"\tstr_append_data(w, " f ", " f "_len);\n")
} else if (serialize) {
append(cg, "serialize",
"\tfor (size_t i = 0; i < " f "_len; i++)\n" \
indent(sprintf(serialize, f "[i]")))
}
append(cg, "deserialize", sprintf(CodegenDeserialize["u32"], f "_len") \
"\tif (!(" f " = calloc(" f "_len + 1, sizeof *" f ")))\n" \
"\t\treturn false;\n")
if (d["type"] == "u8" || d["type"] == "i8") {
append(cg, "deserialize",
"\tif (msg_unpacker_get_available(r) < " f "_len)\n" \
"\t\treturn false;\n" \
"\tmemcpy(" f ", r->data + r->offset, " f "_len);\n" \
"\tr->offset += " f "_len;\n")
} else if (deserialize) {
append(cg, "deserialize",
"\tfor (size_t i = 0; i < " f "_len; i++)\n" \
indent(sprintf(deserialize, f "[i]")))
}
}
function codegen_struct(name, cg, ctype, funcname) {
ctype = "struct " PrefixLower cameltosnake(name)
print ""
print ctype " {"
print cg["fields"] "};"
if (cg["dispose"]) {
funcname = PrefixLower cameltosnake(name) "_free"
print ""
print "static void\n" funcname "(" ctype " *self) {"
print cg["dispose"] "}"
CodegenDispose[name] = "\t" funcname "(&%s);\n"
}
if (cg["serialize"]) {
funcname = PrefixLower cameltosnake(name) "_serialize"
print ""
print "static bool\n" \
funcname "(\n\t\tconst " ctype " *self, struct str *w) {"
print cg["serialize"] "\treturn true;"
print "}"
CodegenSerialize[name] = "\tif (!" funcname "(&%s, w))\n" \
"\t\treturn false;\n"
}
if (cg["deserialize"]) {
funcname = PrefixLower cameltosnake(name) "_deserialize"
print ""
print "static bool\n" \
funcname "(\n\t\t" ctype " *self, struct msg_unpacker *r) {"
print cg["deserialize"] "\treturn true;"
print "}"
CodegenDeserialize[name] = "\tif (!" funcname "(&%s, r))\n" \
"\t\treturn false;\n"
}
CodegenCType[name] = ctype
for (i in cg)
delete cg[i]
}
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")
}
function codegen_union_struct( \
name, casename, cg, scg, structname, fieldname, fullcasename) {
# Don't generate obviously useless structs.
fullcasename = toupper(cameltosnake(cg["tagtype"])) "_" casename
if (!scg["dispose"] && !scg["deserialize"]) {
append(cg, "structless", "\tcase " PrefixUpper fullcasename ":\n")
for (i in scg)
delete scg[i]
return
}
# And thus not all generated structs are present in Types.
structname = name "_" casename
fieldname = tolower(casename)
codegen_struct(structname, scg)
append(cg, "fields", "\t" CodegenCType[structname] " " fieldname ";\n")
if (CodegenDispose[structname])
append(cg, "dispose", "\tcase " PrefixUpper fullcasename ":\n" \
indent(sprintf(CodegenDispose[structname], "self->" fieldname)) \
"\t\tbreak;\n")
# With no de/serialization code, this will simply recognize the tag.
append(cg, "serialize", "\tcase " PrefixUpper fullcasename ":\n" \
indent(sprintf(CodegenSerialize[structname], "self->" fieldname)) \
"\t\tbreak;\n")
append(cg, "deserialize", "\tcase " PrefixUpper fullcasename ":\n" \
indent(sprintf(CodegenDeserialize[structname], "self->" fieldname)) \
"\t\tbreak;\n")
}
function codegen_union(name, cg, exhaustive, f, ctype, funcname) {
ctype = "union " PrefixLower cameltosnake(name)
print ""
print ctype " {"
print cg["fields"] "};"
f = "self->" cg["tagname"]
if (cg["dispose"]) {
funcname = PrefixLower cameltosnake(name) "_free"
print ""
print "static void\n" funcname "(" ctype " *self) {"
print "\tswitch (" f ") {"
if (cg["structless"])
print cg["structless"] \
indent(sprintf(CodegenDispose[cg["tagtype"]], f)) "\t\tbreak;"
print cg["dispose"] "\tdefault:"
print "\t\tbreak;"
print "\t}"
print "}"
CodegenDispose[name] = "\t" funcname "(&%s);\n"
}
{
funcname = PrefixLower cameltosnake(name) "_serialize"
print ""
print "static bool\n" \
funcname "(\n\t\tconst " ctype " *self, struct str *w) {"
print "\tswitch (" f ") {"
if (cg["structless"])
print cg["structless"] \
indent(sprintf(CodegenSerialize[cg["tagtype"]], f)) "\t\tbreak;"
print cg["serialize"] "\tdefault:"
print "\t\treturn false;"
print "\t}"
print "\treturn true;"
print "}"
CodegenSerialize[name] = "\tif (!" funcname "(&%s, w))\n" \
"\t\treturn false;\n"
}
{
funcname = PrefixLower cameltosnake(name) "_deserialize"
print ""
print "static bool\n" \
funcname "(\n\t\t" ctype " *self, struct msg_unpacker *r) {"
print sprintf(CodegenDeserialize[cg["tagtype"]], f)
print "\tswitch (" f ") {"
if (cg["structless"])
print cg["structless"] "\t\tbreak;"
print cg["deserialize"] "\tdefault:"
print "\t\treturn false;"
print "\t}"
print "\treturn true;"
print "}"
CodegenDeserialize[name] = "\tif (!" funcname "(&%s, r))\n" \
"\t\treturn false;\n"
}
CodegenCType[name] = ctype
for (i in cg)
delete cg[i]
}

View File

@ -1,67 +0,0 @@
// 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

@ -1,47 +0,0 @@
// 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

View File

@ -1,350 +0,0 @@
# 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

@ -1,540 +0,0 @@
# lxdrgen-go.awk: Go backend for lxdrgen.awk.
#
# Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
# SPDX-License-Identifier: 0BSD
#
# This backend also enables proxying to other endpoints using JSON.
function define_internal(name, gotype) {
Types[name] = "internal"
CodegenGoType[name] = gotype
}
function define_sint(size, shortname, gotype) {
shortname = "i" size
gotype = "int" size
define_internal(shortname, gotype)
CodegenAppendJSON[shortname] = \
"\tb = strconv.AppendInt(b, int64(%s), 10)\n"
if (size == 8) {
CodegenSerialize[shortname] = "\tdata = append(data, uint8(%s))\n"
CodegenDeserialize[shortname] = \
"\tif len(data) >= 1 {\n" \
"\t\t%s, data = int8(data[0]), data[1:]\n" \
"\t} else {\n" \
"\t\treturn nil, false\n" \
"\t}\n"
return
}
CodegenSerialize[shortname] = \
"\tdata = binary.BigEndian.AppendUint" size "(data, uint" size "(%s))\n"
CodegenDeserialize[shortname] = \
"\tif len(data) >= " (size / 8) " {\n" \
"\t\t%s = " gotype "(binary.BigEndian.Uint" size "(data))\n" \
"\t\tdata = data[" (size / 8) ":]\n" \
"\t} else {\n" \
"\t\treturn nil, false\n" \
"\t}\n"
}
function define_uint(size, shortname, gotype) {
# Both []byte and []uint8 luckily marshal as base64-encoded JSON strings,
# so there's no need to rename the type as an exception.
shortname = "u" size
gotype = "uint" size
define_internal(shortname, gotype)
CodegenAppendJSON[shortname] = \
"\tb = strconv.AppendUint(b, uint64(%s), 10)\n"
if (size == 8) {
CodegenSerialize[shortname] = "\tdata = append(data, %s)\n"
CodegenDeserialize[shortname] = \
"\tif len(data) >= 1 {\n" \
"\t\t%s, data = data[0], data[1:]\n" \
"\t} else {\n" \
"\t\treturn nil, false\n" \
"\t}\n"
return
}
CodegenSerialize[shortname] = \
"\tdata = binary.BigEndian.AppendUint" size "(data, %s)\n"
CodegenDeserialize[shortname] = \
"\tif len(data) >= " (size / 8) " {\n" \
"\t\t%s = binary.BigEndian.Uint" size "(data)\n" \
"\t\tdata = data[" (size / 8) ":]\n" \
"\t} else {\n" \
"\t\treturn nil, false\n" \
"\t}\n"
}
# Currently two outputs cannot coexist within the same package.
function codegen_private(name) {
return "proto" name
}
function codegen_begin( funcname) {
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")
# Cater to "go generate", for what it's worth.
CodegenPackage = ENV["GOPACKAGE"]
if (!CodegenPackage)
CodegenPackage = "main"
print "// Code generated from " FILENAME ". DO NOT EDIT."
print ""
print "package " CodegenPackage
print ""
print "import ("
print "\t`encoding/base64`"
print "\t`encoding/binary`"
print "\t`encoding/json`"
print "\t`errors`"
print "\t`math`"
print "\t`strconv`"
print "\t`unicode/utf8`"
print ")"
print ""
print "// This is a hack to always use the base64 import."
print "var _ = base64.StdEncoding"
print ""
CodegenAppendJSON["bool"] = \
"\tb = strconv.AppendBool(b, %s)\n"
CodegenSerialize["bool"] = \
"\tif %s {\n" \
"\t\tdata = append(data, 1)\n" \
"\t} else {\n" \
"\t\tdata = append(data, 0)\n" \
"\t}\n"
funcname = codegen_private("ConsumeBoolFrom")
print "// " funcname " tries to deserialize a boolean value"
print "// from the beginning of a byte stream. When successful,"
print "// it returns a subslice with any data that might follow."
print "func " funcname "(data []byte, b *bool) ([]byte, bool) {"
print "\tif len(data) < 1 {"
print "\t\treturn nil, false"
print "\t}"
print "\tif data[0] != 0 {"
print "\t\t*b = true"
print "\t} else {"
print "\t\t*b = false"
print "\t}"
print "\treturn data[1:], true"
print "}"
print ""
CodegenDeserialize["bool"] = \
"\tif data, ok = " funcname "(data, &%s); !ok {\n" \
"\t\treturn nil, ok\n" \
"\t}\n"
funcname = codegen_private("AppendStringTo")
print "// " funcname " tries to serialize a string value,"
print "// appending it to the end of a byte stream."
print "func " funcname "(data []byte, s string) ([]byte, bool) {"
print "\tif len(s) > math.MaxUint32 {"
print "\t\treturn nil, false"
print "\t}"
print "\tdata = binary.BigEndian.AppendUint32(data, uint32(len(s)))"
print "\treturn append(data, s...), true"
print "}"
print ""
CodegenSerialize["string"] = \
"\tif data, ok = " funcname "(data, %s); !ok {\n" \
"\t\treturn nil, ok\n" \
"\t}\n"
funcname = codegen_private("ConsumeStringFrom")
print "// " funcname " tries to deserialize a string value"
print "// from the beginning of a byte stream. When successful,"
print "// it returns a subslice with any data that might follow."
print "func " funcname "(data []byte, s *string) ([]byte, bool) {"
print "\tif len(data) < 4 {"
print "\t\treturn nil, false"
print "\t}"
print "\tlength := binary.BigEndian.Uint32(data)"
print "\tif data = data[4:]; uint64(len(data)) < uint64(length) {"
print "\t\treturn nil, false"
print "\t}"
print "\t*s = string(data[:length])"
print "\tif !utf8.ValidString(*s) {"
print "\t\treturn nil, false"
print "\t}"
print "\treturn data[length:], true"
print "}"
print ""
CodegenDeserialize["string"] = \
"\tif data, ok = " funcname "(data, &%s); !ok {\n" \
"\t\treturn nil, ok\n" \
"\t}\n"
funcname = codegen_private("UnmarshalEnumJSON")
print "// " funcname " converts a JSON fragment to an integer,"
print "// ensuring that it's within the expected range of enum values."
print "func " funcname "(data []byte) (int64, error) {"
print "\tvar n int64"
print "\tif err := json.Unmarshal(data, &n); err != nil {"
print "\t\treturn 0, err"
print "\t} else if n > math.MaxInt8 || n < math.MinInt8 {"
print "\t\treturn 0, errors.New(`integer out of range`)"
print "\t} else {"
print "\t\treturn n, nil"
print "\t}"
print "}"
print ""
}
function codegen_constant(name, value) {
print "const " PrefixCamel snaketocamel(name) " = " value
print ""
}
function codegen_enum_value(name, subname, value, cg, goname) {
goname = PrefixCamel name snaketocamel(subname)
append(cg, "fields",
"\t" goname " = " value "\n")
append(cg, "stringer",
"\tcase " goname ":\n" \
"\t\treturn `" snaketocamel(subname) "`\n")
append(cg, "marshal",
goname ",\n")
append(cg, "unmarshal",
"\tcase `" snaketocamel(subname) "`:\n" \
"\t\t*v = " goname "\n")
}
function codegen_enum(name, cg, gotype, fields, funcname) {
gotype = PrefixCamel name
print "type " gotype " int8"
print ""
print "const ("
print cg["fields"] ")"
print ""
print "func (v " gotype ") String() string {"
print "\tswitch v {"
print cg["stringer"] "\tdefault:"
print "\t\treturn strconv.Itoa(int(v))"
print "\t}"
print "}"
print ""
CodegenIsMarshaler[name] = 1
fields = cg["marshal"]
sub(/,\n$/, ":", fields)
gsub(/\n/, "\n\t", fields)
print "func (v " gotype ") MarshalJSON() ([]byte, error) {"
print "\tswitch v {"
print indent("case " fields)
print "\t\treturn []byte(`\"` + v.String() + `\"`), nil"
print "\t}"
print "\treturn json.Marshal(int(v))"
print "}"
print ""
funcname = codegen_private("UnmarshalEnumJSON")
print "func (v *" gotype ") UnmarshalJSON(data []byte) error {"
print "\tvar s string"
print "\tif json.Unmarshal(data, &s) == nil {"
print "\t\t// Handled below."
print "\t} else if n, err := " funcname "(data); err != nil {"
print "\t\treturn err"
print "\t} else {"
print "\t\t*v = " gotype "(n)"
print "\t\treturn nil"
print "\t}"
print ""
print "\tswitch s {"
print cg["unmarshal"] "\tdefault:"
print "\t\treturn errors.New(`unrecognized value: ` + s)"
print "\t}"
print "\treturn nil"
print "}"
print ""
# 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] = "\tdata = append(data, uint8(%s))\n"
CodegenDeserialize[name] = \
"\tif len(data) >= 1 {\n" \
"\t\t%s, data = " gotype "(data[0]), data[1:]\n" \
"\t} else {\n" \
"\t\treturn nil, false\n" \
"\t}\n"
CodegenGoType[name] = gotype
for (i in cg)
delete cg[i]
}
function codegen_marshal(type, f, marshal) {
if (CodegenAppendJSON[type])
return sprintf(CodegenAppendJSON[type], f)
# Complex types are json.Marshalers, there's no need to json.Marshal(&f).
if (CodegenIsMarshaler[type])
marshal = f ".MarshalJSON()"
else
marshal = "json.Marshal(" f ")"
return \
"\tif j, err := " marshal "; err != nil {\n" \
"\t\treturn nil, err\n" \
"\t} else {\n" \
"\t\tb = append(b, j...)\n" \
"\t}\n"
}
function codegen_struct_field_marshal(d, cg, camel, f, marshal) {
camel = snaketocamel(d["name"])
f = "s." camel
if (!d["isarray"]) {
append(cg, "marshal",
"\tb = append(b, `,\"" decapitalize(camel) "\":`...)\n" \
codegen_marshal(d["type"], f))
return
}
# Note that we do not produce `null` for nil slices, unlike encoding/json.
# And arrays never get deserialized as such.
if (d["type"] == "u8") {
append(cg, "marshal",
"\tb = append(b, `,\"" decapitalize(camel) "\":\"`...)\n" \
"\tb = append(b, base64.StdEncoding.EncodeToString(" f ")...)\n" \
"\tb = append(b, '\"')\n")
return
}
append(cg, "marshal",
"\tb = append(b, `,\"" decapitalize(camel) "\":[`...)\n" \
"\tfor i := 0; i < len(" f "); i++ {\n" \
"\t\tif i > 0 {\n" \
"\t\t\tb = append(b, ',')\n" \
"\t\t}\n" \
indent(codegen_marshal(d["type"], f "[i]")) \
"\t}\n" \
"\tb = append(b, ']')\n")
}
function codegen_struct_field(d, cg, camel, f, serialize, deserialize) {
codegen_struct_field_marshal(d, cg)
camel = snaketocamel(d["name"])
f = "s." camel
serialize = CodegenSerialize[d["type"]]
deserialize = CodegenDeserialize[d["type"]]
if (!d["isarray"]) {
append(cg, "fields", "\t" camel " " CodegenGoType[d["type"]] \
" `json:\"" decapitalize(camel) "\"`\n")
append(cg, "serialize", sprintf(serialize, f))
append(cg, "deserialize", sprintf(deserialize, f))
return
}
append(cg, "fields", "\t" camel " []" CodegenGoType[d["type"]] \
" `json:\"" decapitalize(camel) "\"`\n")
# XXX: This should also check if it isn't out-of-range for any reason.
append(cg, "serialize",
sprintf(CodegenSerialize["u32"], "uint32(len(" f "))"))
if (d["type"] == "u8") {
append(cg, "serialize",
"\tdata = append(data, " f "...)\n")
} else {
append(cg, "serialize",
"\tfor i := 0; i < len(" f "); i++ {\n" \
indent(sprintf(serialize, f "[i]")) \
"\t}\n")
}
append(cg, "deserialize",
"\t{\n" \
"\t\tvar length uint32\n" \
indent(sprintf(CodegenDeserialize["u32"], "length")))
if (d["type"] == "u8") {
append(cg, "deserialize",
"\t\tif uint64(len(data)) < uint64(length) {\n" \
"\t\t\treturn nil, false\n" \
"\t\t}\n" \
"\t\t" f ", data = data[:length], data[length:]\n" \
"\t}\n")
} else {
append(cg, "deserialize",
"\t\t" f " = make([]" CodegenGoType[d["type"]] ", length)\n" \
"\t}\n" \
"\tfor i := 0; i < len(" f "); i++ {\n" \
indent(sprintf(deserialize, f "[i]")) \
"\t}\n")
}
}
function codegen_struct_tag(d, cg, camel, f) {
codegen_struct_field_marshal(d, cg)
camel = snaketocamel(d["name"])
f = "s." camel
append(cg, "fields", "\t" camel " " CodegenGoType[d["type"]] \
" `json:\"" decapitalize(camel) "\"`\n")
append(cg, "serialize", sprintf(CodegenSerialize[d["type"]], f))
# Do not deserialize here, that is already done by the containing union.
}
function codegen_struct(name, cg, gotype) {
gotype = PrefixCamel name
print "type " gotype " struct {\n" cg["fields"] "}\n"
if (cg["marshal"]) {
CodegenIsMarshaler[name] = 1
print "func (s *" gotype ") MarshalJSON() ([]byte, error) {"
print "\tb := []byte{}"
print cg["marshal"] "\tb[0] = '{'"
print "\treturn append(b, '}'), nil"
print "}"
print ""
}
if (cg["serialize"]) {
print "func (s *" gotype ") AppendTo(data []byte) ([]byte, bool) {"
print "\tok := true"
print cg["serialize"] "\treturn data, ok"
print "}"
print ""
CodegenSerialize[name] = \
"\tif data, ok = %s.AppendTo(data); !ok {\n" \
"\t\treturn nil, ok\n" \
"\t}\n"
}
if (cg["deserialize"]) {
print "func (s *" gotype ") ConsumeFrom(data []byte) ([]byte, bool) {"
print "\tok := true"
print cg["deserialize"] "\treturn data, ok"
print "}"
print ""
CodegenDeserialize[name] = \
"\tif data, ok = %s.ConsumeFrom(data); !ok {\n" \
"\t\treturn nil, ok\n" \
"\t}\n"
}
CodegenGoType[name] = gotype
for (i in cg)
delete cg[i]
}
function codegen_union_tag(name, d, cg) {
cg["tagtype"] = d["type"]
cg["tagname"] = snaketocamel(d["name"])
# The tag is implied from the type of struct stored in the interface.
}
function codegen_union_struct(name, casename, cg, scg, structname, init) {
# And thus not all generated structs are present in Types.
structname = name snaketocamel(casename)
codegen_struct(structname, scg)
init = CodegenGoType[structname] "{" cg["tagname"] \
": " decapitalize(cg["tagname"]) "}"
append(cg, "unmarshal",
"\tcase " CodegenGoType[cg["tagtype"]] snaketocamel(casename) ":\n" \
"\t\ts := " init "\n" \
"\t\terr = json.Unmarshal(data, &s)\n" \
"\t\tu.Interface = &s\n")
append(cg, "serialize",
"\tcase *" CodegenGoType[structname] ":\n" \
indent(sprintf(CodegenSerialize[structname], "union")))
append(cg, "deserialize",
"\tcase " CodegenGoType[cg["tagtype"]] snaketocamel(casename) ":\n" \
"\t\ts := " init "\n" \
indent(sprintf(CodegenDeserialize[structname], "s")) \
"\t\tu.Interface = &s\n")
}
function codegen_union(name, cg, exhaustive, gotype, tagvar) {
gotype = PrefixCamel name
print "type " gotype " struct {"
print "\tInterface any"
print "}"
print ""
# This cannot be a pointer method, it wouldn't work recursively.
CodegenIsMarshaler[name] = 1
print "func (u " gotype ") MarshalJSON() ([]byte, error) {"
print "\treturn u.Interface.(json.Marshaler).MarshalJSON()"
print "}"
print ""
tagvar = decapitalize(cg["tagname"])
print "func (u *" gotype ") UnmarshalJSON(data []byte) (err error) {"
print "\tvar t struct {"
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." cg["tagname"] "; " tagvar " {"
print cg["unmarshal"] "\tdefault:"
print "\t\terr = errors.New(`unsupported value: ` + " tagvar ".String())"
print "\t}"
print "\treturn err"
print "}"
print ""
# XXX: Consider changing the interface into an AppendTo/ConsumeFrom one,
# that would eliminate these type case switches entirely.
# On the other hand, it would make it possible to send unsuitable structs.
print "func (u *" gotype ") AppendTo(data []byte) ([]byte, bool) {"
print "\tok := true"
print "\tswitch union := u.Interface.(type) {"
print cg["serialize"] "\tdefault:"
print "\t\treturn nil, false"
print "\t}"
print "\treturn data, ok"
print "}"
print ""
CodegenSerialize[name] = \
"\tif data, ok = %s.AppendTo(data); !ok {\n" \
"\t\treturn nil, ok\n" \
"\t}\n"
print "func (u *" gotype ") ConsumeFrom(data []byte) ([]byte, bool) {"
print "\tok := true"
print "\tvar " tagvar " " CodegenGoType[cg["tagtype"]]
print sprintf(CodegenDeserialize[cg["tagtype"]], tagvar)
print "\tswitch " tagvar " {"
print cg["deserialize"] "\tdefault:"
print "\t\treturn nil, false"
print "\t}"
print "\treturn data, ok"
print "}"
print ""
CodegenDeserialize[name] = \
"\tif data, ok = %s.ConsumeFrom(data); !ok {\n" \
"\t\treturn nil, ok\n" \
"\t}\n"
CodegenGoType[name] = gotype
for (i in cg)
delete cg[i]
}

View File

@ -1,226 +0,0 @@
# lxdrgen-mjs.awk: Javascript backend for lxdrgen.awk.
#
# Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
# SPDX-License-Identifier: 0BSD
#
# This backend is currently for decoding the binary format only.
# (JSON is way too expensive to process and transfer.)
#
# Import the resulting script as a Javascript module.
# Identifiers intentionally aren't prefixed.
function define_internal(name) {
Types[name] = "internal"
}
function define_sint(size, shortname) {
shortname = "i" size
define_internal(shortname)
CodegenDeserialize[shortname] = "\t%s = r." shortname "()\n"
print ""
print "\t" shortname "() {"
if (size == "64") {
# XXX: 2^53 - 1 must be enough for anyone. BigInts are a PITA.
print "\t\tconst " shortname \
" = Number(this.getBigInt" size "(this.offset))"
} else {
print "\t\tconst " shortname " = this.getInt" size "(this.offset)"
}
print "\t\tthis.offset += " (size / 8)
print "\t\treturn " shortname
print "\t}"
}
function define_uint(size, shortname) {
shortname = "u" size
define_internal(shortname)
CodegenDeserialize[shortname] = "\t%s = r." shortname "()\n"
print ""
print "\t" shortname "() {"
if (size == "64") {
# XXX: 2^53 - 1 must be enough for anyone. BigInts are a PITA.
print "\t\tconst " shortname \
" = Number(this.getBigUint" size "(this.offset))"
} else {
print "\t\tconst " shortname " = this.getUint" size "(this.offset)"
}
print "\t\tthis.offset += " (size / 8)
print "\t\treturn " shortname
print "\t}"
}
function codegen_begin() {
print "// Code generated from " FILENAME ". DO NOT EDIT."
print ""
print "export class Reader extends DataView {"
print "\tconstructor() {"
print "\t\tsuper(...arguments)"
print "\t\tthis.offset = 0"
print "\t\tthis.decoder = new TextDecoder('utf-8', {fatal: true})"
print "\t}"
print ""
print "\tget empty() {"
print "\t\treturn this.byteLength <= this.offset"
print "\t}"
print ""
print "\trequire(len) {"
print "\t\tif (this.byteLength - this.offset < len)"
print "\t\t\tthrow `Premature end of data`"
print "\t\treturn this.byteOffset + this.offset"
print "\t}"
define_internal("string")
CodegenDeserialize["string"] = "\t%s = r.string()\n"
print ""
print "\tstring() {"
print "\t\tconst len = this.getUint32(this.offset)"
print "\t\tthis.offset += 4"
print "\t\tconst array = new Uint8Array("
print "\t\t\tthis.buffer, this.require(len), len)"
print "\t\tthis.offset += len"
print "\t\treturn this.decoder.decode(array)"
print "\t}"
define_internal("bool")
CodegenDeserialize["bool"] = "\t%s = r.bool()\n"
print ""
print "\tbool() {"
print "\t\tconst u8 = this.getUint8(this.offset)"
print "\t\tthis.offset += 1"
print "\t\treturn u8 != 0"
print "\t}"
define_sint("8")
define_sint("16")
define_sint("32")
define_sint("64")
define_uint("8")
define_uint("16")
define_uint("32")
define_uint("64")
print "}"
}
function codegen_constant(name, value) {
print ""
print "export const " decapitalize(snaketocamel(name)) " = " value
}
function codegen_enum_value(name, subname, value, cg) {
append(cg, "fields", "\t" snaketocamel(subname) ": " value ",\n")
}
function codegen_enum(name, cg) {
print ""
print "export const " name " = Object.freeze({"
print cg["fields"] "})"
CodegenDeserialize[name] = "\t%s = r.i8()\n"
for (i in cg)
delete cg[i]
}
function codegen_struct_field(d, cg, camel, f, deserialize) {
camel = decapitalize(snaketocamel(d["name"]))
f = "s." camel
append(cg, "fields", "\t" camel "\n")
deserialize = CodegenDeserialize[d["type"]]
if (!d["isarray"]) {
append(cg, "deserialize", sprintf(deserialize, f))
return
}
append(cg, "deserialize",
"\t{\n" \
indent(sprintf(CodegenDeserialize["u32"], "const len")))
if (d["type"] == "u8") {
append(cg, "deserialize",
"\t\t" f " = new Uint8Array(\n" \
"\t\t\tr.buffer, r.require(len), len)\n" \
"\t\tr.offset += len\n" \
"\t}\n")
return
}
if (d["type"] == "i8") {
append(cg, "deserialize",
"\t\t" f " = new Int8Array(\n" \
"\t\t\tr.buffer, r.require(len), len)\n" \
"\t\tr.offset += len\n" \
"\t}\n")
return
}
append(cg, "deserialize",
"\t\t" f " = new Array(len)\n" \
"\t}\n" \
"\tfor (let i = 0; i < " f ".length; i++)\n" \
indent(sprintf(deserialize, f "[i]")))
}
function codegen_struct_tag(d, cg) {
append(cg, "fields", "\t" decapitalize(snaketocamel(d["name"])) "\n")
# Do not deserialize here, that is already done by the containing union.
}
function codegen_struct(name, cg) {
print ""
print "export class " name " {"
print cg["fields"] cg["methods"]
print "\tstatic deserialize(r) {"
print "\t\tconst s = new " name "()"
print indent(cg["deserialize"]) "\t\treturn s"
print "\t}"
print "}"
CodegenDeserialize[name] = "\t%s = " name ".deserialize(r)\n"
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, structname) {
append(scg, "methods",
"\n" \
"\tconstructor() {\n" \
"\t\tthis." cg["tagname"] \
" = " cg["tagtype"] "." snaketocamel(casename) "\n" \
"\t}\n")
# And thus not all generated structs are present in Types.
structname = name snaketocamel(casename)
codegen_struct(structname, scg)
append(cg, "deserialize",
"\tcase " cg["tagtype"] "." snaketocamel(casename) ":\n" \
"\t{\n" \
indent(sprintf(CodegenDeserialize[structname], "const s")) \
"\t\treturn s\n" \
"\t}\n")
}
function codegen_union(name, cg, exhaustive, tagvar) {
tagvar = cg["tagname"]
print ""
print "export function deserialize" name "(r) {"
print sprintf(CodegenDeserialize[cg["tagtype"]], "const " tagvar) \
"\tswitch (" tagvar ") {"
print cg["deserialize"] "\tdefault:"
print "\t\tthrow `Unknown " cg["tagtype"] " (${tagvar})`"
print "\t}"
print "}"
CodegenDeserialize[name] = "\t%s = deserialize" name "(r)\n"
for (i in cg)
delete cg[i]
}

View File

@ -1,277 +0,0 @@
# 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,298 +0,0 @@
# lxdrgen.awk: an XDR-derived code generator for network protocols.
#
# 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 \
# -v PrefixCamel=Foo foo.lxdr > foo.{c,go,mjs} | {clang-format,gofmt,...}
# --- Utilities ----------------------------------------------------------------
function cameltosnake(s) {
while (match(s, /[[:lower:]][[:upper:]]/)) {
s = substr(s, 1, RSTART) "_" \
tolower(substr(s, RSTART + 1, RLENGTH - 1)) \
substr(s, RSTART + RLENGTH)
}
return tolower(s)
}
function snaketocamel(s) {
s = toupper(substr(s, 1, 1)) tolower(substr(s, 2))
while (match(s, /_[[:alnum:]]/)) {
s = substr(s, 1, RSTART - 1) \
toupper(substr(s, RSTART + 1, RLENGTH - 1)) \
substr(s, RSTART + RLENGTH)
}
return s
}
function decapitalize(s) {
if (match(s, /^[[:upper:]][[:lower:]]/))
return tolower(substr(s, 1, 1)) substr(s, 2)
if (match(s, /^[[:upper:]]$/))
return tolower(s)
return s
}
function indent(s) {
if (!s)
return s
gsub(/\n/, "\n\t", s)
sub(/\t*$/, "", s)
return "\t" s
}
function append(a, key, value) {
a[key] = a[key] value
}
# --- Parsing ------------------------------------------------------------------
function fatal(message) {
print "// " FILENAME ":" FNR ": fatal error: " message
print FILENAME ":" FNR ": fatal error: " message > "/dev/stderr"
exit 1
}
function skipcomment() {
do {
if (match($0, /[*]\//)) {
$0 = substr($0, RSTART + RLENGTH)
return
}
} while (getline > 0)
fatal("unterminated block comment")
}
function nexttoken() {
do {
if (match($0, /^[[:space:]]+/)) {
$0 = substr($0, RLENGTH + 1)
} else if (match($0, /^\/\/.*/)) {
$0 = ""
} else if (match($0, /^\/[*]/)) {
$0 = substr($0, RLENGTH + 1)
skipcomment()
} else if (match($0, /^[[:alpha:]][[:alnum:]_]*/)) {
Token = substr($0, 1, RLENGTH)
$0 = substr($0, RLENGTH + 1)
return Token
# AWK implementations rarely support non-decimal notations
# in their implicit string-to-number conversions.
} else if (match($0, /^(0|-?[1-9][0-9]*)/)) {
Token = substr($0, 1, RLENGTH)
$0 = substr($0, RLENGTH + 1)
return Token
} else if ($0) {
Token = substr($0, 1, 1)
$0 = substr($0, 2)
return Token
}
} while ($0 || getline > 0)
Token = ""
return Token
}
function expect(v) {
if (!v)
fatal("broken expectations at `" Token "' before `" $0 "'")
return v
}
function accept(what) {
if (Token != what)
return 0
nexttoken()
return 1
}
function identifier( v) {
if (Token !~ /^[[:alpha:]]/)
return 0
v = Token
nexttoken()
return v
}
function number( v) {
if (Token !~ /^(0|-?[1-9])/)
return 0
v = Token
nexttoken()
return v
}
function readnumber( ident) {
ident = identifier()
if (!ident)
return expect(number())
if (!(ident in Consts))
fatal("unknown constant: " ident)
return Consts[ident]
}
function defconst( ident, num) {
if (!accept("const"))
return 0
ident = expect(identifier())
expect(accept("="))
num = readnumber()
if (ident in Consts)
fatal("constant redefined: " ident)
Consts[ident] = num
codegen_constant(ident, num)
return 1
}
function readtype( ident) {
ident = deftype()
if (ident)
return ident
ident = identifier()
if (!ident)
return 0
if (!(ident in Types))
fatal("unknown type: " ident)
return ident
}
function defenum( name, ident, value, cg) {
delete cg[0]
name = expect(identifier())
expect(accept("{"))
while (!accept("}")) {
ident = expect(identifier())
value = value + 1
if (accept("="))
value = readnumber() + 0
if (!value)
fatal("enumeration values cannot be zero")
if (value < -128 || value > 127)
fatal("enumeration value out of range")
expect(accept(","))
append(EnumValues, name, SUBSEP ident)
if (EnumValues[name, ident]++)
fatal("duplicate enum value: " ident)
codegen_enum_value(name, ident, value, cg)
}
Types[name] = "enum"
codegen_enum(name, cg)
return name
}
function readfield(out, nonvoid) {
nonvoid = !accept("void")
if (nonvoid) {
out["type"] = expect(readtype())
out["name"] = expect(identifier())
# TODO: Consider supporting XDR's VLA length limits here.
# TODO: Consider supporting XDR's fixed-length syntax for string limits.
out["isarray"] = accept("<") && expect(accept(">"))
}
expect(accept(";"))
return nonvoid
}
function defstruct( name, d, cg) {
delete d[0]
delete cg[0]
name = expect(identifier())
expect(accept("{"))
while (!accept("}")) {
if (readfield(d))
codegen_struct_field(d, cg)
}
Types[name] = "struct"
codegen_struct(name, cg)
return name
}
function defunion( name, tag, tagtype, tagvalue, cg, scg, d, a, i,
unseen, exhaustive) {
delete cg[0]
delete scg[0]
delete d[0]
name = expect(identifier())
expect(accept("switch"))
expect(accept("("))
tag["type"] = tagtype = expect(readtype())
tag["name"] = expect(identifier())
expect(accept(")"))
if (Types[tagtype] != "enum")
fatal("not an enum type: " tagtype)
codegen_union_tag(name, tag, cg)
split(EnumValues[tagtype], a, SUBSEP)
for (i in a)
unseen[a[i]]++
expect(accept("{"))
while (!accept("}")) {
if (accept("case")) {
if (tagvalue)
codegen_union_struct(name, tagvalue, cg, scg)
tagvalue = expect(identifier())
expect(accept(":"))
if (!unseen[tagvalue]--)
fatal("no such value or duplicate case: " tagtype "." tagvalue)
codegen_struct_tag(tag, scg)
} else if (tagvalue) {
if (readfield(d))
codegen_struct_field(d, scg)
} else {
fatal("union fields must fall under a case")
}
}
if (tagvalue)
codegen_union_struct(name, tagvalue, cg, scg)
# 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, exhaustive)
return name
}
function deftype() {
if (accept("enum"))
return defenum()
if (accept("struct"))
return defstruct()
if (accept("union"))
return defunion()
return 0
}
{
if (PrefixCamel) {
PrefixLower = tolower(cameltosnake(PrefixCamel)) "_"
PrefixUpper = toupper(cameltosnake(PrefixCamel)) "_"
}
# This is not in a BEGIN clause (even though it consumes all input),
# so that the code generator can insert the first FILENAME.
codegen_begin()
nexttoken()
while (Token != "") {
expect(defconst() || deftype())
expect(accept(";"))
}
}