|
|
@@ -5,9 +5,9 @@ import sys |
|
|
|
from ast import literal_eval |
|
|
|
from copy import deepcopy |
|
|
|
|
|
|
|
from .utils import bfs |
|
|
|
from .lexer import Token, TerminalDef, PatternStr, PatternRE |
|
|
|
|
|
|
|
|
|
|
|
from .parse_tree_builder import ParseTreeBuilder |
|
|
|
from .parser_frontends import LALR_TraditionalLexer |
|
|
|
from .common import LexerConf, ParserConf |
|
|
@@ -26,9 +26,6 @@ EXT = '.lark' |
|
|
|
|
|
|
|
_RE_FLAGS = 'imslux' |
|
|
|
|
|
|
|
def is_terminal(sym): |
|
|
|
return sym.isupper() |
|
|
|
|
|
|
|
_TERMINAL_NAMES = { |
|
|
|
'.' : 'DOT', |
|
|
|
',' : 'COMMA', |
|
|
@@ -528,6 +525,41 @@ def import_grammar(grammar_path, base_paths=[]): |
|
|
|
|
|
|
|
return _imported_grammars[grammar_path] |
|
|
|
|
|
|
|
def import_from_grammar_into_namespace(grammar, namespace, aliases): |
|
|
|
imported_terms = dict(grammar.term_defs) |
|
|
|
imported_rules = {n:(n,t,o) for n,t,o in grammar.rule_defs} |
|
|
|
|
|
|
|
term_defs = [] |
|
|
|
rule_defs = [] |
|
|
|
|
|
|
|
def rule_dependencies(symbol): |
|
|
|
if symbol.type != 'RULE': |
|
|
|
return [] |
|
|
|
_, tree, _ = imported_rules[symbol] |
|
|
|
return tree.scan_values(lambda x: x.type in ('RULE', 'TERMINAL')) |
|
|
|
|
|
|
|
def get_namespace_name(name): |
|
|
|
try: |
|
|
|
return aliases[name].value |
|
|
|
except KeyError: |
|
|
|
return '%s.%s' % (namespace, name) |
|
|
|
|
|
|
|
to_import = list(bfs(aliases, rule_dependencies)) |
|
|
|
for symbol in to_import: |
|
|
|
if symbol.type == 'TERMINAL': |
|
|
|
term_defs.append([get_namespace_name(symbol), imported_terms[symbol]]) |
|
|
|
else: |
|
|
|
assert symbol.type == 'RULE' |
|
|
|
rule = imported_rules[symbol] |
|
|
|
for t in rule[1].iter_subtrees(): |
|
|
|
for i, c in enumerate(t.children): |
|
|
|
if isinstance(c, Token) and c.type in ('RULE', 'TERMINAL'): |
|
|
|
t.children[i] = Token(c.type, get_namespace_name(c)) |
|
|
|
rule_defs.append((get_namespace_name(symbol), rule[1], rule[2])) |
|
|
|
|
|
|
|
return term_defs, rule_defs |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def resolve_term_references(term_defs): |
|
|
|
# TODO Cycles detection |
|
|
@@ -569,7 +601,7 @@ def options_from_rule(name, *x): |
|
|
|
|
|
|
|
|
|
|
|
def symbols_from_strcase(expansion): |
|
|
|
return [Terminal(x, filter_out=x.startswith('_')) if is_terminal(x) else NonTerminal(x) for x in expansion] |
|
|
|
return [Terminal(x, filter_out=x.startswith('_')) if x.isupper() else NonTerminal(x) for x in expansion] |
|
|
|
|
|
|
|
@inline_args |
|
|
|
class PrepareGrammar(Transformer_InPlace): |
|
|
@@ -632,6 +664,7 @@ class GrammarLoader: |
|
|
|
|
|
|
|
term_defs = [td if len(td)==3 else (td[0], 1, td[1]) for td in term_defs] |
|
|
|
term_defs = [(name.value, (t, int(p))) for name, p, t in term_defs] |
|
|
|
rule_defs = [options_from_rule(*x) for x in rule_defs] |
|
|
|
|
|
|
|
# Execute statements |
|
|
|
ignore = [] |
|
|
@@ -646,15 +679,14 @@ class GrammarLoader: |
|
|
|
path_node ,= stmt.children |
|
|
|
arg1 = None |
|
|
|
|
|
|
|
dotted_path = path_node.children |
|
|
|
|
|
|
|
if isinstance(arg1, Tree): # Multi import |
|
|
|
dotted_path = path_node.children |
|
|
|
names = arg1.children |
|
|
|
aliases = names # Can't have aliased multi import, so all aliases will be the same as names |
|
|
|
else: # Single import |
|
|
|
names = [dotted_path[-1]] # Get name from dotted path |
|
|
|
dotted_path = path_node.children[:-1] |
|
|
|
names = [path_node.children[-1]] # Get name from dotted path |
|
|
|
aliases = [arg1] if arg1 else names # Aliases if exist |
|
|
|
dotted_path = dotted_path[:-1] |
|
|
|
|
|
|
|
grammar_path = os.path.join(*dotted_path) + EXT |
|
|
|
|
|
|
@@ -668,10 +700,11 @@ class GrammarLoader: |
|
|
|
base_path = os.path.split(base_file)[0] |
|
|
|
g = import_grammar(grammar_path, base_paths=[base_path]) |
|
|
|
|
|
|
|
for name, alias in zip(names, aliases): |
|
|
|
term_options = dict(g.term_defs)[name] |
|
|
|
assert isinstance(term_options, tuple) and len(term_options)==2 |
|
|
|
term_defs.append([alias.value, term_options]) |
|
|
|
aliases_dict = dict(zip(names, aliases)) |
|
|
|
new_td, new_rd = import_from_grammar_into_namespace(g, '.'.join(dotted_path), aliases_dict) |
|
|
|
|
|
|
|
term_defs += new_td |
|
|
|
rule_defs += new_rd |
|
|
|
|
|
|
|
elif stmt.data == 'declare': |
|
|
|
for t in stmt.children: |
|
|
@@ -716,7 +749,7 @@ class GrammarLoader: |
|
|
|
|
|
|
|
resolve_term_references(term_defs) |
|
|
|
|
|
|
|
rules = [options_from_rule(*x) for x in rule_defs] |
|
|
|
rules = rule_defs |
|
|
|
|
|
|
|
rule_names = set() |
|
|
|
for name, _x, _o in rules: |
|
|
@@ -730,7 +763,7 @@ class GrammarLoader: |
|
|
|
used_symbols = {t for x in expansions.find_data('expansion') |
|
|
|
for t in x.scan_values(lambda t: t.type in ('RULE', 'TERMINAL'))} |
|
|
|
for sym in used_symbols: |
|
|
|
if is_terminal(sym): |
|
|
|
if sym.type == 'TERMINAL': |
|
|
|
if sym not in terminal_names: |
|
|
|
raise GrammarError("Token '%s' used but not defined (in rule %s)" % (sym, name)) |
|
|
|
else: |
|
|
|