|
|
@@ -1,3 +1,5 @@ |
|
|
|
from functools import partial |
|
|
|
|
|
|
|
from .common import is_terminal, GrammarError |
|
|
|
from .utils import suppress |
|
|
|
from .lexer import Token |
|
|
@@ -5,15 +7,7 @@ from .grammar import Rule |
|
|
|
|
|
|
|
###{standalone |
|
|
|
|
|
|
|
class NodeBuilder: |
|
|
|
def __init__(self, tree_class, name): |
|
|
|
self.tree_class = tree_class |
|
|
|
self.name = name |
|
|
|
|
|
|
|
def __call__(self, children): |
|
|
|
return self.tree_class(self.name, children) |
|
|
|
|
|
|
|
class Expand1: |
|
|
|
class ExpandSingleChild: |
|
|
|
def __init__(self, node_builder): |
|
|
|
self.node_builder = node_builder |
|
|
|
|
|
|
@@ -23,57 +17,17 @@ class Expand1: |
|
|
|
else: |
|
|
|
return self.node_builder(children) |
|
|
|
|
|
|
|
class Factory: |
|
|
|
def __init__(self, cls, *args): |
|
|
|
self.cls = cls |
|
|
|
self.args = args |
|
|
|
|
|
|
|
def __call__(self, node_builder): |
|
|
|
return self.cls(node_builder, *self.args) |
|
|
|
|
|
|
|
|
|
|
|
class TokenWrapper: |
|
|
|
class CreateToken: |
|
|
|
"Used for fixing the results of scanless parsing" |
|
|
|
|
|
|
|
def __init__(self, node_builder, token_name): |
|
|
|
def __init__(self, token_name, node_builder): |
|
|
|
self.node_builder = node_builder |
|
|
|
self.token_name = token_name |
|
|
|
|
|
|
|
def __call__(self, children): |
|
|
|
return self.node_builder( [Token(self.token_name, ''.join(children))] ) |
|
|
|
|
|
|
|
def identity(node_builder): |
|
|
|
return node_builder |
|
|
|
|
|
|
|
|
|
|
|
class ChildFilter: |
|
|
|
def __init__(self, node_builder, to_include): |
|
|
|
self.node_builder = node_builder |
|
|
|
self.to_include = to_include |
|
|
|
|
|
|
|
def __call__(self, children): |
|
|
|
filtered = [] |
|
|
|
for i, to_expand in self.to_include: |
|
|
|
if to_expand: |
|
|
|
filtered += children[i].children |
|
|
|
else: |
|
|
|
filtered.append(children[i]) |
|
|
|
|
|
|
|
return self.node_builder(filtered) |
|
|
|
|
|
|
|
def create_rule_handler(expansion, keep_all_tokens, filter_out): |
|
|
|
# if not keep_all_tokens: |
|
|
|
to_include = [(i, not is_terminal(sym) and sym.startswith('_')) |
|
|
|
for i, sym in enumerate(expansion) |
|
|
|
if keep_all_tokens |
|
|
|
or not ((is_terminal(sym) and sym.startswith('_')) or sym in filter_out) |
|
|
|
] |
|
|
|
|
|
|
|
if len(to_include) < len(expansion) or any(to_expand for i, to_expand in to_include): |
|
|
|
return Factory(ChildFilter, to_include) |
|
|
|
|
|
|
|
# else, if no filtering required.. |
|
|
|
return identity |
|
|
|
|
|
|
|
class PropagatePositions: |
|
|
|
def __init__(self, node_builder): |
|
|
@@ -98,6 +52,31 @@ class PropagatePositions: |
|
|
|
return res |
|
|
|
|
|
|
|
|
|
|
|
class ChildFilter: |
|
|
|
def __init__(self, to_include, node_builder): |
|
|
|
self.node_builder = node_builder |
|
|
|
self.to_include = to_include |
|
|
|
|
|
|
|
def __call__(self, children): |
|
|
|
filtered = [] |
|
|
|
for i, to_expand in self.to_include: |
|
|
|
if to_expand: |
|
|
|
filtered += children[i].children |
|
|
|
else: |
|
|
|
filtered.append(children[i]) |
|
|
|
|
|
|
|
return self.node_builder(filtered) |
|
|
|
|
|
|
|
def _should_expand(sym): |
|
|
|
return not is_terminal(sym) and sym.startswith('_') |
|
|
|
|
|
|
|
def maybe_create_child_filter(expansion, filter_out): |
|
|
|
to_include = [(i, _should_expand(sym)) for i, sym in enumerate(expansion) if sym not in filter_out] |
|
|
|
|
|
|
|
if len(to_include) < len(expansion) or any(to_expand for i, to_expand in to_include): |
|
|
|
return partial(ChildFilter, to_include) |
|
|
|
|
|
|
|
|
|
|
|
class Callback(object): |
|
|
|
pass |
|
|
|
|
|
|
@@ -112,22 +91,20 @@ class ParseTreeBuilder: |
|
|
|
self.user_aliases = {} |
|
|
|
|
|
|
|
def _init_builders(self, rules): |
|
|
|
filter_out = set() |
|
|
|
for rule in rules: |
|
|
|
if rule.options and rule.options.filter_out: |
|
|
|
assert rule.origin.startswith('_') # Just to make sure |
|
|
|
filter_out.add(rule.origin) |
|
|
|
filter_out = {rule.origin for rule in rules if rule.options and rule.options.filter_out} |
|
|
|
filter_out |= {sym for rule in rules for sym in rule.expansion if is_terminal(sym) and sym.startswith('_')} |
|
|
|
assert all(x.startswith('_') for x in filter_out) |
|
|
|
|
|
|
|
for rule in rules: |
|
|
|
options = rule.options |
|
|
|
keep_all_tokens = self.always_keep_all_tokens or (options.keep_all_tokens if options else False) |
|
|
|
expand1 = options.expand1 if options else False |
|
|
|
expand_single_child = options.expand1 if options else False |
|
|
|
create_token = options.create_token if options else False |
|
|
|
|
|
|
|
wrapper_chain = filter(None, [ |
|
|
|
(expand1 and not rule.alias) and Expand1, |
|
|
|
create_token and Factory(TokenWrapper, create_token), |
|
|
|
create_rule_handler(rule.expansion, keep_all_tokens, filter_out), |
|
|
|
create_token and partial(CreateToken, create_token), |
|
|
|
(expand_single_child and not rule.alias) and ExpandSingleChild, |
|
|
|
maybe_create_child_filter(rule.expansion, () if keep_all_tokens else filter_out), |
|
|
|
self.propagate_positions and PropagatePositions, |
|
|
|
]) |
|
|
|
|
|
|
@@ -144,7 +121,7 @@ class ParseTreeBuilder: |
|
|
|
try: |
|
|
|
f = transformer._get_func(user_callback_name) |
|
|
|
except AttributeError: |
|
|
|
f = NodeBuilder(self.tree_class, user_callback_name) |
|
|
|
f = partial(self.tree_class, user_callback_name) |
|
|
|
|
|
|
|
self.user_aliases[rule] = rule.alias |
|
|
|
rule.alias = internal_callback_name |
|
|
|