| @@ -133,20 +133,29 @@ When importing rules, all their dependencies will be imported into a namespace, | |||||
| **Syntax:** | **Syntax:** | ||||
| ```html | ```html | ||||
| %import <module>.<TERMINAL> | %import <module>.<TERMINAL> | ||||
| %import <module> (<TERM1> <TERM2>) | |||||
| %import <module>.<rule> | |||||
| %import <module>.<TERMINAL> -> <NEWTERMINAL> | |||||
| %import <module>.<rule> -> <newrule> | |||||
| %import <module> (<TERM1> <TERM2> <rule1> <rule2>) | |||||
| ``` | ``` | ||||
| If the module path is absolute, Lark will attempt to load it from the built-in directory (currently, only `common.lark` is available). | If the module path is absolute, Lark will attempt to load it from the built-in directory (currently, only `common.lark` is available). | ||||
| If the module path is relative, such as `.path.to.file`, Lark will attempt to load it from the current working directory. Grammars must have the `.lark` extension. | If the module path is relative, such as `.path.to.file`, Lark will attempt to load it from the current working directory. Grammars must have the `.lark` extension. | ||||
| The rule or terminal can be imported under an other name with the `->` syntax. | |||||
| **Example:** | **Example:** | ||||
| ```perl | ```perl | ||||
| %import common.NUMBER | %import common.NUMBER | ||||
| %import .terminals_file (A B C) | %import .terminals_file (A B C) | ||||
| %import .rules_file.rulea -> ruleb | |||||
| ``` | ``` | ||||
| Note that `%ignore` directives cannot be imported. Imported rules will abide by the `%ignore` directives declared in the main grammar. | |||||
| ### %declare | ### %declare | ||||
| Declare a terminal without defining it. Useful for plugins. | Declare a terminal without defining it. Useful for plugins. | ||||
| @@ -10,10 +10,10 @@ token: TOKEN priority? ":" expansions _NL | |||||
| priority: "." NUMBER | priority: "." NUMBER | ||||
| statement: "%ignore" expansions _NL -> ignore | statement: "%ignore" expansions _NL -> ignore | ||||
| | "%import" import_args ["->" TOKEN] _NL -> import | |||||
| | "%import" import_args ["->" name] _NL -> import | |||||
| | "%declare" name+ -> declare | | "%declare" name+ -> declare | ||||
| import_args: name ("." name)* | |||||
| import_args: "."? name ("." name)* | |||||
| ?expansions: alias (_VBAR alias)* | ?expansions: alias (_VBAR alias)* | ||||
| @@ -6,6 +6,9 @@ grammar_files = [ | |||||
| 'examples/python2.lark', | 'examples/python2.lark', | ||||
| 'examples/python3.lark', | 'examples/python3.lark', | ||||
| 'examples/lark.lark', | 'examples/lark.lark', | ||||
| 'examples/relative-imports/multiples.lark', | |||||
| 'examples/relative-imports/multiple2.lark', | |||||
| 'examples/relative-imports/multiple3.lark', | |||||
| 'lark/grammars/common.lark', | 'lark/grammars/common.lark', | ||||
| ] | ] | ||||
| @@ -0,0 +1 @@ | |||||
| start: ("0" | "1")* "0" | |||||
| @@ -0,0 +1,5 @@ | |||||
| start: mod0mod0+ | |||||
| mod0mod0: "0" | "1" mod1mod0 | |||||
| mod1mod0: "1" | "0" mod2mod1 mod1mod0 | |||||
| mod2mod1: "0" | "1" mod2mod1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| start: "2:" multiple2 | |||||
| | "3:" multiple3 | |||||
| %import .multiple2.start -> multiple2 | |||||
| %import .multiple3.start -> multiple3 | |||||
| @@ -0,0 +1,28 @@ | |||||
| # | |||||
| # This example demonstrates relative imports with rule rewrite | |||||
| # see multiples.lark | |||||
| # | |||||
| # | |||||
| # if b is a number written in binary, and m is either 2 or 3, | |||||
| # the grammar aims to recognise m:b iif b is a multiple of m | |||||
| # | |||||
| # for example, 3:1001 is recognised | |||||
| # because 9 (0b1001) is a multiple of 3 | |||||
| # | |||||
| from lark import Lark, UnexpectedInput | |||||
| parser = Lark.open('multiples.lark', parser='lalr') | |||||
| def is_in_grammar(data): | |||||
| try: | |||||
| parser.parse(data) | |||||
| except UnexpectedInput: | |||||
| return False | |||||
| return True | |||||
| for n_dec in range(100): | |||||
| n_bin = bin(n_dec)[2:] | |||||
| assert is_in_grammar('2:{}'.format(n_bin)) == (n_dec % 2 == 0) | |||||
| assert is_in_grammar('3:{}'.format(n_bin)) == (n_dec % 3 == 0) | |||||
| @@ -139,7 +139,7 @@ RULES = { | |||||
| 'declare': ['_DECLARE _declare_args _NL'], | 'declare': ['_DECLARE _declare_args _NL'], | ||||
| 'import': ['_IMPORT _import_path _NL', | 'import': ['_IMPORT _import_path _NL', | ||||
| '_IMPORT _import_path _LPAR name_list _RPAR _NL', | '_IMPORT _import_path _LPAR name_list _RPAR _NL', | ||||
| '_IMPORT _import_path _TO TERMINAL _NL'], | |||||
| '_IMPORT _import_path _TO name _NL'], | |||||
| '_import_path': ['import_lib', 'import_rel'], | '_import_path': ['import_lib', 'import_rel'], | ||||
| 'import_lib': ['_import_args'], | 'import_lib': ['_import_args'], | ||||
| @@ -586,7 +586,7 @@ def import_from_grammar_into_namespace(grammar, namespace, aliases): | |||||
| try: | try: | ||||
| return aliases[name].value | return aliases[name].value | ||||
| except KeyError: | except KeyError: | ||||
| return '%s.%s' % (namespace, name) | |||||
| return '%s__%s' % (namespace, name) | |||||
| to_import = list(bfs(aliases, rule_dependencies)) | to_import = list(bfs(aliases, rule_dependencies)) | ||||
| for symbol in to_import: | for symbol in to_import: | ||||
| @@ -745,7 +745,7 @@ class GrammarLoader: | |||||
| g = import_grammar(grammar_path, base_paths=[base_path]) | g = import_grammar(grammar_path, base_paths=[base_path]) | ||||
| aliases_dict = dict(zip(names, aliases)) | aliases_dict = dict(zip(names, aliases)) | ||||
| new_td, new_rd = import_from_grammar_into_namespace(g, '.'.join(dotted_path), aliases_dict) | |||||
| new_td, new_rd = import_from_grammar_into_namespace(g, '__'.join(dotted_path), aliases_dict) | |||||
| term_defs += new_td | term_defs += new_td | ||||
| rule_defs += new_rd | rule_defs += new_rd | ||||
| @@ -0,0 +1,10 @@ | |||||
| startab: expr | |||||
| expr: A B | |||||
| | A expr B | |||||
| A: "a" | |||||
| B: "b" | |||||
| %import common.WS | |||||
| %ignore WS | |||||
| @@ -998,11 +998,89 @@ def _make_parser_test(LEXER, PARSER): | |||||
| self.assertEqual(x.children, ['12', 'elephants']) | self.assertEqual(x.children, ['12', 'elephants']) | ||||
| def test_import_rename(self): | |||||
| grammar = """ | |||||
| start: N W | |||||
| %import common.NUMBER -> N | |||||
| %import common.WORD -> W | |||||
| %import common.WS | |||||
| %ignore WS | |||||
| """ | |||||
| l = _Lark(grammar) | |||||
| x = l.parse('12 elephants') | |||||
| self.assertEqual(x.children, ['12', 'elephants']) | |||||
| def test_relative_import(self): | def test_relative_import(self): | ||||
| l = _Lark_open('test_relative_import.lark', rel_to=__file__) | l = _Lark_open('test_relative_import.lark', rel_to=__file__) | ||||
| x = l.parse('12 lions') | x = l.parse('12 lions') | ||||
| self.assertEqual(x.children, ['12', 'lions']) | self.assertEqual(x.children, ['12', 'lions']) | ||||
| def test_relative_import_rename(self): | |||||
| l = _Lark_open('test_relative_import_rename.lark', rel_to=__file__) | |||||
| x = l.parse('12 lions') | |||||
| self.assertEqual(x.children, ['12', 'lions']) | |||||
| def test_relative_rule_import(self): | |||||
| l = _Lark_open('test_relative_rule_import.lark', rel_to=__file__) | |||||
| x = l.parse('xaabby') | |||||
| self.assertEqual(x.children, [ | |||||
| 'x', | |||||
| Tree('expr', ['a', Tree('expr', ['a', 'b']), 'b']), | |||||
| 'y']) | |||||
| def test_relative_rule_import_drop_ignore(self): | |||||
| # %ignore rules are dropped on import | |||||
| l = _Lark_open('test_relative_rule_import_drop_ignore.lark', | |||||
| rel_to=__file__) | |||||
| self.assertRaises((ParseError, UnexpectedInput), | |||||
| l.parse, 'xa abby') | |||||
| def test_relative_rule_import_subrule(self): | |||||
| l = _Lark_open('test_relative_rule_import_subrule.lark', | |||||
| rel_to=__file__) | |||||
| x = l.parse('xaabby') | |||||
| self.assertEqual(x.children, [ | |||||
| 'x', | |||||
| Tree('startab', [ | |||||
| Tree('grammars__ab__expr', [ | |||||
| 'a', Tree('grammars__ab__expr', ['a', 'b']), 'b', | |||||
| ]), | |||||
| ]), | |||||
| 'y']) | |||||
| def test_relative_rule_import_subrule_no_conflict(self): | |||||
| l = _Lark_open( | |||||
| 'test_relative_rule_import_subrule_no_conflict.lark', | |||||
| rel_to=__file__) | |||||
| x = l.parse('xaby') | |||||
| self.assertEqual(x.children, [Tree('expr', [ | |||||
| 'x', | |||||
| Tree('startab', [ | |||||
| Tree('grammars__ab__expr', ['a', 'b']), | |||||
| ]), | |||||
| 'y'])]) | |||||
| self.assertRaises((ParseError, UnexpectedInput), | |||||
| l.parse, 'xaxabyby') | |||||
| def test_relative_rule_import_rename(self): | |||||
| l = _Lark_open('test_relative_rule_import_rename.lark', | |||||
| rel_to=__file__) | |||||
| x = l.parse('xaabby') | |||||
| self.assertEqual(x.children, [ | |||||
| 'x', | |||||
| Tree('ab', ['a', Tree('ab', ['a', 'b']), 'b']), | |||||
| 'y']) | |||||
| def test_multi_import(self): | def test_multi_import(self): | ||||
| grammar = """ | grammar = """ | ||||
| start: NUMBER WORD | start: NUMBER WORD | ||||
| @@ -0,0 +1,7 @@ | |||||
| start: N WORD | |||||
| %import .grammars.test.NUMBER -> N | |||||
| %import common.WORD | |||||
| %import common.WS | |||||
| %ignore WS | |||||
| @@ -0,0 +1,7 @@ | |||||
| start: X expr Y | |||||
| X: "x" | |||||
| Y: "y" | |||||
| %import .grammars.ab.expr | |||||
| @@ -0,0 +1,7 @@ | |||||
| start: X expr Y | |||||
| X: "x" | |||||
| Y: "y" | |||||
| %import .grammars.ab.expr | |||||
| @@ -0,0 +1,7 @@ | |||||
| start: X ab Y | |||||
| X: "x" | |||||
| Y: "y" | |||||
| %import .grammars.ab.expr -> ab | |||||
| @@ -0,0 +1,7 @@ | |||||
| start: X startab Y | |||||
| X: "x" | |||||
| Y: "y" | |||||
| %import .grammars.ab.startab | |||||
| @@ -0,0 +1,9 @@ | |||||
| start: expr | |||||
| expr: X startab Y | |||||
| X: "x" | |||||
| Y: "y" | |||||
| %import .grammars.ab.startab | |||||