"""Data structure extracted from parsing the EDSL which are added within the Rust code.""" from __future__ import annotations # mypy: disallow-untyped-defs, disallow-incomplete-defs, disallow-untyped-calls import typing import os from dataclasses import dataclass from .utils import keep_until from .grammar import Element, Grammar, LenientNt, NtDef, Production @dataclass(frozen=True) class ImplFor: __slots__ = ['param', 'trait', 'for_type'] param: str trait: str for_type: str def eq_productions(grammar: Grammar, prod1: Production, prod2: Production) -> bool: s1 = tuple(e for e in prod1.body if grammar.is_shifted_element(e)) s2 = tuple(e for e in prod2.body if grammar.is_shifted_element(e)) return s1 == s2 def merge_productions(grammar: Grammar, prod1: Production, prod2: Production) -> Production: # Consider all shifted elements as non-moveable elements, and insert other # around these. assert eq_productions(grammar, prod1, prod2) l1 = list(prod1.body) l2 = list(prod2.body) body: typing.List[Element] = [] while l1 != [] and l2 != []: front1 = list(keep_until(l1, grammar.is_shifted_element)) front2 = list(keep_until(l2, grammar.is_shifted_element)) assert front1[-1] == front2[-1] l1 = l1[len(front1):] l2 = l2[len(front2):] if len(front1) == 1: body = body + front2 elif len(front2) == 1: body = body + front1 else: raise ValueError("We do not know how to sort operations yet.") return prod1.copy_with(body=body) @dataclass(frozen=True) class ExtPatch: "Patch an existing grammar rule by adding Code" prod: typing.Tuple[LenientNt, str, NtDef] def apply_patch( self, filename: os.PathLike, grammar: Grammar, nonterminals: typing.Dict[LenientNt, NtDef] ) -> None: # - name: non-terminal. # - namespace: ":" for syntactic or "::" for lexical. Always ":" as # defined by rust_nt_def. # - nt_def: A single non-terminal definition with a single production. (name, namespace, nt_def) = self.prod gnt_def = nonterminals[name] # Find a matching production in the grammar. assert nt_def.params == gnt_def.params new_rhs_list = [] assert len(nt_def.rhs_list) == 1 patch_prod = nt_def.rhs_list[0] applied = False for grammar_prod in gnt_def.rhs_list: if eq_productions(grammar, grammar_prod, patch_prod): grammar_prod = merge_productions(grammar, grammar_prod, patch_prod) applied = True new_rhs_list.append(grammar_prod) if not applied: raise ValueError("{}: Unable to find a matching production for {} in the grammar:\n {}" .format(filename, name, grammar.production_to_str(name, patch_prod))) result = gnt_def.with_rhs_list(new_rhs_list) nonterminals[name] = result @dataclass class GrammarExtension: """A collection of grammar extensions, with added code, added traits for the action functions. """ target: None grammar: typing.List[ExtPatch] filename: os.PathLike def apply_patch( self, grammar: Grammar, nonterminals: typing.Dict[LenientNt, NtDef] ) -> None: # A grammar extension is composed of multiple production patches. for ext in self.grammar: if isinstance(ext, ExtPatch): ext.apply_patch(self.filename, grammar, nonterminals) else: raise ValueError("Extension of type {} not yet supported.".format(ext.__class__))