1
1
"""
2
- Contains optional preprocessor support via pcpp
2
+ Contains optional preprocessor support functions
3
3
"""
4
4
5
5
import io
6
6
import re
7
7
import os
8
+ import subprocess
9
+ import sys
8
10
import typing
9
- from .options import PreprocessorFunction
10
11
11
- from pcpp import Preprocessor , OutputDirective , Action
12
+ from . options import PreprocessorFunction
12
13
13
14
14
15
class PreprocessorError (Exception ):
15
16
pass
16
17
17
18
18
- class _CustomPreprocessor (Preprocessor ):
19
- def __init__ (
20
- self ,
21
- encoding : typing .Optional [str ],
22
- passthru_includes : typing .Optional ["re.Pattern" ],
23
- ):
24
- Preprocessor .__init__ (self )
25
- self .errors : typing .List [str ] = []
26
- self .assume_encoding = encoding
27
- self .passthru_includes = passthru_includes
19
+ #
20
+ # GCC preprocessor support
21
+ #
22
+
23
+
24
+ def _gcc_filter (fname : str , fp : typing .TextIO ) -> str :
25
+ new_output = io .StringIO ()
26
+ keep = True
27
+ fname = fname .replace ("\\ " , "\\ \\ " )
28
+
29
+ for line in fp :
30
+ if line .startswith ("# " ):
31
+ last_quote = line .rfind ('"' )
32
+ if last_quote != - 1 :
33
+ keep = line [:last_quote ].endswith (fname )
34
+
35
+ if keep :
36
+ new_output .write (line )
37
+
38
+ new_output .seek (0 )
39
+ return new_output .read ()
40
+
41
+
42
+ def make_gcc_preprocessor (
43
+ * ,
44
+ defines : typing .List [str ] = [],
45
+ include_paths : typing .List [str ] = [],
46
+ retain_all_content : bool = False ,
47
+ encoding : typing .Optional [str ] = None ,
48
+ gcc_args : typing .List [str ] = ["g++" ],
49
+ print_cmd : bool = True ,
50
+ ) -> PreprocessorFunction :
51
+ """
52
+ Creates a preprocessor function that uses g++ to preprocess the input text.
53
+
54
+ gcc is a high performance and accurate precompiler, but if an #include
55
+ directive can't be resolved or other oddity exists in your input it will
56
+ throw an error.
57
+
58
+ :param defines: list of #define macros specified as "key value"
59
+ :param include_paths: list of directories to search for included files
60
+ :param retain_all_content: If False, only the parsed file content will be retained
61
+ :param encoding: If specified any include files are opened with this encoding
62
+ :param gcc_args: This is the path to G++ and any extra args you might want
63
+ :param print_cmd: Prints the gcc command as its executed
64
+
65
+ .. code-block:: python
66
+
67
+ pp = make_gcc_preprocessor()
68
+ options = ParserOptions(preprocessor=pp)
69
+
70
+ parse_file(content, options=options)
71
+
72
+ """
73
+
74
+ if not encoding :
75
+ encoding = "utf-8"
76
+
77
+ def _preprocess_file (filename : str , content : str ) -> str :
78
+ cmd = gcc_args + ["-w" , "-E" , "-C" ]
79
+
80
+ for p in include_paths :
81
+ cmd .append (f"-I{ p } " )
82
+ for d in defines :
83
+ cmd .append (f"-D{ d .replace (' ' , '=' )} " )
84
+
85
+ kwargs = {"encoding" : encoding }
86
+ if filename == "<str>" :
87
+ cmd .append ("-" )
88
+ filename = "<stdin>"
89
+ kwargs ["input" ] = content
90
+ else :
91
+ cmd .append (filename )
92
+
93
+ if print_cmd :
94
+ print ("+" , " " .join (cmd ), file = sys .stderr )
95
+
96
+ result : str = subprocess .check_output (cmd , ** kwargs ) # type: ignore
97
+ if not retain_all_content :
98
+ result = _gcc_filter (filename , io .StringIO (result ))
99
+
100
+ return result
101
+
102
+ return _preprocess_file
103
+
104
+
105
+ #
106
+ # PCPP preprocessor support (not installed by default)
107
+ #
28
108
29
- def on_error (self , file , line , msg ):
30
- self .errors .append (f"{ file } :{ line } error: { msg } " )
31
109
32
- def on_include_not_found (self , * ignored ):
33
- raise OutputDirective (Action .IgnoreAndPassThrough )
110
+ try :
111
+ import pcpp
112
+ from pcpp import Preprocessor , OutputDirective , Action
34
113
35
- def on_comment (self , * ignored ):
36
- return True
114
+ class _CustomPreprocessor (Preprocessor ):
115
+ def __init__ (
116
+ self ,
117
+ encoding : typing .Optional [str ],
118
+ passthru_includes : typing .Optional ["re.Pattern" ],
119
+ ):
120
+ Preprocessor .__init__ (self )
121
+ self .errors : typing .List [str ] = []
122
+ self .assume_encoding = encoding
123
+ self .passthru_includes = passthru_includes
37
124
125
+ def on_error (self , file , line , msg ):
126
+ self .errors .append (f"{ file } :{ line } error: { msg } " )
38
127
39
- def _filter_self (fname : str , fp : typing .TextIO ) -> str :
128
+ def on_include_not_found (self , * ignored ):
129
+ raise OutputDirective (Action .IgnoreAndPassThrough )
130
+
131
+ def on_comment (self , * ignored ):
132
+ return True
133
+
134
+ except ImportError :
135
+ pcpp = None
136
+
137
+
138
+ def _pcpp_filter (fname : str , fp : typing .TextIO ) -> str :
40
139
# the output of pcpp includes the contents of all the included files, which
41
140
# isn't what a typical user of cxxheaderparser would want, so we strip out
42
141
# the line directives and any content that isn't in our original file
@@ -69,6 +168,13 @@ def make_pcpp_preprocessor(
69
168
Creates a preprocessor function that uses pcpp (which must be installed
70
169
separately) to preprocess the input text.
71
170
171
+ If missing #include files are encountered, this preprocessor will ignore the
172
+ error. This preprocessor is pure python so it's very portable, and is a good
173
+ choice if performance isn't critical.
174
+
175
+ :param defines: list of #define macros specified as "key value"
176
+ :param include_paths: list of directories to search for included files
177
+ :param retain_all_content: If False, only the parsed file content will be retained
72
178
:param encoding: If specified any include files are opened with this encoding
73
179
:param passthru_includes: If specified any #include directives that match the
74
180
compiled regex pattern will be part of the output.
@@ -82,6 +188,9 @@ def make_pcpp_preprocessor(
82
188
83
189
"""
84
190
191
+ if pcpp is None :
192
+ raise PreprocessorError ("pcpp is not installed" )
193
+
85
194
def _preprocess_file (filename : str , content : str ) -> str :
86
195
pp = _CustomPreprocessor (encoding , passthru_includes )
87
196
if include_paths :
@@ -119,6 +228,6 @@ def _preprocess_file(filename: str, content: str) -> str:
119
228
filename = filename .replace (os .sep , "/" )
120
229
break
121
230
122
- return _filter_self (filename , fp )
231
+ return _pcpp_filter (filename , fp )
123
232
124
233
return _preprocess_file
0 commit comments