feat(stdlib): namespaced standard library (issue #2)
Implement the bulk of the namespaced-stdlib proposal (workshopsoft/ludic#2): 156 namespace methods across Math, Text, List, Ease, Collide, World, Net, Sys, Save, Mem, extended Screen, Color functions, extended Random, and Time. All deterministic fixed-point; self-hosting (C-free bootstrap fixpoint holds). Compiler (selfhost/): - Math.*: sqrt/sin/cos/tan/atan2/asin/acos (fixed-point runtime prelude — bit-by-bit isqrt, 256-entry interpolated sine table, Ross atan2), plus hypot/dist/dist2/deg_to_rad/rad_to_deg/posmod/wrap/ping_pong/snapped/ move_toward/smoothstep/lerp/remap/sign/floor/ceil/round. - Text.* (complete): upper/lower/trim/repeat/pad, split/join/replace, and the libc-backed queries. - List.* (complete): insert/remove_at/remove/sort plus the earlier ops. - Ease.* (in/out/in_out/back/bounce) and Collide.* (rects/point_rect/ circles/rect_circle). - Phase 3: World/Net/Sys/Save namespaced over the bare builtins (byte- identical IR) and Mem.* (bytes/words/copy/fill/peek/poke). - Screen.* extended (line/circle/fill_circle/triangle/fill_triangle via new runtime primitives; sprite/sprite_scaled aliases), Color.* functions, Random.* (value/int/sign), Time.* (frame/delta/elapsed/now — new game-loop frame counter). - Fix a lexer bug: fixed-point literals with >4 fractional digits overflowed. Docs & tooling: - 129 new per-symbol doc pages; gen.py made data-driven (namespaces discovered from the docs, no hardcoded list); new check-impl.py enforces that every implemented namespace method / keyword / type / phase has a doc page, wired into `x test-tools`. Document the previously-undocumented keywords (break/continue/where/entry/new/public + and/or/not tokens). - LSP: namespaced signature help (ns_method_sig) covering every namespace. Tests: 12 new self-host/regression tests + a golden render for the drawing primitives. All suites green (selfhost 21, regression 45, tools 29). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ff15c4e01d
commit
a38195128f
235 changed files with 24676 additions and 7762 deletions
7
docs/language/list/_section.md
Normal file
7
docs/language/list/_section.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
id: list
|
||||
title: List
|
||||
order: 6
|
||||
---
|
||||
|
||||
Operations over <code>[]T</code> slices — length, ends access, push/pop, swap, search, and in-place reverse. A slice is a shared growable buffer, so these mutate it in place and every holder sees the change. Arguments are positional.
|
||||
27
docs/language/list/list-clear.md
Normal file
27
docs/language/list/list-clear.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
id: list-clear
|
||||
name: List.clear
|
||||
category: list
|
||||
kind: namespace-method
|
||||
tokens: List.clear
|
||||
sig: List.clear(s) -> void
|
||||
tip: Remove all elements, keeping capacity.
|
||||
order: 2
|
||||
ns: List
|
||||
member: clear
|
||||
---
|
||||
|
||||
Empties <code>s</code> by setting its length back to zero. The backing buffer is kept, so refilling the slice afterward reuses the same memory without reallocating. Use it to reset a per-frame list — collisions, visible tiles, particles to draw — at the start of each update instead of building a new slice every frame.
|
||||
|
||||
Parameters:
|
||||
- `s` — the slice to empty
|
||||
|
||||
```ludic
|
||||
program FrameHits {
|
||||
var hits: []int = new []int
|
||||
|
||||
handler BeginFrame phase Update {
|
||||
List.clear(hits) # start the frame with an empty list
|
||||
}
|
||||
}
|
||||
```
|
||||
31
docs/language/list/list-contains.md
Normal file
31
docs/language/list/list-contains.md
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
id: list-contains
|
||||
name: List.contains
|
||||
category: list
|
||||
kind: namespace-method
|
||||
tokens: List.contains
|
||||
sig: List.contains(s, v) -> bool
|
||||
tip: Whether a value appears in a slice.
|
||||
order: 7
|
||||
ns: List
|
||||
member: contains
|
||||
---
|
||||
|
||||
Scans <code>s</code> and returns <code>true</code> if any element equals <code>v</code>. Comparison is by value for scalar elements (ints, entities, booleans) and by identity for reference elements. Use it for membership tests — has this tile been visited, is this entity already in the target list, is the achievement unlocked. It is a linear scan, so for very large or hot-path sets a dedicated structure will be faster.
|
||||
|
||||
Parameters:
|
||||
- `s` — the slice to search
|
||||
- `v` — the value to look for
|
||||
|
||||
```ludic
|
||||
program Visited {
|
||||
var seen: []int = new []int
|
||||
|
||||
handler Step phase Update {
|
||||
let tile = 42
|
||||
if not List.contains(seen, tile) {
|
||||
List.push(seen, tile)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
29
docs/language/list/list-first.md
Normal file
29
docs/language/list/list-first.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
id: list-first
|
||||
name: List.first
|
||||
category: list
|
||||
kind: namespace-method
|
||||
tokens: List.first
|
||||
sig: List.first(s) -> T
|
||||
tip: The first element of a slice.
|
||||
order: 3
|
||||
ns: List
|
||||
member: first
|
||||
---
|
||||
|
||||
Returns element <code>0</code> of <code>s</code> — the same as <code>s[0]</code>, named for readability. The slice must hold at least one element; calling it on an empty slice reads out of bounds, so guard with <code>List.len(s) > 0</code> when that is possible. Use it to peek at the head of a queue or the front of an ordered list.
|
||||
|
||||
Parameters:
|
||||
- `s` — a non-empty slice
|
||||
|
||||
```ludic
|
||||
program Queue {
|
||||
var waiting: []int = new []int
|
||||
|
||||
handler Serve phase Update {
|
||||
if List.len(waiting) > 0 {
|
||||
let next = List.first(waiting)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
31
docs/language/list/list-index_of.md
Normal file
31
docs/language/list/list-index_of.md
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
id: list-index_of
|
||||
name: List.index_of
|
||||
category: list
|
||||
kind: namespace-method
|
||||
tokens: List.index_of
|
||||
sig: List.index_of(s, v) -> int
|
||||
tip: The index of a value in a slice, or -1 if absent.
|
||||
order: 8
|
||||
ns: List
|
||||
member: index_of
|
||||
---
|
||||
|
||||
Scans <code>s</code> and returns the index of the first element equal to <code>v</code>, or <code>-1</code> if none match. It uses the same value/identity comparison as <code>List.contains</code>, but tells you where the match is so you can act on it — swap it out, read a parallel slice at the same index, or guard with <code>if idx >= 0</code>. Only the first match is reported.
|
||||
|
||||
Parameters:
|
||||
- `s` — the slice to search
|
||||
- `v` — the value to locate
|
||||
|
||||
```ludic
|
||||
program FindSlot {
|
||||
var ids: []int = new []int
|
||||
|
||||
handler Locate phase Update {
|
||||
let slot = List.index_of(ids, 99)
|
||||
if slot >= 0 {
|
||||
List.swap(ids, slot, List.len(ids) - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/list/list-insert.md
Normal file
22
docs/language/list/list-insert.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: list-insert
|
||||
name: List.insert
|
||||
category: list
|
||||
kind: namespace-method
|
||||
tokens: List.insert
|
||||
sig: List.insert(s, i, v) -> void
|
||||
tip: Insert an element at an index, shifting the rest up.
|
||||
order: 10
|
||||
ns: List
|
||||
member: insert
|
||||
---
|
||||
|
||||
Inserts <code>v</code> at index <code>i</code>, moving the elements at and after <code>i</code> one place toward the end and growing the backing buffer if needed. Use it to keep a list ordered as you build it, or to splice an item into the middle. Appending is cheaper with <code>List.push</code>.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
List.insert(queue, 0, next_id) # push to the front
|
||||
}
|
||||
}
|
||||
```
|
||||
29
docs/language/list/list-last.md
Normal file
29
docs/language/list/list-last.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
id: list-last
|
||||
name: List.last
|
||||
category: list
|
||||
kind: namespace-method
|
||||
tokens: List.last
|
||||
sig: List.last(s) -> T
|
||||
tip: The last element of a slice.
|
||||
order: 4
|
||||
ns: List
|
||||
member: last
|
||||
---
|
||||
|
||||
Returns the final element of <code>s</code> — the value at index <code>List.len(s) - 1</code>. Like <code>List.first</code>, it assumes the slice is non-empty, so guard with a length check when the slice might be empty. Use it to read the most recently pushed item without removing it; use <code>List.pop</code> when you want to take it off.
|
||||
|
||||
Parameters:
|
||||
- `s` — a non-empty slice
|
||||
|
||||
```ludic
|
||||
program Trail {
|
||||
var points: []int = new []int
|
||||
|
||||
handler Head phase Update {
|
||||
if List.len(points) > 0 {
|
||||
let newest = List.last(points)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
27
docs/language/list/list-len.md
Normal file
27
docs/language/list/list-len.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
id: list-len
|
||||
name: List.len
|
||||
category: list
|
||||
kind: namespace-method
|
||||
tokens: List.len
|
||||
sig: List.len(s) -> int
|
||||
tip: The number of elements in a slice.
|
||||
order: 0
|
||||
ns: List
|
||||
member: len
|
||||
---
|
||||
|
||||
Returns how many elements <code>s</code> currently holds. It reads the slice's length, which changes as you <code>List.push</code> and <code>List.pop</code>, and is the same value as the bare <code>len(s)</code>. Use it to bound a loop, test for emptiness, or find the last index with <code>List.len(s) - 1</code>.
|
||||
|
||||
Parameters:
|
||||
- `s` — the slice to measure
|
||||
|
||||
```ludic
|
||||
program CountEnemies {
|
||||
var enemies: []int = new []int
|
||||
|
||||
handler Report phase Update {
|
||||
let n = List.len(enemies)
|
||||
}
|
||||
}
|
||||
```
|
||||
29
docs/language/list/list-pop.md
Normal file
29
docs/language/list/list-pop.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
id: list-pop
|
||||
name: List.pop
|
||||
category: list
|
||||
kind: namespace-method
|
||||
tokens: List.pop
|
||||
sig: List.pop(s) -> T
|
||||
tip: Remove and return the last element.
|
||||
order: 5
|
||||
ns: List
|
||||
member: pop
|
||||
---
|
||||
|
||||
Removes the final element of <code>s</code> and returns it, shortening the slice by one. Together with <code>List.push</code> this makes a slice work as a stack: push to add, pop to take the most recent back off. The slice must be non-empty. Use it to unwind a history, process a work list, or undo the last action.
|
||||
|
||||
Parameters:
|
||||
- `s` — a non-empty slice
|
||||
|
||||
```ludic
|
||||
program UndoStack {
|
||||
var history: []int = new []int
|
||||
|
||||
handler Undo phase Update {
|
||||
if List.len(history) > 0 {
|
||||
let last_action = List.pop(history)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
28
docs/language/list/list-push.md
Normal file
28
docs/language/list/list-push.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
id: list-push
|
||||
name: List.push
|
||||
category: list
|
||||
kind: namespace-method
|
||||
tokens: List.push
|
||||
sig: List.push(s, v) -> void
|
||||
tip: Append an element to the end of a slice.
|
||||
order: 1
|
||||
ns: List
|
||||
member: push
|
||||
---
|
||||
|
||||
Adds <code>v</code> to the end of <code>s</code>, growing the backing buffer if needed, so the slice's length increases by one. It is the same operation as the bare <code>push(s, v)</code>. Because a slice is shared, the new element is visible to every holder. Use it to collect spawned entities, build a list of hits, or fill an inventory.
|
||||
|
||||
Parameters:
|
||||
- `s` — the slice to append to
|
||||
- `v` — the element to add (matching the slice's element type)
|
||||
|
||||
```ludic
|
||||
program Collect {
|
||||
var picked: []int = new []int
|
||||
|
||||
handler Grab phase Update {
|
||||
List.push(picked, 7)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/list/list-remove.md
Normal file
22
docs/language/list/list-remove.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: list-remove
|
||||
name: List.remove
|
||||
category: list
|
||||
kind: namespace-method
|
||||
tokens: List.remove
|
||||
sig: List.remove(s, v) -> void
|
||||
tip: Remove the first element equal to a value.
|
||||
order: 12
|
||||
ns: List
|
||||
member: remove
|
||||
---
|
||||
|
||||
Scans for the first element equal to <code>v</code> and removes it (shifting the rest down); if no element matches, the slice is unchanged. Comparison is by value for scalars and by identity for reference elements, matching <code>List.contains</code>. Only the first match is removed.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
List.remove(active, dead_entity)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/list/list-remove_at.md
Normal file
22
docs/language/list/list-remove_at.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: list-remove_at
|
||||
name: List.remove_at
|
||||
category: list
|
||||
kind: namespace-method
|
||||
tokens: List.remove_at
|
||||
sig: List.remove_at(s, i) -> void
|
||||
tip: Remove the element at an index, shifting the rest down.
|
||||
order: 11
|
||||
ns: List
|
||||
member: remove_at
|
||||
---
|
||||
|
||||
Removes the element at index <code>i</code>, moving everything after it one place toward the front and shortening the slice by one. Order is preserved. When order does not matter, swapping the item to the end and calling <code>List.pop</code> is cheaper.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
List.remove_at(effects, expired)
|
||||
}
|
||||
}
|
||||
```
|
||||
28
docs/language/list/list-reverse.md
Normal file
28
docs/language/list/list-reverse.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
id: list-reverse
|
||||
name: List.reverse
|
||||
category: list
|
||||
kind: namespace-method
|
||||
tokens: List.reverse
|
||||
sig: List.reverse(s) -> void
|
||||
tip: Reverse the order of a slice in place.
|
||||
order: 9
|
||||
ns: List
|
||||
member: reverse
|
||||
---
|
||||
|
||||
Reverses <code>s</code> so its first element becomes last and vice versa, mutating the slice in place rather than returning a copy. Use it to flip an order you built back-to-front — a path traced from goal to start, an undo stack you want oldest-first, or a list you appended to and now want to read in the opposite direction.
|
||||
|
||||
Parameters:
|
||||
- `s` — the slice to reverse
|
||||
|
||||
```ludic
|
||||
program PathOrder {
|
||||
var path: []int = new []int
|
||||
|
||||
handler Finalize phase Update {
|
||||
# path was built from the goal back to the start
|
||||
List.reverse(path) # now start -> goal
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/list/list-sort.md
Normal file
22
docs/language/list/list-sort.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: list-sort
|
||||
name: List.sort
|
||||
category: list
|
||||
kind: namespace-method
|
||||
tokens: List.sort
|
||||
sig: List.sort(s) -> void
|
||||
tip: Sort a slice in ascending order, in place.
|
||||
order: 13
|
||||
ns: List
|
||||
member: sort
|
||||
---
|
||||
|
||||
Sorts the elements of <code>s</code> in ascending order in place, using an insertion sort. It is intended for slices of scalar elements (ints, entities, fixed) where <code><</code> is meaningful; reference elements are ordered by identity, which is rarely useful. Insertion sort is simple and fast for the small, nearly-sorted lists games usually hold.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
List.sort(scores)
|
||||
}
|
||||
}
|
||||
```
|
||||
32
docs/language/list/list-swap.md
Normal file
32
docs/language/list/list-swap.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
id: list-swap
|
||||
name: List.swap
|
||||
category: list
|
||||
kind: namespace-method
|
||||
tokens: List.swap
|
||||
sig: List.swap(s, i, j) -> void
|
||||
tip: Exchange the elements at two indices.
|
||||
order: 6
|
||||
ns: List
|
||||
member: swap
|
||||
---
|
||||
|
||||
Exchanges the elements at indices <code>i</code> and <code>j</code> in <code>s</code>, in place. Both indices must be within the slice. Swapping is the building block of shuffles, sorts, and moving an item to the end before <code>List.pop</code> to remove it cheaply without shifting the rest.
|
||||
|
||||
Parameters:
|
||||
- `s` — the slice to modify
|
||||
- `i` — the first index
|
||||
- `j` — the second index
|
||||
|
||||
```ludic
|
||||
program RemoveFast {
|
||||
var actors: []int = new []int
|
||||
|
||||
handler Kill phase Update {
|
||||
let dead = 2
|
||||
let last = List.len(actors) - 1
|
||||
List.swap(actors, dead, last) # move it to the end...
|
||||
let removed = List.pop(actors) # ...then drop it
|
||||
}
|
||||
}
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue