docs(api): per-symbol pages, fuzzy search, deep token linking, hover cards
All checks were successful
docs / build-and-deploy (push) Successful in 2s

Rebuild the API Reference around one page per symbol and richer, verified content.

Pages & navigation
- One HTML page per symbol (kw-*, type-*, phase-*, screen-*, fn-*, annot-*, op-*)
  instead of a single scrolling page; namespace overview pages (ns-screen …
  ns-color) and a searchable index (api.html) with client-side fuzzy search.
- Sticky-header scroll offset (scroll-margin) so a jumped-to entry/param/color is
  never hidden, plus a flash highlight on the scrolled-to target.

Deep linking in every snippet & example
- Namespace members split: `Screen`→namespace page, `fill_rectangle`→method page;
  `Color`→palette page, `Charcoal`→its swatch — separately.
- Named arguments (`width:`) link to that parameter's anchor on the method page.
- Hover any token for a summary card built from the real API data (symbols.json).

Content & coverage
- Full authoritative surface documented from the compiler: every keyword, type,
  the 6 phases (Start/Input/FixedUpdate/Update/LateUpdate/Render, each its own
  page), all 22 annotations, namespace methods with parameter docs, builtins,
  the world_* reflection ABI, networking, operators — 155 symbols.
- Longer, clearer explanations; "model"/"model instance" terminology, not "entity";
  descriptive identifiers in every example (Position{column,row}, Velocity{delta_x,
  delta_y}, Health{current,maximum}, Player/Enemy) — no Pos/Seg/x/dx.
- Accuracy fixes from compiler ground-truth: world_count() takes no arg,
  world_query_next(property, cursor) arg order, event fields bind by name; dropped
  `when` and `module` (not in the self-hosted parser).

Tooling
- inventory.json + check.py: coverage guard (every symbol has a page), duplicate-
  token guard, and broken-link guard — fail CI so docs can't drift.
- validate.py: compiles every ```ludic example against bin/ludicc (158 compile).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-08-29 17:53:22 +03:00
parent 25f987e30d
commit 3c7ec9b016
172 changed files with 5240 additions and 895 deletions

View file

@ -3,9 +3,15 @@ id: op-access
name: Member & index
category: operators
kind: operator
sig: x.field buf[i] s[a..b]
tip: Field/method access, element index, and string slice (a fresh substring).
sig: x.field buffer[i] text[a..b]
tip: Field access, element index, and string slice — usable as value or target.
order: 6
---
Field/method access, element index, and string slice (a fresh substring).
The access operators reach into a compound value. `value.field` reads or writes a property field and chains freely (`route.next.column`); `buffer[index]` reads or writes one element of a slice or raw buffer; and `text[start..end]` produces a fresh substring of the bytes in the half-open range, while `text[index]` reads a single byte as an `int` code point. Each form works both as a <b>value</b> and as an <b>assignment target</b>, and they compose — `route[index].column = 0` is one address computation. Field access also binds method-style calls like `Screen.fill_rectangle`.
```ludic
let head_column: int = segments[0].column
segments[0].column = head_column + 1
let extension: str = file_name[len(file_name) - 4 .. len(file_name)]
```

View file

@ -4,8 +4,14 @@ name: Arithmetic
category: operators
kind: operator
sig: + - * / %
tip: Add, subtract, multiply, integer-divide, remainder.
tip: Add, subtract, multiply, integer-divide, and remainder — on int and fixed.
order: 0
---
Add, subtract, multiply, integer-divide, remainder. On <code>fixed</code> values the same symbols do fixed-point math.
The arithmetic operators are `+` (add), `-` (subtract), `*` (multiply), `/` (divide), and `%` (remainder). On `int` values `/` is integer division that truncates and `%` gives the remainder — handy for wrapping a value or testing a period, as in `elapsed_frames % 30 == 0`. On `fixed` values the same symbols do fixed-point math (multiply and divide are scaled), and mixing an `int` with a `fixed` promotes the `int`. `*`, `/`, and `%` bind tighter than `+` and `-`, so `column * TILE_SIZE + 1` groups as expected.
```ludic
let pixel_x: int = column * TILE_SIZE + 1
let is_even_row: bool = row % 2 == 0
let average: int = (current_health + maximum_health) / 2
```

View file

@ -4,8 +4,16 @@ name: Assignment
category: operators
kind: operator
sig: name = value
tip: Assign to a var, a field, or an element.
tip: Store into a var, a field, or an element — a statement, not an expression.
order: 5
---
Assign to a <code>var</code>, a field, or an element. Not an expression.
Assignment stores a value into a target — a `var` binding, a property field, or a buffer/slice element — with `target = value`. The compound forms `+=`, `-=`, `*=`, and `/=` update in place, so `score += 1` means `score = score + 1`. Assignment is a <b>statement, not an expression</b>: it produces no value, so you cannot write `if x = 0` (use `==` for the test). Only a mutable target accepts it — assigning to a `let` or `const` binding is a compile error — though you may still mutate <b>through</b> an immutable binding that holds a record or slice.
```ludic
var score: int = 0
score = 10
score += 5
let current_position = new_position()
current_position.column = current_position.column + 1
```

View file

@ -4,8 +4,15 @@ name: Bitwise
category: operators
kind: operator
sig: & | ^ ~ << >>
tip: And, or, xor, not, shift left/right.
tip: Bit-level and, or, xor, not, and shifts — with Go-style precedence.
order: 3
---
And, or, xor, not, shift left/right. Shifts and <code>&amp;</code> bind like <code>*</code>; <code>|</code>/<code>^</code> bind like <code>+</code> — tighter than comparison, so <code>flags &amp; MASK == 0</code> needs no parentheses.
The bitwise operators work on the bits of an `int`: `&` (and), `|` (or), `^` (xor), `~` (not), `<<` (shift left), and `>>` (a logical/unsigned shift right). They are the tools for flag sets, packing several small values into one integer, and fast multiply/divide by powers of two. Precedence is Go-style: `<<`, `>>`, and `&` bind like `*` (tightly), while `|` and `^` bind like `+`, and all of them bind <b>tighter than comparison</b> — so `flags & MASK == 0` means `(flags & MASK) == 0` with no parentheses.
```ludic
const FLAG_POISONED: int = 1
const FLAG_SHIELDED: int = 2
var status_flags: int = FLAG_POISONED | FLAG_SHIELDED
var is_shielded: bool = status_flags & FLAG_SHIELDED != 0
```

View file

@ -4,8 +4,14 @@ name: Comment
category: operators
kind: operator
sig: # to end of line
tip: Everything after # on a line is a comment.
tip: Everything after # on a line is a comment, ignored by the compiler.
order: 9
---
Everything after <code>#</code> on a line is a comment.
A comment begins with `#` and runs to the end of the line; the compiler ignores everything after it. Ludic has only this one line-comment form — there is no block-comment syntax — so to comment out several lines put a `#` on each. Use comments to explain intent next to a `const`, to label a section of a handler, or to note a gotcha. The source formatter (`ludic-fmt`) works on tokens, so your comments and blank lines survive a reformat.
```ludic
const TILE_SIZE: int = 16 # pixels per grid cell
var score: int = 0 # reset in the Start phase
# the head is drawn brighter than the body
```

View file

@ -4,8 +4,13 @@ name: Comparison
category: operators
kind: operator
sig: == != < <= > >=
tip: Yield a bool.
tip: Compare two values and yield a bool; on strings == compares contents.
order: 1
---
Yield a <code>bool</code>. On strings, <code>==</code> compares contents.
The comparison operators — `==` (equal), `!=` (not equal), `<`, `<=`, `>`, `>=` — take two values and yield a `bool`, the kind of test an `if` or `where` clause wants. On `str` values `==` and `!=` compare <b>by content</b>, so `direction == "left"` checks the characters, not the pointer (a comparison against `null` stays a pointer test). Comparison binds looser than arithmetic and looser than the bitwise operators, so `flags & MASK == 0` reads as `(flags & MASK) == 0` with no parentheses needed. Chain several conditions together with `and` / `or`.
```ludic
if current_health <= 0 { become GameOver }
if pressed_key == 'w' and elapsed_frames > 0 { row = row - 1 }
```

View file

@ -8,8 +8,10 @@ tip: A backtick string with {expr} holes, each stringified and concatenated.
order: 7
---
A backtick string with <code>{expr}</code> holes, each stringified and concatenated. <code>{{</code> and <code>}}</code> are literal braces.
A backtick string `` `…` `` is an interpolated string: any `{expr}` hole inside it is evaluated, converted to text, and concatenated with the surrounding literal parts. Numbers, `bool`s, and `fixed` values are stringified automatically and `str` values pass through, so `` `score: {score}` `` desugars to `"score: " + str(score)`. It is the readable way to build a message from mixed pieces without hand-writing a `+` chain. Write a literal brace with `{{` or `}}`.
```ludic
print(`score: {score}`)
let status_line: str = `score {score} — health {current_health}/{maximum_health}`
Screen.status(status_line)
print(`wave {wave_number} incoming`)
```

View file

@ -4,8 +4,15 @@ name: Literals
category: operators
kind: operator
sig: 42 0x1E90FF 'w' "text" true null
tip: Decimal and hex ints (hex is how colors are written), a character code in single quotes, a string in double quotes, booleans, and the null pointer.
tip: Integer, hex, character, string, boolean, and null-pointer literals.
order: 8
---
Decimal and hex ints (hex is how colors are written), a character code in single quotes, a string in double quotes, booleans, and the null pointer.
Literals are the fixed values you write directly in source. `42` is a decimal `int` and `0x1E90FF` is a hex `int` — hex is how colors are written, so a raw color is just an integer. A number with a decimal point (`1.5`) is a `fixed`. `'w'` is a character literal, an `int` code point handy for comparing against `Input.key()`. `"text"` is a `str`, `true` / `false` are `bool`s, and `null` is the null-pointer literal used to test an unset record, slice, or `ptr` field.
```ludic
let sky_color: int = 0x1E90FF
let banner: str = "GAME OVER"
if Input.key() == 'w' { row = row - 1 }
if next_segment == null { is_tail = true }
```

View file

@ -4,12 +4,13 @@ name: Logical
category: operators
kind: operator
sig: and or not
tip: Boolean combinators — words, not symbols.
tip: The boolean combinators, spelled as words — never && or || or a bare !.
order: 2
---
Boolean combinators — words, not symbols.
The boolean combinators are `and`, `or`, and `not`. They combine `bool` values — usually the results of comparisons — into the compound conditions an `if`, `while`, or `where` clause tests. Ludic spells them as <b>words, not symbols</b>: `&&` and `||` are not operators, and a bare `!` is rejected with a diagnostic pointing you to `not` (`!=` is unaffected). `and` binds tighter than `or`, so `a and b or c` groups as `(a and b) or c`; add parentheses when you mean otherwise.
```ludic
if k != 0 and mode == 0 { … }
if pressed_key != 0 and not is_paused { apply_input() }
if current_health <= 0 or elapsed_frames > time_limit { become GameOver }
```

View file

@ -4,8 +4,16 @@ name: Range
category: operators
kind: operator
sig: a .. b
tip: A half-open range for for loops: a up to but not including b.
tip: A half-open range for numeric for loops — from a up to but not including b.
order: 4
---
A half-open range for <code>for</code> loops: <code>a</code> up to but not including <code>b</code>.
`a .. b` is a half-open numeric range used by the counting `for` loop: `for index in a .. b` walks `index` from `a` up to but <b>not including</b> `b`. This "up to but not including" convention makes lengths line up naturally — `0 .. len(route)` visits every element index of a slice, and `0 .. GRID_WIDTH` visits every column with no off-by-one. Both endpoints are `int` expressions, so a bound can be a `const`, a `var`, or any computed value. It is distinct from the string slice `text[start..end]`, which uses the same half-open idea for bytes.
```ludic
for row in 0 .. GRID_HEIGHT {
for column in 0 .. GRID_WIDTH {
Screen.put_pixel(x: column, y: row, color: Color.MidnightBlue)
}
}
```