# 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. """Formatter for Emboss source files. This module exports a single function, format_emboss_parse_tree(), which pretty-prints an Emboss parse tree. """ from __future__ import print_function import collections import itertools from compiler.front_end import module_ir from compiler.front_end import tokenizer from compiler.util import parser_types class Config(collections.namedtuple('Config', ['indent_width', 'show_line_types'])): """Configuration for formatting.""" def __new__(cls, indent_width=2, show_line_types=False): return super(cls, Config).__new__(cls, indent_width, show_line_types) class _Row(collections.namedtuple('Row', ['name', 'columns', 'indent'])): """Structured contents of a single line.""" def __new__(cls, name, columns=None, indent=0): return super(cls, _Row).__new__(cls, name, tuple(columns or []), indent) class _Block(collections.namedtuple('Block', ['prefix', 'header', 'body'])): """Structured block of multiple lines.""" def __new__(cls, prefix, header, body): assert header return super(cls, _Block).__new__(cls, prefix, header, body) # Map of productions to their formatters. _formatters = {} def format_emboss_parse_tree(parse_tree, config, used_productions=None): """Formats Emboss source code. Arguments: parse_tree: A parse tree of an Emboss source file. config: A Config tuple with formatting options. used_productions: An optional set to which all used productions will be added. Intended for use by test code to ensure full production coverage. Returns: A string of the reformatted source text. """ if hasattr(parse_tree, 'children'): parsed_children = [format_emboss_parse_tree(child, config, used_productions) for child in parse_tree.children] args = parsed_children + [config] if used_productions is not None: used_productions.add(parse_tree.production) return _formatters[parse_tree.production](*args) else: assert isinstance(parse_tree, parser_types.Token), str(parse_tree) return parse_tree.text def sanity_check_format_result(formatted_text, original_text): """Checks that the given texts are equivalent.""" # The texts are considered equivalent if they tokenize to the same token # stream, except that: # # Multiple consecutive newline tokens are equivalent to a single newline # token. # # Extra newline tokens at the start of the stream should be ignored. # # Whitespace at the start or end of a token should be ignored. This matters # for documentation and comment tokens, which may have had trailing whitespace # in the original text, and for indent tokens, which may contain a different # number of space and/or tab characters. original_tokens, errors = tokenizer.tokenize(original_text, '') if errors: return ['BUG: original text is not tokenizable: {!r}'.format(errors)] formatted_tokens, errors = tokenizer.tokenize(formatted_text, '') if errors: return ['BUG: formatted text is not tokenizable: {!r}'.format(errors)] o_tokens = _collapse_newline_tokens(original_tokens) f_tokens = _collapse_newline_tokens(formatted_tokens) for i in range(len(o_tokens)): if (o_tokens[i].symbol != f_tokens[i].symbol or o_tokens[i].text.strip() != f_tokens[i].text.strip()): return ['BUG: Symbol {} differs: {!r} vs {!r}'.format(i, o_tokens[i], f_tokens[i])] return [] def _collapse_newline_tokens(token_list): r"""Collapses multiple consecutive "\\n" tokens into a single newline.""" result = [] for symbol, group in itertools.groupby(token_list, lambda x: x.symbol): if symbol == '"\\n"': # Skip all newlines if they are at the start, otherwise add a single # newline for each consecutive run of newlines. if result: result.append(list(group)[0]) else: result.extend(group) return result def _indent_row(row): """Adds one level of indent to the given row, returning a new row.""" assert isinstance(row, _Row), repr(row) return _Row(name=row.name, columns=row.columns, indent=row.indent + 1) def _indent_rows(rows): """Adds one level of indent to the given rows, returning a new list.""" return list(map(_indent_row, rows)) def _indent_blocks(blocks): """Adds one level of indent to the given blocks, returning a new list.""" return [_Block(prefix=_indent_rows(block.prefix), header=_indent_row(block.header), body=_indent_rows(block.body)) for block in blocks] def _intersperse(interspersed, sections): """Intersperses `interspersed` between non-empty `sections`.""" result = [] for section in sections: if section: if result: result.extend(interspersed) result.extend(section) return result def _should_add_blank_lines(blocks): """Returns true if blank lines should be added between blocks.""" other_non_empty_lines = 0 last_non_empty_lines = 0 for block in blocks: last_non_empty_lines = len([line for line in block.body + block.prefix if line.columns]) other_non_empty_lines += last_non_empty_lines # Vertical spaces should be added if there are more interior # non-empty-non-header lines than header lines. return len(blocks) <= other_non_empty_lines - last_non_empty_lines def _columnize(blocks, indent_width, indent_columns=1): """Aligns columns in the header rows of the given blocks. The `indent_columns` argument is used to determine how many columns should be indented. With `indent_columns == 1`, the result would be: AA BB CC AAA BBB CCC A B C With `indent_columns == 2`: AA BB CC AAA BBB CCC A B C With `indent_columns == 1`, only the first column is indented compared to surrounding rows; with `indent_columns == 2`, both the first and second columns are indented. Arguments: blocks: A list of _Blocks to columnize. indent_width: The number of spaces per level of indent. indent_columns: The number of columns to indent. Returns: A list of _Rows of the prefix, header, and body _Rows of each block, where the header _Rows of each type have had their columns aligned. """ single_width_separators = {'enum-value': {0, 1}, 'field': {0}} # For each type of row, figure out how many characters each column needs. row_types = collections.defaultdict( lambda: collections.defaultdict(lambda: 0)) for block in blocks: max_lengths = row_types[block.header.name] for i in range(len(block.header.columns)): if i == indent_columns - 1: adjustment = block.header.indent * indent_width else: adjustment = 0 max_lengths[i] = max(max_lengths[i], len(block.header.columns[i]) + adjustment) assert len(row_types) < 3 # Then, for each row, actually columnize it. result = [] for block in blocks: columns = [] for i in range(len(block.header.columns)): column_width = row_types[block.header.name][i] if column_width == 0: # Zero-width columns are entirely omitted, including their column # separators. pass else: if i == indent_columns - 1: # This function only performs the right padding for each column. # Since the left padding for indent will be added later, the # corresponding space needs to be removed from the right padding of # the first column. column_width -= block.header.indent * indent_width if i in single_width_separators.get(block.header.name, []): # Only one space around the "=" in enum values and between the start # and size in field locations. column_width += 1 else: column_width += 2 columns.append(block.header.columns[i].ljust(column_width)) result.append(block.prefix + [_Row(block.header.name, [''.join(columns).rstrip()], block.header.indent)] + block.body) return result def _indent_blanks_and_comments(rows): """Indents blank and comment lines to match the next non-blank line.""" result = [] previous_indent = 0 for row in reversed(rows): if not ''.join(row.columns) or row.name == 'comment': result.append(_Row(row.name, row.columns, previous_indent)) else: result.append(row) previous_indent = row.indent return reversed(result) def _add_blank_rows_on_dedent(rows): """Adds blank rows before dedented lines, where needed.""" result = [] previous_indent = 0 previous_row_was_blank = True for row in rows: row_is_blank = not ''.join(row.columns) found_dedent = previous_indent > row.indent if found_dedent and not previous_row_was_blank and not row_is_blank: result.append(_Row('dedent-space', [], row.indent)) result.append(row) previous_indent = row.indent previous_row_was_blank = row_is_blank return result def _render_row_to_text(row, indent_width): assert len(row.columns) < 2, '{!r}'.format(row) text = ' ' * indent_width * row.indent text += ''.join(row.columns) return text.rstrip() def _render_rows_to_text(rows, indent_width, show_line_types): max_row_name_len = max([0] + [len(row.name) for row in rows]) flattened_rows = [] for row in rows: row_text = _render_row_to_text(row, indent_width) if show_line_types: row_text = row.name.ljust(max_row_name_len) + '|' + row_text flattened_rows.append(row_text) return '\n'.join(flattened_rows + ['']) def _check_productions(): """Asserts that the productions in this module match those in module_ir.""" productions_ok = True for production in module_ir.PRODUCTIONS: if production not in _formatters: productions_ok = False print('@_formats({!r})'.format(str(production))) for production in _formatters: if production not in module_ir.PRODUCTIONS: productions_ok = False print('not @_formats({!r})'.format(str(production))) assert productions_ok, 'Grammar mismatch.' def _formats_with_config(production_text): """Marks a function as a formatter requiring a config argument.""" production = parser_types.Production.parse(production_text) def formats(f): assert production not in _formatters, production _formatters[production] = f return f return formats def _formats(production_text): """Marks a function as the formatter for a particular production.""" def strip_config_argument(f): _formats_with_config(production_text)(lambda *a, **kw: f(*a[:-1], **kw)) return f return strip_config_argument ################################################################################ # From here to the end of the file are functions which recursively format an # Emboss parse tree. # # The format_parse_tree() function will call formatters, bottom-up, for the # entire parse tree. Each formatter will be called with the results of the # formatters for each child node. (The "formatter" for leaf nodes is the # original text of the token.) # # Formatters can be roughly divided into three types: # # The _module formatter is the top-level formatter. It handles final rendering # into text, and returns a string. # # Formatters for productions that are at least one full line return lists of # _Rows. The production 'attribute-line' falls into this category, but # 'attribute' does not. This form allows parallel constructs in separate lines # to be lined up column-wise, even when there are intervening lines that should # not be lined up -- for example, the types and names of struct fields will be # aligned, even if there are documentation, comment, or attribute lines mixed # in. # # Formatters for productions that are smaller than one full line just return # strings. @_formats_with_config('module -> comment-line* doc-line* import-line*' ' attribute-line* type-definition*') def _module(comments, docs, imports, attributes, types, config): """Performs top-level formatting for an Emboss source file.""" # The top-level sections other than types should be separated by single lines. header_rows = _intersperse( [_Row('section-break')], [_strip_empty_leading_trailing_comment_lines(comments), docs, imports, attributes]) # Top-level types should be separated by double lines from themselves and from # the header rows. rows = _intersperse( [_Row('top-type-separator'), _Row('top-type-separator')], [header_rows] + types) # Final fixups. rows = _indent_blanks_and_comments(rows) rows = _add_blank_rows_on_dedent(rows) return _render_rows_to_text(rows, config.indent_width, config.show_line_types) @_formats('doc-line -> doc Comment? eol') def _doc_line(doc, comment, eol): assert not comment, 'Comment should not be possible on the same line as doc.' return [_Row('doc', [doc])] + eol @_formats('import-line -> "import" string-constant "as" snake-word Comment?' ' eol') def _import_line(import_, filename, as_, name, comment, eol): return [_Row('import', ['{} {} {} {} {}'.format( import_, filename, as_, name, comment)])] + eol @_formats('attribute-line -> attribute Comment? eol') def _attribute_line(attribute, comment, eol): return [_Row('attribute', ['{} {}'.format(attribute, comment)])] + eol @_formats('attribute -> "[" attribute-context? "$default"? snake-word ":"' ' attribute-value "]"') def _attribute(open_, context, default, name, colon, value, close): return ''.join([open_, _concatenate_with_spaces(context, default, name + colon, value), close]) @_formats('parameter-definition -> snake-name ":" type') def _parameter_definition(name, colon, type_specifier): return '{}{} {}'.format(name, colon, type_specifier) @_formats('type-definition* -> type-definition type-definition*') def _type_defitinions(definition, definitions): return [definition] + definitions @_formats('bits -> "bits" type-name delimited-parameter-definition-list? ":"' ' Comment? eol bits-body') @_formats('struct -> "struct" type-name delimited-parameter-definition-list?' ' ":" Comment? eol struct-body') def _structure_type(struct, name, parameters, colon, comment, eol, body): return ([_Row('type-header', ['{} {}{}{} {}'.format( struct, name, parameters, colon, comment)])] + eol + body) @_formats('enum -> "enum" type-name ":" Comment? eol enum-body') @_formats('external -> "external" type-name ":" Comment? eol external-body') def _type(struct, name, colon, comment, eol, body): return ([_Row('type-header', ['{} {}{} {}'.format(struct, name, colon, comment)])] + eol + body) @_formats_with_config('bits-body -> Indent doc-line* attribute-line*' ' type-definition* bits-field-block Dedent') @_formats_with_config( 'struct-body -> Indent doc-line* attribute-line*' ' type-definition* struct-field-block Dedent') def _structure_body(indent, docs, attributes, type_definitions, fields, dedent, config): del indent, dedent # Unused. spacing = [_Row('field-separator')] if _should_add_blank_lines(fields) else [] columnized_fields = _columnize(fields, config.indent_width, indent_columns=2) return _indent_rows(_intersperse( spacing, [docs, attributes] + type_definitions + columnized_fields)) @_formats('field-location -> expression "[" "+" expression "]"') def _field_location(start, open_bracket, plus, size, close_bracket): return [start, open_bracket + plus + size + close_bracket] @_formats('anonymous-bits-field-block -> conditional-anonymous-bits-field-block' ' anonymous-bits-field-block') @_formats('anonymous-bits-field-block -> unconditional-anonymous-bits-field' ' anonymous-bits-field-block') @_formats('bits-field-block -> conditional-bits-field-block bits-field-block') @_formats('bits-field-block -> unconditional-bits-field bits-field-block') @_formats('struct-field-block -> conditional-struct-field-block' ' struct-field-block') @_formats('struct-field-block -> unconditional-struct-field struct-field-block') @_formats('unconditional-anonymous-bits-field* ->' ' unconditional-anonymous-bits-field' ' unconditional-anonymous-bits-field*') @_formats('unconditional-anonymous-bits-field+ ->' ' unconditional-anonymous-bits-field' ' unconditional-anonymous-bits-field*') @_formats('unconditional-bits-field* -> unconditional-bits-field' ' unconditional-bits-field*') @_formats('unconditional-bits-field+ -> unconditional-bits-field' ' unconditional-bits-field*') @_formats('unconditional-struct-field* -> unconditional-struct-field' ' unconditional-struct-field*') @_formats('unconditional-struct-field+ -> unconditional-struct-field' ' unconditional-struct-field*') def _structure_block(field, block): """Prepends field to block.""" return field + block @_formats('virtual-field -> "let" snake-name "=" expression Comment? eol' ' field-body?') def _virtual_field(let_keyword, name, equals, value, comment, eol, body): # This formatting doesn't look the best when there are blocks of several # virtual fields next to each other, but works pretty well when they're # intermixed with physical fields. It's probably good enough for now, since # there aren't (yet) any virtual fields in real .embs, and will probably only # be a few in the near future. return [_Block([], _Row('virtual-field', [_concatenate_with( ' ', _concatenate_with_spaces(let_keyword, name, equals, value), comment)]), eol + body)] @_formats('field -> field-location type snake-name abbreviation?' ' attribute* doc? Comment? eol field-body?') def _unconditional_field(location, type_, name, abbreviation, attributes, doc, comment, eol, body): return [_Block([], _Row('field', location + [type_, _concatenate_with_spaces(name, abbreviation), attributes, doc, comment]), eol + body)] @_formats('field-body -> Indent doc-line* attribute-line* Dedent') def _field_body(indent, docs, attributes, dedent): del indent, dedent # Unused return _indent_rows(docs + attributes) @_formats('anonymous-bits-field-definition ->' ' field-location "bits" ":" Comment? eol anonymous-bits-body') def _inline_bits(location, bits, colon, comment, eol, body): # Even though an anonymous bits field technically defines a new, anonymous # type, conceptually it's more like defining a bunch of fields on the # surrounding type, so it is treated as an inline list of blocks, instead of # being separately formatted. header_row = _Row('field', [location[0], location[1] + ' ' + bits + colon, '', '', '', '', comment]) return ([_Block([], header_row, eol + body.header_lines)] + body.field_blocks) @_formats('inline-enum-field-definition ->' ' field-location "enum" snake-name abbreviation? ":" Comment? eol' ' enum-body') @_formats( 'inline-struct-field-definition ->' ' field-location "struct" snake-name abbreviation? ":" Comment? eol' ' struct-body') @_formats('inline-bits-field-definition ->' ' field-location "bits" snake-name abbreviation? ":" Comment? eol' ' bits-body') def _inline_type(location, keyword, name, abbreviation, colon, comment, eol, body): """Formats an inline type in a struct or bits.""" header_row = _Row( 'field', location + [keyword, _concatenate_with_spaces(name, abbreviation) + colon, '', '', comment]) return [_Block([], header_row, eol + body)] @_formats('conditional-struct-field-block -> "if" expression ":" Comment? eol' ' Indent unconditional-struct-field+' ' Dedent') @_formats('conditional-bits-field-block -> "if" expression ":" Comment? eol' ' Indent unconditional-bits-field+' ' Dedent') @_formats('conditional-anonymous-bits-field-block ->' ' "if" expression ":" Comment? eol' ' Indent unconditional-anonymous-bits-field+ Dedent') def _conditional_field(if_, condition, colon, comment, eol, indent, body, dedent): """Formats an `if` construct.""" del indent, dedent # Unused # The body of an 'if' should be columnized with the surrounding blocks, so # much like an inline 'bits', its body is treated as an inline list of blocks. header_row = _Row('if', ['{} {}{} {}'.format(if_, condition, colon, comment)]) indented_body = _indent_blocks(body) assert indented_body, 'Expected body of if condition.' return [_Block([header_row] + eol + indented_body[0].prefix, indented_body[0].header, indented_body[0].body)] + indented_body[1:] _InlineBitsBodyType = collections.namedtuple('InlineBitsBodyType', ['header_lines', 'field_blocks']) @_formats('anonymous-bits-body ->' ' Indent attribute-line* anonymous-bits-field-block Dedent') def _inline_bits_body(indent, attributes, fields, dedent): del indent, dedent # Unused return _InlineBitsBodyType(header_lines=_indent_rows(attributes), field_blocks=_indent_blocks(fields)) @_formats_with_config( 'enum-body -> Indent doc-line* attribute-line* enum-value+' ' Dedent') def _enum_body(indent, docs, attributes, values, dedent, config): del indent, dedent # Unused spacing = [_Row('value-separator')] if _should_add_blank_lines(values) else [] columnized_values = _columnize(values, config.indent_width) return _indent_rows(_intersperse(spacing, [docs, attributes] + columnized_values)) @_formats('enum-value* -> enum-value enum-value*') @_formats('enum-value+ -> enum-value enum-value*') def _enum_values(value, block): return value + block @_formats('enum-value -> constant-name "=" expression attribute* doc? Comment? eol' ' enum-value-body?') def _enum_value(name, equals, value, attributes, docs, comment, eol, body): return [_Block([], _Row('enum-value', [name, equals, value, attributes, docs, comment]), eol + body)] @_formats('enum-value-body -> Indent doc-line* attribute-line* Dedent') def _enum_value_body(indent, docs, attributes, dedent): del indent, dedent # Unused return _indent_rows(docs + attributes) @_formats('external-body -> Indent doc-line* attribute-line* Dedent') def _external_body(indent, docs, attributes, dedent): del indent, dedent # Unused return _indent_rows(_intersperse([_Row('section-break')], [docs, attributes])) @_formats('comment-line -> Comment? "\\n"') def _comment_line(comment, eol): del eol # Unused if comment: return [_Row('comment', [comment])] else: return [_Row('comment')] @_formats('eol -> "\\n" comment-line*') def _eol(eol, comments): del eol # Unused return _strip_empty_leading_trailing_comment_lines(comments) def _strip_empty_leading_trailing_comment_lines(comments): first_non_empty_line = None last_non_empty_line = None for i in range(len(comments)): if comments[i].columns: if first_non_empty_line is None: first_non_empty_line = i last_non_empty_line = i if first_non_empty_line is None: return [] else: return comments[first_non_empty_line:last_non_empty_line + 1] @_formats('attribute-line* -> ') @_formats('anonymous-bits-field-block -> ') @_formats('bits-field-block -> ') @_formats('comment-line* -> ') @_formats('doc-line* -> ') @_formats('enum-value* -> ') @_formats('enum-value-body? -> ') @_formats('field-body? -> ') @_formats('import-line* -> ') @_formats('struct-field-block -> ') @_formats('type-definition* -> ') @_formats('unconditional-anonymous-bits-field* -> ') @_formats('unconditional-bits-field* -> ') @_formats('unconditional-struct-field* -> ') def _empty_list(): return [] @_formats('abbreviation? -> ') @_formats('additive-expression-right* -> ') @_formats('and-expression-right* -> ') @_formats('argument-list -> ') @_formats('array-length-specifier* -> ') @_formats('attribute* -> ') @_formats('attribute-context? -> ') @_formats('comma-then-expression* -> ') @_formats('Comment? -> ') @_formats('"$default"? -> ') @_formats('delimited-argument-list? -> ') @_formats('delimited-parameter-definition-list? -> ') @_formats('doc? -> ') @_formats('equality-expression-right* -> ') @_formats('equality-or-greater-expression-right* -> ') @_formats('equality-or-less-expression-right* -> ') @_formats('field-reference-tail* -> ') @_formats('or-expression-right* -> ') @_formats('parameter-definition-list -> ') @_formats('parameter-definition-list-tail* -> ') @_formats('times-expression-right* -> ') @_formats('type-size-specifier? -> ') def _empty_string(): return '' @_formats('abbreviation? -> abbreviation') @_formats('additive-operator -> "-"') @_formats('additive-operator -> "+"') @_formats('and-operator -> "&&"') @_formats('attribute-context? -> attribute-context') @_formats('attribute-value -> expression') @_formats('attribute-value -> string-constant') @_formats('boolean-constant -> BooleanConstant') @_formats('bottom-expression -> boolean-constant') @_formats('bottom-expression -> builtin-reference') @_formats('bottom-expression -> constant-reference') @_formats('bottom-expression -> field-reference') @_formats('bottom-expression -> numeric-constant') @_formats('builtin-field-word -> "$max_size_in_bits"') @_formats('builtin-field-word -> "$max_size_in_bytes"') @_formats('builtin-field-word -> "$min_size_in_bits"') @_formats('builtin-field-word -> "$min_size_in_bytes"') @_formats('builtin-field-word -> "$size_in_bits"') @_formats('builtin-field-word -> "$size_in_bytes"') @_formats('builtin-reference -> builtin-word') @_formats('builtin-word -> "$is_statically_sized"') @_formats('builtin-word -> "$next"') @_formats('builtin-word -> "$static_size_in_bits"') @_formats('choice-expression -> logical-expression') @_formats('Comment? -> Comment') @_formats('comparison-expression -> additive-expression') @_formats('constant-name -> constant-word') @_formats('constant-reference -> constant-reference-tail') @_formats('constant-reference-tail -> constant-word') @_formats('constant-word -> ShoutyWord') @_formats('"$default"? -> "$default"') @_formats('delimited-argument-list? -> delimited-argument-list') @_formats('doc? -> doc') @_formats('doc -> Documentation') @_formats('enum-value-body? -> enum-value-body') @_formats('equality-operator -> "=="') @_formats('equality-or-greater-expression-right -> equality-expression-right') @_formats('equality-or-greater-expression-right -> greater-expression-right') @_formats('equality-or-less-expression-right -> equality-expression-right') @_formats('equality-or-less-expression-right -> less-expression-right') @_formats('expression -> choice-expression') @_formats('field-body? -> field-body') @_formats('function-name -> "$lower_bound"') @_formats('function-name -> "$present"') @_formats('function-name -> "$max"') @_formats('function-name -> "$upper_bound"') @_formats('greater-operator -> ">="') @_formats('greater-operator -> ">"') @_formats('inequality-operator -> "!="') @_formats('less-operator -> "<="') @_formats('less-operator -> "<"') @_formats('logical-expression -> and-expression') @_formats('logical-expression -> comparison-expression') @_formats('logical-expression -> or-expression') @_formats('multiplicative-operator -> "*"') @_formats('negation-expression -> bottom-expression') @_formats('numeric-constant -> Number') @_formats('or-operator -> "||"') @_formats('snake-name -> snake-word') @_formats('snake-reference -> builtin-field-word') @_formats('snake-reference -> snake-word') @_formats('snake-word -> SnakeWord') @_formats('string-constant -> String') @_formats('type-definition -> bits') @_formats('type-definition -> enum') @_formats('type-definition -> external') @_formats('type-definition -> struct') @_formats('type-name -> type-word') @_formats('type-reference-tail -> type-word') @_formats('type-reference -> type-reference-tail') @_formats('type-size-specifier? -> type-size-specifier') @_formats('type-word -> CamelWord') @_formats('unconditional-anonymous-bits-field -> field') @_formats('unconditional-anonymous-bits-field -> inline-bits-field-definition') @_formats('unconditional-anonymous-bits-field -> inline-enum-field-definition') @_formats('unconditional-bits-field -> unconditional-anonymous-bits-field') @_formats('unconditional-bits-field -> virtual-field') @_formats('unconditional-struct-field -> anonymous-bits-field-definition') @_formats('unconditional-struct-field -> field') @_formats('unconditional-struct-field -> inline-bits-field-definition') @_formats('unconditional-struct-field -> inline-enum-field-definition') @_formats('unconditional-struct-field -> inline-struct-field-definition') @_formats('unconditional-struct-field -> virtual-field') def _identity(x): return x @_formats('argument-list -> expression comma-then-expression*') @_formats('times-expression -> negation-expression times-expression-right*') @_formats('type -> type-reference delimited-argument-list? type-size-specifier?' ' array-length-specifier*') @_formats('array-length-specifier -> "[" expression "]"') @_formats('array-length-specifier* -> array-length-specifier' ' array-length-specifier*') @_formats('type-size-specifier -> ":" numeric-constant') @_formats('attribute-context -> "(" snake-word ")"') @_formats('constant-reference -> snake-reference "." constant-reference-tail') @_formats('constant-reference-tail -> type-word "." constant-reference-tail') @_formats('constant-reference-tail -> type-word "." snake-reference') @_formats('type-reference-tail -> type-word "." type-reference-tail') @_formats('field-reference -> snake-reference field-reference-tail*') @_formats('abbreviation -> "(" snake-word ")"') @_formats('additive-expression-right -> additive-operator times-expression') @_formats('additive-expression-right* -> additive-expression-right' ' additive-expression-right*') @_formats('additive-expression -> times-expression additive-expression-right*') @_formats('array-length-specifier -> "[" "]"') @_formats('delimited-argument-list -> "(" argument-list ")"') @_formats('delimited-parameter-definition-list? ->' ' delimited-parameter-definition-list') @_formats('delimited-parameter-definition-list ->' ' "(" parameter-definition-list ")"') @_formats('parameter-definition-list -> parameter-definition' ' parameter-definition-list-tail*') @_formats('parameter-definition-list-tail* -> parameter-definition-list-tail' ' parameter-definition-list-tail*') @_formats('times-expression-right -> multiplicative-operator' ' negation-expression') @_formats('times-expression-right* -> times-expression-right' ' times-expression-right*') @_formats('field-reference-tail -> "." snake-reference') @_formats('field-reference-tail* -> field-reference-tail field-reference-tail*') @_formats('negation-expression -> additive-operator bottom-expression') @_formats('type-reference -> snake-word "." type-reference-tail') @_formats('bottom-expression -> "(" expression ")"') @_formats('bottom-expression -> function-name "(" argument-list ")"') @_formats('comma-then-expression* -> comma-then-expression' ' comma-then-expression*') @_formats('or-expression-right* -> or-expression-right or-expression-right*') @_formats('less-expression-right-list -> equality-expression-right*' ' less-expression-right' ' equality-or-less-expression-right*') @_formats('or-expression-right+ -> or-expression-right or-expression-right*') @_formats('and-expression -> comparison-expression and-expression-right+') @_formats('comparison-expression -> additive-expression' ' greater-expression-right-list') @_formats('comparison-expression -> additive-expression' ' equality-expression-right+') @_formats('or-expression -> comparison-expression or-expression-right+') @_formats('equality-expression-right+ -> equality-expression-right' ' equality-expression-right*') @_formats('and-expression-right* -> and-expression-right and-expression-right*') @_formats('equality-or-greater-expression-right* ->' ' equality-or-greater-expression-right' ' equality-or-greater-expression-right*') @_formats('and-expression-right+ -> and-expression-right and-expression-right*') @_formats('equality-or-less-expression-right* ->' ' equality-or-less-expression-right' ' equality-or-less-expression-right*') @_formats('equality-expression-right* -> equality-expression-right' ' equality-expression-right*') @_formats('greater-expression-right-list ->' ' equality-expression-right* greater-expression-right' ' equality-or-greater-expression-right*') @_formats('comparison-expression -> additive-expression' ' less-expression-right-list') def _concatenate(*elements): """Concatenates all arguments with no delimiters.""" return ''.join(elements) @_formats('equality-expression-right -> equality-operator additive-expression') @_formats('less-expression-right -> less-operator additive-expression') @_formats('greater-expression-right -> greater-operator additive-expression') @_formats('or-expression-right -> or-operator comparison-expression') @_formats('and-expression-right -> and-operator comparison-expression') def _concatenate_with_prefix_spaces(*elements): return ''.join(' ' + element for element in elements if element) @_formats('attribute* -> attribute attribute*') @_formats('comma-then-expression -> "," expression') @_formats('comparison-expression -> additive-expression inequality-operator' ' additive-expression') @_formats('choice-expression -> logical-expression "?" logical-expression' ' ":" logical-expression') @_formats('parameter-definition-list-tail -> "," parameter-definition') def _concatenate_with_spaces(*elements): return _concatenate_with(' ', *elements) def _concatenate_with(joiner, *elements): return joiner.join(element for element in elements if element) @_formats('attribute-line* -> attribute-line attribute-line*') @_formats('comment-line* -> comment-line comment-line*') @_formats('doc-line* -> doc-line doc-line*') @_formats('import-line* -> import-line import-line*') def _concatenate_lists(head, tail): return head + tail _check_productions()