1#!/usr/local/bin/python3.8 2 3''' 4Parse a C source file. 5 6To use, subclass CParser and override its handle_* methods. Then instantiate 7the class with a string to parse. 8''' 9from __future__ import print_function 10 11 12__docformat__ = 'restructuredtext' 13 14import operator 15import os.path 16import re 17import sys 18import time 19import warnings 20 21from . import cdeclarations 22from . import cgrammar 23from . import preprocessor 24from . import yacc 25 26 27# -------------------------------------------------------------------------- 28# Lexer 29# -------------------------------------------------------------------------- 30 31 32class CLexer(object): 33 34 def __init__(self, cparser): 35 self.cparser = cparser 36 self.type_names = set() 37 self.in_define = False 38 39 def input(self, tokens): 40 self.tokens = tokens 41 self.pos = 0 42 43 def token(self): 44 while self.pos < len(self.tokens): 45 t = self.tokens[self.pos] 46 47 self.pos += 1 48 49 if not t: 50 break 51 52 if t.type == 'PP_DEFINE': 53 self.in_define = True 54 elif t.type == 'PP_END_DEFINE': 55 self.in_define = False 56 57 # Transform PP tokens into C tokens 58 elif t.type == 'LPAREN': 59 t.type = '(' 60 elif t.type == 'PP_NUMBER': 61 t.type = 'CONSTANT' 62 elif t.type == 'IDENTIFIER' and t.value in cgrammar.keywords: 63 t.type = t.value.upper() 64 elif t.type == 'IDENTIFIER' and t.value in self.type_names: 65 if (self.pos < 2 or self.tokens[self.pos - 2].type not in 66 ('VOID', '_BOOL', 'CHAR', 'SHORT', 'INT', 'LONG', 67 'FLOAT', 'DOUBLE', 'SIGNED', 'UNSIGNED', 'ENUM', 68 'STRUCT', 'UNION', 'TYPE_NAME')): 69 t.type = 'TYPE_NAME' 70 71 t.lexer = self 72 t.clexpos = self.pos - 1 73 74 return t 75 return None 76 77# -------------------------------------------------------------------------- 78# Parser 79# -------------------------------------------------------------------------- 80 81 82class CParser(object): 83 '''Parse a C source file. 84 85 Subclass and override the handle_* methods. Call `parse` with a string 86 to parse. 87 ''' 88 89 def __init__(self, options, stddef_types=True, gnu_types=True): 90 self.preprocessor_parser = preprocessor.PreprocessorParser(options, self) 91 self.parser = yacc.Parser() 92 prototype = yacc.yacc(method='LALR', 93 debug=False, 94 module=cgrammar, 95 write_tables=True, 96 outputdir=os.path.dirname(__file__), 97 optimize=True) 98 99 # If yacc is reading tables from a file, then it won't find the error 100 # function... need to set it manually 101 prototype.errorfunc = cgrammar.p_error 102 prototype.init_parser(self.parser) 103 self.parser.cparser = self 104 105 self.lexer = CLexer(self) 106 if not options.no_stddef_types: 107 self.lexer.type_names.add('wchar_t') 108 self.lexer.type_names.add('ptrdiff_t') 109 self.lexer.type_names.add('size_t') 110 if not options.no_gnu_types: 111 self.lexer.type_names.add('__builtin_va_list') 112 if sys.platform == 'win32' and not options.no_python_types: 113 self.lexer.type_names.add('__int64') 114 115 def parse(self, filename, debug=False): 116 '''Parse a file. 117 118 If `debug` is True, parsing state is dumped to stdout. 119 ''' 120 121 self.handle_status('Preprocessing %s' % filename) 122 self.preprocessor_parser.parse(filename) 123 self.lexer.input(self.preprocessor_parser.output) 124 self.handle_status('Parsing %s' % filename) 125 self.parser.parse(lexer=self.lexer, debug=debug) 126 127 # ---------------------------------------------------------------------- 128 # Parser interface. Override these methods in your subclass. 129 # ---------------------------------------------------------------------- 130 131 def handle_error(self, message, filename, lineno): 132 '''A parse error occurred. 133 134 The default implementation prints `lineno` and `message` to stderr. 135 The parser will try to recover from errors by synchronising at the 136 next semicolon. 137 ''' 138 print('%s:%s %s' % (filename, lineno, message), file=sys.stderr) 139 140 def handle_pp_error(self, message): 141 '''The C preprocessor emitted an error. 142 143 The default implementation prints the error to stderr. If processing 144 can continue, it will. 145 ''' 146 print('Preprocessor:', message, file=sys.stderr) 147 148 def handle_status(self, message): 149 '''Progress information. 150 151 The default implementationg prints message to stderr. 152 ''' 153 print(message, file=sys.stderr) 154 155 def handle_define(self, name, params, value, filename, lineno): 156 '''#define `name` `value` 157 or #define `name`(`params`) `value` 158 159 name is a string 160 params is None or a list of strings 161 value is a ...? 162 ''' 163 164 def handle_define_constant(self, name, value, filename, lineno): 165 '''#define `name` `value` 166 167 name is a string 168 value is an ExpressionNode or None 169 ''' 170 171 def handle_define_macro(self, name, params, value, filename, lineno): 172 '''#define `name`(`params`) `value` 173 174 name is a string 175 params is a list of strings 176 value is an ExpressionNode or None 177 ''' 178 179 def impl_handle_declaration(self, declaration, filename, lineno): 180 '''Internal method that calls `handle_declaration`. This method 181 also adds any new type definitions to the lexer's list of valid type 182 names, which affects the parsing of subsequent declarations. 183 ''' 184 if declaration.storage == 'typedef': 185 declarator = declaration.declarator 186 if not declarator: 187 # XXX TEMPORARY while struct etc not filled 188 return 189 while declarator.pointer: 190 declarator = declarator.pointer 191 self.lexer.type_names.add(declarator.identifier) 192 self.handle_declaration(declaration, filename, lineno) 193 194 def handle_declaration(self, declaration, filename, lineno): 195 '''A declaration was encountered. 196 197 `declaration` is an instance of Declaration. Where a declaration has 198 multiple initialisers, each is returned as a separate declaration. 199 ''' 200 pass 201 202 203class DebugCParser(CParser): 204 '''A convenience class that prints each invocation of a handle_* method to 205 stdout. 206 ''' 207 208 def handle_define(self, name, value, filename, lineno): 209 print('#define name=%r, value=%r' % (name, value)) 210 211 def handle_define_constant(self, name, value, filename, lineno): 212 print('#define constant name=%r, value=%r' % (name, value)) 213 214 def handle_declaration(self, declaration, filename, lineno): 215 print(declaration) 216 217 def get_ctypes_type(self, typ, declarator): 218 return typ 219 220 def handle_define_unparseable(self, name, params, value, filename, lineno): 221 if params: 222 original_string = "#define %s(%s) %s" % \ 223 (name, ",".join(params), " ".join(value)) 224 else: 225 original_string = "#define %s %s" % \ 226 (name, " ".join(value)) 227 print(original_string) 228 229if __name__ == '__main__': 230 DebugCParser().parse(sys.argv[1], debug=True) 231