Skip to content

Commit 44023a3

Browse files
committed
Add basic support for �xcept clauses.
This seems to work nicely, but the DOT layout is awful.
1 parent 362c2c3 commit 44023a3

File tree

5 files changed

+119
-43
lines changed

5 files changed

+119
-43
lines changed

src/control-flow/cfg-defs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export type NodeType =
2929
| "SWITCH_CONDITION"
3030
| "SWITCH_MERGE"
3131
| "CASE_CONDITION";
32-
export type EdgeType = "regular" | "consequence" | "alternative";
32+
export type EdgeType = "regular" | "consequence" | "alternative" | "exception";
3333

3434
export type ClusterType =
3535
| "with"

src/control-flow/cfg-python.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,12 +208,29 @@ export class CFGBuilder {
208208
const getBlock = this.blockGetter(blockHandler);
209209

210210
const bodySyntax = this.getSyntax(match, "try-body");
211+
const exceptSyntaxMany = this.getSyntaxMany(match, "except-body");
211212
const finallySyntax = this.getSyntax(match, "finally-body");
212213
return this.withCluster("try-complex", (tryComplexCluster) => {
213214
const bodyBlock = this.withCluster("try", () =>
214215
getBlock(bodySyntax),
215216
) as BasicBlock;
216217

218+
// We handle `except` blocks before the `finally` block to support `return` handling.
219+
const exceptBlocks = exceptSyntaxMany.map((exceptSyntax) =>
220+
this.withCluster("except", () => getBlock(exceptSyntax) as BasicBlock),
221+
);
222+
// We attach the except-blocks to the top of the `try` body.
223+
// In the rendering, we will connect them to the side of the node, and use invisible lines for it.
224+
if (bodyBlock.entry) {
225+
const headNode = bodyBlock.entry;
226+
exceptBlocks.forEach((exceptBlock) => {
227+
if (exceptBlock.entry) {
228+
// Yes, this is effectively a head-to-head link. But that's ok.
229+
this.addEdge(headNode, exceptBlock.entry, "exception");
230+
}
231+
});
232+
}
233+
217234
const finallyBlock = this.withCluster("finally", () => {
218235
// Handle all the return statements from the try block
219236
if (finallySyntax) {
@@ -223,7 +240,7 @@ export class CFGBuilder {
223240
// so that we can link them.
224241
const duplicateFinallyBlock = getBlock(finallySyntax) as BasicBlock;
225242
// We also clone the return node, to place it _after_ the finally block
226-
// We also override the cluster node, pulling it up to the `try-complex`,
243+
// We also override the cluster node, pulling it up to the `try-complex`,
227244
// as the return is neither in a `try`, `except`, or `finally` context.
228245
const returnNodeClone = this.cloneNode(returnNode, {
229246
cluster: tryComplexCluster,
@@ -247,8 +264,16 @@ export class CFGBuilder {
247264
return finallyBlock;
248265
});
249266

250-
if (bodyBlock.exit && finallyBlock?.entry)
251-
this.addEdge(bodyBlock.exit, finallyBlock.entry);
267+
if (finallyBlock?.entry) {
268+
// Connect `try` to `finally`
269+
if (bodyBlock.exit) this.addEdge(bodyBlock.exit, finallyBlock.entry);
270+
271+
// Connect `except` to `finally`
272+
exceptBlocks.forEach((exceptBlock) => {
273+
if (exceptBlock.exit)
274+
this.addEdge(exceptBlock.exit, finallyBlock.entry as string);
275+
});
276+
}
252277

253278
return blockHandler.update({
254279
entry: bodyBlock.entry,

src/control-flow/render.ts

Lines changed: 51 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -172,37 +172,6 @@ function renderHierarchy(
172172
return dotContent;
173173
}
174174

175-
function formatStyle(style: { [attribute: string]: number | string }): string {
176-
return [...Object.entries(style)]
177-
.map(([name, value]) => {
178-
switch (typeof value) {
179-
case "number":
180-
return `${name}=${value};\n`;
181-
case "string":
182-
return `${name}="${value}";\n`;
183-
}
184-
})
185-
.join("");
186-
}
187-
188-
function clusterStyle(cluster: Cluster): string {
189-
const isSelfNested = cluster.type === cluster.parent?.type;
190-
const penwidth = isSelfNested ? 6 : 0;
191-
const color = "white";
192-
switch (cluster.type) {
193-
case "with":
194-
return formatStyle({ penwidth, color, bgcolor: "#ffddff" });
195-
case "try-complex":
196-
return formatStyle({ penwidth, color, bgcolor: "#ddddff" });
197-
case "try":
198-
return formatStyle({ penwidth, color, bgcolor: "#ddffdd" });
199-
case "finally":
200-
return formatStyle({ penwidth, color, bgcolor: "#ffffdd" });
201-
default:
202-
return "";
203-
}
204-
}
205-
206175
function renderSubgraphs(
207176
hierarchy: Hierarchy,
208177
verbose: boolean,
@@ -300,24 +269,69 @@ export function graphToDot(cfg: CFG, verbose: boolean = false): string {
300269
return dotContent;
301270
}
302271

272+
type DotAttributes = { [attribute: string]: number | string | undefined };
273+
function formatStyle(style: DotAttributes): string {
274+
return [...Object.entries(style)]
275+
.map(([name, value]) => {
276+
switch (typeof value) {
277+
case "number":
278+
return `${name}=${value};\n`;
279+
case "string":
280+
return `${name}="${value}";\n`;
281+
case "undefined":
282+
return "";
283+
}
284+
})
285+
.join("");
286+
}
287+
288+
function clusterStyle(cluster: Cluster): string {
289+
const isSelfNested = cluster.type === cluster.parent?.type;
290+
const penwidth = isSelfNested ? 6 : 0;
291+
const color = "white";
292+
switch (cluster.type) {
293+
case "with":
294+
return formatStyle({ penwidth, color, bgcolor: "#ffddff" });
295+
case "try-complex":
296+
return formatStyle({ penwidth, color, bgcolor: "#ddddff" });
297+
case "try":
298+
return formatStyle({ penwidth, color, bgcolor: "#ddffdd" });
299+
case "finally":
300+
return formatStyle({ penwidth, color, bgcolor: "#ffffdd" });
301+
case "except":
302+
return formatStyle({ penwidth, color, bgcolor: "#ffdddd" });
303+
default:
304+
return "";
305+
}
306+
}
307+
303308
function renderEdge(
304309
edge: string,
305310
source: string,
306311
target: string,
307312
topGraph: CFGGraph,
308313
) {
309314
const attributes = topGraph.getEdgeAttributes(edge);
310-
const penwidth = 1;
311-
let color = "blue";
315+
const dotAttrs: DotAttributes = {};
316+
dotAttrs.penwidth = 1;
317+
dotAttrs.color = "blue";
312318
switch (attributes.type) {
313319
case "consequence":
314-
color = "green";
320+
dotAttrs.color = "green";
315321
break;
316322
case "alternative":
317-
color = "red";
323+
dotAttrs.color = "red";
324+
break;
325+
case "regular":
326+
dotAttrs.color = "blue";
327+
break;
328+
case "exception":
329+
dotAttrs.style = "invis";
330+
dotAttrs.headport = "e";
331+
dotAttrs.tailport = "w";
318332
break;
319333
default:
320-
color = "blue";
334+
dotAttrs.color = "fuchsia";
321335
}
322-
return ` ${source} -> ${target} [penwidth=${penwidth} color=${color}];\n`;
336+
return ` ${source} -> ${target} [${formatStyle(dotAttrs)}];\n`;
323337
}

src/frontend/src/lib/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ export function processRecord(
136136
if (trim) cfg = trimFor(cfg);
137137
if (simplify) cfg = simplifyCFG(cfg, mergeNodeAttrs);
138138

139-
const dot = graphToDot(cfg, verbose);
139+
const dot = graphviz.dot(graphToDot(cfg, verbose), "canon");
140140
const svg = graphviz.dot(dot);
141141

142142
return { dot, ast, svg };

src/test/commentTestSamples/sample.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,42 @@ def WithNestedCluster():
156156
return
157157

158158

159+
160+
# exits: 0,
161+
# render: true
162+
def try_except():
163+
try:
164+
f()
165+
except:
166+
g()
167+
return
168+
169+
# exits: 0,
170+
# render: true
171+
def try_except_finally():
172+
try:
173+
f()
174+
except:
175+
g()
176+
finally:
177+
h()
178+
return
179+
180+
# exits: 0,
181+
# render: true
182+
def try_many_except_finally():
183+
try:
184+
f()
185+
except a:
186+
g()
187+
except b:
188+
aa()
189+
except: return
190+
finally:
191+
h()
192+
return
193+
194+
159195
# exits: 5,
160196
# render: true
161197
def try_finally():
@@ -174,4 +210,5 @@ def try_with_finally():
174210
return
175211
pass
176212
finally:
177-
pass
213+
pass
214+

0 commit comments

Comments
 (0)