feat(lang): 0.S - components take states in their header; migrate writes them, never inside a name, never outside the programs given, never into a package's state
- component Name (a: mut A, b: B) { ... }: every getter, default, function and event takes the
header's states before the instance; a member's call to another passes them on; the glue is
supplied them; the template never sees them; a read-only one is read-only in every member
- ludic migrate state: a component's members' needs go into its header (added to an existing one,
mut added where now changed); a field read or a member call inside a component is the compiler's,
so nothing is written inside a name and no ', )' is left; an entry point that declares states
already gets the rest after them
- a program's module named like a package gets <Name>AppState; a program's own file its own state;
a friend module's files go by directory; a package's settable var stays state
- it writes only under the programs and directories given (and runtime/ with --runtime), and
refuses the whole run naming any other file that would have to change
- a name a package already moved into its state is rewritten through it; a read of the runtime's
var through the runtime function that answers it (gl_w: gl_width())
- a state's instance supplied by the runtime is not a uses reference
- tests: state/component, rejected/state_component_ro, rendering/ui_render3d, migrate component
and foreign cases
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
c167ecc714
commit
ce80e64b24
23 changed files with 67967 additions and 61444 deletions
40
LANGUAGE.md
40
LANGUAGE.md
|
|
@ -249,6 +249,7 @@ function hips_of(h: Hiker) -> int { return h.hips }
|
||||||
{ ... }`, `@On(Ping) handler Heard(h: mut Hiker) { ... }`, `@OnSpawn(M) handler Made(h: mut
|
{ ... }`, `@On(Ping) handler Heard(h: mut Hiker) { ... }`, `@OnSpawn(M) handler Made(h: mut
|
||||||
Hiker) { ... }`, a scene's `on enter (h: mut Hiker) { ... }`, `test "name" (h: mut Hiker) { ... }`;
|
Hiker) { ... }`, a scene's `on enter (h: mut Hiker) { ... }`, `test "name" (h: mut Hiker) { ... }`;
|
||||||
- a retained `ui` block, which names a state's instance by the state's name: `font: Menu.title_font`;
|
- a retained `ui` block, which names a state's instance by the state's name: `font: Menu.title_font`;
|
||||||
|
- a component, whose header names its states: `component Tally (score: mut Score) { ... }`;
|
||||||
- a function value: `fn tick` of `function tick(h: mut Hiker, t: Tick)` is `tick` with its
|
- a function value: `fn tick` of `function tick(h: mut Hiker, t: Tick)` is `tick` with its
|
||||||
leading states supplied, a `fn(Tick) -> void` - so a system's functions, a port's bind and any
|
leading states supplied, a `fn(Tick) -> void` - so a system's functions, a port's bind and any
|
||||||
callback a package calls are entry points without saying so;
|
callback a package calls are entry points without saying so;
|
||||||
|
|
@ -287,15 +288,26 @@ each program and, from the compiler's own view of every name:
|
||||||
4. each function's states - those it touches, and those of everything it calls, to a fixed point -
|
4. each function's states - those it touches, and those of everything it calls, to a fixed point -
|
||||||
become its leading parameters, `mut` where it or something it calls writes; a state a run before
|
become its leading parameters, `mut` where it or something it calls writes; a state a run before
|
||||||
declared read-only becomes `mut` where it is now changed;
|
declared read-only becomes `mut` where it is now changed;
|
||||||
5. each call passes them on, and each entry point declares them.
|
5. each call passes them on, each entry point declares them (after any it declares already), and
|
||||||
|
each component's header declares what all its members need.
|
||||||
|
|
||||||
Give it every program at once - a directory stands for the test programs under it - and it merges
|
Give it every program at once - a directory stands for the test programs under it - and it merges
|
||||||
their plans before it edits anything: programs that share a package agree about it, a module two of
|
their plans before it edits anything: programs that share a package agree about it, a module two of
|
||||||
them see different files of is one state, and a path reached as `../../packages/x` is the same
|
them see different files of is one state, and a path reached as `../../packages/x` is the same file
|
||||||
file as `packages/x`. It prints what it cannot decide (a var read in another global's initializer, a
|
as `packages/x`. A program's own file keeps a state of its own (named for the program) whatever module it says, and
|
||||||
reference in generated code), for a person to finish. A later run finds the states an earlier one
|
a `friend module`'s other files go by their directory, since each program declares that module for
|
||||||
made and adds to them. `--runtime` moves the runtime's own vars too. gpp's packages and examples
|
itself. A package's var nothing in the given programs writes stays changing data (a game may set
|
||||||
were moved with one command:
|
`r3d_dem_path`); only a private one of a package's module becomes a `let`. A name the program uses
|
||||||
|
that a package migrated earlier moved into its state (`cam_pos`, now `Render3dState`'s) is rewritten
|
||||||
|
through that state, and a read of the runtime's own var from outside it through the runtime function
|
||||||
|
that answers it (`gl_w` is `gl_width()`). A program's own module named like a package (a game's `module fishing` beside
|
||||||
|
`ludic.fishing`) gets a state of its own, `FishingAppState` / `fishing_app_st`, never the package's.
|
||||||
|
It writes only under the programs and directories it is given (and `runtime/` with `--runtime`):
|
||||||
|
when the programs need a change in a file anywhere else - an installed package, a module imported
|
||||||
|
from a neighbouring directory - it says which and changes nothing. It prints what it cannot decide
|
||||||
|
(a var read in another global's initializer, a reference in generated code), for a person to finish.
|
||||||
|
A later run finds the states an earlier one made and adds to them. `--runtime` moves the runtime's
|
||||||
|
own vars too. gpp's packages and examples were moved with one command:
|
||||||
|
|
||||||
```
|
```
|
||||||
ludic migrate state packages <every example program> packages/ludic.lab/example/plate.ludic
|
ludic migrate state packages <every example program> packages/ludic.lab/example/plate.ludic
|
||||||
|
|
@ -589,6 +601,22 @@ component Counter {
|
||||||
files again when they change on disk and keeps every instance's state.
|
files again when they change on disk and keeps every instance's state.
|
||||||
- **A screen is a component** with no props: `ui_show("Counter", null, x, y, w, h)`, or
|
- **A screen is a component** with no props: `ui_show("Counter", null, x, y, w, h)`, or
|
||||||
`ui_nodes("Counter", null)` for a test.
|
`ui_nodes("Counter", null)` for a test.
|
||||||
|
- **States in the header.** A component that reads the program's data names the states it reads
|
||||||
|
and changes after its name, as an entry point does:
|
||||||
|
|
||||||
|
```ludic
|
||||||
|
# doc-check: skip — a fragment of a program that imports ludic.ui
|
||||||
|
component Tally (score: mut Score, look: Look) {
|
||||||
|
total: int = score.points
|
||||||
|
function big() -> bool { return total > look.big }
|
||||||
|
on add(n: int) { score.points += n }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Every getter, default, function and event takes them before the instance; a member's call to
|
||||||
|
another passes them on; ludic.ui's calls into the component are given the instances; and the
|
||||||
|
template never sees them - `on-click="add(5)"` passes only `5`. A header state without `mut` is
|
||||||
|
read-only in every member.
|
||||||
|
|
||||||
An older, lighter bridge remains: `view Name { ... }` gives a whole template file of `<screen>`s
|
An older, lighter bridge remains: `view Name { ... }` gives a whole template file of `<screen>`s
|
||||||
and `<component>`s (loaded with `ui_load`) one model and one call, and the rest of this section
|
and `<component>`s (loaded with `ui_load`) one model and one call, and the rest of this section
|
||||||
|
|
|
||||||
|
|
@ -25,3 +25,12 @@ Outside the runtime, the drawable's size and scale, the screen's framebuffer and
|
||||||
size are read and set through its functions (`gl_width()`, `gl_set_drawable(w, h)`,
|
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
|
`gl_screen_fbo()`, `gl_pixel_scale()`, `rt_map_tile_size()`), and ludic.ui's render3d backend
|
||||||
installs itself through the `UiRenderers` registry.
|
installs itself through the `UiRenderers` registry.
|
||||||
|
|
||||||
|
A component names the states its members read and change in its header, `component Tally (score:
|
||||||
|
mut Score, look: Look) { ... }`: every getter, default, function and event takes them before the
|
||||||
|
instance, a member's call to another passes them on, ludic.ui's calls into it are supplied them, and
|
||||||
|
the template never sees them. `ludic migrate state` writes those headers (and adds to an entry
|
||||||
|
point's or a component's existing list), gives a program's module named like a package a state of
|
||||||
|
its own (`FishingAppState`), keeps a package's settable vars as state, rewrites a program's
|
||||||
|
references to vars a package already moved into its state, and writes only under the programs and
|
||||||
|
directories it is given - a change needed anywhere else stops the run with the files named.
|
||||||
|
|
|
||||||
7
examples/rejected/state_component_ro.ludic
Normal file
7
examples/rejected/state_component_ro.ludic
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# 0.S: a component's states are read-only unless its header says mut
|
||||||
|
import "ludic.ui"
|
||||||
|
program StateComponentRo {
|
||||||
|
state Look { size: int = 1 }
|
||||||
|
import "state_component_ro/Shelf.ludic"
|
||||||
|
entry { print(1) }
|
||||||
|
}
|
||||||
3
examples/rejected/state_component_ro/Shelf.ludic
Normal file
3
examples/rejected/state_component_ro/Shelf.ludic
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
component Shelf (look: Look) {
|
||||||
|
on grow() { look.size += 1 }
|
||||||
|
}
|
||||||
1
examples/rejected/state_component_ro/Shelf.xml
Normal file
1
examples/rejected/state_component_ro/Shelf.xml
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<div><button on-click="grow()">+</button></div>
|
||||||
13
examples/rendering/ui_render3d.ludic
Normal file
13
examples/rendering/ui_render3d.ludic
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# ui_render3d.ludic - ludic.ui drawn by render3d's overlay: importing its backend is enough to
|
||||||
|
# draw templates (the suite builds it, so the backend always compiles against the renderer)
|
||||||
|
program UiRender3d {
|
||||||
|
numbers float
|
||||||
|
import "ludic.render3d/r3d.ludic"
|
||||||
|
import "ludic.ui"
|
||||||
|
import "ludic.ui/render3d.ludic"
|
||||||
|
entry (ui_st: mut UiState) {
|
||||||
|
ui_render3d(ui_st)
|
||||||
|
ui_render3d_scale(ui_st, 1.5)
|
||||||
|
print("ok")
|
||||||
|
}
|
||||||
|
}
|
||||||
33
examples/state/component.ludic
Normal file
33
examples/state/component.ludic
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
# component.ludic - 0.S: a component's members take the states its header names; the runtime
|
||||||
|
# supplies them when the template asks (Tally.ludic)
|
||||||
|
import "ludic.ui"
|
||||||
|
program StateComponent {
|
||||||
|
numbers float
|
||||||
|
state Score {
|
||||||
|
points: int = 0
|
||||||
|
bigs: int = 0
|
||||||
|
}
|
||||||
|
state Look {
|
||||||
|
prefix: string = "#"
|
||||||
|
big: int = 7
|
||||||
|
}
|
||||||
|
import "component_parts/Tally.ludic"
|
||||||
|
function all_text(n: UiNode) -> string {
|
||||||
|
var out = n.text
|
||||||
|
for i in 0 .. len(n.children) { out = out + all_text(n.children[i]) }
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
function frame(ui_st: mut UiState) -> UiNode {
|
||||||
|
let root: UiNode = ui_nodes(ui_st, "Tally", null)
|
||||||
|
ui_place(ui_st, root, 0.0, 0.0, 300.0, 200.0)
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
entry (ui_st: mut UiState, score: Score) {
|
||||||
|
var root = frame(ui_st)
|
||||||
|
ui_press(ui_st, root.children[0].children[1])
|
||||||
|
root = frame(ui_st)
|
||||||
|
ui_press(ui_st, root.children[0].children[1])
|
||||||
|
root = frame(ui_st)
|
||||||
|
print(`{all_text(root.children[0].children[0])} {score.points} {score.bigs}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
12
examples/state/component_parts/Tally.ludic
Normal file
12
examples/state/component_parts/Tally.ludic
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Tally.ludic - a component that reads and changes states: its header names them, every member
|
||||||
|
# takes them, and the template never sees them
|
||||||
|
component Tally (score: mut Score, look: Look) {
|
||||||
|
prop label: string
|
||||||
|
total: int = score.points
|
||||||
|
shown: string = `{look.prefix}{total}`
|
||||||
|
function big() -> bool { return total > look.big }
|
||||||
|
on add(n: int) {
|
||||||
|
score.points += n
|
||||||
|
if big() { score.bigs += 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
4
examples/state/component_parts/Tally.xml
Normal file
4
examples/state/component_parts/Tally.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<div>
|
||||||
|
<p>{label} {shown}{big() ? ' big' : ''}</p>
|
||||||
|
<button on-click="add(5)">+5</button>
|
||||||
|
</div>
|
||||||
|
|
@ -35,6 +35,9 @@ function vis_check(d: Node, what0: pointer) -> void {
|
||||||
deps_edge(d, what0) # LUDIC_DEPS: the graph ludic deps reads (emit_deps.ludic)
|
deps_edge(d, what0) # LUDIC_DEPS: the graph ludic deps reads (emit_deps.ludic)
|
||||||
if g_vis_off { return }
|
if g_vis_off { return }
|
||||||
if d.file == null { return }
|
if d.file == null { return }
|
||||||
|
# 0.S: a state's instance, supplied by the runtime (a component's or a view's glue, an entry's
|
||||||
|
# parameters): the code that names it only passes it on
|
||||||
|
if d.kind == N_VAR and d.uns == 1 and is_state_ty(d.ty) and len(d.s) > 6 and (d.s[0 .. 6] == "state$") { return }
|
||||||
let what = vis_plain(what0)
|
let what = vis_plain(what0)
|
||||||
var here = g_err_file
|
var here = g_err_file
|
||||||
if here == null { return }
|
if here == null { return }
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,11 @@ function ck_id(e: Node) -> pointer {
|
||||||
ck_vis(g, e.s, e)
|
ck_vis(g, e.s, e)
|
||||||
return g.ty
|
return g.ty
|
||||||
}
|
}
|
||||||
|
let fv = mg_field_var(e.s) # 0.S2: a var a package moved into its state
|
||||||
|
if fv != null {
|
||||||
|
mg_ref(e, fv)
|
||||||
|
return fv.ty
|
||||||
|
}
|
||||||
return "?"
|
return "?"
|
||||||
}
|
}
|
||||||
# --check: the module rules (uses, export, ports - emit_vis.ludic) asked here, where every reference
|
# --check: the module rules (uses, export, ports - emit_vis.ludic) asked here, where every reference
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,8 @@ function ck_let_from(e: Node, ty: pointer) -> void {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if li >= 0 { return }
|
if li >= 0 { return }
|
||||||
let g = ck_global(r.s)
|
var g = ck_global(r.s)
|
||||||
|
if g == null { g = mg_field_var(r.s) }
|
||||||
if g != null and g.kind == N_VAR { ck_alias[k] = g }
|
if g != null and g.kind == N_VAR { ck_alias[k] = g }
|
||||||
}
|
}
|
||||||
function ck_is_ro(li: int) -> bool {
|
function ck_is_ro(li: int) -> bool {
|
||||||
|
|
@ -90,7 +91,8 @@ function ck_write_check(t: Node, what: pointer) -> void {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let g = ck_global(r.s)
|
var g = ck_global(r.s)
|
||||||
|
if g == null { g = mg_field_var(r.s) }
|
||||||
mg_write(t, g)
|
mg_write(t, g)
|
||||||
if ck_module_let(g) {
|
if ck_module_let(g) {
|
||||||
var name = r.s
|
var name = r.s
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,7 @@ property Node {
|
||||||
pos2: int = -1 # 0.S: a second place a rewrite needs (a declaration's end, a '(')
|
pos2: int = -1 # 0.S: a second place a rewrite needs (a declaration's end, a '(')
|
||||||
mg: int = -1 # 0.S2: its index in the migration's tables (migrate.ludic)
|
mg: int = -1 # 0.S2: its index in the migration's tables (migrate.ludic)
|
||||||
vw: int = -1 # 0.S: the view a function was generated for or written in
|
vw: int = -1 # 0.S: the view a function was generated for or written in
|
||||||
|
cm: int = -1 # 0.S: the component a function was generated for or written in
|
||||||
}
|
}
|
||||||
|
|
||||||
# every node remembers where it was parsed (file + the line of the token the
|
# every node remembers where it was parsed (file + the line of the token the
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,11 @@
|
||||||
# on buy() { taps += 1 } # props and state are plain names here
|
# on buy() { taps += 1 } # props and state are plain names here
|
||||||
# }
|
# }
|
||||||
#
|
#
|
||||||
|
# 0.S: `component PauseMenu (clock: ClockState, s: mut Session) { ... }` - the states its members
|
||||||
|
# read and change. Every getter, default, function and event takes them before the instance, the
|
||||||
|
# glue's calls are supplied them by the runtime, a member's call to another passes them on, and
|
||||||
|
# the template never sees them.
|
||||||
|
#
|
||||||
# Each mounted instance is a record of its props and state. The compiler reads the template and the
|
# Each mounted instance is a record of its props and state. The compiler reads the template and the
|
||||||
# styles into the program, so a missing template fails the build and nothing more has to ship, and
|
# styles into the program, so a missing template fails the build and nothing more has to ship, and
|
||||||
# registers the component with ludic.ui when the program starts.
|
# registers the component with ludic.ui when the program starts.
|
||||||
|
|
@ -27,6 +32,8 @@ var g_cm_fnames: [][]pointer = new [][]pointer
|
||||||
var g_cm_calls: [][]Node = new [][]Node
|
var g_cm_calls: [][]Node = new [][]Node
|
||||||
var g_cm_cnames: [][]pointer = new [][]pointer
|
var g_cm_cnames: [][]pointer = new [][]pointer
|
||||||
var g_cm_head: []Node = new []Node
|
var g_cm_head: []Node = new []Node
|
||||||
|
var g_cm_hdr: []int = new []int # 0.S: where state parameters go (after the name)
|
||||||
|
var g_cm_ps: [][]Node = new [][]Node # the states it declares
|
||||||
|
|
||||||
function cm_prefix(name: pointer) -> pointer { return `cmp_{reg_lower(name)}_` }
|
function cm_prefix(name: pointer) -> pointer { return `cmp_{reg_lower(name)}_` }
|
||||||
|
|
||||||
|
|
@ -34,6 +41,8 @@ function cm_prefix(name: pointer) -> pointer { return `cmp_{reg_lower(name)}_` }
|
||||||
function parse_ui_component() -> void {
|
function parse_ui_component() -> void {
|
||||||
pi += 1
|
pi += 1
|
||||||
let name = eat_id()
|
let name = eat_id()
|
||||||
|
push(g_cm_hdr, toks[pi - 1].end)
|
||||||
|
push(g_cm_ps, parse_entry_params())
|
||||||
cm_unique(name)
|
cm_unique(name)
|
||||||
let px = cm_prefix(name)
|
let px = cm_prefix(name)
|
||||||
g_uses_value = true
|
g_uses_value = true
|
||||||
|
|
@ -79,6 +88,7 @@ function cm_member(c: int, px: pointer) -> void {
|
||||||
pi += 1
|
pi += 1
|
||||||
def = cm_getter(px + "def_" + fname, ty, expr())
|
def = cm_getter(px + "def_" + fname, ty, expr())
|
||||||
}
|
}
|
||||||
|
if def != null { def.cm = c }
|
||||||
push(g_cm_defs[c], def)
|
push(g_cm_defs[c], def)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -86,6 +96,7 @@ function cm_member(c: int, px: pointer) -> void {
|
||||||
let ev = is_id("on")
|
let ev = is_id("on")
|
||||||
if ev { toks[pi].text = "function" }
|
if ev { toks[pi].text = "function" }
|
||||||
let f = parse_fn()
|
let f = parse_fn()
|
||||||
|
f.cm = c
|
||||||
push(g_cm_cnames[c], f.s)
|
push(g_cm_cnames[c], f.s)
|
||||||
if ev { f.s = px + "on_" + f.s } else { f.s = px + f.s }
|
if ev { f.s = px + "on_" + f.s } else { f.s = px + f.s }
|
||||||
push(g_cm_calls[c], f)
|
push(g_cm_calls[c], f)
|
||||||
|
|
@ -101,7 +112,9 @@ function cm_member(c: int, px: pointer) -> void {
|
||||||
if not is_op("=") { perr(`component field {fname}: say what it is - {fname} = some_global`) }
|
if not is_op("=") { perr(`component field {fname}: say what it is - {fname} = some_global`) }
|
||||||
pi += 1
|
pi += 1
|
||||||
push(g_cm_fnames[c], fname)
|
push(g_cm_fnames[c], fname)
|
||||||
push(g_cm_fields[c], cm_getter(px + "get_" + fname, ty, expr()))
|
let g = cm_getter(px + "get_" + fname, ty, expr())
|
||||||
|
g.cm = c
|
||||||
|
push(g_cm_fields[c], g)
|
||||||
}
|
}
|
||||||
# a function of the instance that answers one expression (a field, or a default)
|
# a function of the instance that answers one expression (a field, or a default)
|
||||||
function cm_getter(name: pointer, ty: pointer, e: Node) -> Node {
|
function cm_getter(name: pointer, ty: pointer, e: Node) -> Node {
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,17 @@ function cm_infer(c: int, g: Node, fname: pointer) -> pointer {
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
# 0.S: a state's field, through the header (`purse = home.purse`)
|
||||||
|
if e.kind == E_MEMBER and e.a != null and e.a.kind == E_ID {
|
||||||
|
var j = 0
|
||||||
|
while j < len(g_cm_ps[c]) {
|
||||||
|
if (g_cm_ps[c][j].s == e.a.s) {
|
||||||
|
let r = find_comp(g_cm_ps[c][j].ty)
|
||||||
|
if r != null and field_index(r, e.s) >= 0 { return field_type(r, e.s) }
|
||||||
|
}
|
||||||
|
j += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
if e.kind == E_ID {
|
if e.kind == E_ID {
|
||||||
let d = find_global(e.s)
|
let d = find_global(e.s)
|
||||||
if d != null and d.ty != null { return d.ty }
|
if d != null and d.ty != null { return d.ty }
|
||||||
|
|
|
||||||
|
|
@ -55,10 +55,11 @@ function cm_call_src(c: int, px: pointer, rec: pointer, head: Node) -> pointer {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(calls) {
|
while i < len(calls) {
|
||||||
let f = calls[i]
|
let f = calls[i]
|
||||||
var args = "s"
|
var args = "s" # 0.S: its states are supplied, not passed
|
||||||
var k = 1
|
let lead = len(g_cm_ps[c]) + 1
|
||||||
|
var k = lead
|
||||||
while k < len(f.kids) {
|
while k < len(f.kids) {
|
||||||
args = args + ", " + cm_arg(c, f.kids[k], k - 1)
|
args = args + ", " + cm_arg(c, f.kids[k], k - lead)
|
||||||
k += 1
|
k += 1
|
||||||
}
|
}
|
||||||
src = src + ` if name == "{g_cm_cnames[c][i]}" {{\n`
|
src = src + ` if name == "{g_cm_cnames[c][i]}" {{\n`
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,26 @@ function cm_rewrite(c: int, f: Node, self_ty: pointer) -> void {
|
||||||
}
|
}
|
||||||
f.kids = kids
|
f.kids = kids
|
||||||
cm_rw(c, f.a)
|
cm_rw(c, f.a)
|
||||||
|
state_params_onto(f, g_cm_ps[c]) # 0.S: its states, before the instance
|
||||||
|
}
|
||||||
|
# a member's call to another passes its states on (they are its own parameters), then the instance
|
||||||
|
function cm_member_args(c: int, rest: []Node) -> []Node {
|
||||||
|
let args = new []Node
|
||||||
|
var i = 0
|
||||||
|
while i < len(g_cm_ps[c]) {
|
||||||
|
let id = node(E_ID)
|
||||||
|
id.s = g_cm_ps[c][i].s
|
||||||
|
id.pos = -1
|
||||||
|
push(args, id)
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
push(args, cm_self())
|
||||||
|
i = 0
|
||||||
|
while i < len(rest) {
|
||||||
|
push(args, rest[i])
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
return args
|
||||||
}
|
}
|
||||||
function cm_is_keep(c: int, s: pointer) -> bool {
|
function cm_is_keep(c: int, s: pointer) -> bool {
|
||||||
var i = 0
|
var i = 0
|
||||||
|
|
@ -73,20 +93,13 @@ function cm_rw(c: int, n: Node) -> void {
|
||||||
let callee = node(E_ID)
|
let callee = node(E_ID)
|
||||||
callee.s = cm_prefix(g_cm_name[c]) + "get_" + n.s
|
callee.s = cm_prefix(g_cm_name[c]) + "get_" + n.s
|
||||||
n.a = callee
|
n.a = callee
|
||||||
n.kids = new []Node
|
n.kids = cm_member_args(c, new []Node)
|
||||||
push(n.kids, cm_self())
|
n.pos = -1 # a call the compiler wrote: nothing in the source to edit
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if n.kind == E_CALL and n.a != null and n.a.kind == E_ID and cm_is_call(c, n.a.s) and not cm_is_keep(c, n.a.s) {
|
if n.kind == E_CALL and n.a != null and n.a.kind == E_ID and cm_is_call(c, n.a.s) and not cm_is_keep(c, n.a.s) {
|
||||||
n.a.s = cm_prefix(g_cm_name[c]) + n.a.s
|
n.a.s = cm_prefix(g_cm_name[c]) + n.a.s
|
||||||
let args = new []Node
|
n.kids = cm_member_args(c, n.kids)
|
||||||
push(args, cm_self())
|
|
||||||
var i = 0
|
|
||||||
while i < len(n.kids) {
|
|
||||||
push(args, n.kids[i])
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
n.kids = args
|
|
||||||
}
|
}
|
||||||
cm_rw(c, n.a)
|
cm_rw(c, n.a)
|
||||||
cm_rw(c, n.b)
|
cm_rw(c, n.b)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
# first var this program sees, or its declaration when it has one already: `more`)
|
# first var this program sees, or its declaration when it has one already: `more`)
|
||||||
# REF <file> <pos> <len> <text> a reference's name replaced
|
# REF <file> <pos> <len> <text> a reference's name replaced
|
||||||
# INS <file> <pos> <text> text inserted (a parameter, an argument)
|
# INS <file> <pos> <text> text inserted (a parameter, an argument)
|
||||||
|
# CLOSE <file> <pos> <text> text inserted before the first `)` after <pos>
|
||||||
# ? <file>:<line> <what> what the tool cannot decide, for a person
|
# ? <file>:<line> <what> what the tool cannot decide, for a person
|
||||||
var g_migrate: bool = false
|
var g_migrate: bool = false
|
||||||
var g_mg_runtime: bool = false # --migrate-runtime: the runtime's own vars too
|
var g_mg_runtime: bool = false # --migrate-runtime: the runtime's own vars too
|
||||||
|
|
@ -21,7 +22,8 @@ var g_mg_plan: pointer = null
|
||||||
var g_mg_vars: []Node = new []Node # the vars that move
|
var g_mg_vars: []Node = new []Node # the vars that move
|
||||||
var g_mg_vstate: []int = new []int # the state each moves into
|
var g_mg_vstate: []int = new []int # the state each moves into
|
||||||
var g_mg_written: []int = new []int # 1: something writes it
|
var g_mg_written: []int = new []int # 1: something writes it
|
||||||
var g_mg_let: []int = new []int # 1: a value nothing writes - it stays, as a module-level let
|
var g_mg_let: []int = new []int # 1: a value nothing writes - it stays, as a module-level let;
|
||||||
|
# 2: moved already (a package's, migrated before this program)
|
||||||
var g_mr_unit: []int = new []int # per reference: the unit it is read in (-1: none)
|
var g_mr_unit: []int = new []int # per reference: the unit it is read in (-1: none)
|
||||||
var g_ms_name: []pointer = new []pointer
|
var g_ms_name: []pointer = new []pointer
|
||||||
var g_ms_mod: []pointer = new []pointer
|
var g_ms_mod: []pointer = new []pointer
|
||||||
|
|
@ -91,7 +93,23 @@ function mg_state_of_file(f: pointer) -> int {
|
||||||
if len(b) > 6 and (b[len(b) - 6 .. len(b)] == ".ludic") { b = b[0 .. len(b) - 6] }
|
if len(b) > 6 and (b[len(b) - 6 .. len(b)] == ".ludic") { b = b[0 .. len(b) - 6] }
|
||||||
return mg_state_of_mod(`@{f}`, `rt_{b}`)
|
return mg_state_of_mod(`@{f}`, `rt_{b}`)
|
||||||
}
|
}
|
||||||
let m = module_for_uses(f)
|
if (f == g_mg_entry) { # the program's own file: its own state, whatever module
|
||||||
|
let p = mg_param_for(g_game_name)
|
||||||
|
return mg_state_of_mod("", p[0 .. len(p) - 3])
|
||||||
|
}
|
||||||
|
var m = module_for_uses(f)
|
||||||
|
# a friend module (`friend module lab`) is a view of the others each program declares for itself,
|
||||||
|
# not one module: its files' vars go by their directory, as a file in no module does
|
||||||
|
var fi = 0
|
||||||
|
while fi < len(g_mod_friends) {
|
||||||
|
if (g_mod_friends[fi] == m) { m = "" }
|
||||||
|
fi += 1
|
||||||
|
}
|
||||||
|
if not (m == "") and not (module_of(f) == "") and pkg_of_file(f) == "" and mg_pkg_named(mg_short(m)) {
|
||||||
|
# the program's own module named like a package's (a game's `module fishing` beside
|
||||||
|
# ludic.fishing): a state of its own, never merged into the package's
|
||||||
|
return mg_state_of_mod(m, mg_short(m) + "_app")
|
||||||
|
}
|
||||||
if not (m == "") { return mg_state_of_mod(m, mg_short(m)) }
|
if not (m == "") { return mg_state_of_mod(m, mg_short(m)) }
|
||||||
if (f == g_mg_entry) { # the program's own: SceneDemo is SceneDemoState, scene_demo_st
|
if (f == g_mg_entry) { # the program's own: SceneDemo is SceneDemoState, scene_demo_st
|
||||||
let p = mg_param_for(g_game_name)
|
let p = mg_param_for(g_game_name)
|
||||||
|
|
@ -166,6 +184,12 @@ function mg_state_key(s: int) -> pointer {
|
||||||
}
|
}
|
||||||
return `package:{m}`
|
return `package:{m}`
|
||||||
}
|
}
|
||||||
|
# is there a package called ludic.<short> - installed with the toolchain, or fetched into ludic_modules
|
||||||
|
function mg_pkg_named(short: pointer) -> bool {
|
||||||
|
if read_file(`{ludic_home()}packages/ludic.{short}/package.ludic`) != null { return true }
|
||||||
|
let mods = ensure_slash(getenv_or("LUDIC_MODULES", "ludic_modules"))
|
||||||
|
return read_file(`{mods}ludic.{short}/package.ludic`) != null
|
||||||
|
}
|
||||||
function mg_state_decl(name: pointer) -> Node {
|
function mg_state_decl(name: pointer) -> Node {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(prog) {
|
while i < len(prog) {
|
||||||
|
|
@ -190,6 +214,47 @@ function mg_is_target(v: Node) -> bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
# which vars move, and the state each moves into
|
# which vars move, and the state each moves into
|
||||||
|
# a name the program uses that is a field of a state now - a package migrated before it moved its
|
||||||
|
# var there (`cam_pos` is Render3dState's): a reference to that state's field, which the program is
|
||||||
|
# rewritten to reach through the state. Only a name exactly one state has.
|
||||||
|
var g_mg_fv_name: []pointer = new []pointer
|
||||||
|
var g_mg_fv_node: []Node = new []Node
|
||||||
|
function mg_field_var(name: pointer) -> Node {
|
||||||
|
if not g_migrate { return null }
|
||||||
|
var i = 0
|
||||||
|
while i < len(g_mg_fv_name) {
|
||||||
|
if (g_mg_fv_name[i] == name) { return g_mg_fv_node[i] }
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
var found: Node = null
|
||||||
|
var many = false
|
||||||
|
i = 0
|
||||||
|
while i < len(prog) {
|
||||||
|
let d = prog[i]
|
||||||
|
if d.kind == N_COMP and d.uns == 2 and field_index(d, name) >= 0 {
|
||||||
|
if found != null { many = true }
|
||||||
|
found = d
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
var v: Node = null
|
||||||
|
if found != null and not many {
|
||||||
|
v = node(N_VAR)
|
||||||
|
v.s = name
|
||||||
|
v.ty = field_type(found, name)
|
||||||
|
v.file = found.file
|
||||||
|
v.line = found.line
|
||||||
|
v.pos = -1
|
||||||
|
push(g_mg_vars, v)
|
||||||
|
push(g_mg_vstate, mg_state_named(found.s))
|
||||||
|
push(g_mg_written, 0)
|
||||||
|
push(g_mg_let, 2)
|
||||||
|
v.mg = len(g_mg_vars) - 1
|
||||||
|
}
|
||||||
|
push(g_mg_fv_name, name)
|
||||||
|
push(g_mg_fv_node, v)
|
||||||
|
return v
|
||||||
|
}
|
||||||
function mg_collect() -> void {
|
function mg_collect() -> void {
|
||||||
var end = g_prog_user_end
|
var end = g_prog_user_end
|
||||||
if g_mg_runtime { end = len(prog) } # the runtime's declarations come after the program's
|
if g_mg_runtime { end = len(prog) } # the runtime's declarations come after the program's
|
||||||
|
|
@ -283,15 +348,46 @@ function mg_decide_lets() -> void {
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < len(g_mg_vars) {
|
while i < len(g_mg_vars) {
|
||||||
let v = g_mg_vars[i]
|
let v = g_mg_vars[i]
|
||||||
if g_mg_written[i] == 0 and mg_value_ty(v.ty) and not mg_bound_var(v.s) { g_mg_let[i] = 1 }
|
if g_mg_let[i] == 0 and g_mg_written[i] == 0 and mg_value_ty(v.ty) and not mg_bound_var(v.s) and not mg_knob(v) { g_mg_let[i] = 1 }
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
var r = 0
|
var r = 0
|
||||||
while r < len(g_mr_node) {
|
while r < len(g_mr_node) {
|
||||||
if g_mr_node[r].pos < 0 { g_mg_let[g_mr_var[r]] = 0 }
|
if g_mr_node[r].pos < 0 and g_mg_let[g_mr_var[r]] == 1 { g_mg_let[g_mr_var[r]] = 0 }
|
||||||
r += 1
|
r += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
# a package's var a program may set (render3d's r3d_dem_path): nothing in these programs writes it,
|
||||||
|
# but a game using the package may - it stays changing data. A private one of a package's module is
|
||||||
|
# the package's own business.
|
||||||
|
function mg_knob(v: Node) -> bool {
|
||||||
|
let f = mg_norm(v.file)
|
||||||
|
let pkg = not (pkg_of_file(v.file) == "") or s_has_prefix(f, "packages/") or has_sub(f, "/packages/") or has_sub(f, "ludic_modules/")
|
||||||
|
if not pkg { return false }
|
||||||
|
if module_of(v.file) == "" { return true }
|
||||||
|
return v.vis == 1
|
||||||
|
}
|
||||||
|
function s_has_prefix(s: pointer, p: pointer) -> bool { return len(s) >= len(p) and (s[0 .. len(p)] == p) }
|
||||||
|
# the runtime's function that only answers its var `name` (`function gl_width() -> int { return gl_w }`)
|
||||||
|
function mg_runtime_reader(name: pointer) -> pointer {
|
||||||
|
var i = 0
|
||||||
|
while i < len(prog) {
|
||||||
|
let f = prog[i]
|
||||||
|
if f.kind == N_FN and is_runtime_file(f.file) and f.a != null and len(f.a.kids) == 1 and f.a.kids[0].kind == S_RETURN {
|
||||||
|
var np = 0
|
||||||
|
var j = 0
|
||||||
|
while j < len(f.kids) {
|
||||||
|
if f.kids[j].kind == N_PARAM and not is_state_ty(f.kids[j].ty) { np += 1 }
|
||||||
|
j += 1
|
||||||
|
}
|
||||||
|
let x = f.a.kids[0].a
|
||||||
|
if np == 0 and x != null and x.kind == E_MEMBER and x.a != null and x.a.kind == E_ID and (x.s == name) { return f.s }
|
||||||
|
if np == 0 and x != null and x.kind == E_ID and (mg_src_name(x.s) == name) { return f.s }
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
function mg_value_ty(t: pointer) -> bool {
|
function mg_value_ty(t: pointer) -> bool {
|
||||||
if t == null { return false }
|
if t == null { return false }
|
||||||
if (t == "int") or (t == "float") or (t == "fixed") or (t == "long") or (t == "bool") or (t == "string") or (t == "byte") { return true }
|
if (t == "int") or (t == "float") or (t == "fixed") or (t == "long") or (t == "bool") or (t == "string") or (t == "byte") { return true }
|
||||||
|
|
@ -306,10 +402,14 @@ function mg_read_needs() -> void {
|
||||||
let u = g_mr_unit[r]
|
let u = g_mr_unit[r]
|
||||||
let g = g_mg_vars[v]
|
let g = g_mg_vars[v]
|
||||||
if is_runtime_file(g.file) and not is_runtime_file(e.file) {
|
if is_runtime_file(g.file) and not is_runtime_file(e.file) {
|
||||||
# the runtime's state is the runtime's: from outside it is read through its functions
|
# the runtime's state is the runtime's: from outside it is read through its functions - the
|
||||||
mg_say(e, `{mg_src_name(g.s)} is the runtime's; read it through the runtime's function for it`)
|
# one that answers it, when there is one (gl_w: gl_width())
|
||||||
|
let rd = mg_runtime_reader(mg_src_name(g.s))
|
||||||
|
if rd != null and e.pos >= 0 and e.file != null {
|
||||||
|
mg_emit(`REF {e.file} {itoa(e.pos)} {itoa(len(mg_src_name(g.s)))} {mg_esc(`{rd}()`)}`, "")
|
||||||
|
} else { mg_say(e, `{mg_src_name(g.s)} is the runtime's; read it through the runtime's function for it`) }
|
||||||
g_mr_var[r] = -1 - v
|
g_mr_var[r] = -1 - v
|
||||||
} else if g_mg_let[v] == 0 and u != -3 {
|
} else if not (g_mg_let[v] == 1) and u != -3 {
|
||||||
if u >= 0 { mg_need(u, g_mg_vstate[v], false) }
|
if u >= 0 { mg_need(u, g_mg_vstate[v], false) }
|
||||||
else { mg_say(e, `{g.s} is read where no function is (a global's initializer); move it by hand`) }
|
else { mg_say(e, `{g.s} is read where no function is (a global's initializer); move it by hand`) }
|
||||||
if e.pos < 0 and not mg_bound_var(g.s) { mg_say(e, `a reference to {g.s} in generated code (a component, a view, a registry's lookup)`) }
|
if e.pos < 0 and not mg_bound_var(g.s) { mg_say(e, `a reference to {g.s} in generated code (a component, a view, a registry's lookup)`) }
|
||||||
|
|
@ -421,7 +521,7 @@ function mg_norm_line(line: pointer) -> pointer {
|
||||||
}
|
}
|
||||||
if len(w) < 2 { return line }
|
if len(w) < 2 { return line }
|
||||||
let k = w[0]
|
let k = w[0]
|
||||||
if (k == "VAR") or (k == "REF") or (k == "INS") or (k == "LET") { w[1] = mg_norm(w[1]) }
|
if (k == "VAR") or (k == "REF") or (k == "INS") or (k == "LET") or (k == "CLOSE") { w[1] = mg_norm(w[1]) }
|
||||||
if (k == "?") { w[1] = mg_norm(w[1]) }
|
if (k == "?") { w[1] = mg_norm(w[1]) }
|
||||||
if (k == "VAR") and len(w) > 5 { w[5] = mg_norm_key(w[5]) }
|
if (k == "VAR") and len(w) > 5 { w[5] = mg_norm_key(w[5]) }
|
||||||
if (k == "STATE") and len(w) > 6 {
|
if (k == "STATE") and len(w) > 6 {
|
||||||
|
|
@ -571,6 +671,11 @@ function mg_units() -> void {
|
||||||
var u = 0
|
var u = 0
|
||||||
while u < len(g_mu_node) {
|
while u < len(g_mu_node) {
|
||||||
let n = g_mu_node[u]
|
let n = g_mu_node[u]
|
||||||
|
if g_mu_kind[u] == 0 and n.cm >= 0 {
|
||||||
|
mg_comp_acc(u, n.cm) # a component's member: the component declares them
|
||||||
|
u += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
mg_mut_upgrades(u, n)
|
mg_mut_upgrades(u, n)
|
||||||
let need = mg_list(g_mu_need[u])
|
let need = mg_list(g_mu_need[u])
|
||||||
if len(need) > 0 {
|
if len(need) > 0 {
|
||||||
|
|
@ -624,20 +729,26 @@ function mg_unit_edit(u: int, n: Node, text: pointer) -> void {
|
||||||
if kind == 5 { body = n } # a scene's `on enter` block is its own body
|
if kind == 5 { body = n } # a scene's `on enter` block is its own body
|
||||||
if body != null and len(body.kids) > 0 and body.kids[0].tps != null and (body.kids[0].tps == "state") {
|
if body != null and len(body.kids) > 0 and body.kids[0].tps != null and (body.kids[0].tps == "state") {
|
||||||
# it declares some already: whatever it needs beyond them is for a person to add
|
# it declares some already: whatever it needs beyond them is for a person to add
|
||||||
|
# it declares some already: the rest go after them
|
||||||
let need = mg_list(g_mu_need[u])
|
let need = mg_list(g_mu_need[u])
|
||||||
var more = ""
|
var more = ""
|
||||||
|
var last: Node = null
|
||||||
var k = 0
|
var k = 0
|
||||||
while k < len(need) {
|
while k < len(need) {
|
||||||
var have = false
|
var have = false
|
||||||
var j = 0
|
var j = 0
|
||||||
while j < len(body.kids) and body.kids[j].tps != null and (body.kids[j].tps == "state") {
|
while j < len(body.kids) and body.kids[j].tps != null and (body.kids[j].tps == "state") {
|
||||||
if (body.kids[j].ty == g_ms_name[need[k]]) { have = true }
|
if (body.kids[j].ty == g_ms_name[need[k]]) { have = true }
|
||||||
|
last = body.kids[j]
|
||||||
j += 1
|
j += 1
|
||||||
}
|
}
|
||||||
if not have { more = more + " " + g_ms_name[need[k]] }
|
if not have { more = more + ", " + mg_param_text(u, need[k]) }
|
||||||
k += 1
|
k += 1
|
||||||
}
|
}
|
||||||
if len(more) > 0 { mg_say(n, `this entry point declares states already; add{more} to them by hand`) }
|
if len(more) > 0 {
|
||||||
|
if last == null or last.pos2 < 0 or last.file == null { mg_say(n, `this entry point declares states already; add{more} to them by hand`) }
|
||||||
|
else { mg_emit(`CLOSE {last.file} {itoa(last.pos2)} {mg_esc(more)}`, "") }
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if kind == 1 { mg_emit(`INS {n.file} {itoa(n.pos)} {mg_esc(`({text}) `)}`, `{n.file}@{itoa(n.pos)}`) }
|
if kind == 1 { mg_emit(`INS {n.file} {itoa(n.pos)} {mg_esc(`({text}) `)}`, `{n.file}@{itoa(n.pos)}`) }
|
||||||
|
|
@ -653,6 +764,58 @@ function mg_unit_edit(u: int, n: Node, text: pointer) -> void {
|
||||||
mg_emit(`INS {b.file} {itoa(b.pos2)} {mg_esc(`({text})`)}`, `{b.file}@{itoa(b.pos2)}`)
|
mg_emit(`INS {b.file} {itoa(b.pos2)} {mg_esc(`({text})`)}`, `{b.file}@{itoa(b.pos2)}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var g_mcm_need: []pointer = new []pointer # per component: the states its members need
|
||||||
|
var g_mcm_mut: []pointer = new []pointer
|
||||||
|
function mg_comp_acc(u: int, c: int) -> void {
|
||||||
|
while len(g_mcm_need) < len(g_cm_name) {
|
||||||
|
push(g_mcm_need, ",")
|
||||||
|
push(g_mcm_mut, ",")
|
||||||
|
}
|
||||||
|
let need = mg_list(g_mu_need[u])
|
||||||
|
var k = 0
|
||||||
|
while k < len(need) {
|
||||||
|
let key = `,{itoa(need[k])},`
|
||||||
|
if not has_sub(g_mcm_need[c], key) { g_mcm_need[c] = g_mcm_need[c] + itoa(need[k]) + "," }
|
||||||
|
if has_sub(g_mu_mut[u], key) and not has_sub(g_mcm_mut[c], key) { g_mcm_mut[c] = g_mcm_mut[c] + itoa(need[k]) + "," }
|
||||||
|
k += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# each component's header: the states its members need that it does not declare yet, and `mut` on
|
||||||
|
# one it declares read-only that a member now changes
|
||||||
|
function mg_comps() -> void {
|
||||||
|
var c = 0
|
||||||
|
while c < len(g_mcm_need) {
|
||||||
|
let need = mg_list(g_mcm_need[c])
|
||||||
|
let ps = g_cm_ps[c]
|
||||||
|
var text = ""
|
||||||
|
var k = 0
|
||||||
|
while k < len(need) {
|
||||||
|
let s = need[k]
|
||||||
|
let m = has_sub(g_mcm_mut[c], `,{itoa(s)},`)
|
||||||
|
var have: Node = null
|
||||||
|
var j = 0
|
||||||
|
while j < len(ps) {
|
||||||
|
if (ps[j].ty == g_ms_name[s]) { have = ps[j] }
|
||||||
|
j += 1
|
||||||
|
}
|
||||||
|
if have == null {
|
||||||
|
if len(text) > 0 { text = text + ", " }
|
||||||
|
if m { text = text + `{g_ms_param[s]}: mut {g_ms_name[s]}` } else { text = text + `{g_ms_param[s]}: {g_ms_name[s]}` }
|
||||||
|
} else if m and have.uns == 0 and have.pos2 >= 0 {
|
||||||
|
mg_emit(`INS {g_cm_file[c]} {itoa(have.pos2)} {mg_esc("mut ")}`, "")
|
||||||
|
}
|
||||||
|
k += 1
|
||||||
|
}
|
||||||
|
if len(text) > 0 {
|
||||||
|
if len(ps) == 0 { mg_emit(`INS {g_cm_file[c]} {itoa(g_cm_hdr[c])} {mg_esc(` ({text})`)}`, "") } else {
|
||||||
|
# after the last one it declares: its type's end is not kept, so before the `)` that follows
|
||||||
|
let last = ps[len(ps) - 1]
|
||||||
|
mg_emit(`CLOSE {g_cm_file[c]} {itoa(last.pos2)} {mg_esc(`, {text}`)}`, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
function mg_views() -> void {
|
function mg_views() -> void {
|
||||||
var v = 0
|
var v = 0
|
||||||
while v < len(g_mv_need) {
|
while v < len(g_mv_need) {
|
||||||
|
|
@ -680,7 +843,7 @@ function mg_calls() -> void {
|
||||||
let to = g_mc_callee[c]
|
let to = g_mc_callee[c]
|
||||||
let callee = g_mu_node[to]
|
let callee = g_mu_node[to]
|
||||||
let need = mg_list(g_mu_need[to])
|
let need = mg_list(g_mu_need[to])
|
||||||
if len(need) > 0 {
|
if len(need) > 0 and callee.cm < 0 { # a component's member is passed its states by the compiler
|
||||||
var text = ""
|
var text = ""
|
||||||
let passed = g_mc_passed[c] == 1
|
let passed = g_mc_passed[c] == 1
|
||||||
var k = 0
|
var k = 0
|
||||||
|
|
@ -711,7 +874,7 @@ function mg_refs() -> void {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let v = g_mg_vars[g_mr_var[r]]
|
let v = g_mg_vars[g_mr_var[r]]
|
||||||
if e.pos >= 0 and e.file != null and g_mg_let[g_mr_var[r]] == 0 {
|
if e.pos >= 0 and e.file != null and not (g_mg_let[g_mr_var[r]] == 1) {
|
||||||
let s = g_mg_vstate[g_mr_var[r]]
|
let s = g_mg_vstate[g_mr_var[r]]
|
||||||
let nm = mg_src_name(v.s)
|
let nm = mg_src_name(v.s)
|
||||||
var text = `{g_ms_param[s]}.{nm}`
|
var text = `{g_ms_param[s]}.{nm}`
|
||||||
|
|
@ -784,7 +947,7 @@ function mg_finish() -> void {
|
||||||
let v = g_mg_vars[i]
|
let v = g_mg_vars[i]
|
||||||
let vs = g_mg_vstate[i]
|
let vs = g_mg_vstate[i]
|
||||||
if g_mg_let[i] == 1 { mg_emit(`LET {v.file} {itoa(v.pos)}`, "") }
|
if g_mg_let[i] == 1 { mg_emit(`LET {v.file} {itoa(v.pos)}`, "") }
|
||||||
else { mg_emit(`VAR {v.file} {itoa(v.pos)} {itoa(v.pos2)} {g_ms_name[vs]} {anchor[vs]}`, "") }
|
else if g_mg_let[i] == 0 { mg_emit(`VAR {v.file} {itoa(v.pos)} {itoa(v.pos2)} {g_ms_name[vs]} {anchor[vs]}`, "") }
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
s = 0
|
s = 0
|
||||||
|
|
@ -808,6 +971,7 @@ function mg_finish() -> void {
|
||||||
mg_refs()
|
mg_refs()
|
||||||
mg_binds()
|
mg_binds()
|
||||||
mg_units()
|
mg_units()
|
||||||
|
mg_comps()
|
||||||
mg_views()
|
mg_views()
|
||||||
mg_calls()
|
mg_calls()
|
||||||
let f = file_open(g_mg_plan, "wb")
|
let f = file_open(g_mg_plan, "wb")
|
||||||
|
|
|
||||||
|
|
@ -1024,7 +1024,7 @@ function parse_one_decl() -> void {
|
||||||
if is_id("registry") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].text == "of") { parse_registry(); return } # L8
|
if is_id("registry") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].text == "of") { parse_registry(); return } # L8
|
||||||
if is_id("def") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].kind == TK_ID) { parse_def(); return } # L8
|
if is_id("def") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].kind == TK_ID) { parse_def(); return } # L8
|
||||||
if is_id("view") and (toks[pi + 1].kind == TK_ID) and ((toks[pi + 2].text == "{") or (toks[pi + 2].text == "(")) { parse_view(); return } # L11
|
if is_id("view") and (toks[pi + 1].kind == TK_ID) and ((toks[pi + 2].text == "{") or (toks[pi + 2].text == "(")) { parse_view(); return } # L11
|
||||||
if is_id("component") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].text == "{") { parse_ui_component(); return } # L11
|
if is_id("component") and (toks[pi + 1].kind == TK_ID) and ((toks[pi + 2].text == "{") or (toks[pi + 2].text == "(")) { parse_ui_component(); return } # L11
|
||||||
if is_id("state") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].text == "{") { parse_state(); return } # 0.S
|
if is_id("state") and (toks[pi + 1].kind == TK_ID) and (toks[pi + 2].text == "{") { parse_state(); return } # 0.S
|
||||||
if is_id("var") {
|
if is_id("var") {
|
||||||
if not g_allow_globals {
|
if not g_allow_globals {
|
||||||
|
|
|
||||||
64438
selfhost/ludicc.seed.ll
64438
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -13,6 +13,7 @@ var mgx_len: []int = null
|
||||||
var mgx_text: []pointer = null
|
var mgx_text: []pointer = null
|
||||||
var mgx_texts: []pointer = null # the files' texts, read once
|
var mgx_texts: []pointer = null # the files' texts, read once
|
||||||
var mgx_names: []pointer = null
|
var mgx_names: []pointer = null
|
||||||
|
var mgx_roots: []pointer = null # where it may write: the programs and directories it was given
|
||||||
|
|
||||||
function mgx_unesc(s: pointer) -> pointer {
|
function mgx_unesc(s: pointer) -> pointer {
|
||||||
var out = ""
|
var out = ""
|
||||||
|
|
@ -99,32 +100,85 @@ function mgx_close(t: pointer, at: int) -> int {
|
||||||
}
|
}
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
# a directory as the plans spell a path (relative to here, `..` resolved), with a trailing '/'
|
||||||
|
function mgx_dir(d: pointer) -> pointer {
|
||||||
|
var p = mgx_norm(d)
|
||||||
|
if len(p) == 0 or (p == ".") { return "" }
|
||||||
|
if p[len(p) - 1] != '/' { p = p + "/" }
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
function mgx_norm(p0: pointer) -> pointer {
|
||||||
|
var p = p0
|
||||||
|
let cwd = getenv_or("PWD", "")
|
||||||
|
if len(cwd) > 0 and s_starts(p, cwd + "/") { p = p[len(cwd) + 1 .. len(p)] }
|
||||||
|
let abs = len(p) > 0 and p[0] == '/'
|
||||||
|
let segs = new []pointer
|
||||||
|
var n = 0
|
||||||
|
var a = 0
|
||||||
|
for i in 0 .. len(p) + 1 {
|
||||||
|
if i == len(p) or p[i] == '/' {
|
||||||
|
let seg = p[a .. i]
|
||||||
|
if (seg == "..") and n > 0 and not (segs[n - 1] == "..") { n -= 1 }
|
||||||
|
else if not (seg == ".") and not (seg == "") {
|
||||||
|
if n < len(segs) { segs[n] = seg } else { push(segs, seg) }
|
||||||
|
n += 1
|
||||||
|
}
|
||||||
|
a = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var out = ""
|
||||||
|
if abs { out = "/" }
|
||||||
|
for i in 0 .. n {
|
||||||
|
if i > 0 { out = out + "/" }
|
||||||
|
out = out + segs[i]
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
# may it write this file: it is under one of the programs' directories (a package the program only
|
||||||
|
# imports - installed, or a neighbour's - is not the migration's to change)
|
||||||
|
function mgx_mine(path: pointer) -> bool {
|
||||||
|
let p = mgx_norm(path)
|
||||||
|
for i in 0 .. len(mgx_roots) {
|
||||||
|
if mgx_roots[i] == "" and not (p[0] == '/') and not s_starts(p, "../") { return true }
|
||||||
|
if not (mgx_roots[i] == "") and s_starts(p, mgx_roots[i]) { return true }
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
function cmd_migrate() -> int {
|
function cmd_migrate() -> int {
|
||||||
if arg_count() < 3 or not (arg(2) == "state") {
|
if arg_count() < 3 or not (arg(2) == "state") {
|
||||||
err("usage: ludic migrate state [file|dir...] [--runtime] [--unsafe] [--dry-run]\n")
|
err("usage: ludic migrate state [file|dir...] [--runtime] [--unsafe] [--dry-run]\n")
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
let srcs = new []pointer
|
let srcs = new []pointer
|
||||||
|
mgx_roots = new []pointer
|
||||||
var runtime = ""
|
var runtime = ""
|
||||||
var dry = false
|
var dry = false
|
||||||
var ai = 3
|
var ai = 3
|
||||||
while ai < arg_count() {
|
while ai < arg_count() {
|
||||||
let a = arg(ai)
|
let a = arg(ai)
|
||||||
if a == "--runtime" { runtime = " --migrate-runtime" }
|
if a == "--runtime" {
|
||||||
|
runtime = " --migrate-runtime"
|
||||||
|
push(mgx_roots, "runtime/")
|
||||||
|
}
|
||||||
else if a == "--unsafe" { g_unsafe_build = true }
|
else if a == "--unsafe" { g_unsafe_build = true }
|
||||||
else if a == "--dry-run" { dry = true }
|
else if a == "--dry-run" { dry = true }
|
||||||
else if a[0] == '-' { err(`ludic migrate: unknown option {a}\n`); return 2 }
|
else if a[0] == '-' { err(`ludic migrate: unknown option {a}\n`); return 2 }
|
||||||
else if shq(`test -d {sh_single(a)}`) {
|
else if shq(`test -d {sh_single(a)}`) {
|
||||||
let found = test_files_in(a)
|
let found = test_files_in(a)
|
||||||
for k in 0 .. len(found) { push(srcs, found[k]) }
|
for k in 0 .. len(found) { push(srcs, found[k]) }
|
||||||
|
push(mgx_roots, mgx_dir(a))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
push(srcs, a)
|
||||||
|
push(mgx_roots, mgx_dir(dir_of_path(a)))
|
||||||
}
|
}
|
||||||
else { push(srcs, a) }
|
|
||||||
ai += 1
|
ai += 1
|
||||||
}
|
}
|
||||||
if len(srcs) == 0 {
|
if len(srcs) == 0 {
|
||||||
let entry = find_entry("")
|
let entry = find_entry("")
|
||||||
if entry == "" { return no_entry() }
|
if entry == "" { return no_entry() }
|
||||||
push(srcs, entry)
|
push(srcs, entry)
|
||||||
|
push(mgx_roots, mgx_dir(dir_of_path(entry)))
|
||||||
}
|
}
|
||||||
ensure_ludicc()
|
ensure_ludicc()
|
||||||
# every program's plan from the source as it is, then one pass over the files: programs that
|
# every program's plan from the source as it is, then one pass over the files: programs that
|
||||||
|
|
@ -190,11 +244,21 @@ function mgx_apply(lines: []pointer, dry: bool) -> int {
|
||||||
let sexp = new []int
|
let sexp = new []int
|
||||||
let smore = new []int # 1: the state is already declared, and gains fields
|
let smore = new []int # 1: the state is already declared, and gains fields
|
||||||
let reports = new []pointer
|
let reports = new []pointer
|
||||||
|
let foreign = new []pointer # files a plan would change that the migration may not
|
||||||
let lfile = new []pointer
|
let lfile = new []pointer
|
||||||
let lpos = new []int
|
let lpos = new []int
|
||||||
for i in 0 .. len(lines) {
|
for i in 0 .. len(lines) {
|
||||||
let w = dp_words(lines[i])
|
let w = dp_words(lines[i])
|
||||||
if len(w) == 0 { continue }
|
if len(w) == 0 { continue }
|
||||||
|
var at = ""
|
||||||
|
if (w[0] == "VAR") or (w[0] == "REF") or (w[0] == "INS") or (w[0] == "CLOSE") or (w[0] == "LET") { at = w[1] }
|
||||||
|
if (w[0] == "STATE") and len(w) > 2 { at = w[2] }
|
||||||
|
if len(at) > 0 and mgx_roots != null and not mgx_mine(at) {
|
||||||
|
var seen = false
|
||||||
|
for k in 0 .. len(foreign) { if foreign[k] == at { seen = true } }
|
||||||
|
if not seen { push(foreign, at) }
|
||||||
|
continue
|
||||||
|
}
|
||||||
if w[0] == "VAR" and len(w) == 6 {
|
if w[0] == "VAR" and len(w) == 6 {
|
||||||
push(vfile, w[1])
|
push(vfile, w[1])
|
||||||
push(vpos, s_to_int(w[2]))
|
push(vpos, s_to_int(w[2]))
|
||||||
|
|
@ -228,6 +292,11 @@ function mgx_apply(lines: []pointer, dry: bool) -> int {
|
||||||
mgx_edit(w[1], s_to_int(w[2]), s_to_int(w[3]), mgx_unesc(w[4]))
|
mgx_edit(w[1], s_to_int(w[2]), s_to_int(w[3]), mgx_unesc(w[4]))
|
||||||
} else if (w[0] == "INS") and len(w) == 4 {
|
} else if (w[0] == "INS") and len(w) == 4 {
|
||||||
mgx_edit(w[1], s_to_int(w[2]), 0, mgx_unesc(w[3]))
|
mgx_edit(w[1], s_to_int(w[2]), 0, mgx_unesc(w[3]))
|
||||||
|
} else if (w[0] == "CLOSE") and len(w) == 4 {
|
||||||
|
let t = mgx_text_of(w[1])
|
||||||
|
var k = s_to_int(w[2])
|
||||||
|
while t[k] != 0 and t[k] != ')' { k += 1 }
|
||||||
|
mgx_edit(w[1], k, 0, mgx_unesc(w[3]))
|
||||||
} else if w[0] == "LET" and len(w) == 3 {
|
} else if w[0] == "LET" and len(w) == 3 {
|
||||||
push(lfile, w[1])
|
push(lfile, w[1])
|
||||||
push(lpos, s_to_int(w[2]))
|
push(lpos, s_to_int(w[2]))
|
||||||
|
|
@ -249,6 +318,23 @@ function mgx_apply(lines: []pointer, dry: bool) -> int {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
# a change the programs need in a file it may not write (a package's, a neighbour's): refused,
|
||||||
|
# whole - that code is migrated where it lives, or given here too
|
||||||
|
if len(foreign) > 0 {
|
||||||
|
err("ludic migrate: these files would have to change too, and are not under a program or directory given; nothing was changed:\n")
|
||||||
|
for f in 0 .. len(foreign) { err(` {foreign[f]}\n`) }
|
||||||
|
err("migrate them first (a package: in its own checkout), or name their directory as well\n")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
# a var whose state would be declared in a file it may not write: refused, whole
|
||||||
|
for v in 0 .. len(vfile) {
|
||||||
|
var ok = false
|
||||||
|
for k in 0 .. len(sat) { if sat[k] == vstate[v] { ok = true } }
|
||||||
|
if not ok {
|
||||||
|
err(`ludic migrate: {vfile[v]} has a var for the state of {vstate[v]}, which is declared where this migration may not write; nothing was changed\n`)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
# the vars: each line goes, and the first of each state's becomes the state
|
# the vars: each line goes, and the first of each state's becomes the state
|
||||||
let del_file = new []pointer
|
let del_file = new []pointer
|
||||||
let del_a = new []int
|
let del_a = new []int
|
||||||
|
|
|
||||||
|
|
@ -531,6 +531,51 @@ function migrate_case() -> void {
|
||||||
if ran == "2 names, first a" { ok(lbl) } else { bad2(lbl, `ran [{ran}]`) }
|
if ran == "2 names, first a" { ok(lbl) } else { bad2(lbl, `ran [{ran}]`) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# a program built headless with the toolchain and run: its first line
|
||||||
|
function headless_case(path: pointer, exp: pointer, label: pointer) -> void {
|
||||||
|
let out = `{tmp_dir()}/h_{flat(path)}`
|
||||||
|
if not shq(`bin/ludicc --headless examples/{path}.ludic -o {out} > {out}.log 2>&1`) { bad2(label, capture_line(`grep -i error {out}.log | head -1`)); return }
|
||||||
|
let got = capture_line(`{out} < /dev/null`)
|
||||||
|
if got == exp { ok(label) } else { bad2(label, `got [{got}] want [{exp}]`) }
|
||||||
|
}
|
||||||
|
# ludic migrate state and components: the header names what every member needs, a field read in a
|
||||||
|
# member is not edited, a module named like a package keeps a state of its own, and the program
|
||||||
|
# runs as it did
|
||||||
|
function migrate_component_case() -> void {
|
||||||
|
let lbl = "ludic migrate state: a component's header, a module named like a package, the same output"
|
||||||
|
let work = `{tmp_dir()}/migcomp`
|
||||||
|
shell(`rm -rf {work} && mkdir -p {work}/game/parts {work}/game/fishing && cp examples/state/component_parts/Tally.xml {work}/game/parts/`)
|
||||||
|
write_file(`{work}/game/parts/Tally.ludic`, "component Tally {\n prop label: string\n total: int = points\n shown: string = `{prefix}{total}`\n function big() -> bool { return total > 7 }\n on add(n: int) {\n points += n\n if big() { bigs += 1 }\n fish_cast()\n }\n}\n")
|
||||||
|
write_file(`{work}/game/fishing/index.ludic`, "module fishing\nvar casts: int = 0\nexport function fish_cast() -> void { casts += 1 }\nexport function fish_casts() -> int { return casts }\n")
|
||||||
|
var m = "import \"ludic.ui\"\nimport \"fishing\"\nprogram Mig {\n numbers float\n var points: int = 0\n var bigs: int = 0\n var prefix: string = \"#\"\n import \"parts/Tally.ludic\"\n"
|
||||||
|
m = m + " function frame(ui_st: mut UiState) -> UiNode {\n let root: UiNode = ui_nodes(ui_st, \"Tally\", null)\n ui_place(ui_st, root, 0.0, 0.0, 300.0, 200.0)\n return root\n }\n"
|
||||||
|
m = m + " entry (ui_st: mut UiState) {\n var root = frame(ui_st)\n ui_press(ui_st, root.children[0].children[1])\n root = frame(ui_st)\n ui_press(ui_st, root.children[0].children[1])\n root = frame(ui_st)\n"
|
||||||
|
m = m + " print(`{root.children[0].children[0].text} {points} {bigs} {fish_casts()}`)\n }\n}\n"
|
||||||
|
write_file(`{work}/game/main.ludic`, m)
|
||||||
|
let root = capture_line("pwd")
|
||||||
|
if not shq(`cd {work} && {root}/bin/ludic migrate state game/main.ludic > out.txt 2>&1`) { bad2(lbl, capture_line(`tail -1 {work}/out.txt`)); return }
|
||||||
|
let tally = read_file(`{work}/game/parts/Tally.ludic`)
|
||||||
|
if not s_contains(tally, "component Tally (fishing_app_st: mut FishingAppState, mig_st: mut MigState) {") or not s_contains(tally, "if big() { mig_st.bigs += 1 }") { bad2(lbl, `rewrote the component as [{tally}]`); return }
|
||||||
|
if not s_contains(read_file(`{work}/game/fishing/index.ludic`), "export state FishingAppState {") { bad2(lbl, "module fishing's state is not its own"); return }
|
||||||
|
if not shq(`cd {work} && {root}/bin/ludicc --headless game/main.ludic -o mig > cc.txt 2>&1`) { bad2(lbl, capture_line(`grep -i error {work}/cc.txt | head -1`)); return }
|
||||||
|
let ran = capture_line(`{work}/mig < /dev/null`)
|
||||||
|
if ran == " #10 big 10 1 2" { ok(lbl) } else { bad2(lbl, `ran [{ran}]`) }
|
||||||
|
}
|
||||||
|
# ludic migrate state never writes outside what it was given: a module a program imports from
|
||||||
|
# elsewhere that needs changing refuses the whole run, and nothing is written
|
||||||
|
function migrate_foreign_case() -> void {
|
||||||
|
let lbl = "ludic migrate state refuses to change a file outside the programs it was given"
|
||||||
|
let work = `{tmp_dir()}/migforeign`
|
||||||
|
shell(`rm -rf {work} && mkdir -p {work}/lib/extra {work}/game`)
|
||||||
|
write_file(`{work}/lib/extra/index.ludic`, "module extra\nvar n: int = 0\nexport function bump() -> void { n += 1 }\n")
|
||||||
|
let main = "import \"../lib/extra\"\nprogram F {\n entry { bump() }\n}\n"
|
||||||
|
write_file(`{work}/game/main.ludic`, main)
|
||||||
|
let root = capture_line("pwd")
|
||||||
|
if shq(`cd {work} && {root}/bin/ludic migrate state game/main.ludic > out.txt 2>&1`) { bad2(lbl, "it went ahead"); return }
|
||||||
|
if not shq(`grep -q 'lib/extra/index.ludic' {work}/out.txt`) { bad2(lbl, capture(`cat {work}/out.txt`)); return }
|
||||||
|
if not (read_file(`{work}/game/main.ludic`) == main) { bad2(lbl, "the program was changed anyway"); return }
|
||||||
|
ok(lbl)
|
||||||
|
}
|
||||||
# every package's own tests, the way a package author runs them: `ludic test packages`
|
# every package's own tests, the way a package author runs them: `ludic test packages`
|
||||||
function packages_test_case() -> void {
|
function packages_test_case() -> void {
|
||||||
let lbl = "ludic test packages: every package's tests pass"
|
let lbl = "ludic test packages: every package's tests pass"
|
||||||
|
|
@ -910,6 +955,11 @@ function cmd_dev_test() -> int {
|
||||||
reject_case("rejected/mut_not_state", "mut is for a state parameter", "mut is for a state parameter")
|
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/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/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/state_component_ro", "look is read-only here (look: Look)", "a component's state is read-only unless its header says mut")
|
||||||
|
controller_case("state/component", "", " #10 big 10 1", "component.ludic (0.S: a component's header names its states; its getters, functions and events take them and the template never sees them)")
|
||||||
|
headless_case("rendering/ui_render3d", "ok", "ui_render3d.ludic (ludic.ui's render3d backend builds against the renderer)")
|
||||||
|
migrate_component_case()
|
||||||
|
migrate_foreign_case()
|
||||||
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")
|
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()
|
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")
|
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")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue