summaryrefslogtreecommitdiff
path: root/chapter01
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-10-02 13:04:34 -0700
committerCliff Click <cliffc@acm.org>2026-10-02 13:04:34 -0700
commite4dc3273f201ae741637b626998637cce973546b (patch)
tree7607c1da4378da42549e8142371d95bdca4e06e0 /chapter01
parent6cd01a8d646750748a0af221073de8a7787472ad (diff)
downloadSimple-e4dc3273f201ae741637b626998637cce973546b.tar.gz
Simple-e4dc3273f201ae741637b626998637cce973546b.zip
First pass README updates
Diffstat (limited to 'chapter01')
-rw-r--r--chapter01/README.md40
-rw-r--r--chapter01/docs/01-graph.json14
-rw-r--r--chapter01/docs/01-graph.svg129
-rw-r--r--chapter01/docs/01-use-def.json13
-rw-r--r--chapter01/docs/01-use-def.svg104
-rw-r--r--chapter01/docs/Makefile13
6 files changed, 191 insertions, 122 deletions
diff --git a/chapter01/README.md b/chapter01/README.md
index 17e24ea0..ebe2811e 100644
--- a/chapter01/README.md
+++ b/chapter01/README.md
@@ -1,21 +1,23 @@
# Chapter 1: Introduction
+[Introduction](../README.md) |
+[Next: Chapter 2](../chapter02/README.md)
+
# Table of Contents
-1. [Implementation Language](#implementation-language)
-2. [Assumptions](#assumptions)
-3. [Architecture](#architecture)
-4. [Data Structures](#data-structures)
-5. [Intermediate Representation as a Graph of Nodes](#intermediate-representation-as-a-graph-of-nodes)
-6. [Nodes are in a Graph](#nodes-are-in-a-graph)
-7. [Types of Nodes](#types-of-nodes)
-8. [Unique Node ID](#unique-node-id)
-9. [Start Node](#start-node)
+1. [Implementation Language](#implementation-language)
+2. [Assumptions](#assumptions)
+3. [Architecture](#architecture)
+4. [Data Structures](#data-structures)
+5. [Intermediate Representation as a Graph of Nodes](#intermediate-representation-as-a-graph-of-nodes)
+6. [Nodes are in a Graph](#nodes-are-in-a-graph)
+7. [Types of Nodes](#types-of-nodes)
+8. [Unique Node ID](#unique-node-id)
+9. [Start Node](#start-node)
10. [Constant Node](#constant-node)
11. [Return Node](#return-node)
12. [Representation](#representation)
-
You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter01) in a linear Git revision history on the [linear](https://github.com/SeaOfNodes/Simple/tree/linear) branch.
@@ -41,7 +43,7 @@ Our implementation language is Java. We chose Java as it is widely available and
## Assumptions
We assume that the reader is familiar with traditional linear intermediate representations, and is familiar with terms such as Basic Block, Control Flow Graph, etc. No attempt is made to explain these topics.
-If necessary the reader can consult a standard compiler text book.
+If necessary the reader can consult a standard compiler textbook.
## Architecture
@@ -50,7 +52,7 @@ a number of pessimistic peephole optimizations can be performed while parsing a
## Data Structures
-Our data structures are based upon the descriptions provided in following papers:
+Our data structures are based upon the descriptions provided in the following papers:
* [From Quads to Graphs: An Intermediate Representation's Journey](http://softlib.rice.edu/pub/CRPC-TRs/reports/CRPC-TR93366-S.pdf)
* [Combining Analyses, Combining Optimizations](https://dl.acm.org/doi/pdf/10.1145/201059.201061)
@@ -73,9 +75,9 @@ Each `Node` represents an "instruction" as it may appear in traditional IRs.
### Nodes are in a Graph
The key idea of the Sea of Nodes IR is that each Node is linked to other Nodes by def-use dependencies.
-As this is such an important and fundamental aspect of the IR, it is important to understand how we implement this, and depict in graph visuals.
+As this is such an important and fundamental aspect of the IR, it is important to understand how we implement this, and depict this in graph visuals.
-The base `Node` class maintains a list of Nodes that are inputs to it. An input is an edge from a "def" to a "use". What this means is that if `B` is definition, and `A` uses `B`,
+The base `Node` class maintains a list of Nodes that are inputs to it. An input is an edge from a "def" to a "use". What this means is that if `B` is a definition, and `A` uses `B`,
then there is a def-use edge from `B` to `A`.
Visually we show an arrow from the "use" to the "def". Here is an example:
@@ -110,7 +112,7 @@ public abstract class Node {
* <p>
* Outputs directly match inputs, making a directed graph that can be
* walked in either direction. These outputs are typically used for
- * efficient optimizations but otherwise have no semantics meaning.
+ * efficient optimizations but otherwise have no semantic meaning.
*/
public final ArrayList<Node> _outputs;
}
@@ -173,7 +175,7 @@ The Return's output is the value from the data node.
### Representation
-Here is visualization of the program:
+Here is a visualization of the program:
```
return 1;
@@ -181,7 +183,7 @@ return 1;
![Sea of Nodes Visual](./docs/01-graph.svg)
-* Control nodes appear as square boxes with yellow background
-* Control edges are in bold red
-* The edges from Constants to Start are shown in dotted lines as these are not true control edges
+* Control nodes appear as rectangular boxes with pale yellow backgrounds
+* Control edges are red and thicker than data edges
+* The edges from Constants to Start are shown as dashed gray lines as these are not true control edges
* We label each edge with its position in the `_inputs` array, thus `0` means the edge is `_inputs[0]`.
diff --git a/chapter01/docs/01-graph.json b/chapter01/docs/01-graph.json
new file mode 100644
index 00000000..f489c4fe
--- /dev/null
+++ b/chapter01/docs/01-graph.json
@@ -0,0 +1,14 @@
+{
+ "ver": 1,
+ "comp": "chapter01/docs/01-graph.svg",
+ "step": 1,
+ "roots": [],
+ "scope": 0,
+ "nodes": [
+ {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null},
+ {"id": 2, "label": "#1", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 3, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 1, "role": "CTRL", "label": null}, {"idx": 1, "def": 2, "role": "DATA", "label": null}], "proj": null}
+ ],
+ "groups": [
+ ]
+}
diff --git a/chapter01/docs/01-graph.svg b/chapter01/docs/01-graph.svg
index 7f511966..d8ec5cb7 100644
--- a/chapter01/docs/01-graph.svg
+++ b/chapter01/docs/01-graph.svg
@@ -1,56 +1,73 @@
-<?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: chapter01 Pages: 1 -->
-<svg width="114pt" height="220pt"
- viewBox="0.00 0.00 114.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>chapter01</title>
-<polygon fill="white" stroke="transparent" points="-4,4 -4,-216 110,-216 110,4 -4,4"/>
-<g id="clust1" class="cluster">
-<title>cluster_Nodes</title>
-<polygon fill="none" stroke="black" points="8,-8 8,-204 98,-204 98,-8 8,-8"/>
-</g>
-<!-- Start1 -->
-<g id="node1" class="node">
-<title>Start1</title>
-<polygon fill="yellow" stroke="black" points="80,-196 26,-196 26,-160 80,-160 80,-196"/>
-<text text-anchor="middle" x="53" y="-174.3" font-family="Times,serif" font-size="14.00">Start</text>
-</g>
-<!-- Con_2 -->
-<g id="node2" class="node">
-<title>Con_2</title>
-<ellipse fill="none" stroke="black" cx="43" cy="-106" rx="27" ry="18"/>
-<text text-anchor="middle" x="43" y="-102.3" font-family="Times,serif" font-size="14.00">#1</text>
-</g>
-<!-- Con_2&#45;&gt;Start1 -->
-<g id="edge1" class="edge">
-<title>Con_2&#45;&gt;Start1</title>
-<path fill="none" stroke="black" stroke-dasharray="1,5" d="M45.42,-123.95C46.52,-131.65 47.85,-140.97 49.09,-149.64"/>
-<polygon fill="black" stroke="black" points="45.65,-150.32 50.53,-159.72 52.58,-149.33 45.65,-150.32"/>
-<text text-anchor="middle" x="42.42" y="-126.55" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
-</g>
-<!-- Return3 -->
-<g id="node3" class="node">
-<title>Return3</title>
-<polygon fill="yellow" stroke="black" points="86,-52 20,-52 20,-16 86,-16 86,-52"/>
-<text text-anchor="middle" x="53" y="-30.3" font-family="Times,serif" font-size="14.00">Return</text>
-</g>
-<!-- Return3&#45;&gt;Start1 -->
-<g id="edge2" class="edge">
-<title>Return3&#45;&gt;Start1</title>
-<path fill="none" stroke="red" d="M64.05,-52.39C69.74,-62.38 76.08,-75.42 79,-88 82.62,-103.59 82.62,-108.41 79,-124 76.9,-133.04 73.03,-142.32 68.91,-150.53"/>
-<polygon fill="red" stroke="red" points="65.69,-149.14 64.05,-159.61 71.86,-152.45 65.69,-149.14"/>
-<text text-anchor="middle" x="61.05" y="-54.99" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
-</g>
-<!-- Return3&#45;&gt;Con_2 -->
-<g id="edge3" class="edge">
-<title>Return3&#45;&gt;Con_2</title>
-<path fill="none" stroke="black" d="M50.53,-52.3C49.43,-60.02 48.1,-69.29 46.87,-77.89"/>
-<polygon fill="black" stroke="black" points="43.39,-77.5 45.44,-87.9 50.32,-78.49 43.39,-77.5"/>
-<text text-anchor="middle" x="47.53" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
-</g>
-</g>
-</svg>
+<svg aria-label="Compiler graph" width="320" height="208" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 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="e2i0" d="M84,77 C84,101 236,3 236,27" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#2[0] → #1 · ASSOC</title></path><path class="edge CTRL" id="e3i0" d="M84,80 L84,128" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#3[0] → #1 · CTRL</title></path><path class="edge DATA" id="e3i1" d="M236,80 L236,90 L124,90 L124,128" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#3[1] → #2 · 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(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">  #1</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#1 Start
+Type not computed
+
+</title></g><g class="node" id="n2" data-id="2" 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">  #2</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#2 #1
+Type not computed
+
+[0] → #1 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="n3" data-id="3" 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">  #3</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#3 Return
+Type not computed
+
+[0] → #1 CTRL
+[1] → #2 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/chapter01/docs/01-use-def.json b/chapter01/docs/01-use-def.json
new file mode 100644
index 00000000..5e6f2df6
--- /dev/null
+++ b/chapter01/docs/01-use-def.json
@@ -0,0 +1,13 @@
+{
+ "ver": 1,
+ "comp": "chapter01/docs/01-use-def.svg",
+ "step": 1,
+ "roots": [],
+ "scope": 0,
+ "nodes": [
+ {"id": 1, "label": "A", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "DATA", "label": null}], "proj": null},
+ {"id": 2, "label": "B", "type": null, "kind": "DATA", "edges": [], "proj": null}
+ ],
+ "groups": [
+ ]
+}
diff --git a/chapter01/docs/01-use-def.svg b/chapter01/docs/01-use-def.svg
index 644ab5a2..b0ecd3da 100644
--- a/chapter01/docs/01-use-def.svg
+++ b/chapter01/docs/01-use-def.svg
@@ -1,35 +1,69 @@
-<?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: chapter01 Pages: 1 -->
-<svg width="94pt" height="148pt"
- viewBox="0.00 0.00 94.00 148.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 144)">
-<title>chapter01</title>
-<polygon fill="white" stroke="transparent" points="-4,4 -4,-144 90,-144 90,4 -4,4"/>
-<g id="clust1" class="cluster">
-<title>cluster_Nodes</title>
-<polygon fill="none" stroke="black" points="8,-8 8,-132 78,-132 78,-8 8,-8"/>
-</g>
-<!-- A -->
-<g id="node1" class="node">
-<title>A</title>
-<ellipse fill="none" stroke="black" cx="43" cy="-34" rx="27" ry="18"/>
-<text text-anchor="middle" x="43" y="-30.3" font-family="Times,serif" font-size="14.00">A</text>
-</g>
-<!-- B -->
-<g id="node2" class="node">
-<title>B</title>
-<ellipse fill="none" stroke="black" cx="43" cy="-106" rx="27" ry="18"/>
-<text text-anchor="middle" x="43" y="-102.3" font-family="Times,serif" font-size="14.00">B</text>
-</g>
-<!-- A&#45;&gt;B -->
-<g id="edge1" class="edge">
-<title>A&#45;&gt;B</title>
-<path fill="none" stroke="black" d="M43,-52.3C43,-60.02 43,-69.29 43,-77.89"/>
-<polygon fill="black" stroke="black" points="39.5,-77.9 43,-87.9 46.5,-77.9 39.5,-77.9"/>
-</g>
-</g>
-</svg>
+<svg aria-label="Compiler graph" width="168" height="202" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 168 202" 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 DATA" id="e1i0" d="M84,74 L84,122" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#1[0] → #2 · 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(24,128)" 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>A</tspan><tspan class="node-id">  #1</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#1 A
+Type not computed
+
+[0] → #2 DATA</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="n2" data-id="2" transform="translate(24,24)" 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>B</tspan><tspan class="node-id">  #2</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#2 B
+Type not computed
+
+</title></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/chapter01/docs/Makefile b/chapter01/docs/Makefile
index 12659b8f..6fa0f057 100644
--- a/chapter01/docs/Makefile
+++ b/chapter01/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