1import os.path 2import re 3 4from . import common as _common 5 6 7TOOL = 'gcc' 8 9# https://gcc.gnu.org/onlinedocs/cpp/Preprocessor-Output.html 10LINE_MARKER_RE = re.compile(r'^# (\d+) "([^"]+)"(?: [1234])*$') 11PREPROC_DIRECTIVE_RE = re.compile(r'^\s*#\s*(\w+)\b.*') 12COMPILER_DIRECTIVE_RE = re.compile(r''' 13 ^ 14 (.*?) # <before> 15 (__\w+__) # <directive> 16 \s* 17 [(] [(] 18 ( 19 [^()]* 20 (?: 21 [(] 22 [^()]* 23 [)] 24 [^()]* 25 )* 26 ) # <args> 27 ( [)] [)] )? # <closed> 28''', re.VERBOSE) 29 30POST_ARGS = ( 31 '-pthread', 32 '-std=c99', 33 #'-g', 34 #'-Og', 35 #'-Wno-unused-result', 36 #'-Wsign-compare', 37 #'-Wall', 38 #'-Wextra', 39 '-E', 40) 41 42 43def preprocess(filename, incldirs=None, macros=None, samefiles=None): 44 text = _common.preprocess( 45 TOOL, 46 filename, 47 incldirs=incldirs, 48 macros=macros, 49 #preargs=PRE_ARGS, 50 postargs=POST_ARGS, 51 executable=['gcc'], 52 compiler='unix', 53 ) 54 return _iter_lines(text, filename, samefiles) 55 56 57def _iter_lines(text, filename, samefiles, *, raw=False): 58 lines = iter(text.splitlines()) 59 60 # Build the lines and filter out directives. 61 partial = 0 # depth 62 origfile = None 63 for line in lines: 64 m = LINE_MARKER_RE.match(line) 65 if m: 66 lno, origfile = m.groups() 67 lno = int(lno) 68 elif _filter_orig_file(origfile, filename, samefiles): 69 if (m := PREPROC_DIRECTIVE_RE.match(line)): 70 name, = m.groups() 71 if name != 'pragma': 72 raise Exception(line) 73 else: 74 if not raw: 75 line, partial = _strip_directives(line, partial=partial) 76 yield _common.SourceLine( 77 _common.FileInfo(filename, lno), 78 'source', 79 line or '', 80 None, 81 ) 82 lno += 1 83 84 85def _strip_directives(line, partial=0): 86 # We assume there are no string literals with parens in directive bodies. 87 while partial > 0: 88 if not (m := re.match(r'[^{}]*([()])', line)): 89 return None, partial 90 delim, = m.groups() 91 partial += 1 if delim == '(' else -1 # opened/closed 92 line = line[m.end():] 93 94 line = re.sub(r'__extension__', '', line) 95 96 while (m := COMPILER_DIRECTIVE_RE.match(line)): 97 before, _, _, closed = m.groups() 98 if closed: 99 line = f'{before} {line[m.end():]}' 100 else: 101 after, partial = _strip_directives(line[m.end():], 2) 102 line = f'{before} {after or ""}' 103 if partial: 104 break 105 106 return line, partial 107 108 109def _filter_orig_file(origfile, current, samefiles): 110 if origfile == current: 111 return True 112 if origfile == '<stdin>': 113 return True 114 if os.path.isabs(origfile): 115 return False 116 117 for filename in samefiles or (): 118 if filename.endswith(os.path.sep): 119 filename += os.path.basename(current) 120 if origfile == filename: 121 return True 122 123 return False 124