drawlang 0.1.2

Precision diagrams as code — a DSL and renderer built for AI-agent authors
# drawlang cheat sheet

Precision diagrams as code, built for AI-agent authors. You write `.drawl`,
the renderer verifies your work: every render can emit machine-readable
geometry + lints. Iterate like you iterate on tests:
edit → check → render --report → fix lints → repeat.

## CLI loop

    drawlang check file.drawl                     # all errors at once, with fixes
    drawlang render file.drawl -o out.svg --report   # SVG + geometry/lints JSON
    drawlang render file.drawl -o out.png --scale 2 --theme dark
    drawlang query file.drawl 'gpus[2]'           # one element's bbox + ports
    drawlang fmt file.drawl                       # canonical formatting
    drawlang freeze file.drawl                    # lock layout into file.drawl.lock
                                                  # (delete the .lock to re-layout)
    drawlang explain E0214                        # long-form help for any code

Exit codes: 0 ok, 2 errors. `--json` (global) switches diagnostics to JSON.
Trust the report, not your imagination: bbox = [x, y, w, h] px, y grows
downward. The report's top-level keys: `canvas`, `elements[]` (path, kind,
bbox, ports), `edges[]` (from, to, points, label_bbox), `lints[]`
(code, target, message, fix), `stats` (crossings, overlaps). Every lint's
`fix` field is a concrete next step — apply it and re-render until clean.

## File skeleton

    drawl 0.1                      // required version header, first line
                                   // comments: `//` to end of line

    canvas {                       // optional, top-level only
      theme: paper                 // paper | dark
      flow: right                  // right | down | left | up (layout direction)
      padding: 32                  // px around the drawing
      title: "My diagram"          // bold heading above the canvas
    }

## Nodes & properties

    cpu                            // bare node (label defaults to its name)
    cpu { label: "EPYC 9754" }     // props separated by `;` or newline
    big {
      label: "Line 1\nLine 2"      // \n makes multi-line labels
      width: 200                   // explicit size (default: auto-fit the text)
      shape: pill                  // rect (default) | pill | ellipse
    }
    gpu {
      label: "GPU 0"
      hbm { label: "80 GB HBM3e" } // nested child renders stacked inside
    }
    l3 { width: fill }             // stretch to the parent's content width

## Containers (rigid placement, invisible)

    gpus: row { ... }              // left→right; address children as gpus[0]…
    stack: column { ... }          // top→bottom
    cores: grid 2x4 { ... }        // columns x rows, filled row-major

## Groups (labeled boxes, auto-layout inside)

    group host "Host" {            // name, then optional display label
      cpu { label: "EPYC 9754" }
    }

Inside a group, edges drive the layout (a DAG laid out along `flow`). With no
edges among the children, an explicit `flow: down` / `right` stacks them along
that axis; otherwise reach for a `row` / `column` container for rigid order.

## Components & loops

    def gpu(i) {                   // top-level only; params become variables
      label: "GPU {i}"             // {expr} interpolation in any string
      port pcie { side: bottom }
    }

    gpu(0)                         // anonymous instance (addressed by index)
    g0: gpu(0)                     // named instance
    g1: gpu(1) { width: 220 }      // override block applies after the def body

    for i in 0..4 { gpu(i) }       // half-open: 0,1,2,3 — works anywhere

Expressions: `+ - * / %` with parens; `%` is Euclidean so `(i + 1) % 4`
ring-wraps; `+` also concatenates strings.
String escapes: \" \\ \n \t \{ \}.

## Ports

    port nvlink { side: top }          // side: top | right | bottom | left
    port rx { side: left; order: 0 }   // order positions ports sharing a side

## Edges

    a -> b                          // directed
    a <-> b                         // bidirectional (arrows both ends)
    a <- b                          // reverse (stored as b -> a)
    a -> b : "PCIe 5.0 x16"         // label
    a -> b : "lane" { class: bus }  // edge props / class
    host.cpu -> gpus[i].pcie        // endpoints: paths, indices, ports

## Constraints & pins

    constrain {
      align(a, b, top)              // top|bottom|left|right|centerx|centery
      align(gpus[*], top)           // [*] = all children (constraints only)
      gap(a, b, 48)                 // exact px between facing borders
      below(a, b)                   // also: above, left-of, right-of
      same-width(a, b)              // also: same-height, same-size
    }
    pin legend at (24, 600)         // absolute px, (0,0) = top-left of content;
                                    // top-level elements only

Constraints relate siblings only. Inside row/column/grid, only align() and
gap() apply — ordering is declaration order.

## Classes & style keys

    class bus { stroke: 2; color: @accent; routing: orthogonal }
    n { class: bus }                // apply (repeat the line to stack classes)

  node/group: shape · width (px or `fill`) · height · fill · color
              text.color · stroke · dashed · corner · padding · gap · flow
              wrap (px) · bold · text.size · label · class
  edge:       color · stroke · dashed · routing spline|orthogonal|straight
              bold · text.size · wrap · class
  port:       side · order · label
  canvas:     theme · flow · padding · title

Colors: `#rgb` / `#rrggbb` / `#rrggbbaa`, or theme tokens that adapt to both
themes: @accent @accent2 @hot @ok @warn @muted @faint @ink @bg @surface.
Prefer tokens. Use `routing: orthogonal` for buses/lanes — it avoids boxes;
splines go straight through.

## Paths & scoping (the rules that bite)

- Lookup is lexical: a statement sees its own scope, then enclosing scopes.
  Top-level statements must qualify into groups: `host.cpu`, not `cpu`.
- Container children go by index (`gpus[2]`) or name; a trailing segment can
  be a port (`gpus[2].pcie`).
- Edges connect siblings or anything except their own container.
- Ranges are half-open; sibling names must be unique.

## Reading lints (full detail: `drawlang explain <CODE>`)

  W0301  label overflows its shape         → widen, or `wrap: N`
  W0401  two elements overlap              → constraints or pins collided
  W0402  element outside the canvas        → check pin coordinates
  W0410  edge cuts through a box           → set `routing: orthogonal`
  W0411  edge label collides               → shorten label or add gap()
  W0412  edge crosses 3+ others            → reorder declarations
  W0501  unused def/class                  → delete or wire it up
  W0502  constraint had no effect          → wildcard matched < 2 elements
  W0503  constraint ignored in container   → reorder children instead

Errors: E01xx syntax · E02xx names/paths · E03xx properties/values ·
E04xx constraints.

## Quality bar before you call it done

1. `drawlang check` exits 0 with zero warnings.
2. `render --report` shows zero lints and a crossings count you can justify.
3. Verify intent from the report (e.g. "is host left of gpus?" → compare
   bbox x ranges). Don't guess from the source.