1# ------------------------------------------------------------------------------
2# pycparser: ast_transforms.py
3#
4# Some utilities used by the parser to create a friendlier AST.
5#
6# Eli Bendersky [https://eli.thegreenplace.net/]
7# License: BSD
8# ------------------------------------------------------------------------------
9
10from copy import deepcopy
11from typing import Any, cast
12
13from . import c_ast
14
15
16def fix_switch_cases(switch_node: c_ast.Switch) -> c_ast.Switch:
17 """The 'case' statements in a 'switch' come out of parsing with one
18 child node, so subsequent statements are just tucked to the parent
19 Compound. Additionally, consecutive (fall-through) case statements
20 come out messy. This is a peculiarity of the C grammar. The following:
21
22 switch (myvar) {
23 case 10:
24 k = 10;
25 p = k + 1;
26 return 10;
27 case 20:
28 case 30:
29 return 20;
30 default:
31 break;
32 }
33
34 Creates this tree (pseudo-dump):
35
36 Switch
37 ID: myvar
38 Compound:
39 Case 10:
40 k = 10
41 p = k + 1
42 return 10
43 Case 20:
44 Case 30:
45 return 20
46 Default:
47 break
48
49 The goal of this transform is to fix this mess, turning it into the
50 following:
51
52 Switch
53 ID: myvar
54 Compound:
55 Case 10:
56 k = 10
57 p = k + 1
58 return 10
59 Case 20:
60 Case 30:
61 return 20
62 Default:
63 break
64
65 A fixed AST node is returned. The argument may be modified.
66 """
67 assert isinstance(switch_node, c_ast.Switch)
68 if isinstance(switch_node.stmt, c_ast.AttributedStatement):
69 attributed = switch_node.stmt
70 switch_node.stmt = attributed.stmt
71 fix_switch_cases(switch_node)
72 attributed.stmt = switch_node.stmt
73 switch_node.stmt = attributed
74 return switch_node
75 if not isinstance(switch_node.stmt, c_ast.Compound):
76 return switch_node
77
78 # The new Compound child for the Switch, which will collect children in the
79 # correct order
80 new_compound = c_ast.Compound([], switch_node.stmt.coord)
81
82 # The last Case/Default node
83 last_case: c_ast.Case | c_ast.Default | None = None
84
85 # Goes over the children of the Compound below the Switch, adding them
86 # either directly below new_compound or below the last Case as appropriate
87 # (for `switch(cond) {}`, block_items would have been None)
88 for child in switch_node.stmt.block_items or []:
89 if isinstance(child, (c_ast.Case, c_ast.Default)):
90 # If it's a Case/Default:
91 # 1. Add it to the Compound and mark as "last case"
92 # 2. If its immediate child is also a Case or Default, promote it
93 # to a sibling.
94 new_compound.block_items.append(child)
95 _extract_nested_case(child, new_compound.block_items)
96 last_case = new_compound.block_items[-1]
97 else:
98 # Other statements are added as children to the last case, if it
99 # exists.
100 if last_case is None:
101 new_compound.block_items.append(child)
102 else:
103 last_case.stmts.append(child)
104
105 switch_node.stmt = new_compound
106 return switch_node
107
108
109def _extract_nested_case(
110 case_node: c_ast.Case | c_ast.Default, stmts_list: list[c_ast.Node]
111) -> None:
112 """Recursively extract consecutive Case statements that are made nested
113 by the parser and add them to the stmts_list.
114 """
115 if isinstance(case_node.stmts[0], (c_ast.Case, c_ast.Default)):
116 nested = case_node.stmts.pop()
117 stmts_list.append(nested)
118 _extract_nested_case(cast(Any, nested), stmts_list)
119
120
121def fix_atomic_specifiers(
122 decl: c_ast.Decl | c_ast.Typedef | c_ast.Typename,
123) -> c_ast.Decl | c_ast.Typedef | c_ast.Typename:
124 """Atomic specifiers like _Atomic(type) are unusually structured,
125 conferring a qualifier upon the contained type.
126
127 This function fixes a decl with atomic specifiers to have a sane AST
128 structure, by removing spurious Typename->TypeDecl pairs and attaching
129 the _Atomic qualifier in the right place.
130 """
131 # There can be multiple levels of _Atomic in a decl; fix them until a
132 # fixed point is reached.
133 while True:
134 decl, found = _fix_atomic_specifiers_once(decl)
135 if not found:
136 break
137
138 # Match the declaration's qualifiers to the innermost TypeDecl, as for
139 # ordinary declarations. Qualifiers moved onto pointers belong only on
140 # those PtrDecl nodes. Also restore the name lost with the wrapper TypeDecl.
141 typ: Any = decl
142 while not isinstance(typ, c_ast.TypeDecl):
143 try:
144 typ = typ.type
145 except AttributeError:
146 return decl
147 decl.quals = typ.quals[:]
148 if typ.declname is None:
149 typ.declname = decl.name
150
151 return decl
152
153
154def _fix_atomic_specifiers_once(
155 decl: c_ast.Decl | c_ast.Typedef | c_ast.Typename,
156) -> tuple[c_ast.Decl | c_ast.Typedef | c_ast.Typename, bool]:
157 """Performs one 'fix' round of atomic specifiers.
158 Returns (modified_decl, found) where found is True iff a fix was made.
159 """
160 parent: Any = decl
161 grandparent: Any = None
162 node: Any = decl.type
163 while node is not None:
164 if isinstance(node, c_ast.Typename) and "_Atomic" in node.quals:
165 break
166 try:
167 grandparent = parent
168 parent = node
169 node = node.type
170 except AttributeError:
171 # If we've reached a node without a `type` field, it means we won't
172 # find what we're looking for at this point; give up the search
173 # and return the original decl unmodified.
174 return decl, False
175
176 assert isinstance(parent, c_ast.TypeDecl)
177 assert grandparent is not None
178 # _Atomic(T) cannot wrap an array or function type (C11 6.7.2.4p3). Such a T
179 # has no `quals` to merge below, so report it instead of letting the attach
180 # raise AttributeError out of parse().
181 if isinstance(node.type, (c_ast.ArrayDecl, c_ast.FuncDecl)):
182 # Imported here to avoid a circular import at module load time.
183 from .c_parser import ParseError
184
185 raise ParseError(
186 f"{node.coord}: _Atomic cannot be applied to array or function type"
187 )
188 # Declaration specifiers are shared by comma-separated declarators. Copy
189 # the contained type before attaching qualifiers, coordinates, and a name.
190 typ = deepcopy(node.type)
191 if typ.coord is None:
192 # Preserve the declarator coord for _Atomic(T) so TypeDecl doesn't lose
193 # its location when we replace the wrapper Typename.
194 typ.coord = parent.coord
195 # Qualifiers outside _Atomic(T) apply to T itself. In particular, const in
196 # const _Atomic(int *) qualifies the pointer, not the pointed-to int.
197 typ.quals = parent.quals + typ.quals
198 if "_Atomic" not in typ.quals:
199 typ.quals.append("_Atomic")
200 cast(Any, grandparent).type = typ
201 return decl, True