Skip to content

Commit ec0254b

Browse files
committed
Support local frameworks in #include/#import
1 parent d93db0f commit ec0254b

3 files changed

Lines changed: 45 additions & 19 deletions

File tree

rezparser/preprocessor.py

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import typing
23

34
from . import common
45
from . 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+
3347
class 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)

test/DGTest.r

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# import <CarbonCore/CarbonCore.r> ignore me
1+
# import <CoreServices/CoreServices.r> ignore me
22

33
//#include$$format("%s/", "RIncludes")"Types" ".r"; ignore me?"
44
#include "RIncludes/" "Types" ".r"; ignore me?"

test/test.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,7 @@ def main():
5757
parser=parser,
5858
evaluator=evaluator,
5959
include_path=["."],
60-
# TODO Frameworks in frameworks are not properly supported yet
61-
sys_include_path=["/System/Library/Frameworks", "/System/Library/Frameworks/CoreServices.framework/Frameworks"],
60+
sys_include_path=["/System/Library/Frameworks"],
6261
print_func=print,
6362
)
6463

0 commit comments

Comments
 (0)