Targets · JavaScript · Keywords
Keywords
This page shows how each of Program's keywords becomes JavaScript. The output stays close to code you might write by hand: declarations become objects, classes and functions, and the reactive keywords become small calls into the runtime.
Contents
How a Program becomes JavaScript, in parts. Start with the introduction, then follow each part into the rendered output and the runtime it links against.
- Introduction — what the JavaScript target is, and how to read the rest of this section.
- Capabilities — which high-level features are possible, built in or through a library.
- Coding standards — the Frenzi coding standards the generated code follows, global and JavaScript-specific.
- Example — a full program using every keyword, and the JavaScript it renders to.
- Naming considerations — how Program's dotted names are carried into JavaScript.
- Entry point — the generated
main()that wires up the drivers and starts the reactive cycle. - Drivers — the native drivers that provide capabilities like the user interface and animation, draining each cycle's emissions.
- Runtime — the Reactive and Context machinery the rendered code links against.
- Native modules — the hand-written, per-platform JavaScript behind
NATIVEfunctions. - Keywords — how each of Program's keywords renders to JavaScript.
- Project setup — the files and commands to build and run the rendered program.
- Reserved words — the words JavaScript keeps for itself, and how a Program name that collides with one is escaped.
Program keywords in JavaScript
Each of Program's keywords becomes a small, familiar piece of JavaScript, one subsection below. The declarations turn into objects, classes and functions; the reactive keywords turn into short calls into the runtime. A keyword's inline modifier is shown as a subsection nested inside it.
Names pass through unchanged: a dotted Program name is emitted as written, because JavaScript objects nest to any depth. The only identifiers ever rewritten are ordinary variables and parameters whose name happens to be a JavaScript reserved word — see the Reserved words appendix.
MODULE
Each module becomes a nested object, so a dotted name is emitted exactly as written — JavaScript objects nest to any depth, so nothing is flattened.
var Code = {};
Code.Language = {};
Code.Language.Json = {};
// the Code.Language.Json.Value union is attached here
FUNCTION
A function renders to a JavaScript function: its
INPUT and HOLE parameters become arguments in order, the
body's bindings become statements, and the trailing = becomes a return.
function Wrap(body, decorate) {
var decorated = decorate(body);
return decorated;
}
INPUT
An input is a plain function parameter, in declaration order. A forward input is only read.
function FormSubmit(endpointName, formData) {
// reads endpointName …
}
BINDING
The BINDING modifier makes the parameter two-way: the function both reads it and
writes back through it, so a write reaches the caller's STATE. In
JavaScript it stays the same parameter — the write-back happens through a
REVERSE rather than a different signature.
function FormSubmit(endpointName, formData) {
// formData is read AND written back
}
HOLE
A hole is a callback parameter, placed after the ordinary inputs. It is called where the caller's block should run, receiving that hole's own inputs as arguments.
function FromJsonArray(jsonArray, fromJson) {
// fromJson(jsonElement) runs the caller's block per element
}
NATIVE
A native that returns a value is wrapped so it accepts reactive inputs: the guards run first, each
input is unwrapped with Reactive.Value inside a Reactive.Calculate, and the
verbatim js NATIVE string is spliced in as the body — so a native
called with a loading input returns a loading value, for free.
Maths.Integer.ToString = function (value) {
ReactiveSecurity.Gettable(value);
var _value = value;
return Reactive.Calculate(() => {
var value = Reactive.Value(_value);
return String(value); // the verbatim js NATIVE body
});
};
A native with no return value skips the Calculate wrapper and splices its body directly
after the guards.
A record TYPE
A record renders to three generated pieces: a plain immutable class holding the fields, a constructor
that lifts reactive or locked inputs, and one getter per field. The class and getters are generated
machinery, so they live in a reserved _ slot no program name can reach (see
naming considerations).
// the plain immutable shape (reserved) — one constructor arg and one property per FIELD
Point._ = class { constructor(x, y) { this.x = x; this.y = y; } };
// construction — the type name lifts plain, reactive or locked inputs through Calculate
Point = function (x, y) {
ReactiveSecurity.Gettable(x);
ReactiveSecurity.Gettable(y);
return Reactive.Calculate(() => {
var xValue = Reactive.Value(x);
var yValue = Reactive.Value(y);
return new Point._(xValue, yValue);
});
};
// one generated getter per field (reserved)
Point._GetX = function (data) {
return Reactive.GetObjectField(data, (d) => d.x, (d, v) => new Point._(v, d.y));
};
Point._GetY = function (data) {
return Reactive.GetObjectField(data, (d) => d.y, (d, v) => new Point._(d.x, v));
};
Construction runs inside Reactive.Calculate, so a record built from a still-loading field
is itself loading. Each getter runs through Reactive.GetObjectField, so reading a field of
a locked record yields a locked field — and, when the record is a two-way binding, writing the
field rebuilds the record and sets it back. Phase zero read fields only through these
generated getters (Point._GetX(p)); Program also lets you write p.x
directly, which lowers to the very same accessor (see the
example).
A union TYPE
A union renders like a record: a tagged shape in the reserved _ slot, one constructor per
OPTION, and one generated accessor per option. The constructors and accessors lift and
read reactively, exactly as a record's do.
// the tagged shape (reserved)
Shape._ = class { constructor(tag, value) { this.tag = tag; this.value = value; } };
// one constructor per option — lifted through Calculate, like a record
Shape.circle = function (value) {
ReactiveSecurity.Gettable(value);
return Reactive.Calculate(() => new Shape._('circle', Reactive.Value(value)));
};
Shape.rect = function (value) {
ReactiveSecurity.Gettable(value);
return Reactive.Calculate(() => new Shape._('rect', Reactive.Value(value)));
};
// one generated accessor per option — the payload, or null for another shape
Shape._OptionalCircle = function (union) {
return Reactive.Calculate(() => {
var s = Reactive.Value(union);
return s.tag === 'circle' ? s.value : null;
});
};
Reading an option is locked-aware the same way. Phase zero read options only through these generated
accessors (Shape._OptionalCircle(s)); Program also lets you write s.circle
directly, projecting to the payload or, at depth one, a native null.
LET
A let is a single derived binding — one var per step of a computation, each
feeding the next.
var squared = Maths.Real.Multiply(radius, radius);
var area = Maths.Real.Multiply(3.14159, squared);
STATE
A state is a reactive cell, created with Reactive.State.
var count = Reactive.State(0);
LOCKED
The LOCKED modifier passes a second argument of true to
Reactive.State, so the cell starts locked — pending or carrying an error
— rather than holding a settled value. A driver clears the lock when the real value arrives.
var responseState = Reactive.State(null, true); // starts loading
REVERSE
A reverse is a write-back block. Its body computes from the incoming value, and the closing
SET becomes Reactive.Set, pushing the result into an outer
STATE.
function incrementFlag() {
var currentCount = Maths.Integer.Increment(refreshCountState);
Reactive.Set(refreshCountState, currentCount); // SET
}
WITH
A with block swaps one module's implementation for another over its body, through
Module.With, and restores the original binding when the block ends.
function Panel(content) {
return Module.With(Logic, Ui.Logic, function () {
return content(); // the hole, run with Logic = Ui.Logic
});
}