@@ -26,21 +26,6 @@ class RuleOptions: | |||||
self.filter_out = filter_out # remove this rule from the tree | self.filter_out = filter_out # remove this rule from the tree | ||||
# used for "token"-rules in scanless | # used for "token"-rules in scanless | ||||
@classmethod | |||||
def from_rule(cls, name, *x): | |||||
if len(x) > 1: | |||||
priority, expansions = x | |||||
priority = int(priority) | |||||
else: | |||||
expansions ,= x | |||||
priority = None | |||||
keep_all_tokens = name.startswith('!') | |||||
name = name.lstrip('!') | |||||
expand1 = name.startswith('?') | |||||
name = name.lstrip('?') | |||||
return name, expansions, cls(keep_all_tokens, expand1, priority=priority) | |||||
def __repr__(self): | def __repr__(self): | ||||
return 'RuleOptions(%r, %r, %r, %r, %r)' % ( | return 'RuleOptions(%r, %r, %r, %r, %r)' % ( | ||||
@@ -165,14 +165,15 @@ class Lark: | |||||
def _build_parser(self): | def _build_parser(self): | ||||
self.parser_class = get_frontend(self.options.parser, self.options.lexer) | self.parser_class = get_frontend(self.options.parser, self.options.lexer) | ||||
self.parse_tree_builder = ParseTreeBuilder(self.rules, self.options.tree_class, self.options.propagate_positions, self.options.keep_all_tokens) | self.parse_tree_builder = ParseTreeBuilder(self.rules, self.options.tree_class, self.options.propagate_positions, self.options.keep_all_tokens) | ||||
rules, callback = self.parse_tree_builder.apply(self.options.transformer) | |||||
callback = self.parse_tree_builder.apply(self.options.transformer) | |||||
if self.profiler: | if self.profiler: | ||||
for f in dir(callback): | for f in dir(callback): | ||||
if not (f.startswith('__') and f.endswith('__')): | if not (f.startswith('__') and f.endswith('__')): | ||||
setattr(callback, f, self.profiler.make_wrapper('transformer', getattr(callback, f))) | setattr(callback, f, self.profiler.make_wrapper('transformer', getattr(callback, f))) | ||||
parser_conf = ParserConf(rules, callback, self.options.start) | |||||
parser_conf = ParserConf(self.rules, callback, self.options.start) | |||||
return self.parser_class(self.lexer_conf, parser_conf, options=self.options) | return self.parser_class(self.lexer_conf, parser_conf, options=self.options) | ||||
@@ -12,7 +12,7 @@ from .parse_tree_builder import ParseTreeBuilder | |||||
from .parser_frontends import LALR | from .parser_frontends import LALR | ||||
from .parsers.lalr_parser import UnexpectedToken | from .parsers.lalr_parser import UnexpectedToken | ||||
from .common import is_terminal, GrammarError, LexerConf, ParserConf, PatternStr, PatternRE, TokenDef | from .common import is_terminal, GrammarError, LexerConf, ParserConf, PatternStr, PatternRE, TokenDef | ||||
from .grammar import RuleOptions | |||||
from .grammar import RuleOptions, Rule | |||||
from .tree import Tree as T, Transformer, InlineTransformer, Visitor | from .tree import Tree as T, Transformer, InlineTransformer, Visitor | ||||
@@ -485,13 +485,21 @@ class Grammar: | |||||
dict_update_safe(rules, ebnf_to_bnf.new_rules) | dict_update_safe(rules, ebnf_to_bnf.new_rules) | ||||
for tree, _o in rules.values(): | |||||
rule_tree_to_text = RuleTreeToText() | |||||
new_rules = [] | |||||
for origin, (tree, options) in rules.items(): | |||||
simplify_rule.visit(tree) | simplify_rule.visit(tree) | ||||
expansions = rule_tree_to_text.transform(tree) | |||||
rule_tree_to_text = RuleTreeToText() | |||||
rules = {origin: (rule_tree_to_text.transform(tree), options) for origin, (tree, options) in rules.items()} | |||||
for expansion, alias in expansions: | |||||
if alias and origin.startswith('_'): | |||||
raise Exception("Rule %s is marked for expansion (it starts with an underscore) and isn't allowed to have aliases (alias=%s)" % (origin, alias)) | |||||
return tokens, rules, self.ignore | |||||
rule = Rule(origin, expansion, alias, options) | |||||
new_rules.append(rule) | |||||
return tokens, new_rules, self.ignore | |||||
@@ -528,14 +536,28 @@ def resolve_token_references(token_defs): | |||||
if not changed: | if not changed: | ||||
break | break | ||||
def options_from_rule(name, *x): | |||||
if len(x) > 1: | |||||
priority, expansions = x | |||||
priority = int(priority) | |||||
else: | |||||
expansions ,= x | |||||
priority = None | |||||
keep_all_tokens = name.startswith('!') | |||||
name = name.lstrip('!') | |||||
expand1 = name.startswith('?') | |||||
name = name.lstrip('?') | |||||
return name, expansions, RuleOptions(keep_all_tokens, expand1, priority=priority) | |||||
class GrammarLoader: | class GrammarLoader: | ||||
def __init__(self): | def __init__(self): | ||||
tokens = [TokenDef(name, PatternRE(value)) for name, value in TOKENS.items()] | tokens = [TokenDef(name, PatternRE(value)) for name, value in TOKENS.items()] | ||||
rules = [RuleOptions.from_rule(name, x) for name, x in RULES.items()] | |||||
d = {r: ([(x.split(), None) for x in xs], o) for r, xs, o in rules} | |||||
rules, callback = ParseTreeBuilder(d, T).apply() | |||||
rules = [options_from_rule(name, x) for name, x in RULES.items()] | |||||
rules = [Rule(r, x.split(), None, o) for r, xs, o in rules for x in xs] | |||||
callback = ParseTreeBuilder(rules, T).apply() | |||||
lexer_conf = LexerConf(tokens, ['WS', 'COMMENT']) | lexer_conf = LexerConf(tokens, ['WS', 'COMMENT']) | ||||
parser_conf = ParserConf(rules, callback, 'start') | parser_conf = ParserConf(rules, callback, 'start') | ||||
@@ -625,7 +647,7 @@ class GrammarLoader: | |||||
# Resolve token references | # Resolve token references | ||||
resolve_token_references(token_defs) | resolve_token_references(token_defs) | ||||
rules = [RuleOptions.from_rule(*x) for x in rule_defs] | |||||
rules = [options_from_rule(*x) for x in rule_defs] | |||||
rule_names = set() | rule_names = set() | ||||
for name, _x, _o in rules: | for name, _x, _o in rules: | ||||
@@ -109,49 +109,46 @@ class ParseTreeBuilder: | |||||
def _init_builders(self, rules): | def _init_builders(self, rules): | ||||
filter_out = set() | filter_out = set() | ||||
for origin, (expansions, options) in rules.items(): | |||||
if options and options.filter_out: | |||||
assert origin.startswith('_') # Just to make sure | |||||
filter_out.add(origin) | |||||
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) | |||||
for origin, (expansions, options) in rules.items(): | |||||
for rule in rules: | |||||
options = rule.options | |||||
keep_all_tokens = self.always_keep_all_tokens or (options.keep_all_tokens if options else False) | keep_all_tokens = self.always_keep_all_tokens or (options.keep_all_tokens if options else False) | ||||
expand1 = options.expand1 if options else False | expand1 = options.expand1 if options else False | ||||
create_token = options.create_token if options else False | create_token = options.create_token if options else False | ||||
for expansion, alias in expansions: | |||||
if alias and origin.startswith('_'): | |||||
raise Exception("Rule %s is marked for expansion (it starts with an underscore) and isn't allowed to have aliases (alias=%s)" % (origin, alias)) | |||||
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), | |||||
self.propagate_positions and PropagatePositions, | |||||
]) | |||||
wrapper_chain = filter(None, [ | |||||
(expand1 and not alias) and Expand1, | |||||
create_token and Factory(TokenWrapper, create_token), | |||||
create_rule_handler(expansion, keep_all_tokens, filter_out), | |||||
self.propagate_positions and PropagatePositions, | |||||
]) | |||||
yield origin, expansion, options, alias or origin, wrapper_chain | |||||
yield rule, wrapper_chain | |||||
def apply(self, transformer=None): | def apply(self, transformer=None): | ||||
callback = Callback() | callback = Callback() | ||||
new_rules = [] | |||||
for origin, expansion, options, alias, wrapper_chain in self.rule_builders: | |||||
callback_name = '_callback_%s_%s' % (origin, '_'.join(expansion)) | |||||
for rule, wrapper_chain in self.rule_builders: | |||||
internal_callback_name = '_callback_%s_%s' % (rule.origin, '_'.join(rule.expansion)) | |||||
user_callback_name = rule.alias or rule.origin | |||||
try: | try: | ||||
f = transformer._get_func(alias) | |||||
f = transformer._get_func(user_callback_name) | |||||
except AttributeError: | except AttributeError: | ||||
f = NodeBuilder(self.tree_class, alias) | |||||
f = NodeBuilder(self.tree_class, user_callback_name) | |||||
rule.alias = internal_callback_name | |||||
for w in wrapper_chain: | for w in wrapper_chain: | ||||
f = w(f) | f = w(f) | ||||
if hasattr(callback, callback_name): | |||||
raise GrammarError("Rule expansion '%s' already exists in rule %s" % (' '.join(expansion), origin)) | |||||
setattr(callback, callback_name, f) | |||||
new_rules.append( Rule( origin, expansion, callback_name, options )) | |||||
if hasattr(callback, internal_callback_name): | |||||
raise GrammarError("Rule '%s' already exists" % (rule,)) | |||||
setattr(callback, internal_callback_name, f) | |||||
return new_rules, callback | |||||
return callback |