| # 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. |
| |
| """Various types shared through multiple passes of parsing. |
| |
| 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. |
| """ |
| |
| import collections |
| from public import ir_pb2 |
| |
| |
| def _make_position(line, column): |
| """Makes an ir_pb2.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_pb2.Position(line=line, column=column) |
| |
| |
| def _parse_position(text): |
| """Parses an ir_pb2.Position from "line:column" (e.g., "1:2").""" |
| line, column = text.split(":") |
| return _make_position(int(line), int(column)) |
| |
| |
| def format_position(position): |
| """formats an ir_pb2.Position to "line:column" form.""" |
| return "{}:{}".format(position.line, position.column) |
| |
| |
| def make_location(start, end, is_synthetic=False): |
| """Makes an ir_pb2.Location from (line, column) tuples or ir_pb2.Positions.""" |
| if isinstance(start, tuple): |
| start = _make_position(*start) |
| if isinstance(end, tuple): |
| end = _make_position(*end) |
| if not isinstance(start, ir_pb2.Position): |
| raise ValueError("Bad start {!r}".format(start)) |
| elif not isinstance(end, ir_pb2.Position): |
| raise ValueError("Bad end {!r}".format(end)) |
| elif start.line > end.line or ( |
| start.line == end.line and start.column > end.column): |
| raise ValueError("Start {} is after end {}".format(format_position(start), |
| format_position(end))) |
| return ir_pb2.Location(start=start, end=end, is_synthetic=is_synthetic) |
| |
| |
| def format_location(location): |
| """Formats an ir_pb2.Location in format "1:2-3:4" ("start-end").""" |
| return "{}-{}".format(format_position(location.start), |
| format_position(location.end)) |
| |
| |
| def parse_location(text): |
| """Parses an ir_pb2.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. |
| |
| Attributes: |
| symbol: The name of this token ("Indent", "SnakeWord", etc.) |
| text: The original text ("1234", "some_name", etc.) |
| source_location: Where this token came from in the original source file. |
| """ |
| |
| def __str__(self): |
| return "{} {} {}".format(self.symbol, repr(self.text), |
| format_location(self.source_location)) |
| |
| |
| class Production(collections.namedtuple("Production", ["lhs", "rhs"])): |
| """A Production is a simple production from a context-free grammar. |
| |
| A Production takes the form: |
| |
| nonterminal -> symbol* |
| |
| where "nonterminal" is an implicitly non-terminal symbol in the language, |
| and "symbol*" is zero or more terminal or non-terminal symbols which form the |
| non-terminal on the left. |
| |
| Attributes: |
| lhs: The non-terminal symbol on the left-hand-side of the production. |
| rhs: The sequence of symbols on the right-hand-side of the production. |
| """ |
| |
| def __str__(self): |
| return str(self.lhs) + " -> " + " ".join([str(r) for r in self.rhs]) |
| |
| @staticmethod |
| def parse(production_text): |
| """Parses a Production from a "symbol -> symbol symbol symbol" string.""" |
| words = production_text.split() |
| if words[1] != "->": |
| raise SyntaxError |
| return Production(words[0], tuple(words[2:])) |