diff options
39 files changed, 539 insertions, 321 deletions
diff --git a/.editorconfig b/.editorconfig index 9540302..b7ad057 100644 --- a/.editorconfig +++ b/.editorconfig @@ -45,13 +45,13 @@ _mode = gitignore # By specific filename (non-lib9p) ############################################# -[{build-aux/lint-unknown,build-aux/embed-sources.h.gen}] +[{build-aux/embed-sources.h.gen,build-aux/valgrind}] _mode = sh -[{build-aux/lint-{bin,h,generic},build-aux/get-dscname,build-aux/valgrind,build-aux/gcov-prune,libusb/include/libusb/tusb_helpers.h.gen}] +[{build-aux/lint-{src,bin},build-aux/gcov-prune,libusb/include/libusb/tusb_helpers.h.gen}] _mode = bash -[build-aux/stack.c.gen] +[{build-aux/stack.c.gen,libmisc/wrap-cc}] _mode = python3 indent_style = space indent_size = 4 diff --git a/GNUmakefile b/GNUmakefile index 758c5aa..fa76266 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -125,33 +125,17 @@ build-aux/venv: build-aux/requirements.txt touch --no-create $@ # `lint` ############################### -# generic ########## lint: - $(MAKE) -k INNER=t $(patsubst sources_%,lint/%,$(filter sources_%,$(.VARIABLES))) # Only lint binaries if the build scripts pass lint. - $(MAKE) INNER=t lint/bin + $(MAKE) -k INNER=t lint/src + $(MAKE) INNER=t lint/bin +lint/src: $(patsubst sources_%,lint/%,$(filter-out sources_all,$(filter sources_%,$(.VARIABLES)))) lint/bin: build build-aux/lint-bin ./build-aux/lint-bin $(foreach t,$(build_types),build/rp2040-$t/cmd/sbc_harness/sbc_harness.elf) -lint/all: lint/%: build-aux/lint-generic build-aux/get-dscname - ./build-aux/lint-generic $(sources_$*) -lint/unknown: lint/%: build-aux/lint-unknown - ./build-aux/lint-unknown $(sources_$*) +lint/unknown lint/c lint/sh lint/bash lint/python3 lint/make lint/cmake lint/gitignore lint/ini lint/9p-idl lint/9p-log lint/markdown lint/pip lint/man-cat: build-aux/lint-src + ./build-aux/lint-src $(@F) $(sources_$(@F)) +lint/python3: build-aux/venv .PHONY: lint lint/% -# specific ######### -lint/c: lint/%: build-aux/lint-h build-aux/get-dscname - ./build-aux/lint-h $(filter %.h,$(sources_$*)) -lint/sh lint/bash: lint/%: - shellcheck $(sources_$*) - shfmt --diff --case-indent --simplify $(sources_$*) -lint/python3: lint/%: build-aux/venv - ./build-aux/venv/bin/mypy --strict --scripts-are-modules $(sources_$*) - ./build-aux/venv/bin/black --check $(sources_$*) - ./build-aux/venv/bin/isort --check $(sources_$*) - ./build-aux/venv/bin/pylint $(sources_$*) - ! grep -nh 'SPECIAL$$' -- lib9p/core.gen lib9p/core_gen/*.py - ./build-aux/venv/bin/pytest $(foreach f,$(sources_python3),$(if $(filter test_%.py,$(notdir $f)),$f)) -lint/make lint/cmake lint/gitignore lint/ini lint/9p-idl lint/9p-log lint/markdown lint/pip lint/man-cat: lint/%: - @: TODO: Write/adopt linters for these file types # `format` ############################# # generic ########## @@ -162,9 +146,9 @@ format: format/c: format/%: @: TODO: Adopt a C code-formatter format/sh format/bash: format/%: - shfmt --write --case-indent --simplify $(sources_$*) -format/python3: format/%: ./build-aux/venv - ./build-aux/venv/bin/black $(sources_$*) - ./build-aux/venv/bin/isort $(sources_$*) -format/make format/cmake format/gitignore format/ini format/9p-idl format/9p-log format/markdown format/pip format/man-cat: format/%: + shfmt --write --case-indent --simplify $(sources_$(@F)) +format/python3: ./build-aux/venv + ./build-aux/venv/bin/black $(sources_$(@F)) + ./build-aux/venv/bin/isort $(sources_$(@F)) +format/make format/cmake format/gitignore format/ini format/9p-idl format/9p-log format/markdown format/pip format/man-cat: @: TODO: Write/adopt formatters for these file types diff --git a/build-aux/get-dscname b/build-aux/get-dscname deleted file mode 100755 index 34a1b08..0000000 --- a/build-aux/get-dscname +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env bash -# build-aux/get-dscname - Get a file's self-described filename -# -# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> -# SPDX-License-Identifier: AGPL-3.0-or-later - -if [ $# -ne 1 ]; then - echo "$0: expected exactly 1 argument" - exit 2 -fi - -if [[ $1 == */Documentation/* ]] && [[ "$(sed 1q -- "$1")" == 'NAME' ]]; then - sed -n ' - 2{ - s,[/.],_,g; - s,^\s*_,Documentation/,; - s,$,.txt,; - - p; - q; - } - ' -- "$1" -else - sed -n ' - 1,3{ - /^\#!/d; - /^<!--$/d; - /-\*- .* -\*-/d; - s,[/*\# ]*,,; - s/ - .*//; - - p; - q; - } - ' -- "$1" -fi diff --git a/build-aux/lint-generic b/build-aux/lint-generic deleted file mode 100755 index 9bf88dd..0000000 --- a/build-aux/lint-generic +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env bash -# build-aux/lint-generic - Non-language-specific lint checks -# -# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> -# SPDX-License-Identifier: AGPL-3.0-or-later - -RED=$(tput setaf 1) -RESET=$(tput sgr0) - -err() { - printf "${RED}%s${RESET}: %s\n" "$1" "$2" >&2 - r=1 -} - -r=0 -for filename in "$@"; do - # File header ########################################################## - - shebang="$(sed -n '1{/^#!/p;}' "$filename")" - if [[ -x $filename && (-z $shebang || $shebang == '#!/hint/'*) ]]; then - err "$filename" 'is executable but does not have a shebang' - elif [[ (-n $shebang && $shebang != '#!/hint/'*) && ! -x $filename ]]; then - err "$filename" 'has a shebang but is not executable' - fi - case "$shebang" in - '') : ;; - '#!/bin/sh') : ;; - '#!/usr/bin/env bash') : ;; - '#!/usr/bin/env python3') : ;; - *) err "$filename" 'has an unrecognized shebang' ;; - esac - - if ! grep -E -q 'Copyright \(C\) 202[4-9]((-|, )202[5-9])* Luke T. Shumaker' "$filename"; then - err "$filename" 'is missing a copyright statement' - fi - if test -e .git && ! git diff --quiet milestone/2025-01-01 HEAD -- "$filename"; then - if ! grep -E -q 'Copyright \(C\) .*2025 Luke T. Shumaker' "$filename"; then - err "$filename" 'has an outdated copyright statement' - fi - fi - if ! grep -q '\sSPDX-License-Identifier[:] ' "$filename"; then - err "$filename" 'is missing an SPDX-License-Identifier' - fi - - dscname_act=$(./build-aux/get-dscname "$filename") - dscname_exp=$(echo "$filename" | sed \ - -e 's,.*/config/,,' \ - -e 's,.*/config\.h$,config.h,' \ - -e 's,.*include/,,' \ - -e 's,.*static/,,' \ - -e 's/\.wip$//') - if [ "$dscname_act" != "$dscname_exp" ] && [ "cmd/$dscname_act" != "$dscname_exp" ]; then - err "$filename" "self-identifies as $dscname_act (expected $dscname_exp)" - fi - - # File body ############################################################ - - if grep -n --color=auto "$(printf '\\S\t')" "$filename"; then - err "$filename" 'uses tabs for alignment' - fi -done -exit $r diff --git a/build-aux/lint-h b/build-aux/lint-h deleted file mode 100755 index 7459032..0000000 --- a/build-aux/lint-h +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -# build-aux/lint-h - Lint checks for C header files -# -# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> -# SPDX-License-Identifier: AGPL-3.0-or-later - -RED=$(tput setaf 1) -RESET=$(tput sgr0) - -err() { - printf "${RED}%s${RESET}: %s\n" "$1" "$2" >&2 - r=1 -} - -r=0 -for filename in "$@"; do - dscname=$(./build-aux/get-dscname "$filename") - guard=${dscname//'/'/'_'} - guard=${guard//'.'/'_'} - guard="_${guard^^}_" - if ! { grep -Fxq "#ifndef ${guard}" "$filename" && - grep -Fxq "#define ${guard}" "$filename" && - grep -Fxq "#endif /* ${guard} */" "$filename"; }; then - err "$filename" "does not have ${guard} guard" - fi -done -exit $r diff --git a/build-aux/lint-src b/build-aux/lint-src new file mode 100755 index 0000000..033340d --- /dev/null +++ b/build-aux/lint-src @@ -0,0 +1,145 @@ +#!/usr/bin/env bash +# build-aux/lint-src - Lint checks for source files +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +RED=$(tput setaf 1) +RESET=$(tput sgr0) + +err() { + printf "${RED}%s${RESET}: %s\n" "$1" "$2" >&2 + r=1 +} + +get-dscname() { + if [[ $1 == */Documentation/* ]] && [[ "$(sed 1q -- "$1")" == 'NAME' ]]; then + sed -n ' + 2{ + s,[/.],_,g; + s,^\s*_,Documentation/,; + s,$,.txt,; + + p; + q; + } + ' -- "$1" + else + sed -n ' + 1,3{ + /^\#!/d; + /^<!--$/d; + /-\*- .* -\*-/d; + s,[/*\# ]*,,; + s/ - .*//; + + p; + q; + } + ' -- "$1" + fi +} + +{ + filetype=$1 + filenames=("${@:2}") + + r=0 + for filename in "${filenames[@]}"; do + # File header ########################################################## + + shebang="$(sed -n '1{/^#!/p;}' "$filename")" + if [[ -x $filename && (-z $shebang || $shebang == '#!/hint/'*) ]]; then + err "$filename" 'is executable but does not have a shebang' + elif [[ (-n $shebang && $shebang != '#!/hint/'*) && ! -x $filename ]]; then + err "$filename" 'has a shebang but is not executable' + fi + case "$shebang" in + '') : ;; + '#!/bin/sh') : ;; + '#!/usr/bin/env bash') : ;; + '#!/usr/bin/env python3') : ;; + *) err "$filename" 'has an unrecognized shebang' ;; + esac + if [[ -n $shebang && $shebang != */"$filetype" && $shebang != *' '"$filetype" ]]; then + err "$filename" "wrong shebang for $filetype" + fi + + if ! grep -E -q 'Copyright \(C\) 202[4-9]((-|, )202[5-9])* Luke T. Shumaker' "$filename"; then + err "$filename" 'is missing a copyright statement' + fi + if test -e .git && ! git diff --quiet milestone/2025-01-01 HEAD -- "$filename"; then + if ! grep -E -q 'Copyright \(C\) .*2025 Luke T. Shumaker' "$filename"; then + err "$filename" 'has an outdated copyright statement' + fi + fi + if ! grep -q '\sSPDX-License-Identifier[:] ' "$filename"; then + err "$filename" 'is missing an SPDX-License-Identifier' + fi + + dscname_act=$(get-dscname "$filename") + dscname_exp=$(echo "$filename" | sed \ + -e 's,.*/config/,,' \ + -e 's,.*/config\.h$,config.h,' \ + -e 's,.*include/,,' \ + -e 's,.*static/,,' \ + -e 's/\.wip$//') + if [ "$dscname_act" != "$dscname_exp" ] && [ "cmd/$dscname_act" != "$dscname_exp" ]; then + err "$filename" "self-identifies as $dscname_act (expected $dscname_exp)" + fi + + # File body ############################################################ + + if grep -n --color=auto "$(printf '\\S\t')" "$filename"; then + err "$filename" 'uses tabs for alignment' + fi + done + case "$filetype" in + unknown) + for filename in "${filenames[@]}"; do + err "$filename" 'cannot lint unknown file type' + done + ;; + c) + for filename in "${filenames[@]}"; do + if [[ $filename == *.h ]]; then + dscname=$(get-dscname "$filename") + guard=${dscname//'/'/'_'} + guard=${guard//'.'/'_'} + guard="_${guard^^}_" + if ! { grep -Fxq "#ifndef ${guard}" "$filename" && + grep -Fxq "#define ${guard}" "$filename" && + grep -Fxq "#endif /* ${guard} */" "$filename"; }; then + err "$filename" "does not have ${guard} guard" + fi + fi + done + ;; + sh | bash) + shellcheck "${filenames[@]}" || exit $? + shfmt --diff --case-indent --simplify "${filenames[@]}" || exit $? + ;; + python3) + ./build-aux/venv/bin/mypy --strict --scripts-are-modules "${filenames[@]}" || exit $? + ./build-aux/venv/bin/black --check "${filenames[@]}" || exit $? + ./build-aux/venv/bin/isort --check "${filenames[@]}" || exit $? + ./build-aux/venv/bin/pylint "${filenames[@]}" || exit $? + if grep -nh 'SPECIAL$$' -- lib9p/core.gen lib9p/core_gen/*.py; then exit 1; fi + testfiles=() + for filename in "${filenames[@]}"; do + if [[ ${filename##*/} == test_*.py ]]; then + testfiles+=("$filename") + fi + done + ./build-aux/venv/bin/pytest "${testfiles[@]}" || exit $? + ;; + make | cmake | gitignore | ini | 9p-idl | 9p-log | markdown | pip | man-cat) + # TODO: Write/adopt linters for these file types + : + ;; + *) + err "$0" "unknown filetype: ${filetype}" + ;; + esac + exit $r +} diff --git a/build-aux/lint-unknown b/build-aux/lint-unknown deleted file mode 100755 index bc23a81..0000000 --- a/build-aux/lint-unknown +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh -# build-aux/lint-unknown - Lint checks for unknown files -# -# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> -# SPDX-License-Identifier: AGPL-3.0-or-later - -RED=$(tput setaf 1) -RESET=$(tput sgr0) - -err() { - printf "${RED}%s${RESET}: %s\n" "$1" "$2" >&2 - r=1 -} - -r=0 -for filename in "$@"; do - err "$filename" 'cannot lint unknown file type' -done -exit $r diff --git a/build-aux/valgrind b/build-aux/valgrind index 8fe7c6e..0700e4d 100755 --- a/build-aux/valgrind +++ b/build-aux/valgrind @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/bin/sh # build-aux/valgrind - Wrapper around valgrind to keep flags consistent # # Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> diff --git a/cmd/sbc_harness/fs_harness_flash_bin.c b/cmd/sbc_harness/fs_harness_flash_bin.c index 5e6f49c..8bd144e 100644 --- a/cmd/sbc_harness/fs_harness_flash_bin.c +++ b/cmd/sbc_harness/fs_harness_flash_bin.c @@ -201,7 +201,7 @@ static lo_interface lib9p_srv_fio flash_file_fopen(struct flash_file *self, stru self->wbuf.ok = false; } - return lo_box_flash_file_as_lib9p_srv_fio(self); + return LO_BOX(lib9p_srv_fio, self); } /* srv_fio ********************************************************************/ diff --git a/cmd/sbc_harness/fs_harness_flash_bin.h b/cmd/sbc_harness/fs_harness_flash_bin.h index 36382be..148a446 100644 --- a/cmd/sbc_harness/fs_harness_flash_bin.h +++ b/cmd/sbc_harness/fs_harness_flash_bin.h @@ -25,6 +25,5 @@ struct flash_file { END_PRIVATE(FS_HARNESS_FLASH_BIN); }; LO_IMPLEMENTATION_H(lib9p_srv_file, struct flash_file, flash_file); -#define lo_box_flash_file_as_lib9p_srv_file(obj) util9p_box(flash_file, obj) #endif /* _SBC_HARNESS_FS_HARNESS_FLASH_BIN_H_ */ diff --git a/cmd/sbc_harness/fs_harness_uptime_txt.c b/cmd/sbc_harness/fs_harness_uptime_txt.c index 339b410..23ac9d4 100644 --- a/cmd/sbc_harness/fs_harness_uptime_txt.c +++ b/cmd/sbc_harness/fs_harness_uptime_txt.c @@ -89,7 +89,7 @@ static lo_interface lib9p_srv_fio uptime_file_fopen(struct uptime_file *self, st ret->parent = self; ret->buf_len = 0; - return lo_box_uptime_fio_as_lib9p_srv_fio(ret); + return LO_BOX(lib9p_srv_fio, ret); } /* srv_fio ********************************************************************/ diff --git a/cmd/sbc_harness/fs_harness_uptime_txt.h b/cmd/sbc_harness/fs_harness_uptime_txt.h index 7bf2945..c575580 100644 --- a/cmd/sbc_harness/fs_harness_uptime_txt.h +++ b/cmd/sbc_harness/fs_harness_uptime_txt.h @@ -14,6 +14,5 @@ struct uptime_file { uint64_t pathnum; }; LO_IMPLEMENTATION_H(lib9p_srv_file, struct uptime_file, uptime_file); -#define lo_box_uptime_file_as_lib9p_srv_file(obj) util9p_box(uptime_file, obj) #endif /* _SBC_HARNESS_FS_HARNESS_UPTIME_TXT_H_ */ diff --git a/cmd/sbc_harness/main.c b/cmd/sbc_harness/main.c index 71c39b2..d32b775 100644 --- a/cmd/sbc_harness/main.c +++ b/cmd/sbc_harness/main.c @@ -54,11 +54,11 @@ enum { PATH_BASE = __COUNTER__ }; #define STATIC_FILE(STRNAME, ...) UTIL9P_STATIC_FILE(PATH_COUNTER, STRNAME, __VA_ARGS__) #define STATIC_DIR(STRNAME, ...) UTIL9P_STATIC_DIR(PATH_COUNTER, STRNAME, __VA_ARGS__) -#define API_FILE(STRNAME, SYMNAME, ...) \ - lo_box_##SYMNAME##_file_as_lib9p_srv_file(&((struct SYMNAME##_file){ \ - .name = STRNAME, \ - .pathnum = PATH_COUNTER \ - __VA_OPT__(,) __VA_ARGS__ \ +#define API_FILE(STRNAME, SYMNAME, ...) \ + LO_BOX(lib9p_srv_file, &((struct SYMNAME##_file){ \ + .name = STRNAME, \ + .pathnum = PATH_COUNTER \ + __VA_OPT__(,) __VA_ARGS__ \ })) static struct lib9p_srv_file root = @@ -149,7 +149,7 @@ struct { static COROUTINE dhcp_cr(void *) { cr_begin(); - dhcp_client_main(lo_box_w5500_if_as_net_iface(&globals.dev_w5500), "harness"); + dhcp_client_main(LO_BOX(net_iface, &globals.dev_w5500), "harness"); cr_end(); } @@ -157,7 +157,7 @@ static COROUTINE dhcp_cr(void *) { static COROUTINE read9p_cr(void *) { cr_begin(); - lo_interface net_iface iface = lo_box_w5500_if_as_net_iface(&globals.dev_w5500); + lo_interface net_iface iface = LO_BOX(net_iface, &globals.dev_w5500); lo_interface net_stream_listener listener = LO_CALL(iface, tcp_listen, LIB9P_DEFAULT_PORT_9FS); lib9p_srv_accept_and_read_loop(&globals.srv, listener); @@ -204,7 +204,7 @@ COROUTINE init_cr(void *) { 17, /* PIN_CS */ 0, 1, 2, 3); /* DMA channels */ w5500_init(&globals.dev_w5500, "W5500", - lo_box_rp2040_hwspi_as_spi(&globals.dev_spi), + LO_BOX(spi, &globals.dev_spi), 21, /* PIN_INTR */ 20, /* PIN_RESET */ ((struct net_eth_addr){{ diff --git a/lib9p/core_gen/c_validate.py b/lib9p/core_gen/c_validate.py index e7a4017..8997237 100644 --- a/lib9p/core_gen/c_validate.py +++ b/lib9p/core_gen/c_validate.py @@ -67,7 +67,7 @@ def gen_c_validate(versions: set[str], typs: list[idl.UserType]) -> str: "\t\tsize_t len = n;\n" "\t\tVALIDATE_NET_BYTES(len);\n" "\t\tif (!utf8_is_valid_without_nul(&net_bytes[net_offset-len], len))\n" - f'\t\t\treturn lib9p_error(ctx, {c9util.IDENT("ERRNO_L_EBADMSG")}, "message contains invalid UTF-8");\n' + f'\t\t\treturn lib9p_error(ctx, {c9util.IDENT("ERRNO_L_EILSEQ")}, "message contains invalid UTF-8");\n' "\t}\n" ) ret += cutil.macro( diff --git a/lib9p/core_generated.c b/lib9p/core_generated.c index 6e3633f..ad7b210 100644 --- a/lib9p/core_generated.c +++ b/lib9p/core_generated.c @@ -229,12 +229,12 @@ static const lib9p_lock_flags_t lock_flags_masks[LIB9P_VER_NUM] = { return lib9p_error(ctx, LIB9P_ERRNO_L_EBADMSG, "message is too short for content"); \ if (net_offset > net_size) \ return lib9p_error(ctx, LIB9P_ERRNO_L_EBADMSG, "message is too short for content (", net_offset, " > ", net_size, ")"); -#define VALIDATE_NET_UTF8(n) \ - { \ - size_t len = n; \ - VALIDATE_NET_BYTES(len); \ - if (!utf8_is_valid_without_nul(&net_bytes[net_offset-len], len)) \ - return lib9p_error(ctx, LIB9P_ERRNO_L_EBADMSG, "message contains invalid UTF-8"); \ +#define VALIDATE_NET_UTF8(n) \ + { \ + size_t len = n; \ + VALIDATE_NET_BYTES(len); \ + if (!utf8_is_valid_without_nul(&net_bytes[net_offset-len], len)) \ + return lib9p_error(ctx, LIB9P_ERRNO_L_EILSEQ, "message contains invalid UTF-8"); \ } #define RESERVE_HOST_BYTES(n) \ if (__builtin_add_overflow(host_size, n, &host_size)) \ diff --git a/lib9p/core_include/lib9p/core.h b/lib9p/core_include/lib9p/core.h index 4941220..1fb71f1 100644 --- a/lib9p/core_include/lib9p/core.h +++ b/lib9p/core_include/lib9p/core.h @@ -66,19 +66,19 @@ bool lib9p_ctx_has_error(struct lib9p_ctx *ctx); #endif /** Write a <libmisc/fmt.h>-style error into ctx, return -1. */ -#define lib9p_error(ctx, linux_errno, ...) ({ \ - if (!lib9p_ctx_has_error(ctx)) { \ - _lib9p_set_err_num(ctx, linux_errno); \ - struct fmt_buf _w = { \ - .dat = (ctx)->err_msg, \ - .cap = sizeof((ctx)->err_msg), \ - }; \ - lo_interface fmt_dest w = lo_box_fmt_buf_as_fmt_dest(&_w); \ - fmt_print(w, __VA_ARGS__); \ - if (_w.len < _w.cap) \ - memset(_w.dat + _w.len, 0, _w.cap - _w.len); \ - } \ - -1; \ +#define lib9p_error(ctx, linux_errno, ...) ({ \ + if (!lib9p_ctx_has_error(ctx)) { \ + _lib9p_set_err_num(ctx, linux_errno); \ + struct fmt_buf _w = { \ + .dat = (ctx)->err_msg, \ + .cap = sizeof((ctx)->err_msg), \ + }; \ + lo_interface fmt_dest w = LO_BOX(fmt_dest, &_w); \ + fmt_print(w, __VA_ARGS__); \ + if (_w.len < _w.cap) \ + memset(_w.dat + _w.len, 0, _w.cap - _w.len); \ + } \ + -1; \ }) /* misc utilities *************************************************************/ @@ -98,11 +98,7 @@ void fmt_print_lib9p_msg(lo_interface fmt_dest w, struct lib9p_ctx *ctx, enum li * number may be larger than net_bytes due to (1) struct padding, (2) * array pointers. * - * Emits an error (return -1, set ctx->err_num and ctx->err_msg) if - * either the message type is unknown, or if net_bytes is too short - * for that message type, or if an invalid string (invalid UTF-8, - * contains a nul-byte) is encountered. - * + * @param ctx : negotiated protocol parameters, where to record errors * @param net_bytes : the complete request, starting with the "size[4]" * * @return required size, or -1 on error @@ -110,7 +106,7 @@ void fmt_print_lib9p_msg(lo_interface fmt_dest w, struct lib9p_ctx *ctx, enum li * @errno L_EOPNOTSUPP: message is an R-message * @errno L_EOPNOTSUPP: message has unknown type * @errno L_EBADMSG: message is wrong size for content - * @errno L_EBADMSG: message contains invalid UTF-8 + * @errno L_EILSEQ: message contains invalid UTF-8, or the UTF-8 contains a nul-byte * @errno L_EBADMSG: message contains a bitfield with unknown bits * @errno L_EMSGSIZE: would-be return value overflows SSIZE_MAX */ @@ -142,9 +138,9 @@ void lib9p_Tmsg_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes, * * @param ctx : negotiated protocol parameters, where to record errors * @param typ : the message type - * @param msg : the message to encode + * @param msg : the message to encode (`struct lib9p_msg_XXXX` according to `typ`) * - * @return ret_bytes : the buffer to encode to, must be at be at least ctx->max_msg_size bytes + * @return ret : the buffer to encode to * @return whether there was an error (false=success, true=error) * * @errno L_ERANGE: reply does not fit in ctx->max_msg_size diff --git a/lib9p/srv_include/lib9p/srv.h b/lib9p/srv_include/lib9p/srv.h index eb87d6f..89dc986 100644 --- a/lib9p/srv_include/lib9p/srv.h +++ b/lib9p/srv_include/lib9p/srv.h @@ -185,7 +185,7 @@ LO_INTERFACE(lib9p_srv_fio); /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/ LO_FUNC(void , iofree ) \ /** \ * Return the idx-th dirent. idx will always be either 0 or \ - * prev_idx+1. A dirrent with an empty name signals EOF. The string \ + * prev_idx+1. A dirent with an empty name signals EOF. The string \ * must remain valid until the next dread() call or iofree(). \ */ \ LO_FUNC(struct lib9p_srv_dirent , dread , struct lib9p_srv_ctx *, \ diff --git a/lib9p/tests/test_server/fs_flush.c b/lib9p/tests/test_server/fs_flush.c index e6408d7..fade0a1 100644 --- a/lib9p/tests/test_server/fs_flush.c +++ b/lib9p/tests/test_server/fs_flush.c @@ -67,7 +67,7 @@ static lo_interface lib9p_srv_fio flush_file_fopen(struct flush_file *self, stru struct flush_fio *ret = heap_alloc(1, struct flush_fio); ret->parent = self; - return lo_box_flush_fio_as_lib9p_srv_fio(ret); + return LO_BOX(lib9p_srv_fio, ret); } /* srv_fio ********************************************************************/ diff --git a/lib9p/tests/test_server/fs_flush.h b/lib9p/tests/test_server/fs_flush.h index 2b08850..023434b 100644 --- a/lib9p/tests/test_server/fs_flush.h +++ b/lib9p/tests/test_server/fs_flush.h @@ -22,6 +22,5 @@ struct flush_file { } flush_behavior; }; LO_IMPLEMENTATION_H(lib9p_srv_file, struct flush_file, flush_file); -#define lo_box_flush_file_as_lib9p_srv_file(obj) util9p_box(flush_file, obj) #endif /* _LIB9P_TESTS_TEST_SERVER_FS_FLUSH_H_ */ diff --git a/lib9p/tests/test_server/fs_shutdown.c b/lib9p/tests/test_server/fs_shutdown.c index d4ae67e..0dd473d 100644 --- a/lib9p/tests/test_server/fs_shutdown.c +++ b/lib9p/tests/test_server/fs_shutdown.c @@ -66,7 +66,7 @@ static lo_interface lib9p_srv_fio shutdown_file_fopen(struct shutdown_file *self struct shutdown_fio *ret = heap_alloc(1, struct shutdown_fio); ret->parent = self; - return lo_box_shutdown_fio_as_lib9p_srv_fio(ret); + return LO_BOX(lib9p_srv_fio, ret); } /* srv_fio ********************************************************************/ @@ -94,7 +94,7 @@ static uint32_t shutdown_fio_pwrite(struct shutdown_fio *self, struct lib9p_srv_ if (byte_count == 0) return 0; for (size_t i = 0; i < self->parent->nlisteners; i++) - LO_CALL(lo_box_hostnet_tcplist_as_net_stream_listener(&self->parent->listeners[i]), close); + LO_CALL(LO_BOX(net_stream_listener, &self->parent->listeners[i]), close); return byte_count; } static void shutdown_fio_pread(struct shutdown_fio *LM_UNUSED(self), struct lib9p_srv_ctx *LM_UNUSED(ctx), diff --git a/lib9p/tests/test_server/fs_shutdown.h b/lib9p/tests/test_server/fs_shutdown.h index 6b8683c..7b8d327 100644 --- a/lib9p/tests/test_server/fs_shutdown.h +++ b/lib9p/tests/test_server/fs_shutdown.h @@ -18,6 +18,5 @@ struct shutdown_file { size_t nlisteners; }; LO_IMPLEMENTATION_H(lib9p_srv_file, struct shutdown_file, shutdown_file); -#define lo_box_shutdown_file_as_lib9p_srv_file(obj) util9p_box(shutdown_file, obj) #endif /* _LIB9P_TESTS_TEST_SERVER_FS_SHUTDOWN_H_ */ diff --git a/lib9p/tests/test_server/fs_whoami.c b/lib9p/tests/test_server/fs_whoami.c index 7e1d635..6cc46ac 100644 --- a/lib9p/tests/test_server/fs_whoami.c +++ b/lib9p/tests/test_server/fs_whoami.c @@ -92,7 +92,7 @@ static lo_interface lib9p_srv_fio whoami_file_fopen(struct whoami_file *self, st ret->buf_len = 0; ret->buf = NULL; - return lo_box_whoami_fio_as_lib9p_srv_fio(ret); + return LO_BOX(lib9p_srv_fio, ret); } /* srv_fio ********************************************************************/ diff --git a/lib9p/tests/test_server/fs_whoami.h b/lib9p/tests/test_server/fs_whoami.h index 5e1aee9..518e11d 100644 --- a/lib9p/tests/test_server/fs_whoami.h +++ b/lib9p/tests/test_server/fs_whoami.h @@ -15,6 +15,5 @@ struct whoami_file { uint64_t pathnum; }; LO_IMPLEMENTATION_H(lib9p_srv_file, struct whoami_file, whoami_file); -#define lo_box_whoami_file_as_lib9p_srv_file(obj) util9p_box(whoami_file, obj) #endif /* _LIB9P_TESTS_TEST_SERVER_FS_WHOAMI_H_ */ diff --git a/lib9p/tests/test_server/main.c b/lib9p/tests/test_server/main.c index f2a73bf..052d180 100644 --- a/lib9p/tests/test_server/main.c +++ b/lib9p/tests/test_server/main.c @@ -60,11 +60,11 @@ static struct { #define STATIC_DIR(N, STRNAME, ...) \ UTIL9P_STATIC_DIR(N, STRNAME, __VA_ARGS__) -#define API_FILE(N, STRNAME, SYMNAME, ...) \ - lo_box_##SYMNAME##_file_as_lib9p_srv_file(&((struct SYMNAME##_file){ \ - .name = STRNAME, \ - .pathnum = N \ - __VA_OPT__(,) __VA_ARGS__ \ +#define API_FILE(N, STRNAME, SYMNAME, ...) \ + LO_BOX(lib9p_srv_file, &((struct SYMNAME##_file){ \ + .name = STRNAME, \ + .pathnum = N \ + __VA_OPT__(,) __VA_ARGS__ \ })) static struct lib9p_srv_file root = @@ -96,7 +96,7 @@ static COROUTINE read_cr(void *_i) { hostnet_tcp_listener_init(&globals.listeners[i], globals.port); - lib9p_srv_accept_and_read_loop(&globals.srv, lo_box_hostnet_tcplist_as_net_stream_listener(&globals.listeners[i])); + lib9p_srv_accept_and_read_loop(&globals.srv, LO_BOX(net_stream_listener, &globals.listeners[i])); cr_end(); } @@ -164,7 +164,7 @@ int main(int argc, char *argv[]) { struct hostclock clock_monotonic = { .clock_id = CLOCK_MONOTONIC, }; - bootclock = lo_box_hostclock_as_alarmclock(&clock_monotonic); + bootclock = LO_BOX(alarmclock, &clock_monotonic); coroutine_add("init", init_cr, NULL); coroutine_main(); fclose(globals.logstream); diff --git a/lib9p_util/include/util9p/static.h b/lib9p_util/include/util9p/static.h index 5454c24..c204607 100644 --- a/lib9p_util/include/util9p/static.h +++ b/lib9p_util/include/util9p/static.h @@ -9,12 +9,6 @@ #include <lib9p/srv.h> -#define util9p_box(nam, obj) \ - ((struct lib9p_srv_file){ \ - .self = obj, \ - .vtable = (void*)&_lo_##nam##_lib9p_srv_file_vtable, \ - }) - #define UTIL9P_ATIME 1728337905 #define UTIL9P_MTIME 1728337904 @@ -56,12 +50,11 @@ struct util9p_static_dir { lo_interface lib9p_srv_file members[]; }; LO_IMPLEMENTATION_H(lib9p_srv_file, struct util9p_static_dir, util9p_static_dir); -#define lo_box_util9p_static_dir_as_lib9p_srv_file(obj) util9p_box(util9p_static_dir, obj) -#define UTIL9P_STATIC_DIR(PATH, STRNAME, ...) \ - lo_box_util9p_static_dir_as_lib9p_srv_file(&((struct util9p_static_dir){ \ - .c = UTIL9P_STATIC_COMMON(PATH, STRNAME, 0555), \ - .members = { __VA_ARGS__ LO_NULL(lib9p_srv_file) }, \ +#define UTIL9P_STATIC_DIR(PATH, STRNAME, ...) \ + LO_BOX(lib9p_srv_file, &((struct util9p_static_dir){ \ + .c = UTIL9P_STATIC_COMMON(PATH, STRNAME, 0555), \ + .members = { __VA_ARGS__ LO_NULL(lib9p_srv_file) }, \ })) /* File ***********************************************************************/ @@ -74,12 +67,11 @@ struct util9p_static_file { size_t data_size; /* only used if .data_end==NULL */ }; LO_IMPLEMENTATION_H(lib9p_srv_file, struct util9p_static_file, util9p_static_file); -#define lo_box_util9p_static_file_as_lib9p_srv_file(obj) util9p_box(util9p_static_file, obj) -#define UTIL9P_STATIC_FILE(PATH, STRNAME, ...) \ - lo_box_util9p_static_file_as_lib9p_srv_file(&((struct util9p_static_file){ \ - .c = UTIL9P_STATIC_COMMON(PATH, STRNAME, 0444), \ - __VA_ARGS__ \ +#define UTIL9P_STATIC_FILE(PATH, STRNAME, ...) \ + LO_BOX(lib9p_srv_file, &((struct util9p_static_file){ \ + .c = UTIL9P_STATIC_COMMON(PATH, STRNAME, 0444), \ + __VA_ARGS__ \ })) #endif /* _UTIL9P_STATIC_H_ */ diff --git a/lib9p_util/static.c b/lib9p_util/static.c index c35d28c..40810b5 100644 --- a/lib9p_util/static.c +++ b/lib9p_util/static.c @@ -100,7 +100,7 @@ LIB9P_SRV_NOTFILE(struct util9p_static_dir, util9p_static_dir); static lo_interface lib9p_srv_dio util9p_static_dir_dopen(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx) { assert(self); assert(ctx); - return lo_box_util9p_static_dir_as_lib9p_srv_dio(self); + return LO_BOX(lib9p_srv_dio, self); } static void util9p_static_dir_iofree(struct util9p_static_dir *self) { assert(self); @@ -191,7 +191,7 @@ static lo_interface lib9p_srv_fio util9p_static_file_fopen(struct util9p_static_ assert(rd); assert(!wr); assert(!trunc); - return lo_box_util9p_static_file_as_lib9p_srv_fio(self); + return LO_BOX(lib9p_srv_fio, self); } static void util9p_static_file_iofree(struct util9p_static_file *self) { assert(self); diff --git a/libdhcp/tests/test_client.c b/libdhcp/tests/test_client.c index 24b3af6..09557e5 100644 --- a/libdhcp/tests/test_client.c +++ b/libdhcp/tests/test_client.c @@ -99,7 +99,7 @@ static lo_interface net_packet_conn test_udp_conn(struct test_iface *self, uint1 test_assert(local_port == 68); test_assert(!once); once = true; - return lo_box_test_udp_as_net_packet_conn(&self->conn); + return LO_BOX(net_packet_conn, &self->conn); } /******************************************************************************/ @@ -107,7 +107,7 @@ static lo_interface net_packet_conn test_udp_conn(struct test_iface *self, uint1 COROUTINE dhcp_cr(void *) { cr_begin(); struct test_iface iface = {}; - dhcp_client_main(lo_box_test_as_net_iface(&iface), "test-client"); + dhcp_client_main(LO_BOX(net_iface, &iface), "test-client"); cr_end(); } @@ -115,7 +115,7 @@ int main() { struct hostclock clock_monotonic = { .clock_id = CLOCK_MONOTONIC, }; - bootclock = lo_box_hostclock_as_alarmclock(&clock_monotonic); + bootclock = LO_BOX(alarmclock, &clock_monotonic); coroutine_add("dhcp", dhcp_cr, NULL); coroutine_main(); diff --git a/libhw_cr/host_net.c b/libhw_cr/host_net.c index 8016787..c8d4472 100644 --- a/libhw_cr/host_net.c +++ b/libhw_cr/host_net.c @@ -187,7 +187,7 @@ static lo_interface net_stream_conn hostnet_tcplist_accept(struct hostnet_tcp_li listener->active_conn.fd = ret_connfd; listener->active_conn.read_deadline_ns = 0; - return lo_box_hostnet_tcp_as_net_stream_conn(&listener->active_conn); + return LO_BOX(net_stream_conn, &listener->active_conn); } /* TCP listener close() *******************************************************/ diff --git a/libhw_cr/rp2040_hwtimer.c b/libhw_cr/rp2040_hwtimer.c index 8227abb..d9f0a24 100644 --- a/libhw_cr/rp2040_hwtimer.c +++ b/libhw_cr/rp2040_hwtimer.c @@ -44,7 +44,7 @@ static_assert(sizeof(hwtimers)/sizeof(hwtimers[0]) == _RP2040_HWALARM_NUM); lo_interface alarmclock rp2040_hwtimer(enum rp2040_hwalarm_instance alarm_num) { assert(alarm_num < _RP2040_HWALARM_NUM); - return lo_box_rp2040_hwtimer_as_alarmclock(&hwtimers[alarm_num]); + return LO_BOX(alarmclock, &hwtimers[alarm_num]); } diff --git a/libhw_cr/w5500.c b/libhw_cr/w5500.c index c318819..58715c9 100644 --- a/libhw_cr/w5500.c +++ b/libhw_cr/w5500.c @@ -459,7 +459,7 @@ static lo_interface net_stream_listener w5500_if_tcp_listen(struct w5500 *chip, sock->read_deadline_ns = 0; sock->list_open = true; - return lo_box_w5500_tcplist_as_net_stream_listener(sock); + return LO_BOX(net_stream_listener, sock); } static lo_interface net_stream_conn w5500_if_tcp_dial(struct w5500 *chip, @@ -509,7 +509,7 @@ static lo_interface net_stream_conn w5500_if_tcp_dial(struct w5500 *chip, cr_yield(); break; case STATE_TCP_ESTABLISHED: - return lo_box_w5500_tcp_as_net_stream_conn(socket); + return LO_BOX(net_stream_conn, socket); default: goto restart; } @@ -545,7 +545,7 @@ static lo_interface net_packet_conn w5500_if_udp_conn(struct w5500 *chip, uint16 cr_yield(); cr_mutex_unlock(&chip->mu); - return lo_box_w5500_udp_as_net_packet_conn(socket); + return LO_BOX(net_packet_conn, socket); } static bool w5500_if_arp_ping(struct w5500 *chip, struct net_ip4_addr addr) { @@ -598,10 +598,10 @@ static lo_interface net_stream_conn w5500_tcplist_accept(struct _w5500_socket *s break; case STATE_TCP_ESTABLISHED: socket->read_open = true; - /* fall-through */ + [[fallthrough]]; case STATE_TCP_CLOSE_WAIT: socket->write_open = true; - return lo_box_w5500_tcp_as_net_stream_conn(socket); + return LO_BOX(net_stream_conn, socket); default: goto restart; } diff --git a/libmisc/CMakeLists.txt b/libmisc/CMakeLists.txt index c6405ad..07f154b 100644 --- a/libmisc/CMakeLists.txt +++ b/libmisc/CMakeLists.txt @@ -18,6 +18,11 @@ target_sources(libmisc INTERFACE utf8.c ) +target_compile_options(libmisc INTERFACE + -no-integrated-cpp + -wrapper "${CMAKE_CURRENT_SOURCE_DIR}/wrap-cc" +) + add_lib_test(libmisc test_assert) add_lib_test(libmisc test_assert_min) add_lib_test(libmisc test_endian) diff --git a/libmisc/include/libmisc/fmt.h b/libmisc/include/libmisc/fmt.h index c0743ff..135e48b 100644 --- a/libmisc/include/libmisc/fmt.h +++ b/libmisc/include/libmisc/fmt.h @@ -9,6 +9,7 @@ #include <stddef.h> /* for size_t */ #include <stdint.h> /* for (u)int{n}_t */ +#include <stdlib.h> /* for realloc() */ #include <libmisc/macro.h> #include <libmisc/obj.h> @@ -99,6 +100,11 @@ void fmt_print_bool(lo_interface fmt_dest w, bool b); const char * : fmt_print_str , \ bool : fmt_print_bool )(w, val) +/** Same as fmt_print(), but usable from inside of fmt_print(). */ +#define fmt_print2(w, ...) do { LM_FOREACH_PARAM2_(_fmt_param2, (w), __VA_ARGS__) } while (0) +#define _fmt_param2(...) _LM_DEFER2(_fmt_param_indirect)()(__VA_ARGS__) +#define _fmt_param_indirect() _fmt_param + /* print-to-memory ************************************************************/ struct fmt_buf { @@ -107,36 +113,45 @@ struct fmt_buf { }; LO_IMPLEMENTATION_H(fmt_dest, struct fmt_buf, fmt_buf); -#define fmt_snprint(buf, n, ...) ({ \ - struct fmt_buf _w = { .dat = buf, .cap = n }; \ - lo_interface fmt_dest w = lo_box_fmt_buf_as_fmt_dest(&_w); \ - fmt_print(w, __VA_ARGS__); \ - if (_w.len < _w.cap) \ - ((char *)_w.dat)[_w.len] = '\0'; \ - _w.len; \ +#define fmt_snprint(buf, n, ...) ({ \ + struct fmt_buf _w = { .dat = buf, .cap = n }; \ + lo_interface fmt_dest w = LO_BOX(fmt_dest, &_w); \ + fmt_print(w, __VA_ARGS__); \ + if (_w.len < _w.cap) \ + ((char *)_w.dat)[_w.len] = '\0'; \ + _w.len; \ }) -/* justify ********************************************************************/ +#define fmt_asprint(...) ({ \ + struct fmt_buf _w = {}; \ + lo_interface fmt_dest w = LO_BOX(fmt_dest, &_w); \ + fmt_print(w, __VA_ARGS__); \ + while (_w.cap <= _w.len) { \ + _w.cap = _w.len + 1; \ + _w.len = 0; \ + _w.dat = realloc(_w.dat, _w.cap); \ + fmt_print(w, __VA_ARGS__); \ + } \ + ((char *)_w.dat)[_w.len] = '\0'; \ + _w.dat; \ +}) -/* *grubles about not being allowed to nest things* */ -#define _fmt_param_indirect() _fmt_param -#define _fmt_print2(w, ...) do { LM_FOREACH_PARAM2_(_fmt_param2, (w), __VA_ARGS__) } while (0) -#define _fmt_param2(...) _LM_DEFER2(_fmt_param_indirect)()(__VA_ARGS__) +/* justify ********************************************************************/ #define fmt_print_ljust(w, width, fillchar, ...) do { \ size_t beg = LO_CALL(w, tell); \ - _fmt_print2(w, __VA_ARGS__); \ + fmt_print2(w, __VA_ARGS__); \ while ((LO_CALL(w, tell) - beg) < width) \ fmt_print_byte(w, fillchar); \ } while (0) -#define fmt_print_rjust(w, width, fillchar, ...) do { \ - struct fmt_buf _discard = {}; \ - lo_interface fmt_dest discard = lo_box_fmt_buf_as_fmt_dest(&_discard); \ - _fmt_print2(discard, __VA_ARGS__); \ - while (_discard.len++ < width) \ - fmt_print_byte(w, fillchar); \ - _fmt_print2(w, __VA_ARGS__); \ +#define fmt_print_rjust(w, width, fillchar, ...) do { \ + struct fmt_buf _discard = {}; \ + lo_interface fmt_dest discard = LO_BOX2(fmt_dest, &_discard); \ + fmt_print2(discard, __VA_ARGS__); \ + while (_discard.len++ < width) \ + fmt_print_byte(w, fillchar); \ + fmt_print2(w, __VA_ARGS__); \ } while (0) void fmt_print_base16_u8_(lo_interface fmt_dest w, uint8_t x); diff --git a/libmisc/include/libmisc/macro.h b/libmisc/include/libmisc/macro.h index a2d4264..48f52e5 100644 --- a/libmisc/include/libmisc/macro.h +++ b/libmisc/include/libmisc/macro.h @@ -9,6 +9,8 @@ #include <libmisc/assert.h> +/* C: syntax ******************************************************************/ + #define LM_FORCE_SEMICOLON static_assert(1, "force semicolon") #define LM_PARTIAL_SWITCH(VAL) \ @@ -17,14 +19,14 @@ switch (VAL) \ _Pragma("GCC diagnostic pop") \ -/* for function definitions */ +/* C: function definitions ****************************************************/ #define LM_UNUSED(argname) #define LM_ALWAYS_INLINE [[gnu::always_inline]] inline #define LM_NEVER_INLINE [[gnu::noinline]] #define LM_FLATTEN [[gnu::flatten]] -/* types */ +/* C: types *******************************************************************/ /* If it's a pointer instead of an array, then typeof(&ptr[0]) == typeof(ptr) */ #define _LM_IS_ARRAY(ary) (!__builtin_types_compatible_p(typeof(&(ary)[0]), typeof(ary))) @@ -38,7 +40,7 @@ : NULL; \ }) -/* numeric */ +/* C: numeric *****************************************************************/ #define LM_CEILDIV(n, d) ( ((n)+(d)-1) / (d) ) /** Return ceil(n/d) */ #define LM_ROUND_UP(n, d) ( LM_CEILDIV(n, d) * (d) ) /** Return `n` rounded up to the nearest multiple of `d` */ @@ -46,12 +48,12 @@ #define LM_NEXT_POWER_OF_2(x) ( (x) ? 1ULL<<((sizeof(unsigned long long)*8)-__builtin_clzll(x)) : 1) /** Return the lowest power of 2 that is > x */ #define LM_FLOORLOG2(x) ((sizeof(unsigned long long)*8)-__builtin_clzll(x)-1) /** Return floor(log_2(x) */ -/* strings */ +/* CPP: strings ***************************************************************/ #define LM_STR(x) #x #define LM_STR_(x) LM_STR(x) -/* token pasting */ +/* CPP: token pasting *********************************************************/ #define LM_CAT2(a, b) a ## b #define LM_CAT3(a, b, c) a ## b ## c @@ -59,7 +61,7 @@ #define LM_CAT2_(a, b) LM_CAT2(a, b) #define LM_CAT3_(a, b, c) LM_CAT3(a, b, c) -/* macro arguments */ +/* CPP: macro arguments *******************************************************/ #define LM_FIRST(a, ...) a #define LM_SECOND(a, b, ...) b @@ -70,7 +72,7 @@ #define LM_EAT(...) #define LM_EXPAND(...) __VA_ARGS__ -/* conditionals */ +/* CPP: conditionals **********************************************************/ #define LM_T xxTxx #define LM_F xxFxx @@ -82,7 +84,7 @@ #define _LM_IF__xxTxx(...) __VA_ARGS__ LM_EAT #define _LM_IF__xxFxx(...) LM_EXPAND -/* tuples */ +/* CPP: tuples ****************************************************************/ #define LM_IS_TUPLE(x) LM_IS_SENTINEL(_LM_IS_TUPLE x) #define _LM_IS_TUPLE(...) LM_SENTINEL() @@ -112,7 +114,7 @@ #define LM_TUPLES_HEAD(tuples) LM_EXPAND(LM_FIRST LM_EAT() (_LM_TUPLES_COMMA tuples)) #define LM_TUPLES_TAIL(tuples) LM_EAT tuples -/* iteration */ +/* CPP: iteration *************************************************************/ /* The desire to support a high number of iterations is in competition * with the desire for short compile times. 16 is the lowest @@ -168,4 +170,18 @@ /** The same as LM_FOREACH_TUPLE(), but callable from inside of LM_FOREACH_TUPLE(). */ #define LM_FOREACH_TUPLE2(...) _LM_DEFER2(_LM_FOREACH_TUPLE_indirect)()(__VA_ARGS__) +/* CPP: wrap-cc extensions ****************************************************/ + +#ifdef __LIBMISC_ENHANCED_CPP__ +/** + * `LM_DEFAPPEND(macro, val)` is like `#define macro val`, but can (1) + * be used from inside of a macro, and (2) appends to a value if it is + * already defined with LM_DEFAPPEND. There are lots of edge-cases, + * don't get cute. + */ +#define LM_DEFAPPEND(macro, ...) __xx__LM_DEFAPPEND__xx__(#macro, #__VA_ARGS__) LM_FORCE_SEMICOLON +#define LM_DEFAPPEND_(macro, ...) _LM_DEFAPPEND_(#macro, __VA_ARGS__) +#define _LM_DEFAPPEND_(macrostr, ...) __xx__LM_DEFAPPEND__xx__(macrostr, #__VA_ARGS__) LM_FORCE_SEMICOLON +#endif + #endif /* _LIBMISC_MACRO_H_ */ diff --git a/libmisc/include/libmisc/obj.h b/libmisc/include/libmisc/obj.h index 6645db7..6afa391 100644 --- a/libmisc/include/libmisc/obj.h +++ b/libmisc/include/libmisc/obj.h @@ -30,10 +30,6 @@ * * Use `lo_interface {iface_name}` as the type of this interface; it * should not be a pointer type. - * - * If there are any LO_NEST interfaces, this will define a - * `lo_box_{iface_name}_as_{wrapped_iface_name}(obj)` function for - * each. */ #define LO_NEST(_ARG_child_iface_name) \ (lo_nest, _ARG_child_iface_name) @@ -50,28 +46,48 @@ }; \ LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \ _LO_IFACE_PROTO, _ARG_iface_name) \ - extern int LM_CAT2_(_HIDDEN_BOGUS_, __COUNTER__) + LM_FORCE_SEMICOLON -#define _LO_IFACE_VTABLE(_tuple_typ, ...) _LO_IFACE_VTABLE_##_tuple_typ(__VA_ARGS__) -#define _LO_IFACE_VTABLE_lo_nest(_ARG_child_iface_name) const struct _lo_##_ARG_child_iface_name##_vtable *_lo_##_ARG_child_iface_name##_vtable; LM_FOREACH_TUPLE2(_ARG_child_iface_name##_LO_IFACE, _LO_IFACE_VTABLE2) -#define _LO_IFACE_VTABLE_lo_func(_ARG_ret_type, _ARG_func_name, ...) _ARG_ret_type (*_ARG_func_name)(void * __VA_OPT__(,) __VA_ARGS__); +#define _LO_IFACE_VTABLE(_tuple_typ, ...) _LO_IFACE_VTABLE_##_tuple_typ(__VA_ARGS__) +#define _LO_IFACE_VTABLE_lo_nest(_ARG_child_iface_name) \ + const struct _lo_##_ARG_child_iface_name##_vtable *_lo_##_ARG_child_iface_name##_vtable; \ + LM_FOREACH_TUPLE2(_ARG_child_iface_name##_LO_IFACE, _LO_IFACE_VTABLE2) +#define _LO_IFACE_VTABLE_lo_func(_ARG_ret_type, _ARG_func_name, ...) \ + _ARG_ret_type (*_ARG_func_name)(void * __VA_OPT__(,) __VA_ARGS__); #define _LO_IFACE_VTABLE_indirect() _LO_IFACE_VTABLE #define _LO_IFACE_VTABLE2(...) _LM_DEFER2(_LO_IFACE_VTABLE_indirect)()(__VA_ARGS__) -#define _LO_IFACE_PROTO(_ARG_iface_name, _tuple_typ, ...) _LO_IFACE_PROTO_##_tuple_typ(_ARG_iface_name, __VA_ARGS__) -#define _LO_IFACE_PROTO_lo_nest(_ARG_iface_name, _ARG_child_iface_name) \ - LM_ALWAYS_INLINE static lo_interface _ARG_child_iface_name \ - box_##_ARG_iface_name##_as_##_ARG_child_iface_name(lo_interface _ARG_iface_name obj) { \ - return (lo_interface _ARG_child_iface_name){ \ - .self = obj.self, \ - .vtable = obj.vtable->_lo_##_ARG_child_iface_name##_vtable, \ - }; \ - } +#define _LO_IFACE_PROTO(_ARG_iface_name, _tuple_typ, ...) _LO_IFACE_PROTO_##_tuple_typ(_ARG_iface_name, __VA_ARGS__) +#define _LO_IFACE_PROTO_lo_nest(_ARG_iface_name, _ARG_child_iface_name) \ + LM_DEFAPPEND(_LO_REGISTRY_##_ARG_child_iface_name, \ + (lo_interface _ARG_iface_name, _LO_BOX_##_ARG_iface_name##_as_##_ARG_child_iface_name)); \ + LM_DEFAPPEND(_LO_BOX_##_ARG_iface_name##_as_##_ARG_child_iface_name(obj), \ + { .self = obj.self, .vtable = obj.vtable->_lo_##_ARG_child_iface_name##_vtable }); #define _LO_IFACE_PROTO_lo_func(_ARG_iface_name, _ARG_ret_type, _ARG_func_name, ...) \ /* empty */ /** + * `LO_BOX(iface_name, obj)` boxes `obj` as a `lo_interface + * iface_name`. `obj` must be one of: + * + * - A pointer to a value that implements `lo_interface iface_name` + * - An already-boxed instance of `lo_interface iface_name` + * - An already-boxed instance of another interface that + * `iface_name` inherits from. + */ +#define LO_BOX(_ARG_iface_name, obj) _Generic((obj), \ + lo_interface _ARG_iface_name: obj \ + LM_FOREACH_TUPLE(_LO_REGISTRY_##_ARG_iface_name, \ + _LO_BOX, _ARG_iface_name, obj)) +#define LO_BOX2(_ARG_iface_name, obj) _Generic((obj), \ + lo_interface _ARG_iface_name: obj \ + LM_FOREACH_TUPLE2(_LO_REGISTRY_##_ARG_iface_name, \ + _LO_BOX, _ARG_iface_name, obj)) +#define _LO_BOX(_ARG_iface_name, obj, typ, boxfn) \ + , typ: (lo_interface _ARG_iface_name)boxfn(obj) + +/** * `LO_NULL(iface_name)` is the null/nil/zero value for `lo_interface {iface_name}`. */ #define LO_NULL(_ARG_iface_name) ((lo_interface _ARG_iface_name){}) @@ -99,23 +115,17 @@ * file to declare that `{impl_type}` implements the `{iface_name}` * interface with functions named `{impl_name}_{method_name}`. * - * This will also define a `lo_box_{impl_name}_as_{iface_name}(obj)` - * function. - * * You must also call the LO_IMPLEMENTATION_C in a single .c file. */ -#define LO_IMPLEMENTATION_H(_ARG_iface_name, _ARG_impl_type, _ARG_impl_name) \ - /* Vtable. */ \ - extern const struct _lo_##_ARG_iface_name##_vtable \ - _lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable; \ - /* Boxing. */ \ - LM_ALWAYS_INLINE static lo_interface _ARG_iface_name \ - lo_box_##_ARG_impl_name##_as_##_ARG_iface_name(_ARG_impl_type *self) { \ - return (lo_interface _ARG_iface_name){ \ - .self = self, \ - .vtable = &_lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable, \ - }; \ - } \ +#define LO_IMPLEMENTATION_H(_ARG_iface_name, _ARG_impl_type, _ARG_impl_name) \ + /* Vtable. */ \ + extern const struct _lo_##_ARG_iface_name##_vtable \ + _lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable; \ + /* Boxing. */ \ + LM_DEFAPPEND(_LO_REGISTRY_##_ARG_iface_name, \ + (_ARG_impl_type *, _LO_BOX_##_ARG_impl_name##_as_##_ARG_iface_name)); \ + LM_DEFAPPEND(_LO_BOX_##_ARG_impl_name##_as_##_ARG_iface_name(obj), \ + { .self = obj, .vtable = &_lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable }); \ LM_FORCE_SEMICOLON /** diff --git a/libmisc/tests/test_fmt.c b/libmisc/tests/test_fmt.c index a9157d6..64b3b8a 100644 --- a/libmisc/tests/test_fmt.c +++ b/libmisc/tests/test_fmt.c @@ -4,6 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +#include <stdlib.h> /* for free() */ #include <string.h> /* for strcmp(), memcmp(), memset() */ #include <libmisc/fmt.h> @@ -230,5 +231,13 @@ int main() { test_assert(strcmp(str, "{0x68,0x65,0x6C,0x6C,0x6F,0x00}") == 0); memset(str, 0, sizeof(str)); + char *astr = fmt_asprint(""); + test_assert(astr != NULL && astr[0] == '\0'); + free(astr); + + astr = fmt_asprint("hello ", (base2, 9), (qstr, " world!\n")); + test_assert(strcmp(astr, "hello 1001\" world!\\n\"") == 0); + free(astr); + return 0; } diff --git a/libmisc/tests/test_macro.c b/libmisc/tests/test_macro.c index 5157820..6810005 100644 --- a/libmisc/tests/test_macro.c +++ b/libmisc/tests/test_macro.c @@ -178,5 +178,14 @@ int main() { free(act_suffix); } + printf("== LM_DEFAPPEND ===========================================\n"); + LM_DEFAPPEND(mylist, a); + LM_DEFAPPEND(mylist, + b); + { + const char *str = LM_STR_(mylist); + test_assert(strcmp(str, "a b") == 0); + } + return 0; } diff --git a/libmisc/tests/test_obj.c b/libmisc/tests/test_obj.c index 687ad4e..a13b8c9 100644 --- a/libmisc/tests/test_obj.c +++ b/libmisc/tests/test_obj.c @@ -53,7 +53,7 @@ int main() { struct myclass obj = { .a = MAGIC1, }; - lo_interface frobber iface = lo_box_myclass_as_frobber(&obj); + lo_interface frobber iface = LO_BOX(frobber, &obj); test_assert(LO_CALL(iface, frob) == MAGIC1); test_assert(LO_CALL(iface, frob1, MAGIC2) == MAGIC2); LO_CALL(iface, frob0); diff --git a/libmisc/tests/test_obj_nest.c b/libmisc/tests/test_obj_nest.c index d5e563e..ba5ac37 100644 --- a/libmisc/tests/test_obj_nest.c +++ b/libmisc/tests/test_obj_nest.c @@ -64,7 +64,7 @@ static ssize_t myclass_write(struct myclass *self, void *buf, size_t count) { int main() { struct myclass _obj = {}; - lo_interface read_writer obj = lo_box_myclass_as_read_writer(&_obj); + lo_interface read_writer obj = LO_BOX(read_writer, &_obj); test_assert(LO_CALL(obj, write, "Hello", 6) == 6); char buf[6] = {}; test_assert(LO_CALL(obj, read, buf, 3) == 3); diff --git a/libmisc/wrap-cc b/libmisc/wrap-cc new file mode 100755 index 0000000..e7a0b91 --- /dev/null +++ b/libmisc/wrap-cc @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +# libmisc/wrap-cc - Wrapper around GCC to enhance the preprocessor +# +# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +import os +import subprocess +import sys +import typing + + +def scan_tuple( + text: str, beg: int, on_part: typing.Callable[[str], None] | None = None +) -> int: + assert text[beg] == "(" + pos = beg + 1 + arg_start = pos + parens = 1 + instring = False + while parens: + c = text[pos] + if instring: + match c: + case "\\": + pos += 1 + case '"': + instring = False + else: + match c: + case "(": + parens += 1 + case ")": + parens -= 1 + if on_part and parens == 0 and text[beg + 1 : pos].strip(): + on_part(text[arg_start:pos]) + case ",": + if on_part and parens == 1: + on_part(text[arg_start:pos]) + arg_start = pos + 1 + case '"': + instring = True + pos += 1 + assert text[pos - 1] == ")" + return pos - 1 + + +def unquote(cstr: str) -> str: + assert len(cstr) >= 2 and cstr[0] == '"' and cstr[-1] == '"' + cstr = cstr[1:-1] + out = "" + while cstr: + if cstr[0] == "\\": + match cstr[1]: + case "n": + out += "\n" + cstr = cstr[2:] + case "\\": + out += "\\" + cstr = cstr[2:] + case '"': + out += '"' + cstr = cstr[2:] + else: + out += cstr[0] + cstr = cstr[1:] + return out + + +def preprocess(all_args: list[str]) -> typing.NoReturn: + # argparse ################################################################# + _args = all_args + + def shift(n: int) -> list[str]: + nonlocal _args + ret = _args[:n] + _args = _args[n:] + return ret + + arg0 = shift(1)[0] + common_flags: list[str] = [] + output_flags: list[str] = [] + positional: list[str] = [] + while _args: + if len(_args[0]) > 2 and _args[0][0] == "-" and _args[0][1] in "IDU": + _args = [_args[0][:2], _args[0][2:], *_args[1:]] + match _args[0]: + # Mode + case "-E" | "-quiet": + common_flags += shift(1) + case "-lang-asm": + os.execvp(all_args[0], all_args) + # Search path + case "-I" | "-imultilib" | "-isystem": + common_flags += shift(2) + # Define/Undefine + case "-D" | "-U": + common_flags += shift(2) + # Optimization + case "-O0" | "-O1" | "-O2" | "-O3" | "-Os" | "-Ofast" | "-Og" | "-Oz": + common_flags += shift(1) + case "-g": + common_flags += shift(1) + # Output files + case "-MD" | "-MF" | "-MT" | "-dumpbase" | "-dumpbase-ext": + output_flags += shift(2) + case "-o": + output_flags += shift(2) + # Other + case _: + if _args[0].startswith("-"): + if _args[0].startswith("-std="): + common_flags += shift(1) + elif _args[0].startswith("-m"): + common_flags += shift(1) + elif _args[0].startswith("-f"): + common_flags += shift(1) + elif _args[0].startswith("-W"): + common_flags += shift(1) + else: + raise ValueError(f"unknown flag: {_args!r}") + else: + positional += shift(1) + if len(positional) != 1: + raise ValueError("expected 1 input file") + infile = positional[0] + + # enhance ################################################################## + + common_flags += ["-D", "__LIBMISC_ENHANCED_CPP__"] + + text = subprocess.run( + [arg0, *common_flags, infile], + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=sys.stderr, + check=True, + text=True, + ).stdout + + macros: dict[str, str] = {} + + marker = "__xx__LM_DEFAPPEND__xx__" + pos = 0 + while (marker_beg := text.find(marker, pos)) >= 0: + args: list[str] = [] + + def add_arg(arg: str) -> None: + nonlocal args + args.append(arg) + + beg_paren = marker_beg + len(marker) + end_paren = scan_tuple(text, beg_paren, add_arg) + + before = text[:marker_beg] + # old = text[marker_beg : end_paren + 1] + after = text[end_paren + 1 :] + + assert len(args) == 2 + k = unquote(args[0].strip()) + v = unquote(args[1].strip()) + if k not in macros: + macros[k] = v + else: + macros[k] += " " + v + + text = before + after + pos = len(before) + + common_flags += ["-D", marker + "=LM_EAT"] + for k, v in macros.items(): + common_flags += ["-D", k + "=" + v] + + # Run, for-real ############################################################ + os.execvp(arg0, [arg0, *common_flags, *output_flags, infile]) + + +def main(all_args: list[str]) -> typing.NoReturn: + if len(all_args) >= 2 and all_args[0].endswith("cc1") and all_args[1] == "-E": + preprocess(all_args) + else: + os.execvp(all_args[0], all_args) + + +if __name__ == "__main__": + main(sys.argv[1:]) |