feat(lang): 0.S - a reference out of a read-only state is read-only (named so), a program's type named like the runtime's is refused (PadButton), tests for both, the migrate case covers lets; changeset
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
02448e176c
commit
c167ecc714
10 changed files with 13858 additions and 13042 deletions
|
|
@ -412,7 +412,11 @@ methods would take that method's calls - `Random.range` is `rng_range`, so a pac
|
|||
`rng_range(a, b, c)` would receive every `Random.range(1, 6)`. Where the program calls that method,
|
||||
the function is refused (`rng_range is the engine's Random.range, which this program calls (...),
|
||||
and every such call would reach this function instead; choose another name`), as a function named
|
||||
like a compiler built-in (`run`, `exit`) or like one of the engine runtime's own functions is.
|
||||
like a compiler built-in (`run`, `exit`) or like one of the engine runtime's own functions is. So
|
||||
is a type - a property, record, enum, event or state - named like one the runtime declares in a file
|
||||
the program uses: `property PadButton` beside the runtime's `enum PadButton` used to compile and
|
||||
then fail in clang, where the two layouts met (`PadButton is the runtime's enum
|
||||
(runtime/native/input.ludic); choose another name for this property`).
|
||||
|
||||
### Registries (`registry`, `def`)
|
||||
|
||||
|
|
|
|||
27
changes/state.md
Normal file
27
changes/state.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
bump: major
|
||||
type: feature
|
||||
**No function writes a global: a module's changing data is a `state`.** `state Hiker { hips: int =
|
||||
-1 }` is a record with exactly one instance, which no code can name; it reaches code as a parameter,
|
||||
`h: mut Hiker` to change it or `h: Hiker` to read it, and the runtime supplies it at the entry points
|
||||
(`entry (h: mut Hiker)`, a handler's, a listener's or a lifecycle hook's parameters, a scene's `on
|
||||
enter (h: mut Hiker)`, a test's, a `ui` block's `Hiker.hips`, a function value - a system's, a port
|
||||
bind's, a package's callback - an engine system, a namespace method's target and every call into the
|
||||
runtime). A module-level `var` is refused; a module-level `let` is immutable all the way down. A
|
||||
read-only state is checked where it is written - an assignment or a `push` through it or through a
|
||||
local holding part of it, passing it where `mut` is wanted, a `machine` over its field - and so is a
|
||||
module-level `let`. Each test block gets fresh states. A program's type named like one the runtime
|
||||
declares (`property PadButton` beside the runtime's `enum PadButton`) is refused instead of failing
|
||||
in clang.
|
||||
|
||||
`ludic migrate state [file|dir...] [--runtime] [--dry-run]` moves programs there: a var nothing
|
||||
writes becomes a `let`, each module's other vars one state (`FishingState`, `Render3dState`,
|
||||
`SceneDemoState`), every reference `fishing_st.name`, each function's states - to a fixed point over
|
||||
what it calls - its leading parameters, `mut` where anything below it writes, and each entry point
|
||||
declares them; the plans of every program given are merged before a file is edited. gpp's 35
|
||||
mechanic packages, ludic.ui, ludic.render3d, every example, the docs' fences and the runtime were
|
||||
moved with it. The toolchain's own programs build with `--globals` until they move too.
|
||||
|
||||
Outside the runtime, the drawable's size and scale, the screen's framebuffer and the tilemap's tile
|
||||
size are read and set through its functions (`gl_width()`, `gl_set_drawable(w, h)`,
|
||||
`gl_screen_fbo()`, `gl_pixel_scale()`, `rt_map_tile_size()`), and ludic.ui's render3d backend
|
||||
installs itself through the `UiRenderers` registry.
|
||||
9
examples/rejected/module_let_alias.ludic
Normal file
9
examples/rejected/module_let_alias.ludic
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# 0.S: a module-level let is immutable all the way down, through a local that holds it too
|
||||
program ModuleLetAlias {
|
||||
let LIMITS: []int = [1, 2]
|
||||
entry {
|
||||
let l = LIMITS
|
||||
push(l, 3)
|
||||
print(len(LIMITS))
|
||||
}
|
||||
}
|
||||
10
examples/rejected/runtime_type_clash.ludic
Normal file
10
examples/rejected/runtime_type_clash.ludic
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# a program's type named like one the runtime declares is refused (it compiled, and the two layouts
|
||||
# met in the IR): the runtime's input has `enum PadButton`
|
||||
program RuntimeTypeClash {
|
||||
property PadButton { code: int = 0 }
|
||||
entry {
|
||||
let b = new PadButton
|
||||
print(b.code)
|
||||
print(Input.pad_button(0, 1))
|
||||
}
|
||||
}
|
||||
13
examples/rejected/state_alias_readonly.ludic
Normal file
13
examples/rejected/state_alias_readonly.ludic
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# 0.S: a reference read out of a read-only state is read-only too
|
||||
program StateAliasReadonly {
|
||||
property Row { n: int = 0 }
|
||||
state Table { rows: []Row = new []Row }
|
||||
function bump(t: Table) -> void {
|
||||
let r = t.rows[0]
|
||||
r.n = 1
|
||||
}
|
||||
entry (t: mut Table) {
|
||||
push(t.rows, new Row)
|
||||
bump(t)
|
||||
}
|
||||
}
|
||||
|
|
@ -81,6 +81,10 @@ function ck_write_check(t: Node, what: pointer) -> void {
|
|||
mg_need(g_mg_cur, mg_state_named(ck_ro_st[li]), true) # 0.S2: it becomes `mut`
|
||||
return
|
||||
}
|
||||
if ck_is_ro(li) and li < len(ck_ro_st) and ck_ro_st[li] != null and not (ck_ro_st[li] == ck_tys[li]) {
|
||||
ck_err("state", t, `{what}: {r.s} holds part of a read-only {ck_ro_st[li]}; take the state as mut {ck_ro_st[li]} to change it`)
|
||||
return
|
||||
}
|
||||
if ck_is_ro(li) {
|
||||
ck_err("state", t, `{what}: {r.s} is read-only here ({r.s}: {ck_tys[li]}); take it as {r.s}: mut {ck_tys[li]} to change it`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -208,9 +208,29 @@ function ck_runtime_shadows() -> void {
|
|||
let u = ck_fn(d.s)
|
||||
if u != null and u != d { ck_err("shadow", u, `{d.s} is also a function of the engine runtime ({d.file}); choose another name`) }
|
||||
}
|
||||
# a type the runtime declares - `property PadButton` beside its `enum PadButton` compiled,
|
||||
# and the two layouts met in the IR
|
||||
if ck_type_kind(d) != "" {
|
||||
var k = 0
|
||||
while k < g_prog_user_end and k < len(prog) {
|
||||
let o = prog[k]
|
||||
if ck_type_kind(o) != "" and (o.s == d.s) {
|
||||
ck_err("shadow", o, `{d.s} is the runtime's {ck_type_kind(d)} ({d.file}); choose another name for this {ck_type_kind(o)}`)
|
||||
}
|
||||
k += 1
|
||||
}
|
||||
}
|
||||
r += 1
|
||||
}
|
||||
}
|
||||
function ck_type_kind(d: Node) -> pointer {
|
||||
if d.kind == N_ENUM { return "enum" }
|
||||
if d.kind == N_COMP and d.uns == 2 { return "state" }
|
||||
if d.kind == N_COMP { return "property" }
|
||||
if d.kind == N_STRUCT { return "record" }
|
||||
if d.kind == N_EVENT { return "event" }
|
||||
return ""
|
||||
}
|
||||
function ck_hooks(hs: []Node) -> void {
|
||||
var i = 0
|
||||
while i < len(hs) {
|
||||
|
|
|
|||
13401
selfhost/ludicc.seed.ll
13401
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -514,15 +514,15 @@ function migrate_case() -> void {
|
|||
let lbl = "ludic migrate state: vars into a state, parameters threaded, the same output"
|
||||
let work = `{tmp_dir()}/migrate`
|
||||
shell(`rm -rf {work} && mkdir -p {work}`)
|
||||
var src = "program Demo {\n var count: int = 0\n var names: []string = new []string # the names\n"
|
||||
var src = "program Demo {\n var count: int = 0\n var names: []string = new []string # the names\n var title: string = \"names\"\n"
|
||||
src = src + " function add(n: string) -> void {\n push(names, n)\n count += 1\n }\n"
|
||||
src = src + " function total() -> int {\n return count\n }\n"
|
||||
src = src + " function report() -> string {\n return `{total()} names, first {names[0]}`\n }\n"
|
||||
src = src + " function report() -> string {\n return `{total()} {title}, first {names[0]}`\n }\n"
|
||||
src = src + " entry {\n add(\"a\")\n add(\"b\")\n print(report())\n }\n}\n"
|
||||
write_file(`{work}/demo.ludic`, src)
|
||||
if not shq(`bin/ludic migrate state {work}/demo.ludic > {work}/out.txt 2>&1`) { bad2(lbl, capture_line(`tail -1 {work}/out.txt`)); return }
|
||||
let got = read_file(`{work}/demo.ludic`)
|
||||
if not s_contains(got, "state DemoState {") or not s_contains(got, "function add(demo_st: mut DemoState, n: string)") or not s_contains(got, "function total(demo_st: DemoState)") or not s_contains(got, "entry (demo_st: mut DemoState)") {
|
||||
if not s_contains(got, "state DemoState {") or not s_contains(got, "function add(demo_st: mut DemoState, n: string)") or not s_contains(got, "function total(demo_st: DemoState)") or not s_contains(got, "entry (demo_st: mut DemoState)") or not s_contains(got, "let title: string") {
|
||||
bad2(lbl, `rewrote it as [{got}]`)
|
||||
return
|
||||
}
|
||||
|
|
@ -908,6 +908,9 @@ function cmd_dev_test() -> int {
|
|||
reject_case("rejected/module_var", "a module-level var is refused", "a module-level var is refused")
|
||||
reject_case("rejected/module_let_write", "LIMITS is module-level and immutable all the way down", "a module-level let is immutable all the way down")
|
||||
reject_case("rejected/mut_not_state", "mut is for a state parameter", "mut is for a state parameter")
|
||||
reject_case("rejected/state_alias_readonly", "r holds part of a read-only Table; take the state as mut Table", "a reference out of a read-only state is read-only too")
|
||||
reject_case("rejected/module_let_alias", "l holds part of LIMITS, which is module-level and immutable all the way down", "a module-level let is immutable through a local that holds it")
|
||||
reject_case("rejected/runtime_type_clash", "PadButton is the runtime's enum", "a program's type named like one of the runtime's is refused")
|
||||
migrate_case()
|
||||
vis_report_case("rejected/uses_missing", "uses: examples/rejected/uses_valley/items/index.ludic:3: items.inv_add used from", "LUDIC_VIS_REPORT=1 lists a uses violation and builds")
|
||||
feat_case("lang/checked", "", "7.5 3 12 hi 2 ok", "checked.ludic (L4: literals take their slot's kind, string(p), []string as []pointer, null, named args)")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue