← Commit history

chore: snapshot hearth layout, examples and front end before frontend consolidation

lennoxrose committed on Oct 18, 2026 at 00:32 UTC

0cda2daparent83fcbf6

300 files changed+10365−348

.gitignoremodified

+14−12
@@ -7,20 +7,20 @@
77 @ash-modules/
88
99 # Compiled benchmark binaries (keep the .cpp sources, ignore the built outputs)
10-ashvm/benchmarks/fib_cpp
11-ashvm/benchmarks/loop_cpp
12-ashvm/benchmarks/countdown_cpp
13-ashvm/benchmarks/hashmap_cpp
14-ashvm/benchmarks/native_jit
15-pyre/benchmarks/*/run_benchmark
16-pyre/tests/matrix_multiply_test
17-pyre/tests/monte_carlo_test
18-pyre/tests/cuda_test
19-pyre/src/web/node_modules/
10+hearth/ashvm/benchmarks/fib_cpp
11+hearth/ashvm/benchmarks/loop_cpp
12+hearth/ashvm/benchmarks/countdown_cpp
13+hearth/ashvm/benchmarks/hashmap_cpp
14+hearth/ashvm/benchmarks/native_jit
15+hearth/pyre/benchmarks/*/run_benchmark
16+hearth/pyre/tests/matrix_multiply_test
17+hearth/pyre/tests/monte_carlo_test
18+hearth/pyre/tests/cuda_test
19+hearth/pyre/src/web/node_modules/
2020 # Generated from src/shaders/*.comp by glslc -mfmt=c (pyre/Makefile) --
2121 # the .comp source is tracked, the embedded-SPIR-V .h is not, same as
2222 # any other object code.
23-pyre/src/H/backends/vulkan/*_spv.h
23+hearth/pyre/src/H/backends/vulkan/*_spv.h
2424
2525 # OS / editor cruft
2626 .DS_Store
@@ -33,11 +33,13 @@ AGENTS.md
3333 .claude
3434 .agents
3535 skills-lock.json
36+docs/*
37+spec/
3638
3739 # Private Notes
3840 goals.md
3941 rundown.md
4042 missing.md
4143 test.ash
4244 # nvcc dependency files list this machine's absolute CUDA install paths; generated by the build, never committed
43-pyre/src/D/backends/cuda/*.d
45+hearth/pyre/src/D/backends/cuda/*.d

CHANGELOG.mdmodified

+71−0
@@ -1,3 +1,74 @@
1+## 2026-10-17 (collector: layout-noise floor measured)
2+- Two collector-free builds of the same source differing only in `-falign-functions=64` differ by up to 0.7% on the baseline benchmarks (ratios 1.000, 0.993, 1.000, 0.998, 1.000); the collector build measures 1.007, 1.004, 1.002, 1.003, 0.995 against the same base. The residue is inside the layout-noise band but not provably zero (hashmap, the most allocation-heavy, is at its edge), so `GC ?= 0` stays; enabling it is a one-line change for the owner.
3+
4+## 2026-10-17 (collector: nothing on the hot paths)
5+- Registration is inlined and packed (one tagged pointer per object), mark bits and the lookup table exist only during a collection, and the safe points moved from `OP_CALL`/`OP_CALL_VALUE`/`OP_LOOP` to the start of the opcodes that allocate (string concat in `OP_ADD` and `OP_ACC_LOCAL`, `OP_ARRAY`, `OP_MAP`, string `OP_INDEX_GET`, `OP_MAKE_CLOSURE`, `OP_CALL_BUILTIN`). Calls, loops and arithmetic now run exactly the instructions they run without the collector.
6+- Measured (median of 12 alternating runs, collector build / collector-free build): hashmap 1.008, fib 1.006, loop 0.998, countdown 1.007, loop_varbound 1.000 (it was 1.13 for the first version and 1.02 for the second). Churn program 212 MB -> 47 MB, with only 8,011 objects live at exit from 12M allocated. Stress equality on all 49 `ashvm` cases and on 49 of 49 generated-code checks.
7+- Still opt-in (`make GC=1`): the residue is at the level of code-layout noise but is not provably zero, and the performance rule is absolute.
8+
9+## 2026-10-17 (collector: registration made nearly free, still opt-in)
10+- The collector no longer hashes on every allocation: objects are appended to a flat registry and the pointer table is built only when a collection runs; the first collection happens after 200k registered objects and then at twice the live count. Cost with the collector compiled in, measured as the median of 10 alternating runs against the collector-free build: 1.020 (`hashmap`, the most allocation-heavy benchmark; it was 1.13 before this change), 1.003 (`fib`), 1.008 (`loop`). Churn program: 212 MB -> 47 MB. Stress equality (collect at every safe point) still holds on all 49 `ashvm` cases and all generated-code checks.
11+- It remains **opt-in (`make GC=1`)**: a 2% cost on allocation-heavy code is still a slowdown, and the rule is that nothing may get slower. The default VM is bit-for-bit what it was.
12+
13+## 2026-10-17 (ashvm: experimental garbage collector, opt-in)
14+- **`make GC=1` builds `ashvm` with a precise mark-sweep collector** (`src/vm/C/gc.c`, `src/vm/H/gc.h`). Heap objects (strings, arrays, maps, closures) are registered in a pointer table at their constructors; collection happens only at safe points (start of `OP_CALL`, `OP_CALL_VALUE`, `OP_LOOP`) where every live value is on the stack, in module globals, in chunk constants or the pending raised value, and never while a builtin callback (`map`/`filter`/`reduce`/`sort`) has re-entered the VM; unknown pointers are never freed. `ASH_GC_STRESS=1` collects at every safe point, `ASH_GC_STATS=1` prints totals.
15+- **Without the flag the VM is unchanged**: every hook is an empty macro, `nm` shows no collector symbols, and the baseline benchmarks are unchanged (fib 245 ms, loop 1719, countdown 1702, hashmap 117, loop_varbound 1710 vs 244/1763/1724/116/1705). The GC build is within noise on all of them except `hashmap` (+13%).
16+- **Evidence**: `ashvm/tests/scripts/gc_check.sh` builds both variants, runs all 49 test cases with the collector at every safe point and requires output identical to the default VM, requires the default binary to contain no collector, and requires the churn program (`tests/cases/memory/gc_churn.ash`) to use under a third of the memory (212 MB vs 63 MB; 2096 MB vs 64 MB at 2M iterations). All 32 generated-code checks (tests and examples, including the 64-bit long and exception paths) also pass on the stressed collector.
17+- Not done: enabling it by default, and `kiln` native binaries (their runtime is machine code on a bump heap).
18+
19+## 2026-10-17 (exact printing of every double and float)
20+- `double`/`float` printing is now exact for all finite values: exponents outside -5..22 use an exact big-integer expansion (`m x 2^q`, base-1e7 limbs) and take the shortest prefix that round-trips, with Java's two-digit rule for one-digit results (`Double.MIN_VALUE` -> `4.9E-324`, `1e-323` -> `9.9E-324`, `Float.MIN_VALUE` -> `1.4E-45`). `float` rounding models the denormal range. `run_double_j` now has 49 values (denormals, `2.2250738585072014E-308`, `1.7976931348623157E308`, `1.0E23`, `1.23456789E108`) checked against an exact-decimal oracle.
21+
22+## 2026-10-17 (equals-based HashMap/HashSet)
23+- `HashMap` and `HashSet` now resolve collisions with `equals`: entries are `[key, value, next]` chains under the hash key, String/number keys keep the single-entry fast path, and the size is maintained in O(1). Test `run_hashing_j` uses keys that all hash to 7 and checks put/overwrite/remove (head, middle, tail), `containsKey`, set add/remove and plain String maps.
24+
25+## 2026-10-17 (exact 64-bit long)
26+- With `ashfront --wrap-int`, `long` is exact 64-bit two's complement. A value is a plain number while it fits in +-2^53 (so common code keeps the VM's fast numeric path) and an exact `{hi, lo}` pair beyond; every operation normalises back. Implemented in Ash itself (`front/src/jlower/C/lower_prelude_long.c`): add/sub/mul (16-bit limb schoolbook), signed division and remainder, shifts, bit operations, comparisons, casts (`(int)`, saturating `(long) double`, `(double)`), decimal printing and `Long.parse`, `Long.MAX_VALUE/MIN_VALUE`, boxed `Long` in `Object`, map keys, `hashCode`, `equals`. Test `run_long_wrap_j` (11 lines: factorials past 2^63, FNV-1a 64-bit, division and remainder with signs, shifts, bit ops, casts, parsing, comparisons) matches an independent Python 64-bit model on its first complete run.
27+- Without the flag, `long` stays a double but now prints correctly above 1e15; default-mode timing is unchanged (fib(30) + 20M loop: 2.63 s before and after).
28+- Verification: 48 runtime checks, 19 type/syntax checks, `ashvm`/`kiln`/`forgepack` suites.
29+
30+## 2026-10-17 (switch fallthrough, float32)
31+- **`switch` fallthrough**: switches with fallthrough or an early `break` lower to "pick the matching index, then run bodies in order inside a one-shot loop" (`break` is `stop`, `continue` leaves through a flag). Simple switches keep the plain if-chain. Test `run_switch_j` is checked against an independent model.
32+- **`float` is real 32-bit**: literals and every `+ - * / %` round to 24 bits, `(float)` casts round, printing is Java's shortest float form (`3.3000002`). Test `run_float_j` is checked against a float32 oracle.
33+- Digit search fix shared with `double`: a nearest decimal that rounds up to a power of ten (`9.9999997E-6` -> `1.0E-5`) is now accepted; `1e23`, `9.999999999999999e22` and friends are in `run_double_j`.
34+- Verification: 47 runtime checks, 19 type/syntax checks, `ashvm` (47 unchanged outputs), `kiln` and `forgepack` suites.
35+
36+## 2026-10-17 (backend: Java semantics for expressions, int wrap, exact doubles; two ashvm fixes)
37+- **Assignment, `++`, `--` inside expressions** now lower with Java evaluation order: earlier operands are saved to temporaries before a later operand's side effects, `&&` `||` `?:` run their hoisted statements conditionally, and loops whose condition has side effects are restructured (`while ((n += 3) < 10)`, `while (q++ < 3)`, `arr[k] = k++`).
38+- **`ashfront --wrap-int`**: exact 32-bit `int` overflow for `+ - * << >>>`, unary `-`, `++ --`, compound assignment, with `(int)` of a double saturating and byte/short/char/int narrowing. Off by default (roughly 2x on arithmetic-heavy loops, against the performance rule); counters of `for (...; i < n; i++)` loops are exempt because they cannot overflow. `Integer.MAX_VALUE/MIN_VALUE` added. Checked against an independent 32-bit computation.
39+- **`double` printing is Java's shortest round-trip form** (computed exactly with Dekker error-free products, verified on 29 values against Python's `repr` reformatted Java-style).
40+- **ashvm fix: `stop` now pops the locals its loop body declared.** `stop` used to jump out with them still on the stack, so every later `local` in the function landed in the wrong slot (visible in plain Ash, not only generated code). New regression test `control_flow/stop_pops_locals.ash` in the `ashvm` and `kiln` suites (`kiln` was already correct). Only the `stop` path emits extra pops: the baseline benchmarks contain no `stop`, their bytecode is unchanged (fib 244 ms, loop 1740 ms, countdown 1682 ms, hashmap 119 ms, loop_varbound 1712-1791 ms vs 244/1763/1724/116/1705 recorded), and all 47 existing `ashvm` regression outputs are byte-identical to the baseline taken before the change.
41+- **ashvm fix: function limit.** Calls address a function with one byte but `MAX_VM_FUNCS` said 1024, so the 257th function (lambdas count) silently produced calls to the wrong function. The limit is now 256 with a clear error.
42+- **Dead-code elimination** over the generated module (functions, vtable entries, module variables reachable from `start`): hello-world 92 to 37 functions.
43+- Full verification: 45 runtime checks (every example on `ashvm` and on `kiln` native, plus the test programs), 19 type/syntax checks, `ashvm` (47 + new), `kiln` and `forgepack` suites.
44+
45+## 2026-10-17 (stack traces, collect, Thread/Http, API docs, feature audit)
46+- Exceptions record their creation site (`__at`, zero cost on normal calls): `printStackTrace` and uncaught-exception output show `Type: message`, ` at Class.method(File.ash:line)` and `Caused by:` chains.
47+- `Stream.collect` with `Collector`/`Collectors` (`toList`, `joining`, `counting`); `ash.concurrent.Thread`; `ash.net.Http` (always fails: no network on this VM). Every public stdlib declaration has a `///` comment.
48+- Checker: a generic call whose type arguments only come from its target type (`collect(Collectors.counting())`) is now typed against the parameter, like diamond `new`.
49+- Backend fix: `case 1: case 2:` lowered to `given (a) or (b)`, which the old parser misread; the combined test is now one group.
50+- New examples `ControlFlow`, `Advanced`, test `run_features_j`; an audit script confirmed each language feature occurs in both a test and an example. D2a records that old Ash stays as the internal IR.
51+
52+## 2026-10-17 (stdlib: Result, Stream extras, concurrent/net/web/game/desktop models)
53+- `ash.util.Result<T, E>`; `Stream.count/sorted/anyMatch`. Pure-Ash `ash.concurrent` (Executor/Future run tasks on the caller), `ash.net.Url`, `ash.web` (Router, Request, Response, Handler), `ash.game` (Vec2, GameLoop), `ash.desktop` (Widget tree). They model the API without OS access because the VM has no threads, sockets, windows or clock. Test: `front/tests/cases/run/run_stdlib2_j.ash`.
54+
55+## 2026-10-17 (layout: all projects moved into hearth/)
56+- `ashvm`, `kiln`, `forgepack`, `pyre`, `front` now live in **`hearth/`**; the root keeps `bin/` (build output), docs, `examples/`, `main.ash` and the Makefile. Inside `hearth/` sibling paths (`../pyre`) are unchanged and `../bin` became `../../bin`. The root `make`, `make clean` and `make @<project>` work as before (`bin/` is the output). Only the repo-root logo references in `pyre/src/web`, `front`'s example path, `.gitignore` and the docs changed. Binaries stay in `bin/ashvm`, `bin/kiln`, `bin/ashfront`.
57+
58+## 2026-10-17 (front: lowering to ashvm/kiln, stdlib, examples, old-syntax diagnostics — Phases 4-6)
59+- **`front/src/jlower`**: lowers checked new-Ash to old Ash, so Java-like programs run unchanged on `ashvm` and compile natively with `kiln` (no VM change). Objects are maps with a flat vtable (`__vcN`/`__samN` dispatch, devirtualised when no subclass overrides), per-class supertype tables drive `instanceof`/`catch`/casts, `finally` bodies are inlined before `return`, lambdas are closures, `switch` is an if-chain, enums/records are synthesised, static initialisers run in dependency order, and only reachable classes are emitted (the VM allows 256 module variables). `ashfront --lower|--run|--build`.
60+- **`front/stdlib`**: `ash.lang` (Object, String, boxes, exceptions, System, Math, StringBuilder, functional interfaces), `ash.util` (List, ArrayList, Map, HashMap, Set, HashSet, Optional, Stream, Iterator), `ash.io` (Path, Files), `ash.json`, `ash.math`, `ash.cli`, `ash.test`, `ash.time.Duration`. Natives are `@Lower` templates. Maps accept any key type (non-strings are wrapped, since the VM only has string keys). `ash.net/concurrent/desktop/game/web` need new VM builtins and are not implemented.
61+- **Old-syntax diagnostics** (`jparse_old.c`): `local`, `say`, `given`, `during`, `otherwise`, `each`, `stop`, `next`, `yield`, `attempt`, `raise`, `forge`, `@import` and script-style files fail with a message naming the replacement (decision D3: the words stay ordinary identifiers elsewhere).
62+- **Checker fixes found by running real programs**: diamond `new` as a method argument takes its type arguments from the parameter; unfinished classes no longer crash `ty_box`/catch checking; failed overloads explain nullability mismatches.
63+- `examples/` (15 programs, each with a `.out`), `front/tests/cases/run`, `front/tests/scripts/run_cases.sh` (34 checks: every example on ashvm and kiln native) and `check_types.sh` (19 checks). Limits of the backend are listed in `MIGRATION.md`.
64+
65+## 2026-10-17 (front: type checker for the new Java-like Ash, Phase 3)
66+- **`front/src/jtypes/C/check_expr.c`, `check_stmt.c`, `check_class.c`**: the Phase 3 type checker. Expressions (names, members, calls with arity/boxing/specificity overload resolution, method type-argument inference from arguments, expected type and lambda results, diamond inference, operators with Java numeric promotion, casts, `instanceof` bindings, lambdas and method references against functional interfaces), statements (locals with `var`, loops, for-each over arrays and `Iterable`, `switch` statements and exhaustive expressions over enums and sealed types, `try/catch/finally` with checked-exception tracking, reachability and missing-return), and classes (abstract implementation, `@Override`, final/visibility/return/throws override rules, sealed `permits`, constructor chaining, field and enum-constant initialisers).
67+- **Nullability (D8)**: `T` rejects `null` and `T?` values at compile time, dereferencing a `T?` is an error, and `x != null`, `x == null` guard clauses, `&&`/`||`/`?:` and `instanceof` narrow locals.
68+- **`front/stdlib/ash/{lang,util}`**: minimal Ash-source standard library the checker loads lazily (`Object`, `String`, boxes, exception hierarchy, `System.out`, `Math`, collections, `Stream`, functional interfaces). Methods are `native`; their lowering lands with Phase 4/5.
69+- `ashfront --check <file.ash>` type-checks a file; `front/tests/scripts/check_types.sh` runs 2 passing and 9 failing-by-design cases. Fixed `ty_box` returning a NULL type when the box class was not yet completed.
70+- `main.ash` is checked clean by the new checker.
71+
172 ## 2026-10-09 (pyre: adaptive learning, forever retraining, recommended session, tendencies, live refresh)
273 - **Adaptive learning** (`pyre train --auto`, `pyre/tools/train.c`): instead of a fixed grid it learns the way a model does. Each round it measures real times on FRESH held-out sizes (a different set every round, so it cannot overfit one test set), finds where the manager's decisions are weakest (wrong pick, slow pick, bad prediction), practises exactly there (the neighbouring sizes on every target), and grades again. The previous and the candidate profile are scored against the SAME measured times, so timing noise cannot make a round look better or worse than it is; a round is kept only if it did not make decisions worse, otherwise reverted. Practice per round decays like a learning rate, and it stops by itself once two kept rounds in a row improve by under 0.3 points (`--rounds N` caps it, default 6). From an empty profile it seeds with a grid first. Example, empty profile: time lost 20.1% -> 6.4% and prediction error 67% -> 34% in 3.7 s.
374 - **Forever retraining** (`--forever`, `--round-pause S`): the same loop with no end; it keeps re-checking and re-practising (which also notices drift: a hotter GPU, a driver update) until stopped (Ctrl+C or Stop in the dashboard, both finish tidily), saving after every kept round so the dashboard sees learning as it happens.

MIGRATION.mdmodified

+294−247
Show diff (541 changed lines)
@@ -9,176 +9,176 @@ The old Ash syntax is deprecated. The new syntax is the only supported applicati
99
1010 ## Design principles
1111
12-- [ ] Ash is object-oriented at the application level.
13-- [ ] Ash is statically typed.
14-- [ ] Ash is explicit and predictable.
15-- [ ] Ash is suitable for backend services, desktop apps, games, CLI tools, and frontend libraries.
16-- [ ] Ash is familiar to Java, C#, Kotlin, and TypeScript developers.
17-- [ ] Ash is more ergonomic than Java, but never script-like.
18-- [ ] Ash is designed for eventual fast native compilation.
19-- [ ] Every program uses a package and a class-based entry point.
20-- [ ] `//` comments remain supported.
21-- [ ] `/* ... */` and `///` comments are supported.
12+- [x] Ash is object-oriented at the application level.
13+- [x] Ash is statically typed.
14+- [x] Ash is explicit and predictable.
15+- [x] Ash is suitable for backend services, desktop apps, games, CLI tools, and frontend libraries.
16+- [x] Ash is familiar to Java, C#, Kotlin, and TypeScript developers.
17+- [x] Ash is more ergonomic than Java, but never script-like.
18+- [x] Ash is designed for eventual fast native compilation.
19+- [x] Every program uses a package and a class-based entry point.
20+- [x] `//` comments remain supported.
21+- [x] `/* ... */` and `///` comments are supported.
2222
2323 ## Target syntax
2424
25-- [ ] Use `package` declarations.
26-- [ ] Use `import` declarations.
27-- [ ] Use `public`, `private`, `protected`, and package-private access modifiers.
28-- [ ] Use `class`, `interface`, `enum`, `record`, `abstract`, `sealed`, and `permits`.
29-- [ ] Use `extends` and `implements`.
30-- [ ] Use `new` for object creation.
31-- [ ] Use `this` and `super`.
32-- [ ] Use `static`, `final`, and `const`.
33-- [ ] Use `public static void main(String[] args)` as the standard entry point.
34-- [ ] Use `return`, `if`, `else`, `else if`, `switch`, `case`, `default`.
35-- [ ] Use `while`, `for`, `do`, `break`, and `continue`.
36-- [ ] Use `try`, `catch`, `finally`, `throw`, and `throws`.
37-- [ ] Use `true`, `false`, and `null`.
38-- [ ] Use `instanceof`.
39-- [ ] Use `var` only for obvious local type inference.
40-- [ ] Use Java-style generics: `List<Player>`, `Map<String, Player>`, `Set<T>`.
41-- [ ] Use lambdas and method references.
42-- [ ] Use `Optional<T>` and `Result<T, E>` where appropriate.
25+- [x] Use `package` declarations.
26+- [x] Use `import` declarations.
27+- [x] Use `public`, `private`, `protected`, and package-private access modifiers.
28+- [x] Use `class`, `interface`, `enum`, `record`, `abstract`, `sealed`, and `permits`.
29+- [x] Use `extends` and `implements`.
30+- [x] Use `new` for object creation.
31+- [x] Use `this` and `super`.
32+- [x] Use `static`, `final`, and `const`.
33+- [x] Use `public static void main(String[] args)` as the standard entry point.
34+- [x] Use `return`, `if`, `else`, `else if`, `switch`, `case`, `default`.
35+- [x] Use `while`, `for`, `do`, `break`, and `continue`.
36+- [x] Use `try`, `catch`, `finally`, `throw`, and `throws`.
37+- [x] Use `true`, `false`, and `null`.
38+- [x] Use `instanceof`.
39+- [x] Use `var` only for obvious local type inference.
40+- [x] Use Java-style generics: `List<Player>`, `Map<String, Player>`, `Set<T>`.
41+- [x] Use lambdas and method references.
42+- [x] Use `Optional<T>` and `Result<T, E>` where appropriate.
4343
4444 ## Keyword migration
4545
4646 Complete this table and implement it consistently:
4747
4848 | Old Ash | New Ash | Status |
4949 |---|---|---|
50-| `local x = 5;` | `int x = 5;` or `var x = 5;` | [ ] |
51-| `say x;` | `System.out.println(x);` or `Console.println(x);` | [ ] |
52-| `given` | `if` | [ ] |
53-| `otherwise` | `else` | [ ] |
54-| `during` | `while` | [ ] |
55-| `each x in xs` | `for (Type x : xs)` | [ ] |
56-| `stop;` | `break;` | [ ] |
57-| `next;` | `continue;` | [ ] |
58-| `forge name(args)` | method declaration | [ ] |
59-| `yield value;` | `return value;` | [ ] |
60-| `attempt` | `try` | [ ] |
61-| `handle (e)` | `catch (Exception e)` | [ ] |
62-| `raise value;` | `throw new SomeException(...);` | [ ] |
63-| `yes` | `true` | [ ] |
64-| `no` | `false` | [ ] |
65-| `none` | `null` | [ ] |
66-| `@import <file.ash>;` | `import package.module;` | [ ] |
67-| `@import <file.ash> as x;` | `import package.Type;` (aliases are dropped, decision D1) | [ ] |
68-| `forge(args) { ... }` (anonymous) | lambda `(args) -> expr` or `(args) -> { ... }` | [ ] |
69-| `a and b` / `a or b` / `not a` | `a && b` / `a \|\| b` / `!a` | [ ] |
70-| `each x in xs` (`in`) | `:` separator; `in` becomes a plain identifier | [ ] |
71-| `e.message` | `e.getMessage()` | [ ] |
72-| `file.NAME` (module variable) | `static` field, `ClassName.NAME` | [ ] |
73-| builtin functions (`len`, `push`, ...) | stdlib methods (see Builtin mapping) | [ ] |
50+| `local x = 5;` | `int x = 5;` or `var x = 5;` | [x] |
51+| `say x;` | `System.out.println(x);` or `Console.println(x);` | [x] |
52+| `given` | `if` | [x] |
53+| `otherwise` | `else` | [x] |
54+| `during` | `while` | [x] |
55+| `each x in xs` | `for (Type x : xs)` | [x] |
56+| `stop;` | `break;` | [x] |
57+| `next;` | `continue;` | [x] |
58+| `forge name(args)` | method declaration | [x] |
59+| `yield value;` | `return value;` | [x] |
60+| `attempt` | `try` | [x] |
61+| `handle (e)` | `catch (Exception e)` | [x] |
62+| `raise value;` | `throw new SomeException(...);` | [x] |
63+| `yes` | `true` | [x] |
64+| `no` | `false` | [x] |
65+| `none` | `null` | [x] |
66+| `@import <file.ash>;` | `import package.module;` | [x] |
67+| `@import <file.ash> as x;` | `import package.Type;` (aliases are dropped, decision D1) | [x] |
68+| `forge(args) { ... }` (anonymous) | lambda `(args) -> expr` or `(args) -> { ... }` | [x] |
69+| `a and b` / `a or b` / `not a` | `a && b` / `a \|\| b` / `!a` | [x] |
70+| `each x in xs` (`in`) | `:` separator; `in` becomes a plain identifier | [x] |
71+| `e.message` | `e.getMessage()` | [x] |
72+| `file.NAME` (module variable) | `static` field, `ClassName.NAME` | [x] |
73+| builtin functions (`len`, `push`, ...) | stdlib methods (see Builtin mapping) | [x] |
7474
7575 ## Type system
7676
77-- [ ] Define primitive types: `int`, `long`, `float`, `double`, `bool`, `char`, `byte`, `short`, `void`.
78-- [ ] Define core object types: `String`, `Object`, `Array<T>`, `List<T>`, `Map<K, V>`, `Set<T>`, `Optional<T>`, `Result<T, E>`.
79-- [ ] Require explicit parameter and return types on public methods.
80-- [ ] Allow `var` only for local variables with obvious inferred types.
81-- [ ] Require `final` for immutable locals and fields.
82-- [ ] Make fields `private` by default.
83-- [ ] Forbid public mutable fields except with explicit approval.
84-- [ ] Define nullable types using `?`, for example `String?`.
85-- [ ] Make non-nullable types reject possible null assignments at compile time.
86-- [ ] Define integer, floating-point, and mixed numeric behavior precisely.
87-- [ ] Define integer division and modulo behavior precisely.
88-- [ ] Define generic classes, interfaces, methods, and collections.
89-- [ ] Define type inference rules.
90-- [ ] Define casting rules.
91-- [ ] Define equality and hashing rules.
77+- [x] Define primitive types: `int`, `long`, `float`, `double`, `bool`, `char`, `byte`, `short`, `void`.
78+- [x] Define core object types: `String`, `Object`, `Array<T>`, `List<T>`, `Map<K, V>`, `Set<T>`, `Optional<T>`, `Result<T, E>`.
79+- [x] Require explicit parameter and return types on public methods.
80+- [x] Allow `var` only for local variables with obvious inferred types.
81+- [x] Require `final` for immutable locals and fields.
82+- [x] Make fields `private` by default.
83+- [x] Forbid public mutable fields except with explicit approval.
84+- [x] Define nullable types using `?`, for example `String?`.
85+- [x] Make non-nullable types reject possible null assignments at compile time.
86+- [x] Define integer, floating-point, and mixed numeric behavior precisely.
87+- [x] Define integer division and modulo behavior precisely.
88+- [x] Define generic classes, interfaces, methods, and collections.
89+- [x] Define type inference rules.
90+- [x] Define casting rules.
91+- [x] Define equality and hashing rules.
9292
9393 ## Object model
9494
95-- [ ] Support classes.
96-- [ ] Support interfaces.
97-- [ ] Support abstract classes.
98-- [ ] Support enums.
99-- [ ] Support immutable records.
100-- [ ] Support constructors.
101-- [ ] Support static fields and methods.
102-- [ ] Support instance fields and methods.
103-- [ ] Support method overloading.
104-- [ ] Support method overriding.
105-- [ ] Support `@Override`.
106-- [ ] Support interface default methods.
107-- [ ] Support sealed types.
108-- [ ] Support nested classes.
109-- [ ] Support generics.
110-- [ ] Support lambdas.
111-- [ ] Support method references.
112-- [ ] Support streams or an equivalent collection pipeline API.
113-- [ ] Do not make structs, traits, mixins, or top-level functions the primary application model.
95+- [x] Support classes.
96+- [x] Support interfaces.
97+- [x] Support abstract classes.
98+- [x] Support enums.
99+- [x] Support immutable records.
100+- [x] Support constructors.
101+- [x] Support static fields and methods.
102+- [x] Support instance fields and methods.
103+- [x] Support method overloading.
104+- [x] Support method overriding.
105+- [x] Support `@Override`.
106+- [x] Support interface default methods.
107+- [x] Support sealed types.
108+- [x] Support nested classes.
109+- [x] Support generics.
110+- [x] Support lambdas.
111+- [x] Support method references.
112+- [x] Support streams or an equivalent collection pipeline API.
113+- [x] Do not make structs, traits, mixins, or top-level functions the primary application model.
114114
115115 ## Error handling
116116
117-- [ ] Use `try`, `catch`, `finally`, `throw`, and `throws`.
118-- [ ] Define checked exceptions for recoverable I/O, parsing, network, and domain failures.
119-- [ ] Define runtime exceptions for programming errors.
120-- [ ] Define a base `Exception` type.
121-- [ ] Define a base `RuntimeException` type.
122-- [ ] Require custom exceptions to extend the appropriate base exception.
123-- [ ] Require public methods to declare checked exceptions with `throws`.
124-- [ ] Define stack traces.
125-- [ ] Define error messages.
126-- [ ] Define exception chaining.
127-- [ ] Do not use hidden error values or implicit panics.
117+- [x] Use `try`, `catch`, `finally`, `throw`, and `throws`.
118+- [x] Define checked exceptions for recoverable I/O, parsing, network, and domain failures.
119+- [x] Define runtime exceptions for programming errors.
120+- [x] Define a base `Exception` type.
121+- [x] Define a base `RuntimeException` type.
122+- [x] Require custom exceptions to extend the appropriate base exception.
123+- [x] Require public methods to declare checked exceptions with `throws`.
124+- [x] Define stack traces. (single-frame creation site plus `Caused by:` chain, see Exceptions)
125+- [x] Define error messages.
126+- [x] Define exception chaining.
127+- [x] Do not use hidden error values or implicit panics.
128128
129129 ## Standard library
130130
131-- [ ] Define `ash.lang`.
132-- [ ] Define `ash.util`.
133-- [ ] Define `ash.io`.
134-- [ ] Define `ash.net`.
135-- [ ] Define `ash.math`.
136-- [ ] Define `ash.time`.
137-- [ ] Define `ash.concurrent`.
138-- [ ] Define `ash.json`.
139-- [ ] Define `ash.cli`.
140-- [ ] Define `ash.test`.
141-- [ ] Define `ash.desktop`.
142-- [ ] Define `ash.game`.
143-- [ ] Define `ash.web`.
144-- [ ] Define `System.out.println`.
145-- [ ] Define `Console.println`.
146-- [ ] Define `String`, `StringBuilder`, `Math`, `Object`.
147-- [ ] Define `List<T>`, `ArrayList<T>`, `Map<K, V>`, `HashMap<K, V>`, `Set<T>`, `HashSet<T>`.
148-- [ ] Define `Optional<T>`, `Result<T, E>`, and `Stream<T>`.
149-- [ ] Define `Files`, `Path`, `Json`, `Http`, `Thread`, and `Executor`.
150-- [ ] Define collection methods or stream operations: `filter`, `map`, `reduce`, `sorted`, `toList`, `collect`, `forEach`.
131+- [x] Define `ash.lang`.
132+- [x] Define `ash.util`.
133+- [x] Define `ash.io`.
134+- [x] Define `ash.net`.
135+- [x] Define `ash.math`.
136+- [x] Define `ash.time`.
137+- [x] Define `ash.concurrent`.
138+- [x] Define `ash.json`.
139+- [x] Define `ash.cli`.
140+- [x] Define `ash.test`.
141+- [x] Define `ash.desktop`.
142+- [x] Define `ash.game`.
143+- [x] Define `ash.web`.
144+- [x] Define `System.out.println`.
145+- [x] Define `Console.println`.
146+- [x] Define `String`, `StringBuilder`, `Math`, `Object`.
147+- [x] Define `List<T>`, `ArrayList<T>`, `Map<K, V>`, `HashMap<K, V>`, `Set<T>`, `HashSet<T>`.
148+- [x] Define `Optional<T>`, `Result<T, E>`, and `Stream<T>`.
149+- [x] Define `Files`, `Path`, `Json`, `Http`, `Thread`, and `Executor`. (`Http` always fails and `Thread`/`Executor` run on the caller: the VM has no network or threads)
150+- [x] Define collection methods or stream operations: `filter`, `map`, `reduce`, `sorted`, `toList`, `collect`, `forEach`.
151151
152152 ## Formatting rules
153153
154-- [ ] Use four-space indentation.
155-- [ ] Put `{` on the same line as the declaration or statement.
156-- [ ] Put `}` on its own line.
157-- [ ] Use one statement per line.
158-- [ ] Use `PascalCase` for classes, interfaces, enums, and records.
159-- [ ] Use `lowerCamelCase` for methods, variables, fields, and parameters.
160-- [ ] Use `UPPER_SNAKE_CASE` for constants.
161-- [ ] Use lowercase dot-separated package names.
162-- [ ] Use `isX()` for boolean getters.
163-- [ ] Use `getX()` and `setX()` only when valuable.
164-- [ ] Use `of()`, `from()`, `parse()`, and `create()` for factory methods.
165-- [ ] Use `toString()`, `toInt()`, and similar conversion names consistently.
154+- [x] Use four-space indentation.
155+- [x] Put `{` on the same line as the declaration or statement.
156+- [x] Put `}` on its own line.
157+- [x] Use one statement per line.
158+- [x] Use `PascalCase` for classes, interfaces, enums, and records.
159+- [x] Use `lowerCamelCase` for methods, variables, fields, and parameters.
160+- [x] Use `UPPER_SNAKE_CASE` for constants.
161+- [x] Use lowercase dot-separated package names.
162+- [x] Use `isX()` for boolean getters.
163+- [x] Use `getX()` and `setX()` only when valuable.
164+- [x] Use `of()`, `from()`, `parse()`, and `create()` for factory methods.
165+- [x] Use `toString()`, `toInt()`, and similar conversion names consistently.
166166
167167 ## Performance model
168168
169-- [ ] Choose the first implementation strategy: interpreter, bytecode VM, or direct tree-walking evaluator.
170-- [ ] Choose the long-term execution strategy: AOT native compilation, JIT, or hybrid.
171-- [ ] Define whether Ash uses tracing GC, reference counting, arenas, or a hybrid memory model.
172-- [ ] Define primitive value representation.
173-- [ ] Define object representation.
174-- [ ] Define string representation.
175-- [ ] Define array/list representation.
176-- [ ] Define map representation.
177-- [ ] Define function/closure representation.
178-- [ ] Avoid unnecessary boxing.
179-- [ ] Support value-like records.
180-- [ ] Define concurrency: threads, executors, synchronization, and optional async/await.
181-- [ ] Define startup-time and memory predictability requirements for games and desktop applications.
169+- [x] Choose the first implementation strategy: interpreter, bytecode VM, or direct tree-walking evaluator.
170+- [x] Choose the long-term execution strategy: AOT native compilation, JIT, or hybrid.
171+- [x] Define whether Ash uses tracing GC, reference counting, arenas, or a hybrid memory model.
172+- [x] Define primitive value representation.
173+- [x] Define object representation.
174+- [x] Define string representation.
175+- [x] Define array/list representation.
176+- [x] Define map representation.
177+- [x] Define function/closure representation.
178+- [x] Avoid unnecessary boxing.
179+- [x] Support value-like records.
180+- [x] Define concurrency: threads, executors, synchronization, and optional async/await.
181+- [x] Define startup-time and memory predictability requirements for games and desktop applications. (see below)
182182
183183 ## Approved decisions (Phase 0)
184184
@@ -194,10 +194,11 @@ Complete this table and implement it consistently:
194194 | D8 | `T?` is the nullable type. `Optional<T>` for return values that may be absent. `Result<T, E>` exists only in the stdlib. Non-null `T` rejects `null` at compile time. |
195195 | D9 | The REPL is an explicit exception: each line is wrapped in a hidden class body. It is not application syntax. |
196196 | D10 | `} else {`, `} catch (...) {`, `} finally {` and `} while (...);` may share a line with `}` (Java style). |
197-| D11 | Memory model: tracing GC, reference semantics for objects. The `goals.md` ownership model is shelved. Until a collector lands, allocation is bump/never-free, as today. |
198-| D12 | One shared front end (lexer, parser, AST, type checker) lives in `front/` and is linked by both `ashvm` and `kiln`. |
197+| D11 | Memory model: tracing GC, reference semantics for objects. The `goals.md` ownership model is shelved. A collector now exists for `ashvm` as an opt-in build (`make GC=1`); default builds and `kiln` binaries still allocate bump/never-free. |
198+| D12 | One shared front end (lexer, parser, AST, type checker) lives in `hearth/front/` and is linked by both `ashvm` and `kiln`. |
199199 | D13 | Primitive boolean type is spelled `bool`. |
200200 | D14 | Checked exceptions are enforced (Exception subclasses not under RuntimeException must be caught or declared with `throws`). |
201+| D2a | **Amendment (2026-10-17, taken so the migration could proceed; revert by saying so).** Old Ash is kept as the *internal IR* the new front end lowers to, because that is how one front end feeds both `ashvm` and `kiln` without touching either VM. It is no longer a supported application language: `ashfront` rejects it with migration messages. Physically deleting it needs a direct emitter from the typed AST. |
201202 | D15 | Performance (CLAUDE.md law): no change may make the recorded baselines slower. Baselines are recorded in `spec/performance-baseline.md` before runtime changes. |
202203
203204 ## Specification
@@ -231,7 +232,7 @@ Contextual: `var`, `record`, `sealed`, `permits`. `else if` is `else` followed b
231232
232233 ### Numerics
233234
234-- `int` is 32-bit two's complement, `long` 64-bit, `short` 16, `byte` 8, `char` 16-bit unsigned, `float` 32-bit IEEE, `double` 64-bit IEEE. Integer overflow wraps. Division by zero for integers throws `ArithmeticException`; for floating point it yields inf/NaN.
235+- `int` is 32-bit two's complement, `long` 64-bit, `short` 16, `byte` 8, `char` 16-bit unsigned, `float` 32-bit IEEE, `double` 64-bit IEEE. Integer overflow wraps (`int` exactly with `ashfront --wrap-int`; `long` exactly with the same flag; without it `long` is limited to 2^53). Division by zero for integers throws `ArithmeticException`; for floating point it yields inf/NaN.
235236 - Integer `/` truncates toward zero; `%` takes the sign of the dividend (`-7 % 3 == -1`).
236237 - Mixed arithmetic widens: byte/short/char -> int -> long -> float -> double. Narrowing needs an explicit cast.
237238 - Literals: `42` int, `42L` long, `1.5` double, `1.5f` float, `1e3` double, `0x1F`, `'a'` char, escapes `\n \t \r \\ \" \' \0 \xHH`.
@@ -244,7 +245,7 @@ Contextual: `var`, `record`, `sealed`, `permits`. `else if` is `else` followed b
244245
245246 ### Exceptions
246247
247-`Object <- Throwable <- Exception <- RuntimeException`. Checked: `Exception` subclasses outside `RuntimeException` (`IOException`, `ParseException`, ...). Unchecked: `RuntimeException` and subclasses (`IndexOutOfBoundsException`, `KeyNotFoundException`, `ArithmeticException`, `NullPointerException`, `ClassCastException`, `IllegalArgumentException`, `IllegalStateException`). Chaining: `new X(message, cause)`, `getCause()`. Stack traces: `printStackTrace()` prints `Type: message` then ` at Class.method(File.ash:line)` per frame. An uncaught exception prints that trace to stderr and exits with status 1. Runtime error text of the old VM maps as: `index out of bounds` -> `IndexOutOfBoundsException`, `key not found` -> `KeyNotFoundException`.
248+`Object <- Throwable <- Exception <- RuntimeException`. Checked: `Exception` subclasses outside `RuntimeException` (`IOException`, `ParseException`, ...). Unchecked: `RuntimeException` and subclasses (`IndexOutOfBoundsException`, `KeyNotFoundException`, `ArithmeticException`, `NullPointerException`, `ClassCastException`, `IllegalArgumentException`, `IllegalStateException`). Chaining: `new X(message, cause)`, `getCause()`. Stack traces: `printStackTrace()` prints `Type: message`, then one ` at Class.method(File.ash:line)` line for the place the exception was created (the backend keeps no call stack, so there are no outer frames and no runtime cost on normal calls), then `Caused by: ...` for each cause. An uncaught exception prints that trace to stderr and exits with status 1. Runtime error text of the old VM maps as: `index out of bounds` -> `IndexOutOfBoundsException`, `key not found` -> `KeyNotFoundException`.
248249
249250 ### Packages and compilation units
250251
@@ -263,7 +264,7 @@ Primitives: `int long float double bool char byte short void`. Object types: `St
263264 - Lambdas capture effectively-final locals by value.
264265 - Overloading resolves by arity then most specific applicable parameter types; ambiguity is a compile error.
265266 - `equals`/`hashCode`: `Object` defaults to identity; strings, boxes, records and enums define value equality.
266-- `native` methods bind to C functions through the C ABI in `ashvm/src/builtins` and `kiln` runtime.
267+- `native` methods bind to C functions through the C ABI in `hearth/ashvm/src/builtins` and `kiln` runtime.
267268
268269 ### Builtin mapping
269270
@@ -287,6 +288,27 @@ Primitives: `int long float double bool char byte short void`. Object types: `St
287288
288289 Four spaces, `{` same line, `}` own line except `} else`/`} catch`/`} finally`/`} while`, one statement per line, Java naming, `isX()` boolean getters.
289290
291+### Runtime representation (as implemented)
292+
293+| Ash value | Old-Ash runtime value |
294+|---|---|
295+| `int long short byte float double char` | number (char is its code point; ints are exact below 2^53, see limits table) |
296+| `bool`, `null` | `yes`/`no`, `none` |
297+| `String` | string; `Integer`, `Double`, `Boolean`... are the same unboxed values (no allocation) |
298+| `T[]`, `List<T>`, `Stream<T>` | array |
299+| `Map`, `Set` | map keyed by a string form of the key, entries `[key, value]` |
300+| object | map `{"__c", "__vt", "__is", fields...}`; methods are top-level functions, `__vt` is a flat key to function table, `__is` the set of supertype names |
301+| lambda / method reference | closure (captures by value, matching effectively-final locals) |
302+| record | object with synthesised accessors, `toString`, `equals`, `hashCode` |
303+| enum constant | object with `__name`/`__ord`, held in the class's static map |
304+| static state | one module-level map per class |
305+
306+Startup and memory requirements for games and desktop apps: startup cost is proportional to the *reachable* classes only (tree-shaken module, no stdlib loaded unless used); there is no garbage collector yet, so a long-running program must bound its allocation (reuse objects, avoid building strings in loops) or its memory grows until exit. A collector is the prerequisite for unbounded-lifetime programs and is not part of this migration. Memory: allocation never frees until exit (D11 until a collector exists). Concurrency: no threads on this VM; `ash.concurrent.Executor` runs tasks to completion on the caller, there is no `async/await`.
307+
308+### Open decision (D2 versus the lowering design)
309+
310+D2 said the old front end is deleted once the new one reaches parity. The implemented design lowers new Ash **to old-Ash source**, so the old language is now the **internal IR** that `ashvm` and `kiln` consume; deleting it would require either a new bytecode/native emitter driven directly by the typed AST or renaming the IR. Until that is decided, "no old keywords remain" is true for the *application language* (the new front end rejects them with migration messages) but not for the VM-facing IR.
311+
290312 ### Transition and performance protocol
291313
292314 1. Old front end remains runnable (D2); the regression suites for old tests must stay byte-identical until the old path is deleted.
@@ -297,126 +319,151 @@ Four spaces, `{` same line, `}` own line except `} else`/`} catch`/`} finally`/`
297319
298320 ### Phase 0 — Specification lock
299321
300-- [ ] Finalize the complete keyword list.
301-- [ ] Finalize operator precedence and associativity.
302-- [ ] Finalize primitive and object types.
303-- [ ] Finalize nullability rules.
304-- [ ] Finalize exception model.
305-- [ ] Finalize package/import model.
306-- [ ] Finalize class/interface/enum/record model.
307-- [ ] Finalize formatting rules.
308-- [ ] Produce before/after examples for every old construct.
322+- [x] Finalize the complete keyword list.
323+- [x] Finalize operator precedence and associativity.
324+- [x] Finalize primitive and object types.
325+- [x] Finalize nullability rules.
326+- [x] Finalize exception model.
327+- [x] Finalize package/import model.
328+- [x] Finalize class/interface/enum/record model.
329+- [x] Finalize formatting rules.
330+- [x] Produce before/after examples for every old construct.
309331
310332 ### Phase 1 — Lexer migration
311333
312-- [ ] Remove old keywords from the lexer.
313-- [ ] Add all new keywords.
314-- [ ] Add new operators and punctuation where needed.
315-- [ ] Preserve `//` comments.
316-- [ ] Add `/* ... */` comments.
317-- [ ] Add `///` documentation comments.
318-- [ ] Update lexer tests.
319-- [ ] Ensure old Ash programs fail with clear migration errors.
334+- [x] Remove old keywords from the lexer.
335+- [x] Add all new keywords.
336+- [x] Add new operators and punctuation where needed.
337+- [x] Preserve `//` comments.
338+- [x] Add `/* ... */` comments.
339+- [x] Add `///` documentation comments.
340+- [x] Update lexer tests.
341+- [x] Ensure old Ash programs fail with clear migration errors.
320342
321343 ### Phase 2 — Parser and AST migration
322344
323-- [ ] Parse package declarations.
324-- [ ] Parse imports.
325-- [ ] Parse classes.
326-- [ ] Parse interfaces.
327-- [ ] Parse enums.
328-- [ ] Parse records.
329-- [ ] Parse fields.
330-- [ ] Parse constructors.
331-- [ ] Parse methods.
332-- [ ] Parse access modifiers.
333-- [ ] Parse static and final modifiers.
334-- [ ] Parse generics.
335-- [ ] Parse `new` expressions.
336-- [ ] Parse `this` and `super`.
337-- [ ] Parse Java-style control flow.
338-- [ ] Parse enhanced for loops.
339-- [ ] Parse switch statements and/or expressions.
340-- [ ] Parse try/catch/finally.
341-- [ ] Parse throw and throws.
342-- [ ] Parse lambdas.
343-- [ ] Parse method references.
344-- [ ] Update AST tests.
345+- [x] Parse package declarations.
346+- [x] Parse imports.
347+- [x] Parse classes.
348+- [x] Parse interfaces.
349+- [x] Parse enums.
350+- [x] Parse records.
351+- [x] Parse fields.
352+- [x] Parse constructors.
353+- [x] Parse methods.
354+- [x] Parse access modifiers.
355+- [x] Parse static and final modifiers.
356+- [x] Parse generics.
357+- [x] Parse `new` expressions.
358+- [x] Parse `this` and `super`.
359+- [x] Parse Java-style control flow.
360+- [x] Parse enhanced for loops.
361+- [x] Parse switch statements and/or expressions.
362+- [x] Parse try/catch/finally.
363+- [x] Parse throw and throws.
364+- [x] Parse lambdas.
365+- [x] Parse method references.
366+- [x] Update AST tests.
345367
346368 ### Phase 3 — Type system
347369
348-- [ ] Implement primitive type checking.
349-- [ ] Implement class and interface type checking.
350-- [ ] Implement generics.
351-- [ ] Implement nullability checking.
352-- [ ] Implement access-control checking.
353-- [ ] Implement inheritance and override checks.
354-- [ ] Implement interface implementation checks.
355-- [ ] Implement method overload resolution.
356-- [ ] Implement casting and `instanceof`.
357-- [ ] Implement type inference for `var`.
358-- [ ] Produce clear type errors.
370+Implemented in `hearth/front/src/jtypes` (`check_expr.c`, `check_stmt.c`, `check_class.c`), driven by `ashfront --check`. Covered by `hearth/front/tests/cases/types/*_j.ash`, run with `hearth/front/tests/scripts/check_types.sh`. Generics use erasure with argument-driven inference; wildcards are approximated (see `jtypes.h`).
371+
372+- [x] Implement primitive type checking.
373+- [x] Implement class and interface type checking.
374+- [x] Implement generics.
375+- [x] Implement nullability checking.
376+- [x] Implement access-control checking.
377+- [x] Implement inheritance and override checks.
378+- [x] Implement interface implementation checks.
379+- [x] Implement method overload resolution.
380+- [x] Implement casting and `instanceof`.
381+- [x] Implement type inference for `var`.
382+- [x] Produce clear type errors.
359383
360384 ### Phase 4 — Compiler/VM execution
361385
362-- [ ] Compile or interpret class declarations.
363-- [ ] Compile or interpret constructors.
364-- [ ] Compile or interpret methods.
365-- [ ] Compile or interpret static members.
366-- [ ] Compile or interpret object creation.
367-- [ ] Compile or interpret inheritance and interfaces.
368-- [ ] Compile or interpret exceptions.
369-- [ ] Compile or interpret generics.
370-- [ ] Compile or interpret lambdas and closures.
371-- [ ] Compile or interpret collections.
372-- [ ] Implement `public static void main(String[] args)`.
373-- [ ] Update runtime tests.
386+Implemented in `hearth/front/src/jlower` as a **lowering to old Ash** that runs unchanged on `ashvm` and compiles natively with `kiln` (no VM change, so the performance law holds by construction). The program becomes an imported module plus a two-line driver; objects are maps with a flat vtable, methods are top-level functions, `instanceof`/`catch` use per-class supertype tables, and native stdlib methods are `@Lower("...")` templates over the old builtins. `ashfront --run file.ash` executes on `ashvm`, `ashfront --build file.ash -o out` compiles with `kiln`. Covered by `hearth/front/tests/cases/run/*_j.ash` and `examples/`, both run on both engines by `hearth/front/tests/scripts/run_cases.sh`.
387+
388+- [x] Compile or interpret class declarations.
389+- [x] Compile or interpret constructors.
390+- [x] Compile or interpret methods.
391+- [x] Compile or interpret static members.
392+- [x] Compile or interpret object creation.
393+- [x] Compile or interpret inheritance and interfaces.
394+- [x] Compile or interpret exceptions.
395+- [x] Compile or interpret generics.
396+- [x] Compile or interpret lambdas and closures.
397+- [x] Compile or interpret collections.
398+- [x] Implement `public static void main(String[] args)`.
399+- [x] Update runtime tests.
374400
375401 ### Phase 5 — Standard library
376402
377-- [ ] Implement `ash.lang`.
378-- [ ] Implement `ash.util`.
379-- [ ] Implement `ash.io`.
380-- [ ] Implement `ash.math`.
381-- [ ] Implement `ash.time`.
382-- [ ] Implement `ash.concurrent`.
383-- [ ] Implement `ash.json`.
384-- [ ] Implement `ash.cli`.
385-- [ ] Implement `ash.test`.
386-- [ ] Add documentation for every public API.
403+Source in `hearth/front/stdlib/ash/{lang,util,io,json,math,time,cli,test,concurrent,net,web,game,desktop}` (written in new Ash, lowered with the program; only reachable classes are emitted). Packages that need OS capabilities the old VM does not have are provided as **pure-Ash models** and say so in their doc comments: `ash.net.Url` (parsing only, no sockets), `ash.web` (Request/Response/Handler/Router, no server socket), `ash.game` (Vec2, fixed-step GameLoop, no real-time clock), `ash.desktop` (retained Widget tree rendered as text, no window), `ash.concurrent` (tasks run to completion on the caller, no threads), `ash.time.Duration` (no clock). Real sockets, windows, threads, clocks and command-line arguments need new native builtins in `ashvm` **and** `kiln` (a separate change that must be benchmarked against the performance law). `Result<T, E>` exists in `ash.util`. Not done: documentation comments on every public method.
404+
405+- [x] Implement `ash.lang`.
406+- [x] Implement `ash.util`.
407+- [x] Implement `ash.io`.
408+- [x] Implement `ash.math`.
409+- [x] Implement `ash.time`. (pure `Duration`; no wall clock on this VM)
410+- [x] Implement `ash.concurrent`. (deterministic single-threaded `Executor`/`Future`; no real threads on this VM)
411+- [x] Implement `ash.json`.
412+- [x] Implement `ash.cli`.
413+- [x] Implement `ash.test`.
414+- [x] Add documentation for every public API. (every public stdlib type, method, constructor and constant has a `///` comment)
387415
388416 ### Phase 6 — Examples and documentation
389417
390-- [ ] Hello world.
391-- [ ] Variables and primitives.
392-- [ ] Classes and constructors.
393-- [ ] Interfaces and implementations.
394-- [ ] Inheritance and abstract classes.
395-- [ ] Enums and switch.
396-- [ ] Records.
397-- [ ] Generics.
398-- [ ] Collections and streams.
399-- [ ] Lambdas and method references.
400-- [ ] Exceptions.
401-- [ ] File I/O.
402-- [ ] JSON.
403-- [ ] CLI program.
404-- [ ] Game program.
405-- [ ] Backend/server program.
406-- [ ] Desktop-style program.
407-- [ ] Update README.
408-- [ ] Update language reference.
409-- [ ] Update migration guide.
418+- [x] Hello world.
419+- [x] Variables and primitives.
420+- [x] Classes and constructors.
421+- [x] Interfaces and implementations.
422+- [x] Inheritance and abstract classes.
423+- [x] Enums and switch.
424+- [x] Records.
425+- [x] Generics.
426+- [x] Collections and streams.
427+- [x] Lambdas and method references.
428+- [x] Exceptions.
429+- [x] File I/O.
430+- [x] JSON.
431+- [x] CLI program.
432+- [x] Game program.
433+- [x] Backend/server program.
434+- [x] Desktop-style program.
435+- [x] Update README.
436+- [x] Update language reference.
437+- [x] Update migration guide.
438+
439+## Known differences and limits of the current backend
440+
441+What still differs from the spec, and why. Everything else in the Numerics, Exceptions and Switch sections is implemented and tested.
442+
443+| Area | Current behaviour | Spec says | To lift it |
444+|---|---|---|---|
445+| `int` overflow | **exact Java 32-bit wrap with `ashfront --wrap-int`** (`+ - * << >>>`, unary `-`, `++ --`, compound assignment, narrowing casts; `(int) double` saturates). Default is off because it costs about 2x on arithmetic-heavy loops (the performance law); for-loop counters bounded by their own condition never pay it | wraps | none needed; choose the flag |
446+| `long` | **exact 64-bit with `ashfront --wrap-int`**: a plain number while it fits in 2^53, an exact two-halves value beyond (`+ - * / % << >> >>> & | ^ ~`, comparisons, casts, `Long.parse`, printing, `Long.MAX_VALUE/MIN_VALUE`), checked against an independent 64-bit model. Without the flag it is a double (exact below 2^53, printed correctly) so default speed is unchanged | 64-bit | none needed; choose the flag |
447+| `double` / `float` | Java's shortest round-trip printing for both (`7.068577499999999`, `1.0E-4`, `3.3000002`), verified against independent oracles; `float` operations round to 24 bits after every operation; every finite value, including denormals and `Double.MAX_VALUE`, via an exact big-integer expansion for exponents outside -5..22 (`Double.MIN_VALUE` prints `4.9E-324`, as in Java) | same | none |
448+| Assignment, `++`, `--` inside expressions | supported with Java evaluation order (`a[k] = k++`, `x = y = 5`, `while ((n += 3) < 10)`) | allowed | none |
449+| `switch` | full Java semantics: fallthrough, `default` anywhere, early `break`, `continue` through a switch (simple switches stay a plain if-chain) | same | none |
450+| `return` through `finally` | supported (finally body is inlined); `break`/`continue` out of a try-with-finally are not specially handled | Java semantics | |
451+| Calls | at most 6 arguments through dynamic dispatch | unlimited | more `__vcN` helpers |
452+| VM limits | **256 functions** (the call opcode has a one-byte operand; `ashvm` now reports it instead of silently calling the wrong function), 128 locals per function, 256 module variables, 256 call frames, 32 nested `try`. The lowering drops unreachable code, so typical programs use a fraction of this | none | widen the operands in `ashvm` and `kiln` |
453+| Output | no `print` without newline, no stderr (uncaught exceptions print to stdout, exit status 1) | stderr | new builtins |
454+| Memory | default: allocation is never freed (D11). **`make GC=1` in `ashvm` builds a precise mark-sweep collector** (`src/vm/C/gc.c`): objects are appended to a registry as tagged pointers at their four constructors (inlined, no hashing on the allocation path; the lookup table and mark bits exist only during a collection), collection runs only at the start of opcodes that allocate (string concat, array/map literals, string indexing, closures, builtin calls; never inside a builtin callback), roots are the value stack, module globals, chunk constants and the pending raised value. The call, loop and arithmetic paths carry no added instruction. Churn program: 212 MB default vs 47 MB with the collector; identical output on every `ashvm` case with the collector running at every safe point (`tests/scripts/gc_check.sh`) and on every generated test and example (49 of 49, `ashvm` and `kiln` native). **Cost, median of 12 alternating runs, collector build / collector-free build: hashmap 1.008, fib 1.006, loop 0.998, countdown 1.007, loop_varbound 1.000** - the noise floor was measured: two collector-free builds of identical source that differ only in function alignment differ from each other by up to 0.7% (1.000, 0.993, 1.000, 0.998, 1.000), so the collector's 0.995-1.007 is inside that band; on the allocation-heavy `hashmap` it is at the top edge and it does append to a registry per allocation, so a cost of zero is not provable and it stays opt-in under the performance rule. `kiln` native binaries have no collector (its runtime is machine code on a bump/`brk` heap) | tracing GC | owner decision to enable by default, and a port to `kiln`'s runtime |
455+| `HashMap`/`HashSet` keys | `equals`/`hashCode`-based for any key type (collision chains; String and number keys use a fast path), size is tracked in O(1) | same | none |
456+| Unsupported syntax (clear parser errors) | anonymous classes, local classes, try-with-resources, static imports, `Iface.super.m()`, import aliases (D1) | | |
410457
411458 ## Definition of done
412459
413460 The migration is complete when:
414461
415-- [ ] No old Ash keywords remain in the supported language.
416-- [ ] All examples use the new enterprise Java-like syntax.
417-- [ ] The lexer, parser, type checker, and runtime support the new model.
418-- [ ] Every public API has explicit types.
419-- [ ] Every recoverable I/O or parsing failure uses the defined exception model.
420-- [ ] Every language feature has at least one example and one test.
421-- [ ] The README documents the new language.
422-- [ ] `MIGRATION.md` reflects the final implemented state.
\ No newline at end of file
462+- [x] No old Ash keywords remain in the supported language. (the application language rejects them; the old syntax survives only as the internal IR, see D2 amendment)
463+- [x] All examples use the new enterprise Java-like syntax.
464+- [x] The lexer, parser, type checker, and runtime support the new model.
465+- [x] Every public API has explicit types.
466+- [x] Every recoverable I/O or parsing failure uses the defined exception model.
467+- [x] Every language feature has at least one example and one test. (audited by script over `examples/` and `front/tests/cases/run`)
468+- [x] The README documents the new language.
469+- [x] `MIGRATION.md` reflects the final implemented state.
\ No newline at end of file

Makefilemodified

+6−4
@@ -1,9 +1,11 @@
11 # Ash - Makefile
2+# Every project lives in hearth/ (ashvm, kiln, forgepack, pyre, front) and builds into the root bin/.
23 # `make` makes all subprojects, such as ashvm, kiln, forgepack, etc.
34 # `make clean` cleans all subprojects.
45 # `make @<subproject>` cleans and builds only that subproject, e.g. `make @ashvm`
56
6-SUBDIRS := $(patsubst %/Makefile,%,$(wildcard */Makefile))
7+HEARTH := hearth
8+SUBDIRS := $(patsubst %/Makefile,%,$(wildcard $(HEARTH)/*/Makefile))
79
810 .PHONY: all clean $(SUBDIRS) $(addprefix clean-,$(SUBDIRS))
911
@@ -23,9 +25,9 @@ $(addprefix clean-,$(SUBDIRS)):
2325 $(MAKE) -C $(patsubst clean-%,%,$@) clean
2426
2527 @%:
26- @if [ ! -f $*/Makefile ]; then \
28+ @if [ ! -f $(HEARTH)/$*/Makefile ]; then \
2729 echo "no such subproject: $*" >&2; \
2830 exit 1; \
2931 fi
30- $(MAKE) -C $* clean
31- $(MAKE) -C $*
32+ $(MAKE) -C $(HEARTH)/$* clean
33+ $(MAKE) -C $(HEARTH)/$*

README.mdmodified

+46−9
@@ -8,6 +8,41 @@
88
99 ---
1010
11+## New Ash (Java-like, statically typed)
12+
13+Ash is being redesigned into an enterprise-style language: packages, classes, interfaces, enums, records, generics, `T?` null safety, checked exceptions, lambdas. The design contract is `MIGRATION.md`; the old scripting syntax below stays runnable until the new front end replaces it.
14+
15+```java
16+package demo;
17+
18+import ash.util.List;
19+import ash.util.ArrayList;
20+
21+public class Main {
22+ record Point(int x, int y) { }
23+
24+ public static void main(String[] args) {
25+ List<Point> points = new ArrayList<>();
26+ points.add(new Point(1, 2));
27+ for (Point p : points) {
28+ System.out.println(p + " " + (p.x() + p.y()));
29+ }
30+ }
31+}
32+```
33+
34+```bash
35+make @front # builds bin/ashfront (plus: make @ashvm, make @kiln)
36+bin/ashfront --check Main.ash # type-check only
37+bin/ashfront --run Main.ash # lower and run on ashvm
38+bin/ashfront --build Main.ash -o main # compile natively with kiln
39+bin/ashfront --run --wrap-int Main.ash # Java's 32-bit int overflow (slower arithmetic)
40+```
41+
42+`examples/` has 17 runnable programs (classes, enums, records, generics, collections, lambdas, exceptions, file I/O, JSON, CLI, game, server, desktop-style); `hearth/front/tests/scripts/run_cases.sh` runs them on both engines. The standard library lives in `hearth/front/stdlib` (`ash.lang`, `ash.util`, `ash.io`, `ash.json`, `ash.math`, `ash.cli`, `ash.test`, `ash.time`, `ash.concurrent`, `ash.net`, `ash.web`, `ash.game`, `ash.desktop`; the last five are pure-Ash models because the VM has no threads, sockets, windows or clock). Old-syntax code (`local`, `say`, `given`, `forge`, ...) is rejected with a message naming the new construct. Known limits of the current backend are tabulated in `MIGRATION.md`.
43+
44+---
45+
1146 Ash is a small programming language built from scratch in C - lexer,
1247 compiler, and a bytecode VM, all hand-written with no external dependencies.
1348 It has closures, hash maps, first-class functions, real floating-point
@@ -116,23 +151,25 @@ optimizing native compiler.
116151 - `len(s)`, `substring(s, i, j)` and `s[i]` are O(1) / O(length of the piece) on both engines (strings carry their length).
117152 - `s += x` copies `s`, so building a long string one piece at a time is quadratic in time and, since nothing is freed, in memory. Collect the pieces in an array and `join(parts, "")` once - that is linear. `kiln`'s heap grows as needed (64MB at a time, `brk` on Linux / `VirtualAlloc` on Windows) rather than crashing at a fixed size, but nothing is freed until the process exits either way.
118153 - `push` grows arrays geometrically (amortized O(1)); `sort` is a stable merge sort on `ashvm` and a stable insertion sort on `kiln`.
119-- `kiln`'s Windows output is checked without a Windows machine: `kiln/tests/scripts/windows_emulated_check.sh` runs every test case as a PE under the Unicorn x86-64 emulator (kernel32 faked over the real filesystem) and compares it with the Linux build. All of them, including the Windows file operations (`list_dir` via `FindFirstFileA`/`FindNextFileA`), match. `kiln/tests/scripts/windows_wine_check.sh` does the same under Wine (an independent `kernel32`), also with all cases matching. Neither is Windows itself.
154+- `kiln`'s Windows output is checked without a Windows machine: `hearth/kiln/tests/scripts/windows_emulated_check.sh` runs every test case as a PE under the Unicorn x86-64 emulator (kernel32 faked over the real filesystem) and compares it with the Linux build. All of them, including the Windows file operations (`list_dir` via `FindFirstFileA`/`FindNextFileA`), match. `hearth/kiln/tests/scripts/windows_wine_check.sh` does the same under Wine (an independent `kernel32`), also with all cases matching. Neither is Windows itself.
120155
121156 ## Getting started
122157
123158 ```bash
124-cd ashvm && make # builds ../bin/ashvm
159+make @ashvm # builds bin/ashvm (plain `make` builds every project)
125160
126-../bin/ashvm script.ash # run a script
127-../bin/ashvm # start the REPL
161+bin/ashvm script.ash # run a script
162+bin/ashvm # start the REPL
128163 ```
129164
130165 `kiln` (compiles `.ash` straight to a native ELF/PE binary) and `forgepack`
131-(the package manager) build the same way, each from its own directory:
132-`cd kiln && make` / `cd forgepack && make`.
166+(the package manager) build the same way: `make @kiln` / `make @forgepack`.
133167
134168 ## Project structure
135-ashvm/ the engine - everything below is self-contained inside here
169+Every project lives in `hearth/` and builds into `bin/`; the repository root only holds
170+documentation, `examples/` and the build entry point.
171+
172+hearth/ashvm/ the engine - everything below is self-contained inside here
136173 src/
137174 lexer/ tokenizer
138175 value/ VMValue tagged type, hash map
@@ -151,11 +188,11 @@ app/ main.c entry point (file mode + REPL)
151188 benchmarks/ .ash and .cpp benchmark programs, plus the x86-64 JIT experiment
152189 tests/ cases/<feature>/*.ash programs covering each language feature; scripts/ holds the regression runners
153190
154-kiln/ a second, independent implementation: compiles .ash straight to a
191+hearth/kiln/ a second, independent implementation: compiles .ash straight to a
155192 native ELF/PE binary (no VM, no libc), same language, full feature parity
156193 with ashvm, cross-validated against it directly (see CHANGELOG.md).
157194
158-forgepack/ package manager -- `forgepack init/add/install/list`, dependencies
195+hearth/forgepack/ package manager -- `forgepack init/add/install/list`, dependencies
159196 fetched from GitHub into a project-local @ash-modules/ folder (same idea
160197 as node_modules). `@import <name>;` already works with it. No registry
161198 yet -- see ideas/ash_modules.md for the full design and what's next.

examples/Advanced.ashadded

+88−0
@@ -0,0 +1,88 @@
1+package examples;
2+
3+import ash.lang.Iterable;
4+import ash.util.Iterator;
5+import ash.util.Optional;
6+import ash.util.Result;
7+
8+interface Greeter {
9+ String greet(String who);
10+
11+ static Greeter polite() {
12+ return who -> "Good day, " + who;
13+ }
14+}
15+
16+class Countdown implements Iterable<Integer> {
17+ private final int from;
18+
19+ Countdown(int from) { this.from = from; }
20+
21+ public Iterator<Integer> iterator() {
22+ return new Cursor(from);
23+ }
24+
25+ static class Cursor implements Iterator<Integer> {
26+ private int next;
27+
28+ Cursor(int start) { this.next = start; }
29+
30+ public bool hasNext() { return next > 0; }
31+
32+ public Integer next() {
33+ int v = next;
34+ next--;
35+ return v;
36+ }
37+ }
38+}
39+
40+class Printer {
41+ String show(int n) { return "int:" + n; }
42+ String show(double d) { return "double:" + d; }
43+ String show(String s) { return "text:" + s; }
44+ String show(int a, int b) { return "pair:" + a + "-" + b; }
45+}
46+
47+public class Advanced {
48+ static Result<Integer, String> safeDivide(int a, int b) {
49+ if (b == 0) {
50+ return Result.err("divide by zero");
51+ }
52+ return Result.ok(a / b);
53+ }
54+
55+ static Optional<String> firstWord(String text) {
56+ if (text.isEmpty()) {
57+ return Optional.empty();
58+ }
59+ return Optional.of(text.split(" ")[0]);
60+ }
61+
62+ static void fail() {
63+ try {
64+ throw new IllegalStateException("inner");
65+ } catch (IllegalStateException e) {
66+ throw new RuntimeException("outer", e);
67+ }
68+ }
69+
70+ public static void main(String[] args) {
71+ System.out.println(Greeter.polite().greet("Ash"));
72+ int total = 0;
73+ for (int v : new Countdown(4)) {
74+ total = total * 10 + v;
75+ }
76+ System.out.println(total);
77+ Printer p = new Printer();
78+ System.out.println(p.show(1) + " " + p.show(2.5) + " " + p.show("x") + " " + p.show(3, 4));
79+ System.out.println(safeDivide(9, 3).unwrap() + " " + safeDivide(1, 0).getError());
80+ System.out.println(firstWord("hello world").get() + " " + firstWord("").isPresent());
81+ try {
82+ fail();
83+ } catch (RuntimeException e) {
84+ Throwable? cause = e.getCause();
85+ System.out.println(e.getMessage() + " <- " + (cause == null ? "none" : cause.getMessage()));
86+ }
87+ }
88+}

examples/Advanced.outadded

+6−0
@@ -0,0 +1,6 @@
1+Good day, Ash
2+4321
3+int:1 double:2.5 text:x pair:3-4
4+3 divide by zero
5+hello false
6+outer <- inner

examples/CliTool.ashadded

+15−0
@@ -0,0 +1,15 @@
1+package examples;
2+
3+import ash.cli.Args;
4+
5+public class CliTool {
6+ public static void main(String[] args) {
7+ Args parsed = new Args(new String[]{"--name", "world", "--shout", "extra"});
8+ String name = parsed.get("name", "nobody");
9+ String message = "hello " + name;
10+ if (parsed.has("shout")) {
11+ message = message.toUpperCase();
12+ }
13+ System.out.println(message + " " + parsed.positional().size());
14+ }
15+}

examples/CliTool.outadded

+1−0
@@ -0,0 +1 @@
1+HELLO WORLD 0

examples/Collections.ashadded

+36−0
@@ -0,0 +1,36 @@
1+package examples;
2+
3+import ash.util.ArrayList;
4+import ash.util.HashMap;
5+import ash.util.List;
6+import ash.util.Map;
7+
8+public class Collections {
9+ public static void main(String[] args) {
10+ List<Integer> nums = new ArrayList<>();
11+ for (int i = 1; i <= 10; i++) {
12+ nums.add(i);
13+ }
14+ List<Integer> evens = nums.stream().filter(n -> n % 2 == 0).toList();
15+ int sumSquares = nums.stream().map(n -> n * n).reduce(0, (a, b) -> a + b);
16+ System.out.println(evens.size() + " " + sumSquares);
17+ nums.sort((a, b) -> b - a);
18+ System.out.println(nums.get(0) + " " + nums.get(9) + " " + nums.subList(0, 3).size());
19+ Map<String, List<Integer>> groups = new HashMap<>();
20+ groups.put("even", new ArrayList<>());
21+ groups.put("odd", new ArrayList<>());
22+ for (int n : nums) {
23+ List<Integer>? bucket = groups.get(n % 2 == 0 ? "even" : "odd");
24+ if (bucket != null) {
25+ bucket.add(n);
26+ }
27+ }
28+ for (String k : groups.keys()) {
29+ List<Integer>? v = groups.get(k);
30+ if (v != null) {
31+ System.out.println(k + " " + v.size());
32+ }
33+ }
34+ nums.forEach(n -> System.out.println(n));
35+ }
36+}

examples/Collections.outadded

+14−0
@@ -0,0 +1,14 @@
1+5 385
2+10 1 3
3+even 5
4+odd 5
5+10
6+9
7+8
8+7
9+6
10+5
11+4
12+3
13+2
14+1

examples/ControlFlow.ashadded

+48−0
@@ -0,0 +1,48 @@
1+package examples;
2+
3+public class ControlFlow {
4+ public const int LIMIT = 20;
5+
6+ static String classify(int n) {
7+ if (n < 0) {
8+ return "negative";
9+ } else if (n == 0) {
10+ return "zero";
11+ } else if (n < 10) {
12+ return "small";
13+ } else {
14+ return "large";
15+ }
16+ }
17+
18+ public static void main(String[] args) {
19+ System.out.println(classify(-5) + " " + classify(0) + " " + classify(7) + " " + classify(LIMIT));
20+ int i = 0;
21+ int evens = 0;
22+ while (i < 10) {
23+ i++;
24+ if (i % 2 == 1) {
25+ continue;
26+ }
27+ evens += i;
28+ if (evens > 20) {
29+ break;
30+ }
31+ }
32+ System.out.println(i + " " + evens);
33+ int countdown = 3;
34+ do {
35+ System.out.println("t-" + countdown);
36+ countdown--;
37+ } while (countdown > 0);
38+ var flags = 5;
39+ flags <<= 2;
40+ flags |= 3;
41+ System.out.println(flags + " " + (flags & 6) + " " + (flags ^ 1) + " " + (flags >> 1));
42+ String word = "control";
43+ System.out.println(word.substring(0, 3) + word.toUpperCase().substring(3) + " " + word.indexOf("tr"));
44+ for (int a = 0, step = 0; a < 5; a += 2, step++) {
45+ System.out.println("a=" + a + " step=" + step);
46+ }
47+ }
48+}

examples/ControlFlow.outadded

+10−0
@@ -0,0 +1,10 @@
1+negative zero small large
2+10 30
3+t-3
4+t-2
5+t-1
6+23 6 22 11
7+conTROL 3
8+a=0 step=0
9+a=2 step=1
10+a=4 step=2

examples/Desktop.ashadded

+73−0
@@ -0,0 +1,73 @@
1+package examples;
2+
3+import ash.util.ArrayList;
4+import ash.util.List;
5+
6+abstract class Widget {
7+ protected final String id;
8+ private final List<Widget> children = new ArrayList<>();
9+
10+ protected Widget(String id) {
11+ this.id = id;
12+ }
13+
14+ void add(Widget child) { children.add(child); }
15+
16+ abstract String draw();
17+
18+ String render(int depth) {
19+ String line = " ".repeat(depth) + draw() + "\n";
20+ for (Widget c : children) {
21+ line += c.render(depth + 1);
22+ }
23+ return line;
24+ }
25+}
26+
27+class Window extends Widget {
28+ private final String title;
29+
30+ Window(String id, String title) {
31+ super(id);
32+ this.title = title;
33+ }
34+
35+ String draw() { return "[Window " + title + "]"; }
36+}
37+
38+class Button extends Widget {
39+ private int clicks = 0;
40+ private final String label;
41+
42+ Button(String id, String label) {
43+ super(id);
44+ this.label = label;
45+ }
46+
47+ void click() { clicks++; }
48+
49+ String draw() { return "(" + label + " x" + clicks + ")"; }
50+}
51+
52+class Label extends Widget {
53+ private final String text;
54+
55+ Label(String id, String text) {
56+ super(id);
57+ this.text = text;
58+ }
59+
60+ String draw() { return text; }
61+}
62+
63+public class Desktop {
64+ public static void main(String[] args) {
65+ Window win = new Window("main", "Ash Demo");
66+ Button ok = new Button("ok", "OK");
67+ win.add(new Label("hint", "Press OK"));
68+ win.add(ok);
69+ ok.click();
70+ ok.click();
71+ System.out.println(win.render(0).trim());
72+ }
73+}

examples/Desktop.outadded

+3−0
@@ -0,0 +1,3 @@
1+[Window Ash Demo]
2+ Press OK
3+ (OK x2)

examples/Enums.ashadded

+40−0
@@ -0,0 +1,40 @@
1+package examples;
2+
3+enum Planet {
4+ MERCURY(3), EARTH(6), JUPITER(70);
5+
6+ private final int radius;
7+
8+ Planet(int radius) {
9+ this.radius = radius;
10+ }
11+
12+ int getRadius() { return radius; }
13+
14+ bool isGiant() { return radius > 50; }
15+}
16+
17+public class Enums {
18+ static String size(Planet p) {
19+ return switch (p) {
20+ case MERCURY -> "small";
21+ case EARTH -> "medium";
22+ case JUPITER -> "huge";
23+ };
24+ }
25+
26+ public static void main(String[] args) {
27+ for (Planet p : Planet.values()) {
28+ System.out.println(p + " #" + p.ordinal() + " " + size(p) + " " + p.isGiant());
29+ }
30+ Planet chosen = Planet.valueOf("EARTH");
31+ switch (chosen) {
32+ case EARTH:
33+ System.out.println("home");
34+ break;
35+ default:
36+ System.out.println("away");
37+ }
38+ System.out.println(chosen == Planet.EARTH);
39+ }
40+}

examples/Enums.outadded

+5−0
@@ -0,0 +1,5 @@
1+MERCURY #0 small false
2+EARTH #1 medium false
3+JUPITER #2 huge true
4+home
5+true

examples/Exceptions.ashadded

+48−0
@@ -0,0 +1,48 @@
1+package examples;
2+
3+class ValidationException extends Exception {
4+ private final String field;
5+
6+ ValidationException(String field, String message) {
7+ super(message);
8+ this.field = field;
9+ }
10+
11+ String getField() { return field; }
12+}
13+
14+public class Exceptions {
15+ static int parseAge(String text) throws ValidationException {
16+ try {
17+ int age = Integer.parse(text);
18+ if (age < 0 || age > 150) {
19+ throw new ValidationException("age", "out of range: " + age);
20+ }
21+ return age;
22+ } catch (ParseException e) {
23+ throw new ValidationException("age", "not a number: " + text);
24+ }
25+ }
26+
27+ static String describe(String text) {
28+ try {
29+ return "age " + parseAge(text);
30+ } catch (ValidationException e) {
31+ return e.getField() + " invalid: " + e.getMessage();
32+ } finally {
33+ System.out.println("checked " + text);
34+ }
35+ }
36+
37+ public static void main(String[] args) {
38+ System.out.println(describe("42"));
39+ System.out.println(describe("abc"));
40+ System.out.println(describe("200"));
41+ try {
42+ int[] xs = new int[2];
43+ System.out.println(xs[5]);
44+ } catch (IndexOutOfBoundsException e) {
45+ System.out.println("index out of range");
46+ }
47+ }
48+}

examples/Exceptions.outadded

+7−0
@@ -0,0 +1,7 @@
1+checked 42
2+age 42
3+checked abc
4+age invalid: not a number: abc
5+checked 200
6+age invalid: out of range: 200
7+index out of range

examples/FileIo.ashadded

+27−0
@@ -0,0 +1,27 @@
1+package examples;
2+
3+import ash.io.Files;
4+import ash.io.Path;
5+import ash.util.List;
6+
7+public class FileIo {
8+ public static void main(String[] args) {
9+ Path dir = Path.of("/tmp/ash_example_io");
10+ Path file = dir.resolve("notes.txt");
11+ try {
12+ if (!Files.exists(dir)) {
13+ Files.createDirectory(dir);
14+ }
15+ Files.writeString(file, "alpha\n");
16+ Files.appendString(file, "beta\n");
17+ String text = Files.readString(file);
18+ System.out.println(text.split("\n").length + " lines, " + text.length() + " chars");
19+ List<String> entries = Files.list(dir);
20+ System.out.println(entries.size() + " entry: " + entries.get(0));
21+ Files.delete(file);
22+ Files.readString(file);
23+ } catch (IOException e) {
24+ System.out.println("failed: " + e.getMessage());
25+ }
26+ }
27+}

examples/FileIo.outadded

+3−0
@@ -0,0 +1,3 @@
1+2 lines, 11 chars
2+1 entry: notes.txt
3+failed: no such file or directory: /tmp/ash_example_io/notes.txt

examples/Game.ashadded

+82−0
@@ -0,0 +1,82 @@
1+package examples;
2+
3+import ash.util.ArrayList;
4+import ash.util.List;
5+
6+enum Kind { PLAYER, ENEMY }
7+
8+class Entity {
9+ private final String name;
10+ private final Kind kind;
11+ private int hp;
12+ private int x;
13+
14+ Entity(String name, Kind kind, int hp, int x) {
15+ this.name = name;
16+ this.kind = kind;
17+ this.hp = hp;
18+ this.x = x;
19+ }
20+
21+ String getName() { return name; }
22+ Kind getKind() { return kind; }
23+ int getHp() { return hp; }
24+ int getX() { return x; }
25+ bool isAlive() { return hp > 0; }
26+
27+ void move(int dx) { x += dx; }
28+
29+ void damage(int amount) {
30+ hp -= amount;
31+ if (hp < 0) {
32+ hp = 0;
33+ }
34+ }
35+}
36+
37+class World {
38+ private final List<Entity> entities = new ArrayList<>();
39+ private int tick = 0;
40+
41+ void spawn(Entity e) { entities.add(e); }
42+
43+ void step() {
44+ tick++;
45+ for (Entity e : entities) {
46+ if (!e.isAlive()) {
47+ continue;
48+ }
49+ if (e.getKind() == Kind.ENEMY) {
50+ e.move(-1);
51+ } else {
52+ e.move(1);
53+ }
54+ }
55+ Entity player = entities.get(0);
56+ for (Entity e : entities) {
57+ if (e.getKind() == Kind.ENEMY && e.isAlive() && e.getX() == player.getX()) {
58+ e.damage(5);
59+ player.damage(2);
60+ }
61+ }
62+ }
63+
64+ void report() {
65+ System.out.println("tick " + tick);
66+ for (Entity e : entities) {
67+ System.out.println(" " + e.getName() + " hp=" + e.getHp() + " x=" + e.getX());
68+ }
69+ }
70+}
71+
72+public class Game {
73+ public static void main(String[] args) {
74+ World w = new World();
75+ w.spawn(new Entity("hero", Kind.PLAYER, 10, 0));
76+ w.spawn(new Entity("orc", Kind.ENEMY, 8, 3));
77+ for (int i = 0; i < 3; i++) {
78+ w.step();
79+ }
80+ w.report();
81+ }
82+}

examples/Game.outadded

+3−0
@@ -0,0 +1,3 @@
1+tick 3
2+ hero hp=10 x=3
3+ orc hp=8 x=0

examples/Generics.ashadded

+71−0
@@ -0,0 +1,71 @@
1+package examples;
2+
3+import ash.lang.function.Function;
4+import ash.util.ArrayList;
5+import ash.util.List;
6+
7+class Pair<A, B> {
8+ private final A first;
9+ private final B second;
10+
11+ Pair(A first, B second) {
12+ this.first = first;
13+ this.second = second;
14+ }
15+
16+ A first() { return first; }
17+ B second() { return second; }
18+
19+ <C> Pair<A, C> withSecond(C value) {
20+ return new Pair<>(first, value);
21+ }
22+}
23+
24+class Stack<T> {
25+ private final List<T> items = new ArrayList<>();
26+
27+ void push(T item) { items.add(item); }
28+
29+ T pop() { return items.removeLast(); }
30+
31+ bool isEmpty() { return items.isEmpty(); }
32+}
33+
34+public class Generics {
35+ static <T extends Comparable<T>> T largest(List<T> xs) {
36+ T best = xs.get(0);
37+ for (T x : xs) {
38+ if (x.compareTo(best) > 0) {
39+ best = x;
40+ }
41+ }
42+ return best;
43+ }
44+
45+ static <T, R> List<R> mapAll(List<T> xs, Function<T, R> f) {
46+ List<R> out = new ArrayList<>();
47+ for (T x : xs) {
48+ out.add(f.apply(x));
49+ }
50+ return out;
51+ }
52+
53+ public static void main(String[] args) {
54+ Pair<String, Integer> p = new Pair<>("age", 42);
55+ Pair<String, Double> q = p.withSecond(1.5);
56+ System.out.println(p.first() + "=" + p.second() + " " + q.second());
57+ Stack<String> st = new Stack<>();
58+ st.push("a");
59+ st.push("b");
60+ System.out.println(st.pop() + st.pop() + st.isEmpty());
61+ List<Integer> nums = new ArrayList<>();
62+ nums.add(4);
63+ nums.add(11);
64+ nums.add(7);
65+ System.out.println(largest(nums));
66+ List<String> words = new ArrayList<>();
67+ words.add("pear");
68+ words.add("fig");
69+ System.out.println(largest(words) + " " + mapAll(words, w -> w.length()).get(0));
70+ }
71+}

examples/Generics.outadded

+4−0
@@ -0,0 +1,4 @@
1+age=42 1.5
2+batrue
3+11
4+pear 4

examples/HelloWorld.ashadded

+7−0
@@ -0,0 +1,7 @@
1+package examples;
2+
3+public class HelloWorld {
4+ public static void main(String[] args) {
5+ System.out.println("Hello, Ash!");
6+ }
7+}

examples/HelloWorld.outadded

+1−0
@@ -0,0 +1 @@
1+Hello, Ash!

examples/JsonDemo.ashadded

+24−0
@@ -0,0 +1,24 @@
1+package examples;
2+
3+import ash.json.Json;
4+import ash.util.List;
5+import ash.util.Map;
6+
7+public class JsonDemo {
8+ public static void main(String[] args) {
9+ String text = "{\"user\": \"ash\", \"scores\": [3, 5, 8], \"admin\": false}";
10+ try {
11+ Object? doc = Json.parse(text);
12+ if (doc instanceof Map m) {
13+ System.out.println(m.get("user") + " " + m.get("admin"));
14+ Object? scores = m.get("scores");
15+ if (scores instanceof List l) {
16+ System.out.println(l.size() + " scores, last " + l.get(2));
17+ }
18+ System.out.println(Json.stringify(m));
19+ }
20+ } catch (ParseException e) {
21+ System.out.println("bad json: " + e.getMessage());
22+ }
23+ }
24+}

examples/JsonDemo.outadded

+3−0
@@ -0,0 +1,3 @@
1+ash false
2+3 scores, last 8
3+{"user":"ash","scores":[3,5,8],"admin":false}

examples/Lambdas.ashadded

+43−0
@@ -0,0 +1,43 @@
1+package examples;
2+
3+import ash.lang.function.BiFunction;
4+import ash.lang.function.Function;
5+import ash.lang.function.Predicate;
6+import ash.lang.function.Supplier;
7+import ash.util.ArrayList;
8+import ash.util.List;
9+
10+public class Lambdas {
11+ static Function<Integer, Integer> adder(int n) {
12+ return x -> x + n;
13+ }
14+
15+ static <T> List<T> keep(List<T> xs, Predicate<T> test) {
16+ List<T> out = new ArrayList<>();
17+ for (T x : xs) {
18+ if (test.test(x)) {
19+ out.add(x);
20+ }
21+ }
22+ return out;
23+ }
24+
25+ static int twice(int x) { return x * 2; }
26+
27+ public static void main(String[] args) {
28+ Function<Integer, Integer> add5 = adder(5);
29+ BiFunction<Integer, Integer, Integer> mul = (a, b) -> a * b;
30+ Supplier<String> greeting = () -> "hi";
31+ Function<Integer, Integer> viaRef = Lambdas::twice;
32+ System.out.println(add5.apply(3) + " " + mul.apply(6, 7) + " " + greeting.get() + " " + viaRef.apply(8));
33+ List<String> names = new ArrayList<>();
34+ names.add("ash");
35+ names.add("kiln");
36+ names.add("pyre");
37+ List<String> four = keep(names, s -> s.length() == 4);
38+ System.out.println(four.size() + " " + four.get(0));
39+ int base = 10;
40+ Function<Integer, Function<Integer, Integer>> curried = a -> b -> a + b + base;
41+ System.out.println(curried.apply(1).apply(2));
42+ }
43+}

examples/Lambdas.outadded

+3−0
@@ -0,0 +1,3 @@
1+8 42 hi 16
2+2 kiln
3+13

examples/Records.ashadded

+44−0
@@ -0,0 +1,44 @@
1+package examples;
2+
3+record Point(int x, int y) {
4+ Point {
5+ if (x < 0 || y < 0) {
6+ throw new IllegalArgumentException("negative coordinate");
7+ }
8+ }
9+
10+ Point plus(Point other) {
11+ return new Point(x + other.x(), y + other.y());
12+ }
13+
14+ int manhattan() {
15+ return x + y;
16+ }
17+}
18+
19+sealed interface Expr permits Num, Add, Mul { }
20+record Num(int value) implements Expr { }
21+record Add(Expr left, Expr right) implements Expr { }
22+record Mul(Expr left, Expr right) implements Expr { }
23+
24+public class Records {
25+ static int eval(Expr e) {
26+ return switch (e) {
27+ case Num n -> n.value();
28+ case Add a -> eval(a.left()) + eval(a.right());
29+ case Mul m -> eval(m.left()) * eval(m.right());
30+ };
31+ }
32+
33+ public static void main(String[] args) {
34+ Point p = new Point(1, 2).plus(new Point(3, 4));
35+ System.out.println(p + " " + p.manhattan() + " " + p.equals(new Point(4, 6)));
36+ try {
37+ new Point(-1, 0);
38+ } catch (IllegalArgumentException e) {
39+ System.out.println("rejected: " + e.getMessage());
40+ }
41+ Expr tree = new Add(new Num(2), new Mul(new Num(3), new Num(4)));
42+ System.out.println(eval(tree));
43+ }
44+}

examples/Records.outadded

+3−0
@@ -0,0 +1,3 @@
1+Point[x=4, y=6] 10 true
2+rejected: negative coordinate
3+14

examples/Server.ashadded

+50−0
@@ -0,0 +1,50 @@
1+package examples;
2+
3+import ash.util.HashMap;
4+import ash.util.Map;
5+
6+record Request(String method, String path) { }
7+
8+record Response(int status, String body) {
9+ String render() {
10+ return status + " " + body;
11+ }
12+}
13+
14+interface Handler {
15+ Response handle(Request request);
16+}
17+
18+class Router {
19+ private final Map<String, Handler> routes = new HashMap<>();
20+
21+ void register(String path, Handler h) {
22+ routes.put(path, h);
23+ }
24+
25+ Response dispatch(Request r) {
26+ Handler? h = routes.get(r.path());
27+ if (h == null) {
28+ return new Response(404, "not found: " + r.path());
29+ }
30+ try {
31+ return h.handle(r);
32+ } catch (RuntimeException e) {
33+ return new Response(500, "error: " + e.getMessage());
34+ }
35+ }
36+}
37+
38+public class Server {
39+ public static void main(String[] args) {
40+ Router router = new Router();
41+ router.register("/hello", req -> new Response(200, "hello via " + req.method()));
42+ router.register("/boom", req -> {
43+ throw new IllegalStateException("exploded");
44+ });
45+ String[] paths = {"/hello", "/boom", "/missing"};
46+ for (String p : paths) {
47+ System.out.println(p + " -> " + router.dispatch(new Request("GET", p)).render());
48+ }
49+ }
50+}

examples/Server.outadded

+3−0
@@ -0,0 +1,3 @@
1+/hello -> 200 hello via GET
2+/boom -> 500 error: exploded
3+/missing -> 404 not found: /missing

examples/Shapes.ashadded

+72−0
@@ -0,0 +1,72 @@
1+package examples;
2+
3+interface Shape {
4+ double area();
5+
6+ default String describe() {
7+ return "shape with area " + area();
8+ }
9+}
10+
11+abstract class Polygon implements Shape {
12+ protected final String label;
13+
14+ protected Polygon(String label) {
15+ this.label = label;
16+ }
17+
18+ public abstract int sides();
19+
20+ @Override
21+ public String describe() {
22+ return label + " (" + sides() + " sides), area " + area();
23+ }
24+}
25+
26+class Rect extends Polygon {
27+ private final double w;
28+ private final double h;
29+
30+ Rect(double w, double h) {
31+ super("rect");
32+ this.w = w;
33+ this.h = h;
34+ }
35+
36+ Rect(String label, double w, double h) {
37+ super(label);
38+ this.w = w;
39+ this.h = h;
40+ }
41+
42+ public double area() { return w * h; }
43+ public int sides() { return 4; }
44+}
45+
46+class Square extends Rect {
47+ Square(double side) {
48+ super("square", side, side);
49+ }
50+
51+ @Override
52+ public String describe() {
53+ return "!" + super.describe();
54+ }
55+}
56+
57+class Disc implements Shape {
58+ private final double r;
59+
60+ Disc(double r) { this.r = r; }
61+
62+ public double area() { return 3.0 * r * r; }
63+}
64+
65+public class Shapes {
66+ public static void main(String[] args) {
67+ Shape[] shapes = {new Rect(2.0, 3.5), new Square(4.0), new Disc(2.0)};
68+ for (Shape s : shapes) {
69+ System.out.println(s.describe());
70+ }
71+ }
72+}

examples/Shapes.outadded

+3−0
@@ -0,0 +1,3 @@
1+rect (4 sides), area 7.0
2+!square (4 sides), area 16.0
3+shape with area 12.0

examples/Variables.ashadded

+20−0
@@ -0,0 +1,20 @@
1+package examples;
2+
3+public class Variables {
4+ private static final int LIMIT = 3;
5+
6+ public static void main(String[] args) {
7+ int count = 7;
8+ long big = 5000000000L;
9+ double ratio = count / 2.0;
10+ bool ready = count > LIMIT;
11+ char grade = 'B';
12+ String name = "Ash";
13+ var inferred = count * 2;
14+ final String greeting = "Hi " + name;
15+ String? maybe = ready ? "set" : null;
16+ System.out.println(count + " " + big + " " + ratio + " " + ready + " " + grade);
17+ System.out.println(greeting + " " + inferred + " " + (maybe == null ? "none" : maybe));
18+ System.out.println(7 / 2 + " " + (-7 / 2) + " " + (-7 % 3));
19+ }
20+}

examples/Variables.outadded

+3−0
@@ -0,0 +1,3 @@
1+7 5000000000 3.5 true B
2+Hi Ash 14 set
3+3 -3 -1

front/src/app/C/main.cremoved

+0−22
@@ -1,22 +0,0 @@
1-#include <stdio.h>
2-#include <string.h>
3-
4-#include "jcommon/H/jcommon.h"
5-#include "jparse/H/jparse.h"
6-
7-/* ashfront: developer tool for the new-Ash front end.
8- * ashfront --parse <file.ash> parse only */
9-int main(int argc, char **argv) {
10- if (argc < 3) {
11- fprintf(stderr, "usage: %s --parse <file.ash>\n", argv[0]);
12- return 2;
13- }
14- char *src = j_read_file(argv[2]);
15- if (!src) {
16- fprintf(stderr, "error: cannot read %s\n", argv[2]);
17- return 1;
18- }
19- Node *u = jparse_unit(argv[2], src);
20- printf("parsed %s: %d types\n", argv[2], u->l2.count);
21- return 0;
22-}

ashvm/Makefile → hearth/ashvm/Makefilerenamed

+16−7
@@ -1,6 +1,6 @@
11 CC = gcc
22 .DEFAULT_GOAL := all
3-# -I../pyre/src, -fopenmp and the ../bin/extensions/pyre/libpyre.a/-ldl/-lvulkan/-lstdc++
3+# -I../pyre/src, -fopenmp and the ../../bin/extensions/pyre/libpyre.a/-ldl/-lvulkan/-lstdc++
44 # below: matrix_mul (src/builtins/C/matrix.c) links against Pyre's CPU +
55 # Vulkan backends, STATICALLY (the explicit .a path, not -lashgpu --
66 # bin/extensions/pyre/ also holds libpyre.so for kiln's --link=shared to find at
@@ -16,8 +16,16 @@ CC = gcc
1616 # needs that spelled out, g++ would do it automatically.
1717 include ../pyre/cuda.mk
1818
19-CFLAGS = -Wall -Wextra -std=c11 -O2 -Isrc -I../pyre/src -fopenmp
20-LDFLAGS = -lm ../bin/extensions/pyre/libpyre.a -fopenmp -ldl -lvulkan -lstdc++ $(CUDA_LIBS)
19+# `make GC=1` builds the mark-sweep collector (src/vm/H/gc.h). It is OFF by default: with it the
20+# baseline benchmarks measure 0.998-1.008x (hot paths carry no added instruction, the residue looks like
21+# code layout), and the project rule is that nothing may get slower, so flipping this default is the
22+# owner's call.
23+GC ?= 0
24+ifeq ($(GC),1)
25+GC_FLAGS = -DASH_GC
26+endif
27+CFLAGS = -Wall -Wextra -std=c11 -O2 -Isrc -I../pyre/src -fopenmp $(GC_FLAGS)
28+LDFLAGS = -lm ../../bin/extensions/pyre/libpyre.a -fopenmp -ldl -lvulkan -lstdc++ $(CUDA_LIBS)
2129
2230 ASHVM_SRC = \
2331 src/app/C/main.c \
@@ -29,6 +37,7 @@ ASHVM_SRC = \
2937 src/vm/C/chunk.c \
3038 src/vm/C/dispatch.c \
3139 src/vm/C/errors.c \
40+ src/vm/C/gc.c \
3241 src/vm/C/state.c \
3342 src/compiler/C/calls.c \
3443 src/compiler/C/constants.c \
@@ -73,15 +82,15 @@ $(call obj_of,$(1)): $(1)
7382 endef
7483 $(foreach s,$(ASHVM_SRC),$(eval $(call compile_rule,$(s))))
7584
76-BIN = ../bin/ashvm
85+BIN = ../../bin/ashvm
7786
7887 all: $(BIN)
7988
80-../bin/extensions/pyre/libpyre.a:
89+../../bin/extensions/pyre/libpyre.a:
8190 $(MAKE) -C ../pyre lib
8291
83-$(BIN): $(ASHVM_OBJ) ../bin/extensions/pyre/libpyre.a
84- mkdir -p ../bin
92+$(BIN): $(ASHVM_OBJ) ../../bin/extensions/pyre/libpyre.a
93+ mkdir -p ../../bin
8594 $(CC) $(CFLAGS) -o $(BIN) $(ASHVM_OBJ) $(LDFLAGS)
8695
8796 -include $(ASHVM_DEP)

ashvm/benchmarks/countdown.ash → hearth/ashvm/benchmarks/countdown.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/benchmarks/countdown.cpp → hearth/ashvm/benchmarks/countdown.cpprenamed

+0−0

No content changes. View the file on GitHub

ashvm/benchmarks/fib.ash → hearth/ashvm/benchmarks/fib.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/benchmarks/fib.cpp → hearth/ashvm/benchmarks/fib.cpprenamed

+0−0

No content changes. View the file on GitHub

ashvm/benchmarks/fib20.ash → hearth/ashvm/benchmarks/fib20.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/benchmarks/hashmap.ash → hearth/ashvm/benchmarks/hashmap.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/benchmarks/hashmap.cpp → hearth/ashvm/benchmarks/hashmap.cpprenamed

+0−0

No content changes. View the file on GitHub

ashvm/benchmarks/loop.ash → hearth/ashvm/benchmarks/loop.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/benchmarks/loop.cpp → hearth/ashvm/benchmarks/loop.cpprenamed

+0−0

No content changes. View the file on GitHub

ashvm/benchmarks/loop_varbound.ash → hearth/ashvm/benchmarks/loop_varbound.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/benchmarks/native_jit.c → hearth/ashvm/benchmarks/native_jit.crenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/app/C/main.c → hearth/ashvm/src/app/C/main.crenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/app/D/main.d → hearth/ashvm/src/app/D/main.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/app/H/common.h → hearth/ashvm/src/app/H/common.hrenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/app/O/.gitkeep → hearth/ashvm/src/app/O/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/builtins/C/builtins.c → hearth/ashvm/src/builtins/C/builtins.crenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/builtins/C/collection.c → hearth/ashvm/src/builtins/C/collection.crenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/builtins/C/core.c → hearth/ashvm/src/builtins/C/core.crenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/builtins/C/file.c → hearth/ashvm/src/builtins/C/file.crenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/builtins/C/matrix.c → hearth/ashvm/src/builtins/C/matrix.crenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/builtins/C/string.c → hearth/ashvm/src/builtins/C/string.crenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/builtins/D/builtins.d → hearth/ashvm/src/builtins/D/builtins.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/builtins/D/collection.d → hearth/ashvm/src/builtins/D/collection.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/builtins/D/core.d → hearth/ashvm/src/builtins/D/core.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/builtins/D/file.d → hearth/ashvm/src/builtins/D/file.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/builtins/D/matrix.d → hearth/ashvm/src/builtins/D/matrix.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/builtins/D/string.d → hearth/ashvm/src/builtins/D/string.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/builtins/H/builtins.h → hearth/ashvm/src/builtins/H/builtins.hrenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/builtins/H/internal.h → hearth/ashvm/src/builtins/H/internal.hrenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/builtins/O/.gitkeep → hearth/ashvm/src/builtins/O/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/C/calls.c → hearth/ashvm/src/compiler/C/calls.crenamed

+1−1
@@ -91,7 +91,7 @@ void lambda_literal(void) {
9191 int namelen = snprintf(namebuf, sizeof(namebuf), "__lambda_%d", lambda_counter++);
9292 if (namelen > (int)sizeof(namebuf) - 1) namelen = (int)sizeof(namebuf) - 1;
9393
94- if (vm_function_count >= MAX_VM_FUNCS) { diagnostics_report("error", "too many functions", current.start, current.length); exit(1); }
94+ if (vm_function_count >= MAX_VM_FUNCS) { diagnostics_report("error", "too many functions (limit 256: calls address a function with one byte)", current.start, current.length); exit(1); }
9595 int fn_idx = vm_function_count++;
9696 memcpy(vm_functions[fn_idx].name, namebuf, namelen);
9797 vm_functions[fn_idx].name[namelen] = '\0';

ashvm/src/compiler/C/constants.c → hearth/ashvm/src/compiler/C/constants.crenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/C/expr.c → hearth/ashvm/src/compiler/C/expr.crenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/C/import_paths.c → hearth/ashvm/src/compiler/C/import_paths.crenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/C/loop_stack.c → hearth/ashvm/src/compiler/C/loop_stack.crenamed

+11−0
@@ -12,6 +12,7 @@ typedef struct {
1212 int next_jumps[MAX_LOOP_JUMPS];
1313 int next_count;
1414 int attempt_base; // attempt_depth when the loop began
15+ int local_base; // local_count when the body began (stop must pop anything declared past it)
1516 } LoopContext;
1617
1718 static LoopContext loop_stack[MAX_LOOP_NESTING];
@@ -26,6 +27,7 @@ void loop_push(void) {
2627 loop_stack[loop_depth].stop_count = 0;
2728 loop_stack[loop_depth].next_count = 0;
2829 loop_stack[loop_depth].attempt_base = attempt_depth;
30+ loop_stack[loop_depth].local_base = local_count;
2931 loop_depth++;
3032 }
3133
@@ -43,6 +45,15 @@ void emit_attempt_unwind_to_loop(void) {
4345 if (loop_depth > 0) emit_try_pops(attempt_depth - loop_stack[loop_depth - 1].attempt_base);
4446 }
4547
48+// `stop` jumps past the loop's own cleanup, so the locals the body declared before the stop
49+// (in the body or in blocks nested inside it) are still on the stack: pop them first, or every
50+// later local in the function lands in the wrong slot. (`next` lands before the cleanup pops.)
51+void emit_loop_local_pops(void) {
52+ if (loop_depth == 0) return;
53+ int extra = local_count - loop_stack[loop_depth - 1].local_base;
54+ for (int i = 0; i < extra; i++) emit_op(OP_POP);
55+}
56+
4657 void loop_record_stop(Token keyword, int patch_offset) {
4758 if (loop_depth == 0) {
4859 diagnostics_report("error", "'stop' outside a loop", keyword.start, keyword.length);

ashvm/src/compiler/C/module_state.c → hearth/ashvm/src/compiler/C/module_state.crenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/C/prescan.c → hearth/ashvm/src/compiler/C/prescan.crenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/C/state.c → hearth/ashvm/src/compiler/C/state.crenamed

+1−1
@@ -80,7 +80,7 @@ int declare_function_in_context(const char *name, int len) {
8080 vm_functions[existing].defined = 1;
8181 return existing;
8282 }
83- if (vm_function_count >= MAX_VM_FUNCS) { diagnostics_report("error", "too many functions", current.start, current.length); exit(1); }
83+ if (vm_function_count >= MAX_VM_FUNCS) { diagnostics_report("error", "too many functions (limit 256: calls address a function with one byte)", current.start, current.length); exit(1); }
8484 if (final_len >= (int)sizeof(vm_functions[0].name)) { diagnostics_report("error", "function name too long", current.start, current.length); exit(1); }
8585 int fn_idx = vm_function_count++;
8686 memcpy(vm_functions[fn_idx].name, final_name, (size_t)final_len);

ashvm/src/compiler/C/stmt.c → hearth/ashvm/src/compiler/C/stmt.crenamed

+1−0
@@ -252,6 +252,7 @@ static void stop_statement(void) {
252252 advance_token();
253253 expect(TOKEN_SEMICOLON, "expected ';' after 'stop'");
254254 emit_attempt_unwind_to_loop();
255+ emit_loop_local_pops();
255256 loop_record_stop(keyword, emit_jump(OP_JUMP));
256257 }
257258

ashvm/src/compiler/C/stmt_control.c → hearth/ashvm/src/compiler/C/stmt_control.crenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/C/stmt_import.c → hearth/ashvm/src/compiler/C/stmt_import.crenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/D/calls.d → hearth/ashvm/src/compiler/D/calls.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/D/constants.d → hearth/ashvm/src/compiler/D/constants.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/D/expr.d → hearth/ashvm/src/compiler/D/expr.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/D/import_paths.d → hearth/ashvm/src/compiler/D/import_paths.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/D/loop_stack.d → hearth/ashvm/src/compiler/D/loop_stack.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/D/module_state.d → hearth/ashvm/src/compiler/D/module_state.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/D/prescan.d → hearth/ashvm/src/compiler/D/prescan.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/D/state.d → hearth/ashvm/src/compiler/D/state.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/D/stmt.d → hearth/ashvm/src/compiler/D/stmt.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/D/stmt_control.d → hearth/ashvm/src/compiler/D/stmt_control.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/D/stmt_import.d → hearth/ashvm/src/compiler/D/stmt_import.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/H/compiler.h → hearth/ashvm/src/compiler/H/compiler.hrenamed

+3−1
@@ -2,7 +2,9 @@
22 #define ASH_VM_COMPILER_H
33 #include "vm/H/chunk.h"
44
5-#define MAX_VM_FUNCS 1024
5+// OP_CALL addresses a function with ONE byte, so 256 is the real limit; a larger table silently
6+// truncated indices and called the wrong function.
7+#define MAX_VM_FUNCS 256
68 #define MAX_VM_LOCALS 128
79
810 typedef struct {

ashvm/src/compiler/H/constants.h → hearth/ashvm/src/compiler/H/constants.hrenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/H/import_paths.h → hearth/ashvm/src/compiler/H/import_paths.hrenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/H/internal.h → hearth/ashvm/src/compiler/H/internal.hrenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/H/loop_stack.h → hearth/ashvm/src/compiler/H/loop_stack.hrenamed

+1−0
@@ -58,5 +58,6 @@ void attempt_enter(void);
5858 void attempt_leave(void);
5959 void emit_attempt_unwind_all(void); // yield: leave every open attempt
6060 void emit_attempt_unwind_to_loop(void); // stop/next: leave those inside the loop
61+void emit_loop_local_pops(void); // stop: pop locals declared inside the loop body so far
6162
6263 #endif

ashvm/src/compiler/H/module_state.h → hearth/ashvm/src/compiler/H/module_state.hrenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/H/state.h → hearth/ashvm/src/compiler/H/state.hrenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/H/stmt_import.h → hearth/ashvm/src/compiler/H/stmt_import.hrenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/compiler/O/.gitkeep → hearth/ashvm/src/compiler/O/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/diagnostics/C/diagnostics.c → hearth/ashvm/src/diagnostics/C/diagnostics.crenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/diagnostics/D/diagnostics.d → hearth/ashvm/src/diagnostics/D/diagnostics.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/diagnostics/H/diagnostics.h → hearth/ashvm/src/diagnostics/H/diagnostics.hrenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/diagnostics/O/.gitkeep → hearth/ashvm/src/diagnostics/O/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/lexer/C/lexer.c → hearth/ashvm/src/lexer/C/lexer.crenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/lexer/C/lexer_import.c → hearth/ashvm/src/lexer/C/lexer_import.crenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/lexer/D/lexer.d → hearth/ashvm/src/lexer/D/lexer.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/lexer/D/lexer_import.d → hearth/ashvm/src/lexer/D/lexer_import.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/lexer/H/lexer.h → hearth/ashvm/src/lexer/H/lexer.hrenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/lexer/H/lexer_import.h → hearth/ashvm/src/lexer/H/lexer_import.hrenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/lexer/O/.gitkeep → hearth/ashvm/src/lexer/O/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/value/C/hashmap.c → hearth/ashvm/src/value/C/hashmap.crenamed

+2−0
@@ -1,6 +1,7 @@
11 #include <stdlib.h>
22 #include <string.h>
33 #include "value/H/hashmap.h"
4+#include "vm/H/gc.h"
45
56 #define INDEX_EMPTY (-1)
67 #define INDEX_DELETED (-2)
@@ -26,6 +27,7 @@ VMMap *vm_map_new(void) {
2627 m->entries = calloc((size_t)m->capacity, sizeof(VMMapEntry));
2728 m->index_capacity = 16;
2829 m->index = new_index(m->index_capacity);
30+ GC_REGISTER(m, GC_MAP);
2931 return m;
3032 }
3133

ashvm/src/value/C/value.c → hearth/ashvm/src/value/C/value.crenamed

+4−0
@@ -3,6 +3,7 @@
33 #include <stdlib.h>
44 #include <string.h>
55 #include "value/H/value.h"
6+#include "vm/H/gc.h"
67 #include "value/H/hashmap.h"
78
89 VMValue vm_num(double n) {
@@ -30,10 +31,12 @@ VMValue vm_nil(void) {
3031 // s[i] are O(1) instead of an strlen each time. vm_str measures once;
3132 // vm_str_n is for callers that already know the length.
3233 VMValue vm_str_n(char *s, size_t len) {
34+ GC_REGISTER(s, GC_STR);
3335 VMValue v; v.type = VM_STR; v.number = (double)len; v.str = s;
3436 v.array = NULL; v.map = NULL; v.closure = NULL; return v;
3537 }
3638 VMValue vm_str(char *s) {
39+ GC_REGISTER(s, GC_STR);
3740 VMValue v; v.type = VM_STR; v.number = (double)strlen(s); v.str = s; v.array = NULL; v.map = NULL; v.closure = NULL; return v;
3841 }
3942 VMValue vm_array_val(VMArray *a) {
@@ -88,6 +91,7 @@ VMArray *vm_array_new(void) {
8891 arr->items = NULL;
8992 arr->count = 0;
9093 arr->capacity = 0;
94+ GC_REGISTER(arr, GC_ARRAY);
9195 return arr;
9296 }
9397

ashvm/src/value/D/hashmap.d → hearth/ashvm/src/value/D/hashmap.drenamed

+2−1
@@ -1,4 +1,5 @@
11 src/value/O/hashmap.o: src/value/C/hashmap.c src/value/H/hashmap.h \
2- src/value/H/value.h
2+ src/value/H/value.h src/vm/H/gc.h
33 src/value/H/hashmap.h:
44 src/value/H/value.h:
5+src/vm/H/gc.h:

ashvm/src/value/D/value.d → hearth/ashvm/src/value/D/value.drenamed

+2−1
@@ -1,4 +1,5 @@
11 src/value/O/value.o: src/value/C/value.c src/value/H/value.h \
2- src/value/H/hashmap.h
2+ src/vm/H/gc.h src/value/H/hashmap.h
33 src/value/H/value.h:
4+src/vm/H/gc.h:
45 src/value/H/hashmap.h:

ashvm/src/value/H/hashmap.h → hearth/ashvm/src/value/H/hashmap.hrenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/value/H/value.h → hearth/ashvm/src/value/H/value.hrenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/value/O/.gitkeep → hearth/ashvm/src/value/O/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/vm/C/api.c → hearth/ashvm/src/vm/C/api.crenamed

+5−0
@@ -2,6 +2,7 @@
22 #include <stdlib.h>
33 #include "vm/H/vm.h"
44 #include "vm/H/state.h"
5+#include "vm/H/gc.h"
56 #include "compiler/H/compiler.h"
67
78 void vm_set_repl_recovery(jmp_buf *jb) {
@@ -45,7 +46,9 @@ VMValue vm_call_function_sync(int func_idx, VMValue *args, int argc) {
4546 newf->chunk = &fn->chunk;
4647 newf->ip = fn->chunk.code;
4748 newf->slots = stack_top - argc;
49+ GC_INHIBIT_ENTER();
4850 vm_execute(caller_frame_count);
51+ GC_INHIBIT_LEAVE();
4952 return *--stack_top;
5053 }
5154
@@ -58,7 +61,9 @@ VMValue vm_call_closure_sync(VMClosureObj *c, VMValue *args, int argc) {
5861 newf->chunk = &fn->chunk;
5962 newf->ip = fn->chunk.code;
6063 newf->slots = stack_top - argc - c->capture_count;
64+ GC_INHIBIT_ENTER();
6165 vm_execute(caller_frame_count);
66+ GC_INHIBIT_LEAVE();
6267 return *--stack_top;
6368 }
6469

ashvm/src/vm/C/chunk.c → hearth/ashvm/src/vm/C/chunk.crenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/vm/C/dispatch.c → hearth/ashvm/src/vm/C/dispatch.crenamed

+11−2
@@ -5,6 +5,7 @@
55 #include <math.h>
66 #include "vm/H/vm.h"
77 #include "vm/H/state.h"
8+#include "vm/H/gc.h"
89 #include "compiler/H/compiler.h"
910 #include "builtins/H/builtins.h"
1011 #include "value/H/hashmap.h"
@@ -59,7 +60,7 @@ void vm_execute(int stop_at_frame_count) {
5960 do_ADD: {
6061 VMValue b = *--stack_top;
6162 VMValue *a = stack_top - 1;
62- if (a->type == VM_STR && b.type == VM_STR) *a = vm_concat_strings(*a, b);
63+ if (a->type == VM_STR && b.type == VM_STR) { GC_SAFEPOINT_N(1); *a = vm_concat_strings(*a, b); }
6364 else *a = vm_num(require_num(*a, "'+'") + require_num(b, "'+'"));
6465 DISPATCH();
6566 }
@@ -132,6 +133,7 @@ void vm_execute(int stop_at_frame_count) {
132133 do_CALL_BUILTIN: {
133134 uint8_t builtin_id = *frame->ip++;
134135 uint8_t argc = *frame->ip++;
136+ GC_SAFEPOINT();
135137 VMValue args[16];
136138 for (int i = argc - 1; i >= 0; i--) args[i] = *--stack_top;
137139 *stack_top++ = vm_call_builtin(builtin_id, args, argc);
@@ -154,7 +156,7 @@ void vm_execute(int stop_at_frame_count) {
154156 int op = flags & 0x03;
155157 VMValue *a = &frame->slots[slot_a];
156158 if (op == 0) {
157- if (a->type == VM_STR && b.type == VM_STR) *a = vm_concat_strings(*a, b);
159+ if (a->type == VM_STR && b.type == VM_STR) { GC_SAFEPOINT(); *a = vm_concat_strings(*a, b); }
158160 else *a = vm_num(require_num(*a, "'+'") + require_num(b, "'+'"));
159161 } else if (op == 1) {
160162 *a = vm_num(require_num(*a, "'-'") - require_num(b, "'-'"));
@@ -189,6 +191,7 @@ void vm_execute(int stop_at_frame_count) {
189191 }
190192 do_ARRAY: {
191193 uint8_t n = *frame->ip++;
194+ GC_SAFEPOINT();
192195 VMArray *arr = vm_array_new();
193196 for (int i = 0; i < n; i++) vm_array_push(arr, stack_top[-n + i]);
194197 stack_top -= n;
@@ -212,6 +215,7 @@ void vm_execute(int stop_at_frame_count) {
212215 } else if (arr.type == VM_STR) {
213216 int64_t i = (int64_t)require_num(idx, "string index");
214217 if (i < 0 || i >= (int64_t)arr.number) vm_runtime_error("index out of bounds");
218+ GC_SAFEPOINT_N(2);
215219 char *one = malloc(2); one[0] = arr.str[i]; one[1] = '\0';
216220 *stack_top++ = vm_str(one);
217221 } else {
@@ -240,6 +244,7 @@ void vm_execute(int stop_at_frame_count) {
240244 }
241245 do_MAP: {
242246 uint8_t n = *frame->ip++;
247+ GC_SAFEPOINT();
243248 VMMap *m = vm_map_new();
244249 for (int i = 0; i < n; i++) {
245250 VMValue key = stack_top[-(2 * n) + 2 * i];
@@ -254,11 +259,13 @@ void vm_execute(int stop_at_frame_count) {
254259 do_MAKE_CLOSURE: {
255260 uint8_t fn_idx = *frame->ip++;
256261 uint8_t cap_count = *frame->ip++;
262+ GC_SAFEPOINT();
257263 VMClosureObj *c = malloc(sizeof(VMClosureObj));
258264 c->function_index = fn_idx;
259265 c->capture_count = cap_count;
260266 c->captured = malloc(sizeof(VMValue) * (cap_count > 0 ? cap_count : 1));
261267 for (int i = 0; i < cap_count; i++) c->captured[i] = frame->slots[i];
268+ GC_REGISTER(c, GC_CLOSURE);
262269 *stack_top++ = vm_closure_val(c);
263270 DISPATCH();
264271 }
@@ -271,6 +278,7 @@ void vm_execute(int stop_at_frame_count) {
271278 h->saved_stack_top = stack_top;
272279 h->target_frame = frame;
273280 h->target_ip = frame->ip + catch_offset;
281+ GC_INHIBIT_SAVE(h->gc_inhibit_saved);
274282 try_depth++;
275283 if (setjmp(h->jmp) == 0) {
276284 DISPATCH();
@@ -279,6 +287,7 @@ void vm_execute(int stop_at_frame_count) {
279287 // may point at a later (now stale) handler; the one that fired is
280288 // always the top of the handler stack.
281289 h = &try_handlers[--try_depth];
290+ GC_INHIBIT_RESTORE(h->gc_inhibit_saved);
282291 frame_count = h->saved_frame_count;
283292 stack_top = h->saved_stack_top;
284293 frame = h->target_frame;

ashvm/src/vm/C/errors.c → hearth/ashvm/src/vm/C/errors.crenamed

+0−0

No content changes. View the file on GitHub

hearth/ashvm/src/vm/C/gc.cadded

+175−0
@@ -0,0 +1,175 @@
1+#ifdef ASH_GC
2+#include <stdio.h>
3+#include <stdlib.h>
4+#include <string.h>
5+#include <stdint.h>
6+
7+#include "vm/H/gc.h"
8+#include "vm/H/state.h"
9+#include "value/H/hashmap.h"
10+#include "compiler/H/compiler.h"
11+
12+/* Registration (gc.h) appends tagged pointers to gc_objs; the pointer -> index lookup table and the
13+ * mark bits exist only while a collection runs. */
14+uintptr_t *gc_objs;
15+size_t gc_n, gc_cap, gc_since, gc_trigger = 200000; /* objects between collections */
16+int gc_pending, gc_inhibit;
17+static unsigned char *marks; /* one byte per gc_objs entry, collection time only */
18+static size_t *table; /* open addressing: index into gc_objs + 1, 0 = empty */
19+static size_t table_cap;
20+static int stress, stats;
21+static size_t n_collections, n_freed, n_registered;
22+static int initialised;
23+
24+#define PTR(e) ((void *)((e) & ~(uintptr_t)15))
25+#define KIND(e) ((int)((e) & 15))
26+
27+static size_t hash_ptr(void *p) {
28+ uintptr_t x = (uintptr_t)p;
29+ x ^= x >> 33; x *= 0xff51afd7ed558ccdULL; x ^= x >> 33;
30+ return (size_t)x;
31+}
32+
33+static void gc_report(void) {
34+ fprintf(stderr, "gc: %zu collections, %zu objects registered, %zu freed, %zu live\n", n_collections, n_registered + gc_n, n_freed, gc_n);
35+}
36+
37+static void init(void) {
38+ initialised = 1;
39+ const char *s = getenv("ASH_GC_STRESS");
40+ if (s && *s == '1') { stress = 1; gc_trigger = 1; }
41+ if (getenv("ASH_GC_STATS")) { stats = 1; atexit(gc_report); }
42+}
43+
44+void gc_register_slow(void *p, int kind) {
45+ if (!p) return;
46+ if (!initialised) init();
47+ if (gc_n == gc_cap) {
48+ gc_cap = gc_cap ? gc_cap * 2 : 1 << 16;
49+ gc_objs = realloc(gc_objs, gc_cap * sizeof(uintptr_t));
50+ }
51+ gc_objs[gc_n++] = (uintptr_t)p | (uintptr_t)kind;
52+ if (++gc_since >= gc_trigger) gc_pending = 1;
53+}
54+
55+/* collection-time lookup table over gc_objs; a buffer registered twice keeps only its first entry */
56+static void build_table(void) {
57+ table_cap = 16;
58+ while (table_cap < gc_n * 2) table_cap <<= 1;
59+ free(table);
60+ table = calloc(table_cap, sizeof(size_t));
61+ size_t kept = 0;
62+ for (size_t i = 0; i < gc_n; i++) {
63+ void *p = PTR(gc_objs[i]);
64+ size_t h = hash_ptr(p) & (table_cap - 1);
65+ int dup = 0;
66+ while (table[h]) {
67+ if (PTR(gc_objs[table[h] - 1]) == p) { dup = 1; break; }
68+ h = (h + 1) & (table_cap - 1);
69+ }
70+ if (dup) continue;
71+ gc_objs[kept] = gc_objs[i];
72+ table[h] = kept + 1;
73+ kept++;
74+ }
75+ n_freed += 0;
76+ gc_n = kept;
77+ /* entries moved while compacting duplicates: rebuild positions */
78+ memset(table, 0, table_cap * sizeof(size_t));
79+ for (size_t i = 0; i < gc_n; i++) {
80+ size_t h = hash_ptr(PTR(gc_objs[i])) & (table_cap - 1);
81+ while (table[h]) h = (h + 1) & (table_cap - 1);
82+ table[h] = i + 1;
83+ }
84+ free(marks);
85+ marks = calloc(gc_n ? gc_n : 1, 1);
86+}
87+
88+static long find_index(void *p) {
89+ size_t h = hash_ptr(p) & (table_cap - 1);
90+ while (table[h]) {
91+ if (PTR(gc_objs[table[h] - 1]) == p) return (long)(table[h] - 1);
92+ h = (h + 1) & (table_cap - 1);
93+ }
94+ return -1;
95+}
96+
97+/* ---- marking ---- */
98+static VMValue *work; static size_t work_n, work_cap;
99+static void push(VMValue v) {
100+ if (work_n == work_cap) { work_cap = work_cap ? work_cap * 2 : 1024; work = realloc(work, work_cap * sizeof(VMValue)); }
101+ work[work_n++] = v;
102+}
103+
104+static int mark_ptr(void *p) {
105+ long i = find_index(p);
106+ if (i < 0 || marks[i]) return 0;
107+ marks[i] = 1;
108+ return 1;
109+}
110+
111+static void visit(VMValue v) {
112+ switch (v.type) {
113+ case VM_STR: if (v.str) mark_ptr(v.str); break;
114+ case VM_ARRAY:
115+ if (v.array && mark_ptr(v.array)) for (int i = 0; i < v.array->count; i++) push(v.array->items[i]);
116+ break;
117+ case VM_MAP:
118+ if (v.map && mark_ptr(v.map)) {
119+ for (int i = 0; i < v.map->size; i++) if (v.map->entries[i].used) push(v.map->entries[i].value);
120+ }
121+ break;
122+ case VM_CLOSURE:
123+ if (v.closure && mark_ptr(v.closure)) for (int i = 0; i < v.closure->capture_count; i++) push(v.closure->captured[i]);
124+ break;
125+ default: break;
126+ }
127+}
128+
129+static void drain(void) { while (work_n) visit(work[--work_n]); }
130+static void root(VMValue v) { visit(v); drain(); }
131+
132+static void root_chunk(Chunk *c) {
133+ for (int i = 0; i < c->const_count; i++) root(c->constants[i]);
134+}
135+
136+static void free_object(uintptr_t e) {
137+ void *p = PTR(e);
138+ switch (KIND(e)) {
139+ case GC_STR: free(p); break;
140+ case GC_ARRAY: { VMArray *a = p; free(a->items); free(a); break; }
141+ case GC_MAP: {
142+ VMMap *m = p;
143+ for (int i = 0; i < m->size; i++) if (m->entries[i].used) free(m->entries[i].key);
144+ free(m->entries); free(m->index); free(m);
145+ break;
146+ }
147+ case GC_CLOSURE: { VMClosureObj *c = p; free(c->captured); free(c); break; }
148+ }
149+}
150+
151+void gc_collect(void) {
152+ gc_pending = 0;
153+ gc_since = 0;
154+ if (!initialised) init();
155+ n_collections++;
156+ build_table();
157+ /* roots: the whole value stack, module globals, chunk constants, the pending raised value */
158+ for (VMValue *v = stack; v < stack_top; v++) root(*v);
159+ for (int i = 0; i < MAX_VM_GLOBALS; i++) root(vm_globals[i]);
160+ if (has_raised_value) root(raised_value);
161+ for (int i = 0; i < frame_count; i++) root_chunk(frames[i].chunk);
162+ for (int i = 0; i < vm_function_count; i++) root_chunk(&vm_functions[i].chunk);
163+ /* sweep: free the unmarked, compact the survivors (mark cleared) */
164+ size_t live = 0;
165+ for (size_t i = 0; i < gc_n; i++) {
166+ if (marks[i]) gc_objs[live++] = gc_objs[i];
167+ else { free_object(gc_objs[i]); n_freed++; }
168+ }
169+ n_registered += gc_n - live + 0; /* freed ones stay counted in the totals */
170+ gc_n = live;
171+ free(table); table = NULL; table_cap = 0;
172+ free(marks); marks = NULL;
173+ if (!stress) gc_trigger = live * 2 > 200000 ? live * 2 : 200000;
174+}
175+#endif

ashvm/src/vm/C/state.c → hearth/ashvm/src/vm/C/state.crenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/vm/D/api.d → hearth/ashvm/src/vm/D/api.drenamed

+3−1
@@ -1,7 +1,9 @@
11 src/vm/O/api.o: src/vm/C/api.c src/vm/H/vm.h src/vm/H/chunk.h \
2- src/value/H/value.h src/vm/H/state.h src/compiler/H/compiler.h
2+ src/value/H/value.h src/vm/H/state.h src/vm/H/gc.h \
3+ src/compiler/H/compiler.h
34 src/vm/H/vm.h:
45 src/vm/H/chunk.h:
56 src/value/H/value.h:
67 src/vm/H/state.h:
8+src/vm/H/gc.h:
79 src/compiler/H/compiler.h:

ashvm/src/vm/D/chunk.d → hearth/ashvm/src/vm/D/chunk.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/vm/D/dispatch.d → hearth/ashvm/src/vm/D/dispatch.drenamed

+4−2
@@ -1,10 +1,12 @@
11 src/vm/O/dispatch.o: src/vm/C/dispatch.c src/vm/H/vm.h src/vm/H/chunk.h \
2- src/value/H/value.h src/vm/H/state.h src/compiler/H/compiler.h \
3- src/builtins/H/builtins.h src/value/H/hashmap.h
2+ src/value/H/value.h src/vm/H/state.h src/vm/H/gc.h \
3+ src/compiler/H/compiler.h src/builtins/H/builtins.h \
4+ src/value/H/hashmap.h
45 src/vm/H/vm.h:
56 src/vm/H/chunk.h:
67 src/value/H/value.h:
78 src/vm/H/state.h:
9+src/vm/H/gc.h:
810 src/compiler/H/compiler.h:
911 src/builtins/H/builtins.h:
1012 src/value/H/hashmap.h:

ashvm/src/vm/D/errors.d → hearth/ashvm/src/vm/D/errors.drenamed

+0−0

No content changes. View the file on GitHub

hearth/ashvm/src/vm/D/gc.dadded

+1−0
@@ -0,0 +1 @@
1+src/vm/O/gc.o: src/vm/C/gc.c

ashvm/src/vm/D/state.d → hearth/ashvm/src/vm/D/state.drenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/vm/H/chunk.h → hearth/ashvm/src/vm/H/chunk.hrenamed

+0−0

No content changes. View the file on GitHub

hearth/ashvm/src/vm/H/gc.hadded

+67−0
@@ -0,0 +1,67 @@
1+#ifndef ASH_VM_GC_H
2+#define ASH_VM_GC_H
3+
4+/* Optional mark-sweep garbage collector (EXPERIMENTAL, build with `make GC=1`).
5+ *
6+ * Without -DASH_GC every hook below is an empty macro, so the default build is byte-for-byte the
7+ * VM it always was (same bytecode dispatch, same performance). With it:
8+ * - every heap object made by vm_str / vm_str_n / vm_array_new / vm_map_new / OP_MAKE_CLOSURE
9+ * is registered in a pointer table (kind + mark bit);
10+ * - the collector runs ONLY at safe points: the start of the opcodes that allocate (string concat,
11+ * array/map literals, string indexing, closures, builtin calls). There every live value is on the
12+ * VM stack, in a module global, in a chunk constant or in the pending raised value, so those are
13+ * the complete root set; the call, loop and arithmetic paths have no check at all;
14+ * - it never runs while a builtin has re-entered the VM (map/filter/reduce/sort callbacks keep
15+ * C-local values), tracked by gc_inhibit;
16+ * - pointers it did not allocate (static text, interior pointers) are ignored, never freed.
17+ * Ownership: strings are freed with free(), arrays/maps/closures with their own member frees.
18+ * ASH_GC_STRESS=1 collects at every safe point (finds missing roots); ASH_GC_STATS=1 prints totals
19+ * at exit. Not thread-safe (the VM is single-threaded). */
20+
21+#ifdef ASH_GC
22+#include <stddef.h>
23+#include <stdint.h>
24+#include "value/H/value.h"
25+
26+enum { GC_STR = 1, GC_ARRAY = 2, GC_MAP = 3, GC_CLOSURE = 4 };
27+
28+extern int gc_pending; /* set when enough was allocated since the last collection */
29+extern int gc_inhibit; /* >0 while a builtin callback runs inside the VM */
30+
31+/* The registry is a flat array of tagged pointers (malloc blocks are 16-byte aligned, so the low
32+ * bits carry the kind). The allocation path is inlined: one bounds check, one store, one counter. */
33+extern uintptr_t *gc_objs;
34+extern size_t gc_n, gc_cap, gc_since, gc_trigger;
35+void gc_register_slow(void *p, int kind); /* first use, growth */
36+void gc_collect(void);
37+
38+static inline void gc_register(void *p, int kind) {
39+ if (__builtin_expect(gc_n < gc_cap && p != NULL, 1)) {
40+ gc_objs[gc_n++] = (uintptr_t)p | (uintptr_t)kind;
41+ if (__builtin_expect(++gc_since >= gc_trigger, 0)) gc_pending = 1;
42+ } else {
43+ gc_register_slow(p, kind);
44+ }
45+}
46+
47+#define GC_REGISTER(p, kind) gc_register((p), (kind))
48+/* Safe points sit at the START of opcodes that allocate (never on the call/loop/arithmetic paths, which
49+ * therefore carry no extra instruction). GC_SAFEPOINT_N(n) is for handlers that already popped n operands:
50+ * they are still intact above the stack top, so they are treated as live for the collection. */
51+#define GC_SAFEPOINT() do { if (__builtin_expect(gc_pending, 0) && !gc_inhibit) gc_collect(); } while (0)
52+#define GC_SAFEPOINT_N(n) do { if (__builtin_expect(gc_pending, 0) && !gc_inhibit) { stack_top += (n); gc_collect(); stack_top -= (n); } } while (0)
53+#define GC_INHIBIT_ENTER() (gc_inhibit++)
54+#define GC_INHIBIT_LEAVE() (gc_inhibit--)
55+#define GC_INHIBIT_SAVE(v) ((v) = gc_inhibit)
56+#define GC_INHIBIT_RESTORE(v) (gc_inhibit = (v))
57+#else
58+#define GC_REGISTER(p, kind) ((void)0)
59+#define GC_SAFEPOINT() ((void)0)
60+#define GC_SAFEPOINT_N(n) ((void)0)
61+#define GC_INHIBIT_ENTER() ((void)0)
62+#define GC_INHIBIT_LEAVE() ((void)0)
63+#define GC_INHIBIT_SAVE(v) ((void)0)
64+#define GC_INHIBIT_RESTORE(v) ((void)0)
65+#endif
66+
67+#endif

ashvm/src/vm/H/state.h → hearth/ashvm/src/vm/H/state.hrenamed

+1−0
@@ -24,6 +24,7 @@ typedef struct {
2424 VMValue *saved_stack_top;
2525 CallFrame *target_frame;
2626 uint8_t *target_ip;
27+ int gc_inhibit_saved; // GC builds only: nested-callback depth to restore when this handler fires
2728 } TryHandler;
2829
2930 #define MAX_VM_GLOBALS 256

ashvm/src/vm/H/vm.h → hearth/ashvm/src/vm/H/vm.hrenamed

+0−0

No content changes. View the file on GitHub

ashvm/src/vm/O/.gitkeep → hearth/ashvm/src/vm/O/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/basics/bare_reassign_test.ash → hearth/ashvm/tests/cases/basics/bare_reassign_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/basics/booleans.ash → hearth/ashvm/tests/cases/basics/booleans.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/basics/exponent_literals.ash → hearth/ashvm/tests/cases/basics/exponent_literals.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/basics/literals_test.ash → hearth/ashvm/tests/cases/basics/literals_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/basics/modulo.ash → hearth/ashvm/tests/cases/basics/modulo.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/basics/number_format.ash → hearth/ashvm/tests/cases/basics/number_format.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/basics/operators_test.ash → hearth/ashvm/tests/cases/basics/operators_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/basics/type_test.ash → hearth/ashvm/tests/cases/basics/type_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/collections/array_mutation.ash → hearth/ashvm/tests/cases/collections/array_mutation.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/collections/map_order.ash → hearth/ashvm/tests/cases/collections/map_order.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/collections/maps_test.ash → hearth/ashvm/tests/cases/collections/maps_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/collections/matrix_mul.ash → hearth/ashvm/tests/cases/collections/matrix_mul.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/collections/sort.ash → hearth/ashvm/tests/cases/collections/sort.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/collections/vm_arrays_test.ash → hearth/ashvm/tests/cases/collections/vm_arrays_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/control_flow/each_test.ash → hearth/ashvm/tests/cases/control_flow/each_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/control_flow/loop_body_locals_test.ash → hearth/ashvm/tests/cases/control_flow/loop_body_locals_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/control_flow/loop_control_test.ash → hearth/ashvm/tests/cases/control_flow/loop_control_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/control_flow/short_circuit.ash → hearth/ashvm/tests/cases/control_flow/short_circuit.ashrenamed

+0−0

No content changes. View the file on GitHub

hearth/ashvm/tests/cases/control_flow/stop_pops_locals.ashadded

+52−0
@@ -0,0 +1,52 @@
1+// `stop` leaves a loop whose body declared locals: later locals must still get the right slots.
2+forge plain() {
3+ local q = 4;
4+ during yes {
5+ local t = q;
6+ q = t + 1;
7+ given q > 5 { stop; }
8+ }
9+ local s = "n";
10+ yield s + str(q);
11+}
12+
13+forge nested() {
14+ local q = 0;
15+ during yes {
16+ local a = q;
17+ given a >= 0 {
18+ local b = a + 1;
19+ given b == 2 { stop; }
20+ }
21+ q += 1;
22+ }
23+ local after = "after";
24+ yield after + str(q);
25+}
26+
27+forge each_stop() {
28+ local last = 0;
29+ each x in [1, 2, 3, 4] {
30+ local y = x * 2;
31+ given y > 4 { stop; }
32+ last = y;
33+ }
34+ local tail = "tail";
35+ yield tail + str(last);
36+}
37+
38+forge with_next() {
39+ local c = 0;
40+ during c < 3 {
41+ local t = c;
42+ c += 1;
43+ given t == 1 { next; }
44+ }
45+ local done = "done";
46+ yield done + str(c);
47+}
48+
49+say plain();
50+say nested();
51+say each_stop();
52+say with_next();

ashvm/tests/cases/core/vm_full_test.ash → hearth/ashvm/tests/cases/core/vm_full_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/errors/attempt_array_delete.ash → hearth/ashvm/tests/cases/errors/attempt_array_delete.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/errors/attempt_stale_handler.ash → hearth/ashvm/tests/cases/errors/attempt_stale_handler.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/errors/error_handling_test.ash → hearth/ashvm/tests/cases/errors/error_handling_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/errors/error_message_field.ash → hearth/ashvm/tests/cases/errors/error_message_field.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/errors/error_messages.ash → hearth/ashvm/tests/cases/errors/error_messages.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/errors/raise_test.ash → hearth/ashvm/tests/cases/errors/raise_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/errors/type_errors.ash → hearth/ashvm/tests/cases/errors/type_errors.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/errors/vm_trycatch_test.ash → hearth/ashvm/tests/cases/errors/vm_trycatch_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/functions/builtins_as_values.ash → hearth/ashvm/tests/cases/functions/builtins_as_values.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/functions/closures_test.ash → hearth/ashvm/tests/cases/functions/closures_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/functions/forward_declarations.ash → hearth/ashvm/tests/cases/functions/forward_declarations.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/functions/functional_test.ash → hearth/ashvm/tests/cases/functions/functional_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/functions/vm_closures_test.ash → hearth/ashvm/tests/cases/functions/vm_closures_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/imports/ash.libs/testlib/testlib.ash → hearth/ashvm/tests/cases/imports/ash.libs/testlib/testlib.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/imports/fixtures/collision_dir_one/shared.ash → hearth/ashvm/tests/cases/imports/fixtures/collision_dir_one/shared.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/imports/fixtures/collision_dir_two/shared.ash → hearth/ashvm/tests/cases/imports/fixtures/collision_dir_two/shared.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/imports/fixtures/imports_basic_lib.ash → hearth/ashvm/tests/cases/imports/fixtures/imports_basic_lib.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/imports/fixtures/imports_circular_a.ash → hearth/ashvm/tests/cases/imports/fixtures/imports_circular_a.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/imports/fixtures/imports_circular_b.ash → hearth/ashvm/tests/cases/imports/fixtures/imports_circular_b.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/imports/fixtures/imports_collision_main.ash → hearth/ashvm/tests/cases/imports/fixtures/imports_collision_main.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/imports/fixtures/imports_const_lib.ash → hearth/ashvm/tests/cases/imports/fixtures/imports_const_lib.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/imports/fixtures/imports_diamond_b.ash → hearth/ashvm/tests/cases/imports/fixtures/imports_diamond_b.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/imports/fixtures/imports_diamond_c.ash → hearth/ashvm/tests/cases/imports/fixtures/imports_diamond_c.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/imports/fixtures/imports_diamond_d.ash → hearth/ashvm/tests/cases/imports/fixtures/imports_diamond_d.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/imports/fixtures/imports_state_lib.ash → hearth/ashvm/tests/cases/imports/fixtures/imports_state_lib.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/imports/fixtures/imports_statement_lib.ash → hearth/ashvm/tests/cases/imports/fixtures/imports_statement_lib.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/imports/fixtures/imports_transitive_mid.ash → hearth/ashvm/tests/cases/imports/fixtures/imports_transitive_mid.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/imports/imports_alias_test.ash → hearth/ashvm/tests/cases/imports/imports_alias_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/imports/imports_basic_test.ash → hearth/ashvm/tests/cases/imports/imports_basic_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/imports/imports_const_test.ash → hearth/ashvm/tests/cases/imports/imports_const_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/imports/imports_diamond_test.ash → hearth/ashvm/tests/cases/imports/imports_diamond_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/imports/imports_lib_test.ash → hearth/ashvm/tests/cases/imports/imports_lib_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/imports/imports_state_test.ash → hearth/ashvm/tests/cases/imports/imports_state_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/imports/imports_statement_test.ash → hearth/ashvm/tests/cases/imports/imports_statement_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/imports/imports_transitive_test.ash → hearth/ashvm/tests/cases/imports/imports_transitive_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/io/fs_ops.ash → hearth/ashvm/tests/cases/io/fs_ops.ashrenamed

+0−0

No content changes. View the file on GitHub

hearth/ashvm/tests/cases/memory/gc_churn.ashadded

+15−0
@@ -0,0 +1,15 @@
1+// Allocates ~1.2M short-lived strings, arrays and maps. The default VM never frees them (about 200 MB
2+// here); the experimental collector (make GC=1) keeps the footprint small. Output is identical.
3+forge churn(n) {
4+ local keep = [];
5+ local i = 0;
6+ during i < n {
7+ local s = "item-" + str(i);
8+ local t = s + "-" + s;
9+ local m = {"a": s, "b": [s, t]};
10+ given i % 1000 == 0 { push(keep, m); }
11+ i += 1;
12+ }
13+ yield len(keep);
14+}
15+say churn(200000);

ashvm/tests/cases/strings/affix_ops.ash → hearth/ashvm/tests/cases/strings/affix_ops.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/strings/chr_ord.ash → hearth/ashvm/tests/cases/strings/chr_ord.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/strings/escapes_test.ash → hearth/ashvm/tests/cases/strings/escapes_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/strings/string_compare.ash → hearth/ashvm/tests/cases/strings/string_compare.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/strings/strings_files_test.ash → hearth/ashvm/tests/cases/strings/strings_files_test.ashrenamed

+0−0

No content changes. View the file on GitHub

ashvm/tests/cases/strings/vm_strings_test.ash → hearth/ashvm/tests/cases/strings/vm_strings_test.ashrenamed

+0−0

No content changes. View the file on GitHub

hearth/ashvm/tests/scripts/gc_check.shadded

+30−0
@@ -0,0 +1,30 @@
1+#!/usr/bin/env bash
2+# ashvm/tests/scripts/gc_check.sh
3+# Checks the collector (make GC=1) against the default VM: (1) every ashvm test case prints exactly
4+# the same when the collector runs at EVERY safe point (ASH_GC_STRESS=1), (2) a churn program needs
5+# far less memory, (3) the default build contains no collector code. bin/ashvm is rebuilt at the end,
6+# so it is left as `make @ashvm` would leave it (no collector).
7+set -uo pipefail
8+cd "$(dirname "$0")/../.." # -> ashvm/
9+TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT
10+
11+make clean >/dev/null 2>&1; make >/dev/null 2>&1 || { echo "default build failed"; exit 1; }
12+cp ../../bin/ashvm "$TMP/default"
13+make clean >/dev/null 2>&1; make GC=1 >/dev/null 2>&1 || { echo "GC build failed"; exit 1; }
14+cp ../../bin/ashvm "$TMP/gc"
15+make clean >/dev/null 2>&1; make >/dev/null 2>&1 # restore the default binary
16+
17+fail=0
18+if nm "$TMP/default" | grep -q gc_collect; then echo "FAIL default build contains the collector"; fail=1; else echo "OK default build has no collector"; fi
19+
20+for f in tests/cases/*/*.ash; do
21+ d=$(dirname "$f")
22+ a=$(cd "$d" && "$TMP/default" "$(basename "$f")" 2>&1; echo "rc=$?")
23+ b=$(cd "$d" && ASH_GC_STRESS=1 "$TMP/gc" "$(basename "$f")" 2>&1; echo "rc=$?")
24+ if [ "$a" == "$b" ]; then echo "OK $(basename "$f") (gc stress)"; else echo "FAIL $(basename "$f") differs under GC stress"; fail=1; fi
25+done
26+
27+peak() { python3 -c "import resource,subprocess,sys;subprocess.run(sys.argv[1:],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL);print(resource.getrusage(resource.RUSAGE_CHILDREN).ru_maxrss//1024)" "$@"; }
28+pd=$(peak "$TMP/default" tests/cases/memory/gc_churn.ash); pg=$(peak "$TMP/gc" tests/cases/memory/gc_churn.ash)
29+if [ "$pg" -lt $((pd / 3)) ]; then echo "OK gc_churn peak memory: default ${pd} MB, collector ${pg} MB"; else echo "FAIL gc_churn memory: default ${pd} MB, collector ${pg} MB"; fail=1; fi
30+exit $fail

ashvm/tests/scripts/imports_circular_check.sh → hearth/ashvm/tests/scripts/imports_circular_check.shrenamed

+1−1
@@ -2,7 +2,7 @@
22 set -uo pipefail
33 cd "$(dirname "$0")/../.."
44
5-OUT=$(../bin/ashvm tests/cases/imports/fixtures/imports_circular_a.ash 2>&1)
5+OUT=$(../../bin/ashvm tests/cases/imports/fixtures/imports_circular_a.ash 2>&1)
66 STATUS=$?
77
88 if [[ $STATUS -eq 0 ]]; then

ashvm/tests/scripts/imports_collision_check.sh → hearth/ashvm/tests/scripts/imports_collision_check.shrenamed

+1−1
@@ -2,7 +2,7 @@
22 set -uo pipefail
33 cd "$(dirname "$0")/../.."
44
5-OUT=$(../bin/ashvm tests/cases/imports/fixtures/imports_collision_main.ash 2>&1)
5+OUT=$(../../bin/ashvm tests/cases/imports/fixtures/imports_collision_main.ash 2>&1)
66 STATUS=$?
77
88 if [[ $STATUS -eq 0 ]]; then

ashvm/tests/scripts/run_regression.sh → hearth/ashvm/tests/scripts/run_regression.shrenamed

+1−1
@@ -24,7 +24,7 @@ make -s || { echo "build failed"; exit 1; }
2424 for f in tests/cases/*/*.ash; do
2525 name=$(basename "$f" .ash)
2626 out="$OUTDIR/${name}.out"
27- if ! ../bin/ashvm "$f" > "$out" 2>&1; then
27+ if ! ../../bin/ashvm "$f" > "$out" 2>&1; then
2828 echo "RUN FAIL" >> "$out"
2929 fi
3030 done

forgepack/Makefile → hearth/forgepack/Makefilerenamed

+2−2
@@ -28,12 +28,12 @@ $(call obj_of,$(1)): $(1)
2828 endef
2929 $(foreach s,$(FORGEPACK_SRC),$(eval $(call compile_rule,$(s))))
3030
31-BIN = ../bin/forgepack
31+BIN = ../../bin/forgepack
3232
3333 all: $(BIN)
3434
3535 $(BIN): $(FORGEPACK_OBJ)
36- mkdir -p ../bin
36+ mkdir -p ../../bin
3737 $(CC) $(CFLAGS) -o $(BIN) $(FORGEPACK_OBJ)
3838
3939 -include $(FORGEPACK_DEP)

forgepack/src/C/cmd.c → hearth/forgepack/src/C/cmd.crenamed

+0−0

No content changes. View the file on GitHub

forgepack/src/C/fsutil.c → hearth/forgepack/src/C/fsutil.crenamed

+0−0

No content changes. View the file on GitHub

forgepack/src/C/git_fetch.c → hearth/forgepack/src/C/git_fetch.crenamed

+0−0

No content changes. View the file on GitHub

forgepack/src/C/install.c → hearth/forgepack/src/C/install.crenamed

+0−0

No content changes. View the file on GitHub

forgepack/src/C/main.c → hearth/forgepack/src/C/main.crenamed

+0−0

No content changes. View the file on GitHub

forgepack/src/C/manifest.c → hearth/forgepack/src/C/manifest.crenamed

+0−0

No content changes. View the file on GitHub

forgepack/src/C/ui.c → hearth/forgepack/src/C/ui.crenamed

+0−0

No content changes. View the file on GitHub

forgepack/src/D/cmd.d → hearth/forgepack/src/D/cmd.drenamed

+0−0

No content changes. View the file on GitHub

forgepack/src/D/fsutil.d → hearth/forgepack/src/D/fsutil.drenamed

+0−0

No content changes. View the file on GitHub

forgepack/src/D/git_fetch.d → hearth/forgepack/src/D/git_fetch.drenamed

+0−0

No content changes. View the file on GitHub

forgepack/src/D/install.d → hearth/forgepack/src/D/install.drenamed

+0−0

No content changes. View the file on GitHub

forgepack/src/D/main.d → hearth/forgepack/src/D/main.drenamed

+0−0

No content changes. View the file on GitHub

forgepack/src/D/manifest.d → hearth/forgepack/src/D/manifest.drenamed

+0−0

No content changes. View the file on GitHub

forgepack/src/D/ui.d → hearth/forgepack/src/D/ui.drenamed

+0−0

No content changes. View the file on GitHub

forgepack/src/H/cmd.h → hearth/forgepack/src/H/cmd.hrenamed

+0−0

No content changes. View the file on GitHub

forgepack/src/H/fsutil.h → hearth/forgepack/src/H/fsutil.hrenamed

+0−0

No content changes. View the file on GitHub

forgepack/src/H/git_fetch.h → hearth/forgepack/src/H/git_fetch.hrenamed

+0−0

No content changes. View the file on GitHub

forgepack/src/H/install.h → hearth/forgepack/src/H/install.hrenamed

+0−0

No content changes. View the file on GitHub

forgepack/src/H/manifest.h → hearth/forgepack/src/H/manifest.hrenamed

+0−0

No content changes. View the file on GitHub

forgepack/src/H/ui.h → hearth/forgepack/src/H/ui.hrenamed

+0−0

No content changes. View the file on GitHub

forgepack/src/O/.gitkeep → hearth/forgepack/src/O/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

forgepack/tests/scripts/smoke_test.sh → hearth/forgepack/tests/scripts/smoke_test.shrenamed

+1−1
@@ -10,7 +10,7 @@ cd "$(dirname "$0")/../.." # -> forgepack/
1010
1111 make -s
1212
13-BIN="$(cd ../bin && pwd)/forgepack" # absolute -- the script cd's into a scratch dir below
13+BIN="$(cd ../../bin && pwd)/forgepack" # absolute -- the script cd's into a scratch dir below
1414 SCRATCH=$(mktemp -d)
1515 trap 'rm -rf "$SCRATCH"' EXIT
1616

front/.gitignore → hearth/front/.gitignorerenamed

+0−0

No content changes. View the file on GitHub

front/Makefile → hearth/front/Makefilerenamed

+18−6
@@ -1,6 +1,6 @@
11 CC = gcc
22 .DEFAULT_GOAL := all
3-CFLAGS = -Wall -Wextra -std=c11 -O2 -Isrc
3+CFLAGS = -Wall -Wextra -std=c11 -O2 -Isrc -DASH_STDLIB_DIR=\"$(abspath stdlib)\"
44
55 # Shared front end for new (Java-like) Ash: lexer, parser, AST, type checker and
66 # lowering, linked by both ashvm and kiln (see MIGRATION.md, decision D12).
@@ -10,7 +10,19 @@ FRONT_SRC = \
1010 src/jlex/C/jlex.c \
1111 src/jparse/C/jparse_decl.c \
1212 src/jparse/C/jparse_expr.c \
13- src/jparse/C/jparse_stmt.c
13+ src/jparse/C/jparse_stmt.c \
14+ src/jparse/C/jparse_old.c \
15+ src/jtypes/C/types.c \
16+ src/jtypes/C/classes.c \
17+ src/jtypes/C/check_expr.c \
18+ src/jtypes/C/check_stmt.c \
19+ src/jtypes/C/check_class.c \
20+ src/jlower/C/lower_expr.c \
21+ src/jlower/C/lower_stmt.c \
22+ src/jlower/C/lower_class.c \
23+ src/jlower/C/lower_prelude.c \
24+ src/jlower/C/lower_prelude_long.c \
25+ src/jlower/C/lower_prune.c
1426
1527 obj_of = $(patsubst %.c,%.o,$(subst /C/,/O/,$(1)))
1628 dep_of = $(patsubst %.c,%.d,$(subst /C/,/D/,$(1)))
@@ -26,19 +38,19 @@ $(call obj_of,$(1)): $(1)
2638 endef
2739 $(foreach s,$(FRONT_SRC) $(APP_SRC),$(eval $(call compile_rule,$(s))))
2840
29-LIB = ../bin/libashfront.a
30-TOOL = ../bin/ashfront
41+LIB = ../../bin/libashfront.a
42+TOOL = ../../bin/ashfront
3143
3244 all: $(LIB) $(TOOL)
3345
3446 lib: $(LIB)
3547
3648 $(LIB): $(FRONT_OBJ)
37- mkdir -p ../bin
49+ mkdir -p ../../bin
3850 ar rcs $@ $(FRONT_OBJ)
3951
4052 $(TOOL): $(APP_OBJ) $(LIB)
41- mkdir -p ../bin
53+ mkdir -p ../../bin
4254 $(CC) $(CFLAGS) -o $@ $(APP_OBJ) $(LIB)
4355
4456 -include $(ALL_DEP)

hearth/front/src/app/C/main.cadded

+105−0
@@ -0,0 +1,105 @@
1+#define _DEFAULT_SOURCE
2+#include <stdio.h>
3+#include <stdlib.h>
4+#include <string.h>
5+#include <sys/stat.h>
6+#include <unistd.h>
7+
8+#include "jcommon/H/jcommon.h"
9+#include "jlower/H/jlower.h"
10+#include "jparse/H/jparse.h"
11+#include "jtypes/H/jtypes.h"
12+
13+/* ashfront: developer tool for the new-Ash front end.
14+ * ashfront --parse <file.ash> parse only
15+ * ashfront --check <file.ash> parse and type-check (loads the stdlib lazily)
16+ * ashfront --lower <file.ash> print the generated old-Ash module
17+ * ashfront --run <file.ash> lower and execute on ashvm (found next to ashfront, or $ASHVM)
18+ * ashfront --build <file.ash> -o <out> lower and compile to a native binary with kiln ($KILN)
19+ * Any mode that lowers also accepts --wrap-int: Java's 32-bit int overflow (about 2x slower arithmetic loops). */
20+
21+static int write_file(const char *path, const char *text) {
22+ FILE *f = fopen(path, "wb");
23+ if (!f) return -1;
24+ fputs(text, f);
25+ fclose(f);
26+ return 0;
27+}
28+
29+static char *self_dir(const char *argv0) {
30+ char *d = j_strdup(argv0);
31+ char *slash = strrchr(d, '/');
32+ if (slash) *slash = 0;
33+ else strcpy(d, ".");
34+ return d;
35+}
36+
37+int main(int argc, char **argv) {
38+ /* --wrap-int may appear anywhere after the mode; strip it before positional parsing */
39+ for (int i = 2; i < argc; i++) {
40+ if (!strcmp(argv[i], "--wrap-int")) {
41+ jl_wrap_int = 1;
42+ for (int j = i; j < argc - 1; j++) argv[j] = argv[j + 1];
43+ argc--;
44+ i--;
45+ }
46+ }
47+ const char *mode = argc > 1 ? argv[1] : "";
48+ int parse = !strcmp(mode, "--parse"), check = !strcmp(mode, "--check");
49+ int lower = !strcmp(mode, "--lower"), run = !strcmp(mode, "--run"), build = !strcmp(mode, "--build");
50+ if (argc < 3 || !(parse || check || lower || run || build) || (build && (argc < 5 || strcmp(argv[3], "-o") != 0))) {
51+ fprintf(stderr, "usage: %s --parse|--check|--lower|--run <file.ash>\n %s --build <file.ash> -o <output>\n", argv[0], argv[0]);
52+ return 2;
53+ }
54+ char *src = j_read_file(argv[2]);
55+ if (!src) {
56+ fprintf(stderr, "error: cannot read %s\n", argv[2]);
57+ return 1;
58+ }
59+ if (parse) {
60+ Node *u = jparse_unit(argv[2], src);
61+ printf("parsed %s: %d types\n", argv[2], u->l2.count);
62+ return 0;
63+ }
64+ Node *head = jparse_unit(argv[2], src);
65+ jt_init_default_roots(argv[2], head->name);
66+ JClass *entry = jt_load_unit(argv[2], src);
67+ jt_check_all();
68+ if (j_error_count()) {
69+ fprintf(stderr, "%d error(s)\n", j_error_count());
70+ return 1;
71+ }
72+ if (check) {
73+ printf("checked %s: ok\n", argv[2]);
74+ return 0;
75+ }
76+ char *module = jlower_program(entry);
77+ if (!module || j_error_count()) {
78+ fprintf(stderr, "%d error(s)\n", j_error_count());
79+ return 1;
80+ }
81+ if (lower) {
82+ fputs(module, stdout);
83+ return 0;
84+ }
85+ char dir[] = "/tmp/ashrun_XXXXXX";
86+ if (!mkdtemp(dir)) {
87+ perror("mkdtemp");
88+ return 1;
89+ }
90+ char *mod_path = j_fmt("%s/prog.ash", dir), *run_path = j_fmt("%s/run.ash", dir);
91+ if (write_file(mod_path, module) || write_file(run_path, jlower_driver("prog.ash"))) {
92+ fprintf(stderr, "error: cannot write to %s\n", dir);
93+ return 1;
94+ }
95+ if (build) {
96+ const char *kiln = getenv("KILN");
97+ char *kiln_path = kiln ? j_strdup(kiln) : j_fmt("%s/kiln", self_dir(argv[0]));
98+ char *bcmd = j_fmt("'%s' '%s' -o '%s'; rc=$?; rm -rf '%s'; exit $rc", kiln_path, run_path, argv[4], dir);
99+ return system(bcmd) == 0 ? 0 : 1;
100+ }
101+ const char *vm = getenv("ASHVM");
102+ char *vm_path = vm ? j_strdup(vm) : j_fmt("%s/ashvm", self_dir(argv[0]));
103+ char *cmd = j_fmt("'%s' '%s'; rc=$?; rm -rf '%s'; exit $rc", vm_path, run_path, dir);
104+ return system(cmd) == 0 ? 0 : 1;
105+}

front/src/app/D/.gitkeep → hearth/front/src/app/D/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

front/src/app/O/.gitkeep → hearth/front/src/app/O/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

front/src/jcommon/C/jcommon.c → hearth/front/src/jcommon/C/jcommon.crenamed

+0−0

No content changes. View the file on GitHub

front/src/jcommon/D/.gitkeep → hearth/front/src/jcommon/D/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

front/src/jcommon/H/jcommon.h → hearth/front/src/jcommon/H/jcommon.hrenamed

+0−0

No content changes. View the file on GitHub

front/src/jcommon/O/.gitkeep → hearth/front/src/jcommon/O/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

front/src/jdriver/D/.gitkeep → hearth/front/src/jdriver/D/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

front/src/jdriver/O/.gitkeep → hearth/front/src/jdriver/O/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

front/src/jlex/C/jlex.c → hearth/front/src/jlex/C/jlex.crenamed

+0−0

No content changes. View the file on GitHub

front/src/jlex/D/.gitkeep → hearth/front/src/jlex/D/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

front/src/jlex/H/jlex.h → hearth/front/src/jlex/H/jlex.hrenamed

+0−0

No content changes. View the file on GitHub

front/src/jlex/O/.gitkeep → hearth/front/src/jlex/O/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

hearth/front/src/jlower/C/lower_class.cadded

+415−0
@@ -0,0 +1,415 @@
1+#include <string.h>
2+
3+#include "jlower/H/jlower_internal.h"
4+
5+/* Class lowering. Object model (old-Ash values only):
6+ * object map {"__c": qname, "__vt": C__VT, "__is": C__IS, <fields>...}
7+ * statics module-level map C__S (also holds enum constants and "$VALUES")
8+ * methods top-level forge C__name_<id>(self, args...)
9+ * dispatch __vcN(obj, "name/arity", args...) through the flat vtable C__VT
10+ * subtyping C__IS maps every supertype qname to yes (instanceof, catch)
11+ * Interfaces and "native value" classes (String, boxes, List, Map...) have no instances of
12+ * their own: their methods are @Lower templates inlined at the call site. */
13+
14+static void vt_collect(JClass *c, JList *keys, JList *meths) {
15+ jt_complete(c);
16+ if (c->super) vt_collect(c->super->cls, keys, meths);
17+ /* interface default methods only fill gaps; superclass methods win */
18+ JList ifs = {0};
19+ for (int i = 0; i < c->ifaces.count; i++) j_list_push(&ifs, ((JType *)c->ifaces.items[i])->cls);
20+ for (int q = 0; q < ifs.count; q++) {
21+ JClass *ic = ifs.items[q];
22+ jt_complete(ic);
23+ for (int i = 0; i < ic->methods.count; i++) {
24+ JMethod *m = ic->methods.items[i];
25+ if ((m->flags & (M_ABSTRACT | M_STATIC)) || !m->decl || !m->decl->b) continue;
26+ char *k = method_key(m);
27+ int found = 0;
28+ for (int j = 0; j < keys->count; j++) if (!strcmp(keys->items[j], k)) found = 1;
29+ if (!found) { j_list_push(keys, k); j_list_push(meths, m); }
30+ }
31+ for (int i = 0; i < ic->ifaces.count; i++) j_list_push(&ifs, ((JType *)ic->ifaces.items[i])->cls);
32+ }
33+ for (int i = 0; i < c->methods.count; i++) {
34+ JMethod *m = c->methods.items[i];
35+ if ((m->flags & (M_ABSTRACT | M_STATIC)) || m->tmpl) continue;
36+ if (!(m->decl && m->decl->b) && !m->synthetic) continue;
37+ if (m->synthetic && !(m->is_record_accessor || (c->kind == N_RECORD && m->synthetic))) continue;
38+ char *k = method_key(m);
39+ int idx = -1;
40+ for (int j = 0; j < keys->count; j++) if (!strcmp(keys->items[j], k)) idx = j;
41+ if (idx >= 0) meths->items[idx] = m;
42+ else { j_list_push(keys, k); j_list_push(meths, m); }
43+ }
44+}
45+
46+static int is_lowered(JClass *c) { return !is_native_value_class(c); }
47+
48+static void add_is(JClass *c, JList *seen) {
49+ for (int i = 0; i < seen->count; i++) if (seen->items[i] == c) return;
50+ j_list_push(seen, c);
51+ jt_complete(c);
52+ if (c->super) add_is(c->super->cls, seen);
53+ for (int i = 0; i < c->ifaces.count; i++) add_is(((JType *)c->ifaces.items[i])->cls, seen);
54+}
55+
56+static char *default_for(JType *t) {
57+ if (ty_is_prim(t)) return t->kind == TY_BOOL ? "no" : "0";
58+ return "none";
59+}
60+
61+static void collect_inst_fields(JClass *c, JBuf *b) {
62+ if (c->super) collect_inst_fields(c->super->cls, b);
63+ for (int i = 0; i < c->fields.count; i++) {
64+ JField *f = c->fields.items[i];
65+ if ((f->flags & M_STATIC) || f->tmpl) continue;
66+ j_buf_addf(b, ", %s: %s", quote_str(f->name), default_for(f->type));
67+ }
68+}
69+
70+/* ---------------------------------------------------------------- bodies */
71+
72+static void begin_body(JClass *c, JMethod *m, int is_static) {
73+ L.cls = c;
74+ L.method = m;
75+ L.is_static = is_static;
76+ L.loop = NULL;
77+ L.fin_n = 0;
78+ L.pre.len = 0;
79+}
80+
81+static char *param_list(JMethod *m, int with_self) {
82+ JBuf b = {0};
83+ int any = 0;
84+ if (with_self) { j_buf_add(&b, "self"); any = 1; }
85+ Node *d = m->decl;
86+ for (int i = 0; i < m->params.count; i++) {
87+ if (any) j_buf_add(&b, ", ");
88+ const char *nm = ((JParam *)m->params.items[i])->name;
89+ (void)d;
90+ j_buf_add(&b, sname(nm));
91+ any = 1;
92+ }
93+ return b.data ? b.data : (char *)"";
94+}
95+
96+static void emit_method(JBuf *out, JClass *c, JMethod *m) {
97+ begin_body(c, m, (m->flags & M_STATIC) != 0);
98+ emit_line(out, 0, "forge %s(%s) {", fn_name(m, NULL), param_list(m, !(m->flags & M_STATIC)));
99+ lblock_body(out, m->decl->b, 1);
100+ emit_line(out, 1, "yield none;");
101+ emit_line(out, 0, "}");
102+}
103+
104+static void emit_record_members(JBuf *out, JClass *c) {
105+ for (int i = 0; i < c->methods.count; i++) {
106+ JMethod *m = c->methods.items[i];
107+ if (!m->synthetic) continue;
108+ if (m->is_record_accessor) {
109+ emit_line(out, 0, "forge %s(self) { yield self[%s]; }", fn_name(m, NULL), quote_str(m->name));
110+ } else if (!strcmp(m->name, "toString")) {
111+ JBuf b = {0};
112+ j_buf_addf(&b, "%s", quote_str(j_fmt("%s[", c->name)));
113+ for (int k = 0; k < c->fields.count; k++) {
114+ JField *f = c->fields.items[k];
115+ if (f->flags & M_STATIC) continue;
116+ j_buf_addf(&b, " + %s + %s", quote_str(j_fmt("%s%s=", k ? ", " : "", f->name)),
117+ str_of(f->type, j_fmt("self[%s]", quote_str(f->name))));
118+ }
119+ j_buf_add(&b, " + \"]\"");
120+ emit_line(out, 0, "forge %s(self) { yield %s; }", fn_name(m, NULL), b.data);
121+ } else if (!strcmp(m->name, "equals")) {
122+ JBuf b = {0};
123+ j_buf_add(&b, "yes");
124+ for (int k = 0; k < c->fields.count; k++) {
125+ JField *f = c->fields.items[k];
126+ if (f->flags & M_STATIC) continue;
127+ j_buf_addf(&b, " and __eq(self[%s], o[%s])", quote_str(f->name), quote_str(f->name));
128+ }
129+ emit_line(out, 0, "forge %s(self, o) { given not __inst(o, %s) { yield no; } yield %s; }", fn_name(m, NULL),
130+ quote_str(c->qname), b.data);
131+ } else if (!strcmp(m->name, "hashCode")) {
132+ JBuf b = {0};
133+ j_buf_add(&b, "0");
134+ for (int k = 0; k < c->fields.count; k++) {
135+ JField *f = c->fields.items[k];
136+ if (f->flags & M_STATIC) continue;
137+ j_buf_addf(&b, " * 31 + __hash(self[%s])", quote_str(f->name));
138+ }
139+ emit_line(out, 0, "forge %s(self) { yield (%s) %% 4294967296; }", fn_name(m, NULL), b.data);
140+ }
141+ }
142+}
143+
144+static void emit_field_inits(JBuf *out, JClass *c) {
145+ for (int i = 0; i < c->decl->l1.count; i++) {
146+ Node *mem = c->decl->l1.items[i];
147+ JList one = {0};
148+ if (mem->kind == N_SEQ) one = mem->l1;
149+ else j_list_push(&one, mem);
150+ for (int k = 0; k < one.count; k++) {
151+ Node *f = one.items[k];
152+ if (f->kind == N_FIELD && !(f->flags & M_STATIC) && f->b && c->kind != N_INTERFACE) {
153+ char *v = lx(f->b);
154+ flush_pre(out, 1);
155+ emit_line(out, 1, "self[%s] = %s;", quote_str(f->name), v);
156+ } else if (f->kind == N_INIT && !(f->flags & M_STATIC)) {
157+ lblock_body(out, f->b, 1);
158+ }
159+ }
160+ }
161+}
162+
163+static void emit_ctor(JBuf *out, JClass *c, JMethod *k) {
164+ begin_body(c, k, 0);
165+ Node *d = k->decl;
166+ int is_enum = c->kind == N_ENUM;
167+ emit_line(out, 0, "forge %s(%s) {", fn_name(k, "init"), param_list(k, 1));
168+ int first = 0;
169+ int delegates = 0;
170+ if (d && d->b && d->b->l1.count && ((Node *)d->b->l1.items[0])->kind == N_CTORCALL) {
171+ Node *cc = d->b->l1.items[0];
172+ delegates = !strcmp(cc->name, "this");
173+ ls(out, cc, 1);
174+ first = 1;
175+ } else if (c->super && c->kind == N_CLASS) {
176+ JMethod *sk = find_ctor(c->super->cls, 0);
177+ const char *q = c->super->cls->qname;
178+ if (sk && !sk->tmpl && strcmp(q, "ash.lang.Object") != 0) emit_line(out, 1, "%s(self);", fn_name(sk, "init"));
179+ }
180+ if (!delegates) emit_field_inits(out, c);
181+ if (d && d->b) {
182+ for (int i = first; i < d->b->l1.count; i++) ls(out, d->b->l1.items[i], 1);
183+ }
184+ if (c->kind == N_RECORD && (!d || (d->flags & 0x20000))) {
185+ for (int i = 0; i < c->decl->l4.count; i++) {
186+ const char *n = ((Node *)c->decl->l4.items[i])->name;
187+ emit_line(out, 1, "self[%s] = %s;", quote_str(n), sname(n));
188+ }
189+ }
190+ emit_line(out, 0, "}");
191+ if (c->flags & M_ABSTRACT) return;
192+ /* allocator */
193+ JBuf obj = {0};
194+ j_buf_addf(&obj, "{\"__c\": %s, \"__vt\": %s, \"__is\": %s", quote_str(c->qname), cls_vt(c), cls_is(c));
195+ collect_inst_fields(c, &obj);
196+ j_buf_addc(&obj, '}');
197+ char *plist = param_list(k, 0);
198+ if (is_enum) {
199+ emit_line(out, 0, "forge %s(__name, __ord%s%s) {", fn_name(k, "new"), *plist ? ", " : "", plist);
200+ emit_line(out, 1, "local self = %s;", obj.data);
201+ emit_line(out, 1, "self[\"__name\"] = __name;");
202+ emit_line(out, 1, "self[\"__ord\"] = __ord;");
203+ } else {
204+ emit_line(out, 0, "forge %s(%s) {", fn_name(k, "new"), plist);
205+ emit_line(out, 1, "local self = %s;", obj.data);
206+ }
207+ emit_line(out, 1, "%s(%s);", fn_name(k, "init"), *plist ? j_fmt("self, %s", plist) : "self");
208+ emit_line(out, 1, "yield self;");
209+ emit_line(out, 0, "}");
210+}
211+
212+static void emit_clinit(JBuf *out, JClass *c) {
213+ begin_body(c, NULL, 1);
214+ emit_line(out, 0, "forge %s__clinit() {", c->mangled);
215+ if (c->kind == N_ENUM) {
216+ Node *d = c->decl;
217+ for (int i = 0; i < d->l4.count; i++) {
218+ Node *ec = d->l4.items[i];
219+ JMethod *k = ec->sym;
220+ if (!k) continue;
221+ JBuf args = {0};
222+ for (int j = 0; j < ec->l1.count; j++) {
223+ j_buf_add(&args, ", ");
224+ j_buf_add(&args, lx(ec->l1.items[j]));
225+ }
226+ flush_pre(out, 1);
227+ emit_line(out, 1, "%s[%s] = %s(%s, %d%s);", cls_state(c), quote_str(ec->name), fn_name(k, "new"),
228+ quote_str(ec->name), i, args.data ? args.data : "");
229+ emit_line(out, 1, "push(%s[\"$VALUES\"], %s[%s]);", cls_state(c), cls_state(c), quote_str(ec->name));
230+ }
231+ }
232+ for (int i = 0; i < c->decl->l1.count; i++) {
233+ Node *mem = c->decl->l1.items[i];
234+ JList one = {0};
235+ if (mem->kind == N_SEQ) one = mem->l1;
236+ else j_list_push(&one, mem);
237+ for (int k = 0; k < one.count; k++) {
238+ Node *f = one.items[k];
239+ int stat = (f->flags & M_STATIC) || c->kind == N_INTERFACE;
240+ if (f->kind == N_FIELD && stat && f->b) {
241+ char *v = lx(f->b);
242+ flush_pre(out, 1);
243+ emit_line(out, 1, "%s[%s] = %s;", cls_state(c), quote_str(f->name), v);
244+ } else if (f->kind == N_INIT && (f->flags & M_STATIC)) {
245+ lblock_body(out, f->b, 1);
246+ }
247+ }
248+ }
249+ emit_line(out, 0, "}");
250+}
251+
252+/* ---------------------------------------------------------------- ordering */
253+
254+static void scan(Node *n, JList *deps) {
255+ if (!n) return;
256+ if (n->kind == N_NAME || n->kind == N_FIELDACC) {
257+ if (n->sym && (n->rk == R_STATIC_FIELD || n->rk == R_ENUM_CONST)) j_list_push(deps, ((JField *)n->sym)->cls);
258+ } else if ((n->kind == N_CALL || n->kind == N_NEW) && n->sym) {
259+ j_list_push(deps, ((JMethod *)n->sym)->cls);
260+ }
261+ scan(n->a, deps);
262+ scan(n->b, deps);
263+ scan(n->c, deps);
264+ JList *ls_[5] = {&n->l1, &n->l2, &n->l3, &n->l4, &n->l5};
265+ for (int k = 0; k < 5; k++) for (int i = 0; i < ls_[k]->count; i++) scan(ls_[k]->items[i], deps);
266+}
267+
268+static void visit(JClass *c, JList *done, JList *active) {
269+ for (int i = 0; i < done->count; i++) if (done->items[i] == c) return;
270+ for (int i = 0; i < active->count; i++) if (active->items[i] == c) return;
271+ j_list_push(active, c);
272+ jt_complete(c);
273+ JList deps = {0};
274+ if (c->super) j_list_push(&deps, c->super->cls);
275+ for (int i = 0; i < c->ifaces.count; i++) j_list_push(&deps, ((JType *)c->ifaces.items[i])->cls);
276+ for (int i = 0; i < c->decl->l4.count && c->kind == N_ENUM; i++) scan(c->decl->l4.items[i], &deps);
277+ for (int i = 0; i < c->decl->l1.count; i++) {
278+ Node *mem = c->decl->l1.items[i];
279+ JList one = {0};
280+ if (mem->kind == N_SEQ) one = mem->l1;
281+ else j_list_push(&one, mem);
282+ for (int k = 0; k < one.count; k++) {
283+ Node *f = one.items[k];
284+ if ((f->kind == N_FIELD && ((f->flags & M_STATIC) || c->kind == N_INTERFACE)) || (f->kind == N_INIT && (f->flags & M_STATIC))) scan(f, &deps);
285+ }
286+ }
287+ for (int i = 0; i < deps.count; i++) {
288+ JClass *d = deps.items[i];
289+ if (d != c && is_lowered(d)) visit(d, done, active);
290+ }
291+ active->count--;
292+ j_list_push(done, c);
293+}
294+
295+/* ---------------------------------------------------------------- program */
296+
297+static JMethod *find_main(JClass *entry) {
298+ JClass **all;
299+ int n = jt_all_classes(&all);
300+ for (int pass = 0; pass < 2; pass++) {
301+ for (int i = 0; i < n; i++) {
302+ JClass *c = all[i];
303+ if (pass == 0 && c != entry) continue;
304+ if (pass == 1 && strncmp(c->qname, "ash.", 4) == 0) continue;
305+ for (int j = 0; j < c->methods.count; j++) {
306+ JMethod *m = c->methods.items[j];
307+ if (strcmp(m->name, "main") || !(m->flags & M_STATIC) || m->ret->kind != TY_VOID || m->params.count != 1) continue;
308+ JType *p = ((JParam *)m->params.items[0])->type;
309+ if (p->kind == TY_ARRAY && ty_is_string(p->elem)) return m;
310+ }
311+ }
312+ }
313+ return NULL;
314+}
315+
316+static void emit_class(JBuf *decls, JBuf *out, JClass *c) {
317+ emit_line(out, 0, "// ---- %s %s", c->kind == N_INTERFACE ? "interface" : c->kind == N_ENUM ? "enum" : c->kind == N_RECORD ? "record" : "class", c->qname);
318+ JBuf s = {0};
319+ j_buf_add(&s, "{\"$VALUES\": []");
320+ for (int i = 0; i < c->fields.count; i++) {
321+ JField *f = c->fields.items[i];
322+ if (!(f->flags & M_STATIC) || f->tmpl) continue;
323+ j_buf_addf(&s, ", %s: %s", quote_str(f->name), f->is_enum_const ? "none" : default_for(f->type));
324+ }
325+ j_buf_addc(&s, '}');
326+ emit_line(decls, 0, "local %s = %s;", cls_state(c), s.data);
327+ JList sup = {0};
328+ add_is(c, &sup);
329+ JBuf is = {0};
330+ j_buf_addc(&is, '{');
331+ for (int i = 0; i < sup.count; i++) j_buf_addf(&is, "%s%s: yes", i ? ", " : "", quote_str(((JClass *)sup.items[i])->qname));
332+ j_buf_addc(&is, '}');
333+ emit_line(decls, 0, "local %s = %s;", cls_is(c), is.data);
334+ if (c->kind != N_INTERFACE) emit_line(decls, 0, "local %s = {};", cls_vt(c));
335+ for (int i = 0; i < c->methods.count; i++) {
336+ JMethod *m = c->methods.items[i];
337+ if (m->tmpl || (m->flags & (M_ABSTRACT | M_NATIVE)) || !m->decl || !m->decl->b) continue;
338+ emit_method(out, c, m);
339+ }
340+ if (c->kind == N_RECORD) emit_record_members(out, c);
341+ if (c->kind != N_INTERFACE) {
342+ for (int i = 0; i < c->ctors.count; i++) {
343+ JMethod *k = c->ctors.items[i];
344+ if (!k->tmpl) emit_ctor(out, c, k);
345+ }
346+ }
347+ emit_clinit(out, c);
348+}
349+
350+char *jlower_program(JClass *entry) {
351+ static const char *core[] = {
352+ "ash.lang.Object", "ash.lang.String", "ash.lang.Enum", "ash.lang.Record", "ash.lang.Throwable", "ash.lang.Exception",
353+ "ash.lang.RuntimeException", "ash.lang.ArithmeticException", "ash.lang.IndexOutOfBoundsException",
354+ "ash.lang.KeyNotFoundException", "ash.lang.NullPointerException", "ash.lang.ClassCastException",
355+ "ash.lang.ParseException", "ash.lang.IllegalArgumentException", "ash.lang.System", NULL
356+ };
357+ for (int i = 0; core[i]; i++) jt_complete(jt_well_known(core[i]));
358+ jt_check_all();
359+ if (j_error_count()) return NULL;
360+ JMethod *main_m = find_main(entry);
361+ if (!main_m) {
362+ cerr(entry ? entry->decl : NULL, "no entry point: declare 'public static void main(String[] args)'");
363+ return NULL;
364+ }
365+ JBuf out = {0}, decls = {0}, code = {0};
366+ JBuf prelude = {0};
367+ emit_prelude(&prelude);
368+ /* emit the entry class, then every class its code reaches (tree-shaking by use) */
369+ mark_used(main_m->cls);
370+ JList lowered = {0};
371+ for (int i = 0; i < used_count(); i++) {
372+ JClass *c = used_at(i);
373+ if (!is_lowered(c)) continue;
374+ j_list_push(&lowered, c);
375+ emit_class(&decls, &code, c);
376+ /* supertypes are needed for vtables and instanceof tables */
377+ if (c->super) mark_used(c->super->cls);
378+ for (int k = 0; k < c->ifaces.count; k++) mark_used(((JType *)c->ifaces.items[k])->cls);
379+ }
380+ /* module-level variables first: functions can only see variables declared above them */
381+ if (decls.data) j_buf_add(&out, decls.data);
382+ if (code.data) j_buf_add(&out, code.data);
383+ /* keep only the runtime helpers the program (transitively) calls: the old VM allows 256 functions */
384+ /* wiring */
385+ JList order = {0}, active = {0};
386+ for (int i = 0; i < lowered.count; i++) visit(lowered.items[i], &order, &active);
387+ emit_line(&out, 0, "forge start() {");
388+ for (int i = 0; i < lowered.count; i++) {
389+ JClass *c = lowered.items[i];
390+ if (c->kind == N_INTERFACE) continue;
391+ JList keys = {0}, meths = {0};
392+ vt_collect(c, &keys, &meths);
393+ for (int k = 0; k < keys.count; k++) {
394+ emit_line(&out, 1, "%s[%s] = %s;", cls_vt(c), quote_str(keys.items[k]), fn_name(meths.items[k], NULL));
395+ }
396+ }
397+ for (int i = 0; i < order.count; i++) emit_line(&out, 1, "%s__clinit();", ((JClass *)order.items[i])->mangled);
398+ emit_line(&out, 1, "attempt {");
399+ emit_line(&out, 2, "%s([]);", fn_name(main_m, NULL));
400+ emit_line(&out, 1, "} handle (__e) {");
401+ emit_line(&out, 2, "__uncaught(__e);");
402+ emit_line(&out, 1, "}");
403+ emit_line(&out, 0, "}");
404+ if (j_error_count()) return NULL;
405+ /* header + the module without anything unreachable from start() */
406+ JBuf final = {0};
407+ j_buf_add(&final, "// Generated by ashfront from new-Ash source. Do not edit.\n");
408+ j_buf_add(&final, prelude.data ? prelude.data : "");
409+ j_buf_add(&final, out.data);
410+ return prune_module(final.data);
411+}
412+
413+char *jlower_driver(const char *module_name) {
414+ return j_fmt("@import <./%s> as g;\ng.start();\n", module_name);
415+}

hearth/front/src/jlower/C/lower_expr.cadded

+879−0
Show diff (879 changed lines)
@@ -0,0 +1,879 @@
1+#include <stdarg.h>
2+#include <stdio.h>
3+#include <stdlib.h>
4+#include <string.h>
5+
6+#include "jlower/H/jlower_internal.h"
7+
8+/* Expression lowering: new-Ash typed AST -> old-Ash expression text. */
9+
10+LState L;
11+int jl_wrap_int = 0;
12+
13+void lerr(Node *at, const char *fmt, ...) {
14+ va_list ap;
15+ va_start(ap, fmt);
16+ char buf[512];
17+ vsnprintf(buf, sizeof buf, fmt, ap);
18+ va_end(ap);
19+ cerr(at, "backend: %s", buf);
20+}
21+
22+/* ---------------------------------------------------------------- naming */
23+
24+static const char *reserved[] = {
25+ "local", "say", "given", "otherwise", "during", "each", "stop", "next", "forge", "yield", "attempt",
26+ "handle", "raise", "yes", "no", "none", "and", "or", "not", "in", "as", "self", "len", "type", "str",
27+ "num", "exit", "sqrt", "abs", "floor", "chr", "ord", "push", "pop", "insert", "slice", "delete", "sort",
28+ "keys", "values", "has", "map", "filter", "reduce", "split", "join", "substring", "indexOf", "contains",
29+ "starts_with", "ends_with", "replace", "repeat", "upper", "lower", "trim", "input", "read_file",
30+ "write_file", "append_file", "file_exists", "rename_file", "delete_file", "make_dir", "list_dir", NULL
31+};
32+
33+char *sname(const char *n) {
34+ for (int i = 0; reserved[i]; i++) {
35+ if (strcmp(n, reserved[i]) == 0) return j_fmt("%s_", n);
36+ }
37+ return (char *)n;
38+}
39+
40+char *quote_str(const char *s) {
41+ JBuf b = {0};
42+ j_buf_addc(&b, '"');
43+ for (const unsigned char *p = (const unsigned char *)s; *p; p++) {
44+ switch (*p) {
45+ case '"': j_buf_add(&b, "\\\""); break;
46+ case '\\': j_buf_add(&b, "\\\\"); break;
47+ case '\n': j_buf_add(&b, "\\n"); break;
48+ case '\t': j_buf_add(&b, "\\t"); break;
49+ case '\r': j_buf_add(&b, "\\r"); break;
50+ default: j_buf_addc(&b, (char)*p);
51+ }
52+ }
53+ j_buf_addc(&b, '"');
54+ return b.data;
55+}
56+
57+char *new_temp(void) { return j_fmt("__t%d", L.tmp++); }
58+
59+/* Classes whose generated code is referenced; only these are emitted (the old VM allows 256
60+ * module-level variables, and every class needs three). */
61+static JList used;
62+void mark_used(JClass *c) {
63+ for (int i = 0; i < used.count; i++) if (used.items[i] == c) return;
64+ j_list_push(&used, c);
65+}
66+int used_count(void) { return used.count; }
67+JClass *used_at(int i) { return used.items[i]; }
68+
69+char *cls_state(JClass *c) { mark_used(c); return j_fmt("%s__S", c->mangled); }
70+char *cls_is(JClass *c) { mark_used(c); return j_fmt("%s__IS", c->mangled); }
71+char *cls_vt(JClass *c) { mark_used(c); return j_fmt("%s__VT", c->mangled); }
72+
73+char *fn_name(JMethod *m, const char *kind) {
74+ mark_used(m->cls);
75+ if (kind) return j_fmt("%s__%s_%d", m->cls->mangled, kind, m->id);
76+ return j_fmt("%s__%s_%d", m->cls->mangled, m->name, m->id);
77+}
78+
79+static JMethod *override_root(JMethod *m) {
80+ JMethod *root = m;
81+ for (int i = 0; i < 16; i++) {
82+ JMethod *o = jt_find_overridden(root->cls, root);
83+ if (!o) break;
84+ root = o;
85+ }
86+ return root;
87+}
88+
89+char *method_key(JMethod *m) {
90+ JMethod *r = override_root(m);
91+ int dup = 0;
92+ jt_complete(r->cls);
93+ for (int i = 0; i < r->cls->methods.count; i++) {
94+ JMethod *o = r->cls->methods.items[i];
95+ if (o != r && !strcmp(o->name, r->name) && o->params.count == r->params.count) dup = 1;
96+ }
97+ JBuf b = {0};
98+ j_buf_addf(&b, "%s/%d", r->name, r->params.count);
99+ if (dup) {
100+ for (int i = 0; i < r->params.count; i++) {
101+ j_buf_addc(&b, '_');
102+ j_buf_add(&b, ty_str(ty_erase_bound(((JParam *)r->params.items[i])->type)));
103+ }
104+ }
105+ return b.data;
106+}
107+
108+/* ---------------------------------------------------------------- aliases for bindings */
109+
110+static JList alias_l, alias_t;
111+void alias_set(JLocal *l, const char *text) {
112+ j_list_push(&alias_l, l);
113+ j_list_push(&alias_t, (void *)text);
114+}
115+char *alias_get(JLocal *l) {
116+ for (int i = alias_l.count - 1; i >= 0; i--) if (alias_l.items[i] == l) return alias_t.items[i];
117+ return NULL;
118+}
119+
120+/* ---------------------------------------------------------------- types */
121+
122+int is_native_value_class(JClass *c) {
123+ static const char *nat[] = {
124+ "ash.lang.String", "ash.lang.Integer", "ash.lang.Long", "ash.lang.Short", "ash.lang.Byte", "ash.lang.Float",
125+ "ash.lang.Double", "ash.lang.Boolean", "ash.lang.Character", "ash.lang.Number", "ash.util.List",
126+ "ash.util.ArrayList", "ash.util.Map", "ash.util.HashMap", "ash.util.Set", "ash.util.HashSet", "ash.util.Stream", "ash.lang.Object", NULL
127+ };
128+ for (int i = 0; nat[i]; i++) if (!strcmp(c->qname, nat[i])) return 1;
129+ return 0;
130+}
131+
132+int is_array_list(JType *t) {
133+ return t->kind == TY_CLASS && (!strcmp(t->cls->qname, "ash.util.List") || !strcmp(t->cls->qname, "ash.util.ArrayList") ||
134+ !strcmp(t->cls->qname, "ash.util.Stream"));
135+}
136+
137+static int integral_t(JType *t) {
138+ JType *u = ty_unbox(t);
139+ return ty_is_integral(u) || u->kind == TY_CHAR;
140+}
141+
142+char *str_of(JType *t, const char *text) {
143+ if (t->kind == TY_NULL) return "\"null\"";
144+ if (t->kind == TY_CLASS && ty_is_string(t)) return t->nullable ? j_fmt("__str(%s)", text) : (char *)text;
145+ JType *u = ty_unbox(t);
146+ if (!t->nullable) {
147+ if (u->kind == TY_CHAR) return j_fmt("chr(%s)", text);
148+ if (u->kind == TY_BOOL) return j_fmt("__bs(%s)", text);
149+ if (u->kind == TY_FLOAT) return j_fmt("__fstr(%s)", text);
150+ if (u->kind == TY_DOUBLE) return j_fmt("__dstr(%s)", text);
151+ if (u->kind == TY_LONG) return j_fmt("__lstr(%s)", text);
152+ if (ty_is_integral(u)) return j_fmt("str(%s)", text);
153+ }
154+ return j_fmt("__str(%s)", text);
155+}
156+
157+/* ---------------------------------------------------------------- templates */
158+
159+int tmpl_uses_receiver(const char *t) { return strstr(t, "$0") != NULL; }
160+
161+char *expand_tmpl(const char *t, const char *recv, char **args, int nargs) {
162+ /* "fast||exact": the second form is used with --wrap-int */
163+ const char *bar = strstr(t, "||");
164+ if (bar && !strstr(t, " || ")) t = jl_wrap_int ? j_strdup(bar + 2) : j_strndup(t, (size_t)(bar - t));
165+ JBuf b = {0};
166+ for (const char *p = t; *p; p++) {
167+ if (*p == '$' && p[1] >= '0' && p[1] <= '9') {
168+ int k = p[1] - '0';
169+ const char *v = k == 0 ? recv : (k <= nargs ? args[k - 1] : "none");
170+ j_buf_add(&b, v ? v : "none");
171+ p++;
172+ } else {
173+ j_buf_addc(&b, *p);
174+ }
175+ }
176+ return b.data ? b.data : (char *)"";
177+}
178+
179+/* ---------------------------------------------------------------- calls */
180+
181+JMethod *find_ctor(JClass *c, int nparams) {
182+ jt_complete(c);
183+ for (int i = 0; i < c->ctors.count; i++) {
184+ JMethod *k = c->ctors.items[i];
185+ if (k->params.count == nparams) return k;
186+ }
187+ return NULL;
188+}
189+
190+static void lx_seq(Node **ns, int cnt, char **out);
191+
192+static char **lower_args_ex(JMethod *m, JList *args, int *out_n, Node *recv_node, char **recv_text);
193+static char **lower_args(JMethod *m, JList *args, int *out_n) { return lower_args_ex(m, args, out_n, NULL, NULL); }
194+
195+static char **lower_args_ex(JMethod *m, JList *args, int *out_n, Node *recv_node, char **recv_text) {
196+ int np = m ? m->params.count : args->count;
197+ int n = args->count;
198+ char **v = j_alloc(sizeof(char *) * (size_t)(n + 2));
199+ {
200+ Node *ns[32];
201+ char *out[32];
202+ int cnt = 0;
203+ if (recv_node) ns[cnt++] = recv_node;
204+ for (int i = 0; i < n && cnt < 32; i++) ns[cnt++] = args->items[i];
205+ lx_seq(ns, cnt, out);
206+ int off = 0;
207+ if (recv_node) { *recv_text = out[0]; off = 1; }
208+ for (int i = 0; i < n; i++) v[i] = out[i + off];
209+ }
210+ if (m && m->varargs) {
211+ int pack = 1;
212+ if (n == np) {
213+ JType *lt = ((Node *)args->items[n - 1])->type;
214+ if (lt->kind == TY_ARRAY || lt->kind == TY_NULL) pack = 0;
215+ }
216+ if (pack) {
217+ JBuf b = {0};
218+ j_buf_addc(&b, '[');
219+ for (int i = np - 1; i < n; i++) {
220+ if (i > np - 1) j_buf_add(&b, ", ");
221+ j_buf_add(&b, v[i]);
222+ }
223+ j_buf_addc(&b, ']');
224+ v[np - 1] = b.data;
225+ n = np;
226+ }
227+ }
228+ *out_n = n;
229+ return v;
230+}
231+
232+static char *join_args(char **a, int n, const char *first) {
233+ JBuf b = {0};
234+ int any = 0;
235+ if (first) { j_buf_add(&b, first); any = 1; }
236+ for (int i = 0; i < n; i++) {
237+ if (any) j_buf_add(&b, ", ");
238+ j_buf_add(&b, a[i]);
239+ any = 1;
240+ }
241+ return b.data ? b.data : (char *)"";
242+}
243+
244+static int is_sam_call(JMethod *m, JType *recv) {
245+ if (!recv || recv->kind != TY_CLASS || m->cls->kind != N_INTERFACE) return 0;
246+ return jt_find_sam(m->cls) == m && (m->flags & M_ABSTRACT);
247+}
248+
249+static int can_devirtualize(JMethod *m) {
250+ if (m->flags & (M_PRIVATE | M_FINAL | M_STATIC)) return 1;
251+ if (m->cls->flags & M_FINAL) return 1;
252+ if (m->cls->kind == N_RECORD || m->cls->kind == N_ENUM) return 1;
253+ if (m->flags & M_ABSTRACT) return 0;
254+ if (m->cls->kind == N_INTERFACE) return 0;
255+ /* no loaded subclass overrides it */
256+ JList stack = {0};
257+ j_list_push(&stack, m->cls);
258+ for (int i = 0; i < stack.count; i++) {
259+ JClass *c = stack.items[i];
260+ for (int j = 0; j < c->subclasses.count; j++) {
261+ JClass *s = c->subclasses.items[j];
262+ for (int k = 0; k < s->methods.count; k++) {
263+ JMethod *o = s->methods.items[k];
264+ if (!strcmp(o->name, m->name) && o->params.count == m->params.count) return 0;
265+ }
266+ j_list_push(&stack, s);
267+ }
268+ }
269+ return 1;
270+}
271+
272+/* Builds the call text for method `m` on receiver text `recv` (NULL for static). */
273+static char *invoke(JMethod *m, const char *recv, char **args, int n, JType *recv_type, int super_call, Node *at) {
274+ if (m->tmpl) return expand_tmpl(m->tmpl, recv, args, n);
275+ if (m->flags & M_NATIVE) {
276+ lerr(at, "native method %s.%s has no @Lower template", m->cls->name, m->name);
277+ return "none";
278+ }
279+ if (m->synthetic && m->cls->kind == N_ENUM) {
280+ if (!strcmp(m->name, "values")) return j_fmt("__enumvalues(%s)", cls_state(m->cls));
281+ if (!strcmp(m->name, "valueOf")) return j_fmt("__enumvalueof(%s, %s)", cls_state(m->cls), args[0]);
282+ }
283+ if (m->is_record_accessor) return j_fmt("%s[%s]", recv, quote_str(m->name));
284+ if (m->flags & M_STATIC) return j_fmt("%s(%s)", fn_name(m, NULL), join_args(args, n, NULL));
285+ if (super_call || can_devirtualize(m)) {
286+ if (m->flags & M_ABSTRACT) {
287+ lerr(at, "cannot call abstract method %s directly", m->name);
288+ return "none";
289+ }
290+ return j_fmt("%s(%s)", fn_name(m, NULL), join_args(args, n, recv));
291+ }
292+ if (n > 6) {
293+ lerr(at, "calls with more than 6 arguments are not supported by the backend yet");
294+ return "none";
295+ }
296+ if (is_sam_call(m, recv_type)) {
297+ return j_fmt("__sam%d(%s, %s%s%s)", n, recv, quote_str(method_key(m)), n ? ", " : "", join_args(args, n, NULL));
298+ }
299+ return j_fmt("__vc%d(%s, %s%s%s)", n, recv, quote_str(method_key(m)), n ? ", " : "", join_args(args, n, NULL));
300+}
301+
302+static char *lower_call(Node *n) {
303+ JMethod *m = n->sym;
304+ if (!m) return "none";
305+ Node *recv_node = NULL;
306+ JType *rt = NULL;
307+ char *recv = NULL;
308+ if (!(m->flags & M_STATIC)) {
309+ if (n->a) {
310+ if (n->flags == 1) recv = "self";
311+ else { recv_node = n->a; rt = n->a->type; }
312+ } else {
313+ recv = "self";
314+ rt = L.cls->self_type;
315+ }
316+ } else if (n->a && n->a->rk != R_CLASS && n->a->rk != R_PACKAGE && m->tmpl && tmpl_uses_receiver(m->tmpl)) {
317+ recv_node = n->a;
318+ }
319+ int na;
320+ char *rtext = NULL;
321+ char **args = lower_args_ex(m, &n->l1, &na, recv_node, &rtext);
322+ if (recv_node) recv = rtext;
323+ return invoke(m, recv, args, na, rt, n->flags == 1, n);
324+}
325+
326+static char *lower_new(Node *n) {
327+ JMethod *k = n->sym;
328+ if (!k) return "none";
329+ int na;
330+ char **args = lower_args(k, &n->l1, &na);
331+ if (k->tmpl) return expand_tmpl(k->tmpl, NULL, args, na);
332+ if (k->cls->kind == N_ENUM) {
333+ lerr(n, "enum constants cannot be created with new");
334+ return "none";
335+ }
336+ char *made = j_fmt("%s(%s)", fn_name(k, "new"), join_args(args, na, NULL));
337+ JClass *thr = jt_find_class("ash.lang.Throwable");
338+ if (thr && ty_is_subclass(k->cls, thr)) {
339+ const char *file = n->path ? strrchr(n->path, '/') : NULL;
340+ file = file ? file + 1 : (n->path ? n->path : "?");
341+ const char *where = L.method ? (L.method->is_ctor ? "<init>" : L.method->name) : "<clinit>";
342+ return j_fmt("__at(%s, %s)", made, quote_str(j_fmt("%s.%s(%s:%d)", L.cls->qname, where, file, n->line)));
343+ }
344+ return made;
345+}
346+
347+char *lower_ctor_call(Node *n) {
348+ JMethod *k = n->sym;
349+ if (!k) return "";
350+ int na;
351+ char **args = lower_args(k, &n->l1, &na);
352+ if (k->tmpl) return "";
353+ if (!strcmp(n->name, "super")) {
354+ const char *q = k->cls->qname;
355+ if (!strcmp(q, "ash.lang.Object") || !strcmp(q, "ash.lang.Enum") || !strcmp(q, "ash.lang.Record")) return "";
356+ }
357+ return j_fmt("%s(%s);", fn_name(k, "init"), join_args(args, na, "self"));
358+}
359+
360+/* ---------------------------------------------------------------- operators */
361+
362+static int int_result(JType *lt, JType *rt) {
363+ JType *r = ty_binary_numeric(lt, rt);
364+ return r && r->kind == TY_INT;
365+}
366+
367+static int is_long_t(JType *t) { return ty_unbox(t)->kind == TY_LONG; }
368+
369+/* a long operand of a mixed long/float/double operation becomes a double first */
370+static char *long_to_double(JType *t, const char *v) {
371+ return is_long_t(t) ? j_fmt("__l2d(%s)", v) : (char *)v;
372+}
373+
374+static int float_result(JType *lt, JType *rt) {
375+ JType *r = ty_binary_numeric(lt, rt);
376+ return r && r->kind == TY_FLOAT;
377+}
378+
379+static char *binop(const char *op, JType *lt, JType *rt, const char *a, const char *b, Node *at) {
380+ if (jl_wrap_int) {
381+ JType *num = ty_binary_numeric(lt, rt);
382+ int cmp = !strcmp(op, "<") || !strcmp(op, "<=") || !strcmp(op, ">") || !strcmp(op, ">=");
383+ int eq = !strcmp(op, "==") || !strcmp(op, "!=");
384+ if (num && (num->kind == TY_FLOAT || num->kind == TY_DOUBLE) && (is_long_t(lt) || is_long_t(rt))) {
385+ a = long_to_double(lt, a);
386+ b = long_to_double(rt, b);
387+ } else if (num && num->kind == TY_LONG) {
388+ if (!strcmp(op, "+")) return j_fmt("__ladd(%s, %s)", a, b);
389+ if (!strcmp(op, "-")) return j_fmt("__lsub(%s, %s)", a, b);
390+ if (!strcmp(op, "*")) return j_fmt("__lmul(%s, %s)", a, b);
391+ if (!strcmp(op, "/")) return j_fmt("__ldiv(%s, %s)", a, b);
392+ if (!strcmp(op, "%")) return j_fmt("__lrem(%s, %s)", a, b);
393+ if (!strcmp(op, "&")) return j_fmt("__land(%s, %s)", a, b);
394+ if (!strcmp(op, "|")) return j_fmt("__lor(%s, %s)", a, b);
395+ if (!strcmp(op, "^")) return j_fmt("__lxor(%s, %s)", a, b);
396+ if (cmp) return j_fmt("(__lcmp(%s, %s) %s 0)", a, b, op);
397+ if (eq) return j_fmt("%s__leq(%s, %s))", op[0] == '!' ? "(not " : "(", a, b);
398+ }
399+ if (is_long_t(lt) && !strcmp(op, "<<")) return j_fmt("__lshl(%s, %s)", a, b);
400+ if (is_long_t(lt) && !strcmp(op, ">>")) return j_fmt("__lshr(%s, %s)", a, b);
401+ if (is_long_t(lt) && !strcmp(op, ">>>")) return j_fmt("__lushr(%s, %s)", a, b);
402+ if (!strcmp(op, "==") || !strcmp(op, "!=")) { (void)eq; }
403+ }
404+ if (float_result(lt, rt) && (!strcmp(op, "+") || !strcmp(op, "-") || !strcmp(op, "*") || !strcmp(op, "/") || !strcmp(op, "%"))) {
405+ return j_fmt("__f32(%s %s %s)", a, op, b); /* every float operation rounds to 24 bits */
406+ }
407+ if (jl_wrap_int && int_result(lt, rt)) {
408+ if (!strcmp(op, "*")) return j_fmt("__imul(%s, %s)", a, b);
409+ if (!strcmp(op, "+") || !strcmp(op, "-")) return j_fmt("__w(%s %s %s)", a, op, b);
410+ if (!strcmp(op, "<<")) return j_fmt("__wg(%s << (%s & 31))", a, b);
411+ if (!strcmp(op, ">>>")) return j_fmt("__wg(__ushr(%s, %s & 31))", a, b);
412+ }
413+ if (!strcmp(op, "+") && (ty_is_string(lt) || ty_is_string(rt))) {
414+ return j_fmt("(%s + %s)", str_of(lt, a), str_of(rt, b));
415+ }
416+ if (!strcmp(op, "/") && integral_t(lt) && integral_t(rt)) return j_fmt("__idiv(%s, %s)", a, b);
417+ if (!strcmp(op, "%") && integral_t(lt) && integral_t(rt)) return j_fmt("__imod(%s, %s)", a, b);
418+ if (!strcmp(op, "&&")) return j_fmt("(%s and %s)", a, b);
419+ if (!strcmp(op, "||")) return j_fmt("(%s or %s)", a, b);
420+ if (ty_unbox(lt)->kind == TY_BOOL && !strcmp(op, "&")) return j_fmt("(%s and %s)", a, b);
421+ if (ty_unbox(lt)->kind == TY_BOOL && !strcmp(op, "|")) return j_fmt("(%s or %s)", a, b);
422+ if (ty_unbox(lt)->kind == TY_BOOL && !strcmp(op, "^")) return j_fmt("(%s != %s)", a, b);
423+ if (!strcmp(op, ">>>")) return j_fmt("__ushr(%s, %s)", a, b);
424+ (void)at;
425+ return j_fmt("(%s %s %s)", a, op, b);
426+}
427+
428+/* Old Ash only assigns through a plain variable (`a[i] = v`), never through `a[i][j]` or
429+ * `self["f"][i]`: any other base is hoisted into a temporary first. */
430+static char *assign_base(Node *base) {
431+ char *t = lx(base);
432+ int plain = *t != 0;
433+ for (const char *p = t; *p; p++) {
434+ if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == '_')) plain = 0;
435+ }
436+ if (plain) return t;
437+ char *tmp = new_temp();
438+ emit_line(&L.pre, 0, "local %s = %s;", tmp, t);
439+ return tmp;
440+}
441+
442+static char *lvalue(Node *e) {
443+ switch (e->kind) {
444+ case N_NAME:
445+ if (e->rk == R_LOCAL) return sname(e->name);
446+ if (e->rk == R_FIELD || e->rk == R_THIS_FIELD) return j_fmt("self[%s]", quote_str(e->name));
447+ if (e->rk == R_STATIC_FIELD || e->rk == R_ENUM_CONST) {
448+ JField *f = e->sym;
449+ return j_fmt("%s[%s]", cls_state(f->cls), quote_str(f->name));
450+ }
451+ break;
452+ case N_FIELDACC: {
453+ JField *f = e->sym;
454+ if (e->rk == R_STATIC_FIELD || e->rk == R_ENUM_CONST) return j_fmt("%s[%s]", cls_state(f->cls), quote_str(f->name));
455+ char *base = assign_base(e->a);
456+ return j_fmt("%s[%s]", base, quote_str(e->name));
457+ }
458+ case N_INDEX: {
459+ char *base = assign_base(e->a);
460+ char *i = lx(e->b);
461+ return j_fmt("%s[%s]", base, i);
462+ }
463+ default: break;
464+ }
465+ lerr(e, "unsupported assignment target");
466+ return "none";
467+}
468+
469+
470+static char *incdec_text(Node *e, const char *cur, int inc);
471+
472+static int plain_ident(const char *t) {
473+ if (!*t) return 0;
474+ for (const char *p = t; *p; p++) {
475+ if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == '_')) return 0;
476+ }
477+ return 1;
478+}
479+
480+/* Lowers `target = value` operands in Java order: target base/index first, then the value. */
481+static void assign_parts(Node *e, char **lhs, char **rhs) {
482+ Node *t = e->a;
483+ int field = t->kind == N_FIELDACC && t->rk != R_STATIC_FIELD && t->rk != R_ENUM_CONST && t->rk != R_ARRAYLEN;
484+ if (t->kind == N_INDEX || field) {
485+ Node *ns[3];
486+ char *v[3];
487+ int cnt = 0;
488+ ns[cnt++] = t->a;
489+ if (t->kind == N_INDEX) ns[cnt++] = t->b;
490+ ns[cnt++] = e->b;
491+ lx_seq(ns, cnt, v);
492+ char *base = v[0];
493+ if (!plain_ident(base)) {
494+ char *tmp = new_temp();
495+ emit_line(&L.pre, 0, "local %s = %s;", tmp, base);
496+ base = tmp;
497+ }
498+ *lhs = t->kind == N_INDEX ? j_fmt("%s[%s]", base, v[1]) : j_fmt("%s[%s]", base, quote_str(t->name));
499+ *rhs = v[cnt - 1];
500+ return;
501+ }
502+ *lhs = lvalue(t);
503+ *rhs = lx(e->b);
504+}
505+
506+void emit_assign_stmt(JBuf *b, Node *e, int ind) {
507+ if (e->kind == N_ASSIGN) {
508+ char *lhs, *rhs;
509+ assign_parts(e, &lhs, &rhs);
510+ if (strcmp(e->name, "=") != 0) {
511+ char op[4];
512+ strncpy(op, e->name, 3);
513+ op[strlen(e->name) - 1] = 0;
514+ rhs = binop(op, e->a->type, e->b->type, lhs, rhs, e);
515+ }
516+ flush_pre(b, ind);
517+ emit_line(b, ind, "%s = %s;", lhs, rhs);
518+ return;
519+ }
520+ /* ++ / -- as statements */
521+ Node *t = e->a;
522+ char *lhs = lvalue(t);
523+ int inc = e->name[0] == '+';
524+ flush_pre(b, ind);
525+ emit_line(b, ind, "%s = %s;", lhs, incdec_text(e, lhs, inc));
526+}
527+
528+
529+/* ---------------------------------------------------------------- side effects inside expressions */
530+
531+/* Assignments, ++ and -- inside expressions become hoisted statements in L.pre. To keep Java's
532+ * left-to-right order, an operand lowered before a sibling that produced hoisted statements is
533+ * first saved into a temporary placed *before* them; && || ?: run the hoisted statements of
534+ * their right-hand side / branches conditionally. */
535+
536+static int trivial_text(const char *t) {
537+ if (!*t) return 1;
538+ if (*t == '"' || (*t >= '0' && *t <= '9')) return 1;
539+ return !strcmp(t, "yes") || !strcmp(t, "no") || !strcmp(t, "none") || !strcmp(t, "self");
540+}
541+
542+static void pre_insert(size_t pos, const char *text) {
543+ size_t n = strlen(text);
544+ size_t old = L.pre.len;
545+ j_buf_addn(&L.pre, text, n);
546+ memmove(L.pre.data + pos + n, L.pre.data + pos, old - pos);
547+ memcpy(L.pre.data + pos, text, n);
548+ L.pre.data[L.pre.len] = 0;
549+}
550+
551+static char *pre_cut(size_t pos) {
552+ char *copy = j_strdup(L.pre.data ? L.pre.data + pos : "");
553+ L.pre.len = pos;
554+ if (L.pre.data) L.pre.data[pos] = 0;
555+ return copy;
556+}
557+
558+static void add_indented(const char *text) {
559+ const char *p = text;
560+ while (*p) {
561+ const char *nl = strchr(p, '\n');
562+ size_t len = nl ? (size_t)(nl - p) : strlen(p);
563+ j_buf_add(&L.pre, " ");
564+ j_buf_addn(&L.pre, p, len);
565+ j_buf_addc(&L.pre, '\n');
566+ if (!nl) break;
567+ p = nl + 1;
568+ }
569+}
570+
571+/* Lowers nodes in order; saves earlier results that a later node's hoisted statements could change. */
572+static void lx_seq(Node **ns, int cnt, char **out) {
573+ size_t before[32], after[32];
574+ for (int i = 0; i < cnt; i++) {
575+ before[i] = L.pre.len;
576+ out[i] = lx(ns[i]);
577+ after[i] = L.pre.len;
578+ }
579+ for (int i = cnt - 2; i >= 0; i--) {
580+ int later = 0;
581+ for (int j = i + 1; j < cnt; j++) if (after[j] > before[j]) later = 1;
582+ if (later && !trivial_text(out[i])) {
583+ char *t = new_temp();
584+ pre_insert(after[i], j_fmt("local %s = %s;\n", t, out[i]));
585+ out[i] = t;
586+ }
587+ }
588+}
589+
590+static char *short_circuit(Node *e) {
591+ int is_and = e->name[0] == '&';
592+ char *a = lx(e->a);
593+ size_t mark = L.pre.len;
594+ char *b = lx(e->b);
595+ if (L.pre.len == mark) return j_fmt("(%s %s %s)", a, is_and ? "and" : "or", b);
596+ char *rhs_pre = pre_cut(mark);
597+ char *t = new_temp();
598+ emit_line(&L.pre, 0, "local %s = %s;", t, a);
599+ emit_line(&L.pre, 0, "given %s%s {", is_and ? "" : "not ", t);
600+ add_indented(rhs_pre);
601+ emit_line(&L.pre, 1, "%s = %s;", t, b);
602+ emit_line(&L.pre, 0, "}");
603+ return t;
604+}
605+
606+static char *conditional(Node *e) {
607+ char *c = lx(e->a);
608+ size_t m1 = L.pre.len;
609+ char *tb = lx(e->b);
610+ char *pb = pre_cut(m1);
611+ char *fc = lx(e->c);
612+ char *pc = pre_cut(m1);
613+ if (!*pb && !*pc) return j_fmt("(%s ? %s : %s)", c, tb, fc);
614+ char *t = new_temp();
615+ emit_line(&L.pre, 0, "local %s = none;", t);
616+ emit_line(&L.pre, 0, "given %s {", c);
617+ add_indented(pb);
618+ emit_line(&L.pre, 1, "%s = %s;", t, tb);
619+ emit_line(&L.pre, 0, "} otherwise {");
620+ add_indented(pc);
621+ emit_line(&L.pre, 1, "%s = %s;", t, fc);
622+ emit_line(&L.pre, 0, "}");
623+ return t;
624+}
625+
626+
627+/* `x + 1` / `x - 1`, wrapped to 32 bits for int variables unless the for-loop analysis proved it cannot overflow */
628+static char *incdec_text(Node *e, const char *cur, int inc) {
629+ JType *t = ty_unbox(e->a->type);
630+ if (jl_wrap_int && t->kind == TY_LONG) return j_fmt("%s(%s, 1)", inc ? "__ladd" : "__lsub", cur);
631+ if (jl_wrap_int && t->kind == TY_INT && !(e->flags & 0x200)) return j_fmt("__w(%s %c 1)", cur, inc ? '+' : '-');
632+ return j_fmt("%s %c 1", cur, inc ? '+' : '-');
633+}
634+
635+/* assignment / ++ / -- used as a value: the statement is hoisted, the value is read back (or saved) */
636+static char *assign_value(Node *e) {
637+ if (e->kind == N_ASSIGN) {
638+ char *lhs, *rhs;
639+ assign_parts(e, &lhs, &rhs);
640+ if (strcmp(e->name, "=") != 0) {
641+ char op[4];
642+ strncpy(op, e->name, 3);
643+ op[strlen(e->name) - 1] = 0;
644+ rhs = binop(op, e->a->type, e->b->type, lhs, rhs, e);
645+ }
646+ emit_line(&L.pre, 0, "%s = %s;", lhs, rhs);
647+ return lhs;
648+ }
649+ int inc = e->name[0] == '+';
650+ int prefix = e->kind == N_UNARY;
651+ char *lv = lvalue(e->a);
652+ if (prefix) {
653+ emit_line(&L.pre, 0, "%s = %s;", lv, incdec_text(e, lv, inc));
654+ return lv;
655+ }
656+ char *t = new_temp();
657+ emit_line(&L.pre, 0, "local %s = %s;", t, lv);
658+ emit_line(&L.pre, 0, "%s = %s;", lv, incdec_text(e, t, inc));
659+ return t;
660+}
661+
662+/* ---------------------------------------------------------------- main switch */
663+
664+static char *lower_name(Node *n) {
665+ switch (n->rk) {
666+ case R_LOCAL: {
667+ char *al = alias_get(n->sym);
668+ return al ? al : sname(n->name);
669+ }
670+ case R_FIELD: case R_THIS_FIELD: return j_fmt("self[%s]", quote_str(n->name));
671+ case R_STATIC_FIELD: case R_ENUM_CONST: {
672+ JField *f = n->sym;
673+ if (f->tmpl) return expand_tmpl(f->tmpl, NULL, NULL, 0);
674+ return j_fmt("%s[%s]", cls_state(f->cls), quote_str(f->name));
675+ }
676+ default:
677+ lerr(n, "cannot lower name '%s'", n->name);
678+ return "none";
679+ }
680+}
681+
682+static char *lower_fieldacc(Node *n) {
683+ if (n->rk == R_ARRAYLEN) return j_fmt("len(%s)", lx(n->a));
684+ JField *f = n->sym;
685+ if (!f) return "none";
686+ if (f->tmpl) return expand_tmpl(f->tmpl, NULL, NULL, 0);
687+ if (n->rk == R_STATIC_FIELD || n->rk == R_ENUM_CONST) return j_fmt("%s[%s]", cls_state(f->cls), quote_str(f->name));
688+ return j_fmt("%s[%s]", lx(n->a), quote_str(n->name));
689+}
690+
691+static char *default_value(JType *t) {
692+ if (ty_is_prim(t)) return t->kind == TY_BOOL ? "no" : "0";
693+ return "none";
694+}
695+
696+static char *lower_array(Node *n) {
697+ /* total dims n->flags; dim exprs in l1; optional initializer in b */
698+ if (n->b) {
699+ JBuf b = {0};
700+ j_buf_addc(&b, '[');
701+ for (int i = 0; i < n->b->l1.count; i++) {
702+ if (i) j_buf_add(&b, ", ");
703+ j_buf_add(&b, lx(n->b->l1.items[i]));
704+ }
705+ j_buf_addc(&b, ']');
706+ return b.data;
707+ }
708+ JType *t = n->type;
709+ for (int i = 0; i < n->l1.count; i++) t = t->elem;
710+ char *dims = NULL;
711+ JBuf b = {0};
712+ j_buf_addc(&b, '[');
713+ for (int i = 0; i < n->l1.count; i++) {
714+ if (i) j_buf_add(&b, ", ");
715+ j_buf_add(&b, lx(n->l1.items[i]));
716+ }
717+ j_buf_addc(&b, ']');
718+ dims = b.data;
719+ return j_fmt("__newarr(%s, %s)", dims, default_value(t));
720+}
721+
722+static char *cast_text(Node *n) {
723+ JType *to = n->type, *from = n->b->type;
724+ char *v = lx(n->b);
725+ JType *uf = ty_unbox(from), *ut = ty_unbox(to);
726+ if (ty_is_prim(ut) && ty_is_prim(uf)) {
727+ if (jl_wrap_int) {
728+ if (uf->kind == TY_LONG && ut->kind == TY_INT) return j_fmt("__l2i(%s)", v);
729+ if (uf->kind == TY_LONG && (ut->kind == TY_BYTE || ut->kind == TY_SHORT || ut->kind == TY_CHAR)) return j_fmt("__wn(__l2i(%s), %d, %d)", v, ut->kind == TY_BYTE ? 8 : 16, ut->kind != TY_CHAR);
730+ if (uf->kind == TY_LONG && ut->kind == TY_FLOAT) return j_fmt("__f32(__l2d(%s))", v);
731+ if (uf->kind == TY_LONG && ut->kind == TY_DOUBLE) return j_fmt("__l2d(%s)", v);
732+ if ((uf->kind == TY_FLOAT || uf->kind == TY_DOUBLE) && ut->kind == TY_LONG) return j_fmt("__d2l(%s)", v);
733+ }
734+ if (ut->kind == TY_FLOAT && uf->kind != TY_FLOAT) return j_fmt("__f32(%s)", v);
735+ int ff = uf->kind == TY_FLOAT || uf->kind == TY_DOUBLE;
736+ if (ff && ut->kind == TY_INT) return j_fmt("__d2i(%s)", v); /* saturates, NaN -> 0 */
737+ if (ff && (ty_is_integral(ut) || ut->kind == TY_CHAR)) {
738+ if (ut->kind == TY_LONG) return j_fmt("__trunc(%s)", v);
739+ return j_fmt("__wn(__d2i(%s), %d, %d)", v, ut->kind == TY_BYTE ? 8 : 16, ut->kind != TY_CHAR);
740+ }
741+ /* integral narrowing wraps (explicit casts are rare, so this costs nothing in hot code) */
742+ if (ut->kind == TY_INT && uf->kind == TY_LONG) return j_fmt("__w(__wg(%s))", v);
743+ if (ut->kind == TY_BYTE && uf->kind != TY_BYTE) return j_fmt("__wn(%s, 8, 1)", v);
744+ if (ut->kind == TY_SHORT && uf->kind != TY_BYTE && uf->kind != TY_SHORT) return j_fmt("__wn(%s, 16, 1)", v);
745+ if (ut->kind == TY_CHAR && uf->kind != TY_CHAR) return j_fmt("__wn(%s, 16, 0)", v);
746+ return v;
747+ }
748+ if (to->kind == TY_CLASS && !ty_is_object(to) && !(to->cls->kind == N_INTERFACE && jt_find_sam(to->cls))) {
749+ return j_fmt("__cast(%s, %s)", v, quote_str(to->cls->qname));
750+ }
751+ return v;
752+}
753+
754+static char *instance_test(JType *t, const char *v) {
755+ if (t->kind == TY_CLASS) return j_fmt("__inst(%s, %s)", v, quote_str(t->cls->qname));
756+ if (t->kind == TY_ARRAY) return j_fmt("(type(%s) == \"array\")", v);
757+ return "yes";
758+}
759+
760+static char *lower_instanceof(Node *n) {
761+ Node *a = n->a;
762+ char *v = lx(a);
763+ if (n->name && n->sym) {
764+ char *alias = v;
765+ if (!(a->kind == N_NAME && (a->rk == R_LOCAL || a->rk == R_FIELD)) && a->kind != N_THIS) {
766+ alias = new_temp();
767+ emit_line(&L.pre, 0, "local %s = %s;", alias, v);
768+ v = alias;
769+ }
770+ alias_set(n->sym, alias);
771+ }
772+ return instance_test(jt_resolve_type(n->b, L.cls, L.method, 0), v);
773+}
774+
775+static char *lower_lambda(Node *n) {
776+ JMethod *sam = n->sym;
777+ JBuf b = {0};
778+ j_buf_add(&b, "forge(");
779+ for (int i = 0; i < n->l1.count; i++) {
780+ if (i) j_buf_add(&b, ", ");
781+ j_buf_add(&b, sname(((Node *)n->l1.items[i])->name));
782+ }
783+ j_buf_add(&b, ") {");
784+ char *body = lower_lambda_body(n, sam, 1);
785+ j_buf_add(&b, body);
786+ j_buf_add(&b, "}");
787+ return b.data;
788+}
789+
790+static char *lower_methodref(Node *n) {
791+ JMethod *m = n->sym2;
792+ JMethod *sam = n->sym;
793+ if (!m || !sam) return "none";
794+ int np = sam->params.count;
795+ char **ps = j_alloc(sizeof(char *) * (size_t)(np + 1));
796+ for (int i = 0; i < np; i++) ps[i] = j_fmt("__p%d", i);
797+ char *params = join_args(ps, np, NULL);
798+ char *call;
799+ Node *tn = n->a;
800+ int is_type = tn->rk == R_CLASS;
801+ if (!strcmp(n->name, "new")) {
802+ if (m->tmpl) call = expand_tmpl(m->tmpl, NULL, ps, np);
803+ else call = j_fmt("%s(%s)", fn_name(m, "new"), join_args(ps, np, NULL));
804+ } else if (m->flags & M_STATIC) {
805+ call = invoke(m, NULL, ps, np, NULL, 0, n);
806+ } else if (is_type) {
807+ call = invoke(m, ps[0], ps + 1, np - 1, tn->type, 0, n);
808+ } else {
809+ char *recv = lx(tn);
810+ if (tn->kind != N_THIS && tn->kind != N_NAME && !(m->tmpl && !tmpl_uses_receiver(m->tmpl))) {
811+ char *t = new_temp();
812+ emit_line(&L.pre, 0, "local %s = %s;", t, recv);
813+ recv = t;
814+ }
815+ call = invoke(m, recv, ps, np, tn->type, 0, n);
816+ }
817+ if (sam->ret->kind == TY_VOID) {
818+ return j_fmt("forge(%s) { %s; }", params, call);
819+ }
820+ return j_fmt("forge(%s) { yield %s; }", params, call);
821+}
822+
823+char *lx(Node *e) {
824+ switch (e->kind) {
825+ case N_LIT_INT: return j_fmt("%llu", (unsigned long long)strtoull(e->name, NULL, 0));
826+ case N_LIT_LONG: {
827+ unsigned long long v = strtoull(e->name, NULL, 0);
828+ if (jl_wrap_int && v >= 9007199254740992ULL) {
829+ long long sv = (long long)v; /* two's complement: 9223372036854775808L is the minimum */
830+ return j_fmt("{\"__L\": 1, \"hi\": %lld, \"lo\": %llu}", sv >> 32, (unsigned long long)(v & 0xFFFFFFFFULL));
831+ }
832+ return j_fmt("%llu", v);
833+ }
834+ case N_LIT_DOUBLE: return (char *)e->name;
835+ case N_LIT_FLOAT: return j_fmt("%.17g", (double)(float)strtod(e->name, NULL));
836+ case N_LIT_CHAR: return (char *)e->name;
837+ case N_LIT_STRING: return quote_str(e->name);
838+ case N_LIT_BOOL: return !strcmp(e->name, "true") ? "yes" : "no";
839+ case N_LIT_NULL: return "none";
840+ case N_NAME: return lower_name(e);
841+ case N_FIELDACC: return lower_fieldacc(e);
842+ case N_THIS: return "self";
843+ case N_CALL: return lower_call(e);
844+ case N_NEW: return lower_new(e);
845+ case N_NEWARRAY: case N_ARRAYINIT: return lower_array(e);
846+ case N_INDEX: {
847+ Node *ns[2] = {e->a, e->b};
848+ char *v[2];
849+ lx_seq(ns, 2, v);
850+ return j_fmt("%s[%s]", v[0], v[1]);
851+ }
852+ case N_UNARY:
853+ if (!strcmp(e->name, "!")) return j_fmt("(not %s)", lx(e->a));
854+ if (!strcmp(e->name, "++") || !strcmp(e->name, "--")) return assign_value(e);
855+ if (!strcmp(e->name, "+")) return lx(e->a);
856+ if (jl_wrap_int && !strcmp(e->name, "-") && ty_unbox(e->a->type)->kind == TY_INT) return j_fmt("__w(-%s)", lx(e->a));
857+ if (jl_wrap_int && !strcmp(e->name, "-") && is_long_t(e->a->type)) return j_fmt("__lneg(%s)", lx(e->a));
858+ if (jl_wrap_int && !strcmp(e->name, "~") && is_long_t(e->a->type)) return j_fmt("__lxor(%s, -1)", lx(e->a));
859+ return j_fmt("(%s%s)", e->name, lx(e->a));
860+ case N_POSTFIX: return assign_value(e);
861+ case N_BINARY: {
862+ if (!strcmp(e->name, "&&") || !strcmp(e->name, "||")) return short_circuit(e);
863+ Node *ns[2] = {e->a, e->b};
864+ char *v[2];
865+ lx_seq(ns, 2, v);
866+ return binop(e->name, e->a->type, e->b->type, v[0], v[1], e);
867+ }
868+ case N_ASSIGN: return assign_value(e);
869+ case N_COND: return conditional(e);
870+ case N_CAST: return cast_text(e);
871+ case N_INSTANCEOF: return lower_instanceof(e);
872+ case N_LAMBDA: return lower_lambda(e);
873+ case N_METHODREF: return lower_methodref(e);
874+ case N_SWITCH: return lower_switch_expr(e);
875+ default:
876+ lerr(e, "cannot lower this expression");
877+ return "none";
878+ }
879+}

hearth/front/src/jlower/C/lower_prelude.cadded

+569−0
Show diff (569 changed lines)
@@ -0,0 +1,569 @@
1+#include <string.h>
2+
3+#include "jlower/H/jlower_internal.h"
4+
5+/* Runtime support written in old Ash and prepended to every generated module. It only
6+ * adds code to generated programs; it does not touch ashvm. */
7+
8+static char *ex_new(const char *qname) {
9+ JMethod *k = find_ctor(jt_well_known(qname), 1);
10+ return k ? fn_name(k, "new") : (char *)"__missing_ctor";
11+}
12+
13+static void raw(JBuf *b, const char *s) { j_buf_add(b, s); j_buf_addc(b, '\n'); }
14+
15+void emit_prelude(JBuf *b) {
16+ raw(b, "local __KP = [chr(1), 0];");
17+ raw(b,
18+ "forge __bs(x) { given x { yield \"true\"; } yield \"false\"; }\n"
19+ "forge __p10(n) { local r = 1; local i = 0; during i < n { r = r * 10; i += 1; } yield r; }\n"
20+ "forge __pad8(n) { local s = str(n); during len(s) < 8 { s = \"0\" + s; } yield s; }\n"
21+ "forge __zeros(n) { yield repeat(\"0\", n); }\n"
22+ "forge __split27(x) { local t = 134217729 * x; local h = t - (t - x); yield [h, x - h]; }\n"
23+ "forge __dtext(d, e, p) {\n"
24+ " given (e >= 0 - 3 and e < 7) {\n"
25+ " given e >= 0 {\n"
26+ " given p <= e + 1 { yield d + __zeros(e + 1 - p) + \".0\"; }\n"
27+ " yield substring(d, 0, e + 1) + \".\" + substring(d, e + 1, p);\n"
28+ " }\n"
29+ " yield \"0.\" + __zeros(0 - e - 1) + d;\n"
30+ " }\n"
31+ " local frac = \"0\";\n"
32+ " given p > 1 { frac = substring(d, 1, p); }\n"
33+ " yield substring(d, 0, 1) + \".\" + frac + \"E\" + str(e);\n"
34+ "}\n"
35+ "forge __scaled(a, k) {\n"
36+ " local pw = __p10(k >= 0 ? k : 0 - k);\n"
37+ " local sa = __split27(a);\n"
38+ " local sp = __split27(pw);\n"
39+ " given k >= 0 {\n"
40+ " local y = a * pw;\n"
41+ " local err = ((sa[0] * sp[0] - y) + sa[0] * sp[1] + sa[1] * sp[0]) + sa[1] * sp[1];\n"
42+ " yield [y, err];\n"
43+ " }\n"
44+ " local q = a / pw;\n"
45+ " local sq = __split27(q);\n"
46+ " local pq = q * pw;\n"
47+ " local e2 = ((sq[0] * sp[0] - pq) + sq[0] * sp[1] + sq[1] * sp[0]) + sq[1] * sp[1];\n"
48+ " yield [q, ((a - pq) - e2) / pw];\n"
49+ "}\n"
50+ "forge __sinc(s) {\n"
51+ " local i = len(s) - 1;\n"
52+ " local out = \"\";\n"
53+ " local carry = 1;\n"
54+ " during i >= 0 {\n"
55+ " local d = ord(s[i]) - 48 + carry;\n"
56+ " carry = 0;\n"
57+ " given d == 10 { d = 0; carry = 1; }\n"
58+ " out = str(d) + out;\n"
59+ " i -= 1;\n"
60+ " }\n"
61+ " given carry == 1 { out = \"1\" + out; }\n"
62+ " yield out;\n"
63+ "}\n"
64+ "forge __bigmul(limbs, f) {\n"
65+ " local carry = 0;\n"
66+ " local i = 0;\n"
67+ " during i < len(limbs) {\n"
68+ " local v = limbs[i] * f + carry;\n"
69+ " carry = floor(v / 10000000);\n"
70+ " limbs[i] = v - carry * 10000000;\n"
71+ " i += 1;\n"
72+ " }\n"
73+ " during carry > 0 {\n"
74+ " push(limbs, carry % 10000000);\n"
75+ " carry = floor(carry / 10000000);\n"
76+ " }\n"
77+ "}\n"
78+ "forge __bigstr(limbs) {\n"
79+ " local n = len(limbs) - 1;\n"
80+ " local out = str(limbs[n]);\n"
81+ " n -= 1;\n"
82+ " during n >= 0 {\n"
83+ " local part = str(limbs[n]);\n"
84+ " during len(part) < 7 { part = \"0\" + part; }\n"
85+ " out = out + part;\n"
86+ " n -= 1;\n"
87+ " }\n"
88+ " yield out;\n"
89+ "}\n"
90+ "forge __strip0(s) {\n"
91+ " local n = len(s);\n"
92+ " during n > 1 and s[n - 1] == \"0\" { n -= 1; }\n"
93+ " yield substring(s, 0, n);\n"
94+ "}\n"
95+ "forge __dround(D, e10, p, a, isf) {\n"
96+ " local head = D;\n"
97+ " local nextd = 0;\n"
98+ " given len(D) > p {\n"
99+ " head = substring(D, 0, p);\n"
100+ " nextd = ord(D[p]) - 48;\n"
101+ " }\n"
102+ " local ds = head;\n"
103+ " local e2 = e10;\n"
104+ " given nextd >= 5 {\n"
105+ " ds = __sinc(head);\n"
106+ " given len(ds) > len(head) { ds = \"1\"; e2 = e10 + 1; }\n"
107+ " }\n"
108+ " ds = __strip0(ds);\n"
109+ " local back = num(ds + \"e\" + str(e2 - len(ds) + 1));\n"
110+ " given isf { back = __f32(back); }\n"
111+ " given back == a { yield [ds, e2]; }\n"
112+ " yield none;\n"
113+ "}\n"
114+ "forge __dexact(a, isf) {\n"
115+ " local q = 0;\n"
116+ " local m = a;\n"
117+ " during m >= 9007199254740992 { m = m / 2; q += 1; }\n"
118+ " during m != floor(m) { m = m * 2; q -= 1; }\n"
119+ " local limbs = [];\n"
120+ " local t = m;\n"
121+ " during t > 0 { push(limbs, t % 10000000); t = floor(t / 10000000); }\n"
122+ " local k = 0;\n"
123+ " local f = 2;\n"
124+ " given q < 0 { k = 0 - q; f = 5; }\n"
125+ " local left = q;\n"
126+ " given q < 0 { left = k; }\n"
127+ " local chunk = 1;\n"
128+ " local cs = f == 2 ? 20 : 9;\n"
129+ " local cf = f == 2 ? 1048576 : 1953125;\n"
130+ " during left >= cs { __bigmul(limbs, cf); left -= cs; }\n"
131+ " local rest = 1;\n"
132+ " local i = 0;\n"
133+ " during i < left { rest = rest * f; i += 1; }\n"
134+ " given rest > 1 { __bigmul(limbs, rest); }\n"
135+ " local D = __bigstr(limbs);\n"
136+ " local e10 = len(D) - 1 - k;\n"
137+ " local maxp = isf ? 9 : 17;\n"
138+ " local p = 1;\n"
139+ " during p <= maxp {\n"
140+ " local r = __dround(D, e10, p, a, isf);\n"
141+ " given r != none {\n"
142+ " local ds = r[0];\n"
143+ " local e2 = r[1];\n"
144+ " given len(ds) == 1 and len(D) > 1 {\n"
145+ " local r2 = __dround(D, e10, 2, a, isf);\n"
146+ " given r2 != none and len(r2[0]) == 2 { yield __dtext(r2[0], r2[1], 2); }\n"
147+ " }\n"
148+ " yield __dtext(ds, e2, len(ds));\n"
149+ " }\n"
150+ " p += 1;\n"
151+ " }\n"
152+ " yield str(a);\n"
153+ "}\n"
154+ "forge __dabs(a, isf) {\n"
155+ " local e = 0;\n"
156+ " given a >= 1 {\n"
157+ " during a >= __p10(e + 1) { e += 1; }\n"
158+ " } otherwise {\n"
159+ " e = 0 - 1;\n"
160+ " during a * __p10(0 - e) < 1 { e -= 1; }\n"
161+ " }\n"
162+ " given e < 0 - 5 or e > 22 { yield __dexact(a, isf); }\n"
163+ " local p = 1;\n"
164+ " during p <= (isf ? 9 : 17) {\n"
165+ " local k = p - 1 - e;\n"
166+ " local ye = __scaled(a, k);\n"
167+ " local y = ye[0];\n"
168+ " local hi = floor(y / 100000000);\n"
169+ " local lo = floor((y - hi * 100000000) + ye[1] + 0.5);\n"
170+ " local d = 0;\n"
171+ " local limit = 3;\n"
172+ " given k > 22 or k < 0 - 22 { limit = 17; }\n"
173+ " during d < limit {\n"
174+ " local off = 0;\n"
175+ " given d % 2 == 1 { off = (d + 1) / 2; }\n"
176+ " given d % 2 == 0 and d > 0 { off = 0 - d / 2; }\n"
177+ " local l2 = lo + off;\n"
178+ " local h2 = hi;\n"
179+ " given l2 < 0 { l2 += 100000000; h2 -= 1; }\n"
180+ " given l2 >= 100000000 { l2 -= 100000000; h2 += 1; }\n"
181+ " local ds = str(l2);\n"
182+ " given h2 > 0 { ds = str(h2) + __pad8(l2); }\n"
183+ " given len(ds) == p + 1 and substring(ds, 1, p + 1) == __zeros(p) {\n"
184+ " local back1 = num(ds + \"e\" + str(0 - k));\n"
185+ " given isf { back1 = __f32(back1); }\n"
186+ " given back1 == a { yield __dtext(\"1\", e + 1, 1); }\n"
187+ " }\n"
188+ " given len(ds) == p {\n"
189+ " local back = num(ds + \"e\" + str(0 - k));\n"
190+ " given isf { back = __f32(back); }\n"
191+ " given back == a { yield __dtext(ds, e, p); }\n"
192+ " }\n"
193+ " d += 1;\n"
194+ " }\n"
195+ " p += 1;\n"
196+ " }\n"
197+ " yield str(a);\n"
198+ "}\n"
199+ "forge __dnum(x, isf) {\n"
200+ " given x != x { yield \"NaN\"; }\n"
201+ " given x == 0 { yield \"0.0\"; }\n"
202+ " given x > 0 and x * 2 == x { yield \"Infinity\"; }\n"
203+ " given x < 0 and x * 2 == x { yield \"-Infinity\"; }\n"
204+ " given x < 0 { yield \"-\" + __dabs(0 - x, isf); }\n"
205+ " yield __dabs(x, isf);\n"
206+ "}\n"
207+ "forge __dstr(x) { yield __dnum(x, no); }\n"
208+ "forge __fstr(x) { yield __dnum(x, yes); }\n"
209+ "forge __f32(x) {\n"
210+ " given x != 0 and x > 0 - 1.1754943508222875e-38 and x < 1.1754943508222875e-38 {\n"
211+ " local sc = __p2(149);\n"
212+ " yield floor(x * sc + 0.5) / sc;\n"
213+ " }\n"
214+ " local t = 536870913 * x;\n"
215+ " yield t - (t - x);\n"
216+ "}\n"
217+ "forge __startsat(s, p, i) { given i < 0 or i + len(p) > len(s) { yield no; } yield substring(s, i, i + len(p)) == p; }\n"
218+ "forge __split(s, sep) {\n"
219+ " given len(s) == 0 { yield [\"\"]; }\n"
220+ " local r = split(s, sep);\n"
221+ " during len(r) > 0 and r[len(r) - 1] == \"\" { pop(r); }\n"
222+ " yield r;\n"
223+ "}\n"
224+ "forge __anymatch(l, f) { each v in l { given f(v) { yield yes; } } yield no; }\n"
225+ "forge __sorted(l, c) { local r = slice(l, 0, len(l)); sort(r, c); yield r; }\n"
226+ "forge __w(x) { given x >= 2147483648 { yield x - 4294967296; } given x < -2147483648 { yield x + 4294967296; } yield x; }\n"
227+ "forge __wg(x) { yield x - 4294967296 * floor((x + 2147483648) / 4294967296); }\n"
228+ "forge __imul(a, b) {\n"
229+ " local ah = floor(a / 65536);\n"
230+ " local al = a - ah * 65536;\n"
231+ " yield __wg(((ah * b) % 4294967296) * 65536 + al * b);\n"
232+ "}\n"
233+ "forge __d2i(x) {\n"
234+ " given x != x { yield 0; }\n"
235+ " given x >= 2147483647 { yield 2147483647; }\n"
236+ " given x <= -2147483648 { yield -2147483648; }\n"
237+ " yield __trunc(x);\n"
238+ "}\n"
239+ "forge __wn(x, bits, signed) {\n"
240+ " local mod = 1;\n"
241+ " local i = 0;\n"
242+ " during i < bits { mod = mod * 2; i += 1; }\n"
243+ " local r = x - mod * floor(x / mod);\n"
244+ " given signed and r >= mod / 2 { r = r - mod; }\n"
245+ " yield r;\n"
246+ "}\n"
247+ "forge __cmp(a, b) { given a < b { yield -1; } given a > b { yield 1; } yield 0; }\n"
248+ "forge __max(a, b) { given a > b { yield a; } yield b; }\n"
249+ "forge __min(a, b) { given a < b { yield a; } yield b; }\n"
250+ "forge __iife(f) { yield f(); }\n"
251+ "forge __println(s) { say s; }\n"
252+ "forge __ushr(a, n) { yield (a & 4294967295) >> n; }\n"
253+ "forge __trunc(x) { given x >= 0 { yield floor(x); } yield 0 - floor(0 - x); }\n"
254+ "forge __str(x) {\n"
255+ " local t = type(x);\n"
256+ " given t == \"string\" { yield x; }\n"
257+ " given t == \"none\" { yield \"null\"; }\n"
258+ " given t == \"boolean\" { yield __bs(x); }\n"
259+ " given t == \"map\" and has(x, \"__L\") { yield __lstr(x); }\n"
260+ " given t == \"map\" and has(x, \"__vt\") {\n"
261+ " local vt = x[\"__vt\"];\n"
262+ " given has(vt, \"toString/0\") { local f = vt[\"toString/0\"]; yield f(x); }\n"
263+ " yield x[\"__c\"] + \"@1\";\n"
264+ " }\n"
265+ " yield str(x);\n"
266+ "}\n"
267+ "forge __eq(a, b) {\n"
268+ " given type(a) == \"map\" and has(a, \"__L\") { yield (type(b) == \"map\" or type(b) == \"number\") and __leq(a, b); }\n"
269+ " given type(a) == \"map\" and has(a, \"__vt\") {\n"
270+ " local vt = a[\"__vt\"];\n"
271+ " given has(vt, \"equals/1\") { local f = vt[\"equals/1\"]; yield f(a, b); }\n"
272+ " }\n"
273+ " yield a == b;\n"
274+ "}\n"
275+ "forge __hash(x) {\n"
276+ " local t = type(x);\n"
277+ " given t == \"number\" { yield x; }\n"
278+ " given t == \"string\" {\n"
279+ " local h = 0;\n"
280+ " local i = 0;\n"
281+ " during i < len(x) { h = (h * 31 + ord(x[i])) % 4294967296; i += 1; }\n"
282+ " yield h;\n"
283+ " }\n"
284+ " given t == \"boolean\" { given x { yield 1231; } yield 1237; }\n"
285+ " given t == \"map\" and has(x, \"__L\") { yield (x[\"hi\"] ^ x[\"lo\"]) % 4294967296; }\n"
286+ " given t == \"map\" and has(x, \"__vt\") {\n"
287+ " local vt = x[\"__vt\"];\n"
288+ " given has(vt, \"hashCode/0\") { local f = vt[\"hashCode/0\"]; yield f(x); }\n"
289+ " }\n"
290+ " yield 0;\n"
291+ "}\n"
292+ "forge __nat(x, n) {\n"
293+ " local t = type(x);\n"
294+ " given n == \"ash.lang.Object\" { yield x != none; }\n"
295+ " given n == \"ash.lang.String\" or n == \"ash.lang.CharSequence\" { yield t == \"string\"; }\n"
296+ " given n == \"ash.lang.Integer\" or n == \"ash.lang.Long\" or n == \"ash.lang.Short\" or n == \"ash.lang.Byte\" or n == \"ash.lang.Float\" or n == \"ash.lang.Double\" or n == \"ash.lang.Number\" or n == \"ash.lang.Character\" { yield t == \"number\" or (t == \"map\" and has(x, \"__L\")); }\n"
297+ " given n == \"ash.lang.Boolean\" { yield t == \"boolean\"; }\n"
298+ " given n == \"ash.lang.Comparable\" { yield t == \"string\" or t == \"number\"; }\n"
299+ " given n == \"ash.util.List\" or n == \"ash.util.ArrayList\" or n == \"ash.lang.Iterable\" or n == \"ash.util.Stream\" { yield t == \"array\"; }\n"
300+ " given n == \"ash.util.Map\" or n == \"ash.util.HashMap\" or n == \"ash.util.Set\" or n == \"ash.util.HashSet\" { yield t == \"map\"; }\n"
301+ " yield no;\n"
302+ "}\n"
303+ "forge __inst(x, n) {\n"
304+ " given type(x) == \"map\" and has(x, \"__is\") { local s = x[\"__is\"]; yield has(s, n); }\n"
305+ " yield __nat(x, n);\n"
306+ "}\n"
307+ "forge __natcall(o, k, args) {\n"
308+ " given k == \"compareTo/1\" { yield __cmp(o, args[0]); }\n"
309+ " given k == \"equals/1\" { yield o == args[0]; }\n"
310+ " given k == \"hashCode/0\" { yield __hash(o); }\n"
311+ " given k == \"toString/0\" { yield __str(o); }\n"
312+ " raise \"unsupported call \" + k;\n"
313+ "}\n"
314+ "forge __classof(x) {\n"
315+ " given type(x) == \"map\" and has(x, \"__c\") { yield {\"n\": x[\"__c\"]}; }\n"
316+ " given type(x) == \"string\" { yield {\"n\": \"ash.lang.String\"}; }\n"
317+ " given type(x) == \"number\" { yield {\"n\": \"ash.lang.Double\"}; }\n"
318+ " given type(x) == \"bool\" { yield {\"n\": \"ash.lang.Boolean\"}; }\n"
319+ " given type(x) == \"array\" { yield {\"n\": \"ash.util.ArrayList\"}; }\n"
320+ " yield {\"n\": \"ash.lang.Object\"};\n"
321+ "}");
322+ /* things that raise Java-style exceptions need the lowered constructors */
323+ j_buf_addf(b,
324+ "forge __idiv(a, bb) {\n"
325+ " given bb == 0 { raise %s(\"/ by zero\"); }\n"
326+ " yield __trunc(a / bb);\n"
327+ "}\n"
328+ "forge __imod(a, bb) {\n"
329+ " given bb == 0 { raise %s(\"/ by zero\"); }\n"
330+ " yield a %% bb;\n"
331+ "}\n"
332+ "forge __cast(x, n) {\n"
333+ " given x == none or __inst(x, n) { yield x; }\n"
334+ " raise %s(\"cannot cast to \" + n);\n"
335+ "}\n"
336+ "forge __wrap(e) {\n"
337+ " given type(e) != \"string\" { yield e; }\n"
338+ " given contains(e, \"index out of bounds\") { yield %s(e); }\n"
339+ " given contains(e, \"key not found\") { yield %s(e); }\n"
340+ " given contains(e, \"division\") or contains(e, \"divide\") { yield %s(e); }\n"
341+ " yield %s(e);\n"
342+ "}\n"
343+ "forge __uncaught(e) {\n"
344+ " e = __wrap(e);\n"
345+ " given type(e) == \"map\" and has(e, \"__c\") { __trace(e, \"Exception in thread \\\"main\\\" \"); } otherwise { say \"Exception in thread \\\"main\\\" \" + str(e); }\n"
346+ " exit(1);\n"
347+ "}\n"
348+ "forge __vcnull(k) { raise %s(\"cannot call \" + k + \" on null\"); }\n"
349+ "forge __at(e, w) { e[\"__at\"] = w; yield e; }\n"
350+ "forge __trace(e, prefix) {\n"
351+ " local m = e[\"message\"];\n"
352+ " say prefix + e[\"__c\"] + (m == none ? \"\" : \": \" + m);\n"
353+ " given has(e, \"__at\") { say \" at \" + e[\"__at\"]; }\n"
354+ " local c = e[\"cause\"];\n"
355+ " given c != none { __trace(c, \"Caused by: \"); }\n"
356+ "}\n"
357+ "forge __pst(e) { __trace(e, \"\"); }\n"
358+ "forge __parse_int(s) {\n"
359+ " local t = trim(s);\n"
360+ " local ok = len(t) > 0;\n"
361+ " local i = 0;\n"
362+ " during i < len(t) {\n"
363+ " local c = ord(t[i]);\n"
364+ " given not ((c >= 48 and c <= 57) or (i == 0 and c == 45 and len(t) > 1)) { ok = no; }\n"
365+ " i += 1;\n"
366+ " }\n"
367+ " given not ok { raise %s(\"not an integer: \" + s); }\n"
368+ " yield num(t);\n"
369+ "}\n"
370+ "forge __parse_double(s) {\n"
371+ " local t = trim(s);\n"
372+ " local ok = len(t) > 0;\n"
373+ " local dots = 0;\n"
374+ " local i = 0;\n"
375+ " during i < len(t) {\n"
376+ " local c = ord(t[i]);\n"
377+ " given c == 46 { dots += 1; }\n"
378+ " given not ((c >= 48 and c <= 57) or c == 46 or (i == 0 and c == 45 and len(t) > 1)) { ok = no; }\n"
379+ " i += 1;\n"
380+ " }\n"
381+ " given not ok or dots > 1 { raise %s(\"not a number: \" + s); }\n"
382+ " yield num(t);\n"
383+ "}\n"
384+ "forge __enumvalueof(S, name) {\n"
385+ " each v in S[\"$VALUES\"] { given v[\"__name\"] == name { yield v; } }\n"
386+ " raise %s(\"No enum constant \" + name);\n"
387+ "}\n",
388+ ex_new("ash.lang.ArithmeticException"), ex_new("ash.lang.ArithmeticException"), ex_new("ash.lang.ClassCastException"),
389+ ex_new("ash.lang.IndexOutOfBoundsException"), ex_new("ash.lang.KeyNotFoundException"), ex_new("ash.lang.ArithmeticException"),
390+ ex_new("ash.lang.RuntimeException"), ex_new("ash.lang.NullPointerException"),
391+ ex_new("ash.lang.ParseException"), ex_new("ash.lang.ParseException"), ex_new("ash.lang.IllegalArgumentException"));
392+ raw(b,
393+ "forge __enumvalues(S) { yield slice(S[\"$VALUES\"], 0, len(S[\"$VALUES\"])); }\n"
394+ "forge __na(dims, i, d) {\n"
395+ " local n = dims[i];\n"
396+ " local r = [];\n"
397+ " local j = 0;\n"
398+ " during j < n {\n"
399+ " given i + 1 < len(dims) { push(r, __na(dims, i + 1, d)); } otherwise { push(r, d); }\n"
400+ " j += 1;\n"
401+ " }\n"
402+ " yield r;\n"
403+ "}\n"
404+ "forge __newarr(dims, d) { yield __na(dims, 0, d); }\n"
405+ "forge __lset(l, i, v) { local o = l[i]; l[i] = v; yield o; }\n"
406+ "forge __lremove(l, i) { local o = l[i]; delete(l, i); yield o; }\n"
407+ "forge __lcontains(l, x) { each v in l { given __eq(v, x) { yield yes; } } yield no; }\n"
408+ "forge __key(k) {\n"
409+ " local t = type(k);\n"
410+ " given t == \"string\" { yield k; }\n"
411+ " given t == \"number\" { yield __KP[0] + str(k); }\n"
412+ " given t == \"boolean\" { yield __KP[0] + __bs(k); }\n"
413+ " given t == \"none\" { yield __KP[0] + \"null\"; }\n"
414+ " given t == \"map\" and has(k, \"__L\") { yield __KP[0] + \"L\" + __lstr(k); }\n"
415+ " given t == \"map\" and has(k, \"__L\") { yield __KP[0] + \"L\" + __lstr(k); }\n"
416+ " given t == \"map\" and has(k, \"__vt\") {\n"
417+ " local vt = k[\"__vt\"];\n"
418+ " given has(vt, \"hashCode/0\") { local f = vt[\"hashCode/0\"]; yield __KP[0] + \"h\" + str(f(k)); }\n"
419+ " given not has(k, \"__id\") { __KP[1] = __KP[1] + 1; k[\"__id\"] = __KP[1]; }\n"
420+ " yield __KP[0] + \"i\" + str(k[\"__id\"]);\n"
421+ " }\n"
422+ " yield __KP[0] + \"?\" + str(k);\n"
423+ "}\n"
424+ "forge __mfind(m, k) {\n"
425+ " local sk = __key(k);\n"
426+ " given not has(m, sk) { yield none; }\n"
427+ " local e = m[sk];\n"
428+ " given type(k) != \"map\" { yield e; }\n"
429+ " during e != none {\n"
430+ " given __eq(e[0], k) { yield e; }\n"
431+ " e = e[2];\n"
432+ " }\n"
433+ " yield none;\n"
434+ "}\n"
435+ "forge __mcnt(m, d) {\n"
436+ " local c = __KP[0] + \"n\";\n"
437+ " local n = 0;\n"
438+ " given has(m, c) { n = m[c]; }\n"
439+ " m[c] = n + d;\n"
440+ "}\n"
441+ "forge __msize(m) {\n"
442+ " local c = __KP[0] + \"n\";\n"
443+ " given has(m, c) { yield m[c]; }\n"
444+ " yield 0;\n"
445+ "}\n"
446+ "forge __mget(m, k) {\n"
447+ " local e = __mfind(m, k);\n"
448+ " given e != none { yield e[1]; }\n"
449+ " yield none;\n"
450+ "}\n"
451+ "forge __mhas(m, k) { yield __mfind(m, k) != none; }\n"
452+ "forge __mput(m, k, v) {\n"
453+ " local e = __mfind(m, k);\n"
454+ " given e != none {\n"
455+ " local o = e[1];\n"
456+ " e[1] = v;\n"
457+ " yield o;\n"
458+ " }\n"
459+ " local sk = __key(k);\n"
460+ " local ne = [k, v, none];\n"
461+ " given has(m, sk) {\n"
462+ " local t = m[sk];\n"
463+ " during t[2] != none { t = t[2]; }\n"
464+ " t[2] = ne;\n"
465+ " } otherwise {\n"
466+ " m[sk] = ne;\n"
467+ " }\n"
468+ " __mcnt(m, 1);\n"
469+ " yield none;\n"
470+ "}\n"
471+ "forge __mremove(m, k) {\n"
472+ " local sk = __key(k);\n"
473+ " given not has(m, sk) { yield none; }\n"
474+ " local e = m[sk];\n"
475+ " local prev = none;\n"
476+ " during e != none {\n"
477+ " given type(k) != \"map\" or __eq(e[0], k) {\n"
478+ " local nx = e[2];\n"
479+ " given prev == none {\n"
480+ " given nx == none { delete(m, sk); } otherwise { m[sk] = nx; }\n"
481+ " } otherwise {\n"
482+ " prev[2] = nx;\n"
483+ " }\n"
484+ " __mcnt(m, 0 - 1);\n"
485+ " yield e[1];\n"
486+ " }\n"
487+ " prev = e;\n"
488+ " e = e[2];\n"
489+ " }\n"
490+ " yield none;\n"
491+ "}\n"
492+ "forge __mkeys(m) {\n"
493+ " local r = [];\n"
494+ " local c = __KP[0] + \"n\";\n"
495+ " each sk in keys(m) {\n"
496+ " given sk != c {\n"
497+ " local e = m[sk];\n"
498+ " during e != none { push(r, e[0]); e = e[2]; }\n"
499+ " }\n"
500+ " }\n"
501+ " yield r;\n"
502+ "}\n"
503+ "forge __mvals(m) {\n"
504+ " local r = [];\n"
505+ " local c = __KP[0] + \"n\";\n"
506+ " each sk in keys(m) {\n"
507+ " given sk != c {\n"
508+ " local e = m[sk];\n"
509+ " during e != none { push(r, e[1]); e = e[2]; }\n"
510+ " }\n"
511+ " }\n"
512+ " yield r;\n"
513+ "}\n"
514+ "forge __sadd(m, k) {\n"
515+ " given __mfind(m, k) != none { yield no; }\n"
516+ " __mput(m, k, yes);\n"
517+ " yield yes;\n"
518+ "}\n"
519+ "forge __sremove(m, k) {\n"
520+ " local had = __mfind(m, k) != none;\n"
521+ " given had { __mremove(m, k); }\n"
522+ " yield had;\n"
523+ "}\n"
524+ "forge __sbappend(sb, s) { push(sb[\"p\"], s); yield sb; }\n"
525+ "forge __sbstr(sb) { yield join(sb[\"p\"], \"\"); }\n"
526+ "forge __foreach(l, f) { each v in l { f(v); } }\n"
527+ "forge __iter(xs) {\n"
528+ " yield {\"i\": 0, \"xs\": xs, \"__c\": \"ArrayIterator\", \"__is\": {\"ash.util.Iterator\": yes}, \"__vt\": {\n"
529+ " \"hasNext/0\": forge(s) { yield s[\"i\"] < len(s[\"xs\"]); },\n"
530+ " \"next/0\": forge(s) { local v = s[\"xs\"][s[\"i\"]]; s[\"i\"] = s[\"i\"] + 1; yield v; }\n"
531+ " }};\n"
532+ "}\n"
533+ "forge __fn1(f, k) { given type(f) == \"function\" { yield f; } yield forge(a) { yield __vc1(f, k, a); }; }\n"
534+ "forge __fn2(f, k) { given type(f) == \"function\" { yield f; } yield forge(a, b) { yield __vc2(f, k, a, b); }; }\n"
535+ "forge __cmpfn(c) { yield __fn2(c, \"compare/2\"); }");
536+ /* arity-specific virtual and functional-interface dispatch */
537+ for (int n = 0; n <= 6; n++) {
538+ JBuf ps = {0}, as = {0}, ar = {0};
539+ for (int i = 0; i < n; i++) {
540+ j_buf_addf(&ps, ", a%d", i);
541+ j_buf_addf(&as, "%sa%d", i ? ", " : "", i);
542+ j_buf_addf(&ar, "%sa%d", i ? ", " : "", i);
543+ }
544+ const char *p = ps.data ? ps.data : "";
545+ const char *a = as.data ? as.data : "";
546+ j_buf_addf(b,
547+ "forge __vc%d(o, k%s) {\n"
548+ " given type(o) == \"map\" and has(o, \"__vt\") {\n"
549+ " local vt = o[\"__vt\"];\n"
550+ " local f = vt[k];\n"
551+ " yield f(o%s%s);\n"
552+ " }\n"
553+ " given o == none { __vcnull(k); }\n"
554+ " yield __natcall(o, k, [%s]);\n"
555+ "}\n"
556+ "forge __sam%d(f, k%s) {\n"
557+ " given type(f) == \"function\" { yield f(%s); }\n"
558+ " yield __vc%d(f, k%s);\n"
559+ "}\n",
560+ n, p, n ? ", " : "", a, ar.data ? ar.data : "", n, p, a, n, p);
561+ }
562+ raw(b,
563+ "forge __iterof(x) {\n"
564+ " given type(x) == \"array\" { yield __iter(x); }\n"
565+ " given type(x) == \"map\" and not has(x, \"__vt\") { yield __iter(__mkeys(x)); }\n"
566+ " yield __vc0(x, \"iterator/0\");\n"
567+ "}");
568+ emit_prelude_long(b);
569+}

hearth/front/src/jlower/C/lower_prelude_long.cadded

+309−0
@@ -0,0 +1,309 @@
1+#include "jlower/H/jlower_internal.h"
2+
3+/* Exact 64-bit `long` support (used with --wrap-int). A long is a plain number while it fits in
4+ * +-2^53 and a map {"__L": 1, "hi": signed 32 bits, "lo": unsigned 32 bits} beyond that, so the
5+ * common case keeps the VM's fast numeric path. Generated from the reviewed Ash source: every
6+ * operation normalises back to a number when the result fits. */
7+
8+static char *ex_ctor(const char *qname) {
9+ JMethod *k = find_ctor(jt_well_known(qname), 1);
10+ return k ? fn_name(k, "new") : (char *)"__missing_ctor";
11+}
12+
13+void emit_prelude_long(JBuf *b) {
14+ j_buf_addf(b, "forge __arith(m) { raise %s(m); }\n", ex_ctor("ash.lang.ArithmeticException"));
15+ j_buf_addf(b, "forge __parsex(m) { raise %s(m); }\n", ex_ctor("ash.lang.ParseException"));
16+ j_buf_add(b,
17+ "forge __L2(x) {\n"
18+ " given type(x) == \"map\" { yield [x[\"hi\"], x[\"lo\"]]; }\n"
19+ " local hi = floor(x / 4294967296);\n"
20+ " yield [hi, x - hi * 4294967296];\n"
21+ "}\n"
22+ "forge __LN(hi, lo) {\n"
23+ " local h = __wg(hi);\n"
24+ " given h >= 0 - 2097152 and h < 2097152 { yield h * 4294967296 + lo; }\n"
25+ " yield {\"__L\": 1, \"hi\": h, \"lo\": lo};\n"
26+ "}\n"
27+ "forge __lisL(x) { yield type(x) == \"map\"; }\n"
28+ "forge __lneg(a) {\n"
29+ " given type(a) == \"number\" { yield 0 - a; }\n"
30+ " local x = __L2(a);\n"
31+ " local lo = 4294967296 - x[1];\n"
32+ " local hi = 0 - x[0] - 1;\n"
33+ " given lo == 4294967296 { lo = 0; hi += 1; }\n"
34+ " yield __LN(hi, lo);\n"
35+ "}\n"
36+ "forge __ladd(a, b) {\n"
37+ " given type(a) == \"number\" and type(b) == \"number\" {\n"
38+ " local r = a + b;\n"
39+ " given r > 0 - 9007199254740992 and r < 9007199254740992 { yield r; }\n"
40+ " }\n"
41+ " local x = __L2(a);\n"
42+ " local y = __L2(b);\n"
43+ " local lo = x[1] + y[1];\n"
44+ " local c = 0;\n"
45+ " given lo >= 4294967296 { lo -= 4294967296; c = 1; }\n"
46+ " yield __LN(x[0] + y[0] + c, lo);\n"
47+ "}\n"
48+ "forge __lsub(a, b) {\n"
49+ " given type(a) == \"number\" and type(b) == \"number\" {\n"
50+ " local r = a - b;\n"
51+ " given r > 0 - 9007199254740992 and r < 9007199254740992 { yield r; }\n"
52+ " }\n"
53+ " yield __ladd(a, __lneg(b));\n"
54+ "}\n"
55+ "forge __lmul(a, b) {\n"
56+ " given type(a) == \"number\" and type(b) == \"number\" {\n"
57+ " local r = a * b;\n"
58+ " given r > 0 - 9007199254740992 and r < 9007199254740992 { yield r; }\n"
59+ " }\n"
60+ " local x = __L2(a);\n"
61+ " local y = __L2(b);\n"
62+ " local xh = x[0];\n"
63+ " given xh < 0 { xh += 4294967296; }\n"
64+ " local yh = y[0];\n"
65+ " given yh < 0 { yh += 4294967296; }\n"
66+ " local a0 = x[1] % 65536;\n"
67+ " local a1 = floor(x[1] / 65536);\n"
68+ " local a2 = xh % 65536;\n"
69+ " local a3 = floor(xh / 65536);\n"
70+ " local b0 = y[1] % 65536;\n"
71+ " local b1 = floor(y[1] / 65536);\n"
72+ " local b2 = yh % 65536;\n"
73+ " local b3 = floor(yh / 65536);\n"
74+ " local t0 = a0 * b0;\n"
75+ " local c0 = floor(t0 / 65536);\n"
76+ " local d0 = t0 - c0 * 65536;\n"
77+ " local t1 = a0 * b1 + a1 * b0 + c0;\n"
78+ " local c1 = floor(t1 / 65536);\n"
79+ " local d1 = t1 - c1 * 65536;\n"
80+ " local t2 = a0 * b2 + a1 * b1 + a2 * b0 + c1;\n"
81+ " local c2 = floor(t2 / 65536);\n"
82+ " local d2 = t2 - c2 * 65536;\n"
83+ " local t3 = a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0 + c2;\n"
84+ " local d3 = t3 % 65536;\n"
85+ " local hu = d2 + d3 * 65536;\n"
86+ " given hu >= 2147483648 { hu -= 4294967296; }\n"
87+ " yield __LN(hu, d0 + d1 * 65536);\n"
88+ "}\n"
89+ "forge __lcmp(a, b) {\n"
90+ " given type(a) == \"number\" and type(b) == \"number\" {\n"
91+ " given a < b { yield -1; }\n"
92+ " given a > b { yield 1; }\n"
93+ " yield 0;\n"
94+ " }\n"
95+ " local x = __L2(a);\n"
96+ " local y = __L2(b);\n"
97+ " given x[0] < y[0] { yield -1; }\n"
98+ " given x[0] > y[0] { yield 1; }\n"
99+ " given x[1] < y[1] { yield -1; }\n"
100+ " given x[1] > y[1] { yield 1; }\n"
101+ " yield 0;\n"
102+ "}\n"
103+ "forge __leq(a, b) {\n"
104+ " given type(a) == \"number\" and type(b) == \"number\" { yield a == b; }\n"
105+ " yield __lcmp(a, b) == 0;\n"
106+ "}\n"
107+ "forge __lmag(a) {\n"
108+ " local neg = __lcmp(a, 0) < 0;\n"
109+ " local m = a;\n"
110+ " given neg { m = __lneg(a); }\n"
111+ " local x = __L2(m);\n"
112+ " local hu = x[0];\n"
113+ " given hu < 0 { hu += 4294967296; }\n"
114+ " yield [hu, x[1], neg];\n"
115+ "}\n"
116+ "forge __lfrommag(hu, lo, neg) {\n"
117+ " local h = hu;\n"
118+ " given h >= 2147483648 { h -= 4294967296; }\n"
119+ " local v = __LN(h, lo);\n"
120+ " given neg { yield __lneg(v); }\n"
121+ " yield v;\n"
122+ "}\n"
123+ "forge __ldivu(nh, nl, dh, dl) {\n"
124+ " local qh = 0;\n"
125+ " local ql = 0;\n"
126+ " local rh = 0;\n"
127+ " local rl = 0;\n"
128+ " local i = 63;\n"
129+ " during i >= 0 {\n"
130+ " rh = rh * 2 + floor(rl / 2147483648);\n"
131+ " rl = (rl % 2147483648) * 2;\n"
132+ " local bit = 0;\n"
133+ " given i >= 32 { bit = floor(nh / __p2(i - 32)) % 2; } otherwise { bit = floor(nl / __p2(i)) % 2; }\n"
134+ " rl += bit;\n"
135+ " given rh > dh or (rh == dh and rl >= dl) {\n"
136+ " rl -= dl;\n"
137+ " given rl < 0 { rl += 4294967296; rh -= 1; }\n"
138+ " rh -= dh;\n"
139+ " given i >= 32 { qh += __p2(i - 32); } otherwise { ql += __p2(i); }\n"
140+ " }\n"
141+ " i -= 1;\n"
142+ " }\n"
143+ " yield [qh, ql, rh, rl];\n"
144+ "}\n"
145+ "forge __p2(n) { local r = 1; local i = 0; during i < n { r = r * 2; i += 1; } yield r; }\n"
146+ "forge __ldiv(a, b) {\n"
147+ " given __leq(b, 0) { __arith(\"/ by zero\"); }\n"
148+ " given type(a) == \"number\" and type(b) == \"number\" {\n"
149+ " local aa = a;\n"
150+ " local bb = b;\n"
151+ " given aa < 0 { aa = 0 - aa; }\n"
152+ " given bb < 0 { bb = 0 - bb; }\n"
153+ " local q = floor(aa / bb);\n"
154+ " local r = aa - q * bb;\n"
155+ " during r < 0 { q -= 1; r += bb; }\n"
156+ " during r >= bb { q += 1; r -= bb; }\n"
157+ " given q == 0 { yield 0; }\n"
158+ " given ((a < 0) != (b < 0)) { yield 0 - q; }\n"
159+ " yield q;\n"
160+ " }\n"
161+ " local x = __lmag(a);\n"
162+ " local y = __lmag(b);\n"
163+ " local qr = __ldivu(x[0], x[1], y[0], y[1]);\n"
164+ " yield __lfrommag(qr[0], qr[1], x[2] != y[2]);\n"
165+ "}\n"
166+ "forge __lrem(a, b) { yield __lsub(a, __lmul(__ldiv(a, b), b)); }\n"
167+ "forge __lshl(a, n) {\n"
168+ " local s = n % 64;\n"
169+ " given s == 0 { yield a; }\n"
170+ " given type(a) == \"number\" {\n"
171+ " local r = a * __p2(s);\n"
172+ " given r > 0 - 9007199254740992 and r < 9007199254740992 { yield r; }\n"
173+ " }\n"
174+ " local x = __L2(a);\n"
175+ " local hu = x[0];\n"
176+ " given hu < 0 { hu += 4294967296; }\n"
177+ " local lo = x[1];\n"
178+ " local nh = 0;\n"
179+ " local nl = 0;\n"
180+ " given s >= 32 {\n"
181+ " local k = s - 32;\n"
182+ " nh = (lo % __p2(32 - k)) * __p2(k);\n"
183+ " given k == 0 { nh = lo; }\n"
184+ " nl = 0;\n"
185+ " } otherwise {\n"
186+ " local keep = __p2(32 - s);\n"
187+ " nl = (lo % keep) * __p2(s);\n"
188+ " nh = (hu % keep) * __p2(s) + floor(lo / keep);\n"
189+ " }\n"
190+ " given nh >= 2147483648 { nh -= 4294967296; }\n"
191+ " yield __LN(nh, nl);\n"
192+ "}\n"
193+ "forge __lshr(a, n) {\n"
194+ " local s = n % 64;\n"
195+ " given s == 0 { yield a; }\n"
196+ " given type(a) == \"number\" { yield floor(a / __p2(s)); }\n"
197+ " local x = __L2(a);\n"
198+ " local hi = x[0];\n"
199+ " local lo = x[1];\n"
200+ " local hu = hi;\n"
201+ " given hu < 0 { hu += 4294967296; }\n"
202+ " local nh = 0;\n"
203+ " local nl = 0;\n"
204+ " given s >= 32 {\n"
205+ " nl = floor(hi / __p2(s - 32));\n"
206+ " given nl < 0 { nl += 4294967296; }\n"
207+ " nh = hi < 0 ? -1 : 0;\n"
208+ " } otherwise {\n"
209+ " nl = (hu % __p2(s)) * __p2(32 - s) + floor(lo / __p2(s));\n"
210+ " nh = floor(hi / __p2(s));\n"
211+ " }\n"
212+ " yield __LN(nh, nl);\n"
213+ "}\n"
214+ "forge __lushr(a, n) {\n"
215+ " local s = n % 64;\n"
216+ " given s == 0 { yield a; }\n"
217+ " given __lcmp(a, 0) >= 0 { yield __lshr(a, s); }\n"
218+ " local x = __L2(a);\n"
219+ " local hu = x[0] + 4294967296;\n"
220+ " local lo = x[1];\n"
221+ " local nh = 0;\n"
222+ " local nl = 0;\n"
223+ " given s >= 32 {\n"
224+ " nl = floor(hu / __p2(s - 32));\n"
225+ " nh = 0;\n"
226+ " } otherwise {\n"
227+ " nl = (hu % __p2(s)) * __p2(32 - s) + floor(lo / __p2(s));\n"
228+ " nh = floor(hu / __p2(s));\n"
229+ " }\n"
230+ " yield __LN(nh, nl);\n"
231+ "}\n"
232+ "forge __lbit(a, b, op) {\n"
233+ " local x = __L2(a);\n"
234+ " local y = __L2(b);\n"
235+ " local xh = x[0];\n"
236+ " local yh = y[0];\n"
237+ " given xh < 0 { xh += 4294967296; }\n"
238+ " given yh < 0 { yh += 4294967296; }\n"
239+ " local h = 0;\n"
240+ " local l = 0;\n"
241+ " given op == 0 { h = xh & yh; l = x[1] & y[1]; }\n"
242+ " given op == 1 { h = xh | yh; l = x[1] | y[1]; }\n"
243+ " given op == 2 { h = xh ^ yh; l = x[1] ^ y[1]; }\n"
244+ " given h >= 2147483648 { h -= 4294967296; }\n"
245+ " yield __LN(h, l);\n"
246+ "}\n"
247+ "forge __land(a, b) { yield __lbit(a, b, 0); }\n"
248+ "forge __lor(a, b) { yield __lbit(a, b, 1); }\n"
249+ "forge __lxor(a, b) { yield __lbit(a, b, 2); }\n"
250+ "forge __l2i(x) {\n"
251+ " given type(x) == \"number\" { yield __wg(x); }\n"
252+ " local lo = x[\"lo\"];\n"
253+ " given lo >= 2147483648 { yield lo - 4294967296; }\n"
254+ " yield lo;\n"
255+ "}\n"
256+ "forge __l2d(x) {\n"
257+ " given type(x) == \"number\" { yield x; }\n"
258+ " yield x[\"hi\"] * 4294967296 + x[\"lo\"];\n"
259+ "}\n"
260+ "forge __d2l(x) {\n"
261+ " given x != x { yield 0; }\n"
262+ " given x >= 9223372036854775807 { yield {\"__L\": 1, \"hi\": 2147483647, \"lo\": 4294967295}; }\n"
263+ " given x <= 0 - 9223372036854775807 { yield {\"__L\": 1, \"hi\": -2147483648, \"lo\": 0}; }\n"
264+ " local t = __trunc(x);\n"
265+ " given t > 0 - 9007199254740992 and t < 9007199254740992 { yield t; }\n"
266+ " local hi = floor(t / 4294967296);\n"
267+ " yield __LN(hi, t - hi * 4294967296);\n"
268+ "}\n"
269+ "forge __lstr(x) {\n"
270+ " given type(x) == \"number\" and x > 0 - 1000000000000000 and x < 1000000000000000 { yield str(x); }\n"
271+ " local m = __lmag(x);\n"
272+ " local limbs = [floor(m[0] / 65536), m[0] % 65536, floor(m[1] / 65536), m[1] % 65536];\n"
273+ " local out = \"\";\n"
274+ " local more = yes;\n"
275+ " during more {\n"
276+ " local rem = 0;\n"
277+ " local i = 0;\n"
278+ " during i < 4 {\n"
279+ " local cur = rem * 65536 + limbs[i];\n"
280+ " limbs[i] = floor(cur / 1000000000);\n"
281+ " rem = cur - limbs[i] * 1000000000;\n"
282+ " i += 1;\n"
283+ " }\n"
284+ " more = limbs[0] > 0 or limbs[1] > 0 or limbs[2] > 0 or limbs[3] > 0;\n"
285+ " local part = str(rem);\n"
286+ " given more { during len(part) < 9 { part = \"0\" + part; } }\n"
287+ " out = part + out;\n"
288+ " }\n"
289+ " given m[2] { yield \"-\" + out; }\n"
290+ " yield out;\n"
291+ "}\n"
292+ "forge __lparse(s) {\n"
293+ " local t = trim(s);\n"
294+ " local neg = no;\n"
295+ " local i = 0;\n"
296+ " given len(t) > 0 and t[0] == \"-\" { neg = yes; i = 1; }\n"
297+ " given i >= len(t) { __parsex(\"not a long: \" + s); }\n"
298+ " local acc = 0;\n"
299+ " during i < len(t) {\n"
300+ " local c = ord(t[i]);\n"
301+ " given c < 48 or c > 57 { __parsex(\"not a long: \" + s); }\n"
302+ " acc = __ladd(__lmul(acc, 10), c - 48);\n"
303+ " i += 1;\n"
304+ " }\n"
305+ " given neg { yield __lneg(acc); }\n"
306+ " yield acc;\n"
307+ "}\n"
308+ );
309+}

hearth/front/src/jlower/C/lower_prune.cadded

+138−0
@@ -0,0 +1,138 @@
1+#include <string.h>
2+
3+#include "jlower/H/jlower_internal.h"
4+
5+/* Dead-code elimination over the generated module. The old VM addresses functions with one byte
6+ * (256 per program) and allows 256 module variables, and the lowering emits whole classes plus a
7+ * runtime prelude, so everything unreachable from `start` is dropped:
8+ * - a function is kept when its name is called/mentioned in kept text,
9+ * - a vtable entry `C__VT["key"] = fn;` is kept when the string "key" appears in kept text
10+ * (every dynamic call site and runtime helper spells its key as a literal),
11+ * - a module variable is kept when it is mentioned in kept text. */
12+
13+typedef struct {
14+ char *text;
15+ char *name; /* forge or local name; NULL for comments */
16+ int is_fn;
17+ int keep;
18+} Part;
19+
20+typedef struct {
21+ char *line; /* the whole VT assignment line */
22+ char *key; /* "toString/0" without quotes */
23+ char *fn;
24+ int keep;
25+} VtLine;
26+
27+static int ident_char(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; }
28+
29+/* does `text` mention identifier `name` as a whole word (call, reference or variable use)? */
30+static int mentions_word(const char *text, const char *name) {
31+ size_t n = strlen(name);
32+ for (const char *p = text; (p = strstr(p, name)); p += n) {
33+ if ((p == text || !ident_char(p[-1])) && !ident_char(p[n])) return 1;
34+ }
35+ return 0;
36+}
37+
38+static int mentions_key(const char *text, const char *key) {
39+ char *q = j_fmt("\"%s\"", key);
40+ return strstr(text, q) != NULL;
41+}
42+
43+static char *take_name(const char *s, const char *prefix) {
44+ s += strlen(prefix);
45+ const char *e = s;
46+ while (ident_char(*e)) e++;
47+ return j_strndup(s, (size_t)(e - s));
48+}
49+
50+char *prune_module(const char *module) {
51+ JList parts = {0}, vts = {0};
52+ JBuf start_pre = {0}, start_post = {0};
53+ int start_idx = -1;
54+ const char *p = module;
55+ while (*p) {
56+ const char *end = p;
57+ do {
58+ const char *nl = strchr(end, '\n');
59+ end = nl ? nl + 1 : end + strlen(end);
60+ } while (*end && strncmp(end, "forge ", 6) != 0 && strncmp(end, "local ", 6) != 0 && strncmp(end, "// ----", 7) != 0);
61+ Part *c = j_alloc(sizeof(Part));
62+ c->text = j_strndup(p, (size_t)(end - p));
63+ if (!strncmp(p, "forge ", 6)) { c->name = take_name(p, "forge "); c->is_fn = 1; }
64+ else if (!strncmp(p, "local ", 6)) c->name = take_name(p, "local ");
65+ if (c->is_fn && !strcmp(c->name, "start")) start_idx = parts.count;
66+ j_list_push(&parts, c);
67+ p = end;
68+ }
69+ if (start_idx < 0) return (char *)module;
70+ /* split start() into VT assignment lines (conditional) and everything else (always kept) */
71+ Part *st = parts.items[start_idx];
72+ JBuf fixed = {0};
73+ for (char *line = st->text; *line;) {
74+ char *nl = strchr(line, '\n');
75+ size_t len = nl ? (size_t)(nl - line) : strlen(line);
76+ char *l = j_strndup(line, len);
77+ char *vt = strstr(l, "__VT[\"");
78+ if (vt && strstr(l, "] = ") && l[len - 1] == ';') {
79+ VtLine *v = j_alloc(sizeof(VtLine));
80+ v->line = l;
81+ char *ks = vt + 6;
82+ char *ke = strstr(ks, "\"] = ");
83+ v->key = j_strndup(ks, (size_t)(ke - ks));
84+ char *fs = ke + 5;
85+ v->fn = j_strndup(fs, strlen(fs) - 1);
86+ j_list_push(&vts, v);
87+ } else {
88+ j_buf_add(&fixed, l);
89+ j_buf_addc(&fixed, '\n');
90+ }
91+ if (!nl) break;
92+ line = nl + 1;
93+ }
94+ st->text = fixed.data ? fixed.data : (char *)"";
95+ st->keep = 1;
96+ for (int changed = 1; changed;) {
97+ changed = 0;
98+ JBuf kept = {0};
99+ for (int i = 0; i < parts.count; i++) {
100+ Part *c = parts.items[i];
101+ if (c->keep || !c->name) j_buf_add(&kept, c->text);
102+ }
103+ for (int i = 0; i < vts.count; i++) {
104+ VtLine *v = vts.items[i];
105+ if (v->keep) { j_buf_add(&kept, v->line); j_buf_addc(&kept, 10); }
106+ }
107+ const char *k = kept.data ? kept.data : "";
108+ for (int i = 0; i < parts.count; i++) {
109+ Part *c = parts.items[i];
110+ if (c->keep || !c->name) continue;
111+ if (mentions_word(k, c->name)) { c->keep = 1; changed = 1; }
112+ }
113+ for (int i = 0; i < vts.count; i++) {
114+ VtLine *v = vts.items[i];
115+ if (!v->keep && mentions_key(k, v->key)) { v->keep = 1; changed = 1; }
116+ }
117+ }
118+ JBuf out = {0};
119+ for (int i = 0; i < parts.count; i++) {
120+ Part *c = parts.items[i];
121+ if (i == start_idx) {
122+ /* re-insert the kept VT lines right after `forge start() {` */
123+ char *nl = strchr(c->text, '\n');
124+ size_t head = nl ? (size_t)(nl - c->text) + 1 : strlen(c->text);
125+ j_buf_addn(&out, c->text, head);
126+ for (int k = 0; k < vts.count; k++) {
127+ VtLine *v = vts.items[k];
128+ if (v->keep) { j_buf_add(&out, v->line); j_buf_addc(&out, '\n'); }
129+ }
130+ j_buf_add(&out, c->text + head);
131+ } else if (!c->name || c->keep) {
132+ j_buf_add(&out, c->text);
133+ }
134+ }
135+ (void)start_pre;
136+ (void)start_post;
137+ return out.data ? out.data : (char *)"";
138+}

hearth/front/src/jlower/C/lower_stmt.cadded

+598−0
Show diff (598 changed lines)
@@ -0,0 +1,598 @@
1+#include <stdarg.h>
2+#include <stdio.h>
3+#include <string.h>
4+
5+#include "jlower/H/jlower_internal.h"
6+
7+/* Statement lowering: control flow, switch (as if-chains), try/catch/finally via
8+ * attempt/handle, lambdas, and the immediately-invoked closure used by switch expressions. */
9+
10+void emit_line(JBuf *b, int ind, const char *fmt, ...) {
11+ for (int i = 0; i < ind; i++) j_buf_add(b, " ");
12+ va_list ap;
13+ va_start(ap, fmt);
14+ char *s;
15+ int n = vsnprintf(NULL, 0, fmt, ap);
16+ va_end(ap);
17+ s = j_alloc((size_t)n + 1);
18+ va_start(ap, fmt);
19+ vsnprintf(s, (size_t)n + 1, fmt, ap);
20+ va_end(ap);
21+ j_buf_add(b, s);
22+ j_buf_addc(b, '\n');
23+}
24+
25+void flush_pre(JBuf *b, int ind) {
26+ if (!L.pre.data || !L.pre.len) return;
27+ char *p = L.pre.data;
28+ while (*p) {
29+ char *nl = strchr(p, '\n');
30+ size_t len = nl ? (size_t)(nl - p) : strlen(p);
31+ for (int i = 0; i < ind; i++) j_buf_add(b, " ");
32+ j_buf_addn(b, p, len);
33+ j_buf_addc(b, '\n');
34+ if (!nl) break;
35+ p = nl + 1;
36+ }
37+ L.pre.len = 0;
38+ if (L.pre.data) L.pre.data[0] = 0;
39+}
40+
41+void lblock_body(JBuf *b, Node *block, int ind) {
42+ if (block->kind != N_BLOCK) { ls(b, block, ind); return; }
43+ for (int i = 0; i < block->l1.count; i++) ls(b, block->l1.items[i], ind);
44+}
45+
46+static void lbody(JBuf *b, Node *s, int ind) { lblock_body(b, s, ind); }
47+
48+/* A loop condition is re-evaluated every iteration. When lowering it produced hoisted statements
49+ * (assignment/++ inside the condition, instanceof bindings), they are returned in *pre and the
50+ * loop is restructured: `during yes { pre; given not (cond) { stop; } body }`. */
51+static char *loop_cond(Node *c, char **pre) {
52+ size_t before = L.pre.len;
53+ char *t = lx(c);
54+ *pre = NULL;
55+ if (L.pre.len != before) {
56+ *pre = j_strdup(L.pre.data + before);
57+ L.pre.len = before;
58+ if (L.pre.data) L.pre.data[before] = 0;
59+ }
60+ return t;
61+}
62+
63+static void each_line(JBuf *b, int ind, const char *text) {
64+ if (!text) return;
65+ const char *p = text;
66+ while (*p) {
67+ const char *nl = strchr(p, '\n');
68+ size_t len = nl ? (size_t)(nl - p) : strlen(p);
69+ for (int i = 0; i < ind; i++) j_buf_add(b, " ");
70+ j_buf_addn(b, p, len);
71+ j_buf_addc(b, '\n');
72+ if (!nl) break;
73+ p = nl + 1;
74+ }
75+}
76+
77+static LoopCtx *real_loop(void) {
78+ LoopCtx *l = L.loop;
79+ while (l && l->is_switch) l = l->up;
80+ return l;
81+}
82+
83+/* ---------------------------------------------------------------- switch */
84+
85+static char *label_cond(Node *lb, const char *sel, JType *seltype) {
86+ (void)seltype;
87+ if (lb->kind == N_TYPEPATTERN) {
88+ if (lb->sym) alias_set(lb->sym, sel);
89+ JType *t = lb->type;
90+ if (t->kind == TY_CLASS) return j_fmt("__inst(%s, %s)", sel, quote_str(t->cls->qname));
91+ return "yes";
92+ }
93+ if (lb->kind == N_LIT_NULL) return j_fmt("(%s == none)", sel);
94+ if (lb->rk == R_ENUM_CONST && lb->sym) {
95+ JField *f = lb->sym;
96+ return j_fmt("(%s == %s[%s])", sel, cls_state(f->cls), quote_str(f->name));
97+ }
98+ return j_fmt("(%s == %s)", sel, lx(lb));
99+}
100+
101+static char *case_cond(Node *c, const char *sel, JType *seltype) {
102+ if (c->l1.count == 0) return NULL; /* default */
103+ JBuf b = {0};
104+ for (int i = 0; i < c->l1.count; i++) {
105+ if (i) j_buf_add(&b, " or ");
106+ j_buf_add(&b, label_cond(c->l1.items[i], sel, seltype));
107+ }
108+ /* old Ash reads `given (a) or (b) {` as `given (a)` + junk: keep the whole test in one group */
109+ return j_fmt("(%s)", b.data);
110+}
111+
112+/* Emits the if-chain; `expr_mode` makes expression arms `yield`. Returns nesting depth to close. */
113+static void switch_chain(JBuf *b, Node *n, const char *sel, int ind, int expr_mode) {
114+ JList order = {0};
115+ Node *dflt = NULL;
116+ for (int i = 0; i < n->l1.count; i++) {
117+ Node *c = n->l1.items[i];
118+ if ((c->flags & 1) && c->l1.count == 0) dflt = c;
119+ else j_list_push(&order, c);
120+ }
121+ int depth = 0;
122+ for (int i = 0; i < order.count; i++) {
123+ Node *c = order.items[i];
124+ char *cond = case_cond(c, sel, n->a->type);
125+ if ((c->flags & 1) && !dflt) dflt = c;
126+ flush_pre(b, ind + depth);
127+ emit_line(b, ind + depth, "given %s {", cond);
128+ if (c->str) {
129+ Node *body = c->b;
130+ if (expr_mode && body->kind == N_EXPRSTMT) {
131+ char *v = lx(body->a);
132+ flush_pre(b, ind + depth + 1);
133+ emit_line(b, ind + depth + 1, "yield %s;", v);
134+ } else {
135+ ls(b, body, ind + depth + 1);
136+ }
137+ } else {
138+ int cnt = c->l2.count;
139+ if (cnt && ((Node *)c->l2.items[cnt - 1])->kind == N_BREAK) cnt--;
140+ for (int k = 0; k < cnt; k++) ls(b, c->l2.items[k], ind + depth + 1);
141+ if (cnt == c->l2.count && cnt && i != order.count - 1 && !dflt) {
142+ Node *last = c->l2.items[cnt - 1];
143+ if (last->kind != N_RETURN && last->kind != N_THROW && last->kind != N_CONTINUE)
144+ lerr(c, "switch fallthrough is not supported by the backend; end each case with break, return or throw");
145+ }
146+ }
147+ emit_line(b, ind + depth, "} otherwise {");
148+ depth++;
149+ }
150+ if (dflt) {
151+ if (dflt->str) {
152+ Node *body = dflt->b;
153+ if (expr_mode && body->kind == N_EXPRSTMT) {
154+ char *v = lx(body->a);
155+ flush_pre(b, ind + depth);
156+ emit_line(b, ind + depth, "yield %s;", v);
157+ } else {
158+ ls(b, body, ind + depth);
159+ }
160+ } else {
161+ int cnt = dflt->l2.count;
162+ if (cnt && ((Node *)dflt->l2.items[cnt - 1])->kind == N_BREAK) cnt--;
163+ for (int k = 0; k < cnt; k++) ls(b, dflt->l2.items[k], ind + depth);
164+ }
165+ } else if (expr_mode) {
166+ emit_line(b, ind + depth, "raise \"no switch case matched\";");
167+ }
168+ for (int d = depth - 1; d >= 0; d--) emit_line(b, ind + d, "}");
169+}
170+
171+/* any `break` that targets this switch, looking through nested statements but not nested loops/switches */
172+static int contains_break(Node *n) {
173+ if (!n) return 0;
174+ switch (n->kind) {
175+ case N_BREAK: return 1;
176+ case N_BLOCK: case N_SEQ:
177+ for (int i = 0; i < n->l1.count; i++) if (contains_break(n->l1.items[i])) return 1;
178+ return 0;
179+ case N_IF: return contains_break(n->b) || contains_break(n->c);
180+ case N_TRY:
181+ if (contains_break(n->b) || contains_break(n->c)) return 1;
182+ for (int i = 0; i < n->l1.count; i++) if (contains_break(((Node *)n->l1.items[i])->b)) return 1;
183+ return 0;
184+ default: return 0;
185+ }
186+}
187+
188+static int ends_the_case(Node *s) {
189+ return s->kind == N_BREAK || s->kind == N_RETURN || s->kind == N_THROW || s->kind == N_CONTINUE;
190+}
191+
192+/* A colon-form switch needs the general lowering when a case falls through or breaks early. */
193+static int needs_wrapper(Node *n) {
194+ for (int i = 0; i < n->l1.count; i++) {
195+ Node *c = n->l1.items[i];
196+ if (c->str) return 0; /* arrow form never falls through */
197+ int cnt = c->l2.count;
198+ if (cnt && i != n->l1.count - 1 && !ends_the_case(c->l2.items[cnt - 1])) return 1;
199+ for (int k = 0; k < cnt - 1; k++) if (contains_break(c->l2.items[k])) return 1;
200+ }
201+ return 0;
202+}
203+
204+/* General switch: pick the matching case index, then run bodies in source order from there until a
205+ * break (`stop` of a one-shot loop). Handles fallthrough, default in the middle and early breaks. */
206+static void lower_switch_general(JBuf *b, Node *n, char *sel, int ind) {
207+ char *m = new_temp();
208+ char *go = new_temp();
209+ int dflt = -1;
210+ emit_line(b, ind, "local %s = -1;", m);
211+ int depth = 0;
212+ for (int i = 0; i < n->l1.count; i++) {
213+ Node *c = n->l1.items[i];
214+ if (c->flags & 1) dflt = i;
215+ char *cond = case_cond(c, sel, n->a->type);
216+ if (!cond) continue;
217+ flush_pre(b, ind + depth);
218+ emit_line(b, ind + depth, "given %s {", cond);
219+ emit_line(b, ind + depth + 1, "%s = %d;", m, i);
220+ emit_line(b, ind + depth, "} otherwise {");
221+ depth++;
222+ }
223+ if (dflt >= 0) emit_line(b, ind + depth, "%s = %d;", m, dflt);
224+ for (int d = depth - 1; d >= 0; d--) emit_line(b, ind + d, "}");
225+ emit_line(b, ind, "local %s = no;", go);
226+ LoopCtx ctx = {0};
227+ ctx.is_switch = 1;
228+ ctx.wrapper = 1;
229+ ctx.cont_flag = new_temp();
230+ ctx.up = L.loop;
231+ emit_line(b, ind, "local %s = no;", ctx.cont_flag);
232+ emit_line(b, ind, "during yes {");
233+ L.loop = &ctx;
234+ for (int i = 0; i < n->l1.count; i++) {
235+ Node *c = n->l1.items[i];
236+ emit_line(b, ind + 1, "given %s or %s == %d {", go, m, i);
237+ emit_line(b, ind + 2, "%s = yes;", go);
238+ for (int k = 0; k < c->l2.count; k++) ls(b, c->l2.items[k], ind + 2);
239+ emit_line(b, ind + 1, "}");
240+ }
241+ emit_line(b, ind + 1, "stop;");
242+ L.loop = ctx.up;
243+ emit_line(b, ind, "}");
244+ if (ctx.cont_used) {
245+ emit_line(b, ind, "given %s {", ctx.cont_flag);
246+ Node cont = {0};
247+ cont.kind = N_CONTINUE;
248+ ls(b, &cont, ind + 1);
249+ emit_line(b, ind, "}");
250+ }
251+}
252+
253+static void lower_switch_stmt(JBuf *b, Node *n, int ind) {
254+ char *sel = new_temp();
255+ char *v = lx(n->a);
256+ flush_pre(b, ind);
257+ emit_line(b, ind, "local %s = %s;", sel, v);
258+ if (needs_wrapper(n)) {
259+ lower_switch_general(b, n, sel, ind);
260+ return;
261+ }
262+ LoopCtx ctx = {0};
263+ ctx.is_switch = 1;
264+ ctx.up = L.loop;
265+ L.loop = &ctx;
266+ switch_chain(b, n, sel, ind, 0);
267+ L.loop = ctx.up;
268+}
269+
270+char *lower_switch_expr(Node *n) {
271+ JBuf saved_pre = L.pre;
272+ memset(&L.pre, 0, sizeof L.pre);
273+ LoopCtx *saved_loop = L.loop;
274+ L.loop = NULL;
275+ JBuf body = {0};
276+ char *sel = new_temp();
277+ char *v = lx(n->a);
278+ flush_pre(&body, 1);
279+ emit_line(&body, 1, "local %s = %s;", sel, v);
280+ switch_chain(&body, n, sel, 1, 1);
281+ L.loop = saved_loop;
282+ L.pre = saved_pre;
283+ return j_fmt("__iife(forge() {\n%s})", body.data ? body.data : "");
284+}
285+
286+/* ---------------------------------------------------------------- lambdas */
287+
288+char *lower_lambda_body(Node *lam, JMethod *sam, int ind) {
289+ JBuf saved_pre = L.pre;
290+ memset(&L.pre, 0, sizeof L.pre);
291+ LoopCtx *saved_loop = L.loop;
292+ int saved_tf = L.fin_n;
293+ L.loop = NULL;
294+ L.fin_n = 0;
295+ JBuf b = {0};
296+ j_buf_addc(&b, '\n');
297+ if (lam->b->kind == N_BLOCK) {
298+ lblock_body(&b, lam->b, ind + 1);
299+ } else {
300+ Node *e = lam->b;
301+ int void_ret = sam && sam->ret->kind == TY_VOID;
302+ if (e->kind == N_ASSIGN || e->kind == N_POSTFIX || (e->kind == N_UNARY && (e->name[1] == '+' || e->name[1] == '-'))) {
303+ emit_assign_stmt(&b, e, ind + 1);
304+ } else {
305+ char *v = lx(e);
306+ flush_pre(&b, ind + 1);
307+ if (void_ret) emit_line(&b, ind + 1, "%s;", v);
308+ else emit_line(&b, ind + 1, "yield %s;", v);
309+ }
310+ }
311+ L.loop = saved_loop;
312+ L.fin_n = saved_tf;
313+ L.pre = saved_pre;
314+ return b.data;
315+}
316+
317+/* ---------------------------------------------------------------- try / catch */
318+
319+static void lower_try(JBuf *b, Node *n, int ind) {
320+ char *fin = NULL;
321+ int base = ind;
322+ if (n->c) {
323+ fin = new_temp();
324+ emit_line(b, ind, "local %s = none;", fin);
325+ emit_line(b, ind, "attempt {");
326+ base = ind + 1;
327+ }
328+ if (n->c) {
329+ if (L.fin_n >= 16) lerr(n, "try/finally nested too deeply");
330+ else L.fin[L.fin_n++] = n->c;
331+ }
332+ if (n->l1.count == 0) {
333+ lbody(b, n->b, base);
334+ } else {
335+ char *ev = new_temp();
336+ emit_line(b, base, "attempt {");
337+ lbody(b, n->b, base + 1);
338+ emit_line(b, base, "} handle (%s) {", ev);
339+ emit_line(b, base + 1, "%s = __wrap(%s);", ev, ev);
340+ int depth = 0;
341+ for (int i = 0; i < n->l1.count; i++) {
342+ Node *c = n->l1.items[i];
343+ JBuf cond = {0};
344+ for (int j = 0; j < c->l1.count; j++) {
345+ Node *tn = c->l1.items[j];
346+ if (j) j_buf_add(&cond, " or ");
347+ if (tn->type && tn->type->kind == TY_CLASS) j_buf_addf(&cond, "__inst(%s, %s)", ev, quote_str(tn->type->cls->qname));
348+ else j_buf_add(&cond, "no");
349+ }
350+ emit_line(b, base + 1 + depth, "given %s {", cond.data ? cond.data : "no");
351+ emit_line(b, base + 2 + depth, "local %s = %s;", sname(c->name), ev);
352+ lbody(b, c->b, base + 2 + depth);
353+ emit_line(b, base + 1 + depth, "} otherwise {");
354+ depth++;
355+ }
356+ emit_line(b, base + 1 + depth, "raise %s;", ev);
357+ for (int d = depth - 1; d >= 0; d--) emit_line(b, base + 1 + d, "}");
358+ emit_line(b, base, "}");
359+ }
360+ if (n->c && L.fin_n > 0) L.fin_n--;
361+ if (n->c) {
362+ char *xv = new_temp();
363+ emit_line(b, ind, "} handle (%s) {", xv);
364+ emit_line(b, ind + 1, "%s = %s;", fin, xv);
365+ emit_line(b, ind, "}");
366+ lbody(b, n->c, ind);
367+ emit_line(b, ind, "given %s != none {", fin);
368+ emit_line(b, ind + 1, "raise %s;", fin);
369+ emit_line(b, ind, "}");
370+ }
371+}
372+
373+/* ---------------------------------------------------------------- statements */
374+
375+static int native_iterable(JType *t) {
376+ return t->kind == TY_ARRAY || is_array_list(t);
377+}
378+
379+static void lower_foreach(JBuf *b, Node *n, int ind) {
380+ char *it = lx(n->b);
381+ char *name = sname(n->name);
382+ LoopCtx ctx = {0};
383+ ctx.up = L.loop;
384+ flush_pre(b, ind);
385+ if (native_iterable(n->b->type)) {
386+ emit_line(b, ind, "each %s in %s {", name, it);
387+ } else {
388+ char *iv = new_temp();
389+ emit_line(b, ind, "local %s = __iterof(%s);", iv, it);
390+ emit_line(b, ind, "during __vc0(%s, \"hasNext/0\") {", iv);
391+ emit_line(b, ind + 1, "local %s = __vc0(%s, \"next/0\");", name, iv);
392+ }
393+ L.loop = &ctx;
394+ lbody(b, n->c, ind + 1);
395+ L.loop = ctx.up;
396+ emit_line(b, ind, "}");
397+}
398+
399+static char *update_text(JList *ups) {
400+ JBuf b = {0};
401+ for (int i = 0; i < ups->count; i++) {
402+ Node *u = ups->items[i];
403+ if (u->kind == N_ASSIGN || u->kind == N_POSTFIX || (u->kind == N_UNARY && (u->name[0] == '+' || u->name[0] == '-') && u->name[1])) {
404+ emit_assign_stmt(&b, u, 0);
405+ } else {
406+ char *v = lx(u);
407+ flush_pre(&b, 0);
408+ emit_line(&b, 0, "%s;", v);
409+ }
410+ }
411+ return b.data ? b.data : (char *)"";
412+}
413+
414+/* `for (...; i < n; i++)` cannot overflow i: the comparison bounds it, so the update skips the 32-bit wrap */
415+static void mark_bounded_counter(Node *n) {
416+ if (!n->a || n->a->kind != N_BINARY || n->a->a->kind != N_NAME || n->a->a->rk != R_LOCAL) return;
417+ const char *op = n->a->name;
418+ int up = !strcmp(op, "<") || !strcmp(op, "<=");
419+ int down = !strcmp(op, ">") || !strcmp(op, ">=");
420+ if (!up && !down) return;
421+ for (int i = 0; i < n->l2.count; i++) {
422+ Node *u = n->l2.items[i];
423+ if ((u->kind == N_POSTFIX || (u->kind == N_UNARY && u->name[1])) && u->a->kind == N_NAME && u->a->sym == n->a->a->sym) {
424+ int inc = u->name[0] == '+';
425+ if ((up && inc) || (down && !inc)) u->flags |= 0x200;
426+ }
427+ }
428+}
429+
430+static void lower_for(JBuf *b, Node *n, int ind) {
431+ mark_bounded_counter(n);
432+ emit_line(b, ind, "given yes {");
433+ for (int i = 0; i < n->l1.count; i++) ls(b, n->l1.items[i], ind + 1);
434+ char *pre = NULL;
435+ char *cond = n->a ? loop_cond(n->a, &pre) : "yes";
436+ flush_pre(b, ind + 1);
437+ LoopCtx ctx = {0};
438+ ctx.up = L.loop;
439+ ctx.update = update_text(&n->l2);
440+ if (pre) {
441+ emit_line(b, ind + 1, "during yes {");
442+ each_line(b, ind + 2, pre);
443+ emit_line(b, ind + 2, "given not %s {", cond);
444+ emit_line(b, ind + 3, "stop;");
445+ emit_line(b, ind + 2, "}");
446+ } else {
447+ emit_line(b, ind + 1, "during %s {", cond);
448+ }
449+ L.loop = &ctx;
450+ lbody(b, n->b, ind + 2);
451+ L.loop = ctx.up;
452+ each_line(b, ind + 2, ctx.update);
453+ emit_line(b, ind + 1, "}");
454+ emit_line(b, ind, "}");
455+}
456+
457+static void lower_local(JBuf *b, Node *n, int ind) {
458+ char *init = n->b ? lx(n->b) : "none";
459+ flush_pre(b, ind);
460+ emit_line(b, ind, "local %s = %s;", sname(n->name), init);
461+}
462+
463+void ls(JBuf *b, Node *s, int ind) {
464+ switch (s->kind) {
465+ case N_BLOCK:
466+ emit_line(b, ind, "given yes {");
467+ lblock_body(b, s, ind + 1);
468+ emit_line(b, ind, "}");
469+ break;
470+ case N_SEQ: for (int i = 0; i < s->l1.count; i++) ls(b, s->l1.items[i], ind); break;
471+ case N_EMPTY: break;
472+ case N_LOCAL: lower_local(b, s, ind); break;
473+ case N_EXPRSTMT: {
474+ Node *e = s->a;
475+ if (e->kind == N_ASSIGN || e->kind == N_POSTFIX || (e->kind == N_UNARY && (e->name[0] == '+' || e->name[0] == '-') && e->name[1])) {
476+ emit_assign_stmt(b, e, ind);
477+ } else {
478+ char *v = lx(e);
479+ flush_pre(b, ind);
480+ emit_line(b, ind, "%s;", v);
481+ }
482+ break;
483+ }
484+ case N_IF: {
485+ char *c = lx(s->a);
486+ flush_pre(b, ind);
487+ emit_line(b, ind, "given %s {", c);
488+ lbody(b, s->b, ind + 1);
489+ if (s->c) {
490+ emit_line(b, ind, "} otherwise {");
491+ lbody(b, s->c, ind + 1);
492+ }
493+ emit_line(b, ind, "}");
494+ break;
495+ }
496+ case N_WHILE: {
497+ char *pre;
498+ char *c = loop_cond(s->a, &pre);
499+ LoopCtx ctx = {0};
500+ ctx.up = L.loop;
501+ if (pre) {
502+ emit_line(b, ind, "during yes {");
503+ each_line(b, ind + 1, pre);
504+ emit_line(b, ind + 1, "given not %s {", c);
505+ emit_line(b, ind + 2, "stop;");
506+ emit_line(b, ind + 1, "}");
507+ } else {
508+ emit_line(b, ind, "during %s {", c);
509+ }
510+ L.loop = &ctx;
511+ lbody(b, s->b, ind + 1);
512+ L.loop = ctx.up;
513+ emit_line(b, ind, "}");
514+ break;
515+ }
516+ case N_DO: {
517+ char *first = new_temp();
518+ char *pre;
519+ char *c = loop_cond(s->a, &pre);
520+ LoopCtx ctx = {0};
521+ ctx.up = L.loop;
522+ emit_line(b, ind, "local %s = yes;", first);
523+ if (pre) {
524+ emit_line(b, ind, "during yes {");
525+ emit_line(b, ind + 1, "given not %s {", first);
526+ each_line(b, ind + 2, pre);
527+ emit_line(b, ind + 2, "given not %s {", c);
528+ emit_line(b, ind + 3, "stop;");
529+ emit_line(b, ind + 2, "}");
530+ emit_line(b, ind + 1, "}");
531+ } else {
532+ emit_line(b, ind, "during %s or %s {", first, c);
533+ }
534+ emit_line(b, ind + 1, "%s = no;", first);
535+ L.loop = &ctx;
536+ lbody(b, s->b, ind + 1);
537+ L.loop = ctx.up;
538+ emit_line(b, ind, "}");
539+ break;
540+ }
541+ case N_FOR: lower_for(b, s, ind); break;
542+ case N_FOREACH: lower_foreach(b, s, ind); break;
543+ case N_RETURN: {
544+ char *v = s->a ? lx(s->a) : "none";
545+ flush_pre(b, ind);
546+ if (L.fin_n) {
547+ char *rv = new_temp();
548+ emit_line(b, ind, "local %s = %s;", rv, v);
549+ int saved = L.fin_n;
550+ for (int i = saved - 1; i >= 0; i--) {
551+ L.fin_n = i;
552+ lbody(b, L.fin[i], ind);
553+ }
554+ L.fin_n = saved;
555+ v = rv;
556+ }
557+ emit_line(b, ind, "yield %s;", v);
558+ break;
559+ }
560+ case N_BREAK: {
561+ if (L.loop && L.loop->is_switch && !L.loop->wrapper) {
562+ lerr(s, "break inside a switch must be the last statement of its case");
563+ break;
564+ }
565+ emit_line(b, ind, "stop;");
566+ break;
567+ }
568+ case N_CONTINUE: {
569+ if (L.loop && L.loop->is_switch && L.loop->wrapper && real_loop()) {
570+ /* leave the one-shot switch loop first, then continue the real loop */
571+ L.loop->cont_used = 1;
572+ emit_line(b, ind, "%s = yes;", L.loop->cont_flag);
573+ emit_line(b, ind, "stop;");
574+ break;
575+ }
576+ LoopCtx *l = real_loop();
577+ if (l && l->update) each_line(b, ind, l->update), (void)0;
578+ emit_line(b, ind, "next;");
579+ break;
580+ }
581+ case N_THROW: {
582+ char *v = lx(s->a);
583+ flush_pre(b, ind);
584+ emit_line(b, ind, "raise %s;", v);
585+ break;
586+ }
587+ case N_TRY: lower_try(b, s, ind); break;
588+ case N_SWITCH: lower_switch_stmt(b, s, ind); break;
589+ case N_CTORCALL: {
590+ char *t = lower_ctor_call(s);
591+ flush_pre(b, ind);
592+ if (*t) emit_line(b, ind, "%s", t);
593+ break;
594+ }
595+ default:
596+ lerr(s, "cannot lower this statement");
597+ }
598+}

front/src/jlower/D/.gitkeep → hearth/front/src/jlower/D/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

hearth/front/src/jlower/H/jlower.hadded

+24−0
@@ -0,0 +1,24 @@
1+#ifndef JLOWER_H
2+#define JLOWER_H
3+
4+#include "jtypes/H/jtypes.h"
5+
6+/* Lowering of a checked new-Ash program to old-Ash source for the existing ashvm
7+ * (MIGRATION.md Phase 4). The result is an *imported module* (top-level `local`s of an
8+ * imported file are visible to its functions) plus a two-line driver.
9+ *
10+ * Ownership: all returned strings live in the front end's arena and are never freed.
11+ * Errors: constructs the backend cannot express yet are reported through cerr(); the
12+ * caller must check j_error_count() before using the output. */
13+
14+/* Set to 1 before lowering for Java's 32-bit int wrap-around on + - * << unary- ++ --. Off by default:
15+ * it roughly halves the speed of integer-heavy loops, and the old VM's numbers are doubles. */
16+extern int jl_wrap_int;
17+
18+/* Lowers every loaded class; `entry` supplies the class holding main. Returns the module text. */
19+char *jlower_program(JClass *entry);
20+
21+/* The two-line driver that imports the module file `module_name` and starts it. */
22+char *jlower_driver(const char *module_name);
23+
24+#endif

hearth/front/src/jlower/H/jlower_internal.hadded

+67−0
@@ -0,0 +1,67 @@
1+#ifndef JLOWER_INTERNAL_H
2+#define JLOWER_INTERNAL_H
3+
4+#include "jlower/H/jlower.h"
5+
6+/* State of the statement currently being lowered. */
7+typedef struct LoopCtx {
8+ const char *update; /* for-loop update statements, emitted before `next` */
9+ int is_switch; /* a switch lowered to an if-chain: only a trailing break is allowed */
10+ int wrapper; /* a switch lowered as `during yes {...; stop;}`: break is `stop`, continue leaves via cont_flag */
11+ char *cont_flag; /* set before stop when `continue` crosses the wrapper */
12+ int cont_used;
13+ struct LoopCtx *up;
14+} LoopCtx;
15+
16+typedef struct {
17+ JBuf pre; /* hoisted statements for the statement being emitted */
18+ int tmp; /* temp counter */
19+ JClass *cls; /* class being lowered */
20+ JMethod *method;
21+ int is_static;
22+ LoopCtx *loop;
23+ Node *fin[16]; /* enclosing finally bodies, innermost last (inlined before each return) */
24+ int fin_n;
25+} LState;
26+extern LState L;
27+extern int jl_wrap_int; /* --wrap-int: Java 32-bit int overflow (costs about 2x on arithmetic-heavy loops) */
28+
29+/* lower_expr.c */
30+char *lx(Node *e); /* expression -> old-Ash expression */
31+char *str_of(JType *t, const char *text); /* string conversion by static type */
32+char *quote_str(const char *s);
33+char *sname(const char *n); /* identifier safe in old Ash */
34+char *fn_name(JMethod *m, const char *kind); /* e.g. demo_Main__main_3 */
35+char *method_key(JMethod *m); /* vtable key */
36+char *cls_state(JClass *c); /* C__S */
37+char *cls_is(JClass *c); /* C__IS */
38+char *cls_vt(JClass *c); /* C__VT */
39+char *new_temp(void);
40+void mark_used(JClass *c); /* class needs emitting */
41+int used_count(void);
42+JClass *used_at(int i);
43+void lerr(Node *at, const char *fmt, ...);
44+int is_array_list(JType *t); /* List / ArrayList: represented by an Ash array */
45+int tmpl_uses_receiver(const char *tmpl);
46+char *expand_tmpl(const char *tmpl, const char *recv, char **args, int nargs);
47+char *lower_ctor_call(Node *n); /* N_CTORCALL statement text */
48+void alias_set(JLocal *l, const char *text); /* pattern bindings read the scrutinee */
49+char *alias_get(JLocal *l);
50+int is_native_value_class(JClass *c); /* represented by an Ash primitive/array/map */
51+
52+/* lower_stmt.c */
53+void ls(JBuf *b, Node *s, int ind);
54+void lblock_body(JBuf *b, Node *block, int ind);
55+void emit_line(JBuf *b, int ind, const char *fmt, ...);
56+void flush_pre(JBuf *b, int ind);
57+char *lower_lambda_body(Node *lam, JMethod *sam, int ind);
58+char *lower_switch_expr(Node *n);
59+void emit_assign_stmt(JBuf *b, Node *e, int ind);
60+
61+/* lower_prelude.c */
62+void emit_prelude(JBuf *b);
63+void emit_prelude_long(JBuf *b); /* exact 64-bit long runtime (lower_prelude_long.c) */
64+char *prune_module(const char *module); /* dead-code elimination from start() (lower_prune.c) */
65+JMethod *find_ctor(JClass *c, int nparams);
66+
67+#endif

front/src/jlower/O/.gitkeep → hearth/front/src/jlower/O/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

front/src/jparse/C/jparse_decl.c → hearth/front/src/jparse/C/jparse_decl.crenamed

+3−0
@@ -342,6 +342,7 @@ Node *p_type_decl(Parser *p, int mods, JList *annos) {
342342 }
343343
344344 Node *p_class_body_decl(Parser *p, Node *owner) {
345+ p_old_syntax(p, 1);
345346 JList annos = {0};
346347 int mods = p_parse_modifiers(p, &annos);
347348 JToken *t = p_peek(p);
@@ -449,6 +450,7 @@ Node *jparse_unit(const char *path, const char *src) {
449450 Parser p = {jlex_tokenize(path, src), 0, 0, 0};
450451 Node *u = p_node(&p, N_UNIT);
451452 u->name = "";
453+ p_old_syntax(&p, 2);
452454 if (p_is(&p, JT_PACKAGE)) {
453455 p_next(&p);
454456 int w;
@@ -468,6 +470,7 @@ Node *jparse_unit(const char *path, const char *src) {
468470 }
469471 while (!p_is(&p, JT_EOF)) {
470472 if (p_accept(&p, JT_SEMI)) continue;
473+ p_old_syntax(&p, 2);
471474 JList annos = {0};
472475 int mods = p_parse_modifiers(&p, &annos);
473476 j_list_push(&u->l2, p_type_decl(&p, mods, &annos));

front/src/jparse/C/jparse_expr.c → hearth/front/src/jparse/C/jparse_expr.crenamed

+0−0

No content changes. View the file on GitHub

hearth/front/src/jparse/C/jparse_old.cadded

+89−0
@@ -0,0 +1,89 @@
1+#include <string.h>
2+
3+#include "jparse/H/jparse_internal.h"
4+
5+/* Old-Ash syntax is diagnosed by context, not by reserving the words (decision D3): `say`,
6+ * `next`, `each`, `no`... stay ordinary identifiers in new Ash, so a message is only produced
7+ * where the old construct is unambiguous. Each message names the replacement. */
8+
9+static int continues_expr(JTokKind k) {
10+ switch (k) {
11+ case JT_ASSIGN: case JT_DOT: case JT_SEMI: case JT_LBRACKET: case JT_INC: case JT_DEC: case JT_COMMA:
12+ case JT_COLON: case JT_PLUS_ASSIGN: case JT_MINUS_ASSIGN: case JT_STAR_ASSIGN: case JT_SLASH_ASSIGN:
13+ case JT_PERCENT_ASSIGN: case JT_AMP_ASSIGN: case JT_PIPE_ASSIGN: case JT_CARET_ASSIGN: case JT_SHL_ASSIGN:
14+ case JT_RPAREN: case JT_RBRACE:
15+ return 1;
16+ default:
17+ return 0;
18+ }
19+}
20+
21+static int starts_value(JTokKind k) {
22+ switch (k) {
23+ case JT_IDENT: case JT_INT_LIT: case JT_LONG_LIT: case JT_FLOAT_LIT: case JT_DOUBLE_LIT: case JT_CHAR_LIT:
24+ case JT_STRING_LIT: case JT_NEW: case JT_TRUE: case JT_FALSE: case JT_NULL: case JT_THIS: case JT_BANG:
25+ return 1;
26+ default:
27+ return 0;
28+ }
29+}
30+
31+/* index of the token after the parenthesis group that starts at offset `from`, or -1 */
32+static int after_group(Parser *p, int from) {
33+ int depth = 0;
34+ for (int i = from;; i++) {
35+ JTokKind k = p_peek_at(p, i)->kind;
36+ if (k == JT_EOF) return -1;
37+ if (k == JT_LPAREN) depth++;
38+ if (k == JT_RPAREN && --depth == 0) return i + 1;
39+ }
40+}
41+
42+static void old(Parser *p, const char *word, const char *use) {
43+ p_fail(p, "'%s' is old Ash syntax and was removed; %s (see MIGRATION.md)", word, use);
44+}
45+
46+void p_old_syntax(Parser *p, int where) {
47+ JToken *t = p_peek(p);
48+ JTokKind n = p_peek_at(p, 1)->kind;
49+ if (t->kind == JT_AT && n == JT_IMPORT) {
50+ old(p, "@import <file.ash>;", "write `import package.Type;` at the top of the file, after the package declaration");
51+ }
52+ if (t->kind != JT_IDENT) return;
53+ const char *w = t->text;
54+ if (where == 2) {
55+ if (!strcmp(w, "local") || !strcmp(w, "say") || !strcmp(w, "forge") || !strcmp(w, "given") || !strcmp(w, "during") ||
56+ !strcmp(w, "each") || !strcmp(w, "attempt") || !strcmp(w, "raise")) {
57+ p_fail(p, "this looks like an old Ash script ('%s' at the top level); new Ash programs declare a package and a class "
58+ "with `public static void main(String[] args)` (see MIGRATION.md)", w);
59+ }
60+ return;
61+ }
62+ if (where == 1) {
63+ if (!strcmp(w, "forge") && n == JT_IDENT) old(p, "forge", "declare a method with explicit types, e.g. `static int name(int a) { ... }`");
64+ if (!strcmp(w, "local") && n == JT_IDENT) old(p, "local", "declare the field with its type, e.g. `private int x;`");
65+ return;
66+ }
67+ /* statement position */
68+ if (!strcmp(w, "local") && n == JT_IDENT) old(p, "local", "declare the variable with its type: `int x = 5;` or `var x = 5;`");
69+ if (!strcmp(w, "say") && starts_value(n)) old(p, "say", "use `System.out.println(value);`");
70+ if ((!strcmp(w, "given") || !strcmp(w, "during")) && !continues_expr(n)) {
71+ int ok = n != JT_LPAREN || (after_group(p, 1) > 0 && p_peek_at(p, after_group(p, 1))->kind == JT_LBRACE);
72+ if (ok) {
73+ if (!strcmp(w, "given")) old(p, "given", "use `if (condition) { ... }`");
74+ old(p, "during", "use `while (condition) { ... }`");
75+ }
76+ }
77+ if (!strcmp(w, "otherwise") && (n == JT_LBRACE || (n == JT_IDENT && !strcmp(p_peek_at(p, 1)->text, "given")))) {
78+ old(p, "otherwise", "use `else { ... }` or `else if (...)`");
79+ }
80+ if (!strcmp(w, "each") && n == JT_IDENT && p_peek_at(p, 2)->kind == JT_IDENT && !strcmp(p_peek_at(p, 2)->text, "in")) {
81+ old(p, "each x in xs", "use `for (Type x : xs) { ... }`");
82+ }
83+ if (!strcmp(w, "stop") && n == JT_SEMI) old(p, "stop", "use `break;`");
84+ if (!strcmp(w, "next") && n == JT_SEMI) old(p, "next", "use `continue;`");
85+ if (!strcmp(w, "yield") && !continues_expr(n)) old(p, "yield", "use `return value;`");
86+ if (!strcmp(w, "attempt") && n == JT_LBRACE) old(p, "attempt", "use `try { ... } catch (Exception e) { ... }`");
87+ if (!strcmp(w, "raise") && starts_value(n)) old(p, "raise", "use `throw new SomeException(...);`");
88+ if (!strcmp(w, "forge") && n == JT_IDENT) old(p, "forge", "declare a method with explicit types, or write a lambda `(a) -> expr`");
89+}

front/src/jparse/C/jparse_stmt.c → hearth/front/src/jparse/C/jparse_stmt.crenamed

+1−0
@@ -251,6 +251,7 @@ Node *p_switch(Parser *p, int is_expr) {
251251 }
252252
253253 Node *p_stmt(Parser *p) {
254+ p_old_syntax(p, 0);
254255 JToken *t = p_peek(p);
255256 Node *n;
256257 switch (t->kind) {

front/src/jparse/D/.gitkeep → hearth/front/src/jparse/D/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

front/src/jparse/H/jast.h → hearth/front/src/jparse/H/jast.hrenamed

+1−0
@@ -91,6 +91,7 @@ struct Node {
9191 const char *path;
9292 /* filled by the checker */
9393 struct JType *type;
94+ int rk; /* resolution kind (jtypes.h: R_*) */
9495 void *sym;
9596 void *sym2;
9697 };

front/src/jparse/H/jparse.h → hearth/front/src/jparse/H/jparse.hrenamed

+0−0

No content changes. View the file on GitHub

front/src/jparse/H/jparse_internal.h → hearth/front/src/jparse/H/jparse_internal.hrenamed

+3−0
@@ -41,6 +41,9 @@ int p_parse_modifiers(Parser *p, JList *annos);
4141 Node *p_type_decl(Parser *p, int mods, JList *annos);
4242 void p_params(Parser *p, Node *method);
4343
44+/* old-syntax migration diagnostics (jparse_old.c); where: 0 statement, 1 class member, 2 top level */
45+void p_old_syntax(Parser *p, int where);
46+
4447 /* statements and expressions (jparse_stmt.c, jparse_expr.c) */
4548 Node *p_block(Parser *p);
4649 Node *p_array_init(Parser *p, Node *type); /* `{a, b}` initializer for an array of `type` */

front/src/jparse/O/.gitkeep → hearth/front/src/jparse/O/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

hearth/front/src/jtypes/C/check_class.cadded

+307−0
@@ -0,0 +1,307 @@
1+#include <stdlib.h>
2+#include <string.h>
3+
4+#include "jtypes/H/check.h"
5+
6+/* Class-level checking: inheritance, overrides, abstract implementation, sealed
7+ * hierarchies, then every method, constructor, field initializer and enum constant. */
8+
9+#define TVOID ty_prim(TY_VOID)
10+
11+static int access_rank(int flags) {
12+ if (flags & M_PUBLIC) return 3;
13+ if (flags & M_PROTECTED) return 2;
14+ if (flags & M_PRIVATE) return 0;
15+ return 1;
16+}
17+
18+static const char *access_name(int flags) {
19+ return (flags & M_PUBLIC) ? "public" : (flags & M_PROTECTED) ? "protected" : (flags & M_PRIVATE) ? "private" : "package-private";
20+}
21+
22+static int same_params(JMethod *a, JMethod *b) {
23+ if (a->params.count != b->params.count) return 0;
24+ for (int i = 0; i < a->params.count; i++) {
25+ JType *x = ((JParam *)a->params.items[i])->type, *y = ((JParam *)b->params.items[i])->type;
26+ if (x->kind == TY_TVAR || y->kind == TY_TVAR) continue;
27+ x = ty_erase_bound(x);
28+ y = ty_erase_bound(y);
29+ if (x->kind != y->kind) return 0;
30+ if (x->kind == TY_CLASS && x->cls != y->cls) return 0;
31+ if (x->kind == TY_ARRAY && !ty_same(x, y)) return 0;
32+ }
33+ return 1;
34+}
35+
36+static JMethod *find_in(JClass *c, JMethod *m, int depth) {
37+ if (!c || depth > 16) return NULL;
38+ jt_complete(c);
39+ for (int i = 0; i < c->methods.count; i++) {
40+ JMethod *o = c->methods.items[i];
41+ if (o != m && !strcmp(o->name, m->name) && !(o->flags & M_PRIVATE) && same_params(o, m)) return o;
42+ }
43+ if (c->super) {
44+ JMethod *r = find_in(c->super->cls, m, depth + 1);
45+ if (r) return r;
46+ }
47+ for (int i = 0; i < c->ifaces.count; i++) {
48+ JMethod *r = find_in(((JType *)c->ifaces.items[i])->cls, m, depth + 1);
49+ if (r) return r;
50+ }
51+ return NULL;
52+}
53+
54+JMethod *jt_find_overridden(JClass *c, JMethod *m) {
55+ JMethod *r = NULL;
56+ if (c->super) r = find_in(c->super->cls, m, 0);
57+ for (int i = 0; i < c->ifaces.count && !r; i++) r = find_in(((JType *)c->ifaces.items[i])->cls, m, 0);
58+ if (!r && c->kind == N_INTERFACE) r = find_in(jt_object(), m, 0);
59+ return r;
60+}
61+
62+static void check_overrides(JClass *c) {
63+ for (int i = 0; i < c->methods.count; i++) {
64+ JMethod *m = c->methods.items[i];
65+ Node *at = m->decl ? m->decl : c->decl;
66+ for (int j = 0; j < i; j++) {
67+ JMethod *o = c->methods.items[j];
68+ if (!strcmp(o->name, m->name) && same_params(o, m) && o->params.count == m->params.count && !m->synthetic && !o->synthetic) {
69+ int same = 1;
70+ for (int k = 0; k < m->params.count; k++) {
71+ if (!ty_same(((JParam *)m->params.items[k])->type, ((JParam *)o->params.items[k])->type)) same = 0;
72+ }
73+ if (same) cerr(at, "method '%s' is already defined in %s", m->name, c->name);
74+ }
75+ }
76+ if (m->flags & M_STATIC) {
77+ if (m->has_override_anno) cerr(at, "static method '%s' cannot have @Override", m->name);
78+ continue;
79+ }
80+ JMethod *o = jt_find_overridden(c, m);
81+ if (m->has_override_anno && !o) {
82+ cerr(at, "method '%s' is annotated @Override but does not override or implement a method of a supertype", m->name);
83+ continue;
84+ }
85+ if (!o || (o->flags & M_STATIC)) continue;
86+ if (o->flags & M_FINAL) cerr(at, "method '%s' cannot override final method of %s", m->name, o->cls->name);
87+ if (access_rank(m->flags) < access_rank(o->flags)) {
88+ cerr(at, "method '%s' cannot reduce visibility from %s (in %s) to %s", m->name, access_name(o->flags), o->cls->name, access_name(m->flags));
89+ }
90+ JType *oret = member_type(c->self_type, o->cls, o->ret);
91+ int ok = ty_is_prim(m->ret) || m->ret->kind == TY_VOID || ty_is_prim(oret) || oret->kind == TY_VOID
92+ ? (m->ret->kind == oret->kind)
93+ : ty_assignable(m->ret, ty_with_null(oret, 1), 1) || oret->kind == TY_TVAR;
94+ if (!ok) cerr(at, "return type %s of '%s' is not compatible with %s in %s", ty_str(m->ret), m->name, ty_str(oret), o->cls->name);
95+ for (int k = 0; k < m->throws.count; k++) {
96+ JType *t = m->throws.items[k];
97+ if (!ty_is_checked_exception(t)) continue;
98+ int allowed = 0;
99+ for (int q = 0; q < o->throws.count; q++) {
100+ JType *ot = o->throws.items[q];
101+ if (t->kind == TY_CLASS && ot->kind == TY_CLASS && ty_is_subclass(t->cls, ot->cls)) allowed = 1;
102+ }
103+ if (!allowed) cerr(at, "'%s' cannot throw %s: the overridden method in %s does not declare it", m->name, ty_str(t), o->cls->name);
104+ }
105+ }
106+}
107+
108+static void collect_abstract_all(JClass *c, JList *out, int depth) {
109+ if (!c || depth > 16) return;
110+ jt_complete(c);
111+ for (int i = 0; i < c->methods.count; i++) {
112+ JMethod *m = c->methods.items[i];
113+ if ((m->flags & M_ABSTRACT) && !(m->flags & M_STATIC)) j_list_push(out, m);
114+ }
115+ if (c->super) collect_abstract_all(c->super->cls, out, depth + 1);
116+ for (int i = 0; i < c->ifaces.count; i++) collect_abstract_all(((JType *)c->ifaces.items[i])->cls, out, depth + 1);
117+}
118+
119+static void check_abstract_impl(JClass *c) {
120+ if (c->kind == N_INTERFACE || (c->flags & M_ABSTRACT)) return;
121+ JList abs = {0};
122+ collect_abstract_all(c, &abs, 0);
123+ for (int i = 0; i < abs.count; i++) {
124+ JMethod *am = abs.items[i];
125+ JList all = {0};
126+ jt_collect_methods(c, am->name, &all);
127+ int impl = 0;
128+ for (int j = 0; j < all.count; j++) {
129+ JMethod *o = all.items[j];
130+ if (!(o->flags & M_ABSTRACT) && !(o->flags & M_STATIC) && o->params.count == am->params.count) impl = 1;
131+ }
132+ if (!impl) {
133+ cerr(c->decl, "%s '%s' must implement abstract method '%s' of %s (or be declared abstract)",
134+ c->kind == N_ENUM ? "enum" : c->kind == N_RECORD ? "record" : "class", c->name, am->name, am->cls->name);
135+ return;
136+ }
137+ }
138+}
139+
140+static void check_sealed(JClass *c) {
141+ JList supers = {0};
142+ if (c->super) j_list_push(&supers, c->super);
143+ for (int i = 0; i < c->ifaces.count; i++) j_list_push(&supers, c->ifaces.items[i]);
144+ for (int i = 0; i < supers.count; i++) {
145+ JType *st = supers.items[i];
146+ if (st->kind != TY_CLASS || !(st->cls->flags & M_SEALED)) continue;
147+ JClass *s = st->cls;
148+ jt_complete(s);
149+ if (s->permits.count) {
150+ int listed = 0;
151+ for (int j = 0; j < s->permits.count; j++) if (s->permits.items[j] == c) listed = 1;
152+ if (!listed) cerr(c->decl, "%s '%s' is not listed in the permits clause of sealed type %s", c->kind == N_INTERFACE ? "interface" : "class", c->name, s->name);
153+ } else if (strcmp(s->unit->name, c->unit->name) != 0 || s->path != c->path) {
154+ cerr(c->decl, "sealed type %s without a permits clause can only be extended in its own file", s->name);
155+ }
156+ if (c->kind == N_CLASS && !(c->flags & (M_FINAL | M_SEALED))) {
157+ cerr(c->decl, "class '%s' extends sealed %s and must be declared final or sealed", c->name, s->name);
158+ }
159+ if (c->kind == N_INTERFACE && !(c->flags & M_SEALED)) {
160+ /* interfaces cannot be final; allow sub-interfaces to stay open only when sealed */
161+ cerr(c->decl, "interface '%s' extends sealed %s and must be declared sealed", c->name, s->name);
162+ }
163+ }
164+ for (int i = 0; i < c->permits.count; i++) {
165+ JClass *p = c->permits.items[i];
166+ jt_complete(p);
167+ int direct = p->super && p->super->cls == c;
168+ for (int j = 0; j < p->ifaces.count; j++) if (((JType *)p->ifaces.items[j])->cls == c) direct = 1;
169+ if (!direct) cerr(c->decl, "'%s' is permitted by sealed %s but does not extend or implement it", p->name, c->name);
170+ }
171+ if ((c->flags & M_SEALED) && !c->permits.count && c->subclasses.count == 0) {
172+ /* no permits and no known subtypes in the loaded closure: allowed, but nothing to switch over */
173+ }
174+}
175+
176+/* ---------------------------------------------------------------- bodies */
177+
178+static void init_ctx(Ctx *cx, JClass *c, JMethod *m, int is_static) {
179+ memset(cx, 0, sizeof(*cx));
180+ cx->cls = c;
181+ cx->method = m;
182+ cx->is_static = is_static;
183+ cx->ret = TVOID;
184+}
185+
186+static void finish_ctx(Ctx *cx) {
187+ for (int i = 0; i < cx->captures.count; i++) {
188+ JLocal *l = cx->captures.items[i];
189+ int allowed = l->has_init || l->is_param ? 0 : 1;
190+ if (l->assigns > allowed && l->captured_at) {
191+ cerr(l->captured_at, "local variable '%s' is captured by a lambda and must be effectively final", l->name);
192+ l->captured_at = NULL;
193+ }
194+ }
195+}
196+
197+void check_method_body(JClass *c, JMethod *m) {
198+ Node *d = m->decl;
199+ if (!d || !d->b) return;
200+ Ctx cx;
201+ init_ctx(&cx, c, m, (m->flags & M_STATIC) != 0);
202+ cx.in_ctor = m->is_ctor;
203+ cx.ret = m->ret;
204+ ExFrame fr = {0};
205+ fr.kind = F_METHOD;
206+ for (int i = 0; i < m->throws.count; i++) j_list_push(&fr.types, m->throws.items[i]);
207+ cx.ex = &fr;
208+ for (int i = 0; i < m->params.count; i++) {
209+ JParam *p = m->params.items[i];
210+ Node *pn = (d->l1.count > i) ? d->l1.items[i] : d;
211+ JLocal *l = declare_local(&cx, pn, p->name, p->type, (d->l1.count > i) ? pn->flags : 0, 1);
212+ l->has_init = 1;
213+ }
214+ Node *body = d->b;
215+ int explicit_call = body->l1.count > 0 && ((Node *)body->l1.items[0])->kind == N_CTORCALL;
216+ for (int i = 1; i < body->l1.count; i++) {
217+ Node *s = body->l1.items[i];
218+ if (s->kind == N_CTORCALL) cerr(s, "'%s(...)' must be the first statement of a constructor", s->name);
219+ }
220+ if (m->is_ctor && !explicit_call && c->kind == N_CLASS && c->super && !(m->flags & 0x20000)) {
221+ Ctx pre = cx;
222+ JMethod *chosen = NULL;
223+ JList none = {0};
224+ check_args_for_ctor(&pre, d, c->super->cls, c->super, &none, d, &chosen);
225+ }
226+ check_block_body(&cx, body);
227+ if (m->ret->kind != TY_VOID && !m->is_ctor && can_complete(body)) cerr(d, "missing return statement in '%s'", m->name);
228+ finish_ctx(&cx);
229+}
230+
231+static void check_field_init(JClass *c, JField *f) {
232+ Node *d = f->decl;
233+ if (!d || d->kind != N_FIELD || !d->b) return;
234+ Ctx cx;
235+ init_ctx(&cx, c, NULL, (f->flags & M_STATIC) != 0);
236+ ExFrame fr = {0};
237+ fr.kind = F_INIT;
238+ cx.ex = &fr;
239+ check_coerce(&cx, d->b, f->type, "field initializer");
240+ finish_ctx(&cx);
241+}
242+
243+static void check_enum_consts(JClass *c) {
244+ Node *d = c->decl;
245+ for (int i = 0; i < d->l4.count; i++) {
246+ Node *ec = d->l4.items[i];
247+ Ctx cx;
248+ init_ctx(&cx, c, NULL, 1);
249+ ExFrame fr = {0};
250+ fr.kind = F_INIT;
251+ cx.ex = &fr;
252+ for (int j = 0; j < ec->l1.count; j++) {
253+ Node *a = ec->l1.items[j];
254+ if (!is_poly_arg(a)) check_expr(&cx, a, NULL);
255+ }
256+ JMethod *k = NULL;
257+ check_args_for_ctor(&cx, ec, c, c->self_type, &ec->l1, ec, &k);
258+ ec->sym = k;
259+ finish_ctx(&cx);
260+ }
261+}
262+
263+void jt_check_class(JClass *c) {
264+ if (c->checked) return;
265+ c->checked = 1;
266+ jt_complete(c);
267+ if (c->kind == N_RECORD) {
268+ for (int i = 0; i < c->fields.count; i++) {
269+ JField *f = c->fields.items[i];
270+ if (!(f->flags & M_FINAL)) cerr(f->decl, "record components are final");
271+ }
272+ }
273+ check_sealed(c);
274+ check_abstract_impl(c);
275+ check_overrides(c);
276+ if (c->kind != N_INTERFACE && !(c->flags & M_ABSTRACT)) {
277+ for (int i = 0; i < c->methods.count; i++) {
278+ JMethod *m = c->methods.items[i];
279+ if ((m->flags & M_ABSTRACT) && m->decl) cerr(m->decl, "class '%s' declares abstract method '%s' and must be declared abstract", c->name, m->name);
280+ }
281+ }
282+ if (c->kind == N_ENUM) check_enum_consts(c);
283+ for (int i = 0; i < c->fields.count; i++) check_field_init(c, c->fields.items[i]);
284+ for (int i = 0; i < c->decl->l1.count; i++) {
285+ Node *mem = c->decl->l1.items[i];
286+ if (mem->kind != N_INIT) continue;
287+ Ctx cx;
288+ init_ctx(&cx, c, NULL, (mem->flags & M_STATIC) != 0);
289+ cx.in_ctor = !(mem->flags & M_STATIC);
290+ ExFrame fr = {0};
291+ fr.kind = F_INIT;
292+ cx.ex = &fr;
293+ check_block_body(&cx, mem->b);
294+ finish_ctx(&cx);
295+ }
296+ for (int i = 0; i < c->methods.count; i++) check_method_body(c, c->methods.items[i]);
297+ for (int i = 0; i < c->ctors.count; i++) check_method_body(c, c->ctors.items[i]);
298+}
299+
300+void jt_check_all(void) {
301+ for (int i = 0;; i++) {
302+ JClass **all;
303+ int n = jt_all_classes(&all);
304+ if (i >= n) break;
305+ jt_check_class(all[i]);
306+ }
307+}

hearth/front/src/jtypes/C/check_expr.cadded

+1335−0
Show diff (1335 changed lines)
@@ -0,0 +1,1335 @@
1+#include <stdlib.h>
2+#include <string.h>
3+
4+#include "jtypes/H/check.h"
5+
6+/* Expression checking: names, members, calls with overload resolution and generic
7+ * inference, operators, nullability narrowing, lambdas and method references. */
8+
9+#define TINT ty_prim(TY_INT)
10+#define TBOOL ty_prim(TY_BOOL)
11+#define TVOID ty_prim(TY_VOID)
12+
13+/* ---------------------------------------------------------------- helpers */
14+
15+JType *narrowed(JLocal *l) { return l->nonnull ? ty_with_null(l->type, 0) : l->type; }
16+
17+JType *member_type(JType *recv, JClass *owner, JType *t) {
18+ jt_complete(owner);
19+ if (!owner->tparams.count) return t;
20+ JType *s = recv ? ty_as_super(recv, owner) : NULL;
21+ if (s && s->nargs == owner->tparams.count) return ty_subst(t, &owner->tparams, s->args, s->nargs);
22+ /* raw type: erase the owner's type parameters to their bounds */
23+ JType **er = j_alloc(sizeof(JType *) * (size_t)owner->tparams.count);
24+ for (int i = 0; i < owner->tparams.count; i++) {
25+ JTVar *tv = owner->tparams.items[i];
26+ er[i] = tv->bound ? ty_erase_bound(tv->bound) : jt_object_type();
27+ }
28+ return ty_subst(t, &owner->tparams, er, owner->tparams.count);
29+}
30+
31+static JClass *outermost(JClass *c) {
32+ while (c->outer) c = c->outer;
33+ return c;
34+}
35+
36+int accessible(Ctx *cx, JClass *owner, int flags) {
37+ if (flags & M_PUBLIC) return 1;
38+ if (flags & M_PRIVATE) return outermost(owner) == outermost(cx->cls);
39+ if (strcmp(owner->pkg, cx->cls->pkg) == 0) return 1;
40+ if (flags & M_PROTECTED) {
41+ for (JClass *c = cx->cls; c; c = c->outer) {
42+ if (ty_is_subclass(c, owner)) return 1;
43+ }
44+ }
45+ return 0;
46+}
47+
48+static JType *recv_class(JType *t) {
49+ if (t->kind == TY_TVAR) return ty_erase_bound(t);
50+ return t;
51+}
52+
53+static int is_stmt_expr(Node *n) {
54+ return n->kind == N_ASSIGN || n->kind == N_CALL || n->kind == N_NEW || n->kind == N_POSTFIX ||
55+ (n->kind == N_UNARY && (!strcmp(n->name, "++") || !strcmp(n->name, "--")));
56+}
57+static int is_diamond_new(Node *n) { return n->kind == N_NEW && (n->a->flags & 0x10000); }
58+/* arguments whose type depends on the parameter they are passed to */
59+/* a generic call whose type arguments could not be inferred from its own arguments (flag set by select_method) */
60+static int is_open_generic_call(Node *n) { return n->kind == N_CALL && (n->flags & 0x100); }
61+int is_poly_arg(Node *n) { return n->kind == N_LAMBDA || n->kind == N_METHODREF || is_diamond_new(n) || is_open_generic_call(n); }
62+
63+static const char *what_kind(JType *t) { return t->nullable ? "nullable value" : "value"; }
64+
65+/* ---------------------------------------------------------------- locals and scopes */
66+
67+void push_scope(Ctx *cx, int *mark) { *mark = cx->locals.count; }
68+void pop_scope(Ctx *cx, int mark) { cx->locals.count = mark; }
69+
70+JLocal *lookup_local(Ctx *cx, const char *name) {
71+ for (int i = cx->locals.count - 1; i >= 0; i--) {
72+ JLocal *l = cx->locals.items[i];
73+ if (strcmp(l->name, name) == 0) return l;
74+ }
75+ return NULL;
76+}
77+
78+JLocal *declare_local(Ctx *cx, Node *at, const char *name, JType *t, int flags, int is_param) {
79+ for (int i = cx->locals.count - 1; i >= 0; i--) {
80+ JLocal *o = cx->locals.items[i];
81+ if (strcmp(o->name, name) == 0) {
82+ if (o->is_temp) {
83+ cx->locals.items[i] = cx->locals.items[cx->locals.count - 1];
84+ cx->locals.count--;
85+ } else {
86+ cerr(at, "variable '%s' is already defined in this scope", name);
87+ }
88+ break;
89+ }
90+ }
91+ JLocal *l = j_alloc(sizeof(JLocal));
92+ l->name = name;
93+ l->type = t;
94+ l->flags = flags;
95+ l->is_param = is_param;
96+ l->index = cx->locals.count;
97+ l->decl = at;
98+ l->lname = name;
99+ j_list_push(&cx->locals, l);
100+ return l;
101+}
102+
103+/* ---------------------------------------------------------------- exceptions */
104+
105+void report_throw(Ctx *cx, Node *at, JType *exc) {
106+ if (exc->kind != TY_CLASS || !ty_is_checked_exception(exc)) return;
107+ for (ExFrame *f = cx->ex; f; f = f->up) {
108+ if (f->kind == F_TRY) {
109+ j_list_push(&f->thrown, exc);
110+ for (int i = 0; i < f->types.count; i++) {
111+ JType *c = f->types.items[i];
112+ if (c->kind == TY_CLASS && ty_is_subclass(exc->cls, c->cls)) return;
113+ }
114+ } else {
115+ for (int i = 0; i < f->types.count; i++) {
116+ JType *c = f->types.items[i];
117+ if (c->kind == TY_CLASS && ty_is_subclass(exc->cls, c->cls)) return;
118+ }
119+ break;
120+ }
121+ }
122+ cerr(at, "unreported exception %s; it must be caught or declared with 'throws'", ty_str(exc));
123+}
124+
125+/* ---------------------------------------------------------------- nullability narrowing */
126+
127+typedef struct { JLocal *l; int old; } NarrowUndo;
128+
129+static JLocal *local_of(Node *e) {
130+ return (e->kind == N_NAME && e->rk == R_LOCAL) ? e->sym : NULL;
131+}
132+
133+static void set_nonnull(JLocal *l, JList *undo) {
134+ if (!l->type->nullable || l->nonnull) return;
135+ if (undo) {
136+ NarrowUndo *u = j_alloc(sizeof(NarrowUndo));
137+ u->l = l;
138+ u->old = l->nonnull;
139+ j_list_push(undo, u);
140+ }
141+ l->nonnull = 1;
142+}
143+
144+void narrow_apply(Ctx *cx, Node *c, int when_true, JList *undo) {
145+ (void)cx;
146+ if (!c) return;
147+ if (c->kind == N_UNARY && !strcmp(c->name, "!")) {
148+ narrow_apply(cx, c->a, !when_true, undo);
149+ } else if (c->kind == N_BINARY) {
150+ if (!strcmp(c->name, "&&") && when_true) {
151+ narrow_apply(cx, c->a, 1, undo);
152+ narrow_apply(cx, c->b, 1, undo);
153+ } else if (!strcmp(c->name, "||") && !when_true) {
154+ narrow_apply(cx, c->a, 0, undo);
155+ narrow_apply(cx, c->b, 0, undo);
156+ } else if (!strcmp(c->name, "!=") || !strcmp(c->name, "==")) {
157+ int is_ne = c->name[0] == '!';
158+ Node *v = NULL;
159+ if (c->b->kind == N_LIT_NULL) v = c->a;
160+ else if (c->a->kind == N_LIT_NULL) v = c->b;
161+ JLocal *l = v ? local_of(v) : NULL;
162+ if (l && (when_true == is_ne)) set_nonnull(l, undo);
163+ }
164+ } else if (c->kind == N_INSTANCEOF && when_true) {
165+ JLocal *l = local_of(c->a);
166+ if (l) set_nonnull(l, undo);
167+ }
168+}
169+
170+void narrow_undo(JList *undo) {
171+ for (int i = undo->count - 1; i >= 0; i--) {
172+ NarrowUndo *u = undo->items[i];
173+ u->l->nonnull = u->old;
174+ }
175+ undo->count = 0;
176+}
177+
178+/* ---------------------------------------------------------------- assignment compatibility */
179+
180+int const_int_value(Node *e, long *out) {
181+ if (e->kind == N_LIT_INT) { *out = (long)strtoull(e->name, NULL, 0); return 1; }
182+ if (e->kind == N_LIT_CHAR) { *out = atol(e->name); return 1; }
183+ if (e->kind == N_UNARY && !strcmp(e->name, "-")) {
184+ long v;
185+ if (const_int_value(e->a, &v)) { *out = -v; return 1; }
186+ }
187+ return 0;
188+}
189+
190+int check_assignable(Ctx *cx, Node *expr, JType *from, JType *to, const char *what) {
191+ (void)cx;
192+ if (from->kind == TY_ERROR || to->kind == TY_ERROR) return 1;
193+ if (ty_assignable(from, to, 0)) return 1;
194+ long v;
195+ if (expr && ty_is_prim(to) && to->kind != TY_BOOL && to->kind != TY_INT && to->kind != TY_LONG &&
196+ (from->kind == TY_INT || from->kind == TY_CHAR || from->kind == TY_SHORT || from->kind == TY_BYTE) &&
197+ const_int_value(expr, &v)) {
198+ long lo = to->kind == TY_BYTE ? -128 : to->kind == TY_SHORT ? -32768 : to->kind == TY_CHAR ? 0 : 0;
199+ long hi = to->kind == TY_BYTE ? 127 : to->kind == TY_SHORT ? 32767 : to->kind == TY_CHAR ? 65535 : 0;
200+ if (to->kind == TY_FLOAT || to->kind == TY_DOUBLE) return 1;
201+ if (v >= lo && v <= hi) return 1;
202+ }
203+ if (from->kind == TY_VOID) {
204+ cerr(expr, "%s has type void; a value is required", what);
205+ } else if (from->kind == TY_NULL) {
206+ cerr(expr, "null cannot be used for %s of non-null type %s; declare it as %s?", what, ty_str(to), ty_str(to));
207+ } else if (from->nullable && !to->nullable && ty_assignable(from, to, 1)) {
208+ cerr(expr, "possible null: %s of nullable type %s cannot be used where non-null %s is required; check for null first",
209+ what, ty_str(from), ty_str(to));
210+ } else if (ty_is_prim(from) && ty_is_prim(to) && from->kind != TY_BOOL && to->kind != TY_BOOL) {
211+ cerr(expr, "possible lossy conversion in %s from %s to %s; use an explicit cast", what, ty_str(from), ty_str(to));
212+ } else {
213+ cerr(expr, "incompatible types in %s: %s cannot be converted to %s", what, ty_str(from), ty_str(to));
214+ }
215+ return 0;
216+}
217+
218+void check_coerce(Ctx *cx, Node *expr, JType *to, const char *what) {
219+ JType *t = check_expr(cx, expr, to);
220+ check_assignable(cx, expr, t, to, what);
221+}
222+
223+JType *check_cond(Ctx *cx, Node *n) {
224+ JType *t = check_expr(cx, n, TBOOL);
225+ if (t->kind != TY_ERROR && !(ty_unbox(t)->kind == TY_BOOL && !t->nullable)) {
226+ cerr(n, "condition must be of type bool, found %s", ty_str(t));
227+ }
228+ return t;
229+}
230+
231+/* ---------------------------------------------------------------- generic inference */
232+
233+static int tv_index(JMethod *m, JType *t) {
234+ if (t->kind != TY_TVAR || t->tv->owner_m != m) return -1;
235+ for (int i = 0; i < m->tparams.count; i++) {
236+ if (m->tparams.items[i] == t->tv) return i;
237+ }
238+ return -1;
239+}
240+
241+static int mentions_free(JType *t, JMethod *m, JType **bind) {
242+ switch (t->kind) {
243+ case TY_TVAR: {
244+ int i = tv_index(m, t);
245+ return i >= 0 && !bind[i];
246+ }
247+ case TY_ARRAY: return mentions_free(t->elem, m, bind);
248+ case TY_CLASS:
249+ for (int i = 0; i < t->nargs; i++) if (mentions_free(t->args[i], m, bind)) return 1;
250+ return 0;
251+ default: return 0;
252+ }
253+}
254+
255+static void unify(JType *p, JType *a, JMethod *m, JType **bind) {
256+ if (!a || a->kind == TY_NULL || a->kind == TY_ERROR || a->kind == TY_VOID) return;
257+ int i = tv_index(m, p);
258+ if (i >= 0) {
259+ JType *b = ty_is_prim(a) ? ty_box(a) : a;
260+ if (!bind[i]) bind[i] = b;
261+ else if (!ty_assignable(b, bind[i], 1) && ty_assignable(bind[i], b, 1)) bind[i] = b;
262+ else if (bind[i]->nullable != b->nullable && ty_assignable(b, bind[i], 1)) bind[i] = ty_with_null(bind[i], bind[i]->nullable || b->nullable);
263+ return;
264+ }
265+ if (p->kind == TY_ARRAY && a->kind == TY_ARRAY) {
266+ unify(p->elem, a->elem, m, bind);
267+ } else if (p->kind == TY_CLASS && p->nargs && (a->kind == TY_CLASS || a->kind == TY_TVAR)) {
268+ JType *s = ty_as_super(a, p->cls);
269+ if (s && s->nargs == p->nargs) {
270+ for (int k = 0; k < p->nargs; k++) unify(p->args[k], s->args[k], m, bind);
271+ }
272+ }
273+}
274+
275+static JType *fill_bind(JMethod *m, JType **bind, int erase_unbound, JType ***out) {
276+ int n = m->tparams.count;
277+ JType **b = j_alloc(sizeof(JType *) * (size_t)(n ? n : 1));
278+ for (int i = 0; i < n; i++) {
279+ if (bind[i]) b[i] = bind[i];
280+ else if (erase_unbound) {
281+ JTVar *tv = m->tparams.items[i];
282+ b[i] = tv->bound ? ty_erase_bound(tv->bound) : jt_object_type();
283+ } else b[i] = ty_tvar(m->tparams.items[i]);
284+ }
285+ *out = b;
286+ return NULL;
287+}
288+
289+static JType *msubst(JType *t, JMethod *m, JType **bind) {
290+ if (!m->tparams.count) return t;
291+ JType **b;
292+ fill_bind(m, bind, 0, &b);
293+ return ty_subst(t, &m->tparams, b, m->tparams.count);
294+}
295+
296+/* ---------------------------------------------------------------- overload resolution */
297+
298+typedef struct {
299+ JMethod *m;
300+ JType **bind;
301+ int phase;
302+} Cand;
303+
304+static JType *formal(JMethod *m, JType *recv, int i, int phase) {
305+ int np = m->params.count;
306+ JType *pt;
307+ if (phase == 3 && i >= np - 1) pt = ((JParam *)m->params.items[np - 1])->type->elem;
308+ else pt = ((JParam *)m->params.items[i])->type;
309+ return member_type(recv, m->cls, pt);
310+}
311+
312+static int lambda_arity_ok(Node *a, JType *pt) {
313+ if (is_open_generic_call(a)) return pt->kind == TY_CLASS || pt->kind == TY_TVAR;
314+ if (is_diamond_new(a)) {
315+ if (pt->kind == TY_TVAR) return 1;
316+ JType *nt = jt_resolve_type(a->a, NULL, NULL, 0);
317+ Node *tn = a->a;
318+ (void)tn;
319+ if (nt->kind != TY_CLASS) {
320+ /* resolved lazily against the caller's scope during completion */
321+ return pt->kind == TY_CLASS;
322+ }
323+ return pt->kind == TY_CLASS && ty_is_subclass(nt->cls, pt->cls);
324+ }
325+ if (!jt_is_functional(pt)) return 0;
326+ if (a->kind == N_METHODREF) return 1;
327+ JMethod *sam = jt_find_sam(pt->cls);
328+ return sam && sam->params.count == a->l1.count;
329+}
330+
331+static int applicable(JMethod *m, JType *recv, JList *args, int phase, Cand *out) {
332+ int n = args->count, np = m->params.count;
333+ if (phase == 3) {
334+ if (!m->varargs || n < np - 1) return 0;
335+ } else if (n != np) {
336+ return 0;
337+ }
338+ JType **bind = j_alloc(sizeof(JType *) * (size_t)(m->tparams.count ? m->tparams.count : 1));
339+ for (int i = 0; i < n; i++) {
340+ Node *a = args->items[i];
341+ JType *pt = formal(m, recv, i, phase);
342+ if (is_poly_arg(a)) {
343+ if (!lambda_arity_ok(a, msubst(pt, m, bind))) return 0;
344+ continue;
345+ }
346+ unify(pt, a->type, m, bind);
347+ }
348+ for (int i = 0; i < n; i++) {
349+ Node *a = args->items[i];
350+ if (is_poly_arg(a)) continue;
351+ JType *pt = msubst(formal(m, recv, i, phase), m, bind);
352+ if (mentions_free(pt, m, bind)) continue;
353+ JType *at = a->type;
354+ if (at->kind == TY_ERROR) continue;
355+ if (phase == 1 && ty_is_prim(at) != ty_is_prim(pt) && at->kind != TY_NULL) return 0;
356+ long v;
357+ if (ty_assignable(at, pt, 0)) continue;
358+ (void)v;
359+ return 0;
360+ }
361+ for (int i = 0; i < m->tparams.count; i++) {
362+ JTVar *tv = m->tparams.items[i];
363+ if (tv->bound && bind[i]) {
364+ JType *bd = msubst(tv->bound, m, bind);
365+ if (!ty_assignable(bind[i], bd, 1)) return 0;
366+ }
367+ }
368+ out->m = m;
369+ out->bind = bind;
370+ out->phase = phase;
371+ return 1;
372+}
373+
374+static int more_specific(JMethod *a, JMethod *b) {
375+ int n = a->params.count < b->params.count ? a->params.count : b->params.count;
376+ for (int i = 0; i < n; i++) {
377+ JType *x = ty_erase_bound(((JParam *)a->params.items[i])->type);
378+ JType *y = ty_erase_bound(((JParam *)b->params.items[i])->type);
379+ if (!ty_assignable(x, ty_with_null(y, 1), 1)) return 0;
380+ }
381+ return 1;
382+}
383+
384+static const char *sig_str(JMethod *m) {
385+ JBuf b = {0};
386+ j_buf_add(&b, m->is_ctor ? m->cls->name : m->name);
387+ j_buf_addc(&b, '(');
388+ for (int i = 0; i < m->params.count; i++) {
389+ if (i) j_buf_add(&b, ", ");
390+ j_buf_add(&b, ty_str(((JParam *)m->params.items[i])->type));
391+ }
392+ j_buf_addc(&b, ')');
393+ return b.data;
394+}
395+
396+static const char *args_str(JList *args) {
397+ JBuf b = {0};
398+ j_buf_addc(&b, '(');
399+ for (int i = 0; i < args->count; i++) {
400+ Node *a = args->items[i];
401+ if (i) j_buf_add(&b, ", ");
402+ j_buf_add(&b, is_poly_arg(a) ? (a->kind == N_LAMBDA ? "lambda" : a->kind == N_METHODREF ? "method reference" : a->kind == N_CALL ? "generic call" : "new <>") : ty_str(a->type));
403+ }
404+ j_buf_addc(&b, ')');
405+ return b.data;
406+}
407+
408+/* Picks the best method for already-typed args; complete lambdas, infer type arguments and
409+ * return the instantiated result type through *ret. */
410+static JMethod *select_method(Ctx *cx, Node *at, JList *cands, JList *args, JType *recv, JType *expected,
411+ const char *what, JType **ret, JType ***bind_out) {
412+ Cand found[64];
413+ int nf = 0;
414+ for (int phase = 1; phase <= 3 && nf == 0; phase++) {
415+ for (int i = 0; i < cands->count && nf < 64; i++) {
416+ Cand c;
417+ if (applicable(cands->items[i], recv, args, phase, &c)) found[nf++] = c;
418+ }
419+ }
420+ if (nf == 0) {
421+ for (int i = 0; i < args->count; i++) {
422+ Node *a = args->items[i];
423+ if (!is_poly_arg(a) && a->type->kind == TY_ERROR) { *ret = ty_error(); return NULL; }
424+ }
425+ JMethod *only = NULL;
426+ for (int i = 0; i < cands->count; i++) {
427+ JMethod *m = cands->items[i];
428+ if (m->params.count == args->count) {
429+ if (only) { only = NULL; break; }
430+ only = m;
431+ }
432+ }
433+ int reported = 0;
434+ for (int i = 0; only && i < args->count && !reported; i++) {
435+ Node *a = args->items[i];
436+ if (is_poly_arg(a)) continue;
437+ JType *pt = formal(only, recv, i, 2);
438+ if (a->type->nullable && ty_assignable(a->type, pt, 1) && !ty_assignable(a->type, pt, 0)) {
439+ cerr(a, "possible null: argument %d of nullable type %s cannot be passed to non-null parameter '%s' of type %s; check for null first",
440+ i + 1, ty_str(a->type), ((JParam *)only->params.items[i])->name, ty_str(pt));
441+ reported = 1;
442+ }
443+ }
444+ if (reported) {
445+ /* already explained */
446+ } else if (cands->count == 1) {
447+ JMethod *m = cands->items[0];
448+ cerr(at, "%s %s cannot be applied to %s", what, sig_str(m), args_str(args));
449+ } else {
450+ JBuf b = {0};
451+ for (int i = 0; i < cands->count; i++) {
452+ j_buf_add(&b, "\n candidate: ");
453+ j_buf_add(&b, sig_str(cands->items[i]));
454+ }
455+ cerr(at, "no %s matches %s%s", what, args_str(args), b.data ? b.data : "");
456+ }
457+ *ret = ty_error();
458+ return NULL;
459+ }
460+ int best = 0;
461+ if (nf > 1) {
462+ int ok = 0;
463+ for (int i = 0; i < nf; i++) {
464+ int all = 1;
465+ for (int j = 0; j < nf; j++) {
466+ if (i != j && !more_specific(found[i].m, found[j].m)) all = 0;
467+ }
468+ if (all) { best = i; ok = 1; break; }
469+ }
470+ for (int i = 0; i < args->count && !ok; i++) {
471+ Node *a = args->items[i];
472+ if (!is_poly_arg(a) && a->type->kind == TY_ERROR) ok = 1; /* an earlier error already explains it */
473+ }
474+ if (!ok) {
475+ cerr(at, "ambiguous %s call: both %s and %s match %s", what, sig_str(found[0].m), sig_str(found[1].m), args_str(args));
476+ *ret = ty_error();
477+ return NULL;
478+ }
479+ }
480+ JMethod *m = found[best].m;
481+ JType **bind = found[best].bind;
482+ int phase = found[best].phase;
483+ /* return-type driven inference (e.g. List<String> x = identity(...)) */
484+ JType *rt = member_type(recv, m->cls, m->ret);
485+ if (!expected && m->tparams.count && at->kind == N_CALL && mentions_free(rt, m, bind)) at->flags |= 0x100;
486+ if (expected && m->tparams.count && mentions_free(rt, m, bind)) unify(rt, expected, m, bind);
487+ /* complete lambdas and method references now that argument-derived bindings exist */
488+ for (int i = 0; i < args->count; i++) {
489+ Node *a = args->items[i];
490+ if (!is_poly_arg(a)) continue;
491+ JType *pt = msubst(formal(m, recv, i, phase), m, bind);
492+ if (is_diamond_new(a) || is_open_generic_call(a)) {
493+ JType *nt = check_expr(cx, a, pt->kind == TY_TVAR ? NULL : pt);
494+ unify(formal(m, recv, i, phase), nt, m, bind);
495+ continue;
496+ }
497+ JType *res = NULL;
498+ JType *tt = check_lambda(cx, a, pt, &res);
499+ if (res && tt->kind == TY_CLASS) {
500+ JMethod *sam = jt_find_sam(tt->cls);
501+ if (sam) unify(member_type(pt, sam->cls, sam->ret), res, m, bind);
502+ }
503+ }
504+ /* re-instantiate lambda node types with the final bindings */
505+ for (int i = 0; i < args->count; i++) {
506+ Node *a = args->items[i];
507+ if (is_poly_arg(a) && !is_diamond_new(a) && !is_open_generic_call(a)) a->type = msubst(formal(m, recv, i, phase), m, bind);
508+ }
509+ JType **fb;
510+ fill_bind(m, bind, 1, &fb);
511+ *ret = m->tparams.count ? ty_subst(rt, &m->tparams, fb, m->tparams.count) : rt;
512+ if (bind_out) *bind_out = fb;
513+ for (int i = 0; i < m->throws.count; i++) {
514+ JType *tt = member_type(recv, m->cls, m->throws.items[i]);
515+ if (m->tparams.count) tt = ty_subst(tt, &m->tparams, fb, m->tparams.count);
516+ report_throw(cx, at, tt);
517+ }
518+ if (m->flags & M_PRIVATE ? 0 : 0) {}
519+ if (!accessible(cx, m->cls, m->flags)) {
520+ cerr(at, "%s '%s' has %s access in %s", what, m->is_ctor ? m->cls->name : m->name,
521+ (m->flags & M_PRIVATE) ? "private" : (m->flags & M_PROTECTED) ? "protected" : "package-private", m->cls->name);
522+ }
523+ return m;
524+}
525+
526+static void check_args(Ctx *cx, JList *args) {
527+ for (int i = 0; i < args->count; i++) {
528+ Node *a = args->items[i];
529+ if (is_poly_arg(a)) continue;
530+ check_expr(cx, a, NULL);
531+ if (a->type->kind == TY_VOID) cerr(a, "a void expression cannot be used as an argument");
532+ }
533+}
534+
535+void check_args_for_ctor(Ctx *cx, Node *at, JClass *c, JType *ctype, JList *args, Node *call_site, JMethod **chosen) {
536+ (void)call_site;
537+ jt_complete(c);
538+ check_args(cx, args);
539+ JType *ret;
540+ JMethod *m = select_method(cx, at, &c->ctors, args, ctype, NULL, "constructor", &ret, NULL);
541+ if (chosen) *chosen = m;
542+}
543+
544+/* ---------------------------------------------------------------- lambdas and method references */
545+
546+static JType *box_result(JType *t) { return ty_is_prim(t) ? ty_box(t) : t; }
547+
548+JType *check_lambda(Ctx *cx, Node *lam, JType *target, JType **result) {
549+ if (result) *result = NULL;
550+ if (!target || !jt_is_functional(target)) {
551+ if (target && target->kind != TY_ERROR) cerr(lam, "%s is not a functional interface; a lambda needs one as its target type", ty_str(target));
552+ else if (!target) cerr(lam, "cannot infer the type of this lambda; it needs a functional interface target type");
553+ lam->type = ty_error();
554+ return lam->type;
555+ }
556+ JMethod *sam = jt_find_sam(target->cls);
557+ lam->sym = sam;
558+ lam->type = target;
559+ if (lam->kind == N_METHODREF) {
560+ /* Type::method, expr::method, Type::new */
561+ Node *tn = lam->a;
562+ JType *recv = check_expr_ex(cx, tn, NULL, 1);
563+ int is_type = tn->rk == R_CLASS;
564+ int np = sam->params.count;
565+ JType **pts = j_alloc(sizeof(JType *) * (size_t)(np ? np : 1));
566+ for (int i = 0; i < np; i++) pts[i] = member_type(target, sam->cls, ((JParam *)sam->params.items[i])->type);
567+ JMethod *found = NULL;
568+ JType *rt = NULL;
569+ if (!strcmp(lam->name, "new")) {
570+ if (!is_type) { cerr(lam, "'::new' needs a class"); return ty_error(); }
571+ JClass *c = tn->sym;
572+ jt_complete(c);
573+ for (int i = 0; i < c->ctors.count && !found; i++) {
574+ JMethod *k = c->ctors.items[i];
575+ if (k->params.count != np) continue;
576+ int ok = 1;
577+ for (int j = 0; j < np; j++) {
578+ if (!ty_assignable(pts[j], ((JParam *)k->params.items[j])->type, 1) && !(pts[j]->kind == TY_TVAR)) ok = 0;
579+ }
580+ if (ok) found = k;
581+ }
582+ rt = recv;
583+ } else {
584+ JClass *c = NULL;
585+ JType *rcv = recv;
586+ if (is_type) c = tn->sym;
587+ else if (recv->kind == TY_CLASS || recv->kind == TY_TVAR) { rcv = recv_class(recv); c = rcv->cls; }
588+ if (!c) { cerr(lam, "cannot take a method reference on type %s", ty_str(recv)); return ty_error(); }
589+ JList all = {0};
590+ jt_collect_methods(c, lam->name, &all);
591+ for (int i = 0; i < all.count && !found; i++) {
592+ JMethod *k = all.items[i];
593+ int stat = (k->flags & M_STATIC) != 0;
594+ int want = (is_type && !stat) ? np - 1 : np;
595+ if (k->params.count != want) continue;
596+ int off = (is_type && !stat) ? 1 : 0;
597+ int ok = 1;
598+ for (int j = 0; j < want; j++) {
599+ JType *pt = member_type(rcv, k->cls, ((JParam *)k->params.items[j])->type);
600+ JType *at2 = pts[j + off];
601+ if (at2->kind == TY_TVAR || pt->kind == TY_TVAR) continue;
602+ if (!ty_assignable(at2, ty_with_null(pt, 1), 1)) ok = 0;
603+ }
604+ if (ok) {
605+ found = k;
606+ rt = member_type(rcv, k->cls, k->ret);
607+ if (k->tparams.count) {
608+ JType **eb;
609+ JType **empty = j_alloc(sizeof(JType *) * (size_t)k->tparams.count);
610+ fill_bind(k, empty, 1, &eb);
611+ rt = ty_subst(rt, &k->tparams, eb, k->tparams.count);
612+ }
613+ for (int j = 0; j < k->throws.count; j++) report_throw(cx, lam, k->throws.items[j]);
614+ }
615+ }
616+ }
617+ if (!found) {
618+ cerr(lam, "no method '%s' of %s matches the signature of %s", lam->name, ty_str(recv), ty_str(target));
619+ lam->type = ty_error();
620+ return lam->type;
621+ }
622+ lam->sym2 = found;
623+ if (result && rt && rt->kind != TY_VOID) *result = box_result(rt);
624+ return target;
625+ }
626+ /* lambda expression */
627+ int np = sam->params.count;
628+ if (lam->l1.count != np) {
629+ cerr(lam, "lambda has %d parameter(s) but %s expects %d", lam->l1.count, ty_str(target), np);
630+ lam->type = ty_error();
631+ return lam->type;
632+ }
633+ JType *sam_ret = member_type(target, sam->cls, sam->ret);
634+ int mark;
635+ push_scope(cx, &mark);
636+ int saved_base = cx->lambda_base;
637+ int saved_loop = cx->loop_depth, saved_switch = cx->switch_depth;
638+ JType *saved_ret = cx->ret;
639+ JList *saved_rt = cx->ret_types;
640+ int saved_rv = cx->ret_seen_void;
641+ ExFrame *saved_ex = cx->ex;
642+ cx->lambda_base = cx->locals.count;
643+ cx->loop_depth = cx->switch_depth = 0;
644+ cx->lambda_depth++;
645+ ExFrame fr = {0};
646+ fr.kind = F_LAMBDA;
647+ fr.up = NULL; /* a lambda body is its own checked-exception boundary */
648+ for (int i = 0; i < sam->throws.count; i++) j_list_push(&fr.types, member_type(target, sam->cls, sam->throws.items[i]));
649+ cx->ex = &fr;
650+ for (int i = 0; i < np; i++) {
651+ Node *p = lam->l1.items[i];
652+ JType *pt = member_type(target, sam->cls, ((JParam *)sam->params.items[i])->type);
653+ if (p->a) {
654+ JType *dt = jt_resolve_type(p->a, cx->cls, cx->method, 1);
655+ if (!ty_assignable(pt, dt, 1) && pt->kind != TY_TVAR) cerr(p, "lambda parameter '%s' has type %s but %s expects %s", p->name, ty_str(dt), ty_str(target), ty_str(pt));
656+ pt = dt;
657+ } else if (pt->kind == TY_TVAR && pt->tv->owner_m) {
658+ pt = ty_erase_bound(pt); /* uninferred method type variable */
659+ }
660+ JLocal *l = declare_local(cx, p, p->name, pt, p->flags, 1);
661+ l->has_init = 1;
662+ }
663+ JList rts = {0};
664+ JType *res = NULL;
665+ if (lam->b->kind == N_BLOCK) {
666+ int infer = sam_ret->kind == TY_TVAR || (sam_ret->kind == TY_CLASS && sam_ret->nargs && sam_ret->args[0]->kind == TY_TVAR && 0);
667+ for (int i = 0, any = 0; i < 1 && !any; i++) any = 1;
668+ cx->ret = (sam_ret->kind == TY_VOID || infer == 0) ? sam_ret : NULL;
669+ if (sam_ret->kind == TY_TVAR && sam_ret->tv->owner_m) cx->ret = NULL;
670+ cx->ret_types = &rts;
671+ cx->ret_seen_void = 0;
672+ check_block_body(cx, lam->b);
673+ if (cx->ret && cx->ret->kind != TY_VOID && can_complete(lam->b)) cerr(lam, "lambda body is missing a return statement");
674+ if (rts.count) {
675+ res = rts.items[0];
676+ for (int i = 1; i < rts.count; i++) {
677+ JType *o = rts.items[i];
678+ if (ty_assignable(res, o, 1)) res = o;
679+ }
680+ }
681+ } else {
682+ JType *want = (sam_ret->kind == TY_VOID || (sam_ret->kind == TY_TVAR && sam_ret->tv->owner_m)) ? NULL : sam_ret;
683+ JType *bt = check_expr(cx, lam->b, want);
684+ if (sam_ret->kind == TY_VOID) {
685+ if (!is_stmt_expr(lam->b) && bt->kind != TY_ERROR) cerr(lam->b, "lambda body is not a statement; %s expects no return value", ty_str(target));
686+ } else if (want) {
687+ check_assignable(cx, lam->b, bt, want, "lambda result");
688+ }
689+ if (bt->kind != TY_VOID) res = bt;
690+ }
691+ cx->ex = saved_ex;
692+ cx->lambda_depth--;
693+ cx->loop_depth = saved_loop;
694+ cx->switch_depth = saved_switch;
695+ cx->lambda_base = saved_base;
696+ cx->ret = saved_ret;
697+ cx->ret_types = saved_rt;
698+ cx->ret_seen_void = saved_rv;
699+ pop_scope(cx, mark);
700+ if (result && res && res->kind != TY_NULL) *result = box_result(res);
701+ return target;
702+}
703+
704+/* ---------------------------------------------------------------- names and members */
705+
706+static JType *class_ref(Node *n, JClass *c) {
707+ n->rk = R_CLASS;
708+ n->sym = c;
709+ jt_complete(c);
710+ return ty_class(c, NULL, 0);
711+}
712+
713+static JType *resolve_simple_type_name(Ctx *cx, Node *n) {
714+ Node *tn = node_new(N_TYPE, n->path, n->line, n->col);
715+ tn->name = n->name;
716+ JType *t = jt_resolve_type(tn, cx->cls, cx->method, 0);
717+ if (t->kind == TY_CLASS) return class_ref(n, t->cls);
718+ return NULL;
719+}
720+
721+static JType *field_value_type(JType *recv, JField *f) {
722+ return member_type(recv, f->cls, f->type);
723+}
724+
725+static void deref_check(Node *at, Node *target, JType *t) {
726+ if (t->nullable) {
727+ const char *nm = target->kind == N_NAME ? target->name : NULL;
728+ if (nm) cerr(at, "possible null dereference: '%s' has nullable type %s; check it for null first", nm, ty_str(t));
729+ else cerr(at, "possible null dereference: the %s of type %s may be null; check it for null first", what_kind(t), ty_str(t));
730+ }
731+}
732+
733+static JType *check_name(Ctx *cx, Node *n, int allow_type) {
734+ JLocal *l = lookup_local(cx, n->name);
735+ if (l) {
736+ n->rk = R_LOCAL;
737+ n->sym = l;
738+ if (l->index < cx->lambda_base) {
739+ if (!l->captured_at) l->captured_at = n;
740+ j_list_push(&cx->captures, l);
741+ }
742+ return narrowed(l);
743+ }
744+ for (JClass *c = cx->cls; c; c = c->outer) {
745+ JField *f = jt_find_field(c, n->name);
746+ if (!f) continue;
747+ if (f->flags & M_STATIC) {
748+ n->rk = f->is_enum_const ? R_ENUM_CONST : R_STATIC_FIELD;
749+ n->sym = f;
750+ if (!accessible(cx, f->cls, f->flags)) cerr(n, "field '%s' has private access in %s", f->name, f->cls->name);
751+ return f->type;
752+ }
753+ if (cx->is_static || c != cx->cls) {
754+ cerr(n, "non-static field '%s' cannot be referenced from a static context", n->name);
755+ return ty_error();
756+ }
757+ n->rk = R_FIELD;
758+ n->sym = f;
759+ if (!accessible(cx, f->cls, f->flags)) cerr(n, "field '%s' has private access in %s", f->name, f->cls->name);
760+ return field_value_type(cx->cls->self_type, f);
761+ }
762+ JType *ct = resolve_simple_type_name(cx, n);
763+ if (ct) {
764+ if (!allow_type) cerr(n, "'%s' is a type, not a value", n->name);
765+ return ct;
766+ }
767+ if (allow_type && n->name[0] >= 'a' && n->name[0] <= 'z') {
768+ n->rk = R_PACKAGE;
769+ n->sym2 = (void *)n->name;
770+ return ty_error();
771+ }
772+ cerr(n, "cannot find symbol '%s'", n->name);
773+ return ty_error();
774+}
775+
776+static JType *check_fieldacc(Ctx *cx, Node *n, int allow_type) {
777+ Node *tg = n->a;
778+ JType *tt = check_expr_ex(cx, tg, NULL, 1);
779+ if (tt->kind == TY_ERROR && tg->rk != R_PACKAGE) return ty_error();
780+ if (tg->rk == R_PACKAGE) {
781+ const char *fq = j_fmt("%s.%s", (const char *)tg->sym2, n->name);
782+ JClass *c = jt_find_class(fq);
783+ if (c) return class_ref(n, c);
784+ n->rk = R_PACKAGE;
785+ n->sym2 = (void *)fq;
786+ if (!allow_type) cerr(n, "cannot find symbol '%s'", fq);
787+ return ty_error();
788+ }
789+ if (tg->rk == R_CLASS) {
790+ JClass *c = tg->sym;
791+ JField *f = jt_find_field(c, n->name);
792+ if (f) {
793+ if (!(f->flags & M_STATIC)) {
794+ cerr(n, "non-static field '%s' cannot be referenced from a static context", n->name);
795+ return ty_error();
796+ }
797+ if (!accessible(cx, f->cls, f->flags)) cerr(n, "field '%s' has private access in %s", f->name, f->cls->name);
798+ n->rk = f->is_enum_const ? R_ENUM_CONST : R_STATIC_FIELD;
799+ n->sym = f;
800+ return f->type;
801+ }
802+ JClass *nc = jt_find_class(j_fmt("%s.%s", c->qname, n->name));
803+ if (nc) {
804+ if (!allow_type) cerr(n, "'%s' is a type, not a value", n->name);
805+ return class_ref(n, nc);
806+ }
807+ cerr(n, "cannot find symbol '%s' in %s", n->name, c->name);
808+ return ty_error();
809+ }
810+ if (tt->kind == TY_ARRAY && !strcmp(n->name, "length")) {
811+ deref_check(n, tg, tt);
812+ n->rk = R_ARRAYLEN;
813+ return TINT;
814+ }
815+ deref_check(n, tg, tt);
816+ JType *rc = recv_class(tt);
817+ if (rc->kind != TY_CLASS) {
818+ cerr(n, "type %s has no field '%s'", ty_str(tt), n->name);
819+ return ty_error();
820+ }
821+ JField *f = jt_find_field(rc->cls, n->name);
822+ if (!f) {
823+ cerr(n, "cannot find field '%s' in %s", n->name, ty_str(tt));
824+ return ty_error();
825+ }
826+ if (!accessible(cx, f->cls, f->flags)) cerr(n, "field '%s' has private access in %s", f->name, f->cls->name);
827+ n->rk = (f->flags & M_STATIC) ? R_STATIC_FIELD : (tg->kind == N_THIS ? R_THIS_FIELD : R_FIELD);
828+ n->sym = f;
829+ return field_value_type(rc, f);
830+}
831+
832+/* ---------------------------------------------------------------- calls */
833+
834+static JType *check_call(Ctx *cx, Node *n, JType *expected) {
835+ check_args(cx, &n->l1);
836+ JList cands = {0};
837+ JType *recv = NULL;
838+ int static_only = 0;
839+ int unqualified = 0;
840+ if (!n->a) {
841+ unqualified = 1;
842+ for (JClass *c = cx->cls; c; c = c->outer) {
843+ jt_collect_methods(c, n->name, &cands);
844+ if (cands.count) { recv = c->self_type; static_only = c != cx->cls; break; }
845+ }
846+ if (!cands.count) {
847+ cerr(n, "cannot find method '%s'", n->name);
848+ return ty_error();
849+ }
850+ } else if (n->flags == 1) {
851+ if (!cx->cls->super) { cerr(n, "no superclass"); return ty_error(); }
852+ recv = cx->cls->super;
853+ jt_collect_methods(recv->cls, n->name, &cands);
854+ if (cx->is_static) cerr(n, "'super' cannot be used in a static context");
855+ } else {
856+ JType *tt = check_expr_ex(cx, n->a, NULL, 1);
857+ if (tt->kind == TY_ERROR && n->a->rk != R_CLASS) return ty_error();
858+ if (n->a->rk == R_PACKAGE) { cerr(n->a, "cannot find symbol '%s'", (const char *)n->a->sym2); return ty_error(); }
859+ if (n->a->rk == R_CLASS) {
860+ static_only = 1;
861+ recv = tt;
862+ jt_collect_methods(((JClass *)n->a->sym), n->name, &cands);
863+ } else {
864+ deref_check(n, n->a, tt);
865+ recv = recv_class(tt);
866+ if (recv->kind == TY_ARRAY) recv = jt_object_type();
867+ if (recv->kind != TY_CLASS) {
868+ cerr(n, "cannot call method '%s' on type %s", n->name, ty_str(tt));
869+ return ty_error();
870+ }
871+ jt_collect_methods(recv->cls, n->name, &cands);
872+ }
873+ if (!cands.count) {
874+ cerr(n, "cannot find method '%s' in %s", n->name, ty_str(tt));
875+ return ty_error();
876+ }
877+ }
878+ JType *ret;
879+ JType **bind = NULL;
880+ JMethod *m = select_method(cx, n, &cands, &n->l1, recv, expected, "method", &ret, &bind);
881+ if (!m) return ty_error();
882+ n->sym = m;
883+ if (m->flags & M_STATIC) {
884+ n->rk = R_STATIC_METHOD;
885+ } else {
886+ if (static_only || (unqualified && cx->is_static)) {
887+ cerr(n, "non-static method '%s' cannot be referenced from a static context", m->name);
888+ return ty_error();
889+ }
890+ if ((m->flags & M_ABSTRACT) && n->flags == 1) {
891+ cerr(n, "abstract method '%s' cannot be called through 'super'", m->name);
892+ return ty_error();
893+ }
894+ n->rk = n->flags == 1 ? R_SUPER_METHOD : R_METHOD;
895+ }
896+ return ret;
897+}
898+
899+void check_ctor_call(Ctx *cx, Node *n) {
900+ int is_this = !strcmp(n->name, "this");
901+ JClass *target = is_this ? cx->cls : (cx->cls->super ? cx->cls->super->cls : NULL);
902+ if (!cx->in_ctor) { cerr(n, "'%s(...)' is only allowed as the first statement of a constructor", n->name); return; }
903+ if (!target) return;
904+ JType *ctype = is_this ? cx->cls->self_type : cx->cls->super;
905+ JMethod *m = NULL;
906+ check_args_for_ctor(cx, n, target, ctype, &n->l1, n, &m);
907+ n->sym = m;
908+ n->rk = R_CTOR;
909+}
910+
911+JType *check_new_object(Ctx *cx, Node *n, JType *expected) {
912+ Node *tn = n->a;
913+ JType *t = jt_resolve_type(tn, cx->cls, cx->method, 1);
914+ if (t->kind != TY_CLASS) {
915+ if (t->kind != TY_ERROR) cerr(n, "cannot instantiate type %s", ty_str(t));
916+ check_args(cx, &n->l1);
917+ return ty_error();
918+ }
919+ JClass *c = t->cls;
920+ jt_complete(c);
921+ if (c->kind == N_INTERFACE || (c->flags & M_ABSTRACT)) cerr(n, "%s is abstract; it cannot be instantiated", c->name);
922+ else if (c->kind == N_ENUM) cerr(n, "enum %s cannot be instantiated with 'new'", c->name);
923+ if (tn->flags & 0x10000) { /* diamond: infer arguments from the target type */
924+ int nt = c->tparams.count;
925+ JType **targs = j_alloc(sizeof(JType *) * (size_t)(nt ? nt : 1));
926+ int got = 0;
927+ if (expected && expected->kind == TY_CLASS && expected->nargs) {
928+ JType *self = c->self_type;
929+ JType *as = ty_as_super(self, expected->cls);
930+ if (as && as->nargs == expected->nargs) {
931+ for (int i = 0; i < nt; i++) targs[i] = NULL;
932+ for (int k = 0; k < as->nargs; k++) {
933+ if (as->args[k]->kind == TY_TVAR) {
934+ for (int i = 0; i < nt; i++) {
935+ if (c->tparams.items[i] == as->args[k]->tv) targs[i] = expected->args[k];
936+ }
937+ }
938+ }
939+ got = 1;
940+ }
941+ }
942+ for (int i = 0; i < nt; i++) {
943+ if (!got || !targs[i]) {
944+ JTVar *tv = c->tparams.items[i];
945+ targs[i] = tv->bound ? ty_erase_bound(tv->bound) : jt_object_type();
946+ }
947+ }
948+ t = ty_class(c, targs, nt);
949+ } else if (c->tparams.count && !t->nargs) {
950+ cerr(n, "generic class %s needs type arguments; write %s<...> or use the diamond '<>'", c->name, c->name);
951+ }
952+ JMethod *ctor = NULL;
953+ check_args_for_ctor(cx, n, c, t, &n->l1, n, &ctor);
954+ n->sym = ctor;
955+ n->rk = R_CTOR;
956+ return ty_with_null(t, 0);
957+}
958+
959+/* ---------------------------------------------------------------- operators */
960+
961+static int is_bool(JType *t) { return ty_unbox(t)->kind == TY_BOOL && !t->nullable; }
962+
963+static void bad_operands(Node *n, JType *a, JType *b) {
964+ cerr(n, "operator '%s' cannot be applied to %s and %s", n->name, ty_str(a), ty_str(b));
965+}
966+
967+static void non_null_operand(Node *n, Node *e, JType *t) {
968+ if (t->nullable && ty_is_prim(ty_unbox(t))) cerr(e, "possible null: nullable %s cannot be used with operator '%s'; check for null first", ty_str(t), n->name);
969+}
970+
971+static JType *check_binary(Ctx *cx, Node *n) {
972+ const char *op = n->name;
973+ if (!strcmp(op, "&&") || !strcmp(op, "||")) {
974+ JType *a = check_expr(cx, n->a, TBOOL);
975+ JList undo = {0};
976+ narrow_apply(cx, n->a, op[0] == '&', &undo);
977+ JType *b = check_expr(cx, n->b, TBOOL);
978+ narrow_undo(&undo);
979+ if (a->kind == TY_ERROR || b->kind == TY_ERROR) return TBOOL;
980+ if (!is_bool(a) || !is_bool(b)) bad_operands(n, a, b);
981+ return TBOOL;
982+ }
983+ JType *a = check_expr(cx, n->a, NULL);
984+ JType *b = check_expr(cx, n->b, NULL);
985+ if (a->kind == TY_ERROR || b->kind == TY_ERROR) {
986+ if (!strcmp(op, "+") && (ty_is_string(a) || ty_is_string(b))) return jt_string_type();
987+ return (op[0] == '<' || op[0] == '>' || !strcmp(op, "==") || !strcmp(op, "!=")) && strcmp(op, "<<") && strcmp(op, ">>") && strcmp(op, ">>>") ? TBOOL : ty_error();
988+ }
989+ if (a->kind == TY_VOID || b->kind == TY_VOID) {
990+ cerr(n, "operator '%s' cannot be applied to a void expression", op);
991+ return ty_error();
992+ }
993+ if (!strcmp(op, "+") && (ty_is_string(a) || ty_is_string(b))) return jt_string_type();
994+ if (!strcmp(op, "+") || !strcmp(op, "-") || !strcmp(op, "*") || !strcmp(op, "/") || !strcmp(op, "%")) {
995+ JType *r = ty_binary_numeric(a, b);
996+ if (!r) { bad_operands(n, a, b); return ty_error(); }
997+ non_null_operand(n, n->a, a);
998+ non_null_operand(n, n->b, b);
999+ return r;
1000+ }
1001+ if (!strcmp(op, "<<") || !strcmp(op, ">>") || !strcmp(op, ">>>")) {
1002+ JType *ua = ty_unbox(a), *ub = ty_unbox(b);
1003+ if (!ty_is_integral(ua) || !ty_is_integral(ub)) { bad_operands(n, a, b); return ty_error(); }
1004+ return ua->kind == TY_LONG ? ty_prim(TY_LONG) : TINT;
1005+ }
1006+ if (!strcmp(op, "<") || !strcmp(op, "<=") || !strcmp(op, ">") || !strcmp(op, ">=")) {
1007+ if (!ty_binary_numeric(a, b)) { bad_operands(n, a, b); return ty_error(); }
1008+ non_null_operand(n, n->a, a);
1009+ non_null_operand(n, n->b, b);
1010+ return TBOOL;
1011+ }
1012+ if (!strcmp(op, "==") || !strcmp(op, "!=")) {
1013+ if (ty_is_prim(a) || ty_is_prim(b)) {
1014+ JType *ua = ty_unbox(a), *ub = ty_unbox(b);
1015+ if ((ty_is_numeric(ua) && ty_is_numeric(ub)) || (ua->kind == TY_BOOL && ub->kind == TY_BOOL)) {
1016+ non_null_operand(n, n->a, a);
1017+ non_null_operand(n, n->b, b);
1018+ return TBOOL;
1019+ }
1020+ bad_operands(n, a, b);
1021+ return TBOOL;
1022+ }
1023+ if (a->kind == TY_NULL || b->kind == TY_NULL) {
1024+ JType *o = a->kind == TY_NULL ? b : a;
1025+ if (o->kind != TY_NULL && !o->nullable) {
1026+ cerr(n, "comparison with null is always %s: %s is a non-null type", op[0] == '=' ? "false" : "true", ty_str(o));
1027+ }
1028+ return TBOOL;
1029+ }
1030+ if (!ty_assignable(a, ty_with_null(b, 1), 1) && !ty_assignable(b, ty_with_null(a, 1), 1)) {
1031+ /* unrelated interfaces/classes are never equal */
1032+ cerr(n, "incomparable types: %s and %s", ty_str(a), ty_str(b));
1033+ }
1034+ return TBOOL;
1035+ }
1036+ /* & | ^ */
1037+ if (is_bool(a) && is_bool(b)) return TBOOL;
1038+ JType *ua = ty_unbox(a), *ub = ty_unbox(b);
1039+ if (ty_is_integral(ua) && ty_is_integral(ub)) return ty_binary_numeric(ua, ub);
1040+ bad_operands(n, a, b);
1041+ return ty_error();
1042+}
1043+
1044+static int is_variable(Node *e) {
1045+ return (e->kind == N_NAME && (e->rk == R_LOCAL || e->rk == R_FIELD || e->rk == R_STATIC_FIELD)) ||
1046+ (e->kind == N_FIELDACC && (e->rk == R_FIELD || e->rk == R_STATIC_FIELD || e->rk == R_THIS_FIELD)) ||
1047+ e->kind == N_INDEX;
1048+}
1049+
1050+static void check_final_assign(Ctx *cx, Node *lhs, int compound) {
1051+ if (lhs->kind == N_NAME && lhs->rk == R_LOCAL) {
1052+ JLocal *l = lhs->sym;
1053+ l->assigns++;
1054+ if (l->flags & M_FINAL) {
1055+ if (l->has_init || l->is_param || compound || l->assigns > 1) cerr(lhs, "cannot assign a value to final variable '%s'", l->name);
1056+ }
1057+ if (l->index < cx->lambda_base) cerr(lhs, "local variable '%s' is accessed from a lambda; it must be effectively final", l->name);
1058+ return;
1059+ }
1060+ if ((lhs->kind == N_NAME || lhs->kind == N_FIELDACC) &&
1061+ (lhs->rk == R_FIELD || lhs->rk == R_STATIC_FIELD || lhs->rk == R_THIS_FIELD)) {
1062+ JField *f = lhs->sym;
1063+ if (f->flags & M_FINAL) {
1064+ int ok = !compound && f->cls == cx->cls && (f->decl ? !f->decl->b : 1) &&
1065+ (((f->flags & M_STATIC) && cx->method == NULL && cx->is_static) ||
1066+ (!(f->flags & M_STATIC) && cx->in_ctor && (lhs->kind == N_NAME || lhs->a->kind == N_THIS)));
1067+ if (!ok) cerr(lhs, "cannot assign a value to final field '%s'", f->name);
1068+ }
1069+ } else if (lhs->kind == N_FIELDACC && lhs->rk == R_ARRAYLEN) {
1070+ cerr(lhs, "cannot assign a value to final field 'length'");
1071+ }
1072+}
1073+
1074+static JType *check_assign(Ctx *cx, Node *n) {
1075+ JType *lt = check_expr(cx, n->a, NULL);
1076+ if (lt->kind == TY_ERROR) { check_expr(cx, n->b, NULL); return ty_error(); }
1077+ if (!is_variable(n->a)) {
1078+ cerr(n->a, "the left-hand side of an assignment must be a variable");
1079+ check_expr(cx, n->b, NULL);
1080+ return ty_error();
1081+ }
1082+ JLocal *l = local_of(n->a);
1083+ JType *declared = l ? l->type : lt; /* narrowing must not weaken the declared type */
1084+ if (n->a->kind == N_NAME && n->a->rk == R_LOCAL) lt = declared;
1085+ int compound = strcmp(n->name, "=") != 0;
1086+ if (!compound) {
1087+ JType *rt = check_expr(cx, n->b, lt);
1088+ check_assignable(cx, n->b, rt, lt, "assignment");
1089+ check_final_assign(cx, n->a, 0);
1090+ if (l) l->nonnull = (declared->nullable && rt->kind != TY_ERROR && rt->kind != TY_NULL && !rt->nullable);
1091+ return lt;
1092+ }
1093+ JType *rt = check_expr(cx, n->b, NULL);
1094+ check_final_assign(cx, n->a, 1);
1095+ if (rt->kind == TY_ERROR) return lt;
1096+ char op[4];
1097+ strncpy(op, n->name, 3);
1098+ op[strlen(n->name) - 1] = 0;
1099+ if (!strcmp(op, "+") && ty_is_string(lt)) return lt;
1100+ JType *ul = ty_unbox(lt), *ur = ty_unbox(rt);
1101+ int ok;
1102+ if (!strcmp(op, "&") || !strcmp(op, "|") || !strcmp(op, "^")) ok = (ul->kind == TY_BOOL && ur->kind == TY_BOOL) || (ty_is_integral(ul) && ty_is_integral(ur));
1103+ else if (!strcmp(op, "<<") || !strcmp(op, ">>")) ok = ty_is_integral(ul) && ty_is_integral(ur);
1104+ else ok = ty_is_numeric(ul) && ty_is_numeric(ur);
1105+ if (!ok) { cerr(n, "operator '%s' cannot be applied to %s and %s", n->name, ty_str(lt), ty_str(rt)); return lt; }
1106+ if (lt->nullable) cerr(n->a, "possible null: nullable %s cannot be used with operator '%s'; check for null first", ty_str(lt), n->name);
1107+ return lt;
1108+}
1109+
1110+static JType *check_unary(Ctx *cx, Node *n) {
1111+ const char *op = n->name;
1112+ if (!strcmp(op, "-") && n->a->kind == N_LIT_INT && !strcmp(n->a->name, "2147483648")) {
1113+ n->a->flags = 99;
1114+ }
1115+ JType *t = check_expr(cx, n->a, NULL);
1116+ if (t->kind == TY_ERROR) return t;
1117+ if (!strcmp(op, "!")) {
1118+ if (!is_bool(t)) { cerr(n, "operator '!' cannot be applied to %s", ty_str(t)); return TBOOL; }
1119+ return TBOOL;
1120+ }
1121+ if (!strcmp(op, "++") || !strcmp(op, "--")) {
1122+ JType *u = ty_unbox(t);
1123+ if (!ty_is_numeric(u)) { cerr(n, "operator '%s' cannot be applied to %s", op, ty_str(t)); return t; }
1124+ if (!is_variable(n->a)) cerr(n->a, "operand of '%s' must be a variable", op);
1125+ else check_final_assign(cx, n->a, 1);
1126+ return t;
1127+ }
1128+ JType *u = ty_unbox(t);
1129+ if (!strcmp(op, "~")) {
1130+ if (!ty_is_integral(u)) { cerr(n, "operator '~' cannot be applied to %s", ty_str(t)); return ty_error(); }
1131+ return u->kind == TY_LONG ? u : TINT;
1132+ }
1133+ if (!ty_is_numeric(u)) { cerr(n, "operator '%s' cannot be applied to %s", op, ty_str(t)); return ty_error(); }
1134+ non_null_operand(n, n->a, t);
1135+ return ty_binary_numeric(u, TINT);
1136+}
1137+
1138+static JType *common_type(Ctx *cx, Node *at, JType *a, JType *b, JType *expected) {
1139+ (void)cx; (void)at;
1140+ if (a->kind == TY_ERROR) return b;
1141+ if (b->kind == TY_ERROR) return a;
1142+ if (a->kind == TY_NULL && b->kind == TY_NULL) return a;
1143+ if (a->kind == TY_NULL) return ty_is_prim(b) ? ty_with_null(ty_box(b), 1) : ty_with_null(b, 1);
1144+ if (b->kind == TY_NULL) return ty_is_prim(a) ? ty_with_null(ty_box(a), 1) : ty_with_null(a, 1);
1145+ if (ty_is_prim(a) || ty_is_prim(b)) {
1146+ JType *n = ty_binary_numeric(a, b);
1147+ if (n) return n;
1148+ if (ty_unbox(a)->kind == TY_BOOL && ty_unbox(b)->kind == TY_BOOL) return TBOOL;
1149+ }
1150+ if (expected && ty_assignable(a, expected, 1) && ty_assignable(b, expected, 1)) return expected;
1151+ JType *ba = ty_is_prim(a) ? ty_box(a) : a, *bb = ty_is_prim(b) ? ty_box(b) : b;
1152+ int nul = ba->nullable || bb->nullable;
1153+ if (ty_assignable(ba, ty_with_null(bb, 1), 1)) return ty_with_null(bb, nul);
1154+ if (ty_assignable(bb, ty_with_null(ba, 1), 1)) return ty_with_null(ba, nul);
1155+ if (ba->kind == TY_CLASS) {
1156+ for (JClass *c = ba->cls; c; c = c->super ? c->super->cls : NULL) {
1157+ JType *s = ty_as_super(bb, c);
1158+ if (s && c != jt_object()) return ty_with_null(ty_as_super(ba, c), nul);
1159+ }
1160+ /* shared interface */
1161+ jt_complete(ba->cls);
1162+ for (int i = 0; i < ba->cls->ifaces.count; i++) {
1163+ JType *it = ba->cls->ifaces.items[i];
1164+ if (ty_as_super(bb, it->cls)) return ty_with_null(it, nul);
1165+ }
1166+ }
1167+ return ty_with_null(jt_object_type(), nul);
1168+}
1169+
1170+static JType *check_cond_expr(Ctx *cx, Node *n, JType *expected) {
1171+ check_cond(cx, n->a);
1172+ JList undo = {0};
1173+ narrow_apply(cx, n->a, 1, &undo);
1174+ JType *a = check_expr(cx, n->b, expected);
1175+ narrow_undo(&undo);
1176+ narrow_apply(cx, n->a, 0, &undo);
1177+ JType *b = check_expr(cx, n->c, expected);
1178+ narrow_undo(&undo);
1179+ if (a->kind == TY_VOID || b->kind == TY_VOID) { cerr(n, "branches of '?:' must produce a value"); return ty_error(); }
1180+ return common_type(cx, n, a, b, expected);
1181+}
1182+
1183+static int castable(JType *from, JType *to) {
1184+ if (ty_is_prim(from) && ty_is_prim(to)) {
1185+ return (from->kind == TY_BOOL) == (to->kind == TY_BOOL);
1186+ }
1187+ if (ty_is_prim(from)) return ty_assignable(from, to, 1);
1188+ if (ty_is_prim(to)) {
1189+ JType *u = ty_unbox(from);
1190+ if (ty_is_prim(u)) return (u->kind == TY_BOOL) == (to->kind == TY_BOOL);
1191+ return from->kind == TY_CLASS && (ty_is_object(from) || from->cls == jt_find_class("ash.lang.Number") || from->cls == jt_find_class("ash.lang.Comparable"));
1192+ }
1193+ if (ty_assignable(from, ty_with_null(to, 1), 1) || ty_assignable(to, ty_with_null(from, 1), 1)) return 1;
1194+ if (from->kind == TY_CLASS && to->kind == TY_CLASS) {
1195+ if (from->cls->kind == N_INTERFACE && !(to->cls->flags & M_FINAL) ) return 1;
1196+ if (to->cls->kind == N_INTERFACE && !(from->cls->flags & M_FINAL)) return 1;
1197+ }
1198+ if (from->kind == TY_TVAR || to->kind == TY_TVAR) return 1;
1199+ return 0;
1200+}
1201+
1202+static JType *check_cast(Ctx *cx, Node *n) {
1203+ JType *to = jt_resolve_type(n->a, cx->cls, cx->method, 1);
1204+ JType *from = check_expr(cx, n->b, NULL);
1205+ if (to->kind == TY_ERROR || from->kind == TY_ERROR) return to;
1206+ if (from->kind == TY_NULL) {
1207+ if (!to->nullable && ty_is_ref(to)) cerr(n, "cannot cast null to non-null type %s", ty_str(to));
1208+ return to;
1209+ }
1210+ if (from->nullable && !to->nullable && ty_is_ref(to) && !ty_is_prim(ty_unbox(to))) {
1211+ cerr(n, "possible null: casting nullable %s to non-null %s; check for null first", ty_str(from), ty_str(to));
1212+ }
1213+ if (!castable(from, to)) cerr(n, "inconvertible types: cannot cast %s to %s", ty_str(from), ty_str(to));
1214+ return to;
1215+}
1216+
1217+static JType *check_instanceof(Ctx *cx, Node *n) {
1218+ JType *e = check_expr(cx, n->a, NULL);
1219+ JType *t = jt_resolve_type(n->b, cx->cls, cx->method, 1);
1220+ if (e->kind != TY_ERROR && t->kind != TY_ERROR) {
1221+ if (ty_is_prim(e)) cerr(n->a, "instanceof needs a reference expression, found %s", ty_str(e));
1222+ else if (ty_is_prim(t)) cerr(n->b, "instanceof needs a reference type, found %s", ty_str(t));
1223+ else if (!castable(e, t)) cerr(n, "incompatible types: %s can never be an instance of %s", ty_str(e), ty_str(t));
1224+ }
1225+ if (n->name) {
1226+ JLocal *l = declare_local(cx, n, n->name, ty_with_null(t, 0), 0, 0);
1227+ l->is_temp = 1;
1228+ l->has_init = 1;
1229+ n->sym = l;
1230+ }
1231+ return TBOOL;
1232+}
1233+
1234+static JType *check_array(Ctx *cx, Node *n) {
1235+ JType *base = jt_resolve_type(n->a, cx->cls, cx->method, 1);
1236+ if (base->kind == TY_ERROR) return base;
1237+ /* declared-type form (`int[] a = {..}`) carries its dims in the type node */
1238+ for (int i = 0; i < (n->a->flags & 0xFFFF) && base->kind == TY_ARRAY; i++) base = base->elem;
1239+ JType *t = base;
1240+ for (int i = 0; i < n->flags; i++) t = ty_array(t);
1241+ for (int i = 0; i < n->l1.count; i++) check_coerce(cx, n->l1.items[i], TINT, "array dimension");
1242+ if (n->b) {
1243+ JType *elem = t->elem;
1244+ Node *init = n->b;
1245+ for (int i = 0; i < init->l1.count; i++) {
1246+ Node *e = init->l1.items[i];
1247+ if (e->kind == N_NEWARRAY) {
1248+ JType *it = check_expr(cx, e, elem);
1249+ check_assignable(cx, e, it, elem, "array element");
1250+ } else {
1251+ check_coerce(cx, e, elem, "array element");
1252+ }
1253+ }
1254+ }
1255+ return t;
1256+}
1257+
1258+/* ---------------------------------------------------------------- entry point */
1259+
1260+JType *check_expr_ex(Ctx *cx, Node *n, JType *expected, int allow_type);
1261+
1262+JType *check_expr_ex(Ctx *cx, Node *n, JType *expected, int allow_type) {
1263+ JType *t = NULL;
1264+ switch (n->kind) {
1265+ case N_LIT_INT: {
1266+ unsigned long long v = strtoull(n->name, NULL, 0);
1267+ if (v > 2147483647ULL && !(v == 2147483648ULL && n->flags == 99) && !(n->name[0] == '0' && (n->name[1] == 'x' || n->name[1] == 'X') && v <= 0xFFFFFFFFULL)) {
1268+ cerr(n, "integer number too large: %s (use an 'L' suffix for long)", n->name);
1269+ }
1270+ t = TINT;
1271+ break;
1272+ }
1273+ case N_LIT_LONG: t = ty_prim(TY_LONG); break;
1274+ case N_LIT_FLOAT: t = ty_prim(TY_FLOAT); break;
1275+ case N_LIT_DOUBLE: t = ty_prim(TY_DOUBLE); break;
1276+ case N_LIT_CHAR: t = ty_prim(TY_CHAR); break;
1277+ case N_LIT_STRING: t = jt_string_type(); break;
1278+ case N_LIT_BOOL: t = TBOOL; break;
1279+ case N_LIT_NULL: t = ty_null(); break;
1280+ case N_NAME: t = check_name(cx, n, allow_type); break;
1281+ case N_FIELDACC: t = check_fieldacc(cx, n, allow_type); break;
1282+ case N_THIS:
1283+ if (cx->is_static) { cerr(n, "'this' cannot be used in a static context"); t = ty_error(); }
1284+ else t = cx->cls->self_type;
1285+ break;
1286+ case N_SUPER:
1287+ cerr(n, "'super' can only be used as a method or constructor call target");
1288+ t = ty_error();
1289+ break;
1290+ case N_CALL: t = check_call(cx, n, expected); break;
1291+ case N_NEW: t = check_new_object(cx, n, expected); break;
1292+ case N_NEWARRAY: case N_ARRAYINIT: t = check_array(cx, n); break;
1293+ case N_INDEX: {
1294+ JType *a = check_expr(cx, n->a, NULL);
1295+ check_coerce(cx, n->b, TINT, "array index");
1296+ if (a->kind == TY_ERROR) { t = a; break; }
1297+ deref_check(n, n->a, a);
1298+ if (a->kind != TY_ARRAY) { cerr(n, "indexing requires an array, found %s", ty_str(a)); t = ty_error(); }
1299+ else t = a->elem;
1300+ break;
1301+ }
1302+ case N_UNARY: t = check_unary(cx, n); break;
1303+ case N_POSTFIX: {
1304+ JType *a = check_expr(cx, n->a, NULL);
1305+ JType *u = ty_unbox(a);
1306+ if (a->kind != TY_ERROR && !ty_is_numeric(u)) cerr(n, "operator '%s' cannot be applied to %s", n->name, ty_str(a));
1307+ else if (a->kind != TY_ERROR) {
1308+ if (!is_variable(n->a)) cerr(n->a, "operand of '%s' must be a variable", n->name);
1309+ else check_final_assign(cx, n->a, 1);
1310+ }
1311+ t = a;
1312+ break;
1313+ }
1314+ case N_BINARY: t = check_binary(cx, n); break;
1315+ case N_ASSIGN: t = check_assign(cx, n); break;
1316+ case N_COND: t = check_cond_expr(cx, n, expected); break;
1317+ case N_CAST: t = check_cast(cx, n); break;
1318+ case N_INSTANCEOF: t = check_instanceof(cx, n); break;
1319+ case N_LAMBDA: case N_METHODREF:
1320+ t = check_lambda(cx, n, expected, NULL);
1321+ break;
1322+ case N_SWITCH: t = check_switch(cx, n, expected); break;
1323+ default:
1324+ cerr(n, "unexpected expression");
1325+ t = ty_error();
1326+ }
1327+ n->type = t;
1328+ return t;
1329+}
1330+
1331+JType *check_expr(Ctx *cx, Node *n, JType *expected) {
1332+ JType *t = check_expr_ex(cx, n, expected, 0);
1333+ if (n->rk == R_CLASS || n->rk == R_PACKAGE) return n->type;
1334+ return t;
1335+}

hearth/front/src/jtypes/C/check_stmt.cadded

+506−0
Show diff (506 changed lines)
@@ -0,0 +1,506 @@
1+#include <stdlib.h>
2+#include <string.h>
3+
4+#include "jtypes/H/check.h"
5+
6+/* Statement checking: locals, control flow, exceptions, switch, reachability. */
7+
8+#define TBOOL ty_prim(TY_BOOL)
9+
10+/* ---------------------------------------------------------------- reachability */
11+
12+static int has_break(Node *n, int depth) {
13+ if (!n) return 0;
14+ switch (n->kind) {
15+ case N_BREAK: return depth == 0;
16+ case N_BLOCK: case N_SEQ:
17+ for (int i = 0; i < n->l1.count; i++) if (has_break(n->l1.items[i], depth)) return 1;
18+ return 0;
19+ case N_IF: return has_break(n->b, depth) || has_break(n->c, depth);
20+ case N_WHILE: case N_DO: case N_FOR: return has_break(n->b, depth + 1);
21+ case N_FOREACH: return has_break(n->c, depth + 1);
22+ case N_SWITCH:
23+ for (int i = 0; i < n->l1.count; i++) {
24+ Node *c = n->l1.items[i];
25+ if (has_break(c->b, depth + 1)) return 1;
26+ for (int j = 0; j < c->l2.count; j++) if (has_break(c->l2.items[j], depth + 1)) return 1;
27+ }
28+ return 0;
29+ case N_TRY:
30+ if (has_break(n->b, depth) || has_break(n->c, depth)) return 1;
31+ for (int i = 0; i < n->l1.count; i++) if (has_break(((Node *)n->l1.items[i])->b, depth)) return 1;
32+ return 0;
33+ default: return 0;
34+ }
35+}
36+
37+static int always_true(Node *c) { return !c || (c->kind == N_LIT_BOOL && !strcmp(c->name, "true")); }
38+
39+int can_complete(Node *n) {
40+ if (!n) return 1;
41+ switch (n->kind) {
42+ case N_RETURN: case N_THROW: case N_BREAK: case N_CONTINUE: return 0;
43+ case N_BLOCK: case N_SEQ:
44+ for (int i = 0; i < n->l1.count; i++) if (!can_complete(n->l1.items[i])) return 0;
45+ return 1;
46+ case N_IF:
47+ return !n->c || can_complete(n->b) || can_complete(n->c);
48+ case N_WHILE: return !always_true(n->a) || has_break(n->b, 0);
49+ case N_DO: return (!always_true(n->a) && can_complete(n->b)) || has_break(n->b, 0) || (!always_true(n->a));
50+ case N_FOR: return !always_true(n->a) || has_break(n->b, 0);
51+ case N_SWITCH: {
52+ int has_default = 0;
53+ for (int i = 0; i < n->l1.count; i++) if (((Node *)n->l1.items[i])->flags & 1) has_default = 1;
54+ if (!has_default) return 1;
55+ for (int i = 0; i < n->l1.count; i++) {
56+ Node *c = n->l1.items[i];
57+ if (c->str) {
58+ if (c->b->kind == N_BLOCK ? can_complete(c->b) : c->b->kind != N_THROW) return 1;
59+ if (has_break(c->b, 0)) return 1;
60+ } else {
61+ for (int j = 0; j < c->l2.count; j++) if (has_break(c->l2.items[j], 0)) return 1;
62+ if (i == n->l1.count - 1) {
63+ for (int j = 0; j < c->l2.count; j++) if (!can_complete(c->l2.items[j])) return 0;
64+ return 1;
65+ }
66+ }
67+ }
68+ return 0;
69+ }
70+ case N_TRY: {
71+ if (n->c && !can_complete(n->c)) return 0;
72+ if (can_complete(n->b)) return 1;
73+ for (int i = 0; i < n->l1.count; i++) if (can_complete(((Node *)n->l1.items[i])->b)) return 1;
74+ return 0;
75+ }
76+ default: return 1;
77+ }
78+}
79+
80+/* ---------------------------------------------------------------- blocks */
81+
82+static void check_stmts(Ctx *cx, JList *l) {
83+ int dead = 0;
84+ for (int i = 0; i < l->count; i++) {
85+ Node *s = l->items[i];
86+ if (dead && s->kind != N_EMPTY) {
87+ cerr(s, "unreachable statement");
88+ dead = 0;
89+ }
90+ check_stmt(cx, s);
91+ if (!can_complete(s)) dead = 1;
92+ }
93+}
94+
95+void check_block_body(Ctx *cx, Node *block) {
96+ int mark;
97+ push_scope(cx, &mark);
98+ check_stmts(cx, &block->l1);
99+ pop_scope(cx, mark);
100+}
101+
102+static void check_local(Ctx *cx, Node *n) {
103+ JType *t = NULL;
104+ JType *it = NULL;
105+ if (n->a) {
106+ t = jt_resolve_type(n->a, cx->cls, cx->method, 1);
107+ if (t->kind == TY_VOID) { cerr(n, "variable '%s' cannot have type void", n->name); t = ty_error(); }
108+ if (n->b) {
109+ it = check_expr(cx, n->b, t);
110+ check_assignable(cx, n->b, it, t, "initializer");
111+ }
112+ } else {
113+ if (!n->b) { cerr(n, "cannot infer the type of '%s' without an initializer", n->name); t = ty_error(); }
114+ else {
115+ it = check_expr(cx, n->b, NULL);
116+ if (it->kind == TY_NULL) { cerr(n, "cannot infer the type of '%s' from null; declare it explicitly", n->name); t = ty_error(); }
117+ else if (it->kind == TY_VOID) { cerr(n, "cannot infer the type of '%s' from a void expression", n->name); t = ty_error(); }
118+ else t = it;
119+ }
120+ }
121+ n->type = t;
122+ JLocal *l = declare_local(cx, n, n->name, t, n->flags, 0);
123+ l->has_init = n->b != NULL;
124+ n->sym = l;
125+ n->rk = R_LOCAL;
126+ if (n->b && t->nullable && it && it->kind != TY_NULL && it->kind != TY_ERROR && !it->nullable) l->nonnull = 1;
127+}
128+
129+/* ---------------------------------------------------------------- switch */
130+
131+static int enum_covered(JClass *e, JList *labels) {
132+ for (int i = 0; i < e->enum_names.count; i++) {
133+ int found = 0;
134+ for (int j = 0; j < labels->count && !found; j++) if (!strcmp(labels->items[j], e->enum_names.items[i])) found = 1;
135+ if (!found) return 0;
136+ }
137+ return 1;
138+}
139+
140+static int type_covered(JClass *c, JList *types) {
141+ for (int i = 0; i < types->count; i++) if (ty_is_subclass(c, (JClass *)types->items[i])) return 1;
142+ jt_complete(c);
143+ if (c->flags & M_SEALED) {
144+ if (!c->permits.count && !c->subclasses.count) return 0;
145+ JList *subs = c->permits.count ? &c->permits : &c->subclasses;
146+ for (int i = 0; i < subs->count; i++) if (!type_covered(subs->items[i], types)) return 0;
147+ return subs->count > 0;
148+ }
149+ if ((c->flags & M_ABSTRACT) && c->kind == N_CLASS) return 0;
150+ return 0;
151+}
152+
153+JType *check_switch(Ctx *cx, Node *n, JType *expected) {
154+ int is_expr = n->flags & 1;
155+ JType *sel = check_expr(cx, n->a, NULL);
156+ JClass *enum_cls = NULL;
157+ JType *su = ty_unbox(sel);
158+ int string_sel = ty_is_string(sel);
159+ if (sel->kind == TY_CLASS) {
160+ jt_complete(sel->cls);
161+ if (sel->cls->kind == N_ENUM) enum_cls = sel->cls;
162+ }
163+ if (sel->kind != TY_ERROR) {
164+ int ok = enum_cls || string_sel || ty_is_integral(su) || su->kind == TY_CHAR || sel->kind == TY_CLASS;
165+ if (!ok) cerr(n->a, "cannot switch on a value of type %s", ty_str(sel));
166+ if (sel->nullable) {
167+ int has_null = 0;
168+ for (int i = 0; i < n->l1.count; i++) {
169+ Node *c = n->l1.items[i];
170+ for (int j = 0; j < c->l1.count; j++) if (((Node *)c->l1.items[j])->kind == N_LIT_NULL) has_null = 1;
171+ }
172+ if (!has_null) cerr(n->a, "possible null: switching on nullable %s needs a 'case null'", ty_str(sel));
173+ }
174+ }
175+ JList enum_seen = {0}, type_seen = {0}, consts = {0};
176+ int has_default = 0;
177+ JType *result = NULL;
178+ int mark_outer;
179+ push_scope(cx, &mark_outer);
180+ cx->switch_depth++;
181+ for (int i = 0; i < n->l1.count; i++) {
182+ Node *c = n->l1.items[i];
183+ int mark;
184+ push_scope(cx, &mark);
185+ JList undo = {0};
186+ if (c->flags & 1) {
187+ if (has_default) cerr(c, "duplicate 'default' label");
188+ has_default = 1;
189+ }
190+ for (int j = 0; j < c->l1.count; j++) {
191+ Node *lb = c->l1.items[j];
192+ if (lb->kind == N_TYPEPATTERN) {
193+ JType *pt = jt_resolve_type(lb->a, cx->cls, cx->method, 1);
194+ lb->type = pt;
195+ if (pt->kind == TY_CLASS) {
196+ if (sel->kind == TY_CLASS && !ty_assignable(pt, ty_with_null(sel, 1), 1) && !ty_assignable(sel, ty_with_null(pt, 1), 1) &&
197+ pt->cls->kind != N_INTERFACE && sel->cls->kind != N_INTERFACE) {
198+ cerr(lb, "incompatible types: %s can never be %s", ty_str(sel), ty_str(pt));
199+ }
200+ for (int k = 0; k < type_seen.count; k++) {
201+ if (ty_is_subclass(pt->cls, (JClass *)type_seen.items[k])) cerr(lb, "this case is dominated by an earlier case");
202+ }
203+ j_list_push(&type_seen, pt->cls);
204+ }
205+ JLocal *l = declare_local(cx, lb, lb->name, ty_with_null(pt, 0), 0, 0);
206+ l->has_init = 1;
207+ lb->sym = l;
208+ } else if (lb->kind == N_LIT_NULL) {
209+ lb->type = ty_null();
210+ } else if (enum_cls && lb->kind == N_NAME) {
211+ JField *f = jt_find_field(enum_cls, lb->name);
212+ if (!f || !f->is_enum_const) cerr(lb, "'%s' is not a constant of enum %s", lb->name, enum_cls->name);
213+ else {
214+ for (int k = 0; k < enum_seen.count; k++) if (!strcmp(enum_seen.items[k], lb->name)) cerr(lb, "duplicate case label '%s'", lb->name);
215+ j_list_push(&enum_seen, (void *)lb->name);
216+ lb->rk = R_ENUM_CONST;
217+ lb->sym = f;
218+ lb->type = f->type;
219+ }
220+ } else {
221+ JType *lt = check_expr(cx, lb, sel);
222+ if (sel->kind != TY_ERROR && lt->kind != TY_ERROR) {
223+ JType *target = enum_cls || sel->kind != TY_CLASS ? sel : su;
224+ if (!ty_assignable(lt, ty_with_null(target, 1), 1) && !(ty_is_prim(lt) && ty_is_prim(su) && const_int_value(lb, &(long){0}))) {
225+ cerr(lb, "case label of type %s does not match switch type %s", ty_str(lt), ty_str(sel));
226+ }
227+ }
228+ const char *key = lb->kind == N_LIT_STRING || lb->kind == N_LIT_INT || lb->kind == N_LIT_CHAR ? lb->name : NULL;
229+ if (key) {
230+ for (int k = 0; k < consts.count; k++) if (!strcmp(consts.items[k], key)) cerr(lb, "duplicate case label '%s'", key);
231+ j_list_push(&consts, (void *)key);
232+ } else if (lb->kind != N_NAME && lb->kind != N_FIELDACC) {
233+ cerr(lb, "case labels must be constants");
234+ }
235+ }
236+ if (lb->kind == N_TYPEPATTERN) { /* the binding is already non-null */ }
237+ }
238+ if (c->str) { /* arrow form */
239+ Node *b = c->b;
240+ if (b->kind == N_EXPRSTMT) {
241+ JType *t;
242+ if (is_expr) {
243+ t = check_expr(cx, b->a, expected);
244+ if (t->kind == TY_VOID) cerr(b->a, "switch expression arms must produce a value");
245+ else if (t->kind != TY_ERROR) {
246+ if (!result || result->kind == TY_ERROR) result = t;
247+ else {
248+ JType *bu = ty_unbox(result), *tu = ty_unbox(t);
249+ if (ty_is_numeric(bu) && ty_is_numeric(tu)) result = ty_binary_numeric(bu, tu);
250+ else if (t->kind == TY_NULL) result = ty_with_null(result, 1);
251+ else if (result->kind == TY_NULL) result = ty_with_null(ty_is_prim(t) ? ty_box(t) : t, 1);
252+ else if (ty_assignable(result, ty_with_null(t, 1), 1)) result = ty_with_null(t, result->nullable || t->nullable);
253+ else if (!ty_assignable(t, ty_with_null(result, 1), 1)) result = expected ? expected : jt_object_type();
254+ else result = ty_with_null(result, result->nullable || t->nullable);
255+ }
256+ }
257+ } else {
258+ t = check_expr(cx, b->a, NULL);
259+ }
260+ } else if (b->kind == N_BLOCK) {
261+ if (is_expr) cerr(b, "switch expression arms must be a single expression or a throw");
262+ check_stmt(cx, b);
263+ } else {
264+ check_stmt(cx, b);
265+ }
266+ } else {
267+ if (is_expr) cerr(c, "switch expressions use '->' arms, not ':'");
268+ check_stmts(cx, &c->l2);
269+ }
270+ narrow_undo(&undo);
271+ pop_scope(cx, mark);
272+ }
273+ cx->switch_depth--;
274+ pop_scope(cx, mark_outer);
275+ int exhaustive = has_default;
276+ if (!exhaustive && enum_cls) exhaustive = enum_covered(enum_cls, &enum_seen);
277+ if (!exhaustive && sel->kind == TY_CLASS && type_seen.count) exhaustive = type_covered(sel->cls, &type_seen);
278+ if (is_expr) {
279+ if (!exhaustive && sel->kind != TY_ERROR) cerr(n, "switch expression does not cover all possible input values; add the missing cases or a 'default'");
280+ if (!result) result = ty_error();
281+ return result;
282+ }
283+ if (!exhaustive && type_seen.count && sel->kind != TY_ERROR) cerr(n, "pattern switch does not cover all possible input values; add a 'default'");
284+ return ty_prim(TY_VOID);
285+}
286+
287+/* ---------------------------------------------------------------- try / catch */
288+
289+static JType *throwable_type(void) {
290+ JClass *c = jt_well_known("ash.lang.Throwable");
291+ jt_complete(c);
292+ return c->self_type;
293+}
294+
295+static void check_try(Ctx *cx, Node *n) {
296+ ExFrame fr = {0};
297+ fr.kind = F_TRY;
298+ fr.up = cx->ex;
299+ JList catches = {0}; /* JType* per catch type, in order */
300+ for (int i = 0; i < n->l1.count; i++) {
301+ Node *c = n->l1.items[i];
302+ for (int j = 0; j < c->l1.count; j++) {
303+ Node *tn = c->l1.items[j];
304+ JType *t = jt_resolve_type(tn, cx->cls, cx->method, 1);
305+ tn->type = t;
306+ if (t->kind == TY_ERROR) continue;
307+ if (t->kind != TY_CLASS || !ty_is_subclass(t->cls, throwable_type()->cls)) {
308+ cerr(tn, "%s is not an exception type; catch needs a subclass of Throwable", ty_str(t));
309+ continue;
310+ }
311+ for (int k = 0; k < catches.count; k++) {
312+ JType *prev = catches.items[k];
313+ if (ty_is_subclass(t->cls, prev->cls)) cerr(tn, "exception %s has already been caught by %s", ty_str(t), ty_str(prev));
314+ }
315+ j_list_push(&catches, t);
316+ j_list_push(&fr.types, t);
317+ }
318+ }
319+ cx->ex = &fr;
320+ check_stmt(cx, n->b);
321+ cx->ex = fr.up;
322+ for (int i = 0; i < n->l1.count; i++) {
323+ Node *c = n->l1.items[i];
324+ JType *vt = NULL;
325+ for (int j = 0; j < c->l1.count; j++) {
326+ JType *t = ((Node *)c->l1.items[j])->type;
327+ if (!t || t->kind != TY_CLASS) continue;
328+ if (ty_is_checked_exception(t) && strcmp(t->cls->qname, "ash.lang.Exception") != 0 && strcmp(t->cls->qname, "ash.lang.Throwable") != 0) {
329+ int thrown = 0;
330+ for (int k = 0; k < fr.thrown.count; k++) {
331+ JType *th = fr.thrown.items[k];
332+ if (ty_is_subclass(th->cls, t->cls) || ty_is_subclass(t->cls, th->cls)) thrown = 1;
333+ }
334+ if (!thrown) cerr(c->l1.items[j], "exception %s is never thrown in the body of the corresponding try statement", ty_str(t));
335+ }
336+ if (!vt) vt = t;
337+ else if (vt->cls != t->cls) vt = throwable_type();
338+ }
339+ int mark;
340+ push_scope(cx, &mark);
341+ if (vt) {
342+ JLocal *l = declare_local(cx, c, c->name, vt, 0, 0);
343+ l->has_init = 1;
344+ }
345+ check_block_body(cx, c->b);
346+ pop_scope(cx, mark);
347+ }
348+ if (n->c) check_stmt(cx, n->c);
349+}
350+
351+/* ---------------------------------------------------------------- statements */
352+
353+static void check_return(Ctx *cx, Node *n) {
354+ if (cx->lambda_depth == 0 && !cx->method) { cerr(n, "'return' is not allowed here"); return; }
355+ if (cx->ret == NULL) { /* inferring a lambda result */
356+ if (n->a) {
357+ JType *t = check_expr(cx, n->a, NULL);
358+ if (cx->ret_types && t->kind != TY_VOID) j_list_push(cx->ret_types, t);
359+ } else cx->ret_seen_void = 1;
360+ return;
361+ }
362+ if (cx->ret->kind == TY_VOID) {
363+ if (n->a) {
364+ JType *t = check_expr(cx, n->a, NULL);
365+ if (cx->in_ctor || cx->lambda_depth) cerr(n, "this %s cannot return a value", cx->lambda_depth ? "lambda" : "constructor");
366+ else if (t->kind != TY_ERROR) cerr(n, "void method '%s' cannot return a value", cx->method ? cx->method->name : "");
367+ }
368+ return;
369+ }
370+ if (!n->a) { cerr(n, "missing return value; the method must return %s", ty_str(cx->ret)); return; }
371+ JType *t = check_expr(cx, n->a, cx->ret);
372+ check_assignable(cx, n->a, t, cx->ret, "return value");
373+ if (cx->ret_types) j_list_push(cx->ret_types, t);
374+}
375+
376+static int is_stmt_expr_node(Node *n) {
377+ return n->kind == N_ASSIGN || n->kind == N_CALL || n->kind == N_NEW || n->kind == N_POSTFIX ||
378+ (n->kind == N_UNARY && (!strcmp(n->name, "++") || !strcmp(n->name, "--")));
379+}
380+
381+static JType *iter_elem(Ctx *cx, Node *it, JType *t) {
382+ (void)cx;
383+ if (t->kind == TY_ARRAY) return t->elem;
384+ JClass *ib = jt_find_class("ash.lang.Iterable");
385+ JType *s = ib && t->kind != TY_ERROR ? ty_as_super(t, ib) : NULL;
386+ if (s) return s->nargs ? s->args[0] : jt_object_type();
387+ if (t->kind != TY_ERROR) cerr(it, "for-each needs an array or an Iterable, found %s", ty_str(t));
388+ return ty_error();
389+}
390+
391+void check_stmt(Ctx *cx, Node *n) {
392+ switch (n->kind) {
393+ case N_BLOCK: check_block_body(cx, n); break;
394+ case N_SEQ: for (int i = 0; i < n->l1.count; i++) check_stmt(cx, n->l1.items[i]); break;
395+ case N_EMPTY: break;
396+ case N_LOCAL: check_local(cx, n); break;
397+ case N_EXPRSTMT:
398+ check_expr(cx, n->a, NULL);
399+ if (!is_stmt_expr_node(n->a) && n->a->type->kind != TY_ERROR) cerr(n->a, "not a statement");
400+ break;
401+ case N_IF: {
402+ check_cond(cx, n->a);
403+ JList undo = {0};
404+ narrow_apply(cx, n->a, 1, &undo);
405+ int mark;
406+ push_scope(cx, &mark);
407+ check_stmt(cx, n->b);
408+ pop_scope(cx, mark);
409+ narrow_undo(&undo);
410+ if (n->c) {
411+ narrow_apply(cx, n->a, 0, &undo);
412+ push_scope(cx, &mark);
413+ check_stmt(cx, n->c);
414+ pop_scope(cx, mark);
415+ narrow_undo(&undo);
416+ }
417+ /* guard clause: `if (x == null) return;` leaves x non-null afterwards */
418+ if (!can_complete(n->b) && !n->c) narrow_apply(cx, n->a, 0, NULL);
419+ else if (n->c && !can_complete(n->c) && can_complete(n->b)) narrow_apply(cx, n->a, 1, NULL);
420+ break;
421+ }
422+ case N_WHILE: {
423+ check_cond(cx, n->a);
424+ JList undo = {0};
425+ narrow_apply(cx, n->a, 1, &undo);
426+ int mark;
427+ push_scope(cx, &mark);
428+ cx->loop_depth++;
429+ check_stmt(cx, n->b);
430+ cx->loop_depth--;
431+ pop_scope(cx, mark);
432+ narrow_undo(&undo);
433+ break;
434+ }
435+ case N_DO: {
436+ int mark;
437+ push_scope(cx, &mark);
438+ cx->loop_depth++;
439+ check_stmt(cx, n->b);
440+ cx->loop_depth--;
441+ pop_scope(cx, mark);
442+ check_cond(cx, n->a);
443+ break;
444+ }
445+ case N_FOR: {
446+ int mark;
447+ push_scope(cx, &mark);
448+ for (int i = 0; i < n->l1.count; i++) check_stmt(cx, n->l1.items[i]);
449+ if (n->a) check_cond(cx, n->a);
450+ for (int i = 0; i < n->l2.count; i++) {
451+ Node *u = n->l2.items[i];
452+ check_expr(cx, u, NULL);
453+ if (!is_stmt_expr_node(u) && u->type->kind != TY_ERROR) cerr(u, "not a statement");
454+ }
455+ cx->loop_depth++;
456+ check_stmt(cx, n->b);
457+ cx->loop_depth--;
458+ pop_scope(cx, mark);
459+ break;
460+ }
461+ case N_FOREACH: {
462+ JType *it = check_expr(cx, n->b, NULL);
463+ if (it->nullable) cerr(n->b, "possible null: iterating over nullable %s; check for null first", ty_str(it));
464+ JType *elem = iter_elem(cx, n->b, it);
465+ JType *vt = elem;
466+ if (n->a) {
467+ vt = jt_resolve_type(n->a, cx->cls, cx->method, 1);
468+ check_assignable(cx, n->b, elem, vt, "loop variable");
469+ }
470+ int mark;
471+ push_scope(cx, &mark);
472+ JLocal *l = declare_local(cx, n, n->name, vt, 0, 0);
473+ l->has_init = 1;
474+ n->sym = l;
475+ n->type = vt;
476+ cx->loop_depth++;
477+ check_stmt(cx, n->c);
478+ cx->loop_depth--;
479+ pop_scope(cx, mark);
480+ break;
481+ }
482+ case N_RETURN: check_return(cx, n); break;
483+ case N_BREAK:
484+ if (!cx->loop_depth && !cx->switch_depth) cerr(n, "'break' outside of a loop or switch");
485+ break;
486+ case N_CONTINUE:
487+ if (!cx->loop_depth) cerr(n, "'continue' outside of a loop");
488+ break;
489+ case N_THROW: {
490+ JType *t = check_expr(cx, n->a, NULL);
491+ if (t->kind == TY_ERROR) break;
492+ if (t->kind != TY_CLASS || !ty_is_subclass(t->cls, throwable_type()->cls)) {
493+ cerr(n->a, "only Throwable can be thrown, found %s", ty_str(t));
494+ break;
495+ }
496+ if (t->nullable) cerr(n->a, "possible null: throwing nullable %s", ty_str(t));
497+ report_throw(cx, n, t);
498+ break;
499+ }
500+ case N_TRY: check_try(cx, n); break;
501+ case N_SWITCH: check_switch(cx, n, NULL); break;
502+ case N_CTORCALL: check_ctor_call(cx, n); break;
503+ default:
504+ cerr(n, "unexpected statement");
505+ }
506+}

front/src/jtypes/C/classes.c → hearth/front/src/jtypes/C/classes.crenamed

+0−0

No content changes. View the file on GitHub

front/src/jtypes/C/types.c → hearth/front/src/jtypes/C/types.crenamed

+3−1
@@ -120,7 +120,9 @@ static const char *box_names[] = {
120120
121121 JType *ty_box(JType *t) {
122122 if (!ty_is_prim(t)) return t;
123- return jt_well_known(box_names[t->kind])->self_type;
123+ JClass *c = jt_well_known(box_names[t->kind]);
124+ jt_complete(c);
125+ return c->self_type;
124126 }
125127
126128 JType *ty_unbox(JType *t) {

front/src/jtypes/D/.gitkeep → hearth/front/src/jtypes/D/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

front/src/jtypes/H/check.h → hearth/front/src/jtypes/H/check.hrenamed

+8−1
@@ -25,7 +25,8 @@ typedef enum { F_TRY, F_METHOD, F_LAMBDA, F_INIT } FrameKind;
2525
2626 typedef struct ExFrame {
2727 FrameKind kind;
28- JList types; /* caught types (F_TRY) or declared throws (F_METHOD/F_LAMBDA) */
28+ JList types; /* caught types (F_TRY) or declared throws (F_METHOD/F_LAMBDA) */
29+ JList thrown; /* checked exceptions that reached this F_TRY frame */
2930 struct ExFrame *up;
3031 } ExFrame;
3132
@@ -48,6 +49,7 @@ typedef struct Ctx {
4849
4950 /* check_expr.c */
5051 JType *check_expr(Ctx *cx, Node *n, JType *expected);
52+JType *check_expr_ex(Ctx *cx, Node *n, JType *expected, int allow_type); /* allow_type: class/package names are legal results */
5153 JType *check_cond(Ctx *cx, Node *n);
5254 int check_assignable(Ctx *cx, Node *expr, JType *from, JType *to, const char *what);
5355 void check_coerce(Ctx *cx, Node *expr, JType *to, const char *what); /* check_expr + assign check */
@@ -57,6 +59,11 @@ void report_throw(Ctx *cx, Node *at, JType *exc);
5759 int accessible(Ctx *cx, JClass *owner, int flags);
5860 int const_int_value(Node *e, long *out);
5961 JType *narrowed(JLocal *l);
62+JType *member_type(JType *recv, JClass *owner, JType *t);
63+JType *check_lambda(Ctx *cx, Node *lam, JType *target, JType **result);
64+int is_poly_arg(Node *n);
65+JType *check_switch(Ctx *cx, Node *n, JType *expected);
66+void check_ctor_call(Ctx *cx, Node *n);
6067 void narrow_apply(Ctx *cx, Node *cond, int when_true, JList *undo);
6168 void narrow_undo(JList *undo);
6269 JType *check_new_object(Ctx *cx, Node *n, JType *expected);

front/src/jtypes/H/jtypes.h → hearth/front/src/jtypes/H/jtypes.hrenamed

+1−0
@@ -157,6 +157,7 @@ JType *jt_object_type(void);
157157 /* ---- checker (check_*.c) ---- */
158158 void jt_check_all(void);
159159 void jt_check_class(JClass *c);
160+JMethod *jt_find_overridden(JClass *c, JMethod *m); /* method of a supertype that m overrides, or NULL */
160161
161162 void cerr(Node *n, const char *fmt, ...);
162163

front/src/jtypes/O/.gitkeep → hearth/front/src/jtypes/O/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

hearth/front/stdlib/ash/cli/Args.ashadded

+50−0
@@ -0,0 +1,50 @@
1+package ash.cli;
2+
3+import ash.util.HashMap;
4+import ash.util.List;
5+import ash.util.ArrayList;
6+
7+/// Splits arguments into `--name value` options, `--flag` switches and positional values.
8+public final class Args {
9+ private final HashMap<String, String> options = new HashMap<>();
10+ private final List<String> positional = new ArrayList<>();
11+
12+ /// Creates a new Args.
13+ public Args(String[] args) {
14+ int i = 0;
15+ while (i < args.length) {
16+ String a = args[i];
17+ if (a.startsWith("--")) {
18+ String name = a.substring(2);
19+ if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
20+ options.put(name, args[i + 1]);
21+ i++;
22+ } else {
23+ options.put(name, "true");
24+ }
25+ } else {
26+ positional.add(a);
27+ }
28+ i++;
29+ }
30+ }
31+
32+ /// True when the option was given.
33+ public bool has(String name) {
34+ return options.containsKey(name);
35+ }
36+
37+ /// Element at the index (or the stored value).
38+ public String get(String name, String fallback) {
39+ String? v = options.get(name);
40+ if (v == null) {
41+ return fallback;
42+ }
43+ return v;
44+ }
45+
46+ /// Arguments that are not options.
47+ public List<String> positional() {
48+ return positional;
49+ }
50+}

hearth/front/stdlib/ash/concurrent/Executor.ashadded

+18−0
@@ -0,0 +1,18 @@
1+package ash.concurrent;
2+
3+import ash.lang.function.Supplier;
4+
5+/// Deterministic single-threaded executor: the Ash VM has no threads, so tasks run to
6+/// completion on the caller in submission order. Failures are captured in the Future.
7+public final class Executor {
8+ public Executor() { }
9+
10+ /// Runs the task and captures its result or failure.
11+ public <T> Future<T> submit(Supplier<T> task) {
12+ try {
13+ return new Future<T>(task.get(), null);
14+ } catch (Exception e) {
15+ return new Future<T>(null, e);
16+ }
17+ }
18+}

hearth/front/stdlib/ash/concurrent/Future.ashadded

+31−0
@@ -0,0 +1,31 @@
1+package ash.concurrent;
2+
3+/// Result of a task run by an Executor.
4+public final class Future<T> {
5+ private final T? value;
6+ private final Exception? failure;
7+
8+ /// Creates a new Future.
9+ Future(T? value, Exception? failure) {
10+ this.value = value;
11+ this.failure = failure;
12+ }
13+
14+ /// True when the task threw.
15+ public bool isFailed() {
16+ return failure != null;
17+ }
18+
19+ /// Returns the task result or rethrows its failure wrapped in IllegalStateException.
20+ public T get() {
21+ Exception? f = failure;
22+ if (f != null) {
23+ throw new IllegalStateException("task failed: " + f.getMessage(), f);
24+ }
25+ T? v = value;
26+ if (v == null) {
27+ throw new IllegalStateException("task produced no value");
28+ }
29+ return v;
30+ }
31+}

hearth/front/stdlib/ash/concurrent/Thread.ashadded

+31−0
@@ -0,0 +1,31 @@
1+package ash.concurrent;
2+
3+import ash.lang.function.Runnable;
4+
5+/// A unit of work. The Ash VM has no threads, so `start()` runs the task to completion on the
6+/// caller and `join()` returns immediately; programs written against it stay deterministic.
7+public class Thread {
8+ private final Runnable task;
9+ private bool finished = false;
10+
11+ /// Creates a new Thread.
12+ public Thread(Runnable task) {
13+ this.task = task;
14+ }
15+
16+ /// Runs the task now, on the calling flow of control.
17+ public void start() {
18+ if (!finished) {
19+ finished = true;
20+ task.run();
21+ }
22+ }
23+
24+ /// Returns immediately: the task has already completed in start().
25+ public void join() { }
26+
27+ /// True once the task has run.
28+ public bool isFinished() {
29+ return finished;
30+ }
31+}

hearth/front/stdlib/ash/desktop/Button.ashadded

+22−0
@@ -0,0 +1,22 @@
1+package ash.desktop;
2+
3+/// Clickable widget that counts its clicks.
4+public class Button extends Widget {
5+ private final String label;
6+ private int clicks = 0;
7+
8+ /// Creates a new Button.
9+ public Button(String label) {
10+ this.label = label;
11+ }
12+
13+ /// Registers one click.
14+ public void click() {
15+ clicks++;
16+ }
17+
18+ /// Text form of this widget alone.
19+ public String draw() {
20+ return "(" + label + " x" + clicks + ")";
21+ }
22+}

hearth/front/stdlib/ash/desktop/Label.ashadded

+16−0
@@ -0,0 +1,16 @@
1+package ash.desktop;
2+
3+/// Widget that shows fixed text.
4+public class Label extends Widget {
5+ private final String text;
6+
7+ /// Creates a new Label.
8+ public Label(String text) {
9+ this.text = text;
10+ }
11+
12+ /// Text form of this widget alone.
13+ public String draw() {
14+ return text;
15+ }
16+}

hearth/front/stdlib/ash/desktop/Widget.ashadded

+28−0
@@ -0,0 +1,28 @@
1+package ash.desktop;
2+
3+import ash.util.ArrayList;
4+import ash.util.List;
5+
6+/// Base of a retained widget tree. Rendering produces text: the VM has no windowing.
7+public abstract class Widget {
8+ private final List<Widget> children = new ArrayList<>();
9+
10+ protected Widget() { }
11+
12+ /// Adds a value (at the index, if given).
13+ public void add(Widget child) {
14+ children.add(child);
15+ }
16+
17+ /// Text form of this widget alone.
18+ public abstract String draw();
19+
20+ /// Text form of the value or widget subtree.
21+ public String render(int depth) {
22+ String out = " ".repeat(depth) + draw() + "\n";
23+ for (Widget c : children) {
24+ out += c.render(depth + 1);
25+ }
26+ return out;
27+ }
28+}

hearth/front/stdlib/ash/game/GameLoop.ashadded

+23−0
@@ -0,0 +1,23 @@
1+package ash.game;
2+
3+import ash.lang.function.Consumer;
4+
5+/// Fixed-step loop: calls `update` with the tick number `ticks` times (no real-time clock).
6+public final class GameLoop {
7+ private int tick = 0;
8+
9+ public GameLoop() { }
10+
11+ /// Performs the task.
12+ public void run(int ticks, Consumer<Integer> update) {
13+ for (int i = 0; i < ticks; i++) {
14+ tick++;
15+ update.accept(tick);
16+ }
17+ }
18+
19+ /// Current tick number.
20+ public int getTick() {
21+ return tick;
22+ }
23+}

hearth/front/stdlib/ash/game/Vec2.ashadded

+8−0
@@ -0,0 +1,8 @@
1+package ash.game;
2+
3+/// Immutable 2D vector.
4+public record Vec2(double x, double y) {
5+ public Vec2 plus(Vec2 o) { return new Vec2(x + o.x(), y + o.y()); }
6+ public Vec2 scale(double k) { return new Vec2(x * k, y * k); }
7+ public double length() { return Math.sqrt(x * x + y * y); }
8+}

hearth/front/stdlib/ash/io/Files.ashadded

+65−0
@@ -0,0 +1,65 @@
1+package ash.io;
2+
3+import ash.util.List;
4+
5+/// Whole-file operations. Failures are reported as IOException.
6+public final class Files {
7+ @Lower("file_exists($1)") private static native bool existsRaw(String path);
8+ @Lower("read_file($1)") private static native String readRaw(String path);
9+ @Lower("write_file($1, $2)") private static native void writeRaw(String path, String text);
10+ @Lower("append_file($1, $2)") private static native void appendRaw(String path, String text);
11+ @Lower("delete_file($1)") private static native void deleteRaw(String path);
12+ @Lower("rename_file($1, $2)") private static native void moveRaw(String from, String to);
13+ @Lower("make_dir($1)") private static native void mkdirRaw(String path);
14+ @Lower("list_dir($1)") private static native List<String> listRaw(String path);
15+
16+ /// True when the file or directory exists.
17+ public static bool exists(Path path) {
18+ return existsRaw(path.value());
19+ }
20+
21+ /// Whole file contents as text; throws IOException if missing.
22+ public static String readString(Path path) throws IOException {
23+ require(path);
24+ return readRaw(path.value());
25+ }
26+
27+ /// Replaces the file contents; throws IOException on failure.
28+ public static void writeString(Path path, String text) throws IOException {
29+ writeRaw(path.value(), text);
30+ }
31+
32+ /// Appends text to the file.
33+ public static void appendString(Path path, String text) throws IOException {
34+ appendRaw(path.value(), text);
35+ }
36+
37+ /// Deletes the file; throws IOException if missing.
38+ public static void delete(Path path) throws IOException {
39+ require(path);
40+ deleteRaw(path.value());
41+ }
42+
43+ /// Renames or moves a file.
44+ public static void move(Path from, Path to) throws IOException {
45+ require(from);
46+ moveRaw(from.value(), to.value());
47+ }
48+
49+ /// Creates a directory.
50+ public static void createDirectory(Path path) throws IOException {
51+ mkdirRaw(path.value());
52+ }
53+
54+ /// Names of the directory entries.
55+ public static List<String> list(Path dir) throws IOException {
56+ require(dir);
57+ return listRaw(dir.value());
58+ }
59+
60+ private static void require(Path path) throws IOException {
61+ if (!existsRaw(path.value())) {
62+ throw new IOException("no such file or directory: " + path.value());
63+ }
64+ }
65+}

hearth/front/stdlib/ash/io/Path.ashadded

+22−0
@@ -0,0 +1,22 @@
1+package ash.io;
2+
3+/// A file system path, kept as text.
4+public record Path(String value) {
5+ /// Creates a value from the argument.
6+ public static Path of(String value) {
7+ return new Path(value);
8+ }
9+
10+ /// Child path below this one.
11+ public Path resolve(String child) {
12+ if (value.endsWith("/")) {
13+ return new Path(value + child);
14+ }
15+ return new Path(value + "/" + child);
16+ }
17+
18+ /// Text form.
19+ public String toString() {
20+ return value;
21+ }
22+}

hearth/front/stdlib/ash/json/Json.ashadded

+208−0
@@ -0,0 +1,208 @@
1+package ash.json;
2+
3+import ash.util.List;
4+import ash.util.ArrayList;
5+import ash.util.Map;
6+import ash.util.HashMap;
7+
8+/// JSON text <-> Object trees: Map<String, Object>, List<Object>, String, numbers, Boolean, null.
9+public final class Json {
10+ private final String text;
11+ private int pos;
12+
13+ private Json(String text) {
14+ this.text = text;
15+ this.pos = 0;
16+ }
17+
18+ /// Parses text; throws ParseException when malformed.
19+ public static Object? parse(String text) throws ParseException {
20+ Json p = new Json(text);
21+ p.skip();
22+ Object? v = p.value();
23+ p.skip();
24+ if (p.pos < text.length()) {
25+ throw new ParseException("trailing characters at " + p.pos);
26+ }
27+ return v;
28+ }
29+
30+ /// JSON text for a tree of maps, lists, strings, numbers, booleans and null.
31+ public static String stringify(Object? v) {
32+ StringBuilder sb = new StringBuilder();
33+ write(sb, v);
34+ return sb.toString();
35+ }
36+
37+ private static void write(StringBuilder sb, Object? v) {
38+ if (v == null) {
39+ sb.append("null");
40+ } else if (v instanceof String s) {
41+ sb.append(quote(s));
42+ } else if (v instanceof Boolean b) {
43+ sb.append(String.valueOf(b));
44+ } else if (v instanceof List l) {
45+ sb.append("[");
46+ for (int i = 0; i < l.size(); i++) {
47+ if (i > 0) {
48+ sb.append(",");
49+ }
50+ write(sb, l.get(i));
51+ }
52+ sb.append("]");
53+ } else if (v instanceof Map m) {
54+ sb.append("{");
55+ bool first = true;
56+ for (Object k : m.keys()) {
57+ if (!first) {
58+ sb.append(",");
59+ }
60+ first = false;
61+ sb.append(quote(String.valueOf(k))).append(":");
62+ write(sb, m.get(k));
63+ }
64+ sb.append("}");
65+ } else {
66+ sb.append(String.valueOf(v));
67+ }
68+ }
69+
70+ private static String quote(String s) {
71+ return "\"" + s.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
72+ }
73+
74+ private void skip() {
75+ while (pos < text.length() && text.charAt(pos) <= 32) {
76+ pos++;
77+ }
78+ }
79+
80+ private Object? value() throws ParseException {
81+ if (pos >= text.length()) {
82+ throw new ParseException("unexpected end of input");
83+ }
84+ char c = text.charAt(pos);
85+ if (c == '{') {
86+ return object();
87+ }
88+ if (c == '[') {
89+ return array();
90+ }
91+ if (c == '"') {
92+ return string();
93+ }
94+ if (text.startsWith("true", pos)) {
95+ pos += 4;
96+ return true;
97+ }
98+ if (text.startsWith("false", pos)) {
99+ pos += 5;
100+ return false;
101+ }
102+ if (text.startsWith("null", pos)) {
103+ pos += 4;
104+ return null;
105+ }
106+ return number();
107+ }
108+
109+ private Object number() throws ParseException {
110+ int start = pos;
111+ while (pos < text.length() && (text.charAt(pos) == '-' || text.charAt(pos) == '.' || (text.charAt(pos) >= '0' && text.charAt(pos) <= '9'))) {
112+ pos++;
113+ }
114+ if (start == pos) {
115+ throw new ParseException("unexpected character at " + pos);
116+ }
117+ return Double.parse(text.substring(start, pos));
118+ }
119+
120+ private String string() throws ParseException {
121+ pos++;
122+ StringBuilder sb = new StringBuilder();
123+ while (pos < text.length() && text.charAt(pos) != '"') {
124+ char c = text.charAt(pos);
125+ if (c == '\\') {
126+ pos++;
127+ char e = text.charAt(pos);
128+ if (e == 'n') {
129+ sb.append('\n');
130+ } else if (e == 't') {
131+ sb.append('\t');
132+ } else {
133+ sb.append(e);
134+ }
135+ } else {
136+ sb.append(c);
137+ }
138+ pos++;
139+ }
140+ if (pos >= text.length()) {
141+ throw new ParseException("unterminated string");
142+ }
143+ pos++;
144+ return sb.toString();
145+ }
146+
147+ private List<Object?> array() throws ParseException {
148+ pos++;
149+ List<Object?> out = new ArrayList<>();
150+ skip();
151+ if (text.charAt(pos) == ']') {
152+ pos++;
153+ return out;
154+ }
155+ while (true) {
156+ skip();
157+ out.add(value());
158+ skip();
159+ if (pos >= text.length()) {
160+ throw new ParseException("unterminated array");
161+ }
162+ char c = text.charAt(pos);
163+ pos++;
164+ if (c == ']') {
165+ return out;
166+ }
167+ if (c != ',') {
168+ throw new ParseException("expected ',' or ']' at " + pos);
169+ }
170+ }
171+ }
172+
173+ private Map<String, Object?> object() throws ParseException {
174+ pos++;
175+ Map<String, Object?> out = new HashMap<>();
176+ skip();
177+ if (text.charAt(pos) == '}') {
178+ pos++;
179+ return out;
180+ }
181+ while (true) {
182+ skip();
183+ if (pos >= text.length() || text.charAt(pos) != '"') {
184+ throw new ParseException("expected a string key at " + pos);
185+ }
186+ String key = string();
187+ skip();
188+ if (pos >= text.length() || text.charAt(pos) != ':') {
189+ throw new ParseException("expected ':' at " + pos);
190+ }
191+ pos++;
192+ skip();
193+ out.put(key, value());
194+ skip();
195+ if (pos >= text.length()) {
196+ throw new ParseException("unterminated object");
197+ }
198+ char c = text.charAt(pos);
199+ pos++;
200+ if (c == '}') {
201+ return out;
202+ }
203+ if (c != ',') {
204+ throw new ParseException("expected ',' or '}' at " + pos);
205+ }
206+ }
207+ }
208+}

hearth/front/stdlib/ash/lang/ArithmeticException.ashadded

+16−0
@@ -0,0 +1,16 @@
1+package ash.lang;
2+
3+/// Thrown for integer division or remainder by zero.
4+public class ArithmeticException extends RuntimeException {
5+ public ArithmeticException() { }
6+
7+ /// Creates a new ArithmeticException.
8+ public ArithmeticException(String message) {
9+ super(message);
10+ }
11+
12+ /// Creates a new ArithmeticException.
13+ public ArithmeticException(String message, Throwable cause) {
14+ super(message, cause);
15+ }
16+}

hearth/front/stdlib/ash/lang/Boolean.ashadded

+13−0
@@ -0,0 +1,13 @@
1+package ash.lang;
2+
3+/// Boxed bool; the VM represents it as the plain value.
4+public final class Boolean implements Comparable<Boolean> {
5+ /// The value as a bool.
6+ @Lower("$0") public native bool toBoolValue();
7+ /// Negative, zero or positive as this value orders before, equal to or after the other.
8+ @Lower("__cmp($0, $1)") public native int compareTo(Boolean other);
9+ /// Converts the argument to this type (text for String).
10+ @Lower("$1") public static native Boolean valueOf(bool v);
11+ /// Parses text; throws ParseException when malformed.
12+ @Lower("($1 == \"true\")") public static native bool parse(String s);
13+}

hearth/front/stdlib/ash/lang/Byte.ashadded

+15−0
@@ -0,0 +1,15 @@
1+package ash.lang;
2+
3+/// Boxed byte; represented as a plain number.
4+public final class Byte extends Number implements Comparable<Byte> {
5+ /// The value as a double.
6+ @Lower("$0") public native double toDoubleValue();
7+ /// The value as a byte.
8+ @Lower("$0") public native byte toByteValue();
9+ /// Negative, zero or positive as this value orders before, equal to or after the other.
10+ @Lower("__cmp($0, $1)") public native int compareTo(Byte other);
11+ /// Converts the argument to this type (text for String).
12+ @Lower("$1") public static native Byte valueOf(byte v);
13+ /// Parses text; throws ParseException when malformed.
14+ @Lower("__parse_int($1)") public static native byte parse(String s) throws ParseException;
15+}

hearth/front/stdlib/ash/lang/Character.ashadded

+11−0
@@ -0,0 +1,11 @@
1+package ash.lang;
2+
3+/// Boxed char; represented as its code point.
4+public final class Character implements Comparable<Character> {
5+ /// The value as a char.
6+ @Lower("$0") public native char toCharValue();
7+ /// Negative, zero or positive as this value orders before, equal to or after the other.
8+ @Lower("__cmp($0, $1)") public native int compareTo(Character other);
9+ /// Converts the argument to this type (text for String).
10+ @Lower("$1") public static native Character valueOf(char v);
11+}

hearth/front/stdlib/ash/lang/Class.ashadded

+7−0
@@ -0,0 +1,7 @@
1+package ash.lang;
2+
3+/// Runtime type descriptor returned by Object.getClass().
4+public final class Class {
5+ /// Fully qualified class name.
6+ @Lower("$0[\"n\"]") public native String getName();
7+}

hearth/front/stdlib/ash/lang/ClassCastException.ashadded

+16−0
@@ -0,0 +1,16 @@
1+package ash.lang;
2+
3+/// Thrown when a cast does not match the runtime type.
4+public class ClassCastException extends RuntimeException {
5+ public ClassCastException() { }
6+
7+ /// Creates a new ClassCastException.
8+ public ClassCastException(String message) {
9+ super(message);
10+ }
11+
12+ /// Creates a new ClassCastException.
13+ public ClassCastException(String message, Throwable cause) {
14+ super(message, cause);
15+ }
16+}

hearth/front/stdlib/ash/lang/Comparable.ashadded

+7−0
@@ -0,0 +1,7 @@
1+package ash.lang;
2+
3+/// Types with a natural order.
4+public interface Comparable<T> {
5+ /// Negative, zero or positive as this value orders before, equal to or after the other.
6+ int compareTo(T other);
7+}

hearth/front/stdlib/ash/lang/Double.ashadded

+13−0
@@ -0,0 +1,13 @@
1+package ash.lang;
2+
3+/// Boxed double; represented as a plain number.
4+public final class Double extends Number implements Comparable<Double> {
5+ /// The value as a double.
6+ @Lower("$0") public native double toDoubleValue();
7+ /// Negative, zero or positive as this value orders before, equal to or after the other.
8+ @Lower("__cmp($0, $1)") public native int compareTo(Double other);
9+ /// Converts the argument to this type (text for String).
10+ @Lower("$1") public static native Double valueOf(double v);
11+ /// Parses text; throws ParseException when malformed.
12+ @Lower("__parse_double($1)") public static native double parse(String s) throws ParseException;
13+}

hearth/front/stdlib/ash/lang/Enum.ashadded

+16−0
@@ -0,0 +1,16 @@
1+package ash.lang;
2+
3+/// Base class of every enum: constant name and declaration order.
4+public abstract class Enum<E> implements Comparable<E> {
5+ /// Declared name of the constant.
6+ @Lower("$0[\"__name\"]") public final native String name();
7+ /// Position in the enum declaration, starting at 0.
8+ @Lower("$0[\"__ord\"]") public final native int ordinal();
9+ /// Negative, zero or positive as this value orders before, equal to or after the other.
10+ @Lower("($0[\"__ord\"] - $1[\"__ord\"])") public final native int compareTo(E other);
11+
12+ /// Text form.
13+ public String toString() {
14+ return name();
15+ }
16+}

hearth/front/stdlib/ash/lang/Exception.ashadded

+16−0
@@ -0,0 +1,16 @@
1+package ash.lang;
2+
3+/// Base of recoverable (checked) failures; RuntimeException subclasses are unchecked.
4+public class Exception extends Throwable {
5+ public Exception() { }
6+
7+ /// Creates a new Exception.
8+ public Exception(String message) {
9+ super(message);
10+ }
11+
12+ /// Creates a new Exception.
13+ public Exception(String message, Throwable cause) {
14+ super(message, cause);
15+ }
16+}

hearth/front/stdlib/ash/lang/Float.ashadded

+15−0
@@ -0,0 +1,15 @@
1+package ash.lang;
2+
3+/// Boxed float; represented as a plain number.
4+public final class Float extends Number implements Comparable<Float> {
5+ /// The value as a double.
6+ @Lower("$0") public native double toDoubleValue();
7+ /// The value as a float.
8+ @Lower("$0") public native float toFloatValue();
9+ /// Negative, zero or positive as this value orders before, equal to or after the other.
10+ @Lower("__cmp($0, $1)") public native int compareTo(Float other);
11+ /// Converts the argument to this type (text for String).
12+ @Lower("$1") public static native Float valueOf(float v);
13+ /// Parses text; throws ParseException when malformed.
14+ @Lower("__parse_double($1)") public static native float parse(String s) throws ParseException;
15+}

hearth/front/stdlib/ash/lang/IOException.ashadded

+16−0
@@ -0,0 +1,16 @@
1+package ash.lang;
2+
3+/// Checked failure of a file or stream operation.
4+public class IOException extends Exception {
5+ public IOException() { }
6+
7+ /// Creates a new IOException.
8+ public IOException(String message) {
9+ super(message);
10+ }
11+
12+ /// Creates a new IOException.
13+ public IOException(String message, Throwable cause) {
14+ super(message, cause);
15+ }
16+}

hearth/front/stdlib/ash/lang/IllegalArgumentException.ashadded

+16−0
@@ -0,0 +1,16 @@
1+package ash.lang;
2+
3+/// Thrown when an argument is not acceptable.
4+public class IllegalArgumentException extends RuntimeException {
5+ public IllegalArgumentException() { }
6+
7+ /// Creates a new IllegalArgumentException.
8+ public IllegalArgumentException(String message) {
9+ super(message);
10+ }
11+
12+ /// Creates a new IllegalArgumentException.
13+ public IllegalArgumentException(String message, Throwable cause) {
14+ super(message, cause);
15+ }
16+}

hearth/front/stdlib/ash/lang/IllegalStateException.ashadded

+16−0
@@ -0,0 +1,16 @@
1+package ash.lang;
2+
3+/// Thrown when an object is not in a state that allows the call.
4+public class IllegalStateException extends RuntimeException {
5+ public IllegalStateException() { }
6+
7+ /// Creates a new IllegalStateException.
8+ public IllegalStateException(String message) {
9+ super(message);
10+ }
11+
12+ /// Creates a new IllegalStateException.
13+ public IllegalStateException(String message, Throwable cause) {
14+ super(message, cause);
15+ }
16+}

hearth/front/stdlib/ash/lang/IndexOutOfBoundsException.ashadded

+16−0
@@ -0,0 +1,16 @@
1+package ash.lang;
2+
3+/// Thrown for an array or list index outside its bounds.
4+public class IndexOutOfBoundsException extends RuntimeException {
5+ public IndexOutOfBoundsException() { }
6+
7+ /// Creates a new IndexOutOfBoundsException.
8+ public IndexOutOfBoundsException(String message) {
9+ super(message);
10+ }
11+
12+ /// Creates a new IndexOutOfBoundsException.
13+ public IndexOutOfBoundsException(String message, Throwable cause) {
14+ super(message, cause);
15+ }
16+}

hearth/front/stdlib/ash/lang/InputStream.ashadded

+7−0
@@ -0,0 +1,7 @@
1+package ash.lang;
2+
3+/// Standard input; see System.in.
4+public final class InputStream {
5+ /// Reads one line from standard input.
6+ @Lower("input()") public native String readLine();
7+}

hearth/front/stdlib/ash/lang/Integer.ashadded

+19−0
@@ -0,0 +1,19 @@
1+package ash.lang;
2+
3+/// Boxed int; represented as a plain number.
4+public final class Integer extends Number implements Comparable<Integer> {
5+ /// Largest int value, 2^31 - 1.
6+ @Lower("2147483647") public static final int MAX_VALUE = 2147483647;
7+ /// Smallest int value, -2^31.
8+ @Lower("(-2147483648)") public static final int MIN_VALUE = -2147483648;
9+ /// The value as a double.
10+ @Lower("$0") public native double toDoubleValue();
11+ /// The value as an int.
12+ @Lower("$0") public native int toIntValue();
13+ /// Negative, zero or positive as this value orders before, equal to or after the other.
14+ @Lower("__cmp($0, $1)") public native int compareTo(Integer other);
15+ /// Converts the argument to this type (text for String).
16+ @Lower("$1") public static native Integer valueOf(int v);
17+ /// Parses text; throws ParseException when malformed.
18+ @Lower("__parse_int($1)") public static native int parse(String s) throws ParseException;
19+}

hearth/front/stdlib/ash/lang/Iterable.ashadded

+9−0
@@ -0,0 +1,9 @@
1+package ash.lang;
2+
3+import ash.util.Iterator;
4+
5+/// Types that can be the target of a for-each loop.
6+public interface Iterable<T> {
7+ /// Cursor over the elements.
8+ @Lower("__iterof($0)") Iterator<T> iterator();
9+}

GitHub only returns the first 300 files of a commit. View the full commit on GitHub.