#!/usr/bin/env python3 # ex: set filetype=python: """Define and implement the Abstract Syntax Tree for the XDR language.""" import sys from typing import List from dataclasses import dataclass from lark import ast_utils, Transformer from lark.tree import Meta this_module = sys.modules[__name__] big_endian = [] excluded_apis = [] header_name = "none" public_apis = [] structs = set() pass_by_reference = set() constants = {} def xdr_quadlen(val: str) -> int: """Return integer XDR width of an XDR type""" if val in constants: octets = constants[val] else: octets = int(val) return int((octets + 3) / 4) symbolic_widths = { "void": ["XDR_void"], "bool": ["XDR_bool"], "int": ["XDR_int"], "unsigned_int": ["XDR_unsigned_int"], "long": ["XDR_long"], "unsigned_long": ["XDR_unsigned_long"], "hyper": ["XDR_hyper"], "unsigned_hyper": ["XDR_unsigned_hyper"], } # Numeric XDR widths are tracked in a dictionary that is keyed # by type_name because sometimes a caller has nothing more than # the type_name to use to figure out the numeric width. max_widths = { "void": 0, "bool": 1, "int": 1, "unsigned_int": 1, "long": 1, "unsigned_long": 1, "hyper": 2, "unsigned_hyper": 2, } @dataclass class _XdrAst(ast_utils.Ast): """Base class for the XDR abstract syntax tree""" @dataclass class _XdrIdentifier(_XdrAst): """Corresponds to 'identifier' in the XDR language grammar""" symbol: str @dataclass class _XdrValue(_XdrAst): """Corresponds to 'value' in the XDR language grammar""" value: str @dataclass class _XdrConstantValue(_XdrAst): """Corresponds to 'constant' in the XDR language grammar""" value: int @dataclass class _XdrTypeSpecifier(_XdrAst): """Corresponds to 'type_specifier' in the XDR language grammar""" type_name: str c_classifier: str = "" @dataclass class _XdrDefinedType(_XdrTypeSpecifier): """Corresponds to a type defined by the input specification""" def symbolic_width(self) -> List: """Return list containing XDR width of type's components""" return [get_header_name().upper() + "_" + self.type_name + "_sz"] def __post_init__(self): if self.type_name in structs: self.c_classifier = "struct " symbolic_widths[self.type_name] = self.symbolic_width() @dataclass class _XdrBuiltInType(_XdrTypeSpecifier): """Corresponds to a built-in XDR type""" def symbolic_width(self) -> List: """Return list containing XDR width of type's components""" return symbolic_widths[self.type_name] @dataclass class _XdrDeclaration(_XdrAst): """Base class of XDR type declarations""" @dataclass class _XdrFixedLengthOpaque(_XdrDeclaration): """A fixed-length opaque declaration""" name: str size: str template: str = "fixed_length_opaque" def max_width(self) -> int: """Return width of type in XDR_UNITS""" return xdr_quadlen(self.size) def symbolic_width(self) -> List: """Return list containing XDR width of type's components""" return ["XDR_QUADLEN(" + self.size + ")"] def __post_init__(self): max_widths[self.name] = self.max_width() symbolic_widths[self.name] = self.symbolic_width() @dataclass class _XdrVariableLengthOpaque(_XdrDeclaration): """A variable-length opaque declaration""" name: str maxsize: str template: str = "variable_length_opaque" def max_width(self) -> int: """Return width of type in XDR_UNITS""" return 1 + xdr_quadlen(self.maxsize) def symbolic_width(self) -> List: """Return list containing XDR width of type's components""" widths = ["XDR_unsigned_int"] if self.maxsize != "0": widths.append("XDR_QUADLEN(" + self.maxsize + ")") return widths def __post_init__(self): max_widths[self.name] = self.max_width() symbolic_widths[self.name] = self.symbolic_width() @dataclass class _XdrString(_XdrDeclaration): """A (NUL-terminated) variable-length string declaration""" name: str maxsize: str template: str = "string" def max_width(self) -> int: """Return width of type in XDR_UNITS""" return 1 + xdr_quadlen(self.maxsize) def symbolic_width(self) -> List: """Return list containing XDR width of type's components""" widths = ["XDR_unsigned_int"] if self.maxsize != "0": widths.append("XDR_QUADLEN(" + self.maxsize + ")") return widths def __post_init__(self): max_widths[self.name] = self.max_width() symbolic_widths[self.name] = self.symbolic_width() @dataclass class _XdrFixedLengthArray(_XdrDeclaration): """A fixed-length array declaration""" name: str spec: _XdrTypeSpecifier size: str template: str = "fixed_length_array" def max_width(self) -> int: """Return width of type in XDR_UNITS""" return xdr_quadlen(self.size) * max_widths[self.spec.type_name] def symbolic_width(self) -> List: """Return list containing XDR width of type's components""" item_width = " + ".join(symbolic_widths[self.spec.type_name]) return ["(" + self.size + " * (" + item_width + "))"] def __post_init__(self): max_widths[self.name] = self.max_width() symbolic_widths[self.name] = self.symbolic_width() @dataclass class _XdrVariableLengthArray(_XdrDeclaration): """A variable-length array declaration""" name: str spec: _XdrTypeSpecifier maxsize: str template: str = "variable_length_array" def max_width(self) -> int: """Return width of type in XDR_UNITS""" return 1 + (xdr_quadlen(self.maxsize) * max_widths[self.spec.type_name]) def symbolic_width(self) -> List: """Return list containing XDR width of type's components""" widths = ["XDR_unsigned_int"] if self.maxsize != "0": item_width = " + ".join(symbolic_widths[self.spec.type_name]) widths.append("(" + self.maxsize + " * (" + item_width + "))") return widths def __post_init__(self): max_widths[self.name] = self.max_width() symbolic_widths[self.name] = self.symbolic_width() @dataclass class _XdrOptionalData(_XdrDeclaration): """An 'optional_data' declaration""" name: str spec: _XdrTypeSpecifier template: str = "optional_data" def max_width(self) -> int: """Return width of type in XDR_UNITS""" return 1 def symbolic_width(self) -> List: """Return list containing XDR width of type's components""" return ["XDR_bool"] def __post_init__(self): structs.add(self.name) pass_by_reference.add(self.name) max_widths[self.name] = self.max_width() symbolic_widths[self.name] = self.symbolic_width() @dataclass class _XdrBasic(_XdrDeclaration): """A 'basic' declaration""" name: str spec: _XdrTypeSpecifier template: str = "basic" def max_width(self) -> int: """Return width of type in XDR_UNITS""" return max_widths[self.spec.type_name] def symbolic_width(self) -> List: """Return list containing XDR width of type's components""" return symbolic_widths[self.spec.type_name] def __post_init__(self): max_widths[self.name] = self.max_width() symbolic_widths[self.name] = self.symbolic_width() @dataclass class _XdrVoid(_XdrDeclaration): """A void declaration""" name: str = "void" template: str = "void" def max_width(self) -> int: """Return width of type in XDR_UNITS""" return 0 def symbolic_width(self) -> List: """Return list containing XDR width of type's components""" return [] @dataclass class _XdrConstant(_XdrAst): """Corresponds to 'constant_def' in the grammar""" name: str value: str def __post_init__(self): if self.value not in constants: constants[self.name] = int(self.value, 0) @dataclass class _XdrEnumerator(_XdrAst): """An 'identifier = value' enumerator""" name: str value: str def __post_init__(self): if self.value not in constants: constants[self.name] = int(self.value, 0) @dataclass class _XdrEnum(_XdrAst): """An XDR enum definition""" name: str minimum: int maximum: int enumerators: List[_XdrEnumerator] def max_width(self) -> int: """Return width of type in XDR_UNITS""" return 1 def symbolic_width(self) -> List: """Return list containing XDR width of type's components""" return ["XDR_int"] def __post_init__(self): max_widths[self.name] = self.max_width() symbolic_widths[self.name] = self.symbolic_width() @dataclass class _XdrStruct(_XdrAst): """An XDR struct definition""" name: str fields: List[_XdrDeclaration] def max_width(self) -> int: """Return width of type in XDR_UNITS""" width = 0 for field in self.fields: width += field.max_width() return width def symbolic_width(self) -> List: """Return list containing XDR width of type's components""" widths = [] for field in self.fields: widths += field.symbolic_width() return widths def __post_init__(self): structs.add(self.name) pass_by_reference.add(self.name) max_widths[self.name] = self.max_width() symbolic_widths[self.name] = self.symbolic_width() @dataclass class _XdrPointer(_XdrAst): """An XDR pointer definition""" name: str fields: List[_XdrDeclaration] def max_width(self) -> int: """Return width of type in XDR_UNITS""" width = 1 for field in self.fields[0:-1]: width += field.max_width() return width def symbolic_width(self) -> List: """Return list containing XDR width of type's components""" widths = [] widths += ["XDR_bool"] for field in self.fields[0:-1]: widths += field.symbolic_width() return widths def __post_init__(self): structs.add(self.name) pass_by_reference.add(self.name) max_widths[self.name] = self.max_width() symbolic_widths[self.name] = self.symbolic_width() @dataclass class _XdrTypedef(_XdrAst): """An XDR typedef""" declaration: _XdrDeclaration def max_width(self) -> int: """Return width of type in XDR_UNITS""" return self.declaration.max_width() def symbolic_width(self) -> List: """Return list containing XDR width of type's components""" return self.declaration.symbolic_width() def __post_init__(self): if isinstance(self.declaration, _XdrBasic): new_type = self.declaration if isinstance(new_type.spec, _XdrDefinedType): if new_type.spec.type_name in pass_by_reference: pass_by_reference.add(new_type.name) max_widths[new_type.name] = self.max_width() symbolic_widths[new_type.name] = self.symbolic_width() @dataclass class _XdrCaseSpec(_XdrAst): """One case in an XDR union""" values: List[str] arm: _XdrDeclaration template: str = "case_spec" @dataclass class _XdrDefaultSpec(_XdrAst): """Default case in an XDR union""" arm: _XdrDeclaration template: str = "default_spec" @dataclass class _XdrUnion(_XdrAst): """An XDR union""" name: str discriminant: _XdrDeclaration cases: List[_XdrCaseSpec] default: _XdrDeclaration def max_width(self) -> int: """Return width of type in XDR_UNITS""" max_width = 0 for case in self.cases: if case.arm.max_width() > max_width: max_width = case.arm.max_width() if self.default: if self.default.arm.max_width() > max_width: max_width = self.default.arm.max_width() return 1 + max_width def symbolic_width(self) -> List: """Return list containing XDR width of type's components""" max_width = 0 for case in self.cases: if case.arm.max_width() > max_width: max_width = case.arm.max_width() width = case.arm.symbolic_width() if self.default: if self.default.arm.max_width() > max_width: max_width = self.default.arm.max_width() width = self.default.arm.symbolic_width() return symbolic_widths[self.discriminant.name] + width def __post_init__(self): structs.add(self.name) pass_by_reference.add(self.name) max_widths[self.name] = self.max_width() symbolic_widths[self.name] = self.symbolic_width() @dataclass class _RpcProcedure(_XdrAst): """RPC procedure definition""" name: str number: str argument: _XdrTypeSpecifier result: _XdrTypeSpecifier @dataclass class _RpcVersion(_XdrAst): """RPC version definition""" name: str number: str procedures: List[_RpcProcedure] @dataclass class _RpcProgram(_XdrAst): """RPC program definition""" name: str number: str versions: List[_RpcVersion] @dataclass class _Pragma(_XdrAst): """Empty class for pragma directives""" @dataclass class Definition(_XdrAst, ast_utils.WithMeta): """Corresponds to 'definition' in the grammar""" meta: Meta value: _XdrAst @dataclass class Specification(_XdrAst, ast_utils.AsList): """Corresponds to 'specification' in the grammar""" definitions: List[Definition] class ParseToAst(Transformer): """Functions that transform productions into AST nodes""" def identifier(self, children): """Instantiate one _XdrIdentifier object""" return _XdrIdentifier(children[0].value) def value(self, children): """Instantiate one _XdrValue object""" if isinstance(children[0], _XdrIdentifier): return _XdrValue(children[0].symbol) return _XdrValue(children[0].children[0].value) def constant(self, children): """Instantiate one _XdrConstantValue object""" match children[0].data: case "decimal_constant": value = int(children[0].children[0].value, base=10) case "hexadecimal_constant": value = int(children[0].children[0].value, base=16) case "octal_constant": value = int(children[0].children[0].value, base=8) return _XdrConstantValue(value) def type_specifier(self, children): """Instantiate one _XdrTypeSpecifier object""" if isinstance(children[0], _XdrIdentifier): name = children[0].symbol return _XdrDefinedType(type_name=name) name = children[0].data.value return _XdrBuiltInType(type_name=name) def constant_def(self, children): """Instantiate one _XdrConstant object""" name = children[0].symbol value = children[1].value return _XdrConstant(name, value) # cel: Python can compute a min() and max() for the enumerator values # so that the generated code can perform proper range checking. def enum(self, children): """Instantiate one _XdrEnum object""" enum_name = children[0].symbol i = 0 enumerators = [] body = children[1] while i < len(body.children): name = body.children[i].symbol value = body.children[i + 1].value enumerators.append(_XdrEnumerator(name, value)) i = i + 2 return _XdrEnum(enum_name, 0, 0, enumerators) def fixed_length_opaque(self, children): """Instantiate one _XdrFixedLengthOpaque declaration object""" name = children[0].symbol size = children[1].value return _XdrFixedLengthOpaque(name, size) def variable_length_opaque(self, children): """Instantiate one _XdrVariableLengthOpaque declaration object""" name = children[0].symbol if children[1] is not None: maxsize = children[1].value else: maxsize = "0" return _XdrVariableLengthOpaque(name, maxsize) def string(self, children): """Instantiate one _XdrString declaration object""" name = children[0].symbol if children[1] is not None: maxsize = children[1].value else: maxsize = "0" return _XdrString(name, maxsize) def fixed_length_array(self, children): """Instantiate one _XdrFixedLengthArray declaration object""" spec = children[0] name = children[1].symbol size = children[2].value return _XdrFixedLengthArray(name, spec, size) def variable_length_array(self, children): """Instantiate one _XdrVariableLengthArray declaration object""" spec = children[0] name = children[1].symbol if children[2] is not None: maxsize = children[2].value else: maxsize = "0" return _XdrVariableLengthArray(name, spec, maxsize) def optional_data(self, children): """Instantiate one _XdrOptionalData declaration object""" spec = children[0] name = children[1].symbol return _XdrOptionalData(name, spec) def basic(self, children): """Instantiate one _XdrBasic object""" spec = children[0] name = children[1].symbol return _XdrBasic(name, spec) def void(self, children): """Instantiate one _XdrVoid declaration object""" return _XdrVoid() def struct(self, children): """Instantiate one _XdrStruct object""" name = children[0].symbol fields = children[1].children last_field = fields[-1] if ( isinstance(last_field, _XdrOptionalData) and name == last_field.spec.type_name ): return _XdrPointer(name, fields) return _XdrStruct(name, fields) def typedef(self, children): """Instantiate one _XdrTypedef object""" new_type = children[0] return _XdrTypedef(new_type) def case_spec(self, children): """Instantiate one _XdrCaseSpec object""" values = [] for item in children[0:-1]: values.append(item.value) arm = children[-1] return _XdrCaseSpec(values, arm) def default_spec(self, children): """Instantiate one _XdrDefaultSpec object""" arm = children[0] return _XdrDefaultSpec(arm) def union(self, children): """Instantiate one _XdrUnion object""" name = children[0].symbol body = children[1] discriminant = body.children[0].children[0] cases = body.children[1:-1] default = body.children[-1] return _XdrUnion(name, discriminant, cases, default) def procedure_def(self, children): """Instantiate one _RpcProcedure object""" result = children[0] name = children[1].symbol argument = children[2] number = children[3].value return _RpcProcedure(name, number, argument, result) def version_def(self, children): """Instantiate one _RpcVersion object""" name = children[0].symbol number = children[-1].value procedures = children[1:-1] return _RpcVersion(name, number, procedures) def program_def(self, children): """Instantiate one _RpcProgram object""" name = children[0].symbol number = children[-1].value versions = children[1:-1] return _RpcProgram(name, number, versions) def pragma_def(self, children): """Instantiate one _Pragma object""" directive = children[0].children[0].data match directive: case "big_endian_directive": big_endian.append(children[1].symbol) case "exclude_directive": excluded_apis.append(children[1].symbol) case "header_directive": global header_name header_name = children[1].symbol case "public_directive": public_apis.append(children[1].symbol) case _: raise NotImplementedError("Directive not supported") return _Pragma() transformer = ast_utils.create_transformer(this_module, ParseToAst()) def transform_parse_tree(parse_tree): """Transform productions into an abstract syntax tree""" return transformer.transform(parse_tree) def get_header_name() -> str: """Return header name set by pragma header directive""" return header_name