Convert Location to a namedtuple, and associated cleanup (#205)
This change makes the `Location` dataclass, which does not change
frequently, into a new `SourceLocation` namedtuple, and changes the
`SourceLocation` serialization. As a result, with this change:
* `embossc` runs about 25% faster on a large (7kLOC) input; `python3
-OO emboss` runs about 19% faster on the same input.
* Serialized IR is about 45% smaller.
Details:
* Replace the `ir_data.Location` dataclass with a new
`parser_types.SourceLocation` namedtuple. The rename helps clarify
the difference between a location within source code
(`SourceLocation`) and a location within a structure
(`FieldLocation`).
* Similarly, replace `ir_data.Position` with
`parser_types.SourcePosition`.
* Update any place that edits a `SourceLocation` with an appropriate
assignment; e.g., `x.source_location.end = y` becomes
`x.source_location = x.source_location._replace(end=y)`. In most
cases, several fields were updated consecutively; those updates are
been merged.
* Update the JSON serialization to use the compact format.
* Replace `format_location()` and `format_position()` with
`__str__()` methods on `SourceLocation` and `SourcePosition`,
respectively.
* Replace `parse_location()` and `parse_position()` with `from_str()`
class methods on `SourceLocation` and `SourcePosition`,
respectively.
* Move the `make_location()` functionality into
`SourceLocation.__new__()`.
* Update `_to_dict` and `_from_dict` in `IrDataSerializer` to
stringify and destringify `SourceLocation`. It is tempting to
try to do this during the JSON serialization step (with a `default=`
parameter to `json.dumps` and an `object_hook=` parameter to
`json.loads`), but it is tricky to get the `object_hook` to know
when to convert.diff --git a/compiler/back_end/cpp/header_generator.py b/compiler/back_end/cpp/header_generator.py
index 0201d12..392d188 100644
--- a/compiler/back_end/cpp/header_generator.py
+++ b/compiler/back_end/cpp/header_generator.py
@@ -1747,9 +1747,14 @@
original start column.
"""
- new_location = ir_data_utils.copy(source_location)
- new_location.start.column = source_location.start.column + offset[0]
- new_location.end.column = source_location.start.column + offset[1]
+ new_location = source_location._replace(
+ start=source_location.start._replace(
+ column=source_location.start.column + offset[0]
+ ),
+ end=source_location.start._replace(
+ column=source_location.start.column + offset[1]
+ ),
+ )
return new_location
diff --git a/compiler/back_end/cpp/header_generator_test.py b/compiler/back_end/cpp/header_generator_test.py
index 6d31df8..d125d9e 100644
--- a/compiler/back_end/cpp/header_generator_test.py
+++ b/compiler/back_end/cpp/header_generator_test.py
@@ -126,12 +126,11 @@
)
attr = ir.module[0].type[0].attribute[0]
- bad_case_source_location = ir_data.Location()
- bad_case_source_location = ir_data_utils.builder(bad_case_source_location)
- bad_case_source_location.CopyFrom(attr.value.source_location)
- # Location of SHORTY_CASE in the attribute line.
- bad_case_source_location.start.column = 30
- bad_case_source_location.end.column = 41
+ # SourceLocation of SHORTY_CASE in the attribute line.
+ bad_case_source_location = attr.value.source_location._replace(
+ start=attr.value.source_location.start._replace(column=30),
+ end=attr.value.source_location.end._replace(column=41),
+ )
self.assertEqual(
[
@@ -156,12 +155,11 @@
)
attr = ir.module[0].type[0].attribute[0]
- bad_case_source_location = ir_data.Location()
- bad_case_source_location = ir_data_utils.builder(bad_case_source_location)
- bad_case_source_location.CopyFrom(attr.value.source_location)
- # Location of bad_CASE in the attribute line.
- bad_case_source_location.start.column = 43
- bad_case_source_location.end.column = 51
+ # SourceLocation of bad_CASE in the attribute line.
+ bad_case_source_location = attr.value.source_location._replace(
+ start=attr.value.source_location.start._replace(column=43),
+ end=attr.value.source_location.end._replace(column=51),
+ )
self.assertEqual(
[
@@ -186,12 +184,11 @@
)
attr = ir.module[0].type[0].attribute[0]
- bad_case_source_location = ir_data.Location()
- bad_case_source_location = ir_data_utils.builder(bad_case_source_location)
- bad_case_source_location.CopyFrom(attr.value.source_location)
- # Location of BAD_case in the attribute line.
- bad_case_source_location.start.column = 55
- bad_case_source_location.end.column = 63
+ # SourceLocation of BAD_case in the attribute line.
+ bad_case_source_location = attr.value.source_location._replace(
+ start=attr.value.source_location.start._replace(column=55),
+ end=attr.value.source_location.end._replace(column=63),
+ )
self.assertEqual(
[
@@ -216,12 +213,11 @@
)
attr = ir.module[0].type[0].attribute[0]
- bad_case_source_location = ir_data.Location()
- bad_case_source_location = ir_data_utils.builder(bad_case_source_location)
- bad_case_source_location.CopyFrom(attr.value.source_location)
- # Location of the second SHOUTY_CASE in the attribute line.
- bad_case_source_location.start.column = 43
- bad_case_source_location.end.column = 54
+ # SourceLocation of the second SHOUTY_CASE in the attribute line.
+ bad_case_source_location = attr.value.source_location._replace(
+ start=attr.value.source_location.start._replace(column=43),
+ end=attr.value.source_location.end._replace(column=54),
+ )
self.assertEqual(
[
@@ -246,12 +242,11 @@
)
attr = ir.module[0].type[0].attribute[0]
- bad_case_source_location = ir_data.Location()
- bad_case_source_location = ir_data_utils.builder(bad_case_source_location)
- bad_case_source_location.CopyFrom(attr.value.source_location)
- # Location of excess comma.
- bad_case_source_location.start.column = 42
- bad_case_source_location.end.column = 42
+ # SourceLocation of excess comma.
+ bad_case_source_location = attr.value.source_location._replace(
+ start=attr.value.source_location.start._replace(column=42),
+ end=attr.value.source_location.end._replace(column=42),
+ )
self.assertEqual(
[
@@ -274,8 +269,10 @@
" BAZ = 2\n"
)
- bad_case_source_location.start.column = 30
- bad_case_source_location.end.column = 30
+ bad_case_source_location = attr.value.source_location._replace(
+ start=attr.value.source_location.start._replace(column=30),
+ end=attr.value.source_location.end._replace(column=30),
+ )
self.assertEqual(
[
@@ -298,8 +295,10 @@
" BAZ = 2\n"
)
- bad_case_source_location.start.column = 54
- bad_case_source_location.end.column = 54
+ bad_case_source_location = attr.value.source_location._replace(
+ start=attr.value.source_location.start._replace(column=54),
+ end=attr.value.source_location.end._replace(column=54),
+ )
self.assertEqual(
[
@@ -322,8 +321,10 @@
" BAZ = 2\n"
)
- bad_case_source_location.start.column = 45
- bad_case_source_location.end.column = 45
+ bad_case_source_location = attr.value.source_location._replace(
+ start=attr.value.source_location.start._replace(column=45),
+ end=attr.value.source_location.end._replace(column=45),
+ )
self.assertEqual(
[
@@ -346,8 +347,10 @@
" BAZ = 2\n"
)
- bad_case_source_location.start.column = 30
- bad_case_source_location.end.column = 30
+ bad_case_source_location = attr.value.source_location._replace(
+ start=attr.value.source_location.start._replace(column=30),
+ end=attr.value.source_location.end._replace(column=30),
+ )
self.assertEqual(
[
@@ -370,8 +373,10 @@
" BAZ = 2\n"
)
- bad_case_source_location.start.column = 35
- bad_case_source_location.end.column = 35
+ bad_case_source_location = attr.value.source_location._replace(
+ start=attr.value.source_location.start._replace(column=35),
+ end=attr.value.source_location.end._replace(column=35),
+ )
self.assertEqual(
[
@@ -394,8 +399,10 @@
" BAZ = 2\n"
)
- bad_case_source_location.start.column = 31
- bad_case_source_location.end.column = 31
+ bad_case_source_location = attr.value.source_location._replace(
+ start=attr.value.source_location.start._replace(column=31),
+ end=attr.value.source_location.end._replace(column=31),
+ )
self.assertEqual(
[
diff --git a/compiler/front_end/glue.py b/compiler/front_end/glue.py
index f12f27e..8b27260 100644
--- a/compiler/front_end/glue.py
+++ b/compiler/front_end/glue.py
@@ -203,7 +203,7 @@
"""
source_code, errors = file_reader(file_name)
if errors:
- location = parser_types.make_location((1, 1), (1, 1))
+ location = parser_types.SourceLocation((1, 1), (1, 1))
return (
None,
None,
diff --git a/compiler/front_end/glue_test.py b/compiler/front_end/glue_test.py
index 1decae3..fface8a 100644
--- a/compiler/front_end/glue_test.py
+++ b/compiler/front_end/glue_test.py
@@ -24,7 +24,7 @@
from compiler.util import parser_types
from compiler.util import test_util
-_location = parser_types.make_location
+_location = parser_types.SourceLocation
_ROOT_PACKAGE = "testdata.golden"
_GOLDEN_PATH = ""
@@ -232,7 +232,9 @@
self.assertFalse(errors)
# Artificially mark the first field as is_synthetic.
first_field = ir.module[0].type[0].structure.field[0]
- first_field.source_location.is_synthetic = True
+ first_field.source_location = first_field.source_location._replace(
+ is_synthetic=True
+ )
ir, errors = glue.process_ir(ir, None)
self.assertTrue(errors)
self.assertEqual(
@@ -259,8 +261,12 @@
self.assertFalse(errors)
# Artificially mark the name of the second field as is_synthetic.
second_field = ir.module[0].type[0].structure.field[1]
- second_field.name.source_location.is_synthetic = True
- second_field.name.name.source_location.is_synthetic = True
+ second_field.name.source_location = second_field.name.source_location._replace(
+ is_synthetic=True
+ )
+ second_field.name.name.source_location = (
+ second_field.name.name.source_location._replace(is_synthetic=True)
+ )
ir, errors = glue.process_ir(ir, None)
self.assertEqual(1, len(errors))
self.assertEqual("Duplicate name 'field'", errors[0][0].message)
diff --git a/compiler/front_end/lr1.py b/compiler/front_end/lr1.py
index 58d5076..42c648b 100644
--- a/compiler/front_end/lr1.py
+++ b/compiler/front_end/lr1.py
@@ -140,7 +140,9 @@
# ANY_TOKEN is used by mark_error as a "wildcard" token that should be replaced
# by every other token.
-ANY_TOKEN = parser_types.Token(object(), "*", parser_types.parse_location("0:0-0:0"))
+ANY_TOKEN = parser_types.Token(
+ object(), "*", parser_types.SourceLocation.from_str("0:0-0:0")
+)
class Reduction(
@@ -690,26 +692,7 @@
# children, setting the source_location to None in that case.
start_position = None
end_position = None
- for child in children:
- if (
- hasattr(child, "source_location")
- and child.source_location is not None
- ):
- start_position = child.source_location.start
- break
- for child in reversed(children):
- if (
- hasattr(child, "source_location")
- and child.source_location is not None
- ):
- end_position = child.source_location.end
- break
- if start_position is None:
- source_location = None
- else:
- source_location = parser_types.make_location(
- start_position, end_position
- )
+ source_location = parser_types.merge_source_locations(*children)
reduction = Reduction(
next_action.rule.lhs, children, next_action.rule, source_location
)
diff --git a/compiler/front_end/lr1_test.py b/compiler/front_end/lr1_test.py
index 6ca6e67..c77ebcf 100644
--- a/compiler/front_end/lr1_test.py
+++ b/compiler/front_end/lr1_test.py
@@ -34,7 +34,7 @@
result = []
for i in range(len(text)):
result.append(
- Token(text[i], parser_types.make_location((1, i + 1), (1, i + 2)))
+ Token(text[i], parser_types.SourceLocation((1, i + 1), (1, i + 2)))
)
return result
@@ -209,7 +209,7 @@
def test_successful_parse(self):
parser = _alsu_grammar.parser()
- loc = parser_types.parse_location
+ loc = parser_types.SourceLocation.from_str
s_to_c_c = parser_types.Production.parse("S -> C C")
c_to_c_c = parser_types.Production.parse("C -> c C")
c_to_d = parser_types.Production.parse("C -> d")
diff --git a/compiler/front_end/module_ir.py b/compiler/front_end/module_ir.py
index 4a459c2..145bba3 100644
--- a/compiler/front_end/module_ir.py
+++ b/compiler/front_end/module_ir.py
@@ -40,7 +40,7 @@
def __init__(self, l):
assert isinstance(l, list), "_List object must wrap list, not '%r'" % l
self.list = l
- self.source_location = ir_data.Location()
+ self.source_location = parser_types.SourceLocation()
class _ExpressionTail(object):
@@ -65,7 +65,7 @@
def __init__(self, operator, expression):
self.operator = operator
self.expression = expression
- self.source_location = ir_data.Location()
+ self.source_location = parser_types.SourceLocation()
class _FieldWithType(object):
@@ -76,7 +76,7 @@
def __init__(self, field, subtypes=None):
self.field = field
self.subtypes = subtypes or []
- self.source_location = ir_data.Location()
+ self.source_location = parser_types.SourceLocation()
def build_ir(parse_tree, used_productions=None):
@@ -153,11 +153,11 @@
]
used_productions.add(parse_tree.production)
result = _handlers[parse_tree.production](*parsed_children)
- if parse_tree.source_location is not None:
- if result.source_location:
- ir_data_utils.update(result.source_location, parse_tree.source_location)
+ if parse_tree.source_location:
+ if isinstance(result, tuple):
+ result = result._replace(source_location=parse_tree.source_location)
else:
- result.source_location = ir_data_utils.copy(parse_tree.source_location)
+ result.source_location = parse_tree.source_location
return result
else:
# For leaf nodes, the temporary "IR" is just the token. Higher-level rules
@@ -191,7 +191,7 @@
def _make_prelude_import(position):
"""Helper function to construct a synthetic ir_data.Import for the prelude."""
- location = parser_types.make_location(position, position)
+ location = parser_types.SourceLocation(position, position)
return ir_data.Import(
file_name=ir_data.String(text="", source_location=location),
local_name=ir_data.Word(text="", source_location=location),
@@ -279,7 +279,7 @@
and not attributes.list
and not type_definitions.list
):
- module_source_location = parser_types.make_location((1, 1), (1, 1))
+ module_source_location = parser_types.SourceLocation((1, 1), (1, 1))
else:
module_source_location = None
@@ -462,10 +462,10 @@
)
def _choice_expression(condition, question, if_true, colon, if_false):
"""Constructs an IR node for a choice operator (`?:`) expression."""
- location = parser_types.make_location(
+ location = parser_types.SourceLocation(
condition.source_location.start, if_false.source_location.end
)
- operator_location = parser_types.make_location(
+ operator_location = parser_types.SourceLocation(
question.source_location.start, colon.source_location.end
)
# The function_name is a bit weird, but should suffice for any error
@@ -490,7 +490,7 @@
" additive-expression inequality-operator additive-expression"
)
def _comparative_expression(left, operator, right):
- location = parser_types.make_location(
+ location = parser_types.SourceLocation(
left.source_location.start, right.source_location.end
)
return ir_data.Expression(
@@ -538,7 +538,7 @@
"""
e = expression
for right in expression_right.list:
- location = parser_types.make_location(
+ location = parser_types.SourceLocation(
e.source_location.start, right.source_location.end
)
e = ir_data.Expression(
@@ -611,7 +611,7 @@
comparisons = []
for i in range(0, len(sequence) - 1, 2):
left, operator, right = sequence[i : i + 3]
- location = parser_types.make_location(
+ location = parser_types.SourceLocation(
left.source_location.start, right.source_location.end
)
comparisons.append(
@@ -627,7 +627,7 @@
)
e = comparisons[0]
for comparison in comparisons[1:]:
- location = parser_types.make_location(
+ location = parser_types.SourceLocation(
e.source_location.start, comparison.source_location.end
)
e = ir_data.Expression(
@@ -717,7 +717,7 @@
# allowed, but "+-5" or "-+-something" are not.
@_handles("negation-expression -> additive-operator bottom-expression")
def _negation_expression_with_operator(operator, expression):
- phantom_zero_location = ir_data.Location(
+ phantom_zero_location = parser_types.SourceLocation(
start=operator.source_location.start, end=operator.source_location.start
)
return ir_data.Expression(
@@ -733,7 +733,7 @@
expression,
],
function_name=operator,
- source_location=ir_data.Location(
+ source_location=parser_types.SourceLocation(
start=operator.source_location.start, end=expression.source_location.end
),
)
@@ -759,7 +759,7 @@
function=_text_to_function(function.text),
args=arguments.list,
function_name=function,
- source_location=ir_data.Location(
+ source_location=parser_types.SourceLocation(
start=function.source_location.start,
end=close_paren.source_location.end,
),
@@ -811,15 +811,11 @@
@_handles("field-reference -> snake-reference field-reference-tail*")
def _indirect_field_reference(field_reference, field_references):
- if field_references.source_location.HasField("end"):
- end_location = field_references.source_location.end
- else:
- end_location = field_reference.source_location.end
return ir_data.Expression(
field_reference=ir_data.FieldReference(
path=[field_reference] + field_references.list,
- source_location=parser_types.make_location(
- field_reference.source_location.start, end_location
+ source_location=parser_types.merge_source_locations(
+ field_reference, field_references
),
)
)
@@ -869,11 +865,8 @@
def _structure(struct, name, parameters, colon, comment, newline, struct_body):
"""Composes the top-level IR for an Emboss structure."""
del colon, comment, newline # Unused.
- ir_data_utils.builder(struct_body.structure).source_location.start.CopyFrom(
- struct.source_location.start
- )
- ir_data_utils.builder(struct_body.structure).source_location.end.CopyFrom(
- struct_body.source_location.end
+ ir_data_utils.builder(struct_body.structure).source_location = (
+ parser_types.merge_source_locations(struct, struct_body)
)
if struct_body.name:
ir_data_utils.update(struct_body.name, name)
@@ -969,14 +962,12 @@
)
def _unconditional_block_plus_field_block(field, block):
"""Prepends an unconditional field to block."""
- ir_data_utils.builder(field.field).existence_condition.source_location.CopyFrom(
+ ir_data_utils.builder(field.field).existence_condition.source_location = (
field.source_location
)
ir_data_utils.builder(
field.field
- ).existence_condition.boolean_constant.source_location.CopyFrom(
- field.source_location
- )
+ ).existence_condition.boolean_constant.source_location = field.source_location
ir_data_utils.builder(field.field).existence_condition.boolean_constant.value = True
return _List([field] + block.list)
@@ -1032,7 +1023,9 @@
for field in fields.list:
condition = ir_data_utils.builder(field.field).existence_condition
condition.CopyFrom(expression)
- condition.source_location.is_disjoint_from_parent = True
+ condition.source_location = condition.source_location._replace(
+ is_disjoint_from_parent=True
+ )
return fields
@@ -1096,11 +1089,9 @@
field.documentation.extend(field_body.list[0].documentation)
if abbreviation.list:
field.abbreviation.CopyFrom(abbreviation.list[0])
- field.source_location.start.CopyFrom(location.source_location.start)
- if field_body.source_location.HasField("end"):
- field.source_location.end.CopyFrom(field_body.source_location.end)
- else:
- field.source_location.end.CopyFrom(newline.source_location.end)
+ field.source_location = parser_types.merge_source_locations(
+ location, newline, field_body
+ )
return _FieldWithType(field=field_ir)
@@ -1121,11 +1112,9 @@
if field_body.list:
field.attribute.extend(field_body.list[0].attribute)
field.documentation.extend(field_body.list[0].documentation)
- field.source_location.start.CopyFrom(let.source_location.start)
- if field_body.source_location.HasField("end"):
- field.source_location.end.CopyFrom(field_body.source_location.end)
- else:
- field.source_location.end.CopyFrom(newline.source_location.end)
+ field.source_location = parser_types.merge_source_locations(
+ let, newline, field_body
+ )
return _FieldWithType(field=field_ir)
@@ -1192,27 +1181,20 @@
type_name.name.text
)
field.type.atomic_type.reference.source_name.extend([type_name.name])
- field.type.atomic_type.reference.source_location.CopyFrom(type_name.source_location)
+ field.type.atomic_type.reference.source_location = type_name.source_location
field.type.atomic_type.reference.is_local_name = True
- field.type.atomic_type.source_location.CopyFrom(type_name.source_location)
- field.type.source_location.CopyFrom(type_name.source_location)
+ field.type.atomic_type.source_location = type_name.source_location
+ field.type.source_location = type_name.source_location
if abbreviation.list:
field.abbreviation.CopyFrom(abbreviation.list[0])
- field.source_location.start.CopyFrom(location.source_location.start)
- ir_data_utils.builder(body.source_location).start.CopyFrom(
- location.source_location.start
- )
+ body.source_location = parser_types.merge_source_locations(location, body)
if body.HasField("enumeration"):
- ir_data_utils.builder(body.enumeration).source_location.CopyFrom(
- body.source_location
- )
+ ir_data_utils.builder(body.enumeration).source_location = body.source_location
else:
assert body.HasField("structure")
- ir_data_utils.builder(body.structure).source_location.CopyFrom(
- body.source_location
- )
+ ir_data_utils.builder(body.structure).source_location = body.source_location
ir_data_utils.builder(body).name.CopyFrom(type_name)
- field.source_location.end.CopyFrom(body.source_location.end)
+ field.source_location = parser_types.merge_source_locations(location, body)
subtypes = [body] + list(body.subtype)
del body.subtype[:]
return _FieldWithType(field=field_ir, subtypes=subtypes)
@@ -1254,11 +1236,8 @@
@_handles('enum -> "enum" type-name ":" Comment? eol enum-body')
def _enum(enum, name, colon, comment, newline, enum_body):
del colon, comment, newline # Unused.
- ir_data_utils.builder(enum_body.enumeration).source_location.start.CopyFrom(
- enum.source_location.start
- )
- ir_data_utils.builder(enum_body.enumeration).source_location.end.CopyFrom(
- enum_body.source_location.end
+ ir_data_utils.builder(enum_body.enumeration).source_location = (
+ parser_types.merge_source_locations(enum, enum_body)
)
ir_data_utils.builder(enum_body).name.CopyFrom(name)
return enum_body
@@ -1311,8 +1290,8 @@
@_handles('external -> "external" type-name ":" Comment? eol external-body')
def _external(external, name, colon, comment, newline, external_body):
del colon, comment, newline # Unused.
- ir_data_utils.builder(external_body.source_location).start.CopyFrom(
- external.source_location.start
+ external_body.source_location = parser_types.merge_source_locations(
+ external, external_body
)
if external_body.name:
ir_data_utils.update(external_body.name, name)
@@ -1328,7 +1307,7 @@
return ir_data.TypeDefinition(
external=ir_data.External(
# Set source_location here, since it won't be set automatically.
- source_location=ir_data.Location(
+ source_location=parser_types.SourceLocation(
start=indent.source_location.start, end=dedent.source_location.end
)
),
@@ -1366,10 +1345,10 @@
atomic_type_source_location_end = parameters.source_location.end
if size.list:
base_type_source_location_end = size.source_location.end
- base_type_location = parser_types.make_location(
+ base_type_location = parser_types.SourceLocation(
reference.source_location.start, base_type_source_location_end
)
- atomic_type_location = parser_types.make_location(
+ atomic_type_location = parser_types.SourceLocation(
reference.source_location.start, atomic_type_source_location_end
)
t = ir_data.Type(
@@ -1382,7 +1361,7 @@
source_location=base_type_location,
)
for length in array_spec.list:
- location = parser_types.make_location(
+ location = parser_types.SourceLocation(
t.source_location.start, length.source_location.end
)
if isinstance(length, ir_data.Expression):
@@ -1524,7 +1503,7 @@
# Note that the Void's source_location is the space between the brackets (if
# any).
return ir_data.Empty(
- source_location=ir_data.Location(
+ source_location=parser_types.SourceLocation(
start=open_bracket.source_location.end,
end=close_bracket.source_location.start,
)
diff --git a/compiler/front_end/module_ir_test.py b/compiler/front_end/module_ir_test.py
index ad884a8..1931bab 100644
--- a/compiler/front_end/module_ir_test.py
+++ b/compiler/front_end/module_ir_test.py
@@ -454,56 +454,35 @@
"function": "SUBTRACTION",
"function_name": {
"text": "-",
- "source_location": {
- "start": { "line": 8, "column": 3 },
- "end": { "line": 8, "column": 4 }
- }
+ "source_location": "8:3-8:4"
},
"args": [
{
"constant": {
"value": "0",
- "source_location": {
- "start": { "line": 8, "column": 3 },
- "end": { "line": 8, "column": 3 }
- }
+ "source_location": "8:3-8:3"
},
- "source_location": {
- "start": { "line": 8, "column": 3 },
- "end": { "line": 8, "column": 3 }
- }
+ "source_location": "8:3-8:3"
},
{
"function": {
"function": "ADDITION",
"function_name": {
"text": "+",
- "source_location": {
- "start": { "line": 8, "column": 5 },
- "end": { "line": 8, "column": 6 }
- }
+ "source_location": "8:5-8:6"
},
"args": [
{
"constant": { "value": "0" },
- "source_location": {
- "start": { "line": 8, "column": 5 },
- "end": { "line": 8, "column": 5 }
- }
+ "source_location": "8:5-8:5"
},
{
"constant": { "value": "1" },
- "source_location": {
- "start": { "line": 8, "column": 6 },
- "end": { "line": 8, "column": 7 }
- }
+ "source_location": "8:6-8:7"
}
]
},
- "source_location": {
- "start": { "line": 8, "column": 4 },
- "end": { "line": 8, "column": 8 }
- }
+ "source_location": "8:4-8:8"
}
]
}
@@ -513,56 +492,35 @@
"function": "SUBTRACTION",
"function_name": {
"text": "-",
- "source_location": {
- "start": { "line": 8, "column": 12 },
- "end": { "line": 8, "column": 13 }
- }
+ "source_location": "8:12-8:13"
},
"args": [
{
"constant": {
"value": "0",
- "source_location": {
- "start": { "line": 8, "column": 11 },
- "end": { "line": 8, "column": 12 }
- }
+ "source_location": "8:11-8:12"
},
- "source_location": {
- "start": { "line": 8, "column": 11 },
- "end": { "line": 8, "column": 12 }
- }
+ "source_location": "8:11-8:12"
},
{
"function": {
"function": "SUBTRACTION",
"function_name": {
"text": "-",
- "source_location": {
- "start": { "line": 8, "column": 14 },
- "end": { "line": 8, "column": 15 }
- }
+ "source_location": "8:14-8:15"
},
"args": [
{
"constant": { "value": "0" },
- "source_location": {
- "start": { "line": 8, "column": 14 },
- "end": { "line": 8, "column": 14 }
- }
+ "source_location": "8:14-8:14"
},
{
"constant": { "value": "10" },
- "source_location": {
- "start": { "line": 8, "column": 15 },
- "end": { "line": 8, "column": 17 }
- }
+ "source_location": "8:15-8:17"
}
]
},
- "source_location": {
- "start": { "line": 8, "column": 13 },
- "end": { "line": 8, "column": 18 }
- }
+ "source_location": "8:13-8:18"
}
]
}
@@ -642,10 +600,7 @@
}
},
"automatic": {
- "source_location": {
- "start": { "line": 3, "column": 16 },
- "end": { "line": 3, "column": 18 }
- }
+ "source_location": "3:16-3:18"
}
}
},
@@ -675,17 +630,11 @@
"location": {
"start": {
"constant": { "value": "0" },
- "source_location": {
- "start": { "line": 3, "column": 3 },
- "end": { "line": 3, "column": 4 }
- }
+ "source_location": "3:3-3:4"
},
"size": {
"constant": { "value": "1" },
- "source_location": {
- "start": { "line": 3, "column": 9 },
- "end": { "line": 3, "column": 10 }
- }
+ "source_location": "3:9-3:10"
}
}
},
@@ -1825,28 +1774,16 @@
{
"file_name": {
"text": "",
- "source_location": {
- "start": { "line": 1, "column": 1 },
- "end": { "line": 1, "column": 1 }
- }
+ "source_location": "1:1-1:1"
},
"local_name": {
"text": "",
- "source_location": {
- "start": { "line": 1, "column": 1 },
- "end": { "line": 1, "column": 1 }
- }
+ "source_location": "1:1-1:1"
},
- "source_location": {
- "start": { "line": 1, "column": 1 },
- "end": { "line": 1, "column": 1 }
- }
+ "source_location": "1:1-1:1"
}
],
- "source_location": {
- "start": { "line": 1, "column": 1 },
- "end": { "line": 1, "column": 1 }
- }
+ "source_location": "1:1-1:1"
}
===
@@ -3547,15 +3484,9 @@
"reference": { "source_name": [ { "text": "UInt" } ] }
},
"size_in_bits": {
- "source_location": {
- "start": { "line": 3, "column": 15 },
- "end": { "line": 3, "column": 18 }
- }
+ "source_location": "3:15-3:18"
},
- "source_location": {
- "start": { "line": 3, "column": 11 },
- "end": { "line": 3, "column": 18 }
- }
+ "source_location": "3:11-3:18"
},
"name": { "name": { "text": "field" } }
}
@@ -4017,68 +3948,28 @@
Returns:
A list of error messages, or an empty list if no errors.
"""
- if source_location.is_disjoint_from_parent:
- # If source_location.is_disjoint_from_parent, then this source_location is
- # allowed to be outside of the parent's source_location.
- return []
-
result = []
start = None
end = None
- if not source_location.HasField("start"):
+ if not source_location.start:
result.append("{}.start missing".format(path))
else:
start = source_location.start
- if not source_location.HasField("end"):
+ if not source_location.end:
result.append("{}.end missing".format(path))
else:
end = source_location.end
- if start and end:
- if start.HasField("line") and end.HasField("line"):
- if start.line > end.line:
- result.append(
- "{}.start.line > {}.end.line ({} vs {})".format(
- path, path, start.line, end.line
- )
- )
- elif start.line == end.line:
- if (
- start.HasField("column")
- and end.HasField("column")
- and start.column > end.column
- ):
- result.append(
- "{}.start.column > {}.end.column ({} vs {})".format(
- path, path, start.column, end.column
- )
- )
+ if not source_location.is_disjoint_from_parent:
+ # If source_location.is_disjoint_from_parent, then this source_location
+ # is allowed to be outside of the parent's source_location.
+ if min_start and start:
+ if min_start > start:
+ result.append("{}.start before parent start".format(path))
- for name, field in (("start", start), ("end", end)):
- if not field:
- continue
- if field.HasField("line"):
- if field.line <= 0:
- result.append("{}.{}.line <= 0 ({})".format(path, name, field.line))
- else:
- result.append("{}.{}.line missing".format(path, name))
- if field.HasField("column"):
- if field.column <= 0:
- result.append("{}.{}.column <= 0 ({})".format(path, name, field.column))
- else:
- result.append("{}.{}.column missing".format(path, name))
-
- if min_start and start:
- if min_start.line > start.line or (
- min_start.line == start.line and min_start.column > start.column
- ):
- result.append("{}.start before parent start".format(path))
-
- if max_end and end:
- if max_end.line < end.line or (
- max_end.line == end.line and max_end.column < end.column
- ):
- result.append("{}.end after parent end".format(path))
+ if max_end and end:
+ if max_end < end:
+ result.append("{}.end after parent end".format(path))
return result
diff --git a/compiler/front_end/symbol_resolver.py b/compiler/front_end/symbol_resolver.py
index 0590b0d..bb3bf5b 100644
--- a/compiler/front_end/symbol_resolver.py
+++ b/compiler/front_end/symbol_resolver.py
@@ -24,6 +24,7 @@
from compiler.util import ir_data
from compiler.util import ir_data_utils
from compiler.util import ir_util
+from compiler.util import parser_types
from compiler.util import traverse_ir
# TODO(bolms): Symbol resolution raises an exception at the first error, but
@@ -167,7 +168,7 @@
value_builtin_name = ir_data.Word(
text="this",
- source_location=ir_data.Location(is_synthetic=True),
+ source_location=parser_types.SourceLocation(is_synthetic=True),
)
# In "inside field" scope, the name `this` maps back to the field itself.
# This is important for attributes like `[requires]`.
diff --git a/compiler/front_end/synthetics.py b/compiler/front_end/synthetics.py
index f55e32f..94688d4 100644
--- a/compiler/front_end/synthetics.py
+++ b/compiler/front_end/synthetics.py
@@ -28,7 +28,9 @@
if not isinstance(proto, ir_data.Message):
return
if hasattr(proto, "source_location"):
- ir_data_utils.builder(proto).source_location.is_synthetic = True
+ proto.source_location = ir_data_utils.reader(proto).source_location._replace(
+ is_synthetic=True
+ )
for spec, value in ir_data_utils.get_set_fields(proto):
if spec.name != "source_location" and spec.is_dataclass:
if spec.is_sequence:
@@ -263,7 +265,7 @@
expression.CopyFrom(_NEXT_KEYWORD_REPLACEMENT_EXPRESSION)
expression.function.args[0].CopyFrom(last_location.start)
expression.function.args[1].CopyFrom(last_location.size)
- expression.source_location.CopyFrom(original_location)
+ expression.source_location = original_location
_mark_as_synthetic(expression.function)
diff --git a/compiler/front_end/tokenizer.py b/compiler/front_end/tokenizer.py
index defee34..014217f 100644
--- a/compiler/front_end/tokenizer.py
+++ b/compiler/front_end/tokenizer.py
@@ -71,7 +71,7 @@
parser_types.Token(
'"\\n"',
"\n",
- parser_types.make_location(
+ parser_types.SourceLocation(
(line_number, len(line) + 1), (line_number, len(line) + 1)
),
)
@@ -92,7 +92,7 @@
parser_types.Token(
"Indent",
leading_whitespace[len(indent_stack[-1]) :],
- parser_types.make_location(
+ parser_types.SourceLocation(
(line_number, len(indent_stack[-1]) + 1),
(line_number, len(leading_whitespace) + 1),
),
@@ -110,7 +110,7 @@
parser_types.Token(
"Dedent",
"",
- parser_types.make_location(
+ parser_types.SourceLocation(
(line_number, len(leading_whitespace) + 1),
(line_number, len(leading_whitespace) + 1),
),
@@ -122,7 +122,7 @@
[
error.error(
file_name,
- parser_types.make_location(
+ parser_types.SourceLocation(
(line_number, 1),
(line_number, len(leading_whitespace) + 1),
),
@@ -138,7 +138,7 @@
parser_types.Token(
'"\\n"',
"\n",
- parser_types.make_location(
+ parser_types.SourceLocation(
(line_number, len(line) + 1), (line_number, len(line) + 1)
),
)
@@ -148,7 +148,7 @@
parser_types.Token(
"Dedent",
"",
- parser_types.make_location((line_number + 1, 1), (line_number + 1, 1)),
+ parser_types.SourceLocation((line_number + 1, 1), (line_number + 1, 1)),
)
)
return tokens, []
@@ -250,7 +250,7 @@
[
error.error(
file_name,
- parser_types.make_location(
+ parser_types.SourceLocation(
(line_number, offset + 1), (line_number, offset + 2)
),
"Unrecognized token",
@@ -262,7 +262,7 @@
parser_types.Token(
best_candidate_symbol,
best_candidate,
- parser_types.make_location(
+ parser_types.SourceLocation(
(line_number, offset + 1),
(line_number, offset + len(best_candidate) + 1),
),
diff --git a/compiler/front_end/tokenizer_test.py b/compiler/front_end/tokenizer_test.py
index 6a301e6..f858348 100644
--- a/compiler/front_end/tokenizer_test.py
+++ b/compiler/front_end/tokenizer_test.py
@@ -37,7 +37,7 @@
[
error.error(
"file",
- parser_types.make_location((2, 1), (2, 2)),
+ parser_types.SourceLocation((2, 1), (2, 2)),
"Bad indentation",
)
]
@@ -53,7 +53,7 @@
[
error.error(
"file",
- parser_types.make_location((2, 1), (2, 2)),
+ parser_types.SourceLocation((2, 1), (2, 2)),
"Bad indentation",
)
]
@@ -69,7 +69,7 @@
[
error.error(
"file",
- parser_types.make_location((2, 1), (2, 2)),
+ parser_types.SourceLocation((2, 1), (2, 2)),
"Bad indentation",
)
]
@@ -85,7 +85,7 @@
[
error.error(
"file",
- parser_types.make_location((2, 1), (2, 2)),
+ parser_types.SourceLocation((2, 1), (2, 2)),
"Bad indentation",
)
]
@@ -101,7 +101,7 @@
[
error.error(
"file",
- parser_types.make_location((4, 1), (4, 2)),
+ parser_types.SourceLocation((4, 1), (4, 2)),
"Bad indentation",
)
]
@@ -117,7 +117,7 @@
[
error.error(
"name",
- parser_types.make_location((1, 5), (1, 6)),
+ parser_types.SourceLocation((1, 5), (1, 6)),
"Unrecognized token",
)
]
@@ -354,7 +354,7 @@
[
error.error(
"name",
- parser_types.make_location((1, 1), (1, 2)),
+ parser_types.SourceLocation((1, 1), (1, 2)),
"Unrecognized token",
)
]
@@ -382,7 +382,7 @@
[
error.error(
"name",
- parser_types.make_location((1, 1), (1, 2)),
+ parser_types.SourceLocation((1, 1), (1, 2)),
"Unrecognized token",
)
]
@@ -482,10 +482,7 @@
def test_case(self):
self.assertEqual(
- [
- parser_types.format_location(l.source_location)
- for l in tokenizer.tokenize(case, "file")[0]
- ],
+ [str(l.source_location) for l in tokenizer.tokenize(case, "file")[0]],
cases[case],
)
diff --git a/compiler/front_end/write_inference.py b/compiler/front_end/write_inference.py
index bd4e2ba..4af5ba1 100644
--- a/compiler/front_end/write_inference.py
+++ b/compiler/front_end/write_inference.py
@@ -19,6 +19,7 @@
from compiler.util import ir_data
from compiler.util import ir_data_utils
from compiler.util import ir_util
+from compiler.util import parser_types
from compiler.util import traverse_ir
@@ -118,13 +119,13 @@
source_name=[
ir_data.Word(
text="$logical_value",
- source_location=ir_data.Location(is_synthetic=True),
+ source_location=parser_types.SourceLocation(is_synthetic=True),
)
],
- source_location=ir_data.Location(is_synthetic=True),
+ source_location=parser_types.SourceLocation(is_synthetic=True),
),
type=expression.type,
- source_location=ir_data.Location(is_synthetic=True),
+ source_location=parser_types.SourceLocation(is_synthetic=True),
)
# This loop essentially starts with:
diff --git a/compiler/util/BUILD b/compiler/util/BUILD
index ab53344..b0a29a3 100644
--- a/compiler/util/BUILD
+++ b/compiler/util/BUILD
@@ -28,13 +28,18 @@
"ir_data_fields.py",
"ir_data_utils.py",
],
- deps = [],
+ deps = [
+ ":parser_types",
+ ],
)
py_test(
name = "ir_data_fields_test",
srcs = ["ir_data_fields_test.py"],
- deps = [":ir_data"],
+ deps = [
+ ":ir_data",
+ ":parser_types",
+ ],
)
py_test(
@@ -136,9 +141,7 @@
py_library(
name = "parser_types",
srcs = ["parser_types.py"],
- deps = [
- ":ir_data",
- ],
+ deps = [],
)
py_test(
diff --git a/compiler/util/error.py b/compiler/util/error.py
index c580520..93d7a11 100644
--- a/compiler/util/error.py
+++ b/compiler/util/error.py
@@ -36,7 +36,6 @@
]
"""
-from compiler.util import ir_data_utils
from compiler.util import parser_types
# Error levels; represented by the strings that will be included in messages.
@@ -67,26 +66,25 @@
RESET = "\033[0m"
-def _copy(location):
- location = ir_data_utils.copy(location)
+def location_or_default(location):
if not location:
- location = parser_types.make_location((0, 0), (0, 0))
+ return parser_types.SourceLocation((0, 0), (0, 0))
return location
def error(source_file, location, message):
"""Returns an object representing an error message."""
- return _Message(source_file, _copy(location), ERROR, message)
+ return _Message(source_file, location_or_default(location), ERROR, message)
def warn(source_file, location, message):
"""Returns an object representing a warning."""
- return _Message(source_file, _copy(location), WARNING, message)
+ return _Message(source_file, location_or_default(location), WARNING, message)
def note(source_file, location, message):
"""Returns and object representing an informational note."""
- return _Message(source_file, _copy(location), NOTE, message)
+ return _Message(source_file, location_or_default(location), NOTE, message)
class _Message(object):
@@ -139,7 +137,7 @@
if self.location.is_synthetic:
pos = "[compiler bug]"
else:
- pos = parser_types.format_position(self.location.start)
+ pos = str(self.location.start)
source_name = self.source_file or "[prelude]"
if not self.location.is_synthetic and self.source_file in source_code:
source_lines = source_code[self.source_file].splitlines()
@@ -174,20 +172,7 @@
return result
def __repr__(self):
- return (
- "Message({source_file!r}, make_location(({start_line!r}, "
- "{start_column!r}), ({end_line!r}, {end_column!r}), "
- "{is_synthetic!r}), {severity!r}, {message!r})"
- ).format(
- source_file=self.source_file,
- start_line=self.location.start.line,
- start_column=self.location.start.column,
- end_line=self.location.end.line,
- end_column=self.location.end.column,
- is_synthetic=self.location.is_synthetic,
- severity=self.severity,
- message=self.message,
- )
+ return f"Message({repr(self.source_file)}, {repr(self.location)}, {repr(self.severity)}, {repr(self.message)})"
def __eq__(self, other):
return (
diff --git a/compiler/util/error_test.py b/compiler/util/error_test.py
index 45f9920..169163d 100644
--- a/compiler/util/error_test.py
+++ b/compiler/util/error_test.py
@@ -25,12 +25,12 @@
def test_error(self):
error_message = error.error(
- "foo.emb", parser_types.make_location((3, 4), (3, 6)), "Bad thing"
+ "foo.emb", parser_types.SourceLocation((3, 4), (3, 6)), "Bad thing"
)
self.assertEqual("foo.emb", error_message.source_file)
self.assertEqual(error.ERROR, error_message.severity)
self.assertEqual(
- parser_types.make_location((3, 4), (3, 6)), error_message.location
+ parser_types.SourceLocation((3, 4), (3, 6)), error_message.location
)
self.assertEqual("Bad thing", error_message.message)
sourceless_format = error_message.format({})
@@ -40,7 +40,7 @@
)
self.assertEqual(
[
- (error.BOLD, "foo.emb:3:4: "), # Location
+ (error.BOLD, "foo.emb:3:4: "), # SourceLocation
(error.BRIGHT_RED, "error: "), # Severity
(error.BOLD, "Bad thing"), # Message
],
@@ -52,7 +52,7 @@
)
self.assertEqual(
[
- (error.BOLD, "foo.emb:3:4: "), # Location
+ (error.BOLD, "foo.emb:3:4: "), # SourceLocation
(error.BRIGHT_RED, "error: "), # Severity
(error.BOLD, "Bad thing\n"), # Message
(error.WHITE, "abcdefghijklm\n"), # Source snippet
@@ -63,7 +63,9 @@
def test_synthetic_error(self):
error_message = error.error(
- "foo.emb", parser_types.make_location((3, 4), (3, 6), True), "Bad thing"
+ "foo.emb",
+ parser_types.SourceLocation((3, 4), (3, 6), is_synthetic=True),
+ "Bad thing",
)
sourceless_format = error_message.format({})
sourced_format = error_message.format({"foo.emb": "\n\nabcdefghijklm"})
@@ -73,7 +75,7 @@
)
self.assertEqual(
[
- (error.BOLD, "foo.emb:[compiler bug]: "), # Location
+ (error.BOLD, "foo.emb:[compiler bug]: "), # SourceLocation
(error.BRIGHT_RED, "error: "), # Severity
(error.BOLD, "Bad thing"), # Message
],
@@ -85,7 +87,7 @@
)
self.assertEqual(
[
- (error.BOLD, "foo.emb:[compiler bug]: "), # Location
+ (error.BOLD, "foo.emb:[compiler bug]: "), # SourceLocation
(error.BRIGHT_RED, "error: "), # Severity
(error.BOLD, "Bad thing"), # Message
],
@@ -94,12 +96,12 @@
def test_prelude_as_file_name(self):
error_message = error.error(
- "", parser_types.make_location((3, 4), (3, 6)), "Bad thing"
+ "", parser_types.SourceLocation((3, 4), (3, 6)), "Bad thing"
)
self.assertEqual("", error_message.source_file)
self.assertEqual(error.ERROR, error_message.severity)
self.assertEqual(
- parser_types.make_location((3, 4), (3, 6)), error_message.location
+ parser_types.SourceLocation((3, 4), (3, 6)), error_message.location
)
self.assertEqual("Bad thing", error_message.message)
sourceless_format = error_message.format({})
@@ -110,7 +112,7 @@
)
self.assertEqual(
[
- (error.BOLD, "[prelude]:3:4: "), # Location
+ (error.BOLD, "[prelude]:3:4: "), # SourceLocation
(error.BRIGHT_RED, "error: "), # Severity
(error.BOLD, "Bad thing"), # Message
],
@@ -122,7 +124,7 @@
)
self.assertEqual(
[
- (error.BOLD, "[prelude]:3:4: "), # Location
+ (error.BOLD, "[prelude]:3:4: "), # SourceLocation
(error.BRIGHT_RED, "error: "), # Severity
(error.BOLD, "Bad thing\n"), # Message
(error.WHITE, "abcdefghijklm\n"), # Source snippet
@@ -133,12 +135,12 @@
def test_multiline_error_source(self):
error_message = error.error(
- "foo.emb", parser_types.make_location((3, 4), (4, 6)), "Bad thing"
+ "foo.emb", parser_types.SourceLocation((3, 4), (4, 6)), "Bad thing"
)
self.assertEqual("foo.emb", error_message.source_file)
self.assertEqual(error.ERROR, error_message.severity)
self.assertEqual(
- parser_types.make_location((3, 4), (4, 6)), error_message.location
+ parser_types.SourceLocation((3, 4), (4, 6)), error_message.location
)
self.assertEqual("Bad thing", error_message.message)
sourceless_format = error_message.format({})
@@ -150,7 +152,7 @@
)
self.assertEqual(
[
- (error.BOLD, "foo.emb:3:4: "), # Location
+ (error.BOLD, "foo.emb:3:4: "), # SourceLocation
(error.BRIGHT_RED, "error: "), # Severity
(error.BOLD, "Bad thing"), # Message
],
@@ -162,7 +164,7 @@
)
self.assertEqual(
[
- (error.BOLD, "foo.emb:3:4: "), # Location
+ (error.BOLD, "foo.emb:3:4: "), # SourceLocation
(error.BRIGHT_RED, "error: "), # Severity
(error.BOLD, "Bad thing\n"), # Message
(error.WHITE, "abcdefghijklm\n"), # Source snippet
@@ -174,13 +176,13 @@
def test_multiline_error(self):
error_message = error.error(
"foo.emb",
- parser_types.make_location((3, 4), (3, 6)),
+ parser_types.SourceLocation((3, 4), (3, 6)),
"Bad thing\nSome explanation\nMore explanation",
)
self.assertEqual("foo.emb", error_message.source_file)
self.assertEqual(error.ERROR, error_message.severity)
self.assertEqual(
- parser_types.make_location((3, 4), (3, 6)), error_message.location
+ parser_types.SourceLocation((3, 4), (3, 6)), error_message.location
)
self.assertEqual(
"Bad thing\nSome explanation\nMore explanation", error_message.message
@@ -197,13 +199,13 @@
)
self.assertEqual(
[
- (error.BOLD, "foo.emb:3:4: "), # Location
+ (error.BOLD, "foo.emb:3:4: "), # SourceLocation
(error.BRIGHT_RED, "error: "), # Severity
(error.BOLD, "Bad thing\n"), # Message
- (error.BOLD, "foo.emb:3:4: "), # Location, line 2
+ (error.BOLD, "foo.emb:3:4: "), # SourceLocation, line 2
(error.WHITE, "note: "), # "Note" severity, line 2
(error.WHITE, "Some explanation\n"), # Message, line 2
- (error.BOLD, "foo.emb:3:4: "), # Location, line 3
+ (error.BOLD, "foo.emb:3:4: "), # SourceLocation, line 3
(error.WHITE, "note: "), # "Note" severity, line 3
(error.WHITE, "More explanation"), # Message, line 3
],
@@ -219,13 +221,13 @@
)
self.assertEqual(
[
- (error.BOLD, "foo.emb:3:4: "), # Location
+ (error.BOLD, "foo.emb:3:4: "), # SourceLocation
(error.BRIGHT_RED, "error: "), # Severity
(error.BOLD, "Bad thing\n"), # Message
- (error.BOLD, "foo.emb:3:4: "), # Location, line 2
+ (error.BOLD, "foo.emb:3:4: "), # SourceLocation, line 2
(error.WHITE, "note: "), # "Note" severity, line 2
(error.WHITE, "Some explanation\n"), # Message, line 2
- (error.BOLD, "foo.emb:3:4: "), # Location, line 3
+ (error.BOLD, "foo.emb:3:4: "), # SourceLocation, line 3
(error.WHITE, "note: "), # "Note" severity, line 3
(error.WHITE, "More explanation\n"), # Message, line 3
(error.WHITE, "abcdefghijklm\n"), # Source snippet
@@ -236,12 +238,12 @@
def test_warn(self):
warning_message = error.warn(
- "foo.emb", parser_types.make_location((3, 4), (3, 6)), "Not good thing"
+ "foo.emb", parser_types.SourceLocation((3, 4), (3, 6)), "Not good thing"
)
self.assertEqual("foo.emb", warning_message.source_file)
self.assertEqual(error.WARNING, warning_message.severity)
self.assertEqual(
- parser_types.make_location((3, 4), (3, 6)), warning_message.location
+ parser_types.SourceLocation((3, 4), (3, 6)), warning_message.location
)
self.assertEqual("Not good thing", warning_message.message)
sourced_format = warning_message.format({"foo.emb": "\n\nabcdefghijklm"})
@@ -251,7 +253,7 @@
)
self.assertEqual(
[
- (error.BOLD, "foo.emb:3:4: "), # Location
+ (error.BOLD, "foo.emb:3:4: "), # SourceLocation
(error.BRIGHT_YELLOW, "warning: "), # Severity
(error.BOLD, "Not good thing\n"), # Message
(error.WHITE, "abcdefghijklm\n"), # Source snippet
@@ -262,12 +264,12 @@
def test_note(self):
note_message = error.note(
- "foo.emb", parser_types.make_location((3, 4), (3, 6)), "OK thing"
+ "foo.emb", parser_types.SourceLocation((3, 4), (3, 6)), "OK thing"
)
self.assertEqual("foo.emb", note_message.source_file)
self.assertEqual(error.NOTE, note_message.severity)
self.assertEqual(
- parser_types.make_location((3, 4), (3, 6)), note_message.location
+ parser_types.SourceLocation((3, 4), (3, 6)), note_message.location
)
self.assertEqual("OK thing", note_message.message)
sourced_format = note_message.format({"foo.emb": "\n\nabcdefghijklm"})
@@ -277,7 +279,7 @@
)
self.assertEqual(
[
- (error.BOLD, "foo.emb:3:4: "), # Location
+ (error.BOLD, "foo.emb:3:4: "), # SourceLocation
(error.WHITE, "note: "), # Severity
(error.WHITE, "OK thing\n"), # Message
(error.WHITE, "abcdefghijklm\n"), # Source snippet
@@ -288,27 +290,31 @@
def test_equality(self):
note_message = error.note(
- "foo.emb", parser_types.make_location((3, 4), (3, 6)), "thing"
+ "foo.emb", parser_types.SourceLocation((3, 4), (3, 6)), "thing"
)
self.assertEqual(
note_message,
- error.note("foo.emb", parser_types.make_location((3, 4), (3, 6)), "thing"),
+ error.note("foo.emb", parser_types.SourceLocation((3, 4), (3, 6)), "thing"),
)
self.assertNotEqual(
note_message,
- error.warn("foo.emb", parser_types.make_location((3, 4), (3, 6)), "thing"),
+ error.warn("foo.emb", parser_types.SourceLocation((3, 4), (3, 6)), "thing"),
)
self.assertNotEqual(
note_message,
- error.note("foo2.emb", parser_types.make_location((3, 4), (3, 6)), "thing"),
+ error.note(
+ "foo2.emb", parser_types.SourceLocation((3, 4), (3, 6)), "thing"
+ ),
)
self.assertNotEqual(
note_message,
- error.note("foo.emb", parser_types.make_location((2, 4), (3, 6)), "thing"),
+ error.note("foo.emb", parser_types.SourceLocation((2, 4), (3, 6)), "thing"),
)
self.assertNotEqual(
note_message,
- error.note("foo.emb", parser_types.make_location((3, 4), (3, 6)), "thing2"),
+ error.note(
+ "foo.emb", parser_types.SourceLocation((3, 4), (3, 6)), "thing2"
+ ),
)
@@ -348,43 +354,43 @@
def test_split_errors(self):
user_error = [
error.error(
- "foo.emb", parser_types.make_location((1, 2), (3, 4)), "Bad thing"
+ "foo.emb", parser_types.SourceLocation((1, 2), (3, 4)), "Bad thing"
),
error.note(
"foo.emb",
- parser_types.make_location((3, 4), (5, 6)),
+ parser_types.SourceLocation((3, 4), (5, 6)),
"Note: bad thing referrent",
),
]
user_error_2 = [
error.error(
- "foo.emb", parser_types.make_location((8, 9), (10, 11)), "Bad thing"
+ "foo.emb", parser_types.SourceLocation((8, 9), (10, 11)), "Bad thing"
),
error.note(
"foo.emb",
- parser_types.make_location((10, 11), (12, 13)),
+ parser_types.SourceLocation((10, 11), (12, 13)),
"Note: bad thing referrent",
),
]
synthetic_error = [
error.error(
- "foo.emb", parser_types.make_location((1, 2), (3, 4)), "Bad thing"
+ "foo.emb", parser_types.SourceLocation((1, 2), (3, 4)), "Bad thing"
),
error.note(
"foo.emb",
- parser_types.make_location((3, 4), (5, 6), True),
+ parser_types.SourceLocation((3, 4), (5, 6), is_synthetic=True),
"Note: bad thing referrent",
),
]
synthetic_error_2 = [
error.error(
"foo.emb",
- parser_types.make_location((8, 9), (10, 11), True),
+ parser_types.SourceLocation((8, 9), (10, 11), is_synthetic=True),
"Bad thing",
),
error.note(
"foo.emb",
- parser_types.make_location((10, 11), (12, 13)),
+ parser_types.SourceLocation((10, 11), (12, 13)),
"Note: bad thing referrent",
),
]
@@ -407,33 +413,33 @@
def test_filter_errors(self):
user_error = [
error.error(
- "foo.emb", parser_types.make_location((1, 2), (3, 4)), "Bad thing"
+ "foo.emb", parser_types.SourceLocation((1, 2), (3, 4)), "Bad thing"
),
error.note(
"foo.emb",
- parser_types.make_location((3, 4), (5, 6)),
+ parser_types.SourceLocation((3, 4), (5, 6)),
"Note: bad thing referrent",
),
]
synthetic_error = [
error.error(
- "foo.emb", parser_types.make_location((1, 2), (3, 4)), "Bad thing"
+ "foo.emb", parser_types.SourceLocation((1, 2), (3, 4)), "Bad thing"
),
error.note(
"foo.emb",
- parser_types.make_location((3, 4), (5, 6), True),
+ parser_types.SourceLocation((3, 4), (5, 6), is_synthetic=True),
"Note: bad thing referrent",
),
]
synthetic_error_2 = [
error.error(
"foo.emb",
- parser_types.make_location((8, 9), (10, 11), True),
+ parser_types.SourceLocation((8, 9), (10, 11), is_synthetic=True),
"Bad thing",
),
error.note(
"foo.emb",
- parser_types.make_location((10, 11), (12, 13)),
+ parser_types.SourceLocation((10, 11), (12, 13)),
"Note: bad thing referrent",
),
]
@@ -447,7 +453,7 @@
def test_format_errors(self):
errors = [
- [error.note("foo.emb", parser_types.make_location((3, 4), (3, 6)), "note")]
+ [error.note("foo.emb", parser_types.SourceLocation((3, 4), (3, 6)), "note")]
]
sources = {"foo.emb": "x\ny\nz bcd\nq\n"}
self.assertEqual(
diff --git a/compiler/util/ir_data.py b/compiler/util/ir_data.py
index cd12b96..d37c48a 100644
--- a/compiler/util/ir_data.py
+++ b/compiler/util/ir_data.py
@@ -17,12 +17,14 @@
This is limited to purely data and type annotations.
"""
+import collections
import dataclasses
import enum
import sys
from typing import ClassVar, Optional
from compiler.util import ir_data_fields
+from compiler.util import parser_types
@dataclasses.dataclass
@@ -112,37 +114,6 @@
@dataclasses.dataclass
-class Position(Message):
- """A zero-width position within a source file."""
-
- line: int = 0
- """Line (starts from 1)."""
- column: int = 0
- """Column (starts from 1)."""
-
-
-@dataclasses.dataclass
-class Location(Message):
- """A half-open start:end range within a source file."""
-
- start: Optional[Position] = None
- """Beginning of the range"""
- end: Optional[Position] = None
- """One column past the end of the range."""
-
- is_disjoint_from_parent: Optional[bool] = None
- """True if this Location is outside of the parent object's Location."""
-
- is_synthetic: Optional[bool] = None
- """True if this Location's parent was synthesized, and does not directly
- appear in the source file.
-
- The Emboss front end uses this field to cull
- irrelevant error messages.
- """
-
-
-@dataclasses.dataclass
class Word(Message):
"""IR for a bare word in the source file.
@@ -150,7 +121,7 @@
"""
text: Optional[str] = None
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
@dataclasses.dataclass
@@ -158,13 +129,13 @@
"""IR for a string in the source file."""
text: Optional[str] = None
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
@dataclasses.dataclass
class Documentation(Message):
text: Optional[str] = None
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
@dataclasses.dataclass
@@ -172,14 +143,14 @@
"""IR for a boolean constant."""
value: Optional[bool] = None
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
@dataclasses.dataclass
class Empty(Message):
"""Placeholder message for automatic element counts for arrays."""
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
@dataclasses.dataclass
@@ -192,7 +163,7 @@
# TODO(bolms): switch back to int, and just use strings during
# serialization, now that we're free of proto.
value: Optional[str] = None
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
class FunctionMapping(int, enum.Enum):
@@ -240,7 +211,7 @@
function: Optional[FunctionMapping] = None
args: list["Expression"] = ir_data_fields.list_field(lambda: Expression)
function_name: Optional[Word] = None
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
@dataclasses.dataclass
@@ -310,7 +281,7 @@
should not be visible outside of its immediate namespace.
"""
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
"""The location of this NameDefinition in source code."""
@@ -356,7 +327,7 @@
# TODO(bolms): Allow absolute paths starting with ".".
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
"""Note that this is the source_location of the *Reference*, not of the
object to which it refers.
"""
@@ -406,7 +377,7 @@
# TODO(bolms): Make the above change before declaring the IR to be "stable".
path: list[Reference] = ir_data_fields.list_field(Reference)
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
@dataclasses.dataclass
@@ -509,7 +480,7 @@
builtin_reference: Optional[Reference] = ir_data_fields.oneof_field("expression")
type: Optional[ExpressionType] = None
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
@dataclasses.dataclass
@@ -521,7 +492,7 @@
element_count: Optional[Expression] = ir_data_fields.oneof_field("size")
automatic: Optional[Empty] = ir_data_fields.oneof_field("size")
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
@dataclasses.dataclass
@@ -530,7 +501,7 @@
reference: Optional[Reference] = None
runtime_parameter: list[Expression] = ir_data_fields.list_field(Expression)
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
@dataclasses.dataclass
@@ -541,7 +512,7 @@
array_type: Optional[ArrayType] = ir_data_fields.oneof_field("type")
size_in_bits: Optional[Expression] = None
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
@dataclasses.dataclass
@@ -553,7 +524,7 @@
expression: Optional[Expression] = ir_data_fields.oneof_field("value")
string_constant: Optional[String] = ir_data_fields.oneof_field("value")
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
@dataclasses.dataclass
@@ -564,7 +535,7 @@
value: Optional[AttributeValue] = None
back_end: Optional[Word] = None
is_default: Optional[bool] = None
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
@dataclasses.dataclass
@@ -618,7 +589,7 @@
start: Optional[Expression] = None
size: Optional[Expression] = None
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
@dataclasses.dataclass
@@ -689,7 +660,7 @@
`bar`: those fields only conditionally exist in the structure.
"""
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
@dataclasses.dataclass
@@ -733,7 +704,7 @@
be `{ 0, 1, 2, 3, ... }`.
"""
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
@dataclasses.dataclass
@@ -743,7 +714,7 @@
# Externals have no values other than name and attribute list, which are
# common to all type definitions.
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
@dataclasses.dataclass
@@ -759,7 +730,7 @@
attribute: list[Attribute] = ir_data_fields.list_field(Attribute)
"""Value-specific attributes."""
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
@dataclasses.dataclass
@@ -767,7 +738,7 @@
"""IR for an enumerated type definition."""
value: list[EnumValue] = ir_data_fields.list_field(EnumValue)
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
@dataclasses.dataclass
@@ -778,7 +749,7 @@
"""The file to import."""
local_name: Optional[Word] = None
"""The name to use within this module."""
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
@dataclasses.dataclass
@@ -808,7 +779,7 @@
is filled in after initial parsing is finished.
"""
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
class AddressableUnit(int, enum.Enum):
@@ -852,7 +823,7 @@
These are currently only allowed on structures, but in the future they
should be allowed on externals.
"""
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
@dataclasses.dataclass
@@ -869,7 +840,7 @@
"""Other modules imported."""
source_text: Optional[str] = None
"""The original source code."""
- source_location: Optional[Location] = None
+ source_location: Optional[parser_types.SourceLocation] = None
"""Source code covered by this IR."""
source_file_name: Optional[str] = None
"""Name of the source file."""
diff --git a/compiler/util/ir_data_fields_test.py b/compiler/util/ir_data_fields_test.py
index 0376b69..36264b8 100644
--- a/compiler/util/ir_data_fields_test.py
+++ b/compiler/util/ir_data_fields_test.py
@@ -22,6 +22,7 @@
from compiler.util import ir_data
from compiler.util import ir_data_fields
+from compiler.util import parser_types
class TestEnum(enum.Enum):
diff --git a/compiler/util/ir_data_utils.py b/compiler/util/ir_data_utils.py
index e7679a6..f4c9575 100644
--- a/compiler/util/ir_data_utils.py
+++ b/compiler/util/ir_data_utils.py
@@ -29,14 +29,14 @@
if not function.function_name:
function.function_name = Word()
if not function.function_name.source_location:
- function.function_name.source_location = Location()
- word.source_location.end = Position(line=1,column=2)
+ function.function_name.source_location = SourceLocation()
+ word.source_location.end = SourcePosition(line=1,column=2)
```
We can do:
```
def set_function_name_end(function: Function):
- builder(function).function_name.source_location.end = Position(line=1,
+ builder(function).function_name.source_location.end = SourcePosition(line=1,
column=2)
```
@@ -79,6 +79,7 @@
from compiler.util import ir_data
from compiler.util import ir_data_fields
+from compiler.util import parser_types
MessageT = TypeVar("MessageT", bound=ir_data.Message)
@@ -106,11 +107,14 @@
assert ir is not None
values: MutableMapping[str, Any] = {}
for spec, value in field_func(ir):
- if value is not None and spec.is_dataclass:
- if spec.is_sequence:
- value = [self._to_dict(v, field_func) for v in value]
- else:
- value = self._to_dict(value, field_func)
+ if value is not None:
+ if spec.is_dataclass:
+ if spec.is_sequence:
+ value = [self._to_dict(v, field_func) for v in value]
+ else:
+ value = self._to_dict(value, field_func)
+ elif spec.data_type == parser_types.SourceLocation:
+ value = str(value)
values[spec.name] = value
return values
@@ -182,6 +186,8 @@
class_fields[name] = IrDataSerializer._enum_type_converter(
spec.data_type, value
)
+ elif spec.data_type == parser_types.SourceLocation:
+ class_fields[name] = parser_types.SourceLocation.from_str(value)
else:
if spec.is_sequence:
class_fields[name] = value
diff --git a/compiler/util/ir_data_utils_test.py b/compiler/util/ir_data_utils_test.py
index 19ae579..2cadf06 100644
--- a/compiler/util/ir_data_utils_test.py
+++ b/compiler/util/ir_data_utils_test.py
@@ -23,6 +23,7 @@
from compiler.util import ir_data
from compiler.util import ir_data_fields
from compiler.util import ir_data_utils
+from compiler.util import parser_types
class TestEnum(enum.Enum):
@@ -103,7 +104,7 @@
# Try a IR data class
expected_field = ir_data_fields.make_field_spec(
"source_location",
- ir_data.Location,
+ parser_types.SourceLocation,
ir_data_fields.FieldContainer.OPTIONAL,
None,
)
@@ -119,11 +120,11 @@
self.assertEqual(fields["external"], expected_field)
# Try non-optional scalar
- fields = ir_data_utils.field_specs(ir_data.Position)
+ fields = ir_data_utils.field_specs(ir_data.CanonicalName)
expected_field = ir_data_fields.make_field_spec(
- "line", int, ir_data_fields.FieldContainer.NONE, None
+ "module_file", str, ir_data_fields.FieldContainer.NONE, None
)
- self.assertEqual(fields["line"], expected_field)
+ self.assertEqual(fields["module_file"], expected_field)
fields = ir_data_utils.field_specs(ir_data.ArrayType)
expected_field = ir_data_fields.make_field_spec(
@@ -330,9 +331,9 @@
def test_copy_from(self) -> None:
"""Tests that `CopyFrom` works."""
- location = ir_data.Location(
- start=ir_data.Position(line=1, column=1),
- end=ir_data.Position(line=1, column=2),
+ location = parser_types.SourceLocation(
+ start=parser_types.SourcePosition(line=1, column=1),
+ end=parser_types.SourcePosition(line=1, column=2),
)
expression_ir = ir_data.Expression(source_location=location)
template: ir_data.Expression = expression_parser.parse("x + y")
@@ -355,9 +356,9 @@
self.assertIsInstance(template.function, ir_data.Function)
self.assertIsInstance(template.function.args, ir_data_fields.CopyValuesList)
- location = ir_data.Location(
- start=ir_data.Position(line=1, column=1),
- end=ir_data.Position(line=1, column=2),
+ location = parser_types.SourceLocation(
+ start=parser_types.SourcePosition(line=1, column=1),
+ end=parser_types.SourcePosition(line=1, column=2),
)
expression_ir = ir_data.Expression(source_location=location)
self.assertIsInstance(expression_ir, ir_data.Expression)
@@ -515,11 +516,7 @@
{
"constant": {
"value": "0",
- "source_location": {
- "start": {"line": 421, "column": 3},
- "end": {"line": 421, "column": 4},
- "is_synthetic": False,
- },
+ "source_location": "421:3-421:4",
},
"type": {
"integer": {
@@ -529,20 +526,12 @@
"maximum_value": "0",
}
},
- "source_location": {
- "start": {"line": 421, "column": 3},
- "end": {"line": 421, "column": 4},
- "is_synthetic": False,
- },
+ "source_location": "421:3-421:4",
},
{
"constant": {
"value": "1",
- "source_location": {
- "start": {"line": 421, "column": 11},
- "end": {"line": 421, "column": 12},
- "is_synthetic": False,
- },
+ "source_location": "421:11-421:12",
},
"type": {
"integer": {
@@ -552,11 +541,7 @@
"maximum_value": "1",
}
},
- "source_location": {
- "start": {"line": 421, "column": 11},
- "end": {"line": 421, "column": 12},
- "is_synthetic": False,
- },
+ "source_location": "421:11-421:12",
},
]
function_data = {"args": function_args}
diff --git a/compiler/util/ir_util_test.py b/compiler/util/ir_util_test.py
index 07cf4dc..b3deeb2 100644
--- a/compiler/util/ir_util_test.py
+++ b/compiler/util/ir_util_test.py
@@ -782,13 +782,13 @@
"base_type": {
"atomic_type": {
"reference": { },
- "source_location": { "start": { "line": 5 } }
+ "source_location": "5:1-6:1"
}
},
- "source_location": { "start": { "line": 4 } }
+ "source_location": "4:1-6:1"
}
},
- "source_location": { "start": { "line": 3 } }
+ "source_location": "3:1-6:1"
}
}""",
)
diff --git a/compiler/util/parser_types.py b/compiler/util/parser_types.py
index fffe086..a3ef34d 100644
--- a/compiler/util/parser_types.py
+++ b/compiler/util/parser_types.py
@@ -12,72 +12,172 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-"""Various types shared through multiple passes of parsing.
+"""Types related to the LR(1) parser.
-This module contains types used as interfaces between parts of the Emboss front
-end. These types do not really "belong" to either the producers or consumers,
-and in a few cases placing them in one or the other creates unnecessary
-dependencies, so they are defined here.
+This module contains types used by the LR(1) parser, which are also used in
+other parts of the compiler:
+
+ SourcePosition: a position (zero-width) within a source file.
+ SourceLocation: a span within a source file.
+ Production: a grammar production.
+ Token: a token; lr1.Parser operates on lists of tokens.
"""
import collections
-from compiler.util import ir_data
+
+PositionTuple = collections.namedtuple(
+ "PositionTuple", ["line", "column"], defaults=[0, 0]
+)
-def _make_position(line, column):
- """Makes an ir_data.Position from line, column ints."""
- if not isinstance(line, int):
- raise ValueError("Bad line {!r}".format(line))
- elif not isinstance(column, int):
- raise ValueError("Bad column {!r}".format(column))
- return ir_data.Position(line=line, column=column)
+class SourcePosition(PositionTuple):
+ """A zero-width position within a source file.
+
+ Positions are 1-based (the first character of a source file is (1, 1)).
+
+ The special value (0, 0) indicates a missing or unknown position, and is
+ considered falsy. All other values of SourcePosition are truthy.
+
+ Attributes:
+ line: the line within the source file; the first line is 1
+ column: the column within the source line; the first character is 1
+ """
+
+ # This __new__ just adds asserts around PositionTuple.__new__, so it is
+ # unnecessary when running under -O.
+ if __debug__:
+
+ def __new__(cls, /, line=0, column=0):
+ assert isinstance(line, int), f"line {line} is not int"
+ assert isinstance(column, int), f"column {column} is not int"
+ assert line >= 0, f"line {line} is negative"
+ assert column >= 0, f"column {column} is negative"
+ assert (line == 0 and column == 0) or (
+ line != 0 and column != 0
+ ), f"Cannot have line {line} with column {column}"
+ return PositionTuple.__new__(cls, line, column)
+
+ def __str__(self):
+ return f"{self.line}:{self.column}"
+
+ @staticmethod
+ def from_str(value):
+ try:
+ l, c = value.split(":")
+ return SourcePosition(line=int(l.strip()), column=int(c.strip()))
+ except Exception as e:
+ raise ValueError(f"{repr(value)} is not a valid SourcePosition.")
+
+ def __bool__(self):
+ return bool(self.line)
-def _parse_position(text):
- """Parses an ir_data.Position from "line:column" (e.g., "1:2")."""
- line, column = text.split(":")
- return _make_position(int(line), int(column))
+LocationTuple = collections.namedtuple(
+ "LocationTuple",
+ ["start", "end", "is_disjoint_from_parent", "is_synthetic"],
+ defaults=[SourcePosition(), SourcePosition(), False, False],
+)
-def format_position(position):
- """formats an ir_data.Position to "line:column" form."""
- return "{}:{}".format(position.line, position.column)
+class SourceLocation(LocationTuple):
+ """The source location of a node in the IR, as a half-open start:end range
+ Attributes:
+ start: the start of the range
+ end: one character past the end of the range
+ is_disjoint_from_parent: True if this SourceLocation may fall outside
+ of the SourceLocation of the parent node
+ is_synthetic: True if the associated node was generated from
+ user-supplied source code, but is part of a construct that does not
+ directly correspond to something that the user wrote (e.g., a
+ generated virtual field that is assembled from fragments from
+ elsewhere in the IR).
-def make_location(start, end, is_synthetic=False):
- """Makes an ir_data.Location from (line, column) tuples or ir_data.Positions."""
- if isinstance(start, tuple):
- start = _make_position(*start)
- if isinstance(end, tuple):
- end = _make_position(*end)
- if not isinstance(start, ir_data.Position):
- raise ValueError("Bad start {!r}".format(start))
- elif not isinstance(end, ir_data.Position):
- raise ValueError("Bad end {!r}".format(end))
- elif start.line > end.line or (
- start.line == end.line and start.column > end.column
+ SourceLocation is falsy if the start and end are falsy.
+ """
+
+ def __new__(
+ cls,
+ /,
+ start=SourcePosition(),
+ end=SourcePosition(),
+ *,
+ is_disjoint_from_parent=False,
+ is_synthetic=False,
):
- raise ValueError(
- "Start {} is after end {}".format(
- format_position(start), format_position(end)
- )
+ if not isinstance(start, SourcePosition):
+ start = SourcePosition(*start)
+ if not isinstance(end, SourcePosition):
+ end = SourcePosition(*end)
+ assert start <= end, f"start {start} is after end {end}"
+ assert (not start and not end) or (
+ start and end
+ ), "Cannot have have start {start} with end {end}"
+ assert isinstance(
+ is_disjoint_from_parent, bool
+ ), f"is_disjoint_from_parent {is_disjoint_from_parent} is not bool"
+ assert isinstance(
+ is_synthetic, bool
+ ), f"is_synthetic {is_synthetic} is not bool"
+ return LocationTuple.__new__(
+ cls, start, end, is_disjoint_from_parent, is_synthetic
)
- return ir_data.Location(start=start, end=end, is_synthetic=is_synthetic)
+
+ def __str__(self):
+ suffix = ""
+ if self.is_disjoint_from_parent:
+ suffix += "^"
+ if self.is_synthetic:
+ suffix += "*"
+ return f"{self.start}-{self.end}{suffix}"
+
+ @staticmethod
+ def from_str(value):
+ original_value = value
+ try:
+ is_synthetic = False
+ if value[-1] == "*":
+ is_synthetic = True
+ value = value[:-1]
+ is_disjoint_from_parent = False
+ if value[-1] == "^":
+ is_disjoint_from_parent = True
+ value = value[:-1]
+ start, end = value.split("-")
+ return SourceLocation(
+ start=SourcePosition.from_str(start),
+ end=SourcePosition.from_str(end),
+ is_synthetic=is_synthetic,
+ is_disjoint_from_parent=is_disjoint_from_parent,
+ )
+ except Exception as e:
+ raise ValueError(f"{repr(original_value)} is not a valid SourceLocation.")
+
+ def __bool__(self):
+ # Should this check is_synthetic and is_disjoint_from_parent as well?
+ return bool(self.start)
-def format_location(location):
- """Formats an ir_data.Location in format "1:2-3:4" ("start-end")."""
- return "{}-{}".format(
- format_position(location.start), format_position(location.end)
+def merge_source_locations(*nodes):
+ locations = [
+ node.source_location
+ for node in nodes
+ if hasattr(node, "source_location") and node.source_location
+ ]
+ if not locations:
+ return None
+ start = locations[0].start
+ end = locations[-1].end
+ is_synthetic = any(l.is_synthetic for l in locations)
+ is_disjoint_from_parent = any(l.is_disjoint_from_parent for l in locations)
+ return SourceLocation(
+ start=start,
+ end=end,
+ is_synthetic=is_synthetic,
+ is_disjoint_from_parent=is_disjoint_from_parent,
)
-def parse_location(text):
- """Parses an ir_data.Location from format "1:2-3:4" ("start-end")."""
- start, end = text.split("-")
- return make_location(_parse_position(start), _parse_position(end))
-
-
class Token(collections.namedtuple("Token", ["symbol", "text", "source_location"])):
"""A Token is a chunk of text from a source file, and a classification.
@@ -89,7 +189,7 @@
def __str__(self):
return "{} {} {}".format(
- self.symbol, repr(self.text), format_location(self.source_location)
+ self.symbol, repr(self.text), str(self.source_location)
)
diff --git a/compiler/util/parser_types_test.py b/compiler/util/parser_types_test.py
index 097ece4..c66bf2f 100644
--- a/compiler/util/parser_types_test.py
+++ b/compiler/util/parser_types_test.py
@@ -20,82 +20,188 @@
class PositionTest(unittest.TestCase):
- """Tests for Position-related functions in parser_types."""
+ """Tests for SourcePosition-related functions in parser_types."""
- def test_format_position(self):
+ def test_position_str(self):
+ self.assertEqual("1:2", str(parser_types.SourcePosition(line=1, column=2)))
+
+ def test_position_bool(self):
+ self.assertFalse(parser_types.SourcePosition())
+ self.assertFalse(parser_types.SourcePosition(0, 0))
+ self.assertTrue(parser_types.SourcePosition(1, 1))
+
+ def test_position_from_str(self):
self.assertEqual(
- "1:2", parser_types.format_position(ir_data.Position(line=1, column=2))
+ parser_types.SourcePosition(1, 2),
+ parser_types.SourcePosition.from_str("1:2"),
+ )
+ self.assertEqual(
+ parser_types.SourcePosition(0, 0),
+ parser_types.SourcePosition.from_str("0:0"),
+ )
+ self.assertRaises(ValueError, parser_types.SourcePosition.from_str, "0xa:9")
+ self.assertRaises(ValueError, parser_types.SourcePosition.from_str, "9")
+ if __debug__:
+ self.assertRaises(ValueError, parser_types.SourcePosition.from_str, "0:-1")
+ self.assertRaises(ValueError, parser_types.SourcePosition.from_str, "-1:0")
+
+ def test_position_new(self):
+ self.assertEqual(
+ parser_types.SourcePosition(1, 2),
+ parser_types.SourcePosition(line=1, column=2),
+ )
+ if __debug__:
+ self.assertRaises(AssertionError, parser_types.SourcePosition, -1, 1)
+ self.assertRaises(AssertionError, parser_types.SourcePosition, 1, -1)
+ self.assertRaises(AssertionError, parser_types.SourcePosition, None, 1)
+ self.assertRaises(AssertionError, parser_types.SourcePosition, 1, None)
+ self.assertRaises(AssertionError, parser_types.SourcePosition, 1.1, 1)
+ self.assertRaises(AssertionError, parser_types.SourcePosition, 1, 1.1)
+ self.assertRaises(AssertionError, parser_types.SourcePosition, 0, 1)
+ self.assertRaises(AssertionError, parser_types.SourcePosition, 1, 0)
+
+ def test_position_attributes(self):
+ self.assertEqual(1, parser_types.SourcePosition(1, 2).line)
+ self.assertEqual(2, parser_types.SourcePosition(1, 2).column)
+
+ def test_position_order(self):
+ self.assertTrue(
+ parser_types.SourcePosition(1, 2) < parser_types.SourcePosition(2, 2)
+ )
+ self.assertTrue(
+ parser_types.SourcePosition(2, 1) < parser_types.SourcePosition(2, 2)
+ )
+ self.assertFalse(
+ parser_types.SourcePosition(2, 1) < parser_types.SourcePosition(2, 1)
+ )
+ self.assertFalse(
+ parser_types.SourcePosition(2, 2) < parser_types.SourcePosition(2, 1)
)
class LocationTest(unittest.TestCase):
- """Tests for Location-related functions in parser_types."""
+ """Tests for SourceLocation-related functions in parser_types."""
- def test_make_location(self):
+ def test_location_new(self):
self.assertEqual(
- ir_data.Location(
- start=ir_data.Position(line=1, column=2),
- end=ir_data.Position(line=3, column=4),
+ parser_types.SourceLocation(
+ start=parser_types.SourcePosition(line=1, column=2),
+ end=parser_types.SourcePosition(line=3, column=4),
is_synthetic=False,
+ is_disjoint_from_parent=False,
),
- parser_types.make_location((1, 2), (3, 4)),
+ parser_types.SourceLocation((1, 2), (3, 4)),
)
- self.assertEqual(
- ir_data.Location(
- start=ir_data.Position(line=1, column=2),
- end=ir_data.Position(line=3, column=4),
- is_synthetic=False,
- ),
- parser_types.make_location(
- ir_data.Position(line=1, column=2), ir_data.Position(line=3, column=4)
- ),
+ self.assertFalse(parser_types.SourceLocation(is_synthetic=False).is_synthetic)
+ self.assertTrue(parser_types.SourceLocation(is_synthetic=True).is_synthetic)
+ self.assertFalse(
+ parser_types.SourceLocation(
+ is_disjoint_from_parent=False
+ ).is_disjoint_from_parent
)
-
- def test_make_synthetic_location(self):
- self.assertEqual(
- ir_data.Location(
- start=ir_data.Position(line=1, column=2),
- end=ir_data.Position(line=3, column=4),
- is_synthetic=True,
- ),
- parser_types.make_location((1, 2), (3, 4), True),
+ self.assertTrue(
+ parser_types.SourceLocation(
+ is_disjoint_from_parent=True
+ ).is_disjoint_from_parent
)
- self.assertEqual(
- ir_data.Location(
- start=ir_data.Position(line=1, column=2),
- end=ir_data.Position(line=3, column=4),
- is_synthetic=True,
- ),
- parser_types.make_location(
- ir_data.Position(line=1, column=2),
- ir_data.Position(line=3, column=4),
- True,
- ),
- )
+ self.assertRaises(TypeError, parser_types.SourceLocation, None, (3, 4))
+ self.assertRaises(TypeError, parser_types.SourceLocation, (1, 2), None)
+ if __debug__:
+ self.assertRaises(
+ AssertionError, parser_types.SourceLocation, (3, 4), (1, 2)
+ )
+ self.assertRaises(
+ AssertionError, parser_types.SourceLocation, (3, 4), (3, 2)
+ )
+ self.assertRaises(
+ AssertionError,
+ parser_types.SourceLocation,
+ parser_types.SourcePosition(),
+ (1, 2),
+ )
+ self.assertRaises(
+ AssertionError,
+ parser_types.SourceLocation,
+ (1, 2),
+ parser_types.SourcePosition(),
+ )
- def test_make_location_type_checks(self):
- self.assertRaises(ValueError, parser_types.make_location, [1, 2], (1, 2))
- self.assertRaises(ValueError, parser_types.make_location, (1, 2), [1, 2])
-
- def test_make_location_logic_checks(self):
- self.assertRaises(ValueError, parser_types.make_location, (3, 4), (1, 2))
- self.assertRaises(ValueError, parser_types.make_location, (1, 3), (1, 2))
- self.assertTrue(parser_types.make_location((1, 2), (1, 2)))
-
- def test_format_location(self):
+ def test_location_str(self):
self.assertEqual(
"1:2-3:4",
- parser_types.format_location(parser_types.make_location((1, 2), (3, 4))),
+ str(parser_types.SourceLocation((1, 2), (3, 4))),
+ )
+ self.assertEqual(
+ "1:2-3:4^",
+ str(
+ parser_types.SourceLocation(
+ (1, 2), (3, 4), is_disjoint_from_parent=True
+ )
+ ),
+ )
+ self.assertEqual(
+ "1:2-3:4*",
+ str(parser_types.SourceLocation((1, 2), (3, 4), is_synthetic=True)),
+ )
+ self.assertEqual(
+ "1:2-3:4^*",
+ str(
+ parser_types.SourceLocation(
+ (1, 2), (3, 4), is_synthetic=True, is_disjoint_from_parent=True
+ )
+ ),
)
- def test_parse_location(self):
+ def test_location_from_str(self):
self.assertEqual(
- parser_types.make_location((1, 2), (3, 4)),
- parser_types.parse_location("1:2-3:4"),
+ parser_types.SourceLocation((1, 2), (3, 4)),
+ parser_types.SourceLocation.from_str("1:2-3:4"),
)
self.assertEqual(
- parser_types.make_location((1, 2), (3, 4)),
- parser_types.parse_location(" 1 : 2 - 3 : 4 "),
+ parser_types.SourceLocation((1, 2), (3, 4)),
+ parser_types.SourceLocation.from_str(" 1 : 2 - 3 : 4 "),
+ )
+ self.assertEqual(
+ parser_types.SourceLocation((1, 2), (3, 4), is_disjoint_from_parent=True),
+ parser_types.SourceLocation.from_str("1:2-3:4^"),
+ )
+ self.assertEqual(
+ parser_types.SourceLocation((1, 2), (3, 4), is_synthetic=True),
+ parser_types.SourceLocation.from_str("1:2-3:4*"),
+ )
+ self.assertEqual(
+ parser_types.SourceLocation(
+ (1, 2), (3, 4), is_disjoint_from_parent=True, is_synthetic=True
+ ),
+ parser_types.SourceLocation.from_str("1:2-3:4^*"),
+ )
+ self.assertRaises(ValueError, parser_types.SourceLocation.from_str, "1:2-3:")
+ if __debug__:
+ self.assertRaises(
+ ValueError, parser_types.SourceLocation.from_str, "1:2-3:-1"
+ )
+ self.assertRaises(ValueError, parser_types.SourceLocation.from_str, "1:2-3:1%")
+
+ def test_location_attributes(self):
+ self.assertEqual(
+ parser_types.SourceLocation((1, 2), (3, 4)).start,
+ parser_types.SourcePosition(1, 2),
+ )
+ self.assertEqual(
+ parser_types.SourceLocation((1, 2), (3, 4)).end,
+ parser_types.SourcePosition(3, 4),
+ )
+ self.assertFalse(parser_types.SourceLocation((1, 2), (3, 4)).is_synthetic)
+ self.assertFalse(
+ parser_types.SourceLocation((1, 2), (3, 4)).is_disjoint_from_parent
+ )
+ self.assertTrue(
+ parser_types.SourceLocation((1, 2), (3, 4), is_synthetic=True).is_synthetic
+ )
+ self.assertTrue(
+ parser_types.SourceLocation(
+ (1, 2), (3, 4), is_disjoint_from_parent=True
+ ).is_disjoint_from_parent
)
@@ -107,7 +213,7 @@
"FOO 'bar' 1:2-3:4",
str(
parser_types.Token(
- "FOO", "bar", parser_types.make_location((1, 2), (3, 4))
+ "FOO", "bar", parser_types.SourceLocation((1, 2), (3, 4))
)
),
)
diff --git a/compiler/util/test_util.py b/compiler/util/test_util.py
index d54af37..f0a7a9c 100644
--- a/compiler/util/test_util.py
+++ b/compiler/util/test_util.py
@@ -51,6 +51,8 @@
name = spec.name
field_path = "{}{}".format(path, name)
value = getattr(proto, name)
+ if expected_values.HasField(name) and not proto.HasField(name):
+ return False, "{} missing".format(field_path)
if spec.is_dataclass:
if spec.is_sequence:
if len(expected_value) > len(value):
@@ -64,8 +66,6 @@
if not result[0]:
return result
else:
- if expected_values.HasField(name) and not proto.HasField(name):
- return False, "{} missing".format(field_path)
result = proto_is_superset(value, expected_value, field_path)
if not result[0]:
return result
diff --git a/compiler/util/test_util_test.py b/compiler/util/test_util_test.py
index 58e1ad6..11f4ef9 100644
--- a/compiler/util/test_util_test.py
+++ b/compiler/util/test_util_test.py
@@ -30,7 +30,7 @@
test_util.proto_is_superset(
ir_data.Structure(
field=[ir_data.Field()],
- source_location=parser_types.parse_location("1:2-3:4"),
+ source_location=parser_types.SourceLocation.from_str("1:2-3:4"),
),
ir_data.Structure(field=[ir_data.Field()]),
),
@@ -42,7 +42,7 @@
test_util.proto_is_superset(
ir_data.Structure(
field=[ir_data.Field(), ir_data.Field()],
- source_location=parser_types.parse_location("1:2-3:4"),
+ source_location=parser_types.SourceLocation.from_str("1:2-3:4"),
),
ir_data.Structure(field=[ir_data.Field()]),
),
@@ -53,7 +53,8 @@
(False, "field[0] missing"),
test_util.proto_is_superset(
ir_data.Structure(
- field=[], source_location=parser_types.parse_location("1:2-3:4")
+ field=[],
+ source_location=parser_types.SourceLocation.from_str("1:2-3:4"),
),
ir_data.Structure(field=[ir_data.Field(), ir_data.Field()]),
),
@@ -64,25 +65,32 @@
(False, "source_location missing"),
test_util.proto_is_superset(
ir_data.Structure(field=[]),
- ir_data.Structure(source_location=ir_data.Location()),
+ ir_data.Structure(source_location=parser_types.SourceLocation()),
),
)
def test_array_element_differs(self):
self.assertEqual(
- (False, "field[0].source_location.start.line differs: found 1, expected 2"),
+ (
+ False,
+ "field[0].source_location differs: found 1:2-3:4, expected 2:2-3:4",
+ ),
test_util.proto_is_superset(
ir_data.Structure(
field=[
ir_data.Field(
- source_location=parser_types.parse_location("1:2-3:4")
+ source_location=parser_types.SourceLocation.from_str(
+ "1:2-3:4"
+ )
)
]
),
ir_data.Structure(
field=[
ir_data.Field(
- source_location=parser_types.parse_location("2:2-3:4")
+ source_location=parser_types.SourceLocation.from_str(
+ "2:2-3:4"
+ )
)
]
),
@@ -93,8 +101,12 @@
self.assertEqual(
(True, ""),
test_util.proto_is_superset(
- parser_types.parse_location("1:2-3:4"),
- parser_types.parse_location("1:2-3:4"),
+ ir_data.Field(
+ source_location=parser_types.SourceLocation.from_str("1:2-3:4")
+ ),
+ ir_data.Field(
+ source_location=parser_types.SourceLocation.from_str("1:2-3:4")
+ ),
),
)
@@ -105,17 +117,21 @@
ir_data.Structure(field=[ir_data.Field()]),
ir_data.Structure(
field=[ir_data.Field()],
- source_location=parser_types.parse_location("1:2-3:4"),
+ source_location=parser_types.SourceLocation.from_str("1:2-3:4"),
),
),
)
def test_optional_field_differs(self):
self.assertEqual(
- (False, "end.line differs: found 4, expected 3"),
+ (False, "source_location differs: found 1:2-4:4, expected 1:2-3:4"),
test_util.proto_is_superset(
- parser_types.parse_location("1:2-4:4"),
- parser_types.parse_location("1:2-3:4"),
+ ir_data.Field(
+ source_location=parser_types.SourceLocation.from_str("1:2-4:4")
+ ),
+ ir_data.Field(
+ source_location=parser_types.SourceLocation.from_str("1:2-3:4")
+ ),
),
)
diff --git a/compiler/util/traverse_ir.py b/compiler/util/traverse_ir.py
index 5a5ac78..bf83392 100644
--- a/compiler/util/traverse_ir.py
+++ b/compiler/util/traverse_ir.py
@@ -19,6 +19,7 @@
from compiler.util import ir_data
from compiler.util import ir_data_fields
from compiler.util import ir_data_utils
+from compiler.util import parser_types
from compiler.util import simple_memoizer
@@ -219,7 +220,7 @@
# type_to_descendant_types is a map of all types that can be reached from a
# particular type. After the setup, type_to_descendant_types[ir_data.EmbossIr]
# == set(<all types>) and type_to_descendant_types[ir_data.Reference] ==
- # {ir_data.CanonicalName, ir_data.Word, ir_data.Location} and
+ # {ir_data.CanonicalName, ir_data.Word} and
# type_to_descendant_types[ir_data.Word] == set().
#
# The while loop basically ors in the known descendants of each known
diff --git a/testdata/golden/span_se_log_file_status.ir.txt b/testdata/golden/span_se_log_file_status.ir.txt
index b1bfef8..b880c1c 100644
--- a/testdata/golden/span_se_log_file_status.ir.txt
+++ b/testdata/golden/span_se_log_file_status.ir.txt
@@ -3,126 +3,36 @@
{
"name": {
"text": "byte_order",
- "source_location": {
- "start": {
- "line": 17,
- "column": 11
- },
- "end": {
- "line": 17,
- "column": 21
- },
- "is_synthetic": false
- }
+ "source_location": "17:11-17:21"
},
"value": {
"string_constant": {
"text": "LittleEndian",
- "source_location": {
- "start": {
- "line": 17,
- "column": 23
- },
- "end": {
- "line": 17,
- "column": 37
- },
- "is_synthetic": false
- }
+ "source_location": "17:23-17:37"
},
- "source_location": {
- "start": {
- "line": 17,
- "column": 23
- },
- "end": {
- "line": 17,
- "column": 37
- },
- "is_synthetic": false
- }
+ "source_location": "17:23-17:37"
},
"is_default": true,
- "source_location": {
- "start": {
- "line": 17,
- "column": 1
- },
- "end": {
- "line": 17,
- "column": 38
- },
- "is_synthetic": false
- }
+ "source_location": "17:1-17:38"
},
{
"name": {
"text": "namespace",
- "source_location": {
- "start": {
- "line": 18,
- "column": 8
- },
- "end": {
- "line": 18,
- "column": 17
- },
- "is_synthetic": false
- }
+ "source_location": "18:8-18:17"
},
"value": {
"string_constant": {
"text": "emboss::test",
- "source_location": {
- "start": {
- "line": 18,
- "column": 19
- },
- "end": {
- "line": 18,
- "column": 33
- },
- "is_synthetic": false
- }
+ "source_location": "18:19-18:33"
},
- "source_location": {
- "start": {
- "line": 18,
- "column": 19
- },
- "end": {
- "line": 18,
- "column": 33
- },
- "is_synthetic": false
- }
+ "source_location": "18:19-18:33"
},
- "is_default": false,
"back_end": {
"text": "cpp",
- "source_location": {
- "start": {
- "line": 18,
- "column": 2
- },
- "end": {
- "line": 18,
- "column": 7
- },
- "is_synthetic": false
- }
+ "source_location": "18:2-18:7"
},
- "source_location": {
- "start": {
- "line": 18,
- "column": 1
- },
- "end": {
- "line": 20,
- "column": 1
- },
- "is_synthetic": false
- }
+ "is_default": false,
+ "source_location": "18:1-20:1"
}
],
"type": [
@@ -134,68 +44,18 @@
"start": {
"constant": {
"value": "0",
- "source_location": {
- "start": {
- "line": 22,
- "column": 3
- },
- "end": {
- "line": 22,
- "column": 4
- },
- "is_synthetic": false
- }
+ "source_location": "22:3-22:4"
},
- "source_location": {
- "start": {
- "line": 22,
- "column": 3
- },
- "end": {
- "line": 22,
- "column": 4
- },
- "is_synthetic": false
- }
+ "source_location": "22:3-22:4"
},
"size": {
"constant": {
"value": "4",
- "source_location": {
- "start": {
- "line": 22,
- "column": 8
- },
- "end": {
- "line": 22,
- "column": 9
- },
- "is_synthetic": false
- }
+ "source_location": "22:8-22:9"
},
- "source_location": {
- "start": {
- "line": 22,
- "column": 8
- },
- "end": {
- "line": 22,
- "column": 9
- },
- "is_synthetic": false
- }
+ "source_location": "22:8-22:9"
},
- "source_location": {
- "start": {
- "line": 22,
- "column": 3
- },
- "end": {
- "line": 22,
- "column": 10
- },
- "is_synthetic": false
- }
+ "source_location": "22:3-22:10"
},
"type": {
"atomic_type": {
@@ -203,187 +63,48 @@
"source_name": [
{
"text": "UInt",
- "source_location": {
- "start": {
- "line": 22,
- "column": 13
- },
- "end": {
- "line": 22,
- "column": 17
- },
- "is_synthetic": false
- }
+ "source_location": "22:13-22:17"
}
],
- "source_location": {
- "start": {
- "line": 22,
- "column": 13
- },
- "end": {
- "line": 22,
- "column": 17
- },
- "is_synthetic": false
- }
+ "source_location": "22:13-22:17"
},
- "source_location": {
- "start": {
- "line": 22,
- "column": 13
- },
- "end": {
- "line": 22,
- "column": 17
- },
- "is_synthetic": false
- }
+ "source_location": "22:13-22:17"
},
- "source_location": {
- "start": {
- "line": 22,
- "column": 13
- },
- "end": {
- "line": 22,
- "column": 17
- },
- "is_synthetic": false
- }
+ "source_location": "22:13-22:17"
},
"name": {
"name": {
"text": "file_state",
- "source_location": {
- "start": {
- "line": 22,
- "column": 25
- },
- "end": {
- "line": 22,
- "column": 35
- },
- "is_synthetic": false
- }
+ "source_location": "22:25-22:35"
},
- "source_location": {
- "start": {
- "line": 22,
- "column": 25
- },
- "end": {
- "line": 22,
- "column": 35
- },
- "is_synthetic": false
- }
- },
- "source_location": {
- "start": {
- "line": 22,
- "column": 3
- },
- "end": {
- "line": 22,
- "column": 35
- }
+ "source_location": "22:25-22:35"
},
"existence_condition": {
- "source_location": {
- "start": {
- "line": 22,
- "column": 3
- },
- "end": {
- "line": 22,
- "column": 35
- },
- "is_synthetic": false
- },
"boolean_constant": {
- "source_location": {
- "start": {
- "line": 22,
- "column": 3
- },
- "end": {
- "line": 22,
- "column": 35
- },
- "is_synthetic": false
- },
- "value": true
- }
- }
+ "value": true,
+ "source_location": "22:3-22:35"
+ },
+ "source_location": "22:3-22:35"
+ },
+ "source_location": "22:3-22:35"
},
{
"location": {
"start": {
"constant": {
"value": "4",
- "source_location": {
- "start": {
- "line": 23,
- "column": 3
- },
- "end": {
- "line": 23,
- "column": 4
- },
- "is_synthetic": false
- }
+ "source_location": "23:3-23:4"
},
- "source_location": {
- "start": {
- "line": 23,
- "column": 3
- },
- "end": {
- "line": 23,
- "column": 4
- },
- "is_synthetic": false
- }
+ "source_location": "23:3-23:4"
},
"size": {
"constant": {
"value": "12",
- "source_location": {
- "start": {
- "line": 23,
- "column": 8
- },
- "end": {
- "line": 23,
- "column": 10
- },
- "is_synthetic": false
- }
+ "source_location": "23:8-23:10"
},
- "source_location": {
- "start": {
- "line": 23,
- "column": 8
- },
- "end": {
- "line": 23,
- "column": 10
- },
- "is_synthetic": false
- }
+ "source_location": "23:8-23:10"
},
- "source_location": {
- "start": {
- "line": 23,
- "column": 3
- },
- "end": {
- "line": 23,
- "column": 11
- },
- "is_synthetic": false
- }
+ "source_location": "23:3-23:11"
},
"type": {
"array_type": {
@@ -393,265 +114,66 @@
"source_name": [
{
"text": "UInt",
- "source_location": {
- "start": {
- "line": 23,
- "column": 13
- },
- "end": {
- "line": 23,
- "column": 17
- },
- "is_synthetic": false
- }
+ "source_location": "23:13-23:17"
}
],
- "source_location": {
- "start": {
- "line": 23,
- "column": 13
- },
- "end": {
- "line": 23,
- "column": 17
- },
- "is_synthetic": false
- }
+ "source_location": "23:13-23:17"
},
- "source_location": {
- "start": {
- "line": 23,
- "column": 13
- },
- "end": {
- "line": 23,
- "column": 17
- },
- "is_synthetic": false
- }
+ "source_location": "23:13-23:17"
},
"size_in_bits": {
"constant": {
"value": "8",
- "source_location": {
- "start": {
- "line": 23,
- "column": 18
- },
- "end": {
- "line": 23,
- "column": 19
- },
- "is_synthetic": false
- }
+ "source_location": "23:18-23:19"
},
- "source_location": {
- "start": {
- "line": 23,
- "column": 17
- },
- "end": {
- "line": 23,
- "column": 19
- },
- "is_synthetic": false
- }
+ "source_location": "23:17-23:19"
},
- "source_location": {
- "start": {
- "line": 23,
- "column": 13
- },
- "end": {
- "line": 23,
- "column": 19
- },
- "is_synthetic": false
- }
+ "source_location": "23:13-23:19"
},
"element_count": {
"constant": {
"value": "12",
- "source_location": {
- "start": {
- "line": 23,
- "column": 20
- },
- "end": {
- "line": 23,
- "column": 22
- },
- "is_synthetic": false
- }
+ "source_location": "23:20-23:22"
},
- "source_location": {
- "start": {
- "line": 23,
- "column": 19
- },
- "end": {
- "line": 23,
- "column": 23
- },
- "is_synthetic": false
- }
+ "source_location": "23:19-23:23"
},
- "source_location": {
- "start": {
- "line": 23,
- "column": 13
- },
- "end": {
- "line": 23,
- "column": 23
- },
- "is_synthetic": false
- }
+ "source_location": "23:13-23:23"
},
- "source_location": {
- "start": {
- "line": 23,
- "column": 13
- },
- "end": {
- "line": 23,
- "column": 23
- },
- "is_synthetic": false
- }
+ "source_location": "23:13-23:23"
},
"name": {
"name": {
"text": "file_name",
- "source_location": {
- "start": {
- "line": 23,
- "column": 25
- },
- "end": {
- "line": 23,
- "column": 34
- },
- "is_synthetic": false
- }
+ "source_location": "23:25-23:34"
},
- "source_location": {
- "start": {
- "line": 23,
- "column": 25
- },
- "end": {
- "line": 23,
- "column": 34
- },
- "is_synthetic": false
- }
- },
- "source_location": {
- "start": {
- "line": 23,
- "column": 3
- },
- "end": {
- "line": 23,
- "column": 34
- }
+ "source_location": "23:25-23:34"
},
"existence_condition": {
- "source_location": {
- "start": {
- "line": 23,
- "column": 3
- },
- "end": {
- "line": 23,
- "column": 34
- },
- "is_synthetic": false
- },
"boolean_constant": {
- "source_location": {
- "start": {
- "line": 23,
- "column": 3
- },
- "end": {
- "line": 23,
- "column": 34
- },
- "is_synthetic": false
- },
- "value": true
- }
- }
+ "value": true,
+ "source_location": "23:3-23:34"
+ },
+ "source_location": "23:3-23:34"
+ },
+ "source_location": "23:3-23:34"
},
{
"location": {
"start": {
"constant": {
"value": "16",
- "source_location": {
- "start": {
- "line": 24,
- "column": 3
- },
- "end": {
- "line": 24,
- "column": 5
- },
- "is_synthetic": false
- }
+ "source_location": "24:3-24:5"
},
- "source_location": {
- "start": {
- "line": 24,
- "column": 3
- },
- "end": {
- "line": 24,
- "column": 5
- },
- "is_synthetic": false
- }
+ "source_location": "24:3-24:5"
},
"size": {
"constant": {
"value": "4",
- "source_location": {
- "start": {
- "line": 24,
- "column": 8
- },
- "end": {
- "line": 24,
- "column": 9
- },
- "is_synthetic": false
- }
+ "source_location": "24:8-24:9"
},
- "source_location": {
- "start": {
- "line": 24,
- "column": 8
- },
- "end": {
- "line": 24,
- "column": 9
- },
- "is_synthetic": false
- }
+ "source_location": "24:8-24:9"
},
- "source_location": {
- "start": {
- "line": 24,
- "column": 3
- },
- "end": {
- "line": 24,
- "column": 10
- },
- "is_synthetic": false
- }
+ "source_location": "24:3-24:10"
},
"type": {
"atomic_type": {
@@ -659,187 +181,48 @@
"source_name": [
{
"text": "UInt",
- "source_location": {
- "start": {
- "line": 24,
- "column": 13
- },
- "end": {
- "line": 24,
- "column": 17
- },
- "is_synthetic": false
- }
+ "source_location": "24:13-24:17"
}
],
- "source_location": {
- "start": {
- "line": 24,
- "column": 13
- },
- "end": {
- "line": 24,
- "column": 17
- },
- "is_synthetic": false
- }
+ "source_location": "24:13-24:17"
},
- "source_location": {
- "start": {
- "line": 24,
- "column": 13
- },
- "end": {
- "line": 24,
- "column": 17
- },
- "is_synthetic": false
- }
+ "source_location": "24:13-24:17"
},
- "source_location": {
- "start": {
- "line": 24,
- "column": 13
- },
- "end": {
- "line": 24,
- "column": 17
- },
- "is_synthetic": false
- }
+ "source_location": "24:13-24:17"
},
"name": {
"name": {
"text": "file_size_kb",
- "source_location": {
- "start": {
- "line": 24,
- "column": 25
- },
- "end": {
- "line": 24,
- "column": 37
- },
- "is_synthetic": false
- }
+ "source_location": "24:25-24:37"
},
- "source_location": {
- "start": {
- "line": 24,
- "column": 25
- },
- "end": {
- "line": 24,
- "column": 37
- },
- "is_synthetic": false
- }
- },
- "source_location": {
- "start": {
- "line": 24,
- "column": 3
- },
- "end": {
- "line": 24,
- "column": 37
- }
+ "source_location": "24:25-24:37"
},
"existence_condition": {
- "source_location": {
- "start": {
- "line": 24,
- "column": 3
- },
- "end": {
- "line": 24,
- "column": 37
- },
- "is_synthetic": false
- },
"boolean_constant": {
- "source_location": {
- "start": {
- "line": 24,
- "column": 3
- },
- "end": {
- "line": 24,
- "column": 37
- },
- "is_synthetic": false
- },
- "value": true
- }
- }
+ "value": true,
+ "source_location": "24:3-24:37"
+ },
+ "source_location": "24:3-24:37"
+ },
+ "source_location": "24:3-24:37"
},
{
"location": {
"start": {
"constant": {
"value": "20",
- "source_location": {
- "start": {
- "line": 25,
- "column": 3
- },
- "end": {
- "line": 25,
- "column": 5
- },
- "is_synthetic": false
- }
+ "source_location": "25:3-25:5"
},
- "source_location": {
- "start": {
- "line": 25,
- "column": 3
- },
- "end": {
- "line": 25,
- "column": 5
- },
- "is_synthetic": false
- }
+ "source_location": "25:3-25:5"
},
"size": {
"constant": {
"value": "4",
- "source_location": {
- "start": {
- "line": 25,
- "column": 8
- },
- "end": {
- "line": 25,
- "column": 9
- },
- "is_synthetic": false
- }
+ "source_location": "25:8-25:9"
},
- "source_location": {
- "start": {
- "line": 25,
- "column": 8
- },
- "end": {
- "line": 25,
- "column": 9
- },
- "is_synthetic": false
- }
+ "source_location": "25:8-25:9"
},
- "source_location": {
- "start": {
- "line": 25,
- "column": 3
- },
- "end": {
- "line": 25,
- "column": 10
- },
- "is_synthetic": false
- }
+ "source_location": "25:3-25:10"
},
"type": {
"atomic_type": {
@@ -847,242 +230,64 @@
"source_name": [
{
"text": "UInt",
- "source_location": {
- "start": {
- "line": 25,
- "column": 13
- },
- "end": {
- "line": 25,
- "column": 17
- },
- "is_synthetic": false
- }
+ "source_location": "25:13-25:17"
}
],
- "source_location": {
- "start": {
- "line": 25,
- "column": 13
- },
- "end": {
- "line": 25,
- "column": 17
- },
- "is_synthetic": false
- }
+ "source_location": "25:13-25:17"
},
- "source_location": {
- "start": {
- "line": 25,
- "column": 13
- },
- "end": {
- "line": 25,
- "column": 17
- },
- "is_synthetic": false
- }
+ "source_location": "25:13-25:17"
},
- "source_location": {
- "start": {
- "line": 25,
- "column": 13
- },
- "end": {
- "line": 25,
- "column": 17
- },
- "is_synthetic": false
- }
+ "source_location": "25:13-25:17"
},
"name": {
"name": {
"text": "media",
- "source_location": {
- "start": {
- "line": 25,
- "column": 25
- },
- "end": {
- "line": 25,
- "column": 30
- },
- "is_synthetic": false
- }
+ "source_location": "25:25-25:30"
},
- "source_location": {
- "start": {
- "line": 25,
- "column": 25
- },
- "end": {
- "line": 25,
- "column": 30
- },
- "is_synthetic": false
- }
- },
- "source_location": {
- "start": {
- "line": 25,
- "column": 3
- },
- "end": {
- "line": 25,
- "column": 30
- }
+ "source_location": "25:25-25:30"
},
"existence_condition": {
- "source_location": {
- "start": {
- "line": 25,
- "column": 3
- },
- "end": {
- "line": 25,
- "column": 30
- },
- "is_synthetic": false
- },
"boolean_constant": {
- "source_location": {
- "start": {
- "line": 25,
- "column": 3
- },
- "end": {
- "line": 25,
- "column": 30
- },
- "is_synthetic": false
- },
- "value": true
- }
- }
+ "value": true,
+ "source_location": "25:3-25:30"
+ },
+ "source_location": "25:3-25:30"
+ },
+ "source_location": "25:3-25:30"
}
],
- "source_location": {
- "start": {
- "line": 21,
- "column": 1
- },
- "end": {
- "line": 26,
- "column": 1
- }
- }
- },
- "addressable_unit": 8,
- "source_location": {
- "start": {
- "line": 21,
- "column": 1
- },
- "end": {
- "line": 26,
- "column": 1
- },
- "is_synthetic": false
+ "source_location": "21:1-26:1"
},
"name": {
"name": {
"text": "LogFileStatus",
- "source_location": {
- "start": {
- "line": 21,
- "column": 8
- },
- "end": {
- "line": 21,
- "column": 21
- },
- "is_synthetic": false
- }
+ "source_location": "21:8-21:21"
},
- "source_location": {
- "start": {
- "line": 21,
- "column": 8
- },
- "end": {
- "line": 21,
- "column": 21
- },
- "is_synthetic": false
- }
- }
+ "source_location": "21:8-21:21"
+ },
+ "addressable_unit": 8,
+ "source_location": "21:1-26:1"
}
],
"documentation": [
{
"text": "This is a simple, real-world example structure.",
- "source_location": {
- "start": {
- "line": 15,
- "column": 1
- },
- "end": {
- "line": 16,
- "column": 1
- },
- "is_synthetic": false
- }
+ "source_location": "15:1-16:1"
}
],
"foreign_import": [
{
"file_name": {
"text": "",
- "source_location": {
- "start": {
- "line": 16,
- "column": 1
- },
- "end": {
- "line": 16,
- "column": 1
- },
- "is_synthetic": false
- }
+ "source_location": "16:1-16:1"
},
"local_name": {
"text": "",
- "source_location": {
- "start": {
- "line": 16,
- "column": 1
- },
- "end": {
- "line": 16,
- "column": 1
- },
- "is_synthetic": false
- }
+ "source_location": "16:1-16:1"
},
- "source_location": {
- "start": {
- "line": 16,
- "column": 1
- },
- "end": {
- "line": 16,
- "column": 1
- },
- "is_synthetic": false
- }
+ "source_location": "16:1-16:1"
}
],
- "source_location": {
- "start": {
- "line": 1,
- "column": 1
- },
- "end": {
- "line": 26,
- "column": 1
- },
- "is_synthetic": false
- },
- "source_text": "# Copyright 2019 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n-- This is a simple, real-world example structure.\n\n[$default byte_order: \"LittleEndian\"]\n[(cpp) namespace: \"emboss::test\"]\n\n\nstruct LogFileStatus:\n 0 [+4] UInt file_state\n 4 [+12] UInt:8[12] file_name\n 16 [+4] UInt file_size_kb\n 20 [+4] UInt media\n"
+ "source_text": "# Copyright 2019 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n-- This is a simple, real-world example structure.\n\n[$default byte_order: \"LittleEndian\"]\n[(cpp) namespace: \"emboss::test\"]\n\n\nstruct LogFileStatus:\n 0 [+4] UInt file_state\n 4 [+12] UInt:8[12] file_name\n 16 [+4] UInt file_size_kb\n 20 [+4] UInt media\n",
+ "source_location": "1:1-26:1"
}
diff --git a/testdata/golden/span_se_log_file_status.parse_tree.txt b/testdata/golden/span_se_log_file_status.parse_tree.txt
index bd15bdd..e415367 100644
--- a/testdata/golden/span_se_log_file_status.parse_tree.txt
+++ b/testdata/golden/span_se_log_file_status.parse_tree.txt
@@ -3,67 +3,67 @@
comment-line:
Comment?:
Comment '# Copyright 2019 Google LLC' 1:1-1:28
- "\n" '\n' 1:1-1:28
+ "\n" '\n' 1:28-1:28
comment-line*:
comment-line:
Comment?:
Comment '#' 2:1-2:2
- "\n" '\n' 2:1-2:2
+ "\n" '\n' 2:2-2:2
comment-line*:
comment-line:
Comment?:
Comment '# Licensed under the Apache License, Version 2.0 (the "License");' 3:1-3:66
- "\n" '\n' 3:1-3:66
+ "\n" '\n' 3:66-3:66
comment-line*:
comment-line:
Comment?:
Comment '# you may not use this file except in compliance with the License.' 4:1-4:67
- "\n" '\n' 4:1-4:67
+ "\n" '\n' 4:67-4:67
comment-line*:
comment-line:
Comment?:
Comment '# You may obtain a copy of the License at' 5:1-5:42
- "\n" '\n' 5:1-5:42
+ "\n" '\n' 5:42-5:42
comment-line*:
comment-line:
Comment?:
Comment '#' 6:1-6:2
- "\n" '\n' 6:1-6:2
+ "\n" '\n' 6:2-6:2
comment-line*:
comment-line:
Comment?:
Comment '# https://www.apache.org/licenses/LICENSE-2.0' 7:1-7:50
- "\n" '\n' 7:1-7:50
+ "\n" '\n' 7:50-7:50
comment-line*:
comment-line:
Comment?:
Comment '#' 8:1-8:2
- "\n" '\n' 8:1-8:2
+ "\n" '\n' 8:2-8:2
comment-line*:
comment-line:
Comment?:
Comment '# Unless required by applicable law or agreed to in writing, software' 9:1-9:70
- "\n" '\n' 9:1-9:70
+ "\n" '\n' 9:70-9:70
comment-line*:
comment-line:
Comment?:
Comment '# distributed under the License is distributed on an "AS IS" BASIS,' 10:1-10:68
- "\n" '\n' 10:1-10:68
+ "\n" '\n' 10:68-10:68
comment-line*:
comment-line:
Comment?:
Comment '# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.' 11:1-11:75
- "\n" '\n' 11:1-11:75
+ "\n" '\n' 11:75-11:75
comment-line*:
comment-line:
Comment?:
Comment '# See the License for the specific language governing permissions and' 12:1-12:70
- "\n" '\n' 12:1-12:70
+ "\n" '\n' 12:70-12:70
comment-line*:
comment-line:
Comment?:
Comment '# limitations under the License.' 13:1-13:33
- "\n" '\n' 13:1-13:33
+ "\n" '\n' 13:33-13:33
comment-line*:
comment-line:
Comment?
@@ -75,7 +75,7 @@
Documentation '-- This is a simple, real-world example structure.' 15:1-15:51
Comment?
eol:
- "\n" '\n' 15:51-16:1
+ "\n" '\n' 15:51-15:51
comment-line*:
comment-line:
Comment?
@@ -121,7 +121,7 @@
"]" ']' 18:33-18:34
Comment?
eol:
- "\n" '\n' 18:34-20:1
+ "\n" '\n' 18:34-18:34
comment-line*:
comment-line:
Comment?
diff --git a/testdata/golden/span_se_log_file_status.tokens.txt b/testdata/golden/span_se_log_file_status.tokens.txt
index 5474c99..91937f0 100644
--- a/testdata/golden/span_se_log_file_status.tokens.txt
+++ b/testdata/golden/span_se_log_file_status.tokens.txt
@@ -1,32 +1,32 @@
Comment '# Copyright 2019 Google LLC' 1:1-1:28
-"\n" '\n' 1:1-1:28
+"\n" '\n' 1:28-1:28
Comment '#' 2:1-2:2
-"\n" '\n' 2:1-2:2
+"\n" '\n' 2:2-2:2
Comment '# Licensed under the Apache License, Version 2.0 (the "License");' 3:1-3:66
-"\n" '\n' 3:1-3:66
+"\n" '\n' 3:66-3:66
Comment '# you may not use this file except in compliance with the License.' 4:1-4:67
-"\n" '\n' 4:1-4:67
+"\n" '\n' 4:67-4:67
Comment '# You may obtain a copy of the License at' 5:1-5:42
-"\n" '\n' 5:1-5:42
+"\n" '\n' 5:42-5:42
Comment '#' 6:1-6:2
-"\n" '\n' 6:1-6:2
+"\n" '\n' 6:2-6:2
Comment '# https://www.apache.org/licenses/LICENSE-2.0' 7:1-7:50
-"\n" '\n' 7:1-7:50
+"\n" '\n' 7:50-7:50
Comment '#' 8:1-8:2
-"\n" '\n' 8:1-8:2
+"\n" '\n' 8:2-8:2
Comment '# Unless required by applicable law or agreed to in writing, software' 9:1-9:70
-"\n" '\n' 9:1-9:70
+"\n" '\n' 9:70-9:70
Comment '# distributed under the License is distributed on an "AS IS" BASIS,' 10:1-10:68
-"\n" '\n' 10:1-10:68
+"\n" '\n' 10:68-10:68
Comment '# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.' 11:1-11:75
-"\n" '\n' 11:1-11:75
+"\n" '\n' 11:75-11:75
Comment '# See the License for the specific language governing permissions and' 12:1-12:70
-"\n" '\n' 12:1-12:70
+"\n" '\n' 12:70-12:70
Comment '# limitations under the License.' 13:1-13:33
-"\n" '\n' 13:1-13:33
+"\n" '\n' 13:33-13:33
"\n" '\n' 14:1-14:1
Documentation '-- This is a simple, real-world example structure.' 15:1-15:51
-"\n" '\n' 15:51-16:1
+"\n" '\n' 15:51-15:51
"\n" '\n' 16:1-16:1
"[" '[' 17:1-17:2
"$default" '$default' 17:2-17:10
@@ -43,7 +43,7 @@
":" ':' 18:17-18:18
String '"emboss::test"' 18:19-18:33
"]" ']' 18:33-18:34
-"\n" '\n' 18:34-20:1
+"\n" '\n' 18:34-18:34
"\n" '\n' 19:1-19:1
"\n" '\n' 20:1-20:1
"struct" 'struct' 21:1-21:7