summaryrefslogtreecommitdiff
path: root/chapter03
diff options
context:
space:
mode:
Diffstat (limited to 'chapter03')
-rw-r--r--chapter03/README.md72
-rw-r--r--chapter03/docs/03-graph1.json17
-rw-r--r--chapter03/docs/03-graph1.svg222
-rw-r--r--chapter03/docs/03-graph2.json18
-rw-r--r--chapter03/docs/03-graph2.svg226
-rw-r--r--chapter03/docs/03-graph3.json19
-rw-r--r--chapter03/docs/03-graph3.svg242
-rw-r--r--chapter03/docs/03-graph4.json17
-rw-r--r--chapter03/docs/03-graph4.svg211
-rw-r--r--chapter03/docs/03-graph5.json17
-rw-r--r--chapter03/docs/03-graph5.svg211
-rw-r--r--chapter03/docs/Makefile13
-rw-r--r--chapter03/src/main/java/com/seaofnodes/simple/type/Type.java5
13 files changed, 565 insertions, 725 deletions
diff --git a/chapter03/README.md b/chapter03/README.md
index e8944e12..5998efad 100644
--- a/chapter03/README.md
+++ b/chapter03/README.md
@@ -1,13 +1,15 @@
# Chapter 3: Variable Declarations
+[Previous: Chapter 2](../chapter02/README.md) |
+[Next: Chapter 4](../chapter04/README.md)
+
# Table of Contents
1. [Extensions to Intermediate Representation](#extensions-to-intermediate-representation)
2. [Symbol Tables](#symbol-tables)
3. [ScopeNode](#scopenode)
4. [Demonstration](#demonstration)
-5. [Identification of Basic Blocks in SoN graph](#identification-of-basic-blocks-in-son-graph)
-6. [Enabling peephole optimizations](#enabling-peephole-optimizations)
+5. [Enabling peephole optimizations](#enabling-peephole-optimizations)
You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter03) in a linear Git revision history on the [linear](https://github.com/SeaOfNodes/Simple/tree/linear) branch and [compare](https://github.com/SeaOfNodes/Simple/compare/linear-chapter02...linear-chapter03) it to the previous chapter.
@@ -29,7 +31,7 @@ Here is the [complete language grammar](docs/03-grammar.md) for this chapter.
## Extensions to Intermediate Representation
-To recap, our list of Nodes are:
+To recap, our list of Nodes is:
| Node Name | Type | Description | Inputs | Value |
|-----------|---------|------------------------------------|------------------------------------------------------------------|-------------------------------------------------------|
@@ -67,17 +69,17 @@ the stack of symbol tables, in lexical order.
A ScopeNode encapsulates the stack of symbol tables that are used to implement lexical scopes.
-* The nodes referenced by the names in a lexical scope become inputs to the ScopeNode; this makes ScopeNode a user of these values, thus keeping them alive while a particular lexical scope is alive.
- When the lexical scope ends, those inputs are removed. This is crucial because peepholes will remove dead Nodes and every new Node is effectively dead at the instance of creation without such a reference.
-* In [Chapter 5](../chapter05/README.md) when we implement branches, the ScopeNode gets duplicated at a branch point and then gets merged when the branch joins. This allows
- the names to evolve in each branch independently. At the merge point names that have diverged require a Phi node.
+* The nodes referenced by the names in a lexical scope become inputs to the ScopeNode; this makes ScopeNode a user of these values, thus keeping them alive while a particular lexical scope is alive.
+ When the lexical scope ends, those inputs are removed. This is crucial because peepholes will remove dead Nodes and every new Node is effectively dead at the instant of creation without such a reference.
+* In [Chapter 5](../chapter05/README.md) when we implement branches, the ScopeNode gets duplicated at a branch point and then gets merged when the branch joins. This allows
+ the names to evolve in each branch independently. At the merge point names that have diverged require a Phi node.
* In [Chapter 7](../chapter07/README.md) additional logic is deployed for handling Phis from back edges.
* The ScopeNode is used to track some other values such as the Control token starting from [Chapter 4](../chapter04/README.md), and whole memory starting from [Chapter 10a](../chapter10a/README.md). Special name bindings are used
for these, and each name is tracked across branches and then merged at merge points.
* In [Chapter 10a](../chapter10a/README.md) the ScopeNode is enhanced to also maintain the declared type for each name.
From an implementation standpoint, each symbol table is represented as a `HashMap<String,Integer>`, where the `String` is the variable name, and the `Integer` is an index into the inputs of the
-ScopeNode. To access the Node associated with a name, first the symbol table is consulted, and then the ScopeNode's inputs to find the required Node.
+ScopeNode. To access the Node associated with a name, first the symbol table is consulted, and then the ScopeNode's inputs to find the required Node.
The stack of symbol tables is represented as `Stack<HashMap<String,Integer>>`.
When a lexical scope is exited, we not only pop the symbol table representing the
@@ -93,9 +95,9 @@ that uses the variable) this is when the expression dies:
```
-In the visualization, we show the ScopeNode in a separate box running
-underneath the main IR graph; the stack of symbol tables is shown inside the
-box. We show the edge from each variable in the symbol table to the defining
+In the visualization, we show the ScopeNode in a separate purple box. Each
+binding is labelled with its lexical scope level. We show the edge from each
+variable in the symbol table to the defining
Node in the graph.
The ScopeNode is neither a Data Node nor a Control Node; it really is a utility for maintaining symbol tables, name lookups, plays a crucial role in deciding where Phi nodes are required.
@@ -105,7 +107,7 @@ A ScopeNode has no outputs.
## Demonstration
-We show the output from following code.
+We show the output from the following code.
```
01 int a=1;
@@ -118,7 +120,7 @@ We show the output from following code.
09 return c;
```
-* When we start parsing above, we start with a symbol table (at level 0).
+* When we start parsing the example above, we start with a symbol table (at level 0).
The variables `a`, `b`, and `c` are registered in this table.
* On line 4, we start a nested scope. At this point, a new symbol table is pushed (at level 1).
* On line 5, a variable `b` is declared. `b` is registered in the symbol table at level 1. This then
@@ -130,8 +132,8 @@ result of this example is `return 4;`.
![Graph1](./docs/03-graph1.svg)
-The diagram shows the ScopeNode and the symbol tables in the bottom cluster.
-Each table is annotated by its scope level, and shows the variables defined in that scope.
+The diagram shows the ScopeNode's bindings with their scope levels. The two
+bindings for `b` belong to different lexical scopes.
* When we exit the nested scope on line 8, the symbol table at level 1 is popped.
* At line 9, our graph looks like this:
@@ -140,43 +142,43 @@ Each table is annotated by its scope level, and shows the variables defined in t
As we see, only 1 symbol table is present at this point.
-We can also have multiple scopes at the same level during the execution of the program,
-but only one will be active at a time because the symbol table can only hold one symbol table
+We can also have multiple scopes at the same level during the execution of the program,
+but only one will be active at a time because the symbol table can only hold one symbol table
for each scope level.
For example:
```
-01 int a=1;
-02 int b=2;
-03 int c=0;
-04 {
-05 int b=5;
-06 c=a+b;
-08 }
+01 int a=1;
+02 int b=2;
+03 int c=0;
+04 {
+05 int b=5;
+06 c=a+b;
+08 }
09 {
-10 int e=6;
-11 c = a+e;
-13 }
-14 return c;
+10 int e=6;
+11 c = a+e;
+13 }
+14 return c;
```
Here both scopes starting from `(04-08)` and `(09-13)` are at the same level:
```java
public final Stack<HashMap<String, Integer>> _scopes;
```
-The `_scopes` stack holds nested scopes, with index 0 representing the highest (global)
-scope and deeper indices representing more nested scopes.
-Popping returns the current scope and moves to the outer one.
+The `_scopes` stack holds nested scopes, with index 0 representing the highest (global)
+scope and deeper indices representing more nested scopes.
+Popping returns the current scope and moves to the outer one.
There is a direct one-to-one mapping between each index and its corresponding scope.
-The first nested scope(04-08):
+The first nested scope (04-08):
![Graph4](./docs/03-graph4.svg)
-The second nested scope(09-13):
+The second nested scope (09-13):
![Graph5](./docs/03-graph5.svg)
- - In both cases the index which represents the deepness of the scope is 1.
+ - In both cases the index which represents the depth of the scope is 1.
## Enabling peephole optimizations
Once we enable peephole optimizations, the parser is able to reduce all the expressions down to a
@@ -190,7 +192,7 @@ constant. This is illustrated nicely below.
05 return (x0-x1)*(x0-x1) + (y0-y1)*(y0-y1);
```
-The graph for above, is displayed below.
+The graph for the example above is displayed below.
![Graph2](./docs/03-graph3.svg)
diff --git a/chapter03/docs/03-graph1.json b/chapter03/docs/03-graph1.json
new file mode 100644
index 00000000..5dce5431
--- /dev/null
+++ b/chapter03/docs/03-graph1.json
@@ -0,0 +1,17 @@
+{
+ "ver": 1,
+ "comp": "chapter03/docs/03-graph1.svg",
+ "step": 1,
+ "roots": [],
+ "scope": 0,
+ "nodes": [
+ {"id": 1, "label": "Scope", "type": null, "kind": "SCOPE", "edges": [{"idx": 0, "def": 3, "role": "ASSOC", "label": "0: a"}, {"idx": 1, "def": 4, "role": "ASSOC", "label": "0: b"}, {"idx": 2, "def": 7, "role": "ASSOC", "label": "0: c"}, {"idx": 3, "def": 6, "role": "ASSOC", "label": "1: b"}], "proj": null},
+ {"id": 2, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null},
+ {"id": 3, "label": "#1", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 4, "label": "#2", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 6, "label": "#3", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 7, "label": "+", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 3, "role": "DATA", "label": null}, {"idx": 2, "def": 6, "role": "DATA", "label": null}], "proj": null}
+ ],
+ "groups": [
+ ]
+}
diff --git a/chapter03/docs/03-graph1.svg b/chapter03/docs/03-graph1.svg
index ab6f9600..9ad933d5 100644
--- a/chapter03/docs/03-graph1.svg
+++ b/chapter03/docs/03-graph1.svg
@@ -1,136 +1,86 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
- "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<!-- Generated by graphviz version 2.43.0 (0)
- -->
-<!-- Title: chapter03 Pages: 1 -->
-<svg width="238pt" height="300pt"
- viewBox="0.00 0.00 238.00 300.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 296)">
-<title>chapter03</title>
-<polygon fill="white" stroke="transparent" points="-4,4 -4,-296 234,-296 234,4 -4,4"/>
-<g id="clust1" class="cluster">
-<title>cluster_Nodes</title>
-<polygon fill="none" stroke="black" points="8,-88 8,-284 222,-284 222,-88 8,-88"/>
-</g>
-<g id="clust2" class="cluster">
-<title>cluster_Scope1_0</title>
-<polygon fill="none" stroke="black" points="36,-8 36,-76 210,-76 210,-8 36,-8"/>
-</g>
-<g id="clust3" class="cluster">
-<title>cluster_Scope1_1</title>
-<polygon fill="none" stroke="black" points="132,-16 132,-68 202,-68 202,-16 132,-16"/>
-</g>
-<!-- Start2 -->
-<g id="node1" class="node">
-<title>Start2</title>
-<polygon fill="yellow" stroke="black" points="142,-276 88,-276 88,-240 142,-240 142,-276"/>
-<text text-anchor="middle" x="115" y="-254.3" font-family="Times,serif" font-size="14.00">Start</text>
-</g>
-<!-- Con_3 -->
-<g id="node2" class="node">
-<title>Con_3</title>
-<ellipse fill="none" stroke="black" cx="43" cy="-186" rx="27" ry="18"/>
-<text text-anchor="middle" x="43" y="-182.3" font-family="Times,serif" font-size="14.00">#1</text>
-</g>
-<!-- Con_3&#45;&gt;Start2 -->
-<g id="edge1" class="edge">
-<title>Con_3&#45;&gt;Start2</title>
-<path fill="none" stroke="black" stroke-dasharray="1,5" d="M57.57,-201.17C66.92,-210.25 79.3,-222.29 90.17,-232.86"/>
-<polygon fill="black" stroke="black" points="87.79,-235.43 97.4,-239.89 92.67,-230.41 87.79,-235.43"/>
-<text text-anchor="middle" x="54.57" y="-203.77" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
-</g>
-<!-- Con_4 -->
-<g id="node3" class="node">
-<title>Con_4</title>
-<ellipse fill="none" stroke="black" cx="115" cy="-186" rx="27" ry="18"/>
-<text text-anchor="middle" x="115" y="-182.3" font-family="Times,serif" font-size="14.00">#2</text>
-</g>
-<!-- Con_4&#45;&gt;Start2 -->
-<g id="edge2" class="edge">
-<title>Con_4&#45;&gt;Start2</title>
-<path fill="none" stroke="black" stroke-dasharray="1,5" d="M115,-204.3C115,-212.02 115,-221.29 115,-229.89"/>
-<polygon fill="black" stroke="black" points="111.5,-229.9 115,-239.9 118.5,-229.9 111.5,-229.9"/>
-<text text-anchor="middle" x="112" y="-206.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
-</g>
-<!-- Con_6 -->
-<g id="node4" class="node">
-<title>Con_6</title>
-<ellipse fill="none" stroke="black" cx="187" cy="-186" rx="27" ry="18"/>
-<text text-anchor="middle" x="187" y="-182.3" font-family="Times,serif" font-size="14.00">#3</text>
-</g>
-<!-- Con_6&#45;&gt;Start2 -->
-<g id="edge3" class="edge">
-<title>Con_6&#45;&gt;Start2</title>
-<path fill="none" stroke="black" stroke-dasharray="1,5" d="M172.43,-201.17C163.08,-210.25 150.7,-222.29 139.83,-232.86"/>
-<polygon fill="black" stroke="black" points="137.33,-230.41 132.6,-239.89 142.21,-235.43 137.33,-230.41"/>
-<text text-anchor="middle" x="169.43" y="-203.77" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
-</g>
-<!-- Add7 -->
-<g id="node5" class="node">
-<title>Add7</title>
-<ellipse fill="none" stroke="black" cx="136" cy="-114" rx="27" ry="18"/>
-<text text-anchor="middle" x="136" y="-110.3" font-family="Times,serif" font-size="14.00">+</text>
-</g>
-<!-- Add7&#45;&gt;Con_3 -->
-<g id="edge4" class="edge">
-<title>Add7&#45;&gt;Con_3</title>
-<path fill="none" stroke="black" d="M118.51,-128.17C104.48,-138.73 84.58,-153.7 68.74,-165.63"/>
-<polygon fill="black" stroke="black" points="66.3,-163.08 60.41,-171.89 70.51,-168.68 66.3,-163.08"/>
-<text text-anchor="middle" x="115.51" y="-130.77" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
-</g>
-<!-- Add7&#45;&gt;Con_6 -->
-<g id="edge5" class="edge">
-<title>Add7&#45;&gt;Con_6</title>
-<path fill="none" stroke="black" d="M147.32,-130.54C153.92,-139.6 162.38,-151.21 169.8,-161.39"/>
-<polygon fill="black" stroke="black" points="167,-163.49 175.71,-169.51 172.65,-159.36 167,-163.49"/>
-<text text-anchor="middle" x="144.32" y="-133.14" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
-</g>
-<!-- Scope1_0 -->
-<g id="node6" class="node">
-<title>Scope1_0</title>
-<polygon fill="cyan" stroke="transparent" points="53,-31 53,-52 69,-52 69,-31 53,-31"/>
-<polygon fill="none" stroke="black" points="53,-31 53,-52 69,-52 69,-31 53,-31"/>
-<text text-anchor="start" x="56" y="-37.8" font-family="Times,serif" font-size="14.00">0</text>
-<polygon fill="none" stroke="black" points="69,-31 69,-52 84,-52 84,-31 69,-31"/>
-<text text-anchor="start" x="72" y="-37.8" font-family="Times,serif" font-size="14.00">a</text>
-<polygon fill="none" stroke="black" points="84,-31 84,-52 100,-52 100,-31 84,-31"/>
-<text text-anchor="start" x="87" y="-37.8" font-family="Times,serif" font-size="14.00">b</text>
-<polygon fill="none" stroke="black" points="100,-31 100,-52 114,-52 114,-31 100,-31"/>
-<text text-anchor="start" x="103" y="-37.8" font-family="Times,serif" font-size="14.00">c</text>
-</g>
-<!-- Scope1_0&#45;&gt;Con_3 -->
-<g id="edge6" class="edge">
-<title>Scope1_0:Scope1_0_a&#45;&gt;Con_3</title>
-<path fill="none" stroke="cornflowerblue" stroke-dasharray="5,2" d="M76,-53C76,-90.54 63.29,-132.33 53.64,-158.64"/>
-<polygon fill="cornflowerblue" stroke="cornflowerblue" points="50.27,-157.66 50,-168.25 56.81,-160.14 50.27,-157.66"/>
-</g>
-<!-- Scope1_0&#45;&gt;Con_4 -->
-<g id="edge7" class="edge">
-<title>Scope1_0:Scope1_0_b&#45;&gt;Con_4</title>
-<path fill="none" stroke="cornflowerblue" stroke-dasharray="5,2" d="M92,-53C92,-88.29 93.39,-97.33 100,-132 101.65,-140.68 104.07,-150 106.48,-158.37"/>
-<polygon fill="cornflowerblue" stroke="cornflowerblue" points="103.14,-159.42 109.37,-168 109.85,-157.41 103.14,-159.42"/>
-</g>
-<!-- Scope1_0&#45;&gt;Add7 -->
-<g id="edge8" class="edge">
-<title>Scope1_0:Scope1_0_c&#45;&gt;Add7</title>
-<path fill="none" stroke="cornflowerblue" stroke-dasharray="5,2" d="M107,-53C107,-65.71 112.34,-78.55 118.43,-89.08"/>
-<polygon fill="cornflowerblue" stroke="cornflowerblue" points="115.57,-91.1 123.87,-97.68 121.48,-87.35 115.57,-91.1"/>
-</g>
-<!-- Scope1_1 -->
-<g id="node7" class="node">
-<title>Scope1_1</title>
-<polygon fill="cyan" stroke="transparent" points="151,-31 151,-52 167,-52 167,-31 151,-31"/>
-<polygon fill="none" stroke="black" points="151,-31 151,-52 167,-52 167,-31 151,-31"/>
-<text text-anchor="start" x="154" y="-37.8" font-family="Times,serif" font-size="14.00">1</text>
-<polygon fill="none" stroke="black" points="167,-31 167,-52 183,-52 183,-31 167,-31"/>
-<text text-anchor="start" x="170" y="-37.8" font-family="Times,serif" font-size="14.00">b</text>
-</g>
-<!-- Scope1_1&#45;&gt;Con_6 -->
-<g id="edge9" class="edge">
-<title>Scope1_1:Scope1_1_b&#45;&gt;Con_6</title>
-<path fill="none" stroke="cornflowerblue" stroke-dasharray="5,2" d="M175,-53C175,-89.4 179.58,-131.23 183.08,-157.87"/>
-<polygon fill="cornflowerblue" stroke="cornflowerblue" points="179.64,-158.52 184.46,-167.96 186.58,-157.58 179.64,-158.52"/>
-</g>
-</g>
-</svg>
+<svg aria-label="Compiler graph" width="888" height="208" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 888 208" style="font-family: sans-serif;"><style>
+ html, body { height:100%; margin:0; font-family:sans-serif; }
+ .viewer { height:100%; display:grid; grid-template-rows:auto minmax(0,1fr) auto; }
+ .source { padding:8px; border-bottom:1px solid #ccc; }
+ #program { width:min(600px,80vw); vertical-align:middle; }
+ #status { margin-top:4px; }
+ #graph { min-height:0; overflow:hidden; position:relative; background:#fafbfc; }
+ #elk { width:100%; height:100%; }
+ #graph svg { display:block; }
+ .tools { display:flex; flex-wrap:wrap; align-items:center; gap:14px; margin-top:8px; font-size:12px; }
+ .hint { color:#616c78; }
+ .ctrl { color:#b63838; } .mem { color:#2867b2; } .data { color:#536477; }
+ .edge { fill:none; stroke-width:1.5; }
+ .edge.CTRL { stroke-width:2; }
+ .edge.ASSOC { stroke-dasharray:4 4; }
+ .edge.PARSER { stroke-dasharray:5 4; }
+ .parser rect { fill:#f6f0ff; stroke:#7956a5; stroke-width:1.5; stroke-dasharray:5 4; }
+ .edge.selected { stroke-width:3.5; }
+ .edge.bundle { stroke-width:4; }
+ .edge.bundle.selected { stroke-width:6; }
+ .edge-hit { fill:none; stroke:transparent; stroke-width:12; vector-effect:non-scaling-stroke; pointer-events:stroke; cursor:pointer; }
+ .edge-jump { fill:transparent; pointer-events:all; cursor:pointer; }
+ .node { cursor:pointer; }
+ .box { stroke:#758191; stroke-width:1; }
+ .node.held .box { stroke-dasharray:5 3; }
+ .node.near .box { stroke:#c18221; stroke-width:2; }
+ .node.focus .box { stroke:#9c4900; stroke-width:3; }
+ .node.selected .box { stroke:#1569b9; stroke-width:3; }
+ .node.folded .box { stroke-width:4; }
+ .node.folding .box { fill:#f4ccd7; }
+ .fold-tag { font-size:9px; font-weight:700; fill:#963751; }
+ .peep-area { fill:#fff3d9; fill-opacity:.8; stroke:#c18221; stroke-width:2; pointer-events:none; }
+ .node.peep-edit .box { stroke:#9c4900; stroke-width:3; }
+ .node.error .box { stroke:#b42318; stroke-width:4; }
+ #event.error { color:#b42318; font-weight:600; white-space:pre-wrap; overflow-wrap:anywhere; }
+ .peep-ghosts { pointer-events:none; }
+ .region-box { stroke:#9da9b8; stroke-width:1.5; }
+ .fold-btn { cursor:pointer; }
+ .fold-btn rect { fill:white; stroke:#596674; }
+ .fold-btn text { font-size:14px; fill:#152330; }
+ .fold-btn:focus rect { stroke:#1569b9; stroke-width:2; }
+ .shortcut { cursor:pointer; }
+ .shortcut path { fill:none; stroke-width:1.5; }
+ .shortcut rect { fill:#fff8ec; stroke:#b63838; stroke-dasharray:3 2; }
+ .shortcut text { font-size:11px; fill:#783030; }
+ .shortcut.selected rect, .shortcut:focus rect { stroke-width:2.5; }
+ .label { font-size:12px; fill:#152330; font-weight:600; }
+ .node-id { font-size:10px; fill:#596674; font-weight:400; }
+ .type { font-size:11px; fill:#455768; }
+ .port { stroke:#536477; stroke-width:1; }
+ .row { stroke:#b6afcf; stroke-width:1; }
+ .bind { font-size:12px; fill:#152330; }
+ .slot { font-size:10px; fill:#364352; paint-order:stroke; stroke:#fafbfc; stroke-width:3; }
+ #detail { position:absolute; top:8px; right:8px; margin:0; padding:12px; max-width:320px;
+ max-height:45%; overflow:auto; white-space:pre-wrap; overflow-wrap:anywhere;
+ background:#fffffff2; border:1px solid #bac5d0; border-radius:4px; font-size:12px; }
+ .controls { display:flex; flex-wrap:wrap; justify-content:center; align-items:center; gap:6px; padding:6px; }
+ .button { color:white; background:black; font-size:20px; min-width:30px; }
+ .button:disabled { background:grey; }
+ .count { font-size:20px; min-width:35px; text-align:center; }
+ #N { width:6ch; }
+ #scrub { width:clamp(120px,35vw,600px); }
+ </style><defs><marker id="arrow-CTRL" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#b63838"/></marker><marker id="arrow-DATA" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#536477"/></marker><marker id="arrow-MEM" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#2867b2"/></marker><marker id="arrow-ASSOC" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#9396a3"/></marker><marker id="arrow-PARSER" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#7956a5"/></marker></defs><rect width="100%" height="100%" fill="rgb(250, 251, 252)"/><g><g class="regions" style="opacity: 1;"/><rect class="peep-area" rx="36" style="opacity: 0;"/><g class="edges"><path class="edge ASSOC" id="e1i0" d="M236,77 C236,101 696,74 696,98" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#1[0] → #3 · ASSOC · 0: a</title></path><path class="edge ASSOC" id="e1i1" d="M388,77 C388,101 744,74 744,98" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#1[1] → #4 · ASSOC · 0: b</title></path><path class="edge ASSOC" id="e1i2" d="M236,181 C236,205 792,74 792,98" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#1[2] → #7 · ASSOC · 0: c</title></path><path class="edge ASSOC" id="e1i3" d="M540,77 C540,101 840,74 840,98" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#1[3] → #6 · ASSOC · 1: b</title></path><path class="edge ASSOC" id="e3i0" d="M84,77 C84,101 236,3 236,27" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#3[0] → #2 · ASSOC</title></path><path class="edge ASSOC" id="e4i0" d="M84,77 C84,101 388,3 388,27" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#4[0] → #2 · ASSOC</title></path><path class="edge ASSOC" id="e6i0" d="M84,77 C84,101 540,3 540,27" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#6[0] → #2 · ASSOC</title></path><path class="edge DATA" id="e7i1" d="M236,80 L236,128" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#7[1] → #3 · DATA</title></path><path class="edge DATA" id="e7i2" d="M540,80 L540,90 L266,90 L266,128" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#7[2] → #6 · DATA</title></path></g><g class="edge-hits"/><g class="parser" style="opacity: 1;"/><g class="nodes"><g class="node" id="n1" data-id="1" transform="translate(672,98)" style="opacity: 1;"><path class="box" d="M4,0H188Q192,0 192,4V52Q192,56 188,56H4Q0,56 0,52V4Q0,0 4,0Z" fill="#e5e0ff"/><text class="label" text-anchor="middle" x="96" y="48"><tspan>Scope</tspan><tspan class="node-id">  #1</tspan></text><text class="type" text-anchor="middle" y="35" x="96"/><title>#1 Scope
+Type not computed
+
+[0] → #3 ASSOC 0: a
+[1] → #4 ASSOC 0: b
+[2] → #7 ASSOC 0: c
+[3] → #6 ASSOC 1: b</title><line class="row" x1="0" x2="192" y1="30" y2="30"/><line class="row" x1="48" x2="48" y1="0" y2="30"/><line class="row" x1="96" x2="96" y1="0" y2="30"/><line class="row" x1="144" x2="144" y1="0" y2="30"/><text class="bind" x="24" y="20" text-anchor="middle">0: a</text><text class="bind" x="72" y="20" text-anchor="middle">0: b</text><text class="bind" x="120" y="20" text-anchor="middle">0: c</text><text class="bind" x="168" y="20" text-anchor="middle">1: b</text><circle class="port" cx="24" cy="0" r="3" fill="#536477"/><circle class="port" cx="72" cy="0" r="3" fill="#536477"/><circle class="port" cx="120" cy="0" r="3" fill="#536477"/><circle class="port" cx="168" cy="0" r="3" fill="#536477"/><text class="slot" x="24" y="-7" text-anchor="middle">0</text><text class="slot" x="72" y="-7" text-anchor="middle">1</text><text class="slot" x="120" y="-7" text-anchor="middle">2</text><text class="slot" x="168" y="-7" text-anchor="middle">3</text></g><g class="node" id="n2" data-id="2" transform="translate(24,30)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Start</tspan><tspan class="node-id">  #2</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#2 Start
+Type not computed
+
+</title></g><g class="node" id="n3" data-id="3" transform="translate(176,30)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#1</tspan><tspan class="node-id">  #3</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#3 #1
+Type not computed
+
+[0] → #2 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n4" data-id="4" transform="translate(328,30)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#2</tspan><tspan class="node-id">  #4</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#4 #2
+Type not computed
+
+[0] → #2 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n6" data-id="6" transform="translate(480,30)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#3</tspan><tspan class="node-id">  #6</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#6 #3
+Type not computed
+
+[0] → #2 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n7" data-id="7" transform="translate(176,134)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>+</tspan><tspan class="node-id">  #7</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#7 +
+Type not computed
+
+[0] → — DATA
+[1] → #3 DATA
+[2] → #6 DATA</title><circle class="port" cx="30" cy="-3" r="3" fill="white"/><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><circle class="port" cx="90" cy="-3" r="3" fill="#536477"/><text class="slot" x="30" y="-10" text-anchor="middle">0</text><text class="slot" x="60" y="-10" text-anchor="middle">1</text><text class="slot" x="90" y="-10" text-anchor="middle">2</text></g></g><g class="region-heads" style="opacity: 1;"/><g class="shortcuts" style="opacity: 1;"/><g class="peep-ghosts" pointer-events="none"/></g></svg>
diff --git a/chapter03/docs/03-graph2.json b/chapter03/docs/03-graph2.json
new file mode 100644
index 00000000..80397161
--- /dev/null
+++ b/chapter03/docs/03-graph2.json
@@ -0,0 +1,18 @@
+{
+ "ver": 1,
+ "comp": "chapter03/docs/03-graph2.svg",
+ "step": 1,
+ "roots": [],
+ "scope": 0,
+ "nodes": [
+ {"id": 1, "label": "Scope", "type": null, "kind": "SCOPE", "edges": [{"idx": 0, "def": 3, "role": "ASSOC", "label": "0: a"}, {"idx": 1, "def": 4, "role": "ASSOC", "label": "0: b"}, {"idx": 2, "def": 7, "role": "ASSOC", "label": "0: c"}], "proj": null},
+ {"id": 2, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null},
+ {"id": 3, "label": "#1", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 4, "label": "#2", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 6, "label": "#3", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 7, "label": "+", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "DATA", "label": null}, {"idx": 1, "def": 3, "role": "DATA", "label": null}, {"idx": 2, "def": 6, "role": "DATA", "label": null}], "proj": null},
+ {"id": 8, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 2, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "DATA", "label": null}], "proj": null}
+ ],
+ "groups": [
+ ]
+}
diff --git a/chapter03/docs/03-graph2.svg b/chapter03/docs/03-graph2.svg
index 0a539c6a..d59e5dc0 100644
--- a/chapter03/docs/03-graph2.svg
+++ b/chapter03/docs/03-graph2.svg
@@ -1,137 +1,89 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
- "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<!-- Generated by graphviz version 2.43.0 (0)
- -->
-<!-- Title: chapter03 Pages: 1 -->
-<svg width="360pt" height="292pt"
- viewBox="0.00 0.00 360.00 292.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 288)">
-<title>chapter03</title>
-<polygon fill="white" stroke="transparent" points="-4,4 -4,-288 356,-288 356,4 -4,4"/>
-<g id="clust1" class="cluster">
-<title>cluster_Nodes</title>
-<polygon fill="none" stroke="black" points="8,-8 8,-276 242,-276 242,-8 8,-8"/>
-</g>
-<g id="clust2" class="cluster">
-<title>cluster_Scope1_0</title>
-<polygon fill="none" stroke="black" points="250,-8 250,-60 344,-60 344,-8 250,-8"/>
-</g>
-<!-- Start2 -->
-<g id="node1" class="node">
-<title>Start2</title>
-<polygon fill="yellow" stroke="black" points="126,-268 72,-268 72,-232 126,-232 126,-268"/>
-<text text-anchor="middle" x="99" y="-246.3" font-family="Times,serif" font-size="14.00">Start</text>
-</g>
-<!-- Con_3 -->
-<g id="node2" class="node">
-<title>Con_3</title>
-<ellipse fill="none" stroke="black" cx="135" cy="-178" rx="27" ry="18"/>
-<text text-anchor="middle" x="135" y="-174.3" font-family="Times,serif" font-size="14.00">#1</text>
-</g>
-<!-- Con_3&#45;&gt;Start2 -->
-<g id="edge1" class="edge">
-<title>Con_3&#45;&gt;Start2</title>
-<path fill="none" stroke="black" stroke-dasharray="1,5" d="M126.65,-195.24C122.46,-203.39 117.27,-213.47 112.53,-222.69"/>
-<polygon fill="black" stroke="black" points="109.27,-221.38 107.81,-231.88 115.49,-224.58 109.27,-221.38"/>
-<text text-anchor="middle" x="123.65" y="-197.84" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
-</g>
-<!-- Con_4 -->
-<g id="node3" class="node">
-<title>Con_4</title>
-<ellipse fill="none" stroke="black" cx="207" cy="-178" rx="27" ry="18"/>
-<text text-anchor="middle" x="207" y="-174.3" font-family="Times,serif" font-size="14.00">#2</text>
-</g>
-<!-- Con_4&#45;&gt;Start2 -->
-<g id="edge2" class="edge">
-<title>Con_4&#45;&gt;Start2</title>
-<path fill="none" stroke="black" stroke-dasharray="1,5" d="M188.19,-191.19C173.23,-200.89 151.89,-214.72 133.84,-226.42"/>
-<polygon fill="black" stroke="black" points="131.77,-223.59 125.28,-231.97 135.58,-229.46 131.77,-223.59"/>
-<text text-anchor="middle" x="185.19" y="-193.79" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
-</g>
-<!-- Con_6 -->
-<g id="node4" class="node">
-<title>Con_6</title>
-<ellipse fill="none" stroke="black" cx="63" cy="-178" rx="27" ry="18"/>
-<text text-anchor="middle" x="63" y="-174.3" font-family="Times,serif" font-size="14.00">#3</text>
-</g>
-<!-- Con_6&#45;&gt;Start2 -->
-<g id="edge3" class="edge">
-<title>Con_6&#45;&gt;Start2</title>
-<path fill="none" stroke="black" stroke-dasharray="1,5" d="M71.35,-195.24C75.54,-203.39 80.73,-213.47 85.47,-222.69"/>
-<polygon fill="black" stroke="black" points="82.51,-224.58 90.19,-231.88 88.73,-221.38 82.51,-224.58"/>
-<text text-anchor="middle" x="68.35" y="-197.84" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
-</g>
-<!-- Add7 -->
-<g id="node5" class="node">
-<title>Add7</title>
-<ellipse fill="none" stroke="black" cx="71" cy="-106" rx="27" ry="18"/>
-<text text-anchor="middle" x="71" y="-102.3" font-family="Times,serif" font-size="14.00">+</text>
-</g>
-<!-- Add7&#45;&gt;Con_3 -->
-<g id="edge4" class="edge">
-<title>Add7&#45;&gt;Con_3</title>
-<path fill="none" stroke="black" d="M84.57,-121.85C93.31,-131.4 104.83,-144.01 114.66,-154.75"/>
-<polygon fill="black" stroke="black" points="112.12,-157.16 121.45,-162.18 117.28,-152.43 112.12,-157.16"/>
-<text text-anchor="middle" x="81.57" y="-124.45" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
-</g>
-<!-- Add7&#45;&gt;Con_6 -->
-<g id="edge5" class="edge">
-<title>Add7&#45;&gt;Con_6</title>
-<path fill="none" stroke="black" d="M69.02,-124.3C68.14,-132.02 67.08,-141.29 66.1,-149.89"/>
-<polygon fill="black" stroke="black" points="62.61,-149.56 64.95,-159.9 69.57,-150.36 62.61,-149.56"/>
-<text text-anchor="middle" x="66.02" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
-</g>
-<!-- Return8 -->
-<g id="node6" class="node">
-<title>Return8</title>
-<polygon fill="yellow" stroke="black" points="89,-52 23,-52 23,-16 89,-16 89,-52"/>
-<text text-anchor="middle" x="56" y="-30.3" font-family="Times,serif" font-size="14.00">Return</text>
-</g>
-<!-- Return8&#45;&gt;Start2 -->
-<g id="edge6" class="edge">
-<title>Return8&#45;&gt;Start2</title>
-<path fill="none" stroke="red" d="M46.1,-52.47C30.13,-83.17 2.78,-148.1 27,-196 34.7,-211.23 49.06,-223.15 62.94,-231.8"/>
-<polygon fill="red" stroke="red" points="61.52,-235.02 71.92,-237.01 65.03,-228.96 61.52,-235.02"/>
-<text text-anchor="middle" x="43.1" y="-55.07" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
-</g>
-<!-- Return8&#45;&gt;Add7 -->
-<g id="edge7" class="edge">
-<title>Return8&#45;&gt;Add7</title>
-<path fill="none" stroke="black" d="M59.71,-52.3C61.36,-60.02 63.35,-69.29 65.19,-77.89"/>
-<polygon fill="black" stroke="black" points="61.82,-78.85 67.33,-87.9 68.66,-77.38 61.82,-78.85"/>
-<text text-anchor="middle" x="56.71" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
-</g>
-<!-- Scope1_0 -->
-<g id="node7" class="node">
-<title>Scope1_0</title>
-<polygon fill="cyan" stroke="transparent" points="267,-23 267,-44 283,-44 283,-23 267,-23"/>
-<polygon fill="none" stroke="black" points="267,-23 267,-44 283,-44 283,-23 267,-23"/>
-<text text-anchor="start" x="270" y="-29.8" font-family="Times,serif" font-size="14.00">0</text>
-<polygon fill="none" stroke="black" points="283,-23 283,-44 298,-44 298,-23 283,-23"/>
-<text text-anchor="start" x="286" y="-29.8" font-family="Times,serif" font-size="14.00">a</text>
-<polygon fill="none" stroke="black" points="298,-23 298,-44 314,-44 314,-23 298,-23"/>
-<text text-anchor="start" x="301" y="-29.8" font-family="Times,serif" font-size="14.00">b</text>
-<polygon fill="none" stroke="black" points="314,-23 314,-44 328,-44 328,-23 314,-23"/>
-<text text-anchor="start" x="317" y="-29.8" font-family="Times,serif" font-size="14.00">c</text>
-</g>
-<!-- Scope1_0&#45;&gt;Con_3 -->
-<g id="edge8" class="edge">
-<title>Scope1_0:Scope1_0_a&#45;&gt;Con_3</title>
-<path fill="none" stroke="cornflowerblue" stroke-dasharray="5,2" d="M290,-45C290,-77.9 208.02,-132.76 163.3,-160.24"/>
-<polygon fill="cornflowerblue" stroke="cornflowerblue" points="161.36,-157.33 154.63,-165.51 164.99,-163.31 161.36,-157.33"/>
-</g>
-<!-- Scope1_0&#45;&gt;Con_4 -->
-<g id="edge9" class="edge">
-<title>Scope1_0:Scope1_0_b&#45;&gt;Con_4</title>
-<path fill="none" stroke="cornflowerblue" stroke-dasharray="5,2" d="M306,-45C306,-93.23 263.17,-135.12 233.85,-158.15"/>
-<polygon fill="cornflowerblue" stroke="cornflowerblue" points="231.47,-155.56 225.62,-164.4 235.7,-161.14 231.47,-155.56"/>
-</g>
-<!-- Scope1_0&#45;&gt;Add7 -->
-<g id="edge10" class="edge">
-<title>Scope1_0:Scope1_0_c&#45;&gt;Add7</title>
-<path fill="none" stroke="cornflowerblue" stroke-dasharray="5,2" d="M321,-45C321,-86.77 178.58,-78.08 138,-88 127.51,-90.56 116.11,-93.43 105.75,-96.06"/>
-<polygon fill="cornflowerblue" stroke="cornflowerblue" points="104.62,-92.74 95.79,-98.6 106.35,-99.52 104.62,-92.74"/>
-</g>
-</g>
-</svg>
+<svg aria-label="Compiler graph" width="840" height="312" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 840 312" style="font-family: sans-serif;"><style>
+ html, body { height:100%; margin:0; font-family:sans-serif; }
+ .viewer { height:100%; display:grid; grid-template-rows:auto minmax(0,1fr) auto; }
+ .source { padding:8px; border-bottom:1px solid #ccc; }
+ #program { width:min(600px,80vw); vertical-align:middle; }
+ #status { margin-top:4px; }
+ #graph { min-height:0; overflow:hidden; position:relative; background:#fafbfc; }
+ #elk { width:100%; height:100%; }
+ #graph svg { display:block; }
+ .tools { display:flex; flex-wrap:wrap; align-items:center; gap:14px; margin-top:8px; font-size:12px; }
+ .hint { color:#616c78; }
+ .ctrl { color:#b63838; } .mem { color:#2867b2; } .data { color:#536477; }
+ .edge { fill:none; stroke-width:1.5; }
+ .edge.CTRL { stroke-width:2; }
+ .edge.ASSOC { stroke-dasharray:4 4; }
+ .edge.PARSER { stroke-dasharray:5 4; }
+ .parser rect { fill:#f6f0ff; stroke:#7956a5; stroke-width:1.5; stroke-dasharray:5 4; }
+ .edge.selected { stroke-width:3.5; }
+ .edge.bundle { stroke-width:4; }
+ .edge.bundle.selected { stroke-width:6; }
+ .edge-hit { fill:none; stroke:transparent; stroke-width:12; vector-effect:non-scaling-stroke; pointer-events:stroke; cursor:pointer; }
+ .edge-jump { fill:transparent; pointer-events:all; cursor:pointer; }
+ .node { cursor:pointer; }
+ .box { stroke:#758191; stroke-width:1; }
+ .node.held .box { stroke-dasharray:5 3; }
+ .node.near .box { stroke:#c18221; stroke-width:2; }
+ .node.focus .box { stroke:#9c4900; stroke-width:3; }
+ .node.selected .box { stroke:#1569b9; stroke-width:3; }
+ .node.folded .box { stroke-width:4; }
+ .node.folding .box { fill:#f4ccd7; }
+ .fold-tag { font-size:9px; font-weight:700; fill:#963751; }
+ .peep-area { fill:#fff3d9; fill-opacity:.8; stroke:#c18221; stroke-width:2; pointer-events:none; }
+ .node.peep-edit .box { stroke:#9c4900; stroke-width:3; }
+ .node.error .box { stroke:#b42318; stroke-width:4; }
+ #event.error { color:#b42318; font-weight:600; white-space:pre-wrap; overflow-wrap:anywhere; }
+ .peep-ghosts { pointer-events:none; }
+ .region-box { stroke:#9da9b8; stroke-width:1.5; }
+ .fold-btn { cursor:pointer; }
+ .fold-btn rect { fill:white; stroke:#596674; }
+ .fold-btn text { font-size:14px; fill:#152330; }
+ .fold-btn:focus rect { stroke:#1569b9; stroke-width:2; }
+ .shortcut { cursor:pointer; }
+ .shortcut path { fill:none; stroke-width:1.5; }
+ .shortcut rect { fill:#fff8ec; stroke:#b63838; stroke-dasharray:3 2; }
+ .shortcut text { font-size:11px; fill:#783030; }
+ .shortcut.selected rect, .shortcut:focus rect { stroke-width:2.5; }
+ .label { font-size:12px; fill:#152330; font-weight:600; }
+ .node-id { font-size:10px; fill:#596674; font-weight:400; }
+ .type { font-size:11px; fill:#455768; }
+ .port { stroke:#536477; stroke-width:1; }
+ .row { stroke:#b6afcf; stroke-width:1; }
+ .bind { font-size:12px; fill:#152330; }
+ .slot { font-size:10px; fill:#364352; paint-order:stroke; stroke:#fafbfc; stroke-width:3; }
+ #detail { position:absolute; top:8px; right:8px; margin:0; padding:12px; max-width:320px;
+ max-height:45%; overflow:auto; white-space:pre-wrap; overflow-wrap:anywhere;
+ background:#fffffff2; border:1px solid #bac5d0; border-radius:4px; font-size:12px; }
+ .controls { display:flex; flex-wrap:wrap; justify-content:center; align-items:center; gap:6px; padding:6px; }
+ .button { color:white; background:black; font-size:20px; min-width:30px; }
+ .button:disabled { background:grey; }
+ .count { font-size:20px; min-width:35px; text-align:center; }
+ #N { width:6ch; }
+ #scrub { width:clamp(120px,35vw,600px); }
+ </style><defs><marker id="arrow-CTRL" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#b63838"/></marker><marker id="arrow-DATA" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#536477"/></marker><marker id="arrow-MEM" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#2867b2"/></marker><marker id="arrow-ASSOC" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#9396a3"/></marker><marker id="arrow-PARSER" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#7956a5"/></marker></defs><rect width="100%" height="100%" fill="rgb(250, 251, 252)"/><g><g class="regions" style="opacity: 1;"/><rect class="peep-area" rx="36" style="opacity: 0;"/><g class="edges"><path class="edge ASSOC" id="e1i0" d="M236,77 C236,101 696,74 696,98" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#1[0] → #3 · ASSOC · 0: a</title></path><path class="edge ASSOC" id="e1i1" d="M388,77 C388,101 744,74 744,98" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#1[1] → #4 · ASSOC · 0: b</title></path><path class="edge ASSOC" id="e1i2" d="M236,181 C236,205 792,74 792,98" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#1[2] → #7 · ASSOC · 0: c</title></path><path class="edge ASSOC" id="e3i0" d="M84,181 C84,205 236,3 236,27" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#3[0] → #2 · ASSOC</title></path><path class="edge ASSOC" id="e4i0" d="M84,181 C84,205 388,3 388,27" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#4[0] → #2 · ASSOC</title></path><path class="edge ASSOC" id="e6i0" d="M84,181 C84,205 540,3 540,27" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#6[0] → #2 · ASSOC</title></path><path class="edge DATA" id="e7i1" d="M236,80 L236,128" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#7[1] → #3 · DATA</title></path><path class="edge DATA" id="e7i2" d="M540,80 L540,90 L266,90 L266,128" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#7[2] → #6 · DATA</title></path><path class="edge CTRL" id="e8i0" d="M84,184 L84,232" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#8[0] → #2 · CTRL</title></path><path class="edge DATA" id="e8i1" d="M236,184 L236,194 L124,194 L124,232" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#8[1] → #7 · DATA</title></path></g><g class="edge-hits"/><g class="parser" style="opacity: 1;"/><g class="nodes"><g class="node" id="n1" data-id="1" transform="translate(672,98)" style="opacity: 1;"><path class="box" d="M4,0H140Q144,0 144,4V52Q144,56 140,56H4Q0,56 0,52V4Q0,0 4,0Z" fill="#e5e0ff"/><text class="label" text-anchor="middle" x="72" y="48"><tspan>Scope</tspan><tspan class="node-id">  #1</tspan></text><text class="type" text-anchor="middle" y="35" x="72"/><title>#1 Scope
+Type not computed
+
+[0] → #3 ASSOC 0: a
+[1] → #4 ASSOC 0: b
+[2] → #7 ASSOC 0: c</title><line class="row" x1="0" x2="144" y1="30" y2="30"/><line class="row" x1="48" x2="48" y1="0" y2="30"/><line class="row" x1="96" x2="96" y1="0" y2="30"/><text class="bind" x="24" y="20" text-anchor="middle">0: a</text><text class="bind" x="72" y="20" text-anchor="middle">0: b</text><text class="bind" x="120" y="20" text-anchor="middle">0: c</text><circle class="port" cx="24" cy="0" r="3" fill="#536477"/><circle class="port" cx="72" cy="0" r="3" fill="#536477"/><circle class="port" cx="120" cy="0" r="3" fill="#536477"/><text class="slot" x="24" y="-7" text-anchor="middle">0</text><text class="slot" x="72" y="-7" text-anchor="middle">1</text><text class="slot" x="120" y="-7" text-anchor="middle">2</text></g><g class="node" id="n2" data-id="2" transform="translate(24,134)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Start</tspan><tspan class="node-id">  #2</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#2 Start
+Type not computed
+
+</title></g><g class="node" id="n3" data-id="3" transform="translate(176,30)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#1</tspan><tspan class="node-id">  #3</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#3 #1
+Type not computed
+
+[0] → #2 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n4" data-id="4" transform="translate(328,30)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#2</tspan><tspan class="node-id">  #4</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#4 #2
+Type not computed
+
+[0] → #2 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n6" data-id="6" transform="translate(480,30)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#3</tspan><tspan class="node-id">  #6</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#6 #3
+Type not computed
+
+[0] → #2 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n7" data-id="7" transform="translate(176,134)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>+</tspan><tspan class="node-id">  #7</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#7 +
+Type not computed
+
+[0] → — DATA
+[1] → #3 DATA
+[2] → #6 DATA</title><circle class="port" cx="30" cy="-3" r="3" fill="white"/><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><circle class="port" cx="90" cy="-3" r="3" fill="#536477"/><text class="slot" x="30" y="-10" text-anchor="middle">0</text><text class="slot" x="60" y="-10" text-anchor="middle">1</text><text class="slot" x="90" y="-10" text-anchor="middle">2</text></g><g class="node" id="n8" data-id="8" transform="translate(44,238)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Return</tspan><tspan class="node-id">  #8</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#8 Return
+Type not computed
+
+[0] → #2 CTRL
+[1] → #7 DATA</title><circle class="port" cx="40" cy="-3" r="3" fill="#536477"/><circle class="port" cx="80" cy="-3" r="3" fill="#536477"/><text class="slot" x="40" y="-10" text-anchor="middle">0</text><text class="slot" x="80" y="-10" text-anchor="middle">1</text></g></g><g class="region-heads" style="opacity: 1;"/><g class="shortcuts" style="opacity: 1;"/><g class="peep-ghosts" pointer-events="none"/></g></svg>
diff --git a/chapter03/docs/03-graph3.json b/chapter03/docs/03-graph3.json
new file mode 100644
index 00000000..a6d6175a
--- /dev/null
+++ b/chapter03/docs/03-graph3.json
@@ -0,0 +1,19 @@
+{
+ "ver": 1,
+ "comp": "chapter03/docs/03-graph3.svg",
+ "step": 1,
+ "roots": [],
+ "scope": 0,
+ "nodes": [
+ {"id": 1, "label": "Scope", "type": null, "kind": "SCOPE", "edges": [{"idx": 0, "def": 4, "role": "ASSOC", "label": "0: y0"}, {"idx": 1, "def": 3, "role": "ASSOC", "label": "0: x0"}, {"idx": 2, "def": 6, "role": "ASSOC", "label": "0: y1"}, {"idx": 3, "def": 5, "role": "ASSOC", "label": "0: x1"}], "proj": null},
+ {"id": 2, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null},
+ {"id": 3, "label": "#1", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 4, "label": "#2", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 5, "label": "#3", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 6, "label": "#4", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 20, "label": "#8", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 21, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 2, "role": "CTRL", "label": null}, {"idx": 1, "def": 20, "role": "DATA", "label": null}], "proj": null}
+ ],
+ "groups": [
+ ]
+}
diff --git a/chapter03/docs/03-graph3.svg b/chapter03/docs/03-graph3.svg
index 623bd902..89eed120 100644
--- a/chapter03/docs/03-graph3.svg
+++ b/chapter03/docs/03-graph3.svg
@@ -1,151 +1,91 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
- "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<!-- Generated by graphviz version 2.43.0 (0)
- -->
-<!-- Title: chapter03 Pages: 1 -->
-<svg width="554pt" height="220pt"
- viewBox="0.00 0.00 554.00 220.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 216)">
-<title>chapter03</title>
-<polygon fill="white" stroke="transparent" points="-4,4 -4,-216 550,-216 550,4 -4,4"/>
-<g id="clust1" class="cluster">
-<title>cluster_Nodes</title>
-<polygon fill="none" stroke="black" points="8,-8 8,-204 386,-204 386,-8 8,-8"/>
-</g>
-<g id="clust2" class="cluster">
-<title>cluster_Scope1_0</title>
-<polygon fill="none" stroke="black" points="394,-8 394,-60 538,-60 538,-8 394,-8"/>
-</g>
-<!-- Start2 -->
-<g id="node1" class="node">
-<title>Start2</title>
-<polygon fill="yellow" stroke="black" points="198,-196 144,-196 144,-160 198,-160 198,-196"/>
-<text text-anchor="middle" x="171" y="-174.3" font-family="Times,serif" font-size="14.00">Start</text>
-</g>
-<!-- Con_3 -->
-<g id="node2" class="node">
-<title>Con_3</title>
-<ellipse fill="none" stroke="black" cx="207" cy="-106" rx="27" ry="18"/>
-<text text-anchor="middle" x="207" y="-102.3" font-family="Times,serif" font-size="14.00">#1</text>
-</g>
-<!-- Con_3&#45;&gt;Start2 -->
-<g id="edge1" class="edge">
-<title>Con_3&#45;&gt;Start2</title>
-<path fill="none" stroke="black" stroke-dasharray="1,5" d="M198.65,-123.24C194.46,-131.39 189.27,-141.47 184.53,-150.69"/>
-<polygon fill="black" stroke="black" points="181.27,-149.38 179.81,-159.88 187.49,-152.58 181.27,-149.38"/>
-<text text-anchor="middle" x="195.65" y="-125.84" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
-</g>
-<!-- Con_4 -->
-<g id="node3" class="node">
-<title>Con_4</title>
-<ellipse fill="none" stroke="black" cx="135" cy="-106" rx="27" ry="18"/>
-<text text-anchor="middle" x="135" y="-102.3" font-family="Times,serif" font-size="14.00">#2</text>
-</g>
-<!-- Con_4&#45;&gt;Start2 -->
-<g id="edge2" class="edge">
-<title>Con_4&#45;&gt;Start2</title>
-<path fill="none" stroke="black" stroke-dasharray="1,5" d="M143.35,-123.24C147.54,-131.39 152.73,-141.47 157.47,-150.69"/>
-<polygon fill="black" stroke="black" points="154.51,-152.58 162.19,-159.88 160.73,-149.38 154.51,-152.58"/>
-<text text-anchor="middle" x="140.35" y="-125.84" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
-</g>
-<!-- Con_20 -->
-<g id="node4" class="node">
-<title>Con_20</title>
-<ellipse fill="none" stroke="black" cx="63" cy="-106" rx="27" ry="18"/>
-<text text-anchor="middle" x="63" y="-102.3" font-family="Times,serif" font-size="14.00">#8</text>
-</g>
-<!-- Con_20&#45;&gt;Start2 -->
-<g id="edge3" class="edge">
-<title>Con_20&#45;&gt;Start2</title>
-<path fill="none" stroke="black" stroke-dasharray="1,5" d="M81.81,-119.19C96.77,-128.89 118.11,-142.72 136.16,-154.42"/>
-<polygon fill="black" stroke="black" points="134.42,-157.46 144.72,-159.97 138.23,-151.59 134.42,-157.46"/>
-<text text-anchor="middle" x="78.81" y="-121.79" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
-</g>
-<!-- Con_5 -->
-<g id="node5" class="node">
-<title>Con_5</title>
-<ellipse fill="none" stroke="black" cx="351" cy="-106" rx="27" ry="18"/>
-<text text-anchor="middle" x="351" y="-102.3" font-family="Times,serif" font-size="14.00">#3</text>
-</g>
-<!-- Con_5&#45;&gt;Start2 -->
-<g id="edge4" class="edge">
-<title>Con_5&#45;&gt;Start2</title>
-<path fill="none" stroke="black" stroke-dasharray="1,5" d="M329.82,-117.38C324.99,-119.65 319.84,-121.97 315,-124 279.07,-139.06 237.19,-154.21 207.89,-164.43"/>
-<polygon fill="black" stroke="black" points="206.38,-161.25 198.08,-167.84 208.68,-167.87 206.38,-161.25"/>
-<text text-anchor="middle" x="326.82" y="-119.98" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
-</g>
-<!-- Return21 -->
-<g id="node6" class="node">
-<title>Return21</title>
-<polygon fill="yellow" stroke="black" points="89,-52 23,-52 23,-16 89,-16 89,-52"/>
-<text text-anchor="middle" x="56" y="-30.3" font-family="Times,serif" font-size="14.00">Return</text>
-</g>
-<!-- Return21&#45;&gt;Start2 -->
-<g id="edge5" class="edge">
-<title>Return21&#45;&gt;Start2</title>
-<path fill="none" stroke="red" d="M42.04,-52.06C28.34,-71.02 11.57,-101.87 27,-124 50.89,-158.26 99.53,-170.43 133.64,-174.72"/>
-<polygon fill="red" stroke="red" points="133.4,-178.22 143.72,-175.82 134.16,-171.26 133.4,-178.22"/>
-<text text-anchor="middle" x="39.04" y="-54.66" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
-</g>
-<!-- Return21&#45;&gt;Con_20 -->
-<g id="edge6" class="edge">
-<title>Return21&#45;&gt;Con_20</title>
-<path fill="none" stroke="black" d="M57.73,-52.3C58.5,-60.02 59.43,-69.29 60.29,-77.89"/>
-<polygon fill="black" stroke="black" points="56.81,-78.29 61.29,-87.9 63.78,-77.6 56.81,-78.29"/>
-<text text-anchor="middle" x="54.73" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
-</g>
-<!-- Con_6 -->
-<g id="node7" class="node">
-<title>Con_6</title>
-<ellipse fill="none" stroke="black" cx="279" cy="-106" rx="27" ry="18"/>
-<text text-anchor="middle" x="279" y="-102.3" font-family="Times,serif" font-size="14.00">#4</text>
-</g>
-<!-- Con_6&#45;&gt;Start2 -->
-<g id="edge7" class="edge">
-<title>Con_6&#45;&gt;Start2</title>
-<path fill="none" stroke="black" stroke-dasharray="1,5" d="M260.19,-119.19C245.23,-128.89 223.89,-142.72 205.84,-154.42"/>
-<polygon fill="black" stroke="black" points="203.77,-151.59 197.28,-159.97 207.58,-157.46 203.77,-151.59"/>
-<text text-anchor="middle" x="257.19" y="-121.79" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
-</g>
-<!-- Scope1_0 -->
-<g id="node8" class="node">
-<title>Scope1_0</title>
-<polygon fill="cyan" stroke="transparent" points="410,-23 410,-44 426,-44 426,-23 410,-23"/>
-<polygon fill="none" stroke="black" points="410,-23 410,-44 426,-44 426,-23 410,-23"/>
-<text text-anchor="start" x="413" y="-29.8" font-family="Times,serif" font-size="14.00">0</text>
-<polygon fill="none" stroke="black" points="426,-23 426,-44 450,-44 450,-23 426,-23"/>
-<text text-anchor="start" x="429" y="-29.8" font-family="Times,serif" font-size="14.00">y0</text>
-<polygon fill="none" stroke="black" points="450,-23 450,-44 474,-44 474,-23 450,-23"/>
-<text text-anchor="start" x="453" y="-29.8" font-family="Times,serif" font-size="14.00">x0</text>
-<polygon fill="none" stroke="black" points="474,-23 474,-44 498,-44 498,-23 474,-23"/>
-<text text-anchor="start" x="477" y="-29.8" font-family="Times,serif" font-size="14.00">y1</text>
-<polygon fill="none" stroke="black" points="498,-23 498,-44 522,-44 522,-23 498,-23"/>
-<text text-anchor="start" x="501" y="-29.8" font-family="Times,serif" font-size="14.00">x1</text>
-</g>
-<!-- Scope1_0&#45;&gt;Con_3 -->
-<g id="edge9" class="edge">
-<title>Scope1_0:Scope1_0_x0&#45;&gt;Con_3</title>
-<path fill="none" stroke="cornflowerblue" stroke-dasharray="5,2" d="M462,-45C462,-94.6 290.16,-72.65 243,-88 241.08,-88.63 239.12,-89.33 237.17,-90.09"/>
-<polygon fill="cornflowerblue" stroke="cornflowerblue" points="235.39,-87.04 227.58,-94.19 238.15,-93.48 235.39,-87.04"/>
-</g>
-<!-- Scope1_0&#45;&gt;Con_4 -->
-<g id="edge8" class="edge">
-<title>Scope1_0:Scope1_0_y0&#45;&gt;Con_4</title>
-<path fill="none" stroke="cornflowerblue" stroke-dasharray="5,2" d="M438,-45C438,-67.35 411.84,-55.23 390,-60 294.13,-80.94 265.19,-60.49 171,-88 169.06,-88.57 167.09,-89.23 165.13,-89.95"/>
-<polygon fill="cornflowerblue" stroke="cornflowerblue" points="163.39,-86.88 155.5,-93.97 166.08,-93.34 163.39,-86.88"/>
-</g>
-<!-- Scope1_0&#45;&gt;Con_5 -->
-<g id="edge11" class="edge">
-<title>Scope1_0:Scope1_0_x1&#45;&gt;Con_5</title>
-<path fill="none" stroke="cornflowerblue" stroke-dasharray="5,2" d="M510,-45C510,-71.13 433.9,-89.82 387,-98.86"/>
-<polygon fill="cornflowerblue" stroke="cornflowerblue" points="386.09,-95.47 376.9,-100.75 387.37,-102.35 386.09,-95.47"/>
-</g>
-<!-- Scope1_0&#45;&gt;Con_6 -->
-<g id="edge10" class="edge">
-<title>Scope1_0:Scope1_0_y1&#45;&gt;Con_6</title>
-<path fill="none" stroke="cornflowerblue" stroke-dasharray="5,2" d="M486,-45C486,-123.37 388.78,-61.59 315,-88 313.15,-88.66 311.26,-89.39 309.37,-90.16"/>
-<polygon fill="cornflowerblue" stroke="cornflowerblue" points="307.8,-87.02 300.03,-94.23 310.6,-93.44 307.8,-87.02"/>
-</g>
-</g>
-</svg>
+<svg aria-label="Compiler graph" width="1204" height="208" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1204 208" style="font-family: sans-serif;"><style>
+ html, body { height:100%; margin:0; font-family:sans-serif; }
+ .viewer { height:100%; display:grid; grid-template-rows:auto minmax(0,1fr) auto; }
+ .source { padding:8px; border-bottom:1px solid #ccc; }
+ #program { width:min(600px,80vw); vertical-align:middle; }
+ #status { margin-top:4px; }
+ #graph { min-height:0; overflow:hidden; position:relative; background:#fafbfc; }
+ #elk { width:100%; height:100%; }
+ #graph svg { display:block; }
+ .tools { display:flex; flex-wrap:wrap; align-items:center; gap:14px; margin-top:8px; font-size:12px; }
+ .hint { color:#616c78; }
+ .ctrl { color:#b63838; } .mem { color:#2867b2; } .data { color:#536477; }
+ .edge { fill:none; stroke-width:1.5; }
+ .edge.CTRL { stroke-width:2; }
+ .edge.ASSOC { stroke-dasharray:4 4; }
+ .edge.PARSER { stroke-dasharray:5 4; }
+ .parser rect { fill:#f6f0ff; stroke:#7956a5; stroke-width:1.5; stroke-dasharray:5 4; }
+ .edge.selected { stroke-width:3.5; }
+ .edge.bundle { stroke-width:4; }
+ .edge.bundle.selected { stroke-width:6; }
+ .edge-hit { fill:none; stroke:transparent; stroke-width:12; vector-effect:non-scaling-stroke; pointer-events:stroke; cursor:pointer; }
+ .edge-jump { fill:transparent; pointer-events:all; cursor:pointer; }
+ .node { cursor:pointer; }
+ .box { stroke:#758191; stroke-width:1; }
+ .node.held .box { stroke-dasharray:5 3; }
+ .node.near .box { stroke:#c18221; stroke-width:2; }
+ .node.focus .box { stroke:#9c4900; stroke-width:3; }
+ .node.selected .box { stroke:#1569b9; stroke-width:3; }
+ .node.folded .box { stroke-width:4; }
+ .node.folding .box { fill:#f4ccd7; }
+ .fold-tag { font-size:9px; font-weight:700; fill:#963751; }
+ .peep-area { fill:#fff3d9; fill-opacity:.8; stroke:#c18221; stroke-width:2; pointer-events:none; }
+ .node.peep-edit .box { stroke:#9c4900; stroke-width:3; }
+ .node.error .box { stroke:#b42318; stroke-width:4; }
+ #event.error { color:#b42318; font-weight:600; white-space:pre-wrap; overflow-wrap:anywhere; }
+ .peep-ghosts { pointer-events:none; }
+ .region-box { stroke:#9da9b8; stroke-width:1.5; }
+ .fold-btn { cursor:pointer; }
+ .fold-btn rect { fill:white; stroke:#596674; }
+ .fold-btn text { font-size:14px; fill:#152330; }
+ .fold-btn:focus rect { stroke:#1569b9; stroke-width:2; }
+ .shortcut { cursor:pointer; }
+ .shortcut path { fill:none; stroke-width:1.5; }
+ .shortcut rect { fill:#fff8ec; stroke:#b63838; stroke-dasharray:3 2; }
+ .shortcut text { font-size:11px; fill:#783030; }
+ .shortcut.selected rect, .shortcut:focus rect { stroke-width:2.5; }
+ .label { font-size:12px; fill:#152330; font-weight:600; }
+ .node-id { font-size:10px; fill:#596674; font-weight:400; }
+ .type { font-size:11px; fill:#455768; }
+ .port { stroke:#536477; stroke-width:1; }
+ .row { stroke:#b6afcf; stroke-width:1; }
+ .bind { font-size:12px; fill:#152330; }
+ .slot { font-size:10px; fill:#364352; paint-order:stroke; stroke:#fafbfc; stroke-width:3; }
+ #detail { position:absolute; top:8px; right:8px; margin:0; padding:12px; max-width:320px;
+ max-height:45%; overflow:auto; white-space:pre-wrap; overflow-wrap:anywhere;
+ background:#fffffff2; border:1px solid #bac5d0; border-radius:4px; font-size:12px; }
+ .controls { display:flex; flex-wrap:wrap; justify-content:center; align-items:center; gap:6px; padding:6px; }
+ .button { color:white; background:black; font-size:20px; min-width:30px; }
+ .button:disabled { background:grey; }
+ .count { font-size:20px; min-width:35px; text-align:center; }
+ #N { width:6ch; }
+ #scrub { width:clamp(120px,35vw,600px); }
+ </style><defs><marker id="arrow-CTRL" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#b63838"/></marker><marker id="arrow-DATA" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#536477"/></marker><marker id="arrow-MEM" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#2867b2"/></marker><marker id="arrow-ASSOC" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#9396a3"/></marker><marker id="arrow-PARSER" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#7956a5"/></marker></defs><rect width="100%" height="100%" fill="rgb(250, 251, 252)"/><g><g class="regions" style="opacity: 1;"/><rect class="peep-area" rx="36" style="opacity: 0;"/><g class="edges"><path class="edge ASSOC" id="e1i0" d="M388,77 C388,101 1001.5,74 1001.5,98" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#1[0] → #4 · ASSOC · 0: y0</title></path><path class="edge ASSOC" id="e1i1" d="M236,77 C236,101 1052.5,74 1052.5,98" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#1[1] → #3 · ASSOC · 0: x0</title></path><path class="edge ASSOC" id="e1i2" d="M692,77 C692,101 1103.5,74 1103.5,98" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#1[2] → #6 · ASSOC · 0: y1</title></path><path class="edge ASSOC" id="e1i3" d="M540,77 C540,101 1154.5,74 1154.5,98" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#1[3] → #5 · ASSOC · 0: x1</title></path><path class="edge ASSOC" id="e3i0" d="M84,77 C84,101 236,3 236,27" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#3[0] → #2 · ASSOC</title></path><path class="edge ASSOC" id="e4i0" d="M84,77 C84,101 388,3 388,27" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#4[0] → #2 · ASSOC</title></path><path class="edge ASSOC" id="e5i0" d="M84,77 C84,101 540,3 540,27" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#5[0] → #2 · ASSOC</title></path><path class="edge ASSOC" id="e6i0" d="M84,77 C84,101 692,3 692,27" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#6[0] → #2 · ASSOC</title></path><path class="edge ASSOC" id="e20i0" d="M84,77 C84,101 844,3 844,27" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#20[0] → #2 · ASSOC</title></path><path class="edge CTRL" id="e21i0" d="M84,80 L84,128" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#21[0] → #2 · CTRL</title></path><path class="edge DATA" id="e21i1" d="M844,80 L844,90 L124,90 L124,128" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#21[1] → #20 · DATA</title></path></g><g class="edge-hits"/><g class="parser" style="opacity: 1;"/><g class="nodes"><g class="node" id="n1" data-id="1" transform="translate(976,98)" style="opacity: 1;"><path class="box" d="M4,0H200Q204,0 204,4V52Q204,56 200,56H4Q0,56 0,52V4Q0,0 4,0Z" fill="#e5e0ff"/><text class="label" text-anchor="middle" x="102" y="48"><tspan>Scope</tspan><tspan class="node-id">  #1</tspan></text><text class="type" text-anchor="middle" y="35" x="102"/><title>#1 Scope
+Type not computed
+
+[0] → #4 ASSOC 0: y0
+[1] → #3 ASSOC 0: x0
+[2] → #6 ASSOC 0: y1
+[3] → #5 ASSOC 0: x1</title><line class="row" x1="0" x2="204" y1="30" y2="30"/><line class="row" x1="51" x2="51" y1="0" y2="30"/><line class="row" x1="102" x2="102" y1="0" y2="30"/><line class="row" x1="153" x2="153" y1="0" y2="30"/><text class="bind" x="25.5" y="20" text-anchor="middle">0: y0</text><text class="bind" x="76.5" y="20" text-anchor="middle">0: x0</text><text class="bind" x="127.5" y="20" text-anchor="middle">0: y1</text><text class="bind" x="178.5" y="20" text-anchor="middle">0: x1</text><circle class="port" cx="25.5" cy="0" r="3" fill="#536477"/><circle class="port" cx="76.5" cy="0" r="3" fill="#536477"/><circle class="port" cx="127.5" cy="0" r="3" fill="#536477"/><circle class="port" cx="178.5" cy="0" r="3" fill="#536477"/><text class="slot" x="25.5" y="-7" text-anchor="middle">0</text><text class="slot" x="76.5" y="-7" text-anchor="middle">1</text><text class="slot" x="127.5" y="-7" text-anchor="middle">2</text><text class="slot" x="178.5" y="-7" text-anchor="middle">3</text></g><g class="node" id="n2" data-id="2" transform="translate(24,30)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Start</tspan><tspan class="node-id">  #2</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#2 Start
+Type not computed
+
+</title></g><g class="node" id="n3" data-id="3" transform="translate(176,30)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#1</tspan><tspan class="node-id">  #3</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#3 #1
+Type not computed
+
+[0] → #2 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n4" data-id="4" transform="translate(328,30)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#2</tspan><tspan class="node-id">  #4</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#4 #2
+Type not computed
+
+[0] → #2 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n5" data-id="5" transform="translate(480,30)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#3</tspan><tspan class="node-id">  #5</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#5 #3
+Type not computed
+
+[0] → #2 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n6" data-id="6" transform="translate(632,30)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#4</tspan><tspan class="node-id">  #6</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#6 #4
+Type not computed
+
+[0] → #2 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n20" data-id="20" transform="translate(784,30)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#8</tspan><tspan class="node-id">  #20</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#20 #8
+Type not computed
+
+[0] → #2 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n21" data-id="21" transform="translate(44,134)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Return</tspan><tspan class="node-id">  #21</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#21 Return
+Type not computed
+
+[0] → #2 CTRL
+[1] → #20 DATA</title><circle class="port" cx="40" cy="-3" r="3" fill="#536477"/><circle class="port" cx="80" cy="-3" r="3" fill="#536477"/><text class="slot" x="40" y="-10" text-anchor="middle">0</text><text class="slot" x="80" y="-10" text-anchor="middle">1</text></g></g><g class="region-heads" style="opacity: 1;"/><g class="shortcuts" style="opacity: 1;"/><g class="peep-ghosts" pointer-events="none"/></g></svg>
diff --git a/chapter03/docs/03-graph4.json b/chapter03/docs/03-graph4.json
new file mode 100644
index 00000000..b594eee2
--- /dev/null
+++ b/chapter03/docs/03-graph4.json
@@ -0,0 +1,17 @@
+{
+ "ver": 1,
+ "comp": "chapter03/docs/03-graph4.svg",
+ "step": 1,
+ "roots": [],
+ "scope": 0,
+ "nodes": [
+ {"id": 1, "label": "Scope", "type": null, "kind": "SCOPE", "edges": [{"idx": 0, "def": 3, "role": "ASSOC", "label": "0: a"}, {"idx": 1, "def": 4, "role": "ASSOC", "label": "0: b"}, {"idx": 2, "def": 8, "role": "ASSOC", "label": "0: c"}, {"idx": 3, "def": 6, "role": "ASSOC", "label": "1: b"}], "proj": null},
+ {"id": 2, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null},
+ {"id": 3, "label": "#1", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 4, "label": "#2", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 6, "label": "#5", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 8, "label": "#6", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "ASSOC", "label": null}], "proj": null}
+ ],
+ "groups": [
+ ]
+}
diff --git a/chapter03/docs/03-graph4.svg b/chapter03/docs/03-graph4.svg
index 6838e1d4..c773ef9b 100644
--- a/chapter03/docs/03-graph4.svg
+++ b/chapter03/docs/03-graph4.svg
@@ -1,127 +1,84 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<!-- Generated by graphviz version 2.40.1 (20161225.0304)
- -->
-<!-- Title: chapter03 Pages: 1 -->
-<svg width="310pt" height="228pt" viewBox="0.00 0.00 310.00 228.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 224)">
-<title>chapter03</title>
-<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-224 306,-224 306,4 -4,4"/>
-<g id="clust1" class="cluster">
-<title>cluster_Nodes</title>
-<polygon fill="none" stroke="#000000" points="8,-88 8,-212 294,-212 294,-88 8,-88"/>
-</g>
-<g id="clust2" class="cluster">
-<title>cluster_Scope1_0</title>
-<polygon fill="none" stroke="#000000" points="97,-8 97,-76 259,-76 259,-8 97,-8"/>
-</g>
-<g id="clust3" class="cluster">
-<title>cluster_Scope1_1</title>
-<polygon fill="none" stroke="#000000" points="181,-16 181,-68 251,-68 251,-16 181,-16"/>
-</g>
-<!-- Start2 -->
-<g id="node1" class="node">
-<title>Start2</title>
-<polygon fill="#ffff00" stroke="#000000" points="178,-204 124,-204 124,-168 178,-168 178,-204"/>
-<text text-anchor="middle" x="151" y="-181.8" font-family="Times,serif" font-size="14.00" fill="#000000">Start</text>
-</g>
-<!-- Con_3 -->
-<g id="node2" class="node">
-<title>Con_3</title>
-<ellipse fill="none" stroke="#000000" cx="43" cy="-114" rx="27" ry="18"/>
-<text text-anchor="middle" x="43" y="-109.8" font-family="Times,serif" font-size="14.00" fill="#000000">#1</text>
-</g>
-<!-- Con_3&#45;&gt;Start2 -->
-<g id="edge1" class="edge">
-<title>Con_3-&gt;Start2</title>
-<path fill="none" stroke="#000000" stroke-dasharray="1,5" d="M62.3082,-126.8722C77.012,-136.6747 97.6668,-150.4445 115.3448,-162.2299"/>
-<polygon fill="#000000" stroke="#000000" points="113.4829,-165.195 123.7449,-167.8299 117.3658,-159.3707 113.4829,-165.195"/>
-<text text-anchor="middle" x="64.5318" y="-129.2722" font-family="Helvetica,sans-Serif" font-size="8.00" fill="#000000">0</text>
-</g>
-<!-- Con_4 -->
-<g id="node3" class="node">
-<title>Con_4</title>
-<ellipse fill="none" stroke="#000000" cx="115" cy="-114" rx="27" ry="18"/>
-<text text-anchor="middle" x="115" y="-109.8" font-family="Times,serif" font-size="14.00" fill="#000000">#2</text>
-</g>
-<!-- Con_4&#45;&gt;Start2 -->
-<g id="edge2" class="edge">
-<title>Con_4-&gt;Start2</title>
-<path fill="none" stroke="#000000" stroke-dasharray="1,5" d="M123.7146,-131.4292C127.8228,-139.6456 132.8196,-149.6392 137.4089,-158.8179"/>
-<polygon fill="#000000" stroke="#000000" points="134.387,-160.6004 141.9897,-167.9794 140.648,-157.4699 134.387,-160.6004"/>
-<text text-anchor="middle" x="121.491" y="-133.8292" font-family="Helvetica,sans-Serif" font-size="8.00" fill="#000000">0</text>
-</g>
-<!-- Con_6 -->
-<g id="node4" class="node">
-<title>Con_6</title>
-<ellipse fill="none" stroke="#000000" cx="259" cy="-114" rx="27" ry="18"/>
-<text text-anchor="middle" x="259" y="-109.8" font-family="Times,serif" font-size="14.00" fill="#000000">#5</text>
-</g>
-<!-- Con_6&#45;&gt;Start2 -->
-<g id="edge3" class="edge">
-<title>Con_6-&gt;Start2</title>
-<path fill="none" stroke="#000000" stroke-dasharray="1,5" d="M239.6918,-126.8722C224.988,-136.6747 204.3332,-150.4445 186.6552,-162.2299"/>
-<polygon fill="#000000" stroke="#000000" points="184.6342,-159.3707 178.2551,-167.8299 188.5171,-165.195 184.6342,-159.3707"/>
-<text text-anchor="middle" x="237.4682" y="-129.2722" font-family="Helvetica,sans-Serif" font-size="8.00" fill="#000000">0</text>
-</g>
-<!-- Con_8 -->
-<g id="node5" class="node">
-<title>Con_8</title>
-<ellipse fill="none" stroke="#000000" cx="187" cy="-114" rx="27" ry="18"/>
-<text text-anchor="middle" x="187" y="-109.8" font-family="Times,serif" font-size="14.00" fill="#000000">#6</text>
-</g>
-<!-- Con_8&#45;&gt;Start2 -->
-<g id="edge4" class="edge">
-<title>Con_8-&gt;Start2</title>
-<path fill="none" stroke="#000000" stroke-dasharray="1,5" d="M178.2854,-131.4292C174.1772,-139.6456 169.1804,-149.6392 164.5911,-158.8179"/>
-<polygon fill="#000000" stroke="#000000" points="161.352,-157.4699 160.0103,-167.9794 167.613,-160.6004 161.352,-157.4699"/>
-<text text-anchor="middle" x="176.0618" y="-133.8292" font-family="Helvetica,sans-Serif" font-size="8.00" fill="#000000">0</text>
-</g>
-<!-- Scope1_0 -->
-<g id="node6" class="node">
-<title>Scope1_0</title>
-<polygon fill="#00ffff" stroke="transparent" points="113,-31 113,-53 126,-53 126,-31 113,-31"/>
-<polygon fill="none" stroke="#000000" points="113,-31 113,-53 126,-53 126,-31 113,-31"/>
-<text text-anchor="start" x="116" y="-37.4" font-family="Times,serif" font-size="14.00" fill="#000000">0</text>
-<polygon fill="none" stroke="#000000" points="126,-31 126,-53 138,-53 138,-31 126,-31"/>
-<text text-anchor="start" x="128.8927" y="-37.4" font-family="Times,serif" font-size="14.00" fill="#000000">a</text>
-<polygon fill="none" stroke="#000000" points="138,-31 138,-53 151,-53 151,-31 138,-31"/>
-<text text-anchor="start" x="141" y="-37.4" font-family="Times,serif" font-size="14.00" fill="#000000">b</text>
-<polygon fill="none" stroke="#000000" points="151,-31 151,-53 163,-53 163,-31 151,-31"/>
-<text text-anchor="start" x="153.8927" y="-37.4" font-family="Times,serif" font-size="14.00" fill="#000000">c</text>
-</g>
-<!-- Scope1_0&#45;&gt;Con_3 -->
-<g id="edge5" class="edge">
-<title>Scope1_0:Scope1_0_a-&gt;Con_3</title>
-<path fill="none" stroke="#6495ed" stroke-dasharray="5,2" d="M132,-53C132,-81.2283 102.2249,-71.955 79,-88 75.8998,-90.1418 72.6712,-92.4134 69.4762,-94.6873"/>
-<polygon fill="#6495ed" stroke="#6495ed" points="67.2901,-91.9482 61.2087,-100.6239 71.373,-97.6342 67.2901,-91.9482"/>
-</g>
-<!-- Scope1_0&#45;&gt;Con_4 -->
-<g id="edge6" class="edge">
-<title>Scope1_0:Scope1_0_b-&gt;Con_4</title>
-<path fill="none" stroke="#6495ed" stroke-dasharray="5,2" d="M145,-53C145,-65.7791 139.6328,-78.661 133.4388,-89.2726"/>
-<polygon fill="#6495ed" stroke="#6495ed" points="130.3276,-87.6415 127.8968,-97.9538 136.2279,-91.4082 130.3276,-87.6415"/>
-</g>
-<!-- Scope1_0&#45;&gt;Con_8 -->
-<g id="edge7" class="edge">
-<title>Scope1_0:Scope1_0_c-&gt;Con_8</title>
-<path fill="none" stroke="#6495ed" stroke-dasharray="5,2" d="M157,-53C157,-65.7791 162.3672,-78.661 168.5612,-89.2726"/>
-<polygon fill="#6495ed" stroke="#6495ed" points="165.7721,-91.4082 174.1032,-97.9538 171.6724,-87.6415 165.7721,-91.4082"/>
-</g>
-<!-- Scope1_1 -->
-<g id="node7" class="node">
-<title>Scope1_1</title>
-<polygon fill="#00ffff" stroke="transparent" points="203,-31 203,-53 216,-53 216,-31 203,-31"/>
-<polygon fill="none" stroke="#000000" points="203,-31 203,-53 216,-53 216,-31 203,-31"/>
-<text text-anchor="start" x="206" y="-37.4" font-family="Times,serif" font-size="14.00" fill="#000000">1</text>
-<polygon fill="none" stroke="#000000" points="216,-31 216,-53 229,-53 229,-31 216,-31"/>
-<text text-anchor="start" x="219" y="-37.4" font-family="Times,serif" font-size="14.00" fill="#000000">b</text>
-</g>
-<!-- Scope1_1&#45;&gt;Con_6 -->
-<g id="edge8" class="edge">
-<title>Scope1_1:Scope1_1_b-&gt;Con_6</title>
-<path fill="none" stroke="#6495ed" stroke-dasharray="5,2" d="M223,-53C223,-66.7784 229.8964,-80.1812 237.6523,-90.8977"/>
-<polygon fill="#6495ed" stroke="#6495ed" points="235.0006,-93.1875 243.9413,-98.8721 240.497,-88.8528 235.0006,-93.1875"/>
-</g>
-</g>
-</svg> \ No newline at end of file
+<svg aria-label="Compiler graph" width="1040" height="178" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1040 178" style="font-family: sans-serif;"><style>
+ html, body { height:100%; margin:0; font-family:sans-serif; }
+ .viewer { height:100%; display:grid; grid-template-rows:auto minmax(0,1fr) auto; }
+ .source { padding:8px; border-bottom:1px solid #ccc; }
+ #program { width:min(600px,80vw); vertical-align:middle; }
+ #status { margin-top:4px; }
+ #graph { min-height:0; overflow:hidden; position:relative; background:#fafbfc; }
+ #elk { width:100%; height:100%; }
+ #graph svg { display:block; }
+ .tools { display:flex; flex-wrap:wrap; align-items:center; gap:14px; margin-top:8px; font-size:12px; }
+ .hint { color:#616c78; }
+ .ctrl { color:#b63838; } .mem { color:#2867b2; } .data { color:#536477; }
+ .edge { fill:none; stroke-width:1.5; }
+ .edge.CTRL { stroke-width:2; }
+ .edge.ASSOC { stroke-dasharray:4 4; }
+ .edge.PARSER { stroke-dasharray:5 4; }
+ .parser rect { fill:#f6f0ff; stroke:#7956a5; stroke-width:1.5; stroke-dasharray:5 4; }
+ .edge.selected { stroke-width:3.5; }
+ .edge.bundle { stroke-width:4; }
+ .edge.bundle.selected { stroke-width:6; }
+ .edge-hit { fill:none; stroke:transparent; stroke-width:12; vector-effect:non-scaling-stroke; pointer-events:stroke; cursor:pointer; }
+ .edge-jump { fill:transparent; pointer-events:all; cursor:pointer; }
+ .node { cursor:pointer; }
+ .box { stroke:#758191; stroke-width:1; }
+ .node.held .box { stroke-dasharray:5 3; }
+ .node.near .box { stroke:#c18221; stroke-width:2; }
+ .node.focus .box { stroke:#9c4900; stroke-width:3; }
+ .node.selected .box { stroke:#1569b9; stroke-width:3; }
+ .node.folded .box { stroke-width:4; }
+ .node.folding .box { fill:#f4ccd7; }
+ .fold-tag { font-size:9px; font-weight:700; fill:#963751; }
+ .peep-area { fill:#fff3d9; fill-opacity:.8; stroke:#c18221; stroke-width:2; pointer-events:none; }
+ .node.peep-edit .box { stroke:#9c4900; stroke-width:3; }
+ .node.error .box { stroke:#b42318; stroke-width:4; }
+ #event.error { color:#b42318; font-weight:600; white-space:pre-wrap; overflow-wrap:anywhere; }
+ .peep-ghosts { pointer-events:none; }
+ .region-box { stroke:#9da9b8; stroke-width:1.5; }
+ .fold-btn { cursor:pointer; }
+ .fold-btn rect { fill:white; stroke:#596674; }
+ .fold-btn text { font-size:14px; fill:#152330; }
+ .fold-btn:focus rect { stroke:#1569b9; stroke-width:2; }
+ .shortcut { cursor:pointer; }
+ .shortcut path { fill:none; stroke-width:1.5; }
+ .shortcut rect { fill:#fff8ec; stroke:#b63838; stroke-dasharray:3 2; }
+ .shortcut text { font-size:11px; fill:#783030; }
+ .shortcut.selected rect, .shortcut:focus rect { stroke-width:2.5; }
+ .label { font-size:12px; fill:#152330; font-weight:600; }
+ .node-id { font-size:10px; fill:#596674; font-weight:400; }
+ .type { font-size:11px; fill:#455768; }
+ .port { stroke:#536477; stroke-width:1; }
+ .row { stroke:#b6afcf; stroke-width:1; }
+ .bind { font-size:12px; fill:#152330; }
+ .slot { font-size:10px; fill:#364352; paint-order:stroke; stroke:#fafbfc; stroke-width:3; }
+ #detail { position:absolute; top:8px; right:8px; margin:0; padding:12px; max-width:320px;
+ max-height:45%; overflow:auto; white-space:pre-wrap; overflow-wrap:anywhere;
+ background:#fffffff2; border:1px solid #bac5d0; border-radius:4px; font-size:12px; }
+ .controls { display:flex; flex-wrap:wrap; justify-content:center; align-items:center; gap:6px; padding:6px; }
+ .button { color:white; background:black; font-size:20px; min-width:30px; }
+ .button:disabled { background:grey; }
+ .count { font-size:20px; min-width:35px; text-align:center; }
+ #N { width:6ch; }
+ #scrub { width:clamp(120px,35vw,600px); }
+ </style><defs><marker id="arrow-CTRL" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#b63838"/></marker><marker id="arrow-DATA" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#536477"/></marker><marker id="arrow-MEM" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#2867b2"/></marker><marker id="arrow-ASSOC" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#9396a3"/></marker><marker id="arrow-PARSER" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#7956a5"/></marker></defs><rect width="100%" height="100%" fill="rgb(250, 251, 252)"/><g><g class="regions" style="opacity: 1;"/><rect class="peep-area" rx="36" style="opacity: 0;"/><g class="edges"><path class="edge ASSOC" id="e1i0" d="M236,77 C236,101 848,74 848,98" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#1[0] → #3 · ASSOC · 0: a</title></path><path class="edge ASSOC" id="e1i1" d="M388,77 C388,101 896,74 896,98" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#1[1] → #4 · ASSOC · 0: b</title></path><path class="edge ASSOC" id="e1i2" d="M692,77 C692,101 944,74 944,98" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#1[2] → #8 · ASSOC · 0: c</title></path><path class="edge ASSOC" id="e1i3" d="M540,77 C540,101 992,74 992,98" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#1[3] → #6 · ASSOC · 1: b</title></path><path class="edge ASSOC" id="e3i0" d="M84,77 C84,101 236,3 236,27" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#3[0] → #2 · ASSOC</title></path><path class="edge ASSOC" id="e4i0" d="M84,77 C84,101 388,3 388,27" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#4[0] → #2 · ASSOC</title></path><path class="edge ASSOC" id="e6i0" d="M84,77 C84,101 540,3 540,27" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#6[0] → #2 · ASSOC</title></path><path class="edge ASSOC" id="e8i0" d="M84,77 C84,101 692,3 692,27" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#8[0] → #2 · ASSOC</title></path></g><g class="edge-hits"/><g class="parser" style="opacity: 1;"/><g class="nodes"><g class="node" id="n1" data-id="1" transform="translate(824,98)" style="opacity: 1;"><path class="box" d="M4,0H188Q192,0 192,4V52Q192,56 188,56H4Q0,56 0,52V4Q0,0 4,0Z" fill="#e5e0ff"/><text class="label" text-anchor="middle" x="96" y="48"><tspan>Scope</tspan><tspan class="node-id">  #1</tspan></text><text class="type" text-anchor="middle" y="35" x="96"/><title>#1 Scope
+Type not computed
+
+[0] → #3 ASSOC 0: a
+[1] → #4 ASSOC 0: b
+[2] → #8 ASSOC 0: c
+[3] → #6 ASSOC 1: b</title><line class="row" x1="0" x2="192" y1="30" y2="30"/><line class="row" x1="48" x2="48" y1="0" y2="30"/><line class="row" x1="96" x2="96" y1="0" y2="30"/><line class="row" x1="144" x2="144" y1="0" y2="30"/><text class="bind" x="24" y="20" text-anchor="middle">0: a</text><text class="bind" x="72" y="20" text-anchor="middle">0: b</text><text class="bind" x="120" y="20" text-anchor="middle">0: c</text><text class="bind" x="168" y="20" text-anchor="middle">1: b</text><circle class="port" cx="24" cy="0" r="3" fill="#536477"/><circle class="port" cx="72" cy="0" r="3" fill="#536477"/><circle class="port" cx="120" cy="0" r="3" fill="#536477"/><circle class="port" cx="168" cy="0" r="3" fill="#536477"/><text class="slot" x="24" y="-7" text-anchor="middle">0</text><text class="slot" x="72" y="-7" text-anchor="middle">1</text><text class="slot" x="120" y="-7" text-anchor="middle">2</text><text class="slot" x="168" y="-7" text-anchor="middle">3</text></g><g class="node" id="n2" data-id="2" transform="translate(24,30)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Start</tspan><tspan class="node-id">  #2</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#2 Start
+Type not computed
+
+</title></g><g class="node" id="n3" data-id="3" transform="translate(176,30)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#1</tspan><tspan class="node-id">  #3</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#3 #1
+Type not computed
+
+[0] → #2 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n4" data-id="4" transform="translate(328,30)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#2</tspan><tspan class="node-id">  #4</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#4 #2
+Type not computed
+
+[0] → #2 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n6" data-id="6" transform="translate(480,30)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#5</tspan><tspan class="node-id">  #6</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#6 #5
+Type not computed
+
+[0] → #2 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n8" data-id="8" transform="translate(632,30)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#6</tspan><tspan class="node-id">  #8</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#8 #6
+Type not computed
+
+[0] → #2 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g></g><g class="region-heads" style="opacity: 1;"/><g class="shortcuts" style="opacity: 1;"/><g class="peep-ghosts" pointer-events="none"/></g></svg>
diff --git a/chapter03/docs/03-graph5.json b/chapter03/docs/03-graph5.json
new file mode 100644
index 00000000..476ad586
--- /dev/null
+++ b/chapter03/docs/03-graph5.json
@@ -0,0 +1,17 @@
+{
+ "ver": 1,
+ "comp": "chapter03/docs/03-graph5.svg",
+ "step": 1,
+ "roots": [],
+ "scope": 0,
+ "nodes": [
+ {"id": 1, "label": "Scope", "type": null, "kind": "SCOPE", "edges": [{"idx": 0, "def": 3, "role": "ASSOC", "label": "0: a"}, {"idx": 1, "def": 4, "role": "ASSOC", "label": "0: b"}, {"idx": 2, "def": 11, "role": "ASSOC", "label": "0: c"}, {"idx": 3, "def": 9, "role": "ASSOC", "label": "1: e"}], "proj": null},
+ {"id": 2, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null},
+ {"id": 3, "label": "#1", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 4, "label": "#2", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 9, "label": "#6", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 11, "label": "#7", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "ASSOC", "label": null}], "proj": null}
+ ],
+ "groups": [
+ ]
+}
diff --git a/chapter03/docs/03-graph5.svg b/chapter03/docs/03-graph5.svg
index 48e69cb7..a62d03bb 100644
--- a/chapter03/docs/03-graph5.svg
+++ b/chapter03/docs/03-graph5.svg
@@ -1,127 +1,84 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<!-- Generated by graphviz version 2.40.1 (20161225.0304)
- -->
-<!-- Title: chapter03 Pages: 1 -->
-<svg width="310pt" height="228pt" viewBox="0.00 0.00 310.00 228.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 224)">
-<title>chapter03</title>
-<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-224 306,-224 306,4 -4,4"/>
-<g id="clust1" class="cluster">
-<title>cluster_Nodes</title>
-<polygon fill="none" stroke="#000000" points="8,-88 8,-212 294,-212 294,-88 8,-88"/>
-</g>
-<g id="clust2" class="cluster">
-<title>cluster_Scope1_0</title>
-<polygon fill="none" stroke="#000000" points="97,-8 97,-76 259,-76 259,-8 97,-8"/>
-</g>
-<g id="clust3" class="cluster">
-<title>cluster_Scope1_1</title>
-<polygon fill="none" stroke="#000000" points="181,-16 181,-68 251,-68 251,-16 181,-16"/>
-</g>
-<!-- Start2 -->
-<g id="node1" class="node">
-<title>Start2</title>
-<polygon fill="#ffff00" stroke="#000000" points="178,-204 124,-204 124,-168 178,-168 178,-204"/>
-<text text-anchor="middle" x="151" y="-181.8" font-family="Times,serif" font-size="14.00" fill="#000000">Start</text>
-</g>
-<!-- Con_3 -->
-<g id="node2" class="node">
-<title>Con_3</title>
-<ellipse fill="none" stroke="#000000" cx="43" cy="-114" rx="27" ry="18"/>
-<text text-anchor="middle" x="43" y="-109.8" font-family="Times,serif" font-size="14.00" fill="#000000">#1</text>
-</g>
-<!-- Con_3&#45;&gt;Start2 -->
-<g id="edge1" class="edge">
-<title>Con_3-&gt;Start2</title>
-<path fill="none" stroke="#000000" stroke-dasharray="1,5" d="M62.3082,-126.8722C77.012,-136.6747 97.6668,-150.4445 115.3448,-162.2299"/>
-<polygon fill="#000000" stroke="#000000" points="113.4829,-165.195 123.7449,-167.8299 117.3658,-159.3707 113.4829,-165.195"/>
-<text text-anchor="middle" x="64.5318" y="-129.2722" font-family="Helvetica,sans-Serif" font-size="8.00" fill="#000000">0</text>
-</g>
-<!-- Con_4 -->
-<g id="node3" class="node">
-<title>Con_4</title>
-<ellipse fill="none" stroke="#000000" cx="115" cy="-114" rx="27" ry="18"/>
-<text text-anchor="middle" x="115" y="-109.8" font-family="Times,serif" font-size="14.00" fill="#000000">#2</text>
-</g>
-<!-- Con_4&#45;&gt;Start2 -->
-<g id="edge2" class="edge">
-<title>Con_4-&gt;Start2</title>
-<path fill="none" stroke="#000000" stroke-dasharray="1,5" d="M123.7146,-131.4292C127.8228,-139.6456 132.8196,-149.6392 137.4089,-158.8179"/>
-<polygon fill="#000000" stroke="#000000" points="134.387,-160.6004 141.9897,-167.9794 140.648,-157.4699 134.387,-160.6004"/>
-<text text-anchor="middle" x="121.491" y="-133.8292" font-family="Helvetica,sans-Serif" font-size="8.00" fill="#000000">0</text>
-</g>
-<!-- Con_9 -->
-<g id="node4" class="node">
-<title>Con_9</title>
-<ellipse fill="none" stroke="#000000" cx="259" cy="-114" rx="27" ry="18"/>
-<text text-anchor="middle" x="259" y="-109.8" font-family="Times,serif" font-size="14.00" fill="#000000">#6</text>
-</g>
-<!-- Con_9&#45;&gt;Start2 -->
-<g id="edge3" class="edge">
-<title>Con_9-&gt;Start2</title>
-<path fill="none" stroke="#000000" stroke-dasharray="1,5" d="M239.6918,-126.8722C224.988,-136.6747 204.3332,-150.4445 186.6552,-162.2299"/>
-<polygon fill="#000000" stroke="#000000" points="184.6342,-159.3707 178.2551,-167.8299 188.5171,-165.195 184.6342,-159.3707"/>
-<text text-anchor="middle" x="237.4682" y="-129.2722" font-family="Helvetica,sans-Serif" font-size="8.00" fill="#000000">0</text>
-</g>
-<!-- Con_11 -->
-<g id="node5" class="node">
-<title>Con_11</title>
-<ellipse fill="none" stroke="#000000" cx="187" cy="-114" rx="27" ry="18"/>
-<text text-anchor="middle" x="187" y="-109.8" font-family="Times,serif" font-size="14.00" fill="#000000">#7</text>
-</g>
-<!-- Con_11&#45;&gt;Start2 -->
-<g id="edge4" class="edge">
-<title>Con_11-&gt;Start2</title>
-<path fill="none" stroke="#000000" stroke-dasharray="1,5" d="M178.2854,-131.4292C174.1772,-139.6456 169.1804,-149.6392 164.5911,-158.8179"/>
-<polygon fill="#000000" stroke="#000000" points="161.352,-157.4699 160.0103,-167.9794 167.613,-160.6004 161.352,-157.4699"/>
-<text text-anchor="middle" x="176.0618" y="-133.8292" font-family="Helvetica,sans-Serif" font-size="8.00" fill="#000000">0</text>
-</g>
-<!-- Scope1_0 -->
-<g id="node6" class="node">
-<title>Scope1_0</title>
-<polygon fill="#00ffff" stroke="transparent" points="113,-31 113,-53 126,-53 126,-31 113,-31"/>
-<polygon fill="none" stroke="#000000" points="113,-31 113,-53 126,-53 126,-31 113,-31"/>
-<text text-anchor="start" x="116" y="-37.4" font-family="Times,serif" font-size="14.00" fill="#000000">0</text>
-<polygon fill="none" stroke="#000000" points="126,-31 126,-53 138,-53 138,-31 126,-31"/>
-<text text-anchor="start" x="128.8927" y="-37.4" font-family="Times,serif" font-size="14.00" fill="#000000">a</text>
-<polygon fill="none" stroke="#000000" points="138,-31 138,-53 151,-53 151,-31 138,-31"/>
-<text text-anchor="start" x="141" y="-37.4" font-family="Times,serif" font-size="14.00" fill="#000000">b</text>
-<polygon fill="none" stroke="#000000" points="151,-31 151,-53 163,-53 163,-31 151,-31"/>
-<text text-anchor="start" x="153.8927" y="-37.4" font-family="Times,serif" font-size="14.00" fill="#000000">c</text>
-</g>
-<!-- Scope1_0&#45;&gt;Con_3 -->
-<g id="edge5" class="edge">
-<title>Scope1_0:Scope1_0_a-&gt;Con_3</title>
-<path fill="none" stroke="#6495ed" stroke-dasharray="5,2" d="M132,-53C132,-81.2283 102.2249,-71.955 79,-88 75.8998,-90.1418 72.6712,-92.4134 69.4762,-94.6873"/>
-<polygon fill="#6495ed" stroke="#6495ed" points="67.2901,-91.9482 61.2087,-100.6239 71.373,-97.6342 67.2901,-91.9482"/>
-</g>
-<!-- Scope1_0&#45;&gt;Con_4 -->
-<g id="edge6" class="edge">
-<title>Scope1_0:Scope1_0_b-&gt;Con_4</title>
-<path fill="none" stroke="#6495ed" stroke-dasharray="5,2" d="M145,-53C145,-65.7791 139.6328,-78.661 133.4388,-89.2726"/>
-<polygon fill="#6495ed" stroke="#6495ed" points="130.3276,-87.6415 127.8968,-97.9538 136.2279,-91.4082 130.3276,-87.6415"/>
-</g>
-<!-- Scope1_0&#45;&gt;Con_11 -->
-<g id="edge7" class="edge">
-<title>Scope1_0:Scope1_0_c-&gt;Con_11</title>
-<path fill="none" stroke="#6495ed" stroke-dasharray="5,2" d="M157,-53C157,-65.7791 162.3672,-78.661 168.5612,-89.2726"/>
-<polygon fill="#6495ed" stroke="#6495ed" points="165.7721,-91.4082 174.1032,-97.9538 171.6724,-87.6415 165.7721,-91.4082"/>
-</g>
-<!-- Scope1_1 -->
-<g id="node7" class="node">
-<title>Scope1_1</title>
-<polygon fill="#00ffff" stroke="transparent" points="204,-31 204,-53 217,-53 217,-31 204,-31"/>
-<polygon fill="none" stroke="#000000" points="204,-31 204,-53 217,-53 217,-31 204,-31"/>
-<text text-anchor="start" x="207" y="-37.4" font-family="Times,serif" font-size="14.00" fill="#000000">1</text>
-<polygon fill="none" stroke="#000000" points="217,-31 217,-53 229,-53 229,-31 217,-31"/>
-<text text-anchor="start" x="219.8927" y="-37.4" font-family="Times,serif" font-size="14.00" fill="#000000">e</text>
-</g>
-<!-- Scope1_1&#45;&gt;Con_9 -->
-<g id="edge8" class="edge">
-<title>Scope1_1:Scope1_1_e-&gt;Con_9</title>
-<path fill="none" stroke="#6495ed" stroke-dasharray="5,2" d="M223,-53C223,-66.7784 229.8964,-80.1812 237.6523,-90.8977"/>
-<polygon fill="#6495ed" stroke="#6495ed" points="235.0006,-93.1875 243.9413,-98.8721 240.497,-88.8528 235.0006,-93.1875"/>
-</g>
-</g>
-</svg> \ No newline at end of file
+<svg aria-label="Compiler graph" width="1040" height="178" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1040 178" style="font-family: sans-serif;"><style>
+ html, body { height:100%; margin:0; font-family:sans-serif; }
+ .viewer { height:100%; display:grid; grid-template-rows:auto minmax(0,1fr) auto; }
+ .source { padding:8px; border-bottom:1px solid #ccc; }
+ #program { width:min(600px,80vw); vertical-align:middle; }
+ #status { margin-top:4px; }
+ #graph { min-height:0; overflow:hidden; position:relative; background:#fafbfc; }
+ #elk { width:100%; height:100%; }
+ #graph svg { display:block; }
+ .tools { display:flex; flex-wrap:wrap; align-items:center; gap:14px; margin-top:8px; font-size:12px; }
+ .hint { color:#616c78; }
+ .ctrl { color:#b63838; } .mem { color:#2867b2; } .data { color:#536477; }
+ .edge { fill:none; stroke-width:1.5; }
+ .edge.CTRL { stroke-width:2; }
+ .edge.ASSOC { stroke-dasharray:4 4; }
+ .edge.PARSER { stroke-dasharray:5 4; }
+ .parser rect { fill:#f6f0ff; stroke:#7956a5; stroke-width:1.5; stroke-dasharray:5 4; }
+ .edge.selected { stroke-width:3.5; }
+ .edge.bundle { stroke-width:4; }
+ .edge.bundle.selected { stroke-width:6; }
+ .edge-hit { fill:none; stroke:transparent; stroke-width:12; vector-effect:non-scaling-stroke; pointer-events:stroke; cursor:pointer; }
+ .edge-jump { fill:transparent; pointer-events:all; cursor:pointer; }
+ .node { cursor:pointer; }
+ .box { stroke:#758191; stroke-width:1; }
+ .node.held .box { stroke-dasharray:5 3; }
+ .node.near .box { stroke:#c18221; stroke-width:2; }
+ .node.focus .box { stroke:#9c4900; stroke-width:3; }
+ .node.selected .box { stroke:#1569b9; stroke-width:3; }
+ .node.folded .box { stroke-width:4; }
+ .node.folding .box { fill:#f4ccd7; }
+ .fold-tag { font-size:9px; font-weight:700; fill:#963751; }
+ .peep-area { fill:#fff3d9; fill-opacity:.8; stroke:#c18221; stroke-width:2; pointer-events:none; }
+ .node.peep-edit .box { stroke:#9c4900; stroke-width:3; }
+ .node.error .box { stroke:#b42318; stroke-width:4; }
+ #event.error { color:#b42318; font-weight:600; white-space:pre-wrap; overflow-wrap:anywhere; }
+ .peep-ghosts { pointer-events:none; }
+ .region-box { stroke:#9da9b8; stroke-width:1.5; }
+ .fold-btn { cursor:pointer; }
+ .fold-btn rect { fill:white; stroke:#596674; }
+ .fold-btn text { font-size:14px; fill:#152330; }
+ .fold-btn:focus rect { stroke:#1569b9; stroke-width:2; }
+ .shortcut { cursor:pointer; }
+ .shortcut path { fill:none; stroke-width:1.5; }
+ .shortcut rect { fill:#fff8ec; stroke:#b63838; stroke-dasharray:3 2; }
+ .shortcut text { font-size:11px; fill:#783030; }
+ .shortcut.selected rect, .shortcut:focus rect { stroke-width:2.5; }
+ .label { font-size:12px; fill:#152330; font-weight:600; }
+ .node-id { font-size:10px; fill:#596674; font-weight:400; }
+ .type { font-size:11px; fill:#455768; }
+ .port { stroke:#536477; stroke-width:1; }
+ .row { stroke:#b6afcf; stroke-width:1; }
+ .bind { font-size:12px; fill:#152330; }
+ .slot { font-size:10px; fill:#364352; paint-order:stroke; stroke:#fafbfc; stroke-width:3; }
+ #detail { position:absolute; top:8px; right:8px; margin:0; padding:12px; max-width:320px;
+ max-height:45%; overflow:auto; white-space:pre-wrap; overflow-wrap:anywhere;
+ background:#fffffff2; border:1px solid #bac5d0; border-radius:4px; font-size:12px; }
+ .controls { display:flex; flex-wrap:wrap; justify-content:center; align-items:center; gap:6px; padding:6px; }
+ .button { color:white; background:black; font-size:20px; min-width:30px; }
+ .button:disabled { background:grey; }
+ .count { font-size:20px; min-width:35px; text-align:center; }
+ #N { width:6ch; }
+ #scrub { width:clamp(120px,35vw,600px); }
+ </style><defs><marker id="arrow-CTRL" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#b63838"/></marker><marker id="arrow-DATA" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#536477"/></marker><marker id="arrow-MEM" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#2867b2"/></marker><marker id="arrow-ASSOC" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#9396a3"/></marker><marker id="arrow-PARSER" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#7956a5"/></marker></defs><rect width="100%" height="100%" fill="rgb(250, 251, 252)"/><g><g class="regions" style="opacity: 1;"/><rect class="peep-area" rx="36" style="opacity: 0;"/><g class="edges"><path class="edge ASSOC" id="e1i0" d="M236,77 C236,101 848,74 848,98" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#1[0] → #3 · ASSOC · 0: a</title></path><path class="edge ASSOC" id="e1i1" d="M388,77 C388,101 896,74 896,98" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#1[1] → #4 · ASSOC · 0: b</title></path><path class="edge ASSOC" id="e1i2" d="M692,77 C692,101 944,74 944,98" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#1[2] → #11 · ASSOC · 0: c</title></path><path class="edge ASSOC" id="e1i3" d="M540,77 C540,101 992,74 992,98" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#1[3] → #9 · ASSOC · 1: e</title></path><path class="edge ASSOC" id="e3i0" d="M84,77 C84,101 236,3 236,27" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#3[0] → #2 · ASSOC</title></path><path class="edge ASSOC" id="e4i0" d="M84,77 C84,101 388,3 388,27" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#4[0] → #2 · ASSOC</title></path><path class="edge ASSOC" id="e9i0" d="M84,77 C84,101 540,3 540,27" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#9[0] → #2 · ASSOC</title></path><path class="edge ASSOC" id="e11i0" d="M84,77 C84,101 692,3 692,27" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#11[0] → #2 · ASSOC</title></path></g><g class="edge-hits"/><g class="parser" style="opacity: 1;"/><g class="nodes"><g class="node" id="n1" data-id="1" transform="translate(824,98)" style="opacity: 1;"><path class="box" d="M4,0H188Q192,0 192,4V52Q192,56 188,56H4Q0,56 0,52V4Q0,0 4,0Z" fill="#e5e0ff"/><text class="label" text-anchor="middle" x="96" y="48"><tspan>Scope</tspan><tspan class="node-id">  #1</tspan></text><text class="type" text-anchor="middle" y="35" x="96"/><title>#1 Scope
+Type not computed
+
+[0] → #3 ASSOC 0: a
+[1] → #4 ASSOC 0: b
+[2] → #11 ASSOC 0: c
+[3] → #9 ASSOC 1: e</title><line class="row" x1="0" x2="192" y1="30" y2="30"/><line class="row" x1="48" x2="48" y1="0" y2="30"/><line class="row" x1="96" x2="96" y1="0" y2="30"/><line class="row" x1="144" x2="144" y1="0" y2="30"/><text class="bind" x="24" y="20" text-anchor="middle">0: a</text><text class="bind" x="72" y="20" text-anchor="middle">0: b</text><text class="bind" x="120" y="20" text-anchor="middle">0: c</text><text class="bind" x="168" y="20" text-anchor="middle">1: e</text><circle class="port" cx="24" cy="0" r="3" fill="#536477"/><circle class="port" cx="72" cy="0" r="3" fill="#536477"/><circle class="port" cx="120" cy="0" r="3" fill="#536477"/><circle class="port" cx="168" cy="0" r="3" fill="#536477"/><text class="slot" x="24" y="-7" text-anchor="middle">0</text><text class="slot" x="72" y="-7" text-anchor="middle">1</text><text class="slot" x="120" y="-7" text-anchor="middle">2</text><text class="slot" x="168" y="-7" text-anchor="middle">3</text></g><g class="node" id="n2" data-id="2" transform="translate(24,30)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Start</tspan><tspan class="node-id">  #2</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#2 Start
+Type not computed
+
+</title></g><g class="node" id="n3" data-id="3" transform="translate(176,30)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#1</tspan><tspan class="node-id">  #3</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#3 #1
+Type not computed
+
+[0] → #2 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n4" data-id="4" transform="translate(328,30)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#2</tspan><tspan class="node-id">  #4</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#4 #2
+Type not computed
+
+[0] → #2 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n9" data-id="9" transform="translate(480,30)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#6</tspan><tspan class="node-id">  #9</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#9 #6
+Type not computed
+
+[0] → #2 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n11" data-id="11" transform="translate(632,30)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#7</tspan><tspan class="node-id">  #11</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#11 #7
+Type not computed
+
+[0] → #2 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g></g><g class="region-heads" style="opacity: 1;"/><g class="shortcuts" style="opacity: 1;"/><g class="peep-ghosts" pointer-events="none"/></g></svg>
diff --git a/chapter03/docs/Makefile b/chapter03/docs/Makefile
index 12659b8f..6fa0f057 100644
--- a/chapter03/docs/Makefile
+++ b/chapter03/docs/Makefile
@@ -1,12 +1 @@
-GV := $(wildcard *.gv)
-SVG := $(patsubst %.gv,%.svg,$(GV))
-
-.PHONY: all
-all: $(SVG)
-
-%.svg: %.gv
- dot -Tsvg $< > $@
-
-.PHONY: clean
-clean:
- @rm -f $(SVG)
+include ../../graph/docs.mk
diff --git a/chapter03/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter03/src/main/java/com/seaofnodes/simple/type/Type.java
index 7ea17bfb..b98ce6de 100644
--- a/chapter03/src/main/java/com/seaofnodes/simple/type/Type.java
+++ b/chapter03/src/main/java/com/seaofnodes/simple/type/Type.java
@@ -36,5 +36,10 @@ public class Type {
public boolean isConstant() { return _type == TTOP; }
+ @Override
+ public String toString() {
+ return print(new StringBuilder()).toString();
+ }
+
public StringBuilder print(StringBuilder sb) {return is_simple() ? sb.append(STRS[_type]) : sb;}
}