Skip to content

This page is generated from spec.llm.md — edit it there.

ArchLang in one prompt

ArchLang is a tiny declarative language that compiles a .arch source file into a professional floor-plan drawing (SVG/PNG/PDF/DXF). It is built for AI agents: deterministic (same source → identical output), pure (no runtime/IO), and self-correcting (every error carries a machine code and a fix). This page is everything you need to author it. Print it any time with arch spec.

The 7 rules that matter

  1. Units are millimetres. A 4-metre wall is 4000, not 4.
  2. Origin is top-left; +x goes right, +y goes DOWN (screen/SVG convention — not math y-up).
  3. Coordinates are (x, y) tuples; sizes are WxH (e.g. 4000x3000) or <expr> x <expr> with spaces.
  4. Doors and windows must lie ON a wall segment (on its centerline), or you get a W_DOOR_OFF_WALL / W_WINDOW_OFF_WALL warning.
  5. String interpolation is "{expr}" inside double quotes (e.g. label "Unit {i}").
  6. Ids must be unique. Omit id= to auto-generate one; give an id only when you reference it.
  7. Everything is expand-time and purelet/for/if/functions all evaluate during compile.

Structure

arch
plan "Title" {
  units mm            # required-ish settings come first
  grid 50             # snap grid in mm
  scale 1:50          # drawing scale (annotation only)
  north up            # up | down | left | right
  # … elements and scripting …
  title { project "…" drawn_by "…" date "…" }
}

Elements

text
wall <category> thickness <mm> [material <name>] { (x,y) (x,y) … [close] }   # category e.g. exterior/partition; `close` makes a loop
room [id=<name>] at (x,y) size <W>x<H> [label "…"] [uses living|kitchen|dining|bedroom|bath|wc|hall|circulation|storage|utility|office|entry …]   # OR relational: room [id=…] (right-of|left-of|below|above) <roomId> [align top|middle|bottom|left|right] [gap <mm>] size <W>x<H> [label "…"]
door [id=<name>] at (x,y) width <mm> [wall <id|category>] [hinge left|right] [swing in|out]   # must sit on a wall
window [id=<name>] at (x,y) width <mm> [wall <id|category>]   # must sit on a wall
opening [id=<name>] at (x,y) width <mm> [wall <id|category>]   # a leaf-less cased opening (gap in a wall) that still connects the two spaces
furniture <category> (at (x,y) | against wall <id> [segment <n>] [offset <mm>] [side left|right]) size <W>x<H> [label "…"] [rotate 0|90|180|270] [in <roomId>]   # `at` size is plan W×H; `against` size is wall-relative along×depth and derives position+rotation, with `side` inferred from `in <roomId>` when omitted
dim (x,y)->(x,y) offset <mm> [text "…"]   # a dimension line
column [id=<name>] at (x,y) size <W>x<H>

Scripting (all expand-time, deterministic)

  • let NAME = expr — bind a constant. NAME = expr — reassign an existing binding.
  • let f(a, b) = expr — a pure value-function. Built-ins: min max abs sqrt floor ceil round len str.
  • for i in lo..hi { … } — loop over a half-open integer range (0..3 → 0,1,2).
  • if cond { … } else { … } · while cond { … }.
  • set <element>(attr: value) — scoped default for following elements (e.g. set door(swing: out)).
  • Arrays: [a, b, c], indexed arr[i]. Operators: + - * / %, == != < > <= >=, && ||. Comments: # ….
  • import "lib/x.arch": name and component name(args) { … } for reuse.

Keyword reference

  • Settings / control: plan, component, let, theme, title, style, import, for, if, while, else, set
  • Elements: wall, room, door, window, opening, furniture, dim, column
  • Attributes: units, grid, scale, north, dims, material, angle, at, size, width, thickness, label, hinge, swing, offset, text, close, id, project, drawn_by, date, from, as, right-of, left-of, below, above, align, gap, uses, rotate, against, segment, side
  • Enums / values: up, down, left, right, in, out, mm, true, false, top, middle, bottom, center, auto, living, kitchen, dining, bedroom, bath, wc, hall, circulation, storage, utility, office, entry

CLI loop (how an agent drives it)

bash
arch spec                              # print this spec
arch compile plan.arch -o out.svg --json   # render; JSON has { ok, diagnostics, summary }
echo '<source>' | arch compile - --json    # compile from stdin (no temp file)
arch describe plan.arch --json         # semantic facts: rooms, areas, adjacency, what doors connect
arch lint plan.arch --json             # architectural soundness warnings
arch validate plan.arch --json         # parse + lint only (fast, no render)
arch explain E_ROOM_SIZE --json        # look up any error code

Self-correction loop: compile/validate → if ok is false, read each diagnostics[].fix (and line/col/span), edit the source, recompile. Exit code 2 means a deterministic user-source error (fix it; don't blindly retry). Then describe --json to confirm the plan matches intent (right room count, areas, adjacency) without rendering an image.

Common mistakes

MistakeFix
Using metres (size 4x3)Use millimetres (size 4000x3000).
Expecting +y to go up+y goes down; a room below another has a larger y.
Door/window floating in spacePut its at on a wall segment's centerline.
size 4000 (no height)Sizes are WxH: size 4000x3000 (or W x H with spaces).
Reusing an idIds are unique; omit id= to auto-generate.
String math without interpolationUse "{expr}", e.g. label "{aream2(W,H)} m²".

Worked examples

examples/studio.arch

arch
# A compact studio apartment — the canonical ArchLang example.
#
# Architecturally sound (passes `arch lint`): every room opens off a central hall,
# so the bath is never reached through the bedroom; the bath is fully enclosed and
# fitted with real fixtures; and no door leaf sweeps onto furniture. Self-contained
# (no imports) so it compiles from a single file.
plan "Studio 1BR" {
  units mm
  grid 50
  scale 1:50
  north up

  # Exterior shell + partitions. The x=4000 divider runs the FULL height so the bath
  # is walled off from the living space; the right column splits into bedroom / hall / bath.
  wall exterior  thickness 200 { (0,0) (7000,0) (7000,6000) (0,6000) close }
  wall partition thickness 100 { (4000,0) (4000,6000) }
  wall partition thickness 100 { (4000,3000) (7000,3000) }
  wall partition thickness 100 { (4000,4400) (7000,4400) }

  room id=r_living at (0,0)       size 4000x6000 label "Living / Kitchen" uses living kitchen
  room id=r_bed    at (4000,0)    size 3000x3000 label "Bedroom"           uses bedroom
  room id=r_hall   at (4000,3000) size 3000x1400 label "Hall"             uses hall
  room id=r_bath   at (4000,4400) size 3000x1600 label "Bath"             uses bath

  # Entrance into the living space; the hall links it to the bedroom and the bath.
  # Living ↔ hall is a cased opening (circulation needs no door leaf); the bedroom
  # and bath each get a real door off the hall.
  door    id=d_main   at (3000,6000) width 1000 wall exterior  hinge left  swing in
  opening id=o_living at (4000,3700) width 900  wall partition
  door    id=d_bed    at (6400,3000) width 800  wall partition hinge right swing out
  door    id=d_bath   at (4600,4400) width 800  wall partition hinge left  swing out

  window at (0,2000)    width 1500 wall exterior
  window at (7000,1500) width 1200 wall exterior
  window at (7000,5200) width 700  wall exterior

  # Kitchen run along the north wall: sink · counter · stove · fridge (drawn as symbols).
  furniture kitchen_sink at (300,250)  size 800x600
  furniture counter      at (1200,250) size 600x600
  furniture stove        at (1950,250) size 600x600
  furniture fridge       at (2700,250) size 600x650

  # Living + bedroom furniture, kept clear of the door swings.
  furniture sofa at (350,4300) size 2000x900  label "Sofa"
  furniture bed  at (4300,300) size 1500x2000 label "Bed"

  # Bathroom fixtures, kept clear of the door's entry path (the door swings out into
  # the hall): shower in the far corner, basin against the partition, WC on the south
  # wall — the left third of the room stays open so you can actually step inside.
  furniture shower at (6000,5000) size 900x900   # against the E + S walls (corner)
  furniture basin  at (5200,4450) size 600x450   # back to the hall partition
  furniture wc     at (5200,5200) size 400x700   # back to the south wall

  # Dimension strings: room widths above, overall extents around. The reference
  # (witness) points sit on the building's OUTER faces (x=-100/7100, y=-100/6100 for
  # the 200mm shell) so the extension lines start at the wall face and read outward,
  # never poking back into the building — while the spans still measure centerline to
  # centerline (4000 · 3000 · 7000 · 6000).
  dim (4000,-100)->(0,-100)    offset 250 text "4000"
  dim (7000,-100)->(4000,-100) offset 250 text "3000"
  dim (0,6100)->(7000,6100)    offset 500 text "7000"
  dim (7100,6000)->(7100,0)    offset 500 text "6000"

  title {
    project "Studio Apartment"
    drawn_by "ArchCanvas"
    date "2026-06-27"
  }
}

examples/parametric.arch

arch
# Parametric plan (v0.8 scripting): a row of studio units generated with a
# `for` loop over a range, a value-function, an array indexed per unit, a scoped
# `set` rule, an `if`, and string-interpolated labels. Everything is derived
# from the constants — change COUNT and the whole row regenerates.
plan "Parametric — Studio Row" {
  units mm
  grid 50
  scale 1:100
  north up

  # Plan-level constants (visible everywhere below — plan scope is global).
  let WALL  = 200
  let W     = 4000          # unit width
  let H     = 5000          # unit depth
  let DOOR  = 900
  let WIN   = 1600
  let COUNT = 3             # number of units

  # A value-function (pure closure) — area in square metres.
  let aream2(w, h) = w * h / 1000000

  # Per-unit names, indexed by the loop variable.
  let names = ["Studio A", "Studio B", "Studio C"]

  # Entrance doors swing outward throughout this plan (scoped default).
  set door(swing: out)

  for i in 0..COUNT {
    let x = i * W
    wall exterior thickness WALL { (x, 0) (x + W, 0) (x + W, H) (x, H) close }
    room at (x, 0) size W x H label "{names[i]}"
    furniture bed   at (x + 300, 300)      size 1500x2000 label "Bed"
    furniture kitch at (x + W - 1900, 300) size 1600x600  label "Kitchen"
    door   at (x + W / 2, H) width DOOR wall exterior hinge left
    window at (x + W / 2, 0) width WIN  wall exterior

    # The end unit carries a per-unit area dimension (computed by the function).
    # Referenced to the outer face (y = H + WALL/2) so the extension lines start at
    # the wall and read downward, away from the building.
    if i == COUNT - 1 {
      dim (x, H + WALL / 2)->(x + W, H + WALL / 2) offset 600 text "{aream2(W, H)} m² each"
    }
  }

  # Overall run, dimensioned above the building: right-to-left so the offset lands
  # ABOVE the row (outside), and referenced to the outer top face (y = -WALL/2).
  dim (W * COUNT, 0 - WALL / 2)->(0, 0 - WALL / 2) offset 1300 text "{COUNT} units"
}