← Commit history

refactor(front): add shared IR lexer, in-memory driver and REPL session; drop @import driver

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

b893228parent0cda2da

26 files changed+343−526

hearth/ashvm/src/compiler/C/import_paths.cremoved

+0−68
@@ -1,68 +0,0 @@
1-#define _XOPEN_SOURCE 700 // exposes realpath() and PATH_MAX under -std=c11
2-#include <string.h>
3-#include <stdio.h>
4-#include <stdlib.h>
5-#include "compiler/H/import_paths.h"
6-
7-void import_path_dirname(const char *path, char *out, int out_size) {
8- const char *slash = strrchr(path, '/');
9- if (!slash) { snprintf(out, (size_t)out_size, "."); return; }
10- int len = (int)(slash - path);
11- if (len >= out_size) len = out_size - 1;
12- memcpy(out, path, (size_t)len);
13- out[len] = '\0';
14-}
15-
16-void import_derive_namespace(const char *canonical_path, char *out, int out_size) {
17- const char *slash = strrchr(canonical_path, '/');
18- const char *base = slash ? slash + 1 : canonical_path;
19- int len = (int)strlen(base);
20- if (len > 4 && strcmp(base + len - 4, ".ash") == 0) len -= 4;
21- if (len >= out_size) len = out_size - 1;
22- memcpy(out, base, (size_t)len);
23- out[len] = '\0';
24-}
25-
26-static int is_relative_prefix(const char *raw, int len) {
27- if (len >= 2 && raw[0] == '.' && raw[1] == '/') return 1;
28- if (len >= 3 && raw[0] == '.' && raw[1] == '.' && raw[2] == '/') return 1;
29- return 0;
30-}
31-
32-int import_resolve_path(const char *raw_path, int raw_len,
33- const char *importing_file_dir,
34- const char *project_root_dir,
35- char *canonical_out,
36- char *message_out, int message_out_size) {
37- char candidate[PATH_MAX];
38-
39- if (is_relative_prefix(raw_path, raw_len)) {
40- if (raw_len < 4 || strncmp(raw_path + raw_len - 4, ".ash", 4) != 0) {
41- snprintf(message_out, (size_t)message_out_size, "relative import path must end in '.ash'");
42- return 0;
43- }
44- snprintf(candidate, sizeof(candidate), "%s/%.*s", importing_file_dir, raw_len, raw_path);
45- } else {
46- for (int i = 0; i < raw_len; i++) {
47- if (raw_path[i] == '.' || raw_path[i] == '/') {
48- snprintf(message_out, (size_t)message_out_size,
49- "library import name must not contain '.' or '/' (did you mean './%.*s'?)", raw_len, raw_path);
50- return 0;
51- }
52- }
53- // ash.libs/ (hand-written local libraries) wins first; a package
54- // installed by forgepack (see ideas/ash_modules.md) only gets
55- // tried if there's no local library shadowing the same name.
56- snprintf(candidate, sizeof(candidate), "%s/ash.libs/%.*s/%.*s.ash",
57- project_root_dir, raw_len, raw_path, raw_len, raw_path);
58- if (realpath(candidate, canonical_out) != NULL) return 1;
59- snprintf(candidate, sizeof(candidate), "%s/@ash-modules/%.*s/%.*s.ash",
60- project_root_dir, raw_len, raw_path, raw_len, raw_path);
61- }
62-
63- if (realpath(candidate, canonical_out) == NULL) {
64- snprintf(message_out, (size_t)message_out_size, "could not resolve '%.*s'", raw_len, raw_path);
65- return 0;
66- }
67- return 1;
68-}

hearth/ashvm/src/compiler/C/stmt_import.cremoved

+0−235
@@ -1,235 +0,0 @@
1-#define _XOPEN_SOURCE 700 // exposes realpath() and PATH_MAX under -std=c11
2-#include <string.h>
3-#include <stdio.h>
4-#include <stdlib.h>
5-#include <limits.h>
6-#include "compiler/H/internal.h"
7-#include "compiler/H/import_paths.h"
8-#include "diagnostics/H/diagnostics.h"
9-
10-#define MAX_VM_IMPORTS 16
11-#define MAX_VM_IMPORT_DEPTH 8
12-
13-typedef struct {
14- char canonical_path[PATH_MAX];
15- char namespace_name[64];
16-} ImportedFile;
17-
18-static ImportedFile imported_files[MAX_VM_IMPORTS];
19-static int imported_file_count = 0;
20-
21-static char resolving_stack[MAX_VM_IMPORT_DEPTH][PATH_MAX];
22-static int resolving_depth = 0;
23-
24-static char project_root_dir[PATH_MAX];
25-static char current_file_dir[PATH_MAX];
26-
27-static int import_block_open = 1;
28-
29-static void fail(const char *msg) {
30- diagnostics_report("error", msg, current.start, current.length);
31- exit(1);
32-}
33-
34-void imports_init(const char *entry_file_path) {
35- char resolved[PATH_MAX];
36- if (realpath(entry_file_path, resolved) == NULL) fail("could not resolve entry file path");
37- import_path_dirname(resolved, project_root_dir, sizeof(project_root_dir));
38- size_t root_len = strlen(project_root_dir);
39- if (root_len >= sizeof(current_file_dir)) root_len = sizeof(current_file_dir) - 1;
40- memcpy(current_file_dir, project_root_dir, root_len);
41- current_file_dir[root_len] = '\0';
42- import_block_open = 1;
43-}
44-
45-void imports_close_block(void) {
46- import_block_open = 0;
47-}
48-
49-static ImportedFile *registry_find_by_path(const char *canonical) {
50- for (int i = 0; i < imported_file_count; i++) {
51- if (strcmp(imported_files[i].canonical_path, canonical) == 0) return &imported_files[i];
52- }
53- return NULL;
54-}
55-
56-static ImportedFile *registry_find_by_namespace(const char *ns) {
57- for (int i = 0; i < imported_file_count; i++) {
58- if (strcmp(imported_files[i].namespace_name, ns) == 0) return &imported_files[i];
59- }
60- return NULL;
61-}
62-
63-static int resolving_stack_contains(const char *canonical) {
64- for (int i = 0; i < resolving_depth; i++) {
65- if (strcmp(resolving_stack[i], canonical) == 0) return 1;
66- }
67- return 0;
68-}
69-
70-static void report_circular_import(const char *canonical) {
71- char msg[1024];
72- int off = snprintf(msg, sizeof(msg), "circular import detected: ");
73- for (int i = 0; i < resolving_depth; i++) {
74- off += snprintf(msg + off, sizeof(msg) - (size_t)off, "%s -> ", resolving_stack[i]);
75- if (off >= (int)sizeof(msg)) break;
76- }
77- snprintf(msg + off, sizeof(msg) - (size_t)off, "%s", canonical);
78- fail(msg);
79-}
80-
81-static char *read_import_file(const char *path) {
82- FILE *file = fopen(path, "rb");
83- if (!file) fail("could not open resolved import file");
84- fseek(file, 0L, SEEK_END);
85- long size = ftell(file);
86- rewind(file);
87- // Never freed -- an imported file's source buffer must outlive every
88- // later use site that points diagnostics/lexer state back into it,
89- // which can be arbitrarily far ahead in the importing program. The
90- // compiler is a one-shot host process; leaking this until it exits is
91- // the same documented, intentional tradeoff kiln's imports.c makes.
92- char *buffer = malloc((size_t)size + 1);
93- fread(buffer, 1, (size_t)size, file);
94- buffer[size] = '\0';
95- fclose(file);
96- return buffer;
97-}
98-
99-// The restricted top-level loop for a recursively-parsed imported file:
100-// identical shape to compile()'s own `while (current.type != TOKEN_EOF)
101-// statement();` loop (stmt.c). statement() itself enforces the "only
102-// fn/let allowed" restriction by checking get_import_namespace()'s length
103-// AND that block_depth == 0 (see stmt.c) -- so it fires only for this
104-// file's genuine top level, never for statements nested inside one of its
105-// own function bodies.
106-static void parse_imported_file_body(void) {
107- // Each imported file gets its OWN fresh import-block window: OPEN (not
108- // closed), so the imported file's own leading @import statements (if
109- // any -- e.g. a diamond-shaped or chained import) are allowed, exactly
110- // like the entry file's. Calling imports_close_block() here would do
111- // the opposite (close it), which is wrong -- statement() itself closes
112- // the window the moment it dispatches this file's own first
113- // non-import statement, same as it does for the entry file.
114- import_block_open = 1;
115- while (current.type != TOKEN_EOF) statement();
116-}
117-
118-void compile_import_stmt(void) {
119- if (!import_block_open) fail("@import statements must form a contiguous block at the top of the file");
120-
121- if (current.type != TOKEN_LESS) fail("expected '<' after '@import'");
122- Token path = lexer_scan_import_path();
123- if (path.type == TOKEN_ERROR) fail(path.start);
124- advance_token();
125-
126- char canonical[PATH_MAX];
127- char message[256];
128- if (!import_resolve_path(path.start, path.length, current_file_dir, project_root_dir,
129- canonical, message, sizeof(message))) {
130- fail(message);
131- }
132-
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-
146- expect(TOKEN_SEMICOLON, "expected ';' after import path");
147-
148- // Cycle check MUST come before the dedup check: a file already in
149- // imported_files but STILL on resolving_stack is mid-parse (its
150- // registry entry was added before recursing, same as every entry
151- // here) -- that's a genuine cycle, not a finished diamond import.
152- // Checking the registry first would silently treat a still-parsing
153- // file as "already done" and swallow the cycle instead of reporting
154- // it. Only a registry hit with NO matching resolving_stack entry is a
155- // real, already-completed diamond dedup.
156- if (resolving_stack_contains(canonical)) report_circular_import(canonical);
157-
158- ImportedFile *existing = registry_find_by_path(canonical);
159- if (existing != NULL) return; // diamond dedup -- already fully processed, nothing more to do
160-
161- char ns[64];
162- import_derive_namespace(canonical, ns, sizeof(ns));
163- if (has_alias) { memcpy(ns, alias, strlen(alias) + 1); }
164-
165- ImportedFile *collision = registry_find_by_namespace(ns);
166- if (collision != NULL) {
167- // Sized to comfortably fit two PATH_MAX-bounded paths plus the
168- // namespace and surrounding literal text with no truncation, so
169- // -Wformat-truncation can prove this snprintf never overflows.
170- char msg[2 * PATH_MAX + 128];
171- snprintf(msg, sizeof(msg), "namespace '%s' already used by %s (colliding with %s)",
172- ns, collision->canonical_path, canonical);
173- fail(msg);
174- }
175-
176- if (imported_file_count >= MAX_VM_IMPORTS) fail("too many imported files");
177- ImportedFile *entry = &imported_files[imported_file_count++];
178- size_t canonical_len = strlen(canonical);
179- if (canonical_len >= sizeof(entry->canonical_path)) canonical_len = sizeof(entry->canonical_path) - 1;
180- memcpy(entry->canonical_path, canonical, canonical_len);
181- entry->canonical_path[canonical_len] = '\0';
182- size_t ns_len = strlen(ns);
183- if (ns_len >= sizeof(entry->namespace_name)) ns_len = sizeof(entry->namespace_name) - 1;
184- memcpy(entry->namespace_name, ns, ns_len);
185- entry->namespace_name[ns_len] = '\0';
186-
187- if (resolving_depth >= MAX_VM_IMPORT_DEPTH) fail("import nesting too deep");
188- size_t stack_len = strlen(canonical);
189- if (stack_len >= sizeof(resolving_stack[0])) stack_len = sizeof(resolving_stack[0]) - 1;
190- memcpy(resolving_stack[resolving_depth], canonical, stack_len);
191- resolving_stack[resolving_depth][stack_len] = '\0';
192- resolving_depth++;
193-
194- // --- Save everything the recursive parse of the imported file will
195- // clobber, mirroring compiler/C/state.c's own save-recurse-restore
196- // pattern for locals (the C call stack gives correct nesting for
197- // free, same as those). ---
198- DiagnosticsSource saved_diag = diagnostics_save_source();
199- LexerState saved_lexer = lexer_save_state();
200- Token saved_current = current;
201- Token saved_previous = previous;
202- char saved_file_dir[PATH_MAX];
203- size_t saved_len = strlen(current_file_dir);
204- if (saved_len >= sizeof(saved_file_dir)) saved_len = sizeof(saved_file_dir) - 1;
205- memcpy(saved_file_dir, current_file_dir, saved_len);
206- saved_file_dir[saved_len] = '\0';
207- const char *saved_ns; int saved_ns_len;
208- get_import_namespace(&saved_ns, &saved_ns_len);
209- int saved_import_block_open = import_block_open;
210-
211- char *imported_source = read_import_file(canonical);
212- diagnostics_set_source(imported_source, canonical);
213- import_path_dirname(canonical, current_file_dir, sizeof(current_file_dir));
214- set_import_namespace(entry->namespace_name, (int)strlen(entry->namespace_name));
215-
216- prescan_functions(imported_source);
217- lexer_init(imported_source);
218- advance_token();
219- parse_imported_file_body();
220-
221- // --- Restore, in the reverse order, so the importing file's parse
222- // resumes exactly as if the @import line had been a no-op cursor-wise. ---
223- set_import_namespace(saved_ns, saved_ns_len);
224- size_t restore_len = strlen(saved_file_dir);
225- if (restore_len >= sizeof(current_file_dir)) restore_len = sizeof(current_file_dir) - 1;
226- memcpy(current_file_dir, saved_file_dir, restore_len);
227- current_file_dir[restore_len] = '\0';
228- lexer_restore_state(saved_lexer);
229- diagnostics_restore_source(saved_diag);
230- current = saved_current;
231- previous = saved_previous;
232- import_block_open = saved_import_block_open;
233-
234- resolving_depth--;
235-}

hearth/ashvm/src/compiler/D/import_paths.dremoved

+0−3
@@ -1,3 +0,0 @@
1-src/compiler/O/import_paths.o: src/compiler/C/import_paths.c \
2- src/compiler/H/import_paths.h
3-src/compiler/H/import_paths.h:

hearth/ashvm/src/compiler/D/stmt_import.dremoved

+0−16
@@ -1,16 +0,0 @@
1-src/compiler/O/stmt_import.o: src/compiler/C/stmt_import.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/compiler/H/import_paths.h \
6- src/diagnostics/H/diagnostics.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/compiler/H/module_state.h:
15-src/compiler/H/import_paths.h:
16-src/diagnostics/H/diagnostics.h:

hearth/ashvm/src/compiler/H/import_paths.hremoved

+0−39
@@ -1,39 +0,0 @@
1-#ifndef ASH_VM_COMPILER_IMPORT_PATHS_H
2-#define ASH_VM_COMPILER_IMPORT_PATHS_H
3-#include <limits.h>
4-
5-// Resolves the raw text inside `@import <...>` to a canonicalized
6-// (realpath()'d) absolute filesystem path.
7-//
8-// raw_path/raw_len: the text between '<' and '>', exactly as scanned by
9-// lexer_scan_import_path (NOT null-terminated -- callers must use len).
10-// importing_file_dir: directory of the file containing this @import
11-// statement (relative imports resolve against this).
12-// project_root_dir: directory of the entry .ash file passed on the
13-// command line (library imports resolve against this, regardless of
14-// how deeply nested the importing file is).
15-// canonical_out: buffer of at least PATH_MAX bytes.
16-//
17-// Returns 1 and fills canonical_out on success. Returns 0 on failure
18-// (message_out is filled with a short human-readable reason, e.g.
19-// "could not resolve '<path>'" or "library import name must not contain
20-// '.' or '/'" -- callers pass this straight into diagnostics_report).
21-int import_resolve_path(const char *raw_path, int raw_len,
22- const char *importing_file_dir,
23- const char *project_root_dir,
24- char *canonical_out,
25- char *message_out, int message_out_size);
26-
27-// Derives the auto-namespace for a resolved canonical path: the file's
28-// own basename with the trailing ".ash" stripped. Works uniformly for
29-// both relative and library imports because library entry files are
30-// required to be named "<libname>/<libname>.ash" -- the folder name and
31-// the basename-minus-extension are always identical by construction.
32-void import_derive_namespace(const char *canonical_path, char *out, int out_size);
33-
34-// Directory-of-a-path helper (hand-rolled, not libgen.h's dirname(),
35-// which mutates its input and reuses a static buffer across calls --
36-// both a bad fit here). Writes "." if path has no '/'.
37-void import_path_dirname(const char *path, char *out, int out_size);
38-
39-#endif

hearth/ashvm/src/compiler/H/stmt_import.hremoved

+0−8
@@ -1,8 +0,0 @@
1-#ifndef ASH_VM_COMPILER_STMT_IMPORT_H
2-#define ASH_VM_COMPILER_STMT_IMPORT_H
3-
4-void imports_init(const char *entry_file_path);
5-void compile_import_stmt(void); // assumes statement() already consumed TOKEN_IMPORT
6-void imports_close_block(void);
7-
8-#endif

hearth/ashvm/src/lexer/C/lexer_import.cremoved

+0−37
@@ -1,37 +0,0 @@
1-#include "lexer/H/lexer_import.h"
2-
3-LexerState lexer_save_state(void) {
4- LexerState state;
5- state.start = lex_start;
6- state.current = lex_current;
7- state.line = lex_line;
8- return state;
9-}
10-
11-void lexer_restore_state(LexerState state) {
12- lex_start = state.start;
13- lex_current = state.current;
14- lex_line = state.line;
15-}
16-
17-// Called with the lexer's internal `current` pointer sitting immediately
18-// after the '<' the parser just confirmed (via current.type==TOKEN_LESS)
19-// WITHOUT calling advance_token() on it -- so `current` here still points
20-// at the first path character, untouched by normal tokenization. Scans
21-// raw bytes up to '>', consuming the '>' too, so the parser's next
22-// advance_token() call correctly picks up whatever follows it (';').
23-Token lexer_scan_import_path(void) {
24- const char *content_start = lex_current;
25- while (peek() != '>' && peek() != '\n' && !is_at_end()) advance();
26- if (peek() != '>') return error_token("unterminated import path (expected '>')");
27-
28- int length = (int)(lex_current - content_start);
29- advance(); // consume '>'
30-
31- Token token;
32- token.type = TOKEN_IMPORT_PATH;
33- token.start = content_start;
34- token.length = length;
35- token.line = lex_line;
36- return token;
37-}

hearth/ashvm/src/lexer/D/lexer.dremoved

+0−4
@@ -1,4 +0,0 @@
1-src/lexer/O/lexer.o: src/lexer/C/lexer.c src/lexer/H/lexer.h \
2- src/lexer/H/lexer_import.h
3-src/lexer/H/lexer.h:
4-src/lexer/H/lexer_import.h:

hearth/ashvm/src/lexer/D/lexer_import.dremoved

+0−4
@@ -1,4 +0,0 @@
1-src/lexer/O/lexer_import.o: src/lexer/C/lexer_import.c \
2- src/lexer/H/lexer_import.h src/lexer/H/lexer.h
3-src/lexer/H/lexer_import.h:
4-src/lexer/H/lexer.h:

hearth/ashvm/src/lexer/H/lexer_import.hremoved

+0−29
@@ -1,29 +0,0 @@
1-#ifndef ASHVM_LEXER_IMPORT_H
2-#define ASHVM_LEXER_IMPORT_H
3-#include "lexer/H/lexer.h"
4-
5-// Cross-file link between lexer.c (the tokenizer's cursor state and its
6-// low-level scan primitives) and lexer_import.c (state save/restore plus
7-// import-path scanning, split out here to keep lexer.c under the
8-// project's ~200-line guideline) -- not part of the public API. The
9-// public entry points the two files implement together
10-// (lexer_save_state/lexer_restore_state/lexer_scan_import_path) stay
11-// declared in lexer.h exactly as before, so callers outside the lexer
12-// directory don't need to change anything. Mirrors kiln's identical split
13-// between lexer.h (public surface) and lexer_import.h (private,
14-// cross-file plumbing).
15-//
16-// Named lex_* rather than the bare start/current/line: compiler/H/state.h
17-// already declares its own global `Token current`, and since these now
18-// need external linkage to be shared across the two .c files, a second
19-// plain `current` global here would collide with it at link time.
20-extern const char *lex_start;
21-extern const char *lex_current;
22-extern int lex_line;
23-
24-int is_at_end(void);
25-char advance(void);
26-char peek(void);
27-Token error_token(const char *message);
28-
29-#endif

hearth/ashvm/src/lexer/C/lexer.c → hearth/front/src/ir/C/ir_lex.crenamed

+60−55
@@ -1,24 +1,32 @@
11 #include <string.h>
22 #include <ctype.h>
3-#include "lexer/H/lexer.h"
4-#include "lexer/H/lexer_import.h"
3+#include "ir/H/ir_lex.h"
54
6-// lex_* (not the bare start/current/line these had as file-static
7-// before) -- shared with lexer_import.c now, and a plain `current` would
8-// collide with compiler/H/state.h's own global `Token current` at link time.
9-const char *lex_start;
10-const char *lex_current;
11-int lex_line;
5+/* lex_* names: the compilers keep their own `Token current` globals */
6+static const char *lex_start;
7+static const char *lex_current;
8+static int lex_line;
129
1310 void lexer_init(const char *source) {
1411 lex_start = source;
1512 lex_current = source;
1613 lex_line = 1;
1714 }
1815
19-int is_at_end(void) { return *lex_current == '\0'; }
20-char advance(void) { lex_current++; return lex_current[-1]; }
21-char peek(void) { return *lex_current; }
16+LexerState lexer_save_state(void) {
17+ LexerState state = { lex_start, lex_current, lex_line };
18+ return state;
19+}
20+
21+void lexer_restore_state(LexerState state) {
22+ lex_start = state.start;
23+ lex_current = state.current;
24+ lex_line = state.line;
25+}
26+
27+static int is_at_end(void) { return *lex_current == '\0'; }
28+static char advance(void) { lex_current++; return lex_current[-1]; }
29+static char peek(void) { return *lex_current; }
2230 static char peek_next(void) { if (is_at_end()) return '\0'; return lex_current[1]; }
2331
2432 static int match(char expected) {
@@ -42,6 +50,13 @@ static void skip_whitespace(void) {
4250 case '/':
4351 if (peek_next() == '/') {
4452 while (peek() != '\n' && !is_at_end()) advance();
53+ } else if (peek_next() == '*') {
54+ advance(); advance();
55+ while (!is_at_end() && !(peek() == '*' && peek_next() == '/')) {
56+ if (peek() == '\n') lex_line++;
57+ advance();
58+ }
59+ if (!is_at_end()) { advance(); advance(); }
4560 } else {
4661 return;
4762 }
@@ -61,7 +76,7 @@ static Token make_token(TokenType type) {
6176 return token;
6277 }
6378
64-Token error_token(const char *message) {
79+static Token error_token(const char *message) {
6580 Token token;
6681 token.type = TOKEN_ERROR;
6782 token.start = message;
@@ -70,16 +85,23 @@ Token error_token(const char *message) {
7085 return token;
7186 }
7287
88+/* Only decides the token's boundaries; the compilers convert it with strtod,
89+ * which understands hex ("0x1A") and exponents ("1e10", "1.5e-3"). */
7390 static Token number(void) {
91+ if (lex_start[0] == '0' && (peek() == 'x' || peek() == 'X')) {
92+ advance();
93+ while (isxdigit(peek())) advance();
94+ return make_token(TOKEN_NUMBER);
95+ }
7496 while (isdigit(peek())) advance();
7597 if (peek() == '.' && isdigit(peek_next())) {
76- advance(); // consume '.'
98+ advance();
7799 while (isdigit(peek())) advance();
78100 }
79101 if (peek() == 'e' || peek() == 'E') {
80102 char after = peek_next();
81103 if (isdigit(after) || after == '+' || after == '-') {
82- advance(); // consume 'e'/'E'
104+ advance();
83105 if (peek() == '+' || peek() == '-') advance();
84106 while (isdigit(peek())) advance();
85107 }
@@ -91,8 +113,8 @@ static Token string_token(void) {
91113 const char *content_start = lex_current;
92114 while (peek() != '"' && !is_at_end()) {
93115 if (peek() == '\\' && peek_next() != '\0') {
94- advance(); // skip the backslash
95- advance(); // skip the escaped character itself, so a \" doesn't end the string early
116+ advance();
117+ advance();
96118 continue;
97119 }
98120 if (peek() == '\n') lex_line++;
@@ -111,34 +133,24 @@ static Token string_token(void) {
111133 return token;
112134 }
113135
136+static const struct { const char *word; TokenType type; } keywords[] = {
137+ { "say", TOKEN_SAY }, { "local", TOKEN_LOCAL }, { "given", TOKEN_GIVEN },
138+ { "otherwise", TOKEN_OTHERWISE }, { "during", TOKEN_DURING }, { "each", TOKEN_EACH },
139+ { "in", TOKEN_IN }, { "forge", TOKEN_FORGE }, { "yield", TOKEN_YIELD },
140+ { "attempt", TOKEN_ATTEMPT }, { "handle", TOKEN_HANDLE }, { "raise", TOKEN_RAISE },
141+ { "stop", TOKEN_STOP }, { "next", TOKEN_NEXT }, { "yes", TOKEN_YES },
142+ { "no", TOKEN_NO }, { "none", TOKEN_NONE }, { "and", TOKEN_AND },
143+ { "or", TOKEN_OR }, { "not", TOKEN_BANG },
144+};
145+
114146 static Token identifier(void) {
115147 while (isalnum(peek()) || peek() == '_') advance();
116148
117149 int length = (int)(lex_current - lex_start);
118- if (length == 3 && strncmp(lex_start, "say", 3) == 0) return make_token(TOKEN_SAY);
119- if (length == 5 && strncmp(lex_start, "local", 5) == 0) return make_token(TOKEN_LOCAL);
120- if (length == 5 && strncmp(lex_start, "given", 5) == 0) return make_token(TOKEN_GIVEN);
121- if (length == 9 && strncmp(lex_start, "otherwise", 9) == 0) return make_token(TOKEN_OTHERWISE);
122- if (length == 6 && strncmp(lex_start, "during", 6) == 0) return make_token(TOKEN_DURING);
123- if (length == 4 && strncmp(lex_start, "each", 4) == 0) return make_token(TOKEN_EACH);
124- if (length == 2 && strncmp(lex_start, "in", 2) == 0) return make_token(TOKEN_IN);
125- if (length == 5 && strncmp(lex_start, "forge", 5) == 0) return make_token(TOKEN_FORGE);
126- if (length == 5 && strncmp(lex_start, "yield", 5) == 0) return make_token(TOKEN_YIELD);
127- if (length == 7 && strncmp(lex_start, "attempt", 7) == 0) return make_token(TOKEN_ATTEMPT);
128- if (length == 6 && strncmp(lex_start, "handle", 6) == 0) return make_token(TOKEN_HANDLE);
129- if (length == 5 && strncmp(lex_start, "raise", 5) == 0) return make_token(TOKEN_RAISE);
130- if (length == 4 && strncmp(lex_start, "stop", 4) == 0) return make_token(TOKEN_STOP);
131- if (length == 4 && strncmp(lex_start, "next", 4) == 0) return make_token(TOKEN_NEXT);
132- if (length == 3 && strncmp(lex_start, "yes", 3) == 0) return make_token(TOKEN_YES);
133- if (length == 2 && strncmp(lex_start, "no", 2) == 0) return make_token(TOKEN_NO);
134- if (length == 4 && strncmp(lex_start, "none", 4) == 0) return make_token(TOKEN_NONE);
135- if (length == 3 && strncmp(lex_start, "and", 3) == 0) return make_token(TOKEN_AND);
136- if (length == 2 && strncmp(lex_start, "or", 2) == 0) return make_token(TOKEN_OR);
137- // "not" reuses TOKEN_BANG (unary logical negation) -- same meaning as
138- // the symbol it replaces, so unary()'s existing TOKEN_BANG handling in
139- // expr.c needs no change at all.
140- if (length == 3 && strncmp(lex_start, "not", 3) == 0) return make_token(TOKEN_BANG);
141-
150+ for (size_t i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
151+ if ((int)strlen(keywords[i].word) == length && strncmp(lex_start, keywords[i].word, (size_t)length) == 0)
152+ return make_token(keywords[i].type);
153+ }
142154 return make_token(TOKEN_IDENTIFIER);
143155 }
144156
@@ -155,13 +167,6 @@ Token lexer_next_token(void) {
155167 if (c == '"') return string_token();
156168
157169 switch (c) {
158- case '.': return make_token(TOKEN_DOT);
159- case '@':
160- if (strncmp(lex_current, "import", 6) == 0 && !(isalnum((unsigned char)lex_current[6]) || lex_current[6] == '_')) {
161- lex_current += 6;
162- return make_token(TOKEN_IMPORT);
163- }
164- return error_token("expected 'import' after '@'");
165170 case ';': return make_token(TOKEN_SEMICOLON);
166171 case ',': return make_token(TOKEN_COMMA);
167172 case ':': return make_token(TOKEN_COLON);
@@ -172,23 +177,18 @@ Token lexer_next_token(void) {
172177 case '-':
173178 if (match('-')) return make_token(TOKEN_MINUS_MINUS);
174179 return make_token(match('=') ? TOKEN_MINUS_EQUAL : TOKEN_MINUS);
175- case '*': return make_token(match('=') ? TOKEN_STAR_EQUAL : TOKEN_STAR);
180+ case '*':
181+ if (match('*')) return make_token(TOKEN_STAR_STAR);
182+ return make_token(match('=') ? TOKEN_STAR_EQUAL : TOKEN_STAR);
176183 case '/': return make_token(match('=') ? TOKEN_SLASH_EQUAL : TOKEN_SLASH);
177184 case '%': return make_token(TOKEN_PERCENT);
178- case '&': return make_token(TOKEN_AMP);
179- case '|': return make_token(TOKEN_PIPE);
180- case '^': return make_token(TOKEN_CARET);
181- case '~': return make_token(TOKEN_TILDE);
182185 case '(': return make_token(TOKEN_LPAREN);
183186 case ')': return make_token(TOKEN_RPAREN);
184187 case '{': return make_token(TOKEN_LBRACE);
185188 case '}': return make_token(TOKEN_RBRACE);
186189 case '[': return make_token(TOKEN_LBRACKET);
187190 case ']': return make_token(TOKEN_RBRACKET);
188191 case '=': return make_token(match('=') ? TOKEN_EQUAL_EQUAL : TOKEN_EQUAL);
189- // Bare '!' is no longer valid on its own -- logical negation is the
190- // 'not' keyword now (see identifier()). '!=' (inequality) stays
191- // symbolic either way.
192192 case '!':
193193 if (match('=')) return make_token(TOKEN_BANG_EQUAL);
194194 return error_token("unexpected character");
@@ -198,6 +198,11 @@ Token lexer_next_token(void) {
198198 case '>':
199199 if (match('>')) return make_token(TOKEN_SHR);
200200 return make_token(match('=') ? TOKEN_GREATER_EQUAL : TOKEN_GREATER);
201+ case '&': return make_token(TOKEN_AMP);
202+ case '|': return make_token(TOKEN_PIPE);
203+ case '^': return make_token(TOKEN_CARET);
204+ case '~': return make_token(TOKEN_TILDE);
205+ case '.': return make_token(TOKEN_DOT);
201206 }
202207
203208 return error_token("unexpected character");

hearth/ashvm/src/lexer/O/.gitkeep → hearth/front/src/ir/D/.gitkeeprenamed

+0−0

No content changes. View the file on GitHub

hearth/ashvm/src/lexer/H/lexer.h → hearth/front/src/ir/H/ir_lex.hrenamed

+10−8
@@ -1,5 +1,10 @@
1-#ifndef LEXER_H
2-#define LEXER_H
1+#ifndef ASH_IR_LEX_H
2+#define ASH_IR_LEX_H
3+
4+/* Lexer for the internal IR: the text form hearth/front lowers new Ash to and
5+ * that the ashvm compiler and the kiln code generator consume. It is not Ash
6+ * source syntax and no tool reads it from a file or from the user; the only
7+ * producer is jlower. Shared by ashvm and kiln so neither carries its own lexer. */
38
49 typedef enum {
510 TOKEN_SAY,
@@ -9,7 +14,6 @@ typedef enum {
914 TOKEN_DURING,
1015 TOKEN_EACH,
1116 TOKEN_IN,
12- TOKEN_IMPORT,
1317 TOKEN_FORGE,
1418 TOKEN_YIELD,
1519 TOKEN_ATTEMPT,
@@ -22,7 +26,6 @@ typedef enum {
2226 TOKEN_NONE,
2327 TOKEN_IDENTIFIER,
2428 TOKEN_STRING,
25- TOKEN_IMPORT_PATH,
2629 TOKEN_EQUAL,
2730 TOKEN_EQUAL_EQUAL,
2831 TOKEN_BANG_EQUAL,
@@ -60,6 +63,7 @@ typedef enum {
6063 TOKEN_SLASH_EQUAL,
6164 TOKEN_PLUS_PLUS,
6265 TOKEN_MINUS_MINUS,
66+ TOKEN_STAR_STAR,
6367 TOKEN_NUMBER,
6468 TOKEN_SEMICOLON,
6569 TOKEN_EOF,
@@ -73,17 +77,15 @@ typedef struct {
7377 int line;
7478 } Token;
7579
76-void lexer_init(const char *source);
77-Token lexer_next_token(void);
78-
7980 typedef struct {
8081 const char *start;
8182 const char *current;
8283 int line;
8384 } LexerState;
8485
86+void lexer_init(const char *source);
87+Token lexer_next_token(void);
8588 LexerState lexer_save_state(void);
8689 void lexer_restore_state(LexerState state);
87-Token lexer_scan_import_path(void);
8890
8991 #endif

hearth/front/src/ir/O/.gitkeepadded

+0−0

No content changes. View the file on GitHub

hearth/front/src/jdriver/C/jdriver.cadded

+49−0
@@ -0,0 +1,49 @@
1+#include <stdio.h>
2+
3+#include "jcommon/H/jcommon.h"
4+#include "jdriver/H/jdriver.h"
5+#include "jlower/H/jlower.h"
6+#include "jparse/H/jparse.h"
7+#include "jtypes/H/jtypes.h"
8+
9+static JClass *load_and_check(const char *path, const char *src) {
10+ Node *head = jparse_unit(path, src);
11+ jt_init_default_roots(path, head->name);
12+ JClass *entry = jt_load_unit(path, src);
13+ jt_check_all();
14+ if (j_error_count()) {
15+ fprintf(stderr, "%d error(s)\n", j_error_count());
16+ return NULL;
17+ }
18+ return entry;
19+}
20+
21+char *jdriver_compile_source(const char *path, const char *src, int wrap_int) {
22+ jl_wrap_int = wrap_int;
23+ JClass *entry = load_and_check(path, src);
24+ if (!entry) return NULL;
25+ char *ir = jlower_program(entry);
26+ if (!ir || j_error_count()) {
27+ fprintf(stderr, "%d error(s)\n", j_error_count());
28+ return NULL;
29+ }
30+ return ir;
31+}
32+
33+char *jdriver_compile_file(const char *path, int wrap_int) {
34+ char *src = j_read_file(path);
35+ if (!src) {
36+ fprintf(stderr, "error: cannot read %s\n", path);
37+ return NULL;
38+ }
39+ return jdriver_compile_source(path, src, wrap_int);
40+}
41+
42+int jdriver_check_file(const char *path) {
43+ char *src = j_read_file(path);
44+ if (!src) {
45+ fprintf(stderr, "error: cannot read %s\n", path);
46+ return 1;
47+ }
48+ return load_and_check(path, src) ? 0 : 1;
49+}

hearth/front/src/jdriver/H/jdriver.hadded

+24−0
@@ -0,0 +1,24 @@
1+#ifndef JDRIVER_H
2+#define JDRIVER_H
3+
4+/* The single entry point ashvm and kiln use to turn Ash source into something they can execute.
5+ *
6+ * The pipeline is: lex and parse (jlex, jparse) -> type-check (jtypes) -> lower (jlower). The result is
7+ * the internal IR (see ir/H/ir_lex.h): a unit of top-level `forge` and `local` declarations in which
8+ * `forge start()` is the program entry. The IR is never exposed to users; it is passed in memory from
9+ * this module to the ashvm compiler or the kiln code generator.
10+ *
11+ * Ownership: the returned text lives in the front end's arena and is never freed.
12+ * Errors: diagnostics go to stderr; a failed compilation returns NULL (or non-zero). */
13+
14+/* Parses, checks and lowers the program whose entry file is `path`. `wrap_int` selects Java's 32-bit
15+ * int overflow (about 2x slower arithmetic loops). */
16+char *jdriver_compile_file(const char *path, int wrap_int);
17+
18+/* Same for source text already in memory; `path` names it in diagnostics and anchors package roots. */
19+char *jdriver_compile_source(const char *path, const char *src, int wrap_int);
20+
21+/* Parses and type-checks only. Returns 0 when the program is well-typed. */
22+int jdriver_check_file(const char *path);
23+
24+#endif

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

+1−5
@@ -404,12 +404,8 @@ char *jlower_program(JClass *entry) {
404404 if (j_error_count()) return NULL;
405405 /* header + the module without anything unreachable from start() */
406406 JBuf final = {0};
407- j_buf_add(&final, "// Generated by ashfront from new-Ash source. Do not edit.\n");
407+ j_buf_add(&final, "// Internal IR generated by hearth/front. Not Ash source.\n");
408408 j_buf_add(&final, prelude.data ? prelude.data : "");
409409 j_buf_add(&final, out.data);
410410 return prune_module(final.data);
411411 }
412-
413-char *jlower_driver(const char *module_name) {
414- return j_fmt("@import <./%s> as g;\ng.start();\n", module_name);
415-}

hearth/front/src/jlower/H/jlower.hmodified

+3−6
@@ -3,9 +3,9 @@
33
44 #include "jtypes/H/jtypes.h"
55
6-/* Lowering of a checked new-Ash program to old-Ash source for the existing ashvm
7- * (MIGRATION.md Phase 4). The result is an *imported module* (top-level `local`s of an
8- * imported file are visible to its functions) plus a two-line driver.
6+/* Lowering of a checked new-Ash program to the internal IR (ir/H/ir_lex.h) that ashvm and kiln consume.
7+ * The result is one unit of top-level `forge` and `local` declarations; `forge start()` is the program
8+ * entry, and the consumer calls it. The IR is a compiler-internal representation, not Ash source.
99 *
1010 * Ownership: all returned strings live in the front end's arena and are never freed.
1111 * Errors: constructs the backend cannot express yet are reported through cerr(); the
@@ -18,7 +18,4 @@ extern int jl_wrap_int;
1818 /* Lowers every loaded class; `entry` supplies the class holding main. Returns the module text. */
1919 char *jlower_program(JClass *entry);
2020
21-/* The two-line driver that imports the module file `module_name` and starts it. */
22-char *jlower_driver(const char *module_name);
23-
2421 #endif

hearth/front/src/jrepl/C/jrepl.cadded

+148−0
@@ -0,0 +1,148 @@
1+#include <ctype.h>
2+#include <string.h>
3+
4+#include "jcommon/H/jcommon.h"
5+#include "jrepl/H/jrepl.h"
6+
7+struct JRepl {
8+ JBuf imports;
9+ JBuf types;
10+ JBuf methods;
11+ JBuf stmts;
12+};
13+
14+JRepl *jrepl_new(void) { return j_alloc(sizeof(JRepl)); }
15+
16+static const char *skip_ws(const char *p) {
17+ while (*p && isspace((unsigned char)*p)) p++;
18+ return p;
19+}
20+
21+static int is_ident_char(char c) { return isalnum((unsigned char)c) || c == '_'; }
22+
23+/* length of the identifier-or-keyword at p, 0 if none */
24+static size_t word_len(const char *p) {
25+ size_t n = 0;
26+ while (is_ident_char(p[n])) n++;
27+ return n;
28+}
29+
30+static int word_is(const char *p, size_t n, const char *w) { return strlen(w) == n && strncmp(p, w, n) == 0; }
31+
32+static int is_modifier(const char *p, size_t n) {
33+ static const char *mods[] = { "public", "private", "protected", "static", "final", "abstract", "sealed", "native" };
34+ for (size_t i = 0; i < sizeof(mods) / sizeof(mods[0]); i++)
35+ if (word_is(p, n, mods[i])) return 1;
36+ return 0;
37+}
38+
39+static int is_statement_keyword(const char *p, size_t n) {
40+ static const char *kws[] = { "new", "return", "throw", "else", "case", "default", "if", "while", "for", "do",
41+ "switch", "try", "break", "continue", "this", "super", "var" };
42+ for (size_t i = 0; i < sizeof(kws) / sizeof(kws[0]); i++)
43+ if (word_is(p, n, kws[i])) return 1;
44+ return 0;
45+}
46+
47+/* Skips a type reference at p (qualified name, balanced <...>, [] pairs, trailing ?); NULL if p is not one. */
48+static const char *skip_type(const char *p) {
49+ size_t n = word_len(p);
50+ if (!n) return NULL;
51+ p += n;
52+ while (*p == '.' && word_len(p + 1)) p += 1 + word_len(p + 1);
53+ if (*p == '<') {
54+ int depth = 0;
55+ for (; *p; p++) {
56+ if (*p == '<') depth++;
57+ else if (*p == '>' && --depth == 0) { p++; break; }
58+ else if (!(is_ident_char(*p) || strchr(" ,.?[]&", *p))) return NULL;
59+ }
60+ if (depth) return NULL;
61+ }
62+ for (;;) {
63+ const char *q = skip_ws(p);
64+ if (q[0] == '[' && skip_ws(q + 1)[0] == ']') { p = skip_ws(q + 1) + 1; continue; }
65+ if (q[0] == '?') { p = q + 1; continue; }
66+ break;
67+ }
68+ return p;
69+}
70+
71+static int ends_with_terminator(const char *line) {
72+ size_t n = strlen(line);
73+ while (n && isspace((unsigned char)line[n - 1])) n--;
74+ return n && (line[n - 1] == ';' || line[n - 1] == '}');
75+}
76+
77+JReplKind jrepl_classify(const char *line) {
78+ const char *p = skip_ws(line);
79+ size_t n = word_len(p);
80+ if (word_is(p, n, "import")) return JREPL_IMPORT;
81+ const char *q = p;
82+ while (n && is_modifier(q, n)) { q = skip_ws(q + n); n = word_len(q); }
83+ if (word_is(q, n, "class") || word_is(q, n, "interface") || word_is(q, n, "enum")) return JREPL_TYPE;
84+ if (word_is(q, n, "record") && word_len(skip_ws(q + n))) return JREPL_TYPE;
85+ if (n && !is_statement_keyword(q, n)) {
86+ const char *after = skip_type(q);
87+ if (after && after != q && isspace((unsigned char)*after)) {
88+ const char *name = skip_ws(after);
89+ size_t nl = word_len(name);
90+ if (nl && skip_ws(name + nl)[0] == '(') return JREPL_METHOD;
91+ }
92+ }
93+ return ends_with_terminator(line) ? JREPL_STATEMENT : JREPL_EXPRESSION;
94+}
95+
96+static char *strip_semicolons(const char *line) {
97+ char *s = j_strdup(line);
98+ size_t n = strlen(s);
99+ while (n && (isspace((unsigned char)s[n - 1]) || s[n - 1] == ';')) s[--n] = 0;
100+ return s;
101+}
102+
103+/* the text a line contributes to the hidden class or to main */
104+static char *body_of(const char *line, JReplKind kind, int print_value) {
105+ if (kind == JREPL_EXPRESSION) {
106+ char *e = strip_semicolons(line);
107+ return print_value ? j_fmt("System.out.println(%s);\n", e) : j_fmt("%s;\n", e);
108+ }
109+ if (kind == JREPL_METHOD) {
110+ const char *p = skip_ws(line);
111+ for (;;) {
112+ size_t n = word_len(p);
113+ if (word_is(p, n, "static")) return j_fmt("%s\n", line);
114+ if (!is_modifier(p, n)) break;
115+ p = skip_ws(p + n);
116+ }
117+ return j_fmt("static %s\n", line);
118+ }
119+ return j_fmt("%s\n", line);
120+}
121+
122+char *jrepl_source(JRepl *r, const char *line, JReplKind kind, int print_value) {
123+ JBuf out = {0};
124+ char *body = body_of(line, kind, print_value);
125+ if (kind == JREPL_IMPORT) j_buf_add(&out, body);
126+ j_buf_add(&out, r->imports.data ? r->imports.data : "");
127+ j_buf_add(&out, r->types.data ? r->types.data : "");
128+ if (kind == JREPL_TYPE) j_buf_add(&out, body);
129+ j_buf_add(&out, "class Repl {\n");
130+ j_buf_add(&out, r->methods.data ? r->methods.data : "");
131+ if (kind == JREPL_METHOD) j_buf_add(&out, body);
132+ j_buf_add(&out, "public static void main(String[] args) {\n");
133+ j_buf_add(&out, r->stmts.data ? r->stmts.data : "");
134+ j_buf_add(&out, "System.out.println(\"\\x01ash-repl\\x01\");\n");
135+ if (kind == JREPL_STATEMENT || kind == JREPL_EXPRESSION) j_buf_add(&out, body);
136+ j_buf_add(&out, "}\n}\n");
137+ return out.data;
138+}
139+
140+void jrepl_commit(JRepl *r, const char *line, JReplKind kind, int print_value) {
141+ char *body = body_of(line, kind, print_value);
142+ switch (kind) {
143+ case JREPL_IMPORT: j_buf_add(&r->imports, body); break;
144+ case JREPL_TYPE: j_buf_add(&r->types, body); break;
145+ case JREPL_METHOD: j_buf_add(&r->methods, body); break;
146+ default: j_buf_add(&r->stmts, body); break;
147+ }
148+}

hearth/front/src/jrepl/D/.gitkeepadded

+0−0

No content changes. View the file on GitHub

hearth/front/src/jrepl/H/jrepl.hadded

+39−0
@@ -0,0 +1,39 @@
1+#ifndef JREPL_H
2+#define JREPL_H
3+
4+/* Session model for the ashvm REPL (MIGRATION.md D9: the REPL is an explicit exception to the
5+ * class-per-file rule; each accepted line is wrapped into a hidden class body).
6+ *
7+ * A session is the accepted history: imports, type declarations, static methods and statements. To run a
8+ * line, jrepl_source() builds a complete program in which the history statements are replayed, then a
9+ * marker line is printed, then the new line runs. The caller discards everything the program prints
10+ * before JREPL_MARKER, so only the new line's output is shown. Because state is rebuilt by replay,
11+ * non-idempotent side effects of earlier lines (file writes, stdin reads, clocks) happen again.
12+ *
13+ * Ownership: strings live in the front end's arena and are never freed. A session holds no OS
14+ * resources and nothing here terminates the process; compile errors surface only when the returned
15+ * source is compiled in a child process. */
16+
17+#define JREPL_MARKER "\x01" "ash-repl" "\x01"
18+
19+typedef enum {
20+ JREPL_IMPORT, /* import ash.util.List; */
21+ JREPL_TYPE, /* class / interface / enum / record declaration */
22+ JREPL_METHOD, /* Type name(params) { ... } becomes a static method of the hidden class */
23+ JREPL_STATEMENT, /* ends with ';' or '}' */
24+ JREPL_EXPRESSION /* anything else: evaluated and printed */
25+} JReplKind;
26+
27+typedef struct JRepl JRepl;
28+
29+JRepl *jrepl_new(void);
30+JReplKind jrepl_classify(const char *line);
31+
32+/* Program text that replays the history and then runs `line`. For JREPL_EXPRESSION, `print_value`
33+ * selects between printing the value and running it as a statement (for void calls). */
34+char *jrepl_source(JRepl *r, const char *line, JReplKind kind, int print_value);
35+
36+/* Adds an accepted line to the history; call only after jrepl_source() ran without error. */
37+void jrepl_commit(JRepl *r, const char *line, JReplKind kind, int print_value);
38+
39+#endif

hearth/front/src/jrepl/O/.gitkeepadded

+0−0

No content changes. View the file on GitHub

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

+1−1
@@ -208,7 +208,7 @@ void check_method_body(JClass *c, JMethod *m) {
208208 for (int i = 0; i < m->params.count; i++) {
209209 JParam *p = m->params.items[i];
210210 Node *pn = (d->l1.count > i) ? d->l1.items[i] : d;
211- JLocal *l = declare_local(&cx, pn, p->name, p->type, (d->l1.count > i) ? pn->flags : 0, 1);
211+ JLocal *l = jt_declare_local(&cx, pn, p->name, p->type, (d->l1.count > i) ? pn->flags : 0, 1);
212212 l->has_init = 1;
213213 }
214214 Node *body = d->b;

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

+3−3
@@ -75,7 +75,7 @@ JLocal *lookup_local(Ctx *cx, const char *name) {
7575 return NULL;
7676 }
7777
78-JLocal *declare_local(Ctx *cx, Node *at, const char *name, JType *t, int flags, int is_param) {
78+JLocal *jt_declare_local(Ctx *cx, Node *at, const char *name, JType *t, int flags, int is_param) {
7979 for (int i = cx->locals.count - 1; i >= 0; i--) {
8080 JLocal *o = cx->locals.items[i];
8181 if (strcmp(o->name, name) == 0) {
@@ -657,7 +657,7 @@ JType *check_lambda(Ctx *cx, Node *lam, JType *target, JType **result) {
657657 } else if (pt->kind == TY_TVAR && pt->tv->owner_m) {
658658 pt = ty_erase_bound(pt); /* uninferred method type variable */
659659 }
660- JLocal *l = declare_local(cx, p, p->name, pt, p->flags, 1);
660+ JLocal *l = jt_declare_local(cx, p, p->name, pt, p->flags, 1);
661661 l->has_init = 1;
662662 }
663663 JList rts = {0};
@@ -1223,7 +1223,7 @@ static JType *check_instanceof(Ctx *cx, Node *n) {
12231223 else if (!castable(e, t)) cerr(n, "incompatible types: %s can never be an instance of %s", ty_str(e), ty_str(t));
12241224 }
12251225 if (n->name) {
1226- JLocal *l = declare_local(cx, n, n->name, ty_with_null(t, 0), 0, 0);
1226+ JLocal *l = jt_declare_local(cx, n, n->name, ty_with_null(t, 0), 0, 0);
12271227 l->is_temp = 1;
12281228 l->has_init = 1;
12291229 n->sym = l;

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

+4−4
@@ -119,7 +119,7 @@ static void check_local(Ctx *cx, Node *n) {
119119 }
120120 }
121121 n->type = t;
122- JLocal *l = declare_local(cx, n, n->name, t, n->flags, 0);
122+ JLocal *l = jt_declare_local(cx, n, n->name, t, n->flags, 0);
123123 l->has_init = n->b != NULL;
124124 n->sym = l;
125125 n->rk = R_LOCAL;
@@ -202,7 +202,7 @@ JType *check_switch(Ctx *cx, Node *n, JType *expected) {
202202 }
203203 j_list_push(&type_seen, pt->cls);
204204 }
205- JLocal *l = declare_local(cx, lb, lb->name, ty_with_null(pt, 0), 0, 0);
205+ JLocal *l = jt_declare_local(cx, lb, lb->name, ty_with_null(pt, 0), 0, 0);
206206 l->has_init = 1;
207207 lb->sym = l;
208208 } else if (lb->kind == N_LIT_NULL) {
@@ -339,7 +339,7 @@ static void check_try(Ctx *cx, Node *n) {
339339 int mark;
340340 push_scope(cx, &mark);
341341 if (vt) {
342- JLocal *l = declare_local(cx, c, c->name, vt, 0, 0);
342+ JLocal *l = jt_declare_local(cx, c, c->name, vt, 0, 0);
343343 l->has_init = 1;
344344 }
345345 check_block_body(cx, c->b);
@@ -469,7 +469,7 @@ void check_stmt(Ctx *cx, Node *n) {
469469 }
470470 int mark;
471471 push_scope(cx, &mark);
472- JLocal *l = declare_local(cx, n, n->name, vt, 0, 0);
472+ JLocal *l = jt_declare_local(cx, n, n->name, vt, 0, 0);
473473 l->has_init = 1;
474474 n->sym = l;
475475 n->type = vt;

hearth/front/src/jtypes/H/check.hmodified

+1−1
@@ -53,7 +53,7 @@ JType *check_expr_ex(Ctx *cx, Node *n, JType *expected, int allow_type); /* allo
5353 JType *check_cond(Ctx *cx, Node *n);
5454 int check_assignable(Ctx *cx, Node *expr, JType *from, JType *to, const char *what);
5555 void check_coerce(Ctx *cx, Node *expr, JType *to, const char *what); /* check_expr + assign check */
56-JLocal *declare_local(Ctx *cx, Node *at, const char *name, JType *t, int flags, int is_param);
56+JLocal *jt_declare_local(Ctx *cx, Node *at, const char *name, JType *t, int flags, int is_param);
5757 JLocal *lookup_local(Ctx *cx, const char *name);
5858 void report_throw(Ctx *cx, Node *at, JType *exc);
5959 int accessible(Ctx *cx, JClass *owner, int flags);