blob: df9bcf36bef0a134ed2f60ad573325cdde9913a9 [file] [log] [blame]
# 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()