101101 '_DECLARE' : r'%declare' ,
102102 '_EXTEND' : r'%extend' ,
103103 '_IMPORT' : r'%import' ,
104+ '_INCLUDE' : r'%include' ,
104105 'NUMBER' : r'[+-]?\d+' ,
105106}
106107
107108RULES = {
108109 'start' : ['_list' ],
109110 '_list' : ['_item' , '_list _item' ],
110- '_item' : ['rule' , 'term' , 'ignore' , 'import' , 'declare' , 'override' , 'extend' , '_NL' ],
111+ '_item' : ['rule' , 'term' , 'ignore' , 'import' , 'declare' , 'override' , 'extend' , 'include' , ' _NL' ],
111112
112113 'rule' : ['RULE template_params _COLON expansions _NL' ,
113114 'RULE template_params _DOT NUMBER _COLON expansions _NL' ],
164165 'import' : ['_IMPORT _import_path _NL' ,
165166 '_IMPORT _import_path _LPAR name_list _RPAR _NL' ,
166167 '_IMPORT _import_path _TO name _NL' ],
168+ 'include' : ['_INCLUDE _import_path _NL' ],
167169
168170 '_import_path' : ['import_lib' , 'import_rel' ],
169171 'import_lib' : ['_import_args' ],
@@ -811,7 +813,7 @@ def __init__(self, pkg_name: str, search_paths: Tuple[str, ...]=("", )) -> None:
811813 def __repr__ (self ):
812814 return "%s(%r, %r)" % (type (self ).__name__ , self .pkg_name , self .search_paths )
813815
814- def __call__ (self , base_path : Union [None , str , PackageResource ], grammar_path : str , used_files : Dict [str , tuple [str , str ]]= None ) -> Tuple [PackageResource , str ]:
816+ def __call__ (self , base_path : Union [None , str , PackageResource ], grammar_path : str , used_files : Dict [str , Tuple [str , str ]]= None ) -> Tuple [PackageResource , str ]:
815817 if base_path is None :
816818 to_try = self .search_paths
817819 else :
@@ -1137,7 +1139,10 @@ def _unpack_import(self, stmt, grammar_name):
11371139 path_node , = stmt .children
11381140 arg1 = None
11391141
1140- if isinstance (arg1 , Tree ): # Multi import
1142+ if stmt .data == "include" : # we only want the dotted_path, no aliases
1143+ dotted_path = tuple (path_node .children )
1144+ aliases = None
1145+ elif isinstance (arg1 , Tree ): # Multi import
11411146 dotted_path = tuple (path_node .children )
11421147 names = arg1 .children
11431148 aliases = dict (zip (names , names )) # Can't have aliased multi import, so all aliases will be the same as names
@@ -1185,50 +1190,66 @@ def _unpack_definition(self, tree, mangle):
11851190 exp = _mangle_exp (exp , mangle )
11861191 return name , exp , params , opts
11871192
1188-
1189- def load_grammar (self , grammar_text : str , grammar_name : str = "<?>" , mangle : Optional [Callable [[str ], str ]]= None ) -> None :
1193+ def _get_parsed (self , grammar_text , grammar_name ):
11901194 if grammar_text not in self .cached_grammars :
11911195 tree = _parse_grammar (grammar_text , grammar_name )
11921196 self .cached_grammars [grammar_text ] = tree
1193- tree = nr_deepcopy_tree (self .cached_grammars [grammar_text ])
1197+ return nr_deepcopy_tree (self .cached_grammars [grammar_text ])
1198+
1199+ def load_grammar (self , grammar_text , grammar_name = "<?>" , mangle = None ):
1200+ tree = self ._get_parsed (grammar_text , grammar_name )
11941201
11951202 imports = {}
1196- for stmt in tree .children :
1197- if stmt .data == 'import' :
1198- dotted_path , base_path , aliases = self ._unpack_import (stmt , grammar_name )
1199- try :
1200- import_base_path , import_aliases = imports [dotted_path ]
1201- assert base_path == import_base_path , 'Inconsistent base_path for %s.' % '.' .join (dotted_path )
1202- import_aliases .update (aliases )
1203- except KeyError :
1204- imports [dotted_path ] = base_path , aliases
1203+ todo = [tree .children ]
1204+ after_import_todo = []
1205+ while todo :
1206+ stmts = todo .pop (0 )
1207+ after_import_todo .append (stmts )
1208+ for stmt in stmts :
1209+ if stmt .data == 'import' :
1210+ dotted_path , base_path , aliases = self ._unpack_import (stmt , grammar_name )
1211+ try :
1212+ import_base_path , import_aliases = imports [dotted_path ]
1213+ assert base_path == import_base_path , 'Inconsistent base_path for %s.' % '.' .join (dotted_path )
1214+ import_aliases .update (aliases )
1215+ except KeyError :
1216+ imports [dotted_path ] = base_path , aliases
1217+ elif stmt .data == 'include' :
1218+ dotted_path , base_path , aliases = self ._unpack_import (stmt , grammar_name )
1219+ assert aliases is None , "For some reason '_unpack_import' returned aliases for %include"
1220+ text , name = self .resolve_import (dotted_path , base_path )
1221+ new_tree = self ._get_parsed (text , name )
1222+ todo .append (new_tree .children )
1223+
12051224
12061225 for dotted_path , (base_path , aliases ) in imports .items ():
12071226 self .do_import (dotted_path , base_path , aliases , mangle )
12081227
1209- for stmt in tree .children :
1210- if stmt .data in ('term' , 'rule' ):
1211- self ._define (* self ._unpack_definition (stmt , mangle ))
1212- elif stmt .data == 'override' :
1213- r ,= stmt .children
1214- self ._define (* self ._unpack_definition (r , mangle ), override = True )
1215- elif stmt .data == 'extend' :
1216- r ,= stmt .children
1217- self ._extend (* self ._unpack_definition (r , mangle ))
1218- elif stmt .data == 'ignore' :
1219- # if mangle is not None, we shouldn't apply ignore, since we aren't in a toplevel grammar
1220- if mangle is None :
1221- self ._ignore (* stmt .children )
1222- elif stmt .data == 'declare' :
1223- names = [t .value for t in stmt .children ]
1224- if mangle is None :
1225- self ._declare (* names )
1228+ while after_import_todo :
1229+ stmts = after_import_todo .pop (0 )
1230+ for stmt in stmts :
1231+ if stmt .data in ('term' , 'rule' ):
1232+ self ._define (* self ._unpack_definition (stmt , mangle ))
1233+ elif stmt .data == 'override' :
1234+ r ,= stmt .children
1235+ self ._define (* self ._unpack_definition (r , mangle ), override = True )
1236+ elif stmt .data == 'extend' :
1237+ r ,= stmt .children
1238+ self ._extend (* self ._unpack_definition (r , mangle ))
1239+ elif stmt .data == 'ignore' :
1240+ # if mangle is not None, we shouldn't apply ignore, since we aren't in a toplevel grammar
1241+ if mangle is None :
1242+ self ._ignore (* stmt .children )
1243+ elif stmt .data == 'declare' :
1244+ names = [t .value for t in stmt .children ]
1245+ if mangle is None :
1246+ self ._declare (* names )
1247+ else :
1248+ self ._declare (* map (mangle , names ))
1249+ elif stmt .data in ('import' , 'include' ): # These have already been processed
1250+ pass
12261251 else :
1227- self ._declare (* map (mangle , names ))
1228- elif stmt .data == 'import' :
1229- pass
1230- else :
1231- assert False , stmt
1252+ assert False , stmt
12321253
12331254
12341255 term_defs = { name : exp
@@ -1252,9 +1273,8 @@ def rule_dependencies(symbol):
12521273 self ._definitions = {k : v for k , v in self ._definitions .items () if k in _used }
12531274
12541275
1255- def do_import (self , dotted_path : Tuple [str , ...], base_path : Optional [str ], aliases : Dict [str , str ], base_mangle : Optional [ Callable [[ str ], str ]] = None ) -> None :
1276+ def resolve_import (self , dotted_path : Tuple [str , ...], base_path : Optional [str ]) -> Tuple [str , Union [ str , PackageResource ]] :
12561277 assert dotted_path
1257- mangle = _get_mangle ('__' .join (dotted_path ), aliases , base_mangle )
12581278 grammar_path = os .path .join (* dotted_path ) + EXT
12591279 to_try = self .import_paths + ([base_path ] if base_path is not None else []) + [stdlib_loader ]
12601280 for source in to_try :
@@ -1274,21 +1294,26 @@ def do_import(self, dotted_path: Tuple[str, ...], base_path: Optional[str], alia
12741294 h = hashlib .md5 (text .encode ('utf8' )).hexdigest ()
12751295 if self .used_files .setdefault (joined_path , (h ,text ))[0 ] != h :
12761296 raise RuntimeError ("Grammar file was changed during importing" )
1277-
1278- gb = GrammarBuilder (self .global_keep_all_tokens , self .import_paths , self .used_files , self .cached_grammars )
1279- gb .load_grammar (text , joined_path , mangle )
1280- gb ._remove_unused (map (mangle , aliases ))
1281- for name in gb ._definitions :
1282- if name in self ._definitions :
1283- raise GrammarError ("Cannot import '%s' from '%s': Symbol already defined." % (name , grammar_path ))
1284-
1285- self ._definitions .update (** gb ._definitions )
1286- break
1297+ return text , joined_path
12871298 else :
12881299 # Search failed. Make Python throw a nice error.
12891300 open (grammar_path , encoding = 'utf8' )
1290- assert False , "Couldn't import grammar %s, but a corresponding file was found at a place where lark doesn't search for it" % (dotted_path ,)
1301+ raise GrammarError ("Couldn't import grammar %s, but a corresponding file was found at a place where lark"
1302+ " doesn't search for it. This probably means that you wrongly used non-relative imports."
1303+ " Try adding a `.` at the beginning of the path" % (dotted_path ,))
1304+
1305+ def do_import (self , dotted_path : Tuple [str , ...], base_path : Optional [str ], aliases : Dict [str , str ], base_mangle : Optional [Callable [[str ], str ]]= None ):
1306+ mangle = _get_mangle ('__' .join (dotted_path ), aliases , base_mangle )
1307+ text , joined_path = self .resolve_import (dotted_path , base_path )
1308+
1309+ gb = GrammarBuilder (self .global_keep_all_tokens , self .import_paths , self .used_files )
1310+ gb .load_grammar (text , joined_path , mangle )
1311+ gb ._remove_unused (map (mangle , aliases ))
1312+ for name in gb ._definitions :
1313+ if name in self ._definitions :
1314+ raise GrammarError ("Cannot import '%s' from '%s': Symbol already defined." % (name , joined_path ))
12911315
1316+ self ._definitions .update (** gb ._definitions )
12921317
12931318 def validate (self ) -> None :
12941319 for name , (params , exp , _options ) in self ._definitions .items ():
0 commit comments