diff options
Diffstat (limited to 'lib9p/idl')
-rw-r--r-- | lib9p/idl/0000-README.md | 5 | ||||
-rw-r--r-- | lib9p/idl/2002-9P2000.9p | 10 | ||||
-rw-r--r-- | lib9p/idl/2005-9P2000.u.9p | 5 | ||||
-rw-r--r-- | lib9p/idl/2010-9P2000.L.9p | 222 | ||||
-rw-r--r-- | lib9p/idl/__init__.py | 203 |
5 files changed, 302 insertions, 143 deletions
diff --git a/lib9p/idl/0000-README.md b/lib9p/idl/0000-README.md index 036de22..e19a1e8 100644 --- a/lib9p/idl/0000-README.md +++ b/lib9p/idl/0000-README.md @@ -41,6 +41,11 @@ and messages (which are a special-case of structures). msg Tname = "size[4,val=end-&size] typ[1,val=TYP] tag[tag] REST..." +Bitfield bit names may be wrapped in `reserved(...)` or +`subfield(...)`; reserved indicates that the bit is named but is not +allowed to be used, and subfield indicates that the bit is part of a +num/enum that is handled by an alias. + Struct fields that have numeric types (either primitives or `num` types) can add to their type `,val=` and/or `,max=` to specify what the exact value must be and/or what the maximum (inclusive) value is. diff --git a/lib9p/idl/2002-9P2000.9p b/lib9p/idl/2002-9P2000.9p index 438e02f..2a4f7ed 100644 --- a/lib9p/idl/2002-9P2000.9p +++ b/lib9p/idl/2002-9P2000.9p @@ -42,7 +42,7 @@ bitfield dm = 4 # that the file is mounted by the kernel as a 9P transport; # that the kernel has a lock on doing I/O on it, so userspace # can't do I/O on it. - "28=_PLAN9_MOUNT" + "28=reserved(PLAN9_MOUNT)" "27=AUTH" "26=TMP" #... @@ -63,7 +63,7 @@ bitfield qt = 1 "7=DIR" "6=APPEND" "5=EXCL" - "4=_PLAN9_MOUNT" # See "_PLAN9_MOUNT" in "dm" above. + "4=reserved(PLAN9_MOUNT)" # See "PLAN9_MOUNT" in "dm" above. "3=AUTH" # Fun historical fact: QTTMP was a relatively late addition to # Plan 9, in 2003-12. @@ -107,12 +107,12 @@ struct stat = "stat_size[2,val=end-&kern_type]" # "O"pen flags (flags to pass to Topen and Tcreate) # Unused bits *must* be 0. 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 + "0=subfield(mode)" # low bit of the 2-bit READ/WRITE/RDWR/EXEC enum + "1=subfield(mode)" # high bit of the 2-bit READ/WRITE/RDWR/EXEC enum #"2=unused" #"3=unused" "4=TRUNC" - #"5=_reserved_CEXEC" # close-on-exec + "5=reserved(CEXEC)" # close-on-exec "6=RCLOSE" # remove-on-close #"7=unused" diff --git a/lib9p/idl/2005-9P2000.u.9p b/lib9p/idl/2005-9P2000.u.9p index d96bbce..fefe3e9 100644 --- a/lib9p/idl/2005-9P2000.u.9p +++ b/lib9p/idl/2005-9P2000.u.9p @@ -14,6 +14,9 @@ from ./2002-9P2000.9p import * num nuid = 4 "NONUID = ~0" +num errno = 4 + "NOERROR = 0" + struct stat += "file_extension[s]" "file_owner_n_uid[nuid]" "file_owner_n_gid[nuid]" @@ -22,7 +25,7 @@ struct stat += "file_extension[s]" msg Tauth += "n_uid[nuid]" msg Tattach += "n_uid[nuid]" -msg Rerror += "errno[4]" +msg Rerror += "errno[errno]" bitfield dm += "23=DEVICE" "21=NAMEDPIPE" diff --git a/lib9p/idl/2010-9P2000.L.9p b/lib9p/idl/2010-9P2000.L.9p index e21d411..7ac86a6 100644 --- a/lib9p/idl/2010-9P2000.L.9p +++ b/lib9p/idl/2010-9P2000.L.9p @@ -5,82 +5,206 @@ # "9P2000.L" Linux extension # https://github.com/chaos/diod/blob/master/protocol.md +# https://github.com/chaos/diod/blob/master/src/libnpfs/protocol.h version "9P2000.L" from ./2002-9P2000.9p import tag, fid, s, qt, qid from ./2002-9P2000.9p import Rerror from ./2002-9P2000.9p import Tversion, Rversion, Tflush, Rflush, Twalk, Rwalk, Tread, Rread, Twrite, Rwrite, Tclunk, Rclunk, Tremove, Rremove -from ./2005-9P2000.u.9p import nuid, Tauth, Rauth, Tattach, Rattach +from ./2005-9P2000.u.9p import nuid, errno, Tauth, Rauth, Tattach, Rattach + +#num errno += # TODO + +num super_magic = 4 + # See <linux/magic.h> (linux.git include/uapi/linux/magic.h). + # + # To quote `util-linux.git:include/statfs_magic.h`: + # "Unfortunately, Linux kernel header file <linux/magic.h> is + # incomplete mess and kernel returns by statfs f_type many numbers + # that are nowhere specified (in API)." + # + # util-linux <statfs_magic.h> is also incomplete. As is the + # statfs(2) man-page. + # + # I'm working on a patchset to the kernel to get <linux/magic.h> + # to be complete, but in the mean-time I'm just not going to + # bother with putting a list here. + # + # TODO + "V9FS_MAGIC=0x01021997" + +# "L"inux "O"pen flags (flags to pass to Tlopen and Tlcreate) +# +# The values are not specified in in protocol.md, but are specified in +# protocol.h (and are different than the Linux kernel's values, which +# vary by architecture). +bitfield lo = 4 + "0=subfield(mode)" # low bit of the 2-bit RDONLY/WRONLY/RDWR/NOACCESS enum + "1=subfield(mode)" # high bit of the 2-bit RDONLY/WRONLY/RDWR/NOACCESS enum + #"2=unused" + #"3=unused" + #"4=unused" + #"5=unused" + "6=CREATE" + "7=EXCL" + "8=NOCTTY" + "9=TRUNC" + "10=APPEND" + "11=NONBLOCK" + "12=DSYNC" + "13=BSD_FASYNC" + "14=DIRECT" + "15=LARGEFILE" + "16=DIRECTORY" + "17=NOFOLLOW" + "18=NOATIME" + "19=CLOEXEC" + "20=SYNC" + + "RDONLY = 0" + "WRONLY = 1" + "RDWR = 2" + "NOACCESS = 3" + + "MODE_MASK = 0b000000000000000000011" + "FLAG_MASK = 0b111111111111111000000" + +# "D"irentry "T"ype +# +# These match the Linux kernel's values. +num dt = 1 + "UNKNOWN = 0" + "NAMED_PIPE = 1" + "CHAR_DEV = 2" + "DIRECTORY = 4" + "BLOCK_DEV = 6" + "REGULAR = 8" + "SYMLINK = 10" + "SOCKET = 12" + "WHITEOUT = 14" + +# Mode +# +# These match the Linux kernel's values. Why is this 32-bits wide +# instead of just 16? Who knows? +bitfield mode = 4 + #... + "15=subfield(fmt)" # bit of the 4-bit FMT_ enum + "14=subfield(fmt)" # bit of the 4-bit FMT_ enum + "13=subfield(fmt)" # bit of the 4-bit FMT_ enum + "12=subfield(fmt)" # bit of the 4-bit FMT_ enum + #... + "11=PERM_SETGROUP" + "10=PERM_SETUSER" + "9=PERM_STICKY" + "8=PERM_OWNER_R" + "7=PERM_OWNER_W" + "6=PERM_OWNER_X" + "5=PERM_GROUP_R" + "4=PERM_GROUP_W" + "3=PERM_GROUP_X" + "2=PERM_OTHER_R" + "1=PERM_OTHER_W" + "0=PERM_OTHER_X" + + "FMT_NAMED_PIPE = LIB9P_DT_NAMED_PIPE<<12" + "FMT_CHAR_DEV = LIB9P_DT_CHAR_DEV<<12" + "FMT_DIRECTORY = LIB9P_DT_DIRECTORY<<12" + "FMT_BLOCK_DEV = LIB9P_DT_BLOCK_DEV<<12" + "FMT_REGULAR = LIB9P_DT_REGULAR<<12" + "FMT_SYMLINK = LIB9P_DT_SYMLINK<<12" + "FMT_SOCKET = LIB9P_DT_SOCKET<<12" + + "PERM_MASK = 0000777" # PERM_* + "FMT_MASK = 0170000" # _fmt_* + +# A boolean value that is for some reason 4 bytes wide. +num b4 = 4 + "FALSE=0" + "TRUE=1" + # all other values are true also bitfield getattr = 8 - "0=MODE" - "1=NLINK" - "2=UID" - "3=GID" - "4=RDEV" - "5=ATIME" - "6=MTIME" - "7=CTIME" - "8=INO" - "9=SIZE" - "10=BLOCKS" - - "11=BTIME" - "12=GEN" - "13=DATA_VERSION" - - "BASIC=0x000007ff" # Mask for fields up to BLOCKS - "ALL =0x00003fff" # Mask for All fields above + "0=MODE" + "1=NLINK" + "2=UID" + "3=GID" + "4=RDEV" + "5=ATIME" + "6=MTIME" + "7=CTIME" + "8=INO" + "9=SIZE" + "10=BLOCKS" + + "11=BTIME" + "12=GEN" + "13=DATA_VERSION" + + "BASIC=0x000007ff" # Mask for fields up to BLOCKS + "ALL =0x00003fff" # Mask for All fields above bitfield setattr = 4 - "0=MODE" - "1=UID" - "2=GID" - "3=SIZE" - "4=ATIME" - "5=MTIME" - "6=CTIME" - "7=ATIME_SET" - "8=MTIME_SET" + "0=MODE" + "1=UID" + "2=GID" + "3=SIZE" + "4=ATIME" + "5=MTIME" + "6=CTIME" + "7=ATIME_SET" + "8=MTIME_SET" num lock_type = 1 - "RDLCK=0" - "WRLCK=1" - "UNLCK=2" + "RDLCK=0" + "WRLCK=1" + "UNLCK=2" bitfield lock_flags = 4 - "0=BLOCK" - "1=RECLAIM" + "0=BLOCK" + "1=RECLAIM" num lock_status = 1 - "SUCCESS=0" - "BLOCKED=1" - "ERROR=2" - "GRACE=3" + "SUCCESS=0" + "BLOCKED=1" + "ERROR=2" + "GRACE=3" #msg Tlerror = "size[4,val=end-&size] typ[1,val=6] tag[tag] illegal" # analogous to 106/Terror -msg Rlerror = "size[4,val=end-&size] typ[1,val=7] tag[tag] ecode[4]" # analogous to 107/Rerror +msg Rlerror = "size[4,val=end-&size] typ[1,val=7] tag[tag] ecode[errno]" # analogous to 107/Rerror msg Tstatfs = "size[4,val=end-&size] typ[1,val=8] tag[tag] fid[fid]" -msg Rstatfs = "size[4,val=end-&size] typ[1,val=9] tag[tag] type[4] bsize[4] blocks[8] bfree[8] bavail[8] files[8] ffree[8] fsid[8] namelen[4]" -msg Tlopen = "size[4,val=end-&size] typ[1,val=12] tag[tag] fid[fid] flags[4]" # analogous to 112/Topen +msg Rstatfs = "size[4,val=end-&size] typ[1,val=9] tag[tag]" # Description | statfs | statvfs + "type[super_magic]" # Type of filesystem | f_type | - + "bsize[4]" # Block size in bytes | f_bsize | f_bsize + # - # Fragment size in bytes | f_frsize (since Linux 2.6) | f_frsize + "blocks[8]" # Size of FS in f_frsize units | f_blocks | f_blocks + "bfree[8]" # Number of free blocks | f_bfree | f_bfree + "bavail[8]" # Number of free blocks for unprivileged users | f_bavail | b_avail + "files[8]" # Number of inodes | f_files | f_files + "ffree[8]" # Number of free inodes | f_ffree | f_ffree + # - # Number of free inodes for unprivileged users | - | f_favail + "fsid[8]" # Filesystem instance ID | f_fsid | f_fsid + # - # Mount flags | f_flags (since Linux 2.6.36) | f_flag + "namelen[4]" # Maximum filename length | f_namemax | f_namemax +msg Tlopen = "size[4,val=end-&size] typ[1,val=12] tag[tag] fid[fid] flags[lo]" # analogous to 112/Topen msg Rlopen = "size[4,val=end-&size] typ[1,val=13] tag[tag] qid[qid] iounit[4]" # analogous to 113/Ropen -msg Tlcreate = "size[4,val=end-&size] typ[1,val=14] tag[tag] fid[fid] name[s] flags[4] mode[4] gid[nuid]" # analogous to 114/Tcreate +msg Tlcreate = "size[4,val=end-&size] typ[1,val=14] tag[tag] fid[fid] name[s] flags[lo] mode[mode] gid[nuid]" # analogous to 114/Tcreate msg Rlcreate = "size[4,val=end-&size] typ[1,val=15] tag[tag] qid[qid] iounit[4]" # analogous to 115/Rcreate msg Tsymlink = "size[4,val=end-&size] typ[1,val=16] tag[tag] fid[fid] name[s] symtgt[s] gid[nuid]" msg Rsymlink = "size[4,val=end-&size] typ[1,val=17] tag[tag] qid[qid]" -msg Tmknod = "size[4,val=end-&size] typ[1,val=18] tag[tag] dfid[fid] name[s] mode[4] major[4] minor[4] gid[nuid]" +msg Tmknod = "size[4,val=end-&size] typ[1,val=18] tag[tag] dfid[fid] name[s] mode[mode] major[4] minor[4] gid[nuid]" msg Rmknod = "size[4,val=end-&size] typ[1,val=19] tag[tag] qid[qid]" msg Trename = "size[4,val=end-&size] typ[1,val=20] tag[tag] fid[fid] dfid[fid] name[s]" msg Rrename = "size[4,val=end-&size] typ[1,val=21] tag[tag]" msg Treadlink = "size[4,val=end-&size] typ[1,val=22] tag[tag] fid[fid]" msg Rreadlink = "size[4,val=end-&size] typ[1,val=23] tag[tag] target[s]" msg Tgetattr = "size[4,val=end-&size] typ[1,val=24] tag[tag] fid[fid] request_mask[getattr]" -msg Rgetattr = "size[4,val=end-&size] typ[1,val=25] tag[tag] valid[8] qid[qid] mode[4] uid[nuid] gid[nuid] nlink[8]" +msg Rgetattr = "size[4,val=end-&size] typ[1,val=25] tag[tag] valid[getattr] qid[qid] mode[mode] uid[nuid] gid[nuid] nlink[8]" "rdev[8] filesize[8] blksize[8] blocks[8]" "atime_sec[8] atime_nsec[8] mtime_sec[8] mtime_nsec[8]" "ctime_sec[8] ctime_nsec[8] btime_sec[8] btime_nsec[8]" "gen[8] data_version[8]" -msg Tsetattr = "size[4,val=end-&size] typ[1,val=26] tag[tag] fid[fid] valid[setattr] mode[4] uid[nuid] gid[nuid] filesize[8] atime_sec[8] atime_nsec[8] mtime_sec[8] mtime_nsec[8]" +msg Tsetattr = "size[4,val=end-&size] typ[1,val=26] tag[tag] fid[fid] valid[setattr] mode[mode] uid[nuid] gid[nuid] filesize[8] atime_sec[8] atime_nsec[8] mtime_sec[8] mtime_nsec[8]" msg Rsetattr = "size[4,val=end-&size] typ[1,val=27] tag[tag]" #... msg Txattrwalk = "size[4,val=end-&size] typ[1,val=30] tag[tag] fid[fid] newfid[fid] name[s]" @@ -89,18 +213,18 @@ msg Txattrcreate = "size[4,val=end-&size] typ[1,val=32] tag[tag] fid[fid] name[s msg Rxattrcreate = "size[4,val=end-&size] typ[1,val=33] tag[tag]" #... msg Treaddir = "size[4,val=end-&size] typ[1,val=40] tag[tag] fid[fid] offset[8] count[4]" -msg Rreaddir = "size[4,val=end-&size] typ[1,val=41] tag[tag] count[4] count*(data[1])" # data is "qid[qid] offset[8] type[1] name[s]" +msg Rreaddir = "size[4,val=end-&size] typ[1,val=41] tag[tag] count[4] count*(data[1])" # data is "qid[qid] offset[8] type[dt] name[s]" #... -msg Tfsync = "size[4,val=end-&size] typ[1,val=50] tag[tag] fid[fid] datasync[4]" +msg Tfsync = "size[4,val=end-&size] typ[1,val=50] tag[tag] fid[fid] datasync[b4]" msg Rfsync = "size[4,val=end-&size] typ[1,val=51] tag[tag]" msg Tlock = "size[4,val=end-&size] typ[1,val=52] tag[tag] fid[fid] type[lock_type] flags[lock_flags] start[8] length[8] proc_id[4] client_id[s]" msg Rlock = "size[4,val=end-&size] typ[1,val=53] tag[tag] status[lock_status]" -msg Tgetlock = "size[4,val=end-&size] typ[1,val=54] tag[tag] fid[fid] type[1] start[8] length[8] proc_id[4] client_id[s]" -msg Rgetlock = "size[4,val=end-&size] typ[1,val=55] tag[tag] type[1] start[8] length[8] proc_id[4] client_id[s]" +msg Tgetlock = "size[4,val=end-&size] typ[1,val=54] tag[tag] fid[fid] type[lock_type] start[8] length[8] proc_id[4] client_id[s]" +msg Rgetlock = "size[4,val=end-&size] typ[1,val=55] tag[tag] type[lock_type] start[8] length[8] proc_id[4] client_id[s]" # ... msg Tlink = "size[4,val=end-&size] typ[1,val=70] tag[tag] dfid[fid] fid[fid] name[s]" msg Rlink = "size[4,val=end-&size] typ[1,val=71] tag[tag]" -msg Tmkdir = "size[4,val=end-&size] typ[1,val=72] tag[tag] dfid[fid] name[s] mode[4] gid[nuid]" +msg Tmkdir = "size[4,val=end-&size] typ[1,val=72] tag[tag] dfid[fid] name[s] mode[mode] gid[nuid]" msg Rmkdir = "size[4,val=end-&size] typ[1,val=73] tag[tag] qid[qid]" msg Trenameat = "size[4,val=end-&size] typ[1,val=74] tag[tag] olddirfid[fid] oldname[s] newdirfid[fid] newname[s]" msg Rrenameat = "size[4,val=end-&size] typ[1,val=75] tag[tag]" diff --git a/lib9p/idl/__init__.py b/lib9p/idl/__init__.py index 042a438..e7b3670 100644 --- a/lib9p/idl/__init__.py +++ b/lib9p/idl/__init__.py @@ -16,7 +16,7 @@ __all__ = [ "Type", "Primitive", "Number", - *["Bitfield", "BitfieldVal"], + *["Bitfield", "Bit", "BitCat", "BitAlias"], *["Struct", "StructMember", "Expr", "ExprOp", "ExprSym", "ExprLit"], "Message", ] @@ -74,27 +74,49 @@ class Number: return self.static_size -class BitfieldVal: +class BitCat(enum.Enum): + UNUSED = 1 + USED = 2 + RESERVED = 3 + SUBFIELD = 4 + + +class Bit: bitname: str in_versions: set[str] + num: int + cat: BitCat - val: str + def __init__(self, num: int) -> None: + self.bitname = "" + self.in_versions = set() + self.num = num + self.cat = BitCat.UNUSED - def __init__(self) -> None: + +class BitAlias: + bitname: str + in_versions: set[str] + val: str # FIXME: Don't have bitfield aliases be raw C expressions + + def __init__(self, name: str, val: str) -> None: + self.bitname = name self.in_versions = set() + self.val = val class Bitfield: typname: str in_versions: set[str] - prim: Primitive + bits: list[Bit] + names: dict[str, Bit | BitAlias] - bits: list[str] # bitnames - names: dict[str, BitfieldVal] # bits *and* aliases - - def __init__(self) -> None: + def __init__(self, name: str, prim: Primitive) -> None: + self.typname = name self.in_versions = set() + self.prim = prim + self.bits = [Bit(i) for i in range(prim.static_size * 8)] self.names = {} @property @@ -107,21 +129,6 @@ class Bitfield: def max_size(self, version: str) -> int: return self.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 @@ -169,12 +176,10 @@ class StructMember: assert self.cnt if not isinstance(self.cnt.typ, Primitive): raise ValueError( - f"list count must be an integer type: {repr(self.cnt.membname)}" + f"list count must be an integer type: {self.cnt.membname!r}" ) if self.cnt.val: # TODO: allow this? - raise ValueError( - f"list count may not have ,val=: {repr(self.cnt.membname)}" - ) + raise ValueError(f"list count may not have ,val=: {self.cnt.membname!r}") return 0 @property @@ -182,17 +187,15 @@ class StructMember: assert self.cnt if not isinstance(self.cnt.typ, Primitive): raise ValueError( - f"list count must be an integer type: {repr(self.cnt.membname)}" + f"list count must be an integer type: {self.cnt.membname!r}" ) if self.cnt.val: # TODO: allow this? - raise ValueError( - f"list count may not have ,val=: {repr(self.cnt.membname)}" - ) + raise ValueError(f"list count may not have ,val=: {self.cnt.membname!r}") if self.cnt.max: # TODO: be more flexible? if len(self.cnt.max.tokens) != 1: raise ValueError( - f"list count ,max= may only have 1 token: {repr(self.cnt.membname)}" + f"list count ,max= may only have 1 token: {self.cnt.membname!r}" ) match tok := self.cnt.max.tokens[0]: case ExprLit(): @@ -203,7 +206,7 @@ class StructMember: return (1 << 63) - 1 case _: raise ValueError( - f'list count ,max= only allows literal, "s32_max", and "s64_max" tokens: {repr(self.cnt.membname)}' + f'list count ,max= only allows literal, "s32_max", and "s64_max" tokens: {self.cnt.membname!r}' ) return (1 << (self.cnt.typ.value * 8)) - 1 @@ -271,12 +274,15 @@ class Message(Struct): type Type = Primitive | Number | Bitfield | Struct | Message +type UserType = Number | Bitfield | Struct | Message T = typing.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_symname_u = "(?:[A-Z_][A-Z_0-9]*)" # upper-case "symbol" names; bit names +re_symname_l = "(?:[a-z_][a-z_0-9]*)" # lower-case "symbol" names; bit names re_impname = r"(?:\*|" + re_symname + ")" # names we can import re_msgname = r"(?:[TR][a-zA-Z_0-9]*)" # names a message can be @@ -286,8 +292,18 @@ re_expr = f"(?:(?:-|\\+|[0-9]+|&?{re_symname})+)" re_numspec = f"(?P<name>{re_symname})\\s*=\\s*(?P<val>\\S+)" -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_bitspec_bit = ( + "(?P<bitnum>[0-9]+)\\s*=\\s*(?:" + + "|".join( + [ + f"(?P<name_used>{re_symname_u})", + f"reserved\\((?P<name_reserved>{re_symname_u})\\)", + f"subfield\\((?P<name_subfield>{re_symname_l})\\)", + ] + ) + + ")" +) +re_bitspec_alias = f"(?P<name>{re_symname_u})\\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}))*\\]\\)?" @@ -299,45 +315,57 @@ def parse_numspec(ver: str, n: Number, spec: str) -> None: name = m.group("name") val = m.group("val") if name in n.vals: - raise ValueError(f"{n.typname}: name {repr(name)} already assigned") + raise ValueError(f"{n.typname}: name {name!r} already assigned") n.vals[name] = val else: - raise SyntaxError(f"invalid num spec {repr(spec)}") + raise SyntaxError(f"invalid num spec {spec!r}") 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.bitname = name - val.val = f"1<<{bit}" - val.in_versions.add(ver) - - if bit < 0 or bit >= len(bf.bits): - raise ValueError(f"{bf.typname}: bit {bit} is out-of-bounds") - if bf.bits[bit]: - raise ValueError(f"{bf.typname}: bit {bit} already assigned") - bf.bits[bit] = val.bitname + bitnum = int(m.group("bitnum")) + if bitnum < 0 or bitnum >= len(bf.bits): + raise ValueError(f"{bf.typname}: bit num {bitnum} out-of-bounds") + bit = bf.bits[bitnum] + if bit.cat != BitCat.UNUSED: + raise ValueError(f"{bf.typname}: bit num {bitnum} already assigned") + if name := m.group("name_used"): + bit.bitname = name + bit.cat = BitCat.USED + bit.in_versions.add(ver) + elif name := m.group("name_reserved"): + bit.bitname = name + bit.cat = BitCat.RESERVED + bit.in_versions.add(ver) + elif name := m.group("name_subfield"): + bit.bitname = name + bit.cat = BitCat.SUBFIELD + bit.in_versions.add(ver) + if bit.bitname: + if bit.bitname in bf.names: + other = bf.names[bit.bitname] + if ( + isinstance(other, Bit) + and other.cat == bit.cat + and bit.cat == BitCat.SUBFIELD + ): + return + raise ValueError( + f"{bf.typname}: bit name {bit.bitname!r} already assigned" + ) + bf.names[bit.bitname] = bit elif m := re.fullmatch(re_bitspec_alias, spec): - name = m.group("name") - valstr = m.group("val") - - val = BitfieldVal() - val.bitname = name - val.val = valstr - val.in_versions.add(ver) + alias = BitAlias(m.group("name"), m.group("val")) + alias.in_versions.add(ver) + if alias.bitname in bf.names: + raise ValueError( + f"{bf.typname}: bit name {alias.bitname!r} already assigned" + ) + bf.names[alias.bitname] = alias else: - raise SyntaxError(f"invalid bitfield spec {repr(spec)}") - - if val.bitname in bf.names: - raise ValueError(f"{bf.typname}: name {val.bitname} already assigned") - bf.names[val.bitname] = val + raise SyntaxError(f"invalid bitfield spec {spec!r}") def parse_expr(expr: str) -> Expr: @@ -359,7 +387,7 @@ def parse_members(ver: str, env: dict[str, Type], struct: Struct, specs: str) -> for spec in specs.split(): m = re.fullmatch(re_memberspec, spec) if not m: - raise SyntaxError(f"invalid member spec {repr(spec)}") + raise SyntaxError(f"invalid member spec {spec!r}") member = StructMember() member.in_versions = {ver} @@ -369,12 +397,12 @@ def parse_members(ver: str, env: dict[str, Type], struct: Struct, specs: str) -> raise ValueError(f"duplicate member name {member.membname!r}") if m.group("typ") not in env: - raise NameError(f"Unknown type {repr(m.group('typ'))}") + raise NameError(f"Unknown type {m.group('typ')!r}") member.typ = env[m.group("typ")] if cnt := m.group("cnt"): if len(struct.members) == 0 or struct.members[-1].membname != cnt: - raise ValueError(f"list count must be previous item: {repr(cnt)}") + raise ValueError(f"list count must be previous item: {cnt!r}") cnt_mem = struct.members[-1] member.cnt = cnt_mem _ = member.max_cnt # force validation @@ -417,8 +445,8 @@ re_line_cont = f"\\s+{re_string('specs')}" # could be bitfield/struct/msg def parse_file( - filename: str, get_include: typing.Callable[[str], tuple[str, list[Type]]] -) -> tuple[str, list[Type]]: + filename: str, get_include: typing.Callable[[str], tuple[str, list[UserType]]] +) -> tuple[str, list[UserType]]: version: str | None = None env: dict[str, Type] = { "1": Primitive.u8, @@ -430,10 +458,10 @@ def parse_file( def get_type(name: str, tc: type[T]) -> T: nonlocal env if name not in env: - raise NameError(f"Unknown type {repr(name)}") + raise NameError(f"Unknown type {name!r}") ret = env[name] if (not isinstance(ret, tc)) or (ret.__class__.__name__ != tc.__name__): - raise NameError(f"Type {repr(ret.typname)} is not a {tc.__name__}") + raise NameError(f"Type {ret.typname!r} is not a {tc.__name__}") return ret with open(filename, "r", encoding="utf-8") as fh: @@ -466,6 +494,9 @@ def parse_file( typ.in_versions.add(version) case Bitfield(): typ.in_versions.add(version) + for bit in typ.bits: + if other_version in bit.in_versions: + bit.in_versions.add(version) for val in typ.names.values(): if other_version in val.in_versions: val.in_versions.add(version) @@ -481,7 +512,7 @@ def parse_file( env[typ.typname] = typ if symname != "*" and not found: raise ValueError( - f"import: {m.group('file')}: no symbol {repr(symname)}" + f"import: {m.group('file')}: no symbol {symname!r}" ) elif m := re.fullmatch(re_line_num, line): num = Number() @@ -497,15 +528,11 @@ def parse_file( env[num.typname] = num prev = num elif m := re.fullmatch(re_line_bitfield, line): - bf = Bitfield() - bf.typname = 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) * [""] + bf = Bitfield(m.group("name"), prim) + bf.in_versions.add(version) if bf.typname in env: raise ValueError(f"duplicate type name {bf.typname!r}") @@ -577,7 +604,7 @@ def parse_file( 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)] + typs: list[UserType] = [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 = [ @@ -607,35 +634,35 @@ def parse_file( class Parser: - cache: dict[str, tuple[str, list[Type]]] = {} + cache: dict[str, tuple[str, list[UserType]]] = {} - def parse_file(self, filename: str) -> tuple[str, list[Type]]: + def parse_file(self, filename: str) -> tuple[str, list[UserType]]: filename = os.path.normpath(filename) if filename not in self.cache: - def get_include(other_filename: str) -> tuple[str, list[Type]]: + def get_include(other_filename: str) -> tuple[str, list[UserType]]: 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]]: + def all(self) -> tuple[set[str], list[UserType]]: ret_versions: set[str] = set() - ret_typs: dict[str, Type] = {} + ret_typs: dict[str, UserType] = {} for version, typs in self.cache.values(): if version in ret_versions: - raise ValueError(f"duplicate protocol version {repr(version)}") + raise ValueError(f"duplicate protocol version {version!r}") ret_versions.add(version) for typ in typs: if typ.typname in ret_typs: if typ != ret_typs[typ.typname]: - raise ValueError(f"duplicate type name {repr(typ.typname)}") + raise ValueError(f"duplicate type name {typ.typname!r}") else: ret_typs[typ.typname] = typ msgids: set[int] = set() for typ in ret_typs.values(): if isinstance(typ, Message): if typ.msgid in msgids: - raise ValueError(f"duplicate msgid {repr(typ.msgid)}") + raise ValueError(f"duplicate msgid {typ.msgid!r}") msgids.add(typ.msgid) return ret_versions, list(ret_typs.values()) |