11"""
2- Contains optional preprocessor support via pcpp
2+ Contains optional preprocessor support functions
33"""
44
55import io
66import re
77import os
8+ import subprocess
9+ import sys
810import typing
9- from .options import PreprocessorFunction
1011
11- from pcpp import Preprocessor , OutputDirective , Action
12+ from . options import PreprocessorFunction
1213
1314
1415class PreprocessorError (Exception ):
1516 pass
1617
1718
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+ #
28108
29- def on_error (self , file , line , msg ):
30- self .errors .append (f"{ file } :{ line } error: { msg } " )
31109
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
34113
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
37124
125+ def on_error (self , file , line , msg ):
126+ self .errors .append (f"{ file } :{ line } error: { msg } " )
38127
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 :
40139 # the output of pcpp includes the contents of all the included files, which
41140 # isn't what a typical user of cxxheaderparser would want, so we strip out
42141 # the line directives and any content that isn't in our original file
@@ -69,6 +168,13 @@ def make_pcpp_preprocessor(
69168 Creates a preprocessor function that uses pcpp (which must be installed
70169 separately) to preprocess the input text.
71170
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
72178 :param encoding: If specified any include files are opened with this encoding
73179 :param passthru_includes: If specified any #include directives that match the
74180 compiled regex pattern will be part of the output.
@@ -82,6 +188,9 @@ def make_pcpp_preprocessor(
82188
83189 """
84190
191+ if pcpp is None :
192+ raise PreprocessorError ("pcpp is not installed" )
193+
85194 def _preprocess_file (filename : str , content : str ) -> str :
86195 pp = _CustomPreprocessor (encoding , passthru_includes )
87196 if include_paths :
@@ -119,6 +228,6 @@ def _preprocess_file(filename: str, content: str) -> str:
119228 filename = filename .replace (os .sep , "/" )
120229 break
121230
122- return _filter_self (filename , fp )
231+ return _pcpp_filter (filename , fp )
123232
124233 return _preprocess_file
0 commit comments