summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.editorconfig7
-rw-r--r--.gitignore1
-rw-r--r--GNUmakefile5
-rw-r--r--lib9p/9p.generated.c91
-rwxr-xr-xlib9p/idl.gen876
-rw-r--r--lib9p/idl/0000-README.md2
-rw-r--r--lib9p/idl/1992-9P0.9p.wip2
-rw-r--r--lib9p/idl/1995-9P1.9p.wip2
-rw-r--r--lib9p/idl/1996-Styx.9p.wip2
-rw-r--r--lib9p/idl/2002-9P2000.9p4
-rw-r--r--lib9p/idl/2005-9P2000.u.9p2
-rw-r--r--lib9p/idl/2010-9P2000.L.9p.wip2
-rw-r--r--lib9p/idl/2012-9P2000.e.9p2
-rw-r--r--lib9p/idl/__init__.py491
-rw-r--r--lib9p/include/lib9p/9p.generated.h90
15 files changed, 824 insertions, 755 deletions
diff --git a/.editorconfig b/.editorconfig
index 62b7bb2..b95a6ff 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -36,7 +36,12 @@ _mode = sh
[{build-aux/linux-errno.txt.gen,libusb/include/libusb/tusb_helpers.h.gen}]
_mode = bash
-[{lib9p/idl.gen,lib9p/include/lib9p/linux-errno.h.gen,build-aux/stack.c.gen,gdb-helpers/*.py}]
+[{lib9p/idl.gen,lib9p/include/lib9p/linux-errno.h.gen,build-aux/stack.c.gen}]
+_mode = python3
+indent_style = space
+indent_size = 4
+
+[*.py]
_mode = python3
indent_style = space
indent_size = 4
diff --git a/.gitignore b/.gitignore
index 64e74ed..e1f5883 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@
*.log
*.tmp
.mypy_cache/
+__pycache__/
.gdb_history
/build/
diff --git a/GNUmakefile b/GNUmakefile
index 9ebbe86..9f5ad61 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -34,8 +34,8 @@ lib9p/include/lib9p/linux-errno.h: %: %.gen 3rd-party/linux-errno.txt
$^ >$@
generate/files += lib9p/9p.generated.c lib9p/include/lib9p/9p.generated.h
-lib9p/9p.generated.c lib9p/include/lib9p/9p.generated.h &: lib9p/idl.gen lib9p/idl/*.9p
- $^
+lib9p/9p.generated.c lib9p/include/lib9p/9p.generated.h &: lib9p/idl.gen lib9p/idl/__init__.py lib9p/idl lib9p/idl/*.9p
+ $< $(filter %.9p,$^)
generate/files += libusb/include/libusb/tusb_helpers.h 3rd-party/MS-LCID.pdf 3rd-party/MS-LCID.txt
libusb/include/libusb/tusb_helpers.h 3rd-party/MS-LCID.pdf 3rd-party/MS-LCID.txt &: libusb/include/libusb/tusb_helpers.h.gen
@@ -133,7 +133,6 @@ lint/all: lint/%:
-e 's,.*/config/,,' \
-e 's,.*/config\.h$$,config.h,' \
-e 's,.*include/,,' \
- -e 's,^lib9p/idl/,,' \
-e 's/\.wip$$//'); \
if [ "$$dscname_act" != "$$dscname_exp" ] && [ "cmd/$$dscname_act" != "$$dscname_exp" ]; then \
echo "$$filename self-identifies as $$dscname_act (expected $$dscname_exp)"; r=1; \
diff --git a/lib9p/9p.generated.c b/lib9p/9p.generated.c
index d3dc36a..c260647 100644
--- a/lib9p/9p.generated.c
+++ b/lib9p/9p.generated.c
@@ -34,6 +34,46 @@ const char *lib9p_version_str(enum lib9p_version ver) {
return version_strs[ver];
}
+/* bitmasks *******************************************************************/
+
+#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_u
+static const lib9p_dm_t dm_masks[LIB9P_VER_NUM] = {
+#if CONFIG_9P_ENABLE_9P2000
+ [LIB9P_VER_9P2000] = 0b11101100000000000000000111111111,
+#endif /* CONFIG_9P_ENABLE_9P2000 */
+#if CONFIG_9P_ENABLE_9P2000_e
+ [LIB9P_VER_9P2000_e] = 0b11101100000000000000000111111111,
+#endif /* CONFIG_9P_ENABLE_9P2000_e */
+#if CONFIG_9P_ENABLE_9P2000_u
+ [LIB9P_VER_9P2000_u] = 0b11101100101111000000000111111111,
+#endif /* CONFIG_9P_ENABLE_9P2000_u */
+};
+
+static const lib9p_qt_t qt_masks[LIB9P_VER_NUM] = {
+#if CONFIG_9P_ENABLE_9P2000
+ [LIB9P_VER_9P2000] = 0b11101100,
+#endif /* CONFIG_9P_ENABLE_9P2000 */
+#if CONFIG_9P_ENABLE_9P2000_e
+ [LIB9P_VER_9P2000_e] = 0b11101100,
+#endif /* CONFIG_9P_ENABLE_9P2000_e */
+#if CONFIG_9P_ENABLE_9P2000_u
+ [LIB9P_VER_9P2000_u] = 0b11101110,
+#endif /* CONFIG_9P_ENABLE_9P2000_u */
+};
+
+static const lib9p_o_t o_masks[LIB9P_VER_NUM] = {
+#if CONFIG_9P_ENABLE_9P2000
+ [LIB9P_VER_9P2000] = 0b01010011,
+#endif /* CONFIG_9P_ENABLE_9P2000 */
+#if CONFIG_9P_ENABLE_9P2000_e
+ [LIB9P_VER_9P2000_e] = 0b01010011,
+#endif /* CONFIG_9P_ENABLE_9P2000_e */
+#if CONFIG_9P_ENABLE_9P2000_u
+ [LIB9P_VER_9P2000_u] = 0b01010011,
+#endif /* CONFIG_9P_ENABLE_9P2000_u */
+};
+#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_u */
+
/* validate_* *****************************************************************/
LM_ALWAYS_INLINE static bool _validate_size_net(struct _validate_ctx *ctx, uint32_t n) {
@@ -99,17 +139,6 @@ LM_ALWAYS_INLINE static bool validate_s(struct _validate_ctx *ctx) {
return false;
}
-static const lib9p_dm_t dm_masks[LIB9P_VER_NUM] = {
-#if CONFIG_9P_ENABLE_9P2000
- [LIB9P_VER_9P2000] = 0b11101100000000000000000111111111,
-#endif /* CONFIG_9P_ENABLE_9P2000 */
-#if CONFIG_9P_ENABLE_9P2000_e
- [LIB9P_VER_9P2000_e] = 0b11101100000000000000000111111111,
-#endif /* CONFIG_9P_ENABLE_9P2000_e */
-#if CONFIG_9P_ENABLE_9P2000_u
- [LIB9P_VER_9P2000_u] = 0b11101100101111000000000111111111,
-#endif /* CONFIG_9P_ENABLE_9P2000_u */
-};
LM_ALWAYS_INLINE static bool validate_dm(struct _validate_ctx *ctx) {
if (validate_4(ctx))
return true;
@@ -120,17 +149,6 @@ LM_ALWAYS_INLINE static bool validate_dm(struct _validate_ctx *ctx) {
return false;
}
-static const lib9p_qt_t qt_masks[LIB9P_VER_NUM] = {
-#if CONFIG_9P_ENABLE_9P2000
- [LIB9P_VER_9P2000] = 0b11101100,
-#endif /* CONFIG_9P_ENABLE_9P2000 */
-#if CONFIG_9P_ENABLE_9P2000_e
- [LIB9P_VER_9P2000_e] = 0b11101100,
-#endif /* CONFIG_9P_ENABLE_9P2000_e */
-#if CONFIG_9P_ENABLE_9P2000_u
- [LIB9P_VER_9P2000_u] = 0b11101110,
-#endif /* CONFIG_9P_ENABLE_9P2000_u */
-};
LM_ALWAYS_INLINE static bool validate_qt(struct _validate_ctx *ctx) {
if (validate_1(ctx))
return true;
@@ -176,17 +194,6 @@ LM_ALWAYS_INLINE static bool validate_stat(struct _validate_ctx *ctx) {
;
}
-static const lib9p_o_t o_masks[LIB9P_VER_NUM] = {
-#if CONFIG_9P_ENABLE_9P2000
- [LIB9P_VER_9P2000] = 0b01010011,
-#endif /* CONFIG_9P_ENABLE_9P2000 */
-#if CONFIG_9P_ENABLE_9P2000_e
- [LIB9P_VER_9P2000_e] = 0b01010011,
-#endif /* CONFIG_9P_ENABLE_9P2000_e */
-#if CONFIG_9P_ENABLE_9P2000_u
- [LIB9P_VER_9P2000_u] = 0b01010011,
-#endif /* CONFIG_9P_ENABLE_9P2000_u */
-};
LM_ALWAYS_INLINE static bool validate_o(struct _validate_ctx *ctx) {
if (validate_1(ctx))
return true;
@@ -1784,15 +1791,15 @@ LM_FLATTEN static bool marshal_Rswrite(struct _marshal_ctx *ctx, struct lib9p_ms
/* tables / exports ***********************************************************/
-#define _MSG(typ) [LIB9P_TYP_##typ] = { \
- .name = #typ, \
- .basesize = sizeof(struct lib9p_msg_##typ), \
- .validate = validate_##typ, \
- .unmarshal = (_unmarshal_fn_t)unmarshal_##typ, \
- .marshal = (_marshal_fn_t)marshal_##typ, \
+#define _MSG(typ) [LIB9P_TYP_##typ] = { \
+ .name = #typ, \
+ .basesize = sizeof(struct lib9p_msg_##typ), \
+ .validate = validate_##typ, \
+ .unmarshal = (_unmarshal_fn_t)unmarshal_##typ, \
+ .marshal = (_marshal_fn_t)marshal_##typ, \
}
-#define _NONMSG(num) [num] = { \
- .name = #num, \
+#define _NONMSG(num) [num] = { \
+ .name = #num, \
}
struct _table_version _lib9p_versions[LIB9P_VER_NUM] = {
@@ -2843,5 +2850,5 @@ LM_FLATTEN void _lib9p_unmarshal_stat(struct _unmarshal_ctx *ctx, struct lib9p_s
unmarshal_stat(ctx, out);
}
LM_FLATTEN bool _lib9p_marshal_stat(struct _marshal_ctx *ctx, struct lib9p_stat *val) {
- return marshal_stat(ctx, val);
+ return marshal_stat(ctx, val);
}
diff --git a/lib9p/idl.gen b/lib9p/idl.gen
index ae7f1a5..72efb7b 100755
--- a/lib9p/idl.gen
+++ b/lib9p/idl.gen
@@ -5,456 +5,47 @@
# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
# SPDX-License-Identifier: AGPL-3.0-or-later
-import enum
import os.path
-import re
-from abc import ABC, abstractmethod
-from typing import Callable, Final, Literal, TypeAlias, TypeVar, cast
+import sys
+
+sys.path.insert(0, os.path.normpath(os.path.join(__file__, "..")))
+
+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".
-# Types ########################################################################
-
-
-class Primitive(enum.Enum):
- u8 = 1
- u16 = 2
- u32 = 4
- u64 = 8
-
- @property
- def in_versions(self) -> set[str]:
- return set()
-
- @property
- def name(self) -> str:
- return str(self.value)
-
- @property
- def static_size(self) -> int:
- return self.value
-
-
-class Number:
- name: str
- in_versions: set[str]
-
- prim: Primitive
-
- def __init__(self) -> None:
- self.in_versions = set()
-
- @property
- def static_size(self) -> int:
- return self.prim.static_size
-
-
-class BitfieldVal:
- name: str
- in_versions: set[str]
-
- val: str
-
- def __init__(self) -> None:
- self.in_versions = set()
-
-
-class Bitfield:
- name: str
- in_versions: set[str]
-
- prim: Primitive
-
- bits: list[str] # bitnames
- names: dict[str, BitfieldVal] # bits *and* aliases
-
- def __init__(self) -> None:
- self.in_versions = set()
- self.names = {}
-
- @property
- def static_size(self) -> int:
- return self.prim.static_size
-
- def bit_is_valid(self, bit: str | int, ver: str | None = None) -> bool:
- """Return whether the given bit is valid in the given protocol
- version.
-
- """
- bitname = self.bits[bit] if isinstance(bit, int) else bit
- assert bitname in self.bits
- if not bitname:
- return False
- if bitname.startswith("_"):
- return False
- if ver and (ver not in self.names[bitname].in_versions):
- return False
- return True
-
-
-class ExprLit:
- val: int
-
- def __init__(self, val: int) -> None:
- self.val = val
-
-
-class ExprSym:
- name: str
-
- def __init__(self, name: str) -> None:
- self.name = name
-
-
-class ExprOp:
- op: Literal["-", "+"]
-
- def __init__(self, op: Literal["-", "+"]) -> None:
- self.op = op
-
-
-class Expr:
- tokens: list[ExprLit | ExprSym | ExprOp]
-
- def __init__(self) -> None:
- self.tokens = []
-
- def __bool__(self) -> bool:
- return len(self.tokens) > 0
-
-class StructMember:
- # from left-to-right when parsing
- cnt: str | None = None
- name: str
- typ: "Type"
- max: Expr
- val: Expr
+# Utilities ####################################################################
- in_versions: set[str]
-
- @property
- def static_size(self) -> int | None:
- if self.cnt:
- return None
- return self.typ.static_size
-
-
-class Struct:
- name: str
- in_versions: set[str]
-
- members: list[StructMember]
-
- def __init__(self) -> None:
- self.in_versions = set()
-
- @property
- def static_size(self) -> int | None:
- size = 0
- for member in self.members:
- msize = member.static_size
- if msize is None:
- return None
- size += msize
- return size
-
-
-class Message(Struct):
- @property
- def msgid(self) -> int:
- assert len(self.members) >= 3
- assert self.members[1].name == "typ"
- assert self.members[1].static_size == 1
- assert self.members[1].val
- assert len(self.members[1].val.tokens) == 1
- assert isinstance(self.members[1].val.tokens[0], ExprLit)
- return self.members[1].val.tokens[0].val
-
-
-Type: TypeAlias = Primitive | Number | Bitfield | Struct | Message
-# type Type = Primitive | Number | Bitfield | Struct | Message # Change to this once we have Python 3.13
-T = TypeVar("T", Number, Bitfield, Struct, Message)
-
-# Parse *.9p ###################################################################
-
-re_priname = "(?:1|2|4|8)" # primitive names
-re_symname = "(?:[a-zA-Z_][a-zA-Z_0-9]*)" # "symbol" names; most *.9p-defined names
-re_impname = r"(?:\*|" + re_symname + ")" # names we can import
-re_msgname = r"(?:[TR][a-zA-Z_0-9]*)" # names a message can be
-
-re_memtype = f"(?:{re_symname}|{re_priname})" # typenames that a struct member can be
-
-re_expr = f"(?:(?:-|\\+|[0-9]+|&?{re_symname})+)"
-
-re_bitspec_bit = f"(?P<bit>[0-9]+)\\s*=\\s*(?P<name>{re_symname})"
-re_bitspec_alias = f"(?P<name>{re_symname})\\s*=\\s*(?P<val>\\S+)"
-
-re_memberspec = f"(?:(?P<cnt>{re_symname})\\*\\()?(?P<name>{re_symname})\\[(?P<typ>{re_memtype})(?:,max=(?P<max>{re_expr})|,val=(?P<val>{re_expr}))*\\]\\)?"
-
-
-def parse_bitspec(ver: str, bf: Bitfield, spec: str) -> None:
- spec = spec.strip()
-
- bit: int | None
- val: BitfieldVal
- if m := re.fullmatch(re_bitspec_bit, spec):
- bit = int(m.group("bit"))
- name = m.group("name")
-
- val = BitfieldVal()
- val.name = name
- val.val = f"1<<{bit}"
- val.in_versions.add(ver)
-
- if bit < 0 or bit >= len(bf.bits):
- raise ValueError(f"{bf.name}: bit {bit} is out-of-bounds")
- if bf.bits[bit]:
- raise ValueError(f"{bf.name}: bit {bit} already assigned")
- bf.bits[bit] = val.name
- elif m := re.fullmatch(re_bitspec_alias, spec):
- name = m.group("name")
- valstr = m.group("val")
-
- val = BitfieldVal()
- val.name = name
- val.val = valstr
- val.in_versions.add(ver)
- else:
- raise SyntaxError(f"invalid bitfield spec {repr(spec)}")
-
- if val.name in bf.names:
- raise ValueError(f"{bf.name}: name {val.name} already assigned")
- bf.names[val.name] = val
-
-
-def parse_expr(expr: str) -> Expr:
- assert re.fullmatch(re_expr, expr)
- ret = Expr()
- for tok in re.split("([-+])", expr):
- if tok == "-" or tok == "+":
- # I, for the life of me, do not understand why I need this
- # cast() to keep mypy happy.
- ret.tokens += [ExprOp(cast(Literal["-", "+"], tok))]
- elif re.fullmatch("[0-9]+", tok):
- ret.tokens += [ExprLit(int(tok))]
- else:
- ret.tokens += [ExprSym(tok)]
- return ret
+idprefix = "lib9p_"
-def parse_members(ver: str, env: dict[str, Type], struct: Struct, specs: str) -> None:
- for spec in specs.split():
- m = re.fullmatch(re_memberspec, spec)
- if not m:
- raise SyntaxError(f"invalid member spec {repr(spec)}")
-
- member = StructMember()
- member.in_versions = {ver}
-
- member.name = m.group("name")
- if any(x.name == member.name for x in struct.members):
- raise ValueError(f"duplicate member name {repr(member.name)}")
-
- if m.group("typ") not in env:
- raise NameError(f"Unknown type {repr(m.group(2))}")
- member.typ = env[m.group("typ")]
-
- if cnt := m.group("cnt"):
- if len(struct.members) == 0 or struct.members[-1].name != cnt:
- raise ValueError(f"list count must be previous item: {repr(cnt)}")
- if not isinstance(struct.members[-1].typ, Primitive):
- raise ValueError(f"list count must be an integer type: {repr(cnt)}")
- member.cnt = cnt
-
- if maxstr := m.group("max"):
- if (not isinstance(member.typ, Primitive)) or member.cnt:
- raise ValueError("',max=' may only be specified on a non-repeated atom")
- member.max = parse_expr(maxstr)
- else:
- member.max = Expr()
-
- if valstr := m.group("val"):
- if (not isinstance(member.typ, Primitive)) or member.cnt:
- raise ValueError("',val=' may only be specified on a non-repeated atom")
- member.val = parse_expr(valstr)
- else:
- member.val = Expr()
-
- struct.members += [member]
-
-
-def re_string(grpname: str) -> str:
- return f'"(?P<{grpname}>[^"]*)"'
-
-
-re_line_version = f"version\\s+{re_string('version')}"
-re_line_import = f"from\\s+(?P<file>\\S+)\\s+import\\s+(?P<syms>{re_impname}(?:\\s*,\\s*{re_impname})*)"
-re_line_num = f"num\\s+(?P<name>{re_symname})\\s*=\\s*(?P<prim>{re_priname})"
-re_line_bitfield = f"bitfield\\s+(?P<name>{re_symname})\\s*=\\s*(?P<prim>{re_priname})"
-re_line_bitfield_ = (
- f"bitfield\\s+(?P<name>{re_symname})\\s*\\+=\\s*{re_string('member')}"
-)
-re_line_struct = (
- f"struct\\s+(?P<name>{re_symname})\\s*(?P<op>\\+?=)\\s*{re_string('members')}"
-)
-re_line_msg = (
- f"msg\\s+(?P<name>{re_msgname})\\s*(?P<op>\\+?=)\\s*{re_string('members')}"
-)
-re_line_cont = f"\\s+{re_string('specs')}" # could be bitfield/struct/msg
-
-
-def parse_file(
- filename: str, get_include: Callable[[str], tuple[str, list[Type]]]
-) -> tuple[str, list[Type]]:
- version: str | None = None
- env: dict[str, Type] = {
- "1": Primitive.u8,
- "2": Primitive.u16,
- "4": Primitive.u32,
- "8": Primitive.u64,
- }
-
- def get_type(name: str, tc: type[T]) -> T:
- nonlocal env
- if name not in env:
- raise NameError(f"Unknown type {repr(name)}")
- ret = env[name]
- if (not isinstance(ret, tc)) or (ret.__class__.__name__ != tc.__name__):
- raise NameError(f"Type {repr(ret.name)} is not a {tc.__name__}")
- return ret
-
- with open(filename, "r") as fh:
- prev: Type | None = None
- for line in fh:
- line = line.split("#", 1)[0].rstrip()
- if not line:
- continue
- if m := re.fullmatch(re_line_version, line):
- if version:
- raise SyntaxError("must have exactly 1 version line")
- version = m.group("version")
- continue
- if not version:
- raise SyntaxError("must have exactly 1 version line")
-
- if m := re.fullmatch(re_line_import, line):
- other_version, other_typs = get_include(m.group("file"))
- for symname in m.group("syms").split(sep=","):
- symname = symname.strip()
- for typ in other_typs:
- if typ.name == symname or symname == "*":
- match typ:
- case Primitive():
- pass
- case Number():
- typ.in_versions.add(version)
- case Bitfield():
- typ.in_versions.add(version)
- for val in typ.names.values():
- if other_version in val.in_versions:
- val.in_versions.add(version)
- case Struct(): # and Message()
- typ.in_versions.add(version)
- for member in typ.members:
- if other_version in member.in_versions:
- member.in_versions.add(version)
- env[typ.name] = typ
- elif m := re.fullmatch(re_line_num, line):
- num = Number()
- num.name = m.group("name")
- num.in_versions.add(version)
-
- prim = env[m.group("prim")]
- assert isinstance(prim, Primitive)
- num.prim = prim
-
- env[num.name] = num
- prev = num
- elif m := re.fullmatch(re_line_bitfield, line):
- bf = Bitfield()
- bf.name = m.group("name")
- bf.in_versions.add(version)
-
- prim = env[m.group("prim")]
- assert isinstance(prim, Primitive)
- bf.prim = prim
-
- bf.bits = (prim.static_size * 8) * [""]
-
- env[bf.name] = bf
- prev = bf
- elif m := re.fullmatch(re_line_bitfield_, line):
- bf = get_type(m.group("name"), Bitfield)
- parse_bitspec(version, bf, m.group("member"))
-
- prev = bf
- elif m := re.fullmatch(re_line_struct, line):
- match m.group("op"):
- case "=":
- struct = Struct()
- struct.name = m.group("name")
- struct.in_versions.add(version)
- struct.members = []
- parse_members(version, env, struct, m.group("members"))
-
- env[struct.name] = struct
- prev = struct
- case "+=":
- struct = get_type(m.group("name"), Struct)
- parse_members(version, env, struct, m.group("members"))
-
- prev = struct
- elif m := re.fullmatch(re_line_msg, line):
- match m.group("op"):
- case "=":
- msg = Message()
- msg.name = m.group("name")
- msg.in_versions.add(version)
- msg.members = []
- parse_members(version, env, msg, m.group("members"))
-
- env[msg.name] = msg
- prev = msg
- case "+=":
- msg = get_type(m.group("name"), Message)
- parse_members(version, env, msg, m.group("members"))
-
- prev = msg
- elif m := re.fullmatch(re_line_cont, line):
- match prev:
- case Bitfield():
- parse_bitspec(version, prev, m.group("specs"))
- case Struct(): # and Message()
- parse_members(version, env, prev, m.group("specs"))
- case _:
- raise SyntaxError(
- "continuation line must come after a bitfield, struct, or msg line"
- )
- else:
- raise SyntaxError(f"invalid line {repr(line)}")
- if not version:
- raise SyntaxError("must have exactly 1 version line")
+def tab_ljust(s: str, width: int) -> str:
+ cur = len(s.expandtabs(tabsize=8))
+ if cur >= width:
+ return s
+ return s + " " * (width - cur)
- typs: list[Type] = [x for x in env.values() if not isinstance(x, Primitive)]
- for typ in [typ for typ in typs if isinstance(typ, Struct)]:
- valid_syms = ["end", *["&" + m.name for m in typ.members]]
- for member in typ.members:
- for tok in [*member.max.tokens, *member.val.tokens]:
- if isinstance(tok, ExprSym) and tok.name not in valid_syms:
- raise ValueError(
- f"{typ.name}.{member.name}: invalid sym: {tok.name}"
- )
+def add_prefix(p: str, s: str) -> str:
+ if s.startswith("_"):
+ return "_" + p + s[1:]
+ return p + s
- return version, typs
+def join_lines(*args: str) -> str:
+ return "\n".join([a.rstrip() for a in args]).rstrip() + "\n"
-# Generate C ###################################################################
-idprefix = "lib9p_"
+def c_macro(*args: str) -> str:
+ full = join_lines(*args).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:
@@ -473,22 +64,37 @@ def c_ver_cond(versions: set[str]) -> str:
return "( " + (" || ".join(c_ver_cond({v}) for v in sorted(versions))) + " )"
-def c_typename(typ: Type) -> str:
+def c_typename(typ: idl.Type) -> str:
match typ:
- case Primitive():
+ case idl.Primitive():
return f"uint{typ.value*8}_t"
- case Number():
+ case idl.Number():
return f"{idprefix}{typ.name}_t"
- case Bitfield():
+ case idl.Bitfield():
return f"{idprefix}{typ.name}_t"
- case Message():
+ case idl.Message():
return f"struct {idprefix}msg_{typ.name}"
- case Struct():
+ case idl.Struct():
return f"struct {idprefix}{typ.name}"
case _:
raise ValueError(f"not a type: {typ.__class__.__name__}")
+def c_expr(expr: idl.Expr) -> str:
+ ret: list[str] = []
+ for tok in expr.tokens:
+ match tok:
+ case idl.ExprOp():
+ ret += [tok.op]
+ case idl.ExprLit():
+ ret += [str(tok.val)]
+ case idl.ExprSym(name="end"):
+ ret += ["ctx->net_offset"]
+ case idl.ExprSym():
+ ret += [f"_{tok.name[1:]}_offset"]
+ return " ".join(ret)
+
+
_ifdef_stack: list[str | None] = []
@@ -531,14 +137,17 @@ def ifdef_pop(n: int) -> str:
return ret
-def gen_h(versions: set[str], typs: list[Type]) -> str:
+# Generate .h ##################################################################
+
+
+def gen_h(versions: set[str], typs: list[idl.Type]) -> str:
global _ifdef_stack
_ifdef_stack = []
ret = f"""/* Generated by `{' '.join(sys.argv)}`. DO NOT EDIT! */
#ifndef _LIB9P_9P_H_
- #error Do not include <lib9p/9p.generated.h> directly; include <lib9p/9p.h> instead
+\t#error Do not include <lib9p/9p.generated.h> directly; include <lib9p/9p.h> instead
#endif
#include <stdint.h> /* for uint{{n}}_t types */
@@ -556,7 +165,7 @@ def gen_h(versions: set[str], typs: list[Type]) -> str:
ret += "#endif\n"
ret += f"""
-/* versions *******************************************************************/
+/* enum version ***************************************************************/
enum {idprefix}version {{
"""
@@ -574,15 +183,27 @@ enum {idprefix}version {{
ret += f"const char *{idprefix}version_str(enum {idprefix}version);\n"
ret += """
-/* non-message types **********************************************************/
+/* enum msg_type **************************************************************/
+
+"""
+ ret += f"enum {idprefix}msg_type {{ /* uint8_t */\n"
+ namewidth = max(len(msg.name) for msg in typs if isinstance(msg, idl.Message))
+ for msg in [msg for msg in typs if isinstance(msg, idl.Message)]:
+ ret += ifdef_push(1, c_ver_ifdef(msg.in_versions))
+ ret += f"\t{idprefix.upper()}TYP_{msg.name.ljust(namewidth)} = {msg.msgid},\n"
+ ret += ifdef_pop(0)
+ ret += "};\n"
+
+ ret += """
+/* payload types **************************************************************/
"""
- for typ in [typ for typ in typs if not isinstance(typ, Message)]:
+ for typ in typs:
ret += "\n"
ret += ifdef_push(1, c_ver_ifdef(typ.in_versions))
match typ:
- case Number():
+ case idl.Number():
ret += f"typedef {c_typename(typ.prim)} {c_typename(typ)};\n"
- case Bitfield():
+ case idl.Bitfield():
ret += f"typedef {c_typename(typ.prim)} {c_typename(typ)};\n"
names = [
*reversed(
@@ -591,39 +212,48 @@ enum {idprefix}version {{
"",
*[k for k in typ.names if k not in typ.bits],
]
- namewidth = max(len(name) for name in names)
+ prefix = f"{idprefix.upper()}{typ.name.upper()}_"
+ namewidth = max(len(add_prefix(prefix, name)) for name in names)
ret += "\n"
for name in names:
if name == "":
ret += "\n"
- elif name.startswith(" "):
- ret += ifdef_push(2, c_ver_ifdef(typ.in_versions))
- sp = " " * (
- len("# define ")
- + len(idprefix)
- + len(typ.name)
- + 1
- + namewidth
- + 2
- - len("/* unused")
- )
- ret += f"/* unused{sp}(({c_typename(typ)})(1<<{name[1:]})) */\n"
+ continue
+
+ if name.startswith(" "):
+ vers = typ.in_versions
+ c_name = ""
+ c_val = f"1<<{name[1:]}"
else:
- ret += ifdef_push(2, c_ver_ifdef(typ.names[name].in_versions))
- if name.startswith("_"):
- c_name = f"_{idprefix.upper()}{typ.name.upper()}_{name[1:]}"
- else:
- c_name = f"{idprefix.upper()}{typ.name.upper()}_{name}"
- sp1 = " " if _ifdef_stack[-1] else ""
- sp2 = " " if _ifdef_stack[-1] else " "
- sp3 = " " * (2 + namewidth - len(name))
- ret += f"#{sp1}define{sp2}{c_name}{sp3}(({c_typename(typ)})({typ.names[name].val}))\n"
+ vers = typ.names[name].in_versions
+ c_name = add_prefix(prefix, name)
+ c_val = f"{typ.names[name].val}"
+
+ ret += ifdef_push(2, c_ver_ifdef(vers))
+
+ # It is important all of the `beg` strings have
+ # the same length.
+ end = ""
+ if name.startswith(" "):
+ beg = "/* unused"
+ end = " */"
+ elif _ifdef_stack[-1]:
+ beg = "# define"
+ else:
+ beg = "#define "
+
+ ret += f"{beg} {c_name.ljust(namewidth)} (({c_typename(typ)})({c_val})){end}\n"
ret += ifdef_pop(1)
- case Struct():
+ case idl.Struct(): # and idl.Message():
+ ret += c_typename(typ) + " {"
+ if not typ.members:
+ ret += "};\n"
+ continue
+ ret += "\n"
+
typewidth = max(len(c_typename(m.typ)) for m in typ.members)
- ret += c_typename(typ) + " {\n"
for member in typ.members:
if member.val:
continue
@@ -636,57 +266,13 @@ enum {idprefix}version {{
ret += "};\n"
ret += ifdef_pop(0)
- ret += """
-/* messages *******************************************************************/
-
-"""
- ret += f"enum {idprefix}msg_type {{ /* uint8_t */\n"
- namewidth = max(len(msg.name) for msg in typs if isinstance(msg, Message))
- for msg in [msg for msg in typs if isinstance(msg, Message)]:
- ret += ifdef_push(1, c_ver_ifdef(msg.in_versions))
- ret += f"\t{idprefix.upper()}TYP_{msg.name.ljust(namewidth)} = {msg.msgid},\n"
- ret += ifdef_pop(0)
- ret += "};\n"
-
- for msg in [msg for msg in typs if isinstance(msg, Message)]:
- ret += "\n"
- ret += ifdef_push(1, c_ver_ifdef(msg.in_versions))
- ret += c_typename(msg) + " {"
- if not msg.members:
- ret += "};\n"
- continue
- ret += "\n"
-
- typewidth = max(len(c_typename(m.typ)) for m in msg.members)
-
- for member in msg.members:
- if member.val:
- continue
- ret += ifdef_push(2, c_ver_ifdef(member.in_versions))
- ret += f"\t{c_typename(member.typ).ljust(typewidth)} {'*' if member.cnt else ' '}{member.name};\n"
- ret += ifdef_pop(1)
- ret += "};\n"
- ret += ifdef_pop(0)
-
return ret
-def c_expr(expr: Expr) -> str:
- ret: list[str] = []
- for tok in expr.tokens:
- match tok:
- case ExprOp():
- ret += [tok.op]
- case ExprLit():
- ret += [str(tok.val)]
- case ExprSym(name="end"):
- ret += ["ctx->net_offset"]
- case ExprSym():
- ret += [f"_{tok.name[1:]}_offset"]
- return " ".join(ret)
+# Generate .c ##################################################################
-def gen_c(versions: set[str], typs: list[Type]) -> str:
+def gen_c(versions: set[str], typs: list[idl.Type]) -> str:
global _ifdef_stack
_ifdef_stack = []
@@ -732,37 +318,62 @@ const char *{idprefix}version_str(enum {idprefix}version ver) {{
}}
"""
+ # bitmasks #################################################################
+ ret += f"""
+/* bitmasks *******************************************************************/
+"""
+ for typ in typs:
+ if not isinstance(typ, idl.Bitfield):
+ continue
+ ret += "\n"
+ ret += ifdef_push(1, c_ver_ifdef(typ.in_versions))
+ ret += f"static const {c_typename(typ)} {typ.name}_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 += (
+ f"\t[{c_ver_enum(ver)}]{' '*(verwidth-len(ver))} = 0b"
+ + "".join(
+ "1" if typ.bit_is_valid(bitname, ver) else "0"
+ for bitname in reversed(typ.bits)
+ )
+ + ",\n"
+ )
+ ret += ifdef_pop(1)
+ ret += "};\n"
+ ret += ifdef_pop(0)
+
# validate_* ###############################################################
ret += """
/* validate_* *****************************************************************/
LM_ALWAYS_INLINE static bool _validate_size_net(struct _validate_ctx *ctx, uint32_t n) {
- if (__builtin_add_overflow(ctx->net_offset, n, &ctx->net_offset))
- /* If needed-net-size overflowed uint32_t, then
- * there's no way that actual-net-size will live up to
- * that. */
- return lib9p_error(ctx->ctx, LINUX_EBADMSG, "message is too short for content");
- if (ctx->net_offset > ctx->net_size)
- return lib9p_error(ctx->ctx, LINUX_EBADMSG, "message is too short for content");
- return false;
+\tif (__builtin_add_overflow(ctx->net_offset, n, &ctx->net_offset))
+\t\t/* If needed-net-size overflowed uint32_t, then
+\t\t * there's no way that actual-net-size will live up to
+\t\t * that. */
+\t\treturn lib9p_error(ctx->ctx, LINUX_EBADMSG, "message is too short for content");
+\tif (ctx->net_offset > ctx->net_size)
+\t\treturn lib9p_error(ctx->ctx, LINUX_EBADMSG, "message is too short for content");
+\treturn false;
}
LM_ALWAYS_INLINE static bool _validate_size_host(struct _validate_ctx *ctx, size_t n) {
- if (__builtin_add_overflow(ctx->host_extra, n, &ctx->host_extra))
- /* If needed-host-size overflowed size_t, then there's
- * no way that actual-net-size will live up to
- * that. */
- return lib9p_error(ctx->ctx, LINUX_EBADMSG, "message is too short for content");
- return false;
+\tif (__builtin_add_overflow(ctx->host_extra, n, &ctx->host_extra))
+\t\t/* If needed-host-size overflowed size_t, then there's
+\t\t * no way that actual-net-size will live up to
+\t\t * that. */
+\t\treturn lib9p_error(ctx->ctx, LINUX_EBADMSG, "message is too short for content");
+\treturn false;
}
LM_ALWAYS_INLINE static bool _validate_list(struct _validate_ctx *ctx,
size_t cnt,
_validate_fn_t item_fn, size_t item_host_size) {
- for (size_t i = 0; i < cnt; i++)
- if (_validate_size_host(ctx, item_host_size) || item_fn(ctx))
- return true;
- return false;
+\tfor (size_t i = 0; i < cnt; i++)
+\t\tif (_validate_size_host(ctx, item_host_size) || item_fn(ctx))
+\t\t\treturn true;
+\treturn false;
}
#define validate_1(ctx) _validate_size_net(ctx, 1)
@@ -771,27 +382,10 @@ LM_ALWAYS_INLINE static bool _validate_list(struct _validate_ctx *ctx,
#define validate_8(ctx) _validate_size_net(ctx, 8)
"""
for typ in typs:
- inline = "LM_FLATTEN" if isinstance(typ, Message) else "LM_ALWAYS_INLINE"
- argfn = unused if (isinstance(typ, Struct) and not typ.members) else used
+ 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))
-
- if isinstance(typ, Bitfield):
- ret += f"static const {c_typename(typ)} {typ.name}_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 += (
- f"\t[{c_ver_enum(ver)}]{' '*(verwidth-len(ver))} = 0b"
- + "".join(
- "1" if typ.bit_is_valid(bitname, ver) else "0"
- for bitname in reversed(typ.bits)
- )
- + ",\n"
- )
- ret += ifdef_pop(1)
- ret += "};\n"
-
ret += f"{inline} static bool validate_{typ.name}(struct _validate_ctx *{argfn('ctx')}) {{\n"
if typ.name == "d": # SPECIAL
@@ -820,9 +414,9 @@ LM_ALWAYS_INLINE static bool _validate_list(struct _validate_ctx *ctx,
continue
match typ:
- case Number():
+ case idl.Number():
ret += f"\treturn validate_{typ.prim.name}(ctx);\n"
- case Bitfield():
+ case idl.Bitfield():
ret += f"\t if (validate_{typ.static_size}(ctx))\n"
ret += "\t\treturn true;\n"
ret += (
@@ -832,7 +426,7 @@ LM_ALWAYS_INLINE static bool _validate_list(struct _validate_ctx *ctx,
ret += f"\tif (val & ~mask)\n"
ret += f'\t\treturn lib9p_errorf(ctx->ctx, LINUX_EBADMSG, "unknown bits in {typ.name} bitfield: %#0{typ.static_size}"PRIx{typ.static_size*8}, val & ~mask);\n'
ret += "\treturn false;\n"
- case Struct(): # and Message()
+ case idl.Struct(): # and idl.Message()
if len(typ.members) == 0:
ret += "\treturn false;\n"
ret += "}\n"
@@ -849,7 +443,7 @@ LM_ALWAYS_INLINE static bool _validate_list(struct _validate_ctx *ctx,
mark_offset: set[str] = set()
for member in typ.members:
for tok in [*member.max.tokens, *member.val.tokens]:
- if isinstance(tok, ExprSym) and tok.name.startswith("&"):
+ if isinstance(tok, idl.ExprSym) and tok.name.startswith("&"):
if tok.name[1:] not in mark_offset:
ret += f"\tuint32_t _{tok.name[1:]}_offset;\n"
mark_offset.add(tok.name[1:])
@@ -904,37 +498,37 @@ LM_ALWAYS_INLINE static bool _validate_list(struct _validate_ctx *ctx,
/* unmarshal_* ****************************************************************/
LM_ALWAYS_INLINE static void unmarshal_1(struct _unmarshal_ctx *ctx, uint8_t *out) {
- *out = decode_u8le(&ctx->net_bytes[ctx->net_offset]);
- ctx->net_offset += 1;
+\t*out = decode_u8le(&ctx->net_bytes[ctx->net_offset]);
+\tctx->net_offset += 1;
}
LM_ALWAYS_INLINE static void unmarshal_2(struct _unmarshal_ctx *ctx, uint16_t *out) {
- *out = decode_u16le(&ctx->net_bytes[ctx->net_offset]);
- ctx->net_offset += 2;
+\t*out = decode_u16le(&ctx->net_bytes[ctx->net_offset]);
+\tctx->net_offset += 2;
}
LM_ALWAYS_INLINE static void unmarshal_4(struct _unmarshal_ctx *ctx, uint32_t *out) {
- *out = decode_u32le(&ctx->net_bytes[ctx->net_offset]);
- ctx->net_offset += 4;
+\t*out = decode_u32le(&ctx->net_bytes[ctx->net_offset]);
+\tctx->net_offset += 4;
}
LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *out) {
- *out = decode_u64le(&ctx->net_bytes[ctx->net_offset]);
- ctx->net_offset += 8;
+\t*out = decode_u64le(&ctx->net_bytes[ctx->net_offset]);
+\tctx->net_offset += 8;
}
"""
for typ in typs:
- inline = "LM_FLATTEN" if isinstance(typ, Message) else "LM_ALWAYS_INLINE"
- argfn = unused if (isinstance(typ, Struct) and not typ.members) else used
+ 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 += f"{inline} static void unmarshal_{typ.name}(struct _unmarshal_ctx *{argfn('ctx')}, {c_typename(typ)} *out) {{\n"
match typ:
- case Number():
+ case idl.Number():
ret += f"\tunmarshal_{typ.prim.name}(ctx, ({c_typename(typ.prim)} *)out);\n"
- case Bitfield():
+ case idl.Bitfield():
ret += f"\tunmarshal_{typ.prim.name}(ctx, ({c_typename(typ.prim)} *)out);\n"
- case Struct():
+ case idl.Struct():
ret += "\tmemset(out, 0, sizeof(*out));\n"
for member in typ.members:
@@ -977,58 +571,58 @@ LM_ALWAYS_INLINE static void unmarshal_8(struct _unmarshal_ctx *ctx, uint64_t *o
/* marshal_* ******************************************************************/
LM_ALWAYS_INLINE static bool _marshal_too_large(struct _marshal_ctx *ctx) {
- lib9p_errorf(ctx->ctx, LINUX_ERANGE, "%s too large to marshal into %s limit (limit=%"PRIu32")",
- (ctx->net_bytes[4] % 2 == 0) ? "T-message" : "R-message",
- ctx->ctx->version ? "negotiated" : ((ctx->net_bytes[4] % 2 == 0) ? "client" : "server"),
- ctx->ctx->max_msg_size);
- return true;
+\tlib9p_errorf(ctx->ctx, LINUX_ERANGE, "%s too large to marshal into %s limit (limit=%"PRIu32")",
+\t\t(ctx->net_bytes[4] % 2 == 0) ? "T-message" : "R-message",
+\t\tctx->ctx->version ? "negotiated" : ((ctx->net_bytes[4] % 2 == 0) ? "client" : "server"),
+\t\tctx->ctx->max_msg_size);
+\treturn true;
}
LM_ALWAYS_INLINE static bool marshal_1(struct _marshal_ctx *ctx, uint8_t *val) {
- if (ctx->net_offset + 1 > ctx->ctx->max_msg_size)
- return _marshal_too_large(ctx);
- ctx->net_bytes[ctx->net_offset] = *val;
- ctx->net_offset += 1;
- return false;
+\tif (ctx->net_offset + 1 > ctx->ctx->max_msg_size)
+\t\treturn _marshal_too_large(ctx);
+\tctx->net_bytes[ctx->net_offset] = *val;
+\tctx->net_offset += 1;
+\treturn false;
}
LM_ALWAYS_INLINE static bool marshal_2(struct _marshal_ctx *ctx, uint16_t *val) {
- if (ctx->net_offset + 2 > ctx->ctx->max_msg_size)
- return _marshal_too_large(ctx);
- encode_u16le(*val, &ctx->net_bytes[ctx->net_offset]);
- ctx->net_offset += 2;
- return false;
+\tif (ctx->net_offset + 2 > ctx->ctx->max_msg_size)
+\t\treturn _marshal_too_large(ctx);
+\tencode_u16le(*val, &ctx->net_bytes[ctx->net_offset]);
+\tctx->net_offset += 2;
+\treturn false;
}
LM_ALWAYS_INLINE static bool marshal_4(struct _marshal_ctx *ctx, uint32_t *val) {
- if (ctx->net_offset + 4 > ctx->ctx->max_msg_size)
- return true;
- encode_u32le(*val, &ctx->net_bytes[ctx->net_offset]);
- ctx->net_offset += 4;
- return false;
+\tif (ctx->net_offset + 4 > ctx->ctx->max_msg_size)
+\t\treturn true;
+\tencode_u32le(*val, &ctx->net_bytes[ctx->net_offset]);
+\tctx->net_offset += 4;
+\treturn false;
}
LM_ALWAYS_INLINE static bool marshal_8(struct _marshal_ctx *ctx, uint64_t *val) {
- if (ctx->net_offset + 8 > ctx->ctx->max_msg_size)
- return true;
- encode_u64le(*val, &ctx->net_bytes[ctx->net_offset]);
- ctx->net_offset += 8;
- return false;
+\tif (ctx->net_offset + 8 > ctx->ctx->max_msg_size)
+\t\treturn true;
+\tencode_u64le(*val, &ctx->net_bytes[ctx->net_offset]);
+\tctx->net_offset += 8;
+\treturn false;
}
"""
for typ in typs:
- inline = "LM_FLATTEN" if isinstance(typ, Message) else "LM_ALWAYS_INLINE"
- argfn = unused if (isinstance(typ, Struct) and not typ.members) else used
+ 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 += f"{inline} static bool marshal_{typ.name}(struct _marshal_ctx *{argfn('ctx')}, {c_typename(typ)} *{argfn('val')}) {{\n"
match typ:
- case Number():
+ case idl.Number():
ret += f"\treturn marshal_{typ.prim.name}(ctx, ({c_typename(typ.prim)} *)val);\n"
- case Bitfield():
+ case idl.Bitfield():
ret += f"\t{c_typename(typ)} masked_val = *val & {typ.name}_masks[ctx->ctx->version];\n"
ret += f"\treturn marshal_{typ.prim.name}(ctx, ({c_typename(typ.prim)} *)&masked_val);\n"
- case Struct():
+ case idl.Struct():
if len(typ.members) == 0:
ret += "\treturn false;\n"
ret += "}\n"
@@ -1042,7 +636,7 @@ LM_ALWAYS_INLINE static bool marshal_8(struct _marshal_ctx *ctx, uint64_t *val)
ret += f"\tuint32_t _{member.name}_offset;\n"
mark_offset.add(member.name)
for tok in member.val.tokens:
- if isinstance(tok, ExprSym) and tok.name.startswith("&"):
+ if isinstance(tok, idl.ExprSym) and tok.name.startswith("&"):
if tok.name[1:] not in mark_offset:
ret += f"\tuint32_t _{tok.name[1:]}_offset;\n"
mark_offset.add(tok.name[1:])
@@ -1067,7 +661,7 @@ LM_ALWAYS_INLINE static bool marshal_8(struct _marshal_ctx *ctx, uint64_t *val)
ret += f"marshal_{member.typ.name}(ctx, &val->{member.name}[i]);\n"
ret += f"\t err; }})"
elif member.val:
- # Just increment net_offset, don't actually marsha anything (yet).
+ # Just increment net_offset, don't actually marshal anything (yet).
assert member.static_size
ret += (
f"({{ ctx->net_offset += {member.static_size}; false; }})"
@@ -1093,33 +687,36 @@ LM_ALWAYS_INLINE static bool marshal_8(struct _marshal_ctx *ctx, uint64_t *val)
ret += ifdef_pop(0)
# tables / exports #########################################################
- ret += f"""
+ ret += """
/* tables / exports ***********************************************************/
-
-#define _MSG(typ) [{idprefix.upper()}TYP_##typ] = {{ \\
- .name = #typ, \\
- .basesize = sizeof(struct {idprefix}msg_##typ), \\
- .validate = validate_##typ, \\
- .unmarshal = (_unmarshal_fn_t)unmarshal_##typ, \\
- .marshal = (_marshal_fn_t)marshal_##typ, \\
- }}
-#define _NONMSG(num) [num] = {{ \\
- .name = #num, \\
- }}
-
-struct _table_version _{idprefix}versions[{c_ver_enum('NUM')}] = {{
"""
- id2typ: dict[int, Message] = {}
- for msg in [msg for msg in typs if isinstance(msg, Message)]:
+ id2typ: dict[int, idl.Message] = {}
+ for msg in [msg for msg in typs if isinstance(msg, idl.Message)]:
id2typ[msg.msgid] = msg
+ ret += "\n"
+ ret += c_macro(
+ f"#define _MSG(typ) [{idprefix.upper()}TYP_##typ] = {{",
+ f"\t\t.name = #typ,",
+ f"\t\t.basesize = sizeof(struct {idprefix}msg_##typ),",
+ f"\t\t.validate = validate_##typ,",
+ f"\t\t.unmarshal = (_unmarshal_fn_t)unmarshal_##typ,",
+ f"\t\t.marshal = (_marshal_fn_t)marshal_##typ,",
+ f"\t}}",
+ )
+ ret += c_macro(
+ f"#define _NONMSG(num) [num] = {{", f"\t\t.name = #num,", f"\t}}"
+ )
+
+ ret += "\n"
+ ret += f"struct _table_version _{idprefix}versions[{c_ver_enum('NUM')}] = {{\n"
for ver in ["unknown", *sorted(versions)]:
if ver != "unknown":
ret += ifdef_push(1, c_ver_ifdef({ver}))
ret += f"\t[{c_ver_enum(ver)}] = {{ .msgs = {{\n"
for n in range(0, 0x100):
- xmsg: Message | None = id2typ.get(n, None)
+ xmsg: idl.Message | None = id2typ.get(n, None)
if xmsg:
if ver == "unknown": # SPECIAL
if xmsg.name not in ["Tversion", "Rversion", "Rerror"]:
@@ -1137,13 +734,13 @@ struct _table_version _{idprefix}versions[{c_ver_enum('NUM')}] = {{
ret += f"""
LM_FLATTEN bool _{idprefix}validate_stat(struct _validate_ctx *ctx) {{
- return validate_stat(ctx);
+\treturn validate_stat(ctx);
}}
LM_FLATTEN void _{idprefix}unmarshal_stat(struct _unmarshal_ctx *ctx, struct lib9p_stat *out) {{
- unmarshal_stat(ctx, out);
+\tunmarshal_stat(ctx, out);
}}
LM_FLATTEN bool _{idprefix}marshal_stat(struct _marshal_ctx *ctx, struct lib9p_stat *val) {{
- return marshal_stat(ctx, val);
+\treturn marshal_stat(ctx, val);
}}
"""
@@ -1151,36 +748,7 @@ LM_FLATTEN bool _{idprefix}marshal_stat(struct _marshal_ctx *ctx, struct lib9p_s
return ret
-################################################################################
-
-
-class Parser:
- cache: dict[str, tuple[str, list[Type]]] = {}
-
- def parse_file(self, filename: str) -> tuple[str, list[Type]]:
- filename = os.path.normpath(filename)
- if filename not in self.cache:
-
- def get_include(other_filename: str) -> tuple[str, list[Type]]:
- return self.parse_file(os.path.join(filename, "..", other_filename))
-
- self.cache[filename] = parse_file(filename, get_include)
- return self.cache[filename]
-
- def all(self) -> tuple[set[str], list[Type]]:
- ret_versions: set[str] = set()
- ret_typs: dict[str, Type] = {}
- for version, typs in self.cache.values():
- if version in ret_versions:
- raise ValueError(f"duplicate protocol version {repr(version)}")
- ret_versions.add(version)
- for typ in typs:
- if typ.name in ret_typs:
- if typ != ret_typs[typ.name]:
- raise ValueError(f"duplicate type name {repr(typ.name)}")
- else:
- ret_typs[typ.name] = typ
- return ret_versions, list(ret_typs.values())
+# Main #########################################################################
if __name__ == "__main__":
@@ -1188,7 +756,7 @@ if __name__ == "__main__":
if len(sys.argv) < 2:
raise ValueError("requires at least 1 .9p filename")
- parser = Parser()
+ parser = idl.Parser()
for txtname in sys.argv[1:]:
parser.parse_file(txtname)
versions, typs = parser.all()
diff --git a/lib9p/idl/0000-README.md b/lib9p/idl/0000-README.md
index cec27e2..86862b7 100644
--- a/lib9p/idl/0000-README.md
+++ b/lib9p/idl/0000-README.md
@@ -1,5 +1,5 @@
<!--
- 0000-README.md - Overview of 9P protocol definitions
+ lib9p/idl/0000-README.md - Overview of 9P protocol definitions
Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/lib9p/idl/1992-9P0.9p.wip b/lib9p/idl/1992-9P0.9p.wip
index 4278fa3..27d6b33 100644
--- a/lib9p/idl/1992-9P0.9p.wip
+++ b/lib9p/idl/1992-9P0.9p.wip
@@ -1,4 +1,4 @@
-# 1992-9P0.9p - Definitions of 9P0 (Plan 9 1st ed) messages
+# lib9p/idl/1992-9P0.9p - Definitions of 9P0 (Plan 9 1st ed) messages
#
# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
# SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/lib9p/idl/1995-9P1.9p.wip b/lib9p/idl/1995-9P1.9p.wip
index 55814d4..30b9112 100644
--- a/lib9p/idl/1995-9P1.9p.wip
+++ b/lib9p/idl/1995-9P1.9p.wip
@@ -1,4 +1,4 @@
-# 1995-9P1.9p - Definitions of 9P1 (Plan 9 2nd ed and 3rd ed) messages
+# lib9p/idl/1995-9P1.9p - Definitions of 9P1 (Plan 9 2nd ed and 3rd ed) messages
#
# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
# SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/lib9p/idl/1996-Styx.9p.wip b/lib9p/idl/1996-Styx.9p.wip
index 2feb24f..d9d3399 100644
--- a/lib9p/idl/1996-Styx.9p.wip
+++ b/lib9p/idl/1996-Styx.9p.wip
@@ -1,4 +1,4 @@
-# 1996-Styx.9p - Definitions of Styx messages
+# lib9p/idl/1996-Styx.9p - Definitions of Styx messages
#
# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
# SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/lib9p/idl/2002-9P2000.9p b/lib9p/idl/2002-9P2000.9p
index 47d402a..c83f439 100644
--- a/lib9p/idl/2002-9P2000.9p
+++ b/lib9p/idl/2002-9P2000.9p
@@ -1,4 +1,4 @@
-# 2002-9P2000.9p - Definitions of 9P2000 messages
+# lib9p/idl/2002-9P2000.9p - Definitions of 9P2000 messages
#
# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
# SPDX-License-Identifier: AGPL-3.0-or-later
@@ -104,7 +104,7 @@ struct stat = "stat_size[2,val=end-&kern_type]"
# "O"pen flags (flags to pass to Topen and Tcreate)
bitfield o = 1
"0=mode_0" # low bit of the 2-bit READ/WRITE/RDWR/EXEC enum
- "1=mode_1" # high bit of the 2-bit READ/WRITE/RDWR/EXEC enum"
+ "1=mode_1" # high bit of the 2-bit READ/WRITE/RDWR/EXEC enum
#"2=unused"
#"3=unused"
"4=TRUNC"
diff --git a/lib9p/idl/2005-9P2000.u.9p b/lib9p/idl/2005-9P2000.u.9p
index 8b59efa..3eab4ad 100644
--- a/lib9p/idl/2005-9P2000.u.9p
+++ b/lib9p/idl/2005-9P2000.u.9p
@@ -1,4 +1,4 @@
-# 2005-9P2000.u.9p - Definitions of 9P2000.u messages
+# lib9p/idl/2005-9P2000.u.9p - Definitions of 9P2000.u messages
#
# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
# SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/lib9p/idl/2010-9P2000.L.9p.wip b/lib9p/idl/2010-9P2000.L.9p.wip
index 5261f7e..f5e79c5 100644
--- a/lib9p/idl/2010-9P2000.L.9p.wip
+++ b/lib9p/idl/2010-9P2000.L.9p.wip
@@ -1,4 +1,4 @@
-# 2010-9P2000.L.9p - Definitions of 9P2000.L messages
+# lib9p/idl/2010-9P2000.L.9p - Definitions of 9P2000.L messages
#
# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
# SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/lib9p/idl/2012-9P2000.e.9p b/lib9p/idl/2012-9P2000.e.9p
index 27db50f..c113618 100644
--- a/lib9p/idl/2012-9P2000.e.9p
+++ b/lib9p/idl/2012-9P2000.e.9p
@@ -1,4 +1,4 @@
-# 2012-9P2000.e.9p - Definitions of 9P2000.e messages
+# lib9p/idl/2012-9P2000.e.9p - Definitions of 9P2000.e messages
#
# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
# SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/lib9p/idl/__init__.py b/lib9p/idl/__init__.py
new file mode 100644
index 0000000..920d02d
--- /dev/null
+++ b/lib9p/idl/__init__.py
@@ -0,0 +1,491 @@
+# lib9p/idl/__init__.py - A parser for .9p specification files.
+#
+# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+import enum
+import os.path
+import re
+from typing import Callable, Literal, TypeAlias, TypeVar, cast
+
+__all__ = [
+ # entrypoint
+ "Parser",
+ # types
+ "Type",
+ "Primitive",
+ "Number",
+ *["Bitfield", "BitfieldVal"],
+ *["Struct", "StructMember", "Expr", "ExprOp", "ExprSym", "ExprLit"],
+ "Message",
+]
+
+# The syntax that this parses is described in `./0000-README.md`.
+
+# Types ########################################################################
+
+
+class Primitive(enum.Enum):
+ u8 = 1
+ u16 = 2
+ u32 = 4
+ u64 = 8
+
+ @property
+ def in_versions(self) -> set[str]:
+ return set()
+
+ @property
+ def name(self) -> str:
+ return str(self.value)
+
+ @property
+ def static_size(self) -> int:
+ return self.value
+
+
+class Number:
+ name: str
+ in_versions: set[str]
+
+ prim: Primitive
+
+ def __init__(self) -> None:
+ self.in_versions = set()
+
+ @property
+ def static_size(self) -> int:
+ return self.prim.static_size
+
+
+class BitfieldVal:
+ name: str
+ in_versions: set[str]
+
+ val: str
+
+ def __init__(self) -> None:
+ self.in_versions = set()
+
+
+class Bitfield:
+ name: str
+ in_versions: set[str]
+
+ prim: Primitive
+
+ bits: list[str] # bitnames
+ names: dict[str, BitfieldVal] # bits *and* aliases
+
+ def __init__(self) -> None:
+ self.in_versions = set()
+ self.names = {}
+
+ @property
+ def static_size(self) -> int:
+ return self.prim.static_size
+
+ def bit_is_valid(self, bit: str | int, ver: str | None = None) -> bool:
+ """Return whether the given bit is valid in the given protocol
+ version.
+
+ """
+ bitname = self.bits[bit] if isinstance(bit, int) else bit
+ assert bitname in self.bits
+ if not bitname:
+ return False
+ if bitname.startswith("_"):
+ return False
+ if ver and (ver not in self.names[bitname].in_versions):
+ return False
+ return True
+
+
+class ExprLit:
+ val: int
+
+ def __init__(self, val: int) -> None:
+ self.val = val
+
+
+class ExprSym:
+ name: str
+
+ def __init__(self, name: str) -> None:
+ self.name = name
+
+
+class ExprOp:
+ op: Literal["-", "+"]
+
+ def __init__(self, op: Literal["-", "+"]) -> None:
+ self.op = op
+
+
+class Expr:
+ tokens: list[ExprLit | ExprSym | ExprOp]
+
+ def __init__(self) -> None:
+ self.tokens = []
+
+ def __bool__(self) -> bool:
+ return len(self.tokens) > 0
+
+
+class StructMember:
+ # from left-to-right when parsing
+ cnt: str | None = None
+ name: str
+ typ: "Type"
+ max: Expr
+ val: Expr
+
+ in_versions: set[str]
+
+ @property
+ def static_size(self) -> int | None:
+ if self.cnt:
+ return None
+ return self.typ.static_size
+
+
+class Struct:
+ name: str
+ in_versions: set[str]
+
+ members: list[StructMember]
+
+ def __init__(self) -> None:
+ self.in_versions = set()
+
+ @property
+ def static_size(self) -> int | None:
+ size = 0
+ for member in self.members:
+ msize = member.static_size
+ if msize is None:
+ return None
+ size += msize
+ return size
+
+
+class Message(Struct):
+ @property
+ def msgid(self) -> int:
+ assert len(self.members) >= 3
+ assert self.members[1].name == "typ"
+ assert self.members[1].static_size == 1
+ assert self.members[1].val
+ assert len(self.members[1].val.tokens) == 1
+ assert isinstance(self.members[1].val.tokens[0], ExprLit)
+ return self.members[1].val.tokens[0].val
+
+
+Type: TypeAlias = Primitive | Number | Bitfield | Struct | Message
+# type Type = Primitive | Number | Bitfield | Struct | Message # Change to this once we have Python 3.13
+T = TypeVar("T", Number, Bitfield, Struct, Message)
+
+# Parse ########################################################################
+
+re_priname = "(?:1|2|4|8)" # primitive names
+re_symname = "(?:[a-zA-Z_][a-zA-Z_0-9]*)" # "symbol" names; most *.9p-defined names
+re_impname = r"(?:\*|" + re_symname + ")" # names we can import
+re_msgname = r"(?:[TR][a-zA-Z_0-9]*)" # names a message can be
+
+re_memtype = f"(?:{re_symname}|{re_priname})" # typenames that a struct member can be
+
+re_expr = f"(?:(?:-|\\+|[0-9]+|&?{re_symname})+)"
+
+re_bitspec_bit = f"(?P<bit>[0-9]+)\\s*=\\s*(?P<name>{re_symname})"
+re_bitspec_alias = f"(?P<name>{re_symname})\\s*=\\s*(?P<val>\\S+)"
+
+re_memberspec = f"(?:(?P<cnt>{re_symname})\\*\\()?(?P<name>{re_symname})\\[(?P<typ>{re_memtype})(?:,max=(?P<max>{re_expr})|,val=(?P<val>{re_expr}))*\\]\\)?"
+
+
+def parse_bitspec(ver: str, bf: Bitfield, spec: str) -> None:
+ spec = spec.strip()
+
+ bit: int | None
+ val: BitfieldVal
+ if m := re.fullmatch(re_bitspec_bit, spec):
+ bit = int(m.group("bit"))
+ name = m.group("name")
+
+ val = BitfieldVal()
+ val.name = name
+ val.val = f"1<<{bit}"
+ val.in_versions.add(ver)
+
+ if bit < 0 or bit >= len(bf.bits):
+ raise ValueError(f"{bf.name}: bit {bit} is out-of-bounds")
+ if bf.bits[bit]:
+ raise ValueError(f"{bf.name}: bit {bit} already assigned")
+ bf.bits[bit] = val.name
+ elif m := re.fullmatch(re_bitspec_alias, spec):
+ name = m.group("name")
+ valstr = m.group("val")
+
+ val = BitfieldVal()
+ val.name = name
+ val.val = valstr
+ val.in_versions.add(ver)
+ else:
+ raise SyntaxError(f"invalid bitfield spec {repr(spec)}")
+
+ if val.name in bf.names:
+ raise ValueError(f"{bf.name}: name {val.name} already assigned")
+ bf.names[val.name] = val
+
+
+def parse_expr(expr: str) -> Expr:
+ assert re.fullmatch(re_expr, expr)
+ ret = Expr()
+ for tok in re.split("([-+])", expr):
+ if tok == "-" or tok == "+":
+ # I, for the life of me, do not understand why I need this
+ # cast() to keep mypy happy.
+ ret.tokens += [ExprOp(cast(Literal["-", "+"], tok))]
+ elif re.fullmatch("[0-9]+", tok):
+ ret.tokens += [ExprLit(int(tok))]
+ else:
+ ret.tokens += [ExprSym(tok)]
+ return ret
+
+
+def parse_members(ver: str, env: dict[str, Type], struct: Struct, specs: str) -> None:
+ for spec in specs.split():
+ m = re.fullmatch(re_memberspec, spec)
+ if not m:
+ raise SyntaxError(f"invalid member spec {repr(spec)}")
+
+ member = StructMember()
+ member.in_versions = {ver}
+
+ member.name = m.group("name")
+ if any(x.name == member.name for x in struct.members):
+ raise ValueError(f"duplicate member name {repr(member.name)}")
+
+ if m.group("typ") not in env:
+ raise NameError(f"Unknown type {repr(m.group(2))}")
+ member.typ = env[m.group("typ")]
+
+ if cnt := m.group("cnt"):
+ if len(struct.members) == 0 or struct.members[-1].name != cnt:
+ raise ValueError(f"list count must be previous item: {repr(cnt)}")
+ if not isinstance(struct.members[-1].typ, Primitive):
+ raise ValueError(f"list count must be an integer type: {repr(cnt)}")
+ member.cnt = cnt
+
+ if maxstr := m.group("max"):
+ if (not isinstance(member.typ, Primitive)) or member.cnt:
+ raise ValueError("',max=' may only be specified on a non-repeated atom")
+ member.max = parse_expr(maxstr)
+ else:
+ member.max = Expr()
+
+ if valstr := m.group("val"):
+ if (not isinstance(member.typ, Primitive)) or member.cnt:
+ raise ValueError("',val=' may only be specified on a non-repeated atom")
+ member.val = parse_expr(valstr)
+ else:
+ member.val = Expr()
+
+ struct.members += [member]
+
+
+def re_string(grpname: str) -> str:
+ return f'"(?P<{grpname}>[^"]*)"'
+
+
+re_line_version = f"version\\s+{re_string('version')}"
+re_line_import = f"from\\s+(?P<file>\\S+)\\s+import\\s+(?P<syms>{re_impname}(?:\\s*,\\s*{re_impname})*)"
+re_line_num = f"num\\s+(?P<name>{re_symname})\\s*=\\s*(?P<prim>{re_priname})"
+re_line_bitfield = f"bitfield\\s+(?P<name>{re_symname})\\s*=\\s*(?P<prim>{re_priname})"
+re_line_bitfield_ = (
+ f"bitfield\\s+(?P<name>{re_symname})\\s*\\+=\\s*{re_string('member')}"
+)
+re_line_struct = (
+ f"struct\\s+(?P<name>{re_symname})\\s*(?P<op>\\+?=)\\s*{re_string('members')}"
+)
+re_line_msg = (
+ f"msg\\s+(?P<name>{re_msgname})\\s*(?P<op>\\+?=)\\s*{re_string('members')}"
+)
+re_line_cont = f"\\s+{re_string('specs')}" # could be bitfield/struct/msg
+
+
+def parse_file(
+ filename: str, get_include: Callable[[str], tuple[str, list[Type]]]
+) -> tuple[str, list[Type]]:
+ version: str | None = None
+ env: dict[str, Type] = {
+ "1": Primitive.u8,
+ "2": Primitive.u16,
+ "4": Primitive.u32,
+ "8": Primitive.u64,
+ }
+
+ def get_type(name: str, tc: type[T]) -> T:
+ nonlocal env
+ if name not in env:
+ raise NameError(f"Unknown type {repr(name)}")
+ ret = env[name]
+ if (not isinstance(ret, tc)) or (ret.__class__.__name__ != tc.__name__):
+ raise NameError(f"Type {repr(ret.name)} is not a {tc.__name__}")
+ return ret
+
+ with open(filename, "r") as fh:
+ prev: Type | None = None
+ for line in fh:
+ line = line.split("#", 1)[0].rstrip()
+ if not line:
+ continue
+ if m := re.fullmatch(re_line_version, line):
+ if version:
+ raise SyntaxError("must have exactly 1 version line")
+ version = m.group("version")
+ continue
+ if not version:
+ raise SyntaxError("must have exactly 1 version line")
+
+ if m := re.fullmatch(re_line_import, line):
+ other_version, other_typs = get_include(m.group("file"))
+ for symname in m.group("syms").split(sep=","):
+ symname = symname.strip()
+ for typ in other_typs:
+ if typ.name == symname or symname == "*":
+ match typ:
+ case Primitive():
+ pass
+ case Number():
+ typ.in_versions.add(version)
+ case Bitfield():
+ typ.in_versions.add(version)
+ for val in typ.names.values():
+ if other_version in val.in_versions:
+ val.in_versions.add(version)
+ case Struct(): # and Message()
+ typ.in_versions.add(version)
+ for member in typ.members:
+ if other_version in member.in_versions:
+ member.in_versions.add(version)
+ env[typ.name] = typ
+ elif m := re.fullmatch(re_line_num, line):
+ num = Number()
+ num.name = m.group("name")
+ num.in_versions.add(version)
+
+ prim = env[m.group("prim")]
+ assert isinstance(prim, Primitive)
+ num.prim = prim
+
+ env[num.name] = num
+ prev = num
+ elif m := re.fullmatch(re_line_bitfield, line):
+ bf = Bitfield()
+ bf.name = m.group("name")
+ bf.in_versions.add(version)
+
+ prim = env[m.group("prim")]
+ assert isinstance(prim, Primitive)
+ bf.prim = prim
+
+ bf.bits = (prim.static_size * 8) * [""]
+
+ env[bf.name] = bf
+ prev = bf
+ elif m := re.fullmatch(re_line_bitfield_, line):
+ bf = get_type(m.group("name"), Bitfield)
+ parse_bitspec(version, bf, m.group("member"))
+
+ prev = bf
+ elif m := re.fullmatch(re_line_struct, line):
+ match m.group("op"):
+ case "=":
+ struct = Struct()
+ struct.name = m.group("name")
+ struct.in_versions.add(version)
+ struct.members = []
+ parse_members(version, env, struct, m.group("members"))
+
+ env[struct.name] = struct
+ prev = struct
+ case "+=":
+ struct = get_type(m.group("name"), Struct)
+ parse_members(version, env, struct, m.group("members"))
+
+ prev = struct
+ elif m := re.fullmatch(re_line_msg, line):
+ match m.group("op"):
+ case "=":
+ msg = Message()
+ msg.name = m.group("name")
+ msg.in_versions.add(version)
+ msg.members = []
+ parse_members(version, env, msg, m.group("members"))
+
+ env[msg.name] = msg
+ prev = msg
+ case "+=":
+ msg = get_type(m.group("name"), Message)
+ parse_members(version, env, msg, m.group("members"))
+
+ prev = msg
+ elif m := re.fullmatch(re_line_cont, line):
+ match prev:
+ case Bitfield():
+ parse_bitspec(version, prev, m.group("specs"))
+ case Struct(): # and Message()
+ parse_members(version, env, prev, m.group("specs"))
+ case _:
+ raise SyntaxError(
+ "continuation line must come after a bitfield, struct, or msg line"
+ )
+ else:
+ raise SyntaxError(f"invalid line {repr(line)}")
+ if not version:
+ raise SyntaxError("must have exactly 1 version line")
+
+ typs: list[Type] = [x for x in env.values() if not isinstance(x, Primitive)]
+
+ for typ in [typ for typ in typs if isinstance(typ, Struct)]:
+ valid_syms = ["end", *["&" + m.name for m in typ.members]]
+ for member in typ.members:
+ for tok in [*member.max.tokens, *member.val.tokens]:
+ if isinstance(tok, ExprSym) and tok.name not in valid_syms:
+ raise ValueError(
+ f"{typ.name}.{member.name}: invalid sym: {tok.name}"
+ )
+
+ return version, typs
+
+
+# Filesystem ###################################################################
+
+
+class Parser:
+ cache: dict[str, tuple[str, list[Type]]] = {}
+
+ def parse_file(self, filename: str) -> tuple[str, list[Type]]:
+ filename = os.path.normpath(filename)
+ if filename not in self.cache:
+
+ def get_include(other_filename: str) -> tuple[str, list[Type]]:
+ return self.parse_file(os.path.join(filename, "..", other_filename))
+
+ self.cache[filename] = parse_file(filename, get_include)
+ return self.cache[filename]
+
+ def all(self) -> tuple[set[str], list[Type]]:
+ ret_versions: set[str] = set()
+ ret_typs: dict[str, Type] = {}
+ for version, typs in self.cache.values():
+ if version in ret_versions:
+ raise ValueError(f"duplicate protocol version {repr(version)}")
+ ret_versions.add(version)
+ for typ in typs:
+ if typ.name in ret_typs:
+ if typ != ret_typs[typ.name]:
+ raise ValueError(f"duplicate type name {repr(typ.name)}")
+ else:
+ ret_typs[typ.name] = typ
+ return ret_versions, list(ret_typs.values())
diff --git a/lib9p/include/lib9p/9p.generated.h b/lib9p/include/lib9p/9p.generated.h
index 25aacfe..a6dc13c 100644
--- a/lib9p/include/lib9p/9p.generated.h
+++ b/lib9p/include/lib9p/9p.generated.h
@@ -22,7 +22,7 @@
#error config.h must define CONFIG_9P_ENABLE_9P2000_u
#endif
-/* versions *******************************************************************/
+/* enum version ***************************************************************/
enum lib9p_version {
LIB9P_VER_unknown = 0, /* "unknown" */
@@ -40,7 +40,49 @@ enum lib9p_version {
const char *lib9p_version_str(enum lib9p_version);
-/* non-message types **********************************************************/
+/* enum msg_type **************************************************************/
+
+enum lib9p_msg_type { /* uint8_t */
+#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_u
+ LIB9P_TYP_Tversion = 100,
+ LIB9P_TYP_Rversion = 101,
+ LIB9P_TYP_Tauth = 102,
+ LIB9P_TYP_Rauth = 103,
+ LIB9P_TYP_Tattach = 104,
+ LIB9P_TYP_Rattach = 105,
+ LIB9P_TYP_Rerror = 107,
+ LIB9P_TYP_Tflush = 108,
+ LIB9P_TYP_Rflush = 109,
+ LIB9P_TYP_Twalk = 110,
+ LIB9P_TYP_Rwalk = 111,
+ LIB9P_TYP_Topen = 112,
+ LIB9P_TYP_Ropen = 113,
+ LIB9P_TYP_Tcreate = 114,
+ LIB9P_TYP_Rcreate = 115,
+ LIB9P_TYP_Tread = 116,
+ LIB9P_TYP_Rread = 117,
+ LIB9P_TYP_Twrite = 118,
+ LIB9P_TYP_Rwrite = 119,
+ LIB9P_TYP_Tclunk = 120,
+ LIB9P_TYP_Rclunk = 121,
+ LIB9P_TYP_Tremove = 122,
+ LIB9P_TYP_Rremove = 123,
+ LIB9P_TYP_Tstat = 124,
+ LIB9P_TYP_Rstat = 125,
+ LIB9P_TYP_Twstat = 126,
+ LIB9P_TYP_Rwstat = 127,
+#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_u */
+#if CONFIG_9P_ENABLE_9P2000_e
+ LIB9P_TYP_Tsession = 150,
+ LIB9P_TYP_Rsession = 151,
+ LIB9P_TYP_Tsread = 152,
+ LIB9P_TYP_Rsread = 153,
+ LIB9P_TYP_Tswrite = 154,
+ LIB9P_TYP_Rswrite = 155,
+#endif /* CONFIG_9P_ENABLE_9P2000_e */
+};
+
+/* payload types **************************************************************/
#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_u
typedef uint16_t lib9p_tag_t;
@@ -156,51 +198,7 @@ typedef uint8_t lib9p_o_t;
#define LIB9P_O_EXEC ((lib9p_o_t)(3))
#define LIB9P_O_MODE_MASK ((lib9p_o_t)(0b00000011))
#define LIB9P_O_FLAG_MASK ((lib9p_o_t)(0b11111100))
-#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_u */
-/* messages *******************************************************************/
-
-enum lib9p_msg_type { /* uint8_t */
-#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_u
- LIB9P_TYP_Tversion = 100,
- LIB9P_TYP_Rversion = 101,
- LIB9P_TYP_Tauth = 102,
- LIB9P_TYP_Rauth = 103,
- LIB9P_TYP_Tattach = 104,
- LIB9P_TYP_Rattach = 105,
- LIB9P_TYP_Rerror = 107,
- LIB9P_TYP_Tflush = 108,
- LIB9P_TYP_Rflush = 109,
- LIB9P_TYP_Twalk = 110,
- LIB9P_TYP_Rwalk = 111,
- LIB9P_TYP_Topen = 112,
- LIB9P_TYP_Ropen = 113,
- LIB9P_TYP_Tcreate = 114,
- LIB9P_TYP_Rcreate = 115,
- LIB9P_TYP_Tread = 116,
- LIB9P_TYP_Rread = 117,
- LIB9P_TYP_Twrite = 118,
- LIB9P_TYP_Rwrite = 119,
- LIB9P_TYP_Tclunk = 120,
- LIB9P_TYP_Rclunk = 121,
- LIB9P_TYP_Tremove = 122,
- LIB9P_TYP_Rremove = 123,
- LIB9P_TYP_Tstat = 124,
- LIB9P_TYP_Rstat = 125,
- LIB9P_TYP_Twstat = 126,
- LIB9P_TYP_Rwstat = 127,
-#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_u */
-#if CONFIG_9P_ENABLE_9P2000_e
- LIB9P_TYP_Tsession = 150,
- LIB9P_TYP_Rsession = 151,
- LIB9P_TYP_Tsread = 152,
- LIB9P_TYP_Rsread = 153,
- LIB9P_TYP_Tswrite = 154,
- LIB9P_TYP_Rswrite = 155,
-#endif /* CONFIG_9P_ENABLE_9P2000_e */
-};
-
-#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_u
struct lib9p_msg_Tversion {
lib9p_tag_t tag;
uint32_t max_msg_size;