@m0saic/knowledge 0.2.0
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.
- package/LICENSE +21 -0
- package/README.md +71 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +7 -0
- package/docs/README.md +60 -0
- package/docs/file-formats/m0-iteration-protocol.md +120 -0
- package/docs/file-formats/m0p-and-custom-field.md +195 -0
- package/docs/handbook/README.md +27 -0
- package/docs/handbook/composition-arithmetic.md +278 -0
- package/docs/handbook/dsl-complexity.md +75 -0
- package/docs/handbook/dsl-rules.md +367 -0
- package/docs/handbook/feasibility-precision-quantization.md +591 -0
- package/docs/handbook/m0-construction-methods.md +201 -0
- package/docs/handbook/precision-tiers.md +84 -0
- package/docs/m0saic-thesis.md +95 -0
- package/docs/runtime/README.md +17 -0
- package/docs/runtime/cli-usage.md +372 -0
- package/docs/runtime/ffmpeg-expression-limits.md +117 -0
- package/docs/runtime/reduce-to-one.md +96 -0
- package/docs/skills/README.md +40 -0
- package/docs/skills/axis-and-geometry.md +103 -0
- package/docs/skills/dsl-stdlib-method-catalog.md +7 -0
- package/docs/skills/identity.md +123 -0
- package/docs/skills/labels-and-masks.md +170 -0
- package/docs/skills/m0saic-string-generation.md +251 -0
- package/docs/skills/more-atoms-not-bigger-atoms.md +77 -0
- package/docs/skills/overlay-semantics.md +194 -0
- package/docs/skills/parse-apis.md +79 -0
- package/docs/skills/passthrough-semantics.md +136 -0
- package/docs/skills/structural-construction.md +86 -0
- package/docs/skills/text-in-templates.md +126 -0
- package/docs/skills/zero-overlay-analysis.md +87 -0
- package/docs/templates/README.md +65 -0
- package/docs/templates/capability-templates.md +72 -0
- package/docs/templates/construction-strategy.md +329 -0
- package/docs/templates/data-pipeline.md +324 -0
- package/docs/templates/emission-patterns.md +130 -0
- package/docs/templates/geometry-recipes.md +248 -0
- package/docs/templates/layout-contract.md +168 -0
- package/docs/templates/output-resolution-tree.md +202 -0
- package/docs/templates/patterns/case-study-lessons.md +69 -0
- package/docs/templates/patterns/perf-authoring-rules.md +100 -0
- package/docs/templates/patterns/primitive-extraction-pattern.md +103 -0
- package/docs/templates/philosophy-and-contract.md +310 -0
- package/docs/templates/recursion-nested-rendering.md +138 -0
- package/docs/templates/reference/grid.md +104 -0
- package/docs/templates/reference/json-prop-type.md +169 -0
- package/docs/templates/reference/mosaic-color.md +81 -0
- package/docs/templates/reference/mosaic-placement-props.md +103 -0
- package/docs/templates/reference/prop-bindings.md +203 -0
- package/docs/templates/reference/template-flags.md +205 -0
- package/docs/templates/render-lifecycle.md +117 -0
- package/docs/templates/rendering-model-contract.md +392 -0
- package/docs/templates/standalone-pack-authoring.md +233 -0
- package/docs/templates/theming.md +81 -0
- package/docs/templates/ui-controls.md +150 -0
- package/package.json +37 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
# m0 DSL — Grammar, Semantics, and Validation
|
|
2
|
+
|
|
3
|
+
The exact surface grammar, semantic rules, and validation invariants for the m0
|
|
4
|
+
DSL — the deterministic spatial-layout language that powers m0saic. Any system
|
|
5
|
+
that generates, transforms, or mutates m0 strings must follow this document and
|
|
6
|
+
must prove correctness with the canonical validator. Visual inspection is never
|
|
7
|
+
sufficient.
|
|
8
|
+
|
|
9
|
+
The DSL is strict, deterministic, count-exact, and validator-authoritative.
|
|
10
|
+
Posture (as of 2026-07-27): **frozen at v1.1.0**
|
|
11
|
+
([https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/package.json](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/package.json); verify there).
|
|
12
|
+
Grammar changes are explicitly avoided; the 1.1.0 minor carried the 2026-06-03
|
|
13
|
+
overlay-body relaxation (`ZERO_SOURCE_OVERLAY` no longer raised) and the
|
|
14
|
+
public-API prune.
|
|
15
|
+
|
|
16
|
+
> **Source of truth:** [https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src).
|
|
17
|
+
> Validator: [`validate/m0StringValidator.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/validate/m0StringValidator.ts);
|
|
18
|
+
> error specs: [`errors/errors.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/errors/errors.ts);
|
|
19
|
+
> warning specs: [`warnings/warnings.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/warnings/warnings.ts);
|
|
20
|
+
> parsers: [`parse/m0StringParser.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/parse/m0StringParser.ts);
|
|
21
|
+
> types: [`types.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/types.ts).
|
|
22
|
+
|
|
23
|
+
## Canonicalization (always applied first)
|
|
24
|
+
|
|
25
|
+
Before validation, the reference validator canonicalizes:
|
|
26
|
+
|
|
27
|
+
1. Remove all whitespace
|
|
28
|
+
2. Replace every `F` with `1`
|
|
29
|
+
3. Replace every `>` with `0`
|
|
30
|
+
|
|
31
|
+
All correctness rules apply **after canonicalization**. Generators should emit
|
|
32
|
+
canonical form directly. `toCanonicalM0String` / `toPrettyM0String` are exported
|
|
33
|
+
from `@m0saic/dsl`
|
|
34
|
+
([`format/m0StringFormat.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/format/m0StringFormat.ts)).
|
|
35
|
+
|
|
36
|
+
### Compact form (transport only — outside the grammar)
|
|
37
|
+
|
|
38
|
+
A third form, **compact**, exists for length-constrained transports: pretty form
|
|
39
|
+
with runs of `>` or `-` folded into `N>` / `N-` (`6[1,0,0,0,0,1]` → `6[F,4>F]`).
|
|
40
|
+
Compact is **outside the grammar** — a folded string fails `isValidM0String` by
|
|
41
|
+
construction, and nothing in the parser, validator, or file formats accepts it.
|
|
42
|
+
Always unfold before parse, validate, or persist; files stay canonical.
|
|
43
|
+
`toCompactM0String` / `fromCompactM0String` are exported from `@m0saic/dsl`
|
|
44
|
+
([`format/m0StringCompact.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/format/m0StringCompact.ts)),
|
|
45
|
+
and the round-trip law is
|
|
46
|
+
`fromCompactM0String(toCompactM0String(x)) === toCanonicalM0String(x)`.
|
|
47
|
+
|
|
48
|
+
The fold is unambiguous because NUMBER may only be followed by `(` or `[`
|
|
49
|
+
(invariant 2 under "Grammar invariants", below), so a digit adjacent to `>` or
|
|
50
|
+
`-` is free namespace no valid m0 can occupy. That
|
|
51
|
+
argument is also why the fold is only expressible over *pretty* tokens: it turns
|
|
52
|
+
on `>` and `-` living outside the digit alphabet. `0` is itself a digit and
|
|
53
|
+
appears inside ordinary split counts (`10(`, `100(`), so a canonical marker `N0`
|
|
54
|
+
would be built from the same characters counts are, with nothing delimiting the
|
|
55
|
+
run count from the digits after it — no free namespace, and no left-to-right way
|
|
56
|
+
to find the marker's edges.
|
|
57
|
+
|
|
58
|
+
`fromCompactM0String` is meant to be fed by untrusted input (share URLs are
|
|
59
|
+
forgeable) and bounds expansion before allocating: it throws on a zero count, a
|
|
60
|
+
non-safe-integer count, or a total past `MAX_COMPACT_EXPANSION` (10M chars — the
|
|
61
|
+
same ceiling `perf/large-dsl-ceiling.break.test.ts` treats as supported).
|
|
62
|
+
|
|
63
|
+
Consumers today are the layout share URL
|
|
64
|
+
(the Mosaic Desktop / Web app source (not published): compact on build, expand on parse)
|
|
65
|
+
and the editor's compact token view, which renders the same codec so the string
|
|
66
|
+
on screen matches the one a link carries. Share links are self-contained by
|
|
67
|
+
construction — there is no server-side share store — because compact fits ~98.8%
|
|
68
|
+
of layouts under the URL limit and the remainder exports a `.m0` file.
|
|
69
|
+
|
|
70
|
+
Compact was evaluated and **rejected** as a storage format — gzip already beats
|
|
71
|
+
the fold on stored bytes, and the file-format layers canonicalize on read AND
|
|
72
|
+
write.
|
|
73
|
+
|
|
74
|
+
## Allowed characters (after canonicalization)
|
|
75
|
+
|
|
76
|
+
Digits `0–9`, classifiers `( ) [ ]`, overlays `{ }`, separator `,`, null tile
|
|
77
|
+
`-`. No letters, no spaces, no other symbols.
|
|
78
|
+
|
|
79
|
+
## Token types
|
|
80
|
+
|
|
81
|
+
The DSL is parsed as a strict token stream:
|
|
82
|
+
|
|
83
|
+
| Token | Form | Meaning |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| PRIMITIVE | `0` | zero-frame (donates space forward) |
|
|
86
|
+
| PRIMITIVE | `-` | null-render tile (hole) |
|
|
87
|
+
| PRIMITIVEONE | `1` | rendered tile |
|
|
88
|
+
| NUMBER | digits forming an integer (`2`, `10`, `154`) | split count; `0` is NOT a NUMBER token |
|
|
89
|
+
| CLASSIFIEROPEN | `(` / `[` | column split (horizontal) / row split (vertical) |
|
|
90
|
+
| CLASSIFIERCLOSE | `)` / `]` | closes a split |
|
|
91
|
+
| OBJECTOPEN / OBJECTCLOSE | `{` / `}` | overlay object |
|
|
92
|
+
| COMMA | `,` | slot separator |
|
|
93
|
+
|
|
94
|
+
## Root form
|
|
95
|
+
|
|
96
|
+
A m0 string must resolve to exactly one root node. Valid root shapes:
|
|
97
|
+
|
|
98
|
+
- `1` · `1{...}`
|
|
99
|
+
- `N(...)` · `N[...]` · `N(...){...}` · `N[...]{...}` (N ≥ 2)
|
|
100
|
+
|
|
101
|
+
The root may carry an overlay. There is no implicit wrapping — the DSL
|
|
102
|
+
represents a single explicit tree. Invalid roots: `0`, `-` (both
|
|
103
|
+
`INVALID_EMPTY`), `{1}`, `1{1}{1}` (`OVERLAY_CHAIN`).
|
|
104
|
+
|
|
105
|
+
## Node forms
|
|
106
|
+
|
|
107
|
+
### Primitives
|
|
108
|
+
|
|
109
|
+
`1` (rendered tile), `0` (zero-frame), `-` (null tile). Each may carry one
|
|
110
|
+
overlay: `1{1}`, `0{1}`, `-{1}`. Overlays never appear standalone — they always
|
|
111
|
+
attach to a primitive or container.
|
|
112
|
+
|
|
113
|
+
### Numeric containers (splits)
|
|
114
|
+
|
|
115
|
+
`N( ... )` — column split; `N[ ... ]` — row split.
|
|
116
|
+
|
|
117
|
+
- **N ≥ 2.** A 1-way split is rejected: `1(` / `1[` →
|
|
118
|
+
`ILLEGAL_ONE_SPLIT` ("Use a count >= 2 for splits",
|
|
119
|
+
[`m0StringValidator.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/validate/m0StringValidator.ts) ~L793).
|
|
120
|
+
- **Count-exact:** the container must contain **exactly N child slots**. There
|
|
121
|
+
is no compression *in the grammar*; all slots are explicit. (The compact
|
|
122
|
+
transport form folds runs — see "Compact form" under Canonicalization,
|
|
123
|
+
above — but it is not valid m0 and never reaches the validator.)
|
|
124
|
+
- Commas are separators only; slot order is semantically meaningful and
|
|
125
|
+
preserved; slots may be primitives, nested containers, and may carry overlays.
|
|
126
|
+
|
|
127
|
+
Valid: `2(1,1)` · `3[1,0,1]` · `4(0,0,0,1)` · `2(1{1},1)`
|
|
128
|
+
Invalid: `2(1)` · `3(1,1)` · `2(1,1,1)` (all `TOKEN_COUNT`) · `3()` (`INVALID_EMPTY`) · `1(1)` (`ILLEGAL_ONE_SPLIT`)
|
|
129
|
+
|
|
130
|
+
## Overlay objects `{}`
|
|
131
|
+
|
|
132
|
+
An overlay may follow a primitive or a numeric container: `node{ <valid m0> }`.
|
|
133
|
+
|
|
134
|
+
- Overlay contents are validated recursively.
|
|
135
|
+
- The overlay subtree renders within the parent's rectangle: origin = parent
|
|
136
|
+
rect origin, size = parent rect size. Overlays never escape their parent rect.
|
|
137
|
+
- Overlays may themselves contain splits and further overlays: `2(1,1){3[1,0,1]}`,
|
|
138
|
+
`1{2[3(1,1,1),3(1,1,1)]}`.
|
|
139
|
+
- Chained overlays are illegal: `1{1}{1}` → `OVERLAY_CHAIN` (nest instead:
|
|
140
|
+
`1{1{1}}`).
|
|
141
|
+
|
|
142
|
+
## Zero-frame (`0`) semantics
|
|
143
|
+
|
|
144
|
+
`0` consumes a split slot but produces no rendered output. Consecutive `0`s form
|
|
145
|
+
a **donation run**: each `0` grows the merged region; the next claimant (`1` or
|
|
146
|
+
`-`) absorbs all donated space. `3(0,0,1)` → the `1` spans three slots.
|
|
147
|
+
|
|
148
|
+
### Overlays on `0`
|
|
149
|
+
|
|
150
|
+
> An overlay attached to `0` applies to the merged region accumulated **at the
|
|
151
|
+
> moment the overlay appears**. It does not retroactively resize when later
|
|
152
|
+
> tokens grow the region.
|
|
153
|
+
|
|
154
|
+
Worked example — `4(0,0{2(1,1)},0,1)`:
|
|
155
|
+
|
|
156
|
+
1. First `0` — merged region = 1 slot
|
|
157
|
+
2. `0{2(1,1)}` — merged region = 2 slots; overlay canvas = those 2 slots
|
|
158
|
+
3. Third `0` — merged region = 3 slots
|
|
159
|
+
4. `1` — absorbs all 4 slots; the overlay stays sized to the 2-slot region
|
|
160
|
+
|
|
161
|
+
Result: base tile spans full width; the overlay occupies the left half only.
|
|
162
|
+
With multiple zero overlays (`5(0{1},0{1},0,0,1)`) each overlay is independent
|
|
163
|
+
and anchored at its creation-time region size. In short: `0` = grow region;
|
|
164
|
+
`0{...}` = grow region AND paint an overlay over the current region; the
|
|
165
|
+
claimant finalizes the region but never resizes earlier overlays.
|
|
166
|
+
|
|
167
|
+
### Overlay paint order (deferred painting)
|
|
168
|
+
|
|
169
|
+
When multiple overlays exist at the same level, paint order is **not**
|
|
170
|
+
left-to-right by source position. The engine defers zero-overlay painting until
|
|
171
|
+
all sibling overlays are collected, then paints sorted by:
|
|
172
|
+
|
|
173
|
+
1. **Area, descending** — the largest overlay paints first (bottom of the
|
|
174
|
+
overlay stack).
|
|
175
|
+
2. **Stable rootId, ascending** — deterministic tie-break at identical area.
|
|
176
|
+
|
|
177
|
+
Smaller overlays therefore paint on top of larger ones. Source:
|
|
178
|
+
[`parse/sortDeferredOverlays.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/parse/sortDeferredOverlays.ts),
|
|
179
|
+
called from [`parse/m0StringParser.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/parse/m0StringParser.ts).
|
|
180
|
+
Generators must trust the engine's ordering — do not try to encode z-order via
|
|
181
|
+
source position.
|
|
182
|
+
|
|
183
|
+
## Null tile (`-`) semantics
|
|
184
|
+
|
|
185
|
+
Consumes space, renders nothing, does **not** donate space forward, may carry
|
|
186
|
+
overlays. Bare `-` as the root is invalid (`INVALID_EMPTY`).
|
|
187
|
+
|
|
188
|
+
## Grammar invariants (hard rules)
|
|
189
|
+
|
|
190
|
+
1. First token must be NUMBER or `1`
|
|
191
|
+
2. NUMBER must be immediately followed by `(` or `[`
|
|
192
|
+
3. All enclosures `() [] {}` must be balanced and correctly nested
|
|
193
|
+
4. Every numeric container must contain exactly N child slots (N ≥ 2)
|
|
194
|
+
5. No empty splits (`N()` / `N[]`)
|
|
195
|
+
6. Token transitions must obey the validator's state rules
|
|
196
|
+
7. Every donation run must resolve to a claimant
|
|
197
|
+
8. A valid string must produce at least one renderable frame at the root
|
|
198
|
+
9. Strings that produce zero-size frames at execution resolution fail at parse
|
|
199
|
+
(`SPLIT_EXCEEDS_AXIS`, below)
|
|
200
|
+
|
|
201
|
+
## Passthrough-to-nothing
|
|
202
|
+
|
|
203
|
+
The validator rejects any split whose **last child** begins with passthrough
|
|
204
|
+
`0` — a trailing passthrough has no next tile to donate to. An overlay on the
|
|
205
|
+
`0` does not change this: the donation still goes nowhere. The rule applies
|
|
206
|
+
recursively inside nested classifiers AND overlay bodies, and also covers the
|
|
207
|
+
all-donors case (`2(0,0)` — structurally balanced, but no claimant exists).
|
|
208
|
+
|
|
209
|
+
Invalid: `2(1,0)` · `2(1,0{1})` · `3(1,1,0{1})` · `2(0,0)`
|
|
210
|
+
Valid: `2(0,1)` · `2(0{1},1)` · `3(1,0,1)`
|
|
211
|
+
|
|
212
|
+
Error code: `PASSTHROUGH_TO_NOTHING`.
|
|
213
|
+
|
|
214
|
+
## No-sources
|
|
215
|
+
|
|
216
|
+
The validator rejects any layout containing **no leaf `1` anywhere** — all-`-`
|
|
217
|
+
/ all-`0` layouts produce no renderable output.
|
|
218
|
+
|
|
219
|
+
Invalid: `2(-,-)` · `3[-,-,-]` — Valid: `2(1,-)` · `2(-,-){1}` (a source in an
|
|
220
|
+
overlay counts).
|
|
221
|
+
|
|
222
|
+
Error code: `NO_SOURCES`.
|
|
223
|
+
|
|
224
|
+
## Overlay body rules
|
|
225
|
+
|
|
226
|
+
An overlay body `{...}` must contribute **at least one node** to the graph, but
|
|
227
|
+
those nodes do **not** have to paint. This lets overlays act as logical-owner
|
|
228
|
+
anchors (carrying `stableKey` + label without contributing rendered tiles).
|
|
229
|
+
|
|
230
|
+
Rejected (`INVALID_EMPTY`):
|
|
231
|
+
|
|
232
|
+
- `1{}` — empty body, no nodes at all
|
|
233
|
+
- `1{0}` — bare passthrough at the overlay root with nothing to donate to
|
|
234
|
+
- `1{-{}}` — recursive: the inner `{}` fails the same rule (deepest offender reported)
|
|
235
|
+
|
|
236
|
+
Accepted — at least one node, even if nothing paints:
|
|
237
|
+
|
|
238
|
+
- `1{-}` — single null node; valid logical-owner anchor
|
|
239
|
+
- `1{2(-,-)}` — all-null split; structural nodes, no paint
|
|
240
|
+
- `1{2(0,-)}` — passthrough with a sibling to donate to
|
|
241
|
+
- `2(-{F},-{F})` · `1{-{2(0,-)}}` · `1{2[1,1]{1}}`
|
|
242
|
+
|
|
243
|
+
The whole-string `NO_SOURCES` check still enforces that the ROOT layout paints
|
|
244
|
+
at least one source tile — the relaxation is per-overlay, so nested bodies can
|
|
245
|
+
be paint-free but the root must paint something. The legacy `ZERO_SOURCE_OVERLAY`
|
|
246
|
+
code is **no longer raised** (see the error table below).
|
|
247
|
+
|
|
248
|
+
## Runtime feasibility guard: `SPLIT_EXCEEDS_AXIS`
|
|
249
|
+
|
|
250
|
+
`SPLIT_EXCEEDS_AXIS` is NOT a grammar error and is **never emitted by the
|
|
251
|
+
validator** — `isValidM0String` / `validateM0String` operate purely on the
|
|
252
|
+
token stream and have no notion of pixel dimensions. A string that will fail at
|
|
253
|
+
runtime due to infeasibility still validates `ok: true`.
|
|
254
|
+
|
|
255
|
+
It is emitted only by `parseM0StringComplete(m0, w, h)` after concrete frame
|
|
256
|
+
geometry is computed: every produced frame is checked for
|
|
257
|
+
`width > 0 && height > 0`, and any zero-size frame (or an empty frame set)
|
|
258
|
+
returns `{ ok: false, error: SPLIT_EXCEEDS_AXIS }`
|
|
259
|
+
([`m0StringParser.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/parse/m0StringParser.ts) ~L1840).
|
|
260
|
+
The convenience wrappers (`parseM0StringToLogicalFrames`,
|
|
261
|
+
`parseM0StringToFullGraph`) delegate to `parseM0StringComplete` and collapse any
|
|
262
|
+
failure to `[]` — they do not surface the error object.
|
|
263
|
+
|
|
264
|
+
To preflight infeasibility **before** parse, use `computeFeasibility` (next
|
|
265
|
+
section). This is the recommended generator-side check.
|
|
266
|
+
|
|
267
|
+
## Feasibility, precision, quantization → the geometry doc
|
|
268
|
+
|
|
269
|
+
Everything about whether a valid string **renders** at given dims, **looks
|
|
270
|
+
right**, and stays **balanced** — `computeFeasibility`, the precision floor,
|
|
271
|
+
quantization spread, outside-in remainder distribution, GCD collapse — lives in
|
|
272
|
+
[`feasibility-precision-quantization.md`](feasibility-precision-quantization.md)
|
|
273
|
+
(canonical). Quick reference: `computeFeasibility(m0)` → `{ minWidthPx,
|
|
274
|
+
minHeightPx }` (`@m0saic/dsl`,
|
|
275
|
+
[`feasibility/computeFeasibility.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/feasibility/computeFeasibility.ts));
|
|
276
|
+
render at dims ≥ that floor or the parse fails with `SPLIT_EXCEEDS_AXIS`.
|
|
277
|
+
|
|
278
|
+
## Validation API
|
|
279
|
+
|
|
280
|
+
A string is valid **only if it passes the canonical validator**. Exported from
|
|
281
|
+
`@m0saic/dsl`
|
|
282
|
+
([`validate/m0StringValidator.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/validate/m0StringValidator.ts)):
|
|
283
|
+
|
|
284
|
+
- `isValidM0String(s): boolean` — boolean shortcut.
|
|
285
|
+
- `validateM0String(s): { ok: true } | { ok: false, error }` — structured
|
|
286
|
+
result. **`error` is singular** — one `M0ValidationError` (`code`, `kind`,
|
|
287
|
+
`message`, `span`, `position`, optional `details`); there is no `errors`
|
|
288
|
+
array.
|
|
289
|
+
|
|
290
|
+
The validator runs in O(n) using a structural index; performance is consistent
|
|
291
|
+
across layout depth. Public error types (`M0ValidationErrorKind`,
|
|
292
|
+
`M0ValidationErrorCode`, `M0ValidationError`, `M0ValidationResult`) are
|
|
293
|
+
exported; the `M0_VALIDATION_ERROR_SPECS` table and `makeValidationError` are
|
|
294
|
+
internal ([`errors/index.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/errors/index.ts)).
|
|
295
|
+
|
|
296
|
+
## Error codes (complete, as of 2026-07-27)
|
|
297
|
+
|
|
298
|
+
Every code declares a `kind` in
|
|
299
|
+
[`errors/errors.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/errors/errors.ts) —
|
|
300
|
+
`SYNTAX` (the string is not well-formed m0), `ANTIPATTERN` (well-formed DSL the
|
|
301
|
+
engine rejects by policy), `SEMANTIC` (reserved). A consumer uses `kind` to
|
|
302
|
+
decide grammar-bug vs policy-reject. Re-verify with:
|
|
303
|
+
`grep -n "kind:" https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/errors/errors.ts.
|
|
304
|
+
|
|
305
|
+
| Code | Kind | Trigger |
|
|
306
|
+
|---|---|---|
|
|
307
|
+
| `OVERLAY_CHAIN` | SYNTAX | chained overlays (`1{1}{1}`) — nest instead |
|
|
308
|
+
| `INVALID_CHAR` | SYNTAX | character outside the allowed set |
|
|
309
|
+
| `UNBALANCED` | SYNTAX | unbalanced `()` / `[]` / `{}` |
|
|
310
|
+
| `TOKEN_RULE` | SYNTAX | illegal token transition |
|
|
311
|
+
| `TOKEN_COUNT` | SYNTAX | container child count ≠ N |
|
|
312
|
+
| `ILLEGAL_ONE_SPLIT` | SYNTAX | `1(` / `1[` — split count must be ≥ 2 |
|
|
313
|
+
| `INVALID_EMPTY` | SYNTAX | degenerate input: empty string, bare `0` / `-` root, `N()`, empty overlay body `{}`, bare-passthrough body `{0}` |
|
|
314
|
+
| `PASSTHROUGH_TO_NOTHING` | ANTIPATTERN | trailing `0` in any split (recursive; an overlay on the `0` doesn't save it) |
|
|
315
|
+
| `NO_SOURCES` | ANTIPATTERN | no leaf `1` anywhere in the string |
|
|
316
|
+
| `SPLIT_EXCEEDS_AXIS` | ANTIPATTERN | parser-only: a split produced a 0-size frame at the given w×h |
|
|
317
|
+
| `ZERO_SOURCE_OVERLAY` | SEMANTIC | **reserved, never emitted since 2026-06-03** — the overlay-body relaxation dropped it (validator comment at [`m0StringValidator.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/validate/m0StringValidator.ts) ~L485: "The ZERO_SOURCE_OVERLAY rule above was dropped"); paint-free bodies now validate, `INVALID_EMPTY` covers the degenerate cases |
|
|
318
|
+
|
|
319
|
+
## Warnings
|
|
320
|
+
|
|
321
|
+
Warnings are a **parse-time** surface, separate from validation errors — they
|
|
322
|
+
flag legal-but-suspect layouts without failing them.
|
|
323
|
+
|
|
324
|
+
- `parseM0StringComplete(input, width, height, opts?)` returns `ParseM0Result`:
|
|
325
|
+
`{ ok: true, ir, precision, warnings }` or
|
|
326
|
+
`{ ok: false, error, precision, warnings }` — `precision` and `warnings` are
|
|
327
|
+
**always present** regardless of `ok`.
|
|
328
|
+
- One warning code exists: `PRECISION_EXCEEDS_NORM` — emitted when the string's
|
|
329
|
+
`maxSplitAny` exceeds `opts.precisionNorm` (**default 100**;
|
|
330
|
+
[`m0StringParser.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/parse/m0StringParser.ts) ~L1814).
|
|
331
|
+
Raise the norm deliberately for intentionally fine layouts.
|
|
332
|
+
- Specs live in `M0_WARNING_SPECS`
|
|
333
|
+
([`warnings/warnings.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/warnings/warnings.ts));
|
|
334
|
+
the spec table and `makeWarning` are internal — the public surface is the
|
|
335
|
+
`M0Warning` / `M0WarningCode` types (exported from
|
|
336
|
+
[`types.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/types.ts)) and the `warnings` array on
|
|
337
|
+
parse results.
|
|
338
|
+
|
|
339
|
+
Note `computePrecisionFromString` is an **internal** dsl helper, not public API
|
|
340
|
+
(barrel comment in [`parse/index.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/parse/index.ts));
|
|
341
|
+
for precision metrics use `getComplexityMetricsFast(m0).precision` — see
|
|
342
|
+
[`dsl-complexity.md`](dsl-complexity.md).
|
|
343
|
+
|
|
344
|
+
## Rules for generators
|
|
345
|
+
|
|
346
|
+
1. **Emit canonical form** (`1` not `F`, `0` not `>`, no whitespace).
|
|
347
|
+
2. **Build token arrays first; count child slots mechanically; assert the count
|
|
348
|
+
equals N.** Never guess counts.
|
|
349
|
+
3. **Validate every string** with `isValidM0String` / `validateM0String` before
|
|
350
|
+
use. If validation fails, the string is invalid — regardless of intent.
|
|
351
|
+
4. **Preflight feasibility when render dims are known:** refuse to emit when
|
|
352
|
+
`targetWidth < f.minWidthPx || targetHeight < f.minHeightPx` for
|
|
353
|
+
`f = computeFeasibility(m0)`.
|
|
354
|
+
5. **Watch the warning surface:** a `PRECISION_EXCEEDS_NORM` on parse means the
|
|
355
|
+
layout is highly granular — expensive, size-sensitive, hard to reason about.
|
|
356
|
+
6. If correctness cannot be proven, do not emit.
|
|
357
|
+
|
|
358
|
+
## Known limitations
|
|
359
|
+
|
|
360
|
+
**Unbounded nesting depth.** Neither the grammar nor the implementation caps
|
|
361
|
+
nesting depth. The parser uses an iterative explicit-stack strategy
|
|
362
|
+
(`parseInternal` in
|
|
363
|
+
[`parse/m0StringParser.ts`](https://github.com/m0saic-dsl/m0/blob/main/packages/dsl/src/parse/m0StringParser.ts)),
|
|
364
|
+
not native recursion, so depths beyond 2000 work in practice; inputs are bounded
|
|
365
|
+
only by heap. There is no `DEPTH_EXCEEDED` error code. Generators emitting
|
|
366
|
+
machine-produced layouts should self-impose a depth cap if memory
|
|
367
|
+
predictability matters.
|