plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


4299cfb — Peter John 2026-11-04T23:01:45+05:30
docs: language simplification run B plan
docs/plans/2026-11-04-language-simplification-b.md ADDED
@@ -0,0 +1,109 @@
1
+ # Language simplification, run B: one product type, `struct`
2
+
3
+ **Review plan:** docs/plans/2026-11-04-language-simplification.review.md
4
+ **Goal:** `record` and `struct` become one immutable product type `struct`. The compiler decides each struct's layout in one place (flat value vs boxed heap struct); `record` disappears from the grammar.
5
+ **Architecture:** tree-sitter grammar, plum-core parser/AST/lowering (embeds, ctors, traits), plum-checker, plum-wasm-codegen, plum-std, plum-examples.
6
+ **Tech stack:** Rust 2021, tree-sitter, Plum.
7
+ **Detail:** contracts
8
+ **Target-min:** 35
9
+
10
+ ## Global constraints
11
+ - Agents cannot run `cargo test` (hook): `cargo build --workspace --tests`, run binaries in `target/debug/deps` (newest by mtime). Own scratchpad subdir + CARGO_TARGET_DIR.
12
+ - Grammar edits: `tree-sitter generate --disable-optimizations`; update corpus.
13
+ - Gate for every task: all workspace test binaries pass; `plum test plum-std plum-examples` 65/65 files green (669 tests at start).
14
+ - Never run `plum format` on real files. Don't commit. Checkpoint Owns globs match ONE directory level; list nested dirs explicitly.
15
+ - Rulings (review-plan defaults, user may change): layout rule lives in ONE function — a struct is flat (today's `struct` layout) when it has no embedding, no `mut` field, is not recursive and all fields are flat-able (primitives or other flat structs); otherwise boxed (today's `record` layout). `--explain` prints each struct's layout. `==` keeps today's meaning per layout (field-wise for flat, identity for boxed) until the deferred deep-`==` item. `mut` fields stay (FBIP removes them later). Embedding and traits are untouched in this run (run C).
16
+
17
+ ## Task graph
18
+ ```mermaid
19
+ flowchart LR
20
+ T1[grammar accepts struct for both]
21
+ T2[one AST kind + layout fn]
22
+ T3[codegen uses layout fn]
23
+ T4[rename record to struct]
24
+ T5[remove record keyword]
25
+ T6[verify run B]
26
+ %% taskgraph
27
+ T1 --> T2
28
+ T2 --> T3
29
+ T3 --> T4
30
+ T4 --> T5
31
+ T5 --> T6
32
+ ```
33
+ Ruling: sequential - each step changes the same AST kind the next depends on - cost: no parallelism.
34
+
35
+ ## Package A: compiler
36
+ **Probe entry:** run=plum test plum-std plum-examples
37
+
38
+ ### T1 grammar accepts struct for both · Complexity 3 · Est. context 35k
39
+ **Owns:** plum-tooling/tree-sitter-plum/grammar.js, plum-tooling/tree-sitter-plum/src/*.c, plum-tooling/tree-sitter-plum/src/*.json, plum-tooling/tree-sitter-plum/test/corpus/*.txt, plum-core/src/parser.rs
40
+ **Components:** 1
41
+ **LOC owned:** 40
42
+ **Tests:** fast
43
+ **Model:** sonnet
44
+ **Produces:** `struct` accepts everything `record` accepts today (generics `struct Box[T](..)`, embedding, methods block, `mut` fields); `record` still parses as an alias
45
+ e.g. `struct Box[T](value: T)` parses (generic struct)
46
+ e.g. `struct H(Actor, k: Int)` parses (embedding)
47
+ **Test code:** corpus cases for both e.g. lines; existing corpus and parser tests green.
48
+
49
+ ### T2 one AST kind and the layout function · Complexity 5 · Est. context 50k
50
+ **Owns:** plum-core/src/ast.rs, plum-core/src/parser.rs, plum-core/src/embeds.rs, plum-core/src/ctors.rs, plum-core/src/traits.rs, plum-core/src/layout.rs, plum-core/src/lib.rs, plum-checker/src/lib.rs, plum-checker/src/unions.rs, plum-core/tests/layout_test.rs
51
+ **Components:** 2
52
+ **LOC owned:** 120
53
+ **Tests:** slow
54
+ **Model:** sonnet
55
+ **Consumes:** `struct` accepts everything `record` accepts today (generics `struct Box[T](..)`, embedding, methods block, `mut` fields); `record` still parses as an alias (T1)
56
+ **Produces:** ast::Enum gets one `isStruct: bool` replacing `isValue` + `isRecord` · layout::structLayout(e: &Enum, all: &[Enum]) -> Layout { Flat, Boxed }
57
+ e.g. `struct Vec2(x: Float, y: Float)` -> Flat
58
+ e.g. `struct Node(kids: List[Node])` -> Boxed (recursive)
59
+ e.g. `struct Walker(Actor, speed: Float)` -> Boxed (embedding)
60
+ **Review Focus:** every former `isValue` site now asks `structLayout(..) == Flat`, every former `isRecord` site asks `isStruct` (or `Boxed` where layout matters); no behaviour change for existing programs.
61
+ **Test code:** layout_test for the e.g. lines + a struct with a `mut` field -> Boxed; gates pass.
62
+
63
+ ### T3 codegen uses the layout function · Complexity 4 · Est. context 45k
64
+ **Owns:** plum-wasm-codegen/src/lib.rs, plum-cli/src/main.rs
65
+ **Components:** 1
66
+ **LOC owned:** 80
67
+ **Tests:** slow
68
+ **Model:** sonnet
69
+ **Consumes:** layout::structLayout(e: &Enum, all: &[Enum]) -> Layout { Flat, Boxed } (T2)
70
+ **Produces:** `--explain` prints `layout <Name>: flat|boxed` per struct
71
+ **Test code:** codegen tests + e2e_records pass; a CLI test (plum-cli/tests) for the `--explain` line.
72
+
73
+ ## Package B: std and examples
74
+ **Probe entry:** run=plum test plum-std plum-examples
75
+
76
+ ### T4 rename record to struct · Complexity 3 · Est. context 40k
77
+ **Owns:** plum-std/*.plum, plum-std/game/*.plum, plum-std/game/*/*.plum, plum-examples/*.plum, plum-examples/games/*/*.plum, plum-examples/games/*/*/*.plum, plum-wasm-codegen/tests/*.rs, plum-wasm-codegen/tests/e2e_types/*.rs, plum-wasm-codegen/tests/e2e_records/*.rs, plum-wasm-codegen/tests/e2e_tail/*.rs, plum-wasm-codegen/tests/e2e_mem/*.rs, plum-checker/tests/*.rs, plum-checker/tests/features/*.rs, plum-core/tests/*.rs, plum-cli/tests/*.rs, plum-lsp/src/*.rs, README.md
78
+ **Components:** 1
79
+ **LOC owned:** 60
80
+ **Tests:** slow
81
+ **Model:** sonnet
82
+ **Consumes:** layout::structLayout(e: &Enum, all: &[Enum]) -> Layout { Flat, Boxed } (T2)
83
+ **Produces:** none
84
+ **Test code:** no `record` keyword left in std, examples, embedded test sources, README; every renamed type keeps its old layout (compare `--explain` before/after for std + examples); gates pass.
85
+
86
+ ### T5 remove the record keyword · Complexity 2 · Est. context 30k
87
+ **Owns:** plum-tooling/tree-sitter-plum/grammar.js, plum-tooling/tree-sitter-plum/src/*.c, plum-tooling/tree-sitter-plum/src/*.json, plum-tooling/tree-sitter-plum/queries/plum/*.scm, plum-tooling/tree-sitter-plum/test/corpus/*.txt, plum-tooling/vscode-plum/syntaxes/*.json, plum-core/src/parser.rs
88
+ **Components:** 1
89
+ **LOC owned:** 30
90
+ **Tests:** slow
91
+ **Model:** sonnet
92
+ **Produces:** none
93
+ **Test code:** `record X(..)` is a plain parse error; corpus and gates pass.
94
+
95
+ ### T6 verify run B · Complexity 2 · Est. context 30k
96
+ **Owns:** docs/plans/2026-11-04-language-simplification-b.results.md
97
+ **Components:** 1
98
+ **LOC owned:** 40
99
+ **Tests:** slow
100
+ **Model:** sonnet
101
+ **Produces:** none
102
+ **Test code:** workspace binaries, plum test, plum-game host_test; layout of every std/example struct listed (flat vs boxed) and unchanged vs the start; Rust LOC per crate vs start.
103
+
104
+ ## Review Focus
105
+ - Package A: deep, state — layout decided in exactly one place; no program changes layout; `==` semantics unchanged.
106
+ - Package B: file — mechanical rename completeness.
107
+
108
+ ## Self-review
109
+ Covers run B of the review plan (one product type). Embedding-as-subtyping and trait removal are run C; Option/Result is run D.