| # Copyright 2019 Google LLC |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Tests for front_end.synthetics.""" |
| |
| import unittest |
| from compiler.front_end import glue |
| from compiler.front_end import synthetics |
| from compiler.util import error |
| from compiler.util import ir_data |
| from compiler.util import test_util |
| |
| |
| class SyntheticsTest(unittest.TestCase): |
| |
| def _find_attribute(self, field, name): |
| result = None |
| for attribute in field.attribute: |
| if attribute.name.text == name: |
| self.assertIsNone(result) |
| result = attribute |
| self.assertIsNotNone(result) |
| return result |
| |
| def _make_ir(self, emb_text): |
| ir, unused_debug_info, errors = glue.parse_emboss_file( |
| "m.emb", |
| test_util.dict_file_reader({"m.emb": emb_text}), |
| stop_before_step="desugar") |
| assert not errors, errors |
| return ir |
| |
| def test_nothing_to_do(self): |
| ir = self._make_ir("struct Foo:\n" |
| " 0 [+1] UInt x\n" |
| " 1 [+1] UInt:8[] y\n") |
| self.assertEqual([], synthetics.desugar(ir)) |
| |
| def test_adds_anonymous_bits_fields(self): |
| ir = self._make_ir("struct Foo:\n" |
| " 0 [+1] bits:\n" |
| " 0 [+4] Bar bar\n" |
| " 4 [+4] UInt uint\n" |
| " 1 [+1] bits:\n" |
| " 0 [+4] Bits nested_bits\n" |
| "enum Bar:\n" |
| " BAR = 0\n" |
| "bits Bits:\n" |
| " 0 [+4] UInt uint\n") |
| self.assertEqual([], synthetics.desugar(ir)) |
| structure = ir.module[0].type[0].structure |
| # The first field should be the anonymous bits structure. |
| self.assertTrue(structure.field[0].HasField("location")) |
| # Then the aliases generated for those structures. |
| self.assertEqual("bar", structure.field[1].name.name.text) |
| self.assertEqual("uint", structure.field[2].name.name.text) |
| # Then the second anonymous bits. |
| self.assertTrue(structure.field[3].HasField("location")) |
| # Then the alias from the second anonymous bits. |
| self.assertEqual("nested_bits", structure.field[4].name.name.text) |
| |
| def test_adds_correct_existence_condition(self): |
| ir = self._make_ir("struct Foo:\n" |
| " 0 [+1] bits:\n" |
| " 0 [+4] UInt bar\n") |
| self.assertEqual([], synthetics.desugar(ir)) |
| bits_field = ir.module[0].type[0].structure.field[0] |
| alias_field = ir.module[0].type[0].structure.field[1] |
| self.assertEqual("bar", alias_field.name.name.text) |
| self.assertEqual(bits_field.name.name.text, |
| alias_field.existence_condition.function.args[0].function. |
| args[0].field_reference.path[0].source_name[-1].text) |
| self.assertEqual(bits_field.name.name.text, |
| alias_field.existence_condition.function.args[1].function. |
| args[0].field_reference.path[0].source_name[-1].text) |
| self.assertEqual("bar", |
| alias_field.existence_condition.function.args[1].function. |
| args[0].field_reference.path[1].source_name[-1].text) |
| self.assertEqual( |
| ir_data.FunctionMapping.PRESENCE, |
| alias_field.existence_condition.function.args[0].function.function) |
| self.assertEqual( |
| ir_data.FunctionMapping.PRESENCE, |
| alias_field.existence_condition.function.args[1].function.function) |
| self.assertEqual(ir_data.FunctionMapping.AND, |
| alias_field.existence_condition.function.function) |
| |
| def test_adds_correct_read_transform(self): |
| ir = self._make_ir("struct Foo:\n" |
| " 0 [+1] bits:\n" |
| " 0 [+4] UInt bar\n") |
| self.assertEqual([], synthetics.desugar(ir)) |
| bits_field = ir.module[0].type[0].structure.field[0] |
| alias_field = ir.module[0].type[0].structure.field[1] |
| self.assertEqual("bar", alias_field.name.name.text) |
| self.assertEqual( |
| bits_field.name.name.text, |
| alias_field.read_transform.field_reference.path[0].source_name[-1].text) |
| self.assertEqual( |
| "bar", |
| alias_field.read_transform.field_reference.path[1].source_name[-1].text) |
| |
| def test_adds_correct_abbreviation(self): |
| ir = self._make_ir("struct Foo:\n" |
| " 0 [+1] bits:\n" |
| " 0 [+4] UInt bar\n" |
| " 4 [+4] UInt baz (qux)\n") |
| self.assertEqual([], synthetics.desugar(ir)) |
| bar_alias = ir.module[0].type[0].structure.field[1] |
| baz_alias = ir.module[0].type[0].structure.field[2] |
| self.assertFalse(bar_alias.HasField("abbreviation")) |
| self.assertEqual("qux", baz_alias.abbreviation.text) |
| |
| def test_anonymous_bits_sets_correct_is_synthetic(self): |
| ir = self._make_ir("struct Foo:\n" |
| " 0 [+1] bits:\n" |
| " 0 [+4] UInt bar (b)\n") |
| self.assertEqual([], synthetics.desugar(ir)) |
| bits_field = ir.module[0].type[0].subtype[0].structure.field[0] |
| alias_field = ir.module[0].type[0].structure.field[1] |
| self.assertFalse(alias_field.name.source_location.is_synthetic) |
| self.assertTrue(alias_field.HasField("abbreviation")) |
| self.assertFalse(alias_field.abbreviation.source_location.is_synthetic) |
| self.assertTrue(alias_field.HasField("read_transform")) |
| read_alias = alias_field.read_transform |
| self.assertTrue(read_alias.source_location.is_synthetic) |
| self.assertTrue( |
| read_alias.field_reference.path[0].source_location.is_synthetic) |
| alias_condition = alias_field.existence_condition |
| self.assertTrue(alias_condition.source_location.is_synthetic) |
| self.assertTrue( |
| alias_condition.function.args[0].source_location.is_synthetic) |
| self.assertTrue(bits_field.name.source_location.is_synthetic) |
| self.assertTrue(bits_field.name.name.source_location.is_synthetic) |
| self.assertTrue(bits_field.abbreviation.source_location.is_synthetic) |
| |
| def test_adds_text_output_skip_attribute_to_anonymous_bits(self): |
| ir = self._make_ir("struct Foo:\n" |
| " 0 [+1] bits:\n" |
| " 0 [+4] UInt bar (b)\n") |
| self.assertEqual([], synthetics.desugar(ir)) |
| bits_field = ir.module[0].type[0].structure.field[0] |
| text_output_attribute = self._find_attribute(bits_field, "text_output") |
| self.assertEqual("Skip", text_output_attribute.value.string_constant.text) |
| |
| def test_skip_attribute_is_marked_as_synthetic(self): |
| ir = self._make_ir("struct Foo:\n" |
| " 0 [+1] bits:\n" |
| " 0 [+4] UInt bar\n") |
| self.assertEqual([], synthetics.desugar(ir)) |
| bits_field = ir.module[0].type[0].structure.field[0] |
| attribute = self._find_attribute(bits_field, "text_output") |
| self.assertTrue(attribute.source_location.is_synthetic) |
| self.assertTrue(attribute.name.source_location.is_synthetic) |
| self.assertTrue(attribute.value.source_location.is_synthetic) |
| self.assertTrue( |
| attribute.value.string_constant.source_location.is_synthetic) |
| |
| def test_adds_size_in_bytes(self): |
| ir = self._make_ir("struct Foo:\n" |
| " 1 [+l] UInt:8[] bytes\n" |
| " 0 [+1] UInt length (l)\n") |
| self.assertEqual([], synthetics.desugar(ir)) |
| structure = ir.module[0].type[0].structure |
| size_in_bytes_field = structure.field[2] |
| max_size_in_bytes_field = structure.field[3] |
| min_size_in_bytes_field = structure.field[4] |
| self.assertEqual("$size_in_bytes", size_in_bytes_field.name.name.text) |
| self.assertEqual(ir_data.FunctionMapping.MAXIMUM, |
| size_in_bytes_field.read_transform.function.function) |
| self.assertEqual("$max_size_in_bytes", |
| max_size_in_bytes_field.name.name.text) |
| self.assertEqual(ir_data.FunctionMapping.UPPER_BOUND, |
| max_size_in_bytes_field.read_transform.function.function) |
| self.assertEqual("$min_size_in_bytes", |
| min_size_in_bytes_field.name.name.text) |
| self.assertEqual(ir_data.FunctionMapping.LOWER_BOUND, |
| min_size_in_bytes_field.read_transform.function.function) |
| # The correctness of $size_in_bytes et al are tested much further down |
| # stream, in tests of the generated C++ code. |
| |
| def test_adds_size_in_bits(self): |
| ir = self._make_ir("bits Foo:\n" |
| " 1 [+9] UInt hi\n" |
| " 0 [+1] Flag lo\n") |
| self.assertEqual([], synthetics.desugar(ir)) |
| structure = ir.module[0].type[0].structure |
| size_in_bits_field = structure.field[2] |
| max_size_in_bits_field = structure.field[3] |
| min_size_in_bits_field = structure.field[4] |
| self.assertEqual("$size_in_bits", size_in_bits_field.name.name.text) |
| self.assertEqual(ir_data.FunctionMapping.MAXIMUM, |
| size_in_bits_field.read_transform.function.function) |
| self.assertEqual("$max_size_in_bits", |
| max_size_in_bits_field.name.name.text) |
| self.assertEqual(ir_data.FunctionMapping.UPPER_BOUND, |
| max_size_in_bits_field.read_transform.function.function) |
| self.assertEqual("$min_size_in_bits", |
| min_size_in_bits_field.name.name.text) |
| self.assertEqual(ir_data.FunctionMapping.LOWER_BOUND, |
| min_size_in_bits_field.read_transform.function.function) |
| # The correctness of $size_in_bits et al are tested much further down |
| # stream, in tests of the generated C++ code. |
| |
| def test_adds_text_output_skip_attribute_to_size_in_bytes(self): |
| ir = self._make_ir("struct Foo:\n" |
| " 1 [+l] UInt:8[] bytes\n" |
| " 0 [+1] UInt length (l)\n") |
| self.assertEqual([], synthetics.desugar(ir)) |
| size_in_bytes_field = ir.module[0].type[0].structure.field[2] |
| self.assertEqual("$size_in_bytes", size_in_bytes_field.name.name.text) |
| text_output_attribute = self._find_attribute(size_in_bytes_field, |
| "text_output") |
| self.assertEqual("Skip", text_output_attribute.value.string_constant.text) |
| |
| def test_replaces_next(self): |
| ir = self._make_ir("struct Foo:\n" |
| " 1 [+2] UInt:8[] a\n" |
| " $next [+4] UInt b\n" |
| " $next [+1] UInt c\n") |
| self.assertEqual([], synthetics.desugar(ir)) |
| offset_of_b = ir.module[0].type[0].structure.field[1].location.start |
| self.assertTrue(offset_of_b.HasField("function")) |
| self.assertEqual(offset_of_b.function.function, ir_data.FunctionMapping.ADDITION) |
| self.assertEqual(offset_of_b.function.args[0].constant.value, "1") |
| self.assertEqual(offset_of_b.function.args[1].constant.value, "2") |
| offset_of_c = ir.module[0].type[0].structure.field[2].location.start |
| self.assertEqual( |
| offset_of_c.function.args[0].function.args[0].constant.value, "1") |
| self.assertEqual( |
| offset_of_c.function.args[0].function.args[1].constant.value, "2") |
| self.assertEqual(offset_of_c.function.args[1].constant.value, "4") |
| |
| def test_next_in_first_field(self): |
| ir = self._make_ir("struct Foo:\n" |
| " $next [+2] UInt:8[] a\n" |
| " $next [+4] UInt b\n") |
| struct = ir.module[0].type[0].structure |
| self.assertEqual([[ |
| error.error("m.emb", struct.field[0].location.start.source_location, |
| "`$next` may not be used in the first physical field of " + |
| "a structure; perhaps you meant `0`?"), |
| ]], synthetics.desugar(ir)) |
| |
| def test_next_in_size(self): |
| ir = self._make_ir("struct Foo:\n" |
| " 0 [+2] UInt:8[] a\n" |
| " 1 [+$next] UInt b\n") |
| struct = ir.module[0].type[0].structure |
| self.assertEqual([[ |
| error.error("m.emb", struct.field[1].location.size.source_location, |
| "`$next` may only be used in the start expression of a " + |
| "physical field."), |
| ]], synthetics.desugar(ir)) |
| |
| |
| if __name__ == "__main__": |
| unittest.main() |