forma-dsl-mcp 0.1.4

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 (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +97 -0
  3. package/package.json +52 -0
  4. package/src/bin.js +30 -0
  5. package/src/catalogue.js +703 -0
  6. package/src/document.js +285 -0
  7. package/src/examples.js +212 -0
  8. package/src/guide.js +313 -0
  9. package/src/reference/README.md +153 -0
  10. package/src/reference/api/describe-parameters.md +167 -0
  11. package/src/reference/api/evaluation-context.md +221 -0
  12. package/src/reference/api/export.md +185 -0
  13. package/src/reference/api/geometry-node.md +215 -0
  14. package/src/reference/api/kernel.md +182 -0
  15. package/src/reference/api/low-level.md +239 -0
  16. package/src/reference/api/render.md +217 -0
  17. package/src/reference/api/values.md +223 -0
  18. package/src/reference/errors.md +253 -0
  19. package/src/reference/language/control-flow.md +160 -0
  20. package/src/reference/language/expressions.md +206 -0
  21. package/src/reference/language/overview.md +156 -0
  22. package/src/reference/language/scope.md +184 -0
  23. package/src/reference/language/syntax.md +210 -0
  24. package/src/reference/reference/align.md +189 -0
  25. package/src/reference/reference/booleans.md +231 -0
  26. package/src/reference/reference/component.md +248 -0
  27. package/src/reference/reference/conversions.md +269 -0
  28. package/src/reference/reference/functions.md +314 -0
  29. package/src/reference/reference/local.md +197 -0
  30. package/src/reference/reference/model.md +213 -0
  31. package/src/reference/reference/param.md +230 -0
  32. package/src/reference/reference/part.md +229 -0
  33. package/src/reference/reference/refinement.md +284 -0
  34. package/src/reference/reference/shapes-2d.md +309 -0
  35. package/src/reference/reference/shapes-3d.md +279 -0
  36. package/src/reference/reference/transforms.md +246 -0
  37. package/src/reference.js +160 -0
  38. package/src/server.js +219 -0
  39. package/src/tools.js +523 -0
  40. package/src/workspace.js +204 -0
@@ -0,0 +1,206 @@
1
+ # Expressions
2
+
3
+ Expressions appear on the right of every attribute, in `for` sequences, in `if` conditions,
4
+ in `part` labels and inside `${ }` splices. The language is dynamically typed: values carry
5
+ their type, and a mismatch is reported where it is used.
6
+
7
+ ---
8
+
9
+ ## Values
10
+
11
+ | Type | Literals | Notes |
12
+ | --- | --- | --- |
13
+ | **number** | `12`, `0.5`, `1e3` | Double precision. Infinities and NaN cannot be written and are rejected where a block reads a number |
14
+ | **string** | `"text"`, `"a ${b} c"` | Double quotes only; see [Syntax → Strings](syntax.md#strings) |
15
+ | **bool** | `true`, `false` | |
16
+ | **list** | `[1, 2, 3]`, `[[0,0], [1,0]]` | Heterogeneous and nestable. A list of numbers is what every block means by a *vector* |
17
+ | **object** | `{ w = 3, h = 4 }`, `{ w: 3 }` | `=` and `:` are interchangeable. Read with `.key` |
18
+ | **null** | `null` | Falsy; renders as the empty string in interpolation |
19
+
20
+ There is no separate vector type in the language — a vector is a list of numbers, and blocks
21
+ say how many components they want. Where a block wants a vector, a **single number is
22
+ accepted as shorthand for every component**, so `scale { factor = 2 }` and
23
+ `scale { factor = [2, 2, 2] }` are the same.
24
+
25
+ ---
26
+
27
+ ## Operators
28
+
29
+ Listed loosest-binding first. Everything in one row has equal precedence and associates left
30
+ to right.
31
+
32
+ | Precedence | Operators | Notes |
33
+ | --- | --- | --- |
34
+ | 1 (loosest) | `cond ? a : b` | Right-associative |
35
+ | 2 | `\|\|` | Short-circuits; always yields a bool |
36
+ | 3 | `&&` | Short-circuits; always yields a bool |
37
+ | 4 | `==` `!=` | Structural equality |
38
+ | 5 | `<` `<=` `>` `>=` | Both sides must be the same type |
39
+ | 6 | `+` `-` | |
40
+ | 7 | `*` `/` `%` | |
41
+ | 8 | `-x` `!x` | Prefix |
42
+ | 9 (tightest) | `a.b` `a[i]` `f(x)` | Postfix |
43
+
44
+ ### Arithmetic
45
+
46
+ `+`, `-`, `*` and `/` are overloaded so that vector maths reads the way it is meant to.
47
+
48
+ ```hcl
49
+ 2 + 3 // 5
50
+ [10, 20] + [1, 2] // [11, 22] componentwise
51
+ [10, 20] - [1, 2] // [9, 18] componentwise
52
+ [10, 20] * 2 // [20, 40] scalar broadcast
53
+ 2 * [10, 20] // [20, 40] either order
54
+ [10, 20] * [2, 3] // [20, 60] componentwise
55
+ [10, 20] / 2 // [5, 10] scalar only
56
+ 10 % 3 // 1
57
+ ```
58
+
59
+ What each accepts:
60
+
61
+ | Operator | Number ∘ number | List ∘ list | List ∘ number | Number ∘ list | Anything with a string |
62
+ | --- | --- | --- | --- | --- | --- |
63
+ | `+` | sum | componentwise | ✗ | ✗ | concatenation |
64
+ | `-` | difference | componentwise | ✗ | ✗ | ✗ |
65
+ | `*` | product | componentwise | broadcast | broadcast | ✗ |
66
+ | `/` | quotient | ✗ | broadcast | ✗ | ✗ |
67
+ | `%` | remainder | ✗ | ✗ | ✗ | ✗ |
68
+
69
+ Two lists of different lengths cannot be combined:
70
+ `cannot combine vectors of length 2 and 3`.
71
+
72
+ **Division and modulo by zero are errors**, not `Infinity` or `NaN` — a NaN would otherwise
73
+ travel all the way into the kernel and surface as a degenerate vertex a long way from its
74
+ cause.
75
+
76
+ ### Concatenation
77
+
78
+ `+` concatenates when either side is a string. The other side is rendered by the same rules
79
+ as [interpolation](#string-interpolation):
80
+
81
+ ```hcl
82
+ "part_" + 3 // "part_3"
83
+ "size " + [1, 2] // "size [1, 2]"
84
+ ```
85
+
86
+ ### Comparison and equality
87
+
88
+ `==` and `!=` compare structurally, so two lists with the same numbers are equal:
89
+
90
+ ```hcl
91
+ [1, 2] == [1, 2] // true
92
+ 1 == "1" // false — different types are never equal
93
+ ```
94
+
95
+ `<`, `<=`, `>` and `>=` require both sides to be the same type, and compare numbers
96
+ numerically and strings lexicographically. Mixing them is an error:
97
+ `cannot compare a number with a string`.
98
+
99
+ ### Logic
100
+
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
103
+ when given non-bool operands: `1 || false` is `true`, not `1`.
104
+
105
+ `!x` negates the [truthiness](#truthiness) of any value. Unary `-` requires a number:
106
+ `cannot negate a non-number`.
107
+
108
+ ### Conditional
109
+
110
+ ```hcl
111
+ color = i % 2 == 0 ? "#3f7fbf" : "#bf6b3f"
112
+ ```
113
+
114
+ Binds looser than everything else, so the condition needs no parentheses. It is
115
+ right-associative, so `a ? b : c ? d : e` chains as `a ? b : (c ? d : e)`.
116
+
117
+ ---
118
+
119
+ ## Truthiness
120
+
121
+ Conditions — in `? :`, `&&`, `||`, `!` and [`if`](control-flow.md#if) — accept any value.
122
+
123
+ | Falsy | Truthy |
124
+ | --- | --- |
125
+ | `false` | `true` |
126
+ | `0` | any other number |
127
+ | `""` | any non-empty string |
128
+ | `[]` | any non-empty list |
129
+ | `null` | any object, including `{}` |
130
+
131
+ Empty strings and empty lists being falsy is what makes `if var.label { ... }` and
132
+ `if var.sizes { ... }` read the way they are written.
133
+
134
+ ---
135
+
136
+ ## Member access and indexing
137
+
138
+ ```hcl
139
+ local size = [40, 30, 12]
140
+ size.x // 40
141
+ size.y // 30
142
+ size.z // 12
143
+ size[0] // 40
144
+
145
+ local opts = { wall = 2, gap = 0.4 }
146
+ opts.wall // 2
147
+ opts["wall"] // 2
148
+ ```
149
+
150
+ `.x`, `.y` and `.z` on a **list** are sugar for indices 0, 1 and 2 — they are not a vector
151
+ type. They are read on a list of any length, so `[1, 2].z` yields nothing rather than
152
+ raising; the failure surfaces later, wherever that nothing is used.
153
+
154
+ On an **object**, a missing key is an error: `no attribute "wdith"`. Reading a property of
155
+ `null` is too: `cannot read "x" of nothing`.
156
+
157
+ Indexing past the end of a list is *not* an error; it yields undefined, which then usually
158
+ fails where it is used. Indexing something that is neither a list, a string nor an object is
159
+ an error: `cannot index this value`.
160
+
161
+ ---
162
+
163
+ ## Function calls
164
+
165
+ ```hcl
166
+ max(sizes) // variadic, flattens its arguments
167
+ pow(2, 10)
168
+ sin(30) // 0.5 — every angle is in degrees
169
+ ```
170
+
171
+ Functions take **positional arguments only**. Named arguments are an error:
172
+ `pow: functions take positional arguments`. A fixed-arity function checks its count:
173
+ `len takes 1 argument(s), got 2`.
174
+
175
+ There are no user-defined functions. To name a piece of geometry, declare a
176
+ [`component`](../reference/component.md); to name a value, declare a
177
+ [`local`](../reference/local.md).
178
+
179
+ See the [function reference](../reference/functions.md) for all thirty of them.
180
+
181
+ ---
182
+
183
+ ## String interpolation
184
+
185
+ `${ ... }` splices any expression into a string.
186
+
187
+ ```hcl
188
+ part "leg_${i}" { ... }
189
+ color = "#${hex}"
190
+ description = "wall ${var.wall}mm, ${len(sizes)} slots"
191
+ ```
192
+
193
+ How each value renders:
194
+
195
+ | Value | Rendered as |
196
+ | --- | --- |
197
+ | number | `3`, `0.5` — JavaScript's default number formatting |
198
+ | string | itself |
199
+ | bool | `true` / `false` |
200
+ | list | `[1, 2, 3]` — elements joined by `, ` inside brackets, recursively |
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 |
203
+
204
+ A splice must contain exactly one expression; anything left over is an error:
205
+ `trailing input in ${ }`. Errors raised inside a splice are reported at the position of the
206
+ string that contains it, since the splice has no line of its own.
@@ -0,0 +1,156 @@
1
+ # Document structure
2
+
3
+ A forma document is a flat list of top-level blocks. Only four kinds may appear there:
4
+ [`param`](../reference/param.md), [`local`](../reference/local.md),
5
+ [`component`](../reference/component.md) and [`model`](../reference/model.md). Anything else
6
+ at the top level is an error.
7
+
8
+ ```hcl
9
+ param wall = 2 // an input
10
+ local sizes = [10, 20, 30] // a computed value
11
+
12
+ component "plate" { // a reusable block
13
+ param size = [20, 20]
14
+ extrude { height = wall rect { size = size center = true } }
15
+ }
16
+
17
+ model "stack" { // something to render
18
+ for i, s in sizes {
19
+ translate { offset = [0, 0, i * wall] plate { size = [s, s] } }
20
+ }
21
+ }
22
+ ```
23
+
24
+ Order does not matter between declarations of different kinds — a `model` may use a
25
+ `component` declared below it. Order does matter among `local`s, which are evaluated top to
26
+ bottom. See [Scope and names](scope.md).
27
+
28
+ ---
29
+
30
+ ## The pipeline
31
+
32
+ ```
33
+ source ──▶ lexer ──▶ parser ──▶ evaluator ──▶ geometry nodes ──▶ Manifold (WASM) ──▶ mesh
34
+ │ │
35
+ params digest cache
36
+ ```
37
+
38
+ Each stage has a distinct job, and knowing which stage raised an error tells you what kind of
39
+ mistake it was.
40
+
41
+ 1. **Lexer** turns characters into tokens. It rejects unknown characters, unterminated
42
+ strings and unterminated block comments.
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
45
+ nested blocks.
46
+ 3. **Evaluator** resolves params and locals, then walks the models. It rejects unknown names,
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.
49
+ 4. **Kernel** solves the node tree into concrete solids, memoized by node digest in an
50
+ [`EvaluationContext`](../api/evaluation-context.md).
51
+
52
+ The separation between steps 3 and 4 is what makes editing cheap. The evaluator is pure and
53
+ fast; the kernel is where the time goes, and it is asked only about subtrees whose content
54
+ actually changed.
55
+
56
+ The one exception is [`align`](../reference/align.md), which has to measure its child before
57
+ it can decide where to put it. It is the only block that reaches into the kernel during
58
+ evaluation, and the only one that fails when there is no kernel context.
59
+
60
+ ---
61
+
62
+ ## Nesting is composition
63
+
64
+ There is no separate expression language for geometry. A block's children *are* its operands,
65
+ written inside its braces:
66
+
67
+ ```hcl
68
+ difference { // subtract…
69
+ box { size = [20, 20, 10] } // …this is the first operand
70
+ cylinder { radius = 5 height = 30 } // …and this is cut out of it
71
+ }
72
+ ```
73
+
74
+ **Several children in a row are implicitly unioned.** Every operation except the three
75
+ booleans unions its children before acting on them, so a transform applied to two shapes
76
+ moves both together:
77
+
78
+ ```hcl
79
+ translate {
80
+ offset = [0, 0, 10]
81
+ box { size = [4, 4, 4] } // these two are unioned first,
82
+ sphere { radius = 3 } // then the union is moved
83
+ }
84
+ ```
85
+
86
+ [`difference`](../reference/booleans.md#difference) is the operation where order matters: the
87
+ first child is the body and every later child is cut out of it. `union` and `intersection`
88
+ receive their children as a list too, but the result does not depend on the order.
89
+
90
+ An operation with no children is an error; a shape with children is an error.
91
+
92
+ ---
93
+
94
+ ## Dimensionality
95
+
96
+ Every piece of geometry is either 2D or 3D, and the language tracks which.
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.
101
+ - **Operations** either fix what they accept (`extrude` takes 2D, `refine` takes 3D) or pass
102
+ their children's dimensionality through (`translate`, `rotate`, `hull`, the booleans).
103
+
104
+ Three rules follow, and each has its own error message:
105
+
106
+ | Rule | Error when broken |
107
+ | --- | --- |
108
+ | The children of one block must all be the same dimensionality | `"union" cannot mix 2D and 3D shapes` |
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` |
111
+
112
+ To get from 2D to 3D use [`extrude`](../reference/conversions.md#extrude) or
113
+ [`revolve`](../reference/conversions.md#revolve); to go back use
114
+ [`project`](../reference/conversions.md#project) or
115
+ [`slice`](../reference/conversions.md#slice).
116
+
117
+ ---
118
+
119
+ ## Units and orientation
120
+
121
+ - **Lengths are unitless.** Every consumer treats them as millimetres — STL has no unit
122
+ either — but nothing in the language enforces that.
123
+ - **Angles are always in degrees.** Everywhere: `rotate { z = 90 }`, `revolve { angle = 180 }`,
124
+ `sin(30)`, `param a { type = angle }`. There is no radian form in the language. The
125
+ JavaScript [`Angle`](../api/values.md#angle) class stores radians internally, and that is
126
+ the only place radians appear.
127
+ - **Z is up.** `extrude` sweeps along +Z. `revolve` spins a profile written in the XY plane
128
+ about the Y axis, mapping the profile's Y to the result's Z, so a profile drawn from
129
+ `[0, 0]` upwards produces a solid standing on the XY plane.
130
+ - **3D rotations apply X, then Y, then Z**, matching OpenSCAD, so a model ported from there
131
+ lands in the same orientation.
132
+
133
+ ---
134
+
135
+ ## Identity is content
136
+
137
+ The evaluator does not produce a call graph — it produces a tree of immutable nodes, each
138
+ carrying a 128-bit digest of its own content and its children's digests. Two structurally
139
+ identical subtrees have the same digest and therefore *are* the same node.
140
+
141
+ That has consequences worth designing around:
142
+
143
+ - A component used twenty times is evaluated once. Eight spheres in a loop cost one sphere
144
+ and eight transforms.
145
+ - Two shapes written with their attributes in a different order still share a cache entry —
146
+ object keys are sorted before they are digested.
147
+ - The members of a `union` are sorted by subtree size, so two unions written in a different
148
+ order also share a digest. `difference` is never reordered, because its first child means
149
+ something different from the rest.
150
+ - Nested transforms collapse into a single matrix, so a deep stack of `translate` blocks is
151
+ one kernel call rather than one per level.
152
+ - The digest is computed from the IEEE bits of every number, never from a runtime-seeded
153
+ hash, so it is the same in every browser and every run.
154
+
155
+ See [`GeometryNode`](../api/geometry-node.md) for the node types themselves, and
156
+ [`EvaluationContext`](../api/evaluation-context.md) for the cache built on top of them.
@@ -0,0 +1,184 @@
1
+ # Scope and names
2
+
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
5
+ declaration may refer to another.
6
+
7
+ ---
8
+
9
+ ## The root scope
10
+
11
+ Built once, before any geometry, in this order:
12
+
13
+ 1. **Constants** — `pi` and `e`.
14
+ 2. **Type names** — `number`, `string`, `bool`, `vector`, `list`, `angle`, each bound to
15
+ itself as a string, so `type = number` in a param block resolves with no quoting.
16
+ 3. **Params**, in source order. Each is bound under its bare name as it is resolved, so a
17
+ later param's default may refer to an earlier one.
18
+ 4. **`var`** — an object holding every resolved param.
19
+ 5. **Locals**, in source order. Each is bound as it is resolved, so a later local may refer
20
+ to an earlier one.
21
+
22
+ The order has two consequences worth knowing:
23
+
24
+ ```hcl
25
+ param a = 3
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
28
+
29
+ local d = var.a + 1 // ✓ locals are built after var
30
+ local e = f // ✗ unknown name "f" — a later local
31
+ local f = 2
32
+ ```
33
+
34
+ ---
35
+
36
+ ## `var.` versus the bare name
37
+
38
+ Every param is reachable two ways: bare, and under `var`.
39
+
40
+ ```hcl
41
+ param wall = 2
42
+
43
+ model "m" {
44
+ box { size = [wall, var.wall, 1] } // the same value, twice
45
+ }
46
+ ```
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
50
+ to reach an input:
51
+
52
+ ```hcl
53
+ param a = 1
54
+ local a = 2 // shadows the bare name
55
+
56
+ model "m" {
57
+ box { size = [a, var.a, 1] } // [2, 1, 1]
58
+ }
59
+ ```
60
+
61
+ Reading a local through `var` fails, because it is not there: `no attribute "a"`.
62
+
63
+ Prefer `var.` in models for anything that came from a param. It is the convention the
64
+ editor's examples follow, and it makes an input visually distinct from a local at the point
65
+ of use.
66
+
67
+ ---
68
+
69
+ ## Body scopes
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.
73
+
74
+ **Locals declared in a body are visible to the whole body**, not only to what follows them,
75
+ because every local in a body is resolved before any of its blocks are built:
76
+
77
+ ```hcl
78
+ model "m" {
79
+ box { size = [s, 1, 1] } // ✓ s is already bound
80
+ local s = 4
81
+ }
82
+ ```
83
+
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
86
+ local.
87
+
88
+ ---
89
+
90
+ ## Component isolation
91
+
92
+ A [`component`](../reference/component.md) body gets a fresh scope rooted at the **document
93
+ root**, not at the call site. A name that happens to exist where the component is used does
94
+ not leak into it.
95
+
96
+ ```hcl
97
+ local g = 7
98
+
99
+ component "c" {
100
+ param s = 1
101
+ box { size = [s, g, 1] } // ✓ its own param, and a document local
102
+ }
103
+
104
+ model "m" {
105
+ local q = 5
106
+ c { } // c cannot see q
107
+ }
108
+ ```
109
+
110
+ Inside a component body, these are in scope:
111
+
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.`)
114
+ and document locals
115
+ - its own body locals
116
+
117
+ Nothing from the caller. Values travel in through params and no other way.
118
+
119
+ ---
120
+
121
+ ## Model params
122
+
123
+ A `model` may declare its own `param` blocks. They behave like locals with a declaration
124
+ syntax, not like document params:
125
+
126
+ - Each **must** have a default — `model "m": param "s" needs a default`.
127
+ - They are **not** settable from outside. `render(source, { params: { s: 9 } })` sets
128
+ document params only; a model param keeps its default.
129
+ - They are bound in the model's scope under the bare name, and are not added to `var`.
130
+
131
+ ```hcl
132
+ model "bracket" {
133
+ param thickness { default = 4 } // a fixed constant for this model
134
+ box { size = [20, 20, thickness] }
135
+ }
136
+ ```
137
+
138
+ For an input the caller can change, declare the param at the top level instead.
139
+
140
+ ---
141
+
142
+ ## Loop variables
143
+
144
+ [`for`](control-flow.md#for) binds one or two names in a child scope, live for one iteration:
145
+
146
+ ```hcl
147
+ for size in sizes { ... } // size is the value
148
+ for i, size in sizes { ... } // i is the index, size the value
149
+ ```
150
+
151
+ The names shadow anything of the same name in an enclosing scope, and are gone after the
152
+ loop.
153
+
154
+ ---
155
+
156
+ ## Shadowing summary
157
+
158
+ Innermost wins. From outermost to innermost:
159
+
160
+ ```
161
+ constants and type names
162
+ └─ params (bare) · var · locals
163
+ └─ model scope: model params, model body locals
164
+ └─ block body locals
165
+ └─ for-loop variables
166
+ ```
167
+
168
+ A `component` body cuts the chain: it starts again from the root scope regardless of where
169
+ it was called.
170
+
171
+ ---
172
+
173
+ ## What cannot be shadowed
174
+
175
+ - **Builtin block names.** Declaring `component "box"` is an error:
176
+ `component "box" shadows a builtin block`. Components and builtins share one namespace so
177
+ a call site reads the same either way.
178
+ - **Builtin function names.** There are no user-defined functions, so nothing can shadow
179
+ them.
180
+ - **Duplicate declarations** of the same kind are errors: `param "a" is declared twice`,
181
+ `component "c" is declared twice`, `model "m" is declared twice`. Locals are the exception:
182
+ a second `local a` is accepted and silently replaces the first everywhere, including in
183
+ code written above it. Nothing warns about it, so treat it as a mistake to avoid rather
184
+ than a feature.