File: cdecl.py

package info (click to toggle)
pycparser 3.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,252 kB
  • sloc: python: 8,647; ansic: 1,981; makefile: 12; sh: 11
file content (201 lines) | stat: -rw-r--r-- 6,848 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# -----------------------------------------------------------------
# pycparser: cdecl.py
#
# Example of the CDECL tool using pycparser. CDECL "explains" C type
# declarations in plain English.
#
# The AST generated by pycparser from the given declaration is traversed
# recursively to build the explanation. Note that the declaration must be a
# valid external declaration in C. As shown below, typedef can be optionally
# expanded.
#
# For example:
#
#   c_decl = 'typedef int Node; const Node* (*ar)[10];'
#
#   explain_c_declaration(c_decl)
#   => ar is a pointer to array[10] of pointer to const Node
#
# struct and typedef can be optionally expanded:
#
#   explain_c_declaration(c_decl, expand_typedef=True)
#   => ar is a pointer to array[10] of pointer to const int
#
#   c_decl = 'struct P {int x; int y;} p;'
#
#   explain_c_declaration(c_decl)
#   => p is a struct P
#
#   explain_c_declaration(c_decl, expand_struct=True)
#   => p is a struct P containing {x is a int, y is a int}
#
# Eli Bendersky [https://eli.thegreenplace.net/]
# License: BSD
# -----------------------------------------------------------------
import copy
import sys
from typing import Optional

# This is not required if you've installed pycparser into
# your site-packages/ with setup.py
sys.path.extend([".", ".."])

from pycparser import c_parser, c_ast


def explain_c_declaration(
    c_decl: str, expand_struct: bool = False, expand_typedef: bool = False
) -> str:
    """Parses the declaration in c_decl and returns a text
    explanation as a string.

    The last external node of the string is used, to allow earlier typedefs
    for used types.

    expand_struct=True will spell out struct definitions recursively.
    expand_typedef=True will expand typedef'd types.
    """
    parser = c_parser.CParser()

    try:
        node = parser.parse(c_decl, filename="<stdin>")
    except c_parser.ParseError:
        e = sys.exc_info()[1]
        return "Parse error:" + str(e)

    if not isinstance(node, c_ast.FileAST) or not isinstance(node.ext[-1], c_ast.Decl):
        return "Not a valid declaration"

    try:
        expanded = expand_struct_typedef(
            node.ext[-1],
            node,
            expand_struct=expand_struct,
            expand_typedef=expand_typedef,
        )
    except Exception as e:
        return "Not a valid declaration: " + str(e)

    return _explain_decl_node(expanded)


def _explain_decl_node(decl_node: c_ast.Decl) -> str:
    """Receives a c_ast.Decl note and returns its explanation in
    English.
    """
    storage = " ".join(decl_node.storage) + " " if decl_node.storage else ""

    return decl_node.name + " is a " + storage + _explain_type(decl_node.type)


def _explain_type(decl: c_ast.Node) -> str:
    """Recursively explains a type decl node"""
    match decl:
        case c_ast.TypeDecl():
            quals = " ".join(decl.quals) + " " if decl.quals else ""
            return quals + _explain_type(decl.type)
        case c_ast.Typename() | c_ast.Decl():
            return _explain_type(decl.type)
        case c_ast.IdentifierType():
            return " ".join(decl.names)
        case c_ast.PtrDecl():
            quals = " ".join(decl.quals) + " " if decl.quals else ""
            return quals + "pointer to " + _explain_type(decl.type)
        case c_ast.ArrayDecl():
            arr = "array"
            if decl.dim is not None:
                arr += f"[{decl.dim.value}]"
            return arr + " of " + _explain_type(decl.type)
        case c_ast.FuncDecl():
            if decl.args is not None:
                params = [_explain_type(param) for param in decl.args.params]
                args = ", ".join(params)
            else:
                args = ""
            return f"function({args}) returning " + _explain_type(decl.type)
        case c_ast.Struct():
            decls = [_explain_decl_node(mem_decl) for mem_decl in decl.decls]
            members = ", ".join(decls)
            struct_name = f" {decl.name}" if decl.name else ""
            contents = f"containing {{{members}}}" if members else ""
            return f"struct{struct_name} " + contents
        case _:
            return ""


def expand_struct_typedef(
    cdecl: c_ast.Decl,
    file_ast: c_ast.FileAST,
    expand_struct: bool = False,
    expand_typedef: bool = False,
) -> c_ast.Decl:
    """Expand struct & typedef and return a new expanded node."""
    decl_copy = copy.deepcopy(cdecl)
    _expand_in_place(decl_copy, file_ast, expand_struct, expand_typedef)
    return decl_copy


def _expand_in_place(
    decl: c_ast.Node,
    file_ast: c_ast.FileAST,
    expand_struct: bool = False,
    expand_typedef: bool = False,
) -> c_ast.Node:
    """Recursively expand struct & typedef in place, throw RuntimeError if
    undeclared struct or typedef are used
    """
    match decl:
        case c_ast.Decl() | c_ast.TypeDecl() | c_ast.PtrDecl() | c_ast.ArrayDecl():
            decl.type = _expand_in_place(
                decl.type, file_ast, expand_struct, expand_typedef
            )
        case c_ast.Struct():
            if not decl.decls:
                struct = _find_struct(decl.name, file_ast)
                if struct is None:
                    raise RuntimeError(f"using undeclared struct {decl.name}")
                decl.decls = struct.decls

            for i, mem_decl in enumerate(decl.decls):
                decl.decls[i] = _expand_in_place(
                    mem_decl, file_ast, expand_struct, expand_typedef
                )
            if not expand_struct:
                decl.decls = []
        case c_ast.IdentifierType() if decl.names[0] not in ("int", "char"):
            typedef = _find_typedef(decl.names[0], file_ast)
            if typedef is None:
                raise RuntimeError(f"using undeclared type {decl.names[0]}")
            if expand_typedef:
                return typedef.type
        case _:
            pass

    return decl


def _find_struct(name: str, file_ast: c_ast.FileAST) -> Optional[c_ast.Struct]:
    """Receives a struct name and return declared struct object in file_ast"""
    for node in file_ast.ext:
        if isinstance(node, c_ast.Decl) and isinstance(node.type, c_ast.Struct):
            if node.type.name == name:
                return node.type
    return None


def _find_typedef(name: str, file_ast: c_ast.FileAST) -> Optional[c_ast.Typedef]:
    """Receives a type name and return typedef object in file_ast"""
    for node in file_ast.ext:
        if isinstance(node, c_ast.Typedef) and node.name == name:
            return node
    return None


if __name__ == "__main__":
    if len(sys.argv) > 1:
        c_decl = sys.argv[1]
    else:
        c_decl = "char *(*(**foo[][8])())[];"

    print("Explaining the declaration: " + c_decl + "\n")
    print(explain_c_declaration(c_decl) + "\n")