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