docs/language: a page for every keyword and attribute the vocabulary has

Keywords: module uses friend export internal numbers unsafe mut port bind action
reducer registry of as from def open alias component prop view (structure),
shows lasts then loads (scenes), system (ecs), dispatch (control), true false
null (operators). Attributes: @Ref @OneOf @Range @Unit @Asset @Color, @Node /
@Clip / @Material, @Tint @Derived, @Text / @Multiline, @Key, @AppendOnly /
@ByKey, @PerMap / @Chunked, @frame @max, @owns / @creates / @releases,
@deterministic @alloc_ok. Each is in tools/docgen/inventory.json; every fence
that is not marked skip parses (ludicc --fmt). annot-clearcolor's token loses its
quotes, which no reader strips.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-29 23:59:47 +03:00
parent eb2d6106af
commit e2528c1b10
49 changed files with 746 additions and 7 deletions

View file

@ -0,0 +1,16 @@
---
id: kw-action
name: action
category: structure
kind: keyword
tokens: action
sig: action Name { field: Type, ... }
tip: What the player asked for, dispatched and applied by each state's reducer.
order: 61
---
An <code>action</code> is a record that says what was asked for in the game's words (<code>Move</code>, <code>OpenPack</code>). Input code <code>dispatch</code>es it, and every <code>reducer</code> declared on it changes its own state. The queue drains at the end of every frame phase, each action's reducers in the order of their states' names.
```ludic
action Move { dx: int = 0, dy: int = 0 }
```

View file

@ -0,0 +1,19 @@
---
id: kw-alias
name: alias
category: structure
kind: keyword
tokens: alias
sig: namespace Name { alias method = function_name }
tip: A namespace method that is another function.
order: 69
---
Inside a <code>namespace</code> block, <code>alias m = f</code> makes <code>Name.m(...)</code> a call of <code>f</code>, checked against its parameters. It is how an engine namespace declared in Ludic forwards to the functions that implement it.
```ludic
# doc-check: skip — the aliased function lives elsewhere
namespace Trail {
alias length = trail_length
}
```

View file

@ -0,0 +1,12 @@
---
id: kw-as
name: as
category: structure
kind: keyword
tokens: as
sig: registry Name of Record as PREFIX
tip: The prefix of a registry's generated constants.
order: 65
---
<code>as P</code> after a registry's record makes the compiler write a constant for each entry, <code>P_KEY</code> in upper case, holding the entry's index - so code reads <code>IT_ROPE</code> rather than looking the key up.

View file

@ -0,0 +1,17 @@
---
id: kw-bind
name: bind
category: structure
kind: keyword
tokens: bind
sig: bind Port { member: function_name, ... }
tip: The program's answers to a port: one function per member.
order: 60
---
<code>bind</code> answers a <code>port</code>: each member names a function of the member's type. It is written once, where the program is put together, and it is the only code that knows both the asking module and the one that answers.
```ludic
# doc-check: skip — a module spans files
bind ClockWorld { rest_scale: party_rest_scale }
```

View file

@ -0,0 +1,21 @@
---
id: kw-component
name: component
category: structure
kind: keyword
tokens: component
sig: component Name[(states)] { prop p: T = v, state s: T = v, function ..., on e(...) { } }
tip: A UI component: its props, state, functions and events, beside Name.xml and Name.lss.
order: 70
---
A <code>component</code> is a piece of interface: the code file declares its <code>prop</code>s (handed in by its parent), its own <code>state</code>, the functions its template calls and the events it handles with <code>on</code>; <code>Name.xml</code> beside it is its template and <code>Name.lss</code> its styles. The states named in its header are supplied by the runtime and never seen by the template.
```ludic
# doc-check: skip — a component needs its template beside it
component Counter {
prop step: int = 1
state count: int = 0
on add() { count = count + step }
}
```

View file

@ -0,0 +1,17 @@
---
id: kw-def
name: def
category: structure
kind: keyword
tokens: def
sig: def Registry key { field: value, ... } / def Registry from "file.lres"
tip: Entries of a registry, written in code or read from a resource file.
order: 67
---
<code>def</code> adds entries to a registry: one written inline, or every entry of a resource file. An <code>open</code> registry takes <code>def</code>s from other modules, which is how a package's table gets a game's rows.
```ludic
# doc-check: skip — the registry is declared elsewhere
def Tools lantern { weight: 1.5 }
```

View file

@ -0,0 +1,17 @@
---
id: kw-export
name: export
category: structure
kind: keyword
tokens: export
sig: export function / property / state / registry ...
tip: Makes a declaration visible outside its module.
order: 54
---
<code>export</code> in front of a declaration makes it reachable from other modules; without it a module's names are its own. It works on every declaration - functions, records, states, events, actions, ports, registries, views and components - and <code>@export</code> is the same thing written as an attribute. Export deliberately: a name nobody else asks for stays private.
```ludic
# doc-check: skip — a module spans files
export function balance(b: Bank) -> int { return b.total }
```

View file

@ -0,0 +1,17 @@
---
id: kw-friend
name: friend
category: structure
kind: keyword
tokens: friend
sig: friend module name [of a, b]
tip: A module that sees other modules' private names - the lab, the tests.
order: 53
---
<code>friend module lab</code> declares a module that may name every private name of every module; <code>friend module lab of bank, sky</code> limits it to those. It is for test and staging code that must reach inside a system without the system exporting its internals.
```ludic
# doc-check: skip — a module spans files
friend module lab of bank, sky
```

View file

@ -0,0 +1,12 @@
---
id: kw-from
name: from
category: structure
kind: keyword
tokens: from
sig: registry ... from "file.lres" / def Registry from "file.lres"
tip: The resource file a registry's entries are read from.
order: 66
---
<code>from</code> names the <code>.lres</code> file the compiler reads a registry's entries from, relative to the declaring file (or, in a <code>@PerMap</code> registry, to each map's directory). The entries are checked against the record at build time, and an entry's place in the file is its index.

View file

@ -0,0 +1,19 @@
---
id: kw-internal
name: internal
category: structure
kind: keyword
tokens: internal
sig: namespace Name { internal function helper() { } }
tip: Inside a namespace: a function kept out of the Name.* surface.
order: 55
---
In a <code>namespace</code> block every function is part of the <code>Name.*</code> surface unless it says <code>internal</code>: then it is emitted as an ordinary helper the namespace's own methods can call, and <code>Name.helper</code> is not a method.
```ludic
namespace Trail {
internal function step(n: int) -> int { return n + 1 }
function next(n: int) -> int { return step(n) }
}
```

View file

@ -0,0 +1,19 @@
---
id: kw-module
name: module
category: structure
kind: keyword
tokens: module
sig: module name [in layer L] [uses a, b]
tip: Names the module a directory's files belong to; only what it exports is reachable from outside.
order: 51
---
<code>module</code> opens a directory's barrel (<code>index.ludic</code>) and says which module every file under it belongs to. A name a module does not <code>export</code> is private to it: another module that names it is refused at compile time, which is what makes a private name safe to change. The line may place the module in a layer (<code>in layer L</code>) and say what it may reach (<code>uses</code>).
```ludic
# doc-check: skip — a module spans files
# bank/index.ludic
module bank uses base
import "ledger.ludic"
```

View file

@ -0,0 +1,17 @@
---
id: kw-mut
name: mut
category: structure
kind: keyword
tokens: mut
sig: function f(st: mut State)
tip: A parameter the function may change - how a state is written.
order: 58
---
A state reaches a function only as a parameter: <code>h: HikerState</code> to read it, <code>h: mut HikerState</code> to change it, so a function's signature is everything it touches. The compiler refuses a write through a parameter that is not <code>mut</code>, and <code>ludic migrate state --tighten</code> takes <code>mut</code> off every one nothing down the chain writes.
```ludic
state Counter { n: int = 0 }
function bump(c: mut Counter) -> void { c.n = c.n + 1 }
```

View file

@ -0,0 +1,18 @@
---
id: kw-numbers
name: numbers
category: structure
kind: keyword
tokens: numbers
sig: numbers float
tip: This file's bare decimals are floats.
order: 56
---
<code>numbers float</code> at the top of a file makes a bare decimal such as <code>1.5</code> a <code>float</code> rather than a <code>fixed</code>. Such a file also refuses to promote a computed <code>int</code> silently: write <code>float(n)</code> where a count becomes a number.
```ludic
# doc-check: skip — a file-level line
numbers float
const GRAVITY: float = 9.81
```

View file

@ -0,0 +1,12 @@
---
id: kw-of
name: of
category: structure
kind: keyword
tokens: of
sig: registry Name of Record / friend module m of a, b
tip: Says what a registry holds, or whose private names a friend module sees.
order: 64
---
<code>of</code> names the record a <code>registry</code>'s entries are (<code>registry Tools of Tool</code>), and, on a <code>friend module</code> line, the modules whose private names it may see.

View file

@ -0,0 +1,12 @@
---
id: kw-open
name: open
category: structure
kind: keyword
tokens: open
sig: open registry Name of Record
tip: A registry other modules may add entries to with def.
order: 68
---
An <code>open registry</code> is one whose entries may come from other modules' <code>def</code>s, merged in a deterministic order; a closed one takes entries only from its own module. A package declares its table open so the game can fill it.

View file

@ -0,0 +1,19 @@
---
id: kw-port
name: port
category: structure
kind: keyword
tokens: port
sig: port Name { member: fn(T) -> R [= default], ... }
tip: The questions a module asks the world, bound once by the program.
order: 59
---
A <code>port</code> is what a module needs to ask the world, as named function members in primitive types. The program answers it once with <code>bind</code>; a member left unbound without a default is a compile error. Ports let a package ask a question without reaching into the module that knows the answer.
```ludic
# doc-check: skip — a module spans files
export port ClockWorld {
rest_scale: fn() -> float
}
```

View file

@ -0,0 +1,12 @@
---
id: kw-prop
name: prop
category: structure
kind: keyword
tokens: prop
sig: component Name { prop name: Type = default }
tip: A component's value handed in by its parent.
order: 71
---
<code>prop</code> declares a component member its parent sets from the template (<code>&lt;Counter step="2"/&gt;</code>); <code>state</code> declares one the component keeps for itself. Both are fields of the instance, listed with their types and defaults in <code>ludic schema</code>.

View file

@ -0,0 +1,20 @@
---
id: kw-reducer
name: reducer
category: structure
kind: keyword
tokens: reducer
sig: reducer State on Action(st: mut State, reads..., a: Action) { ... }
tip: Applies an action to one state; it may read others.
order: 62
---
A <code>reducer</code> writes exactly one state when its action is dispatched: the first parameter is that state, <code>mut</code>, the last is the action, and any states between are read-only. A change that must touch several states in a set order is a chain: a reducer dispatches the next action.
```ludic
state Pos { x: int = 0 }
action Move { dx: int = 0 }
reducer Pos on Move(p: mut Pos, a: Move) {
p.x = p.x + a.dx
}
```

View file

@ -0,0 +1,18 @@
---
id: kw-registry
name: registry
category: structure
kind: keyword
tokens: registry
sig: [open] registry Name of Record [as PREFIX] [from "file.lres"]
tip: A table of named entries of one record type, with a constant per entry.
order: 63
---
A <code>registry</code> is a table of named entries of one record: its entries come from <code>def</code> declarations or a resource file named by <code>from</code>, and <code>as P</code> generates a constant <code>P_KEY</code> per entry holding its index. Editors read it through <code>ludic schema</code>, and attributes such as <code>@AppendOnly</code> and <code>@PerMap</code> say how its entries may change and where they live.
```ludic
# doc-check: skip — the file it names is beside the example
property Tool { key: string = "", weight: float = 0.0 }
registry Tools of Tool as TL from "data/tools.lres"
```

View file

@ -0,0 +1,17 @@
---
id: kw-unsafe
name: unsafe
category: structure
kind: keyword
tokens: unsafe
sig: unsafe function f() { } / unsafe { ... }
tip: Raw memory is allowed inside: bytes(), free, Memory.*, indexing a pointer, calling C.
order: 57
---
Ludic's memory is safe unless code says <code>unsafe</code>: raw allocation, freeing, pointer indexing and foreign calls are compile errors elsewhere. An <code>unsafe function</code> or an <code>unsafe { ... }</code> block allows them inside, and only packages and the runtime are trusted to write it; a program's own files need <code>--unsafe</code>.
```ludic
# doc-check: skip — needs --unsafe
unsafe function raw(n: int) -> pointer { return bytes(n) }
```

View file

@ -0,0 +1,17 @@
---
id: kw-uses
name: uses
category: structure
kind: keyword
tokens: uses
sig: module name uses a, b, c
tip: The modules a module may reach; a reach not on the line is a compile error that names the fix.
order: 52
---
<code>uses</code> ends a <code>module</code> line with the modules its files may name. The compiler holds the module to that list and refuses a list that goes round: when adding a module would make a cycle, the question goes through a <code>port</code> the asker declares and the program binds instead.
```ludic
# doc-check: skip — a module spans files
module sky uses base, events, clock
```

View file

@ -0,0 +1,20 @@
---
id: kw-view
name: view
category: structure
kind: keyword
tokens: view
sig: view Name[(states)] { field = expr, function q(...), on e(...) { } }
tip: The bridge to a template: what it may read and what it may do.
order: 72
---
A <code>view</code> says what a template may read (its fields, each an expression) and what it may do (its queries and its <code>on</code> events), and nothing else crosses. The compiler writes <code>view_name()</code>, whose model is a value object of every field and whose call runs a query or an event by name.
```ludic
# doc-check: skip — the functions it names are elsewhere
view Yard {
spots: int = yard_spots()
on buy(hf: int) { yard_buy(hf) }
}
```