| @@ -104,7 +104,7 @@ TERMINALS = { | |||||
| RULES = { | RULES = { | ||||
| 'start': ['_list'], | 'start': ['_list'], | ||||
| '_list': ['_item', '_list _item'], | '_list': ['_item', '_list _item'], | ||||
| '_item': ['rule', 'term', 'statement', '_NL'], | |||||
| '_item': ['rule', 'term', 'ignore', 'import', 'declare', 'override', 'extend', '_NL'], | |||||
| 'rule': ['RULE template_params _COLON expansions _NL', | 'rule': ['RULE template_params _COLON expansions _NL', | ||||
| 'RULE template_params _DOT NUMBER _COLON expansions _NL'], | 'RULE template_params _DOT NUMBER _COLON expansions _NL'], | ||||
| @@ -151,7 +151,6 @@ RULES = { | |||||
| 'term': ['TERMINAL _COLON expansions _NL', | 'term': ['TERMINAL _COLON expansions _NL', | ||||
| 'TERMINAL _DOT NUMBER _COLON expansions _NL'], | 'TERMINAL _DOT NUMBER _COLON expansions _NL'], | ||||
| 'statement': ['ignore', 'import', 'declare', 'override', 'extend'], | |||||
| 'override': ['_OVERRIDE rule', | 'override': ['_OVERRIDE rule', | ||||
| '_OVERRIDE term'], | '_OVERRIDE term'], | ||||
| 'extend': ['_EXTEND rule', | 'extend': ['_EXTEND rule', | ||||
| @@ -799,12 +798,12 @@ def _find_used_symbols(tree): | |||||
| for t in x.scan_values(lambda t: t.type in ('RULE', 'TERMINAL'))} | for t in x.scan_values(lambda t: t.type in ('RULE', 'TERMINAL'))} | ||||
| def _grammar_parser(): | |||||
| def _get_parser(): | |||||
| try: | try: | ||||
| return _grammar_parser.cache | |||||
| return _get_parser.cache | |||||
| except AttributeError: | except AttributeError: | ||||
| terminals = [TerminalDef(name, PatternRE(value)) for name, value in TERMINALS.items()] | terminals = [TerminalDef(name, PatternRE(value)) for name, value in TERMINALS.items()] | ||||
| rules = [options_from_rule(name, None, x) for name, x in RULES.items()] | rules = [options_from_rule(name, None, x) for name, x in RULES.items()] | ||||
| rules = [Rule(NonTerminal(r), symbols_from_strcase(x.split()), i, None, o) | rules = [Rule(NonTerminal(r), symbols_from_strcase(x.split()), i, None, o) | ||||
| for r, _p, xs, o in rules for i, x in enumerate(xs)] | for r, _p, xs, o in rules for i, x in enumerate(xs)] | ||||
| @@ -814,8 +813,8 @@ def _grammar_parser(): | |||||
| parser_conf = ParserConf(rules, callback, ['start']) | parser_conf = ParserConf(rules, callback, ['start']) | ||||
| lexer_conf.lexer_type = 'standard' | lexer_conf.lexer_type = 'standard' | ||||
| parser_conf.parser_type = 'lalr' | parser_conf.parser_type = 'lalr' | ||||
| _grammar_parser.cache = ParsingFrontend(lexer_conf, parser_conf, {}) | |||||
| return _grammar_parser.cache | |||||
| _get_parser.cache = ParsingFrontend(lexer_conf, parser_conf, {}) | |||||
| return _get_parser.cache | |||||
| GRAMMAR_ERRORS = [ | GRAMMAR_ERRORS = [ | ||||
| ('Unclosed parenthesis', ['a: (\n']), | ('Unclosed parenthesis', ['a: (\n']), | ||||
| @@ -833,20 +832,22 @@ GRAMMAR_ERRORS = [ | |||||
| def _parse_grammar(text, name, start='start'): | def _parse_grammar(text, name, start='start'): | ||||
| try: | try: | ||||
| return PrepareGrammar().transform(_grammar_parser().parse(text + '\n', start)) | |||||
| tree = _get_parser().parse(text + '\n', start) | |||||
| except UnexpectedCharacters as e: | except UnexpectedCharacters as e: | ||||
| context = e.get_context(text) | context = e.get_context(text) | ||||
| raise GrammarError("Unexpected input at line %d column %d in %s: \n\n%s" % | raise GrammarError("Unexpected input at line %d column %d in %s: \n\n%s" % | ||||
| (e.line, e.column, name, context)) | (e.line, e.column, name, context)) | ||||
| except UnexpectedToken as e: | except UnexpectedToken as e: | ||||
| context = e.get_context(text) | context = e.get_context(text) | ||||
| error = e.match_examples(_grammar_parser().parse, GRAMMAR_ERRORS, use_accepts=True) | |||||
| error = e.match_examples(_get_parser().parse, GRAMMAR_ERRORS, use_accepts=True) | |||||
| if error: | if error: | ||||
| raise GrammarError("%s, at line %s column %s\n\n%s" % (error, e.line, e.column, context)) | raise GrammarError("%s, at line %s column %s\n\n%s" % (error, e.line, e.column, context)) | ||||
| elif 'STRING' in e.expected: | elif 'STRING' in e.expected: | ||||
| raise GrammarError("Expecting a value at line %s column %s\n\n%s" % (e.line, e.column, context)) | raise GrammarError("Expecting a value at line %s column %s\n\n%s" % (e.line, e.column, context)) | ||||
| raise | raise | ||||
| return PrepareGrammar().transform(tree) | |||||
| class GrammarBuilder: | class GrammarBuilder: | ||||
| def __init__(self, global_keep_all_tokens=False, import_paths=None): | def __init__(self, global_keep_all_tokens=False, import_paths=None): | ||||
| @@ -855,7 +856,7 @@ class GrammarBuilder: | |||||
| self._definitions = {} | self._definitions = {} | ||||
| self._ignore_names = [] | self._ignore_names = [] | ||||
| def _is_term(self, name): | def _is_term(self, name): | ||||
| # Imported terminals are of the form `Path__to__Grammar__file__TERMINAL_NAME` | # Imported terminals are of the form `Path__to__Grammar__file__TERMINAL_NAME` | ||||
| # Only the last part is the actual name, and the rest might contain mixed case | # Only the last part is the actual name, and the rest might contain mixed case | ||||
| @@ -869,13 +870,13 @@ class GrammarBuilder: | |||||
| args['type' + postfix] = lowercase_type = ("rule", "terminal")[self._is_term(name)] | args['type' + postfix] = lowercase_type = ("rule", "terminal")[self._is_term(name)] | ||||
| args['Type' + postfix] = lowercase_type.title() | args['Type' + postfix] = lowercase_type.title() | ||||
| raise GrammarError(msg.format(**args)) | raise GrammarError(msg.format(**args)) | ||||
| def _check_options(self, name, options): | def _check_options(self, name, options): | ||||
| if self._is_term(name): | if self._is_term(name): | ||||
| if options is None: | if options is None: | ||||
| options = 1 | options = 1 | ||||
| # if we don't use Integral here, we run into python2.7/python3 problems with long vs int | # if we don't use Integral here, we run into python2.7/python3 problems with long vs int | ||||
| elif not isinstance(options, Integral): | |||||
| elif not isinstance(options, Integral): | |||||
| raise GrammarError("Terminal require a single int as 'options' (e.g. priority), got %s" % (type(options),)) | raise GrammarError("Terminal require a single int as 'options' (e.g. priority), got %s" % (type(options),)) | ||||
| else: | else: | ||||
| if options is None: | if options is None: | ||||
| @@ -886,7 +887,7 @@ class GrammarBuilder: | |||||
| options.keep_all_tokens = True | options.keep_all_tokens = True | ||||
| return options | return options | ||||
| def _define(self, name, exp, params=(), options=None, override=False): | def _define(self, name, exp, params=(), options=None, override=False): | ||||
| if (name in self._definitions) ^ override: | if (name in self._definitions) ^ override: | ||||
| if override: | if override: | ||||
| @@ -900,7 +901,7 @@ class GrammarBuilder: | |||||
| def _extend(self, name, exp, params=(), options=None): | def _extend(self, name, exp, params=(), options=None): | ||||
| if name not in self._definitions: | if name not in self._definitions: | ||||
| self._grammar_error("Can't extend {type} {name} as it wasn't defined before", name) | self._grammar_error("Can't extend {type} {name} as it wasn't defined before", name) | ||||
| if tuple(params) != tuple(self._definitions[name][0]): | |||||
| if tuple(params) != tuple(self._definitions[name][0]): | |||||
| self._grammar_error("Cannot extend {type} with different parameters: {name}", name) | self._grammar_error("Cannot extend {type} with different parameters: {name}", name) | ||||
| # TODO: think about what to do with 'options' | # TODO: think about what to do with 'options' | ||||
| base = self._definitions[name][1] | base = self._definitions[name][1] | ||||
| @@ -929,11 +930,11 @@ class GrammarBuilder: | |||||
| name = '__IGNORE_%d'% len(self._ignore_names) | name = '__IGNORE_%d'% len(self._ignore_names) | ||||
| self._ignore_names.append(name) | self._ignore_names.append(name) | ||||
| self._definitions[name] = ((), t, 1) | self._definitions[name] = ((), t, 1) | ||||
| def _declare(self, *names): | def _declare(self, *names): | ||||
| for name in names: | for name in names: | ||||
| self._define(name, None) | self._define(name, None) | ||||
| def _mangle_exp(self, exp, mangle): | def _mangle_exp(self, exp, mangle): | ||||
| if mangle is None: | if mangle is None: | ||||
| return exp | return exp | ||||
| @@ -944,21 +945,23 @@ class GrammarBuilder: | |||||
| t.children[i] = Token(c.type, mangle(c.value)) | t.children[i] = Token(c.type, mangle(c.value)) | ||||
| return exp | return exp | ||||
| def _unpack_definition(self, tree, mangle): | def _unpack_definition(self, tree, mangle): | ||||
| if tree.data == 'rule': | if tree.data == 'rule': | ||||
| name, params, exp, opts = options_from_rule(*tree.children) | name, params, exp, opts = options_from_rule(*tree.children) | ||||
| else: | else: | ||||
| name = tree.children[0].value | name = tree.children[0].value | ||||
| params = () | |||||
| params = () # TODO terminal templates | |||||
| opts = int(tree.children[1]) if len(tree.children) == 3 else 1 # priority | opts = int(tree.children[1]) if len(tree.children) == 3 else 1 # priority | ||||
| exp = tree.children[-1] | exp = tree.children[-1] | ||||
| if mangle is not None: | if mangle is not None: | ||||
| params = tuple(mangle(p) for p in params) | params = tuple(mangle(p) for p in params) | ||||
| name = mangle(name) | name = mangle(name) | ||||
| exp = self._mangle_exp(exp, mangle) | exp = self._mangle_exp(exp, mangle) | ||||
| return name, exp, params, opts | return name, exp, params, opts | ||||
| def _unpack_import(self, stmt, grammar_name): | def _unpack_import(self, stmt, grammar_name): | ||||
| if len(stmt.children) > 1: | if len(stmt.children) > 1: | ||||
| path_node, arg1 = stmt.children | path_node, arg1 = stmt.children | ||||
| @@ -992,19 +995,15 @@ class GrammarBuilder: | |||||
| base_path = os.path.split(base_file)[0] | base_path = os.path.split(base_file)[0] | ||||
| else: | else: | ||||
| base_path = os.path.abspath(os.path.curdir) | base_path = os.path.abspath(os.path.curdir) | ||||
| return dotted_path, base_path, aliases | return dotted_path, base_path, aliases | ||||
| def load_grammar(self, grammar_text, grammar_name="<?>", mangle=None): | def load_grammar(self, grammar_text, grammar_name="<?>", mangle=None): | ||||
| tree = _parse_grammar(grammar_text, grammar_name) | tree = _parse_grammar(grammar_text, grammar_name) | ||||
| imports = {} # imports are collect over the whole file to prevent duplications | imports = {} # imports are collect over the whole file to prevent duplications | ||||
| actions = [] # Some statements need to be delayed (override and extend) till after imports are handled | |||||
| for stmt in tree.children: | for stmt in tree.children: | ||||
| if stmt.data in ('term', 'rule'): | |||||
| self._define(*self._unpack_definition(stmt, mangle)) | |||||
| continue | |||||
| assert stmt.data == 'statement', stmt.data | |||||
| stmt ,= stmt.children | |||||
| if stmt.data == 'import': | if stmt.data == 'import': | ||||
| dotted_path, base_path, aliases = self._unpack_import(stmt, grammar_name) | dotted_path, base_path, aliases = self._unpack_import(stmt, grammar_name) | ||||
| try: | try: | ||||
| @@ -1013,30 +1012,35 @@ class GrammarBuilder: | |||||
| import_aliases.update(aliases) | import_aliases.update(aliases) | ||||
| except KeyError: | except KeyError: | ||||
| imports[dotted_path] = base_path, aliases | imports[dotted_path] = base_path, aliases | ||||
| for dotted_path, (base_path, aliases) in imports.items(): | |||||
| self.do_import(dotted_path, base_path, aliases, mangle) | |||||
| for stmt in tree.children: | |||||
| if stmt.data in ('term', 'rule'): | |||||
| self._define(*self._unpack_definition(stmt, mangle)) | |||||
| elif stmt.data == 'override': | |||||
| r ,= stmt.children | |||||
| self._define(*self._unpack_definition(r, mangle), override=True) | |||||
| elif stmt.data == 'extend': | |||||
| r ,= stmt.children | |||||
| self._extend(*self._unpack_definition(r, mangle)) | |||||
| elif stmt.data == 'ignore': | elif stmt.data == 'ignore': | ||||
| # if mangle is not None, we shouldn't apply ignore, since we aren't in a toplevel grammar | # if mangle is not None, we shouldn't apply ignore, since we aren't in a toplevel grammar | ||||
| if mangle is None: | if mangle is None: | ||||
| self._ignore(*stmt.children) | self._ignore(*stmt.children) | ||||
| elif stmt.data == 'declare': | elif stmt.data == 'declare': | ||||
| names = [t.value for t in stmt.children] | |||||
| if mangle is None: | if mangle is None: | ||||
| self._declare(*(t.value for t in stmt.children)) | |||||
| self._declare(*names) | |||||
| else: | else: | ||||
| self._declare(*(mangle(t.value) for t in stmt.children)) | |||||
| elif stmt.data == 'override': | |||||
| r ,= stmt.children | |||||
| actions.append((self._define, self._unpack_definition(r, mangle) + (True,))) | |||||
| elif stmt.data == 'extend': | |||||
| r ,= stmt.children | |||||
| actions.append((self._extend, self._unpack_definition(r, mangle))) | |||||
| self._declare(*map(mangle, names)) | |||||
| elif stmt.data == 'import': | |||||
| pass | |||||
| else: | else: | ||||
| assert False, stmt | assert False, stmt | ||||
| for dotted_path, (base_path, aliases) in imports.items(): | |||||
| self.do_import(dotted_path, base_path, aliases, mangle) | |||||
| for f, args in actions: | |||||
| f(*args) | |||||
| def do_import(self, dotted_path, base_path, aliases, base_mangle=None): | def do_import(self, dotted_path, base_path, aliases, base_mangle=None): | ||||
| mangle = self.get_mangle('__'.join(dotted_path), aliases, base_mangle) | mangle = self.get_mangle('__'.join(dotted_path), aliases, base_mangle) | ||||
| grammar_path = os.path.join(*dotted_path) + EXT | grammar_path = os.path.join(*dotted_path) + EXT | ||||
| @@ -1062,7 +1066,7 @@ class GrammarBuilder: | |||||
| def get_mangle(self, prefix, aliases, base_mangle=None): | def get_mangle(self, prefix, aliases, base_mangle=None): | ||||
| def mangle(s): | def mangle(s): | ||||
| if s in aliases: | if s in aliases: | ||||
| s = aliases[s] | |||||
| s = aliases[s] | |||||
| else: | else: | ||||
| if s[0] == '_': | if s[0] == '_': | ||||
| s = '_%s__%s' % (prefix, s[1:]) | s = '_%s__%s' % (prefix, s[1:]) | ||||
| @@ -1080,7 +1084,7 @@ class GrammarBuilder: | |||||
| raise GrammarError("Template Parameter conflicts with rule %s (in template %s)" % (p, name)) | raise GrammarError("Template Parameter conflicts with rule %s (in template %s)" % (p, name)) | ||||
| if p in params[:i]: | if p in params[:i]: | ||||
| raise GrammarError("Duplicate Template Parameter %s (in template %s)" % (p, name)) | raise GrammarError("Duplicate Template Parameter %s (in template %s)" % (p, name)) | ||||
| if exp is None: # Remaining checks don't work for abstract rules/terminals | if exp is None: # Remaining checks don't work for abstract rules/terminals | ||||
| continue | continue | ||||
| @@ -1094,7 +1098,7 @@ class GrammarBuilder: | |||||
| expected, actual = len(self._definitions[sym][0]), len(args) | expected, actual = len(self._definitions[sym][0]), len(args) | ||||
| self._grammar_error("Wrong number of template arguments used for {name} " | self._grammar_error("Wrong number of template arguments used for {name} " | ||||
| "(expected %s, got %s) (in {type2} {name2})" % (expected, actual), sym, name) | "(expected %s, got %s) (in {type2} {name2})" % (expected, actual), sym, name) | ||||
| for sym in _find_used_symbols(exp): | for sym in _find_used_symbols(exp): | ||||
| if sym not in self._definitions and sym not in params: | if sym not in self._definitions and sym not in params: | ||||
| self._grammar_error("{Type} '{name}' used but not defined (in {type2} {name2})", sym, name) | self._grammar_error("{Type} '{name}' used but not defined (in {type2} {name2})", sym, name) | ||||