summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke T. Shumaker <lukeshu@lukeshu.com>2025-03-23 00:53:44 -0600
committerLuke T. Shumaker <lukeshu@lukeshu.com>2025-03-23 03:05:06 -0600
commitc1a1f287ed883bed049627da0fd8395197ebf876 (patch)
tree8bc42e9dd28bb7203639bd7bce75b70374a514f7
parent057f17b3f22b8b0112f87b6c3128df6925b8f27a (diff)
lib9p: protogen: pull cutil.py out of __init__.py
-rw-r--r--lib9p/protogen/__init__.py187
-rw-r--r--lib9p/protogen/cutil.py84
2 files changed, 148 insertions, 123 deletions
diff --git a/lib9p/protogen/__init__.py b/lib9p/protogen/__init__.py
index 21b5161..8a9a371 100644
--- a/lib9p/protogen/__init__.py
+++ b/lib9p/protogen/__init__.py
@@ -1,6 +1,5 @@
# lib9p/protogen/__init__.py - Generate C marshalers/unmarshalers for
-# .9p files defining 9P protocol
-# variants.
+# .9p files defining 9P protocol variants
#
# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
# SPDX-License-Identifier: AGPL-3.0-or-later
@@ -13,6 +12,8 @@ import typing
import idl
+from . import 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".
@@ -25,16 +26,6 @@ __all__ = ["main"]
idprefix = "lib9p_"
-u32max = (1 << 32) - 1
-u64max = (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 add_prefix(p: str, s: str) -> str:
if s.startswith("_"):
@@ -42,15 +33,6 @@ def add_prefix(p: str, s: str) -> str:
return p + s
-def c_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"
-
-
def c_ver_enum(ver: str) -> str:
return f"{idprefix.upper()}VER_{ver.replace('.', '_')}"
@@ -105,47 +87,6 @@ def c_expr(expr: idl.Expr, lookup_sym: typing.Callable[[str], str]) -> str:
return " ".join(ret)
-_ifdef_stack: list[str | None] = []
-
-
-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
-
-
# topo_sorted() ################################################################
@@ -378,8 +319,7 @@ def get_buffer_size(typ: idl.Type, version: str) -> BufferSize:
def gen_h(versions: set[str], typs: list[idl.UserType]) -> str:
- global _ifdef_stack
- _ifdef_stack = []
+ cutil.ifdef_init()
ret = f"""/* Generated by `{' '.join(sys.argv)}`. DO NOT EDIT! */
@@ -424,10 +364,10 @@ enum {idprefix}version {{
verwidth = max(len(v) for v in fullversions)
for ver in fullversions:
if ver in versions:
- ret += ifdef_push(1, c_ver_ifdef({ver}))
+ ret += cutil.ifdef_push(1, c_ver_ifdef({ver}))
ret += f"\t{c_ver_enum(ver)},"
ret += (" " * (verwidth - len(ver))) + ' /* "' + ver.split()[0] + '" */\n'
- ret += ifdef_pop(0)
+ ret += cutil.ifdef_pop(0)
ret += f"\t{c_ver_enum('NUM')},\n"
ret += "};\n"
@@ -441,9 +381,9 @@ enum {idprefix}version {{
if n not in id2typ:
continue
msg = id2typ[n]
- ret += ifdef_push(1, c_ver_ifdef(msg.in_versions))
+ ret += cutil.ifdef_push(1, c_ver_ifdef(msg.in_versions))
ret += f"\t{idprefix.upper()}TYP_{msg.typname:<{namewidth}} = {msg.msgid},\n"
- ret += ifdef_pop(0)
+ ret += cutil.ifdef_pop(0)
ret += "};\n"
ret += """
@@ -469,21 +409,21 @@ enum {idprefix}version {{
for typ in topo_sorted(typs):
ret += "\n"
- ret += ifdef_push(1, c_ver_ifdef(typ.in_versions))
+ ret += cutil.ifdef_push(1, c_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 < u64max
+ 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 > u32max:
+ 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
@@ -527,7 +467,7 @@ enum {idprefix}version {{
vers = bit.in_versions
if bit.cat == idl.BitCat.UNUSED:
vers = typ.in_versions
- ret += ifdef_push(2, c_ver_ifdef(vers))
+ ret += cutil.ifdef_push(2, c_ver_ifdef(vers))
# It is important all of the `beg` strings have
# the same length.
@@ -536,10 +476,10 @@ enum {idprefix}version {{
case (
idl.BitCat.USED | idl.BitCat.RESERVED | idl.BitCat.SUBFIELD
):
- if _ifdef_stack[-1]:
- beg = "# define"
- else:
+ if cutil.ifdef_leaf_is_noop():
beg = "#define "
+ else:
+ beg = "# define"
case idl.BitCat.UNUSED:
beg = "/* unused"
end = " */"
@@ -555,18 +495,18 @@ enum {idprefix}version {{
ret += "\n"
for alias in aliases:
- ret += ifdef_push(2, c_ver_ifdef(alias.in_versions))
+ ret += cutil.ifdef_push(2, c_ver_ifdef(alias.in_versions))
end = ""
- if _ifdef_stack[-1]:
- beg = "# define"
- else:
+ if cutil.ifdef_leaf_is_noop():
beg = "#define "
+ else:
+ beg = "# define"
c_name = bitname(alias)
c_val = alias.val
ret += f"{beg} {c_name:<{namewidth}} (({c_typename(typ)})({c_val})){end}\n"
- ret += ifdef_pop(1)
+ ret += cutil.ifdef_pop(1)
del bitname
case idl.Struct(): # and idl.Message():
ret += c_typename(typ) + " {"
@@ -580,12 +520,12 @@ enum {idprefix}version {{
for member in typ.members:
if member.val:
continue
- ret += ifdef_push(2, c_ver_ifdef(member.in_versions))
+ ret += cutil.ifdef_push(2, c_ver_ifdef(member.in_versions))
ret += f"\t{c_typename(member.typ, member):<{typewidth}} {'*' if member.cnt else ' '}{member.membname};\n"
- ret += ifdef_pop(1)
+ ret += cutil.ifdef_pop(1)
ret += "};\n"
del typ
- ret += ifdef_pop(0)
+ ret += cutil.ifdef_pop(0)
ret += """
/* containers *****************************************************************/
@@ -675,8 +615,7 @@ enum {idprefix}version {{
def gen_c(versions: set[str], typs: list[idl.UserType]) -> str:
- global _ifdef_stack
- _ifdef_stack = []
+ cutil.ifdef_init()
ret = f"""/* Generated by `{' '.join(sys.argv)}`. DO NOT EDIT! */
@@ -711,7 +650,7 @@ def gen_c(versions: set[str], typs: list[idl.UserType]) -> str:
ret = f"const {tentry} _{idprefix}table_{grp}_{meth}[{c_ver_enum('NUM')}][{hex(len(range(*rng)))}] = {{\n"
for ver in ["unknown", *sorted(versions)]:
if ver != "unknown":
- ret += ifdef_push(1, c_ver_ifdef({ver}))
+ ret += cutil.ifdef_push(1, c_ver_ifdef({ver}))
ret += f"\t[{c_ver_enum(ver)}] = {{\n"
for n in range(*rng):
xmsg: idl.Message | None = id2typ.get(n, None)
@@ -725,7 +664,7 @@ def gen_c(versions: set[str], typs: list[idl.UserType]) -> str:
if xmsg:
ret += f"\t\t_MSG_{meth.upper()}({xmsg.typname}),\n"
ret += "\t},\n"
- ret += ifdef_pop(0)
+ ret += cutil.ifdef_pop(0)
ret += "};\n"
return ret
@@ -752,9 +691,9 @@ const char *const _{idprefix}table_ver_name[{c_ver_enum('NUM')}] = {{
"""
for ver in ["unknown", *sorted(versions)]:
if ver in versions:
- ret += ifdef_push(1, c_ver_ifdef({ver}))
+ ret += cutil.ifdef_push(1, c_ver_ifdef({ver}))
ret += f'\t[{c_ver_enum(ver)}] = "{ver}",\n'
- ret += ifdef_pop(0)
+ ret += cutil.ifdef_pop(0)
ret += "};\n"
ret += "\n"
@@ -769,11 +708,11 @@ const char *const _{idprefix}table_ver_name[{c_ver_enum('NUM')}] = {{
if not isinstance(typ, idl.Bitfield):
continue
ret += "\n"
- ret += ifdef_push(1, c_ver_ifdef(typ.in_versions))
+ ret += cutil.ifdef_push(1, c_ver_ifdef(typ.in_versions))
ret += f"static const {c_typename(typ)} {typ.typname}_masks[{c_ver_enum('NUM')}] = {{\n"
verwidth = max(len(ver) for ver in versions)
for ver in sorted(versions):
- ret += ifdef_push(2, c_ver_ifdef({ver}))
+ ret += cutil.ifdef_push(2, c_ver_ifdef({ver}))
ret += (
f"\t[{c_ver_enum(ver)}]{' '*(verwidth-len(ver))} = 0b"
+ "".join(
@@ -787,9 +726,9 @@ const char *const _{idprefix}table_ver_name[{c_ver_enum('NUM')}] = {{
)
+ ",\n"
)
- ret += ifdef_pop(1)
+ ret += cutil.ifdef_pop(1)
ret += "};\n"
- ret += ifdef_pop(0)
+ ret += cutil.ifdef_pop(0)
# validate_* ###############################################################
ret += """
@@ -839,7 +778,7 @@ LM_ALWAYS_INLINE static bool validate_8(struct _validate_ctx *ctx) { return _val
inline = "LM_FLATTEN" if isinstance(typ, idl.Message) else "LM_ALWAYS_INLINE"
argfn = unused if (isinstance(typ, idl.Struct) and not typ.members) else used
ret += "\n"
- ret += ifdef_push(1, c_ver_ifdef(typ.in_versions))
+ ret += cutil.ifdef_push(1, c_ver_ifdef(typ.in_versions))
ret += f"{inline} static bool validate_{typ.typname}(struct _validate_ctx *{argfn('ctx')}) {{\n"
match typ:
@@ -865,9 +804,9 @@ LM_ALWAYS_INLINE static bool validate_8(struct _validate_ctx *ctx) { return _val
# Pass 1 - declare value variables
for member in typ.members:
if should_save_value(typ, member):
- ret += ifdef_push(2, c_ver_ifdef(member.in_versions))
+ ret += cutil.ifdef_push(2, c_ver_ifdef(member.in_versions))
ret += f"\t{c_typename(member.typ)} {member.membname};\n"
- ret += ifdef_pop(1)
+ ret += cutil.ifdef_pop(1)
# Pass 2 - declare offset variables
mark_offset: set[str] = set()
@@ -881,7 +820,7 @@ LM_ALWAYS_INLINE static bool validate_8(struct _validate_ctx *ctx) { return _val
# Pass 3 - main pass
ret += "\treturn false\n"
for member in typ.members:
- ret += ifdef_push(2, c_ver_ifdef(member.in_versions))
+ ret += cutil.ifdef_push(2, c_ver_ifdef(member.in_versions))
ret += "\t || "
if member.in_versions != typ.in_versions:
ret += "( " + c_ver_cond(member.in_versions) + " && "
@@ -925,20 +864,20 @@ LM_ALWAYS_INLINE static bool validate_8(struct _validate_ctx *ctx) { return _val
if member.max:
assert member.static_size
nbits = member.static_size * 8
- ret += ifdef_push(2, c_ver_ifdef(member.in_versions))
+ ret += cutil.ifdef_push(2, c_ver_ifdef(member.in_versions))
ret += f"\t || ({{ uint{nbits}_t max = {c_expr(member.max, lookup_sym)}; (((uint{nbits}_t){member.membname}) > max) &&\n"
ret += f'\t lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "{member.membname} value is too large (%"PRIu{nbits}" > %"PRIu{nbits}")", {member.membname}, max); }})\n'
if member.val:
assert member.static_size
nbits = member.static_size * 8
- ret += ifdef_push(2, c_ver_ifdef(member.in_versions))
+ ret += cutil.ifdef_push(2, c_ver_ifdef(member.in_versions))
ret += f"\t || ({{ uint{nbits}_t exp = {c_expr(member.val, lookup_sym)}; (((uint{nbits}_t){member.membname}) != exp) &&\n"
ret += f'\t lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "{member.membname} value is wrong (actual:%"PRIu{nbits}" != correct:%"PRIu{nbits}")", (uint{nbits}_t){member.membname}, exp); }})\n'
- ret += ifdef_pop(1)
+ ret += cutil.ifdef_pop(1)
ret += "\t ;\n"
ret += "}\n"
- ret += ifdef_pop(0)
+ ret += cutil.ifdef_pop(0)
# unmarshal_* ##############################################################
ret += """
@@ -968,7 +907,7 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o
inline = "LM_FLATTEN" if isinstance(typ, idl.Message) else "LM_ALWAYS_INLINE"
argfn = unused if (isinstance(typ, idl.Struct) and not typ.members) else used
ret += "\n"
- ret += ifdef_push(1, c_ver_ifdef(typ.in_versions))
+ ret += cutil.ifdef_push(1, c_ver_ifdef(typ.in_versions))
ret += f"{inline} static void unmarshal_{typ.typname}(struct _unmarshal_ctx *{argfn('ctx')}, {c_typename(typ)} *out) {{\n"
match typ:
case idl.Number():
@@ -979,7 +918,7 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o
ret += "\tmemset(out, 0, sizeof(*out));\n"
for member in typ.members:
- ret += ifdef_push(2, c_ver_ifdef(member.in_versions))
+ ret += cutil.ifdef_push(2, c_ver_ifdef(member.in_versions))
if member.val:
ret += f"\tctx->net_offset += {member.static_size};\n"
continue
@@ -1005,16 +944,16 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o
ret += "\t}\n"
else:
ret += f"unmarshal_{member.typ.typname}(ctx, &out->{member.membname});\n"
- ret += ifdef_pop(1)
+ ret += cutil.ifdef_pop(1)
ret += "}\n"
- ret += ifdef_pop(0)
+ ret += cutil.ifdef_pop(0)
# marshal_* ################################################################
ret += """
/* marshal_* ******************************************************************/
"""
- ret += c_macro(
+ ret += cutil.macro(
"#define MARSHAL_BYTES_ZEROCOPY(ctx, data, len)\n"
"\tif (ctx->net_iov[ctx->net_iov_cnt-1].iov_len)\n"
"\t\tctx->net_iov_cnt++;\n"
@@ -1022,7 +961,7 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o
"\tctx->net_iov[ctx->net_iov_cnt-1].iov_len = len;\n"
"\tctx->net_iov_cnt++;\n"
)
- ret += c_macro(
+ ret += cutil.macro(
"#define MARSHAL_BYTES(ctx, data, len)\n"
"\tif (!ctx->net_iov[ctx->net_iov_cnt-1].iov_base)\n"
"\t\tctx->net_iov[ctx->net_iov_cnt-1].iov_base = &ctx->net_copied[ctx->net_copied_size];\n"
@@ -1030,7 +969,7 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o
"\tctx->net_copied_size += len;\n"
"\tctx->net_iov[ctx->net_iov_cnt-1].iov_len += len;\n"
)
- ret += c_macro(
+ ret += cutil.macro(
"#define MARSHAL_U8LE(ctx, val)\n"
"\tif (!ctx->net_iov[ctx->net_iov_cnt-1].iov_base)\n"
"\t\tctx->net_iov[ctx->net_iov_cnt-1].iov_base = &ctx->net_copied[ctx->net_copied_size];\n"
@@ -1038,7 +977,7 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o
"\tctx->net_copied_size += 1;\n"
"\tctx->net_iov[ctx->net_iov_cnt-1].iov_len += 1;\n"
)
- ret += c_macro(
+ ret += cutil.macro(
"#define MARSHAL_U16LE(ctx, val)\n"
"\tif (!ctx->net_iov[ctx->net_iov_cnt-1].iov_base)\n"
"\t\tctx->net_iov[ctx->net_iov_cnt-1].iov_base = &ctx->net_copied[ctx->net_copied_size];\n"
@@ -1046,7 +985,7 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o
"\tctx->net_copied_size += 2;\n"
"\tctx->net_iov[ctx->net_iov_cnt-1].iov_len += 2;\n"
)
- ret += c_macro(
+ ret += cutil.macro(
"#define MARSHAL_U32LE(ctx, val)\n"
"\tif (!ctx->net_iov[ctx->net_iov_cnt-1].iov_base)\n"
"\t\tctx->net_iov[ctx->net_iov_cnt-1].iov_base = &ctx->net_copied[ctx->net_copied_size];\n"
@@ -1054,7 +993,7 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o
"\tctx->net_copied_size += 4;\n"
"\tctx->net_iov[ctx->net_iov_cnt-1].iov_len += 4;\n"
)
- ret += c_macro(
+ ret += cutil.macro(
"#define MARSHAL_U64LE(ctx, val)\n"
"\tif (!ctx->net_iov[ctx->net_iov_cnt-1].iov_base)\n"
"\t\tctx->net_iov[ctx->net_iov_cnt-1].iov_base = &ctx->net_copied[ctx->net_copied_size];\n"
@@ -1108,11 +1047,11 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o
)
multiline += f"{'\t'*indent_depth}}}\n"
for vers, sub in self.cond.items():
- multiline += ifdef_push(indent_depth + 1, c_ver_ifdef(vers))
+ multiline += cutil.ifdef_push(indent_depth + 1, c_ver_ifdef(vers))
multiline += f"{'\t'*indent_depth}if {c_ver_cond(vers)} {{\n"
multiline += sub.gen_c("", dstvar, root, indent_depth + 1, loop_depth)
multiline += f"{'\t'*indent_depth}}}\n"
- multiline += ifdef_pop(indent_depth)
+ multiline += cutil.ifdef_pop(indent_depth)
if dsttyp:
if not oneline:
oneline.append("0")
@@ -1204,13 +1143,13 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o
continue
assert isinstance(typ, idl.Struct)
ret += "\n"
- ret += ifdef_push(1, c_ver_ifdef(typ.in_versions))
+ ret += cutil.ifdef_push(1, c_ver_ifdef(typ.in_versions))
ret += f"static bool marshal_{typ.typname}(struct _marshal_ctx *ctx, {c_typename(typ)} *val) {{\n"
# Pass 1 - check size
max_size = max(typ.max_size(v) for v in typ.in_versions)
- if max_size > u32max: # SPECIAL (9P2000.e)
+ 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
)
@@ -1247,7 +1186,7 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o
ret += f"{'\t'*(len(stack)-1)}}}\n"
if stack[-1][1]:
ifdef_depth -= 1
- ret += ifdef_pop(ifdef_depth)
+ ret += cutil.ifdef_pop(ifdef_depth)
stack = stack[:-1]
loopdepth = sum(1 for elem in path.elems if elem.cnt)
@@ -1270,7 +1209,7 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o
if name == "end":
if not path.elems:
nonlocal max_size
- if max_size > u32max:
+ if max_size > cutil.UINT32_MAX:
ret += f"{'\t'*len(stack)}uint32_t {name_prefix}end = (uint32_t)needed_size;\n"
else:
ret += f"{'\t'*len(stack)}uint32_t {name_prefix}end = needed_size;\n"
@@ -1295,7 +1234,9 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o
child = path.elems[-1]
parent = path.elems[-2].typ if len(path.elems) > 1 else path.root
if child.in_versions < parent.in_versions:
- ret += ifdef_push(ifdef_depth + 1, c_ver_ifdef(child.in_versions))
+ ret += cutil.ifdef_push(
+ ifdef_depth + 1, c_ver_ifdef(child.in_versions)
+ )
ifdef_depth += 1
ret += f"{'\t'*len(stack)}if ({c_ver_cond(child.in_versions)}) {{\n"
stack.append((path, True))
@@ -1338,7 +1279,7 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o
ret += "\treturn false;\n"
ret += "}\n"
- ret += ifdef_pop(0)
+ ret += cutil.ifdef_pop(0)
# function tables ##########################################################
ret += """
@@ -1350,20 +1291,20 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o
rerror = next(typ for typ in typs if typ.typname == "Rerror")
ret += f"\t[{c_ver_enum('unknown')}] = {rerror.min_size('9P2000')},\n" # SPECIAL (initialization)
for ver in sorted(versions):
- ret += ifdef_push(1, c_ver_ifdef({ver}))
+ ret += cutil.ifdef_push(1, c_ver_ifdef({ver}))
ret += f"\t[{c_ver_enum(ver)}] = {rerror.min_size(ver)},\n"
- ret += ifdef_pop(0)
+ ret += cutil.ifdef_pop(0)
ret += "};\n"
ret += "\n"
- ret += c_macro(
+ ret += cutil.macro(
f"#define _MSG_RECV(typ) [{idprefix.upper()}TYP_##typ/2] = {{\n"
f"\t\t.basesize = sizeof(struct {idprefix}msg_##typ),\n"
f"\t\t.validate = validate_##typ,\n"
f"\t\t.unmarshal = (_unmarshal_fn_t)unmarshal_##typ,\n"
f"\t}}\n"
)
- ret += c_macro(
+ ret += cutil.macro(
f"#define _MSG_SEND(typ) [{idprefix.upper()}TYP_##typ/2] = {{\n"
f"\t\t.marshal = (_marshal_fn_t)marshal_##typ,\n"
f"\t}}\n"
diff --git a/lib9p/protogen/cutil.py b/lib9p/protogen/cutil.py
new file mode 100644
index 0000000..a78cd17
--- /dev/null
+++ b/lib9p/protogen/cutil.py
@@ -0,0 +1,84 @@
+# 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]