Synth Language Reference
Synth is an AI-native programming language that transpiles to JavaScript. It is designed from the ground up to be written, read, and reasoned about by language models — with unambiguous grammar, intent-first declarations, constraint-enforced types, and explicit effect tracking.
let mut and store.
At a glance
Quick Start
Synth ships as a Node.js CLI. Install from npm or build from source, then run the bootstrap compiler against any .syn file.
Hello, Synth
Design Goals
| Goal | What it means in practice |
|---|---|
| Unambiguous grammar | Every construct has exactly one parse. No statement/expression duality. No semicolon insertion edge cases. |
| Intent over ceremony | @intent is a first-class source annotation — machine-readable purpose metadata for humans and downstream tools. |
| Constraints at the type level | Types carry where predicates. The compiler can emit __validate_* helpers for constrained parameters. |
| Explicit effects | @pure, @exhaustive, and @throws are checker-enforced. @effects is required on async fn. |
| Flat, scannable structure | No class hierarchies. No this. Data is records; behaviour is functions that take records. |
| Opt-in exhaustiveness | Add @exhaustive on a function to require full tagged-union coverage in its match body. |
| AI-first output | Generated JS embeds JSDoc with type signatures. Intent and constraint metadata live in Synth source today. |
Functions
All functions in Synth are declared with fn. The :: separator introduces the full type signature. Functions are first-class values and can be passed as arguments or returned from other functions.
Full-form function
The canonical form uses :: to separate the name from the typed signature, with the body in a block.
Short-form function v0.5 expanded
When the body is a single expression, use = expr to eliminate the block. An optional return-type annotation is supported with ->. A block body { ... } is also valid without the :: sigil.
Nested functions v0.5
A fn declaration inside a block body is emitted as a let-bound lambda — a local helper scoped to the enclosing function.
Lambda expressions
.field accessor shorthand
A bare .fieldName in any expression position is syntactic sugar for a single-argument lambda that accesses that field. Chained access is supported.
Types & Constraints
Synth's primitive types map directly to JavaScript. Type aliases add names; where clauses add runtime-enforced predicates.
Primitive types
| Synth type | JS equivalent | Notes |
|---|---|---|
| int | number (integer) | No distinct integer runtime; validated by constraint if needed |
| float | number | – |
| string | string | Supports interpolation and triple-quote literals |
| bool | boolean | – |
| void | undefined | Return type for side-effecting functions |
| list<T> | Array | – |
Type aliases
Constrained types with where
Any type alias can carry a where predicate. For constrained parameters, the compiler may emit __validate_* helpers at function entry.
__validate_TypeName check at function entry for
constrained-type parameters. Coverage depends on how the parameter is used — the
constraint definition remains the source of truth.
Records
Records are named structured data types — plain objects with known fields. They have no methods and carry no behaviour; that belongs in standalone functions.
Immutable updates
Records are updated functionally. The spread operator returns a new record with changed fields; the original is untouched.
Enums
An enum declares a closed set of named string values. It compiles to a frozen, immutable object — safe to use as a constant, in pattern matching, and across module boundaries.
Enum pattern matching
Use the Enum.Variant syntax inside a match expression.
Generated output
Tagged Unions
Tagged unions (also called sum types or algebraic data types) express a value that is exactly one of several named variants. They are the preferred replacement for inheritance hierarchies.
tag property used by pattern matching.
Pattern Matching
match is an expression, not a statement. Every arm produces a value. The checker warns when a match is not exhaustive unless a wildcard arm is present.
Basic patterns
when guards
Any arm can carry a boolean guard. Guards have full access to names bound by the pattern.
Tagged union patterns
Pattern types
| Pattern syntax | Matches |
|---|---|
| _ | Anything (wildcard, no binding) |
| n | Anything, binds subject to n |
| "hello" | Exact string literal |
| 42 | Exact number literal |
| true / false | Exact boolean |
| Circle | Tagged union variant (unit or payload), by tag name |
| Circle { r } | Tagged union variant, destructures payload field r |
| n when expr | Binding pattern with guard expression |
| likely "claim" | Soft semantic match — closest embedding above threshold (v1.1) |
likely arms v1.1
Soft classifiers inside match. Exact / hard arms are tried first; then all
likely claims compete via the host embedding API; then _ fallback.
Default threshold is 0.28. Claims are string phrases that describe the intent —
not regexes.
SynthRuntime.setEmbed((text) => /* Float32Array */)
or assign globalThis.__synth_embed. Without a host embedder, Synth uses a
hashed bag-of-words vector so demos work offline. See the
Intent Router demo.
Pipelines
The pipeline operator |> threads a value through a sequence of transformations. Each step receives the previous step's result as its first argument.
|> as name — named intermediate results
Any pipeline step can be named with as. When present, the entire pipeline emits as an IIFE block with const bindings, making intermediates available for reuse or debugging.
Destructuring
Destructuring let unpacks object and array values into named bindings in a single statement.
Strings
Template literals `…${expr}…` v1.0
Backtick template literals support full Synth expression interpolation — arithmetic, function calls, stdlib method syntax, anything. Each ${…} is parsed and compiled as a Synth expression, so ${items.count()} correctly compiles to ${$count(items)}. Templates may span multiple lines. This is the recommended interpolation syntax.
String interpolation "{ident}"
Any quoted string containing {ident} or {ident.prop} is automatically treated as an interpolated string. Limited to bare identifiers and property chains — for arbitrary expressions, use template literals.
Triple-quote strings """..."""
Multiline string literals that preserve newlines exactly. A leading newline immediately after """ is stripped for idiomatic alignment. Support the same {ident} interpolation as regular strings. Compile to JavaScript template literals.
{name} inside "…" strings as interpolation. To emit a literal
curly brace, use a backtick template literal — braces inside templates are only special
when written as ${…}.
Optional Chaining & Nullish Coalescing
Safe navigation through potentially-null values without nested conditionals.
| Operator | Meaning |
|---|---|
| a?.b | Access b on a; returns undefined if a is null/undefined |
| a?.[i] | Index a at i; safe if a is null/undefined |
| f?.() | Call f if it is not null/undefined |
| a ?? b | Return a unless it is null/undefined, then return b |
Control Flow
for i in lo..hi (exclusive range), for i in lo..=hi (inclusive range),
for x in array (forEach), and while condition { body } (v0.9.8+).
Use break / continue for loop control. The standard library also provides
map, filter, and fold for functional iteration.
Result Type & ? Propagation
Synth has a built-in Result<T> tagged union for explicit error handling.
Construct values with ok() / err(), consume them with match
or stdlib helpers, and chain fallible calls cleanly with the ? propagation operator.
Constructing and consuming Result
? — early-return on Err
| Helper | Signature | Description |
|---|---|---|
ok(v) | T → Result<T> | Wrap a success value |
err(msg) | string → Result<never> | Wrap an error message |
is_ok(r) | Result → bool | True when the result is Ok |
is_err(r) | Result → bool | True when the result is Err |
unwrap(r) | Result<T> → T | Extract value; throws if Err |
unwrap_or(r, fallback) | Result<T> → T → T | Extract value or return fallback |
expr? evaluates expr. If the result is Err, it
immediately returns that Err from the enclosing function. If it is Ok,
it unwraps the value and execution continues. The enclosing function must also return
Result — annotate it with @throws.
Purity & Totality
House style: write annotations above the function
(@pure, @intent, … then fn), not inside the body.
That works for both short = forms and braced bodies, and keeps metadata visible before the signature.
@pure declares that a function has no side effects — it should not access globals, mutate state, or call effectful methods. The checker verifies this and emits warnings on violations.
@total documents that a function is defined for all inputs. It is a declaration for readers and downstream tools — not currently enforced by the checker.
@pure function that references console, Math.random,
or any other global with side effects will produce a compiler warning. Violations are
warnings, not hard errors, as of v1.0.1.
Intent
The single most important annotation in Synth. @intent is a machine-readable natural language description of what a function is for — not what it does mechanically, but why it exists. It lives in Synth source today; JSDoc emission is planned for a future release.
@intent tags to
understand a function's purpose without executing it or reading its full body.
This is the foundation for synth --spec (v1.2) — extract intent without reading the full body.
Effects
Declares the set of side effects a function produces. As of v1.0.1, the checker warns when an async fn lacks an @effects declaration. It does not yet verify that the body matches the declared effect list.
Memoization
Wraps a @pure @total function in a Map-based memoization cache at compile time. Results are cached after the first call; subsequent calls with the same arguments return instantly.
@memo requires @pure and @total. The compiler will
warn if you annotate a function with @memo without both.
Exhaustiveness
Opt-in exhaustiveness checking for functions whose body is a match expression. Place @exhaustive on the line(s) above the function — the checker verifies that all variants of a tagged union are covered.
Tests
Top-level inline test declarations. Tests are registered in __synth_tests at runtime and can be run via __runSynthTests() in the browser, or the CLI --test flag.
Fallible Functions
Mark a function as fallible. @throws fn f() -> Result<T> signals
to the checker and to callers that this function may return Err. The static
checker warns if a @throws result is silently ignored without a match
or ? operator.
fn).
Combine freely: @pure @throws then fn foo() on the next line.
Standard Library
The callable surface of Synth — every helper in synth.stdlib.js.
Generated JS uses a $ prefix (e.g. map → $map).
Most list and string functions also support method syntax:
xs.map(f) compiles to $map(xs, f).
| Function | Signature | Description |
|---|---|---|
| Lists | ||
map(xs, f) | list → fn → list | Transform each element |
filter(xs, f) | list → fn → list | Keep elements where f is true |
fold(xs, init, f) | list → any → fn → any | Fold left with initial accumulator |
find(xs, f) | list → fn → any | First matching element, or undefined |
find_index(xs, f) | list → fn → int | Index of first match, or -1 |
any(xs, f) / all(xs, f) | list → fn → bool | Any / every element matches |
count(xs, f?) | list → fn? → int | Length, or count of matches |
sum(xs) / sum_by(xs, f) | list → number | Sum values / sum of keys |
min(xs) / max(xs) | list<number> → number | Minimum / maximum value |
min_by(xs, f) / max_by(xs, f) | list → fn → any | Element with smallest / largest key |
sort_by(xs, f) / sort_by_desc(xs, f) | list → fn → list | Copy sorted by key (original untouched) |
first(xs) / last(xs) | list → any | First / last element |
take(xs, n) / drop(xs, n) | list → int → list | First n / drop first n |
flat(xs) / flat_map(xs, f) | list → list | Flatten one level / map then flatten |
uniq(xs) | list → list | Remove duplicates (Set semantics) |
chunk(xs, n) | list → int → list<list> | Split into groups of n |
set_at(xs, i, v) | list → int → any → list | Copy with index i replaced |
reverse(xs) | list → list | Copy reversed |
zip(xs, ys) | list → list → list<[a,b]> | Pair elements by index |
range(lo, hi) | int → int → list<int> | [lo, lo+1, …, hi−1] |
groupBy(xs, f) | list → fn → Map | Group elements by key |
| Strings | ||
trim(s) | string → string | Strip surrounding whitespace |
split(s, sep) | string → string → list | Split on separator |
starts_with(s, p) / ends_with(s, p) | string → string → bool | Prefix / suffix test |
contains(s, sub) | string → string → bool | Substring test |
to_upper(s) / to_lower(s) | string → string | Case conversion |
replace_all(s, from, to) | string → string → string → string | Replace every occurrence |
pad_start(s, len, ch?) / pad_end(s, len, ch?) | string → int → string? | Pad to length (default pad " ") |
| Math & Conversion | ||
clamp(v, lo, hi) | number → number → number → number | Constrain to [lo, hi] |
abs / round / floor / ceil / sqrt | number → number | Standard math |
pow(base, exp) | number → number → number | Exponentiation |
random() | () → float | Uniform random in [0, 1) |
random_int(lo, hi) | int → int → int | Random integer in [lo, hi] inclusive |
parse_int(s, radix?) | string → int? | Parse integer; null if invalid |
parse_float(s) | string → float? | Parse float; null if invalid |
| Objects & Composition | ||
pick(obj, keys) | object → list → object | New object with only the given keys |
omit(obj, keys) | object → list → object | New object without the given keys |
pipe(...fns) | fn* → fn | Left-to-right function composition |
| Result helpers · v0.6 | ||
ok(v) | T → Result<T> | Wrap a success value |
err(msg) | string → Result | Wrap an error message |
is_ok(r) / is_err(r) | Result → bool | Test variant |
unwrap(r) | Result<T> → T | Extract value; throws on Err |
unwrap_or(r, fallback) | Result<T> → T → T | Extract value or return fallback |
| Async & IO | ||
delay(ms) | int → Promise | Promise sleep for await |
println(...args) | any* → void | console.log with trailing blank line |
__synth_tests / __runSynthTests() | — | Registered @test declarations and browser runner |
| Embed / likely · v1.1 | ||
embed(text) | string → vector | Embed text (host __synth_embed or offline bag-of-words) |
cosine(a, b) | vector → vector → float | Cosine similarity |
likely_best(subject, claims, threshold?) | string → list<string> → float? → int | Best claim index above threshold, or -1 |
score_claims(subject, claims, threshold?) | string → list<string> → float? → object | { scores, best, bestScore } for every claim |
SynthRuntime.setEmbed(fn) | fn → void | Install a host embedder |
SynthRuntime.embed / likelyBest / scoreClaims | — | Host mirrors of the stdlib helpers |
Validation presets
Built-in regex presets used with constrained string types
(e.g. type Email = string where value matches email).
Defined as __synth_presets in the stdlib.
CLI
| Command | Description |
|---|---|
synth input.syn output.js | Transpile a single Synth source file to JavaScript |
synth --test input.syn | Run all @test declarations and print pass/fail summary. Exits with code 1 on failure. |
synth --bundle entry.syn out.js | v0.5 Bundle a multi-file project. Resolves all imports recursively, topo-sorts modules, emits one JS file. |
synth --check input.syn | v1.0.0 Static analysis only — no JS emitted. Reports checker warnings and parse errors. |
synth --fmt input.syn | v1.0.0 Format source in-place. Rewrites the file with canonical spacing and indentation. |
synth --spec input.syn [-o out.json] | v1.2.0 Extract AI-queryable JSON (@intent, constraints, likely, refine). Follows imports like --bundle. |
Spec Extraction
synth --spec walks the AST and emits a JSON document agents can query —
no need to re-parse bodies for intent metadata. Schema version 0.1 (freeze to 1.0 at v2.0).
Top-level shape:
| Field | Description |
|---|---|
synthSpecVersion | Schema id — currently "0.1" |
entry | Relative path of the entry .syn file |
modules[] | One object per resolved module (import graph, topo order) |
Per module:
| Field | Description |
|---|---|
file | Module path relative to repo root |
functions[] | name, line, intent, annotations, params, returnType, likely, exactMatchArms |
types[] | Type aliases with optional constraint (where clauses) |
refines[] | refine x: "claim" semantic narrowing declarations |
Numeric Literals
Synth v0.9.5 extends numeric literals with underscore separators, hexadecimal 0x…, and binary 0b… prefixes. Separators are stripped at compile time; the notation is preserved in generated JS output.
| Form | Example | Description |
|---|---|---|
decimal | 1_000_000 | Underscore separators for readability; stripped at compile time |
0x… | 0xFF_00_00 | Hexadecimal literal; notation preserved in output |
0b… | 0b1010_1010 | Binary literal; notation preserved in output |
float | 3.141_592 | Underscores allowed before or after the decimal point |
New CLI Commands
Static analysis and formatting ship with the v1.0.1 bootstrap toolchain.
synth --check
Runs the static checker without emitting any output file. Useful in CI or as a pre-commit hook.
$ synth --check src/main.syn ✓ main.syn — no issues $ synth --check src/main.syn WARNING [line 14]: @pure function 'render' accesses effectful global 'console'
synth --fmt
Canonical formatter. Normalises 2-space indentation, spacing around operators (=, ->, =>, ::, |>, binary arithmetic), brace padding, and trailing whitespace. Does not modify </> spacing to preserve generic type parameters. Rewrites the source file in-place.
$ synth --fmt src/main.syn ✓ main.syn — formatted ✓ main.syn — already canonical # if no changes needed
Browser Compiler API
compiler.bootstrap.js is a self-contained IIFE bundle that runs the full Synth bootstrap compiler client-side. Load it in any web page — no server, no build step, no dependencies.
<script src="compiler.bootstrap.js"></script>
<script>
const { js, errors, warnings } = SynthCompiler.compile(source);
</script>
SynthCompiler.compile(source)
Transpiles an Synth source string to JavaScript. Always returns an object — never throws.
| Field | Type | Description |
|---|---|---|
js | string | null | Transpiled JavaScript, or null if there were parse errors |
errors | Error[] | Parse or compiler errors (see below). Empty array on success. |
warnings | Warning[] | Checker warnings (@pure, @exhaustive, @effects). May be non-empty even when js is set. |
Error object shape
| Field | Type | Description |
|---|---|---|
message | string | Human-readable error message including line and column |
line | number | 1-based source line number |
col | number | 1-based column number |
kind | "parse" | "error" | "codegen" | Error category |
SynthCompiler.check(source)
Runs only the static checker. Returns { warnings, errors }. Useful for lightweight lint-on-type without full codegen.
SynthCompiler.stdlib / SynthCompiler.backend
SynthCompiler.stdlib is the stdlib JS source string bundled with the compiler. SynthCompiler.backend is "bootstrap" for this bundle.
SynthCompiler.version
String — the compiler version, e.g. "1.0.0".
Modules
Split a project across multiple .syn files. Use import to bring names into scope and export to mark declarations as public.
| Syntax | Description |
|---|---|
import { a, b } from "./path" | Bring named symbols into scope. Path is relative to the importing file. .syn extension is optional. |
export fn name ... | Mark a function as part of the module's public API. |
export type T = ... | Export a type alias or tagged union. |
export record R { ... } | Export a record type declaration. |
The bundler resolves imports depth-first, topo-sorts modules so dependencies come before dependents, and emits a single JS file with the stdlib included once at the top. All bundled code shares a single flat scope — no runtime module system overhead.
refine
refine x: "prose claim" is an AI-native semantic narrowing statement.
It annotates a binding with a human-readable constraint that is too nuanced for a
type signature — emitted as structured JSDoc and queryable by AI tooling.
It compiles to a no-op comment at runtime; the value is unchanged.
| Syntax | Notes |
|---|---|
refine x: "claim" | Annotates the binding x with a semantic constraint. x must be in scope. Claim is a free-form English string. |
Multiple refine on one binding | Each emits a separate JSDoc tag — all constraints are recorded. |
| Works with any type | Works on int, string, float, records, tagged unions, etc. |
refine fills the gap between machine-checkable types and human intent.
A type system can guarantee price: int — but it cannot guarantee
"price must be a positive USD amount before tax." refine captures that
intent in a form that language models can read, validate, and reason about.
Generic Type Parameters
Add type parameters to record, fn, and type declarations
using angle-bracket syntax. Type parameters are erased at compile time — no runtime overhead.
Generic records
Generic functions
Generic type aliases
Pair<int, string> compiles to an ordinary JS object.
Interfaces
Declare structural type contracts with interface. Any record that carries the
required fields and methods satisfies the interface — no explicit implements
keyword needed. Interfaces are emitted as JSDoc @interface comments.
| Syntax | Notes |
|---|---|
interface Name { field: Type } | Declares a structural contract. Any record with matching fields satisfies it. |
interface Name<T> { ... } | Generic interface — type param available in field and method types. |
method: fn() -> T | Method signature using a function type expression. See fn(T)->U Types. |
implements declaration —
a record satisfies an interface if its shape matches. This is intentional: it keeps AI-generated
code composable without requiring explicit trait registration.
fn(T) -> U Function Types
Function types are first-class in Synth. Use fn(A, B) -> C syntax anywhere
a type is expected — in record fields, interface definitions, and generic function signatures.
| Syntax | Meaning |
|---|---|
fn() -> T | Zero-argument function returning T |
fn(A) -> B | Single-argument function from A to B |
fn(A, B) -> C | Two-argument function |
fn(T) -> void | Side-effecting callback |
let infer
let infer x = expr is an AI-native annotation that signals the type of a binding
should be resolved from context by the model — not written explicitly by the programmer.
It compiles to an ordinary let with no runtime difference.
let infer is a signal to language model tooling: "I know this value has a type —
resolve it from the surrounding context rather than asking me to spell it out." In long pipelines
or generic chains, the type is often obvious from usage. infer makes that intent explicit
and queryable via synth --spec (v1.2).
store
store Name { field: Type = default } declares a reactive mutable state container —
an explicit effects boundary that separates pure logic from observable state. Getters expose each
field; .set(patch) updates state and notifies all subscribers atomically.
| Syntax | Meaning |
|---|---|
store Name { f: T = v } | Declare a reactive store with field f defaulting to v |
Name.field | Read current field value (getter) |
Name.set({ field: val }) | Patch state and notify all subscribers |
Name.subscribe(fn) | Register a callback, fired with new state on every change (and once immediately) |
store compiles to a self-contained IIFE that encapsulates a private _state
object, a subscriber list, ES5-style get accessors for each field, and a set(patch)
method that applies a shallow merge and calls all subscribers. Zero runtime dependency — pure JS.
async fn & await
Prefix any function declaration with async to make it asynchronous. Inside an
async fn, use await expr to resolve a Promise without blocking.
The stdlib provides delay(ms) for timer-based pauses.
| Syntax | Meaning |
|---|---|
async fn name(…) { … } | Async function — returns Promise<T> |
await expr | Await a Promise inside an async function |
await delay(ms) | Non-blocking pause for ms milliseconds (stdlib) |
@effects ["timer"] | Declare async effects — enforced by the checker |
async fn is declared without @effects.
Use @effects ["timer"] for delay()-based loops,
@effects ["network"] for fetch/XMLHttpRequest,
or @effects ["io"] for generic I/O. This keeps side-effect surfaces visible at the
declaration site — readable by both humans and AI tools.
on StoreName.change { … }
on StoreName.change { body } is syntactic sugar for registering a reactive
subscription. The body runs once immediately when the subscription is registered, then
again after every StoreName.set(…) call. Multiple subscriptions can target
the same store.
on Kingdom.change { render() } compiles to
Kingdom.subscribe(() => { render() }) — a plain JS function call.
There is no virtual DOM, diffing, or framework magic. The subscriber list is maintained
inside the store's IIFE closure.
Keywords & Annotations
Reserved words from the lexer, plus contextual forms and annotations. This is the complete roster for the current language version.
Lexer keywords
These are reserved by keyword_type in the lexer. Do not use them as ordinary
identifiers (variable names, record fields, type names). Parameter names are an exception —
fn f(type: string) is allowed.
Contextual keywords
Parsed as identifiers, but special-cased at declaration sites. Safe as ordinary names outside those forms — still treat them as reserved in practice when generating code.
| Word | Form | Notes |
|---|---|---|
store |
store Name { ... } |
Reactive mutable state. See store. |
on |
on Store.change { ... } |
Subscribe to store updates. See on…change. |
Annotations
Written above fn (house style). Not keywords — they are @-prefixed
attributes checked or recorded by the compiler.
async fn)
@memoMemoize pure results
@exhaustiveRequire full match coverage
@testRunnable test declaration
@throwsFallible Result-returning function
Built-in type names
Colored as types by highlighters; they are ordinary identifiers in the lexer. Prefer not to shadow them.
compiler/token.syn (keyword_type).
Site highlighting uses demo/synth-highlight.js; the VS Code grammar is
vscode-extension/syntaxes/synth.tmLanguage.json. Keep those three aligned
when adding a keyword or annotation.
Version History
synth --spec— extract@intent, constraints,likely, andrefineas JSON- Multi-file — follows imports like
--bundle; optional-o out.json - Schema 0.1 —
entry+modules[]document shape
likelymatch arms — soft semantic classifiers insidematch- Host embedding API —
SynthRuntime.setEmbed/__synth_embed, with offline bag-of-words default - Intent Router demo — exact arms first, then
likelyrouting
- Object spread codegen —
{ ...obj }emits real JS spread instead of a broken_spread_shape - Zero-arg method calls — no trailing comma in generated calls (fixes empty Controls widgets /
$flat) - Constraint validators —
type T = … where …now emits__validate_Thelpers and param guards - Demo polish — shared site footer; stdlib script tags for Combat / Music / RPG
- npm homepage fix — package listing now points to
synth-pl.github.io/synth/
- Template literals —
`Hello ${name}, score ${pts * 2}`; full Synth expression interpolation including stdlib method syntax and/orprecedence fix — parenthesized boolean sub-expressions are preserved correctly in generated JS- Object literals in lambdas —
x => { id: x, val: x * 2 }auto-detected; nodowrapper needed - Reserved words as parameter names —
fn f(type: string)and other keywords now valid in parameter position - New stdlib —
find,find_index,parse_int,parse_float - npm publish —
npm install -g synth-lang; VS Code extension for.synhighlighting
- Spread operator —
[...xs, x]and{ ...obj, key: val }in arrays and objects - Object shorthand —
{ x, y }for{ x: x, y: y } - Rest parameters —
fn sum_all(...nums) - Stdlib method syntax —
value.fn(args)compiles to$fn(value, args); enables chaining donotation — block expressions with implicit last-value result; async IIFE when body containsawait
whileloops —while condition { body }withbreak/continuesupport
- List helpers —
take,drop,uniq,chunk,set_at,reverse,sum_by,min_by,max_by,sort_by,sort_by_desc,flat_map,zip - String helpers —
trim,split,starts_with,ends_with,contains,to_upper,to_lower,replace_all,pad_start,pad_end - Math helpers —
clamp,abs,round,floor,ceil,pow,sqrt,random,random_int
- Compound assignment —
+=,-=,*=,/=,%=,??= --compactflag — strip blank lines and JSDoc from output; reduces token usage for AI tools$stdlib prefix — generated calls use$mapinstead ofsynth_map; smaller output
enumtype —enum Color = Red | Green | Blue; compiles toObject.freeze({ Red: 'Red', … }); supportsEnum.Variantpattern matching inmatch- Numeric separators —
1_000_000,0xFF_FF,0b1010_1010; underscore stripped at compile time - Hex & binary literals —
0xFFand0b1010fully supported; notation preserved in generated JS - Multi-line lambdas —
item => { let x = f(item)\n x * 2 }; block body with implicit last-expression return - Multi-error reporting — parser collects all errors with panic-mode recovery;
parse()returns{ ast, errors } SynthCompiler.compile()updated — browser bundle uses multi-error parser;errorsmay contain multiple items; version string is"0.9.5"
synth --check <file>— static analysis only; reports checker warnings and parse errors without emitting JSsynth --fmt <file>— canonical formatter; rewrites source in-place with normalised spacing and indentationcompiler.bootstrap.js— browser-compatible IIFE bundle; full bootstrap compiler running client-side viaSynthCompiler.compile(src)- Structured error objects —
compile()returns{ js, errors, warnings }with{ message, line, col, kind }per error - Demo: Synth Playground — live in-browser IDE with split-pane editor, real-time transpilation, live result execution, and 10 preloaded examples
async fn— asynchronous function declarations that transpile to async arrow functionsawait expr— resolve a Promise inside an async function; valid at statement or expression levelstore Name { field: Type = default }— reactive state with.set(patch), getters, and.subscribe(fn); compiles to a self-contained IIFEon StoreName.change { … }— reactive subscription sugar; desugars toStoreName.subscribe(() => { … })@effectsenforcement — checker warns if an async fn lacks an@effectsdeclarationdelay(ms)stdlib — Promise-based async sleep helper for use withawait- Bare
return—returnwithout a value now valid in all function bodies - Demo: Chronicle — reactive fantasy kingdom log; reactive
store,async fn tick(),await delay(), andon Kingdom.change { render() }
- Generic type parameters —
record Pair<A, B>,fn map<T, U>,type Maybe<T>; erased at runtime (JS-transparent) interfacedeclarations — structural type contracts; emitted as JSDoc@interfacecommentsfn(T) -> Utype expressions — first-class function types usable in record fields, interfaces, and signatureslet infer x = expr— signals to AI tooling that the type should be resolved from context, not explicitly annotated- Demo: The Bazaar — RPG item shop with 20 typed items, generic filter/sort/map,
interface Tradeable, and an immutableAppStaterecord
- Built-in
Resulttype —ok(value)/err(message)constructors;is_ok,is_err,unwrap,unwrap_orin stdlib ?propagation operator —let n = parse(s)?returnsErrimmediately; line-aware disambiguation from ternary@throwsannotation — marks fallible functions; checker warns ifok()/err()are never calledrefine x: "claim"— semantic value narrowing; structured comment today, model-checked in v1.0- Pre-function annotations —
@throws fn foo()syntax now valid - Demo: Dungeon Configurator —
config.synwith chained@throwsparsers; precise per-field error messages
for i in lo..hi/lo..=hi— exclusive and inclusive range loopsfor x in array— forEach loop; compiles tofor...ofbreak/continue— full loop controllet mut x = val— explicit mutable binding
- Compiler: top-level
let& bare expressions —TopLevelLetandTopLevelExprAST nodes - Bundler correctness — output path argument fix; stdlib emitted exactly once per bundle
- Dungeon demo overhaul — room-based generator, avalanche hash, door bottlenecks, torch sconces, immutable
AppState
import { x, y } from "./path"— multi-file projects; relative path resolution per-moduleexport fn / export type / export record— declare public API surface; bundler tracks exported symbolssynth --bundle <entry> [out.js]— recursive import resolution, topo-sort, stdlib emitted once- Auto-bundle in
--test— files with imports are auto-bundled before running@testdeclarations - Optional
ifparens — bothif (cond) { }andif cond { }are valid - Return-type short-form —
fn f(x) -> T = exprandfn f(x) -> T { block }both valid - Nested
fnin blocks — local helper functions inside blocks, emitted asletlambdas - Demo: Dungeon Map Toolkit — 4-file module demo introducing
import/exportacrosstiles,generator,renderer, andmain
whenguards inmatch— boolean guard on any arm; binding patterns scope correctly via IIFE?.optional chaining — safe member, index, and call access on nullable values??nullish coalescing — short-circuit fallback; precedence above ternary, below logical OR- Destructuring
let— object ({ a, b }), rename ({ x: ax }), and array ([a, b]) forms - Triple-quote strings
"""..."""— multiline literals with interpolation, compile to template literals - Tagged union types —
type T = | Variant { fields } | Unit; factory fns + frozen constants @testdeclarations — inline tests with--testCLI flag;__runSynthTests()for browser- Pipeline
|> as name— bind intermediate pipeline results to names; emits as IIFE block
- String interpolation —
"Hello {name}"compiles to a JS template literal automatically .fieldaccessor shorthand —.nameas implicit lambda in pipelines and higher-order calls- Param validation injection —
where-constrained type params auto-inject guard clauses at function entry @memoannotation — compile-timeMap-based memoization for@pure @totalfunctions
- Short-form functions —
fn name(args) = exprsingle-expression shorthand - Pipeline operator
|>— left-to-right functional composition - Expanded stdlib —
map,filter,reduce,find,sum,range,clamp - DOM helpers —
elandmountfor building UIs in pure Synth
- Core language —
fn,let,if/else,match, ternary - Type system — primitive types, type aliases,
whereconstraints, record types - Annotations —
@pure,@total,@intent,@effects,@exhaustive - Static checker —
@pureviolation detection, exhaustiveness analysis - Transpiler — Lexer → Parser → AST → Codegen pipeline; JSDoc metadata embedding
- VS Code extension — TextMate grammar for syntax highlighting
Roadmap to v2.0
v1.2 shipped synth --spec. Next up is explain blocks, then the LSP and debug loop
through v2.0. Past releases live in
Version History.
explain { … }blocks in the ASTsynth --specincludes explain + intent in one document- Optional
@intent→ JSDoc emission in codegen
- LSP diagnostics (reuse the checker)
- Document symbols + hover (types, intent, explain from spec)
- Same-file go-to-definition
- VS Code / Cursor extension hosts the server
- Cross-file go-to-def / find-references via the module graph
- Completions for keywords, locals, and imports
- Signature help
- Codegen emits
.mapalongside JS - Playground + demos map stack traces to
.synlines - Browser DevTools “original source” story
- Richer errors with source context
synth check/ watch recipes for CI- Extension publish (Open VSX) + formatter/LSP parity fixes
- Frozen agent protocol —
synth --specschema 1.0 - Full tooling loop: edit, hover intent, debug mapped sources
- Package metadata / registry hooks as needed
- Breaking changes only if the agent surface requires them