| # Copyright 2019 Google LLC |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """module_ir contains code for generating module-level IRs from parse trees. |
| |
| The primary export is build_ir(), which takes a parse tree (as returned by a |
| parser from lr1.py), and returns a module-level intermediate representation |
| ("module IR"). |
| |
| This module also notably exports PRODUCTIONS and START_SYMBOL, which should be |
| fed to lr1.Grammar in order to create a parser for the Emboss language. |
| """ |
| |
| import re |
| import sys |
| |
| from compiler.util import ir_pb2 |
| from compiler.util import name_conversion |
| from compiler.util import parser_types |
| |
| |
| # Intermediate types; should not be found in the final IR. |
| class _List(object): |
| """A list with source location information.""" |
| __slots__ = ('list', 'source_location') |
| |
| def __init__(self, l): |
| assert isinstance(l, list), "_List object must wrap list, not '%r'" % l |
| self.list = l |
| self.source_location = ir_pb2.Location() |
| |
| |
| class _ExpressionTail(object): |
| """A fragment of an expression with an operator and right-hand side. |
| |
| _ExpressionTail is the tail of an expression, consisting of an operator and |
| the right-hand argument to the operator; for example, in the expression (6+8), |
| the _ExpressionTail would be "+8". |
| |
| This is used as a temporary object while converting the right-recursive |
| "expression" and "times-expression" productions into left-associative |
| Expressions. |
| |
| Attributes: |
| operator: An ir_pb2.Word of the operator's name. |
| expression: The expression on the right side of the operator. |
| source_location: The source location of the operation fragment. |
| """ |
| __slots__ = ('operator', 'expression', 'source_location') |
| |
| def __init__(self, operator, expression): |
| self.operator = operator |
| self.expression = expression |
| self.source_location = ir_pb2.Location() |
| |
| |
| class _FieldWithType(object): |
| """A field with zero or more types defined inline with that field.""" |
| __slots__ = ('field', 'subtypes', 'source_location') |
| |
| def __init__(self, field, subtypes=None): |
| self.field = field |
| self.subtypes = subtypes or [] |
| self.source_location = ir_pb2.Location() |
| |
| |
| def build_ir(parse_tree, used_productions=None): |
| r"""Builds a module-level intermediate representation from a valid parse tree. |
| |
| The parse tree is precisely dictated by the exact productions in the grammar |
| used by the parser, with no semantic information. _really_build_ir transforms |
| this "raw" form into a stable, cooked representation, thereby isolating |
| subsequent steps from the exact details of the grammar. |
| |
| (Probably incomplete) list of transformations: |
| |
| * ParseResult and Token nodes are replaced with Module, Attribute, Struct, |
| Type, etc. objects. |
| |
| * Purely syntactic tokens ('"["', '"struct"', etc.) are discarded. |
| |
| * Repeated elements are transformed from tree form to list form: |
| |
| a* |
| / \ |
| b a* |
| / \ |
| c a* |
| / \ |
| d a* |
| |
| (where b, c, and d are nodes of type "a") becomes [b, c, d]. |
| |
| * The values of numeric constants (Number, etc. tokens) are parsed. |
| |
| * Different classes of names (snake_names, CamelNames, ShoutyNames) are |
| folded into a single "Name" type, since they are guaranteed to appear in |
| the correct places in the parse tree. |
| |
| |
| Arguments: |
| parse_tree: A parse tree. Each leaf node should be a parser_types.Token |
| object, and each non-leaf node should have a 'symbol' attribute specifying |
| which grammar symbol it represents, and a 'children' attribute containing |
| a list of child nodes. This is the format returned by the parsers |
| produced by the lr1 module, when run against tokens from the tokenizer |
| module. |
| used_productions: If specified, used_productions.add() will be called with |
| each production actually used in parsing. This can be useful when |
| developing the grammar and writing tests; in particular, it can be used to |
| figure out which productions are *not* used when parsing a particular |
| file. |
| |
| Returns: |
| A module-level intermediate representation (module IR) for an Emboss module |
| (source file). This IR will not have symbols resolved; that must be done on |
| a forest of module IRs so that names from other modules can be resolved. |
| """ |
| |
| # TODO(b/140259131): Refactor _really_build_ir to be less recursive/use an |
| # explicit stack. |
| old_recursion_limit = sys.getrecursionlimit() |
| sys.setrecursionlimit(16 * 1024) # ~8000 top-level entities in one module. |
| try: |
| result = _really_build_ir(parse_tree, used_productions) |
| finally: |
| sys.setrecursionlimit(old_recursion_limit) |
| return result |
| |
| |
| def _really_build_ir(parse_tree, used_productions): |
| """Real implementation of build_ir().""" |
| if used_productions is None: |
| used_productions = set() |
| if hasattr(parse_tree, 'children'): |
| parsed_children = [_really_build_ir(child, used_productions) |
| for child in parse_tree.children] |
| used_productions.add(parse_tree.production) |
| result = _handlers[parse_tree.production](*parsed_children) |
| if parse_tree.source_location is not None: |
| result.source_location.CopyFrom(parse_tree.source_location) |
| return result |
| else: |
| # For leaf nodes, the temporary "IR" is just the token. Higher-level rules |
| # will translate it to a real IR. |
| assert isinstance(parse_tree, parser_types.Token), str(parse_tree) |
| return parse_tree |
| |
| # Map of productions to their handlers. |
| _handlers = {} |
| |
| _anonymous_name_counter = 0 |
| |
| |
| def _get_anonymous_field_name(): |
| global _anonymous_name_counter |
| _anonymous_name_counter += 1 |
| return 'emboss_reserved_anonymous_field_{}'.format(_anonymous_name_counter) |
| |
| |
| def _handles(production_text): |
| """_handles marks a function as the handler for a particular production.""" |
| production = parser_types.Production.parse(production_text) |
| |
| def handles(f): |
| _handlers[production] = f |
| return f |
| |
| return handles |
| |
| |
| def _make_prelude_import(position): |
| """Helper function to construct a synthetic ir_pb2.Import for the prelude.""" |
| location = parser_types.make_location(position, position) |
| return ir_pb2.Import( |
| file_name=ir_pb2.String(text='', source_location=location), |
| local_name=ir_pb2.Word(text='', source_location=location), |
| source_location=location) |
| |
| |
| def _text_to_operator(text): |
| """Converts an operator's textual name to its corresponding enum.""" |
| operations = { |
| '+': ir_pb2.FunctionMapping.ADDITION, |
| '-': ir_pb2.FunctionMapping.SUBTRACTION, |
| '*': ir_pb2.FunctionMapping.MULTIPLICATION, |
| '==': ir_pb2.FunctionMapping.EQUALITY, |
| '!=': ir_pb2.FunctionMapping.INEQUALITY, |
| '&&': ir_pb2.FunctionMapping.AND, |
| '||': ir_pb2.FunctionMapping.OR, |
| '>': ir_pb2.FunctionMapping.GREATER, |
| '>=': ir_pb2.FunctionMapping.GREATER_OR_EQUAL, |
| '<': ir_pb2.FunctionMapping.LESS, |
| '<=': ir_pb2.FunctionMapping.LESS_OR_EQUAL, |
| } |
| return operations[text] |
| |
| |
| def _text_to_function(text): |
| """Converts a function's textual name to its corresponding enum.""" |
| functions = { |
| '$max': ir_pb2.FunctionMapping.MAXIMUM, |
| '$present': ir_pb2.FunctionMapping.PRESENCE, |
| '$upper_bound': ir_pb2.FunctionMapping.UPPER_BOUND, |
| '$lower_bound': ir_pb2.FunctionMapping.LOWER_BOUND, |
| } |
| return functions[text] |
| |
| |
| ################################################################################ |
| # Grammar & parse tree to IR translation. |
| # |
| # From here to (almost) the end of the file are functions which recursively |
| # build an IR. The @_handles annotations indicate the exact grammar |
| # production(s) handled by each function. The handler function should take |
| # exactly one argument for each symbol in the production's RHS. |
| # |
| # The actual Emboss grammar is extracted directly from the @_handles |
| # annotations, so this is also the grammar definition. For convenience, the |
| # grammar can be viewed separately in g3doc/grammar.md. |
| # |
| # At the end, symbols whose names end in "*", "+", or "?" are extracted from the |
| # grammar, and appropriate productions are added for zero-or-more, one-or-more, |
| # or zero-or-one lists, respectively. (This is analogous to the *, +, and ? |
| # operators in regex.) It is necessary for this to happen here (and not in |
| # lr1.py) because the generated productions must be associated with |
| # IR-generation functions. |
| |
| |
| # A module file is a list of documentation, then imports, then top-level |
| # attributes, then type definitions. Any section may be missing. |
| # TODO(bolms): Should Emboss disallow completely empty files? |
| @_handles('module -> comment-line* doc-line* import-line* attribute-line*' |
| ' type-definition*') |
| def _file(leading_newlines, docs, imports, attributes, type_definitions): |
| """Assembles the top-level IR for a module.""" |
| del leading_newlines # Unused. |
| # Figure out the best synthetic source_location for the synthesized prelude |
| # import. |
| if imports.list: |
| position = imports.list[0].source_location.start |
| elif docs.list: |
| position = docs.list[0].source_location.end |
| elif attributes.list: |
| position = attributes.list[0].source_location.start |
| elif type_definitions.list: |
| position = type_definitions.list[0].source_location.start |
| else: |
| position = 1, 1 |
| |
| # If the source file is completely empty, build_ir won't automatically |
| # populate the source_location attribute for the module. |
| if (not docs.list and not imports.list and not attributes.list and |
| not type_definitions.list): |
| module_source_location = parser_types.make_location((1, 1), (1, 1)) |
| else: |
| module_source_location = None |
| |
| return ir_pb2.Module( |
| documentation=docs.list, |
| foreign_import=[_make_prelude_import(position)] + imports.list, |
| attribute=attributes.list, |
| type=type_definitions.list, |
| source_location=module_source_location) |
| |
| |
| @_handles('import-line ->' |
| ' "import" string-constant "as" snake-word Comment? eol') |
| def _import(import_, file_name, as_, local_name, comment, eol): |
| del import_, as_, comment, eol # Unused |
| return ir_pb2.Import(file_name=file_name, local_name=local_name) |
| |
| |
| @_handles('doc-line -> doc Comment? eol') |
| def _doc_line(doc, comment, eol): |
| del comment, eol # Unused. |
| return doc |
| |
| |
| @_handles('doc -> Documentation') |
| def _doc(documentation): |
| # As a special case, an empty documentation string may omit the trailing |
| # space. |
| if documentation.text == '--': |
| doc_text = '-- ' |
| else: |
| doc_text = documentation.text |
| assert doc_text[0:3] == '-- ', ( |
| "Documentation token '{}' in unknown format.".format( |
| documentation.text)) |
| return ir_pb2.Documentation(text=doc_text[3:]) |
| |
| |
| # A attribute-line is just a attribute on its own line. |
| @_handles('attribute-line -> attribute Comment? eol') |
| def _attribute_line(attr, comment, eol): |
| del comment, eol # Unused. |
| return attr |
| |
| |
| # A attribute is [name = value]. |
| @_handles('attribute -> "[" attribute-context? "$default"?' |
| ' snake-word ":" attribute-value "]"') |
| def _attribute(open_bracket, context_specifier, default_specifier, name, colon, |
| attribute_value, close_bracket): |
| del open_bracket, colon, close_bracket # Unused. |
| if context_specifier.list: |
| return ir_pb2.Attribute(name=name, |
| value=attribute_value, |
| is_default=bool(default_specifier.list), |
| back_end=context_specifier.list[0]) |
| else: |
| return ir_pb2.Attribute(name=name, |
| value=attribute_value, |
| is_default=bool(default_specifier.list)) |
| |
| |
| @_handles('attribute-context -> "(" snake-word ")"') |
| def _attribute_context(open_paren, context_name, close_paren): |
| del open_paren, close_paren # Unused. |
| return context_name |
| |
| |
| @_handles('attribute-value -> expression') |
| def _attribute_value_expression(expression): |
| return ir_pb2.AttributeValue(expression=expression) |
| |
| |
| @_handles('attribute-value -> string-constant') |
| def _attribute_value_string(string): |
| return ir_pb2.AttributeValue(string_constant=string) |
| |
| |
| @_handles('boolean-constant -> BooleanConstant') |
| def _boolean_constant(boolean): |
| return ir_pb2.BooleanConstant(value=(boolean.text == 'true')) |
| |
| |
| @_handles('string-constant -> String') |
| def _string_constant(string): |
| """Turns a String token into an ir_pb2.String, with proper unescaping. |
| |
| Arguments: |
| string: A String token. |
| |
| Returns: |
| An ir_pb2.String with the "text" field set to the unescaped value of |
| string.text. |
| """ |
| # TODO(bolms): If/when this logic becomes more complex (e.g., to handle \NNN |
| # or \xNN escapes), extract this into a separate module with separate tests. |
| assert string.text[0] == '"' |
| assert string.text[-1] == '"' |
| assert len(string.text) >= 2 |
| result = [] |
| for substring in re.split(r'(\\.)', string.text[1:-1]): |
| if substring and substring[0] == '\\': |
| assert len(substring) == 2 |
| result.append({'\\': '\\', '"': '"', 'n': '\n'}[substring[1]]) |
| else: |
| result.append(substring) |
| return ir_pb2.String(text=''.join(result)) |
| |
| |
| # In Emboss, '&&' and '||' may not be mixed without parentheses. These are all |
| # fine: |
| # |
| # x && y && z |
| # x || y || z |
| # (x || y) && z |
| # x || (y && z) |
| # |
| # These are syntax errors: |
| # |
| # x || y && z |
| # x && y || z |
| # |
| # This is accomplished by making && and || separate-but-equal in the precedence |
| # hierarchy. Instead of the more traditional: |
| # |
| # logical-expression -> or-expression |
| # or-expression -> and-expression or-expression-right* |
| # or-expression-right -> '||' and-expression |
| # and-expression -> equality-expression and-expression-right* |
| # and-expression-right -> '&&' equality-expression |
| # |
| # Or, using yacc-style precedence specifiers: |
| # |
| # %left "||" |
| # %left "&&" |
| # expression -> expression |
| # | expression '||' expression |
| # | expression '&&' expression |
| # |
| # Emboss uses a slightly more complex grammar, in which '&&' and '||' are |
| # parallel, but unmixable: |
| # |
| # logical-expression -> and-expression |
| # | or-expression |
| # | equality-expression |
| # or-expression -> equality-expression or-expression-right+ |
| # or-expression-right -> '||' equality-expression |
| # and-expression -> equality-expression and-expression-right+ |
| # and-expression-right -> '&&' equality-expression |
| # |
| # In either case, explicit parenthesization is handled elsewhere in the grammar. |
| @_handles('logical-expression -> and-expression') |
| @_handles('logical-expression -> or-expression') |
| @_handles('logical-expression -> comparison-expression') |
| @_handles('choice-expression -> logical-expression') |
| @_handles('expression -> choice-expression') |
| def _expression(expression): |
| return expression |
| |
| |
| # The `logical-expression`s here means that ?: can't be chained without |
| # parentheses. `x < 0 ? -1 : (x == 0 ? 0 : 1)` is OK, but `x < 0 ? -1 : x == 0 |
| # ? 0 : 1` is not. Parentheses are also needed in the middle: `x <= 0 ? x < 0 ? |
| # -1 : 0 : 1` is not syntactically valid. |
| @_handles('choice-expression -> logical-expression "?" logical-expression' |
| ' ":" logical-expression') |
| def _choice_expression(condition, question, if_true, colon, if_false): |
| location = parser_types.make_location( |
| condition.source_location.start, if_false.source_location.end) |
| operator_location = parser_types.make_location( |
| question.source_location.start, colon.source_location.end) |
| # The function_name is a bit weird, but should suffice for any error messages |
| # that might need it. |
| return ir_pb2.Expression( |
| function=ir_pb2.Function(function=ir_pb2.FunctionMapping.CHOICE, |
| args=[condition, if_true, if_false], |
| function_name=ir_pb2.Word( |
| text='?:', |
| source_location=operator_location), |
| source_location=location)) |
| |
| |
| @_handles('comparison-expression -> additive-expression') |
| def _no_op_comparative_expression(expression): |
| return expression |
| |
| |
| @_handles('comparison-expression ->' |
| ' additive-expression inequality-operator additive-expression') |
| def _comparative_expression(left, operator, right): |
| location = parser_types.make_location( |
| left.source_location.start, right.source_location.end) |
| return ir_pb2.Expression( |
| function=ir_pb2.Function(function=_text_to_operator(operator.text), |
| args=[left, right], |
| function_name=operator, |
| source_location=location)) |
| |
| |
| @_handles('additive-expression -> times-expression additive-expression-right*') |
| @_handles('times-expression -> negation-expression times-expression-right*') |
| @_handles('and-expression -> comparison-expression and-expression-right+') |
| @_handles('or-expression -> comparison-expression or-expression-right+') |
| def _binary_operator_expression(expression, expression_right): |
| """Builds the IR for a chain of equal-precedence left-associative operations. |
| |
| _binary_operator_expression transforms a right-recursive list of expression |
| tails into a left-associative Expression tree. For example, given the |
| arguments: |
| |
| 6, (Tail("+", 7), Tail("-", 8), Tail("+", 10)) |
| |
| _expression produces a structure like: |
| |
| Expression(Expression(Expression(6, "+", 7), "-", 8), "+", 10) |
| |
| This transformation is necessary because strict LR(1) grammars do not allow |
| left recursion. |
| |
| Note that this method is used for several productions; each of those |
| productions handles a different precedence level, but are identical in form. |
| |
| Arguments: |
| expression: An ir_pb2.Expression which is the head of the (expr, operator, |
| expr, operator, expr, ...) list. |
| expression_right: A list of _ExpressionTails corresponding to the (operator, |
| expr, operator, expr, ...) list that comes after expression. |
| |
| Returns: |
| An ir_pb2.Expression with the correct recursive structure to represent a |
| list of left-associative operations. |
| """ |
| e = expression |
| for right in expression_right.list: |
| location = parser_types.make_location( |
| e.source_location.start, right.source_location.end) |
| e = ir_pb2.Expression( |
| function=ir_pb2.Function( |
| function=_text_to_operator(right.operator.text), |
| args=[e, right.expression], |
| function_name=right.operator, |
| source_location=location), |
| source_location=location) |
| return e |
| |
| |
| @_handles('comparison-expression ->' |
| ' additive-expression equality-expression-right+') |
| @_handles('comparison-expression ->' |
| ' additive-expression less-expression-right-list') |
| @_handles('comparison-expression ->' |
| ' additive-expression greater-expression-right-list') |
| def _chained_comparison_expression(expression, expression_right): |
| """Builds the IR for a chain of comparisons, like a == b == c. |
| |
| Like _binary_operator_expression, _chained_comparison_expression transforms a |
| right-recursive list of expression tails into a left-associative Expression |
| tree. Unlike _binary_operator_expression, extra AND nodes are added. For |
| example, the following expression: |
| |
| 0 <= b <= 64 |
| |
| must be translated to the conceptually-equivalent expression: |
| |
| 0 <= b && b <= 64 |
| |
| (The middle subexpression is duplicated -- this would be a problem in a |
| programming language like C where expressions like `x++` have side effects, |
| but side effects do not make sense in a data definition language like Emboss.) |
| |
| _chained_comparison_expression receives a left-hand head expression and a list |
| of tails, like: |
| |
| 6, (Tail("<=", b), Tail("<=", 64)) |
| |
| which it translates to a structure like: |
| |
| Expression(Expression(6, "<=", b), "&&", Expression(b, "<=", 64)) |
| |
| The Emboss grammar is constructed such that sequences of "<", "<=", and "==" |
| comparisons may be chained, and sequences of ">", ">=", and "==" can be |
| chained, but greater and less-than comparisons may not; e.g., "b < 64 > a" is |
| not allowed. |
| |
| Arguments: |
| expression: An ir_pb2.Expression which is the head of the (expr, operator, |
| expr, operator, expr, ...) list. |
| expression_right: A list of _ExpressionTails corresponding to the (operator, |
| expr, operator, expr, ...) list that comes after expression. |
| |
| Returns: |
| An ir_pb2.Expression with the correct recursive structure to represent a |
| chain of left-associative comparison operations. |
| """ |
| sequence = [expression] |
| for right in expression_right.list: |
| sequence.append(right.operator) |
| sequence.append(right.expression) |
| comparisons = [] |
| for i in range(0, len(sequence) - 1, 2): |
| left, operator, right = sequence[i:i+3] |
| location = parser_types.make_location( |
| left.source_location.start, right.source_location.end) |
| comparisons.append(ir_pb2.Expression( |
| function=ir_pb2.Function( |
| function=_text_to_operator(operator.text), |
| args=[left, right], |
| function_name=operator, |
| source_location=location), |
| source_location=location)) |
| e = comparisons[0] |
| for comparison in comparisons[1:]: |
| location = parser_types.make_location( |
| e.source_location.start, comparison.source_location.end) |
| e = ir_pb2.Expression( |
| function=ir_pb2.Function( |
| function=ir_pb2.FunctionMapping.AND, |
| args=[e, comparison], |
| function_name=ir_pb2.Word( |
| text='&&', |
| source_location=comparison.function.args[0].source_location), |
| source_location=location), |
| source_location=location) |
| return e |
| |
| |
| # _chained_comparison_expression, above, handles three types of chains: `a == b |
| # == c`, `a < b <= c`, and `a > b >= c`. |
| # |
| # This requires a bit of subtlety in the productions for |
| # `x-expression-right-list`, because the `==` operator may be freely mixed into |
| # greater-than or less-than chains, like `a < b == c <= d` or `a > b == c >= d`, |
| # but greater-than and less-than may not be mixed; i.e., `a < b >= c` is |
| # disallowed. |
| # |
| # In order to keep the grammar unambiguous -- that is, in order to ensure that |
| # every valid input can only be parsed in exactly one way -- the languages |
| # defined by `equality-expression-right*`, `greater-expression-right-list`, and |
| # `less-expression-right-list` cannot overlap. |
| # |
| # `equality-expression-right*`, by definition, only contains `== n` elements. |
| # By forcing `greater-expression-right-list` to contain at least one |
| # `greater-expression-right`, we can ensure that a chain like `== n == m` cannot |
| # be parsed as a `greater-expression-right-list`. Similar logic applies in the |
| # less-than case. |
| # |
| # There is another potential source of ambiguity here: if |
| # `greater-expression-right-list` were |
| # |
| # greater-expression-right-list -> |
| # equality-or-greater-expression-right* greater-expression-right |
| # equality-or-greater-expression-right* |
| # |
| # then a sequence like '> b > c > d' could be parsed as any of: |
| # |
| # () (> b) ((> c) (> d)) |
| # ((> b)) (> c) ((> d)) |
| # ((> b) (> c)) (> d) () |
| # |
| # By using `equality-expression-right*` for the first symbol, only the first |
| # parse is possible. |
| @_handles('greater-expression-right-list ->' |
| ' equality-expression-right* greater-expression-right' |
| ' equality-or-greater-expression-right*') |
| @_handles('less-expression-right-list ->' |
| ' equality-expression-right* less-expression-right' |
| ' equality-or-less-expression-right*') |
| def _chained_comparison_tails(start, middle, end): |
| return _List(start.list + [middle] + end.list) |
| |
| |
| @_handles('equality-or-greater-expression-right -> equality-expression-right') |
| @_handles('equality-or-greater-expression-right -> greater-expression-right') |
| @_handles('equality-or-less-expression-right -> equality-expression-right') |
| @_handles('equality-or-less-expression-right -> less-expression-right') |
| def _equality_or_less_or_greater(right): |
| return right |
| |
| |
| @_handles('and-expression-right -> and-operator comparison-expression') |
| @_handles('or-expression-right -> or-operator comparison-expression') |
| @_handles('additive-expression-right -> additive-operator times-expression') |
| @_handles('equality-expression-right -> equality-operator additive-expression') |
| @_handles('greater-expression-right -> greater-operator additive-expression') |
| @_handles('less-expression-right -> less-operator additive-expression') |
| @_handles('times-expression-right ->' |
| ' multiplicative-operator negation-expression') |
| def _expression_right_production(operator, expression): |
| return _ExpressionTail(operator, expression) |
| |
| |
| # This supports a single layer of unary plus/minus, so "+5" and "-value" are |
| # allowed, but "+-5" or "-+-something" are not. |
| @_handles('negation-expression -> additive-operator bottom-expression') |
| def _negation_expression_with_operator(operator, expression): |
| phantom_zero_location = ir_pb2.Location(start=operator.source_location.start, |
| end=operator.source_location.start) |
| return ir_pb2.Expression( |
| function=ir_pb2.Function( |
| function=_text_to_operator(operator.text), |
| args=[ir_pb2.Expression( |
| constant=ir_pb2.NumericConstant( |
| value='0', |
| source_location=phantom_zero_location), |
| source_location=phantom_zero_location), expression], |
| function_name=operator, |
| source_location=ir_pb2.Location( |
| start=operator.source_location.start, |
| end=expression.source_location.end))) |
| |
| |
| @_handles('negation-expression -> bottom-expression') |
| def _negation_expression(expression): |
| return expression |
| |
| |
| @_handles('bottom-expression -> "(" expression ")"') |
| def _bottom_expression_parentheses(open_paren, expression, close_paren): |
| del open_paren, close_paren # Unused. |
| return expression |
| |
| |
| @_handles('bottom-expression -> function-name "(" argument-list ")"') |
| def _bottom_expression_function(function, open_paren, arguments, close_paren): |
| del open_paren # Unused. |
| return ir_pb2.Expression( |
| function=ir_pb2.Function( |
| function=_text_to_function(function.text), |
| args=arguments.list, |
| function_name=function, |
| source_location=ir_pb2.Location( |
| start=function.source_location.start, |
| end=close_paren.source_location.end))) |
| |
| |
| @_handles('comma-then-expression -> "," expression') |
| def _comma_then_expression(comma, expression): |
| del comma # Unused. |
| return expression |
| |
| |
| @_handles('argument-list -> expression comma-then-expression*') |
| def _argument_list(head, tail): |
| tail.list.insert(0, head) |
| return tail |
| |
| |
| @_handles('argument-list ->') |
| def _empty_argument_list(): |
| return _List([]) |
| |
| |
| @_handles('bottom-expression -> numeric-constant') |
| def _bottom_expression_from_numeric_constant(constant): |
| return ir_pb2.Expression(constant=constant) |
| |
| |
| @_handles('bottom-expression -> constant-reference') |
| def _bottom_expression_from_constant_reference(reference): |
| return ir_pb2.Expression(constant_reference=reference) |
| |
| |
| @_handles('bottom-expression -> builtin-reference') |
| def _bottom_expression_from_builtin(reference): |
| return ir_pb2.Expression(builtin_reference=reference) |
| |
| |
| @_handles('bottom-expression -> boolean-constant') |
| def _bottom_expression_from_boolean_constant(boolean): |
| return ir_pb2.Expression(boolean_constant=boolean) |
| |
| |
| @_handles('bottom-expression -> field-reference') |
| def _bottom_expression_from_reference(reference): |
| return reference |
| |
| |
| @_handles('field-reference -> snake-reference field-reference-tail*') |
| def _indirect_field_reference(field_reference, field_references): |
| if field_references.source_location.HasField('end'): |
| end_location = field_references.source_location.end |
| else: |
| end_location = field_reference.source_location.end |
| return ir_pb2.Expression(field_reference=ir_pb2.FieldReference( |
| path=[field_reference] + field_references.list, |
| source_location=parser_types.make_location( |
| field_reference.source_location.start, end_location))) |
| |
| |
| # If "Type.field" ever becomes syntactically valid, it will be necessary to |
| # check that enum values are compile-time constants. |
| @_handles('field-reference-tail -> "." snake-reference') |
| def _field_reference_tail(dot, reference): |
| del dot # Unused. |
| return reference |
| |
| |
| @_handles('numeric-constant -> Number') |
| def _numeric_constant(number): |
| # All types of numeric constant tokenize to the same symbol, because they are |
| # interchangeable in source code. |
| if number.text[0:2] == '0b': |
| n = int(number.text.replace('_', '')[2:], 2) |
| elif number.text[0:2] == '0x': |
| n = int(number.text.replace('_', '')[2:], 16) |
| else: |
| n = int(number.text.replace('_', ''), 10) |
| return ir_pb2.NumericConstant(value=str(n)) |
| |
| |
| @_handles('type-definition -> struct') |
| @_handles('type-definition -> bits') |
| @_handles('type-definition -> enum') |
| @_handles('type-definition -> external') |
| def _type_definition(type_definition): |
| return type_definition |
| |
| |
| # struct StructureName: |
| # ... fields ... |
| # bits BitName: |
| # ... fields ... |
| @_handles('struct -> "struct" type-name delimited-parameter-definition-list?' |
| ' ":" Comment? eol struct-body') |
| @_handles('bits -> "bits" type-name delimited-parameter-definition-list? ":"' |
| ' Comment? eol bits-body') |
| def _structure(struct, name, parameters, colon, comment, newline, struct_body): |
| """Composes the top-level IR for an Emboss structure.""" |
| del colon, comment, newline # Unused. |
| struct_body.structure.source_location.start.CopyFrom( |
| struct.source_location.start) |
| struct_body.structure.source_location.end.CopyFrom( |
| struct_body.source_location.end) |
| struct_body.name.CopyFrom(name) |
| if parameters.list: |
| struct_body.runtime_parameter.extend(parameters.list[0].list) |
| return struct_body |
| |
| |
| @_handles('delimited-parameter-definition-list ->' |
| ' "(" parameter-definition-list ")"') |
| def _delimited_parameter_definition_list(open_paren, parameters, close_paren): |
| del open_paren, close_paren # Unused |
| return parameters |
| |
| |
| @_handles('parameter-definition -> snake-name ":" type') |
| def _parameter_definition(name, double_colon, parameter_type): |
| del double_colon # Unused |
| return ir_pb2.RuntimeParameter(name=name, physical_type_alias=parameter_type) |
| |
| |
| @_handles('parameter-definition-list-tail -> "," parameter-definition') |
| def _parameter_definition_list_tail(comma, parameter): |
| del comma # Unused. |
| return parameter |
| |
| |
| @_handles('parameter-definition-list -> parameter-definition' |
| ' parameter-definition-list-tail*') |
| def _parameter_definition_list(head, tail): |
| tail.list.insert(0, head) |
| return tail |
| |
| |
| @_handles('parameter-definition-list ->') |
| def _empty_parameter_definition_list(): |
| return _List([]) |
| |
| |
| # The body of a struct: basically, the part after the first line. |
| @_handles('struct-body -> Indent doc-line* attribute-line*' |
| ' type-definition* struct-field-block Dedent') |
| def _struct_body(indent, docs, attributes, types, fields, dedent): |
| del indent, dedent # Unused. |
| return _structure_body(docs, attributes, types, fields, |
| ir_pb2.AddressableUnit.BYTE) |
| |
| |
| def _structure_body(docs, attributes, types, fields, addressable_unit): |
| """Constructs the body of a structure (bits or struct) definition.""" |
| return ir_pb2.TypeDefinition( |
| structure=ir_pb2.Structure(field=[field.field for field in fields.list]), |
| documentation=docs.list, |
| attribute=attributes.list, |
| subtype=types.list + [subtype for field in fields.list for subtype in |
| field.subtypes], |
| addressable_unit=addressable_unit) |
| |
| |
| @_handles('struct-field-block ->') |
| @_handles('bits-field-block ->') |
| @_handles('anonymous-bits-field-block ->') |
| def _empty_field_block(): |
| return _List([]) |
| |
| |
| @_handles('struct-field-block ->' |
| ' conditional-struct-field-block struct-field-block') |
| @_handles('bits-field-block ->' |
| ' conditional-bits-field-block bits-field-block') |
| @_handles('anonymous-bits-field-block -> conditional-anonymous-bits-field-block' |
| ' anonymous-bits-field-block') |
| def _conditional_block_plus_field_block(conditional_block, block): |
| return _List(conditional_block.list + block.list) |
| |
| |
| @_handles('struct-field-block ->' |
| ' unconditional-struct-field struct-field-block') |
| @_handles('bits-field-block ->' |
| ' unconditional-bits-field bits-field-block') |
| @_handles('anonymous-bits-field-block ->' |
| ' unconditional-anonymous-bits-field anonymous-bits-field-block') |
| def _unconditional_block_plus_field_block(field, block): |
| """Prepends an unconditional field to block.""" |
| field.field.existence_condition.source_location.CopyFrom( |
| field.source_location) |
| field.field.existence_condition.boolean_constant.source_location.CopyFrom( |
| field.source_location) |
| field.field.existence_condition.boolean_constant.value = True |
| return _List([field] + block.list) |
| |
| |
| # Struct "fields" are regular fields, inline enums, bits, or structs, anonymous |
| # inline bits, or virtual fields. |
| @_handles('unconditional-struct-field -> field') |
| @_handles('unconditional-struct-field -> inline-enum-field-definition') |
| @_handles('unconditional-struct-field -> inline-bits-field-definition') |
| @_handles('unconditional-struct-field -> inline-struct-field-definition') |
| @_handles('unconditional-struct-field -> anonymous-bits-field-definition') |
| @_handles('unconditional-struct-field -> virtual-field') |
| # Bits fields are "regular" fields, inline enums or bits, or virtual fields. |
| # |
| # Inline structs and anonymous inline bits are not allowed inside of bits: |
| # anonymous inline bits are pointless, and inline structs do not make sense, |
| # since a struct cannot be a part of a bits. |
| # |
| # Anonymous inline bits may not include virtual fields; instead, the virtual |
| # field should be a direct part of the enclosing structure. |
| @_handles('unconditional-anonymous-bits-field -> field') |
| @_handles('unconditional-anonymous-bits-field -> inline-enum-field-definition') |
| @_handles('unconditional-anonymous-bits-field -> inline-bits-field-definition') |
| @_handles('unconditional-bits-field -> unconditional-anonymous-bits-field') |
| @_handles('unconditional-bits-field -> virtual-field') |
| def _unconditional_field(field): |
| """Handles the unifying grammar production for a struct or bits field.""" |
| return field |
| |
| |
| # TODO(bolms): Add 'elif' and 'else' support. |
| # TODO(bolms): Should nested 'if' blocks be allowed? |
| @_handles('conditional-struct-field-block ->' |
| ' "if" expression ":" Comment? eol' |
| ' Indent unconditional-struct-field+ Dedent') |
| @_handles('conditional-bits-field-block ->' |
| ' "if" expression ":" Comment? eol' |
| ' Indent unconditional-bits-field+ Dedent') |
| @_handles('conditional-anonymous-bits-field-block ->' |
| ' "if" expression ":" Comment? eol' |
| ' Indent unconditional-anonymous-bits-field+ Dedent') |
| def _conditional_field_block(if_keyword, expression, colon, comment, newline, |
| indent, fields, dedent): |
| """Applies an existence_condition to each element of fields.""" |
| del if_keyword, newline, colon, comment, indent, dedent # Unused. |
| for field in fields.list: |
| condition = field.field.existence_condition |
| condition.CopyFrom(expression) |
| condition.source_location.is_disjoint_from_parent = True |
| return fields |
| |
| |
| # The body of a bit field definition: basically, the part after the first line. |
| @_handles('bits-body -> Indent doc-line* attribute-line*' |
| ' type-definition* bits-field-block Dedent') |
| def _bits_body(indent, docs, attributes, types, fields, dedent): |
| del indent, dedent # Unused. |
| return _structure_body(docs, attributes, types, fields, |
| ir_pb2.AddressableUnit.BIT) |
| |
| |
| # Inline bits (defined as part of a field) are more restricted than standalone |
| # bits. |
| @_handles('anonymous-bits-body ->' |
| ' Indent attribute-line* anonymous-bits-field-block Dedent') |
| def _anonymous_bits_body(indent, attributes, fields, dedent): |
| del indent, dedent # Unused. |
| return _structure_body(_List([]), attributes, _List([]), fields, |
| ir_pb2.AddressableUnit.BIT) |
| |
| |
| # A field is: |
| # range type name (abbr) [attr: value] [attr2: value] -- doc |
| # -- doc |
| # -- doc |
| # [attr3: value] |
| # [attr4: value] |
| @_handles('field ->' |
| ' field-location type snake-name abbreviation? attribute* doc?' |
| ' Comment? eol field-body?') |
| def _field(location, field_type, name, abbreviation, attributes, doc, comment, |
| newline, field_body): |
| """Constructs an ir_pb2.Field from the given components.""" |
| del comment # Unused |
| field = ir_pb2.Field(location=location, |
| type=field_type, |
| name=name, |
| attribute=attributes.list, |
| documentation=doc.list) |
| if field_body.list: |
| field.attribute.extend(field_body.list[0].attribute) |
| field.documentation.extend(field_body.list[0].documentation) |
| if abbreviation.list: |
| field.abbreviation.CopyFrom(abbreviation.list[0]) |
| field.source_location.start.CopyFrom(location.source_location.start) |
| if field_body.source_location.HasField('end'): |
| field.source_location.end.CopyFrom(field_body.source_location.end) |
| else: |
| field.source_location.end.CopyFrom(newline.source_location.end) |
| return _FieldWithType(field=field) |
| |
| |
| # A "virtual field" is: |
| # let name = value |
| # -- doc |
| # -- doc |
| # [attr1: value] |
| # [attr2: value] |
| @_handles('virtual-field ->' |
| ' "let" snake-name "=" expression Comment? eol field-body?') |
| def _virtual_field(let, name, equals, value, comment, newline, field_body): |
| """Constructs an ir_pb2.Field from the given components.""" |
| del equals, comment # Unused |
| field = ir_pb2.Field(read_transform=value, name=name) |
| if field_body.list: |
| field.attribute.extend(field_body.list[0].attribute) |
| field.documentation.extend(field_body.list[0].documentation) |
| field.source_location.start.CopyFrom(let.source_location.start) |
| if field_body.source_location.HasField('end'): |
| field.source_location.end.CopyFrom(field_body.source_location.end) |
| else: |
| field.source_location.end.CopyFrom(newline.source_location.end) |
| return _FieldWithType(field=field) |
| |
| |
| # An inline enum is: |
| # range "enum" name (abbr): |
| # -- doc |
| # -- doc |
| # [attr3: value] |
| # [attr4: value] |
| # NAME = 10 |
| # NAME2 = 20 |
| @_handles('inline-enum-field-definition ->' |
| ' field-location "enum" snake-name abbreviation? ":" Comment? eol' |
| ' enum-body') |
| def _inline_enum_field(location, enum, name, abbreviation, colon, comment, |
| newline, enum_body): |
| """Constructs an ir_pb2.Field for an inline enum field.""" |
| del enum, colon, comment, newline # Unused. |
| return _inline_type_field(location, name, abbreviation, enum_body) |
| |
| |
| @_handles( |
| 'inline-struct-field-definition ->' |
| ' field-location "struct" snake-name abbreviation? ":" Comment? eol' |
| ' struct-body') |
| def _inline_struct_field(location, struct, name, abbreviation, colon, comment, |
| newline, struct_body): |
| del struct, colon, comment, newline # Unused. |
| return _inline_type_field(location, name, abbreviation, struct_body) |
| |
| |
| @_handles('inline-bits-field-definition ->' |
| ' field-location "bits" snake-name abbreviation? ":" Comment? eol' |
| ' bits-body') |
| def _inline_bits_field(location, bits, name, abbreviation, colon, comment, |
| newline, bits_body): |
| del bits, colon, comment, newline # Unused. |
| return _inline_type_field(location, name, abbreviation, bits_body) |
| |
| |
| def _inline_type_field(location, name, abbreviation, body): |
| """Shared implementation of _inline_enum_field and _anonymous_bit_field.""" |
| field = ir_pb2.Field(location=location, |
| name=name, |
| attribute=body.attribute, |
| documentation=body.documentation) |
| # All attributes should be attached to the field, not the type definition: if |
| # the user wants to use type attributes, they should create a separate type |
| # definition and reference it. |
| del body.attribute[:] |
| type_name = ir_pb2.NameDefinition() |
| type_name.CopyFrom(name) |
| type_name.name.text = name_conversion.snake_to_camel(type_name.name.text) |
| field.type.atomic_type.reference.source_name.extend([type_name.name]) |
| field.type.atomic_type.reference.source_location.CopyFrom( |
| type_name.source_location) |
| field.type.atomic_type.reference.is_local_name = True |
| field.type.atomic_type.source_location.CopyFrom(type_name.source_location) |
| field.type.source_location.CopyFrom(type_name.source_location) |
| if abbreviation.list: |
| field.abbreviation.CopyFrom(abbreviation.list[0]) |
| field.source_location.start.CopyFrom(location.source_location.start) |
| body.source_location.start.CopyFrom(location.source_location.start) |
| if body.HasField('enumeration'): |
| body.enumeration.source_location.CopyFrom(body.source_location) |
| else: |
| assert body.HasField('structure') |
| body.structure.source_location.CopyFrom(body.source_location) |
| body.name.CopyFrom(type_name) |
| field.source_location.end.CopyFrom(body.source_location.end) |
| subtypes = [body] + list(body.subtype) |
| del body.subtype[:] |
| return _FieldWithType(field=field, subtypes=subtypes) |
| |
| |
| @_handles('anonymous-bits-field-definition ->' |
| ' field-location "bits" ":" Comment? eol anonymous-bits-body') |
| def _anonymous_bit_field(location, bits_keyword, colon, comment, newline, |
| bits_body): |
| """Constructs an ir_pb2.Field for an anonymous bit field.""" |
| del colon, comment, newline # Unused. |
| name = ir_pb2.NameDefinition( |
| name=ir_pb2.Word( |
| text=_get_anonymous_field_name(), |
| source_location=bits_keyword.source_location), |
| source_location=bits_keyword.source_location, |
| is_anonymous=True) |
| return _inline_type_field(location, name, _List([]), bits_body) |
| |
| |
| @_handles('field-body -> Indent doc-line* attribute-line* Dedent') |
| def _field_body(indent, docs, attributes, dedent): |
| del indent, dedent # Unused. |
| return ir_pb2.Field(documentation=docs.list, attribute=attributes.list) |
| |
| |
| # A parenthetically-denoted abbreviation. |
| @_handles('abbreviation -> "(" snake-word ")"') |
| def _abbreviation(open_paren, word, close_paren): |
| del open_paren, close_paren # Unused. |
| return word |
| |
| |
| # enum EnumName: |
| # ... values ... |
| @_handles('enum -> "enum" type-name ":" Comment? eol enum-body') |
| def _enum(enum, name, colon, comment, newline, enum_body): |
| del colon, comment, newline # Unused. |
| enum_body.enumeration.source_location.start.CopyFrom( |
| enum.source_location.start) |
| enum_body.enumeration.source_location.end.CopyFrom( |
| enum_body.source_location.end) |
| enum_body.name.CopyFrom(name) |
| return enum_body |
| |
| |
| # [enum Foo:] |
| # name = value |
| # name = value |
| @_handles('enum-body -> Indent doc-line* attribute-line* enum-value+ Dedent') |
| def _enum_body(indent, docs, attributes, values, dedent): |
| del indent, dedent # Unused. |
| return ir_pb2.TypeDefinition( |
| enumeration=ir_pb2.Enum(value=values.list), |
| documentation=docs.list, |
| attribute=attributes.list, |
| addressable_unit=ir_pb2.AddressableUnit.BIT) |
| |
| |
| # name = value |
| @_handles('enum-value -> ' |
| ' constant-name "=" expression attribute* doc? Comment? eol enum-value-body?') |
| def _enum_value(name, equals, expression, attribute, documentation, comment, newline, |
| body): |
| del equals, comment, newline # Unused. |
| result = ir_pb2.EnumValue(name=name, |
| value=expression, |
| documentation=documentation.list, |
| attribute=attribute.list) |
| if body.list: |
| result.documentation.extend(body.list[0].documentation) |
| result.attribute.extend(body.list[0].attribute) |
| return result |
| |
| |
| @_handles('enum-value-body -> Indent doc-line* attribute-line* Dedent') |
| def _enum_value_body(indent, docs, attributes, dedent): |
| del indent, dedent # Unused. |
| return ir_pb2.EnumValue(documentation=docs.list, attribute=attributes.list) |
| |
| |
| # An external is just a declaration that a type exists and has certain |
| # attributes. |
| @_handles('external -> "external" type-name ":" Comment? eol external-body') |
| def _external(external, name, colon, comment, newline, external_body): |
| del colon, comment, newline # Unused. |
| external_body.source_location.start.CopyFrom(external.source_location.start) |
| external_body.name.CopyFrom(name) |
| return external_body |
| |
| |
| # This syntax implicitly requires either a documentation line or a attribute |
| # line, or it won't parse (because no Indent/Dedent tokens will be emitted). |
| @_handles('external-body -> Indent doc-line* attribute-line* Dedent') |
| def _external_body(indent, docs, attributes, dedent): |
| return ir_pb2.TypeDefinition( |
| external=ir_pb2.External( |
| # Set source_location here, since it won't be set automatically. |
| source_location=ir_pb2.Location(start=indent.source_location.start, |
| end=dedent.source_location.end)), |
| documentation=docs.list, |
| attribute=attributes.list) |
| |
| |
| @_handles('field-location -> expression "[" "+" expression "]"') |
| def _field_location(start, open_bracket, plus, size, close_bracket): |
| del open_bracket, plus, close_bracket # Unused. |
| return ir_pb2.FieldLocation(start=start, size=size) |
| |
| |
| @_handles('delimited-argument-list -> "(" argument-list ")"') |
| def _type_argument_list(open_paren, arguments, close_paren): |
| del open_paren, close_paren # Unused |
| return arguments |
| |
| |
| # A type is "TypeName" or "TypeName[length]" or "TypeName[length][length]", etc. |
| # An array type may have an empty length ("Type[]"). This is only valid for the |
| # outermost length (the last set of brackets), but that must be checked |
| # elsewhere. |
| @_handles('type -> type-reference delimited-argument-list? type-size-specifier?' |
| ' array-length-specifier*') |
| def _type(reference, parameters, size, array_spec): |
| """Builds the IR for a type specifier.""" |
| base_type_source_location_end = reference.source_location.end |
| atomic_type_source_location_end = reference.source_location.end |
| if parameters.list: |
| base_type_source_location_end = parameters.source_location.end |
| atomic_type_source_location_end = parameters.source_location.end |
| if size.list: |
| base_type_source_location_end = size.source_location.end |
| base_type_location = parser_types.make_location( |
| reference.source_location.start, |
| base_type_source_location_end) |
| atomic_type_location = parser_types.make_location( |
| reference.source_location.start, |
| atomic_type_source_location_end) |
| t = ir_pb2.Type( |
| atomic_type=ir_pb2.AtomicType( |
| reference=reference, |
| source_location=atomic_type_location, |
| runtime_parameter=parameters.list[0].list if parameters.list else []), |
| size_in_bits=size.list[0] if size.list else None, |
| source_location=base_type_location) |
| for length in array_spec.list: |
| location = parser_types.make_location( |
| t.source_location.start, length.source_location.end) |
| if isinstance(length, ir_pb2.Expression): |
| t = ir_pb2.Type( |
| array_type=ir_pb2.ArrayType(base_type=t, |
| element_count=length, |
| source_location=location), |
| source_location=location) |
| elif isinstance(length, ir_pb2.Empty): |
| t = ir_pb2.Type( |
| array_type=ir_pb2.ArrayType(base_type=t, |
| automatic=length, |
| source_location=location), |
| source_location=location) |
| else: |
| assert False, "Shouldn't be here." |
| return t |
| |
| |
| # TODO(bolms): Should symbolic names or expressions be allowed? E.g., |
| # UInt:FIELD_SIZE or UInt:(16 + 16)? |
| @_handles('type-size-specifier -> ":" numeric-constant') |
| def _type_size_specifier(colon, numeric_constant): |
| """handles the ":32" part of a type specifier like "UInt:32".""" |
| del colon |
| return ir_pb2.Expression(constant=numeric_constant) |
| |
| |
| # The distinctions between different formats of NameDefinitions, Words, and |
| # References are enforced during parsing, but not propagated to the IR. |
| @_handles('type-name -> type-word') |
| @_handles('snake-name -> snake-word') |
| @_handles('constant-name -> constant-word') |
| def _name(word): |
| return ir_pb2.NameDefinition(name=word) |
| |
| |
| @_handles('type-word -> CamelWord') |
| @_handles('snake-word -> SnakeWord') |
| @_handles('builtin-field-word -> "$size_in_bits"') |
| @_handles('builtin-field-word -> "$size_in_bytes"') |
| @_handles('builtin-field-word -> "$max_size_in_bits"') |
| @_handles('builtin-field-word -> "$max_size_in_bytes"') |
| @_handles('builtin-field-word -> "$min_size_in_bits"') |
| @_handles('builtin-field-word -> "$min_size_in_bytes"') |
| @_handles('builtin-word -> "$is_statically_sized"') |
| @_handles('builtin-word -> "$static_size_in_bits"') |
| @_handles('builtin-word -> "$next"') |
| @_handles('constant-word -> ShoutyWord') |
| @_handles('and-operator -> "&&"') |
| @_handles('or-operator -> "||"') |
| @_handles('less-operator -> "<="') |
| @_handles('less-operator -> "<"') |
| @_handles('greater-operator -> ">="') |
| @_handles('greater-operator -> ">"') |
| @_handles('equality-operator -> "=="') |
| @_handles('inequality-operator -> "!="') |
| @_handles('additive-operator -> "+"') |
| @_handles('additive-operator -> "-"') |
| @_handles('multiplicative-operator -> "*"') |
| @_handles('function-name -> "$max"') |
| @_handles('function-name -> "$present"') |
| @_handles('function-name -> "$upper_bound"') |
| @_handles('function-name -> "$lower_bound"') |
| def _word(word): |
| return ir_pb2.Word(text=word.text) |
| |
| |
| @_handles('type-reference -> type-reference-tail') |
| @_handles('constant-reference -> constant-reference-tail') |
| def _un_module_qualified_type_reference(reference): |
| return reference |
| |
| |
| @_handles('constant-reference-tail -> constant-word') |
| @_handles('type-reference-tail -> type-word') |
| @_handles('snake-reference -> snake-word') |
| @_handles('snake-reference -> builtin-field-word') |
| def _reference(word): |
| return ir_pb2.Reference(source_name=[word]) |
| |
| |
| @_handles('builtin-reference -> builtin-word') |
| def _builtin_reference(word): |
| return ir_pb2.Reference(source_name=[word], |
| canonical_name=ir_pb2.CanonicalName( |
| object_path=[word.text])) |
| |
| |
| # Because constant-references ("Enum.NAME") are used in the same contexts as |
| # field-references ("field.subfield"), module-qualified constant references |
| # ("module.Enum.VALUE") have to take snake-reference, not snake-word, on the |
| # left side of the dot. Otherwise, when a "snake_word" is followed by a "." in |
| # an expression context, the LR(1) parser cannot determine whether to reduce the |
| # snake-word to snake-reference (to eventually become field-reference), or to |
| # shift the dot onto the stack (to eventually become constant-reference). By |
| # using snake-reference as the head of both, the parser can always reduce, then |
| # shift the dot, then determine whether to proceed with constant-reference if it |
| # sees "snake_name.TypeName" or field-reference if it sees |
| # "snake_name.snake_name". |
| @_handles('constant-reference -> snake-reference "." constant-reference-tail') |
| def _module_qualified_constant_reference(new_head, dot, reference): |
| del dot # Unused. |
| new_source_name = list(new_head.source_name) + list(reference.source_name) |
| del reference.source_name[:] |
| reference.source_name.extend(new_source_name) |
| return reference |
| |
| |
| @_handles('constant-reference-tail -> type-word "." constant-reference-tail') |
| # module.Type.SubType.name is a reference to something that *must* be a |
| # constant. |
| @_handles('constant-reference-tail -> type-word "." snake-reference') |
| @_handles('type-reference-tail -> type-word "." type-reference-tail') |
| @_handles('type-reference -> snake-word "." type-reference-tail') |
| def _qualified_reference(word, dot, reference): |
| """Adds a name. or Type. qualification to the head of a reference.""" |
| del dot # Unused. |
| new_source_name = [word] + list(reference.source_name) |
| del reference.source_name[:] |
| reference.source_name.extend(new_source_name) |
| return reference |
| |
| |
| # Arrays are properly translated to IR in _type(). |
| @_handles('array-length-specifier -> "[" expression "]"') |
| def _array_length_specifier(open_bracket, length, close_bracket): |
| del open_bracket, close_bracket # Unused. |
| return length |
| |
| |
| # An array specifier can end with empty brackets ("arr[3][]"), in which case the |
| # array's size is inferred from the size of its enclosing field. |
| @_handles('array-length-specifier -> "[" "]"') |
| def _auto_array_length_specifier(open_bracket, close_bracket): |
| # Note that the Void's source_location is the space between the brackets (if |
| # any). |
| return ir_pb2.Empty( |
| source_location=ir_pb2.Location(start=open_bracket.source_location.end, |
| end=close_bracket.source_location.start)) |
| |
| |
| @_handles('eol -> "\\n" comment-line*') |
| def _eol(eol, comments): |
| del comments # Unused |
| return eol |
| |
| |
| @_handles('comment-line -> Comment? "\\n"') |
| def _comment_line(comment, eol): |
| del comment # Unused |
| return eol |
| |
| |
| def _finalize_grammar(): |
| """_Finalize adds productions for foo*, foo+, and foo? symbols.""" |
| star_symbols = set() |
| plus_symbols = set() |
| option_symbols = set() |
| for production in _handlers: |
| for symbol in production.rhs: |
| if symbol[-1] == '*': |
| star_symbols.add(symbol[:-1]) |
| elif symbol[-1] == '+': |
| # symbol+ relies on the rule for symbol* |
| star_symbols.add(symbol[:-1]) |
| plus_symbols.add(symbol[:-1]) |
| elif symbol[-1] == '?': |
| option_symbols.add(symbol[:-1]) |
| for symbol in star_symbols: |
| _handles('{s}* -> {s} {s}*'.format(s=symbol))( |
| lambda e, r: _List([e] + r.list)) |
| _handles('{s}* ->'.format(s=symbol))(lambda: _List([])) |
| for symbol in plus_symbols: |
| _handles('{s}+ -> {s} {s}*'.format(s=symbol))( |
| lambda e, r: _List([e] + r.list)) |
| for symbol in option_symbols: |
| _handles('{s}? -> {s}'.format(s=symbol))(lambda e: _List([e])) |
| _handles('{s}? ->'.format(s=symbol))(lambda: _List([])) |
| |
| |
| _finalize_grammar() |
| |
| # End of grammar. |
| ################################################################################ |
| |
| # These export the grammar used by module_ir so that parser_generator can build |
| # a parser for the same language. |
| START_SYMBOL = 'module' |
| EXPRESSION_START_SYMBOL = 'expression' |
| PRODUCTIONS = list(_handlers.keys()) |