# ArchLang GBNF grammar — a constrained-decoding grammar for llama.cpp-style samplers. # # GENERATED by scripts/gen-gbnf.ts from src/grammar/tokens.ts (keywords/operators) # plus USE_KINDS / FURNITURE_ANCHORS from src/ast.ts. Do NOT edit by hand — run # `npx tsx scripts/gen-gbnf.ts`; CI checks drift (test/gbnf-drift.test.ts). # # Feed it to a constrained sampler (llama.cpp `--grammar-file`, or the `grammar` # field of a server completion) to force a model to emit syntactically well-formed # ArchLang. This is a PRACTICAL grammar: intentionally a superset / approximation of # the hand-written parser (src/parser.ts) — it constrains the keyword-first # statement shapes and enum vocabularies but accepts some token spacings and # attribute orders the parser is stricter about. It must never REJECT a valid .arch; # the parser + its catalogued diagnostics remain the source of truth for validity. # Deterministic bytes (no version / date). root ::= ws "plan" rws string ws "{" ws ( plan-stmt ws )* "}" ws plan-stmt ::= setting | title-stmt | axes-stmt | acc-stmt | theme-stmt | style-stmt | component-stmt | import-stmt | strip-stmt | level-stmt | block-stmt block-stmt ::= element | let-stmt | for-stmt | if-stmt | while-stmt | set-stmt | zone-stmt | place-stmt | instance-stmt | assign-stmt element ::= wall-stmt | room-stmt | door-stmt | window-stmt | opening-stmt | furniture-stmt | dim-stmt | column-stmt | stair-stmt | elevator-stmt | escalator-stmt | roof-stmt | void-stmt | outdoor-stmt | fence-stmt setting ::= units-stmt | grid-stmt | paper-stmt | scale-stmt | height-stmt | north-stmt | site-stmt | dims-stmt | schedule-stmt | legend-stmt units-stmt ::= "units" rws "mm" grid-stmt ::= "grid" rws number paper-stmt ::= "paper" rws paper-size ( rws paper-orientation )? paper-size ::= "A4" | "a4" | "A3" | "a3" | "A2" | "a2" | "A1" | "a1" | "A0" | "a0" paper-orientation ::= "landscape" | "portrait" scale-stmt ::= "scale" rws number ws ":" ws number height-stmt ::= "height" rws expr north-stmt ::= "north" rws north-dir north-dir ::= "up" | "down" | "left" | "right" | number site-stmt ::= "site" ws "{" ws ( site-field ws )* "}" site-field ::= "street" rws compass-dir | "hemisphere" rws hemisphere | "boundary" ws point ws point ws point ( ws point )* compass-dir ::= "north" | "south" | "east" | "west" hemisphere ::= "north" | "south" dims-stmt ::= "dims" rws "auto" ( rws dims-mode )? dims-mode ::= "overall" | "rooms" | "walls" | "all" schedule-stmt ::= "schedule" rws schedule-subject schedule-subject ::= "rooms" legend-stmt ::= "legend" acc-stmt ::= ( "accTitle" | "accDescr" ) rws string title-stmt ::= "title" ws "{" ws ( title-field ws )* "}" title-field ::= ( "project" | "drawn_by" | "date" ) rws string axes-stmt ::= "axes" ws "{" ws ( axes-row ws )* "}" axes-row ::= ( "x" | "y" ) rws "at" ws expr ( ws "," ws expr )* theme-stmt ::= "theme" ( rws "from" rws string | ( rws ident )? ( ws theme-block )? ) theme-block ::= "{" ws ( theme-entry ws )* "}" theme-entry ::= ident ws ":"? ws config-value style-stmt ::= "style" rws ident ws "{" ws ( style-entry ws )* "}" style-entry ::= ident ws ":"? ws string config-value ::= string | number component-stmt ::= "component" rws ident ws "(" ws param-list? ws ")" ws block param-list ::= ident ( ws "," ws ident )* import-stmt ::= "import" rws string ( ws ":" ws import-items | rws "as" rws ident ) import-items ::= "*" | import-item ( ws "," ws import-item )* import-item ::= ident ( rws "as" rws ident )? let-stmt ::= "let" rws ident ( ws "(" ws param-list? ws ")" )? ws "=" ws expr for-stmt ::= "for" rws ident rws "in" ws expr ws block if-stmt ::= "if" ws expr ws block ( ws "else" ws block )? while-stmt ::= "while" ws expr ws block set-stmt ::= "set" rws element-kw ws "(" ws set-entries? ws ")" element-kw ::= "wall" | "room" | "door" | "window" | "opening" | "furniture" | "dim" | "column" | "stair" | "elevator" | "escalator" | "roof" | "void" | "outdoor" | "fence" set-entries ::= set-entry ( ws "," ws set-entry )* set-entry ::= ident ws ":" ws expr instance-stmt ::= ident ws "(" ws ( expr ( ws "," ws expr )* )? ws ")" place-stmt ::= "place" rws ident ws "(" ws ( expr ( ws "," ws expr )* )? ws ")" rws "as" rws ident rws "at" ws point ( rws "rotate" ws number )? ( rws "mirror" rws mirror-axis )? mirror-axis ::= "x" | "y" assign-stmt ::= ident ws "=" ws expr block ::= "{" ws ( block-stmt ws )* "}" strip-stmt ::= "strip" rws ( strip-h | strip-v ) ws "{" ws ( strip-room ws )* "}" strip-h ::= ( "right" | "left" ) rws "at" ws point rws "gap" ws expr ( rws "height" ws expr )? strip-v ::= ( "down" | "up" ) rws "at" ws point rws "gap" ws expr ( rws "width" ws expr )? strip-room ::= "room" rws id-opt "size" ws strip-size ( ws strip-label )? ( ws room-uses )? strip-label ::= "label" ws string strip-size ::= expr ( ws "x" ws expr )? level-stmt ::= "level" rws ( "-" ws )? number ( rws string )? ( rws "height" ws expr )? ws block zone-stmt ::= "zone" rws ident ( rws string )? ws block wall-stmt ::= "wall" rws id-opt ident rws "thickness" ws expr ( ws wall-material )? ( ws "height" ws expr )? ws "{" ws point ws ( wall-vertex ws )+ ( "close" ws )? "}" wall-material ::= "material" rws ident ( rws ( "scale" | "angle" ) ws expr ){0,2} wall-vertex ::= point | wall-arc wall-arc ::= "arc" ws point ws "radius" ws expr ( rws arc-dir )? ( rws "major" )? arc-dir ::= "cw" | "ccw" room-stmt ::= "room" rws id-opt ( room-rect | room-poly | room-circle ) ( ws room-label )? ( ws room-uses )? room-rect ::= room-pos ws "size" ws dims room-poly ::= "polygon" ws point ws point ws point ( ws point )* room-circle ::= "circle" rws "at" ws point ws "radius" ws expr room-pos ::= "at" ws point | rel-dir rws ref ( rws "align" rws ident )? ( rws "gap" ws expr )? rel-dir ::= "right-of" | "left-of" | "below" | "above" room-label ::= "label" ws string ( ws "at" ws point )? room-uses ::= "uses" rws use-kind ( rws use-kind )* use-kind ::= "living" | "kitchen" | "dining" | "bedroom" | "bath" | "wc" | "hall" | "circulation" | "storage" | "utility" | "office" | "entry" | "garage" door-stmt ::= "door" rws id-opt ( door-kind rws )? opening-placement door-clauses door-kind ::= "hinged" | "sliding" | "barn" | "bifold" | "pocket" | "garage" door-clauses ::= ( ws "hinge" rws hinge-val )? ( ws "swing" rws swing-val )? ( ws "slide" rws slide-val )? ( ws "open" ws expr )? ( ws "head" ws expr )? hinge-val ::= "near" rws ( "start" | "end" ) | "left" | "right" swing-val ::= "into" rws ref | "in" | "out" slide-val ::= "left" | "right" window-stmt ::= "window" rws id-opt opening-placement ( ws "sill" ws expr )? ( ws "head" ws expr )? opening-stmt ::= "opening" rws id-opt opening-placement ( ws "head" ws expr )? opening-placement ::= opening-free | opening-hosted opening-free ::= "at" ws point ws "width" ws expr ( ws "wall" rws ref )? opening-hosted ::= "on" rws ref rws "at" ws attach-pos ws "width" ws expr attach-pos ::= "center" | attach-expr ws "%" | attach-expr furniture-stmt ::= "furniture" rws id-opt ident rws ( furn-placed | furn-roomed ) furn-placed ::= ( furn-at | furn-against ) furn-tail ( ws "in" rws ref )? furn-roomed ::= "in" rws ref rws in-place furn-tail furn-at ::= "at" ws point furn-against ::= "against" rws "wall" rws ref ( ws "segment" ws expr )? ( ws "offset" ws expr )? ( ws "side" rws ident )? in-place ::= "centered" | "anchor" rws anchor ( ws "flush" )? ( ws "inset" ws expr )? anchor ::= "top-left" | "top" | "top-right" | "left" | "center" | "right" | "bottom-left" | "bottom" | "bottom-right" furn-tail ::= ( ws "size" ws dims )? ( ws "label" ws string )? ( ws "rotate" ws expr )? dim-stmt ::= "dim" ( ( rws dim-ref )? ws point ws "->" ws point | rws dim-curve ) ( ws "offset" ws expr )? ( ws "text" ws string )? dim-curve ::= "radius" rws ref ( rws "segment" ws expr )? | "diameter" rws ref dim-ref ::= "faces" | "clear" column-stmt ::= "column" rws id-opt "at" ws point ws "size" ws dims stair-stmt ::= "stair" rws id-opt "at" ws point ws "size" ws dims ws "dir" rws vert-dir ( ws "width" ws expr )? elevator-stmt ::= "elevator" rws id-opt "at" ws point ws "size" ws dims escalator-stmt ::= "escalator" rws id-opt "at" ws point ws "size" ws dims ws "dir" rws vert-dir vert-dir ::= "up" | "down" roof-stmt ::= "roof" rws ( roof-overhang | roof-polygon ) roof-overhang ::= "overhang" ws expr ( rws "wall" rws ref )? roof-polygon ::= "polygon" ws point ws point ws point ( ws point )* void-stmt ::= "void" rws id-opt "at" ws point ws "size" ws dims outdoor-stmt ::= "outdoor" rws id-opt outdoor-kind rws ( outdoor-rect | outdoor-poly ) ( ws outdoor-label )? ( ws outdoor-rail )? outdoor-kind ::= "lawn" | "planting" | "paving" | "deck" | "gravel" | "water" | "driveway" | "patio" | "balcony" outdoor-rect ::= "at" ws point ws "size" ws dims outdoor-poly ::= "polygon" ws point ws point ws point ( ws point )* outdoor-label ::= "label" ws string outdoor-rail ::= "rail" rws rail-edge ( ( ws "," ws | rws ) rail-edge )* rail-edge ::= "top" | "bottom" | "left" | "right" | "all" | "none" fence-stmt ::= "fence" rws id-opt ( fence-style rws )? "{" ws point ws ( point ws )+ ( "close" ws )? "}" fence-style ::= "picket" | "panel" | "post" id-opt ::= ( "id" ws "=" ws ident rws )? point ::= "(" ws expr ws "," ws expr ws ")" dims ::= expr ws "x" ws expr expr ::= or-expr or-expr ::= and-expr ( ws "||" ws and-expr )* and-expr ::= eq-expr ( ws "&&" ws eq-expr )* eq-expr ::= cmp-expr ( ws eq-op ws cmp-expr )* eq-op ::= "==" | "!=" cmp-expr ::= range-expr ( ws cmp-op ws range-expr )* cmp-op ::= "<=" | ">=" | "<" | ">" range-expr ::= add-expr ( ws ".." ws add-expr )* add-expr ::= mul-expr ( ws add-op ws mul-expr )* add-op ::= "+" | "-" mul-expr ::= unary-expr ( ws mul-op ws unary-expr )* mul-op ::= "*" | "/" | "%" attach-expr ::= nm-or-expr nm-or-expr ::= nm-and-expr ( ws "||" ws nm-and-expr )* nm-and-expr ::= nm-eq-expr ( ws "&&" ws nm-eq-expr )* nm-eq-expr ::= nm-cmp-expr ( ws eq-op ws nm-cmp-expr )* nm-cmp-expr ::= nm-range-expr ( ws cmp-op ws nm-range-expr )* nm-range-expr ::= nm-add-expr ( ws ".." ws nm-add-expr )* nm-add-expr ::= nm-mul-expr ( ws add-op ws nm-mul-expr )* nm-mul-expr ::= unary-expr ( ws nm-mul-op ws unary-expr )* nm-mul-op ::= "*" | "/" unary-expr ::= unary-op ws unary-expr | postfix-expr unary-op ::= "-" | "+" | "!" postfix-expr ::= atom ( ws "[" ws expr ws "]" )* atom ::= number | string | array | if-expr | call | ref | "(" ws expr ws ")" call ::= ident ws "(" ws ( expr ( ws "," ws expr )* )? ws ")" array ::= "[" ws ( expr ( ws "," ws expr )* )? ws "]" if-expr ::= "if" ws expr ws "{" ws expr ws "}" ws "else" ws "{" ws expr ws "}" number ::= ( digits frac? | frac ) unit? digits ::= [0-9]+ frac ::= "." digits unit ::= "mm" | "cm" | "m" ident ::= [a-zA-Z_] [a-zA-Z0-9_]* ref ::= ident ( "." ident )* string ::= "\"" str-char* "\"" str-char ::= str-plain | str-esc | interp str-plain ::= [^"\\{}\n] str-esc ::= "\\" ( [^\n] | "\n" ) interp ::= "{" ws expr ws "}" ws ::= sp cont* rws ::= sp1 ws sp ::= [ \t\r]{0,80} sp1 ::= [ \t\r] | comment "\n" | "\n" cont ::= comment "\n" sp | "\n" sp comment ::= "#" [^\n]*