| # 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. | |
| """Routines to generate a shift-reduce parser from the module_ir module.""" | |
| import pkgutil | |
| from compiler.front_end import lr1 | |
| from compiler.front_end import module_ir | |
| from compiler.front_end import tokenizer | |
| from compiler.util import simple_memoizer | |
| class ParserGenerationError(Exception): | |
| """An error occurred during parser generation.""" | |
| pass | |
| def parse_error_examples(error_example_text): | |
| """Parses error examples from error_example_text. | |
| Arguments: | |
| error_example_text: The text of an error example file. | |
| Returns: | |
| A list of tuples, suitable for passing into generate_parser. | |
| Raises: | |
| ParserGenerationError: There is a problem parsing the error examples. | |
| """ | |
| error_examples = error_example_text.split("\n" + "=" * 80 + "\n") | |
| result = [] | |
| # Everything before the first "======" line is explanatory text: ignore it. | |
| for error_example in error_examples[1:]: | |
| message_and_examples = error_example.split("\n" + "-" * 80 + "\n") | |
| if len(message_and_examples) != 2: | |
| raise ParserGenerationError( | |
| "Expected one error message and one example section in:\n" + | |
| error_example) | |
| message, example_text = message_and_examples | |
| examples = example_text.split("\n---\n") | |
| for example in examples: | |
| # TODO(bolms): feed a line number into tokenize, so that tokenization | |
| # failures refer to the correct line within error_example_text. | |
| tokens, errors = tokenizer.tokenize(example, "") | |
| if errors: | |
| raise ParserGenerationError(str(errors)) | |
| for i in range(len(tokens)): | |
| if tokens[i].symbol == "BadWord" and tokens[i].text == "$ANY": | |
| tokens[i] = lr1.ANY_TOKEN | |
| error_token = None | |
| for i in range(len(tokens)): | |
| if tokens[i].symbol == "BadWord" and tokens[i].text == "$ERR": | |
| error_token = tokens[i + 1] | |
| del tokens[i] | |
| break | |
| else: | |
| raise ParserGenerationError( | |
| "No error token marker '$ERR' in:\n" + error_example) | |
| result.append((tokens, error_token, message.strip(), example)) | |
| return result | |
| def generate_parser(start_symbol, productions, error_examples): | |
| """Generates a parser from grammar, and applies error_examples. | |
| Arguments: | |
| start_symbol: the start symbol of the grammar (a string) | |
| productions: a list of parser_types.Production in the grammar | |
| error_examples: A list of (source tokens, error message, source text) | |
| tuples. | |
| Returns: | |
| A parser. | |
| Raises: | |
| ParserGenerationError: There is a problem generating the parser. | |
| """ | |
| parser = lr1.Grammar(start_symbol, productions).parser() | |
| if parser.conflicts: | |
| raise ParserGenerationError("\n".join([str(c) for c in parser.conflicts])) | |
| for example in error_examples: | |
| mark_result = parser.mark_error(example[0], example[1], example[2]) | |
| if mark_result: | |
| raise ParserGenerationError( | |
| "error marking example: {}\nExample:\n{}".format( | |
| mark_result, example[3])) | |
| return parser | |
| @simple_memoizer.memoize | |
| def _load_module_parser(): | |
| path = "compiler.front_end" | |
| error_examples = parse_error_examples( | |
| pkgutil.get_data(path, "error_examples").decode("utf-8")) | |
| return generate_parser(module_ir.START_SYMBOL, module_ir.PRODUCTIONS, | |
| error_examples) | |
| @simple_memoizer.memoize | |
| def _load_expression_parser(): | |
| return generate_parser(module_ir.EXPRESSION_START_SYMBOL, | |
| module_ir.PRODUCTIONS, []) | |
| def parse_module(tokens): | |
| """Parses the provided Emboss token list into an Emboss module parse tree.""" | |
| return _load_module_parser().parse(tokens) | |
| def parse_expression(tokens): | |
| """Parses the provided Emboss token list into an expression parse tree.""" | |
| return _load_expression_parser().parse(tokens) |