← Commit history
Language gaps found while building ash-json, on both ashvm and kiln
lennoxrose committed on Oct 8, 2026 at 15:59 UTC
163 files changed+4017−527
Booleans (yes/no), insertion-ordered maps, one number format, string comparison, chr/ord, sort, pop/insert/slice/delete(array, i), contains/ starts_with/ends_with/repeat, rename_file/delete_file/make_dir/list_dir, exit(code), builtins as values, forward declarations, import aliases, module-level variables in imported files, short-circuit and/or, a raise that takes any value with e.message, identical error wording, and fixes for stale attempt handlers (VM recovery bug; yield/stop/next now pop the handler). Function limit raised to 1024. kiln: push grows arrays geometrically; Windows file operations. Adds Windows test scripts that run the PE builds under Wine or an emulator.
CHANGELOG.mdmodified
+53−0@@ -1,3 +1,56 @@1
+## 2026-10-08 (item 22 reversed: no JSON in the language)2
+A built-in `json_*` library compiled into both engines was added and then removed again: the goal is general tools, not code for one module, and it needed name-mention detection and a special lookup rule in the compilers. The 'JSON in the stdlib?' item is answered by ash-json itself: its 0.3.0 codec is plain Ash built on the new tools (real booleans, insertion-ordered maps, `chr`/`ord`, forward declarations, `contains`/`repeat`, `rename_file`), with no compiler support. It parses and prints `\uXXXX` (surrogate pairs too), `\b`, `\f` and reports `json: <what> at line L, column C`. Its suite passes on both engines.3
+4
+## 2026-10-08 (the rest of the language gaps ash-json found)5
+Second batch; the numbers in parentheses are the entries of ash-json's list of language gaps. Every change is on both `ashvm` and `kiln` with a test in each `tests/cases/` tree, and the regression diffs below were checked against a baseline built from the previous commit (the intended ones: booleans printing as `yes`/`no`, insertion-ordered maps, the new number format, unified error wording). As before, there is deliberately no `remove` builtin (ash-json defines its own `remove` functions, and a builtin name would shadow them); `delete(array, i)` covers it.6
+7
+**Booleans (4):** new value type. `yes`/`no`, comparisons, `not`, `and`/`or`, `has`, `file_exists`, `contains`/`starts_with`/`ends_with`, `rename_file`/`delete_file`/`make_dir` and `write_file`/`append_file` produce it. Prints `yes`/`no`, `type()` is `"boolean"`, `str(yes)` is `"yes"`, `yes == 1` is false (arithmetic and truthiness still treat it as 1/0). `ashvm`: `VM_BOOL`, `vm_values_equal()` (also gives arrays/maps/functions identity equality, as kiln already did). `kiln`: `TAG_BOOL`. Conditions on both engines now accept booleans, numbers and `none` and raise `invalid operand type in condition` for a string/array/map/function (kiln used to treat a map as truthy; `kiln` has one `codegen_pop_and_test_truthy` instead of three copies). `filter` accepts a boolean-returning predicate.8
+9
+**Insertion-ordered maps (3):** `ashvm`'s open-addressing table became a dense entry list plus an index (deleting no longer leaves probe-chain holes, which also fixed lookups after a delete); `kiln`'s `delete` shifts later entries down instead of swapping in the last one. `keys`/`values`/printing agree on both.10
+11
+**Number format (8):** one format for both engines. `ashvm` used `%lld`/`%g` (6 significant digits), `kiln` printed 6 fraction digits and produced garbage past int64. Now: integers plainly below 1e15, otherwise up to 6 fraction digits with trailing zeros dropped, and scientific (`1e+21`, `1.5e-07`) at or above 1e15 / below 1e-6; `nan`/`inf`/`-inf`. `kiln`: `codegen/C/runtime/number_format.c`, shared by `say` and `str` (it replaces two copies and also fixes a carry bug at `x.9999999`); `ashvm`: `vm_format_number`. 6 fraction digits is not shortest-round-trip; that stays a documented limit.12
+13
+**`var.field` and `e.message` (9):** `x.name` on a local variable is `x["name"]`; indexing a string with a string yields the string itself, so `handle (e) { say e.message; }` works for built-in errors (strings) and raised maps alike on both engines.14
+15
+**Error wording and structured errors (9):** caught messages are identical on both engines and carry no prefix or detail (`index out of bounds`, `key not found`, `invalid operand type in '+'`, `could not open file`, ...); `kiln` adds `error: ` itself when an error is uncaught. `raise` takes any value (`kiln`'s raise routine now carries the value's tag), so `raise {"kind": "parse", "line": 3}` is caught as a map. Built-in errors remain strings (making every caught error a map would break `"x: " + e`); `ashvm` also gained string indexing `s[i]` to match `kiln`.16
+17
+**Sort (6):** `sort(array)` / `sort(array, cmp)`, in place, stable, returns the array; `cmp(a, b) > 0` means `a` goes after `b`. `ashvm`: merge sort; `kiln`: insertion sort (O(n^2)).18
+19
+**Files (17):** `rename_file`, `delete_file`, `make_dir`, `list_dir` (names without `.`/`..`, directory order). Windows `kiln` targets get real `MoveFileA`/`DeleteFileA`/`CreateDirectoryA` imports, and `list_dir` walks `FindFirstFileA`/`FindNextFileA`/`FindClose` (shared with the Linux path through `emit_add_entry`). The Windows code is verified by running it: `kiln/tests/scripts/windows_emulated_check.sh` executes each test case's PE under the Unicorn emulator with a Python stand-in for kernel32 (real files underneath), and all 63 runnable cases match their Linux builds, `fs_ops` included. The same cases also match under Wine 11.19 (`windows_wine_check.sh`), including `C:\\` paths for the file operations, which removes the dependence on my own kernel32 stand-in. Neither is real Windows.20
+21
+**Strings (18, 19):** `contains`, `starts_with`, `ends_with`, `repeat`. Complexity, as requested: strings carry their length now on `ashvm` (`len`, `substring`, `s[i]` no longer `strlen`), and are length-prefixed on `kiln`, so `len`/`s[i]` are O(1) and `substring` O(piece). `s += x` copies the whole string (quadratic over a loop, and nothing is freed); `join(parts, "")` is the linear builder. `kiln`'s `push` grew arrays by 4 slots per reallocation (quadratic memory against its 16 MB bump heap, so a 200k-element array crashed); it doubles now.22
+23
+**Builtins as values (11):** `type(len)`, `map(xs, str)`, `local f = len;` compile a one-line wrapper lambda by re-lexing a synthesized `forge (a0, ...) { yield name(a0, ...); }`. Variadic builtins (`sort`) wrap with their minimum arity.24
+25
+**Forward declarations (13):** a pre-pass over each file reserves every named `forge name(params)` (with its arity), so calls can precede the definition and functions can call each other. `ashvm`: `compiler/C/prescan.c`; `kiln`: calls to a not-yet-defined function emit a placeholder that is patched when the body is compiled (`function_add_call_fixup`/`function_resolve_fixups`). A reserved name that is never defined is `undefined function`. A local variable now shadows a function of the same name on `ashvm` too (kiln already did).26
+27
+**Imports (14, 15, 16):** `@import <file.ash> as name;` chooses the namespace. Transitive imports are now documented (README) and covered by `imports_transitive_test`. Every top-level `local` of an imported file is a module-level variable: its expression runs once where the `@import` is, the file's functions read it and update it (`NAME = ...;`, `NAME += ...;`, `NAME++;`, `NAME[i] = ...;`), and `file.NAME` reads it from outside. `ashvm`: `OP_GET_GLOBAL`/`OP_SET_GLOBAL` (256 slots); `kiln`: a 128-slot table in the globals region of every binary (+2 KB). It replaces the old "literal number/string constant only" rule (such constants were read-only).28
+29
+**Smaller (7, 10, 12, 20, 21):** exponent literals and `num("1.5e2")` already landed in the first batch; `exit(code)` ends the program with a status (an uncaught error exits 1), which is what a test runner needs; the `kiln` output that "silently stopped" after `attempt { delete(array, 0); ... }` came from `delete` treating an array as a map and is gone now that `delete(array, i)` exists (`attempt_array_delete` covers it).30
+31
+**Not done:** nothing left from that list.32
+33
+## 2026-10-08 (language gaps found by ash-json: function limit, `attempt` unwinding, short-circuit, string order, `chr`/`ord`, array mutation, more)34
+First batch from the ash-json 0.2.0 needs list (numbers are the entries of ash-json's list of language gaps). Everything below behaves the same on `ashvm` and `kiln` and has a test in both `tests/cases/` trees.35
+36
+**Function limit (0):** `MAX_VM_FUNCS` / `MAX_KILN_FUNCS` 64 -> 1024, so `@import <ash-json>;` (87 functions) loads.37
+38
+**Stale `attempt` handlers (9b):** two separate causes. `ashvm`: after a `longjmp`, `do_TRY_PUSH`'s recovery branch read its plain local `h`, which still pointed at a later, already-popped nested handler, so a caught error restored the wrong frame/stack. It now re-derives the handler from `try_depth`. Both engines: leaving an `attempt` block early with `yield`, `stop` or `next` skipped the handler pop, so the handler stayed active for the rest of the program. The compilers now count open `attempt` blocks per function (`attempt_enter`/`attempt_leave` in each `loop_stack.c`, which also remembers the count at loop entry) and emit the handler pops before those jumps/returns. No VM cost; kiln had this bug too (found by the new test).39
+40
+**`and` / `or` short-circuit on `ashvm` (10a):** compiled to `OP_JUMP_IF_FALSE` plus the old `OP_AND`/`OP_OR` against a constant (to keep the result 0/1); no new opcode.41
+42
+**String `<` `>` `<=` `>=` (1):** bytewise on both engines. `ashvm`: one `ORDER` macro used by the four ops and the fused `OP_CMP_JUMP`. `kiln`: `codegen_string_order()` leaves cmp-style flags that `push_bool` consumes like `ucomisd`'s. Mixed string/number is still a type error on `ashvm`; kiln keeps its "assume number" limit there.43
+44
+**`chr(n)` / `ord(s)` (2):** byte <-> one-byte string. `chr` takes 1..255 (`ashvm` strings are C strings, so no 0), `ord` of `""` is an error.45
+46
+**Arrays (5):** `delete(array, i)` removes at an index (returns the array, like `delete(map, k)`), plus new `pop(array)`, `insert(array, i, v)` (`i` may equal `len`) and `slice(array, from, to)` (`[from, to)`, strict bounds). kiln: `codegen/C/collections/array_mutate.c`; `insert` validates first, then reuses `push()` for growth. Not added as `remove`: ash-json defines functions called `remove`, and a builtin name shadows user functions.47
+48
+**Numbers (7, 12):** `ashvm` lexes exponent literals (`1e3`, `1.5e-3`) like kiln already did; kiln's `num()` now parses `e`/`E` exponents (`num("-1.5e2")` was `-1.5`).49
+50
+**Type errors (10, 10b):** kiln `"x" + 1` / `1 + "x"` and `str("text")` now raise (`invalid operand type in '+'`, `str() expects a number`) instead of returning garbage.51
+52
+**Still open at this point:** everything not listed above; the rest of the list is in the next entry up.53
+154
## 2026-10-07 (three more parity bugs, found verifying the JSON module before handing it off)255
Before writing `mission.md` (a hand-off doc for shipping the first real `forgepack` module — a JSON codec in pure Ash), actually ran the module's code through both engines rather than just writing it and hoping. Found three more real bugs this way, on top of `type()` above — the module itself was the test case the existing regression suites never exercised (real, non-trivial, deeply-nested-and-recursive user code, not hand-picked feature probes).356
README.mdmodified
+23−7@@ -88,20 +88,36 @@ optimizing native compiler.8888
8989
## Language features9090
91
-- Numbers (real doubles), strings (with escape sequences), arrays, hash maps, `none`92
-- `yes`/`no` boolean literals (plain sugar for `1`/`0` - truthiness is number-based throughout)93
-- Functions, recursion, closures with by-value capture94
-- First-class functions - pass them around, store them in variables, call them indirectly95
-- `map`, `filter`, `reduce`, `type` (runtime type introspection: `"number"/"string"/"array"/"map"/"function"/"none"`), and 20+ other built-ins (string ops, file I/O, math)96
-- `attempt` / `handle` error handling, plus `raise expr;` for user-thrown errors with a runtime string message97
-- `each (x in array)` and `during` loops, with `stop` / `next` for early exit and skip-to-next-iteration91
+- Numbers (real doubles, with `1e3`-style literals), strings (with escape sequences), arrays, hash maps, `none`92
+- A real boolean type: `yes` / `no`, and everything that answers a question (`==`, `<`, `not`, `and`, `or`, `has`, `contains`, `file_exists`, ...) produces one. They print as `yes` / `no`, `type(yes)` is `"boolean"`, and `yes == 1` is false. Conditions accept booleans, numbers and `none`; a string, array, map or function in a condition is an error93
+- `and` / `or` short-circuit; strings compare bytewise with `< <= > >=`94
+- Maps keep insertion order (`keys`, `values`, printing); `delete` closes the gap95
+- Functions, recursion, closures with by-value capture, and forward declarations: a function can be called before its `forge` appears, so mutual recursion just works96
+- First-class functions - pass them around, store them in variables, call them indirectly. Builtins are values too: `map(xs, str)`, `local f = len;`97
+- Builtins:98
+ - core: `len type str num exit sqrt abs floor chr ord`99
+ - collections: `push pop insert slice delete sort keys values has map filter reduce` (`delete(array, i)` removes an index, `sort(array)` / `sort(array, cmp)` sorts in place and is stable; `cmp(a, b)` returns a number, `> 0` meaning `a` goes after `b`)100
+ - strings: `split join substring indexOf contains starts_with ends_with replace repeat upper lower trim`101
+ - files: `read_file write_file append_file file_exists rename_file delete_file make_dir list_dir`102
+- One number-to-text format on both engines: integers print plainly up to 1e15, other values with up to 6 fraction digits, and anything beyond that (or below 1e-6) in scientific form (`1e+21`, `1.5e-07`)103
+- `attempt` / `handle` error handling. Built-in errors raise a string with the same wording on both engines (`index out of bounds`, `key not found`, `invalid operand type in '+'`, ...); `raise expr;` raises any value, so a map gives a structured error. `e.message` (more generally `var.field`, same as `var["field"]`) reads a field of a raised map, and on a plain string error it is the string itself, so `e.message` works for every error104
+- `each (x in array)` and `during` loops, with `stop` / `next` for early exit and skip-to-next-iteration (leaving an `attempt` block that way pops its handler)98105
- Compound assignment (`+= -= *= /=`) and `++` / `--`99106
- Ternary `cond ? a : b`100107
- Bitwise operators: `& | ^ ~ << >>`108
+- Imports: `@import <./file.ash>;` makes the file's functions callable as `file.name(...)`; `@import <./file.ash> as alias;` picks the namespace. Imports are transitive (a file imported by a file you import is callable by its own namespace too) and each file is loaded once. A top-level `local` in an imported file is a module-level variable: its expression runs once at import, and the file's functions can read and update it (`NAME = ...;`, `NAME += ...;`, `NAME[i] = ...;`); outside, `file.NAME` reads it109
+- `exit(code)` ends the program with that status; an uncaught error exits with status 1101110
- Colorized, Rust-style diagnostics for both compile-time and runtime102111
errors: file:line:column, a source snippet, a caret, and a contextual hint103112
- An interactive REPL with persistent variables across lines104113
114
+### Performance notes115
+116
+- `len(s)`, `substring(s, i, j)` and `s[i]` are O(1) / O(length of the piece) on both engines (strings carry their length).117
+- `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 (and `kiln` has a fixed 16 MB heap). Collect the pieces in an array and `join(parts, "")` once - that is linear.118
+- `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.120
+105121
## Getting started106122
107123
```bashashvm/Makefilemodified
+2−0@@ -23,6 +23,8 @@ ASHVM_SRC = \2323
src/compiler/C/stmt.c \2424
src/compiler/C/stmt_control.c \2525
src/compiler/C/stmt_import.c \26
+ src/compiler/C/module_state.c \27
+ src/compiler/C/prescan.c \2628
src/builtins/C/builtins.c \2729
src/builtins/C/collection.c \2830
src/builtins/C/core.c \ashvm/src/builtins/C/builtins.cmodified
+25−2@@ -8,7 +8,24 @@ static const char *builtin_names[] = {88
"map", "filter", "reduce", "keys", "values", "has", "delete",99
"split", "join", "substring", "indexOf", "replace", "upper", "lower", "trim",1010
"read_file", "write_file", "append_file", "file_exists",11
- "type"11
+ "type", "chr", "ord",12
+ "pop", "insert", "slice", "sort",13
+ "rename_file", "delete_file", "make_dir", "list_dir",14
+ "contains", "starts_with", "ends_with", "repeat",15
+ "exit"16
+};17
+// Argument count each builtin is wrapped with when used as a value18
+// (`map(xs, str)`); same order as builtin_names.19
+static const int builtin_arity[] = {20
+ 1, 2, 0, 1, 1, 1, 1, 1,21
+ 2, 2, 3, 1, 1, 2, 2,22
+ 2, 2, 3, 2, 3, 1, 1, 1,23
+ 1, 2, 2, 1,24
+ 1, 1, 1,25
+ 1, 3, 3, 1,26
+ 2, 1, 1, 1,27
+ 2, 2, 2, 2,28
+ 11229
};1330
#define NUM_BUILTINS (int)(sizeof(builtin_names) / sizeof(builtin_names[0]))1431
@@ -19,11 +36,17 @@ int vm_builtin_lookup(const char *name, int len) {1936
return -1;2037
}2138
39
+int vm_builtin_arity(int id) { return builtin_arity[id]; }40
+2241
VMValue vm_call_builtin(int id, VMValue *args, int argc) {2342
if (id >= 0 && id <= 7) return vm_call_builtin_core(id, args, argc);2443
if (id >= 8 && id <= 14) return vm_call_builtin_collection(id, args, argc);2544
if (id >= 15 && id <= 22) return vm_call_builtin_string(id, args, argc);2645
if (id >= 23 && id <= 26) return vm_call_builtin_file(id, args, argc);27
- if (id == 27) return vm_call_builtin_core(id, args, argc); // type46
+ if (id >= 27 && id <= 29) return vm_call_builtin_core(id, args, argc); // type, chr, ord47
+ if (id == 42) return vm_call_builtin_core(id, args, argc); // exit48
+ if (id >= 38 && id <= 41) return vm_call_builtin_string(id, args, argc); // contains, starts_with, ends_with, repeat49
+ if (id >= 34 && id <= 37) return vm_call_builtin_file(id, args, argc); // rename_file, delete_file, make_dir, list_dir50
+ if (id >= 30 && id <= 33) return vm_call_builtin_collection(id, args, argc); // pop, insert, slice, sort2851
vm_runtime_error("unknown builtin id: %d\n", id);2952
}ashvm/src/builtins/C/collection.cmodified
+75−6@@ -21,7 +21,7 @@ VMValue vm_call_builtin_collection(int id, VMValue *args, int argc) {2121
for (int i = 0; i < args[0].array->count; i++) {2222
VMValue call_args[1] = { args[0].array->items[i] };2323
VMValue keep = vm_call_callable_sync(args[1], call_args, 1);24
- if (keep.type == VM_NUM && keep.number != 0) vm_array_push(result, args[0].array->items[i]);24
+ if ((keep.type == VM_NUM || keep.type == VM_BOOL) && keep.number != 0) vm_array_push(result, args[0].array->items[i]);2525
}2626
return vm_array_val(result);2727
}@@ -37,7 +37,7 @@ VMValue vm_call_builtin_collection(int id, VMValue *args, int argc) {3737
case 11: { // keys3838
if (argc != 1 || args[0].type != VM_MAP) { vm_runtime_error("keys() expects a map\n"); }3939
VMArray *result = vm_array_new();40
- for (int i = 0; i < args[0].map->capacity; i++) {40
+ for (int i = 0; i < args[0].map->size; i++) {4141
if (args[0].map->entries[i].used) {4242
char *k = args[0].map->entries[i].key;4343
char *copy = malloc(strlen(k) + 1); strcpy(copy, k);@@ -49,20 +49,89 @@ VMValue vm_call_builtin_collection(int id, VMValue *args, int argc) {4949
case 12: { // values5050
if (argc != 1 || args[0].type != VM_MAP) { vm_runtime_error("values() expects a map\n"); }5151
VMArray *result = vm_array_new();52
- for (int i = 0; i < args[0].map->capacity; i++) {52
+ for (int i = 0; i < args[0].map->size; i++) {5353
if (args[0].map->entries[i].used) vm_array_push(result, args[0].map->entries[i].value);5454
}5555
return vm_array_val(result);5656
}5757
case 13: { // has5858
if (argc != 2 || args[0].type != VM_MAP || args[1].type != VM_STR) { vm_runtime_error("has(map, key) expected\n"); }59
- return vm_num(vm_map_has(args[0].map, args[1].str) ? 1 : 0);59
+ return vm_bool(vm_map_has(args[0].map, args[1].str));6060
}61
- case 14: { // delete62
- if (argc != 2 || args[0].type != VM_MAP || args[1].type != VM_STR) { vm_runtime_error("delete(map, key) expected\n"); }61
+ case 14: { // delete -- a map key, or an array index (later items shift left)62
+ if (argc == 2 && args[0].type == VM_ARRAY && args[1].type == VM_NUM) {63
+ VMArray *a = args[0].array;64
+ int i = (int)args[1].number;65
+ if (i < 0 || i >= a->count) { vm_runtime_error("index out of bounds"); }66
+ memmove(&a->items[i], &a->items[i + 1], sizeof(VMValue) * (size_t)(a->count - i - 1));67
+ a->count--;68
+ return args[0];69
+ }70
+ if (argc != 2 || args[0].type != VM_MAP || args[1].type != VM_STR) { vm_runtime_error("delete(map, key) or delete(array, index) expected\n"); }6371
vm_map_delete(args[0].map, args[1].str);6472
return args[0];6573
}74
+ case 30: { // pop -- removes and returns the last item75
+ if (argc != 1 || args[0].type != VM_ARRAY) { vm_runtime_error("pop(array) expected\n"); }76
+ if (args[0].array->count == 0) { vm_runtime_error("pop() on an empty array\n"); }77
+ return args[0].array->items[--args[0].array->count];78
+ }79
+ case 31: { // insert -- insert(array, index, value); index may equal len80
+ if (argc != 3 || args[0].type != VM_ARRAY || args[1].type != VM_NUM) { vm_runtime_error("insert(array, index, value) expected\n"); }81
+ VMArray *a = args[0].array;82
+ int i = (int)args[1].number;83
+ if (i < 0 || i > a->count) { vm_runtime_error("index out of bounds"); }84
+ vm_array_push(a, args[2]); // grows; the new slot is then shifted into place85
+ memmove(&a->items[i + 1], &a->items[i], sizeof(VMValue) * (size_t)(a->count - 1 - i));86
+ a->items[i] = args[2];87
+ return args[0];88
+ }89
+ case 32: { // slice -- slice(array, from, to) is a new array of items [from, to)90
+ if (argc != 3 || args[0].type != VM_ARRAY || args[1].type != VM_NUM || args[2].type != VM_NUM) { vm_runtime_error("slice(array, from, to) expected\n"); }91
+ int from = (int)args[1].number, to = (int)args[2].number;92
+ if (from < 0 || to > args[0].array->count || from > to) { vm_runtime_error("slice bounds out of range"); }93
+ VMArray *result = vm_array_new();94
+ for (int i = from; i < to; i++) vm_array_push(result, args[0].array->items[i]);95
+ return vm_array_val(result);96
+ }97
+ case 33: { // sort -- in place, stable merge sort; sort(array) or sort(array, cmp)98
+ if ((argc != 1 && argc != 2) || args[0].type != VM_ARRAY) { vm_runtime_error("sort(array) or sort(array, cmp) expected\n"); }99
+ int has_cmp = (argc == 2);100
+ if (has_cmp && args[1].type != VM_FUNCTION && args[1].type != VM_CLOSURE) { vm_runtime_error("sort(array, cmp) expected\n"); }101
+ VMArray *a = args[0].array;102
+ int n = a->count;103
+ if (n < 2) return args[0];104
+ VMValue *src = a->items;105
+ VMValue *tmp = malloc(sizeof(VMValue) * (size_t)n);106
+ for (int width = 1; width < n; width *= 2) {107
+ for (int lo = 0; lo < n; lo += 2 * width) {108
+ int mid = lo + width < n ? lo + width : n;109
+ int hi = lo + 2 * width < n ? lo + 2 * width : n;110
+ int l = lo, r = mid, o = lo;111
+ while (l < mid && r < hi) {112
+ // take the left item unless it must come after the right one (keeps it stable)113
+ int right_first;114
+ if (has_cmp) {115
+ VMValue call_args[2] = { src[l], src[r] };116
+ VMValue res = vm_call_callable_sync(args[1], call_args, 2);117
+ if (res.type != VM_NUM) { free(tmp); vm_runtime_error("sort() comparator must return a number\n"); }118
+ right_first = res.number > 0;119
+ } else if (src[l].type == VM_NUM && src[r].type == VM_NUM) {120
+ right_first = src[l].number > src[r].number;121
+ } else if (src[l].type == VM_STR && src[r].type == VM_STR) {122
+ right_first = strcmp(src[l].str, src[r].str) > 0;123
+ } else { free(tmp); vm_runtime_error("sort() cannot compare these values\n"); }124
+ tmp[o++] = right_first ? src[r++] : src[l++];125
+ }126
+ while (l < mid) tmp[o++] = src[l++];127
+ while (r < hi) tmp[o++] = src[r++];128
+ }129
+ VMValue *swap = src; src = tmp; tmp = swap;130
+ }131
+ if (src != a->items) memcpy(a->items, src, sizeof(VMValue) * (size_t)n);132
+ free(src == a->items ? tmp : src);133
+ return args[0];134
+ }66135
}67136
vm_runtime_error("unknown builtin id: %d\n", id);68137
}ashvm/src/builtins/C/core.cmodified
+24−3@@ -10,7 +10,7 @@ VMValue vm_call_builtin_core(int id, VMValue *args, int argc) {1010
switch (id) {1111
case 0: { // len1212
if (argc != 1) { vm_runtime_error("len() expects 1 argument\n"); }13
- if (args[0].type == VM_STR) return vm_num((int64_t)strlen(args[0].str));13
+ if (args[0].type == VM_STR) return vm_num(args[0].number);1414
if (args[0].type == VM_ARRAY) return vm_num(args[0].array->count);1515
if (args[0].type == VM_MAP) return vm_num(args[0].map->count);1616
vm_runtime_error("len() expects a string, array, or map\n");@@ -29,10 +29,13 @@ VMValue vm_call_builtin_core(int id, VMValue *args, int argc) {2929
return vm_str(copy);3030
}3131
case 3: { // str32
+ if (argc == 1 && args[0].type == VM_BOOL) {33
+ char *copy = malloc(4); strcpy(copy, args[0].number != 0 ? "yes" : "no");34
+ return vm_str(copy);35
+ }3236
if (argc != 1 || args[0].type != VM_NUM) { vm_runtime_error("str() expects a number\n"); }3337
char buf[64];34
- if (args[0].number == (long long)args[0].number) snprintf(buf, sizeof(buf), "%lld", (long long)args[0].number);35
- else snprintf(buf, sizeof(buf), "%g", args[0].number);38
+ vm_format_number(args[0].number, buf, sizeof(buf));3639
char *copy = malloc(strlen(buf) + 1); strcpy(copy, buf);3740
return vm_str(copy);3841
}@@ -62,6 +65,7 @@ VMValue vm_call_builtin_core(int id, VMValue *args, int argc) {6265
const char *name;6366
switch (args[0].type) {6467
case VM_NUM: name = "number"; break;68
+ case VM_BOOL: name = "boolean"; break;6569
case VM_STR: name = "string"; break;6670
case VM_ARRAY: name = "array"; break;6771
case VM_MAP: name = "map"; break;@@ -72,6 +76,23 @@ VMValue vm_call_builtin_core(int id, VMValue *args, int argc) {7276
char *copy = malloc(strlen(name) + 1); strcpy(copy, name);7377
return vm_str(copy);7478
}79
+ case 42: { // exit -- exit(code) ends the program with that status80
+ if (argc != 1 || args[0].type != VM_NUM) { vm_runtime_error("exit(code) expected\n"); }81
+ fflush(stdout);82
+ exit((int)args[0].number);83
+ }84
+ case 28: { // chr -- byte value to a one-byte string (strings are C strings: no 0)85
+ if (argc != 1 || args[0].type != VM_NUM) { vm_runtime_error("chr() expects a number\n"); }86
+ double n = args[0].number;87
+ if (n < 1 || n > 255) { vm_runtime_error("chr() expects a number from 1 to 255\n"); }88
+ char *s = malloc(2); s[0] = (char)(unsigned char)n; s[1] = '\0';89
+ return vm_str(s);90
+ }91
+ case 29: { // ord -- first byte of a string, 0..25592
+ if (argc != 1 || args[0].type != VM_STR) { vm_runtime_error("ord() expects a string\n"); }93
+ if (args[0].str[0] == '\0') { vm_runtime_error("ord() expects a non-empty string\n"); }94
+ return vm_num((unsigned char)args[0].str[0]);95
+ }7596
}7697
vm_runtime_error("unknown builtin id: %d\n", id);7798
}ashvm/src/builtins/C/file.cmodified
+35−7@@ -1,6 +1,8 @@11
#include <stdio.h>22
#include <stdlib.h>33
#include <string.h>4
+#include <dirent.h>5
+#include <sys/stat.h>46
#include "builtins/H/internal.h"57
#include "vm/H/vm.h"68
@@ -9,30 +11,56 @@ VMValue vm_call_builtin_file(int id, VMValue *args, int argc) {911
case 23: { // read_file1012
if (argc != 1 || args[0].type != VM_STR) { vm_runtime_error("read_file(path) expected\n"); }1113
FILE *f = fopen(args[0].str, "rb");12
- if (!f) { vm_runtime_error("could not open file: %s\n", args[0].str); }14
+ if (!f) { vm_runtime_error("could not open file"); }1315
fseek(f, 0, SEEK_END); long size = ftell(f); rewind(f);1416
char *buf = malloc(size + 1); size_t n = fread(buf, 1, size, f); buf[n] = '\0'; fclose(f);1517
return vm_str(buf);1618
}1719
case 24: { // write_file1820
if (argc != 2 || args[0].type != VM_STR || args[1].type != VM_STR) { vm_runtime_error("write_file(path, content) expected\n"); }1921
FILE *f = fopen(args[0].str, "wb");20
- if (!f) { vm_runtime_error("could not write file: %s\n", args[0].str); }22
+ if (!f) { vm_runtime_error("could not open file"); }2123
fwrite(args[1].str, 1, strlen(args[1].str), f); fclose(f);22
- return vm_num(1);24
+ return vm_bool(1);2325
}2426
case 25: { // append_file2527
if (argc != 2 || args[0].type != VM_STR || args[1].type != VM_STR) { vm_runtime_error("append_file(path, content) expected\n"); }2628
FILE *f = fopen(args[0].str, "ab");27
- if (!f) { vm_runtime_error("could not open file for append: %s\n", args[0].str); }29
+ if (!f) { vm_runtime_error("could not open file"); }2830
fwrite(args[1].str, 1, strlen(args[1].str), f); fclose(f);29
- return vm_num(1);31
+ return vm_bool(1);3032
}3133
case 26: { // file_exists3234
if (argc != 1 || args[0].type != VM_STR) { vm_runtime_error("file_exists(path) expected\n"); }3335
FILE *f = fopen(args[0].str, "rb");34
- if (f) { fclose(f); return vm_num(1); }35
- return vm_num(0);36
+ if (f) { fclose(f); return vm_bool(1); }37
+ return vm_bool(0);38
+ }39
+ case 34: { // rename_file(from, to) -> 1 on success, 0 on failure40
+ if (argc != 2 || args[0].type != VM_STR || args[1].type != VM_STR) { vm_runtime_error("rename_file(from, to) expected\n"); }41
+ return vm_bool(rename(args[0].str, args[1].str) == 0);42
+ }43
+ case 35: { // delete_file(path)44
+ if (argc != 1 || args[0].type != VM_STR) { vm_runtime_error("delete_file(path) expected\n"); }45
+ return vm_bool(remove(args[0].str) == 0);46
+ }47
+ case 36: { // make_dir(path)48
+ if (argc != 1 || args[0].type != VM_STR) { vm_runtime_error("make_dir(path) expected\n"); }49
+ return vm_bool(mkdir(args[0].str, 0755) == 0);50
+ }51
+ case 37: { // list_dir(path) -> names, no "." / "..", in directory order52
+ if (argc != 1 || args[0].type != VM_STR) { vm_runtime_error("list_dir(path) expected\n"); }53
+ DIR *d = opendir(args[0].str);54
+ if (!d) { vm_runtime_error("cannot open directory"); }55
+ VMArray *result = vm_array_new();56
+ struct dirent *e;57
+ while ((e = readdir(d)) != NULL) {58
+ if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) continue;59
+ char *copy = malloc(strlen(e->d_name) + 1); strcpy(copy, e->d_name);60
+ vm_array_push(result, vm_str(copy));61
+ }62
+ closedir(d);63
+ return vm_array_val(result);3664
}3765
}3866
vm_runtime_error("unknown builtin id: %d\n", id);ashvm/src/builtins/C/string.cmodified
+24−2@@ -44,13 +44,13 @@ VMValue vm_call_builtin_string(int id, VMValue *args, int argc) {4444
}4545
case 17: { // substring4646
if (argc != 3 || args[0].type != VM_STR) { vm_runtime_error("substring(str, start, end) expected\n"); }47
- int slen = (int)strlen(args[0].str);47
+ int slen = (int)args[0].number; // cached length4848
int start = (int)args[1].number; int end = (int)args[2].number;4949
if (start < 0) start = 0;5050
if (end > slen) end = slen;5151
if (start > end) start = end;5252
char *buf = malloc(end - start + 1); memcpy(buf, args[0].str + start, end - start); buf[end - start] = '\0';53
- return vm_str(buf);53
+ return vm_str_n(buf, (size_t)(end - start));5454
}5555
case 18: { // indexOf5656
if (argc != 2 || args[0].type != VM_STR || args[1].type != VM_STR) { vm_runtime_error("indexOf(str, search) expected\n"); }@@ -76,6 +76,28 @@ VMValue vm_call_builtin_string(int id, VMValue *args, int argc) {7676
memcpy(buf + blen, cur, remaining); blen += remaining; buf[blen] = '\0';7777
return vm_str(buf);7878
}79
+ case 38: { // contains80
+ if (argc != 2 || args[0].type != VM_STR || args[1].type != VM_STR) { vm_runtime_error("contains(str, search) expected\n"); }81
+ return vm_bool(strstr(args[0].str, args[1].str) != NULL);82
+ }83
+ case 39: { // starts_with84
+ if (argc != 2 || args[0].type != VM_STR || args[1].type != VM_STR) { vm_runtime_error("starts_with(str, prefix) expected\n"); }85
+ return vm_bool(strncmp(args[0].str, args[1].str, strlen(args[1].str)) == 0);86
+ }87
+ case 40: { // ends_with88
+ if (argc != 2 || args[0].type != VM_STR || args[1].type != VM_STR) { vm_runtime_error("ends_with(str, suffix) expected\n"); }89
+ size_t sl = strlen(args[0].str), xl = strlen(args[1].str);90
+ return vm_bool(xl <= sl && strcmp(args[0].str + (sl - xl), args[1].str) == 0);91
+ }92
+ case 41: { // repeat93
+ if (argc != 2 || args[0].type != VM_STR || args[1].type != VM_NUM) { vm_runtime_error("repeat(str, count) expected\n"); }94
+ if (args[1].number < 0) { vm_runtime_error("repeat() count must not be negative\n"); }95
+ size_t n = (size_t)args[1].number, sl = strlen(args[0].str);96
+ char *buf = malloc(sl * n + 1);97
+ for (size_t k = 0; k < n; k++) memcpy(buf + k * sl, args[0].str, sl);98
+ buf[sl * n] = '\0';99
+ return vm_str(buf);100
+ }79101
case 20: { // upper80102
if (argc != 1 || args[0].type != VM_STR) { vm_runtime_error("upper() expects a string\n"); }81103
char *copy = malloc(strlen(args[0].str) + 1); strcpy(copy, args[0].str);ashvm/src/builtins/H/builtins.hmodified
+3−0@@ -5,4 +5,7 @@55
int vm_builtin_lookup(const char *name, int len);66
VMValue vm_call_builtin(int id, VMValue *args, int argc);77
8
+// Number of arguments the builtin is wrapped with when used as a value.9
+int vm_builtin_arity(int id);10
+811
#endifashvm/src/compiler/C/calls.cmodified
+35−6@@ -8,8 +8,9 @@88
99
void emit_call(Token id) {1010
int builtin_id = vm_builtin_lookup(id.start, id.length);11
- int fn_idx = (builtin_id == -1) ? find_function_in_context(id.start, id.length) : -1;12
- int local_slot = (builtin_id == -1 && fn_idx == -1) ? resolve_local(id.start, id.length) : -1;11
+ // A local variable shadows a function of the same name (as on kiln).12
+ int local_slot = (builtin_id == -1) ? resolve_local(id.start, id.length) : -1;13
+ int fn_idx = (builtin_id == -1 && local_slot == -1) ? find_function_in_context(id.start, id.length) : -1;1314
1415
int argc = 0;1516
if (current.type != TOKEN_RPAREN) {@@ -95,6 +96,7 @@ void lambda_literal(void) {9596
memcpy(vm_functions[fn_idx].name, namebuf, namelen);9697
vm_functions[fn_idx].name[namelen] = '\0';9798
vm_functions[fn_idx].arity = 0;99
+ vm_functions[fn_idx].defined = 1;98100
chunk_init(&vm_functions[fn_idx].chunk);99101
100102
char param_names[8][64];@@ -124,9 +126,9 @@ void lambda_literal(void) {124126
char outer_locals[MAX_VM_LOCALS][64];125127
int outer_local_count = local_count;126128
memcpy(outer_locals, local_names, sizeof(local_names));127
- int outer_loop_depth;128
- loop_depth_save(&outer_loop_depth);129
- loop_depth_reset();129
+ LoopScope outer_loop_scope;130
+ loop_scope_save(&outer_loop_scope);131
+ loop_scope_reset();130132
131133
chunk = &vm_functions[fn_idx].chunk;132134
local_count = 0;@@ -140,7 +142,7 @@ void lambda_literal(void) {140142
chunk = outer_chunk;141143
local_count = outer_local_count;142144
memcpy(local_names, outer_locals, sizeof(local_names));143
- loop_depth_restore(outer_loop_depth);145
+ loop_scope_restore(outer_loop_scope);144146
145147
emit_op(OP_MAKE_CLOSURE);146148
emit((uint8_t)fn_idx);@@ -202,3 +204,30 @@ int try_fuse_condition(TokenType terminator) {202204
emit(0xff); emit(0xff);203205
return patch_loc;204206
}207
+208
+// A builtin name used as a value (`map(xs, str)`, `local f = len;`) becomes a209
+// small wrapper lambda `forge (a0, ...) { yield name(a0, ...); }`, compiled by210
+// pointing the lexer at a synthesized snippet. `current` is the token after the211
+// name; everything is restored afterwards. Returns 0 if `id` isn't a builtin.212
+int emit_builtin_value(Token id) {213
+ int builtin_id = vm_builtin_lookup(id.start, id.length);214
+ if (builtin_id == -1) return 0;215
+ int arity = vm_builtin_arity(builtin_id);216
+217
+ char *snippet = malloc(64 + 2 * (size_t)id.length + 12 * (size_t)arity); // kept: tokens point into it218
+ int n = sprintf(snippet, "forge (");219
+ for (int i = 0; i < arity; i++) n += sprintf(snippet + n, "%s__%d", i ? ", " : "", i);220
+ n += sprintf(snippet + n, ") { yield %.*s(", id.length, id.start);221
+ for (int i = 0; i < arity; i++) n += sprintf(snippet + n, "%s__%d", i ? ", " : "", i);222
+ sprintf(snippet + n, "); }");223
+224
+ Token saved_current = current, saved_previous = previous;225
+ LexerState saved_lexer = lexer_save_state();226
+ lexer_init(snippet);227
+ advance_token(); // current = 'forge'228
+ lambda_literal();229
+ lexer_restore_state(saved_lexer);230
+ current = saved_current;231
+ previous = saved_previous;232
+ return 1;233
+}ashvm/src/compiler/C/expr.cmodified
+48−4@@ -21,9 +21,9 @@ static void primary(void) {2121
return;2222
}2323
if (current.type == TOKEN_YES || current.type == TOKEN_NO) {24
- double v = current.type == TOKEN_YES ? 1.0 : 0.0;24
+ int v = current.type == TOKEN_YES;2525
advance_token();26
- emit_constant(vm_num(v));26
+ emit_constant(vm_bool(v));2727
return;2828
}2929
if (current.type == TOKEN_NONE) {@@ -68,6 +68,20 @@ static void primary(void) {6868
Token id = current;6969
advance_token();7070
71
+ // `var.field` on a variable (not a namespace) is var["field"]; on an error72
+ // string it is the string itself, so `e.message` works for built-in errors73
+ // and for raised maps alike.74
+ if (current.type == TOKEN_DOT && resolve_local(id.start, id.length) != -1) {75
+ emit2(OP_GET_LOCAL, (uint8_t)resolve_local(id.start, id.length));76
+ while (current.type == TOKEN_DOT) {77
+ advance_token();78
+ expect(TOKEN_IDENTIFIER, "expected field name after '.'");79
+ emit_constant(vm_str(vm_copy_string_escaped(previous.start, previous.length)));80
+ emit_op(OP_INDEX_GET);81
+ }82
+ return;83
+ }84
+7185
if (current.type == TOKEN_DOT) {7286
advance_token();7387
expect(TOKEN_IDENTIFIER, "expected name after '.'");@@ -85,6 +99,8 @@ static void primary(void) {8599
86100
VMConstant *k = resolve_constant(combined, combined_len);87101
if (k != NULL) { codegen_constant_value(k); return; }102
+ int state = module_state_resolve_qualified(combined, combined_len);103
+ if (state != -1) { emit2(OP_GET_GLOBAL, (uint8_t)state); return; }88104
89105
diagnostics_report("error", "undefined namespaced function or constant", id.start, id.length);90106
exit(1);@@ -112,6 +128,10 @@ static void primary(void) {112128
if (k != NULL) { codegen_constant_value(k); return; }113129
}114130
131
+ int state = module_state_resolve(id.start, id.length);132
+ if (state != -1) { emit2(OP_GET_GLOBAL, (uint8_t)state); return; }133
+ if (emit_builtin_value(id)) return;134
+115135
char msg[128];116136
snprintf(msg, sizeof(msg), "undefined variable: %.*s", id.length, id.start);117137
diagnostics_report("error", msg, id.start, id.length);@@ -208,13 +228,37 @@ static void bitwise(void) {208228
}209229
}210230
231
+// `and` / `or` short-circuit: the right operand only runs when the left232
+// doesn't already decide the result. Built from the same OP_JUMP_IF_FALSE233
+// the ternary uses; the right operand is normalized to 0/1 by OP_AND/OP_OR234
+// against a constant, so the result is the same 0/1 the old eager ops gave.211235
static void logical_and(void) {212236
bitwise();213
- while (current.type == TOKEN_AND) { advance_token(); bitwise(); emit_op(OP_AND); }237
+ while (current.type == TOKEN_AND) {238
+ advance_token();239
+ int lhs_false = emit_jump(OP_JUMP_IF_FALSE);240
+ bitwise();241
+ emit_constant(vm_num(1));242
+ emit_op(OP_AND);243
+ int end = emit_jump(OP_JUMP);244
+ patch_jump(lhs_false);245
+ emit_constant(vm_bool(0));246
+ patch_jump(end);247
+ }214248
}215249
static void logical_or(void) {216250
logical_and();217
- while (current.type == TOKEN_OR) { advance_token(); logical_and(); emit_op(OP_OR); }251
+ while (current.type == TOKEN_OR) {252
+ advance_token();253
+ int try_rhs = emit_jump(OP_JUMP_IF_FALSE);254
+ emit_constant(vm_bool(1));255
+ int end = emit_jump(OP_JUMP);256
+ patch_jump(try_rhs);257
+ logical_and();258
+ emit_constant(vm_num(0));259
+ emit_op(OP_OR);260
+ patch_jump(end);261
+ }218262
}219263
220264
// a ? b : c -- lowest precedence, sits above logical_or. Right-associativeashvm/src/compiler/C/loop_stack.cmodified
+20−3@@ -11,10 +11,12 @@ typedef struct {1111
int stop_count;1212
int next_jumps[MAX_LOOP_JUMPS];1313
int next_count;14
+ int attempt_base; // attempt_depth when the loop began1415
} LoopContext;1516
1617
static LoopContext loop_stack[MAX_LOOP_NESTING];1718
static int loop_depth = 0;19
+static int attempt_depth = 0; // attempt blocks currently open in this function body1820
1921
void loop_push(void) {2022
if (loop_depth >= MAX_LOOP_NESTING) {@@ -23,9 +25,24 @@ void loop_push(void) {2325
}2426
loop_stack[loop_depth].stop_count = 0;2527
loop_stack[loop_depth].next_count = 0;28
+ loop_stack[loop_depth].attempt_base = attempt_depth;2629
loop_depth++;2730
}2831
32
+void attempt_enter(void) { attempt_depth++; }33
+void attempt_leave(void) { attempt_depth--; }34
+35
+static void emit_try_pops(int count) {36
+ for (int i = 0; i < count; i++) emit_op(OP_TRY_POP);37
+}38
+39
+void emit_attempt_unwind_all(void) { emit_try_pops(attempt_depth); }40
+41
+// stop/next leave every attempt block opened since the innermost loop began.42
+void emit_attempt_unwind_to_loop(void) {43
+ if (loop_depth > 0) emit_try_pops(attempt_depth - loop_stack[loop_depth - 1].attempt_base);44
+}45
+2946
void loop_record_stop(Token keyword, int patch_offset) {3047
if (loop_depth == 0) {3148
diagnostics_report("error", "'stop' outside a loop", keyword.start, keyword.length);@@ -63,6 +80,6 @@ void loop_pop_and_patch_stops(void) {6380
loop_depth--;6481
}6582
66
-void loop_depth_save(int *out) { *out = loop_depth; }67
-void loop_depth_reset(void) { loop_depth = 0; }68
-void loop_depth_restore(int saved) { loop_depth = saved; }83
+void loop_scope_save(LoopScope *out) { out->loops = loop_depth; out->attempts = attempt_depth; }84
+void loop_scope_reset(void) { loop_depth = 0; attempt_depth = 0; }85
+void loop_scope_restore(LoopScope saved) { loop_depth = saved.loops; attempt_depth = saved.attempts; }ashvm/src/compiler/C/module_state.cadded
+49−0@@ -0,0 +1,49 @@1
+#include <stdio.h>2
+#include <stdlib.h>3
+#include <string.h>4
+#include "compiler/H/module_state.h"5
+#include "compiler/H/internal.h"6
+#include "vm/H/state.h"7
+#include "diagnostics/H/diagnostics.h"8
+9
+static char names[MAX_VM_GLOBALS][96];10
+static int count = 0;11
+12
+int module_state_resolve_qualified(const char *name, int len) {13
+ for (int i = 0; i < count; i++) {14
+ if ((int)strlen(names[i]) == len && strncmp(names[i], name, (size_t)len) == 0) return i;15
+ }16
+ return -1;17
+}18
+19
+int module_state_resolve(const char *name, int len) {20
+ const char *ns; int ns_len;21
+ get_import_namespace(&ns, &ns_len);22
+ if (ns_len == 0) return -1;23
+ char buf[128];24
+ int n = snprintf(buf, sizeof(buf), "%.*s.%.*s", ns_len, ns, len, name);25
+ if (n < 0 || n >= (int)sizeof(buf)) return -1;26
+ return module_state_resolve_qualified(buf, n);27
+}28
+29
+int module_state_declare(const char *name, int len) {30
+ const char *ns; int ns_len;31
+ get_import_namespace(&ns, &ns_len);32
+ char buf[128];33
+ int n = snprintf(buf, sizeof(buf), "%.*s.%.*s", ns_len, ns, len, name);34
+ if (n < 0 || n >= (int)sizeof(names[0])) {35
+ diagnostics_report("error", "module-level name too long (including namespace prefix)", current.start, current.length);36
+ exit(1);37
+ }38
+ if (module_state_resolve_qualified(buf, n) != -1) {39
+ diagnostics_report("error", "module-level name already declared", current.start, current.length);40
+ exit(1);41
+ }42
+ if (count >= MAX_VM_GLOBALS) {43
+ diagnostics_report("error", "too many module-level variables", current.start, current.length);44
+ exit(1);45
+ }46
+ memcpy(names[count], buf, (size_t)n);47
+ names[count][n] = '\0';48
+ return count++;49
+}ashvm/src/compiler/C/prescan.cadded
+48−0@@ -0,0 +1,48 @@1
+#include <stdio.h>2
+#include <stdlib.h>3
+#include "compiler/H/internal.h"4
+#include "compiler/H/compiler.h"5
+#include "diagnostics/H/diagnostics.h"6
+7
+// Forward declarations. Before a file is compiled, scan its tokens for named8
+// `forge name(a, b)` declarations and reserve each name with its arity, so an9
+// earlier function (or top-level code) can call a later one, and two functions10
+// can call each other. A declaration still has to appear somewhere in the11
+// program; check_all_functions_defined() reports a name that was reserved but12
+// never defined. Lambdas (`forge (x) { ... }`) have no name and are skipped.13
+void prescan_functions(const char *source) {14
+ LexerState saved = lexer_save_state();15
+ lexer_init(source);16
+ Token t = lexer_next_token();17
+ while (t.type != TOKEN_EOF) {18
+ if (t.type == TOKEN_FORGE) {19
+ Token name = lexer_next_token();20
+ if (name.type != TOKEN_IDENTIFIER) { t = name; continue; }21
+ Token paren = lexer_next_token();22
+ if (paren.type != TOKEN_LPAREN) { t = paren; continue; }23
+ int arity = 0;24
+ t = lexer_next_token();25
+ if (t.type != TOKEN_RPAREN) {26
+ arity = 1;27
+ while (t.type != TOKEN_RPAREN && t.type != TOKEN_EOF) {28
+ if (t.type == TOKEN_COMMA) arity++;29
+ t = lexer_next_token();30
+ }31
+ }32
+ predeclare_function_in_context(name.start, name.length, arity);33
+ }34
+ if (t.type != TOKEN_EOF) t = lexer_next_token();35
+ }36
+ lexer_restore_state(saved);37
+}38
+39
+void check_all_functions_defined(void) {40
+ for (int i = 0; i < vm_function_count; i++) {41
+ if (!vm_functions[i].defined) {42
+ char msg[128];43
+ snprintf(msg, sizeof(msg), "undefined function: %s", vm_functions[i].name);44
+ diagnostics_report("error", msg, current.start, current.length);45
+ exit(1);46
+ }47
+ }48
+}ashvm/src/compiler/C/state.cmodified
+29−0@@ -73,14 +73,43 @@ int declare_function_in_context(const char *name, int len) {7373
final_name = buf;7474
final_len = combined_len;7575
}76
+ // A prescan (prescan.c) may already have reserved this name so earlier code77
+ // could call it; the real declaration fills that entry in.78
+ int existing = find_function(final_name, final_len);79
+ if (existing != -1 && !vm_functions[existing].defined) {80
+ vm_functions[existing].defined = 1;81
+ return existing;82
+ }7683
if (vm_function_count >= MAX_VM_FUNCS) { diagnostics_report("error", "too many functions", current.start, current.length); exit(1); }7784
if (final_len >= (int)sizeof(vm_functions[0].name)) { diagnostics_report("error", "function name too long", current.start, current.length); exit(1); }7885
int fn_idx = vm_function_count++;7986
memcpy(vm_functions[fn_idx].name, final_name, (size_t)final_len);8087
vm_functions[fn_idx].name[final_len] = '\0';88
+ vm_functions[fn_idx].defined = 1;8189
return fn_idx;8290
}8391
92
+// Reserves `name` (under the current import namespace) with a known arity, not93
+// yet defined, so calls that appear before the `forge` still compile.94
+void predeclare_function_in_context(const char *name, int len, int arity) {95
+ char buf[128];96
+ const char *final_name = name;97
+ int final_len = len;98
+ if (g_import_ns_len > 0) {99
+ int combined_len = build_namespaced_name(buf, sizeof(buf), name, len);100
+ if (combined_len < 0) return; // the real declaration reports it101
+ final_name = buf;102
+ final_len = combined_len;103
+ }104
+ if (find_function(final_name, final_len) != -1) return;105
+ if (vm_function_count >= MAX_VM_FUNCS || final_len >= (int)sizeof(vm_functions[0].name)) return;106
+ int fn_idx = vm_function_count++;107
+ memcpy(vm_functions[fn_idx].name, final_name, (size_t)final_len);108
+ vm_functions[fn_idx].name[final_len] = '\0';109
+ vm_functions[fn_idx].arity = arity;110
+ vm_functions[fn_idx].defined = 0;111
+}112
+84113
int find_function_in_context(const char *name, int len) {85114
if (g_import_ns_len == 0) return find_function(name, len);86115
char buf[128];ashvm/src/compiler/C/stmt.cmodified
+38−7@@ -38,7 +38,19 @@ static void local_statement(void) {3838
// inside one of that file's own functions" except this check -- it3939
// only makes sense at block_depth == 0, same as the sibling guard in4040
// statement() above (which already gets this right).41
- if (block_depth == 0 && try_declare_import_constant(name, len)) return;41
+ if (block_depth == 0) {42
+ const char *ns; int ns_len;43
+ get_import_namespace(&ns, &ns_len);44
+ if (ns_len > 0) {45
+ // A top-level `local` of an imported file is a module-level variable.46
+ int state_slot = module_state_declare(name, len);47
+ expression();48
+ expect(TOKEN_SEMICOLON, "expected ';' after statement");49
+ emit2(OP_SET_GLOBAL, (uint8_t)state_slot);50
+ emit_op(OP_POP);51
+ return;52
+ }53
+ }4254
4355
int slot = resolve_local(name, len);4456
@@ -96,9 +108,13 @@ static void local_statement(void) {96108
}97109
}98110
111
+// A variable a statement can read and write is either a local slot (>= 0) or a112
+// module-level variable of the current imported file, encoded as -2 - index.99113
static int resolve_local_or_error(Token id) {100114
int slot = resolve_local(id.start, id.length);101115
if (slot == -1) {116
+ int state = module_state_resolve(id.start, id.length);117
+ if (state != -1) return -2 - state;102118
char msg[128];103119
snprintf(msg, sizeof(msg), "undefined variable: %.*s", id.length, id.start);104120
diagnostics_report("error", msg, id.start, id.length);@@ -113,9 +129,18 @@ static int resolve_local_or_error(Token id) {113129
// scope limit as `-`/`*`/`/` themselves. Always the general (GET_LOCAL,114130
// expression(), op, SET_LOCAL) path, not the OP_ACC_LOCAL fusion -- the115131
// RHS here is a full expression, not just a number-or-local operand.132
+static void emit_ref_get(int ref) {133
+ if (ref >= 0) emit2(OP_GET_LOCAL, (uint8_t)ref);134
+ else emit2(OP_GET_GLOBAL, (uint8_t)(-2 - ref));135
+}136
+static void emit_ref_set(int ref) {137
+ if (ref >= 0) emit2(OP_SET_LOCAL, (uint8_t)ref);138
+ else emit2(OP_SET_GLOBAL, (uint8_t)(-2 - ref));139
+}140
+116141
static void compound_assignment_statement(int slot, TokenType op) {117142
advance_token(); // consume the += / -= / *= / /=118
- emit2(OP_GET_LOCAL, (uint8_t)slot);143
+ emit_ref_get(slot);119144
expression();120145
expect(TOKEN_SEMICOLON, "expected ';' after assignment");121146
switch (op) {@@ -124,7 +149,7 @@ static void compound_assignment_statement(int slot, TokenType op) {124149
case TOKEN_STAR_EQUAL: emit_op(OP_MUL); break;125150
default: emit_op(OP_DIV); break; // TOKEN_SLASH_EQUAL126151
}127
- emit2(OP_SET_LOCAL, (uint8_t)slot);152
+ emit_ref_set(slot);128153
emit_op(OP_POP);129154
}130155
@@ -134,10 +159,10 @@ static void compound_assignment_statement(int slot, TokenType op) {134159
static void incdec_statement(int slot, TokenType op) {135160
advance_token(); // consume ++ or --136161
expect(TOKEN_SEMICOLON, "expected ';' after statement");137
- emit2(OP_GET_LOCAL, (uint8_t)slot);162
+ emit_ref_get(slot);138163
emit_constant(vm_num(1));139164
emit_op(op == TOKEN_PLUS_PLUS ? OP_ADD : OP_SUB);140
- emit2(OP_SET_LOCAL, (uint8_t)slot);165
+ emit_ref_set(slot);141166
emit_op(OP_POP);142167
}143168
@@ -175,7 +200,7 @@ static void identifier_statement(void) {175200
}176201
if (current.type == TOKEN_LBRACKET) {177202
int slot = resolve_local_or_error(id);178
- emit2(OP_GET_LOCAL, (uint8_t)slot);203
+ emit_ref_get(slot);179204
advance_token();180205
expression();181206
expect(TOKEN_RBRACKET, "expected ']'");@@ -210,7 +235,7 @@ static void identifier_statement(void) {210235
advance_token();211236
expression();212237
expect(TOKEN_SEMICOLON, "expected ';' after assignment");213
- emit2(OP_SET_LOCAL, (uint8_t)slot);238
+ emit_ref_set(slot);214239
emit_op(OP_POP);215240
return;216241
}@@ -226,6 +251,7 @@ static void stop_statement(void) {226251
Token keyword = current;227252
advance_token();228253
expect(TOKEN_SEMICOLON, "expected ';' after 'stop'");254
+ emit_attempt_unwind_to_loop();229255
loop_record_stop(keyword, emit_jump(OP_JUMP));230256
}231257
@@ -237,6 +263,7 @@ static void next_statement(void) {237263
Token keyword = current;238264
advance_token();239265
expect(TOKEN_SEMICOLON, "expected ';' after 'next'");266
+ emit_attempt_unwind_to_loop();240267
loop_record_next(keyword, emit_jump(OP_JUMP));241268
}242269
@@ -276,6 +303,7 @@ void statement(void) {276303
if (current.type != TOKEN_SEMICOLON) expression();277304
else emit_constant(vm_num(0));278305
expect(TOKEN_SEMICOLON, "expected ';' after yield");306
+ emit_attempt_unwind_all();279307
emit_op(OP_RETURN);280308
return;281309
}@@ -300,10 +328,12 @@ Chunk *compile(const char *source, const char *source_path) {300328
local_count = 0;301329
chunk_init(&main_chunk);302330
chunk = &main_chunk;331
+ prescan_functions(source);303332
lexer_init(source);304333
advance_token();305334
imports_init(source_path);306335
while (current.type != TOKEN_EOF) statement();336
+ check_all_functions_defined();307337
return &main_chunk;308338
}309339
@@ -312,6 +342,7 @@ Chunk *compile(const char *source, const char *source_path) {312342
Chunk *compile_repl_line(const char *source, Chunk *target_chunk, int *out_start_offset) {313343
chunk = target_chunk;314344
*out_start_offset = chunk->count;345
+ prescan_functions(source);315346
lexer_init(source);316347
advance_token();317348
while (current.type != TOKEN_EOF) statement();ashvm/src/compiler/C/stmt_control.cmodified
+6−4@@ -41,9 +41,9 @@ void compile_forge_decl(void) {4141
char outer_locals[MAX_VM_LOCALS][64];4242
int outer_local_count = local_count;4343
memcpy(outer_locals, local_names, sizeof(local_names));44
- int outer_loop_depth;45
- loop_depth_save(&outer_loop_depth);46
- loop_depth_reset();44
+ LoopScope outer_loop_scope;45
+ loop_scope_save(&outer_loop_scope);46
+ loop_scope_reset();4747
4848
chunk = &vm_functions[fn_idx].chunk;4949
local_count = 0;@@ -56,7 +56,7 @@ void compile_forge_decl(void) {5656
chunk = outer_chunk;5757
local_count = outer_local_count;5858
memcpy(local_names, outer_locals, sizeof(local_names));59
- loop_depth_restore(outer_loop_depth);59
+ loop_scope_restore(outer_loop_scope);6060
}6161
6262
// Conditions may be parenthesized or not (`given (x < n) {` and@@ -123,7 +123,9 @@ void compile_attempt_stmt(void) {123123
124124
int attempt_jump = emit_jump(OP_TRY_PUSH);125125
126
+ attempt_enter();126127
block();128
+ attempt_leave();127129
128130
emit_op(OP_TRY_POP);129131
int skip_handle = emit_jump(OP_JUMP);ashvm/src/compiler/C/stmt_import.cmodified
+15−0@@ -130,6 +130,19 @@ void compile_import_stmt(void) {130130
fail(message);131131
}132132
133
+ // Optional `as name`: the namespace this file's functions are called through,134
+ // instead of the one derived from its file name.135
+ char alias[64];136
+ int has_alias = 0;137
+ if (current.type == TOKEN_IDENTIFIER && current.length == 2 && strncmp(current.start, "as", 2) == 0) {138
+ advance_token();139
+ expect(TOKEN_IDENTIFIER, "expected a namespace name after 'as'");140
+ if (previous.length >= (int)sizeof(alias)) fail("namespace name too long");141
+ memcpy(alias, previous.start, (size_t)previous.length);142
+ alias[previous.length] = '\0';143
+ has_alias = 1;144
+ }145
+133146
expect(TOKEN_SEMICOLON, "expected ';' after import path");134147
135148
// Cycle check MUST come before the dedup check: a file already in@@ -147,6 +160,7 @@ void compile_import_stmt(void) {147160
148161
char ns[64];149162
import_derive_namespace(canonical, ns, sizeof(ns));163
+ if (has_alias) { memcpy(ns, alias, strlen(alias) + 1); }150164
151165
ImportedFile *collision = registry_find_by_namespace(ns);152166
if (collision != NULL) {@@ -199,6 +213,7 @@ void compile_import_stmt(void) {199213
import_path_dirname(canonical, current_file_dir, sizeof(current_file_dir));200214
set_import_namespace(entry->namespace_name, (int)strlen(entry->namespace_name));201215
216
+ prescan_functions(imported_source);202217
lexer_init(imported_source);203218
advance_token();204219
parse_imported_file_body();ashvm/src/compiler/D/calls.dmodified
+3−2@@ -1,15 +1,16 @@11
src/compiler/O/calls.o: src/compiler/C/calls.c src/compiler/H/internal.h \22
src/compiler/H/state.h src/compiler/H/compiler.h src/vm/H/chunk.h \33
src/value/H/value.h src/lexer/H/lexer.h src/compiler/H/constants.h \4
- src/builtins/H/builtins.h src/diagnostics/H/diagnostics.h \5
- src/compiler/H/loop_stack.h4
+ src/compiler/H/module_state.h src/builtins/H/builtins.h \5
+ src/diagnostics/H/diagnostics.h src/compiler/H/loop_stack.h66
src/compiler/H/internal.h:77
src/compiler/H/state.h:88
src/compiler/H/compiler.h:99
src/vm/H/chunk.h:1010
src/value/H/value.h:1111
src/lexer/H/lexer.h:1212
src/compiler/H/constants.h:13
+src/compiler/H/module_state.h:1314
src/builtins/H/builtins.h:1415
src/diagnostics/H/diagnostics.h:1516
src/compiler/H/loop_stack.h:ashvm/src/compiler/D/constants.dmodified
+3−1@@ -1,12 +1,14 @@11
src/compiler/O/constants.o: src/compiler/C/constants.c \22
src/compiler/H/constants.h src/value/H/value.h src/compiler/H/internal.h \33
src/compiler/H/state.h src/compiler/H/compiler.h src/vm/H/chunk.h \4
- src/lexer/H/lexer.h src/diagnostics/H/diagnostics.h4
+ src/lexer/H/lexer.h src/compiler/H/module_state.h \5
+ src/diagnostics/H/diagnostics.h56
src/compiler/H/constants.h:67
src/value/H/value.h:78
src/compiler/H/internal.h:89
src/compiler/H/state.h:910
src/compiler/H/compiler.h:1011
src/vm/H/chunk.h:1112
src/lexer/H/lexer.h:13
+src/compiler/H/module_state.h:1214
src/diagnostics/H/diagnostics.h:ashvm/src/compiler/D/expr.dmodified
+2−1@@ -1,12 +1,13 @@11
src/compiler/O/expr.o: src/compiler/C/expr.c src/compiler/H/internal.h \22
src/compiler/H/state.h src/compiler/H/compiler.h src/vm/H/chunk.h \33
src/value/H/value.h src/lexer/H/lexer.h src/compiler/H/constants.h \4
- src/diagnostics/H/diagnostics.h4
+ src/compiler/H/module_state.h src/diagnostics/H/diagnostics.h55
src/compiler/H/internal.h:66
src/compiler/H/state.h:77
src/compiler/H/compiler.h:88
src/vm/H/chunk.h:99
src/value/H/value.h:1010
src/lexer/H/lexer.h:1111
src/compiler/H/constants.h:12
+src/compiler/H/module_state.h:1213
src/diagnostics/H/diagnostics.h:ashvm/src/compiler/D/module_state.dadded
+15−0@@ -0,0 +1,15 @@1
+src/compiler/O/module_state.o: src/compiler/C/module_state.c \2
+ src/compiler/H/module_state.h src/compiler/H/internal.h \3
+ src/compiler/H/state.h src/compiler/H/compiler.h src/vm/H/chunk.h \4
+ src/value/H/value.h src/lexer/H/lexer.h src/compiler/H/constants.h \5
+ src/vm/H/state.h src/diagnostics/H/diagnostics.h6
+src/compiler/H/module_state.h:7
+src/compiler/H/internal.h:8
+src/compiler/H/state.h:9
+src/compiler/H/compiler.h:10
+src/vm/H/chunk.h:11
+src/value/H/value.h:12
+src/lexer/H/lexer.h:13
+src/compiler/H/constants.h:14
+src/vm/H/state.h:15
+src/diagnostics/H/diagnostics.h:ashvm/src/compiler/D/prescan.dadded
+14−0@@ -0,0 +1,14 @@1
+src/compiler/O/prescan.o: src/compiler/C/prescan.c \2
+ src/compiler/H/internal.h src/compiler/H/state.h \3
+ src/compiler/H/compiler.h src/vm/H/chunk.h src/value/H/value.h \4
+ src/lexer/H/lexer.h src/compiler/H/constants.h \5
+ src/compiler/H/module_state.h src/diagnostics/H/diagnostics.h6
+src/compiler/H/internal.h:7
+src/compiler/H/state.h:8
+src/compiler/H/compiler.h:9
+src/vm/H/chunk.h:10
+src/value/H/value.h:11
+src/lexer/H/lexer.h:12
+src/compiler/H/constants.h:13
+src/compiler/H/module_state.h:14
+src/diagnostics/H/diagnostics.h:ashvm/src/compiler/D/stmt.dmodified
+3−1@@ -1,13 +1,15 @@11
src/compiler/O/stmt.o: src/compiler/C/stmt.c src/compiler/H/internal.h \22
src/compiler/H/state.h src/compiler/H/compiler.h src/vm/H/chunk.h \33
src/value/H/value.h src/lexer/H/lexer.h src/compiler/H/constants.h \4
- src/diagnostics/H/diagnostics.h src/compiler/H/loop_stack.h4
+ src/compiler/H/module_state.h src/diagnostics/H/diagnostics.h \5
+ src/compiler/H/loop_stack.h56
src/compiler/H/internal.h:67
src/compiler/H/state.h:78
src/compiler/H/compiler.h:89
src/vm/H/chunk.h:910
src/value/H/value.h:1011
src/lexer/H/lexer.h:1112
src/compiler/H/constants.h:13
+src/compiler/H/module_state.h:1214
src/diagnostics/H/diagnostics.h:1315
src/compiler/H/loop_stack.h:ashvm/src/compiler/D/stmt_control.dmodified
+3−2@@ -2,15 +2,16 @@ src/compiler/O/stmt_control.o: src/compiler/C/stmt_control.c \22
src/compiler/H/internal.h src/compiler/H/state.h \33
src/compiler/H/compiler.h src/vm/H/chunk.h src/value/H/value.h \44
src/lexer/H/lexer.h src/compiler/H/constants.h \5
- src/diagnostics/H/diagnostics.h src/builtins/H/builtins.h \6
- src/compiler/H/loop_stack.h5
+ src/compiler/H/module_state.h src/diagnostics/H/diagnostics.h \6
+ src/builtins/H/builtins.h src/compiler/H/loop_stack.h77
src/compiler/H/internal.h:88
src/compiler/H/state.h:99
src/compiler/H/compiler.h:1010
src/vm/H/chunk.h:1111
src/value/H/value.h:1212
src/lexer/H/lexer.h:1313
src/compiler/H/constants.h:14
+src/compiler/H/module_state.h:1415
src/diagnostics/H/diagnostics.h:1516
src/builtins/H/builtins.h:1617
src/compiler/H/loop_stack.h:ashvm/src/compiler/D/stmt_import.dmodified
+3−1@@ -2,13 +2,15 @@ src/compiler/O/stmt_import.o: src/compiler/C/stmt_import.c \22
src/compiler/H/internal.h src/compiler/H/state.h \33
src/compiler/H/compiler.h src/vm/H/chunk.h src/value/H/value.h \44
src/lexer/H/lexer.h src/compiler/H/constants.h \5
- src/compiler/H/import_paths.h src/diagnostics/H/diagnostics.h5
+ src/compiler/H/module_state.h src/compiler/H/import_paths.h \6
+ src/diagnostics/H/diagnostics.h67
src/compiler/H/internal.h:78
src/compiler/H/state.h:89
src/compiler/H/compiler.h:910
src/vm/H/chunk.h:1011
src/value/H/value.h:1112
src/lexer/H/lexer.h:1213
src/compiler/H/constants.h:14
+src/compiler/H/module_state.h:1315
src/compiler/H/import_paths.h:1416
src/diagnostics/H/diagnostics.h:ashvm/src/compiler/H/compiler.hmodified
+2−1@@ -2,12 +2,13 @@22
#define ASH_VM_COMPILER_H33
#include "vm/H/chunk.h"44
5
-#define MAX_VM_FUNCS 645
+#define MAX_VM_FUNCS 102466
#define MAX_VM_LOCALS 12877
88
typedef struct {99
char name[64];1010
int arity;11
+ int defined; // 0 while only forward-declared by the prescan (see compiler/C/prescan.c)1112
Chunk chunk;1213
} VMFunction;1314
ashvm/src/compiler/H/internal.hmodified
+8−0@@ -2,15 +2,23 @@22
#define ASH_VM_COMPILER_INTERNAL_H33
#include "compiler/H/state.h"44
#include "compiler/H/constants.h"5
+#include "compiler/H/module_state.h"56
67
// Cross-file forward declarations between compiler_expr.c and78
// compiler_stmt.c -- not part of the public API (compiler.h).89
910
// compiler_expr.c1011
void expression(void);1112
13
+// compiler_prescan.c -- forward declarations: finds every `forge name(...)` in a14
+// source text up front so calls can appear before the definition, and checks at15
+// the end that every reserved name did get defined.16
+void prescan_functions(const char *source);17
+void check_all_functions_defined(void);18
+1219
// compiler_calls.c1320
void emit_call(Token id);21
+int emit_builtin_value(Token id); // builtin name used as a value -> wrapper lambda; 0 if not a builtin1422
void emit_call_named(const char *name, int len);1523
void lambda_literal(void);1624
// Tries to compile `identifier CMP (number|identifier)` directly into aashvm/src/compiler/H/loop_stack.hmodified
+15−3@@ -43,8 +43,20 @@ void loop_pop_and_patch_stops(void);4343
// reach past it into an ENCLOSING loop it happens to be textually written4444
// inside -- jumps can't cross Chunks anyway, so this is what turns that4545
// into a clean compile error instead of nonsense bytecode.46
-void loop_depth_save(int *out);47
-void loop_depth_reset(void);48
-void loop_depth_restore(int saved);46
+typedef struct { int loops; int attempts; } LoopScope;47
+void loop_scope_save(LoopScope *out);48
+void loop_scope_reset(void);49
+void loop_scope_restore(LoopScope saved);50
+51
+// An attempt block pushes a runtime handler (OP_TRY_PUSH) that only its own52
+// normal fallthrough pops, so any other way out of the block -- yield, stop,53
+// next -- must pop it explicitly or the handler is left active and a later54
+// raise jumps into it. attempt_enter/leave bracket the attempt block (not the55
+// handle block, whose handler is already gone); the emit_* calls put one56
+// OP_TRY_POP per open handler right before the exit jump/return.57
+void attempt_enter(void);58
+void attempt_leave(void);59
+void emit_attempt_unwind_all(void); // yield: leave every open attempt60
+void emit_attempt_unwind_to_loop(void); // stop/next: leave those inside the loop4961
5062
#endifashvm/src/compiler/H/module_state.hadded
+19−0@@ -0,0 +1,19 @@1
+#ifndef ASH_VM_COMPILER_MODULE_STATE_H2
+#define ASH_VM_COMPILER_MODULE_STATE_H3
+4
+// Module-level state: every top-level `local NAME = <expression>;` of an5
+// imported file. The expression runs once, where the `@import` appears (so6
+// before the importing file's own code), and the value lives in a numbered7
+// global slot instead of a frame, so every function of that file -- and8
+// `ns.NAME` from the importer -- can read it, and `NAME = ...;`, `NAME += ...;`,9
+// `NAME[i] = ...;` update it. Names are qualified with the file's namespace10
+// ("ns.NAME"), like imported functions.11
+12
+// Reserves a slot for NAME in the current import namespace; returns its index.13
+int module_state_declare(const char *name, int len);14
+// A bare NAME inside an imported file (resolved in that file's namespace), or a15
+// fully qualified "ns.NAME". Return the slot index or -1.16
+int module_state_resolve(const char *name, int len);17
+int module_state_resolve_qualified(const char *name, int len);18
+19
+#endifashvm/src/compiler/H/state.hmodified
+1−0@@ -40,6 +40,7 @@ void get_import_namespace(const char **out_ns, int *out_len);4040
// imports.c-equivalent code and compile_forge_decl share it). Exits via4141
// diagnostics_report + exit(1) on table-full or name-too-long, matching4242
// every other error path in this file.43
+void predeclare_function_in_context(const char *name, int len, int arity);4344
int declare_function_in_context(const char *name, int len);4445
4546
// While a namespace is set: resolves ONLY "ns.NAME" (no bare fallback).ashvm/src/diagnostics/C/diagnostics.cmodified
+1−1@@ -67,7 +67,7 @@ static const char *lookup_hint(const char *message) {6767
if (has_prefix(message, "index out of bounds")) return "check the array's length before indexing";6868
if (has_prefix(message, "key not found")) return "use `has(map, key)` to check before accessing";6969
if (has_prefix(message, "could not open file")) return "check the file path is correct";70
- if (has_prefix(message, "type error")) return "check the value's type before using it this way";70
+ if (has_prefix(message, "invalid operand type")) return "check the value's type before using it this way";7171
if (has_prefix(message, "could not resolve")) return "check the import path is correct and the file exists";7272
if (has_prefix(message, "circular import")) return "break the cycle by removing one of the imports in the chain shown above";7373
if (has_prefix(message, "namespace")) return "give one of the colliding files/libraries a different name, or move one out of the way";ashvm/src/lexer/C/lexer.cmodified
+8−0@@ -76,6 +76,14 @@ static Token number(void) {7676
advance(); // consume '.'7777
while (isdigit(peek())) advance();7878
}79
+ if (peek() == 'e' || peek() == 'E') {80
+ char after = peek_next();81
+ if (isdigit(after) || after == '+' || after == '-') {82
+ advance(); // consume 'e'/'E'83
+ if (peek() == '+' || peek() == '-') advance();84
+ while (isdigit(peek())) advance();85
+ }86
+ }7987
return make_token(TOKEN_NUMBER);8088
}8189
ashvm/src/value/C/hashmap.cmodified
+72−40@@ -2,77 +2,109 @@22
#include <string.h>33
#include "value/H/hashmap.h"44
5
+#define INDEX_EMPTY (-1)6
+#define INDEX_DELETED (-2)7
+58
static unsigned long hash_string(const char *s) {69
unsigned long h = 5381;710
int c;811
while ((c = *s++)) h = ((h << 5) + h) + (unsigned long)c;912
return h;1013
}1114
15
+static int *new_index(int capacity) {16
+ int *index = malloc(sizeof(int) * (size_t)capacity);17
+ for (int i = 0; i < capacity; i++) index[i] = INDEX_EMPTY;18
+ return index;19
+}20
+1221
VMMap *vm_map_new(void) {1322
VMMap *m = malloc(sizeof(VMMap));1423
m->capacity = 8;24
+ m->size = 0;1525
m->count = 0;16
- m->entries = calloc(m->capacity, sizeof(VMMapEntry));26
+ m->entries = calloc((size_t)m->capacity, sizeof(VMMapEntry));27
+ m->index_capacity = 16;28
+ m->index = new_index(m->index_capacity);1729
return m;1830
}1931
20
-static void vm_map_resize(VMMap *m) {21
- int old_capacity = m->capacity;22
- VMMapEntry *old_entries = m->entries;23
- m->capacity *= 2;24
- m->entries = calloc(m->capacity, sizeof(VMMapEntry));25
- m->count = 0;26
- for (int i = 0; i < old_capacity; i++) {27
- if (old_entries[i].used) vm_map_set(m, old_entries[i].key, old_entries[i].value);32
+// Position in `entries` of `key`, or -1.33
+static int find_entry(VMMap *m, const char *key) {34
+ unsigned long h = hash_string(key) % (unsigned long)m->index_capacity;35
+ for (int probes = 0; probes < m->index_capacity; probes++) {36
+ int pos = m->index[h];37
+ if (pos == INDEX_EMPTY) return -1;38
+ if (pos >= 0 && strcmp(m->entries[pos].key, key) == 0) return pos;39
+ h = (h + 1) % (unsigned long)m->index_capacity;2840
}29
- free(old_entries);41
+ return -1;42
+}43
+44
+static void index_insert(VMMap *m, int pos) {45
+ unsigned long h = hash_string(m->entries[pos].key) % (unsigned long)m->index_capacity;46
+ while (m->index[h] >= 0) h = (h + 1) % (unsigned long)m->index_capacity;47
+ m->index[h] = pos;48
+}49
+50
+// Called when `entries` is full: drop dead slots (keeping order), grow if the51
+// live entries still fill more than half, and rebuild the index.52
+static void compact_and_grow(VMMap *m) {53
+ int live = 0;54
+ for (int i = 0; i < m->size; i++) {55
+ if (m->entries[i].used) m->entries[live++] = m->entries[i];56
+ }57
+ m->size = live;58
+ if (live * 2 >= m->capacity) {59
+ m->capacity *= 2;60
+ m->entries = realloc(m->entries, sizeof(VMMapEntry) * (size_t)m->capacity);61
+ }62
+ for (int i = m->size; i < m->capacity; i++) m->entries[i].used = 0;63
+ free(m->index);64
+ m->index_capacity = m->capacity * 2;65
+ m->index = new_index(m->index_capacity);66
+ for (int i = 0; i < m->size; i++) index_insert(m, i);3067
}3168
3269
void vm_map_set(VMMap *m, char *key, VMValue value) {33
- if ((double)(m->count + 1) / m->capacity > 0.7) vm_map_resize(m);34
- unsigned long h = hash_string(key) % (unsigned long)m->capacity;35
- while (m->entries[h].used) {36
- if (strcmp(m->entries[h].key, key) == 0) {37
- m->entries[h].value = value;38
- free(key);39
- return;40
- }41
- h = (h + 1) % (unsigned long)m->capacity;70
+ int pos = find_entry(m, key);71
+ if (pos >= 0) {72
+ m->entries[pos].value = value;73
+ free(key);74
+ return;4275
}43
- m->entries[h].key = key;44
- m->entries[h].value = value;45
- m->entries[h].used = 1;76
+ if (m->size >= m->capacity) compact_and_grow(m);77
+ pos = m->size++;78
+ m->entries[pos].key = key;79
+ m->entries[pos].value = value;80
+ m->entries[pos].used = 1;4681
m->count++;82
+ index_insert(m, pos);4783
}4884
4985
int vm_map_get(VMMap *m, const char *key, VMValue *out) {50
- unsigned long h = hash_string(key) % (unsigned long)m->capacity;51
- unsigned long start = h;52
- while (m->entries[h].used) {53
- if (strcmp(m->entries[h].key, key) == 0) { *out = m->entries[h].value; return 1; }54
- h = (h + 1) % (unsigned long)m->capacity;55
- if (h == start) break;56
- }57
- return 0;86
+ int pos = find_entry(m, key);87
+ if (pos < 0) return 0;88
+ *out = m->entries[pos].value;89
+ return 1;5890
}5991
6092
int vm_map_has(VMMap *m, const char *key) {61
- VMValue tmp;62
- return vm_map_get(m, key, &tmp);93
+ return find_entry(m, key) >= 0;6394
}6495
6596
void vm_map_delete(VMMap *m, const char *key) {66
- unsigned long h = hash_string(key) % (unsigned long)m->capacity;67
- unsigned long start = h;68
- while (m->entries[h].used) {69
- if (strcmp(m->entries[h].key, key) == 0) {70
- free(m->entries[h].key);71
- m->entries[h].used = 0;97
+ unsigned long h = hash_string(key) % (unsigned long)m->index_capacity;98
+ for (int probes = 0; probes < m->index_capacity; probes++) {99
+ int pos = m->index[h];100
+ if (pos == INDEX_EMPTY) return;101
+ if (pos >= 0 && strcmp(m->entries[pos].key, key) == 0) {102
+ free(m->entries[pos].key);103
+ m->entries[pos].used = 0;104
+ m->index[h] = INDEX_DELETED;72105
m->count--;73106
return;74107
}75
- h = (h + 1) % (unsigned long)m->capacity;76
- if (h == start) break;108
+ h = (h + 1) % (unsigned long)m->index_capacity;77109
}78110
}ashvm/src/value/C/value.cmodified
+67−10@@ -1,3 +1,4 @@1
+#include <math.h>12
#include <stdio.h>23
#include <stdlib.h>34
#include <string.h>@@ -7,11 +8,33 @@78
VMValue vm_num(double n) {89
VMValue v; v.type = VM_NUM; v.number = n; v.str = NULL; v.array = NULL; v.map = NULL; v.closure = NULL; return v;910
}11
+VMValue vm_bool(int b) {12
+ VMValue v; v.type = VM_BOOL; v.number = b ? 1 : 0; v.str = NULL; v.array = NULL; v.map = NULL; v.closure = NULL; return v;13
+}14
+int vm_values_equal(VMValue a, VMValue b) {15
+ if (a.type != b.type) return 0;16
+ switch (a.type) {17
+ case VM_STR: return strcmp(a.str, b.str) == 0;18
+ case VM_NUM: case VM_BOOL: case VM_FUNCTION: return a.number == b.number;19
+ case VM_NIL: return 1;20
+ case VM_ARRAY: return a.array == b.array;21
+ case VM_MAP: return a.map == b.map;22
+ case VM_CLOSURE: return a.closure == b.closure;23
+ }24
+ return 0;25
+}1026
VMValue vm_nil(void) {1127
VMValue v; v.type = VM_NIL; v.number = 0; v.str = NULL; v.array = NULL; v.map = NULL; v.closure = NULL; return v;1228
}29
+// A string value caches its byte length in `number`, so len(), substring() and30
+// s[i] are O(1) instead of an strlen each time. vm_str measures once;31
+// vm_str_n is for callers that already know the length.32
+VMValue vm_str_n(char *s, size_t len) {33
+ VMValue v; v.type = VM_STR; v.number = (double)len; v.str = s;34
+ v.array = NULL; v.map = NULL; v.closure = NULL; return v;35
+}1336
VMValue vm_str(char *s) {14
- VMValue v; v.type = VM_STR; v.number = 0; v.str = s; v.array = NULL; v.map = NULL; v.closure = NULL; return v;37
+ 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;1538
}1639
VMValue vm_array_val(VMArray *a) {1740
VMValue v; v.type = VM_ARRAY; v.number = 0; v.str = NULL; v.array = a; v.map = NULL; v.closure = NULL; return v;@@ -51,14 +74,13 @@ char *vm_copy_string_escaped(const char *start, int length) {5174
return buf;5275
}5376
54
-char *vm_concat_strings(const char *a, const char *b) {55
- int la = (int)strlen(a);56
- int lb = (int)strlen(b);77
+VMValue vm_concat_strings(VMValue a, VMValue b) {78
+ size_t la = (size_t)a.number, lb = (size_t)b.number;5779
char *buf = malloc(la + lb + 1);58
- memcpy(buf, a, la);59
- memcpy(buf + la, b, lb);80
+ memcpy(buf, a.str, la);81
+ memcpy(buf + la, b.str, lb);6082
buf[la + lb] = '\0';61
- return buf;83
+ return vm_str_n(buf, la + lb);6284
}6385
6486
VMArray *vm_array_new(void) {@@ -77,10 +99,45 @@ void vm_array_push(VMArray *arr, VMValue v) {7799
arr->items[arr->count++] = v;78100
}79101
102
+// Drops trailing zeros (and a bare '.') from the fraction in the mantissa103
+// ending at `end` (exclusive); returns the new end.104
+static char *trim_fraction(char *start, char *end) {105
+ if (!memchr(start, '.', (size_t)(end - start))) return end;106
+ while (end > start && end[-1] == '0') end--;107
+ if (end > start && end[-1] == '.') end--;108
+ return end;109
+}110
+111
+// Ash's one number-to-text format (kiln's number_format_emit matches it):112
+// nan/inf/-inf; plain decimal with at most 6 fraction digits (trailing zeros113
+// dropped) for 0 and 1e-6 <= |x| < 1e15; otherwise scientific with a mantissa114
+// in [1,10) ("1e+21", "1.5e-07").115
+void vm_format_number(double x, char *buf, size_t size) {116
+ if (isnan(x)) { snprintf(buf, size, "nan"); return; }117
+ if (isinf(x)) { snprintf(buf, size, x < 0 ? "-inf" : "inf"); return; }118
+ double a = fabs(x);119
+ if (a == 0 || (a >= 1e-6 && a < 1e15)) {120
+ snprintf(buf, size, "%.6f", x);121
+ char *end = trim_fraction(buf, buf + strlen(buf));122
+ *end = '\0';123
+ if (strcmp(buf, "-0") == 0) strcpy(buf, "0");124
+ return;125
+ }126
+ snprintf(buf, size, "%.6e", x);127
+ char *e = strchr(buf, 'e');128
+ char exponent[16];129
+ snprintf(exponent, sizeof(exponent), "%s", e);130
+ char *end = trim_fraction(buf, e);131
+ memmove(end, exponent, strlen(exponent) + 1);132
+}133
+80134
void vm_print_value(VMValue v) {81135
if (v.type == VM_NUM) {82
- if (v.number == (long long)v.number) printf("%lld", (long long)v.number);83
- else printf("%g", v.number);136
+ char buf[64];137
+ vm_format_number(v.number, buf, sizeof(buf));138
+ printf("%s", buf);139
+ } else if (v.type == VM_BOOL) {140
+ printf("%s", v.number != 0 ? "yes" : "no");84141
} else if (v.type == VM_STR) {85142
printf("%s", v.str);86143
} else if (v.type == VM_NIL) {@@ -101,7 +158,7 @@ void vm_print_value(VMValue v) {101158
} else if (v.type == VM_MAP) {102159
printf("{");103160
int first = 1;104
- for (int i = 0; i < v.map->capacity; i++) {161
+ for (int i = 0; i < v.map->size; i++) {105162
if (v.map->entries[i].used) {106163
if (!first) printf(", ");107164
printf("\"%s\": ", v.map->entries[i].key);ashvm/src/value/H/hashmap.hmodified
+10−2@@ -2,6 +2,11 @@22
#define ASH_VM_HASHMAP_H33
#include "value/H/value.h"44
5
+// Insertion-ordered map: `entries` is a dense list in the order keys were first6
+// set; `index` is an open-addressing table of positions into it, used only for7
+// lookup. Deleting leaves a dead slot (used == 0) so the order of the others8
+// is untouched; dead slots are squeezed out when the list fills up. Iterate9
+// `entries[0 .. size)` and skip entries with used == 0.510
typedef struct {611
char *key;712
VMValue value;@@ -10,8 +15,11 @@ typedef struct {1015
1116
struct VMMap {1217
VMMapEntry *entries;13
- int count;14
- int capacity;18
+ int size; // slots filled in `entries`, live or dead19
+ int count; // live entries20
+ int capacity; // allocated slots in `entries`21
+ int *index; // index_capacity slots: -1 empty, -2 deleted, else a position in entries22
+ int index_capacity;1523
};1624
1725
VMMap *vm_map_new(void);ashvm/src/value/H/value.hmodified
+8−2@@ -2,7 +2,7 @@22
#define ASH_VM_VALUE_H33
#include <stdint.h>44
5
-typedef enum { VM_NUM, VM_STR, VM_ARRAY, VM_MAP, VM_FUNCTION, VM_CLOSURE, VM_NIL } VMType;5
+typedef enum { VM_NUM, VM_STR, VM_ARRAY, VM_MAP, VM_FUNCTION, VM_CLOSURE, VM_NIL, VM_BOOL } VMType;66
77
typedef struct VMArray {88
struct VMValue *items;@@ -28,15 +28,21 @@ typedef struct VMValue {2828
} VMValue;2929
3030
VMValue vm_num(double n);31
+32
+// Number -> text in Ash's single format (see value.c).33
+void vm_format_number(double x, char *buf, size_t size);3134
VMValue vm_nil(void);35
+VMValue vm_bool(int b); // yes / no: number holds 1 / 036
+int vm_values_equal(VMValue a, VMValue b); // == semantics: by value for strings, numbers, booleans, none; by identity otherwise3237
VMValue vm_str(char *s);38
+VMValue vm_str_n(char *s, size_t len); // len = strlen(s), already known3339
VMValue vm_array_val(VMArray *a);3440
VMValue vm_map_val(VMMap *m);3541
VMValue vm_function_val(int function_index);3642
VMValue vm_closure_val(VMClosureObj *c);3743
3844
char *vm_copy_string_escaped(const char *start, int length);39
-char *vm_concat_strings(const char *a, const char *b);45
+VMValue vm_concat_strings(VMValue a, VMValue b); // both VM_STR4046
4147
VMArray *vm_array_new(void);4248
void vm_array_push(VMArray *arr, VMValue v);ashvm/src/vm/C/dispatch.cmodified
+44−45@@ -16,6 +16,12 @@1616
// that makes this VM fast in the first place. Everything that CAN live1717
// outside it (error handling, the public call/run API, shared state) does,1818
// in vm_errors.c / vm_api.c / vm_state.c.19
+// Ordering comparison: numbers numerically, strings bytewise (strcmp);20
+// anything else (or a mix) is the usual type error.21
+#define ORDER(a, b, op) \22
+ (((a).type == VM_STR && (b).type == VM_STR) ? strcmp((a).str, (b).str) op 0 \23
+ : require_num((a), "comparison") op require_num((b), "comparison"))24
+1925
void vm_execute(int stop_at_frame_count) {2026
CallFrame *volatile frame = &frames[frame_count - 1];2127
@@ -34,7 +40,8 @@ void vm_execute(int stop_at_frame_count) {3440
&&do_TRY_PUSH, &&do_TRY_POP,3541
&&do_RAISE,3642
&&do_BAND, &&do_BOR, &&do_BXOR, &&do_BNOT, &&do_SHL, &&do_SHR,37
- &&do_TRUNC_LOCALS43
+ &&do_TRUNC_LOCALS,44
+ &&do_GET_GLOBAL, &&do_SET_GLOBAL3845
};3946
4047
#define DISPATCH() \@@ -52,7 +59,7 @@ void vm_execute(int stop_at_frame_count) {5259
do_ADD: {5360
VMValue b = *--stack_top;5461
VMValue *a = stack_top - 1;55
- if (a->type == VM_STR && b.type == VM_STR) *a = vm_str(vm_concat_strings(a->str, b.str));62
+ if (a->type == VM_STR && b.type == VM_STR) *a = vm_concat_strings(*a, b);5663
else *a = vm_num(require_num(*a, "'+'") + require_num(b, "'+'"));5764
DISPATCH();5865
}@@ -61,33 +68,15 @@ void vm_execute(int stop_at_frame_count) {6168
do_DIV: { VMValue b = *--stack_top; VMValue *a = stack_top - 1; *a = vm_num(require_num(*a, "'/'") / require_num(b, "'/'")); DISPATCH(); }6269
do_MOD: { VMValue b = *--stack_top; VMValue *a = stack_top - 1; *a = vm_num(fmod(require_num(*a, "'%'"), require_num(b, "'%'"))); DISPATCH(); }6370
do_NEG: { stack_top[-1] = vm_num(-require_num(stack_top[-1], "unary '-'")); DISPATCH(); }64
- do_NOT: { stack_top[-1] = vm_num(require_num(stack_top[-1], "'!'") == 0 ? 1 : 0); DISPATCH(); }65
- do_EQ: {66
- VMValue b = *--stack_top; VMValue *a = stack_top - 1;67
- int eq;68
- if (a->type == VM_STR && b.type == VM_STR) eq = strcmp(a->str, b.str) == 0;69
- else if (a->type == VM_NUM && b.type == VM_NUM) eq = a->number == b.number;70
- else if (a->type == VM_NIL && b.type == VM_NIL) eq = 1;71
- else eq = 0;72
- *a = vm_num(eq);73
- DISPATCH();74
- }75
- do_NEQ: {76
- VMValue b = *--stack_top; VMValue *a = stack_top - 1;77
- int eq;78
- if (a->type == VM_STR && b.type == VM_STR) eq = strcmp(a->str, b.str) == 0;79
- else if (a->type == VM_NUM && b.type == VM_NUM) eq = a->number == b.number;80
- else if (a->type == VM_NIL && b.type == VM_NIL) eq = 1;81
- else eq = 0;82
- *a = vm_num(!eq);83
- DISPATCH();84
- }85
- do_LT: { VMValue b = *--stack_top; VMValue *a = stack_top - 1; *a = vm_num(require_num(*a, "comparison") < require_num(b, "comparison")); DISPATCH(); }86
- do_LE: { VMValue b = *--stack_top; VMValue *a = stack_top - 1; *a = vm_num(require_num(*a, "comparison") <= require_num(b, "comparison")); DISPATCH(); }87
- do_GT: { VMValue b = *--stack_top; VMValue *a = stack_top - 1; *a = vm_num(require_num(*a, "comparison") > require_num(b, "comparison")); DISPATCH(); }88
- do_GE: { VMValue b = *--stack_top; VMValue *a = stack_top - 1; *a = vm_num(require_num(*a, "comparison") >= require_num(b, "comparison")); DISPATCH(); }89
- do_AND: { VMValue b = *--stack_top; VMValue *a = stack_top - 1; *a = vm_num((truthy(*a, "'&&'") && truthy(b, "'&&'")) ? 1 : 0); DISPATCH(); }90
- do_OR: { VMValue b = *--stack_top; VMValue *a = stack_top - 1; *a = vm_num((truthy(*a, "'||'") || truthy(b, "'||'")) ? 1 : 0); DISPATCH(); }71
+ do_NOT: { stack_top[-1] = vm_bool(!truthy(stack_top[-1], "'!'")); DISPATCH(); }72
+ do_EQ: { VMValue b = *--stack_top; VMValue *a = stack_top - 1; *a = vm_bool(vm_values_equal(*a, b)); DISPATCH(); }73
+ do_NEQ: { VMValue b = *--stack_top; VMValue *a = stack_top - 1; *a = vm_bool(!vm_values_equal(*a, b)); DISPATCH(); }74
+ do_LT: { VMValue b = *--stack_top; VMValue *a = stack_top - 1; *a = vm_bool(ORDER(*a, b, <)); DISPATCH(); }75
+ do_LE: { VMValue b = *--stack_top; VMValue *a = stack_top - 1; *a = vm_bool(ORDER(*a, b, <=)); DISPATCH(); }76
+ do_GT: { VMValue b = *--stack_top; VMValue *a = stack_top - 1; *a = vm_bool(ORDER(*a, b, >)); DISPATCH(); }77
+ do_GE: { VMValue b = *--stack_top; VMValue *a = stack_top - 1; *a = vm_bool(ORDER(*a, b, >=)); DISPATCH(); }78
+ do_AND: { VMValue b = *--stack_top; VMValue *a = stack_top - 1; *a = vm_bool(truthy(*a, "'&&'") && truthy(b, "'&&'")); DISPATCH(); }79
+ do_OR: { VMValue b = *--stack_top; VMValue *a = stack_top - 1; *a = vm_bool(truthy(*a, "'||'") || truthy(b, "'||'")); DISPATCH(); }9180
do_PRINT: { VMValue v = *--stack_top; vm_print_value(v); printf("\n"); DISPATCH(); }9281
do_POP: { --stack_top; DISPATCH(); }9382
do_GET_LOCAL: { uint8_t slot = *frame->ip++; *stack_top++ = frame->slots[slot]; DISPATCH(); }@@ -165,7 +154,7 @@ void vm_execute(int stop_at_frame_count) {165154
int op = flags & 0x03;166155
VMValue *a = &frame->slots[slot_a];167156
if (op == 0) {168
- if (a->type == VM_STR && b.type == VM_STR) *a = vm_str(vm_concat_strings(a->str, b.str));157
+ if (a->type == VM_STR && b.type == VM_STR) *a = vm_concat_strings(*a, b);169158
else *a = vm_num(require_num(*a, "'+'") + require_num(b, "'+'"));170159
} else if (op == 1) {171160
*a = vm_num(require_num(*a, "'-'") - require_num(b, "'-'"));@@ -185,20 +174,14 @@ void vm_execute(int stop_at_frame_count) {185174
int cmp_code = flags & 0x0F;186175
int taken;187176
if (cmp_code == 4 || cmp_code == 5) {188
- int eq;189
- if (a.type == VM_STR && b.type == VM_STR) eq = strcmp(a.str, b.str) == 0;190
- else if (a.type == VM_NUM && b.type == VM_NUM) eq = a.number == b.number;191
- else if (a.type == VM_NIL && b.type == VM_NIL) eq = 1;192
- else eq = 0;177
+ int eq = vm_values_equal(a, b);193178
taken = (cmp_code == 4) ? eq : !eq;194179
} else {195
- double av = require_num(a, "comparison");196
- double bv = require_num(b, "comparison");197180
switch (cmp_code) {198
- case 0: taken = av < bv; break;199
- case 1: taken = av <= bv; break;200
- case 2: taken = av > bv; break;201
- default: taken = av >= bv; break;181
+ case 0: taken = ORDER(a, b, <); break;182
+ case 1: taken = ORDER(a, b, <=); break;183
+ case 2: taken = ORDER(a, b, >); break;184
+ default: taken = ORDER(a, b, >=); break;202185
}203186
}204187
if (!taken) frame->ip += off;@@ -217,13 +200,20 @@ void vm_execute(int stop_at_frame_count) {217200
VMValue arr = *--stack_top;218201
if (arr.type == VM_ARRAY) {219202
int64_t i = (int64_t)require_num(idx, "array index");220
- if (i < 0 || i >= arr.array->count) vm_runtime_error("index out of bounds: %lld", (long long)i);203
+ if (i < 0 || i >= arr.array->count) vm_runtime_error("index out of bounds");221204
*stack_top++ = arr.array->items[i];222205
} else if (arr.type == VM_MAP) {223206
if (idx.type != VM_STR) vm_runtime_error("map keys must be strings");224207
VMValue out;225
- if (!vm_map_get(arr.map, idx.str, &out)) vm_runtime_error("key not found: %s", idx.str);208
+ if (!vm_map_get(arr.map, idx.str, &out)) vm_runtime_error("key not found");226209
*stack_top++ = out;210
+ } else if (arr.type == VM_STR && idx.type == VM_STR) {211
+ *stack_top++ = arr; // string[string]: an error value's `.message` is the string itself212
+ } else if (arr.type == VM_STR) {213
+ int64_t i = (int64_t)require_num(idx, "string index");214
+ if (i < 0 || i >= (int64_t)arr.number) vm_runtime_error("index out of bounds");215
+ char *one = malloc(2); one[0] = arr.str[i]; one[1] = '\0';216
+ *stack_top++ = vm_str(one);227217
} else {228218
type_error("indexing");229219
}@@ -235,12 +225,14 @@ void vm_execute(int stop_at_frame_count) {235225
VMValue arr = *--stack_top;236226
if (arr.type == VM_ARRAY) {237227
int64_t i = (int64_t)require_num(idx, "array index");238
- if (i < 0 || i >= arr.array->count) vm_runtime_error("index out of bounds: %lld", (long long)i);228
+ if (i < 0 || i >= arr.array->count) vm_runtime_error("index out of bounds");239229
arr.array->items[i] = value;240230
} else if (arr.type == VM_MAP) {241231
if (idx.type != VM_STR) vm_runtime_error("map keys must be strings");242232
char *key_copy = malloc(strlen(idx.str) + 1); strcpy(key_copy, idx.str);243233
vm_map_set(arr.map, key_copy, value);234
+ } else if (arr.type == VM_STR) {235
+ vm_runtime_error("cannot assign to a string index");244236
} else {245237
type_error("index-assign");246238
}@@ -283,7 +275,10 @@ void vm_execute(int stop_at_frame_count) {283275
if (setjmp(h->jmp) == 0) {284276
DISPATCH();285277
} else {286
- try_depth--;278
+ // `h` is a plain local reused by every TRY_PUSH, so after a longjmp it279
+ // may point at a later (now stale) handler; the one that fired is280
+ // always the top of the handler stack.281
+ h = &try_handlers[--try_depth];287282
frame_count = h->saved_frame_count;288283
stack_top = h->saved_stack_top;289284
frame = h->target_frame;@@ -325,5 +320,9 @@ void vm_execute(int stop_at_frame_count) {325320
// static per chunk, "what the stack should look like once this block326321
// is done" is just "n slots past frame->slots" -- always correct327322
// regardless of the path taken through the block this time.323
+ // Module-level variables (see compiler/C/module_state.c). SET_GLOBAL keeps the324
+ // value on the stack, like SET_LOCAL.325
+ do_GET_GLOBAL: { uint8_t idx = *frame->ip++; *stack_top++ = vm_globals[idx]; DISPATCH(); }326
+ do_SET_GLOBAL: { uint8_t idx = *frame->ip++; vm_globals[idx] = stack_top[-1]; DISPATCH(); }328327
do_TRUNC_LOCALS: { uint8_t n = *frame->ip++; stack_top = frame->slots + n; DISPATCH(); }329328
}ashvm/src/vm/C/errors.cmodified
+10−9@@ -1,6 +1,7 @@11
#include <stdio.h>22
#include <stdlib.h>33
#include <stdarg.h>4
+#include <string.h>45
#include "vm/H/vm.h"56
#include "vm/H/state.h"67
#include "diagnostics/H/diagnostics.h"@@ -15,6 +16,8 @@ void vm_runtime_error(const char *fmt, ...) {1516
va_start(args, fmt);1617
vsnprintf(error_message, sizeof(error_message), fmt, args);1718
va_end(args);19
+ size_t n = strlen(error_message); // messages are caught as values: no trailing newline20
+ while (n > 0 && error_message[n - 1] == '\n') error_message[--n] = '\0';1821
1922
if (try_depth > 0) {2023
longjmp(try_handlers[try_depth - 1].jmp, 1);@@ -29,37 +32,35 @@ void vm_runtime_error(const char *fmt, ...) {2932
}3033
}3134
32
-// `raise expr;` -- expr must already be a VM_STR (checked here, not at33
-// compile time, since the raised value is a general expression that could34
-// be anything at runtime). Mirrors vm_runtime_error's three-way dispatch35
+// `raise expr;` -- any value (a string is what built-in errors raise; a map36
+// gives a structured error). Mirrors vm_runtime_error's three-way dispatch3537
// (attempt handler / REPL recovery / hard exit) exactly, but hands back3638
// the exact raised VMValue via `raised_value` instead of funneling it3739
// through the fixed-size error_message buffer -- see state.h.3840
void vm_raise_value(VMValue msg) {39
- if (msg.type != VM_STR) type_error("'raise'");40
-4141
raised_value = msg;42
+ const char *text = msg.type == VM_STR ? msg.str : "uncaught non-string value";4243
has_raised_value = 1;4344
4445
if (try_depth > 0) {4546
longjmp(try_handlers[try_depth - 1].jmp, 1);4647
} else if (repl_recovery_jmp != NULL) {47
- diagnostics_report("error", msg.str, current_pos, current_poslen);48
+ diagnostics_report("error", text, current_pos, current_poslen);4849
frame_count = repl_saved_frame_count;4950
stack_top = repl_saved_stack_top;5051
longjmp(*repl_recovery_jmp, 1);5152
} else {52
- diagnostics_report("error", msg.str, current_pos, current_poslen);53
+ diagnostics_report("error", text, current_pos, current_poslen);5354
exit(1);5455
}5556
}5657
5758
void type_error(const char *context) {58
- vm_runtime_error("type error: invalid operand type in %s", context);59
+ vm_runtime_error("invalid operand type in %s", context);5960
}6061
6162
double require_num(VMValue v, const char *context) {62
- if (v.type != VM_NUM) type_error(context);63
+ if (v.type != VM_NUM && v.type != VM_BOOL) type_error(context);6364
return v.number;6465
}6566
ashvm/src/vm/C/state.cmodified
+1−0@@ -2,6 +2,7 @@22
#include "vm/H/state.h"33
44
VMValue stack[STACK_MAX];5
+VMValue vm_globals[MAX_VM_GLOBALS];56
VMValue *stack_top;67
CallFrame frames[FRAMES_MAX];78
int frame_count;ashvm/src/vm/H/chunk.hmodified
+2−1@@ -25,7 +25,8 @@ typedef enum {2525
OP_TRY_POP,2626
OP_RAISE,2727
OP_BAND, OP_BOR, OP_BXOR, OP_BNOT, OP_SHL, OP_SHR,28
- OP_TRUNC_LOCALS28
+ OP_TRUNC_LOCALS,29
+ OP_GET_GLOBAL, OP_SET_GLOBAL2930
} OpCode;3031
3132
typedef struct {ashvm/src/vm/H/state.hmodified
+2−0@@ -26,6 +26,8 @@ typedef struct {2626
uint8_t *target_ip;2727
} TryHandler;2828
29
+#define MAX_VM_GLOBALS 25630
+extern VMValue vm_globals[MAX_VM_GLOBALS]; // module-level variables of imported files2931
extern VMValue stack[STACK_MAX];3032
extern VMValue *stack_top;3133
extern CallFrame frames[FRAMES_MAX];ashvm/tests/cases/basics/booleans.ashadded
+35−0@@ -0,0 +1,35 @@1
+say yes;2
+say no;3
+say 1 < 2;4
+say 2 < 1;5
+say not yes;6
+say not 0;7
+say 1 == 1;8
+say "a" == "a";9
+say "a" == "b";10
+say 1 and 1;11
+say 0 or 1;12
+say yes == 1;13
+say yes == yes;14
+say type(yes);15
+say type(1 < 2);16
+say [yes, no, 3 > 4];17
+say {"ok": yes};18
+say str(no) + str(yes);19
+say has({"a": 1}, "a");20
+say has({"a": 1}, "b");21
+say yes + 1;22
+say file_exists("/nonexistent");23
+say contains("abc", "b");24
+local flag = 5 > 3;25
+given flag { say "flag set"; }26
+given not flag { say "bad"; } otherwise { say "ok"; }27
+say flag and not no;28
+say none == none;29
+say not none;30
+attempt { given {"a": 1} { say "bad"; } } handle (e) { say "map condition"; }31
+attempt { given "x" { say "bad"; } } handle (e) { say "string condition"; }32
+local n = 0;33
+during n < 3 { n++; }34
+say n;35
+say yes ? "t" : "f";ashvm/tests/cases/basics/exponent_literals.ashadded
+6−0@@ -0,0 +1,6 @@1
+say 1e3;2
+say 1.5e-3;3
+say 2E2 + 1;4
+say num("-1.5e2");5
+say num("1e3") + 1;6
+say num("2.5E-1");ashvm/tests/cases/basics/number_format.ashadded
+26−0@@ -0,0 +1,26 @@1
+say 0;2
+say 3;3
+say -42;4
+say 0.5;5
+say -0.25;6
+say 3.14159265;7
+say 0.1 + 0.2;8
+say 123456789012;9
+say 999999999999999;10
+say 1e15;11
+say 1e21;12
+say -1e21;13
+say 1.5e-7;14
+say 1e-7;15
+say 0.000001;16
+say 0.0000005;17
+say 123456.789;18
+say 1e100;19
+say 1.2345678e30;20
+say 9.9999996;21
+say 0.9999999;22
+say 2.5e-300;23
+say 1 / 0;24
+say -1 / 0;25
+say str(1e21) + "|" + str(1.5) + "|" + str(-7);26
+say [1.5, 1e21, 2];ashvm/tests/cases/collections/array_mutation.ashadded
+16−0@@ -0,0 +1,16 @@1
+local a = [1, 2, 3, 4];2
+delete(a, 1);3
+say a;4
+say pop(a);5
+say a;6
+insert(a, 0, 9);7
+insert(a, len(a), 7);8
+insert(a, 1, 8);9
+say a;10
+say slice(a, 1, 3);11
+say slice(a, 0, 0);12
+attempt { pop([]); } handle (e) { say "empty"; }13
+attempt { delete(a, 10); } handle (e) { say "oob"; }14
+local m = {"k": 1};15
+delete(m, "k");16
+say len(m);ashvm/tests/cases/collections/map_order.ashadded
+30−0@@ -0,0 +1,30 @@1
+local m = {};2
+m["b"] = 1; m["zeta"] = 2; m["alpha"] = 3; m["a"] = 4; m["mid"] = 5;3
+say keys(m);4
+say values(m);5
+delete(m, "alpha");6
+say keys(m);7
+m["alpha"] = 9;8
+m["b"] = 100;9
+say keys(m);10
+say m;11
+delete(m, "b");12
+delete(m, "zzz");13
+say keys(m);14
+say len(m);15
+say has(m, "b");16
+say has(m, "mid");17
+local big = {};18
+local i = 0;19
+during i < 100 { big[str(i)] = i; i++; }20
+i = 0;21
+during i < 100 { given i % 2 == 0 { delete(big, str(i)); } i++; }22
+say len(big);23
+say big["51"];24
+say keys(big)[0];25
+say keys(big)[49];26
+i = 0;27
+during i < 100 { given i % 2 == 0 { big[str(i)] = i; } i++; }28
+say len(big);29
+say keys(big)[50];30
+say big["98"];ashvm/tests/cases/collections/sort.ashadded
+20−0@@ -0,0 +1,20 @@1
+local a = [5, 2, 9, 1, 5, 6, -3, 0];2
+sort(a);3
+say a;4
+local s = ["pear", "apple", "fig", "banana", "apple", ""];5
+say sort(s);6
+local m = [{"k": 2, "t": "a"}, {"k": 1, "t": "b"}, {"k": 2, "t": "c"}, {"k": 1, "t": "d"}];7
+sort(m, forge (x, y) { yield x["k"] - y["k"]; });8
+say map(m, forge (x) { yield x["t"]; });9
+say sort([3, 1, 2], forge (x, y) { yield y - x; });10
+say sort([]);11
+say sort([1]);12
+local big = [];13
+local i = 0;14
+during i < 200 { push(big, (i * 37) % 101); i++; }15
+sort(big);16
+local ok = yes;17
+i = 1;18
+during i < len(big) { given big[i - 1] > big[i] { ok = no; } i++; }19
+say ok;20
+say len(big);ashvm/tests/cases/control_flow/short_circuit.ashadded
+11−0@@ -0,0 +1,11 @@1
+local a = [1, 2];2
+given 5 < len(a) and a[5] == 1 { say "bad"; } otherwise { say "ok and"; }3
+given 0 < len(a) or a[5] == 1 { say "ok or"; }4
+say 1 and 2;5
+say 0 or 3;6
+say 0 and 1;7
+say 1 or 0;8
+say 1 and 0 or 1;9
+local n = 0;10
+during n < 5 and n != 3 { n++; }11
+say n;ashvm/tests/cases/errors/attempt_array_delete.ashadded
+10−0@@ -0,0 +1,10 @@1
+local a = [1, 2, 3];2
+attempt { delete(a, 0); say "deleted"; say a; push(a, 9); delete(a, 99); say "unreachable"; } handle (e) { say "caught: " + e; }3
+say a;4
+attempt { local m = {"x": 1}; delete(m, "x"); say m; say m["x"]; } handle (e) { say "caught: " + e; }5
+local i = 0;6
+during i < 3 {7
+ attempt { delete(a, 10); } handle (e) { say "again: " + e; }8
+ i++;9
+}10
+say "end";ashvm/tests/cases/errors/attempt_stale_handler.ashadded
+21−0@@ -0,0 +1,21 @@1
+forge f() {2
+ local i = 0;3
+ during i < 3 {4
+ attempt { local x = 1; } handle (e) { say "inner"; }5
+ i++;6
+ }7
+ raise "boom";8
+}9
+attempt { f(); } handle (e) { say "outer: " + e; }10
+forge g() {11
+ attempt { yield 5; } handle (e) { say "no"; }12
+}13
+forge h() { local v = g(); raise "boom2"; }14
+attempt { h(); } handle (e) { say "outer: " + e; }15
+local i = 0;16
+during i < 3 {17
+ i++;18
+ attempt { next; } handle (e) { say "no"; }19
+}20
+attempt { raise "second"; } handle (e) { say "caught " + e; }21
+say "done";ashvm/tests/cases/errors/error_message_field.ashadded
+5−0@@ -0,0 +1,5 @@1
+attempt { local a = [1]; say a[3]; } handle (e) { say e.message; say "x: " + e; }2
+attempt { raise {"message": "custom", "line": 3}; } handle (e) { say e.message; say e.line; }3
+attempt { raise "plain"; } handle (e) { say e.message == e; }4
+local m = {"inner": {"deep": 7}};5
+say m.inner.deep;ashvm/tests/cases/errors/error_messages.ashadded
+23−0@@ -0,0 +1,23 @@1
+local a = [1, 2];2
+attempt { say a[5]; } handle (e) { say e; }3
+attempt { say {"k": 1}["zz"]; } handle (e) { say e; }4
+attempt { say "abc"[9]; } handle (e) { say e; }5
+attempt { say "x" + 1; } handle (e) { say e; }6
+attempt { say 1 + "x"; } handle (e) { say e; }7
+attempt { say str("t"); } handle (e) { say e; }8
+attempt { pop([]); } handle (e) { say e; }9
+attempt { delete([1], 3); } handle (e) { say e; }10
+attempt { insert([1], 3, 0); } handle (e) { say e; }11
+attempt { slice([1, 2], 1, 5); } handle (e) { say e; }12
+attempt { chr(0); } handle (e) { say e; }13
+attempt { ord(""); } handle (e) { say e; }14
+attempt { repeat("a", -2); } handle (e) { say e; }15
+attempt { read_file("/nonexistent/file"); } handle (e) { say e; }16
+attempt { list_dir("/nonexistent"); } handle (e) { say e; }17
+attempt { given [1] { say "bad"; } } handle (e) { say e; }18
+attempt { raise "custom"; } handle (e) { say e; }19
+attempt { raise {"kind": "parse", "line": 3, "col": 7}; } handle (e) { say e["kind"] + ":" + str(e["line"]) + ":" + str(e["col"]); }20
+attempt { raise [1, 2]; } handle (e) { say e; }21
+attempt { raise 42; } handle (e) { say e + 1; }22
+forge f() { raise {"m": "inner"}; }23
+attempt { f(); } handle (e) { say e["m"]; }ashvm/tests/cases/errors/type_errors.ashadded
+6−0@@ -0,0 +1,6 @@1
+attempt { say "x" + 1; } handle (e) { say "caught plus"; }2
+attempt { say 1 + "x"; } handle (e) { say "caught plus2"; }3
+attempt { say str("text"); } handle (e) { say "caught str"; }4
+say "a" + "b";5
+say 1 + 2;6
+say str(5);ashvm/tests/cases/functions/builtins_as_values.ashadded
+9−0@@ -0,0 +1,9 @@1
+say type(len);2
+say map([1, 22, 333], str);3
+say map(["a", "bb"], len);4
+say map(["ab", "cd"], upper);5
+say filter([1, 2, 3, 4], forge (x) { yield x > 2; });6
+local f = len;7
+say f("hello");8
+say reduce([1, 2, 3], forge (a, b) { yield a + b; }, 0);9
+say map([4, 9], sqrt);ashvm/tests/cases/functions/forward_declarations.ashadded
+20−0@@ -0,0 +1,20 @@1
+say is_even(10);2
+say is_odd(7);3
+say is_even(7);4
+forge is_even(n) {5
+ given n == 0 { yield yes; }6
+ yield is_odd(n - 1);7
+}8
+forge is_odd(n) {9
+ given n == 0 { yield no; }10
+ yield is_even(n - 1);11
+}12
+say twice(3);13
+forge twice(x) { yield helper(x) * 2; }14
+forge helper(x) { yield x + 1; }15
+local f = helper;16
+say f(5);17
+say map([1, 2], twice);18
+forge shadow(a) { yield a; }19
+local shadow = forge (a) { yield a * 100; };20
+say shadow(2);ashvm/tests/cases/imports/fixtures/imports_state_lib.ashadded
+15−0@@ -0,0 +1,15 @@1
+local DIGITS = "0123456789abcdef";2
+local TABLE = [10, 20, 30];3
+local COUNT = 0;4
+local CONFIG = {"name": "stateful", "level": 2};5
+local DOUBLE = 21 * 2;6
+local ENABLED = yes;7
+8
+forge bump() { COUNT += 1; yield COUNT; }9
+forge hexdigit(n) { yield substring(DIGITS, n, n + 1); }10
+forge table_at(i) { yield TABLE[i]; }11
+forge add_item(v) { push(TABLE, v); yield len(TABLE); }12
+forge set_level(l) { CONFIG["level"] = l; }13
+forge level() { yield CONFIG["level"]; }14
+forge reset() { COUNT = 0; }15
+forge bump2() { COUNT++; yield COUNT; }ashvm/tests/cases/imports/fixtures/imports_transitive_mid.ashadded
+3−0@@ -0,0 +1,3 @@1
+@import <./imports_basic_lib.ash>;2
+3
+forge mid_triple(x) { yield imports_basic_lib.add(x, imports_basic_lib.add(x, x)); }ashvm/tests/cases/imports/imports_alias_test.ashadded
+6−0@@ -0,0 +1,6 @@1
+@import <./fixtures/imports_basic_lib.ash> as lib;2
+@import <./fixtures/imports_const_lib.ash> as k;3
+4
+say lib.add(3, 4);5
+say lib.helper(10);6
+say k.GREETING;ashvm/tests/cases/imports/imports_state_test.ashadded
+17−0@@ -0,0 +1,17 @@1
+@import <./fixtures/imports_state_lib.ash>;2
+3
+say imports_state_lib.bump();4
+say imports_state_lib.bump();5
+say imports_state_lib.hexdigit(11);6
+say imports_state_lib.table_at(1);7
+say imports_state_lib.add_item(40);8
+say imports_state_lib.add_item(50);9
+say imports_state_lib.TABLE;10
+imports_state_lib.set_level(9);11
+say imports_state_lib.level();12
+say imports_state_lib.CONFIG["name"];13
+say imports_state_lib.DOUBLE;14
+say imports_state_lib.ENABLED;15
+imports_state_lib.reset();16
+say imports_state_lib.bump2();17
+say imports_state_lib.COUNT;ashvm/tests/cases/imports/imports_transitive_test.ashadded
+5−0@@ -0,0 +1,5 @@1
+@import <./fixtures/imports_transitive_mid.ash>;2
+3
+say imports_transitive_mid.mid_triple(4);4
+say imports_basic_lib.add(1, 2);5
+say imports_basic_lib.helper(10);ashvm/tests/cases/io/fs_ops.ashadded
+15−0@@ -0,0 +1,15 @@1
+local d = "/tmp/ash_fs_test_dir";2
+delete_file(d + "/b.txt"); delete_file(d + "/a.txt"); delete_file(d + "/c.txt");3
+make_dir(d);4
+say file_exists(d + "/a.txt");5
+write_file(d + "/a.txt", "hello");6
+write_file(d + "/b.txt", "world");7
+say rename_file(d + "/b.txt", d + "/c.txt");8
+say rename_file(d + "/nope.txt", d + "/x.txt");9
+say sort(list_dir(d));10
+say read_file(d + "/c.txt");11
+say delete_file(d + "/a.txt");12
+say delete_file(d + "/a.txt");13
+say delete_file(d + "/c.txt");14
+say list_dir(d);15
+attempt { list_dir(d + "/missing"); } handle (e) { say "no dir"; }ashvm/tests/cases/strings/affix_ops.ashadded
+16−0@@ -0,0 +1,16 @@1
+say contains("hello world", "o w");2
+say contains("hello", "xyz");3
+say contains("hello", "");4
+say starts_with("hello", "he");5
+say starts_with("hello", "hello!");6
+say starts_with("hello", "");7
+say starts_with("hello", "el");8
+say ends_with("hello", "llo");9
+say ends_with("hello", "hell");10
+say ends_with("hello", "");11
+say ends_with("lo", "hello");12
+say repeat("ab", 3);13
+say repeat("x", 0);14
+say repeat("", 5) + "|";15
+say len(repeat("abc", 1000));16
+attempt { repeat("a", -1); } handle (e) { say "neg"; }ashvm/tests/cases/strings/chr_ord.ashadded
+7−0@@ -0,0 +1,7 @@1
+say ord("A");2
+say chr(97) + chr(98);3
+say ord(chr(200));4
+say chr(ord("z") - 1);5
+local s = "hello";6
+local i = 0;7
+during i < len(s) { say ord(substring(s, i, i + 1)); i++; }ashvm/tests/cases/strings/string_compare.ashadded
+9−0@@ -0,0 +1,9 @@1
+say "a" < "b";2
+say "b" < "a";3
+say "abc" <= "abc";4
+say "abd" > "abc";5
+say "" < "a";6
+say "Z" < "a";7
+say 1 < 2;8
+local s = "m";9
+given s >= "k" { say "fused ok"; }kiln/Makefilemodified
+8−0@@ -11,6 +11,7 @@ KILN_SRC = \1111
src/parser/C/core/parser.c \1212
src/parser/C/declarations/constants.c \1313
src/parser/C/declarations/functions.c \14
+ src/parser/C/declarations/module_state.c \1415
src/parser/C/declarations/vars.c \1516
src/parser/C/imports/import_paths.c \1617
src/parser/C/imports/imports.c \@@ -19,13 +20,16 @@ KILN_SRC = \1920
src/parser/C/statements/parser_control.c \2021
src/parser/C/statements/attempt_handle.c \2122
src/codegen/C/builtins/builtins.c \23
+ src/codegen/C/builtins/exit_builtin.c \2224
src/codegen/C/builtins/filter_builtin.c \2325
src/codegen/C/builtins/map_builtin.c \2426
src/codegen/C/builtins/type_builtin.c \2527
src/codegen/C/builtins/math_builtins.c \2628
src/codegen/C/builtins/num_builtin.c \2729
src/codegen/C/builtins/reduce_builtin.c \30
+ src/codegen/C/builtins/sort_builtin.c \2831
src/codegen/C/builtins/str_builtin.c \32
+ src/codegen/C/collections/array_mutate.c \2933
src/codegen/C/collections/arrays.c \3034
src/codegen/C/collections/keys_values.c \3135
src/codegen/C/collections/maps.c \@@ -43,6 +47,7 @@ KILN_SRC = \4347
src/codegen/C/io/argv_builtin.c \4448
src/codegen/C/io/argv_builtin_windows.c \4549
src/codegen/C/io/file_path.c \50
+ src/codegen/C/io/fs_builtins.c \4651
src/codegen/C/io/input_builtin.c \4752
src/codegen/C/io/read_file_builtin.c \4853
src/codegen/C/io/write_file_builtins.c \@@ -52,8 +57,11 @@ KILN_SRC = \5257
src/codegen/C/platform/win_file_call.c \5358
src/codegen/C/runtime/errors.c \5459
src/codegen/C/runtime/heap.c \60
+ src/codegen/C/runtime/number_format.c \5561
src/codegen/C/runtime/print_int.c \62
+ src/codegen/C/strings/affix_builtins.c \5663
src/codegen/C/strings/case_builtins.c \64
+ src/codegen/C/strings/char_builtins.c \5765
src/codegen/C/strings/indexof_builtin.c \5866
src/codegen/C/strings/join_builtin.c \5967
src/codegen/C/strings/replace_builtin.c \kiln/src/codegen/C/builtins/builtins.cmodified
+83−0@@ -12,6 +12,7 @@1212
#include "codegen/H/io/input_builtin.h"1313
#include "codegen/H/io/argv_builtin.h"1414
#include "codegen/H/builtins/type_builtin.h"15
+#include "codegen/H/builtins/exit_builtin.h"1516
#include "parser/H/core/parser.h"1617
1718
int codegen_try_builtin_call(const char *name, int len) {@@ -39,6 +40,22 @@ int codegen_try_builtin_call(const char *name, int len) {3940
codegen_builtin_append_file();4041
return 1;4142
}43
+ if (len == 11 && strncmp(name, "rename_file", 11) == 0) {44
+ codegen_builtin_rename_file();45
+ return 1;46
+ }47
+ if (len == 11 && strncmp(name, "delete_file", 11) == 0) {48
+ codegen_builtin_delete_file();49
+ return 1;50
+ }51
+ if (len == 8 && strncmp(name, "make_dir", 8) == 0) {52
+ codegen_builtin_make_dir();53
+ return 1;54
+ }55
+ if (len == 8 && strncmp(name, "list_dir", 8) == 0) {56
+ codegen_builtin_list_dir();57
+ return 1;58
+ }4259
if (len == 11 && strncmp(name, "file_exists", 11) == 0) {4360
codegen_expression();4461
expect(TOKEN_RPAREN, "expected ')' after argument");@@ -95,6 +112,15 @@ int codegen_try_builtin_call(const char *name, int len) {95112
codegen_builtin_values();96113
return 1;97114
}115
+ if (len == 8 && strncmp(name, "contains", 8) == 0) { codegen_builtin_contains(); return 1; }116
+ if (len == 11 && strncmp(name, "starts_with", 11) == 0) { codegen_builtin_starts_with(); return 1; }117
+ if (len == 9 && strncmp(name, "ends_with", 9) == 0) { codegen_builtin_ends_with(); return 1; }118
+ if (len == 6 && strncmp(name, "repeat", 6) == 0) { codegen_builtin_repeat(); return 1; }119
+ if (len == 4 && strncmp(name, "exit", 4) == 0) { codegen_builtin_exit(); return 1; }120
+ if (len == 4 && strncmp(name, "sort", 4) == 0) {121
+ codegen_builtin_sort();122
+ return 1;123
+ }98124
if (len == 3 && strncmp(name, "map", 3) == 0) {99125
codegen_builtin_map();100126
return 1;@@ -167,6 +193,44 @@ int codegen_try_builtin_call(const char *name, int len) {167193
codegen_builtin_delete();168194
return 1;169195
}196
+ if (len == 3 && strncmp(name, "pop", 3) == 0) {197
+ codegen_expression();198
+ expect(TOKEN_RPAREN, "expected ')' after argument");199
+ codegen_builtin_pop();200
+ return 1;201
+ }202
+ if (len == 6 && strncmp(name, "insert", 6) == 0) {203
+ codegen_expression();204
+ expect(TOKEN_COMMA, "expected ',' after array argument");205
+ codegen_expression();206
+ expect(TOKEN_COMMA, "expected ',' after index argument");207
+ codegen_expression();208
+ expect(TOKEN_RPAREN, "expected ')' after arguments");209
+ codegen_builtin_insert();210
+ return 1;211
+ }212
+ if (len == 5 && strncmp(name, "slice", 5) == 0) {213
+ codegen_expression();214
+ expect(TOKEN_COMMA, "expected ',' after array argument");215
+ codegen_expression();216
+ expect(TOKEN_COMMA, "expected ',' after start argument");217
+ codegen_expression();218
+ expect(TOKEN_RPAREN, "expected ')' after arguments");219
+ codegen_builtin_slice();220
+ return 1;221
+ }222
+ if (len == 3 && strncmp(name, "chr", 3) == 0) {223
+ codegen_expression();224
+ expect(TOKEN_RPAREN, "expected ')' after argument");225
+ codegen_builtin_chr();226
+ return 1;227
+ }228
+ if (len == 3 && strncmp(name, "ord", 3) == 0) {229
+ codegen_expression();230
+ expect(TOKEN_RPAREN, "expected ')' after argument");231
+ codegen_builtin_ord();232
+ return 1;233
+ }170234
if (len == 4 && strncmp(name, "type", 4) == 0) {171235
codegen_expression();172236
expect(TOKEN_RPAREN, "expected ')' after argument");@@ -175,3 +239,22 @@ int codegen_try_builtin_call(const char *name, int len) {175239
}176240
return 0;177241
}242
+243
+// Argument count each builtin is wrapped with when used as a value244
+// (`map(xs, str)`); -1 if `name` is not a builtin. Same set and arities as245
+// ashvm's builtin table.246
+int codegen_builtin_arity(const char *name, int len) {247
+ static const struct { const char *name; int arity; } table[] = {248
+ {"len", 1}, {"push", 2}, {"input", 0}, {"str", 1}, {"num", 1}, {"sqrt", 1}, {"abs", 1}, {"floor", 1},249
+ {"map", 2}, {"filter", 2}, {"reduce", 3}, {"keys", 1}, {"values", 1}, {"has", 2}, {"delete", 2},250
+ {"split", 2}, {"join", 2}, {"substring", 3}, {"indexOf", 2}, {"replace", 3}, {"upper", 1}, {"lower", 1}, {"trim", 1},251
+ {"read_file", 1}, {"write_file", 2}, {"append_file", 2}, {"file_exists", 1},252
+ {"type", 1}, {"chr", 1}, {"ord", 1}, {"pop", 1}, {"insert", 3}, {"slice", 3}, {"sort", 1},253
+ {"rename_file", 2}, {"delete_file", 1}, {"make_dir", 1}, {"list_dir", 1},254
+ {"contains", 2}, {"starts_with", 2}, {"ends_with", 2}, {"repeat", 2}, {"exit", 1}, {"argv", 0},255
+ };256
+ for (size_t i = 0; i < sizeof(table) / sizeof(table[0]); i++) {257
+ if ((int)strlen(table[i].name) == len && strncmp(table[i].name, name, (size_t)len) == 0) return table[i].arity;258
+ }259
+ return -1;260
+}kiln/src/codegen/C/builtins/exit_builtin.cadded
+30−0@@ -0,0 +1,30 @@1
+#include "codegen/H/builtins/exit_builtin.h"2
+#include "codegen/H/expressions/expr.h"3
+#include "codegen/H/emit/emit.h"4
+#include "codegen/H/emit/emit_sse.h"5
+#include "codegen/H/platform/win_call.h"6
+#include "app/H/target.h"7
+#include "parser/H/core/parser.h"8
+9
+// exit(code): ends the process with that status. Output is written10
+// unbuffered, so there is nothing to flush first.11
+void codegen_builtin_exit(void) {12
+ codegen_expression();13
+ expect(TOKEN_RPAREN, "expected ')' after argument");14
+ emit_pop_reg(code, REG_RAX); // payload15
+ emit_pop_reg(code, REG_RBX); // tag (ignored)16
+ emit_movq_xmm_from_reg(code, XMM0, REG_RAX);17
+ emit_cvttsd2si(code, REG_RDI, XMM0);18
+ if (kiln_get_target() == KILN_TARGET_WINDOWS) {19
+ win_call_begin(code, 0);20
+ win_call_arg_reg(code, 0, REG_RDI);21
+ win_call_import(code, PE_IMPORT_EXIT_PROCESS);22
+ } else {23
+ emit_mov_reg_imm64(code, REG_RAX, 60); // syscall: exit24
+ emit_syscall(code);25
+ }26
+ // never returns; keep the stack-machine model balanced for the caller27
+ emit_mov_reg_imm64(code, REG_RBX, 5 /* TAG_NIL */);28
+ emit_push_reg(code, REG_RBX);29
+ emit_push_reg(code, REG_RBX);30
+}kiln/src/codegen/C/builtins/num_builtin.cmodified
+67−0@@ -117,6 +117,73 @@ void codegen_builtin_num(void) {117117
emit_patch_jump(code, not_dot);118118
emit_patch_jump(code, no_frac_check);119119
120
+ // ---- optional exponent: e/E, optional sign, digits ----121
+ emit_cmp_reg_reg(code, REG_RSI, REG_RDI);122
+ int exp_end = emit_jcc_rel32(code, COND_GE);123
+ emit_load_byte_reg(code, REG_RCX, REG_RSI);124
+ emit_cmp_reg_imm32(code, REG_RCX, 'e');125
+ int is_e = emit_jcc_rel32(code, COND_E);126
+ emit_cmp_reg_imm32(code, REG_RCX, 'E');127
+ int exp_none = emit_jcc_rel32(code, COND_NE);128
+ emit_patch_jump(code, is_e);129
+ emit_add_reg_imm8(code, REG_RSI, 1);130
+131
+ load_double_const(XMM2, 10.0); // (clobbers RAX, so before the flag)132
+ emit_mov_reg_imm64(code, REG_RAX, 0); // exponent-negative flag133
+ emit_pxor_xmm_xmm(code, XMM3); // exponent magnitude, as a double134
+ emit_cmp_reg_reg(code, REG_RSI, REG_RDI);135
+ int exp_sign_skip = emit_jcc_rel32(code, COND_GE);136
+ emit_load_byte_reg(code, REG_RCX, REG_RSI);137
+ emit_cmp_reg_imm32(code, REG_RCX, '-');138
+ int exp_not_minus = emit_jcc_rel32(code, COND_NE);139
+ emit_mov_reg_imm64(code, REG_RAX, 1);140
+ emit_add_reg_imm8(code, REG_RSI, 1);141
+ int exp_sign_done = emit_jmp_rel32(code);142
+ emit_patch_jump(code, exp_not_minus);143
+ emit_cmp_reg_imm32(code, REG_RCX, '+');144
+ int exp_not_plus = emit_jcc_rel32(code, COND_NE);145
+ emit_add_reg_imm8(code, REG_RSI, 1);146
+ emit_patch_jump(code, exp_not_plus);147
+ emit_patch_jump(code, exp_sign_done);148
+ emit_patch_jump(code, exp_sign_skip);149
+150
+ int exp_loop = code->count;151
+ emit_cmp_reg_reg(code, REG_RSI, REG_RDI);152
+ int exp_digits_end = emit_jcc_rel32(code, COND_GE);153
+ emit_load_byte_reg(code, REG_RCX, REG_RSI);154
+ emit_cmp_reg_imm32(code, REG_RCX, '0');155
+ int exp_lt = emit_jcc_rel32(code, COND_LT);156
+ emit_cmp_reg_imm32(code, REG_RCX, '9');157
+ int exp_gt = emit_jcc_rel32(code, COND_GT);158
+ emit_mulsd(code, XMM3, XMM2);159
+ emit_sub_reg_imm8(code, REG_RCX, '0');160
+ emit_cvtsi2sd(code, XMM1, REG_RCX);161
+ emit_addsd(code, XMM3, XMM1);162
+ emit_add_reg_imm8(code, REG_RSI, 1);163
+ emit_jmp_back(code, exp_loop);164
+ emit_patch_jump(code, exp_lt);165
+ emit_patch_jump(code, exp_gt);166
+ emit_patch_jump(code, exp_digits_end);167
+168
+ // Scale by 10^exponent, one multiply (or divide, for a negative169
+ // exponent) at a time -- exact for the common small cases.170
+ emit_cvttsd2si(code, REG_RDX, XMM3);171
+ int scale_loop = code->count;172
+ emit_cmp_reg_imm32(code, REG_RDX, 0);173
+ int scale_done = emit_jcc_rel32(code, COND_LE);174
+ emit_cmp_reg_imm32(code, REG_RAX, 0);175
+ int scale_down = emit_jcc_rel32(code, COND_NE);176
+ emit_mulsd(code, XMM0, XMM2);177
+ int scale_next = emit_jmp_rel32(code);178
+ emit_patch_jump(code, scale_down);179
+ emit_divsd(code, XMM0, XMM2);180
+ emit_patch_jump(code, scale_next);181
+ emit_dec_reg(code, REG_RDX);182
+ emit_jmp_back(code, scale_loop);183
+ emit_patch_jump(code, scale_done);184
+ emit_patch_jump(code, exp_none);185
+ emit_patch_jump(code, exp_end);186
+120187
// ---- apply sign ----121188
emit_cmp_reg_imm32(code, REG_RBX, 0);122189
int no_negate = emit_jcc_rel32(code, COND_E);kiln/src/codegen/C/builtins/sort_builtin.cadded
+164−0@@ -0,0 +1,164 @@1
+#include "codegen/H/builtins/higher_order.h"2
+#include "codegen/H/expressions/expr.h"3
+#include "codegen/H/functions/closures.h"4
+#include "codegen/H/strings/strings.h"5
+#include "codegen/H/emit/emit.h"6
+#include "codegen/H/emit/emit_sse.h"7
+#include "codegen/H/emit/value.h"8
+#include "parser/H/core/parser.h"9
+#include "parser/H/declarations/vars.h"10
+11
+// sort(array) / sort(array, cmp): in-place, stable insertion sort; pushes the12
+// array back (like push()). Without cmp, numbers sort numerically and strings13
+// bytewise (a pair of strings -> string order, anything else -> numeric). With14
+// cmp, an element moves left while cmp(previous, key) is a number > 0.15
+//16
+// Scratch layout (see vars.h's higher_order_scratch_offset):17
+// [0]=cmp_tag [8]=cmp_payload [16]=data_ptr [24]=count [32]=i [40]=j18
+// [48]=key_tag [56]=key_payload [64]=array_object19
+void codegen_builtin_sort(void) {20
+ codegen_expression(); // array21
+ int has_cmp = 0;22
+ if (current.type == TOKEN_COMMA) {23
+ advance_token();24
+ codegen_expression(); // cmp25
+ has_cmp = 1;26
+ }27
+ expect(TOKEN_RPAREN, "expected ')' after arguments");28
+29
+ int32_t base = higher_order_scratch_offset();30
+31
+ if (has_cmp) {32
+ emit_pop_reg(code, REG_RAX);33
+ emit_pop_reg(code, REG_RBX);34
+ emit_store_mem_disp32(code, REG_RBP, base + 0, REG_RBX);35
+ emit_store_mem_disp32(code, REG_RBP, base + 8, REG_RAX);36
+ }37
+ emit_pop_reg(code, REG_RSI); // array object38
+ emit_pop_reg(code, REG_RCX); // tag (ignored)39
+ emit_store_mem_disp32(code, REG_RBP, base + 64, REG_RSI);40
+ emit_load_mem_disp32(code, REG_RAX, REG_RSI, 8);41
+ emit_load_mem_disp32(code, REG_RDX, REG_RSI, 16);42
+ emit_store_mem_disp32(code, REG_RBP, base + 24, REG_RAX);43
+ emit_store_mem_disp32(code, REG_RBP, base + 16, REG_RDX);44
+ emit_mov_reg_imm64(code, REG_RCX, 1);45
+ emit_store_mem_disp32(code, REG_RBP, base + 32, REG_RCX); // i = 146
+47
+ int outer = code->count;48
+ emit_load_mem_disp32(code, REG_RCX, REG_RBP, base + 32);49
+ emit_load_mem_disp32(code, REG_RDX, REG_RBP, base + 24);50
+ emit_cmp_reg_reg(code, REG_RCX, REG_RDX);51
+ int all_done = emit_jcc_rel32(code, COND_GE);52
+53
+ // key = data[i]; j = i54
+ emit_load_mem_disp32(code, REG_RSI, REG_RBP, base + 16);55
+ emit_mov_reg_imm64(code, REG_RAX, 16);56
+ emit_imul_reg_reg(code, REG_RCX, REG_RAX);57
+ emit_add_reg_reg(code, REG_RSI, REG_RCX);58
+ emit_load_mem_disp32(code, REG_RBX, REG_RSI, 0);59
+ emit_load_mem_disp32(code, REG_RAX, REG_RSI, 8);60
+ emit_store_mem_disp32(code, REG_RBP, base + 48, REG_RBX);61
+ emit_store_mem_disp32(code, REG_RBP, base + 56, REG_RAX);62
+ emit_load_mem_disp32(code, REG_RCX, REG_RBP, base + 32);63
+ emit_store_mem_disp32(code, REG_RBP, base + 40, REG_RCX);64
+65
+ int inner = code->count;66
+ emit_load_mem_disp32(code, REG_RCX, REG_RBP, base + 40);67
+ emit_cmp_reg_imm32(code, REG_RCX, 0);68
+ int place = emit_jcc_rel32(code, COND_LE);69
+70
+ // RSI = &data[j - 1]71
+ emit_load_mem_disp32(code, REG_RSI, REG_RBP, base + 16);72
+ emit_sub_reg_imm8(code, REG_RCX, 1);73
+ emit_mov_reg_imm64(code, REG_RAX, 16);74
+ emit_imul_reg_reg(code, REG_RCX, REG_RAX);75
+ emit_add_reg_reg(code, REG_RSI, REG_RCX);76
+77
+ int not_greater;78
+ if (has_cmp) {79
+ emit_load_mem_disp32(code, REG_RAX, REG_RBP, base + 0);80
+ emit_push_reg(code, REG_RAX);81
+ emit_load_mem_disp32(code, REG_RAX, REG_RBP, base + 8);82
+ emit_push_reg(code, REG_RAX);83
+ emit_load_mem_disp32(code, REG_RAX, REG_RSI, 0);84
+ emit_push_reg(code, REG_RAX);85
+ emit_load_mem_disp32(code, REG_RAX, REG_RSI, 8);86
+ emit_push_reg(code, REG_RAX);87
+ emit_load_mem_disp32(code, REG_RAX, REG_RBP, base + 48);88
+ emit_push_reg(code, REG_RAX);89
+ emit_load_mem_disp32(code, REG_RAX, REG_RBP, base + 56);90
+ emit_push_reg(code, REG_RAX);91
+ codegen_call_closure_value(2);92
+ emit_pop_reg(code, REG_RAX); // result payload93
+ emit_pop_reg(code, REG_RBX); // result tag (assumed NUMBER)94
+ emit_movq_xmm_from_reg(code, XMM0, REG_RAX);95
+ emit_pxor_xmm_xmm(code, XMM1);96
+ emit_ucomisd(code, XMM0, XMM1);97
+ not_greater = emit_jcc_rel32(code, COND_BE);98
+ // the call clobbered RSI: recompute &data[j - 1]99
+ emit_load_mem_disp32(code, REG_RCX, REG_RBP, base + 40);100
+ emit_load_mem_disp32(code, REG_RSI, REG_RBP, base + 16);101
+ emit_sub_reg_imm8(code, REG_RCX, 1);102
+ emit_mov_reg_imm64(code, REG_RAX, 16);103
+ emit_imul_reg_reg(code, REG_RCX, REG_RAX);104
+ emit_add_reg_reg(code, REG_RSI, REG_RCX);105
+ } else {106
+ emit_load_mem_disp32(code, REG_RBX, REG_RSI, 0); // previous tag107
+ emit_load_mem_disp32(code, REG_RAX, REG_RSI, 8); // previous payload108
+ emit_load_mem_disp32(code, REG_RDX, REG_RBP, base + 48); // key tag109
+ emit_load_mem_disp32(code, REG_RCX, REG_RBP, base + 56); // key payload110
+ emit_cmp_reg_imm32(code, REG_RBX, TAG_STRING);111
+ int a_num = emit_jcc_rel32(code, COND_NE);112
+ emit_cmp_reg_imm32(code, REG_RDX, TAG_STRING);113
+ int b_num = emit_jcc_rel32(code, COND_NE);114
+ codegen_string_order();115
+ int flags_ready = emit_jmp_rel32(code);116
+ emit_patch_jump(code, a_num);117
+ emit_patch_jump(code, b_num);118
+ emit_movq_xmm_from_reg(code, XMM0, REG_RAX);119
+ emit_movq_xmm_from_reg(code, XMM1, REG_RCX);120
+ emit_ucomisd(code, XMM0, XMM1);121
+ emit_patch_jump(code, flags_ready);122
+ not_greater = emit_jcc_rel32(code, COND_BE);123
+ // string_order clobbered RSI: recompute &data[j - 1]124
+ emit_load_mem_disp32(code, REG_RCX, REG_RBP, base + 40);125
+ emit_load_mem_disp32(code, REG_RSI, REG_RBP, base + 16);126
+ emit_sub_reg_imm8(code, REG_RCX, 1);127
+ emit_mov_reg_imm64(code, REG_RAX, 16);128
+ emit_imul_reg_reg(code, REG_RCX, REG_RAX);129
+ emit_add_reg_reg(code, REG_RSI, REG_RCX);130
+ }131
+132
+ // data[j] = data[j - 1]; j--133
+ emit_load_mem_disp32(code, REG_RAX, REG_RSI, 0);134
+ emit_store_mem_disp32(code, REG_RSI, 16, REG_RAX);135
+ emit_load_mem_disp32(code, REG_RAX, REG_RSI, 8);136
+ emit_store_mem_disp32(code, REG_RSI, 24, REG_RAX);137
+ emit_load_mem_disp32(code, REG_RCX, REG_RBP, base + 40);138
+ emit_sub_reg_imm8(code, REG_RCX, 1);139
+ emit_store_mem_disp32(code, REG_RBP, base + 40, REG_RCX);140
+ emit_jmp_back(code, inner);141
+142
+ // data[j] = key; i++143
+ emit_patch_jump(code, place);144
+ emit_patch_jump(code, not_greater);145
+ emit_load_mem_disp32(code, REG_RCX, REG_RBP, base + 40);146
+ emit_load_mem_disp32(code, REG_RSI, REG_RBP, base + 16);147
+ emit_mov_reg_imm64(code, REG_RAX, 16);148
+ emit_imul_reg_reg(code, REG_RCX, REG_RAX);149
+ emit_add_reg_reg(code, REG_RSI, REG_RCX);150
+ emit_load_mem_disp32(code, REG_RBX, REG_RBP, base + 48);151
+ emit_load_mem_disp32(code, REG_RAX, REG_RBP, base + 56);152
+ emit_store_mem_disp32(code, REG_RSI, 0, REG_RBX);153
+ emit_store_mem_disp32(code, REG_RSI, 8, REG_RAX);154
+ emit_load_mem_disp32(code, REG_RCX, REG_RBP, base + 32);155
+ emit_add_reg_imm8(code, REG_RCX, 1);156
+ emit_store_mem_disp32(code, REG_RBP, base + 32, REG_RCX);157
+ emit_jmp_back(code, outer);158
+159
+ emit_patch_jump(code, all_done);160
+ emit_load_mem_disp32(code, REG_RAX, REG_RBP, base + 64);161
+ emit_mov_reg_imm64(code, REG_RBX, TAG_ARRAY);162
+ emit_push_reg(code, REG_RBX);163
+ emit_push_reg(code, REG_RAX);164
+}kiln/src/codegen/C/builtins/str_builtin.cmodified
+24−88@@ -4,98 +4,32 @@44
#include "codegen/H/emit/emit_sse.h"55
#include "codegen/H/emit/value.h"66
#include "codegen/H/runtime/heap.h"7
+#include "codegen/H/runtime/errors.h"8
+#include "codegen/H/strings/strings.h"9
+#include "codegen/H/runtime/number_format.h"710
#include "codegen/H/emit/bytes.h"811
#include "parser/H/core/parser.h"912
10
-static void load_double_const(XReg dst, double v) {11
- uint64_t bits;12
- memcpy(&bits, &v, sizeof(bits));13
- emit_mov_reg_imm64(code, REG_RAX, bits);14
- emit_movq_xmm_from_reg(code, dst, REG_RAX);15
-}16
-17
-// Scratch layout while building digits, same shape as print_int.c:18
-// [rsp, rsp+32) integer digits (+ optional '-'), built backward19
-// rsp+32 '.' (only if there's a fraction)20
-// [rsp+33, rsp+39) up to 6 fraction digits, built forward13
+// str(number): formats via number_format_emit (see its header for the format)14
+// into a stack scratch region, then copies the text into a fresh string.2115
void codegen_builtin_str(void) {2216
emit_pop_reg(code, REG_RAX); // payload23
- emit_pop_reg(code, REG_RBX); // tag (assumed NUMBER)24
- emit_movq_xmm_from_reg(code, XMM0, REG_RAX);25
- emit_sub_reg_imm8(code, REG_RSP, 40);26
-27
- // ---- sign ----28
- emit_pxor_xmm_xmm(code, XMM2);29
- emit_ucomisd(code, XMM0, XMM2);30
- emit_mov_reg_imm64(code, REG_RBX, 0);31
- int skip_negate = emit_jcc_rel32(code, COND_AE);32
- emit_pxor_xmm_xmm(code, XMM2);33
- emit_subsd(code, XMM2, XMM0);34
- emit_movsd_xmm_xmm(code, XMM0, XMM2);35
- emit_mov_reg_imm64(code, REG_RBX, 1);36
- emit_patch_jump(code, skip_negate);37
-38
- // ---- split into integer part (RAX) and fractional remainder (XMM0) ----39
- emit_cvttsd2si(code, REG_RAX, XMM0);40
- emit_cvtsi2sd(code, XMM1, REG_RAX);41
- emit_subsd(code, XMM0, XMM1);42
-43
- // ---- integer digits, backward ----44
- emit_mov_reg_reg(code, REG_RCX, REG_RSP);45
- emit_add_reg_imm8(code, REG_RCX, 32);46
- int loop_start = code->count;47
- emit_dec_reg(code, REG_RCX);48
- emit_cqo(code);49
- emit_mov_reg_imm64(code, REG_RSI, 10);50
- emit_idiv_reg(code, REG_RSI);51
- emit_add_reg_imm8(code, REG_RDX, '0');52
- emit_store_byte_reg(code, REG_RCX, REG_RDX);53
- emit_cmp_reg_imm32(code, REG_RAX, 0);54
- emit_jcc_back(code, COND_NE, loop_start);55
-56
- // ---- sign prepend ----57
- emit_cmp_reg_imm32(code, REG_RBX, 0);58
- int skip_sign = emit_jcc_rel32(code, COND_E);59
- emit_dec_reg(code, REG_RCX);60
- emit_store_byte_imm(code, REG_RCX, '-');61
- emit_patch_jump(code, skip_sign);62
-63
- // ---- fraction digits, forward, round-half-up first (see64
- // print_int.c's comment for why) ----65
- load_double_const(XMM1, 0.0000005);66
- emit_addsd(code, XMM0, XMM1);67
- emit_mov_reg_reg(code, REG_RSI, REG_RSP);68
- emit_add_reg_imm8(code, REG_RSI, 33);69
- load_double_const(XMM3, 10.0);70
- emit_mov_reg_imm64(code, REG_RBX, 0); // frac_len71
- for (int i = 0; i < 6; i++) {72
- emit_mulsd(code, XMM0, XMM3);73
- emit_cvttsd2si(code, REG_RAX, XMM0);74
- emit_cvtsi2sd(code, XMM1, REG_RAX);75
- emit_subsd(code, XMM0, XMM1);76
- emit_cmp_reg_imm32(code, REG_RAX, 0);77
- int skip_mark = emit_jcc_rel32(code, COND_E);78
- emit_mov_reg_imm64(code, REG_RBX, (uint64_t)(i + 1));79
- emit_patch_jump(code, skip_mark);80
- emit_add_reg_imm8(code, REG_RAX, '0');81
- emit_store_byte_reg(code, REG_RSI, REG_RAX);82
- emit_add_reg_imm8(code, REG_RSI, 1);83
- }84
-85
- // ---- decide '.' placement, compute the content's end pointer (no86
- // trailing newline -- this is a string value, not a print) ----87
- emit_mov_reg_reg(code, REG_RDX, REG_RSP);88
- emit_add_reg_imm8(code, REG_RDX, 32);89
- emit_cmp_reg_imm32(code, REG_RBX, 0);90
- int whole_number = emit_jcc_rel32(code, COND_E);91
- emit_store_byte_imm(code, REG_RDX, '.');92
- emit_mov_reg_reg(code, REG_RDX, REG_RSP);93
- emit_add_reg_imm8(code, REG_RDX, 33);94
- emit_add_reg_reg(code, REG_RDX, REG_RBX);95
- int after_dot = emit_jmp_rel32(code);96
- emit_patch_jump(code, whole_number);97
- emit_patch_jump(code, after_dot);98
-17
+ emit_pop_reg(code, REG_RBX); // tag18
+ emit_cmp_reg_imm32(code, REG_RBX, TAG_NUMBER);19
+ int is_number = emit_jcc_rel32(code, COND_E);20
+ emit_cmp_reg_imm32(code, REG_RBX, TAG_BOOL);21
+ int is_bool = emit_jcc_rel32(code, COND_E);22
+ errors_emit_die(code, "runtime error: str() expects a number");23
+ emit_patch_jump(code, is_bool);24
+ emit_cmp_reg_imm32(code, REG_RAX, 0); // payload bits: 0.0 is all zero25
+ int bool_no = emit_jcc_rel32(code, COND_E);26
+ codegen_string_literal_bytes("yes", 3);27
+ int bool_done_yes = emit_jmp_rel32(code);28
+ emit_patch_jump(code, bool_no);29
+ codegen_string_literal_bytes("no", 2);30
+ int bool_done = emit_jmp_rel32(code);31
+ emit_patch_jump(code, is_number);32
+ number_format_emit(code); // RCX = text start, RDX = text end (on the stack)9933
emit_sub_reg_reg(code, REG_RDX, REG_RCX); // RDX = content length10034
10135
// ---- heap-allocate a length-prefixed block and copy the digits in ----@@ -110,12 +44,14 @@ void codegen_builtin_str(void) {11044
emit_mov_reg_reg(code, REG_RBX, REG_RCX); // src = digit buffer start11145
bytes_emit_copy(code); // clobbers RAX,RBX,RCX,RDX,RDI; leaves RSI = block addr11246
113
- emit_add_reg_imm8(code, REG_RSP, 40); // release the digit scratch47
+ emit_add_reg_imm8(code, REG_RSP, NUMBER_FORMAT_SCRATCH); // release the digit scratch11448
11549
emit_mov_reg_reg(code, REG_RAX, REG_RSI);11650
emit_add_reg_imm8(code, REG_RAX, 8); // payload = block+811751
11852
emit_mov_reg_imm64(code, REG_RBX, TAG_STRING);11953
emit_push_reg(code, REG_RBX);12054
emit_push_reg(code, REG_RAX);55
+ emit_patch_jump(code, bool_done_yes);56
+ emit_patch_jump(code, bool_done);12157
}kiln/src/codegen/C/builtins/type_builtin.cmodified
+8−2@@ -4,7 +4,7 @@44
#include "codegen/H/emit/value.h"55
#include "parser/H/core/parser.h"66
7
-// Pops (tag, payload), pushes one of six string literals depending on7
+// Pops (tag, payload), pushes one of seven string literals depending on88
// the tag -- TAG_FUNCTION covers both a plain named function and a99
// closure, matching value.h's documented "capture_count=0 is just a1010
// zero-capture closure" shape (kiln has no separate closure tag the way@@ -16,7 +16,7 @@ void codegen_builtin_type(void) {1616
emit_pop_reg(code, REG_RAX); // payload (unused -- only the tag matters)1717
emit_pop_reg(code, REG_RBX); // tag1818
19
- int jumps[5];19
+ int jumps[6];2020
int n = 0;2121
2222
emit_cmp_reg_imm32(code, REG_RBX, TAG_NUMBER);@@ -25,6 +25,12 @@ void codegen_builtin_type(void) {2525
jumps[n++] = emit_jmp_rel32(code);2626
emit_patch_jump(code, not_number);2727
28
+ emit_cmp_reg_imm32(code, REG_RBX, TAG_BOOL);29
+ int not_bool = emit_jcc_rel32(code, COND_NE);30
+ codegen_string_literal_bytes("boolean", 7);31
+ jumps[n++] = emit_jmp_rel32(code);32
+ emit_patch_jump(code, not_bool);33
+2834
emit_cmp_reg_imm32(code, REG_RBX, TAG_STRING);2935
int not_string = emit_jcc_rel32(code, COND_NE);3036
codegen_string_literal_bytes("string", 6);kiln/src/codegen/C/collections/array_mutate.cadded
+218−0@@ -0,0 +1,218 @@1
+#include "codegen/H/collections/arrays.h"2
+#include "codegen/H/emit/emit_internal.h"3
+#include "codegen/H/emit/emit_sse.h"4
+#include "codegen/H/emit/value.h"5
+#include "codegen/H/runtime/heap.h"6
+#include "codegen/H/runtime/errors.h"7
+#include "parser/H/core/parser.h"8
+9
+// Array element = 16 bytes (tag, payload); object = [capacity][count][data_ptr].10
+// See codegen/H/emit/value.h.11
+12
+// RAX = index (a double's bits) -> RCX = integer index.13
+static void index_to_int(void) {14
+ emit_movq_xmm_from_reg(code, XMM0, REG_RAX);15
+ emit_cvttsd2si(code, REG_RCX, XMM0);16
+}17
+18
+// Dies unless 0 <= RCX < count (or <= count when `allow_end`); RSI = array object.19
+static void check_index(int allow_end, const char *msg) {20
+ emit_cmp_reg_imm32(code, REG_RCX, 0);21
+ int neg = emit_jcc_rel32(code, COND_LT);22
+ emit_load_mem_disp32(code, REG_RDX, REG_RSI, 8); // count23
+ emit_cmp_reg_reg(code, REG_RCX, REG_RDX);24
+ int big = emit_jcc_rel32(code, allow_end ? COND_GT : COND_GE);25
+ int ok = emit_jmp_rel32(code);26
+ emit_patch_jump(code, neg);27
+ emit_patch_jump(code, big);28
+ errors_emit_die(code, msg);29
+ emit_patch_jump(code, ok);30
+}31
+32
+// Called by codegen_builtin_delete when its first argument is an array.33
+// In: RDI = index payload, RSI = array object.34
+void codegen_array_delete_index(void) {35
+ emit_mov_reg_reg(code, REG_RAX, REG_RDI);36
+ index_to_int();37
+ check_index(0, "index out of bounds");38
+39
+ emit_load_mem_disp32(code, REG_RBX, REG_RSI, 8); // count40
+ emit_sub_reg_reg(code, REG_RBX, REG_RCX);41
+ emit_sub_reg_imm8(code, REG_RBX, 1); // items to shift left42
+ emit_load_mem_disp32(code, REG_RDI, REG_RSI, 16); // data_ptr43
+ emit_mov_reg_imm64(code, REG_RAX, 16);44
+ emit_imul_reg_reg(code, REG_RCX, REG_RAX);45
+ emit_add_reg_reg(code, REG_RDI, REG_RCX); // &data[index]46
+47
+ int loop = code->count;48
+ emit_cmp_reg_imm32(code, REG_RBX, 0);49
+ int done = emit_jcc_rel32(code, COND_E);50
+ emit_load_mem_disp32(code, REG_RAX, REG_RDI, 16); emit_store_mem_disp32(code, REG_RDI, 0, REG_RAX);51
+ emit_load_mem_disp32(code, REG_RAX, REG_RDI, 24); emit_store_mem_disp32(code, REG_RDI, 8, REG_RAX);52
+ emit_add_reg_imm8(code, REG_RDI, 16);53
+ emit_dec_reg(code, REG_RBX);54
+ emit_jmp_back(code, loop);55
+ emit_patch_jump(code, done);56
+57
+ emit_load_mem_disp32(code, REG_RAX, REG_RSI, 8);58
+ emit_sub_reg_imm8(code, REG_RAX, 1);59
+ emit_store_mem_disp32(code, REG_RSI, 8, REG_RAX); // count--60
+61
+ emit_mov_reg_imm64(code, REG_RBX, TAG_ARRAY);62
+ emit_push_reg(code, REG_RBX);63
+ emit_push_reg(code, REG_RSI); // delete() returns the container, like for maps64
+}65
+66
+void codegen_builtin_pop(void) {67
+ emit_pop_reg(code, REG_RSI); // array object68
+ emit_pop_reg(code, REG_RBX); // tag (ignored)69
+70
+ emit_load_mem_disp32(code, REG_RAX, REG_RSI, 8); // count71
+ emit_cmp_reg_imm32(code, REG_RAX, 0);72
+ int nonempty = emit_jcc_rel32(code, COND_NE);73
+ errors_emit_die(code, "runtime error: pop() on an empty array");74
+ emit_patch_jump(code, nonempty);75
+76
+ emit_sub_reg_imm8(code, REG_RAX, 1);77
+ emit_store_mem_disp32(code, REG_RSI, 8, REG_RAX); // count--78
+ emit_load_mem_disp32(code, REG_RDX, REG_RSI, 16); // data_ptr79
+ emit_mov_reg_imm64(code, REG_RCX, 16);80
+ emit_imul_reg_reg(code, REG_RAX, REG_RCX);81
+ emit_add_reg_reg(code, REG_RAX, REG_RDX); // &data[old count - 1]82
+ emit_load_mem_disp32(code, REG_RBX, REG_RAX, 0);83
+ emit_load_mem_disp32(code, REG_RAX, REG_RAX, 8);84
+ emit_push_reg(code, REG_RBX);85
+ emit_push_reg(code, REG_RAX);86
+}87
+88
+// insert(array, index, value): validates the index, appends via push() (so89
+// growth is shared), then shifts the tail right and drops the value in.90
+void codegen_builtin_insert(void) {91
+ emit_pop_reg(code, REG_RAX); // value payload92
+ emit_pop_reg(code, REG_RBX); // value tag93
+ emit_pop_reg(code, REG_RCX); // index payload94
+ emit_pop_reg(code, REG_RDX); // index tag (ignored)95
+ emit_pop_reg(code, REG_RSI); // array object96
+ emit_pop_reg(code, REG_RDI); // array tag97
+98
+ emit_push_reg(code, REG_RAX); // keep value payload across the index check99
+ emit_mov_reg_reg(code, REG_RAX, REG_RCX);100
+ index_to_int();101
+ check_index(1, "index out of bounds");102
+ emit_pop_reg(code, REG_RAX);103
+104
+ emit_push_reg(code, REG_RBX); // saved: value tag105
+ emit_push_reg(code, REG_RAX); // value payload106
+ emit_push_reg(code, REG_RCX); // index107
+ emit_push_reg(code, REG_RDI); // push() arguments: array, then value108
+ emit_push_reg(code, REG_RSI);109
+ emit_push_reg(code, REG_RBX);110
+ emit_push_reg(code, REG_RAX);111
+ codegen_builtin_push();112
+ emit_pop_reg(code, REG_RSI); // array object (push() returns it)113
+ emit_pop_reg(code, REG_RDI); // tag114
+ emit_pop_reg(code, REG_RCX); // index115
+116
+ emit_load_mem_disp32(code, REG_RBX, REG_RSI, 8); // new count117
+ emit_sub_reg_imm8(code, REG_RBX, 1);118
+ emit_mov_reg_reg(code, REG_RDX, REG_RBX); // last index119
+ emit_sub_reg_reg(code, REG_RBX, REG_RCX); // items to shift right120
+ emit_load_mem_disp32(code, REG_RDI, REG_RSI, 16); // data_ptr121
+ emit_mov_reg_imm64(code, REG_RAX, 16);122
+ emit_imul_reg_reg(code, REG_RDX, REG_RAX);123
+ emit_add_reg_reg(code, REG_RDI, REG_RDX); // &data[last]124
+125
+ int loop = code->count;126
+ emit_cmp_reg_imm32(code, REG_RBX, 0);127
+ int done = emit_jcc_rel32(code, COND_E);128
+ emit_load_mem_disp32(code, REG_RAX, REG_RDI, -16); emit_store_mem_disp32(code, REG_RDI, 0, REG_RAX);129
+ emit_load_mem_disp32(code, REG_RAX, REG_RDI, -8); emit_store_mem_disp32(code, REG_RDI, 8, REG_RAX);130
+ emit_sub_reg_imm8(code, REG_RDI, 16);131
+ emit_dec_reg(code, REG_RBX);132
+ emit_jmp_back(code, loop);133
+ emit_patch_jump(code, done);134
+135
+ emit_pop_reg(code, REG_RAX); // value payload136
+ emit_pop_reg(code, REG_RBX); // value tag137
+ emit_store_mem_disp32(code, REG_RDI, 0, REG_RBX);138
+ emit_store_mem_disp32(code, REG_RDI, 8, REG_RAX);139
+140
+ emit_mov_reg_imm64(code, REG_RBX, TAG_ARRAY);141
+ emit_push_reg(code, REG_RBX);142
+ emit_push_reg(code, REG_RSI);143
+}144
+145
+// slice(array, from, to): a new array of items [from, to).146
+void codegen_builtin_slice(void) {147
+ emit_pop_reg(code, REG_RAX); // to payload148
+ emit_pop_reg(code, REG_RBX);149
+ emit_pop_reg(code, REG_RCX); // from payload150
+ emit_pop_reg(code, REG_RBX);151
+ emit_pop_reg(code, REG_RSI); // array object152
+ emit_pop_reg(code, REG_RBX);153
+154
+ emit_movq_xmm_from_reg(code, XMM0, REG_RAX);155
+ emit_cvttsd2si(code, REG_RDX, XMM0); // to156
+ emit_movq_xmm_from_reg(code, XMM0, REG_RCX);157
+ emit_cvttsd2si(code, REG_RCX, XMM0); // from158
+159
+ emit_cmp_reg_imm32(code, REG_RCX, 0);160
+ int bad1 = emit_jcc_rel32(code, COND_LT);161
+ emit_load_mem_disp32(code, REG_RAX, REG_RSI, 8); // count162
+ emit_cmp_reg_reg(code, REG_RDX, REG_RAX);163
+ int bad2 = emit_jcc_rel32(code, COND_GT);164
+ emit_cmp_reg_reg(code, REG_RCX, REG_RDX);165
+ int bad3 = emit_jcc_rel32(code, COND_GT);166
+ int ok = emit_jmp_rel32(code);167
+ emit_patch_jump(code, bad1);168
+ emit_patch_jump(code, bad2);169
+ emit_patch_jump(code, bad3);170
+ errors_emit_die(code, "runtime error: slice bounds out of range");171
+ emit_patch_jump(code, ok);172
+173
+ emit_sub_reg_reg(code, REG_RDX, REG_RCX); // n174
+ emit_push_reg(code, REG_RSI);175
+ emit_push_reg(code, REG_RCX); // from176
+ emit_push_reg(code, REG_RDX); // n177
+178
+ emit_mov_reg_reg(code, REG_RDI, REG_RDX);179
+ emit_mov_reg_imm64(code, REG_RAX, 16);180
+ emit_imul_reg_reg(code, REG_RDI, REG_RAX);181
+ emit_add_reg_imm8(code, REG_RDI, 16); // room for one spare slot (n may be 0)182
+ heap_emit_alloc(code); // RAX = data block183
+ emit_push_reg(code, REG_RAX);184
+ emit_mov_reg_imm64(code, REG_RDI, ARRAY_OBJECT_SIZE);185
+ heap_emit_alloc(code); // RAX = array object186
+187
+ emit_pop_reg(code, REG_RBX); // data block188
+ emit_pop_reg(code, REG_RCX); // n189
+ emit_pop_reg(code, REG_RDX); // from190
+ emit_pop_reg(code, REG_RSI); // source array191
+192
+ emit_mov_reg_reg(code, REG_RDI, REG_RCX);193
+ emit_add_reg_imm8(code, REG_RDI, 1);194
+ emit_store_mem_disp32(code, REG_RAX, 0, REG_RDI); // capacity = n + 1195
+ emit_store_mem_disp32(code, REG_RAX, 8, REG_RCX); // count = n196
+ emit_store_mem_disp32(code, REG_RAX, 16, REG_RBX); // data_ptr197
+198
+ emit_load_mem_disp32(code, REG_RSI, REG_RSI, 16); // source data_ptr199
+ emit_mov_reg_imm64(code, REG_RDI, 16);200
+ emit_imul_reg_reg(code, REG_RDX, REG_RDI);201
+ emit_add_reg_reg(code, REG_RSI, REG_RDX); // &source[from]202
+ emit_mov_reg_reg(code, REG_RDI, REG_RBX); // dest cursor203
+204
+ int loop = code->count;205
+ emit_cmp_reg_imm32(code, REG_RCX, 0);206
+ int done = emit_jcc_rel32(code, COND_E);207
+ emit_load_mem_disp32(code, REG_RDX, REG_RSI, 0); emit_store_mem_disp32(code, REG_RDI, 0, REG_RDX);208
+ emit_load_mem_disp32(code, REG_RDX, REG_RSI, 8); emit_store_mem_disp32(code, REG_RDI, 8, REG_RDX);209
+ emit_add_reg_imm8(code, REG_RSI, 16);210
+ emit_add_reg_imm8(code, REG_RDI, 16);211
+ emit_dec_reg(code, REG_RCX);212
+ emit_jmp_back(code, loop);213
+ emit_patch_jump(code, done);214
+215
+ emit_mov_reg_imm64(code, REG_RBX, TAG_ARRAY);216
+ emit_push_reg(code, REG_RBX);217
+ emit_push_reg(code, REG_RAX);218
+}kiln/src/codegen/C/collections/arrays.cmodified
+10−4@@ -72,7 +72,7 @@ void codegen_array_index_read(void) {7272
emit_pop_reg(code, REG_RSI); // array object address7373
emit_pop_reg(code, REG_RBX); // array tag (assumed ARRAY)7474
75
- bounds_check("runtime error: array index out of bounds"); // RCX=index, RDX=data_ptr75
+ bounds_check("index out of bounds"); // RCX=index, RDX=data_ptr7676
7777
// element address = data_ptr + RCX*167878
emit_mov_reg_imm64(code, REG_RAX, 16);@@ -98,7 +98,7 @@ void codegen_array_index_store(void) {9898
emit_push_reg(code, REG_RAX); // value payload9999
emit_mov_reg_reg(code, REG_RAX, REG_RCX); // index payload -> RAX, where bounds_check expects it100100
101
- bounds_check("runtime error: array index out of bounds"); // RCX=index, RDX=data_ptr101
+ bounds_check("index out of bounds"); // RCX=index, RDX=data_ptr102102
103103
emit_mov_reg_imm64(code, REG_RAX, 16);104104
emit_imul_reg_reg(code, REG_RCX, REG_RAX);@@ -129,8 +129,14 @@ void codegen_builtin_push(void) {129129
emit_cmp_reg_reg(code, REG_RDX, REG_RAX);130130
int has_room = emit_jcc_rel32(code, COND_LT);131131
132
- // --- grow: new data block sized capacity+4, copy the old elements over ---133
- emit_add_reg_imm8(code, REG_RAX, 4); // RAX = new_capacity132
+ // --- grow: new data block of double the capacity (at least 4 slots), copy the133
+ // old elements over. Doubling keeps total allocation linear in the final134
+ // size; the bump allocator never frees the old block. ---135
+ emit_add_reg_reg(code, REG_RAX, REG_RAX); // RAX = new_capacity = 2 * capacity136
+ emit_cmp_reg_imm32(code, REG_RAX, 4);137
+ int big_enough = emit_jcc_rel32(code, COND_GE);138
+ emit_mov_reg_imm64(code, REG_RAX, 4);139
+ emit_patch_jump(code, big_enough);134140
emit_mov_reg_reg(code, REG_RDI, REG_RAX);135141
emit_mov_reg_imm64(code, REG_RBX, 16);136142
emit_imul_reg_reg(code, REG_RDI, REG_RBX); // RDI = new data block sizekiln/src/codegen/C/collections/maps_mutate.cmodified
+22−8@@ -1,4 +1,5 @@11
#include "codegen/H/collections/maps.h"2
+#include "codegen/H/collections/arrays.h"23
#include "codegen/H/collections/maps_internal.h"34
#include "codegen/H/emit/emit_internal.h"45
#include "codegen/H/emit/value.h"@@ -9,7 +10,7 @@910
static void push_number_local(double v) {1011
uint64_t bits;1112
__builtin_memcpy(&bits, &v, sizeof(bits));12
- emit_mov_reg_imm64(code, REG_RBX, TAG_NUMBER);13
+ emit_mov_reg_imm64(code, REG_RBX, TAG_BOOL);1314
emit_push_reg(code, REG_RBX);1415
emit_mov_reg_imm64(code, REG_RAX, bits);1516
emit_push_reg(code, REG_RAX);@@ -128,15 +129,20 @@ void codegen_builtin_delete(void) {128129
emit_pop_reg(code, REG_RDI); // key payload129130
emit_pop_reg(code, REG_RBX); // key tag (ignored)130131
emit_pop_reg(code, REG_RSI); // map object131
- emit_pop_reg(code, REG_RBX); // map tag (ignored)132
+ emit_pop_reg(code, REG_RBX); // map tag133
+134
+ emit_cmp_reg_imm32(code, REG_RBX, TAG_ARRAY);135
+ int is_map = emit_jcc_rel32(code, COND_NE);136
+ codegen_array_delete_index(); // delete(array, index)137
+ int array_done = emit_jmp_rel32(code);138
+ emit_patch_jump(code, is_map);132139
133140
emit_sub_reg_imm8(code, REG_RSP, 8);134141
emit_mov_reg_reg(code, REG_RCX, REG_RSP);135142
emit_store_mem_disp32(code, REG_RCX, 0, REG_RSI);136143
137144
int not_found = find_entry_index();138
- // found: RBX = entry addr to remove -- overwrite it with the last139
- // entry's data (order doesn't matter for a map), then count--145
+ // found: RBX = entry addr to remove -- close the gap, then count--140146
emit_mov_reg_reg(code, REG_RCX, REG_RSP);141147
emit_load_mem_disp32(code, REG_RSI, REG_RCX, 0);142148
emit_load_mem_disp32(code, REG_RAX, REG_RSI, 8); // count@@ -147,10 +153,17 @@ void codegen_builtin_delete(void) {147153
emit_imul_reg_reg(code, REG_RCX, REG_RAX);148154
emit_add_reg_reg(code, REG_RCX, REG_RDX); // RCX = last entry's address149155
150
- emit_load_mem_disp32(code, REG_RAX, REG_RCX, 0); emit_store_mem_disp32(code, REG_RBX, 0, REG_RAX);151
- emit_load_mem_disp32(code, REG_RAX, REG_RCX, 8); emit_store_mem_disp32(code, REG_RBX, 8, REG_RAX);152
- emit_load_mem_disp32(code, REG_RAX, REG_RCX, 16); emit_store_mem_disp32(code, REG_RBX, 16, REG_RAX);153
- emit_load_mem_disp32(code, REG_RAX, REG_RCX, 24); emit_store_mem_disp32(code, REG_RBX, 24, REG_RAX);156
+ // shift every later entry down one slot, so the survivors keep their order157
+ int shift = code->count;158
+ emit_cmp_reg_reg(code, REG_RBX, REG_RCX);159
+ int shifted = emit_jcc_rel32(code, COND_GE);160
+ emit_load_mem_disp32(code, REG_RAX, REG_RBX, 32); emit_store_mem_disp32(code, REG_RBX, 0, REG_RAX);161
+ emit_load_mem_disp32(code, REG_RAX, REG_RBX, 40); emit_store_mem_disp32(code, REG_RBX, 8, REG_RAX);162
+ emit_load_mem_disp32(code, REG_RAX, REG_RBX, 48); emit_store_mem_disp32(code, REG_RBX, 16, REG_RAX);163
+ emit_load_mem_disp32(code, REG_RAX, REG_RBX, 56); emit_store_mem_disp32(code, REG_RBX, 24, REG_RAX);164
+ emit_add_reg_imm8(code, REG_RBX, MAP_ENTRY_SIZE);165
+ emit_jmp_back(code, shift);166
+ emit_patch_jump(code, shifted);154167
155168
emit_load_mem_disp32(code, REG_RAX, REG_RSI, 8);156169
emit_sub_reg_imm8(code, REG_RAX, 1);@@ -173,4 +186,5 @@ void codegen_builtin_delete(void) {173186
emit_push_reg(code, REG_RSI);174187
175188
emit_patch_jump(code, done);189
+ emit_patch_jump(code, array_done);176190
}kiln/src/codegen/C/emit/emit_mem.cmodified
+7−0@@ -85,6 +85,13 @@ void emit_call_back(CodeBuf *buf, int target_offset) {8585
emit_i32(buf, rel);8686
}8787
88
+int emit_call_rel32(CodeBuf *buf) {89
+ emit_byte(buf, 0xE8);90
+ int patch_offset = buf->count;91
+ emit_i32(buf, 0);92
+ return patch_offset;93
+}94
+8895
void emit_ret(CodeBuf *buf) {8996
emit_byte(buf, 0xC3);9097
}kiln/src/codegen/C/emit/layout.cmodified
+6−0@@ -37,6 +37,12 @@ uint64_t kiln_try_depth_addr(void) {3737
return KILN_TRY_DEPTH_ADDR;3838
}3939
40
+uint64_t kiln_modstate_addr(void) {41
+ if (kiln_get_target() == KILN_TARGET_WINDOWS) return KILN_PE_MODSTATE_ADDR;42
+ if (is_linux_shared()) return KILN_DYN_MODSTATE_ADDR;43
+ return KILN_MODSTATE_ADDR;44
+}45
+4046
uint64_t kiln_try_handlers_addr(void) {4147
if (kiln_get_target() == KILN_TARGET_WINDOWS) return KILN_PE_TRY_HANDLERS_ADDR;4248
if (is_linux_shared()) return KILN_DYN_GLOBALS_ADDR + 8 + KILN_TRY_DEPTH_SIZE;kiln/src/codegen/C/expressions/expr.cmodified
+89−17@@ -11,8 +11,10 @@1111
#include "codegen/H/collections/maps.h"1212
#include "codegen/H/builtins/builtins.h"1313
#include "codegen/H/functions/closures.h"14
+#include "codegen/H/runtime/errors.h"1415
#include "parser/H/core/parser.h"1516
#include "parser/H/declarations/vars.h"17
+#include "parser/H/declarations/module_state.h"1618
#include "parser/H/declarations/functions.h"1719
#include "parser/H/declarations/constants.h"1820
@@ -37,6 +39,33 @@3739
// milestone, not silently pretended-away here).3840
3941
static void power(void);42
+static void emit_index_read(void);43
+44
+// A builtin name used as a value (`map(xs, str)`, `local f = len;`) becomes a45
+// small wrapper lambda `forge (a0, ...) { yield name(a0, ...); }`, compiled by46
+// pointing the lexer at a synthesized snippet. `current` is the token after47
+// the name; everything is restored afterwards. Returns 0 if `id` isn't a builtin.48
+static int codegen_builtin_value(Token id) {49
+ int arity = codegen_builtin_arity(id.start, id.length);50
+ if (arity < 0) return 0;51
+52
+ char *snippet = malloc(64 + 2 * (size_t)id.length + 12 * (size_t)arity); // kept: tokens point into it53
+ int n = sprintf(snippet, "forge (");54
+ for (int i = 0; i < arity; i++) n += sprintf(snippet + n, "%s__%d", i ? ", " : "", i);55
+ n += sprintf(snippet + n, ") { yield %.*s(", id.length, id.start);56
+ for (int i = 0; i < arity; i++) n += sprintf(snippet + n, "%s__%d", i ? ", " : "", i);57
+ sprintf(snippet + n, "); }");58
+59
+ Token saved_current = current, saved_previous = previous;60
+ LexerState saved_lexer = lexer_save_state();61
+ lexer_init(snippet);62
+ advance_token(); // current = 'forge'63
+ codegen_lambda_expr();64
+ lexer_restore_state(saved_lexer);65
+ current = saved_current;66
+ previous = saved_previous;67
+ return 1;68
+}4069
4170
static void primary(void) {4271
if (current.type == TOKEN_NUMBER) {@@ -77,7 +106,7 @@ static void primary(void) {77106
advance_token();78107
uint64_t bits;79108
memcpy(&bits, &v, sizeof(bits));80
- emit_mov_reg_imm64(code, REG_RBX, TAG_NUMBER);109
+ emit_mov_reg_imm64(code, REG_RBX, TAG_BOOL);81110
emit_push_reg(code, REG_RBX);82111
emit_mov_reg_imm64(code, REG_RAX, bits);83112
emit_push_reg(code, REG_RAX);@@ -95,6 +124,24 @@ static void primary(void) {95124
Token id = current;96125
advance_token();97126
127
+ // `var.field` on a variable (not a namespace) is var["field"]; on an128
+ // error string it is the string itself, so `e.message` works for built-in129
+ // errors and for raised maps alike.130
+ if (current.type == TOKEN_DOT && resolve_var(id.start, id.length) != -1) {131
+ int slot = resolve_var(id.start, id.length);132
+ emit_load_mem_disp32(code, REG_RBX, REG_RBP, var_slot_tag_offset(slot));133
+ emit_push_reg(code, REG_RBX);134
+ emit_load_mem_disp32(code, REG_RAX, REG_RBP, var_slot_payload_offset(slot));135
+ emit_push_reg(code, REG_RAX);136
+ while (current.type == TOKEN_DOT) {137
+ advance_token();138
+ expect(TOKEN_IDENTIFIER, "expected field name after '.'");139
+ codegen_string_literal_bytes(previous.start, previous.length);140
+ emit_index_read();141
+ }142
+ return;143
+ }144
+98145
if (current.type == TOKEN_DOT) {99146
advance_token();100147
expect(TOKEN_IDENTIFIER, "expected name after '.'");@@ -112,6 +159,8 @@ static void primary(void) {112159
113160
KilnConstant *k = resolve_constant(combined, combined_len);114161
if (k != NULL) { codegen_constant_value(k); return; }162
+ int state = module_state_resolve_qualified(combined, combined_len);163
+ if (state != -1) { module_state_emit_load(state); return; }115164
116165
parse_error("undefined namespaced function or constant");117166
}@@ -154,6 +203,10 @@ static void primary(void) {154203
KilnConstant *k = resolve_constant(combined, combined_len);155204
if (k != NULL) { codegen_constant_value(k); return; }156205
}206
+ int state = module_state_resolve(id.start, id.length);207
+ if (state != -1) { module_state_emit_load(state); return; }208
+ if (codegen_builtin_value(id)) return;209
+157210
char msg[128];158211
snprintf(msg, sizeof(msg), "undefined variable: %.*s", id.length, id.start);159212
parse_error(msg);@@ -181,13 +234,10 @@ static void primary(void) {181234
// the same order, and dispatches. A while loop (not a single check) so182235
// chains like `arr[0][1]` work, matching ashvm/src/compiler/expr.c's183236
// postfix() shape.184
-static void postfix(void) {185
- primary();186
- while (current.type == TOKEN_LBRACKET) {187
- advance_token();188
- codegen_expression();189
- expect(TOKEN_RBRACKET, "expected ']' after index");190
-237
+// Both the base and the index are already pushed (base deeper). Pops both and238
+// pushes base[index] for an array, map or string. A string indexed by a string239
+// is an error value's `.message`: it yields the string itself.240
+static void emit_index_read(void) {191241
emit_pop_reg(code, REG_RAX); // index payload192242
emit_pop_reg(code, REG_RBX); // index tag193243
emit_pop_reg(code, REG_RCX); // base payload@@ -206,9 +256,24 @@ static void postfix(void) {206256
codegen_map_index_read();207257
int done2 = emit_jmp_rel32(code);208258
emit_patch_jump(code, is_string);259
+ emit_cmp_reg_imm32(code, REG_RBX, TAG_STRING); // string[string]: the value itself260
+ int by_number = emit_jcc_rel32(code, COND_NE);261
+ emit_add_reg_imm8(code, REG_RSP, 16);262
+ int done3 = emit_jmp_rel32(code);263
+ emit_patch_jump(code, by_number);209264
codegen_string_index_read();210265
emit_patch_jump(code, done);211266
emit_patch_jump(code, done2);267
+ emit_patch_jump(code, done3);268
+}269
+270
+static void postfix(void) {271
+ primary();272
+ while (current.type == TOKEN_LBRACKET) {273
+ advance_token();274
+ codegen_expression();275
+ expect(TOKEN_RBRACKET, "expected ']' after index");276
+ emit_index_read();212277
}213278
}214279
@@ -222,26 +287,22 @@ static void unary(void) {222287
if (current.type == TOKEN_BANG) {223288
advance_token();224289
unary();225
- emit_pop_reg(code, REG_RAX); // payload226
- emit_pop_reg(code, REG_RBX); // tag (ignored)227
- emit_movq_xmm_from_reg(code, XMM0, REG_RAX);228
- emit_pxor_xmm_xmm(code, XMM1);229
- emit_ucomisd(code, XMM0, XMM1);290
+ codegen_pop_and_test_truthy();230291
int is_zero = emit_jcc_rel32(code, COND_E);231
- emit_mov_reg_imm64(code, REG_RBX, TAG_NUMBER);292
+ emit_mov_reg_imm64(code, REG_RBX, TAG_BOOL);232293
emit_push_reg(code, REG_RBX);233294
emit_pxor_xmm_xmm(code, XMM0);234295
emit_movq_reg_from_xmm(code, REG_RAX, XMM0);235
- emit_push_reg(code, REG_RAX); // nonzero operand -> 0.0296
+ emit_push_reg(code, REG_RAX); // truthy operand -> no236297
int done = emit_jmp_rel32(code);237298
emit_patch_jump(code, is_zero);238
- emit_mov_reg_imm64(code, REG_RBX, TAG_NUMBER);299
+ emit_mov_reg_imm64(code, REG_RBX, TAG_BOOL);239300
emit_push_reg(code, REG_RBX);240301
double one = 1.0;241302
uint64_t one_bits;242303
memcpy(&one_bits, &one, sizeof(one_bits));243304
emit_mov_reg_imm64(code, REG_RAX, one_bits);244
- emit_push_reg(code, REG_RAX); // zero operand -> 1.0305
+ emit_push_reg(code, REG_RAX); // falsy operand -> yes245306
emit_patch_jump(code, done);246307
return;247308
}@@ -400,6 +461,17 @@ void codegen_apply_plus(void) {400461
int done = emit_jmp_rel32(code);401462
emit_patch_jump(code, not_string);402463
emit_patch_jump(code, not_string2);464
+ // A string on exactly one side is a type error (as on ashvm), not a465
+ // numeric add of a heap pointer.466
+ emit_cmp_reg_imm32(code, REG_RBX, TAG_STRING);467
+ int a_is_string = emit_jcc_rel32(code, COND_E);468
+ emit_cmp_reg_imm32(code, REG_RDX, TAG_STRING);469
+ int b_is_string = emit_jcc_rel32(code, COND_E);470
+ int numeric = emit_jmp_rel32(code);471
+ emit_patch_jump(code, a_is_string);472
+ emit_patch_jump(code, b_is_string);473
+ errors_emit_die(code, "runtime error: invalid operand type in '+'");474
+ emit_patch_jump(code, numeric);403475
emit_movq_xmm_from_reg(code, XMM0, REG_RAX);404476
emit_movq_xmm_from_reg(code, XMM1, REG_RCX);405477
emit_addsd(code, XMM0, XMM1);kiln/src/codegen/C/expressions/expr_bool.cmodified
+30−15@@ -5,6 +5,7 @@55
#include "codegen/H/emit/emit_sse.h"66
#include "codegen/H/emit/value.h"77
#include "codegen/H/strings/strings.h"8
+#include "codegen/H/runtime/errors.h"89
#include "parser/H/core/parser.h"910
1011
// Precedence climbing continued from expr.c: comparison -> logical_and ->@@ -14,7 +15,7 @@1415
1516
// Pushes a literal double 0.0 or 1.0, tagged NUMBER.1617
static void push_number_literal(double v) {17
- emit_mov_reg_imm64(code, REG_RBX, TAG_NUMBER);18
+ emit_mov_reg_imm64(code, REG_RBX, TAG_BOOL);1819
emit_push_reg(code, REG_RBX);1920
if (v == 0.0) {2021
emit_pxor_xmm_xmm(code, XMM0);@@ -43,11 +44,19 @@ static void push_bool(Cond cc_if_true) {4344
}4445
4546
// Pops the top-of-stack value and sets flags as if its payload were46
-// compared against 0.0 (ucomisd), for truthiness tests -- assumes47
-// NUMBER, matching ashvm's own truthy(), which requires a number too.48
-static void pop_and_test_truthy(void) {49
- emit_pop_reg(code, REG_RAX); // payload50
- emit_pop_reg(code, REG_RBX); // tag (assumed NUMBER)47
+// compared against 0.0 (ucomisd), for truthiness tests: COND_E afterward48
+// means falsy. Numbers, booleans and none are testable (their payload is49
+// 0.0 when falsy); strings, arrays, maps and functions are a type error, as50
+// on ashvm.51
+void codegen_pop_and_test_truthy(void) {52
+ emit_pop_reg(code, REG_RAX); // payload53
+ emit_pop_reg(code, REG_RBX); // tag54
+ emit_mov_reg_reg(code, REG_RCX, REG_RBX);55
+ emit_sub_reg_imm8(code, REG_RCX, 1);56
+ emit_cmp_reg_imm32(code, REG_RCX, 4); // unsigned: tags 1..4 (STRING..FUNCTION) fall below 457
+ int testable = emit_jcc_rel32(code, COND_AE);58
+ errors_emit_die(code, "runtime error: invalid operand type in condition");59
+ emit_patch_jump(code, testable);5160
emit_movq_xmm_from_reg(code, XMM0, REG_RAX);5261
emit_pxor_xmm_xmm(code, XMM1);5362
emit_ucomisd(code, XMM0, XMM1);@@ -90,9 +99,19 @@ static void comparison(void) {9099
emit_patch_jump(code, done);91100
emit_patch_jump(code, done2);92101
} else {102
+ // Two strings order bytewise; anything else is compared as numbers.103
+ emit_cmp_reg_imm32(code, REG_RBX, TAG_STRING);104
+ int a_not_string = emit_jcc_rel32(code, COND_NE);105
+ emit_cmp_reg_imm32(code, REG_RDX, TAG_STRING);106
+ int b_not_string = emit_jcc_rel32(code, COND_NE);107
+ codegen_string_order();108
+ int flags_ready = emit_jmp_rel32(code);109
+ emit_patch_jump(code, a_not_string);110
+ emit_patch_jump(code, b_not_string);93111
emit_movq_xmm_from_reg(code, XMM0, REG_RAX);94112
emit_movq_xmm_from_reg(code, XMM1, REG_RCX);95113
emit_ucomisd(code, XMM0, XMM1);114
+ emit_patch_jump(code, flags_ready);96115
switch (op) {97116
case TOKEN_LESS: push_bool(COND_B); break;98117
case TOKEN_LESS_EQUAL: push_bool(COND_BE); break;@@ -151,10 +170,10 @@ static void logical_and(void) {151170
bitwise();152171
while (current.type == TOKEN_AND) {153172
advance_token();154
- pop_and_test_truthy();173
+ codegen_pop_and_test_truthy();155174
int to_false = emit_jcc_rel32(code, COND_E);156175
bitwise();157
- pop_and_test_truthy();176
+ codegen_pop_and_test_truthy();158177
int to_false2 = emit_jcc_rel32(code, COND_E);159178
push_number_literal(1.0);160179
int to_end = emit_jmp_rel32(code);@@ -171,10 +190,10 @@ static void logical_or(void) {171190
logical_and();172191
while (current.type == TOKEN_OR) {173192
advance_token();174
- pop_and_test_truthy();193
+ codegen_pop_and_test_truthy();175194
int to_true = emit_jcc_rel32(code, COND_NE);176195
logical_and();177
- pop_and_test_truthy();196
+ codegen_pop_and_test_truthy();178197
int to_true2 = emit_jcc_rel32(code, COND_NE);179198
push_number_literal(0.0);180199
int to_end = emit_jmp_rel32(code);@@ -194,11 +213,7 @@ void codegen_expression(void) {194213
logical_or();195214
if (current.type == TOKEN_QUESTION) {196215
advance_token();197
- emit_pop_reg(code, REG_RAX); // condition payload198
- emit_pop_reg(code, REG_RBX); // condition tag (assumed NUMBER)199
- emit_movq_xmm_from_reg(code, XMM0, REG_RAX);200
- emit_pxor_xmm_xmm(code, XMM1);201
- emit_ucomisd(code, XMM0, XMM1);216
+ codegen_pop_and_test_truthy();202217
int else_jump = emit_jcc_rel32(code, COND_E);203218
204219
codegen_expression(); // true branchkiln/src/codegen/C/expressions/expr_call.cmodified
+4−2@@ -39,7 +39,8 @@ void codegen_call(Token id) {3939
snprintf(msg, sizeof(msg), "function %.*s expected %d args, got %d", id.length, id.start, fn->arity, argc);4040
parse_error(msg);4141
}42
- emit_call_back(code, fn->code_offset);42
+ if (fn->defined) emit_call_back(code, fn->code_offset);43
+ else function_add_call_fixup(fn, emit_call_rel32(code));4344
if (argc > 0) emit_add_reg_imm32(code, REG_RSP, 16 * argc); // caller cleans up pushed args (each now 16 bytes)4445
emit_push_reg(code, REG_RBX); // return convention: RBX=tag, RAX=payload4546
emit_push_reg(code, REG_RAX);@@ -67,7 +68,8 @@ void codegen_call_named(const char *name, int len) {6768
snprintf(msg, sizeof(msg), "function %.*s expected %d args, got %d", len, name, fn->arity, argc);6869
parse_error(msg);6970
}70
- emit_call_back(code, fn->code_offset);71
+ if (fn->defined) emit_call_back(code, fn->code_offset);72
+ else function_add_call_fixup(fn, emit_call_rel32(code));7173
if (argc > 0) emit_add_reg_imm32(code, REG_RSP, 16 * argc);7274
emit_push_reg(code, REG_RBX);7375
emit_push_reg(code, REG_RAX);kiln/src/codegen/C/functions/closures.cmodified
+5−2@@ -36,8 +36,11 @@ void closures_push_value(void) {3636
3737
void codegen_named_function_value(KilnFunction *fn) {3838
closures_alloc_object(0);39
- uint64_t addr = kiln_code_base() + (uint64_t)fn->code_offset;40
- emit_mov_reg_imm64(code, REG_RCX, addr);39
+ if (fn->defined) {40
+ emit_mov_reg_imm64(code, REG_RCX, kiln_code_base() + (uint64_t)fn->code_offset);41
+ } else {42
+ function_add_abs_fixup(fn, emit_mov_reg_imm64_patchable(code, REG_RCX)); // filled in when the body is compiled43
+ }4144
emit_store_mem_disp32(code, REG_RBX, 0, REG_RCX);4245
closures_push_value();4346
}kiln/src/codegen/C/functions/lambda.cmodified
+4−4@@ -46,9 +46,9 @@ void codegen_lambda_expr(void) {4646
int outer_each_depth;4747
each_depth_save(&outer_each_depth);4848
each_depth_reset();49
- int outer_loop_depth;50
- loop_depth_save(&outer_loop_depth);51
- loop_depth_reset();49
+ LoopScope outer_loop_depth;50
+ loop_scope_save(&outer_loop_depth);51
+ loop_scope_reset();5252
for (int i = 0; i < capture_count; i++) {5353
declare_var(outer_vars.names[i], (int)strlen(outer_vars.names[i]));5454
}@@ -103,7 +103,7 @@ void codegen_lambda_expr(void) {103103
104104
vars_restore(&outer_vars);105105
each_depth_restore(outer_each_depth);106
- loop_depth_restore(outer_loop_depth);106
+ loop_scope_restore(outer_loop_depth);107107
emit_patch_jump(code, skip_jump);108108
109109
uint64_t addr = kiln_code_base() + (uint64_t)lambda_code_offset;kiln/src/codegen/C/io/fs_builtins.cadded
+299−0@@ -0,0 +1,299 @@1
+#include <stdint.h>2
+#include "codegen/H/io/file_builtins.h"3
+#include "codegen/H/io/file_path.h"4
+#include "codegen/H/expressions/expr.h"5
+#include "codegen/H/emit/emit_sse.h"6
+#include "codegen/H/emit/value.h"7
+#include "codegen/H/emit/bytes.h"8
+#include "codegen/H/collections/arrays.h"9
+#include "codegen/H/platform/platform_file.h"10
+#include "codegen/H/runtime/errors.h"11
+#include "codegen/H/runtime/heap.h"12
+#include "codegen/H/strings/string_alloc.h"13
+#include "app/H/target.h"14
+#include "codegen/H/platform/win_call.h"15
+#include "parser/H/core/parser.h"16
+#include "parser/H/declarations/vars.h"17
+18
+// rename_file / delete_file / make_dir / list_dir. The first three return 1 on19
+// success and 0 on failure (like write_file); list_dir raises when the20
+// directory cannot be opened.21
+22
+// RAX = status (0 = success) -> pushes the NUMBER 1.0 / 0.0.23
+static void push_status_as_number(void) {24
+ emit_cmp_reg_imm32(code, REG_RAX, 0);25
+ int failed = emit_jcc_rel32(code, COND_NE);26
+ emit_mov_reg_imm64(code, REG_RAX, 0x3FF0000000000000ull); // 1.027
+ int done = emit_jmp_rel32(code);28
+ emit_patch_jump(code, failed);29
+ emit_mov_reg_imm64(code, REG_RAX, 0); // 0.030
+ emit_patch_jump(code, done);31
+ emit_mov_reg_imm64(code, REG_RBX, TAG_BOOL);32
+ emit_push_reg(code, REG_RBX);33
+ emit_push_reg(code, REG_RAX);34
+}35
+36
+// Pops a string and leaves its null-terminated copy's address in RAX.37
+static void pop_path_nullterm(void) {38
+ emit_pop_reg(code, REG_RSI);39
+ emit_pop_reg(code, REG_RBX); // tag (ignored)40
+ emit_load_mem_disp32(code, REG_RDX, REG_RSI, -8);41
+ filepath_emit_nullterm(code);42
+}43
+44
+void codegen_builtin_rename_file(void) {45
+ codegen_expression(); // from46
+ expect(TOKEN_COMMA, "expected ',' after first path");47
+ codegen_expression(); // to48
+ expect(TOKEN_RPAREN, "expected ')' after arguments");49
+ int32_t base = higher_order_scratch_offset();50
+ pop_path_nullterm();51
+ emit_store_mem_disp32(code, REG_RBP, base + 24, REG_RAX); // to52
+ pop_path_nullterm();53
+ emit_mov_reg_reg(code, REG_RDI, REG_RAX);54
+ emit_load_mem_disp32(code, REG_RSI, REG_RBP, base + 24);55
+ platform_emit_rename(code);56
+ push_status_as_number();57
+}58
+59
+void codegen_builtin_delete_file(void) {60
+ codegen_expression();61
+ expect(TOKEN_RPAREN, "expected ')' after argument");62
+ pop_path_nullterm();63
+ emit_mov_reg_reg(code, REG_RDI, REG_RAX);64
+ platform_emit_delete(code);65
+ push_status_as_number();66
+}67
+68
+void codegen_builtin_make_dir(void) {69
+ codegen_expression();70
+ expect(TOKEN_RPAREN, "expected ')' after argument");71
+ pop_path_nullterm();72
+ emit_mov_reg_reg(code, REG_RDI, REG_RAX);73
+ platform_emit_mkdir(code);74
+ push_status_as_number();75
+}76
+77
+// list_dir(path): array of entry names (no "." / ".."), in directory order.78
+// Linux walks getdents64 records; Windows walks FindFirstFileA / FindNextFileA.79
+//80
+// Scratch: [24]=fd/find handle [32]=dirent / WIN32_FIND_DATA buffer81
+// [40]=bytes read [48]=offset [56]=result array object82
+// [64]=pattern buffer [72]=path length (Windows)83
+84
+// An empty result array in [base + 56].85
+static void emit_new_result_array(int32_t base) {86
+ emit_mov_reg_imm64(code, REG_RDI, 16);87
+ heap_emit_alloc(code);88
+ emit_store_mem_disp32(code, REG_RBP, base + 32, REG_RAX); // (temp) data block89
+ emit_mov_reg_imm64(code, REG_RDI, ARRAY_OBJECT_SIZE);90
+ heap_emit_alloc(code);91
+ emit_load_mem_disp32(code, REG_RSI, REG_RBP, base + 32);92
+ emit_mov_reg_imm64(code, REG_RCX, 0);93
+ emit_store_mem_disp32(code, REG_RAX, 0, REG_RCX);94
+ emit_store_mem_disp32(code, REG_RAX, 8, REG_RCX);95
+ emit_store_mem_disp32(code, REG_RAX, 16, REG_RSI);96
+ emit_store_mem_disp32(code, REG_RBP, base + 56, REG_RAX);97
+}98
+99
+// RSI = a NUL-terminated entry name. Appends a copy to the result array unless100
+// it is "." or "..". Clobbers every register.101
+static void emit_add_entry(int32_t base) {102
+ emit_load_byte_reg(code, REG_RAX, REG_RSI);103
+ emit_cmp_reg_imm32(code, REG_RAX, '.');104
+ int has_name = emit_jcc_rel32(code, COND_NE);105
+ emit_add_reg_imm8(code, REG_RSI, 1);106
+ emit_load_byte_reg(code, REG_RAX, REG_RSI);107
+ emit_sub_reg_imm8(code, REG_RSI, 1);108
+ emit_cmp_reg_imm32(code, REG_RAX, 0);109
+ int skip_dot = emit_jcc_rel32(code, COND_E);110
+ emit_cmp_reg_imm32(code, REG_RAX, '.');111
+ int has_name2 = emit_jcc_rel32(code, COND_NE);112
+ emit_add_reg_imm8(code, REG_RSI, 2);113
+ emit_load_byte_reg(code, REG_RAX, REG_RSI);114
+ emit_sub_reg_imm8(code, REG_RSI, 2);115
+ emit_cmp_reg_imm32(code, REG_RAX, 0);116
+ int skip_dotdot = emit_jcc_rel32(code, COND_E);117
+ emit_patch_jump(code, has_name);118
+ emit_patch_jump(code, has_name2);119
+120
+ // RDX = strlen(name)121
+ emit_mov_reg_imm64(code, REG_RDX, 0);122
+ int len_loop = code->count;123
+ emit_mov_reg_reg(code, REG_RCX, REG_RSI);124
+ emit_add_reg_reg(code, REG_RCX, REG_RDX);125
+ emit_load_byte_reg(code, REG_RAX, REG_RCX);126
+ emit_cmp_reg_imm32(code, REG_RAX, 0);127
+ int len_done = emit_jcc_rel32(code, COND_E);128
+ emit_add_reg_imm8(code, REG_RDX, 1);129
+ emit_jmp_back(code, len_loop);130
+ emit_patch_jump(code, len_done);131
+132
+ // new string = copy of the name133
+ emit_mov_reg_reg(code, REG_RBX, REG_RSI); // src (survives string_alloc, as in upper())134
+ string_alloc_emit_prefixed(code); // RAX = block, RDX = length135
+ emit_mov_reg_reg(code, REG_RDI, REG_RAX);136
+ emit_add_reg_imm8(code, REG_RDI, 8);137
+ emit_push_reg(code, REG_RAX);138
+ bytes_emit_copy(code); // RDI=dest, RBX=src, RDX=count139
+ emit_pop_reg(code, REG_RAX);140
+ emit_add_reg_imm8(code, REG_RAX, 8); // string payload141
+142
+ // push(result, name)143
+ emit_mov_reg_imm64(code, REG_RBX, TAG_STRING);144
+ emit_mov_reg_reg(code, REG_RCX, REG_RAX);145
+ emit_load_mem_disp32(code, REG_RSI, REG_RBP, base + 56);146
+ emit_mov_reg_imm64(code, REG_RDX, TAG_ARRAY);147
+ emit_push_reg(code, REG_RDX);148
+ emit_push_reg(code, REG_RSI);149
+ emit_push_reg(code, REG_RBX);150
+ emit_push_reg(code, REG_RCX);151
+ codegen_builtin_push();152
+ emit_pop_reg(code, REG_RAX); // discard push()'s result153
+ emit_pop_reg(code, REG_RAX);154
+155
+ emit_patch_jump(code, skip_dot);156
+ emit_patch_jump(code, skip_dotdot);157
+}158
+159
+static void emit_push_result_array(int32_t base) {160
+ emit_load_mem_disp32(code, REG_RAX, REG_RBP, base + 56);161
+ emit_mov_reg_imm64(code, REG_RBX, TAG_ARRAY);162
+ emit_push_reg(code, REG_RBX);163
+ emit_push_reg(code, REG_RAX);164
+}165
+166
+#define WIN32_FIND_DATA_SIZE 320167
+#define WIN32_FIND_DATA_NAME_OFFSET 44168
+169
+// Windows: pattern = path + "\\*", then FindFirstFileA / FindNextFileA / FindClose.170
+static void emit_list_dir_windows(int32_t base) {171
+ emit_pop_reg(code, REG_RSI); // path payload172
+ emit_pop_reg(code, REG_RBX);173
+ emit_load_mem_disp32(code, REG_RDX, REG_RSI, -8);174
+ emit_store_mem_disp32(code, REG_RBP, base + 72, REG_RDX);175
+ emit_store_mem_disp32(code, REG_RBP, base + 80, REG_RSI);176
+ emit_mov_reg_reg(code, REG_RDI, REG_RDX);177
+ emit_add_reg_imm8(code, REG_RDI, 3); // "\\", "*", NUL178
+ heap_emit_alloc(code);179
+ emit_store_mem_disp32(code, REG_RBP, base + 64, REG_RAX);180
+ emit_mov_reg_reg(code, REG_RDI, REG_RAX);181
+ emit_load_mem_disp32(code, REG_RBX, REG_RBP, base + 80);182
+ emit_load_mem_disp32(code, REG_RDX, REG_RBP, base + 72);183
+ bytes_emit_copy(code);184
+ emit_load_mem_disp32(code, REG_RAX, REG_RBP, base + 64);185
+ emit_load_mem_disp32(code, REG_RDX, REG_RBP, base + 72);186
+ emit_add_reg_reg(code, REG_RAX, REG_RDX);187
+ emit_store_byte_imm(code, REG_RAX, '\\');188
+ emit_add_reg_imm8(code, REG_RAX, 1);189
+ emit_store_byte_imm(code, REG_RAX, '*');190
+ emit_add_reg_imm8(code, REG_RAX, 1);191
+ emit_store_byte_imm(code, REG_RAX, 0);192
+193
+ emit_new_result_array(base);194
+ emit_mov_reg_imm64(code, REG_RDI, WIN32_FIND_DATA_SIZE);195
+ heap_emit_alloc(code);196
+ emit_store_mem_disp32(code, REG_RBP, base + 32, REG_RAX);197
+198
+ emit_load_mem_disp32(code, REG_RSI, REG_RBP, base + 64);199
+ emit_mov_reg_reg(code, REG_RDI, REG_RAX);200
+ win_call_begin(code, 0);201
+ win_call_arg_reg(code, 0, REG_RSI);202
+ win_call_arg_reg(code, 1, REG_RDI);203
+ win_call_import(code, PE_IMPORT_FIND_FIRST_FILE_A);204
+ win_call_end(code);205
+ emit_store_mem_disp32(code, REG_RBP, base + 24, REG_RAX);206
+ emit_cmp_reg_imm32(code, REG_RAX, -1); // INVALID_HANDLE_VALUE207
+ int opened = emit_jcc_rel32(code, COND_NE);208
+ errors_emit_die(code, "runtime error: cannot open directory");209
+ emit_patch_jump(code, opened);210
+211
+ int next_entry = code->count;212
+ emit_load_mem_disp32(code, REG_RSI, REG_RBP, base + 32);213
+ emit_add_reg_imm8(code, REG_RSI, WIN32_FIND_DATA_NAME_OFFSET);214
+ emit_add_entry(base);215
+216
+ emit_load_mem_disp32(code, REG_RSI, REG_RBP, base + 24);217
+ emit_load_mem_disp32(code, REG_RDI, REG_RBP, base + 32);218
+ win_call_begin(code, 0);219
+ win_call_arg_reg(code, 0, REG_RSI);220
+ win_call_arg_reg(code, 1, REG_RDI);221
+ win_call_import(code, PE_IMPORT_FIND_NEXT_FILE_A);222
+ win_call_end(code);223
+ emit_cmp_reg_imm32(code, REG_RAX, 0);224
+ emit_jcc_back(code, COND_NE, next_entry);225
+226
+ emit_load_mem_disp32(code, REG_RSI, REG_RBP, base + 24);227
+ win_call_begin(code, 0);228
+ win_call_arg_reg(code, 0, REG_RSI);229
+ win_call_import(code, PE_IMPORT_FIND_CLOSE);230
+ win_call_end(code);231
+ emit_push_result_array(base);232
+}233
+234
+void codegen_builtin_list_dir(void) {235
+ codegen_expression();236
+ expect(TOKEN_RPAREN, "expected ')' after argument");237
+ int32_t base = higher_order_scratch_offset();238
+ if (kiln_get_target() == KILN_TARGET_WINDOWS) {239
+ emit_list_dir_windows(base);240
+ return;241
+ }242
+ pop_path_nullterm();243
+244
+ emit_mov_reg_reg(code, REG_RDI, REG_RAX);245
+ emit_mov_reg_imm64(code, REG_RSI, 0x10000); // O_RDONLY | O_DIRECTORY246
+ emit_mov_reg_imm64(code, REG_RDX, 0);247
+ emit_mov_reg_imm64(code, REG_RAX, 2); // syscall: open248
+ emit_syscall(code);249
+ emit_cmp_reg_imm32(code, REG_RAX, 0);250
+ int opened = emit_jcc_rel32(code, COND_GE);251
+ errors_emit_die(code, "runtime error: cannot open directory");252
+ emit_patch_jump(code, opened);253
+ emit_store_mem_disp32(code, REG_RBP, base + 24, REG_RAX);254
+255
+ emit_new_result_array(base);256
+257
+ emit_mov_reg_imm64(code, REG_RDI, 4096);258
+ heap_emit_alloc(code);259
+ emit_store_mem_disp32(code, REG_RBP, base + 32, REG_RAX); // dirent buffer260
+261
+ int fill = code->count;262
+ emit_load_mem_disp32(code, REG_RDI, REG_RBP, base + 24);263
+ emit_load_mem_disp32(code, REG_RSI, REG_RBP, base + 32);264
+ emit_mov_reg_imm64(code, REG_RDX, 4096);265
+ emit_mov_reg_imm64(code, REG_RAX, 217); // syscall: getdents64266
+ emit_syscall(code);267
+ emit_cmp_reg_imm32(code, REG_RAX, 0);268
+ int eof = emit_jcc_rel32(code, COND_LE);269
+ emit_store_mem_disp32(code, REG_RBP, base + 40, REG_RAX);270
+ emit_mov_reg_imm64(code, REG_RCX, 0);271
+ emit_store_mem_disp32(code, REG_RBP, base + 48, REG_RCX);272
+273
+ int next_entry = code->count;274
+ emit_load_mem_disp32(code, REG_RCX, REG_RBP, base + 48);275
+ emit_load_mem_disp32(code, REG_RDX, REG_RBP, base + 40);276
+ emit_cmp_reg_reg(code, REG_RCX, REG_RDX);277
+ int buffer_done = emit_jcc_rel32(code, COND_GE);278
+279
+ // RSI = entry; advance the stored offset by d_reclen (u16 at +16) up front280
+ emit_load_mem_disp32(code, REG_RSI, REG_RBP, base + 32);281
+ emit_add_reg_reg(code, REG_RSI, REG_RCX);282
+ emit_load_mem_disp32(code, REG_RAX, REG_RSI, 16);283
+ emit_mov_reg_imm64(code, REG_RDX, 0xFFFF);284
+ emit_and_reg_reg(code, REG_RAX, REG_RDX);285
+ emit_add_reg_reg(code, REG_RCX, REG_RAX);286
+ emit_store_mem_disp32(code, REG_RBP, base + 48, REG_RCX);287
+ emit_add_reg_imm8(code, REG_RSI, 19); // d_name288
+289
+ emit_add_entry(base);290
+ emit_jmp_back(code, next_entry);291
+292
+ emit_patch_jump(code, buffer_done);293
+ emit_jmp_back(code, fill);294
+295
+ emit_patch_jump(code, eof);296
+ emit_load_mem_disp32(code, REG_RDI, REG_RBP, base + 24);297
+ platform_emit_close(code);298
+ emit_push_result_array(base);299
+}kiln/src/codegen/C/io/write_file_builtins.cmodified
+1−1@@ -9,7 +9,7 @@99
#include "parser/H/declarations/vars.h"1010
1111
static void push_number(double v) {12
- emit_mov_reg_imm64(code, REG_RBX, TAG_NUMBER);12
+ emit_mov_reg_imm64(code, REG_RBX, TAG_BOOL);1313
emit_push_reg(code, REG_RBX);1414
if (v == 0.0) {1515
emit_pxor_xmm_xmm(code, XMM0);kiln/src/codegen/C/platform/platform_file.cmodified
+53−0@@ -108,3 +108,56 @@ void platform_emit_file_size(CodeBuf *code) {108108
emit_syscall(code); // rewind (syscall preserves RDI, the fd survives for the caller's next use)109109
emit_pop_reg(code, REG_RAX);110110
}111
+112
+// Windows BOOL (nonzero = success) -> the Linux-style "0 = success" contract.113
+static void bool_to_status(CodeBuf *code) {114
+ emit_cmp_reg_imm32(code, REG_RAX, 0);115
+ int failed = emit_jcc_rel32(code, COND_E);116
+ emit_mov_reg_imm64(code, REG_RAX, 0);117
+ int done = emit_jmp_rel32(code);118
+ emit_patch_jump(code, failed);119
+ emit_mov_reg_imm64(code, REG_RAX, 1);120
+ emit_patch_jump(code, done);121
+}122
+123
+void platform_emit_rename(CodeBuf *code) {124
+ if (kiln_get_target() == KILN_TARGET_WINDOWS) {125
+ win_call_begin(code, 0);126
+ win_call_arg_reg(code, 0, REG_RDI);127
+ win_call_arg_reg(code, 1, REG_RSI);128
+ win_call_import(code, PE_IMPORT_MOVE_FILE_A);129
+ win_call_end(code);130
+ bool_to_status(code);131
+ return;132
+ }133
+ emit_mov_reg_imm64(code, REG_RAX, 82); // syscall: rename134
+ emit_syscall(code);135
+}136
+137
+void platform_emit_delete(CodeBuf *code) {138
+ if (kiln_get_target() == KILN_TARGET_WINDOWS) {139
+ win_call_begin(code, 0);140
+ win_call_arg_reg(code, 0, REG_RDI);141
+ win_call_import(code, PE_IMPORT_DELETE_FILE_A);142
+ win_call_end(code);143
+ bool_to_status(code);144
+ return;145
+ }146
+ emit_mov_reg_imm64(code, REG_RAX, 87); // syscall: unlink147
+ emit_syscall(code);148
+}149
+150
+void platform_emit_mkdir(CodeBuf *code) {151
+ if (kiln_get_target() == KILN_TARGET_WINDOWS) {152
+ win_call_begin(code, 0);153
+ win_call_arg_reg(code, 0, REG_RDI);154
+ win_call_arg_imm64(code, 1, 0); // lpSecurityAttributes = NULL155
+ win_call_import(code, PE_IMPORT_CREATE_DIRECTORY_A);156
+ win_call_end(code);157
+ bool_to_status(code);158
+ return;159
+ }160
+ emit_mov_reg_imm64(code, REG_RSI, 493); // mode 0755161
+ emit_mov_reg_imm64(code, REG_RAX, 83); // syscall: mkdir162
+ emit_syscall(code);163
+}kiln/src/codegen/C/runtime/errors.cmodified
+40−2@@ -11,6 +11,22 @@1111
1212
static int raise_routine_offset = -1;1313
14
+// Writes `text` (<= 31 bytes) to stderr via a stack buffer. Clobbers15
+// RAX/RCX/RDX/RSI (and RBX/RDI on Windows), like platform_emit_write_stderr.16
+static void write_stderr_text(CodeBuf *code, const char *text) {17
+ int n = (int)strlen(text);18
+ emit_sub_reg_imm8(code, REG_RSP, 32);19
+ emit_mov_reg_reg(code, REG_RCX, REG_RSP);20
+ for (int i = 0; i < n; i++) {21
+ emit_store_byte_imm(code, REG_RCX, (uint8_t)text[i]);22
+ emit_add_reg_imm8(code, REG_RCX, 1);23
+ }24
+ emit_mov_reg_reg(code, REG_RSI, REG_RSP);25
+ emit_mov_reg_imm64(code, REG_RDX, (uint64_t)n);26
+ platform_emit_write_stderr(code);27
+ emit_add_reg_imm8(code, REG_RSP, 32);28
+}29
+1430
static int is_shared(void) {1531
return kiln_get_link_mode() == KILN_LINK_SHARED && !kiln_is_compiling_so();1632
}@@ -36,7 +52,8 @@ static void embed_message(CodeBuf *code, const char *msg, uint64_t *out_addr, in3652
*out_len = len;3753
}3854
39
-// Raise routine -- inputs RSI=message addr, RDX=message len. If an `attempt`55
+// Raise routine -- inputs RBX=value tag, RSI=value payload, RDX=message len56
+// (only meaningful when the tag is STRING). If an `attempt`4057
// is active (kiln_try_depth_addr() > 0), pops the innermost handler,4158
// restores its saved RSP/RBP (kiln's hand-rolled longjmp -- no libc4259
// setjmp in a freestanding binary), stores the message as a TAG_STRING@@ -47,6 +64,7 @@ static void emit_raise_routine(CodeBuf *code) {4764
int skip = emit_jmp_rel32(code);4865
raise_routine_offset = code->count;4966
67
+ emit_push_reg(code, REG_RBX); // the raised value's tag, kept until the handler's slot is known5068
emit_mov_reg_imm64(code, REG_RAX, kiln_try_depth_addr());5169
emit_load_mem_disp32(code, REG_RAX, REG_RAX, 0);5270
emit_cmp_reg_imm32(code, REG_RAX, 0);@@ -66,12 +84,12 @@ static void emit_raise_routine(CodeBuf *code) {6684
emit_load_mem_disp32(code, REG_RDX, REG_RAX, 16); // handle target (clobbers RDX -- fine, msg len only needed on the fatal branch we didn't take)6785
emit_load_mem_disp32(code, REG_RBX, REG_RAX, 24); // error variable's rbp-relative tag offset6886
87
+ emit_pop_reg(code, REG_RAX); // the raised value's tag6988
emit_mov_reg_reg(code, REG_RSP, REG_RCX);7089
emit_mov_reg_reg(code, REG_RBP, REG_RDI);7190
7291
emit_mov_reg_reg(code, REG_RCX, REG_RBP);7392
emit_add_reg_reg(code, REG_RCX, REG_RBX); // RCX = error variable's address74
- emit_mov_reg_imm64(code, REG_RAX, TAG_STRING);7593
emit_store_mem_disp32(code, REG_RCX, 0, REG_RAX); // tag7694
emit_store_mem_disp32(code, REG_RCX, 8, REG_RSI); // payload = message address7795
@@ -81,7 +99,21 @@ static void emit_raise_routine(CodeBuf *code) {8199
// RSI/RDX still hold this call's original message addr/len -- nothing82100
// between the raise routine's entry and here touches them on the path83101
// that skips straight past the attempt-active handling.102
+ emit_pop_reg(code, REG_RAX); // tag103
+ emit_push_reg(code, REG_RSI);104
+ emit_push_reg(code, REG_RDX);105
+ emit_push_reg(code, REG_RAX);106
+ write_stderr_text(code, "error: ");107
+ emit_pop_reg(code, REG_RAX);108
+ emit_pop_reg(code, REG_RDX);109
+ emit_pop_reg(code, REG_RSI);110
+ emit_cmp_reg_imm32(code, REG_RAX, TAG_STRING);111
+ int is_text = emit_jcc_rel32(code, COND_E);112
+ write_stderr_text(code, "uncaught non-string value");113
+ int text_done = emit_jmp_rel32(code);114
+ emit_patch_jump(code, is_text);84115
platform_emit_write_stderr(code);116
+ emit_patch_jump(code, text_done);85117
emit_sub_reg_imm8(code, REG_RSP, 8);86118
emit_mov_reg_reg(code, REG_RCX, REG_RSP);87119
emit_store_byte_imm(code, REG_RCX, '\n');@@ -121,8 +153,14 @@ static void emit_die(CodeBuf *code) {121153
}122154
123155
void errors_emit_die(CodeBuf *code, const char *msg) {156
+ // Call sites spell messages "runtime error: ..."; the caught value is just157
+ // the text after that (identical to ashvm's), and the fatal path adds its own158
+ // "error: " prefix.159
+ static const char prefix[] = "runtime error: ";160
+ if (strncmp(msg, prefix, sizeof(prefix) - 1) == 0) msg += sizeof(prefix) - 1;124161
uint64_t addr; int len;125162
embed_message(code, msg, &addr, &len);163
+ emit_mov_reg_imm64(code, REG_RBX, TAG_STRING);126164
emit_mov_reg_imm64(code, REG_RSI, addr);127165
emit_mov_reg_imm64(code, REG_RDX, (uint64_t)len);128166
emit_die(code);kiln/src/codegen/C/runtime/number_format.cadded
+222−0@@ -0,0 +1,222 @@1
+#include <string.h>2
+#include "codegen/H/runtime/number_format.h"3
+#include "codegen/H/emit/emit_internal.h"4
+#include "codegen/H/emit/emit_sse.h"5
+6
+static void load_double_const(CodeBuf *code, XReg dst, double v) {7
+ uint64_t bits;8
+ memcpy(&bits, &v, sizeof(bits));9
+ emit_mov_reg_imm64(code, REG_RAX, bits);10
+ emit_movq_xmm_from_reg(code, dst, REG_RAX);11
+}12
+13
+// Writes `text` at [RCX...] advancing RCX, for the NaN/inf literals.14
+static void store_text(CodeBuf *code, const char *text) {15
+ for (const char *p = text; *p; p++) {16
+ emit_store_byte_imm(code, REG_RCX, (uint8_t)*p);17
+ emit_add_reg_imm8(code, REG_RCX, 1);18
+ }19
+}20
+21
+// Scratch layout (relative to RSP after the sub; kiln's emitter has no22
+// [rsp+disp] addressing, so everything is reached through pointer registers):23
+// [0, 32) sign + integer digits, built backward from 3224
+// 32 '.'25
+// [33, 39) up to 6 fraction digits, built forward26
+// 39... exponent text ('e', sign, digits) in scientific mode (<= 6 bytes)27
+// Register roles: RBX = sign, then frac_len; RDI = the decimal exponent in28
+// scientific mode, or NF_PLAIN in plain mode.29
+#define NF_PLAIN 0x7FFFFFFF30
+void number_format_emit(CodeBuf *code) {31
+ emit_sub_reg_imm8(code, REG_RSP, NUMBER_FORMAT_SCRATCH);32
+ emit_movq_xmm_from_reg(code, XMM0, REG_RAX);33
+34
+ // ---- NaN / infinity: exponent bits all ones ----35
+ emit_mov_reg_imm64(code, REG_RCX, 0x7FF0000000000000ull);36
+ emit_mov_reg_reg(code, REG_RDX, REG_RAX);37
+ emit_and_reg_reg(code, REG_RDX, REG_RCX);38
+ emit_cmp_reg_reg(code, REG_RDX, REG_RCX);39
+ int finite = emit_jcc_rel32(code, COND_NE);40
+41
+ emit_mov_reg_imm64(code, REG_RCX, 0x000FFFFFFFFFFFFFull);42
+ emit_and_reg_reg(code, REG_RCX, REG_RAX);43
+ emit_cmp_reg_imm32(code, REG_RCX, 0);44
+ int is_inf = emit_jcc_rel32(code, COND_E);45
+ emit_mov_reg_reg(code, REG_RCX, REG_RSP);46
+ store_text(code, "nan");47
+ int special_done_nan = emit_jmp_rel32(code);48
+49
+ emit_patch_jump(code, is_inf);50
+ emit_mov_reg_reg(code, REG_RCX, REG_RSP);51
+ emit_pxor_xmm_xmm(code, XMM2);52
+ emit_ucomisd(code, XMM0, XMM2);53
+ int inf_pos = emit_jcc_rel32(code, COND_AE);54
+ store_text(code, "-");55
+ emit_patch_jump(code, inf_pos);56
+ store_text(code, "inf");57
+ emit_patch_jump(code, special_done_nan);58
+ emit_mov_reg_reg(code, REG_RDX, REG_RCX); // end59
+ emit_mov_reg_reg(code, REG_RCX, REG_RSP); // start60
+ int special_done = emit_jmp_rel32(code);61
+62
+ emit_patch_jump(code, finite);63
+64
+ // ---- sign: RBX = 1 if negative, XMM0 = |x| ----65
+ emit_pxor_xmm_xmm(code, XMM2);66
+ emit_ucomisd(code, XMM0, XMM2);67
+ emit_mov_reg_imm64(code, REG_RBX, 0);68
+ int skip_negate = emit_jcc_rel32(code, COND_AE);69
+ emit_subsd(code, XMM2, XMM0);70
+ emit_movsd_xmm_xmm(code, XMM0, XMM2);71
+ emit_mov_reg_imm64(code, REG_RBX, 1);72
+ emit_patch_jump(code, skip_negate);73
+74
+ // ---- plain (0 or 1e-6 <= x < 1e15) vs scientific ----75
+ emit_mov_reg_imm64(code, REG_RDI, NF_PLAIN);76
+ emit_pxor_xmm_xmm(code, XMM2);77
+ emit_ucomisd(code, XMM0, XMM2);78
+ int plain = emit_jcc_rel32(code, COND_E); // zero79
+ load_double_const(code, XMM1, 1e15);80
+ emit_ucomisd(code, XMM0, XMM1);81
+ int sci = emit_jcc_rel32(code, COND_AE);82
+ load_double_const(code, XMM1, 1e-6);83
+ emit_ucomisd(code, XMM0, XMM1);84
+ int sci_small = emit_jcc_rel32(code, COND_B);85
+86
+ emit_patch_jump(code, plain);87
+ // plain: nudge for round-half-up (carry reaches the integer part), then split88
+ load_double_const(code, XMM1, 0.0000005);89
+ emit_addsd(code, XMM0, XMM1);90
+ emit_cvttsd2si(code, REG_RAX, XMM0);91
+ emit_cvtsi2sd(code, XMM1, REG_RAX);92
+ emit_subsd(code, XMM0, XMM1);93
+ int to_digits = emit_jmp_rel32(code);94
+95
+ // scientific: normalize XMM0 into [1,10), counting the exponent in RDI96
+ emit_patch_jump(code, sci);97
+ emit_patch_jump(code, sci_small);98
+ emit_mov_reg_imm64(code, REG_RDI, 0);99
+ load_double_const(code, XMM3, 10.0);100
+ load_double_const(code, XMM1, 1.0);101
+ emit_ucomisd(code, XMM0, XMM1);102
+ int too_big = emit_jcc_rel32(code, COND_AE);103
+ int small_loop = code->count;104
+ emit_ucomisd(code, XMM0, XMM1);105
+ int normalized_small = emit_jcc_rel32(code, COND_AE);106
+ emit_mulsd(code, XMM0, XMM3);107
+ emit_sub_reg_imm8(code, REG_RDI, 1);108
+ emit_jmp_back(code, small_loop);109
+ emit_patch_jump(code, too_big);110
+ int big_loop = code->count;111
+ emit_ucomisd(code, XMM0, XMM3);112
+ int normalized_big = emit_jcc_rel32(code, COND_B);113
+ emit_divsd(code, XMM0, XMM3);114
+ emit_add_reg_imm8(code, REG_RDI, 1);115
+ emit_jmp_back(code, big_loop);116
+ emit_patch_jump(code, normalized_small);117
+ emit_patch_jump(code, normalized_big);118
+ // round the mantissa; 9.9999996 carries to 10 -> 1 and exponent + 1119
+ load_double_const(code, XMM2, 0.0000005);120
+ emit_addsd(code, XMM0, XMM2);121
+ emit_ucomisd(code, XMM0, XMM3);122
+ int no_carry = emit_jcc_rel32(code, COND_B);123
+ emit_movsd_xmm_xmm(code, XMM0, XMM1); // 1.0124
+ emit_add_reg_imm8(code, REG_RDI, 1);125
+ emit_patch_jump(code, no_carry);126
+ emit_cvttsd2si(code, REG_RAX, XMM0);127
+ emit_cvtsi2sd(code, XMM1, REG_RAX);128
+ emit_subsd(code, XMM0, XMM1);129
+130
+ // ---- integer digits (RAX), backward ----131
+ emit_patch_jump(code, to_digits);132
+ emit_mov_reg_reg(code, REG_RCX, REG_RSP);133
+ emit_add_reg_imm8(code, REG_RCX, 32);134
+ int loop_start = code->count;135
+ emit_dec_reg(code, REG_RCX);136
+ emit_cqo(code);137
+ emit_mov_reg_imm64(code, REG_RSI, 10);138
+ emit_idiv_reg(code, REG_RSI);139
+ emit_add_reg_imm8(code, REG_RDX, '0');140
+ emit_store_byte_reg(code, REG_RCX, REG_RDX);141
+ emit_cmp_reg_imm32(code, REG_RAX, 0);142
+ emit_jcc_back(code, COND_NE, loop_start);143
+144
+ emit_cmp_reg_imm32(code, REG_RBX, 0);145
+ int skip_sign = emit_jcc_rel32(code, COND_E);146
+ emit_dec_reg(code, REG_RCX);147
+ emit_store_byte_imm(code, REG_RCX, '-');148
+ emit_patch_jump(code, skip_sign);149
+150
+ // ---- fraction digits, forward; RBX = how many are significant ----151
+ emit_mov_reg_reg(code, REG_RSI, REG_RSP);152
+ emit_add_reg_imm8(code, REG_RSI, 33);153
+ load_double_const(code, XMM3, 10.0);154
+ emit_mov_reg_imm64(code, REG_RBX, 0);155
+ for (int i = 0; i < 6; i++) {156
+ emit_mulsd(code, XMM0, XMM3);157
+ emit_cvttsd2si(code, REG_RAX, XMM0);158
+ emit_cvtsi2sd(code, XMM1, REG_RAX);159
+ emit_subsd(code, XMM0, XMM1);160
+ emit_cmp_reg_imm32(code, REG_RAX, 0);161
+ int skip_mark = emit_jcc_rel32(code, COND_E);162
+ emit_mov_reg_imm64(code, REG_RBX, (uint64_t)(i + 1));163
+ emit_patch_jump(code, skip_mark);164
+ emit_add_reg_imm8(code, REG_RAX, '0');165
+ emit_store_byte_reg(code, REG_RSI, REG_RAX);166
+ emit_add_reg_imm8(code, REG_RSI, 1);167
+ }168
+169
+ // ---- '.' placement: RSI = end of text ----170
+ emit_mov_reg_reg(code, REG_RSI, REG_RSP);171
+ emit_add_reg_imm8(code, REG_RSI, 32);172
+ emit_cmp_reg_imm32(code, REG_RBX, 0);173
+ int whole = emit_jcc_rel32(code, COND_E);174
+ emit_mov_reg_reg(code, REG_RDX, REG_RSP);175
+ emit_add_reg_imm8(code, REG_RDX, 32);176
+ emit_store_byte_imm(code, REG_RDX, '.');177
+ emit_add_reg_imm8(code, REG_RSI, 1);178
+ emit_add_reg_reg(code, REG_RSI, REG_RBX);179
+ emit_patch_jump(code, whole);180
+181
+ // ---- exponent suffix (scientific mode only) ----182
+ emit_cmp_reg_imm32(code, REG_RDI, NF_PLAIN);183
+ int no_exp = emit_jcc_rel32(code, COND_E);184
+ emit_store_byte_imm(code, REG_RSI, 'e');185
+ emit_add_reg_imm8(code, REG_RSI, 1);186
+ emit_mov_reg_reg(code, REG_RAX, REG_RDI);187
+ emit_cmp_reg_imm32(code, REG_RAX, 0);188
+ int exp_neg = emit_jcc_rel32(code, COND_LT);189
+ emit_store_byte_imm(code, REG_RSI, '+');190
+ int sign_done = emit_jmp_rel32(code);191
+ emit_patch_jump(code, exp_neg);192
+ emit_store_byte_imm(code, REG_RSI, '-');193
+ emit_mov_reg_imm64(code, REG_RDX, 0);194
+ emit_sub_reg_reg(code, REG_RDX, REG_RAX);195
+ emit_mov_reg_reg(code, REG_RAX, REG_RDX); // |e|196
+ emit_patch_jump(code, sign_done);197
+ emit_add_reg_imm8(code, REG_RSI, 1);198
+ // hundreds digit only when |e| >= 100199
+ emit_cmp_reg_imm32(code, REG_RAX, 100);200
+ int two_digits = emit_jcc_rel32(code, COND_LT);201
+ emit_cqo(code);202
+ emit_mov_reg_imm64(code, REG_RBX, 100);203
+ emit_idiv_reg(code, REG_RBX); // RAX = hundreds, RDX = rest204
+ emit_add_reg_imm8(code, REG_RAX, '0');205
+ emit_store_byte_reg(code, REG_RSI, REG_RAX);206
+ emit_add_reg_imm8(code, REG_RSI, 1);207
+ emit_mov_reg_reg(code, REG_RAX, REG_RDX);208
+ emit_patch_jump(code, two_digits);209
+ emit_cqo(code);210
+ emit_mov_reg_imm64(code, REG_RBX, 10);211
+ emit_idiv_reg(code, REG_RBX); // RAX = tens, RDX = ones212
+ emit_add_reg_imm8(code, REG_RAX, '0');213
+ emit_store_byte_reg(code, REG_RSI, REG_RAX);214
+ emit_add_reg_imm8(code, REG_RSI, 1);215
+ emit_add_reg_imm8(code, REG_RDX, '0');216
+ emit_store_byte_reg(code, REG_RSI, REG_RDX);217
+ emit_add_reg_imm8(code, REG_RSI, 1);218
+ emit_patch_jump(code, no_exp);219
+220
+ emit_mov_reg_reg(code, REG_RDX, REG_RSI); // end of text (RCX = start)221
+ emit_patch_jump(code, special_done);222
+}kiln/src/codegen/C/runtime/print_int.cmodified
+23−91@@ -1,5 +1,6 @@11
#include <string.h>22
#include "codegen/H/runtime/print_int.h"3
+#include "codegen/H/runtime/number_format.h"34
#include "codegen/H/emit/emit_internal.h"45
#include "codegen/H/emit/emit_sse.h"56
#include "codegen/H/emit/value.h"@@ -9,17 +10,6 @@910
#include "elf/H/elf_dynamic_call.h"1011
#include "app/H/target.h"1112
12
-// Loads a compile-time-known double literal into an xmm register, via a13
-// GP register holding its raw IEEE-754 bit pattern -- same technique14
-// codegen/C/expressions/expr.c uses for user-written number literals (there's no "mov15
-// xmm, imm64" instruction).16
-static void load_double_const(CodeBuf *code, XReg dst, double v) {17
- uint64_t bits;18
- memcpy(&bits, &v, sizeof(bits));19
- emit_mov_reg_imm64(code, REG_RAX, bits);20
- emit_movq_xmm_from_reg(code, dst, REG_RAX);21
-}22
-2313
// Embeds a fixed byte sequence directly in the code stream (jumped over2414
// at runtime, never executed) and writes it to stdout -- used for the2515
// small fixed strings ("none", "<function>") that don't need per-call@@ -90,87 +80,12 @@ static void emit_number_routine(CodeBuf *code) {9080
int routine_skip = emit_jmp_rel32(code);9181
number_routine_offset = code->count;9282
93
- emit_movq_xmm_from_reg(code, XMM0, REG_RAX);94
- emit_sub_reg_imm8(code, REG_RSP, 40);95
-96
- // ---- sign ----97
- emit_pxor_xmm_xmm(code, XMM2); // XMM2 = +0.0, used as the comparison zero98
- emit_ucomisd(code, XMM0, XMM2);99
- emit_mov_reg_imm64(code, REG_RBX, 0);100
- int skip_negate = emit_jcc_rel32(code, COND_AE); // XMM0 >= 0 -> not negative101
- emit_pxor_xmm_xmm(code, XMM2);102
- emit_subsd(code, XMM2, XMM0); // XMM2 = 0.0 - XMM0 = -XMM0103
- emit_movsd_xmm_xmm(code, XMM0, XMM2);104
- emit_mov_reg_imm64(code, REG_RBX, 1);105
- emit_patch_jump(code, skip_negate);106
-107
- // ---- split into integer part (RAX) and fractional remainder (XMM0, now in [0,1)) ----108
- emit_cvttsd2si(code, REG_RAX, XMM0);109
- emit_cvtsi2sd(code, XMM1, REG_RAX);110
- emit_subsd(code, XMM0, XMM1);111
-112
- // ---- integer digits, backward ----113
- emit_mov_reg_reg(code, REG_RCX, REG_RSP);114
- emit_add_reg_imm8(code, REG_RCX, 32);115
- int loop_start = code->count;116
- emit_dec_reg(code, REG_RCX);117
- emit_cqo(code);118
- emit_mov_reg_imm64(code, REG_RSI, 10);119
- emit_idiv_reg(code, REG_RSI);120
- emit_add_reg_imm8(code, REG_RDX, '0');121
- emit_store_byte_reg(code, REG_RCX, REG_RDX);122
- emit_cmp_reg_imm32(code, REG_RAX, 0);123
- emit_jcc_back(code, COND_NE, loop_start);124
-125
- // ---- sign prepend ----126
- emit_cmp_reg_imm32(code, REG_RBX, 0);127
- int skip_sign = emit_jcc_rel32(code, COND_E);128
- emit_dec_reg(code, REG_RCX);129
- emit_store_byte_imm(code, REG_RCX, '-');130
- emit_patch_jump(code, skip_sign);131
-132
- // ---- fraction digits, forward, 6 unrolled steps -- see print_int.c's133
- // history for why the 0.0000005 round-half-up nudge is needed ----134
- load_double_const(code, XMM1, 0.0000005);135
- emit_addsd(code, XMM0, XMM1);136
- emit_mov_reg_reg(code, REG_RSI, REG_RSP);137
- emit_add_reg_imm8(code, REG_RSI, 33);138
- load_double_const(code, XMM3, 10.0);139
- emit_mov_reg_imm64(code, REG_RBX, 0); // repurposed: frac_len (sign already applied above)140
- for (int i = 0; i < 6; i++) {141
- emit_mulsd(code, XMM0, XMM3);142
- emit_cvttsd2si(code, REG_RAX, XMM0); // this step's digit, 0-9143
- emit_cvtsi2sd(code, XMM1, REG_RAX);144
- emit_subsd(code, XMM0, XMM1); // remainder for the next digit145
- emit_cmp_reg_imm32(code, REG_RAX, 0);146
- int skip_mark = emit_jcc_rel32(code, COND_E);147
- emit_mov_reg_imm64(code, REG_RBX, (uint64_t)(i + 1)); // "kept up through here"148
- emit_patch_jump(code, skip_mark);149
- emit_add_reg_imm8(code, REG_RAX, '0');150
- emit_store_byte_reg(code, REG_RSI, REG_RAX);151
- emit_add_reg_imm8(code, REG_RSI, 1);152
- }153
-154
- // ---- decide '.' placement, then a single write(2) -- no newline ----155
- emit_mov_reg_reg(code, REG_RDX, REG_RSP);156
- emit_add_reg_imm8(code, REG_RDX, 32);157
- emit_cmp_reg_imm32(code, REG_RBX, 0);158
- int whole_number = emit_jcc_rel32(code, COND_E);159
- emit_store_byte_imm(code, REG_RDX, '.');160
- emit_mov_reg_reg(code, REG_RDX, REG_RSP);161
- emit_add_reg_imm8(code, REG_RDX, 33);162
- emit_add_reg_reg(code, REG_RDX, REG_RBX); // + frac_len163
- int after_dot = emit_jmp_rel32(code);164
- emit_patch_jump(code, whole_number);165
- // RDX already points one past the last int digit -- the correct166
- // end-of-content position when there's no fraction.167
- emit_patch_jump(code, after_dot);168
-169
- emit_mov_reg_reg(code, REG_RSI, REG_RCX); // buf start170
- emit_sub_reg_reg(code, REG_RDX, REG_RCX); // length = content_end - buf_start83
+ number_format_emit(code); // RCX = text start, RDX = text end (on the stack)84
+ emit_mov_reg_reg(code, REG_RSI, REG_RCX);85
+ emit_sub_reg_reg(code, REG_RDX, REG_RCX);17186
platform_emit_write_stdout(code);17287
173
- emit_add_reg_imm8(code, REG_RSP, 40);88
+ emit_add_reg_imm8(code, REG_RSP, NUMBER_FORMAT_SCRATCH);17489
emit_ret(code);17590
emit_patch_jump(code, routine_skip);17691
}@@ -211,7 +126,7 @@ static void emit_print_recursive_routine(CodeBuf *code) {211126
emit_mov_reg_reg(code, REG_RBP, REG_RSP);212127
emit_sub_reg_imm8(code, REG_RSP, 32); // [RBP-8/-16/-24] loop state, [RBP-32] map's current entry addr213128
214
- int to_epilogue[5];129
+ int to_epilogue[8];215130
int n_epilogue = 0;216131
217132
emit_cmp_reg_imm32(code, REG_RBX, TAG_STRING);@@ -251,6 +166,23 @@ static void emit_print_recursive_routine(CodeBuf *code) {251166
}252167
emit_patch_jump(code, not_number);253168
169
+ emit_cmp_reg_imm32(code, REG_RBX, TAG_BOOL);170
+ int not_bool = emit_jcc_rel32(code, COND_NE);171
+ {172
+ static int yes_offset = -1;173
+ static int no_offset = -1;174
+ if (yes_offset == -1) yes_offset = emit_literal(code, "yes", 3);175
+ if (no_offset == -1) no_offset = emit_literal(code, "no", 2);176
+ emit_cmp_reg_imm32(code, REG_RAX, 0); // payload bits: 0.0 is all zero177
+ int is_no = emit_jcc_rel32(code, COND_E);178
+ write_literal(code, yes_offset, 3);179
+ to_epilogue[n_epilogue++] = emit_jmp_rel32(code);180
+ emit_patch_jump(code, is_no);181
+ write_literal(code, no_offset, 2);182
+ to_epilogue[n_epilogue++] = emit_jmp_rel32(code);183
+ }184
+ emit_patch_jump(code, not_bool);185
+254186
emit_cmp_reg_imm32(code, REG_RBX, TAG_ARRAY);255187
int not_array = emit_jcc_rel32(code, COND_NE);256188
{kiln/src/codegen/C/strings/affix_builtins.cadded
+134−0@@ -0,0 +1,134 @@1
+#include "codegen/H/strings/string_builtins.h"2
+#include "codegen/H/strings/string_alloc.h"3
+#include "codegen/H/expressions/expr.h"4
+#include "codegen/H/emit/emit.h"5
+#include "codegen/H/emit/emit_sse.h"6
+#include "codegen/H/emit/bytes.h"7
+#include "codegen/H/emit/value.h"8
+#include "codegen/H/runtime/errors.h"9
+#include "parser/H/core/parser.h"10
+#include "parser/H/declarations/vars.h"11
+12
+static void push_number(double v) {13
+ emit_mov_reg_imm64(code, REG_RBX, TAG_BOOL);14
+ emit_push_reg(code, REG_RBX);15
+ uint64_t bits;16
+ __builtin_memcpy(&bits, &v, sizeof(bits));17
+ emit_mov_reg_imm64(code, REG_RAX, bits);18
+ emit_push_reg(code, REG_RAX);19
+}20
+21
+// contains(s, search) = indexOf(s, search) >= 022
+void codegen_builtin_contains(void) {23
+ codegen_builtin_indexof();24
+ emit_pop_reg(code, REG_RAX);25
+ emit_pop_reg(code, REG_RBX);26
+ emit_movq_xmm_from_reg(code, XMM0, REG_RAX);27
+ emit_pxor_xmm_xmm(code, XMM1);28
+ emit_ucomisd(code, XMM0, XMM1);29
+ int found = emit_jcc_rel32(code, COND_AE);30
+ push_number(0.0);31
+ int done = emit_jmp_rel32(code);32
+ emit_patch_jump(code, found);33
+ push_number(1.0);34
+ emit_patch_jump(code, done);35
+}36
+37
+// starts_with(s, prefix) / ends_with(s, suffix): compare `affix_len` bytes at38
+// the start (or at s_len - affix_len) of s.39
+static void affix(int at_end) {40
+ codegen_expression(); // s41
+ expect(TOKEN_COMMA, "expected ',' after string argument");42
+ codegen_expression(); // affix43
+ expect(TOKEN_RPAREN, "expected ')' after arguments");44
+45
+ emit_pop_reg(code, REG_RCX); // affix payload46
+ emit_pop_reg(code, REG_RBX);47
+ emit_pop_reg(code, REG_RSI); // s payload48
+ emit_pop_reg(code, REG_RBX);49
+ emit_load_mem_disp32(code, REG_RDX, REG_RCX, -8); // affix_len50
+ emit_load_mem_disp32(code, REG_RDI, REG_RSI, -8); // s_len51
+ emit_cmp_reg_reg(code, REG_RDX, REG_RDI);52
+ int too_long = emit_jcc_rel32(code, COND_GT);53
+ if (at_end) {54
+ emit_sub_reg_reg(code, REG_RDI, REG_RDX);55
+ emit_add_reg_reg(code, REG_RSI, REG_RDI);56
+ }57
+ int loop = code->count;58
+ emit_cmp_reg_imm32(code, REG_RDX, 0);59
+ int matched = emit_jcc_rel32(code, COND_E);60
+ emit_load_byte_reg(code, REG_RAX, REG_RSI);61
+ emit_load_byte_reg(code, REG_RBX, REG_RCX);62
+ emit_cmp_reg_reg(code, REG_RAX, REG_RBX);63
+ int mismatch = emit_jcc_rel32(code, COND_NE);64
+ emit_add_reg_imm8(code, REG_RSI, 1);65
+ emit_add_reg_imm8(code, REG_RCX, 1);66
+ emit_dec_reg(code, REG_RDX);67
+ emit_jmp_back(code, loop);68
+69
+ emit_patch_jump(code, too_long);70
+ emit_patch_jump(code, mismatch);71
+ push_number(0.0);72
+ int done = emit_jmp_rel32(code);73
+ emit_patch_jump(code, matched);74
+ push_number(1.0);75
+ emit_patch_jump(code, done);76
+}77
+78
+void codegen_builtin_starts_with(void) { affix(0); }79
+void codegen_builtin_ends_with(void) { affix(1); }80
+81
+// repeat(s, n): s concatenated n times.82
+// Scratch: [0]=s [8]=len [16]=remaining [24]=dest cursor [32]=block83
+void codegen_builtin_repeat(void) {84
+ codegen_expression(); // s85
+ expect(TOKEN_COMMA, "expected ',' after string argument");86
+ codegen_expression(); // n87
+ expect(TOKEN_RPAREN, "expected ')' after arguments");88
+89
+ int32_t base = higher_order_scratch_offset();90
+ emit_pop_reg(code, REG_RAX);91
+ emit_pop_reg(code, REG_RBX);92
+ emit_movq_xmm_from_reg(code, XMM0, REG_RAX);93
+ emit_cvttsd2si(code, REG_RCX, XMM0); // n94
+ emit_pop_reg(code, REG_RSI);95
+ emit_pop_reg(code, REG_RBX);96
+97
+ emit_cmp_reg_imm32(code, REG_RCX, 0);98
+ int n_ok = emit_jcc_rel32(code, COND_GE);99
+ errors_emit_die(code, "runtime error: repeat() count must not be negative");100
+ emit_patch_jump(code, n_ok);101
+102
+ emit_load_mem_disp32(code, REG_RDX, REG_RSI, -8); // len103
+ emit_store_mem_disp32(code, REG_RBP, base + 0, REG_RSI);104
+ emit_store_mem_disp32(code, REG_RBP, base + 8, REG_RDX);105
+ emit_store_mem_disp32(code, REG_RBP, base + 16, REG_RCX);106
+ emit_imul_reg_reg(code, REG_RDX, REG_RCX); // total length107
+ string_alloc_emit_prefixed(code); // RAX = block, RDX = total108
+ emit_store_mem_disp32(code, REG_RBP, base + 32, REG_RAX);109
+ emit_add_reg_imm8(code, REG_RAX, 8);110
+ emit_store_mem_disp32(code, REG_RBP, base + 24, REG_RAX);111
+112
+ int loop = code->count;113
+ emit_load_mem_disp32(code, REG_RCX, REG_RBP, base + 16);114
+ emit_cmp_reg_imm32(code, REG_RCX, 0);115
+ int finished = emit_jcc_rel32(code, COND_E);116
+ emit_sub_reg_imm8(code, REG_RCX, 1);117
+ emit_store_mem_disp32(code, REG_RBP, base + 16, REG_RCX);118
+ emit_load_mem_disp32(code, REG_RDI, REG_RBP, base + 24);119
+ emit_load_mem_disp32(code, REG_RBX, REG_RBP, base + 0);120
+ emit_load_mem_disp32(code, REG_RDX, REG_RBP, base + 8);121
+ bytes_emit_copy(code);122
+ emit_load_mem_disp32(code, REG_RDI, REG_RBP, base + 24);123
+ emit_load_mem_disp32(code, REG_RDX, REG_RBP, base + 8);124
+ emit_add_reg_reg(code, REG_RDI, REG_RDX);125
+ emit_store_mem_disp32(code, REG_RBP, base + 24, REG_RDI);126
+ emit_jmp_back(code, loop);127
+ emit_patch_jump(code, finished);128
+129
+ emit_load_mem_disp32(code, REG_RAX, REG_RBP, base + 32);130
+ emit_add_reg_imm8(code, REG_RAX, 8);131
+ emit_mov_reg_imm64(code, REG_RBX, TAG_STRING);132
+ emit_push_reg(code, REG_RBX);133
+ emit_push_reg(code, REG_RAX);134
+}kiln/src/codegen/C/strings/char_builtins.cadded
+54−0@@ -0,0 +1,54 @@1
+#include "codegen/H/strings/string_builtins.h"2
+#include "codegen/H/strings/string_alloc.h"3
+#include "codegen/H/runtime/errors.h"4
+#include "codegen/H/emit/emit_internal.h"5
+#include "codegen/H/emit/emit_sse.h"6
+#include "codegen/H/emit/value.h"7
+#include "parser/H/core/parser.h"8
+9
+// chr(n): a one-byte string holding byte n (1..255). Same allocation shape as10
+// codegen_string_index_read's one-char result.11
+void codegen_builtin_chr(void) {12
+ emit_pop_reg(code, REG_RAX); // payload13
+ emit_pop_reg(code, REG_RBX); // tag (ignored, assumed NUMBER)14
+ emit_movq_xmm_from_reg(code, XMM0, REG_RAX);15
+ emit_cvttsd2si(code, REG_RBX, XMM0); // code (RBX survives string_alloc_emit_prefixed)16
+17
+ emit_cmp_reg_imm32(code, REG_RBX, 1);18
+ int too_small = emit_jcc_rel32(code, COND_LT);19
+ emit_cmp_reg_imm32(code, REG_RBX, 255);20
+ int too_big = emit_jcc_rel32(code, COND_GT);21
+ int ok = emit_jmp_rel32(code);22
+ emit_patch_jump(code, too_small);23
+ emit_patch_jump(code, too_big);24
+ errors_emit_die(code, "runtime error: chr() expects a number from 1 to 255");25
+ emit_patch_jump(code, ok);26
+27
+ emit_mov_reg_imm64(code, REG_RDX, 1);28
+ string_alloc_emit_prefixed(code); // RAX = new 1-byte block, length prefix written29
+ emit_add_reg_imm8(code, REG_RAX, 8);30
+ emit_store_byte_reg(code, REG_RAX, REG_RBX);31
+32
+ emit_mov_reg_imm64(code, REG_RBX, TAG_STRING);33
+ emit_push_reg(code, REG_RBX);34
+ emit_push_reg(code, REG_RAX);35
+}36
+37
+// ord(s): the first byte of s as a number (0..255).38
+void codegen_builtin_ord(void) {39
+ emit_pop_reg(code, REG_RSI); // string payload40
+ emit_pop_reg(code, REG_RBX); // tag (ignored, assumed STRING)41
+42
+ emit_load_mem_disp32(code, REG_RDX, REG_RSI, -8); // length43
+ emit_cmp_reg_imm32(code, REG_RDX, 0);44
+ int nonempty = emit_jcc_rel32(code, COND_NE);45
+ errors_emit_die(code, "runtime error: ord() expects a non-empty string");46
+ emit_patch_jump(code, nonempty);47
+48
+ emit_load_byte_reg(code, REG_RAX, REG_RSI);49
+ emit_cvtsi2sd(code, XMM0, REG_RAX);50
+ emit_movq_reg_from_xmm(code, REG_RAX, XMM0);51
+ emit_mov_reg_imm64(code, REG_RBX, TAG_NUMBER);52
+ emit_push_reg(code, REG_RBX);53
+ emit_push_reg(code, REG_RAX);54
+}kiln/src/codegen/C/strings/strings.cmodified
+28−2@@ -12,7 +12,7 @@1212
static void push_number_literal(double v) {1313
uint64_t bits;1414
__builtin_memcpy(&bits, &v, sizeof(bits));15
- emit_mov_reg_imm64(code, REG_RBX, TAG_NUMBER);15
+ emit_mov_reg_imm64(code, REG_RBX, TAG_BOOL);1616
emit_push_reg(code, REG_RBX);1717
emit_mov_reg_imm64(code, REG_RAX, bits);1818
emit_push_reg(code, REG_RAX);@@ -141,6 +141,32 @@ void codegen_string_compare(int invert) {141141
emit_patch_jump(code, skip_true);142142
}143143
144
+void codegen_string_order(void) {145
+ emit_load_mem_disp32(code, REG_RBX, REG_RAX, -8); // bytes left in a146
+ emit_load_mem_disp32(code, REG_RDX, REG_RCX, -8); // bytes left in b147
+148
+ int loop_start = code->count;149
+ emit_cmp_reg_imm32(code, REG_RBX, 0);150
+ int a_done = emit_jcc_rel32(code, COND_E);151
+ emit_cmp_reg_imm32(code, REG_RDX, 0);152
+ int b_done = emit_jcc_rel32(code, COND_E);153
+ emit_load_byte_reg(code, REG_RSI, REG_RAX);154
+ emit_load_byte_reg(code, REG_RDI, REG_RCX);155
+ emit_cmp_reg_reg(code, REG_RSI, REG_RDI);156
+ int differ = emit_jcc_rel32(code, COND_NE); // flags from the first differing byte157
+ emit_add_reg_imm8(code, REG_RAX, 1);158
+ emit_add_reg_imm8(code, REG_RCX, 1);159
+ emit_dec_reg(code, REG_RBX);160
+ emit_dec_reg(code, REG_RDX);161
+ emit_jmp_back(code, loop_start);162
+163
+ // One side ran out: the one with bytes left is greater (both out: equal).164
+ emit_patch_jump(code, a_done);165
+ emit_patch_jump(code, b_done);166
+ emit_cmp_reg_reg(code, REG_RBX, REG_RDX);167
+ emit_patch_jump(code, differ);168
+}169
+144170
void codegen_string_index_read(void) {145171
emit_pop_reg(code, REG_RAX); // index payload146172
emit_pop_reg(code, REG_RBX); // index tag (ignored, assumed NUMBER)@@ -158,7 +184,7 @@ void codegen_string_index_read(void) {158184
int ok = emit_jmp_rel32(code);159185
emit_patch_jump(code, neg);160186
emit_patch_jump(code, toobig);161
- errors_emit_die(code, "runtime error: string index out of bounds");187
+ errors_emit_die(code, "index out of bounds");162188
emit_patch_jump(code, ok);163189
164190
emit_add_reg_reg(code, REG_RSI, REG_RCX); // char addresskiln/src/codegen/D/builtins/builtins.dmodified
+3−1@@ -7,7 +7,8 @@ src/codegen/O/builtins/builtins.o: src/codegen/C/builtins/builtins.c \77
src/codegen/H/collections/keys_values.h \88
src/codegen/H/strings/string_builtins.h src/codegen/H/io/file_builtins.h \99
src/codegen/H/io/input_builtin.h src/codegen/H/io/argv_builtin.h \10
- src/codegen/H/builtins/type_builtin.h src/parser/H/core/parser.h \10
+ src/codegen/H/builtins/type_builtin.h \11
+ src/codegen/H/builtins/exit_builtin.h src/parser/H/core/parser.h \1112
src/codegen/H/emit/emit.h1213
src/codegen/H/builtins/builtins.h:1314
src/codegen/H/expressions/expr.h:@@ -23,5 +24,6 @@ src/codegen/H/io/file_builtins.h:2324
src/codegen/H/io/input_builtin.h:2425
src/codegen/H/io/argv_builtin.h:2526
src/codegen/H/builtins/type_builtin.h:27
+src/codegen/H/builtins/exit_builtin.h:2628
src/parser/H/core/parser.h:2729
src/codegen/H/emit/emit.h:kiln/src/codegen/D/builtins/exit_builtin.dadded
+22−0@@ -0,0 +1,22 @@1
+src/codegen/O/builtins/exit_builtin.o: \2
+ src/codegen/C/builtins/exit_builtin.c \3
+ src/codegen/H/builtins/exit_builtin.h src/codegen/H/expressions/expr.h \4
+ src/lexer/H/lexer.h src/codegen/H/emit/emit.h \5
+ src/codegen/H/emit/emit_sse.h src/codegen/H/platform/win_call.h \6
+ src/pe/H/pe_writer.h src/codegen/H/emit/runtime_layout.h \7
+ src/codegen/H/emit/runtime_import_enum.h \8
+ src/codegen/H/emit/runtime_exports.def src/pe/H/pe_imports.def \9
+ src/app/H/target.h src/parser/H/core/parser.h10
+src/codegen/H/builtins/exit_builtin.h:11
+src/codegen/H/expressions/expr.h:12
+src/lexer/H/lexer.h:13
+src/codegen/H/emit/emit.h:14
+src/codegen/H/emit/emit_sse.h:15
+src/codegen/H/platform/win_call.h:16
+src/pe/H/pe_writer.h:17
+src/codegen/H/emit/runtime_layout.h:18
+src/codegen/H/emit/runtime_import_enum.h:19
+src/codegen/H/emit/runtime_exports.def:20
+src/pe/H/pe_imports.def:21
+src/app/H/target.h:22
+src/parser/H/core/parser.h:kiln/src/codegen/D/builtins/sort_builtin.dadded
+19−0@@ -0,0 +1,19 @@1
+src/codegen/O/builtins/sort_builtin.o: \2
+ src/codegen/C/builtins/sort_builtin.c \3
+ src/codegen/H/builtins/higher_order.h src/codegen/H/expressions/expr.h \4
+ src/lexer/H/lexer.h src/codegen/H/functions/closures.h \5
+ src/parser/H/declarations/functions.h src/codegen/H/strings/strings.h \6
+ src/codegen/H/emit/emit.h src/codegen/H/emit/emit_sse.h \7
+ src/codegen/H/emit/value.h src/parser/H/core/parser.h \8
+ src/parser/H/declarations/vars.h9
+src/codegen/H/builtins/higher_order.h:10
+src/codegen/H/expressions/expr.h:11
+src/lexer/H/lexer.h:12
+src/codegen/H/functions/closures.h:13
+src/parser/H/declarations/functions.h:14
+src/codegen/H/strings/strings.h:15
+src/codegen/H/emit/emit.h:16
+src/codegen/H/emit/emit_sse.h:17
+src/codegen/H/emit/value.h:18
+src/parser/H/core/parser.h:19
+src/parser/H/declarations/vars.h:kiln/src/codegen/D/builtins/str_builtin.dmodified
+7−2@@ -2,13 +2,18 @@ src/codegen/O/builtins/str_builtin.o: \22
src/codegen/C/builtins/str_builtin.c \33
src/codegen/H/builtins/convert_builtins.h src/codegen/H/emit/emit.h \44
src/codegen/H/emit/emit_sse.h src/codegen/H/emit/value.h \5
- src/codegen/H/runtime/heap.h src/codegen/H/emit/bytes.h \6
- src/parser/H/core/parser.h src/lexer/H/lexer.h5
+ src/codegen/H/runtime/heap.h src/codegen/H/runtime/errors.h \6
+ src/codegen/H/strings/strings.h src/codegen/H/runtime/number_format.h \7
+ src/codegen/H/emit/bytes.h src/parser/H/core/parser.h \8
+ src/lexer/H/lexer.h79
src/codegen/H/builtins/convert_builtins.h:810
src/codegen/H/emit/emit.h:911
src/codegen/H/emit/emit_sse.h:1012
src/codegen/H/emit/value.h:1113
src/codegen/H/runtime/heap.h:14
+src/codegen/H/runtime/errors.h:15
+src/codegen/H/strings/strings.h:16
+src/codegen/H/runtime/number_format.h:1217
src/codegen/H/emit/bytes.h:1318
src/parser/H/core/parser.h:1419
src/lexer/H/lexer.h:kiln/src/codegen/D/collections/array_mutate.dadded
+16−0@@ -0,0 +1,16 @@1
+src/codegen/O/collections/array_mutate.o: \2
+ src/codegen/C/collections/array_mutate.c \3
+ src/codegen/H/collections/arrays.h src/codegen/H/emit/emit_internal.h \4
+ src/codegen/H/emit/emit.h src/codegen/H/emit/emit_sse.h \5
+ src/codegen/H/emit/value.h src/codegen/H/runtime/heap.h \6
+ src/codegen/H/runtime/errors.h src/parser/H/core/parser.h \7
+ src/lexer/H/lexer.h8
+src/codegen/H/collections/arrays.h:9
+src/codegen/H/emit/emit_internal.h:10
+src/codegen/H/emit/emit.h:11
+src/codegen/H/emit/emit_sse.h:12
+src/codegen/H/emit/value.h:13
+src/codegen/H/runtime/heap.h:14
+src/codegen/H/runtime/errors.h:15
+src/parser/H/core/parser.h:16
+src/lexer/H/lexer.h:kiln/src/codegen/D/collections/maps_mutate.dmodified
+2−0@@ -1,11 +1,13 @@11
src/codegen/O/collections/maps_mutate.o: \22
src/codegen/C/collections/maps_mutate.c src/codegen/H/collections/maps.h \3
+ src/codegen/H/collections/arrays.h \34
src/codegen/H/collections/maps_internal.h \45
src/codegen/H/emit/emit_internal.h src/codegen/H/emit/emit.h \56
src/codegen/H/emit/value.h src/codegen/H/runtime/heap.h \67
src/codegen/H/emit/bytes.h src/parser/H/core/parser.h \78
src/lexer/H/lexer.h89
src/codegen/H/collections/maps.h:10
+src/codegen/H/collections/arrays.h:911
src/codegen/H/collections/maps_internal.h:1012
src/codegen/H/emit/emit_internal.h:1113
src/codegen/H/emit/emit.h:kiln/src/codegen/D/expressions/expr.dmodified
+5−1@@ -5,7 +5,9 @@ src/codegen/O/expressions/expr.o: src/codegen/C/expressions/expr.c \55
src/codegen/H/strings/strings.h src/codegen/H/collections/arrays.h \66
src/codegen/H/collections/maps.h src/codegen/H/builtins/builtins.h \77
src/codegen/H/functions/closures.h src/parser/H/declarations/functions.h \8
- src/parser/H/core/parser.h src/parser/H/declarations/vars.h \8
+ src/codegen/H/runtime/errors.h src/parser/H/core/parser.h \9
+ src/parser/H/declarations/vars.h \10
+ src/parser/H/declarations/module_state.h \911
src/parser/H/declarations/constants.h1012
src/codegen/H/expressions/expr.h:1113
src/lexer/H/lexer.h:@@ -19,6 +21,8 @@ src/codegen/H/collections/maps.h:1921
src/codegen/H/builtins/builtins.h:2022
src/codegen/H/functions/closures.h:2123
src/parser/H/declarations/functions.h:24
+src/codegen/H/runtime/errors.h:2225
src/parser/H/core/parser.h:2326
src/parser/H/declarations/vars.h:27
+src/parser/H/declarations/module_state.h:2428
src/parser/H/declarations/constants.h:kiln/src/codegen/D/expressions/expr_bool.dmodified
+2−1@@ -3,12 +3,13 @@ src/codegen/O/expressions/expr_bool.o: \33
src/lexer/H/lexer.h src/codegen/H/expressions/expr_internal.h \44
src/codegen/H/emit/emit.h src/codegen/H/emit/emit_sse.h \55
src/codegen/H/emit/value.h src/codegen/H/strings/strings.h \6
- src/parser/H/core/parser.h6
+ src/codegen/H/runtime/errors.h src/parser/H/core/parser.h77
src/codegen/H/expressions/expr.h:88
src/lexer/H/lexer.h:99
src/codegen/H/expressions/expr_internal.h:1010
src/codegen/H/emit/emit.h:1111
src/codegen/H/emit/emit_sse.h:1212
src/codegen/H/emit/value.h:1313
src/codegen/H/strings/strings.h:14
+src/codegen/H/runtime/errors.h:1415
src/parser/H/core/parser.h:kiln/src/codegen/D/io/fs_builtins.dadded
+35−0@@ -0,0 +1,35 @@1
+src/codegen/O/io/fs_builtins.o: src/codegen/C/io/fs_builtins.c \2
+ src/codegen/H/io/file_builtins.h src/codegen/H/io/file_path.h \3
+ src/codegen/H/emit/emit.h src/codegen/H/expressions/expr.h \4
+ src/lexer/H/lexer.h src/codegen/H/emit/emit_sse.h \5
+ src/codegen/H/emit/value.h src/codegen/H/emit/bytes.h \6
+ src/codegen/H/collections/arrays.h \7
+ src/codegen/H/platform/platform_file.h src/codegen/H/runtime/errors.h \8
+ src/codegen/H/runtime/heap.h src/codegen/H/strings/string_alloc.h \9
+ src/app/H/target.h src/codegen/H/platform/win_call.h \10
+ src/pe/H/pe_writer.h src/codegen/H/emit/runtime_layout.h \11
+ src/codegen/H/emit/runtime_import_enum.h \12
+ src/codegen/H/emit/runtime_exports.def src/pe/H/pe_imports.def \13
+ src/parser/H/core/parser.h src/parser/H/declarations/vars.h14
+src/codegen/H/io/file_builtins.h:15
+src/codegen/H/io/file_path.h:16
+src/codegen/H/emit/emit.h:17
+src/codegen/H/expressions/expr.h:18
+src/lexer/H/lexer.h:19
+src/codegen/H/emit/emit_sse.h:20
+src/codegen/H/emit/value.h:21
+src/codegen/H/emit/bytes.h:22
+src/codegen/H/collections/arrays.h:23
+src/codegen/H/platform/platform_file.h:24
+src/codegen/H/runtime/errors.h:25
+src/codegen/H/runtime/heap.h:26
+src/codegen/H/strings/string_alloc.h:27
+src/app/H/target.h:28
+src/codegen/H/platform/win_call.h:29
+src/pe/H/pe_writer.h:30
+src/codegen/H/emit/runtime_layout.h:31
+src/codegen/H/emit/runtime_import_enum.h:32
+src/codegen/H/emit/runtime_exports.def:33
+src/pe/H/pe_imports.def:34
+src/parser/H/core/parser.h:35
+src/parser/H/declarations/vars.h:kiln/src/codegen/D/runtime/number_format.dadded
+8−0@@ -0,0 +1,8 @@1
+src/codegen/O/runtime/number_format.o: \2
+ src/codegen/C/runtime/number_format.c \3
+ src/codegen/H/runtime/number_format.h src/codegen/H/emit/emit.h \4
+ src/codegen/H/emit/emit_internal.h src/codegen/H/emit/emit_sse.h5
+src/codegen/H/runtime/number_format.h:6
+src/codegen/H/emit/emit.h:7
+src/codegen/H/emit/emit_internal.h:8
+src/codegen/H/emit/emit_sse.h:kiln/src/codegen/D/runtime/print_int.dmodified
+5−4@@ -1,15 +1,16 @@11
src/codegen/O/runtime/print_int.o: src/codegen/C/runtime/print_int.c \22
src/codegen/H/runtime/print_int.h src/codegen/H/emit/emit.h \3
- src/codegen/H/emit/emit_internal.h src/codegen/H/emit/emit_sse.h \4
- src/codegen/H/emit/value.h src/codegen/H/emit/layout.h \5
- src/codegen/H/platform/win_call.h src/pe/H/pe_writer.h \6
- src/codegen/H/emit/runtime_layout.h \3
+ src/codegen/H/runtime/number_format.h src/codegen/H/emit/emit_internal.h \4
+ src/codegen/H/emit/emit_sse.h src/codegen/H/emit/value.h \5
+ src/codegen/H/emit/layout.h src/codegen/H/platform/win_call.h \6
+ src/pe/H/pe_writer.h src/codegen/H/emit/runtime_layout.h \77
src/codegen/H/emit/runtime_import_enum.h \88
src/codegen/H/emit/runtime_exports.def src/pe/H/pe_imports.def \99
src/codegen/H/platform/platform_console.h src/elf/H/elf_dynamic_call.h \1010
src/elf/H/elf_dynamic.h src/elf/H/elf_writer.h src/app/H/target.h1111
src/codegen/H/runtime/print_int.h:1212
src/codegen/H/emit/emit.h:13
+src/codegen/H/runtime/number_format.h:1314
src/codegen/H/emit/emit_internal.h:1415
src/codegen/H/emit/emit_sse.h:1516
src/codegen/H/emit/value.h:kiln/src/codegen/D/strings/affix_builtins.dadded
+19−0@@ -0,0 +1,19 @@1
+src/codegen/O/strings/affix_builtins.o: \2
+ src/codegen/C/strings/affix_builtins.c \3
+ src/codegen/H/strings/string_builtins.h \4
+ src/codegen/H/strings/string_alloc.h src/codegen/H/emit/emit.h \5
+ src/codegen/H/expressions/expr.h src/lexer/H/lexer.h \6
+ src/codegen/H/emit/emit_sse.h src/codegen/H/emit/bytes.h \7
+ src/codegen/H/emit/value.h src/codegen/H/runtime/errors.h \8
+ src/parser/H/core/parser.h src/parser/H/declarations/vars.h9
+src/codegen/H/strings/string_builtins.h:10
+src/codegen/H/strings/string_alloc.h:11
+src/codegen/H/emit/emit.h:12
+src/codegen/H/expressions/expr.h:13
+src/lexer/H/lexer.h:14
+src/codegen/H/emit/emit_sse.h:15
+src/codegen/H/emit/bytes.h:16
+src/codegen/H/emit/value.h:17
+src/codegen/H/runtime/errors.h:18
+src/parser/H/core/parser.h:19
+src/parser/H/declarations/vars.h:kiln/src/codegen/D/strings/char_builtins.dadded
+16−0@@ -0,0 +1,16 @@1
+src/codegen/O/strings/char_builtins.o: \2
+ src/codegen/C/strings/char_builtins.c \3
+ src/codegen/H/strings/string_builtins.h \4
+ src/codegen/H/strings/string_alloc.h src/codegen/H/emit/emit.h \5
+ src/codegen/H/runtime/errors.h src/codegen/H/emit/emit_internal.h \6
+ src/codegen/H/emit/emit_sse.h src/codegen/H/emit/value.h \7
+ src/parser/H/core/parser.h src/lexer/H/lexer.h8
+src/codegen/H/strings/string_builtins.h:9
+src/codegen/H/strings/string_alloc.h:10
+src/codegen/H/emit/emit.h:11
+src/codegen/H/runtime/errors.h:12
+src/codegen/H/emit/emit_internal.h:13
+src/codegen/H/emit/emit_sse.h:14
+src/codegen/H/emit/value.h:15
+src/parser/H/core/parser.h:16
+src/lexer/H/lexer.h:kiln/src/codegen/H/builtins/builtins.hmodified
+4−0@@ -8,4 +8,8 @@88
// caller falls through to a user-defined function lookup.99
int codegen_try_builtin_call(const char *name, int len);1010
11
+// Argument count a builtin is wrapped with when used as a value, or -1 if12
+// `name` is not a builtin.13
+int codegen_builtin_arity(const char *name, int len);14
+1115
#endifkiln/src/codegen/H/builtins/exit_builtin.hadded
+8−0@@ -0,0 +1,8 @@1
+#ifndef KILN_EXIT_BUILTIN_H2
+#define KILN_EXIT_BUILTIN_H3
+4
+// exit(code): ends the process with that exit status (self-parses its argument,5
+// starting right after the already-consumed '(').6
+void codegen_builtin_exit(void);7
+8
+#endifkiln/src/codegen/H/builtins/higher_order.hmodified
+4−0@@ -16,4 +16,8 @@ void codegen_builtin_filter(void);1616
// reduce(array, fn, initial): pushes fn(...fn(fn(initial, e0), e1)..., eN).1717
void codegen_builtin_reduce(void);1818
19
+// sort(array) / sort(array, cmp): in-place stable sort, pushes the array.20
+// cmp(a, b) returns a number; > 0 means a goes after b.21
+void codegen_builtin_sort(void);22
+1923
#endifkiln/src/codegen/H/collections/arrays.hmodified
+8−0@@ -28,4 +28,12 @@ void codegen_builtin_push(void);2828
// len(array): pushes the element count as a NUMBER.2929
void codegen_builtin_len(void);3030
31
+// pop/insert/slice and delete(array, i): see C/collections/array_mutate.c.32
+// Arguments already pushed left-to-right, like push()/len().33
+void codegen_builtin_pop(void);34
+void codegen_builtin_insert(void);35
+void codegen_builtin_slice(void);36
+// delete() on an array: RDI = index payload, RSI = array object; pushes the array.37
+void codegen_array_delete_index(void);38
+3139
#endifkiln/src/codegen/H/emit/emit.hmodified
+3−0@@ -99,6 +99,9 @@ void emit_jcc_back(CodeBuf *buf, Cond cc, int target_offset);9999
// in the source, same single-pass limitation ashvm's compiler has -- so100100
// the target's code offset is always already known, no patching needed).101101
void emit_call_back(CodeBuf *buf, int target_offset);102
+// Forward call: emits `call rel32` with a placeholder and returns its patch103
+// offset (rel32 = target - (patch_offset + 4)).104
+int emit_call_rel32(CodeBuf *buf);102105
void emit_ret(CodeBuf *buf);103106
104107
// CALL r/m64 (opcode FF /2) -- indirect call through a register holdingkiln/src/codegen/H/emit/layout.hmodified
+1−0@@ -10,6 +10,7 @@1010
// elf/H/elf_writer.h vs pe/H/pe_writer.h), those hardcodes would each need1111
// their own target check -- so instead every call site goes through one1212
// of these, and the target check lives here exactly once.13
+uint64_t kiln_modstate_addr(void); // start of the module-state slot table (16 bytes per slot)1314
uint64_t kiln_code_base(void); // add a code offset to get an absolute address1415
uint64_t kiln_heap_ptr_addr(void);1516
uint64_t kiln_try_depth_addr(void);kiln/src/codegen/H/emit/runtime_layout.hmodified
+8−0@@ -11,4 +11,12 @@1111
#define KILN_TRY_HANDLERS_SIZE (MAX_KILN_TRY_DEPTH * KILN_TRY_HANDLER_SIZE)1212
#define KILN_TRY_DEPTH_SIZE 81313
14
+15
+// Module-level state: top-level `local`s of an imported file whose value is16
+// computed at import time live in a fixed table of (tag, payload) slots in the17
+// globals region (see parser/C/declarations/module_state.c), because a called18
+// function's own frame can't reach the importing frame.19
+#define KILN_MODSTATE_SLOTS 12820
+#define KILN_MODSTATE_SIZE (16 * KILN_MODSTATE_SLOTS)21
+1422
#endifkiln/src/codegen/H/emit/value.hmodified
+7−6@@ -31,14 +31,15 @@3131
// M5's `==`/`!=`, see codegen/C/expressions/expr_bool.c -- nil==nil compares equal3232
// tags then falls into the numeric path, 0.0==0.0; nil compared against3333
// any other tag is caught by the tags-differ branch before ever34
-// touching the payload). `true`/`false` are NOT a separate tag --35
-// they're just named literals for TAG_NUMBER 1.0/0.0 (missing.md's36
-// "boolean type" note is intentionally only half-addressed this way:37
-// the whole codebase already treats "truthy" as "NUMBER != 0", so a38
-// distinct boolean tag would mean touching that logic everywhere39
-// instead of adding one clean new case).34
+// touching the payload).4035
#define TAG_NIL 54136
37
+// A real boolean: `yes` / `no`, and what comparisons, `!`, `and`/`or`, has() and38
+// the *_exists / contains-style builtins produce. The payload is 1.0 / 0.0, so39
+// truthiness and arithmetic (which only look at the payload) keep working;40
+// `yes == 1` is false because the tags differ. Prints as yes / no.41
+#define TAG_BOOL 642
+4243
// An array's payload is the address of a small, fixed-size, STABLE4344
// "array object" (see codegen/H/strings/strings.h's comment on why absolute4445
// addresses are safe: kiln is always a fixed, non-PIE executable):kiln/src/codegen/H/expressions/expr.hmodified
+4−0@@ -33,4 +33,8 @@ void codegen_call_named(const char *name, int len);3333
// the exact same logic instead of a second, drifting copy of it.3434
void codegen_apply_plus(void);3535
36
+// Pops a value and sets flags for a condition: COND_E afterward means falsy.37
+// Errors on strings, arrays, maps and functions.38
+void codegen_pop_and_test_truthy(void);39
+3640
#endifkiln/src/codegen/H/io/file_builtins.hmodified
+7−0@@ -9,4 +9,11 @@ void codegen_builtin_write_file(void);99
void codegen_builtin_append_file(void);1010
void codegen_builtin_file_exists(void);1111
12
+// rename_file(from, to) / delete_file(path) / make_dir(path): 1 on success, 0 on failure.13
+// list_dir(path): array of entry names. Implemented in C/io/fs_builtins.c.14
+void codegen_builtin_rename_file(void);15
+void codegen_builtin_delete_file(void);16
+void codegen_builtin_make_dir(void);17
+void codegen_builtin_list_dir(void);18
+1219
#endifkiln/src/codegen/H/platform/platform_file.hmodified
+6−0@@ -21,4 +21,10 @@ void platform_emit_write_bytes(CodeBuf *code); // in: RDI=fd/handle, RSI=buf2121
// limit already documented elsewhere for file I/O).2222
void platform_emit_file_size(CodeBuf *code); // in: RDI=fd/handle -> out: RAX=size2323
24
+// Filesystem operations. All three return RAX = 0 on success, nonzero on failure,25
+// on both targets.26
+void platform_emit_rename(CodeBuf *code); // in: RDI=nullterm from, RSI=nullterm to27
+void platform_emit_delete(CodeBuf *code); // in: RDI=nullterm path28
+void platform_emit_mkdir(CodeBuf *code); // in: RDI=nullterm path29
+2430
#endifkiln/src/codegen/H/runtime/errors.hmodified
+4−4@@ -31,10 +31,10 @@ void errors_emit_die(CodeBuf *code, const char *msg);3131
// the adjacent code segment).3232
void errors_emit_fatal(CodeBuf *code, const char *msg);3333
34
-// Like errors_emit_die, but for a RUNTIME string value (parser.c's35
-// `raise expr;`) instead of a compile-time-constant C string -- the36
-// caller must already have RSI=message payload and RDX=message length37
-// loaded (a raise expression's already-popped (tag,payload), payload is34
+// Like errors_emit_die, but for a RUNTIME value (parser.c's `raise expr;`,35
+// any type) instead of a compile-time-constant C string -- the caller must36
+// already have RBX=tag and RSI=payload loaded, plus RDX=length when the tag is37
+// STRING (a raise expression's already-popped (tag,payload), payload is3838
// already a valid length-prefixed TAG_STRING pointer, so there's nothing3939
// to embed, just the same jump to the raise routine errors_emit_die uses.4040
void errors_emit_die_dynamic(CodeBuf *code);kiln/src/codegen/H/runtime/number_format.hadded
+21−0@@ -0,0 +1,21 @@1
+#ifndef KILN_NUMBER_FORMAT_H2
+#define KILN_NUMBER_FORMAT_H3
+#include "codegen/H/emit/emit.h"4
+5
+// Ash's one number-to-text format (ashvm's vm_format_number matches it):6
+// - NaN / inf / -inf -> "nan" / "inf" / "-inf"7
+// - |x| < 1e15 and |x| >= 1e-6 (or 0): plain decimal, at most 6 fraction8
+// digits (rounded), trailing zeros dropped, so 3.0 -> "3", 0.5 -> "0.5"9
+// - otherwise scientific: mantissa as above in [1,10), then 'e', a sign and10
+// at least two exponent digits ("1e+21", "1.5e-07")11
+//12
+// In: RAX = the double's raw bits.13
+// Out: RCX = first byte, RDX = one past the last byte of the text, which lives14
+// in a 64-byte scratch region on the stack. RSP has been lowered by15
+// NUMBER_FORMAT_SCRATCH; the caller consumes the text, then does16
+// `add rsp, NUMBER_FORMAT_SCRATCH`.17
+// Clobbers RAX, RBX, RCX, RDX, RSI, RDI, XMM0-XMM3.18
+#define NUMBER_FORMAT_SCRATCH 6419
+void number_format_emit(CodeBuf *code);20
+21
+#endifkiln/src/codegen/H/strings/string_builtins.hmodified
+11−0@@ -7,6 +7,10 @@ void codegen_builtin_upper(void);77
void codegen_builtin_lower(void);88
void codegen_builtin_trim(void);99
10
+// chr(n) / ord(s): byte <-> one-byte string (argument already pushed).11
+void codegen_builtin_chr(void);12
+void codegen_builtin_ord(void);13
+1014
// substring/indexOf/split/join/replace self-parse their (multiple)1115
// arguments starting right after the already-consumed '(', same1216
// convention as codegen/H/builtins/higher_order.h's map/filter/reduce.@@ -16,4 +20,11 @@ void codegen_builtin_split(void);1620
void codegen_builtin_join(void);1721
void codegen_builtin_replace(void);1822
23
+// contains/starts_with/ends_with (-> 1/0) and repeat(s, n); self-parse like24
+// indexOf. Implemented in C/strings/affix_builtins.c.25
+void codegen_builtin_contains(void);26
+void codegen_builtin_starts_with(void);27
+void codegen_builtin_ends_with(void);28
+void codegen_builtin_repeat(void);29
+1930
#endifkiln/src/codegen/H/strings/strings.hmodified
+6−0@@ -28,6 +28,12 @@ void codegen_string_concat(void);2828
// byte-different (invert=1, i.e. `!=`).2929
void codegen_string_compare(int invert);3030
31
+// Lexicographic (bytewise) order of two strings, a_payload=RAX, b_payload=RCX.32
+// Pushes nothing: leaves the flags as if a cmp of a against b had run, so the33
+// caller can branch with the same unsigned conditions ucomisd uses (COND_B/BE/34
+// A/AE). Clobbers RAX, RCX, RBX, RDX, RSI, RDI.35
+void codegen_string_order(void);36
+3137
// Assumes index already pushed (top) and string already pushed (below3238
// it), both (tag, payload) pairs -- postfix()'s `s[i]` read, the3339
// TAG_STRING case (previously missing entirely -- see missing.md #1:kiln/src/elf/H/elf_dynamic.hmodified
+2−1@@ -61,7 +61,8 @@6161
#define KILN_DYN_RELA_SIZE (KILN_DYN_RELAENT_SIZE * (unsigned)RUNTIME_IMPORT_COUNT_)6262
6363
#define KILN_DYN_GLOBALS_ADDR (KILN_DYN_RELA_ADDR + KILN_DYN_RELA_SIZE)64
-#define KILN_DYN_GLOBALS_SIZE (8 + KILN_TRY_DEPTH_SIZE + KILN_TRY_HANDLERS_SIZE + KILN_ARGV_SIZE)64
+#define KILN_DYN_GLOBALS_SIZE (8 + KILN_TRY_DEPTH_SIZE + KILN_TRY_HANDLERS_SIZE + KILN_ARGV_SIZE + KILN_MODSTATE_SIZE)65
+#define KILN_DYN_MODSTATE_ADDR (KILN_DYN_GLOBALS_ADDR + 8 + KILN_TRY_DEPTH_SIZE + KILN_TRY_HANDLERS_SIZE + KILN_ARGV_SIZE)6566
#define KILN_DYN_CODE_START_OFFSET (KILN_DYN_GLOBALS_ADDR + KILN_DYN_GLOBALS_SIZE - KILN_LOAD_BASE)6667
6768
// Absolute VA of `which`'s GOT slot -- loaded via a compile-time-knownkiln/src/elf/H/elf_writer.hmodified
+2−1@@ -34,11 +34,12 @@3434
// inside it (see codegen/C/io/argv_builtin.c).3535
#define KILN_ARGV_SIZE 83636
37
-#define KILN_GLOBALS_SIZE (8 + KILN_TRY_DEPTH_SIZE + KILN_TRY_HANDLERS_SIZE + KILN_ARGV_SIZE) // heap_ptr, try_depth, handler stack, argv37
+#define KILN_GLOBALS_SIZE (8 + KILN_TRY_DEPTH_SIZE + KILN_TRY_HANDLERS_SIZE + KILN_ARGV_SIZE + KILN_MODSTATE_SIZE) // heap_ptr, try_depth, handler stack, argv, module state3838
#define KILN_GLOBALS_ADDR (KILN_LOAD_BASE + KILN_EHDR_SIZE + KILN_PHDR_SIZE)3939
#define KILN_TRY_DEPTH_ADDR (KILN_GLOBALS_ADDR + 8)4040
#define KILN_TRY_HANDLERS_ADDR (KILN_TRY_DEPTH_ADDR + KILN_TRY_DEPTH_SIZE)4141
#define KILN_ARGV_ADDR (KILN_TRY_HANDLERS_ADDR + KILN_TRY_HANDLERS_SIZE)42
+#define KILN_MODSTATE_ADDR (KILN_ARGV_ADDR + KILN_ARGV_SIZE)4243
#define KILN_CODE_START_OFFSET (KILN_EHDR_SIZE + KILN_PHDR_SIZE + KILN_GLOBALS_SIZE)4344
4445
// Writes `machine_code` as a minimal, standalone, directly-executablekiln/src/parser/C/core/parser.cmodified
+67−59@@ -4,6 +4,7 @@44
#include "parser/H/core/parser.h"55
#include "parser/H/core/parser_internal.h"66
#include "parser/H/declarations/vars.h"7
+#include "parser/H/declarations/module_state.h"78
#include "parser/H/statements/loop_stack.h"89
#include "parser/H/declarations/functions.h"910
#include "parser/H/imports/imports.h"@@ -77,26 +78,14 @@ static void local_statement(void) {7778
// that file's own functions -- the sibling guard further down in7879
// statement() already gets this right, this one didn't.7980
if (ns_len > 0 && block_depth == 0) {80
- char combined[96];81
- int combined_len = snprintf(combined, sizeof(combined), "%.*s.%.*s", ns_len, ns, len, name);82
- if (current.type == TOKEN_NUMBER) {83
- char clean[128];84
- int clean_len = 0;85
- for (int i = 0; i < current.length; i++) {86
- if (current.start[i] == '_') continue;87
- clean[clean_len++] = current.start[i];88
- }89
- clean[clean_len] = '\0';90
- double v = strtod(clean, NULL);91
- advance_token();92
- declare_number_constant(combined, combined_len, v);93
- } else if (current.type == TOKEN_STRING) {94
- declare_string_constant(combined, combined_len, current.start, current.length);95
- advance_token();96
- } else {97
- parse_error("imported top-level 'local' must be a literal number or string constant");98
- }81
+ // A top-level `local` of an imported file is a module-level variable: the82
+ // expression runs once, at the point of the `@import`, and the value lives83
+ // in a global slot every function of that file (and `ns.NAME`) can read,84
+ // and `NAME = ...;` can update (see parser/H/declarations/module_state.h).85
+ int state_slot = module_state_declare(name, len);86
+ codegen_expression();9987
expect(TOKEN_SEMICOLON, "expected ';' after statement");88
+ module_state_emit_store(state_slot);10089
return;10190
}10291
@@ -111,20 +100,46 @@ static void local_statement(void) {111100
emit_store_mem_disp32(code, REG_RBP, var_slot_payload_offset(slot), REG_RAX);112101
}113102
103
+// A variable that statements can read and write: either a frame slot of the104
+// current function, or (inside an imported file) a module-level variable.105
+typedef struct { int global; int index; } VarRef;106
+107
+static VarRef resolve_ref_or_error(Token id) {108
+ int slot = resolve_var(id.start, id.length);109
+ if (slot != -1) return (VarRef){ 0, slot };110
+ int state = module_state_resolve(id.start, id.length);111
+ if (state != -1) return (VarRef){ 1, state };112
+ char msg[128];113
+ snprintf(msg, sizeof(msg), "undefined variable: %.*s", id.length, id.start);114
+ parse_error(msg);115
+ return (VarRef){ 0, -1 };116
+}117
+118
+static void ref_push(VarRef r) {119
+ if (r.global) { module_state_emit_load(r.index); return; }120
+ emit_load_mem_disp32(code, REG_RBX, REG_RBP, var_slot_tag_offset(r.index));121
+ emit_push_reg(code, REG_RBX);122
+ emit_load_mem_disp32(code, REG_RAX, REG_RBP, var_slot_payload_offset(r.index));123
+ emit_push_reg(code, REG_RAX);124
+}125
+126
+// Stores RBX (tag) / RAX (payload) into the variable.127
+static void ref_store_regs(VarRef r) {128
+ if (r.global) {129
+ emit_push_reg(code, REG_RBX);130
+ emit_push_reg(code, REG_RAX);131
+ module_state_emit_store(r.index);132
+ return;133
+ }134
+ emit_store_mem_disp32(code, REG_RBP, var_slot_tag_offset(r.index), REG_RBX);135
+ emit_store_mem_disp32(code, REG_RBP, var_slot_payload_offset(r.index), REG_RAX);136
+}137
+114138
// IDENT[index] = expr ; -- writes through an existing array OR map115139
// variable (which one is only known at runtime -- see expr.c's postfix()116140
// for the read-side version of this same dispatch).117141
static void index_assignment_statement(Token id) {118
- int slot = resolve_var(id.start, id.length);119
- if (slot == -1) {120
- char msg[128];121
- snprintf(msg, sizeof(msg), "undefined variable: %.*s", id.length, id.start);122
- parse_error(msg);123
- }124
- emit_load_mem_disp32(code, REG_RBX, REG_RBP, var_slot_tag_offset(slot));125
- emit_push_reg(code, REG_RBX);126
- emit_load_mem_disp32(code, REG_RAX, REG_RBP, var_slot_payload_offset(slot));127
- emit_push_reg(code, REG_RAX);142
+ ref_push(resolve_ref_or_error(id));128143
129144
advance_token(); // consume '['130145
codegen_expression();@@ -171,28 +186,15 @@ static void call_statement(Token id) {171186
emit_pop_reg(code, REG_RBX);172187
}173188
174
-static int resolve_var_or_error(Token id) {175
- int slot = resolve_var(id.start, id.length);176
- if (slot == -1) {177
- char msg[128];178
- snprintf(msg, sizeof(msg), "undefined variable: %.*s", id.length, id.start);179
- parse_error(msg);180
- }181
- return slot;182
-}183
-184189
// x += e ; / x -= e ; / x *= e ; / x /= e ; -- sugar for x = x <op> e.185190
// `+=` reuses codegen_apply_plus() (the exact same tag-aware string-concat-186191
// or-numeric-add logic `+` itself uses); `-=`/`*=`/`/=` are NUMBER-only,187192
// same scope limit as `-`/`*`/`/` themselves.188193
static void compound_assignment_statement(Token id, TokenType op) {189
- int slot = resolve_var_or_error(id);194
+ VarRef ref = resolve_ref_or_error(id);190195
advance_token(); // consume the += / -= / *= / /=191196
192
- emit_load_mem_disp32(code, REG_RBX, REG_RBP, var_slot_tag_offset(slot));193
- emit_push_reg(code, REG_RBX);194
- emit_load_mem_disp32(code, REG_RAX, REG_RBP, var_slot_payload_offset(slot));195
- emit_push_reg(code, REG_RAX);197
+ ref_push(ref);196198
197199
codegen_expression();198200
expect(TOKEN_SEMICOLON, "expected ';' after assignment");@@ -218,19 +220,20 @@ static void compound_assignment_statement(Token id, TokenType op) {218220
219221
emit_pop_reg(code, REG_RAX); // result payload220222
emit_pop_reg(code, REG_RBX); // result tag221
- emit_store_mem_disp32(code, REG_RBP, var_slot_tag_offset(slot), REG_RBX);222
- emit_store_mem_disp32(code, REG_RBP, var_slot_payload_offset(slot), REG_RAX);223
+ ref_store_regs(ref);223224
}224225
225226
// x++ ; / x-- ; -- sugar for x = x + 1 / x = x - 1. Statement-only (not226227
// a pre/post-increment EXPRESSION with a value of its own -- that needs227228
// primary()/postfix() changes this project's scope doesn't call for).228229
static void incdec_statement(Token id, TokenType op) {229
- int slot = resolve_var_or_error(id);230
+ VarRef ref = resolve_ref_or_error(id);230231
advance_token(); // consume ++ or --231232
expect(TOKEN_SEMICOLON, "expected ';' after statement");232233
233
- emit_load_mem_disp32(code, REG_RAX, REG_RBP, var_slot_payload_offset(slot));234
+ ref_push(ref);235
+ emit_pop_reg(code, REG_RAX); // payload236
+ emit_pop_reg(code, REG_RBX); // tag (ignored)234237
emit_movq_xmm_from_reg(code, XMM0, REG_RAX);235238
double one = 1.0;236239
uint64_t one_bits;@@ -241,8 +244,7 @@ static void incdec_statement(Token id, TokenType op) {241244
else emit_subsd(code, XMM0, XMM1);242245
emit_movq_reg_from_xmm(code, REG_RAX, XMM0);243246
emit_mov_reg_imm64(code, REG_RBX, TAG_NUMBER);244
- emit_store_mem_disp32(code, REG_RBP, var_slot_tag_offset(slot), REG_RBX);245
- emit_store_mem_disp32(code, REG_RBP, var_slot_payload_offset(slot), REG_RAX);247
+ ref_store_regs(ref);246248
}247249
248250
// IDENT = expr ; -- `name` must already be declared via `let`.@@ -284,11 +286,10 @@ static void assignment_statement(void) {284286
codegen_expression();285287
expect(TOKEN_SEMICOLON, "expected ';' after assignment");286288
287
- int slot = resolve_var_or_error(id);289
+ VarRef ref = resolve_ref_or_error(id);288290
emit_pop_reg(code, REG_RAX); // payload289291
emit_pop_reg(code, REG_RBX); // tag290
- emit_store_mem_disp32(code, REG_RBP, var_slot_tag_offset(slot), REG_RBX);291
- emit_store_mem_disp32(code, REG_RBP, var_slot_payload_offset(slot), REG_RAX);292
+ ref_store_regs(ref);292293
}293294
294295
// yield [expr] ; -- bare `yield;` returns 0, matching ashc/ashvm.@@ -306,14 +307,14 @@ static void yield_statement(void) {306307
emit_mov_reg_imm64(code, REG_RAX, 0); // 0.0's bits are all zero307308
emit_mov_reg_imm64(code, REG_RBX, TAG_NUMBER);308309
}310
+ emit_attempt_unwind_all(); // leave open attempts; only touches RCX/RDX, so the result in RAX/RBX survives309311
emit_mov_reg_reg(code, REG_RSP, REG_RBP);310312
emit_pop_reg(code, REG_RBP);311313
emit_ret(code);312314
}313315
314
-// raise expr ; -- expr must be a STRING (matches attempt/handle's own315
-// convention: every caught error, built-in or user-raised, is a plain316
-// string). Reuses codegen/C/runtime/errors.c's raise routine directly --316
+// raise expr ; -- expr may be any value: a string (what every built-in317
+// runtime error raises) or, say, a map for structured errors. Reuses codegen/C/runtime/errors.c's raise routine directly --317318
// the exact same handle/unwind mechanism M9's built-in errors (array318319
// bounds, missing map key) already go through, just with a runtime319320
// message instead of a compile-time-constant one. (The runtime "raise@@ -324,9 +325,12 @@ static void raise_statement(void) {324325
codegen_expression();325326
expect(TOKEN_SEMICOLON, "expected ';' after 'raise'");326327
327
- emit_pop_reg(code, REG_RSI); // message payload (assumed STRING)328
- emit_pop_reg(code, REG_RBX); // message tag (ignored)329
- emit_load_mem_disp32(code, REG_RDX, REG_RSI, -8); // length328
+ emit_pop_reg(code, REG_RSI); // raised payload329
+ emit_pop_reg(code, REG_RBX); // raised tag330
+ emit_cmp_reg_imm32(code, REG_RBX, TAG_STRING);331
+ int not_text = emit_jcc_rel32(code, COND_NE);332
+ emit_load_mem_disp32(code, REG_RDX, REG_RSI, -8); // length (strings only)333
+ emit_patch_jump(code, not_text);330334
errors_emit_die_dynamic(code);331335
}332336
@@ -337,6 +341,7 @@ static void raise_statement(void) {337341
static void stop_statement(void) {338342
advance_token();339343
expect(TOKEN_SEMICOLON, "expected ';' after 'stop'");344
+ emit_attempt_unwind_to_loop();340345
loop_record_stop(emit_jmp_rel32(code));341346
}342347
@@ -347,6 +352,7 @@ static void stop_statement(void) {347352
static void next_statement(void) {348353
advance_token();349354
expect(TOKEN_SEMICOLON, "expected ';' after 'next'");355
+ emit_attempt_unwind_to_loop();350356
loop_record_next(emit_jmp_rel32(code));351357
}352358
@@ -394,6 +400,7 @@ void statement(void) {394400
void compile_program(const char *source, const char *source_path, CodeBuf *out) {395401
code = out;396402
code_init(code);403
+ prescan_functions(source);397404
lexer_init(source);398405
advance_token();399406
imports_init(source_path);@@ -429,5 +436,6 @@ void compile_program(const char *source, const char *source_path, CodeBuf *out)429436
errors_emit_startup(code);430437
431438
while (current.type != TOKEN_EOF) statement();439
+ check_all_functions_defined();432440
codegen_exit0(code);433441
}kiln/src/parser/C/declarations/functions.cmodified
+100−2@@ -2,6 +2,9 @@22
#include <stdio.h>33
#include "parser/H/declarations/functions.h"44
#include "parser/H/core/parser.h"5
+#include "lexer/H/lexer.h"6
+#include "codegen/H/emit/emit.h"7
+#include "codegen/H/emit/layout.h"58
69
static KilnFunction functions[MAX_KILN_FUNCS];710
static int function_count = 0;@@ -25,6 +28,7 @@ KilnFunction *declare_function(const char *name, int len) {2528
fn->name[len] = '\0';2629
fn->code_offset = 0;2730
fn->arity = 0;31
+ fn->defined = 1;2832
return fn;2933
}3034
@@ -49,12 +53,106 @@ static int build_namespaced_name(char *buf, int buf_size, const char *name, int4953
return g_import_ns_len + 1 + len;5054
}5155
56
+// A prescan (prescan_functions below) may already have reserved the name so57
+// earlier code could call it; the real declaration then fills that entry in.58
+static KilnFunction *declare_or_define(const char *name, int len) {59
+ KilnFunction *existing = resolve_function(name, len);60
+ if (existing != NULL && !existing->defined) {61
+ existing->defined = 1;62
+ return existing;63
+ }64
+ return declare_function(name, len);65
+}66
+5267
KilnFunction *declare_function_in_context(const char *name, int len) {53
- if (g_import_ns_len == 0) return declare_function(name, len);68
+ if (g_import_ns_len == 0) return declare_or_define(name, len);5469
char buf[128];5570
int combined_len = build_namespaced_name(buf, sizeof(buf), name, len);5671
if (combined_len < 0) parse_error("function name too long (including namespace prefix)");57
- return declare_function(buf, combined_len);72
+ return declare_or_define(buf, combined_len);73
+}74
+75
+void predeclare_function_in_context(const char *name, int len, int arity) {76
+ const char *final_name = name;77
+ int final_len = len;78
+ char buf[128];79
+ if (g_import_ns_len > 0) {80
+ final_len = build_namespaced_name(buf, sizeof(buf), name, len);81
+ if (final_len < 0) return; // the real declaration reports it82
+ final_name = buf;83
+ }84
+ if (resolve_function(final_name, final_len) != NULL) return;85
+ if (function_count >= MAX_KILN_FUNCS || final_len >= (int)sizeof(functions[0].name)) return;86
+ KilnFunction *fn = &functions[function_count++];87
+ memcpy(fn->name, final_name, (size_t)final_len);88
+ fn->name[final_len] = '\0';89
+ fn->code_offset = 0;90
+ fn->arity = arity;91
+ fn->defined = 0;92
+}93
+94
+void prescan_functions(const char *source) {95
+ LexerState saved = lexer_save_state();96
+ lexer_init(source);97
+ Token t = lexer_next_token();98
+ while (t.type != TOKEN_EOF) {99
+ if (t.type == TOKEN_FORGE) {100
+ Token name = lexer_next_token();101
+ if (name.type != TOKEN_IDENTIFIER) { t = name; continue; }102
+ Token paren = lexer_next_token();103
+ if (paren.type != TOKEN_LPAREN) { t = paren; continue; }104
+ int arity = 0;105
+ t = lexer_next_token();106
+ if (t.type != TOKEN_RPAREN) {107
+ arity = 1;108
+ while (t.type != TOKEN_RPAREN && t.type != TOKEN_EOF) {109
+ if (t.type == TOKEN_COMMA) arity++;110
+ t = lexer_next_token();111
+ }112
+ }113
+ predeclare_function_in_context(name.start, name.length, arity);114
+ }115
+ if (t.type != TOKEN_EOF) t = lexer_next_token();116
+ }117
+ lexer_restore_state(saved);118
+}119
+120
+#define MAX_KILN_FIXUPS 4096121
+static struct { KilnFunction *fn; int patch_offset; int absolute; } fixups[MAX_KILN_FIXUPS];122
+static int fixup_count = 0;123
+124
+static void add_fixup(KilnFunction *fn, int patch_offset, int absolute) {125
+ if (fixup_count >= MAX_KILN_FIXUPS) parse_error("too many calls to functions defined later");126
+ fixups[fixup_count].fn = fn;127
+ fixups[fixup_count].patch_offset = patch_offset;128
+ fixups[fixup_count].absolute = absolute;129
+ fixup_count++;130
+}131
+void function_add_call_fixup(KilnFunction *fn, int patch_offset) { add_fixup(fn, patch_offset, 0); }132
+void function_add_abs_fixup(KilnFunction *fn, int patch_offset) { add_fixup(fn, patch_offset, 1); }133
+134
+void function_resolve_fixups(KilnFunction *fn) {135
+ int kept = 0;136
+ for (int i = 0; i < fixup_count; i++) {137
+ if (fixups[i].fn != fn) { fixups[kept++] = fixups[i]; continue; }138
+ if (fixups[i].absolute) {139
+ emit_patch_imm64(code, fixups[i].patch_offset, kiln_code_base() + (uint64_t)fn->code_offset);140
+ } else {141
+ int32_t rel = fn->code_offset - (fixups[i].patch_offset + 4);142
+ memcpy(code->code + fixups[i].patch_offset, &rel, 4);143
+ }144
+ }145
+ fixup_count = kept;146
+}147
+148
+void check_all_functions_defined(void) {149
+ for (int i = 0; i < function_count; i++) {150
+ if (!functions[i].defined) {151
+ char msg[128];152
+ snprintf(msg, sizeof(msg), "undefined function: %s", functions[i].name);153
+ parse_error(msg);154
+ }155
+ }58156
}59157
60158
KilnFunction *resolve_function_in_context(const char *name, int len) {kiln/src/parser/C/declarations/module_state.cadded
+58−0@@ -0,0 +1,58 @@1
+#include <stdio.h>2
+#include <string.h>3
+#include "parser/H/declarations/module_state.h"4
+#include "parser/H/declarations/functions.h"5
+#include "parser/H/declarations/constants.h"6
+#include "parser/H/core/parser.h"7
+#include "codegen/H/emit/emit.h"8
+#include "codegen/H/emit/layout.h"9
+#include "codegen/H/emit/runtime_layout.h"10
+11
+static char names[KILN_MODSTATE_SLOTS][96];12
+static int count = 0;13
+14
+int module_state_resolve_qualified(const char *name, int len) {15
+ for (int i = 0; i < count; i++) {16
+ if ((int)strlen(names[i]) == len && strncmp(names[i], name, (size_t)len) == 0) return i;17
+ }18
+ return -1;19
+}20
+21
+int module_state_resolve(const char *name, int len) {22
+ const char *ns; int ns_len;23
+ get_import_namespace(&ns, &ns_len);24
+ if (ns_len == 0) return -1;25
+ char buf[128];26
+ int n = snprintf(buf, sizeof(buf), "%.*s.%.*s", ns_len, ns, len, name);27
+ if (n < 0 || n >= (int)sizeof(buf)) return -1;28
+ return module_state_resolve_qualified(buf, n);29
+}30
+31
+int module_state_declare(const char *name, int len) {32
+ const char *ns; int ns_len;33
+ get_import_namespace(&ns, &ns_len);34
+ char buf[128];35
+ int n = snprintf(buf, sizeof(buf), "%.*s.%.*s", ns_len, ns, len, name);36
+ if (n < 0 || n >= (int)sizeof(names[0])) parse_error("module-level name too long (including namespace prefix)");37
+ if (module_state_resolve_qualified(buf, n) != -1 || resolve_constant(buf, n) != NULL) parse_error("module-level name already declared");38
+ if (count >= KILN_MODSTATE_SLOTS) parse_error("too many module-level variables");39
+ memcpy(names[count], buf, (size_t)n);40
+ names[count][n] = '\0';41
+ return count++;42
+}43
+44
+void module_state_emit_load(int index) {45
+ emit_mov_reg_imm64(code, REG_RSI, kiln_modstate_addr() + 16u * (unsigned)index);46
+ emit_load_mem_disp32(code, REG_RBX, REG_RSI, 0);47
+ emit_push_reg(code, REG_RBX);48
+ emit_load_mem_disp32(code, REG_RAX, REG_RSI, 8);49
+ emit_push_reg(code, REG_RAX);50
+}51
+52
+void module_state_emit_store(int index) {53
+ emit_pop_reg(code, REG_RAX); // payload54
+ emit_pop_reg(code, REG_RBX); // tag55
+ emit_mov_reg_imm64(code, REG_RSI, kiln_modstate_addr() + 16u * (unsigned)index);56
+ emit_store_mem_disp32(code, REG_RSI, 0, REG_RBX);57
+ emit_store_mem_disp32(code, REG_RSI, 8, REG_RAX);58
+}kiln/src/parser/C/imports/imports.cmodified
+15−0@@ -129,6 +129,19 @@ void import_statement(void) {129129
parse_error(message);130130
}131131
132
+ // Optional `as name`: the namespace this file's functions are called through,133
+ // instead of the one derived from its file name.134
+ char alias[64];135
+ int has_alias = 0;136
+ if (current.type == TOKEN_IDENTIFIER && current.length == 2 && strncmp(current.start, "as", 2) == 0) {137
+ advance_token();138
+ expect(TOKEN_IDENTIFIER, "expected a namespace name after 'as'");139
+ if (previous.length >= (int)sizeof(alias)) parse_error("namespace name too long");140
+ memcpy(alias, previous.start, (size_t)previous.length);141
+ alias[previous.length] = '\0';142
+ has_alias = 1;143
+ }144
+132145
expect(TOKEN_SEMICOLON, "expected ';' after import path");133146
134147
// Cycle check MUST come before the dedup check: a file already in@@ -146,6 +159,7 @@ void import_statement(void) {146159
147160
char ns[64];148161
import_derive_namespace(canonical, ns, sizeof(ns));162
+ if (has_alias) { memcpy(ns, alias, strlen(alias) + 1); }149163
150164
ImportedFile *collision = registry_find_by_namespace(ns);151165
if (collision != NULL) {@@ -198,6 +212,7 @@ void import_statement(void) {198212
import_path_dirname(canonical, current_file_dir, sizeof(current_file_dir));199213
set_import_namespace(entry->namespace_name, (int)strlen(entry->namespace_name));200214
215
+ prescan_functions(imported_source);201216
lexer_init(imported_source);202217
advance_token();203218
parse_imported_file_body();kiln/src/parser/C/statements/attempt_handle.cmodified
+11−4@@ -1,6 +1,7 @@11
#include "parser/H/core/parser.h"22
#include "parser/H/core/parser_internal.h"33
#include "parser/H/declarations/vars.h"4
+#include "parser/H/statements/loop_stack.h"45
#include "codegen/H/emit/emit.h"56
#include "codegen/H/runtime/errors.h"67
#include "codegen/H/emit/layout.h"@@ -23,6 +24,13 @@2324
// is actually seen. attempt/handle is inline (like given/during), not a2425
// new call frame, so this works the same way given/during's own bodies2526
// share the enclosing function's variable table.27
+void attempt_emit_handler_pop(void) {28
+ emit_mov_reg_imm64(code, REG_RDX, kiln_try_depth_addr());29
+ emit_load_mem_disp32(code, REG_RCX, REG_RDX, 0);30
+ emit_dec_reg(code, REG_RCX);31
+ emit_store_mem_disp32(code, REG_RDX, 0, REG_RCX);32
+}33
+2634
void attempt_statement(void) {2735
VarBlockScope saved_scope = vars_scope_begin();2836
advance_token(); // consume 'attempt'@@ -58,15 +66,14 @@ void attempt_statement(void) {5866
errors_emit_fatal(code, "runtime error: too many nested try blocks");5967
emit_patch_jump(code, skip_overflow_die);6068
69
+ attempt_enter();6170
block(); // compiles A71
+ attempt_leave();6272
6373
// Normal completion (no error was raised): pop the handler -- past6474
// this point, an error inside the enclosing scope should NOT be6575
// caught by this attempt anymore.66
- emit_mov_reg_imm64(code, REG_RAX, kiln_try_depth_addr());67
- emit_load_mem_disp32(code, REG_RCX, REG_RAX, 0);68
- emit_dec_reg(code, REG_RCX);69
- emit_store_mem_disp32(code, REG_RAX, 0, REG_RCX);76
+ attempt_emit_handler_pop();7077
7178
int skip_handle = emit_jmp_rel32(code);7279
int handle_code_offset = code->count;kiln/src/parser/C/statements/loop_stack.cmodified
+20−3@@ -9,15 +9,18 @@ typedef struct {99
int stop_count;1010
int next_jumps[MAX_LOOP_JUMPS];1111
int next_count;12
+ int attempt_base; // attempt_depth when the loop began1213
} LoopContext;1314
1415
static LoopContext loop_stack[MAX_LOOP_NESTING];1516
static int loop_depth = 0;17
+static int attempt_depth = 0; // attempt blocks currently open in this function body1618
1719
void loop_push(void) {1820
if (loop_depth >= MAX_LOOP_NESTING) parse_error("too many nested loops");1921
loop_stack[loop_depth].stop_count = 0;2022
loop_stack[loop_depth].next_count = 0;23
+ loop_stack[loop_depth].attempt_base = attempt_depth;2124
loop_depth++;2225
}2326
@@ -46,6 +49,20 @@ void loop_pop_and_patch_stops(void) {4649
loop_depth--;4750
}4851
49
-void loop_depth_save(int *out) { *out = loop_depth; }50
-void loop_depth_reset(void) { loop_depth = 0; }51
-void loop_depth_restore(int saved) { loop_depth = saved; }52
+void loop_scope_save(LoopScope *out) { out->loops = loop_depth; out->attempts = attempt_depth; }53
+void loop_scope_reset(void) { loop_depth = 0; attempt_depth = 0; }54
+void loop_scope_restore(LoopScope saved) { loop_depth = saved.loops; attempt_depth = saved.attempts; }55
+56
+void attempt_enter(void) { attempt_depth++; }57
+void attempt_leave(void) { attempt_depth--; }58
+59
+static void emit_handler_pops(int count) {60
+ for (int i = 0; i < count; i++) attempt_emit_handler_pop();61
+}62
+63
+void emit_attempt_unwind_all(void) { emit_handler_pops(attempt_depth); }64
+65
+// stop/next leave every attempt block opened since the innermost loop began.66
+void emit_attempt_unwind_to_loop(void) {67
+ if (loop_depth > 0) emit_handler_pops(attempt_depth - loop_stack[loop_depth - 1].attempt_base);68
+}kiln/src/parser/C/statements/parser_control.cmodified
+7−19@@ -9,19 +9,6 @@99
#include "codegen/H/emit/emit_sse.h"1010
#include "codegen/H/emit/value.h"1111
12
-// Truthiness test for given/during conditions: pop the value (tag13
-// discarded, assumed NUMBER -- matches ashvm's own truthy(), which14
-// requires a number too) and compare its payload against 0.0 via ucomisd15
-// (not GP cmp -- see emit_sse.h). COND_E afterward means "was zero, i.e.16
-// falsy".17
-static void pop_and_test_truthy(void) {18
- emit_pop_reg(code, REG_RAX); // payload19
- emit_pop_reg(code, REG_RBX); // tag (assumed NUMBER)20
- emit_movq_xmm_from_reg(code, XMM0, REG_RAX);21
- emit_pxor_xmm_xmm(code, XMM1); // XMM1 = +0.022
- emit_ucomisd(code, XMM0, XMM1);23
-}24
-2512
// Conditions may be parenthesized or not (`given (x < n) {` and2613
// `given x < n {` both work) -- unlike ashvm, there's no superinstruction2714
// fusion here to keep firing either way, so this is just a plain optional@@ -42,7 +29,7 @@ void given_statement(void) {4229
int parenthesized = consume_optional_lparen();4330
codegen_expression();4431
if (parenthesized) expect(TOKEN_RPAREN, "expected ')' after condition");45
- pop_and_test_truthy();32
+ codegen_pop_and_test_truthy();4633
int otherwise_jump = emit_jcc_rel32(code, COND_E);4734
4835
block();@@ -63,7 +50,7 @@ void during_statement(void) {6350
int parenthesized = consume_optional_lparen();6451
codegen_expression();6552
if (parenthesized) expect(TOKEN_RPAREN, "expected ')' after condition");66
- pop_and_test_truthy();53
+ codegen_pop_and_test_truthy();6754
int exit_jump = emit_jcc_rel32(code, COND_E);6855
6956
loop_push();@@ -127,16 +114,17 @@ void forge_statement(void) {127114
128115
int skip_jump = emit_jmp_rel32(code);129116
fn->code_offset = code->count;117
+ function_resolve_fixups(fn);130118
131119
VarScope outer_vars;132120
vars_save(&outer_vars);133121
vars_clear();134122
int outer_each_depth;135123
each_depth_save(&outer_each_depth);136124
each_depth_reset();137
- int outer_loop_depth;138
- loop_depth_save(&outer_loop_depth);139
- loop_depth_reset();125
+ LoopScope outer_loop_depth;126
+ loop_scope_save(&outer_loop_depth);127
+ loop_scope_reset();140128
141129
emit_push_reg(code, REG_RBP);142130
emit_mov_reg_reg(code, REG_RBP, REG_RSP);@@ -173,6 +161,6 @@ void forge_statement(void) {173161
174162
vars_restore(&outer_vars);175163
each_depth_restore(outer_each_depth);176
- loop_depth_restore(outer_loop_depth);164
+ loop_scope_restore(outer_loop_depth);177165
emit_patch_jump(code, skip_jump);178166
}kiln/src/parser/D/core/parser.dmodified
+2−0@@ -1,6 +1,7 @@11
src/parser/O/core/parser.o: src/parser/C/core/parser.c \22
src/parser/H/core/parser.h src/lexer/H/lexer.h src/codegen/H/emit/emit.h \33
src/parser/H/core/parser_internal.h src/parser/H/declarations/vars.h \4
+ src/parser/H/declarations/module_state.h \45
src/parser/H/statements/loop_stack.h \56
src/parser/H/declarations/functions.h src/parser/H/imports/imports.h \67
src/parser/H/declarations/constants.h src/codegen/H/expressions/expr.h \@@ -16,6 +17,7 @@ src/lexer/H/lexer.h:1617
src/codegen/H/emit/emit.h:1718
src/parser/H/core/parser_internal.h:1819
src/parser/H/declarations/vars.h:20
+src/parser/H/declarations/module_state.h:1921
src/parser/H/statements/loop_stack.h:2022
src/parser/H/declarations/functions.h:2123
src/parser/H/imports/imports.h:kiln/src/parser/D/declarations/functions.dmodified
+3−1@@ -1,8 +1,10 @@11
src/parser/O/declarations/functions.o: \22
src/parser/C/declarations/functions.c \33
src/parser/H/declarations/functions.h src/parser/H/core/parser.h \4
- src/lexer/H/lexer.h src/codegen/H/emit/emit.h4
+ src/lexer/H/lexer.h src/codegen/H/emit/emit.h \5
+ src/codegen/H/emit/layout.h56
src/parser/H/declarations/functions.h:67
src/parser/H/core/parser.h:78
src/lexer/H/lexer.h:89
src/codegen/H/emit/emit.h:10
+src/codegen/H/emit/layout.h:kiln/src/parser/D/declarations/module_state.dadded
+15−0@@ -0,0 +1,15 @@1
+src/parser/O/declarations/module_state.o: \2
+ src/parser/C/declarations/module_state.c \3
+ src/parser/H/declarations/module_state.h \4
+ src/parser/H/declarations/functions.h \5
+ src/parser/H/declarations/constants.h src/parser/H/core/parser.h \6
+ src/lexer/H/lexer.h src/codegen/H/emit/emit.h \7
+ src/codegen/H/emit/layout.h src/codegen/H/emit/runtime_layout.h8
+src/parser/H/declarations/module_state.h:9
+src/parser/H/declarations/functions.h:10
+src/parser/H/declarations/constants.h:11
+src/parser/H/core/parser.h:12
+src/lexer/H/lexer.h:13
+src/codegen/H/emit/emit.h:14
+src/codegen/H/emit/layout.h:15
+src/codegen/H/emit/runtime_layout.h:kiln/src/parser/D/statements/attempt_handle.dmodified
+3−2@@ -2,13 +2,14 @@ src/parser/O/statements/attempt_handle.o: \22
src/parser/C/statements/attempt_handle.c src/parser/H/core/parser.h \33
src/lexer/H/lexer.h src/codegen/H/emit/emit.h \44
src/parser/H/core/parser_internal.h src/parser/H/declarations/vars.h \5
- src/codegen/H/runtime/errors.h src/codegen/H/emit/layout.h \6
- src/codegen/H/emit/runtime_layout.h5
+ src/parser/H/statements/loop_stack.h src/codegen/H/runtime/errors.h \6
+ src/codegen/H/emit/layout.h src/codegen/H/emit/runtime_layout.h77
src/parser/H/core/parser.h:88
src/lexer/H/lexer.h:99
src/codegen/H/emit/emit.h:1010
src/parser/H/core/parser_internal.h:1111
src/parser/H/declarations/vars.h:12
+src/parser/H/statements/loop_stack.h:1213
src/codegen/H/runtime/errors.h:1314
src/codegen/H/emit/layout.h:1415
src/codegen/H/emit/runtime_layout.h:kiln/src/parser/H/declarations/functions.hmodified
+17−1@@ -13,15 +13,31 @@1313
// programs room to import a handful of libraries without hitting the cap1414
// immediately; still a hard ceiling, noted as a tuning knob in the design1515
// spec, not solved generally here.16
-#define MAX_KILN_FUNCS 6416
+#define MAX_KILN_FUNCS 10241717
#define MAX_KILN_PARAMS 81818
1919
typedef struct {2020
char name[64];2121
int code_offset;2222
int arity;23
+ int defined; // 0 while only forward-declared by the prescan; code_offset is valid once 12324
} KilnFunction;2425
26
+// --- Forward declarations ---27
+// Before a file is compiled, prescan_functions() reserves every named28
+// `forge name(...)` in it (with its arity), so calls may appear before the29
+// definition and functions may call each other. A call to a not-yet-defined30
+// function emits a placeholder that function_add_call_fixup /31
+// function_add_abs_fixup record; forge_statement resolves them the moment the32
+// body's start is known (function_resolve_fixups). check_all_functions_defined()33
+// reports a reserved name that never got defined.34
+void prescan_functions(const char *source);35
+void predeclare_function_in_context(const char *name, int len, int arity);36
+void function_add_call_fixup(KilnFunction *fn, int patch_offset); // rel32 of a `call`37
+void function_add_abs_fixup(KilnFunction *fn, int patch_offset); // imm64 holding the absolute address38
+void function_resolve_fixups(KilnFunction *fn);39
+void check_all_functions_defined(void);40
+2541
// Returns a pointer to the function's entry, or NULL if not declared.2642
KilnFunction *resolve_function(const char *name, int len);2743
kiln/src/parser/H/declarations/module_state.hadded
+25−0@@ -0,0 +1,25 @@1
+#ifndef KILN_MODULE_STATE_H2
+#define KILN_MODULE_STATE_H3
+4
+// Module-level state: a top-level `local NAME = <expression>;` in an imported5
+// file whose value is not a plain number/string literal. The expression runs6
+// once, where the `@import` appears (so before the importing file's own code),7
+// and the value lives in a numbered slot in the globals region instead of a8
+// frame, so every function of that file -- and `ns.NAME` from the importer --9
+// can read it, and `NAME = ...;`, `NAME += ...;`, `NAME[i] = ...;` update it.10
+// Slots are named under the file's namespace ("ns.NAME"), like imported11
+// functions and constants.12
+13
+// Reserves a slot for NAME in the current import namespace; returns its index.14
+int module_state_declare(const char *name, int len);15
+16
+// A bare NAME inside an imported file (resolved in that file's namespace), or17
+// a fully qualified "ns.NAME". Return the slot index or -1.18
+int module_state_resolve(const char *name, int len);19
+int module_state_resolve_qualified(const char *name, int len);20
+21
+// Push the slot's (tag, payload) / pop a (tag, payload) into it.22
+void module_state_emit_load(int index);23
+void module_state_emit_store(int index);24
+25
+#endifkiln/src/parser/H/statements/loop_stack.hmodified
+17−3@@ -40,8 +40,22 @@ void loop_pop_and_patch_stops(void);4040
// each_depth_save/reset/restore): a stop/next inside a function or4141
// lambda body must never reach past that body into an ENCLOSING loop it4242
// happens to be textually written inside.43
-void loop_depth_save(int *out);44
-void loop_depth_reset(void);45
-void loop_depth_restore(int saved);43
+typedef struct { int loops; int attempts; } LoopScope;44
+void loop_scope_save(LoopScope *out);45
+void loop_scope_reset(void);46
+void loop_scope_restore(LoopScope saved);47
+48
+// An attempt block pushes a runtime handler that only its own normal49
+// fallthrough pops, so any other way out of the block -- yield, stop, next --50
+// must pop it explicitly or a later raise jumps into the stale handler.51
+// attempt_enter/leave bracket the attempt block (not the handle block); the52
+// emit_* calls put one handler pop per open attempt right before the exit.53
+void attempt_enter(void);54
+void attempt_leave(void);55
+void emit_attempt_unwind_all(void); // yield: leave every open attempt56
+void emit_attempt_unwind_to_loop(void); // stop/next: leave those inside the loop57
+58
+// Emits the code that pops the innermost runtime handler (attempt_handle.c).59
+void attempt_emit_handler_pop(void);4660
4761
#endifkiln/src/pe/H/pe_imports.defmodified
+6−0@@ -14,3 +14,9 @@ PE_IMPORT(GET_FILE_SIZE, "GetFileSize")1414
PE_IMPORT(VIRTUAL_ALLOC, "VirtualAlloc")1515
PE_IMPORT(GET_COMMAND_LINE_A, "GetCommandLineA")1616
PE_IMPORT(GET_STD_HANDLE, "GetStdHandle")17
+PE_IMPORT(MOVE_FILE_A, "MoveFileA")18
+PE_IMPORT(DELETE_FILE_A, "DeleteFileA")19
+PE_IMPORT(CREATE_DIRECTORY_A, "CreateDirectoryA")20
+PE_IMPORT(FIND_FIRST_FILE_A, "FindFirstFileA")21
+PE_IMPORT(FIND_NEXT_FILE_A, "FindNextFileA")22
+PE_IMPORT(FIND_CLOSE, "FindClose")kiln/src/pe/H/pe_writer.hmodified
+2−1@@ -34,7 +34,8 @@3434
// No argv slot: GetCommandLineA() can be called at any point, unlike3535
// Linux's argv() which has to snapshot the kernel's original RSP before3636
// anything else touches it (see codegen/C/io/argv_builtin.c).37
-#define KILN_PE_GLOBALS_SIZE (8 + KILN_TRY_DEPTH_SIZE + KILN_TRY_HANDLERS_SIZE)37
+#define KILN_PE_MODSTATE_ADDR (KILN_PE_TRY_HANDLERS_ADDR + KILN_TRY_HANDLERS_SIZE)38
+#define KILN_PE_GLOBALS_SIZE (8 + KILN_TRY_DEPTH_SIZE + KILN_TRY_HANDLERS_SIZE + KILN_MODSTATE_SIZE)3839
3940
// Every kernel32.dll function kiln's Windows codegen calls -- single list4041
// in pe_imports.def, shared by this enum and pe_writer.c's actual tablekiln/tests/cases/basics/booleans.ashadded
+35−0@@ -0,0 +1,35 @@1
+say yes;2
+say no;3
+say 1 < 2;4
+say 2 < 1;5
+say not yes;6
+say not 0;7
+say 1 == 1;8
+say "a" == "a";9
+say "a" == "b";10
+say 1 and 1;11
+say 0 or 1;12
+say yes == 1;13
+say yes == yes;14
+say type(yes);15
+say type(1 < 2);16
+say [yes, no, 3 > 4];17
+say {"ok": yes};18
+say str(no) + str(yes);19
+say has({"a": 1}, "a");20
+say has({"a": 1}, "b");21
+say yes + 1;22
+say file_exists("/nonexistent");23
+say contains("abc", "b");24
+local flag = 5 > 3;25
+given flag { say "flag set"; }26
+given not flag { say "bad"; } otherwise { say "ok"; }27
+say flag and not no;28
+say none == none;29
+say not none;30
+attempt { given {"a": 1} { say "bad"; } } handle (e) { say "map condition"; }31
+attempt { given "x" { say "bad"; } } handle (e) { say "string condition"; }32
+local n = 0;33
+during n < 3 { n++; }34
+say n;35
+say yes ? "t" : "f";kiln/tests/cases/basics/exponent_literals.ashadded
+6−0@@ -0,0 +1,6 @@1
+say 1e3;2
+say 1.5e-3;3
+say 2E2 + 1;4
+say num("-1.5e2");5
+say num("1e3") + 1;6
+say num("2.5E-1");kiln/tests/cases/basics/number_format.ashadded
+26−0@@ -0,0 +1,26 @@1
+say 0;2
+say 3;3
+say -42;4
+say 0.5;5
+say -0.25;6
+say 3.14159265;7
+say 0.1 + 0.2;8
+say 123456789012;9
+say 999999999999999;10
+say 1e15;11
+say 1e21;12
+say -1e21;13
+say 1.5e-7;14
+say 1e-7;15
+say 0.000001;16
+say 0.0000005;17
+say 123456.789;18
+say 1e100;19
+say 1.2345678e30;20
+say 9.9999996;21
+say 0.9999999;22
+say 2.5e-300;23
+say 1 / 0;24
+say -1 / 0;25
+say str(1e21) + "|" + str(1.5) + "|" + str(-7);26
+say [1.5, 1e21, 2];kiln/tests/cases/collections/array_mutation.ashadded
+16−0@@ -0,0 +1,16 @@1
+local a = [1, 2, 3, 4];2
+delete(a, 1);3
+say a;4
+say pop(a);5
+say a;6
+insert(a, 0, 9);7
+insert(a, len(a), 7);8
+insert(a, 1, 8);9
+say a;10
+say slice(a, 1, 3);11
+say slice(a, 0, 0);12
+attempt { pop([]); } handle (e) { say "empty"; }13
+attempt { delete(a, 10); } handle (e) { say "oob"; }14
+local m = {"k": 1};15
+delete(m, "k");16
+say len(m);kiln/tests/cases/collections/map_order.ashadded
+30−0@@ -0,0 +1,30 @@1
+local m = {};2
+m["b"] = 1; m["zeta"] = 2; m["alpha"] = 3; m["a"] = 4; m["mid"] = 5;3
+say keys(m);4
+say values(m);5
+delete(m, "alpha");6
+say keys(m);7
+m["alpha"] = 9;8
+m["b"] = 100;9
+say keys(m);10
+say m;11
+delete(m, "b");12
+delete(m, "zzz");13
+say keys(m);14
+say len(m);15
+say has(m, "b");16
+say has(m, "mid");17
+local big = {};18
+local i = 0;19
+during i < 100 { big[str(i)] = i; i++; }20
+i = 0;21
+during i < 100 { given i % 2 == 0 { delete(big, str(i)); } i++; }22
+say len(big);23
+say big["51"];24
+say keys(big)[0];25
+say keys(big)[49];26
+i = 0;27
+during i < 100 { given i % 2 == 0 { big[str(i)] = i; } i++; }28
+say len(big);29
+say keys(big)[50];30
+say big["98"];kiln/tests/cases/collections/sort.ashadded
+20−0@@ -0,0 +1,20 @@1
+local a = [5, 2, 9, 1, 5, 6, -3, 0];2
+sort(a);3
+say a;4
+local s = ["pear", "apple", "fig", "banana", "apple", ""];5
+say sort(s);6
+local m = [{"k": 2, "t": "a"}, {"k": 1, "t": "b"}, {"k": 2, "t": "c"}, {"k": 1, "t": "d"}];7
+sort(m, forge (x, y) { yield x["k"] - y["k"]; });8
+say map(m, forge (x) { yield x["t"]; });9
+say sort([3, 1, 2], forge (x, y) { yield y - x; });10
+say sort([]);11
+say sort([1]);12
+local big = [];13
+local i = 0;14
+during i < 200 { push(big, (i * 37) % 101); i++; }15
+sort(big);16
+local ok = yes;17
+i = 1;18
+during i < len(big) { given big[i - 1] > big[i] { ok = no; } i++; }19
+say ok;20
+say len(big);kiln/tests/cases/control_flow/short_circuit.ashadded
+11−0@@ -0,0 +1,11 @@1
+local a = [1, 2];2
+given 5 < len(a) and a[5] == 1 { say "bad"; } otherwise { say "ok and"; }3
+given 0 < len(a) or a[5] == 1 { say "ok or"; }4
+say 1 and 2;5
+say 0 or 3;6
+say 0 and 1;7
+say 1 or 0;8
+say 1 and 0 or 1;9
+local n = 0;10
+during n < 5 and n != 3 { n++; }11
+say n;kiln/tests/cases/errors/attempt_array_delete.ashadded
+10−0@@ -0,0 +1,10 @@1
+local a = [1, 2, 3];2
+attempt { delete(a, 0); say "deleted"; say a; push(a, 9); delete(a, 99); say "unreachable"; } handle (e) { say "caught: " + e; }3
+say a;4
+attempt { local m = {"x": 1}; delete(m, "x"); say m; say m["x"]; } handle (e) { say "caught: " + e; }5
+local i = 0;6
+during i < 3 {7
+ attempt { delete(a, 10); } handle (e) { say "again: " + e; }8
+ i++;9
+}10
+say "end";kiln/tests/cases/errors/attempt_stale_handler.ashadded
+21−0@@ -0,0 +1,21 @@1
+forge f() {2
+ local i = 0;3
+ during i < 3 {4
+ attempt { local x = 1; } handle (e) { say "inner"; }5
+ i++;6
+ }7
+ raise "boom";8
+}9
+attempt { f(); } handle (e) { say "outer: " + e; }10
+forge g() {11
+ attempt { yield 5; } handle (e) { say "no"; }12
+}13
+forge h() { local v = g(); raise "boom2"; }14
+attempt { h(); } handle (e) { say "outer: " + e; }15
+local i = 0;16
+during i < 3 {17
+ i++;18
+ attempt { next; } handle (e) { say "no"; }19
+}20
+attempt { raise "second"; } handle (e) { say "caught " + e; }21
+say "done";kiln/tests/cases/errors/error_message_field.ashadded
+5−0@@ -0,0 +1,5 @@1
+attempt { local a = [1]; say a[3]; } handle (e) { say e.message; say "x: " + e; }2
+attempt { raise {"message": "custom", "line": 3}; } handle (e) { say e.message; say e.line; }3
+attempt { raise "plain"; } handle (e) { say e.message == e; }4
+local m = {"inner": {"deep": 7}};5
+say m.inner.deep;kiln/tests/cases/errors/error_messages.ashadded
+23−0@@ -0,0 +1,23 @@1
+local a = [1, 2];2
+attempt { say a[5]; } handle (e) { say e; }3
+attempt { say {"k": 1}["zz"]; } handle (e) { say e; }4
+attempt { say "abc"[9]; } handle (e) { say e; }5
+attempt { say "x" + 1; } handle (e) { say e; }6
+attempt { say 1 + "x"; } handle (e) { say e; }7
+attempt { say str("t"); } handle (e) { say e; }8
+attempt { pop([]); } handle (e) { say e; }9
+attempt { delete([1], 3); } handle (e) { say e; }10
+attempt { insert([1], 3, 0); } handle (e) { say e; }11
+attempt { slice([1, 2], 1, 5); } handle (e) { say e; }12
+attempt { chr(0); } handle (e) { say e; }13
+attempt { ord(""); } handle (e) { say e; }14
+attempt { repeat("a", -2); } handle (e) { say e; }15
+attempt { read_file("/nonexistent/file"); } handle (e) { say e; }16
+attempt { list_dir("/nonexistent"); } handle (e) { say e; }17
+attempt { given [1] { say "bad"; } } handle (e) { say e; }18
+attempt { raise "custom"; } handle (e) { say e; }19
+attempt { raise {"kind": "parse", "line": 3, "col": 7}; } handle (e) { say e["kind"] + ":" + str(e["line"]) + ":" + str(e["col"]); }20
+attempt { raise [1, 2]; } handle (e) { say e; }21
+attempt { raise 42; } handle (e) { say e + 1; }22
+forge f() { raise {"m": "inner"}; }23
+attempt { f(); } handle (e) { say e["m"]; }kiln/tests/cases/errors/type_errors.ashadded
+6−0@@ -0,0 +1,6 @@1
+attempt { say "x" + 1; } handle (e) { say "caught plus"; }2
+attempt { say 1 + "x"; } handle (e) { say "caught plus2"; }3
+attempt { say str("text"); } handle (e) { say "caught str"; }4
+say "a" + "b";5
+say 1 + 2;6
+say str(5);kiln/tests/cases/functions/builtins_as_values.ashadded
+9−0@@ -0,0 +1,9 @@1
+say type(len);2
+say map([1, 22, 333], str);3
+say map(["a", "bb"], len);4
+say map(["ab", "cd"], upper);5
+say filter([1, 2, 3, 4], forge (x) { yield x > 2; });6
+local f = len;7
+say f("hello");8
+say reduce([1, 2, 3], forge (a, b) { yield a + b; }, 0);9
+say map([4, 9], sqrt);kiln/tests/cases/functions/forward_declarations.ashadded
+20−0@@ -0,0 +1,20 @@1
+say is_even(10);2
+say is_odd(7);3
+say is_even(7);4
+forge is_even(n) {5
+ given n == 0 { yield yes; }6
+ yield is_odd(n - 1);7
+}8
+forge is_odd(n) {9
+ given n == 0 { yield no; }10
+ yield is_even(n - 1);11
+}12
+say twice(3);13
+forge twice(x) { yield helper(x) * 2; }14
+forge helper(x) { yield x + 1; }15
+local f = helper;16
+say f(5);17
+say map([1, 2], twice);18
+forge shadow(a) { yield a; }19
+local shadow = forge (a) { yield a * 100; };20
+say shadow(2);kiln/tests/cases/imports/fixtures/imports_state_lib.ashadded
+15−0@@ -0,0 +1,15 @@1
+local DIGITS = "0123456789abcdef";2
+local TABLE = [10, 20, 30];3
+local COUNT = 0;4
+local CONFIG = {"name": "stateful", "level": 2};5
+local DOUBLE = 21 * 2;6
+local ENABLED = yes;7
+8
+forge bump() { COUNT += 1; yield COUNT; }9
+forge hexdigit(n) { yield substring(DIGITS, n, n + 1); }10
+forge table_at(i) { yield TABLE[i]; }11
+forge add_item(v) { push(TABLE, v); yield len(TABLE); }12
+forge set_level(l) { CONFIG["level"] = l; }13
+forge level() { yield CONFIG["level"]; }14
+forge reset() { COUNT = 0; }15
+forge bump2() { COUNT++; yield COUNT; }kiln/tests/cases/imports/fixtures/imports_transitive_mid.ashadded
+3−0@@ -0,0 +1,3 @@1
+@import <./imports_basic_lib.ash>;2
+3
+forge mid_triple(x) { yield imports_basic_lib.add(x, imports_basic_lib.add(x, x)); }kiln/tests/cases/imports/imports_alias_test.ashadded
+6−0@@ -0,0 +1,6 @@1
+@import <./fixtures/imports_basic_lib.ash> as lib;2
+@import <./fixtures/imports_const_lib.ash> as k;3
+4
+say lib.add(3, 4);5
+say lib.helper(10);6
+say k.GREETING;kiln/tests/cases/imports/imports_state_test.ashadded
+17−0@@ -0,0 +1,17 @@1
+@import <./fixtures/imports_state_lib.ash>;2
+3
+say imports_state_lib.bump();4
+say imports_state_lib.bump();5
+say imports_state_lib.hexdigit(11);6
+say imports_state_lib.table_at(1);7
+say imports_state_lib.add_item(40);8
+say imports_state_lib.add_item(50);9
+say imports_state_lib.TABLE;10
+imports_state_lib.set_level(9);11
+say imports_state_lib.level();12
+say imports_state_lib.CONFIG["name"];13
+say imports_state_lib.DOUBLE;14
+say imports_state_lib.ENABLED;15
+imports_state_lib.reset();16
+say imports_state_lib.bump2();17
+say imports_state_lib.COUNT;kiln/tests/cases/imports/imports_transitive_test.ashadded
+5−0@@ -0,0 +1,5 @@1
+@import <./fixtures/imports_transitive_mid.ash>;2
+3
+say imports_transitive_mid.mid_triple(4);4
+say imports_basic_lib.add(1, 2);5
+say imports_basic_lib.helper(10);kiln/tests/cases/io/fs_ops.ashadded
+15−0@@ -0,0 +1,15 @@1
+local d = "/tmp/ash_fs_test_dir";2
+delete_file(d + "/b.txt"); delete_file(d + "/a.txt"); delete_file(d + "/c.txt");3
+make_dir(d);4
+say file_exists(d + "/a.txt");5
+write_file(d + "/a.txt", "hello");6
+write_file(d + "/b.txt", "world");7
+say rename_file(d + "/b.txt", d + "/c.txt");8
+say rename_file(d + "/nope.txt", d + "/x.txt");9
+say sort(list_dir(d));10
+say read_file(d + "/c.txt");11
+say delete_file(d + "/a.txt");12
+say delete_file(d + "/a.txt");13
+say delete_file(d + "/c.txt");14
+say list_dir(d);15
+attempt { list_dir(d + "/missing"); } handle (e) { say "no dir"; }kiln/tests/cases/strings/affix_ops.ashadded
+16−0@@ -0,0 +1,16 @@1
+say contains("hello world", "o w");2
+say contains("hello", "xyz");3
+say contains("hello", "");4
+say starts_with("hello", "he");5
+say starts_with("hello", "hello!");6
+say starts_with("hello", "");7
+say starts_with("hello", "el");8
+say ends_with("hello", "llo");9
+say ends_with("hello", "hell");10
+say ends_with("hello", "");11
+say ends_with("lo", "hello");12
+say repeat("ab", 3);13
+say repeat("x", 0);14
+say repeat("", 5) + "|";15
+say len(repeat("abc", 1000));16
+attempt { repeat("a", -1); } handle (e) { say "neg"; }kiln/tests/cases/strings/chr_ord.ashadded
+7−0@@ -0,0 +1,7 @@1
+say ord("A");2
+say chr(97) + chr(98);3
+say ord(chr(200));4
+say chr(ord("z") - 1);5
+local s = "hello";6
+local i = 0;7
+during i < len(s) { say ord(substring(s, i, i + 1)); i++; }kiln/tests/cases/strings/string_compare.ashadded
+9−0@@ -0,0 +1,9 @@1
+say "a" < "b";2
+say "b" < "a";3
+say "abc" <= "abc";4
+say "abd" > "abc";5
+say "" < "a";6
+say "Z" < "a";7
+say 1 < 2;8
+local s = "m";9
+given s >= "k" { say "fused ok"; }kiln/tests/scripts/run_pe_emulated.pyadded
+163−0@@ -0,0 +1,163 @@1
+"""Runs a kiln-produced PE32+ console program under the Unicorn x86-64 emulator.2
+kernel32 is faked in Python against the real filesystem; the program's own code3
+(including its Windows file-operation paths) is executed for real."""4
+import sys, os, struct, pefile5
+from unicorn import *6
+from unicorn.x86_const import *7
+8
+path = sys.argv[1]9
+pe = pefile.PE(path)10
+base = pe.OPTIONAL_HEADER.ImageBase11
+mu = Uc(UC_ARCH_X86, UC_MODE_64)12
+13
+def align(n, a=0x1000): return (n + a - 1) // a * a14
+15
+image_size = align(pe.OPTIONAL_HEADER.SizeOfImage)16
+mu.mem_map(base, image_size + 0x1000)17
+data = open(path, 'rb').read()18
+mu.mem_write(base, data[:pe.OPTIONAL_HEADER.SizeOfHeaders])19
+for s in pe.sections:20
+ mu.mem_write(base + s.VirtualAddress, s.get_data())21
+22
+STUBS = 0x7ff00000000023
+mu.mem_map(STUBS, 0x10000)24
+mu.mem_write(STUBS, b'\xc3' * 0x10000)25
+stub_names = {}26
+for i, entry in enumerate(pe.DIRECTORY_ENTRY_IMPORT):27
+ for imp in entry.imports:28
+ addr = STUBS + 16 * len(stub_names)29
+ stub_names[addr] = imp.name.decode()30
+ mu.mem_write(imp.address - base + base if imp.address >= base else base + imp.address, struct.pack('<Q', addr))31
+32
+STACK = 0x7fff0000000033
+mu.mem_map(STACK, 0x400000)34
+HEAPS = [0x100000000]35
+mu.reg_write(UC_X86_REG_RSP, STACK + 0x400000 - 0x1000)36
+37
+handles = {}38
+next_handle = [0x100]39
+out = sys.stdout.buffer40
+cmdline = base + image_size41
+mu.mem_write(cmdline, b'prog.exe\0')42
+43
+def cstr(addr):44
+ b = bytearray()45
+ while True:46
+ c = mu.mem_read(addr, 1)[0]47
+ if c == 0: return bytes(b).decode()48
+ b.append(c)49
+ addr += 150
+51
+def arg(n):52
+ regs = [UC_X86_REG_RCX, UC_X86_REG_RDX, UC_X86_REG_R8, UC_X86_REG_R9]53
+ if n < 4: return mu.reg_read(regs[n])54
+ rsp = mu.reg_read(UC_X86_REG_RSP)55
+ return struct.unpack('<Q', mu.mem_read(rsp + 8 + 8 * n, 8))[0] # return addr, then shadow(32) = args 1-4 slots56
+57
+def stack_arg(n):58
+ rsp = mu.reg_read(UC_X86_REG_RSP)59
+ return struct.unpack('<Q', mu.mem_read(rsp + 8 + 8 * n, 8))[0]60
+61
+def ret(v): mu.reg_write(UC_X86_REG_RAX, v & 0xFFFFFFFFFFFFFFFF)62
+INVALID = 0xFFFFFFFFFFFFFFFF63
+calls = []64
+65
+def alloc(size):66
+ start = HEAPS[0]67
+ size = align(size, 0x1000)68
+ mu.mem_map(start, size)69
+ HEAPS[0] += size70
+ return start71
+72
+def new_handle(obj):73
+ h = next_handle[0]; next_handle[0] += 474
+ handles[h] = obj75
+ return h76
+77
+def to_posix(p): return p.replace('\\', '/')78
+79
+def hook(uc, address, size, user):80
+ if not (STUBS <= address < STUBS + 0x10000): return81
+ name = stub_names.get(address)82
+ if name is None: return83
+ calls.append(name)84
+ if name == 'ExitProcess':85
+ sys.stdout.flush(); out.flush()86
+ os._exit(arg(0) & 0xFF)87
+ elif name == 'GetStdHandle': ret({0xFFFFFFF6: 1, 0xFFFFFFF5: 2, 0xFFFFFFF4: 3}.get(arg(0) & 0xFFFFFFFF, INVALID))88
+ elif name == 'WriteFile':89
+ h, buf, n, written = arg(0), arg(1), arg(2), arg(3)90
+ d = bytes(uc.mem_read(buf, n))91
+ if h == 2 or h == 3: sys.stderr.buffer.write(d); sys.stderr.flush()92
+ elif h == 1 or h == 0x1: out.write(d); out.flush()93
+ else:94
+ f = handles.get(h)95
+ if f is None: ret(0); return96
+ f['file'].write(d)97
+ if written: uc.mem_write(written, struct.pack('<I', n))98
+ ret(1)99
+ elif name == 'ReadFile':100
+ h, buf, n, got = arg(0), arg(1), arg(2), arg(3)101
+ f = handles.get(h)102
+ d = f['file'].read(n) if f else b''103
+ uc.mem_write(buf, d)104
+ if got: uc.mem_write(got, struct.pack('<I', len(d)))105
+ ret(1 if f else 0)106
+ elif name == 'CreateFileA':107
+ p, access, share, sa, disp = to_posix(cstr(arg(0))), arg(1), arg(2), arg(3), stack_arg(4)108
+ try:109
+ if disp == 3: f = open(p, 'rb') # OPEN_EXISTING110
+ elif disp == 2: f = open(p, 'wb') # CREATE_ALWAYS111
+ elif disp == 4: f = open(p, 'ab') # OPEN_ALWAYS112
+ else: ret(INVALID); return113
+ ret(new_handle({'file': f}))114
+ except OSError:115
+ ret(INVALID)116
+ elif name == 'CloseHandle':117
+ f = handles.pop(arg(0), None)118
+ if f and 'file' in f: f['file'].close()119
+ ret(1)120
+ elif name == 'GetFileSize':121
+ f = handles.get(arg(0)); ret(os.fstat(f['file'].fileno()).st_size if f else INVALID)122
+ elif name == 'VirtualAlloc': ret(alloc(arg(1)))123
+ elif name == 'GetCommandLineA': ret(cmdline)124
+ elif name == 'MoveFileA':125
+ try: os.rename(to_posix(cstr(arg(0))), to_posix(cstr(arg(1)))); ret(1)126
+ except OSError: ret(0)127
+ elif name == 'DeleteFileA':128
+ try: os.remove(to_posix(cstr(arg(0)))); ret(1)129
+ except OSError: ret(0)130
+ elif name == 'CreateDirectoryA':131
+ try: os.mkdir(to_posix(cstr(arg(0)))); ret(1)132
+ except OSError: ret(0)133
+ elif name == 'FindFirstFileA':134
+ pattern = to_posix(cstr(arg(0)))135
+ assert pattern.endswith('/*'), pattern136
+ try: names = ['.', '..'] + sorted(os.listdir(pattern[:-2]))137
+ except OSError: ret(INVALID); return138
+ h = new_handle({'names': names, 'i': 0})139
+ fill_find(uc, arg(1), h); ret(h)140
+ elif name == 'FindNextFileA':141
+ ret(1 if fill_find(uc, arg(1), arg(0)) else 0)142
+ elif name == 'FindClose':143
+ handles.pop(arg(0), None); ret(1)144
+ else:145
+ raise RuntimeError('unimplemented kernel32 call: ' + name)146
+147
+def fill_find(uc, buf, h):148
+ st = handles[h]149
+ if st['i'] >= len(st['names']): return False150
+ nm = st['names'][st['i']].encode(); st['i'] += 1151
+ uc.mem_write(buf, b'\0' * 320)152
+ uc.mem_write(buf + 44, nm + b'\0')153
+ return True154
+155
+mu.hook_add(UC_HOOK_CODE, hook, begin=STUBS, end=STUBS + 0x10000)156
+entry = base + pe.OPTIONAL_HEADER.AddressOfEntryPoint157
+try:158
+ mu.emu_start(entry, 0, count=2_000_000_000)159
+except UcError as e:160
+ sys.stdout.flush(); out.flush()161
+ print('EMULATION ERROR:', e, 'at rip', hex(mu.reg_read(UC_X86_REG_RIP)), file=sys.stderr)162
+ sys.exit(99)163
+sys.stdout.flush(); out.flush()kiln/tests/scripts/windows_emulated_check.shadded
+29−0@@ -0,0 +1,29 @@1
+#!/usr/bin/env bash2
+# kiln/tests/scripts/windows_emulated_check.sh3
+# No Windows machine needed: builds every kiln test case as a Windows PE (--target=windows),4
+# runs it under the Unicorn x86-64 emulator (run_pe_emulated.py, which fakes kernel32 on top of the5
+# real filesystem), and compares its output with the same program built for Linux.6
+# Needs python3 with `unicorn` and `pefile`; a venv is created in /tmp/kiln_winemu on first use.7
+set -uo pipefail8
+cd "$(dirname "$0")/../.." # -> kiln/9
+KILN="$PWD/../bin/kiln"10
+SCRIPTS="$PWD/tests/scripts"11
+VENV=/tmp/kiln_winemu12
+if [ ! -x "$VENV/bin/python" ]; then13
+ python3 -m venv "$VENV" && "$VENV/bin/pip" install -q unicorn pefile || { echo "could not set up the emulator venv"; exit 1; }14
+fi15
+make -s || { echo "build failed"; exit 1; }16
+OUT=/tmp/kiln_winemu_out; mkdir -p "$OUT"17
+pass=0; fail=018
+for f in tests/cases/*/*.ash; do19
+ n=$(basename "$f" .ash); d=$(dirname "$f")20
+ case $n in argv|input*|*circular*|*collision*) continue;; esac # need real arguments / stdin / expected failure21
+ rm -rf /tmp/ash_fs_test_dir22
+ (cd "$d" && "$KILN" "$n.ash" -o "$OUT/lin_$n" >/dev/null 2>&1 && "$KILN" "$n.ash" -o "$OUT/win_$n.exe" --target=windows >/dev/null 2>&1) || { echo "COMPILE-FAIL: $n"; fail=$((fail + 1)); continue; }23
+ (cd "$d" && timeout 120 "$OUT/lin_$n" < /dev/null > "$OUT/lin_$n.out" 2>&1)24
+ rm -rf /tmp/ash_fs_test_dir25
+ (cd "$d" && timeout 600 "$VENV/bin/python" "$SCRIPTS/run_pe_emulated.py" "$OUT/win_$n.exe" < /dev/null > "$OUT/win_$n.out" 2>&1)26
+ if cmp -s "$OUT/lin_$n.out" "$OUT/win_$n.out"; then pass=$((pass + 1)); else fail=$((fail + 1)); echo "DIFF: $n"; fi27
+done28
+echo "windows (emulated) vs linux: same $pass, different $fail"29
+[ "$fail" -eq 0 ]kiln/tests/scripts/windows_wine_check.shadded
+29−0@@ -0,0 +1,29 @@1
+#!/usr/bin/env bash2
+# kiln/tests/scripts/windows_wine_check.sh3
+# Builds every kiln test case as a Windows PE (--target=windows), runs it under Wine -- whose4
+# kernel32 is an independent reimplementation of the Windows API -- and compares its output with5
+# the same program built for Linux.6
+# WINE=/path/to/wine kiln/tests/scripts/windows_wine_check.sh7
+# Without WINE it uses `wine` from PATH. A portable build works without root, e.g.8
+# https://github.com/Kron4ek/Wine-Builds (unpack the amd64 tarball, point WINE at bin/wine).9
+set -uo pipefail10
+cd "$(dirname "$0")/../.." # -> kiln/11
+KILN="$PWD/../bin/kiln"12
+WINE="${WINE:-wine}"13
+command -v "$WINE" >/dev/null 2>&1 || [ -x "$WINE" ] || { echo "wine not found (set WINE=/path/to/wine)"; exit 1; }14
+export WINEPREFIX="${WINEPREFIX:-$HOME/.cache/ash_wine_prefix}" WINEDEBUG=-all WINEDLLOVERRIDES="mscoree,mshtml=" DISPLAY=15
+make -s || { echo "build failed"; exit 1; }16
+OUT=/tmp/kiln_wine_out; mkdir -p "$OUT"17
+pass=0; fail=018
+for f in tests/cases/*/*.ash; do19
+ n=$(basename "$f" .ash); d=$(dirname "$f")20
+ case $n in argv|input*|*circular*|*collision*) continue;; esac # need real arguments / stdin / expected failure21
+ rm -rf /tmp/ash_fs_test_dir22
+ (cd "$d" && "$KILN" "$n.ash" -o "$OUT/lin_$n" >/dev/null 2>&1 && "$KILN" "$n.ash" -o "$OUT/win_$n.exe" --target=windows >/dev/null 2>&1) || { echo "COMPILE-FAIL: $n"; fail=$((fail + 1)); continue; }23
+ (cd "$d" && timeout 120 "$OUT/lin_$n" < /dev/null > "$OUT/lin_$n.out" 2>&1)24
+ rm -rf /tmp/ash_fs_test_dir25
+ (cd "$d" && timeout 300 "$WINE" "$OUT/win_$n.exe" < /dev/null > "$OUT/win_$n.out" 2>/dev/null)26
+ if cmp -s "$OUT/lin_$n.out" "$OUT/win_$n.out"; then pass=$((pass + 1)); else fail=$((fail + 1)); echo "DIFF: $n"; fi27
+done28
+echo "windows (wine) vs linux: same $pass, different $fail"29
+[ "$fail" -eq 0 ]