1 #-----------------------------------------------------------------
2 # pycparser: cdecl.py
3 #
4 # Example of the CDECL tool using pycparser. CDECL "explains" C type
5 # declarations in plain English.
6 #
7 # The AST generated by pycparser from the given declaration is traversed
8 # recursively to build the explanation. Note that the declaration must be a
9 # valid external declaration in C. As shown below, typedef can be optionally
10 # expanded.
11 #
12 # For example:
13 #
14 #   c_decl = 'typedef int Node; const Node* (*ar)[10];'
15 #
16 #   explain_c_declaration(c_decl)
17 #   => ar is a pointer to array[10] of pointer to const Node
18 #
19 # struct and typedef can be optionally expanded:
20 #
21 #   explain_c_declaration(c_decl, expand_typedef=True)
22 #   => ar is a pointer to array[10] of pointer to const int
23 #
24 #   c_decl = 'struct P {int x; int y;} p;'
25 #
26 #   explain_c_declaration(c_decl)
27 #   => p is a struct P
28 #
29 #   explain_c_declaration(c_decl, expand_struct=True)
30 #   => p is a struct P containing {x is a int, y is a int}
31 #
32 # Eli Bendersky [https://eli.thegreenplace.net/]
33 # License: BSD
34 #-----------------------------------------------------------------
35 import copy
36 import sys
37 
38 # This is not required if you've installed pycparser into
39 # your site-packages/ with setup.py
40 #
41 sys.path.extend(['.', '..'])
42 
43 from pycparser import c_parser, c_ast
44 
45 
46 def explain_c_declaration(c_decl, expand_struct=False, expand_typedef=False):
47     """ Parses the declaration in c_decl and returns a text
48         explanation as a string.
49 
50         The last external node of the string is used, to allow earlier typedefs
51         for used types.
52 
53         expand_struct=True will spell out struct definitions recursively.
54         expand_typedef=True will expand typedef'd types.
55     """
56     parser = c_parser.CParser()
57 
58     try:
59         node = parser.parse(c_decl, filename='<stdin>')
60     except c_parser.ParseError:
61         e = sys.exc_info()[1]
62         return "Parse error:" + str(e)
63 
64     if (not isinstance(node, c_ast.FileAST) or
65         not isinstance(node.ext[-1], c_ast.Decl)
66         ):
67         return "Not a valid declaration"
68 
69     try:
70         expanded = expand_struct_typedef(node.ext[-1], node,
71                                          expand_struct=expand_struct,
72                                          expand_typedef=expand_typedef)
73     except Exception as e:
74         return "Not a valid declaration: " + str(e)
75 
76     return _explain_decl_node(expanded)
77 
78 
79 def _explain_decl_node(decl_node):
80     """ Receives a c_ast.Decl note and returns its explanation in
81         English.
82     """
83     storage = ' '.join(decl_node.storage) + ' ' if decl_node.storage else ''
84 
85     return (decl_node.name +
86             " is a " +
87             storage +
88             _explain_type(decl_node.type))
89 
90 
91 def _explain_type(decl):
92     """ Recursively explains a type decl node
93     """
94     typ = type(decl)
95 
96     if typ == c_ast.TypeDecl:
97         quals = ' '.join(decl.quals) + ' ' if decl.quals else ''
98         return quals + _explain_type(decl.type)
99     elif typ == c_ast.Typename or typ == c_ast.Decl:
100         return _explain_type(decl.type)
101     elif typ == c_ast.IdentifierType:
102         return ' '.join(decl.names)
103     elif typ == c_ast.PtrDecl:
104         quals = ' '.join(decl.quals) + ' ' if decl.quals else ''
105         return quals + 'pointer to ' + _explain_type(decl.type)
106     elif typ == c_ast.ArrayDecl:
107         arr = 'array'
108         if decl.dim: arr += '[%s]' % decl.dim.value
109 
110         return arr + " of " + _explain_type(decl.type)
111 
112     elif typ == c_ast.FuncDecl:
113         if decl.args:
114             params = [_explain_type(param) for param in decl.args.params]
115             args = ', '.join(params)
116         else:
117             args = ''
118 
119         return ('function(%s) returning ' % (args) +
120                 _explain_type(decl.type))
121 
122     elif typ == c_ast.Struct:
123         decls = [_explain_decl_node(mem_decl) for mem_decl in decl.decls]
124         members = ', '.join(decls)
125 
126         return ('struct%s ' % (' ' + decl.name if decl.name else '') +
127                 ('containing {%s}' % members if members else ''))
128 
129 
130 def expand_struct_typedef(cdecl, file_ast,
131                           expand_struct=False,
132                           expand_typedef=False):
133     """Expand struct & typedef and return a new expanded node."""
134     decl_copy = copy.deepcopy(cdecl)
135     _expand_in_place(decl_copy, file_ast, expand_struct, expand_typedef)
136     return decl_copy
137 
138 
139 def _expand_in_place(decl, file_ast, expand_struct=False, expand_typedef=False):
140     """Recursively expand struct & typedef in place, throw RuntimeError if
141        undeclared struct or typedef are used
142     """
143     typ = type(decl)
144 
145     if typ in (c_ast.Decl, c_ast.TypeDecl, c_ast.PtrDecl, c_ast.ArrayDecl):
146         decl.type = _expand_in_place(decl.type, file_ast, expand_struct,
147                                      expand_typedef)
148 
149     elif typ == c_ast.Struct:
150         if not decl.decls:
151             struct = _find_struct(decl.name, file_ast)
152             if not struct:
153                 raise RuntimeError('using undeclared struct %s' % decl.name)
154             decl.decls = struct.decls
155 
156         for i, mem_decl in enumerate(decl.decls):
157             decl.decls[i] = _expand_in_place(mem_decl, file_ast, expand_struct,
158                                              expand_typedef)
159         if not expand_struct:
160             decl.decls = []
161 
162     elif (typ == c_ast.IdentifierType and
163           decl.names[0] not in ('int', 'char')):
164         typedef = _find_typedef(decl.names[0], file_ast)
165         if not typedef:
166             raise RuntimeError('using undeclared type %s' % decl.names[0])
167 
168         if expand_typedef:
169             return typedef.type
170 
171     return decl
172 
173 
174 def _find_struct(name, file_ast):
175     """Receives a struct name and return declared struct object in file_ast
176     """
177     for node in file_ast.ext:
178         if (type(node) == c_ast.Decl and
179            type(node.type) == c_ast.Struct and
180            node.type.name == name):
181             return node.type
182 
183 
184 def _find_typedef(name, file_ast):
185     """Receives a type name and return typedef object in file_ast
186     """
187     for node in file_ast.ext:
188         if type(node) == c_ast.Typedef and node.name == name:
189             return node
190 
191 
192 if __name__ == "__main__":
193     if len(sys.argv) > 1:
194         c_decl  = sys.argv[1]
195     else:
196         c_decl = "char *(*(**foo[][8])())[];"
197 
198     print("Explaining the declaration: " + c_decl + "\n")
199     print(explain_c_declaration(c_decl) + "\n")
200