Merge lang/machines e4265f5f: phase 27.1 - a state machine as data (@Machine(Record.field) on a registry of transitions: generated row reducers and tick, the field written only by its table, graph checks, schema machines); reseeded, syntax regenerated

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-30 16:39:26 +03:00
commit d7adc28125
34 changed files with 131221 additions and 109416 deletions

View file

@ -41,6 +41,7 @@
"annot-target",
"annot-column",
"annot-rowverb",
"annot-machine",
"annot-permap",
"annot-range",
"annot-ref",

View file

@ -269,8 +269,20 @@ function dp_fn_name(n: pointer) -> pointer {
}
return n
}
# 27.3: a row reducer's symbol says `Deer__in__Herd__deer` for `Deer in Herd.deer`
function dp_row_name(s: pointer) -> pointer {
# 27.3: a row reducer's symbol says `Deer__in__Herd__deer` for `Deer in Herd.deer`; one a @Machine
# wrote ends `__machine__DeerSteps` (27.1)
function dp_row_name(s0: pointer) -> pointer {
var s = s0
var tail = ""
var k = 0
while k + 11 <= slen(s0) {
if s0[k .. k + 11] == "__machine__" {
s = s0[0 .. k]
tail = ` (machine {s0[k + 11 .. slen(s0)]})`
break
}
k += 1
}
var out = ""
var seen = false
var i = 0
@ -287,7 +299,7 @@ function dp_row_name(s: pointer) -> pointer {
i += 1
}
}
return out
return out + tail
}
function dp_own(k: int) -> bool { return dp_pkg[k] == 0 }
# an edge the numbers count: between two of the program's own modules

View file

@ -30,6 +30,8 @@ function selfhost_frags() -> []pointer {
push(f, "selfhost/frontend/state.ludic")
push(f, "selfhost/frontend/actions.ludic")
push(f, "selfhost/frontend/actions_rows.ludic")
push(f, "selfhost/frontend/machines.ludic")
push(f, "selfhost/frontend/machines_write.ludic")
push(f, "selfhost/frontend/migrate.ludic")
push(f, "selfhost/frontend/registry.ludic")
push(f, "selfhost/frontend/registry_finish.ludic")

View file

@ -1252,6 +1252,17 @@ function cmd_dev_test() -> int {
reject_case("rejected/row_reducer_writes_state", "a row reducer writes its row alone, and n is a mut Noise", "a row reducer writes no state")
reject_case("rejected/rowverb_no_row", "@RowVerb scare: a row verb takes the row first", "a @RowVerb takes its row first")
reject_case("rejected/row_reducer_owner", "Herd is module herd's, and this reducer is in the program", "only the state's module reduces its table's rows")
feat_case("actions/machine", "", "0 0 1 1", "machine.ludic (27.1: @Machine - a state machine as data: the reducer on Spook, first match by guard, enter, and the tick's guard-only transitions)")
reject_case("rejected/machine_unreachable", "Mood.Fled is never reached from the start, Mood.Calm", "every state of a machine is reached from its start")
reject_case("rejected/machine_no_way_out", "Mood.Fled has no way out", "every state of a machine has a way out")
reject_case("rejected/machine_on_unknown", "on: \"Spok\" names no action", "a machine's on names an action")
reject_case("rejected/machine_on_no_target", "on: \"Shout\" names no row", "a machine's action names its row with @Target")
reject_case("rejected/machine_self_no_guard", "a transition from Wary to itself with no guard", "a self-transition has a guard")
reject_case("rejected/machine_ambiguous", "settles and stays both leave Wary on Spook, and settles has no guard", "one trigger, one unguarded way out")
reject_case("rejected/machine_guard_writes", "guard deer_nervous asks and changes nothing", "a machine's guard writes nothing through its row")
reject_case("rejected/machine_field_written", "Deer.mood is the machine DeerSteps's (@Machine(Deer.mood))", "a machine's field changes only by a transition in its table")
reject_case("rejected/machine_not_registry", "@Machine(Deer.mood) marks the registry of a machine's transitions", "@Machine is on a registry")
reject_case("rejected/machine_not_enum", "Deer.mood is int - a machine's state is a plain enum", "a machine's state is an enum field")
feat_case("state/counter", "", "11 3 heard 5", "counter.ludic (0.S: a state, mut and read-only parameters, entry, listener and fn-value injection)")
feat_case("state/port_field", "", "40", "port_field.ludic (0.S: a port member bound to a state's field)")
spec_case("state/tested", "== 2 passed, 0 failed ==")
@ -1373,6 +1384,7 @@ function cmd_dev_test() -> int {
schema_case("actions/modules", "{\"action\": \"PickUp\", \"module\": null, \"at\": \"examples/actions/modules.ludic:8:21\"}", "--emit-schema: code.dispatch - every dispatch site")
schema_case("actions/rows", "{\"record\": \"Deer\", \"table\": \"Herd.deer\", \"state\": \"Herd\", \"action\": \"Spook\", \"target\": \"who\", \"predicted\": false, \"net\": null, \"module\": null, \"at\": \"examples/actions/rows.ludic:17:32\"}", "--emit-schema: code.row_reducers - a row reducer's record, table, action and target")
schema_case("actions/rows", "{\"name\": \"deer_move\", \"record\": \"Deer\", \"module\": null, \"at\": \"examples/actions/rows.ludic:24:21\"}", "--emit-schema: code.row_verbs - a @RowVerb and its record")
schema_case("actions/machine", "{\"registry\": \"DeerSteps\", \"record\": \"Deer\", \"field\": \"mood\", \"enum\": \"Mood\", \"table\": \"Herd.deer\", \"start\": \"Calm\", \"states\": [\"Calm\", \"Wary\", \"Fled\"], \"actions\": [\"Spook\"], \"tick\": \"deer_steps_tick\", \"module\": null, \"at\": \"examples/actions/machine.ludic:28:32\"}", "--emit-schema: code.machines - a @Machine's registry, field, enum, table, start, actions and tick")
schema_case("actions/rows", "{\"name\": \"Spook\", \"module\": null, \"at\": \"examples/actions/rows.ludic:14:10\", \"target\": \"who\"}", "--emit-schema: code.actions - an action's @Target field")
schema_case("modules/ports", "{\"name\": \"Clock\", \"module\": \"clock\", \"at\": \"examples/modules/station/clock/index.ludic:4:13\", \"bound\": true, \"members\": [{\"name\": \"now\", \"type\": \"fn()->int\", \"default\": null, \"required\": true}, {\"name\": \"day\", \"type\": \"fn()->int\", \"default\": \"fn first_day\", \"required\": false}]}", "--emit-schema: code.ports - a port's members, their fn types and defaults")
schema_case("modules/ports", "{\"port\": \"Clock\", \"member\": \"now\", \"fn\": \"game_hours\", \"value\": \"fn game_hours\", \"module\": \"app\", \"at\": \"examples/modules/station/app/index.ludic:3:14\"}", "--emit-schema: code.binds - a bind's member, the function it names and where")

View file

@ -84,10 +84,10 @@ static const char* LUDIC_PHASES[] = {
static const char* LUDIC_ATTRIBUTES[] = {
"Ref","OneOf","Range","Unit","Asset","Color","Node","Clip","Material","Tint","Derived","Text",
"Multiline","Key","Computed","Sync","frame","max","owns","Target","Column","AppendOnly","ByKey",
"PerMap","Chunked","TextKey","export","Public","On","Queries","OnSpawn","OnDespawn","OnAttach",
"OnDetach","OnEnable","OnDisable","OnStart","OnQuit","Owned","Server","Predicted","ToServer",
"ToClients","System","EngineSystem","Namespace","deterministic","ClearColor","alloc_ok",
"creates","releases","RowVerb","Handles","Reads","Writes", 0
"PerMap","Chunked","Machine","TextKey","export","Public","On","Queries","OnSpawn","OnDespawn",
"OnAttach","OnDetach","OnEnable","OnDisable","OnStart","OnQuit","Owned","Server","Predicted",
"ToServer","ToClients","System","EngineSystem","Namespace","deterministic","ClearColor",
"alloc_ok","creates","releases","RowVerb","Handles","Reads","Writes", 0
};
/* ludic-dev syntax: end */
static const char* LUDIC_WIDGETS[] = {