summaryrefslogtreecommitdiff
path: root/lib9p/protogen
diff options
context:
space:
mode:
Diffstat (limited to 'lib9p/protogen')
-rw-r--r--lib9p/protogen/__init__.py57
-rw-r--r--lib9p/protogen/c.py201
-rw-r--r--lib9p/protogen/c9util.py134
-rw-r--r--lib9p/protogen/c_format.py142
-rw-r--r--lib9p/protogen/c_marshal.py403
-rw-r--r--lib9p/protogen/c_unmarshal.py138
-rw-r--r--lib9p/protogen/c_validate.py299
-rw-r--r--lib9p/protogen/cutil.py84
-rw-r--r--lib9p/protogen/h.py535
-rw-r--r--lib9p/protogen/idlutil.py112
10 files changed, 0 insertions, 2105 deletions
diff --git a/lib9p/protogen/__init__.py b/lib9p/protogen/__init__.py
deleted file mode 100644
index c2c6173..0000000
--- a/lib9p/protogen/__init__.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# lib9p/protogen/__init__.py - Generate C marshalers/unmarshalers for
-# .9p files defining 9P protocol variants
-#
-# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
-# SPDX-License-Identifier: AGPL-3.0-or-later
-
-import os.path
-import sys
-import typing
-
-import idl
-
-from . import c, h
-
-# pylint: disable=unused-variable
-__all__ = ["main"]
-
-
-def main() -> None:
- if typing.TYPE_CHECKING:
-
- class ANSIColors:
- MAGENTA = "\x1b[35m"
- RED = "\x1b[31m"
- RESET = "\x1b[0m"
-
- else:
- from _colorize import ANSIColors # Present in Python 3.13+
-
- if len(sys.argv) < 2:
- raise ValueError("requires at least 1 .9p filename")
- parser = idl.Parser()
- for txtname in sys.argv[1:]:
- try:
- parser.parse_file(txtname)
- except SyntaxError as e:
- print(
- f"{ANSIColors.RED}{e.filename}{ANSIColors.RESET}:{ANSIColors.MAGENTA}{e.lineno}{ANSIColors.RESET}: {e.msg}",
- file=sys.stderr,
- )
- assert e.text
- print(f"\t{e.text}", file=sys.stderr)
- text_suffix = e.text.lstrip()
- text_prefix = e.text[: -len(text_suffix)]
- print(
- f"\t{text_prefix}{ANSIColors.RED}{'~'*len(text_suffix)}{ANSIColors.RESET}",
- file=sys.stderr,
- )
- sys.exit(2)
- versions, typs = parser.all()
- outdir = os.path.normpath(os.path.join(sys.argv[0], ".."))
- with open(
- os.path.join(outdir, "include/lib9p/9p.generated.h"), "w", encoding="utf-8"
- ) as fh:
- fh.write(h.gen_h(versions, typs))
- with open(os.path.join(outdir, "9p.generated.c"), "w", encoding="utf-8") as fh:
- fh.write(c.gen_c(versions, typs))
diff --git a/lib9p/protogen/c.py b/lib9p/protogen/c.py
deleted file mode 100644
index a6824ce..0000000
--- a/lib9p/protogen/c.py
+++ /dev/null
@@ -1,201 +0,0 @@
-# lib9p/protogen/c.py - Generate 9p.generated.c
-#
-# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
-# SPDX-License-Identifier: AGPL-3.0-or-later
-
-import sys
-
-import idl
-
-from . import c9util, c_format, c_marshal, c_unmarshal, c_validate, cutil
-
-# This strives to be "general-purpose" in that it just acts on the
-# *.9p inputs; but (unfortunately?) there are a few special-cases in
-# this script, marked with "SPECIAL".
-
-
-# pylint: disable=unused-variable
-__all__ = ["gen_c"]
-
-
-def gen_c(versions: set[str], typs: list[idl.UserType]) -> str:
- cutil.ifdef_init()
-
- ret = f"""/* Generated by `{' '.join(sys.argv)}`. DO NOT EDIT! */
-
-#include <stdbool.h>
-#include <stddef.h> /* for size_t */
-#include <inttypes.h> /* for PRI* macros */
-#include <string.h> /* for memset() */
-
-#include <libmisc/assert.h>
-#include <libmisc/endian.h>
-
-#include <lib9p/9p.h>
-
-#include "tables.h"
-#include "utf8.h"
-"""
- # libobj vtables ###########################################################
- ret += """
-/* libobj vtables *************************************************************/
-"""
- for typ in typs:
- ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions))
- ret += f"LO_IMPLEMENTATION_C(fmt_formatter, {c9util.typename(typ)}, {c9util.basename(typ)}, static);\n"
- ret += cutil.ifdef_pop(0)
-
- # utilities ################################################################
- ret += """
-/* utilities ******************************************************************/
-"""
-
- id2typ: dict[int, idl.Message] = {}
- for msg in [msg for msg in typs if isinstance(msg, idl.Message)]:
- id2typ[msg.msgid] = msg
-
- for v in sorted(versions):
- ret += f"#if CONFIG_9P_ENABLE_{v.replace('.', '_')}\n"
- ret += (
- f"\t#define _is_ver_{v.replace('.', '_')}(v) (v == {c9util.ver_enum(v)})\n"
- )
- ret += "#else\n"
- ret += f"\t#define _is_ver_{v.replace('.', '_')}(v) false\n"
- ret += "#endif\n"
- ret += "\n"
- ret += "/**\n"
- ret += f" * is_ver(ctx, ver) is essentially `(ctx->version == {c9util.Ident('VER_')}##ver)`, but\n"
- ret += f" * compiles correctly (to `false`) even if `{c9util.Ident('VER_')}##ver` isn't defined\n"
- ret += " * (because `!CONFIG_9P_ENABLE_##ver`). This is useful when `||`ing\n"
- ret += " * several version checks together.\n"
- ret += " */\n"
- ret += "#define is_ver(ctx, ver) _is_ver_##ver((ctx)->version)\n"
-
- # bitmasks #################################################################
- ret += """
-/* bitmasks *******************************************************************/
-"""
- for typ in typs:
- if not isinstance(typ, idl.Bitfield):
- continue
- ret += "\n"
- ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions))
- ret += f"static const {c9util.typename(typ)} {typ.typname}_masks[{c9util.ver_enum('NUM')}] = {{\n"
- verwidth = max(len(ver) for ver in versions)
- for ver in sorted(versions):
- ret += cutil.ifdef_push(2, c9util.ver_ifdef({ver}))
- ret += (
- f"\t[{c9util.ver_enum(ver)}]{' '*(verwidth-len(ver))} = 0b"
- + "".join(
- (
- "1"
- if (bit.cat == "USED" or isinstance(bit.cat, idl.BitNum))
- and ver in bit.in_versions
- else "0"
- )
- for bit in reversed(typ.bits)
- )
- + ",\n"
- )
- ret += cutil.ifdef_pop(1)
- ret += "};\n"
- ret += cutil.ifdef_pop(0)
-
- # validate_* ###############################################################
- ret += c_validate.gen_c_validate(versions, typs)
-
- # unmarshal_* ##############################################################
- ret += c_unmarshal.gen_c_unmarshal(versions, typs)
-
- # marshal_* ################################################################
- ret += c_marshal.gen_c_marshal(versions, typs)
-
- # *_format #################################################################
- ret += c_format.gen_c_format(versions, typs)
-
- # tables.h #################################################################
- ret += """
-/* tables.h *******************************************************************/
-"""
-
- ret += "\n"
- ret += f"const struct {c9util.ident('_ver_tentry')} {c9util.ident('_table_ver')}[{c9util.ver_enum('NUM')}] = {{\n"
- rerror = next(typ for typ in typs if typ.typname == "Rerror")
- for ver in ["unknown", *sorted(versions)]:
- if ver == "unknown":
- min_msg_size = rerror.min_size("9P2000") # SPECIAL (initialization)
- else:
- ret += cutil.ifdef_push(1, c9util.ver_ifdef({ver}))
- min_msg_size = rerror.min_size(ver)
- ret += f'\t[{c9util.ver_enum(ver)}] = {{.name="{ver}", .min_msg_size={min_msg_size}}},\n'
- ret += cutil.ifdef_pop(0)
- ret += "};\n"
-
- def msg_table(
- cstruct: str, cname: str, each: str, _range: tuple[int, int, int]
- ) -> str:
- ret = f"const struct {c9util.ident(cstruct)} {c9util.ident(cname)}[{c9util.ver_enum('NUM')}][{hex(len(range(*_range)))}] = {{\n"
- for ver in ["unknown", *sorted(versions)]:
- if ver != "unknown":
- ret += cutil.ifdef_push(1, c9util.ver_ifdef({ver}))
- ret += f"\t[{c9util.ver_enum(ver)}] = {{\n"
- for n in range(*_range):
- xmsg: idl.Message | None = id2typ.get(n, None)
- if xmsg:
- if ver == "unknown": # SPECIAL (initialization)
- if xmsg.typname not in ["Tversion", "Rversion", "Rerror"]:
- xmsg = None
- else:
- if ver not in xmsg.in_versions:
- xmsg = None
- if xmsg:
- ret += f"\t\t{each}({xmsg.typname}),\n"
- ret += "\t},\n"
- ret += cutil.ifdef_pop(0)
- ret += "};\n"
- return ret
-
- ret += "\n"
- ret += cutil.macro(
- f"#define _MSG(typ) [{c9util.Ident('TYP_')}##typ] = {{\n"
- f"\t\t.name = #typ,\n"
- f"\t\t.box_as_fmt_formatter = (_box_as_fmt_formatter_fn_t)lo_box_{c9util.ident('msg_')}##typ##_as_fmt_formatter,\n"
- f"\t}}\n"
- )
- ret += msg_table("_msg_tentry", "_table_msg", "_MSG", (0, 0x100, 1))
-
- ret += "\n"
- ret += cutil.macro(
- f"#define _MSG_RECV(typ) [{c9util.Ident('TYP_')}##typ/2] = {{\n"
- f"\t\t.validate = validate_##typ,\n"
- f"\t\t.unmarshal = (_unmarshal_fn_t)unmarshal_##typ,\n"
- f"\t}}\n"
- )
- ret += cutil.macro(
- f"#define _MSG_SEND(typ) [{c9util.Ident('TYP_')}##typ/2] = {{\n"
- f"\t\t.marshal = (_marshal_fn_t)marshal_##typ,\n"
- f"\t}}\n"
- )
- ret += "\n"
- ret += msg_table("_recv_tentry", "_table_Tmsg_recv", "_MSG_RECV", (0, 0x100, 2))
- ret += "\n"
- ret += msg_table("_recv_tentry", "_table_Rmsg_recv", "_MSG_RECV", (1, 0x100, 2))
- ret += "\n"
- ret += msg_table("_send_tentry", "_table_Tmsg_send", "_MSG_SEND", (0, 0x100, 2))
- ret += "\n"
- ret += msg_table("_send_tentry", "_table_Rmsg_send", "_MSG_SEND", (1, 0x100, 2))
-
- ret += f"""
-LM_FLATTEN ssize_t {c9util.ident('_stat_validate')}(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes, uint32_t *ret_net_size) {{
-\treturn validate_stat(ctx, net_size, net_bytes, ret_net_size);
-}}
-LM_FLATTEN void {c9util.ident('_stat_unmarshal')}(struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out) {{
-\tunmarshal_stat(ctx, net_bytes, out);
-}}
-LM_FLATTEN bool {c9util.ident('_stat_marshal')}(struct lib9p_ctx *ctx, struct {c9util.ident('stat')} *val, struct _marshal_ret *ret) {{
-\treturn marshal_stat(ctx, val, ret);
-}}
-"""
-
- ############################################################################
- return ret
diff --git a/lib9p/protogen/c9util.py b/lib9p/protogen/c9util.py
deleted file mode 100644
index cf91951..0000000
--- a/lib9p/protogen/c9util.py
+++ /dev/null
@@ -1,134 +0,0 @@
-# lib9p/protogen/c9util.py - Utilities for generating lib9p-specific C
-#
-# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
-# SPDX-License-Identifier: AGPL-3.0-or-later
-
-import re
-import typing
-
-import idl
-
-# This strives to be "general-purpose" in that it just acts on the
-# *.9p inputs; but (unfortunately?) there are a few special-cases in
-# this script, marked with "SPECIAL".
-
-# pylint: disable=unused-variable
-__all__ = [
- "add_prefix",
- "ident",
- "Ident",
- "IDENT",
- "ver_enum",
- "ver_ifdef",
- "ver_cond",
- "typename",
- "idl_expr",
-]
-
-# idents #######################################################################
-
-
-def add_prefix(p: str, s: str) -> str:
- if s.startswith("_"):
- return "_" + p + s[1:]
- return p + s
-
-
-def _ident(p: str, s: str) -> str:
- return add_prefix(p, s.replace(".", "_"))
-
-
-def ident(s: str) -> str:
- return _ident("lib9p_", s)
-
-
-def Ident(s: str) -> str:
- return _ident("lib9p_".upper(), s)
-
-
-def IDENT(s: str) -> str:
- return _ident("lib9p_", s).upper()
-
-
-# versions #####################################################################
-
-
-def ver_enum(ver: str) -> str:
- return Ident("VER_" + ver)
-
-
-def ver_ifdef(versions: typing.Collection[str]) -> str:
- return " || ".join(
- f"CONFIG_9P_ENABLE_{v.replace('.', '_')}" for v in sorted(versions)
- )
-
-
-def ver_cond(versions: typing.Collection[str]) -> str:
- if len(versions) == 1:
- v = next(v for v in versions)
- return f"is_ver(ctx, {v.replace('.', '_')})"
- return "( " + (" || ".join(ver_cond({v}) for v in sorted(versions))) + " )"
-
-
-# misc #########################################################################
-
-
-def basename(typ: idl.UserType) -> str:
- match typ:
- case idl.Number():
- return ident(typ.typname)
- case idl.Bitfield():
- return ident(typ.typname)
- case idl.Message():
- return ident(f"msg_{typ.typname}")
- case idl.Struct():
- return ident(typ.typname)
- case _:
- raise ValueError(f"not a defined type: {typ.__class__.__name__}")
-
-
-def typename(typ: idl.Type, parent: idl.StructMember | None = None) -> str:
- match typ:
- case idl.Primitive():
- if typ.value == 1 and parent and parent.cnt: # SPECIAL (string)
- return "[[gnu::nonstring]] char"
- return f"uint{typ.value*8}_t"
- case idl.Number():
- return f"{basename(typ)}_t"
- case idl.Bitfield():
- return f"{basename(typ)}_t"
- case idl.Message():
- return f"struct {basename(typ)}"
- case idl.Struct():
- return f"struct {basename(typ)}"
- case _:
- raise ValueError(f"not a type: {typ.__class__.__name__}")
-
-
-def idl_expr(
- expr: idl.Expr, lookup_sym: typing.Callable[[str], str], bitwidth: int = 0
-) -> str:
- ret: list[str] = []
- for tok in expr.tokens:
- match tok:
- case idl.ExprOp():
- ret.append(tok.op)
- case idl.ExprLit():
- if bitwidth:
- ret.append(f"{tok.val:#0{bitwidth}b}")
- else:
- ret.append(str(tok.val))
- case idl.ExprSym():
- if m := re.fullmatch(r"^u(8|16|32|64)_max$", tok.symname):
- ret.append(f"UINT{m.group(1)}_MAX")
- elif m := re.fullmatch(r"^s(8|16|32|64)_max$", tok.symname):
- ret.append(f"INT{m.group(1)}_MAX")
- else:
- ret.append(lookup_sym(tok.symname))
- case idl.ExprOff():
- ret.append(lookup_sym("&" + tok.membname))
- case idl.ExprNum():
- ret.append(Ident(add_prefix(f"{tok.numname}_".upper(), tok.valname)))
- case _:
- assert False
- return " ".join(ret)
diff --git a/lib9p/protogen/c_format.py b/lib9p/protogen/c_format.py
deleted file mode 100644
index a1bcbf3..0000000
--- a/lib9p/protogen/c_format.py
+++ /dev/null
@@ -1,142 +0,0 @@
-# lib9p/protogen/c_format.py - Generate C pretty-print functions
-#
-# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
-# SPDX-License-Identifier: AGPL-3.0-or-later
-
-
-import idl
-
-from . import c9util, cutil
-
-# This strives to be "general-purpose" in that it just acts on the
-# *.9p inputs; but (unfortunately?) there are a few special-cases in
-# this script, marked with "SPECIAL".
-
-# pylint: disable=unused-variable
-__all__ = ["gen_c_format"]
-
-
-def bf_numname(typ: idl.Bitfield, num: idl.BitNum, base: str) -> str:
- prefix = f"{typ.typname}_{num.numname}_".upper()
- return c9util.Ident(c9util.add_prefix(prefix, base))
-
-
-def gen_c_format(versions: set[str], typs: list[idl.UserType]) -> str:
- ret = """
-/* *_format *******************************************************************/
-"""
- for typ in typs:
- ret += "\n"
- ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions))
- ret += f"static void {c9util.basename(typ)}_format({c9util.typename(typ)} *self, struct fmt_state *state) {{\n"
- match typ:
- case idl.Number():
- if typ.vals:
- ret += "\tswitch (*self) {\n"
- for name in typ.vals:
- ret += f"\tcase {c9util.Ident(c9util.add_prefix(f'{typ.typname}_'.upper(), name))}:\n"
- ret += f'\t\tfmt_state_puts(state, "{name}");\n'
- ret += "\t\tbreak;\n"
- ret += "\tdefault:\n"
- ret += f'\t\tfmt_state_printf(state, "%"PRIu{typ.static_size*8}, *self);\n'
- ret += "\t}\n"
- else:
- ret += f'\t\tfmt_state_printf(state, "%"PRIu{typ.static_size*8}, *self);\n'
- case idl.Bitfield():
- val = "*self"
- if typ.typname == "dm": # SPECIAL (pretty file permissions)
- val = f"(*self & ~(({c9util.typename(typ)})0777))"
- ret += "\tbool empty = true;\n"
- ret += "\tfmt_state_putchar(state, '(');\n"
- nums: set[str] = set()
-
- for bit in reversed(typ.bits):
- match bit.cat:
- case "UNUSED" | "USED" | "RESERVED":
- if bit.cat == "UNUSED":
- bitname = f"1<<{bit.num}"
- else:
- bitname = bit.bitname
- ret += f"\tif ({val} & (UINT{typ.static_size*8}_C(1)<<{bit.num})) {{\n"
- ret += "\t\tif (!empty)\n"
- ret += "\t\t\tfmt_state_putchar(state, '|');\n"
- ret += f'\t\tfmt_state_puts(state, "{bitname}");\n'
- ret += "\t\tempty = false;\n"
- ret += "\t}\n"
- case idl.BitNum():
- if bit.cat.numname in nums:
- continue
- ret += f"\tswitch ({val} & {bf_numname(typ, bit.cat, 'MASK')}) {{\n"
- for name in bit.cat.vals:
- ret += f"\tcase {bf_numname(typ, bit.cat, name)}:\n"
- bitname = c9util.add_prefix(
- f"{bit.cat.numname}_".upper(), name
- )
- ret += "\t\tif (!empty)\n"
- ret += "\t\t\tfmt_state_putchar(state, '|');\n"
- ret += f'\t\tfmt_state_puts(state, "{bitname}");\n'
- ret += "\t\tempty = false;\n"
- ret += "\t\tbreak;\n"
- ret += "\tdefault:\n"
- ret += "\t\tif (!empty)\n"
- ret += "\t\t\tfmt_state_putchar(state, '|');\n"
- ret += f'\t\tfmt_state_printf(state, "%"PRIu{typ.static_size*8}, {val} & {bf_numname(typ, bit.cat, 'MASK')});\n'
- ret += "\t\tempty = false;\n"
- ret += "\t}\n"
- nums.add(bit.cat.numname)
- if typ.typname == "dm": # SPECIAL (pretty file permissions)
- ret += "\tif (!empty)\n"
- ret += "\t\tfmt_state_putchar(state, '|');\n"
- ret += f'\tfmt_state_printf(state, "%#04"PRIo{typ.static_size*8}, *self & 0777);\n'
- else:
- ret += "\tif (empty)\n"
- ret += "\t\tfmt_state_putchar(state, '0');\n"
- ret += "\tfmt_state_putchar(state, ')');\n"
- case idl.Struct(typname="s"): # SPECIAL(string)
- ret += "\t/* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47781 */\n"
- ret += "#pragma GCC diagnostic push\n"
- ret += '#pragma GCC diagnostic ignored "-Wformat"\n'
- ret += '#pragma GCC diagnostic ignored "-Wformat-extra-args"\n'
- ret += '\tfmt_state_printf(state, "%.*q", self->len, self->utf8);\n'
- ret += "#pragma GCC diagnostic pop\n"
- case idl.Struct(): # and idl.Message():
- if isinstance(typ, idl.Message):
- ret += f'\tfmt_state_puts(state, "{typ.typname} {{");\n'
- else:
- ret += "\tfmt_state_putchar(state, '{');\n"
- for member in typ.members:
- if member.val:
- continue
- ret += cutil.ifdef_push(2, c9util.ver_ifdef(member.in_versions))
- if member.cnt:
- if member.typ.static_size == 1: # SPECIAL (data)
- ret += f'\tfmt_state_puts(state, " {member.membname}=<bytedata>");\n'
- continue
- if isinstance(member.cnt, int):
- cnt_str = str(member.cnt)
- cnt_typ = "size_t"
- else:
- cnt_str = f"self->{member.cnt.membname}"
- cnt_typ = c9util.typename(member.cnt.typ)
- ret += f'\tfmt_state_puts(state, " {member.membname}=[");\n'
- ret += f"\tfor ({cnt_typ} i = 0; i < {cnt_str}; i++) {{\n"
- ret += "\t\tif (i)\n"
- ret += '\t\t\tfmt_state_puts(state, ", ");\n'
- if isinstance(member.typ, idl.Primitive):
- ret += f'\t\tfmt_state_printf(state, "%"PRIu{member.typ.static_size*8}, self->{member.membname}[i]);\n'
- else:
- ret += f"\t\t{c9util.basename(member.typ)}_format(&self->{member.membname}[i], state);\n"
- ret += "\t}\n"
- ret += '\tfmt_state_puts(state, " ]");\n'
- else:
- ret += f'\tfmt_state_puts(state, " {member.membname}=");\n'
- if isinstance(member.typ, idl.Primitive):
- ret += f'\tfmt_state_printf(state, "%"PRIu{member.typ.static_size*8}, self->{member.membname});\n'
- else:
- ret += f"\t{c9util.basename(member.typ)}_format(&self->{member.membname}, state);\n"
- ret += cutil.ifdef_pop(1)
- ret += '\tfmt_state_puts(state, " }");\n'
- ret += "}\n"
- ret += cutil.ifdef_pop(0)
-
- return ret
diff --git a/lib9p/protogen/c_marshal.py b/lib9p/protogen/c_marshal.py
deleted file mode 100644
index 4dab864..0000000
--- a/lib9p/protogen/c_marshal.py
+++ /dev/null
@@ -1,403 +0,0 @@
-# lib9p/protogen/c_marshal.py - Generate C marshal functions
-#
-# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
-# SPDX-License-Identifier: AGPL-3.0-or-later
-
-import typing
-
-import idl
-
-from . import c9util, cutil, idlutil
-
-# This strives to be "general-purpose" in that it just acts on the
-# *.9p inputs; but (unfortunately?) there are a few special-cases in
-# this script, marked with "SPECIAL".
-
-
-# pylint: disable=unused-variable
-__all__ = ["gen_c_marshal"]
-
-# get_offset_expr() ############################################################
-
-
-class OffsetExpr:
- static: int
- cond: dict[frozenset[str], "OffsetExpr"]
- rep: list[tuple[idlutil.Path | int, "OffsetExpr"]]
-
- def __init__(self) -> None:
- self.static = 0
- self.rep = []
- self.cond = {}
-
- def add(self, other: "OffsetExpr") -> None:
- self.static += other.static
- self.rep += other.rep
- for k, v in other.cond.items():
- if k in self.cond:
- self.cond[k].add(v)
- else:
- self.cond[k] = v
-
- def gen_c(
- self,
- dsttyp: str,
- dstvar: str,
- root: str,
- indent_depth: int,
- loop_depth: int,
- ) -> str:
- oneline: list[str] = []
- multiline = ""
- if self.static:
- oneline.append(str(self.static))
- for cnt, sub in self.rep:
- if isinstance(cnt, int):
- cnt_str = str(cnt)
- cnt_typ = "size_t"
- else:
- cnt_str = cnt.c_str(root)
- cnt_typ = c9util.typename(cnt.elems[-1].typ)
- if not sub.cond and not sub.rep:
- if sub.static == 1:
- oneline.append(cnt_str)
- else:
- oneline.append(f"({cnt_str})*{sub.static}")
- continue
- loopvar = chr(ord("i") + loop_depth)
- multiline += f"{'\t'*indent_depth}for ({cnt_typ} {loopvar} = 0; {loopvar} < {cnt_str}; {loopvar}++) {{\n"
- multiline += sub.gen_c("", dstvar, root, indent_depth + 1, loop_depth + 1)
- multiline += f"{'\t'*indent_depth}}}\n"
- for vers, sub in self.cond.items():
- multiline += cutil.ifdef_push(indent_depth + 1, c9util.ver_ifdef(vers))
- multiline += f"{'\t'*indent_depth}if {c9util.ver_cond(vers)} {{\n"
- multiline += sub.gen_c("", dstvar, root, indent_depth + 1, loop_depth)
- multiline += f"{'\t'*indent_depth}}}\n"
- multiline += cutil.ifdef_pop(indent_depth)
- ret = ""
- if dsttyp:
- if not oneline:
- oneline.append("0")
- ret += f"{'\t'*indent_depth}{dsttyp} {dstvar} = {' + '.join(oneline)};\n"
- elif oneline:
- ret += f"{'\t'*indent_depth}{dstvar} += {' + '.join(oneline)};\n"
- ret += multiline
- return ret
-
-
-type OffsetExprRecursion = typing.Callable[[idlutil.Path], idlutil.WalkCmd]
-
-
-def get_offset_expr(typ: idl.UserType, recurse: OffsetExprRecursion) -> OffsetExpr:
- if not isinstance(typ, idl.Struct):
- assert typ.static_size
- ret = OffsetExpr()
- ret.static = typ.static_size
- return ret
-
- class ExprStackItem(typing.NamedTuple):
- path: idlutil.Path
- expr: OffsetExpr
- pop: typing.Callable[[], None]
-
- expr_stack: list[ExprStackItem]
-
- def pop_root() -> None:
- assert False
-
- def pop_cond() -> None:
- nonlocal expr_stack
- key = frozenset(expr_stack[-1].path.elems[-1].in_versions)
- if key in expr_stack[-2].expr.cond:
- expr_stack[-2].expr.cond[key].add(expr_stack[-1].expr)
- else:
- expr_stack[-2].expr.cond[key] = expr_stack[-1].expr
- expr_stack = expr_stack[:-1]
-
- def pop_rep() -> None:
- nonlocal expr_stack
- member_path = expr_stack[-1].path
- member = member_path.elems[-1]
- assert member.cnt
- cnt: idlutil.Path | int
- if isinstance(member.cnt, int):
- cnt = member.cnt
- else:
- cnt = member_path.parent().add(member.cnt)
- expr_stack[-2].expr.rep.append((cnt, expr_stack[-1].expr))
- expr_stack = expr_stack[:-1]
-
- def handle(
- path: idlutil.Path,
- ) -> tuple[idlutil.WalkCmd, typing.Callable[[], None] | None]:
- nonlocal recurse
-
- ret = recurse(path)
- if ret != idlutil.WalkCmd.KEEP_GOING:
- return ret, None
-
- nonlocal expr_stack
- expr_stack_len = len(expr_stack)
-
- def pop() -> None:
- nonlocal expr_stack
- nonlocal expr_stack_len
- while len(expr_stack) > expr_stack_len:
- expr_stack[-1].pop()
-
- if path.elems:
- child = path.elems[-1]
- parent = path.elems[-2].typ if len(path.elems) > 1 else path.root
- if child.in_versions < parent.in_versions:
- expr_stack.append(
- ExprStackItem(path=path, expr=OffsetExpr(), pop=pop_cond)
- )
- if child.cnt:
- expr_stack.append(
- ExprStackItem(path=path, expr=OffsetExpr(), pop=pop_rep)
- )
- if not isinstance(child.typ, idl.Struct):
- assert child.typ.static_size
- expr_stack[-1].expr.static += child.typ.static_size
- return ret, pop
-
- expr_stack = [
- ExprStackItem(path=idlutil.Path(typ), expr=OffsetExpr(), pop=pop_root)
- ]
- idlutil.walk(typ, handle)
- return expr_stack[0].expr
-
-
-def go_to_end(path: idlutil.Path) -> idlutil.WalkCmd:
- return idlutil.WalkCmd.KEEP_GOING
-
-
-def go_to_tok(name: str) -> typing.Callable[[idlutil.Path], idlutil.WalkCmd]:
- def ret(path: idlutil.Path) -> idlutil.WalkCmd:
- if len(path.elems) == 1 and path.elems[0].membname == name:
- return idlutil.WalkCmd.ABORT
- return idlutil.WalkCmd.KEEP_GOING
-
- return ret
-
-
-# Generate .c ##################################################################
-
-
-def gen_c_marshal(versions: set[str], typs: list[idl.UserType]) -> str:
- ret = """
-/* marshal_* ******************************************************************/
-
-"""
- ret += cutil.macro(
- "#define MARSHAL_BYTES_ZEROCOPY(ctx, data, len)\n"
- "\tif (ret->net_iov[ret->net_iov_cnt-1].iov_len)\n"
- "\t\tret->net_iov_cnt++;\n"
- "\tret->net_iov[ret->net_iov_cnt-1].iov_base = data;\n"
- "\tret->net_iov[ret->net_iov_cnt-1].iov_len = len;\n"
- "\tret->net_iov_cnt++;\n"
- )
- ret += cutil.macro(
- "#define MARSHAL_BYTES(ctx, data, len)\n"
- "\tif (!ret->net_iov[ret->net_iov_cnt-1].iov_base)\n"
- "\t\tret->net_iov[ret->net_iov_cnt-1].iov_base = &ret->net_copied[ret->net_copied_size];\n"
- "\tmemcpy(&ret->net_copied[ret->net_copied_size], data, len);\n"
- "\tret->net_copied_size += len;\n"
- "\tret->net_iov[ret->net_iov_cnt-1].iov_len += len;\n"
- )
- ret += cutil.macro(
- "#define MARSHAL_U8LE(ctx, val)\n"
- "\tif (!ret->net_iov[ret->net_iov_cnt-1].iov_base)\n"
- "\t\tret->net_iov[ret->net_iov_cnt-1].iov_base = &ret->net_copied[ret->net_copied_size];\n"
- "\tret->net_copied[ret->net_copied_size] = val;\n"
- "\tret->net_copied_size += 1;\n"
- "\tret->net_iov[ret->net_iov_cnt-1].iov_len += 1;\n"
- )
- ret += cutil.macro(
- "#define MARSHAL_U16LE(ctx, val)\n"
- "\tif (!ret->net_iov[ret->net_iov_cnt-1].iov_base)\n"
- "\t\tret->net_iov[ret->net_iov_cnt-1].iov_base = &ret->net_copied[ret->net_copied_size];\n"
- "\tuint16le_encode(&ret->net_copied[ret->net_copied_size], val);\n"
- "\tret->net_copied_size += 2;\n"
- "\tret->net_iov[ret->net_iov_cnt-1].iov_len += 2;\n"
- )
- ret += cutil.macro(
- "#define MARSHAL_U32LE(ctx, val)\n"
- "\tif (!ret->net_iov[ret->net_iov_cnt-1].iov_base)\n"
- "\t\tret->net_iov[ret->net_iov_cnt-1].iov_base = &ret->net_copied[ret->net_copied_size];\n"
- "\tuint32le_encode(&ret->net_copied[ret->net_copied_size], val);\n"
- "\tret->net_copied_size += 4;\n"
- "\tret->net_iov[ret->net_iov_cnt-1].iov_len += 4;\n"
- )
- ret += cutil.macro(
- "#define MARSHAL_U64LE(ctx, val)\n"
- "\tif (!ret->net_iov[ret->net_iov_cnt-1].iov_base)\n"
- "\t\tret->net_iov[ret->net_iov_cnt-1].iov_base = &ret->net_copied[ret->net_copied_size];\n"
- "\tuint64le_encode(&ret->net_copied[ret->net_copied_size], val);\n"
- "\tret->net_copied_size += 8;\n"
- "\tret->net_iov[ret->net_iov_cnt-1].iov_len += 8;\n"
- )
-
- class IndentLevel(typing.NamedTuple):
- ifdef: bool # whether this is both `{` and `#if`, or just `{`
-
- indent_stack: list[IndentLevel]
-
- def ifdef_lvl() -> int:
- return sum(1 if lvl.ifdef else 0 for lvl in indent_stack)
-
- def indent_lvl() -> int:
- return len(indent_stack)
-
- max_size: int
-
- def handle(
- path: idlutil.Path,
- ) -> tuple[idlutil.WalkCmd, typing.Callable[[], None]]:
- nonlocal ret
- nonlocal indent_stack
- nonlocal max_size
- indent_stack_len = len(indent_stack)
-
- def pop() -> None:
- nonlocal ret
- nonlocal indent_stack
- nonlocal indent_stack_len
- while len(indent_stack) > indent_stack_len:
- if len(indent_stack) == indent_stack_len + 1 and indent_stack[-1].ifdef:
- break
- ret += f"{'\t'*(indent_lvl()-1)}}}\n"
- if indent_stack.pop().ifdef:
- ret += cutil.ifdef_pop(ifdef_lvl())
-
- loopdepth = sum(1 for elem in path.elems if elem.cnt)
- struct = path.elems[-1].typ if path.elems else path.root
- if isinstance(struct, idl.Struct):
- offsets: list[str] = []
- for member in struct.members:
- if not member.val:
- continue
- for tok in member.val.tokens:
- match tok:
- case idl.ExprSym(symname="end"):
- if tok.symname not in offsets:
- offsets.append(tok.symname)
- case idl.ExprOff():
- if f"&{tok.membname}" not in offsets:
- offsets.append(f"&{tok.membname}")
- for name in offsets:
- name_prefix = f"offsetof{''.join('_'+m.membname for m in path.elems)}_"
- if name == "end":
- if not path.elems:
- if max_size > cutil.UINT32_MAX:
- ret += f"{'\t'*indent_lvl()}uint32_t {name_prefix}end = (uint32_t)needed_size;\n"
- else:
- ret += f"{'\t'*indent_lvl()}uint32_t {name_prefix}end = needed_size;\n"
- continue
- recurse: OffsetExprRecursion = go_to_end
- else:
- assert name.startswith("&")
- name = name[1:]
- recurse = go_to_tok(name)
- expr = get_offset_expr(struct, recurse)
- expr_prefix = path.c_str("val->", loopdepth)
- if not expr_prefix.endswith(">"):
- expr_prefix += "."
- ret += expr.gen_c(
- "uint32_t",
- name_prefix + name,
- expr_prefix,
- indent_lvl(),
- loopdepth,
- )
- if not path.elems:
- return idlutil.WalkCmd.KEEP_GOING, pop
-
- child = path.elems[-1]
- parent = path.elems[-2].typ if len(path.elems) > 1 else path.root
- if child.in_versions < parent.in_versions:
- if line := cutil.ifdef_push(
- ifdef_lvl() + 1, c9util.ver_ifdef(child.in_versions)
- ):
- ret += line
- ret += (
- f"{'\t'*indent_lvl()}if ({c9util.ver_cond(child.in_versions)}) {{\n"
- )
- indent_stack.append(IndentLevel(ifdef=True))
- if child.cnt:
- if isinstance(child.cnt, int):
- cnt_str = str(child.cnt)
- cnt_typ = "size_t"
- else:
- cnt_str = path.parent().add(child.cnt).c_str("val->")
- cnt_typ = c9util.typename(child.cnt.typ)
- if child.typ.static_size == 1: # SPECIAL (zerocopy)
- if path.root.typname == "stat": # SPECIAL (stat)
- ret += f"{'\t'*indent_lvl()}MARSHAL_BYTES(ctx, {path.c_str('val->')[:-3]}, {cnt_str});\n"
- else:
- ret += f"{'\t'*indent_lvl()}MARSHAL_BYTES_ZEROCOPY(ctx, {path.c_str('val->')[:-3]}, {cnt_str});\n"
- return idlutil.WalkCmd.KEEP_GOING, pop
- loopvar = chr(ord("i") + loopdepth - 1)
- ret += f"{'\t'*indent_lvl()}for ({cnt_typ} {loopvar} = 0; {loopvar} < {cnt_str}; {loopvar}++) {{\n"
- indent_stack.append(IndentLevel(ifdef=False))
- if not isinstance(child.typ, idl.Struct):
- if child.val:
-
- def lookup_sym(sym: str) -> str:
- nonlocal path
- if sym.startswith("&"):
- sym = sym[1:]
- return f"offsetof{''.join('_'+m.membname for m in path.elems[:-1])}_{sym}"
-
- val = c9util.idl_expr(child.val, lookup_sym)
- else:
- val = path.c_str("val->")
- if isinstance(child.typ, idl.Bitfield):
- val += f" & {child.typ.typname}_masks[ctx->version]"
- ret += f"{'\t'*indent_lvl()}MARSHAL_U{child.typ.static_size*8}LE(ctx, {val});\n"
- return idlutil.WalkCmd.KEEP_GOING, pop
-
- for typ in typs:
- if not (
- isinstance(typ, idl.Message) or typ.typname == "stat"
- ): # SPECIAL (include stat)
- continue
- assert isinstance(typ, idl.Struct)
- ret += "\n"
- ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions))
- ret += f"static bool marshal_{typ.typname}(struct lib9p_ctx *ctx, {c9util.typename(typ)} *val, struct _marshal_ret *ret) {{\n"
-
- # Pass 1 - check size
- max_size = max(typ.max_size(v) for v in typ.in_versions)
-
- if max_size > cutil.UINT32_MAX: # SPECIAL (9P2000.e)
- ret += get_offset_expr(typ, go_to_end).gen_c(
- "uint64_t", "needed_size", "val->", 1, 0
- )
- ret += "\tif (needed_size > (uint64_t)(ctx->max_msg_size)) {\n"
- else:
- ret += get_offset_expr(typ, go_to_end).gen_c(
- "uint32_t", "needed_size", "val->", 1, 0
- )
- ret += "\tif (needed_size > ctx->max_msg_size) {\n"
- if isinstance(typ, idl.Message): # SPECIAL (disable for stat)
- ret += '\t\tlib9p_errorf(ctx, LINUX_ERANGE, "%s message too large to marshal into %s limit (limit=%"PRIu32")",\n'
- ret += f'\t\t\t"{typ.typname}",\n'
- ret += f'\t\t\tctx->version ? "negotiated" : "{'client' if typ.msgid % 2 == 0 else 'server'}",\n'
- ret += "\t\t\tctx->max_msg_size);\n"
- ret += "\t\treturn true;\n"
- ret += "\t}\n"
-
- # Pass 2 - write data
- indent_stack = [IndentLevel(ifdef=True)]
- idlutil.walk(typ, handle)
- while len(indent_stack) > 1:
- ret += f"{'\t'*(indent_lvl()-1)}}}\n"
- if indent_stack.pop().ifdef:
- ret += cutil.ifdef_pop(ifdef_lvl())
-
- # Return
- ret += "\treturn false;\n"
- ret += "}\n"
- ret += cutil.ifdef_pop(0)
- return ret
diff --git a/lib9p/protogen/c_unmarshal.py b/lib9p/protogen/c_unmarshal.py
deleted file mode 100644
index 34635f9..0000000
--- a/lib9p/protogen/c_unmarshal.py
+++ /dev/null
@@ -1,138 +0,0 @@
-# lib9p/protogen/c_unmarshal.py - Generate C unmarshal functions
-#
-# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
-# SPDX-License-Identifier: AGPL-3.0-or-later
-
-import typing
-
-import idl
-
-from . import c9util, cutil, idlutil
-
-# This strives to be "general-purpose" in that it just acts on the
-# *.9p inputs; but (unfortunately?) there are a few special-cases in
-# this script, marked with "SPECIAL".
-
-
-# pylint: disable=unused-variable
-__all__ = ["gen_c_unmarshal"]
-
-
-def gen_c_unmarshal(versions: set[str], typs: list[idl.UserType]) -> str:
- ret = """
-/* unmarshal_* ****************************************************************/
-
-"""
- ret += cutil.macro(
- "#define UNMARSHAL_BYTES(ctx, data_lvalue, len)\n"
- "\tdata_lvalue = (char *)&net_bytes[net_offset];\n"
- "\tnet_offset += len;\n"
- )
- ret += cutil.macro(
- "#define UNMARSHAL_U8LE(ctx, val_lvalue)\n"
- "\tval_lvalue = net_bytes[net_offset];\n"
- "\tnet_offset += 1;\n"
- )
- ret += cutil.macro(
- "#define UNMARSHAL_U16LE(ctx, val_lvalue)\n"
- "\tval_lvalue = uint16le_decode(&net_bytes[net_offset]);\n"
- "\tnet_offset += 2;\n"
- )
- ret += cutil.macro(
- "#define UNMARSHAL_U32LE(ctx, val_lvalue)\n"
- "\tval_lvalue = uint32le_decode(&net_bytes[net_offset]);\n"
- "\tnet_offset += 4;\n"
- )
- ret += cutil.macro(
- "#define UNMARSHAL_U64LE(ctx, val_lvalue)\n"
- "\tval_lvalue = uint64le_decode(&net_bytes[net_offset]);\n"
- "\tnet_offset += 8;\n"
- )
-
- class IndentLevel(typing.NamedTuple):
- ifdef: bool # whether this is both `{` and `#if`, or just `{`
-
- indent_stack: list[IndentLevel]
-
- def ifdef_lvl() -> int:
- return sum(1 if lvl.ifdef else 0 for lvl in indent_stack)
-
- def indent_lvl() -> int:
- return len(indent_stack)
-
- def handle(
- path: idlutil.Path,
- ) -> tuple[idlutil.WalkCmd, typing.Callable[[], None]]:
- nonlocal ret
- nonlocal indent_stack
- indent_stack_len = len(indent_stack)
-
- def pop() -> None:
- nonlocal ret
- nonlocal indent_stack
- nonlocal indent_stack_len
- while len(indent_stack) > indent_stack_len:
- if len(indent_stack) == indent_stack_len + 1 and indent_stack[-1].ifdef:
- break
- ret += f"{'\t'*(indent_lvl()-1)}}}\n"
- if indent_stack.pop().ifdef:
- ret += cutil.ifdef_pop(ifdef_lvl())
-
- if not path.elems:
- return idlutil.WalkCmd.KEEP_GOING, pop
-
- child = path.elems[-1]
- parent = path.elems[-2].typ if len(path.elems) > 1 else path.root
- if child.in_versions < parent.in_versions:
- if line := cutil.ifdef_push(
- ifdef_lvl() + 1, c9util.ver_ifdef(child.in_versions)
- ):
- ret += line
- ret += (
- f"{'\t'*indent_lvl()}if ({c9util.ver_cond(child.in_versions)}) {{\n"
- )
- indent_stack.append(IndentLevel(ifdef=True))
- if child.cnt:
- if isinstance(child.cnt, int):
- cnt_str = str(child.cnt)
- cnt_typ = "size_t"
- else:
- cnt_str = path.parent().add(child.cnt).c_str("out->")
- cnt_typ = c9util.typename(child.cnt.typ)
- if child.typ.static_size == 1: # SPECIAL (zerocopy)
- ret += f"{'\t'*indent_lvl()}UNMARSHAL_BYTES(ctx, {path.c_str('out->')[:-3]}, {cnt_str});\n"
- return idlutil.WalkCmd.KEEP_GOING, pop
- ret += f"{'\t'*indent_lvl()}{path.c_str('out->')[:-3]} = extra;\n"
- ret += f"{'\t'*indent_lvl()}extra += sizeof({path.c_str('out->')[:-3]}[0]) * {cnt_str};\n"
- loopdepth = sum(1 for elem in path.elems if elem.cnt)
- loopvar = chr(ord("i") + loopdepth - 1)
- ret += f"{'\t'*indent_lvl()}for ({cnt_typ} {loopvar} = 0; {loopvar} < {cnt_str}; {loopvar}++) {{\n"
- indent_stack.append(IndentLevel(ifdef=False))
- if not isinstance(child.typ, idl.Struct):
- if child.val:
- ret += f"{'\t'*indent_lvl()}net_offset += {child.typ.static_size};\n"
- else:
- ret += f"{'\t'*indent_lvl()}UNMARSHAL_U{child.typ.static_size*8}LE(ctx, {path.c_str('out->')});\n"
- return idlutil.WalkCmd.KEEP_GOING, pop
-
- for typ in typs:
- if not (
- isinstance(typ, idl.Message) or typ.typname == "stat"
- ): # SPECIAL (include stat)
- continue
- assert isinstance(typ, idl.Struct)
- ret += "\n"
- ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions))
- ret += f"static void unmarshal_{typ.typname}([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) {{\n"
- ret += f"\t{c9util.typename(typ)} *out = out_buf;\n"
- ret += "\t[[gnu::unused]] void *extra = &out[1];\n"
- ret += "\tuint32_t net_offset = 0;\n"
-
- indent_stack = [IndentLevel(ifdef=True)]
- idlutil.walk(typ, handle)
- while len(indent_stack) > 0:
- ret += f"{'\t'*(indent_lvl()-1)}}}\n"
- if indent_stack.pop().ifdef and indent_stack:
- ret += cutil.ifdef_pop(ifdef_lvl())
- ret += cutil.ifdef_pop(0)
- return ret
diff --git a/lib9p/protogen/c_validate.py b/lib9p/protogen/c_validate.py
deleted file mode 100644
index 535a750..0000000
--- a/lib9p/protogen/c_validate.py
+++ /dev/null
@@ -1,299 +0,0 @@
-# lib9p/protogen/c_validate.py - Generate C validation functions
-#
-# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
-# SPDX-License-Identifier: AGPL-3.0-or-later
-
-import typing
-
-import idl
-
-from . import c9util, cutil, idlutil
-
-# This strives to be "general-purpose" in that it just acts on the
-# *.9p inputs; but (unfortunately?) there are a few special-cases in
-# this script, marked with "SPECIAL".
-
-
-# pylint: disable=unused-variable
-__all__ = ["gen_c_validate"]
-
-
-def should_save_offset(parent: idl.Struct, child: idl.StructMember) -> bool:
- if child.val or child.max or isinstance(child.typ, idl.Bitfield):
- return True
- for sibling in parent.members:
- if sibling.val:
- for tok in sibling.val.tokens:
- if isinstance(tok, idl.ExprOff) and tok.membname == child.membname:
- return True
- if sibling.max:
- for tok in sibling.max.tokens:
- if isinstance(tok, idl.ExprOff) and tok.membname == child.membname:
- return True
- return False
-
-
-def should_save_end_offset(struct: idl.Struct) -> bool:
- for memb in struct.members:
- if memb.val:
- for tok in memb.val.tokens:
- if isinstance(tok, idl.ExprSym) and tok.symname == "end":
- return True
- if memb.max:
- for tok in memb.max.tokens:
- if isinstance(tok, idl.ExprSym) and tok.symname == "end":
- return True
- return False
-
-
-def gen_c_validate(versions: set[str], typs: list[idl.UserType]) -> str:
- ret = """
-/* validate_* *****************************************************************/
-
-"""
- ret += cutil.macro(
- "#define VALIDATE_NET_BYTES(n)\n"
- "\tif (__builtin_add_overflow(net_offset, n, &net_offset))\n"
- "\t\t/* If needed-net-size overflowed uint32_t, then\n"
- "\t\t * there's no way that actual-net-size will live up to\n"
- "\t\t * that. */\n"
- '\t\treturn lib9p_error(ctx, LINUX_EBADMSG, "message is too short for content");\n'
- "\tif (net_offset > net_size)\n"
- '\t\treturn lib9p_errorf(ctx, LINUX_EBADMSG, "message is too short for content (%"PRIu32" > %"PRIu32") @ %d", net_offset, net_size, __LINE__);\n'
- )
- ret += cutil.macro(
- "#define VALIDATE_NET_UTF8(n)\n"
- "\t{\n"
- "\t\tsize_t len = n;\n"
- "\t\tVALIDATE_NET_BYTES(len);\n"
- "\t\tif (!is_valid_utf8_without_nul(&net_bytes[net_offset-len], len))\n"
- '\t\t\treturn lib9p_error(ctx, LINUX_EBADMSG, "message contains invalid UTF-8");\n'
- "\t}\n"
- )
- ret += cutil.macro(
- "#define RESERVE_HOST_BYTES(n)\n"
- "\tif (__builtin_add_overflow(host_size, n, &host_size))\n"
- "\t\t/* If needed-host-size overflowed ssize_t, then there's\n"
- "\t\t * no way that actual-net-size will live up to\n"
- "\t\t * that. */\n"
- '\t\treturn lib9p_error(ctx, LINUX_EBADMSG, "message is too short for content");\n'
- )
-
- ret += "#define GET_U8LE(off) (net_bytes[off])\n"
- ret += "#define GET_U16LE(off) uint16le_decode(&net_bytes[off])\n"
- ret += "#define GET_U32LE(off) uint32le_decode(&net_bytes[off])\n"
- ret += "#define GET_U64LE(off) uint64le_decode(&net_bytes[off])\n"
-
- ret += "#define LAST_U8LE() GET_U8LE(net_offset-1)\n"
- ret += "#define LAST_U16LE() GET_U16LE(net_offset-2)\n"
- ret += "#define LAST_U32LE() GET_U32LE(net_offset-4)\n"
- ret += "#define LAST_U64LE() GET_U64LE(net_offset-8)\n"
-
- class IndentLevel(typing.NamedTuple):
- ifdef: bool # whether this is both `{` and `#if`, or just `{`
-
- indent_stack: list[IndentLevel]
-
- def ifdef_lvl() -> int:
- return sum(1 if lvl.ifdef else 0 for lvl in indent_stack)
-
- def indent_lvl() -> int:
- return len(indent_stack)
-
- incr_buf: int
-
- def incr_flush() -> None:
- nonlocal ret
- nonlocal incr_buf
- if incr_buf:
- ret += f"{'\t'*indent_lvl()}VALIDATE_NET_BYTES({incr_buf});\n"
- incr_buf = 0
-
- def gen_validate_size(path: idlutil.Path) -> None:
- nonlocal ret
- nonlocal incr_buf
- nonlocal indent_stack
-
- assert path.elems
- child = path.elems[-1]
- parent = path.elems[-2].typ if len(path.elems) > 1 else path.root
- assert isinstance(parent, idl.Struct)
-
- if child.in_versions < parent.in_versions:
- if line := cutil.ifdef_push(
- ifdef_lvl() + 1, c9util.ver_ifdef(child.in_versions)
- ):
- incr_flush()
- ret += line
- ret += (
- f"{'\t'*indent_lvl()}if ({c9util.ver_cond(child.in_versions)}) {{\n"
- )
- indent_stack.append(IndentLevel(ifdef=True))
- if should_save_offset(parent, child):
- ret += f"{'\t'*indent_lvl()}uint32_t offsetof{''.join('_'+m.membname for m in path.elems)} = net_offset + {incr_buf};\n"
- if child.cnt:
- if isinstance(child.cnt, int):
- cnt_str = str(child.cnt)
- cnt_typ = "size_t"
- else:
- assert child.cnt.typ.static_size
- incr_flush()
- cnt_str = f"LAST_U{child.cnt.typ.static_size*8}LE()"
- cnt_typ = c9util.typename(child.cnt.typ)
- if child.membname == "utf8": # SPECIAL (string)
- assert child.typ.static_size == 1
- # Yes, this is content-validation and "belongs" in
- # gen_validate_content(), not here. But it's just
- # easier this way.
- incr_flush()
- ret += f"{'\t'*indent_lvl()}VALIDATE_NET_UTF8({cnt_str});\n"
- return
- if child.typ.static_size == 1: # SPECIAL (zerocopy)
- if isinstance(child.cnt, int):
- incr_buf += child.cnt
- return
- incr_flush()
- ret += f"{'\t'*indent_lvl()}VALIDATE_NET_BYTES({cnt_str});\n"
- return
- loopdepth = sum(1 for elem in path.elems if elem.cnt)
- loopvar = chr(ord("i") + loopdepth - 1)
- incr_flush()
- ret += f"{'\t'*indent_lvl()}for ({cnt_typ} {loopvar} = 0, cnt = {cnt_str}; {loopvar} < cnt; {loopvar}++) {{\n"
- indent_stack.append(IndentLevel(ifdef=False))
- ret += f"{'\t'*indent_lvl()}RESERVE_HOST_BYTES(sizeof({c9util.typename(child.typ)}));\n"
- if not isinstance(child.typ, idl.Struct):
- incr_buf += child.typ.static_size
-
- def gen_validate_content(path: idlutil.Path) -> None:
- nonlocal ret
- nonlocal incr_buf
- nonlocal indent_stack
-
- assert path.elems
- child = path.elems[-1]
- parent = path.elems[-2].typ if len(path.elems) > 1 else path.root
- assert isinstance(parent, idl.Struct)
-
- def lookup_sym(sym: str) -> str:
- if sym.startswith("&"):
- sym = sym[1:]
- return f"offsetof{''.join('_'+m.membname for m in path.elems[:-1])}_{sym}"
-
- if child.val:
- incr_flush()
- assert child.typ.static_size
- nbits = child.typ.static_size * 8
- nbits = child.typ.static_size * 8
- if nbits < 32 and any(
- isinstance(tok, idl.ExprSym)
- and (tok.symname == "end" or tok.symname.startswith("&"))
- for tok in child.val.tokens
- ):
- nbits = 32
- act = f"(uint{nbits}_t)GET_U{nbits}LE({lookup_sym(f'&{child.membname}')})"
- exp = f"(uint{nbits}_t)({c9util.idl_expr(child.val, lookup_sym)})"
- ret += f"{'\t'*indent_lvl()}if ({act} != {exp})\n"
- ret += f'{"\t"*(indent_lvl()+1)}return lib9p_errorf(ctx, LINUX_EBADMSG, "{path} value is wrong: actual: %"PRIu{nbits}" != correct:%"PRIu{nbits},\n'
- ret += f"{'\t'*(indent_lvl()+2)}{act}, {exp});\n"
- if child.max:
- incr_flush()
- assert child.typ.static_size
- nbits = child.typ.static_size * 8
- if nbits < 32 and any(
- isinstance(tok, idl.ExprSym)
- and (tok.symname == "end" or tok.symname.startswith("&"))
- for tok in child.max.tokens
- ):
- nbits = 32
- act = f"(uint{nbits}_t)GET_U{nbits}LE({lookup_sym(f'&{child.membname}')})"
- exp = f"(uint{nbits}_t)({c9util.idl_expr(child.max, lookup_sym)})"
- ret += f"{'\t'*indent_lvl()}if ({act} > {exp})\n"
- ret += f'{"\t"*(indent_lvl()+1)}return lib9p_errorf(ctx, LINUX_EBADMSG, "{path} value is too large: %"PRIu{nbits}" > %"PRIu{nbits},\n'
- ret += f"{'\t'*(indent_lvl()+2)}{act}, {exp});\n"
- if isinstance(child.typ, idl.Bitfield):
- incr_flush()
- nbytes = child.typ.static_size
- nbits = nbytes * 8
- act = f"GET_U{nbits}LE({lookup_sym(f'&{child.membname}')})"
- ret += f"{'\t'*indent_lvl()}if ({act} & ~{child.typ.typname}_masks[ctx->version])\n"
- ret += f'{"\t"*(indent_lvl()+1)}return lib9p_errorf(ctx, LINUX_EBADMSG, "unknown bits in {child.typ.typname} bitfield: %#0{nbytes*2}"PRIx{nbits},\n'
- ret += f"{'\t'*(indent_lvl()+2)}{act} & ~{child.typ.typname}_masks[ctx->version]);\n"
-
- def handle(
- path: idlutil.Path,
- ) -> tuple[idlutil.WalkCmd, typing.Callable[[], None]]:
- nonlocal ret
- nonlocal incr_buf
- nonlocal indent_stack
- indent_stack_len = len(indent_stack)
- pop_struct = path.elems[-1].typ if path.elems else path.root
- pop_path = path
- pop_indent_stack_len: int
-
- def pop() -> None:
- nonlocal ret
- nonlocal indent_stack
- nonlocal indent_stack_len
- nonlocal pop_struct
- nonlocal pop_path
- nonlocal pop_indent_stack_len
- if isinstance(pop_struct, idl.Struct):
- while len(indent_stack) > pop_indent_stack_len:
- incr_flush()
- ret += f"{'\t'*(indent_lvl()-1)}}}\n"
- if indent_stack.pop().ifdef:
- ret += cutil.ifdef_pop(ifdef_lvl())
- parent = pop_struct
- path = pop_path
- if should_save_end_offset(parent):
- ret += f"{'\t'*indent_lvl()}uint32_t offsetof{''.join('_'+m.membname for m in path.elems)}_end = net_offset + {incr_buf};\n"
- for child in parent.members:
- gen_validate_content(pop_path.add(child))
- while len(indent_stack) > indent_stack_len:
- if len(indent_stack) == indent_stack_len + 1 and indent_stack[-1].ifdef:
- break
- incr_flush()
- ret += f"{'\t'*(indent_lvl()-1)}}}\n"
- if indent_stack.pop().ifdef:
- ret += cutil.ifdef_pop(ifdef_lvl())
-
- if path.elems:
- gen_validate_size(path)
-
- pop_indent_stack_len = len(indent_stack)
-
- return idlutil.WalkCmd.KEEP_GOING, pop
-
- for typ in typs:
- if not (
- isinstance(typ, idl.Message) or typ.typname == "stat"
- ): # SPECIAL (include stat)
- continue
- assert isinstance(typ, idl.Struct)
- ret += "\n"
- ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions))
- if typ.typname == "stat": # SPECIAL (stat)
- ret += f"static ssize_t validate_{typ.typname}(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes, uint32_t *ret_net_size) {{\n"
- else:
- ret += f"static ssize_t validate_{typ.typname}(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) {{\n"
-
- ret += "\tuint32_t net_offset = 0;\n"
- ret += f"\tssize_t host_size = sizeof({c9util.typename(typ)});\n"
-
- incr_buf = 0
- indent_stack = [IndentLevel(ifdef=True)]
- idlutil.walk(typ, handle)
- while len(indent_stack) > 1:
- incr_flush()
- ret += f"{'\t'*(indent_lvl()-1)}}}\n"
- if indent_stack.pop().ifdef:
- ret += cutil.ifdef_pop(ifdef_lvl())
-
- incr_flush()
- if typ.typname == "stat": # SPECIAL (stat)
- ret += "\tif (ret_net_size)\n"
- ret += "\t\t*ret_net_size = net_offset;\n"
- ret += "\treturn (ssize_t)host_size;\n"
- ret += "}\n"
- ret += cutil.ifdef_pop(0)
- return ret
diff --git a/lib9p/protogen/cutil.py b/lib9p/protogen/cutil.py
deleted file mode 100644
index 8df6db9..0000000
--- a/lib9p/protogen/cutil.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# lib9p/protogen/cutil.py - Utilities for generating C code
-#
-# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
-# SPDX-License-Identifier: AGPL-3.0-or-later
-
-# pylint: disable=unused-variable
-__all__ = [
- "UINT32_MAX",
- "UINT64_MAX",
- "macro",
- "ifdef_init",
- "ifdef_push",
- "ifdef_pop",
- "ifdef_leaf_is_noop",
-]
-
-UINT32_MAX = (1 << 32) - 1
-UINT64_MAX = (1 << 64) - 1
-
-
-def tab_ljust(s: str, width: int) -> str:
- cur = len(s.expandtabs(tabsize=8))
- if cur >= width:
- return s
- return s + " " * (width - cur)
-
-
-def macro(full: str) -> str:
- full = full.rstrip()
- assert "\n" in full
- lines = [l.rstrip() for l in full.split("\n")]
- width = max(len(l.expandtabs(tabsize=8)) for l in lines[:-1])
- lines = [tab_ljust(l, width) for l in lines]
- return " \\\n".join(lines).rstrip() + "\n"
-
-
-_ifdef_stack: list[str | None] = []
-
-
-def ifdef_init() -> None:
- global _ifdef_stack
- _ifdef_stack = []
-
-
-def ifdef_push(n: int, _newval: str) -> str:
- # Grow the stack as needed
- while len(_ifdef_stack) < n:
- _ifdef_stack.append(None)
-
- # Set some variables
- parentval: str | None = None
- for x in _ifdef_stack[:-1]:
- if x is not None:
- parentval = x
- oldval = _ifdef_stack[-1]
- newval: str | None = _newval
- if newval == parentval:
- newval = None
-
- # Put newval on the stack.
- _ifdef_stack[-1] = newval
-
- # Build output.
- ret = ""
- if newval != oldval:
- if oldval is not None:
- ret += f"#endif /* {oldval} */\n"
- if newval is not None:
- ret += f"#if {newval}\n"
- return ret
-
-
-def ifdef_pop(n: int) -> str:
- global _ifdef_stack
- ret = ""
- while len(_ifdef_stack) > n:
- if _ifdef_stack[-1] is not None:
- ret += f"#endif /* {_ifdef_stack[-1]} */\n"
- _ifdef_stack = _ifdef_stack[:-1]
- return ret
-
-
-def ifdef_leaf_is_noop() -> bool:
- return not _ifdef_stack[-1]
diff --git a/lib9p/protogen/h.py b/lib9p/protogen/h.py
deleted file mode 100644
index 3b33419..0000000
--- a/lib9p/protogen/h.py
+++ /dev/null
@@ -1,535 +0,0 @@
-# lib9p/protogen/h.py - Generate 9p.generated.h
-#
-# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
-# SPDX-License-Identifier: AGPL-3.0-or-later
-
-import sys
-import typing
-
-import idl
-
-from . import c9util, cutil, idlutil
-
-# This strives to be "general-purpose" in that it just acts on the
-# *.9p inputs; but (unfortunately?) there are a few special-cases in
-# this script, marked with "SPECIAL".
-
-# pylint: disable=unused-variable
-__all__ = ["gen_h"]
-
-# get_buffer_size() ############################################################
-
-
-class BufferSize(typing.NamedTuple):
- min_size: int # really just here to sanity-check against typ.min_size(version)
- exp_size: int # "expected" or max-reasonable size
- max_size: int # really just here to sanity-check against typ.max_size(version)
- max_copy: int
- max_copy_extra: str
- max_iov: int
- max_iov_extra: str
-
-
-class TmpBufferSize:
- min_size: int
- exp_size: int
- max_size: int
- max_copy: int
- max_copy_extra: str
- max_iov: int
- max_iov_extra: str
-
- tmp_starts_with_copy: bool
- tmp_ends_with_copy: bool
-
- def __init__(self) -> None:
- self.min_size = 0
- self.exp_size = 0
- self.max_size = 0
- self.max_copy = 0
- self.max_copy_extra = ""
- self.max_iov = 0
- self.max_iov_extra = ""
- self.tmp_starts_with_copy = False
- self.tmp_ends_with_copy = False
-
-
-def _get_buffer_size(typ: idl.Type, version: str) -> TmpBufferSize:
- assert isinstance(typ, idl.Primitive) or (version in typ.in_versions)
-
- ret = TmpBufferSize()
-
- if not isinstance(typ, idl.Struct):
- assert typ.static_size
- ret.min_size = typ.static_size
- ret.exp_size = typ.static_size
- ret.max_size = typ.static_size
- ret.max_copy = typ.static_size
- ret.max_iov = 1
- ret.tmp_starts_with_copy = True
- ret.tmp_ends_with_copy = True
- return ret
-
- def handle(path: idlutil.Path) -> tuple[idlutil.WalkCmd, None]:
- nonlocal ret
- if path.elems:
- child = path.elems[-1]
- if version not in child.in_versions:
- return idlutil.WalkCmd.DONT_RECURSE, None
- if child.cnt:
- if child.typ.static_size == 1: # SPECIAL (zerocopy)
- ret.max_iov += 1
- # HEURISTIC: 27 for strings (max-strlen from 9P1), 8KiB for other data
- ret.exp_size += 27 if child.membname == "utf8" else 8192
- ret.max_size += child.max_cnt
- ret.tmp_ends_with_copy = False
- return idlutil.WalkCmd.DONT_RECURSE, None
- sub = _get_buffer_size(child.typ, version)
- ret.exp_size += sub.exp_size * 16 # HEURISTIC: MAXWELEM
- ret.max_size += sub.max_size * child.max_cnt
- if child.membname == "wname" and path.root.typname in (
- "Tsread",
- "Tswrite",
- ): # SPECIAL (9P2000.e)
- assert ret.tmp_ends_with_copy
- assert sub.tmp_starts_with_copy
- assert not sub.tmp_ends_with_copy
- ret.max_copy_extra = (
- f" + (CONFIG_9P_MAX_9P2000_e_WELEM * {sub.max_copy})"
- )
- ret.max_iov_extra = (
- f" + (CONFIG_9P_MAX_9P2000_e_WELEM * {sub.max_iov})"
- )
- ret.max_iov -= 1
- else:
- ret.max_copy += sub.max_copy * child.max_cnt
- if sub.max_iov == 1 and sub.tmp_starts_with_copy: # is purely copy
- ret.max_iov += 1
- else: # contains zero-copy segments
- ret.max_iov += sub.max_iov * child.max_cnt
- if ret.tmp_ends_with_copy and sub.tmp_starts_with_copy:
- # we can merge this one
- ret.max_iov -= 1
- if (
- sub.tmp_ends_with_copy
- and sub.tmp_starts_with_copy
- and sub.max_iov > 1
- ):
- # we can merge these
- ret.max_iov -= child.max_cnt - 1
- ret.tmp_ends_with_copy = sub.tmp_ends_with_copy
- return idlutil.WalkCmd.DONT_RECURSE, None
- if not isinstance(child.typ, idl.Struct):
- assert child.typ.static_size
- if not ret.tmp_ends_with_copy:
- if ret.max_size == 0:
- ret.tmp_starts_with_copy = True
- ret.max_iov += 1
- ret.tmp_ends_with_copy = True
- ret.min_size += child.typ.static_size
- ret.exp_size += child.typ.static_size
- ret.max_size += child.typ.static_size
- ret.max_copy += child.typ.static_size
- return idlutil.WalkCmd.KEEP_GOING, None
-
- idlutil.walk(typ, handle)
- assert ret.min_size == typ.min_size(version)
- assert ret.max_size == typ.max_size(version)
- return ret
-
-
-def get_buffer_size(typ: idl.Type, version: str) -> BufferSize:
- tmp = _get_buffer_size(typ, version)
- return BufferSize(
- min_size=tmp.min_size,
- exp_size=tmp.exp_size,
- max_size=tmp.max_size,
- max_copy=tmp.max_copy,
- max_copy_extra=tmp.max_copy_extra,
- max_iov=tmp.max_iov,
- max_iov_extra=tmp.max_iov_extra,
- )
-
-
-# Generate .h ##################################################################
-
-
-def gen_h(versions: set[str], typs: list[idl.UserType]) -> str:
- cutil.ifdef_init()
-
- ret = f"""/* Generated by `{' '.join(sys.argv)}`. DO NOT EDIT! */
-
-#ifndef _LIB9P_9P_H_
-\t#error Do not include <lib9p/9p.generated.h> directly; include <lib9p/9p.h> instead
-#endif
-
-#include <stdint.h> /* for uint{{n}}_t types */
-
-#include <libfmt/fmt.h> /* for fmt_formatter */
-#include <libhw/generic/net.h> /* for struct iovec */
-"""
-
- id2typ: dict[int, idl.Message] = {}
- for msg in [msg for msg in typs if isinstance(msg, idl.Message)]:
- id2typ[msg.msgid] = msg
-
- ret += """
-/* config *********************************************************************/
-
-#include "config.h"
-"""
- for ver in sorted(versions):
- ret += "\n"
- ret += f"#ifndef {c9util.ver_ifdef({ver})}\n"
- ret += f"\t#error config.h must define {c9util.ver_ifdef({ver})}\n"
- if ver == "9P2000.e": # SPECIAL (9P2000.e)
- ret += "#else\n"
- ret += f"\t#if {c9util.ver_ifdef({ver})}\n"
- ret += "\t\t#ifndef CONFIG_9P_MAX_9P2000_e_WELEM\n"
- ret += f"\t\t\t#error if {c9util.ver_ifdef({ver})} then config.h must define CONFIG_9P_MAX_9P2000_e_WELEM\n"
- ret += "\t\t#endif\n"
- ret += "\t\tstatic_assert(CONFIG_9P_MAX_9P2000_e_WELEM > 0);\n"
- ret += "\t#endif\n"
- ret += "#endif\n"
-
- ret += f"""
-/* enum version ***************************************************************/
-
-enum {c9util.ident('version')} {{
-"""
- fullversions = ["unknown = 0", *sorted(versions)]
- verwidth = max(len(v) for v in fullversions)
- for ver in fullversions:
- if ver in versions:
- ret += cutil.ifdef_push(1, c9util.ver_ifdef({ver}))
- ret += f"\t{c9util.ver_enum(ver)},"
- ret += (" " * (verwidth - len(ver))) + ' /* "' + ver.split()[0] + '" */\n'
- ret += cutil.ifdef_pop(0)
- ret += f"\t{c9util.ver_enum('NUM')},\n"
- ret += "};\n"
- ret += f"LO_IMPLEMENTATION_H(fmt_formatter, enum {c9util.ident('version')}, {c9util.ident('version')});\n"
-
- ret += """
-/* enum msg_type **************************************************************/
-
-"""
- ret += f"enum {c9util.ident('msg_type')} {{ /* uint8_t */\n"
- namewidth = max(len(msg.typname) for msg in typs if isinstance(msg, idl.Message))
- for n in range(0x100):
- if n not in id2typ:
- continue
- msg = id2typ[n]
- ret += cutil.ifdef_push(1, c9util.ver_ifdef(msg.in_versions))
- ret += f"\t{c9util.Ident(f'TYP_{msg.typname:<{namewidth}}')} = {msg.msgid},\n"
- ret += cutil.ifdef_pop(0)
- ret += "};\n"
- ret += f"LO_IMPLEMENTATION_H(fmt_formatter, enum {c9util.ident('msg_type')}, {c9util.ident('msg_type')});\n"
-
- ret += """
-/* payload types **************************************************************/
-"""
-
- def per_version_comment(
- typ: idl.UserType, fn: typing.Callable[[idl.UserType, str], str]
- ) -> str:
- lines: dict[str, str] = {}
- for version in sorted(typ.in_versions):
- lines[version] = fn(typ, version)
- if len(set(lines.values())) == 1:
- for _, line in lines.items():
- return f"/* {line} */\n"
- assert False
- else:
- ret = ""
- v_width = max(len(c9util.ver_enum(v)) for v in typ.in_versions)
- for version, line in lines.items():
- ret += f"/* {c9util.ver_enum(version):<{v_width}}: {line} */\n"
- return ret
-
- for typ in idlutil.topo_sorted(typs):
- ret += "\n"
- ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions))
-
- def sum_size(typ: idl.UserType, version: str) -> str:
- sz = get_buffer_size(typ, version)
- assert (
- sz.min_size <= sz.exp_size
- and sz.exp_size <= sz.max_size
- and sz.max_size < cutil.UINT64_MAX
- )
- ret = ""
- if sz.min_size == sz.max_size:
- ret += f"size = {sz.min_size:,}"
- else:
- ret += f"min_size = {sz.min_size:,} ; exp_size = {sz.exp_size:,} ; max_size = {sz.max_size:,}"
- if sz.max_size > cutil.UINT32_MAX:
- ret += " (warning: >UINT32_MAX)"
- ret += f" ; max_iov = {sz.max_iov:,}{sz.max_iov_extra} ; max_copy = {sz.max_copy:,}{sz.max_copy_extra}"
- return ret
-
- ret += per_version_comment(typ, sum_size)
-
- match typ:
- case idl.Number():
- ret += gen_number(typ)
- case idl.Bitfield():
- ret += gen_bitfield(typ)
- case idl.Struct(): # and idl.Message():
- ret += gen_struct(typ)
- ret += cutil.ifdef_pop(0)
-
- ret += """
-/* containers *****************************************************************/
-"""
- ret += "\n"
- ret += f"#define {c9util.IDENT('_MAX')}(a, b) ((a) > (b)) ? (a) : (b)\n"
-
- tmsg_max_iov: dict[str, int] = {}
- tmsg_max_copy: dict[str, int] = {}
- rmsg_max_iov: dict[str, int] = {}
- rmsg_max_copy: dict[str, int] = {}
- for typ in typs:
- if not isinstance(typ, idl.Message):
- continue
- if typ.typname in ("Tsread", "Tswrite"): # SPECIAL (9P2000.e)
- continue
- max_iov = tmsg_max_iov if typ.msgid % 2 == 0 else rmsg_max_iov
- max_copy = tmsg_max_copy if typ.msgid % 2 == 0 else rmsg_max_copy
- for version in typ.in_versions:
- if version not in max_iov:
- max_iov[version] = 0
- max_copy[version] = 0
- sz = get_buffer_size(typ, version)
- if sz.max_iov > max_iov[version]:
- max_iov[version] = sz.max_iov
- if sz.max_copy > max_copy[version]:
- max_copy[version] = sz.max_copy
-
- for name, table in [
- ("tmsg_max_iov", tmsg_max_iov),
- ("tmsg_max_copy", tmsg_max_copy),
- ("rmsg_max_iov", rmsg_max_iov),
- ("rmsg_max_copy", rmsg_max_copy),
- ]:
- inv: dict[int, set[str]] = {}
- for version, maxval in table.items():
- if maxval not in inv:
- inv[maxval] = set()
- inv[maxval].add(version)
-
- ret += "\n"
- directive = "if"
- seen_e = False # SPECIAL (9P2000.e)
- for maxval in sorted(inv, reverse=True):
- ret += f"#{directive} {c9util.ver_ifdef(inv[maxval])}\n"
- indent = 1
- if name.startswith("tmsg") and not seen_e: # SPECIAL (9P2000.e)
- typ = next(typ for typ in typs if typ.typname == "Tswrite")
- sz = get_buffer_size(typ, "9P2000.e")
- match name:
- case "tmsg_max_iov":
- maxexpr = f"{sz.max_iov}{sz.max_iov_extra}"
- case "tmsg_max_copy":
- maxexpr = f"{sz.max_copy}{sz.max_copy_extra}"
- case _:
- assert False
- ret += f"\t#if {c9util.ver_ifdef({"9P2000.e"})}\n"
- ret += f"\t\t#define {c9util.IDENT(name)} {c9util.IDENT('_MAX')}({maxval}, {maxexpr})\n"
- ret += "\t#else\n"
- indent += 1
- ret += f"{'\t'*indent}#define {c9util.IDENT(name)} {maxval}\n"
- if name.startswith("tmsg") and not seen_e: # SPECIAL (9P2000.e)
- ret += "\t#endif\n"
- if "9P2000.e" in inv[maxval]:
- seen_e = True
- directive = "elif"
- ret += "#endif\n"
-
- ret += "\n"
- ret += f"struct {c9util.ident('Tmsg_send_buf')} {{\n"
- ret += "\tsize_t iov_cnt;\n"
- ret += f"\tstruct iovec iov[{c9util.IDENT('TMSG_MAX_IOV')}];\n"
- ret += f"\tuint8_t copied[{c9util.IDENT('TMSG_MAX_COPY')}];\n"
- ret += "};\n"
-
- ret += "\n"
- ret += f"struct {c9util.ident('Rmsg_send_buf')} {{\n"
- ret += "\tsize_t iov_cnt;\n"
- ret += f"\tstruct iovec iov[{c9util.IDENT('RMSG_MAX_IOV')}];\n"
- ret += f"\tuint8_t copied[{c9util.IDENT('RMSG_MAX_COPY')}];\n"
- ret += "};\n"
-
- return ret
-
-
-def gen_number(typ: idl.Number) -> str:
- ret = f"typedef {c9util.typename(typ.prim)} {c9util.typename(typ)};\n"
- ret += f"LO_IMPLEMENTATION_H(fmt_formatter, {c9util.typename(typ)}, {c9util.basename(typ)});\n"
-
- def lookup_sym(sym: str) -> str:
- assert False
-
- def cname(base: str) -> str:
- prefix = f"{typ.typname}_".upper()
- return c9util.Ident(c9util.add_prefix(prefix, base))
-
- namewidth = max(len(cname(name)) for name in typ.vals)
- for name, val in typ.vals.items():
- c_name = cname(name)
- c_val = c9util.idl_expr(val, lookup_sym)
- ret += f"#define {c_name:<{namewidth}} (({c9util.typename(typ)})({c_val}))\n"
- return ret
-
-
-def gen_bitfield(typ: idl.Bitfield) -> str:
- ret = f"typedef {c9util.typename(typ.prim)} {c9util.typename(typ)};\n"
- ret += f"LO_IMPLEMENTATION_H(fmt_formatter, {c9util.typename(typ)}, {c9util.basename(typ)});\n"
-
- def lookup_sym(sym: str) -> str:
- assert False
-
- # There are 4 parts here: bits, aliases, masks, and numbers.
-
- # 1. bits
-
- def bitname(bit: idl.Bit) -> str:
- prefix = f"{typ.typname}_".upper()
- base = bit.bitname
- match bit:
- case idl.Bit(cat="RESERVED"):
- base = "_RESERVED_" + base
- case idl.Bit(cat=idl.BitNum()):
- base += "_*"
- case idl.Bit(cat="UNUSED"):
- base = f"_UNUSED_{bit.num}"
- return c9util.Ident(c9util.add_prefix(prefix, base))
-
- namewidth = max(len(bitname(bit)) for bit in typ.bits)
-
- ret += "/* bits */\n"
- for bit in reversed(typ.bits):
- vers = bit.in_versions
- if bit.cat == "UNUSED":
- vers = typ.in_versions
- ret += cutil.ifdef_push(2, c9util.ver_ifdef(vers))
-
- # It is important all of the `beg` strings have
- # the same length.
- end = ""
- match bit.cat:
- case "USED" | "RESERVED" | "UNUSED":
- if cutil.ifdef_leaf_is_noop():
- beg = "#define "
- else:
- beg = "# define"
- case idl.BitNum():
- beg = "/* number"
- end = " */"
-
- c_name = bitname(bit)
- c_val = f"UINT{typ.static_size*8}_C(1)<<{bit.num}"
- ret += (
- f"{beg} {c_name:<{namewidth}} (({c9util.typename(typ)})({c_val})){end}\n"
- )
- ret += cutil.ifdef_pop(1)
-
- # 2. aliases
- if typ.aliases:
-
- def aliasname(alias: idl.BitAlias) -> str:
- prefix = f"{typ.typname}_".upper()
- base = alias.bitname
- return c9util.Ident(c9util.add_prefix(prefix, base))
-
- ret += "/* aliases */\n"
- for alias in typ.aliases.values():
- ret += cutil.ifdef_push(2, c9util.ver_ifdef(alias.in_versions))
-
- end = ""
- if cutil.ifdef_leaf_is_noop():
- beg = "#define "
- else:
- beg = "# define"
-
- c_name = aliasname(alias)
- c_val = c9util.idl_expr(alias.val, lookup_sym)
- ret += f"{beg} {c_name:<{namewidth}} (({c9util.typename(typ)})({c_val})){end}\n"
-
- ret += cutil.ifdef_pop(1)
-
- # 3. masks
- if typ.masks:
-
- def maskname(mask: idl.BitAlias) -> str:
- prefix = f"{typ.typname}_".upper()
- base = mask.bitname
- return c9util.Ident(c9util.add_prefix(prefix, base) + "_MASK")
-
- ret += "/* masks */\n"
- for mask in typ.masks.values():
- ret += cutil.ifdef_push(2, c9util.ver_ifdef(mask.in_versions))
-
- end = ""
- if cutil.ifdef_leaf_is_noop():
- beg = "#define "
- else:
- beg = "# define"
-
- c_name = maskname(mask)
- c_val = c9util.idl_expr(mask.val, lookup_sym, bitwidth=typ.static_size * 8)
- ret += f"{beg} {c_name:<{namewidth}} (({c9util.typename(typ)})({c_val})){end}\n"
-
- ret += cutil.ifdef_pop(1)
-
- # 4. numbers
- def numname(num: idl.BitNum, base: str) -> str:
- prefix = f"{typ.typname}_{num.numname}_".upper()
- return c9util.Ident(c9util.add_prefix(prefix, base))
-
- for num in typ.nums.values():
- namewidth = max(
- len(numname(num, base))
- for base in [
- *[alias.bitname for alias in num.vals.values()],
- "MASK",
- ]
- )
- ret += f"/* number: {num.numname} */\n"
- for alias in num.vals.values():
- ret += cutil.ifdef_push(2, c9util.ver_ifdef(alias.in_versions))
-
- end = ""
- if cutil.ifdef_leaf_is_noop():
- beg = "#define "
- else:
- beg = "# define"
-
- c_name = numname(num, alias.bitname)
- c_val = c9util.idl_expr(alias.val, lookup_sym)
- ret += f"{beg} {c_name:<{namewidth}} (({c9util.typename(typ)})({c_val})){end}\n"
- ret += cutil.ifdef_pop(1)
- c_name = numname(num, "MASK")
- c_val = f"{num.mask:#0{typ.static_size*8}b}"
- ret += (
- f"{beg} {c_name:<{namewidth}} (({c9util.typename(typ)})({c_val})){end}\n"
- )
-
- return ret
-
-
-def gen_struct(typ: idl.Struct) -> str: # and idl.Message
- ret = c9util.typename(typ) + " {"
- if typ.members:
- ret += "\n"
-
- typewidth = max(len(c9util.typename(m.typ, m)) for m in typ.members)
-
- for member in typ.members:
- if member.val:
- continue
- ret += cutil.ifdef_push(2, c9util.ver_ifdef(member.in_versions))
- ret += f"\t{c9util.typename(member.typ, member):<{typewidth}} {'*' if member.cnt else ' '}{member.membname};\n"
- ret += cutil.ifdef_pop(1)
- ret += "};\n"
- ret += f"LO_IMPLEMENTATION_H(fmt_formatter, {c9util.typename(typ)}, {c9util.basename(typ)});\n"
- return ret
diff --git a/lib9p/protogen/idlutil.py b/lib9p/protogen/idlutil.py
deleted file mode 100644
index dc4d012..0000000
--- a/lib9p/protogen/idlutil.py
+++ /dev/null
@@ -1,112 +0,0 @@
-# lib9p/protogen/idlutil.py - Utilities for working with the 9P idl package
-#
-# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
-# SPDX-License-Identifier: AGPL-3.0-or-later
-
-import enum
-import graphlib
-import typing
-
-import idl
-
-# pylint: disable=unused-variable
-__all__ = [
- "topo_sorted",
- "Path",
- "WalkCmd",
- "WalkHandler",
- "walk",
-]
-
-# topo_sorted() ################################################################
-
-
-def topo_sorted(typs: list[idl.UserType]) -> typing.Iterable[idl.UserType]:
- ts: graphlib.TopologicalSorter[idl.UserType] = graphlib.TopologicalSorter()
- for typ in typs:
- match typ:
- case idl.Number():
- ts.add(typ)
- case idl.Bitfield():
- ts.add(typ)
- case idl.Struct(): # and idl.Message():
- deps = [
- member.typ
- for member in typ.members
- if not isinstance(member.typ, idl.Primitive)
- ]
- ts.add(typ, *deps)
- return ts.static_order()
-
-
-# walk() #######################################################################
-
-
-class Path:
- root: idl.Type
- elems: list[idl.StructMember]
-
- def __init__(
- self, root: idl.Type, elems: list[idl.StructMember] | None = None
- ) -> None:
- self.root = root
- self.elems = elems if elems is not None else []
-
- def add(self, elem: idl.StructMember) -> "Path":
- return Path(self.root, self.elems + [elem])
-
- def parent(self) -> "Path":
- return Path(self.root, self.elems[:-1])
-
- def c_str(self, base: str, loopdepth: int = 0) -> str:
- ret = base
- for i, elem in enumerate(self.elems):
- if i > 0:
- ret += "."
- ret += elem.membname
- if elem.cnt:
- ret += f"[{chr(ord('i')+loopdepth)}]"
- loopdepth += 1
- return ret
-
- def __str__(self) -> str:
- return self.c_str(self.root.typname + "->")
-
-
-class WalkCmd(enum.Enum):
- KEEP_GOING = 1
- DONT_RECURSE = 2
- ABORT = 3
-
-
-type WalkHandler = typing.Callable[
- [Path], tuple[WalkCmd, typing.Callable[[], None] | None]
-]
-
-
-def _walk(path: Path, handle: WalkHandler) -> WalkCmd:
- typ = path.elems[-1].typ if path.elems else path.root
-
- ret, atexit = handle(path)
-
- if isinstance(typ, idl.Struct):
- match ret:
- case WalkCmd.KEEP_GOING:
- for member in typ.members:
- if _walk(path.add(member), handle) == WalkCmd.ABORT:
- ret = WalkCmd.ABORT
- break
- case WalkCmd.DONT_RECURSE:
- ret = WalkCmd.KEEP_GOING
- case WalkCmd.ABORT:
- ret = WalkCmd.ABORT
- case _:
- assert False, f"invalid cmd: {ret}"
-
- if atexit:
- atexit()
- return ret
-
-
-def walk(typ: idl.Type, handle: WalkHandler) -> None:
- _walk(Path(typ), handle)