| # 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() |