drawlang 0.1.0

Precision diagrams as code — a DSL and renderer built for AI-agent authors
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
//! `drawlang explain <code>` — the long-form story behind every diagnostic.

pub fn cmd_explain(code: &str) -> u8 {
    let code = code.to_uppercase();
    match REGISTRY.iter().find(|(c, _)| *c == code) {
        Some((c, text)) => {
            println!("{c}: {}", text.trim());
            0
        }
        None => {
            eprintln!("error: unknown code `{code}`");
            let known: Vec<&str> = REGISTRY.iter().map(|(c, _)| *c).collect();
            eprintln!("known codes: {}", known.join(", "));
            2
        }
    }
}

const REGISTRY: &[(&str, &str)] = &[
    // ----- lexing / parsing -------------------------------------------------
    (
        "E0101",
        r#"
Unexpected character.

The lexer hit a character that isn't part of drawlang syntax. Common causes:
smart quotes pasted from a document (use plain "double quotes"), tabs inside
identifiers, or stray punctuation. Strings take double quotes only.
"#,
    ),
    (
        "E0102",
        r#"
Unterminated or malformed string.

Strings open and close with `"` on one line. For multi-line labels embed
`\n`: label: "line one\nline two". Valid escapes: \" \\ \n \t \{ \}.
"#,
    ),
    (
        "E0103",
        r#"
Expected one token, found another.

The parser knows exactly what it needs here and tells you what it found
instead. The label under the caret names the expectation. Most often a
missing `:` in a property, a missing `{`, or a missing comma in a list.
"#,
    ),
    (
        "E0104",
        r#"
Broken string interpolation.

`{expr}` inside a string evaluates the expression: "GPU {i}". This error
means a `{` never closed, the braces were empty, or more than one expression
was inside. For a literal brace, escape it: \{.
"#,
    ),
    (
        "E0105",
        r#"
Malformed number, color, or grid size.

Numbers are plain pixels with no unit suffix (write `width: 200`, not
`200px`). Colors are `#rgb`, `#rrggbb`, or `#rrggbbaa`. Grid sizes are
`COLSxROWS`, like `grid 2x4`.
"#,
    ),
    (
        "E0106",
        r#"
Unsupported drawl version.

The header pins the language version this file was written against. This
tool reads version 0.1: `drawl 0.1`.
"#,
    ),
    (
        "E0107",
        r#"
Declaration names must be single identifiers.

`gpus[0]` or `host.cpu` *refer* to elements; declarations create them, so
they take a plain name: `cpu { ... }`. To nest, declare inside the parent's
block.
"#,
    ),
    (
        "E0108",
        r#"
`[*]` outside a constraint.

The wildcard expands to all children of a container and only makes sense in
constraint arguments, like `align(gpus[*], top)`. Edges and properties need
a specific element: `gpus[0]`.
"#,
    ),
    (
        "E0110",
        r#"
Not a statement.

Statements are: nodes (`id { ... }`), containers (`id: row { ... }`), edges
(`a -> b`), properties (`key: value`), or the keywords canvas, def, group,
class, constrain, pin, for, port. Check for a typo in the first word — the
error suggests the closest keyword when one is plausible.
"#,
    ),
    (
        "W0101",
        r#"
Missing version header.

Files should start with `drawl 0.1` so future versions of the tool can keep
old diagrams rendering identically.
"#,
    ),
    (
        "W0102",
        r#"
Duplicate canvas block.

Only one `canvas { ... }` block applies; the later one wins. Merge them.
"#,
    ),
    // ----- semantics --------------------------------------------------------
    (
        "E0201",
        r#"
Unknown element.

The path's first segment is looked up in the current scope, then in each
enclosing scope (lexical scoping). Statements at the top level see only
top-level names — to reach into a group, qualify the path: `host.cpu`, not
`cpu`. The error lists visible names and suggests the closest one.
"#,
    ),
    (
        "E0202",
        r#"
Index out of range.

`container[i]` indexes children in declaration order, starting at 0. The
error reports how many children exist. Loop bounds are a common off-by-one
source: `for i in 0..4` runs i = 0, 1, 2, 3.
"#,
    ),
    (
        "E0203",
        r#"
Duplicate name.

Sibling elements, ports on one node, and top-level defs/classes each share a
namespace; two declarations with one name make paths ambiguous. Rename one,
or use a `for` loop index in the name's label instead.
"#,
    ),
    (
        "E0204",
        r#"
Unknown component.

`name(args)` instantiates `def name(params) { ... }`. Defs live at the top
level. The error suggests the closest defined component.
"#,
    ),
    (
        "E0205",
        r#"
Wrong number of component arguments.

The instantiation must match the def's parameter list exactly. The
diagnostic shows the definition site and its parameters.
"#,
    ),
    (
        "E0206",
        r#"
Unknown variable.

Variables come from `for` loop counters and component parameters, and are
visible only inside their block. The error lists what's in scope.
"#,
    ),
    (
        "E0207",
        r#"
Wildcard must end the path.

`gpus[*]` is the whole set of children; nothing can come after it.
"#,
    ),
    (
        "E0208",
        r#"
Self-edge.

An edge needs two distinct endpoints. To show a loopback, model it as two
ports on the node and connect those.
"#,
    ),
    (
        "E0209",
        r#"
Edge into own container.

An element can't connect to something that contains it — there's no
meaningful place for the arrow to start and end. Connect two siblings, or a
specific child of the container.
"#,
    ),
    (
        "E0301",
        r#"
Unknown property.

Properties are validated against the known set per context. The diagnostic
suggests the closest key or lists them all. See `drawlang cheatsheet` for
the full table.
"#,
    ),
    (
        "E0302",
        r#"
Wrong value type for this property.

Each property takes one shape of value: `width: 200` (number), `label: "x"`
(string), `dashed: true` (boolean word), `fill: @accent` (color). The error
shows the expected form.
"#,
    ),
    (
        "E0303",
        r#"
Unknown keyword value.

This property takes one of a fixed set of words (e.g. `side: top|right|
bottom|left`). The error suggests the closest valid one.
"#,
    ),
    (
        "E0304",
        r#"
Unknown theme token.

Tokens resolve against the active theme so one source renders well in both:
@accent @accent2 @hot @ok @warn @muted @faint @ink @bg @surface. Prefer
tokens over raw hex.
"#,
    ),
    (
        "E0305",
        r#"
Unknown class.

`class: name` references a top-level `class name { ... }`. Classes must be
defined in the same file. The error suggests the closest defined class.
"#,
    ),
    (
        "E0306",
        r#"
Bad loop bound.

`for i in a..b` needs whole numbers with b > a, and runs at most 4096
iterations. The range is half-open: `0..4` is 0, 1, 2, 3.
"#,
    ),
    (
        "E0307",
        r#"
Division or modulo by zero in an expression.
"#,
    ),
    (
        "E0308",
        r#"
Type mismatch in an expression.

Arithmetic needs numbers. `+` also concatenates when either side is a
string. Note `%` is Euclidean (always non-negative) so ring indices like
`(i + 1) % n` behave.
"#,
    ),
    (
        "E0309",
        r#"
Statement in the wrong place.

canvas/def/class live at the top level only; ports live inside nodes;
classes and canvas blocks contain only properties. The message says which
rule applies.
"#,
    ),
    (
        "E0310",
        r#"
Grid overfilled.

`grid 2x4` holds 8 children (columns x rows). Enlarge the grid or remove
children; extra rows are not invented silently.
"#,
    ),
    (
        "E0311",
        r#"
Too many elements (limit 4096).

Almost always a runaway `for` loop or unintended nesting. The limit keeps
renders fast and reports readable.
"#,
    ),
    // ----- constraints ------------------------------------------------------
    (
        "E0312",
        r#"
Component instantiation too deep (limit 64).

A `def` is instantiating itself, directly or through another component.
Diagrams are finite; break the cycle, or build repetition with `for` loops
instead of recursion.
"#,
    ),
    (
        "E0401",
        r#"
Unknown constraint.

Constraints: align(a, b, ..., EDGE), gap(a, b, PX), below/above/left-of/
right-of(a, b), same-width/same-height/same-size(a, b, ...).
"#,
    ),
    (
        "E0402",
        r#"
Wrong constraint arguments.

The help line shows the exact usage. Targets are element paths (wildcard
`container[*]` allowed for align/same-*); distances are plain pixel numbers.
"#,
    ),
    (
        "E0403",
        r#"
Bad alignment edge.

The last argument of align() picks the edge: top, bottom, left, right,
centerx, centery.
"#,
    ),
    (
        "E0404",
        r#"
Constraint targets aren't siblings.

Constraints act within one container's coordinate system. To relate elements
in different containers, constrain the containers themselves.
"#,
    ),
    (
        "E0405",
        r#"
Pin on a nested element.

Pins set absolute canvas positions and only apply to top-level elements.
Pin the outermost container; layout places its contents.
"#,
    ),
    (
        "E0406",
        r#"
Conflicting constraints.

No layout can satisfy this constraint together with the earlier ones (e.g.
gap(a, b, 50) and gap(a, b, 100)). Remove or relax one. The first
conflicting constraint is the one flagged.
"#,
    ),
    // ----- visual lints ------------------------------------------------------
    (
        "W0301",
        r#"
Label overflows its shape.

Only possible when an explicit width/height is smaller than the measured
text. Fixes, best first: remove the explicit size (boxes auto-fit), set the
suggested larger width, or set `wrap: N` to wrap the text at N pixels.
"#,
    ),
    (
        "W0401",
        r#"
Two elements overlap.

Usually pinned elements colliding, or conflicting constraints resolving on
top of each other. The lint names both elements and the overlap area; the
geometry report has exact rectangles.
"#,
    ),
    (
        "W0402",
        r#"
Element outside the canvas.

The canvas normally grows to fit; this fires when pins push content past the
origin. Pins are content-relative pixels with (0, 0) at the top-left.
"#,
    ),
    (
        "W0410",
        r#"
Edge cuts through a box.

Spline edges take the direct path and don't dodge obstacles. Set
`routing: orthogonal` on the edge or its class — orthogonal routes go around
boxes (that's also what buses and lanes should look like).
"#,
    ),
    (
        "W0411",
        r#"
Edge label collides with something.

The label placer tries above/below each clear run of the route; this fires
when nothing clear was found. Shorten the label, or give the layout more
room (gap() on the crowded elements).
"#,
    ),
    (
        "W0412",
        r#"
Heavily crossed edge.

This edge crosses three or more others — a strong signal the element order
fights the connection pattern. Reorder the declarations (layout follows
declaration order within a layer), or route the edge orthogonally.
"#,
    ),
    (
        "W0501",
        r#"
Unused definition.

A def that's never instantiated or a class that's never referenced. Delete
it, or wire it up — dead definitions confuse future readers (and agents).
"#,
    ),
    (
        "W0502",
        r#"
Constraint with no effect.

align/same-* with fewer than two targets after wildcard expansion. Often an
empty container or a wildcard on the wrong path.
"#,
    ),
    (
        "W0503",
        r#"
Constraint vs. rigid container.

row/column/grid place children themselves. Inside them, align() picks the
cross-axis alignment, gap(a, b, px) adjusts the spacing between adjacent
children — but ordering constraints (below/left-of/...) do nothing: reorder
the declarations instead.
"#,
    ),
];