forma-dsl-mcp 0.3.7 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +18 -18
  2. package/package.json +3 -3
  3. package/src/bin.js +1 -1
  4. package/src/catalogue.js +15 -15
  5. package/src/document.js +8 -38
  6. package/src/examples.js +1 -1
  7. package/src/guide.js +24 -24
  8. package/src/reference/README.md +15 -5
  9. package/src/reference/api/describe-parameters.md +4 -4
  10. package/src/reference/api/evaluation-context.md +13 -13
  11. package/src/reference/api/export.md +7 -7
  12. package/src/reference/api/geometry-node.md +9 -9
  13. package/src/reference/api/kernel.md +8 -8
  14. package/src/reference/api/low-level.md +9 -9
  15. package/src/reference/api/render.md +8 -8
  16. package/src/reference/api/values.md +12 -12
  17. package/src/reference/cli.md +146 -0
  18. package/src/reference/errors.md +14 -14
  19. package/src/reference/language/control-flow.md +3 -3
  20. package/src/reference/language/expressions.md +10 -10
  21. package/src/reference/language/overview.md +11 -11
  22. package/src/reference/language/scope.md +13 -13
  23. package/src/reference/language/syntax.md +4 -4
  24. package/src/reference/reference/align.md +9 -9
  25. package/src/reference/reference/booleans.md +5 -5
  26. package/src/reference/reference/component.md +12 -12
  27. package/src/reference/reference/conversions.md +8 -8
  28. package/src/reference/reference/functions.md +8 -8
  29. package/src/reference/reference/local.md +4 -4
  30. package/src/reference/reference/model.md +10 -10
  31. package/src/reference/reference/param.md +8 -8
  32. package/src/reference/reference/part.md +12 -12
  33. package/src/reference/reference/refinement.md +10 -10
  34. package/src/reference/reference/shapes-2d.md +15 -15
  35. package/src/reference/reference/shapes-3d.md +13 -13
  36. package/src/reference/reference/transforms.md +10 -10
  37. package/src/reference.js +4 -3
  38. package/src/roots.js +2 -2
  39. package/src/server.js +2 -2
  40. package/src/tools.js +15 -15
  41. package/src/workspace.js +7 -7
@@ -25,7 +25,7 @@ Raised while turning characters into tokens. Always a typo.
25
25
 
26
26
  | Message | Cause |
27
27
  | --- | --- |
28
- | `unexpected character "…"` | A character that cannot begin a token — a smart quote, a stray backtick, a non-ASCII dash |
28
+ | `unexpected character "…"` | A character that cannot begin a token - a smart quote, a stray backtick, a non-ASCII dash |
29
29
  | `unterminated string` | A `"` with no closing `"`, or a literal newline inside one |
30
30
  | `unterminated block comment` | A `/*` with no `*/`. Block comments do not nest |
31
31
  | `unknown escape \q` | Only `\n` `\t` `\r` `\"` `\\` `\$` exist |
@@ -42,7 +42,7 @@ Raised while turning tokens into a tree. Always a grammar mistake.
42
42
  | `expected a block, found "…"` | Something at the top level that is not a block |
43
43
  | `unknown top-level block "shape"` | Only `param`, `local`, `component` and `model` may appear at the top level |
44
44
  | `expected an attribute or block, found "…"` | Inside a body, an entry that does not start with a name |
45
- | `expected {, found "…"` | A `param` with no `=` and no body — write `param s { }`. Also a `component` or `model` with no body |
45
+ | `expected {, found "…"` | A `param` with no `=` and no body - write `param s { }`. Also a `component` or `model` with no body |
46
46
  | `expected =, found "…"` | A `local` with no value |
47
47
  | `expected ident, found "…"` | A keyword where a name was required |
48
48
  | `"for" cannot be used as a block type` | One of the eleven [keywords](language/syntax.md#keywords) used as a block name |
@@ -93,7 +93,7 @@ Raised while evaluating.
93
93
  | Message | Cause |
94
94
  | --- | --- |
95
95
  | `unknown name "x"` | Nothing in scope binds it. See [Scope](language/scope.md) |
96
- | `no attribute "x"` | A missing key on an object — including `var.x` for something that is not a param |
96
+ | `no attribute "x"` | A missing key on an object - including `var.x` for something that is not a param |
97
97
  | `cannot read "x" of nothing` | A property read on `null` |
98
98
  | `cannot index this value` | Indexing something that is not a list, string or object |
99
99
  | `cannot negate a non-number` | Unary `-` on a string, list or bool |
@@ -115,7 +115,7 @@ operator: `outer - inner`. See [Identifiers](language/syntax.md#identifiers).
115
115
  ### `unknown name "var"`
116
116
 
117
117
  A param default referred to `var`, which is assembled only after every param has resolved.
118
- Use the earlier param's bare name instead — see
118
+ Use the earlier param's bare name instead - see
119
119
  [the root scope](language/scope.md#the-root-scope).
120
120
 
121
121
  ---
@@ -135,11 +135,11 @@ Raised while building geometry.
135
135
 
136
136
  | Message | Cause |
137
137
  | --- | --- |
138
- | `unknown block "bloop" — the blocks are …` | Not a builtin and not a declared component. The message lists every block there is, this document's own components first |
138
+ | `unknown block "bloop" - the blocks are …` | Not a builtin and not a declared component. The message lists every block there is, this document's own components first |
139
139
  | `"box" does not take a label` | Only [`part`](reference/part.md) takes a meaningful label |
140
140
  | `"box" is a shape and cannot contain other blocks` | A shape is a leaf. Usually a missing `}` swallowed the next block |
141
141
  | `"translate" needs at least one shape inside it` | An operation with nothing to act on |
142
- | `box: unknown attribute "colour" — box reads …` | Every block rejects attributes it did not read, so a typo fails loudly, and names what it would have read |
142
+ | `box: unknown attribute "colour" - box reads …` | Every block rejects attributes it did not read, so a typo fails loudly, and names what it would have read |
143
143
  | `attribute "size" is set twice` | The same attribute written twice in one block |
144
144
  | `box: "size" must be a number or an array of 3 numbers` | Wrong shape of value |
145
145
  | `circle: "radius" must be a finite number` | A `NaN` or `Infinity` reached the block |
@@ -149,7 +149,7 @@ Raised while building geometry.
149
149
  | `cylinder: give "radius"/"diameter", or both "bottom_radius" and "top_radius"` | One end radius on its own is ambiguous |
150
150
  | `offset: "join" must be one of "Square", "Round", "Miter"` | An enum attribute with an unrecognised value |
151
151
 
152
- ### `box: unknown attribute "colour" — box reads "center", "size"`
152
+ ### `box: unknown attribute "colour" - box reads "center", "size"`
153
153
 
154
154
  Worth dwelling on, because it is the error that saves the most time. A misspelled attribute
155
155
  would otherwise be silent, and the model would render subtly wrong with nothing to point at.
@@ -158,8 +158,8 @@ anything through.
158
158
 
159
159
  Both this and `unknown block` carry the alternatives, because the reader is often a program
160
160
  with no copy of this manual: an error that says only what is wrong leaves it guessing at
161
- spellings. A block that branches on which of two spellings it was given — `radius` against
162
- `diameter` — reads only one of them, so the list is what that call would accept rather than
161
+ spellings. A block that branches on which of two spellings it was given - `radius` against
162
+ `diameter` - reads only one of them, so the list is what that call would accept rather than
163
163
  always the whole set.
164
164
 
165
165
  ---
@@ -170,7 +170,7 @@ always the whole set.
170
170
  | --- | --- |
171
171
  | `"union" cannot mix 2D and 3D shapes` | Children of different dimensionality in one block |
172
172
  | `"extrude" takes 2D geometry, got 3D` | A block that fixes what it takes got the other thing |
173
- | `part "body" is 2D — extrude or revolve it before rendering` | A cross-section reached the renderer |
173
+ | `part "body" is 2D - extrude or revolve it before rendering` | A cross-section reached the renderer |
174
174
  | `cannot combine 2D and 3D geometry in a union` | The same mistake, from the [node API](api/geometry-node.md) rather than a document |
175
175
 
176
176
  See [Dimensionality](language/overview.md#dimensionality) for the three rules, and
@@ -186,7 +186,7 @@ See [Dimensionality](language/overview.md#dimensionality) for the three rules, a
186
186
  | `c: unknown param "t"` | Components have no pass-through: every value must be declared |
187
187
  | `component "c" produced no geometry` | An empty body, a loop over an empty list, or an `if` that took no branch |
188
188
  | `a component cannot declare parts` | Scene structure belongs in the model |
189
- | `nesting deeper than 64 blocks — is a component using itself?` | Direct or mutual recursion. Rewrite as a `for` loop |
189
+ | `nesting deeper than 64 blocks - is a component using itself?` | Direct or mutual recursion. Rewrite as a `for` loop |
190
190
 
191
191
  ---
192
192
 
@@ -200,7 +200,7 @@ See [Dimensionality](language/overview.md#dimensionality) for the three rules, a
200
200
  | `the document declares no model` | Add a `model` block |
201
201
  | `no model named "x"` | `render(source, { model })` named one that does not exist |
202
202
 
203
- A [`part`](reference/part.md) nested inside an operation raises **nothing** — it is silently
203
+ A [`part`](reference/part.md) nested inside an operation raises **nothing** - it is silently
204
204
  dropped. If a part is missing from a render, check it is written directly in the model.
205
205
 
206
206
  ---
@@ -217,7 +217,7 @@ dropped. If a part is missing from a render, check it is written directly in the
217
217
 
218
218
  | Message | Cause |
219
219
  | --- | --- |
220
- | `align needs a kernel context — render the model rather than building it` | An `Evaluator` built with no context. [`render`](api/render.md) always supplies one |
220
+ | `align needs a kernel context - render the model rather than building it` | An `Evaluator` built with no context. [`render`](api/render.md) always supplies one |
221
221
  | `align needs at least one shape inside it` | Nothing to measure |
222
222
  | `align: "x" must be "min", "center", "max" or a number` | The spelling is `"center"` |
223
223
 
@@ -239,7 +239,7 @@ Raised by the JavaScript API rather than by a document.
239
239
 
240
240
  ## Silent behaviours
241
241
 
242
- Not errors, and each is deliberate — but each has surprised someone. A parameter reaching the
242
+ Not errors, and each is deliberate - but each has surprised someone. A parameter reaching the
243
243
  end of its range should not break a model, so these degenerate quietly:
244
244
 
245
245
  | What | Result |
@@ -1,7 +1,7 @@
1
1
  # Control flow
2
2
 
3
3
  Two constructs, both of which appear where a block appears and produce geometry rather than
4
- a value. Neither is an expression — for a value that depends on a condition, use
4
+ a value. Neither is an expression - for a value that depends on a condition, use
5
5
  [`cond ? a : b`](expressions.md#conditional).
6
6
 
7
7
  ---
@@ -38,7 +38,7 @@ difference {
38
38
  | **Sequence** | Any expression that evaluates to a list. Iterating anything else is an error: `for: expected a list to iterate` |
39
39
  | **Names** | One name binds the value. Two names bind the index and then the value |
40
40
  | **Scope** | Each iteration gets a fresh child scope holding just those names |
41
- | **Result** | The concatenation of what every iteration produced — geometry, and any `part`s |
41
+ | **Result** | The concatenation of what every iteration produced - geometry, and any `part`s |
42
42
 
43
43
  #### Caveats
44
44
 
@@ -47,7 +47,7 @@ difference {
47
47
  [`if`](#if) when a parameter can legitimately reach zero.
48
48
  - `range(a, b)` **excludes** `b`, and produces an empty list rather than looping forever when
49
49
  the range runs backwards. See [`range`](../reference/functions.md#range).
50
- - Iterating a string does not work — strings are not lists here. Use
50
+ - Iterating a string does not work - strings are not lists here. Use
51
51
  [`len`](../reference/functions.md#len) and index into it if you need the characters.
52
52
  - There is no `break`, no `continue` and no accumulator. A loop that needs to skip items
53
53
  should filter the list it iterates, or wrap its body in an `if`.
@@ -17,7 +17,7 @@ their type, and a mismatch is reported where it is used.
17
17
  | **object** | `{ w = 3, h = 4 }`, `{ w: 3 }` | `=` and `:` are interchangeable. Read with `.key` |
18
18
  | **null** | `null` | Falsy; renders as the empty string in interpolation |
19
19
 
20
- There is no separate vector type in the language — a vector is a list of numbers, and blocks
20
+ There is no separate vector type in the language - a vector is a list of numbers, and blocks
21
21
  say how many components they want. Where a block wants a vector, a **single number is
22
22
  accepted as shorthand for every component**, so `scale { factor = 2 }` and
23
23
  `scale { factor = [2, 2, 2] }` are the same.
@@ -69,7 +69,7 @@ What each accepts:
69
69
  Two lists of different lengths cannot be combined:
70
70
  `cannot combine vectors of length 2 and 3`.
71
71
 
72
- **Division and modulo by zero are errors**, not `Infinity` or `NaN` — a NaN would otherwise
72
+ **Division and modulo by zero are errors**, not `Infinity` or `NaN` - a NaN would otherwise
73
73
  travel all the way into the kernel and surface as a degenerate vertex a long way from its
74
74
  cause.
75
75
 
@@ -89,7 +89,7 @@ as [interpolation](#string-interpolation):
89
89
 
90
90
  ```hcl
91
91
  [1, 2] == [1, 2] // true
92
- 1 == "1" // false — different types are never equal
92
+ 1 == "1" // false - different types are never equal
93
93
  ```
94
94
 
95
95
  `<`, `<=`, `>` and `>=` require both sides to be the same type, and compare numbers
@@ -99,7 +99,7 @@ numerically and strings lexicographically. Mixing them is an error:
99
99
  ### Logic
100
100
 
101
101
  `&&` and `||` short-circuit, so the right side is not evaluated when the left decides the
102
- answer — `count > 0 && list[0] > 1` is safe on an empty list. Both always return a bool, even
102
+ answer - `count > 0 && list[0] > 1` is safe on an empty list. Both always return a bool, even
103
103
  when given non-bool operands: `1 || false` is `true`, not `1`.
104
104
 
105
105
  `!x` negates the [truthiness](#truthiness) of any value. Unary `-` requires a number:
@@ -118,7 +118,7 @@ right-associative, so `a ? b : c ? d : e` chains as `a ? b : (c ? d : e)`.
118
118
 
119
119
  ## Truthiness
120
120
 
121
- Conditions — in `? :`, `&&`, `||`, `!` and [`if`](control-flow.md#if) — accept any value.
121
+ Conditions - in `? :`, `&&`, `||`, `!` and [`if`](control-flow.md#if) - accept any value.
122
122
 
123
123
  | Falsy | Truthy |
124
124
  | --- | --- |
@@ -147,7 +147,7 @@ opts.wall // 2
147
147
  opts["wall"] // 2
148
148
  ```
149
149
 
150
- `.x`, `.y` and `.z` on a **list** are sugar for indices 0, 1 and 2 — they are not a vector
150
+ `.x`, `.y` and `.z` on a **list** are sugar for indices 0, 1 and 2 - they are not a vector
151
151
  type. They are read on a list of any length, so `[1, 2].z` yields nothing rather than
152
152
  raising; the failure surfaces later, wherever that nothing is used.
153
153
 
@@ -165,7 +165,7 @@ an error: `cannot index this value`.
165
165
  ```hcl
166
166
  max(sizes) // variadic, flattens its arguments
167
167
  pow(2, 10)
168
- sin(30) // 0.5 — every angle is in degrees
168
+ sin(30) // 0.5 - every angle is in degrees
169
169
  ```
170
170
 
171
171
  Functions take **positional arguments only**. Named arguments are an error:
@@ -194,12 +194,12 @@ How each value renders:
194
194
 
195
195
  | Value | Rendered as |
196
196
  | --- | --- |
197
- | number | `3`, `0.5` — JavaScript's default number formatting |
197
+ | number | `3`, `0.5` - JavaScript's default number formatting |
198
198
  | string | itself |
199
199
  | bool | `true` / `false` |
200
- | list | `[1, 2, 3]` — elements joined by `, ` inside brackets, recursively |
200
+ | list | `[1, 2, 3]` - elements joined by `, ` inside brackets, recursively |
201
201
  | `null` | the empty string |
202
- | object | `{w: 3, h: 4}` — each entry rendered by these same rules. Interpolating one is usually a mistake, but it at least names which object |
202
+ | object | `{w: 3, h: 4}` - each entry rendered by these same rules. Interpolating one is usually a mistake, but it at least names which object |
203
203
 
204
204
  A splice must contain exactly one expression; anything left over is an error:
205
205
  `trailing input in ${ }`. Errors raised inside a splice are reported at the position of the
@@ -21,7 +21,7 @@ model "stack" { // something to render
21
21
  }
22
22
  ```
23
23
 
24
- Order does not matter between declarations of different kinds — a `model` may use a
24
+ Order does not matter between declarations of different kinds - a `model` may use a
25
25
  `component` declared below it. Order does matter among `local`s, which are evaluated top to
26
26
  bottom. See [Scope and names](scope.md).
27
27
 
@@ -41,11 +41,11 @@ mistake it was.
41
41
  1. **Lexer** turns characters into tokens. It rejects unknown characters, unterminated
42
42
  strings and unterminated block comments.
43
43
  2. **Parser** turns tokens into an abstract syntax tree. It rejects anything that is not
44
- grammatical — a missing brace, a keyword used as a block type, a `param` block containing
44
+ grammatical - a missing brace, a keyword used as a block type, a `param` block containing
45
45
  nested blocks.
46
46
  3. **Evaluator** resolves params and locals, then walks the models. It rejects unknown names,
47
47
  bad operands, unknown blocks, unknown attributes and dimensionality mismatches. Its output
48
- is a tree of [geometry nodes](../api/geometry-node.md) — it never calls the kernel itself.
48
+ is a tree of [geometry nodes](../api/geometry-node.md) - it never calls the kernel itself.
49
49
  4. **Kernel** solves the node tree into concrete solids, memoized by node digest in an
50
50
  [`EvaluationContext`](../api/evaluation-context.md).
51
51
 
@@ -95,9 +95,9 @@ An operation with no children is an error; a shape with children is an error.
95
95
 
96
96
  Every piece of geometry is either 2D or 3D, and the language tracks which.
97
97
 
98
- - **2D shapes** — `rect`, `rounded_rect`, `circle`, `ellipse`, `regular_polygon`, `stadium`,
99
- `polygon` — produce cross-sections.
100
- - **3D shapes** — `box`, `sphere`, `cylinder`, `cone`, `torus` — produce solids.
98
+ - **2D shapes** - `rect`, `rounded_rect`, `circle`, `ellipse`, `regular_polygon`, `stadium`,
99
+ `polygon` - produce cross-sections.
100
+ - **3D shapes** - `box`, `sphere`, `cylinder`, `cone`, `torus` - produce solids.
101
101
  - **Operations** either fix what they accept (`extrude` takes 2D, `refine` takes 3D) or pass
102
102
  their children's dimensionality through (`translate`, `rotate`, `hull`, the booleans).
103
103
 
@@ -107,7 +107,7 @@ Three rules follow, and each has its own error message:
107
107
  | --- | --- |
108
108
  | The children of one block must all be the same dimensionality | `"union" cannot mix 2D and 3D shapes` |
109
109
  | A block that fixes what it takes must get it | `"extrude" takes 2D geometry, got 3D` |
110
- | Anything rendered must be 3D | `part "body" is 2D — extrude or revolve it before rendering` |
110
+ | Anything rendered must be 3D | `part "body" is 2D - extrude or revolve it before rendering` |
111
111
 
112
112
  To get from 2D to 3D use [`extrude`](../reference/conversions.md#extrude) or
113
113
  [`revolve`](../reference/conversions.md#revolve); to go back use
@@ -118,8 +118,8 @@ To get from 2D to 3D use [`extrude`](../reference/conversions.md#extrude) or
118
118
 
119
119
  ## Units and orientation
120
120
 
121
- - **Lengths are unitless.** Every consumer treats them as millimetres — STL has no unit
122
- either — but nothing in the language enforces that.
121
+ - **Lengths are unitless.** Every consumer treats them as millimetres - STL has no unit
122
+ either - but nothing in the language enforces that.
123
123
  - **Angles are always in degrees.** Everywhere: `rotate { z = 90 }`, `revolve { angle = 180 }`,
124
124
  `sin(30)`, `param a { type = angle }`. There is no radian form in the language. The
125
125
  JavaScript [`Angle`](../api/values.md#angle) class stores radians internally, and that is
@@ -134,7 +134,7 @@ To get from 2D to 3D use [`extrude`](../reference/conversions.md#extrude) or
134
134
 
135
135
  ## Identity is content
136
136
 
137
- The evaluator does not produce a call graph — it produces a tree of immutable nodes, each
137
+ The evaluator does not produce a call graph - it produces a tree of immutable nodes, each
138
138
  carrying a 128-bit digest of its own content and its children's digests. Two structurally
139
139
  identical subtrees have the same digest and therefore *are* the same node.
140
140
 
@@ -142,7 +142,7 @@ That has consequences worth designing around:
142
142
 
143
143
  - A component used twenty times is evaluated once. Eight spheres in a loop cost one sphere
144
144
  and eight transforms.
145
- - Two shapes written with their attributes in a different order still share a cache entry —
145
+ - Two shapes written with their attributes in a different order still share a cache entry -
146
146
  object keys are sorted before they are digested.
147
147
  - The members of a `union` are sorted by subtree size, so two unions written in a different
148
148
  order also share a digest. `difference` is never reordered, because its first child means
@@ -1,7 +1,7 @@
1
1
  # Scope and names
2
2
 
3
3
  Every name in a document resolves through a chain of scopes. This page says exactly what is
4
- in each one, and in what order they are built — which is what determines whether a
4
+ in each one, and in what order they are built - which is what determines whether a
5
5
  declaration may refer to another.
6
6
 
7
7
  ---
@@ -10,12 +10,12 @@ declaration may refer to another.
10
10
 
11
11
  Built once, before any geometry, in this order:
12
12
 
13
- 1. **Constants** — `pi` and `e`.
14
- 2. **Type names** — `number`, `string`, `bool`, `vector`, `list`, `angle`, each bound to
13
+ 1. **Constants** - `pi` and `e`.
14
+ 2. **Type names** - `number`, `string`, `bool`, `vector`, `list`, `angle`, each bound to
15
15
  itself as a string, so `type = number` in a param block resolves with no quoting.
16
16
  3. **Params**, in source order. Each is bound under its bare name as it is resolved, so a
17
17
  later param's default may refer to an earlier one.
18
- 4. **`var`** — an object holding every resolved param.
18
+ 4. **`var`** - an object holding every resolved param.
19
19
  5. **Locals**, in source order. Each is bound as it is resolved, so a later local may refer
20
20
  to an earlier one.
21
21
 
@@ -24,10 +24,10 @@ The order has two consequences worth knowing:
24
24
  ```hcl
25
25
  param a = 3
26
26
  param b = a * 2 // ✓ an earlier param, by its bare name
27
- param c = var.a * 2 // ✗ unknown name "var" — var does not exist yet
27
+ param c = var.a * 2 // ✗ unknown name "var" - var does not exist yet
28
28
 
29
29
  local d = var.a + 1 // ✓ locals are built after var
30
- local e = f // ✗ unknown name "f" — a later local
30
+ local e = f // ✗ unknown name "f" - a later local
31
31
  local f = 2
32
32
  ```
33
33
 
@@ -45,8 +45,8 @@ model "m" {
45
45
  }
46
46
  ```
47
47
 
48
- They differ when something shadows the bare name. `var` holds **params only** — never
49
- locals, never component params, never loop variables — so `var.NAME` is the unambiguous way
48
+ They differ when something shadows the bare name. `var` holds **params only** - never
49
+ locals, never component params, never loop variables - so `var.NAME` is the unambiguous way
50
50
  to reach an input:
51
51
 
52
52
  ```hcl
@@ -68,8 +68,8 @@ of use.
68
68
 
69
69
  ## Body scopes
70
70
 
71
- Every block body — a `model`, a `component`, a builtin block, a `for` iteration, an `if`
72
- branch — evaluates in a fresh scope nested inside its parent.
71
+ Every block body - a `model`, a `component`, a builtin block, a `for` iteration, an `if`
72
+ branch - evaluates in a fresh scope nested inside its parent.
73
73
 
74
74
  **Locals declared in a body are visible to the whole body**, not only to what follows them,
75
75
  because every local in a body is resolved before any of its blocks are built:
@@ -82,7 +82,7 @@ model "m" {
82
82
  ```
83
83
 
84
84
  That is the opposite of the top-level rule, where locals are strictly ordered. Within one
85
- body, ordering between locals still applies — a body local may not refer to a later body
85
+ body, ordering between locals still applies - a body local may not refer to a later body
86
86
  local.
87
87
 
88
88
  ---
@@ -110,7 +110,7 @@ model "m" {
110
110
  Inside a component body, these are in scope:
111
111
 
112
112
  - its own `param`s, bound from the attributes at the call site
113
- - everything in the root scope — constants, type names, document params (bare and via `var.`)
113
+ - everything in the root scope - constants, type names, document params (bare and via `var.`)
114
114
  and document locals
115
115
  - its own body locals
116
116
 
@@ -123,7 +123,7 @@ Nothing from the caller. Values travel in through params and no other way.
123
123
  A `model` may declare its own `param` blocks. They behave like locals with a declaration
124
124
  syntax, not like document params:
125
125
 
126
- - Each **must** have a default — `model "m": param "s" needs a default`.
126
+ - Each **must** have a default - `model "m": param "s" needs a default`.
127
127
  - They are **not** settable from outside. `render(source, { params: { s: 9 } })` sets
128
128
  document params only; a model param keeps its default.
129
129
  - They are bound in the model's scope under the bare name, and are not added to `var`.
@@ -31,7 +31,7 @@ Decimal, with an optional fraction and an optional exponent.
31
31
  There is no hexadecimal, no separator, and no unit suffix. A leading `-` is the unary minus
32
32
  operator, not part of the literal.
33
33
 
34
- `2e` and `2em` are **not** numbers with exponents — the lexer backs out of a bare `e` and
34
+ `2e` and `2em` are **not** numbers with exponents - the lexer backs out of a bare `e` and
35
35
  lets it start an identifier, so `2e` lexes as the number `2` followed by the name `e`.
36
36
 
37
37
  ---
@@ -46,7 +46,7 @@ Double quotes only. A string may not contain a literal newline.
46
46
  "a tab\there, a quote\", a literal \${ not a splice"
47
47
  ```
48
48
 
49
- **Escapes.** Exactly six: `\n`, `\t`, `\r`, `\"`, `\\` and `\$`. Anything else is an error —
49
+ **Escapes.** Exactly six: `\n`, `\t`, `\r`, `\"`, `\\` and `\$`. Anything else is an error -
50
50
  `unknown escape \q`. `\$` exists so a literal `${` can be written.
51
51
 
52
52
  **Interpolation.** `${ ... }` splices in any expression. Braces inside the splice are
@@ -65,7 +65,7 @@ height wall_thickness _private min-width part2
65
65
  ```
66
66
 
67
67
  > **Hyphens are part of a name.** `a-b` is one identifier, not `a` minus `b`. Write
68
- > subtraction with spaces: `a - b`. This is the single most common surprise in the lexer —
68
+ > subtraction with spaces: `a - b`. This is the single most common surprise in the lexer -
69
69
  > `local gap = outer-inner` fails with `unknown name "outer-inner"`.
70
70
 
71
71
  ### Keywords
@@ -111,7 +111,7 @@ Labels come in two flavours:
111
111
  | `component "name"`, `model "name"` | Static | The name must be a plain string or identifier. `${ }` is rejected: `a label cannot contain ${ }` |
112
112
  | `part "name"` | An expression | Evaluated at build time, so a part in a loop can name itself: `part "leg_${i}"` |
113
113
 
114
- Blocks that take no label reject one — `"box" does not take a label`. Two blocks accept a
114
+ Blocks that take no label reject one - `"box" does not take a label`. Two blocks accept a
115
115
  label and quietly ignore it: [`align`](../reference/align.md) and
116
116
  [component calls](../reference/component.md).
117
117
 
@@ -46,13 +46,13 @@ the *minimum* face at that coordinate. `z = 5` means "sit this on a surface at h
46
46
 
47
47
  #### Returns
48
48
 
49
- The children, unioned and translated. Dimensionality passes through — a 2D child accepts
49
+ The children, unioned and translated. Dimensionality passes through - a 2D child accepts
50
50
  `x` and `y` only.
51
51
 
52
52
  #### Caveats
53
53
 
54
54
  - **`align` needs a kernel context.** It is the one block that cannot be evaluated without
55
- one: `align needs a kernel context — render the model rather than building it`. Building a
55
+ one: `align needs a kernel context - render the model rather than building it`. Building a
56
56
  node tree with a bare `Evaluator` and no context will fail on it.
57
57
  - **It costs a kernel round-trip.** The child has to be solved before the alignment can be
58
58
  computed, so the subtree is evaluated at build time rather than lazily. The digest cache
@@ -60,7 +60,7 @@ The children, unioned and translated. Dimensionality passes through — a 2D chi
60
60
  - It measures the **bounding box**, not the geometry. For anything that is not
61
61
  axis-aligned, the box is larger than the shape.
62
62
  - Children are unioned first, so several blocks inside one `align` move together.
63
- - A label — `align "name" { }` — is accepted and silently ignored.
63
+ - A label - `align "name" { }` - is accepted and silently ignored.
64
64
 
65
65
  ---
66
66
 
@@ -81,7 +81,7 @@ align {
81
81
  }
82
82
  ```
83
83
 
84
- Most shapes take a `center = true` attribute, which is cheaper — it changes how the shape is
84
+ Most shapes take a `center = true` attribute, which is cheaper - it changes how the shape is
85
85
  built rather than measuring it afterwards. Reach for `align` when the thing being centred is
86
86
  a **composition** rather than a single shape, and there is no one attribute to set.
87
87
 
@@ -131,9 +131,9 @@ align {
131
131
 
132
132
  | Approach | When |
133
133
  | --- | --- |
134
- | `center = true` on the shape | A single primitive. Free — it changes how the shape is built |
134
+ | `center = true` on the shape | A single primitive. Free - it changes how the shape is built |
135
135
  | `translate` by a known amount | You already know the dimensions, usually because they are params |
136
- | `align` | The extent is **computed** — a hull, a composition, an offset outline — and writing the arithmetic would mean duplicating it |
136
+ | `align` | The extent is **computed** - a hull, a composition, an offset outline - and writing the arithmetic would mean duplicating it |
137
137
 
138
138
  A model that knows its own dimensions should use them:
139
139
 
@@ -154,7 +154,7 @@ kernel round-trip. For anything small, prefer clarity.
154
154
 
155
155
  ## Troubleshooting
156
156
 
157
- ### `align needs a kernel context — render the model rather than building it`
157
+ ### `align needs a kernel context - render the model rather than building it`
158
158
 
159
159
  An `Evaluator` was constructed without a `context`, and the document contains an `align`.
160
160
  [`render`](../api/render.md) always supplies one, so this only comes up when driving the
@@ -170,12 +170,12 @@ unaffected.
170
170
 
171
171
  ### `align: "x" must be "min", "center", "max" or a number`
172
172
 
173
- The value is a string that is not one of the three keywords — `"middle"` and `"centre"` are
173
+ The value is a string that is not one of the three keywords - `"middle"` and `"centre"` are
174
174
  the usual culprits. The spelling is `"center"`.
175
175
 
176
176
  ### `align needs at least one shape inside it`
177
177
 
178
- The body produced nothing — an `if` that took no branch, or a `for` over an empty list.
178
+ The body produced nothing - an `if` that took no branch, or a `for` over an empty list.
179
179
 
180
180
  ### `align: unknown attribute "z"` on a 2D shape
181
181
 
@@ -27,7 +27,7 @@ None.
27
27
 
28
28
  - **Usually unnecessary.** Sibling blocks are already implicitly unioned, so
29
29
  `part "p" { a b }` and `part "p" { union { a b } }` are the same. Write `union`
30
- explicitly when it makes the grouping clearer — typically as one operand of a
30
+ explicitly when it makes the grouping clearer - typically as one operand of a
31
31
  `difference`.
32
32
  - The order of the children does not affect the result, and does not affect the digest
33
33
  either: union members are sorted by subtree size before hashing, so two unions written in a
@@ -86,7 +86,7 @@ None.
86
86
 
87
87
  #### Caveats
88
88
 
89
- - With one child, it is that child. With none, it is an error —
89
+ - With one child, it is that child. With none, it is an error -
90
90
  `"intersection" needs at least one shape inside it`.
91
91
  - Order does not affect the result.
92
92
 
@@ -149,7 +149,7 @@ intersection {
149
149
  }
150
150
  ```
151
151
 
152
- For a single flat cut, [`trim`](refinement.md#trim) is cheaper — it cuts with a half-space
152
+ For a single flat cut, [`trim`](refinement.md#trim) is cheaper - it cuts with a half-space
153
153
  and needs no second solid.
154
154
 
155
155
  ### Grouping so a transform applies to several shapes
@@ -164,7 +164,7 @@ translate {
164
164
  }
165
165
  ```
166
166
 
167
- Inside a `difference`, though, each child is separate — so to subtract two shapes *as one
167
+ Inside a `difference`, though, each child is separate - so to subtract two shapes *as one
168
168
  body*, group them explicitly:
169
169
 
170
170
  ```hcl
@@ -178,7 +178,7 @@ difference {
178
178
  ```
179
179
 
180
180
  Here the `union` is not decorative: without it the two cylinders would be two separate
181
- cutters, which happens to give the same result for a difference — but not for an
181
+ cutters, which happens to give the same result for a difference - but not for an
182
182
  `intersection`, where it changes the answer entirely.
183
183
 
184
184
  ---
@@ -27,7 +27,7 @@ model "m" {
27
27
  Declares a block named `NAME`. Its body is ordinary geometry; its `param` declarations are
28
28
  the attributes a call site may pass.
29
29
 
30
- Once declared, it is called like any other block — the call site cannot tell a component
30
+ Once declared, it is called like any other block - the call site cannot tell a component
31
31
  from a builtin, which is the point.
32
32
 
33
33
  #### Body
@@ -41,7 +41,7 @@ from a builtin, which is the point.
41
41
 
42
42
  #### Returns
43
43
 
44
- One geometry node — everything in the body, unioned. The dimensionality is whatever the body
44
+ One geometry node - everything in the body, unioned. The dimensionality is whatever the body
45
45
  produced, so a component may return 2D or 3D geometry and be used wherever that fits.
46
46
 
47
47
  #### Caveats
@@ -60,8 +60,8 @@ produced, so a component may return 2D or 3D geometry and be used wherever that
60
60
  branch, fails with `component "c" produced no geometry`.
61
61
  - **Recursion is not supported.** A component that calls itself has no base case the
62
62
  evaluator can see, and hits the nesting limit:
63
- `nesting deeper than 64 blocks — is a component using itself?`
64
- - A label on a call — `my_component "label" { }` — is accepted and silently ignored.
63
+ `nesting deeper than 64 blocks - is a component using itself?`
64
+ - A label on a call - `my_component "label" { }` - is accepted and silently ignored.
65
65
  Do not rely on it.
66
66
 
67
67
  ---
@@ -88,7 +88,7 @@ component "tray" {
88
88
  }
89
89
  ```
90
90
 
91
- Note the empty braces on `lid`. `param lid` on its own is a **syntax error** — a param needs
91
+ Note the empty braces on `lid`. `param lid` on its own is a **syntax error** - a param needs
92
92
  either `= expression` or a braced body, even an empty one.
93
93
 
94
94
  Inside the body, a param is read by its bare name. There is no `var.` for component params;
@@ -172,7 +172,7 @@ model "m" {
172
172
  ### Reuse is free
173
173
 
174
174
  Identity in the geometry tree is content identity, so a component called twenty times with
175
- the same arguments is **evaluated once** — its digest is the same each time, and the kernel
175
+ the same arguments is **evaluated once** - its digest is the same each time, and the kernel
176
176
  sees one node. Calling it twenty times with twenty different sizes does cost twenty
177
177
  evaluations, because the content genuinely differs.
178
178
 
@@ -190,7 +190,7 @@ A `param` with no default and no body:
190
190
 
191
191
  ```hcl
192
192
  component "c" {
193
- param s // ✗ syntax error — the parser is looking for `=` or `{`
193
+ param s // ✗ syntax error - the parser is looking for `=` or `{`
194
194
  box { size = [s, s, s] }
195
195
  }
196
196
 
@@ -207,19 +207,19 @@ the call site, or give the declaration a default.
207
207
 
208
208
  ### `c: unknown param "t"`
209
209
 
210
- The call passed an attribute the component does not declare — usually a typo, or an
210
+ The call passed an attribute the component does not declare - usually a typo, or an
211
211
  attribute meant for a block inside the component. Components have no pass-through: every
212
212
  value they need must be declared.
213
213
 
214
214
  ### `component "box" shadows a builtin block`
215
215
 
216
- The name is already a builtin. Pick another — `rounded_box`, `my_box`, `case_body`. See the
216
+ The name is already a builtin. Pick another - `rounded_box`, `my_box`, `case_body`. See the
217
217
  [full block list](../README.md#shapes) for what is taken.
218
218
 
219
219
  ### `a component cannot declare parts`
220
220
 
221
221
  A [`part`](part.md) is a piece of the scene, not geometry an enclosing block can operate on,
222
- so it cannot come out of a component. Invert it — put the part in the model and call the
222
+ so it cannot come out of a component. Invert it - put the part in the model and call the
223
223
  component inside it:
224
224
 
225
225
  ```hcl
@@ -231,7 +231,7 @@ component "c" { box { size = [1, 1, 1] } }
231
231
  model "m" { part "p" { c { } } }
232
232
  ```
233
233
 
234
- ### `nesting deeper than 64 blocks — is a component using itself?`
234
+ ### `nesting deeper than 64 blocks - is a component using itself?`
235
235
 
236
236
  Almost always direct or mutual recursion. There is no base case the evaluator can detect,
237
237
  because a component's body is expanded before any condition inside it is known. Rewrite the
@@ -239,7 +239,7 @@ recursion as a [`for`](../language/control-flow.md#for) loop.
239
239
 
240
240
  ### `component "c" produced no geometry`
241
241
 
242
- The body evaluated to nothing — an empty body, a loop over an empty list, or an `if` with no
242
+ The body evaluated to nothing - an empty body, a loop over an empty list, or an `if` with no
243
243
  `else` whose condition was false. A component must return something; guard at the call site
244
244
  instead:
245
245