plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
cdfddc4
— Peter John
2026-11-05T10:50:07+05:30
decisions: run D uses Option/Result with postfix ? as the only sugar
docs/backlog.md
CHANGED
|
@@ -8,7 +8,7 @@ Open work, in the order the user queued it, plus smaller items found during buil
|
|
|
8
8
|
- **Explicit function generics** (user 2026-11-04: right after cleanup phase 2) — functions declare their generics (`fun map[B](...)`) instead of every single capital letter being a generic; allows longer generic names and one-letter concrete types/tags. Grammar change + about 140 signatures in std/examples. Phase 2's "generics by scope" makes this mostly a grammar + signature rewrite.
|
|
9
9
|
- **Io as a built-in primitive with extern methods** (user 2026-11-04: LAST, once the whole compiler refactor and explicit generics are done) — `docs/plans/2026-11-04-io-primitive-extern-methods.review.md`. `primitive Io` with `extern fun f(self, ..)` methods replaces the ~55 top-level externs + forwarding `record Io` methods. A fake/recording `Io` comes later via a trait or language mocks.
|
|
10
10
|
- **Move the Rust code into `compiler/`** (user 2026-11-04: once all the refactoring is done) — every Rust crate (plum-core, plum-checker, plum-wasm-codegen, plum-cli, plum-lsp, plum-host, plum-test-support, plum-runtime, plum-game) goes under `compiler/`; `plum-std` and `plum-examples` stay at the top level. Mechanical but touches hardcoded paths: workspace `Cargo.toml` members, every `CARGO_MANIFEST_DIR/..` repoRoot (test-support, tests, cli), the loader's std lookup, the checker's `isStdFile`/`isIoFile` path tests, `cmdBuild`'s runtime manifest path, plum-game web-test fixtures, scripts/, Makefile.toml, plum-tooling (vscode/tree-sitter paths), README and docs links. Open question for then: keep the `plum-` prefixes inside `compiler/` or drop them (`compiler/core`, …).
|
|
11
|
-
2. **Simplify the language: no traits, no union enums, native inline variants** (decided by the user 2026-11-04; runs right after compiler-cleanup phase 3a, before explicit generics and 3b; decision record §13). Traits are removed as a language feature and replaced by records embedding records with subtyping + `override` dispatch (a `Ship` embedding `Node` is accepted wherever a `Node` is, and calls dispatch to Ship's overrides — the scene graph's `trait Actor` becomes a base record). Union enums (`enum X = A | B` of records) are removed: `JsonParse` and the union examples become inline-variant enums, union syntax becomes an error. Inline variants compile natively as struct subtypes of their enum, deleting `hoistInlineVariants`, the `List__Cons(Cons(..))` wrappers, union forwarders, `union_lower` and the checker's union plan. `unboxed_variants.plum` is a compiler probe: move it to a codegen test. Same plan also covers (user 2026-11-04): `=` reassign-only, no generators (`fun*`/`yield`; Seq deleted, one loop form `for x := range v` over Int or any `next(self) -> Pair[T, Self]?`), no variadics (`...T`; `[..]` literal → `Cons`/`Nil`, `[k => v]` → `Map.empty().with(..)`), no value enums, `record` merged into `struct` now, and **run D: error sets / `T!E` / `T?` / `null` replaced by std `Option[T]` and `Result[T, E]` enums**,
|
|
11
|
+
2. **Simplify the language: no traits, no union enums, native inline variants** (decided by the user 2026-11-04; runs right after compiler-cleanup phase 3a, before explicit generics and 3b; decision record §13). Traits are removed as a language feature and replaced by records embedding records with subtyping + `override` dispatch (a `Ship` embedding `Node` is accepted wherever a `Node` is, and calls dispatch to Ship's overrides — the scene graph's `trait Actor` becomes a base record). Union enums (`enum X = A | B` of records) are removed: `JsonParse` and the union examples become inline-variant enums, union syntax becomes an error. Inline variants compile natively as struct subtypes of their enum, deleting `hoistInlineVariants`, the `List__Cons(Cons(..))` wrappers, union forwarders, `union_lower` and the checker's union plan. `unboxed_variants.plum` is a compiler probe: move it to a codegen test. Same plan also covers (user 2026-11-04): `=` reassign-only, no generators (`fun*`/`yield`; Seq deleted, one loop form `for x := range v` over Int or any `next(self) -> Pair[T, Self]?`), no variadics (`...T`; `[..]` literal → `Cons`/`Nil`, `[k => v]` → `Map.empty().with(..)`), no value enums, `record` merged into `struct` now, and **run D: error sets / `T!E` / `T?` / `null` replaced by std `Option[T]` and `Result[T, E]` enums**, with postfix `?` as the ONLY sugar (revised 2026-11-05: `try`, `?:`, `?.`, `catch`, `T?`, `E!T`, `null` all removed; runs right after run C; decision record §13 item 1). Runs A (in progress), B (struct), C (traits/unions), D (Option/Result).
|
|
12
12
|
3. **Benchmark** before FBIP (user: FBIP waits for it).
|
|
13
13
|
4. **FBIP: no heap mutation** — persistent std collections updated in place when unique, Koka-style `fip` functions as a checked in-place guarantee, a reference count used only as a uniqueness check if static proof falls short. Worklist = the `mut` fields left today (see below). (Decision record §9.)
|
|
14
14
|
5. **One product type, `struct`** — after FBIP, merge `record` into an immutable `struct` with compiler-chosen boxing (optional hint, shown by `--explain`). (Decision record §9b.)
|
|
@@ -23,7 +23,6 @@ Open work, in the order the user queued it, plus smaller items found during buil
|
|
|
23
23
|
- **Language ideas to discuss later** (user 2026-11-05: "add them ideas to be discussed later"):
|
|
24
24
|
- **Single inheritance only** (user: "makes sense"). After run C only the FIRST embedded struct makes a subtype; later embeds only copy fields/methods. Drop the later embeds and use a plain field (`ship: Ship`) instead: one rule, and most of the field splicing in `embeds.rs` goes.
|
|
25
25
|
- **One way to set up a struct.** Drop `fun init(self)` in favour of field defaults + ordinary constructor functions. `init` is what forces the Boxed layout and the EmbedInit machinery in `ctors.rs`. Main user: `std/game/imperative/Scene.plum`.
|
|
26
|
-
- **Error syntax down to one form** (user 2026-11-05: "just keeping try or ?, rest syntax can be removed"). Decide before writing run D's plan: one propagation form (postfix `?` recommended) on `Option`/`Result`; `?:` → `unwrapOr(..)`, `?.` → `?` or `map`, `catch` → `match`, `E!T` → `Result[T, E]`; `T?` possibly kept as type shorthand for `Option[T]`.
|
|
27
26
|
- **From Pony:** (1) a two-level ownership model (unique + mutable vs shared + immutable, like Pony's `iso`/`val` with `consume`) as the visible face of FBIP, replacing `mut` fields — take into the FBIP plan; (2) no ambient authority: no globals, a function without `Io` provably does no IO — what deterministic sim needs; (3) Pony partial functions (`?`, no error value) argue for `Option` + `?` as the common path and `Result` only when the error value matters. Not copied: union types, structural interfaces, six capabilities.
|
|
28
27
|
|
|
29
28
|
- **Self-hosting groundwork** (user 2026-11-04: plan now, build later) — `docs/plans/2026-11-04-self-hosting-groundwork.review.md`, design in `docs/self-hosting.md`. All host I/O onto `Io` (`Os.plum` deleted, `io.now()`, `io.httpGet`), then a Plum lexer, a winnow-style combinator library with chumsky-style errors, an AST and a parser in `compiler/` that parses every `.plum` file. AST comparison with the Rust parser and new host calls (args, exit, env, stderr, binary files) wait for cleanup phase 2a. The `Io` move should land before the `Io`-primitive plan above.
|
docs/decisions/2026-11-language-and-scene-decisions.md
CHANGED
|
@@ -130,6 +130,7 @@ The user decided to simplify the language before the compiler is refactored furt
|
|
|
130
130
|
- This supersedes §11's "has traits" and the enum/union split in §8b.
|
|
131
131
|
- **Same plan, also decided 2026-11-04** (user: "1 to 6 all can be done"):
|
|
132
132
|
1. Errors (revised later the same day, user: "Yes please"): error sets, `T!E` and `T?` as special types are REMOVED in favour of two ordinary std enums, `Option[T] = None | Some(value: T)` and `Result[T, E] = Ok(value: T) | Err(error: E)` with `E` an ordinary enum. `null` is removed (→ `None`). The syntax stays as parser sugar: `T?` → `Option[T]`, `E!T` / `error{..}!T` → `Result[T, E]` (an inline set becomes an ordinary enum), `try e` → early-return on `Err`/`None`, `a ?: b` → `unwrapOr`, `x?.f` → `map`, `catch` → `match`. No automatic error-set merging: different error enums meet through `mapErr`. This reverses the 2026-10-25 move away from Option/Result, deliberately, to remove error-set types and inference from the checker. Runs as run D, after A-C.
|
|
133
|
+
- **Revised 2026-11-05** (user: "just move to Option/Result as return types and use one operation ? as syntax sugar", run right after run C): the ONLY sugar is postfix `?` on an `Option`/`Result` value inside a function returning `Option`/`Result` (early-return on `None`/`Err`, like Rust). Return types are written `Option[T]` / `Result[T, E]`. Removed as plain parse errors: `try`, `?:` (→ `.unwrapOr(..)`), `?.` (→ `?` then `.f`, or `.map(..)`), `catch` (→ `match`), `T?` and `E!T` type sugar, `error{..}` inline sets (→ an ordinary enum), `null` (→ `None`). `crash` stays (it is a panic, not error handling).
|
|
133
134
|
2. `=` only reassigns; `:=` is the only declaration (no declare-on-first-use).
|
|
134
135
|
3. Generators (`fun*`/`yield`) are removed; `Seq` and their std/example users (Seq.plum, Map, Int, List, functional/sequences examples) are rewritten on plain functions/loops.
|
|
135
136
|
4. Variadics `...T` are removed; callers pass a list/slice literal (`List.init`, `Map(...)`).
|