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 typing import Any, List, Tuple, cast
11
12from . import c_ast
13
14
15def fix_switch_cases(switch_node: c_ast.Switch) -> c_ast.Switch:
16 """The 'case' statements in a 'switch' come out of parsing with one
17 child node, so subsequent statements are just tucked to the parent
18 Compound. Additionally, consecutive (fall-through) case statements
19 come out messy. This is a peculiarity of the C grammar. The following:
20
21 switch (myvar) {
22 case 10:
23 k = 10;
24 p = k + 1;
25 return 10;
26 case 20:
27 case 30:
28 return 20;
29 default:
30 break;
31 }
32
33 Creates this tree (pseudo-dump):
34
35 Switch
36 ID: myvar
37 Compound:
38 Case 10:
39 k = 10
40 p = k + 1
41 return 10
42 Case 20:
43 Case 30:
44 return 20
45 Default:
46 break
47
48 The goal of this transform is to fix this mess, turning it into the
49 following:
50
51 Switch
52 ID: myvar
53 Compound:
54 Case 10:
55 k = 10
56 p = k + 1
57 return 10
58 Case 20:
59 Case 30:
60 return 20
61 Default:
62 break
63
64 A fixed AST node is returned. The argument may be modified.
65 """
66 assert isinstance(switch_node, c_ast.Switch)
67 if not isinstance(switch_node.stmt, c_ast.Compound):
68 return switch_node
69
70 # The new Compound child for the Switch, which will collect children in the
71 # correct order
72 new_compound = c_ast.Compound([], switch_node.stmt.coord)
73
74 # The last Case/Default node
75 last_case: c_ast.Case | c_ast.Default | None = None
76
77 # Goes over the children of the Compound below the Switch, adding them
78 # either directly below new_compound or below the last Case as appropriate
79 # (for `switch(cond) {}`, block_items would have been None)
80 for child in switch_node.stmt.block_items or []:
81 if isinstance(child, (c_ast.Case, c_ast.Default)):
82 # If it's a Case/Default:
83 # 1. Add it to the Compound and mark as "last case"
84 # 2. If its immediate child is also a Case or Default, promote it
85 # to a sibling.
86 new_compound.block_items.append(child)
87 _extract_nested_case(child, new_compound.block_items)
88 last_case = new_compound.block_items[-1]
89 else:
90 # Other statements are added as children to the last case, if it
91 # exists.
92 if last_case is None:
93 new_compound.block_items.append(child)
94 else:
95 last_case.stmts.append(child)
96
97 switch_node.stmt = new_compound
98 return switch_node
99
100
101def _extract_nested_case(
102 case_node: c_ast.Case | c_ast.Default, stmts_list: List[c_ast.Node]
103) -> None:
104 """Recursively extract consecutive Case statements that are made nested
105 by the parser and add them to the stmts_list.
106 """
107 if isinstance(case_node.stmts[0], (c_ast.Case, c_ast.Default)):
108 nested = case_node.stmts.pop()
109 stmts_list.append(nested)
110 _extract_nested_case(cast(Any, nested), stmts_list)
111
112
113def fix_atomic_specifiers(
114 decl: c_ast.Decl | c_ast.Typedef,
115) -> c_ast.Decl | c_ast.Typedef:
116 """Atomic specifiers like _Atomic(type) are unusually structured,
117 conferring a qualifier upon the contained type.
118
119 This function fixes a decl with atomic specifiers to have a sane AST
120 structure, by removing spurious Typename->TypeDecl pairs and attaching
121 the _Atomic qualifier in the right place.
122 """
123 # There can be multiple levels of _Atomic in a decl; fix them until a
124 # fixed point is reached.
125 while True:
126 decl, found = _fix_atomic_specifiers_once(decl)
127 if not found:
128 break
129
130 # Make sure to add an _Atomic qual on the topmost decl if needed. Also
131 # restore the declname on the innermost TypeDecl (it gets placed in the
132 # wrong place during construction).
133 typ: Any = decl
134 while not isinstance(typ, c_ast.TypeDecl):
135 try:
136 typ = typ.type
137 except AttributeError:
138 return decl
139 if "_Atomic" in typ.quals and "_Atomic" not in decl.quals:
140 decl.quals.append("_Atomic")
141 if typ.declname is None:
142 typ.declname = decl.name
143
144 return decl
145
146
147def _fix_atomic_specifiers_once(
148 decl: c_ast.Decl | c_ast.Typedef,
149) -> Tuple[c_ast.Decl | c_ast.Typedef, bool]:
150 """Performs one 'fix' round of atomic specifiers.
151 Returns (modified_decl, found) where found is True iff a fix was made.
152 """
153 parent: Any = decl
154 grandparent: Any = None
155 node: Any = decl.type
156 while node is not None:
157 if isinstance(node, c_ast.Typename) and "_Atomic" in node.quals:
158 break
159 try:
160 grandparent = parent
161 parent = node
162 node = node.type
163 except AttributeError:
164 # If we've reached a node without a `type` field, it means we won't
165 # find what we're looking for at this point; give up the search
166 # and return the original decl unmodified.
167 return decl, False
168
169 assert isinstance(parent, c_ast.TypeDecl)
170 assert grandparent is not None
171 if node.type.coord is None:
172 # Preserve the declarator coord for _Atomic(T) so TypeDecl doesn't lose
173 # its location when we replace the wrapper Typename.
174 node.type.coord = parent.coord
175 cast(Any, grandparent).type = node.type
176 if "_Atomic" not in node.type.quals:
177 node.type.quals.append("_Atomic")
178 return decl, True