blob: 3ddd20e46a2282d125773a15e7370b07fca83823 [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 doc? Comment? eol'
' enum-value-body?')
def _enum_value(name, equals, value, docs, comment, eol, body):
return [_Block([], _Row('enum-value', [name, equals, value, docs, comment]),
eol + body)]
@_formats('enum-value-body -> Indent doc-line* Dedent')
def _enum_value_body(indent, docs, dedent):
del indent, dedent # Unused
return _indent_rows(docs)
@_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 -> "$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()