11import os
2+ import typing
23
34from . import common
45from . import lexer
@@ -30,30 +31,44 @@ class PreprocessError(common.RezParserError):
3031 __slots__ = ()
3132
3233
34+ class IncludeState (object ):
35+ lexer : typing .Any
36+ filename : str
37+ framework : typing .Optional [str ]
38+
39+ def __init__ (self , * , lexer , filename , framework ):
40+ super ().__init__ ()
41+
42+ self .lexer = lexer
43+ self .filename = filename
44+ self .framework = framework
45+
46+
3347class RezPreprocessor (object ):
3448 @property
3549 def lineno (self ):
36- return self .lexers [- 1 ].lineno
50+ return self .include_stack [- 1 ]. lexer .lineno
3751
3852 @lineno .setter
3953 def lineno (self , lineno ):
40- self .lexers [- 1 ].lineno = lineno
54+ self .include_stack [- 1 ]. lexer .lineno = lineno
4155
4256 @property
4357 def lexpos (self ):
44- return self .lexers [- 1 ].lexpos
58+ return self .include_stack [- 1 ]. lexer .lexpos
4559
4660 @lexpos .setter
4761 def lexpos (self , lexpos ):
48- self .lexers [- 1 ].lexpos = lexpos
62+ self .include_stack [- 1 ]. lexer .lexpos = lexpos
4963
50- def __init__ (self , lexer , * , parser = None , evaluator = None , macros = None , derez = False , include_path = None , sys_include_path = None , print_func = None ):
64+ def __init__ (self , lexer , * , filename = "<input>" , parser = None , evaluator = None , macros = None , derez = False , include_path = None , sys_include_path = None , print_func = None ):
5165 super ().__init__ ()
5266
53- self .lexers = [lexer ]
5467 self .parser = parser
5568 self .evaluator = evaluator
5669
70+ self .include_stack = [IncludeState (lexer = lexer , filename = filename , framework = None )]
71+
5772 # Mapping of macro names (case-insensitive, all names must be passed through str.casefold) to lists of expansion tokens.
5873 self .macros = {
5974 "true" : [common .Token ("INTLIT_DEC" , "1" )],
@@ -122,8 +137,9 @@ def __iter__(self):
122137 return iter (self .token , None )
123138
124139 def input (self , * args , ** kwargs ):
125- self .lexers = [self .lexers [- 1 ].clone ()]
126- self .lexers [- 1 ].input (* args , ** kwargs )
140+ base = self .include_stack [0 ]
141+ self .include_stack [:] = [IncludeState (lexer = base .lexer .clone (), filename = base .filename , framework = None )]
142+ self .include_stack [- 1 ].lexer .input (* args , ** kwargs )
127143
128144 def _eval_expression (self , tokens ):
129145 return self .evaluator .eval (self .parser .parse_expr (tokens , lexer .NoOpLexer ()))
@@ -133,11 +149,11 @@ def _token_internal(self, *, expand=True):
133149 try :
134150 tok = self .expansion_stack .pop (0 )
135151 except IndexError :
136- tok = self .lexers [- 1 ].token ()
152+ tok = self .include_stack [- 1 ]. lexer .token ()
137153
138154 if tok is None :
139- if len (self .lexers ) > 1 :
140- self .lexers .pop ()
155+ if len (self .include_stack ) > 1 :
156+ self .include_stack .pop ()
141157 else :
142158 return tok
143159 elif tok == "expansion_end" :
@@ -246,7 +262,7 @@ def token(self):
246262
247263 if not once or (name , angle ) not in self .included_files :
248264 self .included_files .add ((name , angle ))
249- self .lexers .append (self .lexer_for_include (name , angle = angle ))
265+ self .include_stack .append (self .state_for_include (name , angle = angle ))
250266 elif tok .type == "PP_PRINTF" :
251267 printf_tokens = []
252268 printf_token = self ._token_internal ()
@@ -369,10 +385,20 @@ def token(self):
369385 else :
370386 return tok
371387
372- def lexer_for_include (self , name , * , angle ):
388+ def state_for_include (self , name , * , angle ):
389+ # By default, search only the system include path.
373390 include_path = self .sys_include_path
391+
392+ # For each header in the include stack that comes from a framework, also search the corresponding local sub-frameworks.
393+ for state in self .include_stack :
394+ if state .framework is not None :
395+ include_path .insert (0 , os .path .join (state .framework , "Frameworks" ))
396+
397+ # If the include is quoted and not angled, also search the local include path.
374398 if not angle :
375- include_path = self .include_path + include_path
399+ include_path [0 :0 ] = self .include_path
400+
401+ framework = None
376402
377403 for dir in include_path :
378404 try :
@@ -389,12 +415,13 @@ def lexer_for_include(self, name, *, angle):
389415 try :
390416 with open (os .path .join (dir , * parts ), "r" , encoding = common .STRING_ENCODING ) as f :
391417 text = f .read ()
418+ framework = os .path .join (dir , parts [0 ])
392419 break
393420 except FileNotFoundError :
394421 pass
395422 else :
396423 raise PreprocessError (f"File { name !r} (angle = { angle } ) not found on include path" )
397424
398- sublexer = self .lexers [- 1 ].clone ()
425+ sublexer = self .include_stack [- 1 ]. lexer .clone ()
399426 sublexer .input (text )
400- return sublexer
427+ return IncludeState ( lexer = sublexer , filename = name , framework = framework )
0 commit comments