← 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
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=c112
-#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 package54
- // installed by forgepack (see ideas/ash_modules.md) only gets55
- // 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=c112
-#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 1611
-#define MAX_VM_IMPORT_DEPTH 812
-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 every88
- // later use site that points diagnostics/lexer state back into it,89
- // which can be arbitrarily far ahead in the importing program. The90
- // compiler is a one-shot host process; leaking this until it exits is91
- // 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 "only102
-// fn/let allowed" restriction by checking get_import_namespace()'s length103
-// AND that block_depth == 0 (see stmt.c) -- so it fires only for this104
-// file's genuine top level, never for statements nested inside one of its105
-// own function bodies.106
-static void parse_imported_file_body(void) {107
- // Each imported file gets its OWN fresh import-block window: OPEN (not108
- // closed), so the imported file's own leading @import statements (if109
- // any -- e.g. a diamond-shaped or chained import) are allowed, exactly110
- // like the entry file's. Calling imports_close_block() here would do111
- // the opposite (close it), which is wrong -- statement() itself closes112
- // the window the moment it dispatches this file's own first113
- // 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 in149
- // imported_files but STILL on resolving_stack is mid-parse (its150
- // registry entry was added before recursing, same as every entry151
- // here) -- that's a genuine cycle, not a finished diamond import.152
- // Checking the registry first would silently treat a still-parsing153
- // file as "already done" and swallow the cycle instead of reporting154
- // it. Only a registry hit with NO matching resolving_stack entry is a155
- // 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 do160
-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 the168
- // namespace and surrounding literal text with no truncation, so169
- // -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 will195
- // clobber, mirroring compiler/C/state.c's own save-recurse-restore196
- // pattern for locals (the C call stack gives correct nesting for197
- // 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 parse222
- // 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.h3
-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.h7
-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_H2
-#define ASH_VM_COMPILER_IMPORT_PATHS_H3
-#include <limits.h>4
-5
-// Resolves the raw text inside `@import <...>` to a canonicalized6
-// (realpath()'d) absolute filesystem path.7
-//8
-// raw_path/raw_len: the text between '<' and '>', exactly as scanned by9
-// lexer_scan_import_path (NOT null-terminated -- callers must use len).10
-// importing_file_dir: directory of the file containing this @import11
-// statement (relative imports resolve against this).12
-// project_root_dir: directory of the entry .ash file passed on the13
-// command line (library imports resolve against this, regardless of14
-// 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 failure18
-// (message_out is filled with a short human-readable reason, e.g.19
-// "could not resolve '<path>'" or "library import name must not contain20
-// '.' 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's28
-// own basename with the trailing ".ash" stripped. Works uniformly for29
-// both relative and library imports because library entry files are30
-// required to be named "<libname>/<libname>.ash" -- the folder name and31
-// 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
-#endifhearth/ashvm/src/compiler/H/stmt_import.hremoved
+0−8@@ -1,8 +0,0 @@1
-#ifndef ASH_VM_COMPILER_STMT_IMPORT_H2
-#define ASH_VM_COMPILER_STMT_IMPORT_H3
-4
-void imports_init(const char *entry_file_path);5
-void compile_import_stmt(void); // assumes statement() already consumed TOKEN_IMPORT6
-void imports_close_block(void);7
-8
-#endifhearth/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 immediately18
-// after the '<' the parser just confirmed (via current.type==TOKEN_LESS)19
-// WITHOUT calling advance_token() on it -- so `current` here still points20
-// at the first path character, untouched by normal tokenization. Scans21
-// raw bytes up to '>', consuming the '>' too, so the parser's next22
-// 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.h3
-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.h3
-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_H2
-#define ASHVM_LEXER_IMPORT_H3
-#include "lexer/H/lexer.h"4
-5
-// Cross-file link between lexer.c (the tokenizer's cursor state and its6
-// low-level scan primitives) and lexer_import.c (state save/restore plus7
-// import-path scanning, split out here to keep lexer.c under the8
-// project's ~200-line guideline) -- not part of the public API. The9
-// public entry points the two files implement together10
-// (lexer_save_state/lexer_restore_state/lexer_scan_import_path) stay11
-// declared in lexer.h exactly as before, so callers outside the lexer12
-// directory don't need to change anything. Mirrors kiln's identical split13
-// 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.h17
-// already declares its own global `Token current`, and since these now18
-// need external linkage to be shared across the two .c files, a second19
-// 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
-#endifhearth/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-static7
-// before) -- shared with lexer_import.c now, and a plain `current` would8
-// 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 backslash95
- advance(); // skip the escaped character itself, so a \" doesn't end the string early116
+ 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 as138
- // the symbol it replaces, so unary()'s existing TOKEN_BANG handling in139
- // 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 the190
- // 'not' keyword now (see identifier()). '!=' (inequality) stays191
- // 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−0No 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_H2
-#define LEXER_H1
+#ifndef ASH_IR_LEX_H2
+#define ASH_IR_LEX_H3
+4
+/* Lexer for the internal IR: the text form hearth/front lowers new Ash to and5
+ * that the ashvm compiler and the kiln code generator consume. It is not Ash6
+ * source syntax and no tool reads it from a file or from the user; the only7
+ * 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
#endifhearth/front/src/ir/O/.gitkeepadded
+0−0No 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_H2
+#define JDRIVER_H3
+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 is7
+ * the internal IR (see ir/H/ir_lex.h): a unit of top-level `forge` and `local` declarations in which8
+ * `forge start()` is the program entry. The IR is never exposed to users; it is passed in memory from9
+ * 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-bit15
+ * 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
+#endifhearth/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 ashvm7
- * (MIGRATION.md Phase 4). The result is an *imported module* (top-level `local`s of an8
- * 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 program8
+ * 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
#endifhearth/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−0No content changes. View the file on GitHub
hearth/front/src/jrepl/H/jrepl.hadded
+39−0@@ -0,0 +1,39 @@1
+#ifndef JREPL_H2
+#define JREPL_H3
+4
+/* Session model for the ashvm REPL (MIGRATION.md D9: the REPL is an explicit exception to the5
+ * 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 a8
+ * line, jrepl_source() builds a complete program in which the history statements are replayed, then a9
+ * marker line is printed, then the new line runs. The caller discards everything the program prints10
+ * 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 OS14
+ * resources and nothing here terminates the process; compile errors surface only when the returned15
+ * 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
+#endifhearth/front/src/jrepl/O/.gitkeepadded
+0−0No 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); /* allo5353
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);