| #!/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__] |
| |
| excluded_apis = [] |
| header_name = "none" |
| public_apis = [] |
| enums = set() |
| structs = set() |
| pass_by_reference = set() |
| |
| |
| @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""" |
| |
| |
| @dataclass |
| class _XdrBuiltInType(_XdrTypeSpecifier): |
| """Corresponds to a built-in XDR type""" |
| |
| |
| @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" |
| |
| |
| @dataclass |
| class _XdrVariableLengthOpaque(_XdrDeclaration): |
| """A variable-length opaque declaration""" |
| |
| name: str |
| maxsize: str |
| template: str = "variable_length_opaque" |
| |
| |
| @dataclass |
| class _XdrVariableLengthString(_XdrDeclaration): |
| """A (NUL-terminated) variable-length string declaration""" |
| |
| name: str |
| maxsize: str |
| template: str = "variable_length_string" |
| |
| |
| @dataclass |
| class _XdrFixedLengthArray(_XdrDeclaration): |
| """A fixed-length array declaration""" |
| |
| name: str |
| spec: _XdrTypeSpecifier |
| size: str |
| template: str = "fixed_length_array" |
| |
| |
| @dataclass |
| class _XdrVariableLengthArray(_XdrDeclaration): |
| """A variable-length array declaration""" |
| |
| name: str |
| spec: _XdrTypeSpecifier |
| maxsize: str |
| template: str = "variable_length_array" |
| |
| |
| @dataclass |
| class _XdrOptionalData(_XdrDeclaration): |
| """An 'optional_data' declaration""" |
| |
| name: str |
| spec: _XdrTypeSpecifier |
| template: str = "optional_data" |
| |
| |
| @dataclass |
| class _XdrBasic(_XdrDeclaration): |
| """A 'basic' declaration""" |
| |
| name: str |
| spec: _XdrTypeSpecifier |
| template: str = "basic" |
| |
| |
| @dataclass |
| class _XdrVoid(_XdrDeclaration): |
| """A void declaration""" |
| |
| template: str = "void" |
| |
| |
| @dataclass |
| class _XdrConstant(_XdrAst): |
| """Corresponds to 'constant_def' in the grammar""" |
| |
| name: str |
| value: str |
| |
| |
| @dataclass |
| class _XdrEnumerator(_XdrAst): |
| """An 'identifier = value' enumerator""" |
| |
| name: str |
| value: str |
| |
| |
| @dataclass |
| class _XdrEnum(_XdrAst): |
| """An XDR enum definition""" |
| |
| name: str |
| minimum: int |
| maximum: int |
| enumerators: List[_XdrEnumerator] |
| |
| |
| @dataclass |
| class _XdrStruct(_XdrAst): |
| """An XDR struct definition""" |
| |
| name: str |
| fields: List[_XdrDeclaration] |
| |
| |
| @dataclass |
| class _XdrPointer(_XdrAst): |
| """An XDR pointer definition""" |
| |
| name: str |
| fields: List[_XdrDeclaration] |
| |
| |
| @dataclass |
| class _XdrTypedef(_XdrAst): |
| """An XDR typedef""" |
| |
| declaration: _XdrDeclaration |
| |
| |
| @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 |
| |
| |
| @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 type_specifier object""" |
| c_classifier = "" |
| if isinstance(children[0], _XdrIdentifier): |
| name = children[0].symbol |
| if name in enums: |
| c_classifier = "enum " |
| if name in structs: |
| c_classifier = "struct " |
| return _XdrDefinedType( |
| type_name=name, |
| c_classifier=c_classifier, |
| ) |
| |
| token = children[0].data |
| return _XdrBuiltInType( |
| type_name=token.value, |
| c_classifier=c_classifier, |
| ) |
| |
| 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 |
| enums.add(enum_name) |
| |
| 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 variable_length_string(self, children): |
| """Instantiate one _XdrVariableLengthString declaration object""" |
| name = children[0].symbol |
| if children[1] is not None: |
| maxsize = children[1].value |
| else: |
| maxsize = "0" |
| |
| return _XdrVariableLengthString(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 |
| structs.add(name) |
| pass_by_reference.add(name) |
| |
| 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 |
| structs.add(name) |
| pass_by_reference.add(name) |
| 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] |
| if isinstance(new_type, _XdrBasic) and isinstance( |
| new_type.spec, _XdrDefinedType |
| ): |
| if new_type.spec.type_name in pass_by_reference: |
| pass_by_reference.add(new_type.name) |
| |
| 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 |
| structs.add(name) |
| pass_by_reference.add(name) |
| |
| 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 "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 |