Language guide
This is a tour of the whole WCHNT language, from the file format down to the Target layer. The Tutorial shows these pieces working together; this page is the reference. This first draft is written by AI. But will shortly be rewritten by a human
The file format
A WCHNT program is markdown. Code lives in fenced blocks under reserved ## headings, in this
order:
| Section | Purpose |
|---|---|
## Schema |
declare classes, types, and relationships |
## Construction |
build the initial object graph |
## Methods |
pure-ish behaviour as expressions |
## Target Methods |
methods that take platform types (@Type/name) |
## Target |
name the host and its entry points |
## Import may appear first to merge sibling pages. Prose and unrelated fences are ignored.
Schema
The schema is a list of lines of the form ClassName = components. It's the single place where
the shape of the whole assemblage is declared.
Composition
Game = PlayArea Ball Paddle/paddle1 Paddle/paddle2
Rect = Int/x Int/y Int/width Int/height
PlayArea Ballare components (fields) ofGame.- Default field name = type name with a lower-cased first letter (
PlayArea→playArea). /nameoverrides the field name, and is required when a class has two fields of the same type.Int,Float,String,Boolare primitives from the host platform.
Relationship sigils
A sigil on a component changes the kind of relationship, not the field's type.
| Sigil | Name | Meaning |
|---|---|---|
| (none) | ordinary | owned by the parent; built alongside it |
: |
context-specific | child belongs to this parent; it gets a back-reference |
@ |
external | borrowed from outside the assemblage |
$ |
reactive | an observable/subscriber relationship (see update) |
Car = :Engine String/model
Engine = Int/cylinders
Game = PlayArea Ball $Time
Car = :Engine gives the Engine a field pointing back to its Car (called theCar), so engine
methods can read sibling data. Game = … $Time means Time is observable and Game subscribes
to it.
Collections
Team = String/name [Player]/players
School = {String:Discipline}/disciplines
[Player] is an array of players; {String:Discipline} is a map keyed by string.
Sum types and enums
Shape = Circle | Triangle
Circle = Int/radius
Triangle = Int/base Int/height
A line that is all | defines an interface (Shape) implemented by the classes on the right.
BuildType = "Dev" | "Local" | "Deploy"
A line of string literals defines a Haxe-style enum.
Construction
Construction is the initial data, written as a nested literal:
[:Game
[:PlayArea [0 0 800 600]]
[:Ball 200 150 6 5 16]]
- The first element of a bracket is the class name; the rest are arguments by position.
- Labels may be omitted where the compiler can infer them from the schema.
- Arrays:
[:Array/Player [:Player "Ada"] [:Player "Bob"]] - Maps:
[:Map/{String:Int} "Ada": 42] - Sum types must be tagged:
[:Circle 5]vs[:Triangle 4 8].
A construction block may bind intermediate values with = before a final expression:
players = [:Array/Player [:Player "Ada"] [:Player "Bob"]].
[:Team "Aces" players]
Methods
A method is ClassName::methodName = { body }. The body is an expression; its value is the
return value.
Rect::area = { width * height }
Ball::move = { Rect/bounds |
[:Ball (x + dx) (y + dy) dx dy rad]
}
- Arguments go before a
|in a block, becoming a lambda when the body has parameters. - Parameters may be bare names (
px) or typed (Rect/bounds) — a type is needed for field access. - External host types are written
@Type/nameand must live in## Target Methods.
Statements and lets
A block is a sequence of statements separated by . (a full stop). Only the last statement is
the result; earlier ones are let-style bindings and may not be reassigned.
Rect::doubleWidth = {
w = width * 2.
[:Rect x y w height]
}
Expressions
- Arithmetic:
+ - * / %(integer division;%is modulo) - Comparison:
== != < <= > >= - Logic:
and,or,not if/elseis an expression; both branches required (andelse ifchains work)- Field paths:
ball.x,playArea.rect.width(no spaces around dots) - Method calls:
this.move(),ball.step(playArea.rect),a.b.c() - Constructing:
[:Ball 1 2 3 4 5], arrays and maps as in Construction - Lambdas:
{ x | x * 2 }
Collections in methods
Arrays have length(), cons, head, tail, plus the combinators map, filter, fold:
players.map({ p | p.name })
players.filter({ p | p.score > 0 })
players.fold(0, { acc, p | acc + p.score })
Maps have put, get, remove (writes copy the map). Strings have length(), concat, and
substring(start, end). Ints have times: 3.times({ i | i * 2 }).
update and reactive dependencies
Ordinary methods are pure and return new objects. update is the one piece of mutation: it
rewrites this in place and — if the object is observable — notifies its subscribers.
Time::update = { [:Time (t + 1)] }
Game::update = {
[:Game playArea [:Ball (ball.x + ball.dx) (ball.y + ball.dy) ball.dx ball.dy ball.rad] time]
}
$Time on Game means: when Time.update() finishes, Game.update() runs automatically.
Identity objects ($ observables and > mailboxes) are mutated in place, never replaced.
Target
Target names the outer environment — the layer that changes when you move platform.
| Host | Entry points | Notes |
|---|---|---|
%terminal |
%main |
Haxe, compiled and run by go.sh |
%openfl |
%init + %step |
Haxe, windowed (Lime/OpenFL) |
%canvas |
%init + %step |
JavaScript, runs in the browser (Play) |
%name(...) helpers bind a Haxe function callable from Methods; %main (terminal) or
%init/%step (openfl/canvas) are the program entry. The browser harness exposes
gameFactory(), wchntGraphics, and input — see the Play page. Drawing is
done through the portable wchntGraphics object.
Further reading
- The working spec for Schema:
doc/schema.mdin the source repo - Methods,
update, and interfaces:doc/method.md - Target hosts:
doc/target.md - The live interpreter:
doc/live.md