Version: 0.4.3 Language: ReScript (compiled to JavaScript/TypeScript) Last Updated: 2026-06-11
This reference covers the V2 parser types (wyreframe/parser/v2, declared in src/parser/v2/V2Parser.d.ts). Legacy V1 types are summarized at the end.
- Encoding Conventions
- Positions and Locations
- Enumerations
- Layout Types
- AST Node Types
- Error and Warning Types
- Options Types
- Result Type
- Legacy V1 Types
V2 AST values are ReScript variants exposed to JavaScript as tagged objects:
{ TAG: 'ButtonNode', _0: { location, id, text } }- Narrow unions on the
TAGfield; the payload is always_0. - Some field names carry a trailing underscore to avoid ReScript keywords:
end_(location end),Center_(distribution). - All AST records are immutable — the parser never mutates them after construction; treat them as read-only.
- All positions are 0-based. Columns are visual columns (Unicode wide chars = 2, tabs expanded by
tabSize).
interface Position {
row: number; // 0-based line index
col: number; // 0-based visual column (Unicode + tab-aware)
offset: number; // 0-based offset into the source
}interface SourceLocation {
start: Position;
end_: Position; // exclusive
}Every AST node, error, and warning carries a SourceLocation.
Pixel-free rectangle of a container in grid coordinates:
interface Bounds {
x: number; // left col
y: number; // top row
width: number;
height: number;
}type DeviceType = 'Mobile' | 'Tablet' | 'Desktop';
type Alignment = 'Left' | 'Center' | 'Right';
type DividerStyle = 'Normal' | 'Bold';
type LayoutDirection = 'Row' | 'Column' | 'Mixed';
type Distribution = 'Equal' | 'SpaceBetween' | 'SpaceAround' | 'Start' | 'End' | 'Center_';Layout never duplicates nodes — groups address children by index range into the parent's children array (single source of truth).
interface ElementGroup {
direction: LayoutDirection;
start: number; // inclusive index into parent.children
end_: number; // exclusive index
startRow: number; // visual row the group starts on
}interface LayoutInfo {
direction: LayoutDirection; // overall direction (Mixed if groups differ)
groups: ElementGroup[];
distribution?: Distribution; // optional spacing hint
}Present on SceneNode, ComponentNode, and ContainerNode.
type AstNode =
| SceneNode | ComponentNode | ContainerNode
| TextNode | ButtonNode | LinkNode | InputNode | SelectNode
| CheckboxNode | RadioNode | DividerNode
| StringNode | EmojiNode | PropPlaceholderNode
| ErrorNode;A parse result's roots — each top-level @scene: / @component::
type BlockNode =
| { TAG: 'SceneBlock'; _0: SceneNode['_0'] }
| { TAG: 'ComponentBlock'; _0: ComponentNode['_0'] };{
TAG: 'SceneNode',
_0: {
location: SourceLocation;
slug: string; // from @scene: <slug>
title?: string; // from @title:
device?: DeviceType; // from @device:
transition?: string; // from @transition:
children: AstNode[];
layout: LayoutInfo;
}
}{
TAG: 'ComponentNode',
_0: {
location: SourceLocation;
slug: string; // from @component: <slug>
props: PropDefinition[]; // from @props:
children: AstNode[];
layout: LayoutInfo;
}
}
interface PropDefinition {
name: string;
optional: boolean; // true for `name?`
defaultValue?: string;
}{
TAG: 'ContainerNode',
_0: {
location: SourceLocation;
id?: string; // from +--#id--+ or | #id |
name?: string; // from +--name--+
children: AstNode[];
layout: LayoutInfo;
bounds: Bounds; // grid rectangle of the box
containsErrorRecovery: boolean; // true if recovery occurred inside
}
}// Plain text (fallback element)
{ TAG: 'TextNode', _0: { location, content: string, align: Alignment } }
// [ Label ]
{ TAG: 'ButtonNode', _0: { location, id: string, text: string } } // id auto-slugged
// < Label >
{ TAG: 'LinkNode', _0: { location, id: string, text: string } } // id auto-slugged
// [__placeholder__]
{ TAG: 'InputNode', _0: { location, placeholder: string } }
// [v: placeholder]
{ TAG: 'SelectNode', _0: { location, id: string, placeholder: string } }
// [x] Label / [ ] Label
{ TAG: 'CheckboxNode', _0: { location, checked: boolean, label: string } }
// (*) Label / ( ) Label
{ TAG: 'RadioNode', _0: { location, selected: boolean, label: string, group?: string } }
// --- / === / --- label --- / -#id-
{ TAG: 'DividerNode', _0: { location, style: DividerStyle, id?: string, label?: string } }Radio group is assigned automatically by the grouping heuristics (vertical run, same-row, same-container).
"text" literal with interpolation parts:
{
TAG: 'StringNode',
_0: {
location: SourceLocation;
content: string; // resolved text content
interpolations: InterpolationContent[];
multiline: boolean;
}
}
type InterpolationContent =
| { TAG: 'Literal'; _0: string }
| { TAG: 'PropRef'; _0: PropPlaceholderNode['_0'] }
| { TAG: 'EmojiRef'; _0: EmojiNode['_0'] };{ TAG: 'EmojiNode', _0: { location, shortcode: string, emoji: string } }{
TAG: 'PropPlaceholderNode',
_0: {
location: SourceLocation;
name: string;
required: boolean; // false for ${name?} and ${name:default}
defaultValue?: string; // from ${name:default}
}
}Inserted during error recovery where an element failed to parse:
{ TAG: 'ErrorNode', _0: { location, message: string, recoveredContent?: string } }type ErrorCode =
| 'InvalidIdFormat'
| 'MultipleIdDeclarations'
| 'UnclosedInput'
| 'UnclosedString'
| 'UnclosedContainer'
| 'MissingBlockDeclaration'
| 'NestedBlockDeclaration'
| 'MaxDepthExceeded';Plain-string codes for parameterless warnings; { TAG, _0 } variants for codes carrying a payload:
type WarningCode =
| 'PropOutsideComponent'
| 'MixedDividerLabelId'
| 'MissingCheckboxLabel'
| 'MissingRadioLabel'
| 'MisalignedContainerCorner'
| 'MisalignedContainerWall'
| 'InconsistentContainerWidth'
| 'RadioGroupAmbiguous'
| 'LooksLikeButton' | 'LooksLikeInput' | 'LooksLikeCheckbox' | 'LooksLikeRadio'
| { TAG: 'UnknownEmoji'; _0: string } // shortcode name
| { TAG: 'DuplicatePropName'; _0: string }
| { TAG: 'DuplicateContainerId'; _0: string }
| { TAG: 'UnknownPropReference'; _0: string }
| { TAG: 'MultipleRadiosSelected'; _0: string }; // group nameinterface ParseError {
code: ErrorCode;
message: string;
location: SourceLocation;
recoverable: boolean; // false in strict mode or for fatal errors
}
interface ParseWarning {
code: WarningCode;
message: string;
location: SourceLocation;
ruleId?: string; // heuristic-driven warnings only, e.g. 'container.wallAlignment'
}interface ParseOptions {
strict?: boolean; // default false
tabSize?: number; // default 4
maxDepth?: number; // default 10
heuristics?: HeuristicsPartial;
emojiRegistry?: Record<string, string>; // shortcode -> emoji
}interface HeuristicsPartial {
containerColumnTolerance?: number; // default 1
containerWidthTolerance?: number; // default 2
radioHorizontalGap?: number; // default 6
radioVerticalColumnTolerance?: number; // default 1
radioMaxBlankRows?: number; // default 0
centerSymmetryThreshold?: number; // default 0.15
rightAlignThreshold?: number; // default 0.10
dividerMinRun?: number; // default 3
nearMissTokenDistance?: number; // default 1
}interface ParseResult {
ast?: BlockNode; // first block (=== blocks[0]); absent when blocks is empty
blocks: BlockNode[]; // all top-level blocks in declaration order
errors: ParseError[];
warnings: ParseWarning[];
success: boolean; // errors.length === 0
}import { parse, type AstNode } from 'wyreframe/parser/v2';
function collectInputs(node: AstNode, out: string[] = []): string[] {
switch (node.TAG) {
case 'InputNode':
out.push(node._0.placeholder);
break;
case 'SceneNode':
case 'ComponentNode':
case 'ContainerNode':
node._0.children.forEach(c => collectInputs(c, out));
break;
}
return out;
}Exported from the
wyreframemain package. These describe the V1 parser/renderer surface (V1 syntax) and will be removed when V1 is retired.
| Category | Types |
|---|---|
| Core | Position (row/col only), Bounds (top/left/bottom/right), Alignment |
| Grid | CellChar, Grid |
| Elements | Element, BoxElement, ButtonElement, InputElement, LinkElement, CheckboxElement, TextElement, DividerElement, RowElement, SectionElement |
| Scenes | Scene, AST |
| Device | DeviceType (6 values incl. laptop, *-landscape), DeviceDimensions, parseDeviceType |
| Interactions | InteractionVariant, InteractionAction, GotoAction, BackAction, ForwardAction, ValidateAction, CallAction, Interaction, SceneInteractions |
| Errors | ParseError (message/line/column), ErrorCode, per-code error types, ErrorContext |
| Rendering | RenderOptions, RenderResult, SceneManager, TransitionType, DeadEndClickInfo |
| Utility | Result, Option |
Key differences from V2:
- V1
Positionhas nooffsetand counts code units, not visual columns. - V1 elements use
Box/Row/Sectionwrappers; V2 expresses grouping viaLayoutInfoindex ranges. - V1
ParseErrorhas optionalline/column; V2 always provides a fullSourceLocationand a machine-readablecode. - V1
DeviceTypeis lowercase with 6 values; V2DeviceTypeis'Mobile' | 'Tablet' | 'Desktop'.
See api.md → Legacy V1 API for usage.
Version: 0.4.3 Last Updated: 2026-06-11 License: GPL-3.0