refactor(lang): rename the ptr/ptrs types to pointer/pointers

Expand the abbreviated pointer types to full words on the language surface:
  ptr   ->  pointer     (a raw address / FFI handle)
  ptrs  ->  pointers    (a buffer of pointers)

The Ludic type name is distinct from LLVM's own `ptr` spelling: llty() maps
`pointer`/`pointers` to LLVM `ptr`, and the emitted IR keeps `ptr`, so only
the Ludic-level surface changes. Rewrites type annotations across all
sources, the 8 hardcoded pointer type-tags, the `pointers`-buffer indexing
in emit_addr, the grammars/LSP/JetBrains tokens, and the docs
(type-ptr -> type-pointer, type-ptrs -> type-pointers). int/bool keep their
conventional short spelling (like Math).

Reseeded; C-free fixpoint holds; all suites green (45/24/29); site + check.py OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-08-30 01:58:54 +03:00
parent b7745a4600
commit effb3f637f
76 changed files with 516 additions and 516 deletions

View file

@ -9,7 +9,7 @@ tip: Mark fields as replicated and models as owned so the compiler generates net
order: 4
---
These are the two declarative switches at the top of Ludic's networking: <code>@Sync</code> says <b>what</b> replicates and <code>@Owned</code> says <b>who</b> owns an instance. Replication is opt-in at the field level and decided per model use-site — a field crosses the wire only when it is both replicable (marked <code>@Sync</code>, either on the field or via <code>@Sync property P</code> which marks every field of <code>P</code>) <b>and</b> participating (the model marks the component <code>@Sync</code>). So the same property can replicate in one model and not another, and there is no <code>@NoSync</code> because the surface is purely additive. From these marks the compiler generates the per-model <code>serialize</code>/<code>apply</code> codecs; <code>@Sync</code> on a non-POD-scalar field (like a <code>ptr</code>) is a compile error, since a machine-local pointer cannot cross the wire. <code>@Owned</code> adds the owner slot the ownership builtins and <code>@Predicted</code> read. See the dedicated <code>@Owned</code> page for ownership details.
These are the two declarative switches at the top of Ludic's networking: <code>@Sync</code> says <b>what</b> replicates and <code>@Owned</code> says <b>who</b> owns an instance. Replication is opt-in at the field level and decided per model use-site — a field crosses the wire only when it is both replicable (marked <code>@Sync</code>, either on the field or via <code>@Sync property P</code> which marks every field of <code>P</code>) <b>and</b> participating (the model marks the component <code>@Sync</code>). So the same property can replicate in one model and not another, and there is no <code>@NoSync</code> because the surface is purely additive. From these marks the compiler generates the per-model <code>serialize</code>/<code>apply</code> codecs; <code>@Sync</code> on a non-POD-scalar field (like a <code>pointer</code>) is a compile error, since a machine-local pointer cannot cross the wire. <code>@Owned</code> adds the owner slot the ownership builtins and <code>@Predicted</code> read. See the dedicated <code>@Owned</code> page for ownership details.
```ludic
program SyncedWorld {

View file

@ -4,7 +4,7 @@ name: bytes
category: builtins
kind: builtin
tokens: bytes
sig: bytes(n) -> ptr
sig: bytes(n) -> pointer
tip: Allocate a raw buffer of n bytes and return a pointer to it.
order: 50
---

View file

@ -4,7 +4,7 @@ name: file_stderr
category: builtins
kind: builtin
tokens: file_stderr
sig: file_stderr() -> ptr
sig: file_stderr() -> pointer
tip: The standard-error stream handle for use with file_write.
order: 50
---

View file

@ -4,7 +4,7 @@ name: file_stdout
category: builtins
kind: builtin
tokens: file_stdout
sig: file_stdout() -> ptr
sig: file_stdout() -> pointer
tip: The standard-output stream handle for use with file_write.
order: 50
---

View file

@ -4,7 +4,7 @@ name: Memory.bytes
category: memory
kind: namespace-method
tokens: Memory.bytes
sig: Memory.bytes(n) -> ptr
sig: Memory.bytes(n) -> pointer
tip: Allocate n bytes.
order: 0
ns: Memory

View file

@ -8,7 +8,7 @@ tip: Integer, hex, character, string, boolean, and null-pointer literals.
order: 8
---
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 `string`, `true` / `false` are `bool`s, and `null` is the null-pointer literal used to test an unset record, slice, or `ptr` field.
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 `string`, `true` / `false` are `bool`s, and `null` is the null-pointer literal used to test an unset record, slice, or `pointer` field.
```ludic
let sky_color: int = 0x1E90FF

View file

@ -9,7 +9,7 @@ tip: Bind a name to an external native symbol — the seam for platform and libr
order: 12
---
An <code>extern function</code> declares a function whose body lives outside Ludic and binds it to a native symbol resolved at link time. It is the seam through which Ludic reaches anything with a native interface — a system library, a math routine, or even another `.ludic` file compiled as a `module`. You write the Ludic signature you want to call and give the real symbol name after `=`; the linker connects them (pass `-L`/`-l` to `ludicc` to point at the library). Types must match the foreign ABI, so map each parameter and the return to the right Ludic type (`int`, `fixed`, `ptr`, …).
An <code>extern function</code> declares a function whose body lives outside Ludic and binds it to a native symbol resolved at link time. It is the seam through which Ludic reaches anything with a native interface — a system library, a math routine, or even another `.ludic` file compiled as a `module`. You write the Ludic signature you want to call and give the real symbol name after `=`; the linker connects them (pass `-L`/`-l` to `ludicc` to point at the library). Types must match the foreign ABI, so map each parameter and the return to the right Ludic type (`int`, `fixed`, `pointer`, …).
Parameters:
- `a` — a typed argument passed straight through to the foreign symbol

View file

@ -4,7 +4,7 @@ name: System.file_open
category: system
kind: namespace-method
tokens: System.file_open
sig: System.file_open(path, mode) -> ptr
sig: System.file_open(path, mode) -> pointer
tip: Open a file.
order: 6
ns: System

View file

@ -4,7 +4,7 @@ name: System.stderr
category: system
kind: namespace-method
tokens: System.stderr
sig: System.stderr() -> ptr
sig: System.stderr() -> pointer
tip: The standard error handle.
order: 13
ns: System

View file

@ -4,7 +4,7 @@ name: System.stdout
category: system
kind: namespace-method
tokens: System.stdout
sig: System.stdout() -> ptr
sig: System.stdout() -> pointer
tip: The standard output handle.
order: 12
ns: System

View file

@ -9,7 +9,7 @@ tip: A raw buffer indexed one byte at a time — each buffer[i] reads or writes
order: 7
---
`byte` is a raw buffer viewed one byte at a time: indexing `buffer[index]` reads or writes a single 8-bit value. It is the byte-sized view of the memory a `ptr` addresses — allocate it with `bytes(count)` and bind it as `byte` when you want per-byte access, for example to build up text, decode a file, or pack a compact grid. Each element is an `int` in the range of a byte. Like the other raw buffers it is unchecked, so you are responsible for staying in bounds.
`byte` is a raw buffer viewed one byte at a time: indexing `buffer[index]` reads or writes a single 8-bit value. It is the byte-sized view of the memory a `pointer` addresses — allocate it with `bytes(count)` and bind it as `byte` when you want per-byte access, for example to build up text, decode a file, or pack a compact grid. Each element is an `int` in the range of a byte. Like the other raw buffers it is unchecked, so you are responsible for staying in bounds.
```ludic
let name_buffer: byte = bytes(16)

View file

@ -0,0 +1,18 @@
---
id: type-pointer
name: pointer
category: types
kind: type
tokens: pointer
sig: pointer
tip: A raw address into memory — a byte buffer from bytes(n), or an FFI handle.
order: 5
---
`pointer` is a raw address into memory — the low-level type for runtime and foreign-function work, not something an everyday game reaches for. Allocate a raw byte buffer with `bytes(count)`, which returns a `pointer` you index as `buffer[index]` to read or write one byte; retype the binding as `words` / `fixeds` / `pointers` to index in larger element sizes. A `pointer` is also how an `extern function` passes an opaque C handle across the ABI. Test one for emptiness against the `null` literal.
```ludic
let scratch: pointer = bytes(256)
scratch[0] = 65
if scratch != null { scratch[1] = 66 }
```

View file

@ -0,0 +1,25 @@
---
id: type-pointers
name: pointers
category: types
kind: type
tokens: pointers
sig: pointers
tip: A buffer of pointers — p[i] reads and writes a pointer.
order: 9
---
A <code>pointers</code> is a raw buffer whose elements are pointers: index it with <code>buffer[i]</code> to read or write a <code>pointer</code>, with no bounds checking. Allocate the storage with <code>words(n)</code> (a pointer fits in a slot) and type the binding as <code>pointers</code> so indexing uses the pointer element size. It is the low-level building block for hand-rolled data structures — a table of records made with <code>new</code>, a free list, a bucket array — for when you want manual control instead of a growable <code>[]T</code> slice.
```ludic
program Records {
property Position { column: int = 0, row: int = 0 }
handler Setup phase Start {
let slots: pointers = words(2)
slots[0] = new Position
slots[1] = new Position
print(2)
}
}
```

View file

@ -1,18 +0,0 @@
---
id: type-ptr
name: ptr
category: types
kind: type
tokens: ptr
sig: ptr
tip: A raw address into memory — a byte buffer from bytes(n), or an FFI handle.
order: 5
---
`ptr` is a raw address into memory — the low-level type for runtime and foreign-function work, not something an everyday game reaches for. Allocate a raw byte buffer with `bytes(count)`, which returns a `ptr` you index as `buffer[index]` to read or write one byte; retype the binding as `words` / `fixeds` / `ptrs` to index in larger element sizes. A `ptr` is also how an `extern function` passes an opaque C handle across the ABI. Test one for emptiness against the `null` literal.
```ludic
let scratch: ptr = bytes(256)
scratch[0] = 65
if scratch != null { scratch[1] = 66 }
```

View file

@ -1,25 +0,0 @@
---
id: type-ptrs
name: ptrs
category: types
kind: type
tokens: ptrs
sig: ptrs
tip: A buffer of pointers — p[i] reads and writes a ptr.
order: 9
---
A <code>ptrs</code> is a raw buffer whose elements are pointers: index it with <code>buffer[i]</code> to read or write a <code>ptr</code>, with no bounds checking. Allocate the storage with <code>words(n)</code> (a pointer fits in a slot) and type the binding as <code>ptrs</code> so indexing uses the pointer element size. It is the low-level building block for hand-rolled data structures — a table of records made with <code>new</code>, a free list, a bucket array — for when you want manual control instead of a growable <code>[]T</code> slice.
```ludic
program Records {
property Position { column: int = 0, row: int = 0 }
handler Setup phase Start {
let slots: ptrs = words(2)
slots[0] = new Position
slots[1] = new Position
print(2)
}
}
```

View file

@ -9,7 +9,7 @@ tip: A raw buffer indexed as 32-bit words — each buffer[i] reads or writes an
order: 6
---
`words` is a raw buffer viewed as a sequence of 32-bit words: indexing `buffer[index]` reads or writes one `int`. It is the same underlying memory a `ptr` addresses, retyped so the element size is a word instead of a byte — allocate the storage with `words(count)` (count 32-bit words) and bind it as `words` to index it that way. Reach for it when you need a flat integer array outside the ECS — a lookup table, a scratch grid — and want plain integer indexing without the growable-slice header. Like all raw buffers it is unbounded and unchecked, so keep your own length.
`words` is a raw buffer viewed as a sequence of 32-bit words: indexing `buffer[index]` reads or writes one `int`. It is the same underlying memory a `pointer` addresses, retyped so the element size is a word instead of a byte — allocate the storage with `words(count)` (count 32-bit words) and bind it as `words` to index it that way. Reach for it when you need a flat integer array outside the ECS — a lookup table, a scratch grid — and want plain integer indexing without the growable-slice header. Like all raw buffers it is unbounded and unchecked, so keep your own length.
```ludic
let height_map: words = words(64)