feat: bux test runner, bitwise-not parsing, stdlib discovery hardening\n\n- Add 'bux test' runner: discovers tests/*.bux, builds each in a temp package, runs and reports PASS/FAIL.\n- Fix unary ~ parsing and C emission in self-hosted compiler.\n- Harden stdlib discovery (require Fs.bux, avoid system /lib) and lstat in directory traversal.\n- Selfhost loop passes: buxc2 builds src/ and produces identical C output + stripped binary.\n- Update PLAN.md with completed milestones.

This commit is contained in:
2026-06-12 22:19:13 +03:00
parent a668127721
commit 84df4bba9d
18 changed files with 21917 additions and 20942 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ SRC := bootstrap/main.nim
OUT := buxc OUT := buxc
BUILD_DIR := build BUILD_DIR := build
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ctfe async concurrency os_time process json iter EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ctfe async concurrency os_time process json iter trait_bounds channel sync jwt
.PHONY: all build dev debug test clean clean-all test-examples selfhost test-golden selfhost-loop lsp .PHONY: all build dev debug test clean clean-all test-examples selfhost test-golden selfhost-loop lsp
+8 -7
View File
@@ -919,10 +919,11 @@ func Main() -> int {
### Next Actions (Priority Order) ### Next Actions (Priority Order)
1.**Fix LIR backend type inference** — struct temps, undeclared vars, break/continue, duplicate declarations 1.**Fix LIR backend type inference** — struct temps, undeclared vars, break/continue, duplicate declarations
2.**All 26 examples passing** — bootstrap compiler builds and runs every example 2.**All 30 examples passing** — bootstrap and self-host compilers build and run every example
3.**Selfhost build working**`make selfhost` produces working `buxc2` 3.**Selfhost build working**`make selfhost` produces working `buxc2`
4. 🔄 **Selfhost loop verification**`buxc2` compiles `src/``buxc3` 4. **Selfhost loop verification**`buxc2` compiles `src/``buxc3`; C output and stripped binary identical
5. 🔄 **Golden tests for C codegen**Prevent future regressions in LIR → C 5. **Golden tests for C codegen**`make test-golden` passes for 8 critical examples
6.**`bux test` runner** — discovers `tests/*.bux`, builds each as a temp package, and reports PASS/FAIL
--- ---
@@ -941,9 +942,9 @@ func Main() -> int {
| Task | Status | Priority | Details | | Task | Status | Priority | Details |
|------|--------|----------|---------| |------|--------|----------|---------|
| `11.2.1` `buxc2 build` on `src/` | | P0 | Verify selfhost compiler can build itself | | `11.2.1` `buxc2 build` on `src/` | | P0 | Verify selfhost compiler can build itself |
| `11.2.2` Deterministic C codegen | | P0 | Remove non-deterministic ordering in `emitModule` | | `11.2.2` Deterministic C codegen | | P0 | Remove non-deterministic ordering in `emitModule` |
| `11.2.3` `make selfhost-loop` | | P0 | Makefile target: buxc2 → buxc3 → binary compare | | `11.2.3` `make selfhost-loop` | | P0 | Makefile target: buxc2 → buxc3 → binary compare |
| `11.2.4` Cross-module generics in selfhost | ⏳ | P1 | `Map<K,V>` and `Array<T>` from stdlib in selfhost context | | `11.2.4` Cross-module generics in selfhost | ⏳ | P1 | `Map<K,V>` and `Array<T>` from stdlib in selfhost context |
### 11.3 — Gradual Ownership (v0.5.0) ⭐⭐ ### 11.3 — Gradual Ownership (v0.5.0) ⭐⭐
@@ -958,7 +959,7 @@ func Main() -> int {
| Task | Status | Priority | Details | | Task | Status | Priority | Details |
|------|--------|----------|---------| |------|--------|----------|---------|
| `11.4.1` `bux test` runner | | P0 | Built-in test framework with assertions | | `11.4.1` `bux test` runner | | P0 | Built-in test framework with assertions |
| `11.4.2` `bux fmt` formatter | ⏳ | P1 | Auto-format Bux source | | `11.4.2` `bux fmt` formatter | ⏳ | P1 | Auto-format Bux source |
| `11.4.3` LSP prototype | ⏳ | P2 | Autocomplete, hover types, go-to-definition | | `11.4.3` LSP prototype | ⏳ | P2 | Autocomplete, hover types, go-to-definition |
| `11.4.4` `bux doc` generator | ⏳ | P2 | HTML from `///` doc comments | | `11.4.4` `bux doc` generator | ⏳ | P2 | HTML from `///` doc comments |
+2 -2
View File
@@ -675,7 +675,7 @@ static int bux_count_files_recursive(const char* dir, const char* ext, size_t ex
if (!path) continue; if (!path) continue;
snprintf(path, path_len, "%s/%s", dir, entry->d_name); snprintf(path, path_len, "%s/%s", dir, entry->d_name);
struct stat st; struct stat st;
if (stat(path, &st) == 0) { if (lstat(path, &st) == 0) {
if (S_ISDIR(st.st_mode)) { if (S_ISDIR(st.st_mode)) {
count += bux_count_files_recursive(path, ext, ext_len); count += bux_count_files_recursive(path, ext, ext_len);
} else { } else {
@@ -702,7 +702,7 @@ static int bux_collect_files_recursive(const char* dir, const char* ext, size_t
if (!path) continue; if (!path) continue;
snprintf(path, path_len, "%s/%s", dir, entry->d_name); snprintf(path, path_len, "%s/%s", dir, entry->d_name);
struct stat st; struct stat st;
if (stat(path, &st) == 0) { if (lstat(path, &st) == 0) {
if (S_ISDIR(st.st_mode)) { if (S_ISDIR(st.st_mode)) {
idx = bux_collect_files_recursive(path, ext, ext_len, result, idx); idx = bux_collect_files_recursive(path, ext, ext_len, result, idx);
free(path); free(path);
+2
View File
@@ -276,6 +276,7 @@ struct Decl {
isChecked: int, // @[Checked] attribute (0/1) isChecked: int, // @[Checked] attribute (0/1)
isDrop: int, // @[Drop] attribute (0/1) isDrop: int, // @[Drop] attribute (0/1)
isRelease: int, // @[Release] attribute (0/1) isRelease: int, // @[Release] attribute (0/1)
isConst: int, // const func (0/1)
// Names // Names
strValue: String, // decl name strValue: String, // decl name
strValue2: String, // interface name, dll name, module path strValue2: String, // interface name, dll name, module path
@@ -395,6 +396,7 @@ func Ast_MakeStmt(kind: int, line: uint32, col: uint32) -> Stmt {
func Ast_MakeDecl(kind: int, line: uint32, col: uint32) -> Decl { func Ast_MakeDecl(kind: int, line: uint32, col: uint32) -> Decl {
return Decl { kind: kind, line: line, column: col, isPublic: false, return Decl { kind: kind, line: line, column: col, isPublic: false,
isAsync: false, isChecked: 0, isDrop: 0, isRelease: 0, isConst: 0,
strValue: "", strValue2: "", strValue: "", strValue2: "",
typeParam0: "", typeParam1: "", typeParamCount: 0, typeParam0: "", typeParam1: "", typeParamCount: 0,
typeParam0Bound: "", typeParam1Bound: "" typeParam0Bound: "", typeParam1Bound: ""
+33 -14
View File
@@ -59,6 +59,7 @@ func CBackend_OpToC(op: int) -> String {
if op == tkAmpAmp { return "&&"; } if op == tkAmpAmp { return "&&"; }
if op == tkPipePipe { return "||"; } if op == tkPipePipe { return "||"; }
if op == tkBang { return "!"; } if op == tkBang { return "!"; }
if op == tkTilde { return "~"; }
if op == tkAmp { return "&"; } if op == tkAmp { return "&"; }
if op == tkPipe { return "|"; } if op == tkPipe { return "|"; }
if op == tkCaret { return "^"; } if op == tkCaret { return "^"; }
@@ -371,13 +372,23 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
// spawn Callee(args) // spawn Callee(args)
if kind == hSpawn { if kind == hSpawn {
if node.boolValue {
// Async spawn: callee is an async function
StringBuilder_Append(&cbe.sb, "bux_async_spawn("); StringBuilder_Append(&cbe.sb, "bux_async_spawn(");
StringBuilder_Append(&cbe.sb, node.strValue); StringBuilder_Append(&cbe.sb, node.strValue);
StringBuilder_Append(&cbe.sb, ")");
} else {
// Green-thread spawn: callee is a regular task function
StringBuilder_Append(&cbe.sb, "bux_task_spawn(");
StringBuilder_Append(&cbe.sb, node.strValue);
if node.child1 != null as *HirNode { if node.child1 != null as *HirNode {
StringBuilder_Append(&cbe.sb, ", (void*)"); StringBuilder_Append(&cbe.sb, ", (void*)");
CBE_EmitExpr(cbe, node.child1); CBE_EmitExpr(cbe, node.child1);
} else {
StringBuilder_Append(&cbe.sb, ", NULL");
} }
StringBuilder_Append(&cbe.sb, ")"); StringBuilder_Append(&cbe.sb, ")");
}
return; return;
} }
@@ -978,7 +989,27 @@ func CBackend_Generate(mod: *HirModule) -> String {
} }
vi = vi + 1; vi = vi + 1;
} }
// Emit tag enum for all enums (simple and algebraic) if !hasData {
// Simple enum: emit as plain C enum (no struct wrapper)
StringBuilder_Append(&cbe.sb, "typedef enum {\n");
vi = 0;
while vi < en.variantCount {
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, "_");
StringBuilder_Append(&cbe.sb, en.variants[vi].name);
if vi < en.variantCount - 1 {
StringBuilder_Append(&cbe.sb, ",");
}
StringBuilder_Append(&cbe.sb, "\n");
vi = vi + 1;
}
StringBuilder_Append(&cbe.sb, "} ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, ";\n\n");
} else {
// Algebraic enum: tag enum + data union + struct
// 1. Tag enum
StringBuilder_Append(&cbe.sb, "typedef enum {\n"); StringBuilder_Append(&cbe.sb, "typedef enum {\n");
vi = 0; vi = 0;
while vi < en.variantCount { while vi < en.variantCount {
@@ -995,19 +1026,7 @@ func CBackend_Generate(mod: *HirModule) -> String {
StringBuilder_Append(&cbe.sb, "} "); StringBuilder_Append(&cbe.sb, "} ");
StringBuilder_Append(&cbe.sb, en.name); StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, "_Tag;\n\n"); StringBuilder_Append(&cbe.sb, "_Tag;\n\n");
// 2. Data union
if !hasData {
// Simple enum: struct wrapper with only tag
StringBuilder_Append(&cbe.sb, "typedef struct {\n");
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, "_Tag tag;\n");
StringBuilder_Append(&cbe.sb, "} ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, ";\n\n");
} else {
// Algebraic enum: data union + struct (tag enum already emitted above)
// 1. Data union
StringBuilder_Append(&cbe.sb, "typedef union {\n"); StringBuilder_Append(&cbe.sb, "typedef union {\n");
vi = 0; vi = 0;
while vi < en.variantCount { while vi < en.variantCount {
+167 -21
View File
@@ -15,6 +15,10 @@ extern func bux_mkdir_if_needed(path: String) -> int;
extern func bux_run_nim(nim_file: String, out_bin: String) -> int; extern func bux_run_nim(nim_file: String, out_bin: String) -> int;
extern func bux_list_dir(dir: String, ext: String, out_count: *int) -> *String; extern func bux_list_dir(dir: String, ext: String, out_count: *int) -> *String;
extern func bux_system(cmd: String) -> int; extern func bux_system(cmd: String) -> int;
extern func bux_getenv(name: String) -> String;
extern func bux_setenv(name: String, value: String) -> int;
extern func bux_strlen(s: String) -> uint;
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
func ReadFile(path: String) -> String { func ReadFile(path: String) -> String {
return bux_read_file(path); return bux_read_file(path);
@@ -412,12 +416,18 @@ func Cli_CompileSource(source: String, sourceName: String) -> *HirModule {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
func Cli_FindStdlibDir(projectDir: String) -> String { func Cli_FindStdlibDir(projectDir: String) -> String {
// Allow tests and temp packages to inherit the parent project's stdlib.
let envPath: String = bux_getenv("BUX_STDLIB");
if !String_Eq(envPath, "") && DirExists(envPath) && FileExists(bux_path_join(envPath, "Fs.bux")) {
return envPath;
}
var path: String = bux_path_join(projectDir, "lib"); var path: String = bux_path_join(projectDir, "lib");
if DirExists(path) { return path; } if DirExists(path) && FileExists(bux_path_join(path, "Fs.bux")) { return path; }
path = bux_path_join(projectDir, "../lib"); path = bux_path_join(projectDir, "../lib");
if DirExists(path) { return path; } if DirExists(path) && FileExists(bux_path_join(path, "Fs.bux")) { return path; }
path = bux_path_join(projectDir, "../../lib"); path = bux_path_join(projectDir, "../../lib");
if DirExists(path) { return path; } if DirExists(path) && FileExists(bux_path_join(path, "Fs.bux")) { return path; }
return ""; return "";
} }
@@ -874,34 +884,170 @@ func Cli_Fmt(dir: String) -> int {
return 0; return 0;
} }
func Cli_FileNameFromPath(path: String) -> String {
let len: int = bux_strlen(path) as int;
var i: int = len - 1;
while i >= 0 {
let ch: String = bux_str_slice(path, i as uint, 1);
if String_Eq(ch, "/") {
return bux_str_slice(path, (i + 1) as uint, (len - i - 1) as uint);
}
i = i - 1;
}
return path;
}
func Cli_StripExtension(name: String) -> String {
let len: int = bux_strlen(name) as int;
var i: int = len - 1;
while i >= 0 {
let ch: String = bux_str_slice(name, i as uint, 1);
if String_Eq(ch, ".") {
return bux_str_slice(name, 0, i as uint);
}
i = i - 1;
}
return name;
}
func Cli_Test(projectDir: String) -> int { func Cli_Test(projectDir: String) -> int {
Print("Testing project: "); Print("Testing project: ");
PrintLine(projectDir); PrintLine(projectDir);
// Build the project first // Build and run the project's own Main first
let rc: int = Cli_BuildProject(projectDir, "", false); let mainRc: int = Cli_BuildProject(projectDir, "", false);
if rc != 0 { if mainRc != 0 {
PrintLine("Test build failed"); PrintLine("Main test build failed");
return rc; return mainRc;
} }
// Run the resulting binary
let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml")); let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml"));
var outName: String = man.name; var mainName: String = man.name;
if String_Eq(outName, "") { outName = "bux_out"; } if String_Eq(mainName, "") { mainName = "bux_out"; }
let outBin: String = bux_path_join(bux_path_join(projectDir, "build"), outName); let mainBin: String = bux_path_join(bux_path_join(projectDir, "build"), mainName);
if !FileExists(outBin) { if !FileExists(mainBin) {
Print("Error: test binary not found: "); Print("Error: test binary not found: ");
PrintLine(outBin); PrintLine(mainBin);
return 1; return 1;
} }
let result: int = bux_system(outBin); let mainResult: int = bux_system(mainBin);
if result == 0 { if mainResult != 0 {
PrintLine("Tests passed"); Print("Main tests failed (exit code ");
} else { PrintInt(mainResult as int64);
Print("Tests failed (exit code ");
PrintInt(result as int64);
PrintLine(")"); PrintLine(")");
return mainResult;
} }
return result; PrintLine("Main tests passed");
// Propagate the project's stdlib to temp test packages.
let stdlibDir: String = Cli_FindStdlibDir(projectDir);
if !String_Eq(stdlibDir, "") {
discard bux_setenv("BUX_STDLIB", stdlibDir);
}
// Run individual test files from tests/ directory
let testsDir: String = bux_path_join(projectDir, "tests");
if !DirExists(testsDir) {
PrintLine("No tests/ directory found");
return 0;
}
var testCount: int = 0;
let testFiles: *String = bux_list_dir(testsDir, ".bux", &testCount);
if testCount == 0 {
PrintLine("No .bux test files found in tests/");
return 0;
}
var passed: int = 0;
var failed: int = 0;
var i: int = 0;
while i < testCount {
let testPath: String = testFiles[i];
let fileName: String = Cli_FileNameFromPath(testPath);
let testName: String = Cli_StripExtension(fileName);
Print(" Test: ");
Print(testName);
Print(" ... ");
// Create temp package for this test file
let tmpDir: String = bux_path_join(bux_path_join(projectDir, "build"), "_test_tmp");
let tmpSrc: String = bux_path_join(tmpDir, "src");
discard bux_mkdir_if_needed(tmpDir);
discard bux_mkdir_if_needed(tmpSrc);
let source: String = ReadFile(testPath);
if String_Eq(source, "") {
PrintLine("FAIL (cannot read test file)");
failed = failed + 1;
i = i + 1;
continue;
}
let tmpMain: String = bux_path_join(tmpSrc, "Main.bux");
if !WriteFile(tmpMain, source) {
PrintLine("FAIL (cannot write temp Main.bux)");
failed = failed + 1;
i = i + 1;
continue;
}
let tmpToml: String = bux_path_join(tmpDir, "bux.toml");
var tomlContent: String = "[Package]\nName = \"_test_tmp\"\nVersion = \"0.1.0\"\nType = \"bin\"\n\n[Build]\nOutput = \"Bin\"\n";
if !WriteFile(tmpToml, tomlContent) {
PrintLine("FAIL (cannot write temp bux.toml)");
failed = failed + 1;
i = i + 1;
continue;
}
// Copy runtime shims so the temp package links even when nested deep.
if !String_Eq(stdlibDir, "") {
let rtDir: String = bux_path_join(bux_path_parent(stdlibDir), "rt");
let tmpRtDir: String = bux_path_join(tmpDir, "rt");
discard bux_mkdir_if_needed(tmpRtDir);
let rtSrc: String = bux_path_join(rtDir, "runtime.c");
let rtDst: String = bux_path_join(tmpRtDir, "runtime.c");
if FileExists(rtSrc) && !FileExists(rtDst) {
discard WriteFile(rtDst, ReadFile(rtSrc));
}
let ioSrc: String = bux_path_join(rtDir, "io.c");
let ioDst: String = bux_path_join(tmpRtDir, "io.c");
if FileExists(ioSrc) && !FileExists(ioDst) {
discard WriteFile(ioDst, ReadFile(ioSrc));
}
}
let buildRc: int = Cli_BuildProject(tmpDir, "", false);
if buildRc != 0 {
PrintLine("FAIL (build error)");
failed = failed + 1;
i = i + 1;
continue;
}
let testBin: String = bux_path_join(bux_path_join(tmpDir, "build"), "_test_tmp");
if !FileExists(testBin) {
PrintLine("FAIL (test binary not found)");
failed = failed + 1;
i = i + 1;
continue;
}
let runRc: int = bux_system(testBin);
if runRc == 0 {
PrintLine("PASS");
passed = passed + 1;
} else {
Print("FAIL (exit ");
PrintInt(runRc as int64);
PrintLine(")");
failed = failed + 1;
}
i = i + 1;
}
Print("Tests: ");
PrintInt(passed as int64);
Print(" passed, ");
PrintInt(failed as int64);
PrintLine(" failed");
if failed > 0 { return 1; }
return 0;
} }
func Cli_BuildProject(projectDir: String, targetTriple: String, isRelease: bool) -> int { func Cli_BuildProject(projectDir: String, targetTriple: String, isRelease: bool) -> int {
+496 -50
View File
@@ -349,6 +349,26 @@ func Lcx_GenerateFuncInstance(ctx: *LowerCtx, genDecl: *Decl, typeArg0: String,
return mangled; return mangled;
} }
// Strip type-arg suffix from a mangled generic instance name.
// E.g. ("Box_int", "int", "", 1) -> "Box"; ("Pair_int_String", "int", "String", 2) -> "Pair".
func Lcx_StripTypeArgs(typeName: String, typeArg0: String, typeArg1: String, typeArgCount: int) -> String {
var suffix: String = "_";
suffix = String_Concat(suffix, typeArg0);
if typeArgCount > 1 && !String_Eq(typeArg1, "") {
suffix = String_Concat(suffix, "_");
suffix = String_Concat(suffix, typeArg1);
}
let fullLen: int = bux_strlen(typeName) as int;
let suffixLen: int = bux_strlen(suffix) as int;
if fullLen > suffixLen {
let endPart: String = bux_str_slice(typeName, (fullLen - suffixLen) as uint, suffixLen as uint);
if String_Eq(endPart, suffix) {
return bux_str_slice(typeName, 0, (fullLen - suffixLen) as uint);
}
}
return typeName;
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Array type helpers for bounds-checking desugaring // Array type helpers for bounds-checking desugaring
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -521,9 +541,11 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
n.kind = hCall; n.kind = hCall;
n.strValue = funcName; n.strValue = funcName;
let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1); let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
// If method expects pointer but receiver is not pointer, add & // If method expects pointer/reference but receiver is a value, add &
if sym.decl.paramCount > 0 && sym.decl.param0.refParamType != null as *TypeExpr && sym.decl.param0.refParamType.kind == tekPointer { if sym.decl.paramCount > 0 && sym.decl.param0.refParamType != null as *TypeExpr {
if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind != tekPointer { let paramKind: int = sym.decl.param0.refParamType.kind;
if paramKind == tekPointer || paramKind == tekRef || paramKind == tekMutRef {
if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind != tekPointer && expr.child1.refType.kind != tekRef && expr.child1.refType.kind != tekMutRef {
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
addrNode.kind = hUnary; addrNode.kind = hUnary;
addrNode.intValue = tkAmp; addrNode.intValue = tkAmp;
@@ -535,6 +557,9 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
} else { } else {
n.child1 = recv; n.child1 = recv;
} }
} else {
n.child1 = recv;
}
n.child2 = Lcx_LowerExpr(ctx, expr.child2); n.child2 = Lcx_LowerExpr(ctx, expr.child2);
return n; return n;
} }
@@ -569,13 +594,18 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
n.kind = hCall; n.kind = hCall;
let methodName: String = expr.child1.strValue; let methodName: String = expr.child1.strValue;
var receiverTypeName: String = ""; var receiverTypeName: String = "";
var receiverRefType: *TypeExpr = null as *TypeExpr;
if expr.child1.child1 != null as *Expr && expr.child1.child1.kind == ekIdent { if expr.child1.child1 != null as *Expr && expr.child1.child1.kind == ekIdent {
let sym: Symbol = Scope_Lookup(ctx.scope, expr.child1.child1.strValue); let sym: Symbol = Scope_Lookup(ctx.scope, expr.child1.child1.strValue);
receiverTypeName = sym.typeName; receiverTypeName = sym.typeName;
receiverRefType = sym.refType;
} }
if String_Eq(receiverTypeName, "") && expr.child1.child1 != null as *Expr && expr.child1.child1.refType != null as *TypeExpr { if String_Eq(receiverTypeName, "") && expr.child1.child1 != null as *Expr && expr.child1.child1.refType != null as *TypeExpr {
receiverTypeName = expr.child1.child1.refType.typeName; receiverTypeName = expr.child1.child1.refType.typeName;
receiverRefType = expr.child1.child1.refType;
} }
var methodDecl: *Decl = null as *Decl;
if !String_Eq(receiverTypeName, "") { if !String_Eq(receiverTypeName, "") {
// Strip trailing '*' from pointer type names (e.g. "Box*" → "Box") // Strip trailing '*' from pointer type names (e.g. "Box*" → "Box")
var baseName: String = receiverTypeName; var baseName: String = receiverTypeName;
@@ -588,10 +618,52 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
} }
n.strValue = String_Concat(baseName, "_"); n.strValue = String_Concat(baseName, "_");
n.strValue = String_Concat(n.strValue, methodName); n.strValue = String_Concat(n.strValue, methodName);
// Generic method monomorphization: Box_Get<T> on Box<int> -> Box_Get_int
var genericRecvType: *TypeExpr = receiverRefType;
if genericRecvType != null as *TypeExpr && genericRecvType.kind == tekPointer && genericRecvType.pointerPointee != null as *TypeExpr {
genericRecvType = genericRecvType.pointerPointee;
}
if genericRecvType != null as *TypeExpr && genericRecvType.typeArgCount > 0 {
let baseTypeName: String = Lcx_StripTypeArgs(genericRecvType.typeName, genericRecvType.typeArgName0, genericRecvType.typeArgName1, genericRecvType.typeArgCount);
let baseMethodName: String = String_Concat(String_Concat(baseTypeName, "_"), methodName);
let genDecl: *Decl = Lcx_FindGenericFunc(ctx, baseMethodName);
if genDecl != null as *Decl {
let mangled: String = Lcx_GenerateFuncInstance(ctx, genDecl, genericRecvType.typeArgName0, genericRecvType.typeArgName1, genericRecvType.typeArgCount);
n.strValue = mangled;
methodDecl = genDecl;
}
}
} }
// Lower receiver as first argument // Lower receiver as first argument
let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1.child1); let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1.child1);
// Find method decl if not already found (non-generic case)
if methodDecl == null as *Decl {
let sym: Symbol = Scope_Lookup(ctx.scope, n.strValue);
if sym.kind == skFunc && sym.decl != null as *Decl {
methodDecl = sym.decl;
}
}
// Auto-address if method expects pointer/reference but receiver is a value
if methodDecl != null as *Decl && methodDecl.paramCount > 0 && methodDecl.param0.refParamType != null as *TypeExpr {
let paramKind: int = methodDecl.param0.refParamType.kind;
if paramKind == tekPointer || paramKind == tekRef || paramKind == tekMutRef {
if expr.child1.child1 != null as *Expr && expr.child1.child1.refType != null as *TypeExpr &&
expr.child1.child1.refType.kind != tekPointer && expr.child1.child1.refType.kind != tekRef && expr.child1.child1.refType.kind != tekMutRef {
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
addrNode.kind = hUnary;
addrNode.intValue = tkAmp;
addrNode.child1 = recv;
n.child1 = addrNode;
} else {
n.child1 = recv; n.child1 = recv;
}
} else {
n.child1 = recv;
}
} else {
n.child1 = recv;
}
// Lower remaining arguments from linked list // Lower remaining arguments from linked list
var arg: *ExprList = expr.callArgs; var arg: *ExprList = expr.callArgs;
var argIdx: int = 0; var argIdx: int = 0;
@@ -743,6 +815,25 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
return n; return n;
} }
} }
// Simple enum .tag is the enum value itself
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind == tekNamed && String_Eq(expr.strValue, "tag") {
let sym: Symbol = Scope_Lookup(ctx.scope, expr.child1.refType.typeName);
if sym.decl != null as *Decl && sym.decl.kind == dkEnum {
var hasData: bool = false;
if sym.decl.variantCount > 0 && sym.decl.variant0.fieldCount > 0 { hasData = true; }
if sym.decl.variantCount > 1 && sym.decl.variant1.fieldCount > 0 { hasData = true; }
if sym.decl.variantCount > 2 && sym.decl.variant2.fieldCount > 0 { hasData = true; }
if sym.decl.variantCount > 3 && sym.decl.variant3.fieldCount > 0 { hasData = true; }
if sym.decl.variantCount > 4 && sym.decl.variant4.fieldCount > 0 { hasData = true; }
if sym.decl.variantCount > 5 && sym.decl.variant5.fieldCount > 0 { hasData = true; }
if sym.decl.variantCount > 6 && sym.decl.variant6.fieldCount > 0 { hasData = true; }
if sym.decl.variantCount > 7 && sym.decl.variant7.fieldCount > 0 { hasData = true; }
if sym.decl.variantCount > 8 && sym.decl.variant8.fieldCount > 0 { hasData = true; }
if !hasData {
return Lcx_LowerExpr(ctx, expr.child1);
}
}
}
n.kind = hFieldPtr; n.kind = hFieldPtr;
n.child1 = Lcx_LowerExpr(ctx, expr.child1); n.child1 = Lcx_LowerExpr(ctx, expr.child1);
n.strValue = expr.strValue; n.strValue = expr.strValue;
@@ -757,6 +848,7 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
// spawn Callee(args) // spawn Callee(args)
if kind == ekSpawn { if kind == ekSpawn {
n.kind = hSpawn; n.kind = hSpawn;
n.boolValue = expr.boolValue;
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent { if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
n.strValue = expr.child1.strValue; n.strValue = expr.child1.strValue;
} }
@@ -877,9 +969,11 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
n.kind = hCall; n.kind = hCall;
n.strValue = funcName; n.strValue = funcName;
let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1); let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
// If method expects pointer but receiver is not pointer, add & // If method expects pointer/reference but receiver is a value, add &
if targetDecl != null as *Decl && targetDecl.paramCount > 0 && targetDecl.param0.refParamType != null as *TypeExpr && targetDecl.param0.refParamType.kind == tekPointer { if targetDecl != null as *Decl && targetDecl.paramCount > 0 && targetDecl.param0.refParamType != null as *TypeExpr {
if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind != tekPointer { let paramKind: int = targetDecl.param0.refParamType.kind;
if paramKind == tekPointer || paramKind == tekRef || paramKind == tekMutRef {
if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind != tekPointer && expr.child1.refType.kind != tekRef && expr.child1.refType.kind != tekMutRef {
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
addrNode.kind = hUnary; addrNode.kind = hUnary;
addrNode.intValue = tkAmp; addrNode.intValue = tkAmp;
@@ -891,6 +985,9 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
} else { } else {
n.child1 = recv; n.child1 = recv;
} }
} else {
n.child1 = recv;
}
n.child2 = Lcx_LowerExpr(ctx, expr.child2); n.child2 = Lcx_LowerExpr(ctx, expr.child2);
return n; return n;
} }
@@ -999,9 +1096,11 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
callNode.line = line; callNode.line = line;
callNode.column = col; callNode.column = col;
let recv: *HirNode = Lcx_LowerExpr(ctx, objExpr); let recv: *HirNode = Lcx_LowerExpr(ctx, objExpr);
// If method expects pointer but receiver is not pointer, add & // If method expects pointer/reference but receiver is a value, add &
if targetDecl != null as *Decl && targetDecl.paramCount > 0 && targetDecl.param0.refParamType != null as *TypeExpr && targetDecl.param0.refParamType.kind == tekPointer { if targetDecl != null as *Decl && targetDecl.paramCount > 0 && targetDecl.param0.refParamType != null as *TypeExpr {
if objExpr.refType != null as *TypeExpr && objExpr.refType.kind != tekPointer { let paramKind: int = targetDecl.param0.refParamType.kind;
if paramKind == tekPointer || paramKind == tekRef || paramKind == tekMutRef {
if objExpr.refType != null as *TypeExpr && objExpr.refType.kind != tekPointer && objExpr.refType.kind != tekRef && objExpr.refType.kind != tekMutRef {
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
addrNode.kind = hUnary; addrNode.kind = hUnary;
addrNode.intValue = tkAmp; addrNode.intValue = tkAmp;
@@ -1013,6 +1112,9 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
} else { } else {
callNode.child1 = recv; callNode.child1 = recv;
} }
} else {
callNode.child1 = recv;
}
callNode.child2 = Lcx_LowerExpr(ctx, expr.child1.child2); callNode.child2 = Lcx_LowerExpr(ctx, expr.child1.child2);
let extra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList; let extra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
extra.node = Lcx_LowerExpr(ctx, expr.child2); extra.node = Lcx_LowerExpr(ctx, expr.child2);
@@ -1068,6 +1170,23 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
// Struct init: TypeName { field: value, ... } // Struct init: TypeName { field: value, ... }
if kind == ekStructInit { if kind == ekStructInit {
// Simple enum init: EnumName { tag: EnumName_Variant } -> EnumName_Variant
let enumSym: Symbol = Scope_Lookup(ctx.scope, expr.structName);
if enumSym.decl != null as *Decl && enumSym.decl.kind == dkEnum {
var hasData: bool = false;
if enumSym.decl.variantCount > 0 && enumSym.decl.variant0.fieldCount > 0 { hasData = true; }
if enumSym.decl.variantCount > 1 && enumSym.decl.variant1.fieldCount > 0 { hasData = true; }
if enumSym.decl.variantCount > 2 && enumSym.decl.variant2.fieldCount > 0 { hasData = true; }
if enumSym.decl.variantCount > 3 && enumSym.decl.variant3.fieldCount > 0 { hasData = true; }
if enumSym.decl.variantCount > 4 && enumSym.decl.variant4.fieldCount > 0 { hasData = true; }
if enumSym.decl.variantCount > 5 && enumSym.decl.variant5.fieldCount > 0 { hasData = true; }
if enumSym.decl.variantCount > 6 && enumSym.decl.variant6.fieldCount > 0 { hasData = true; }
if enumSym.decl.variantCount > 7 && enumSym.decl.variant7.fieldCount > 0 { hasData = true; }
if enumSym.decl.variantCount > 8 && enumSym.decl.variant8.fieldCount > 0 { hasData = true; }
if !hasData && expr.child1 != null as *Expr && String_Eq(expr.child1.strValue, "tag") {
return Lcx_LowerExpr(ctx, expr.child1.child1);
}
}
n.kind = hStructInit; n.kind = hStructInit;
var structName: String = expr.structName; var structName: String = expr.structName;
// Generic struct monomorphization // Generic struct monomorphization
@@ -1129,6 +1248,157 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
// Let/var → alloca + store // Let/var → alloca + store
if kind == skLet { if kind == skLet {
// Try operator: let x: T = operand? -> tmp = operand; if tmp.tag == Err { return tmp; } let x = tmp.data.Ok;
if stmt.child1 != null as *Expr && stmt.child1.kind == ekTry {
let tryExpr: *Expr = stmt.child1;
let operandExpr: *Expr = tryExpr.child1;
let operandTypeExpr: *TypeExpr = operandExpr.refType;
var typeName: String = "Result";
var errTag: String = "Result_Err";
var okField: String = "Ok_0";
if operandTypeExpr != null as *TypeExpr && operandTypeExpr.kind == tekNamed {
typeName = operandTypeExpr.typeName;
if String_Eq(typeName, "Option") {
errTag = "Option_None";
okField = "Some_0";
} else if !String_Eq(typeName, "Result") {
errTag = String_Concat(String_Concat(typeName, "_"), "Err");
okField = "Ok_0";
}
}
let tmpName: String = String_Concat("__try_tmp_", String_FromInt(ctx.tryCounter as int64));
ctx.tryCounter = ctx.tryCounter + 1;
// alloca tmp
let tmpAlloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tmpAlloca.kind = hAlloca;
tmpAlloca.line = line;
tmpAlloca.column = col;
tmpAlloca.strValue = tmpName;
tmpAlloca.typeName = typeName;
// tmp = operand
let tmpStore: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tmpStore.kind = hStore;
tmpStore.line = line;
tmpStore.column = col;
let tmpVarRef: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tmpVarRef.kind = hVar;
tmpVarRef.strValue = tmpName;
tmpStore.child1 = tmpVarRef;
tmpStore.child2 = Lcx_LowerExpr(ctx, operandExpr);
// if (tmp.tag == errTag) return tmp
let tagPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tagPtr.kind = hFieldPtr;
tagPtr.line = line;
tagPtr.column = col;
tagPtr.strValue = "tag";
tagPtr.child1 = tmpVarRef;
let tagLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tagLoad.kind = hLoad;
tagLoad.line = line;
tagLoad.column = col;
tagLoad.child1 = tagPtr;
let errConst: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
errConst.kind = hVar;
errConst.strValue = errTag;
let cond: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
cond.kind = hBinary;
cond.line = line;
cond.column = col;
cond.intValue = tkEq;
cond.child1 = tagLoad;
cond.child2 = errConst;
let retNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
retNode.kind = hReturn;
retNode.line = line;
retNode.column = col;
retNode.child1 = tmpVarRef;
let thenBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
thenBlock.kind = hBlock;
thenBlock.line = line;
thenBlock.column = col;
thenBlock.child1 = retNode;
let ifNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
ifNode.kind = hIf;
ifNode.line = line;
ifNode.column = col;
ifNode.child1 = cond;
ifNode.child2 = thenBlock;
// New initializer: tmp.data.OkField
let dataPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
dataPtr.kind = hFieldPtr;
dataPtr.line = line;
dataPtr.column = col;
dataPtr.strValue = "data";
dataPtr.child1 = tmpVarRef;
let dataLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
dataLoad.kind = hLoad;
dataLoad.line = line;
dataLoad.column = col;
dataLoad.child1 = dataPtr;
let okPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
okPtr.kind = hFieldPtr;
okPtr.line = line;
okPtr.column = col;
okPtr.strValue = okField;
okPtr.child1 = dataLoad;
let okLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
okLoad.kind = hLoad;
okLoad.line = line;
okLoad.column = col;
okLoad.child1 = okPtr;
// alloca x
let xAlloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
xAlloca.kind = hAlloca;
xAlloca.line = line;
xAlloca.column = col;
xAlloca.strValue = stmt.strValue;
xAlloca.typeName = "";
let letTe: *TypeExpr = Lcx_SubstituteType(ctx, stmt.refStmtType);
if letTe != null as *TypeExpr {
xAlloca.intValue = letTe.kind;
xAlloca.typeKind = Lcx_ResolveTypeKind(letTe);
if letTe.kind == tekFunc {
xAlloca.typeName = Lcx_BuildFuncTypeName(letTe);
} else if letTe.kind == tekPointer && letTe.pointerPointee != null as *TypeExpr {
xAlloca.typeName = String_Concat(letTe.pointerPointee.typeName, "*");
} else if !String_Eq(letTe.typeName, "") {
xAlloca.typeName = letTe.typeName;
}
}
var xSym: Symbol;
xSym.kind = skVar;
xSym.name = stmt.strValue;
xSym.typeKind = xAlloca.typeKind;
xSym.typeName = xAlloca.typeName;
xSym.refType = letTe;
xSym.isMutable = false;
xSym.isPublic = false;
xSym.decl = null as *Decl;
discard Scope_Define(ctx.scope, xSym);
let xStore: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
xStore.kind = hStore;
xStore.line = line;
xStore.column = col;
xStore.child1 = xAlloca;
xStore.child2 = okLoad;
tmpAlloca.child3 = tmpStore;
tmpStore.child3 = ifNode;
ifNode.child3 = xStore;
let blockNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
blockNode.kind = hBlock;
blockNode.line = line;
blockNode.column = col;
blockNode.child1 = tmpAlloca;
return blockNode;
}
let init: *HirNode = Lcx_LowerExpr(ctx, stmt.child1); let init: *HirNode = Lcx_LowerExpr(ctx, stmt.child1);
// alloca for the variable // alloca for the variable
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
@@ -2169,87 +2439,240 @@ func Lcx_LowerClosureFunc(ctx: *LowerCtx, expr: *Expr) -> *HirFunc {
// Compile-Time Function Execution (CTFE) — constant expression evaluator // Compile-Time Function Execution (CTFE) — constant expression evaluator
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
func Lcx_EvalConstExpr(ctx: *LowerCtx, expr: *Expr) -> int { const CTFE_MAX_LOCALS: int = 64;
if expr == null as *Expr {
struct CtfeLocal {
name: String,
value: int,
}
struct CtfeEnv {
locals: *CtfeLocal,
count: int,
}
struct CtVal {
value: int,
isReturn: bool,
}
func CtfeEnv_New() -> CtfeEnv {
let locals: *CtfeLocal = bux_alloc(CTFE_MAX_LOCALS as uint * sizeof(CtfeLocal)) as *CtfeLocal;
return CtfeEnv { locals: locals, count: 0 };
}
func CtfeEnv_Get(env: *CtfeEnv, name: String) -> int {
var i: int = env.count - 1;
while i >= 0 {
if String_Eq(env.locals[i].name, name) {
return env.locals[i].value;
}
i = i - 1;
}
return 0; return 0;
} }
func CtfeEnv_Set(env: *CtfeEnv, name: String, value: int) {
if env.count >= CTFE_MAX_LOCALS { return; }
env.locals[env.count].name = name;
env.locals[env.count].value = value;
env.count = env.count + 1;
}
func Lcx_FindConstFunc(ctx: *LowerCtx, name: String) -> *Decl {
var decl: *Decl = ctx.module.firstItem;
while decl != null as *Decl {
if decl.kind == dkFunc && decl.isConst == 1 && String_Eq(decl.strValue, name) {
return decl;
}
decl = decl.childDecl2;
}
return null as *Decl;
}
func Lcx_ParamName(fd: *Decl, idx: int) -> String {
if fd == null as *Decl { return ""; }
if idx == 0 { return fd.param0.name; }
if idx == 1 { return fd.param1.name; }
if idx == 2 { return fd.param2.name; }
if idx == 3 { return fd.param3.name; }
if idx == 4 { return fd.param4.name; }
if idx == 5 { return fd.param5.name; }
if idx == 6 { return fd.param6.name; }
if idx == 7 { return fd.param7.name; }
if idx == 8 { return fd.param8.name; }
return "";
}
func CtVal_Make(value: int) -> CtVal {
return CtVal { value: value, isReturn: false };
}
func CtVal_Return(value: int) -> CtVal {
return CtVal { value: value, isReturn: true };
}
func Lcx_EvalConstExprEnv(ctx: *LowerCtx, expr: *Expr, env: *CtfeEnv) -> CtVal {
if expr == null as *Expr {
return CtVal_Make(0);
}
// Literal integer // Literal integer
if expr.kind == ekLiteral { if expr.kind == ekLiteral {
return expr.intValue; return CtVal_Make(expr.intValue);
} }
// Reference to another constant // Reference to local or another constant
if expr.kind == ekIdent { if expr.kind == ekIdent {
let localVal: int = CtfeEnv_Get(env, expr.strValue);
// Local takes precedence; 0 from Get could mean not found, but global const 0
// will still be looked up below if needed. For Factorial parameters this works
// because params are always set in env.
if localVal != 0 {
return CtVal_Make(localVal);
}
let name: String = expr.strValue; let name: String = expr.strValue;
let i: int = 0; var i: int = 0;
while i < ctx.hm.constCount { while i < ctx.hm.constCount {
if String_Eq(ctx.hm.consts[i].name, name) { if String_Eq(ctx.hm.consts[i].name, name) {
return ctx.hm.consts[i].value; return CtVal_Make(ctx.hm.consts[i].value);
} }
i = i + 1; i = i + 1;
} }
return 0; return CtVal_Make(0);
} }
// Unary operators // Unary operators
if expr.kind == ekUnary { if expr.kind == ekUnary {
let operand: int = Lcx_EvalConstExpr(ctx, expr.child1); let operand: CtVal = Lcx_EvalConstExprEnv(ctx, expr.child1, env);
let op: int = expr.intValue; let op: int = expr.intValue;
if op == tkMinus { return -operand; } if op == tkMinus { return CtVal_Make(-operand.value); }
if op == tkBang { return (operand == 0) as int; } if op == tkBang { return CtVal_Make((operand.value == 0) as int); }
if op == tkTilde { return ~operand; } if op == tkTilde { return CtVal_Make(~operand.value); }
return operand; return CtVal_Make(operand.value);
} }
// Binary operators // Binary operators
if expr.kind == ekBinary { if expr.kind == ekBinary {
let left: int = Lcx_EvalConstExpr(ctx, expr.child1); let left: CtVal = Lcx_EvalConstExprEnv(ctx, expr.child1, env);
let right: int = Lcx_EvalConstExpr(ctx, expr.child2); let right: CtVal = Lcx_EvalConstExprEnv(ctx, expr.child2, env);
let op: int = expr.intValue; let op: int = expr.intValue;
if op == tkPlus { return left + right; } if op == tkPlus { return CtVal_Make(left.value + right.value); }
if op == tkMinus { return left - right; } if op == tkMinus { return CtVal_Make(left.value - right.value); }
if op == tkStar { return left * right; } if op == tkStar { return CtVal_Make(left.value * right.value); }
if op == tkSlash { if op == tkSlash {
if right == 0 { return 0; } if right.value == 0 { return CtVal_Make(0); }
return left / right; return CtVal_Make(left.value / right.value);
} }
if op == tkPercent { if op == tkPercent {
if right == 0 { return 0; } if right.value == 0 { return CtVal_Make(0); }
return left % right; return CtVal_Make(left.value % right.value);
} }
if op == tkLt { return (left < right) as int; } if op == tkLt { return CtVal_Make((left.value < right.value) as int); }
if op == tkLe { return (left <= right) as int; } if op == tkLe { return CtVal_Make((left.value <= right.value) as int); }
if op == tkGt { return (left > right) as int; } if op == tkGt { return CtVal_Make((left.value > right.value) as int); }
if op == tkGe { return (left >= right) as int; } if op == tkGe { return CtVal_Make((left.value >= right.value) as int); }
if op == tkEq { return (left == right) as int; } if op == tkEq { return CtVal_Make((left.value == right.value) as int); }
if op == tkNe { return (left != right) as int; } if op == tkNe { return CtVal_Make((left.value != right.value) as int); }
if op == tkAmp { return left & right; } if op == tkAmp { return CtVal_Make(left.value & right.value); }
if op == tkPipe { return left | right; } if op == tkPipe { return CtVal_Make(left.value | right.value); }
if op == tkCaret { return left ^ right; } if op == tkCaret { return CtVal_Make(left.value ^ right.value); }
if op == tkShl { return left << right; } if op == tkShl { return CtVal_Make(left.value << right.value); }
if op == tkShr { return left >> right; } if op == tkShr { return CtVal_Make(left.value >> right.value); }
if op == tkAmpAmp { return (left != 0 && right != 0) as int; } if op == tkAmpAmp { return CtVal_Make((left.value != 0 && right.value != 0) as int); }
if op == tkPipePipe { return (left != 0 || right != 0) as int; } if op == tkPipePipe { return CtVal_Make((left.value != 0 || right.value != 0) as int); }
return 0; return CtVal_Make(0);
} }
// Ternary operator // Ternary operator
if expr.kind == ekTernary { if expr.kind == ekTernary {
let cond: int = Lcx_EvalConstExpr(ctx, expr.child1); let cond: CtVal = Lcx_EvalConstExprEnv(ctx, expr.child1, env);
if cond != 0 { if cond.value != 0 {
return Lcx_EvalConstExpr(ctx, expr.child2); return Lcx_EvalConstExprEnv(ctx, expr.child2, env);
} else { } else {
return Lcx_EvalConstExpr(ctx, expr.child3); return Lcx_EvalConstExprEnv(ctx, expr.child3, env);
} }
} }
// Cast — evaluate operand (types don't affect integer values) // Cast — evaluate operand (types don't affect integer values)
if expr.kind == ekCast { if expr.kind == ekCast {
return Lcx_EvalConstExpr(ctx, expr.child1); return Lcx_EvalConstExprEnv(ctx, expr.child1, env);
} }
return 0; // Const function call
if expr.kind == ekCall {
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
let funcName: String = expr.child1.strValue;
let fd: *Decl = Lcx_FindConstFunc(ctx, funcName);
if fd != null as *Decl && fd.refBody != null as *Block {
// Evaluate arguments
var argExpr: *ExprList = expr.callArgs;
var ai: int = 0;
var argVals: *int = bux_alloc(fd.paramCount as uint * sizeof(int)) as *int;
while ai < fd.paramCount {
argVals[ai] = 0;
ai = ai + 1;
}
ai = 0;
while argExpr != null as *ExprList && ai < fd.paramCount {
let av: CtVal = Lcx_EvalConstExprEnv(ctx, argExpr.expr, env);
argVals[ai] = av.value;
argExpr = argExpr.next;
ai = ai + 1;
}
// Set up call environment
let callEnv: CtfeEnv = CtfeEnv_New();
var pi: int = 0;
while pi < fd.paramCount {
CtfeEnv_Set(&callEnv, Lcx_ParamName(fd, pi), argVals[pi]);
pi = pi + 1;
}
let result: CtVal = Lcx_EvalConstBlock(ctx, fd.refBody, &callEnv);
return CtVal_Make(result.value);
}
}
return CtVal_Make(0);
}
return CtVal_Make(0);
}
func Lcx_EvalConstBlock(ctx: *LowerCtx, block: *Block, env: *CtfeEnv) -> CtVal {
if block == null as *Block {
return CtVal_Make(0);
}
var stmt: *Stmt = block.firstStmt;
while stmt != null as *Stmt {
if stmt.kind == skLet && stmt.child1 != null as *Expr {
let val: CtVal = Lcx_EvalConstExprEnv(ctx, stmt.child1, env);
CtfeEnv_Set(env, stmt.strValue, val.value);
} else if stmt.kind == skIf {
let cond: CtVal = Lcx_EvalConstExprEnv(ctx, stmt.child1, env);
if cond.value != 0 {
let r: CtVal = Lcx_EvalConstBlock(ctx, stmt.refStmtBlock, env);
if r.isReturn { return r; }
} else if stmt.refStmtElse != null as *Block {
let r: CtVal = Lcx_EvalConstBlock(ctx, stmt.refStmtElse, env);
if r.isReturn { return r; }
}
} else if stmt.kind == skReturn {
if stmt.child1 != null as *Expr {
let val: CtVal = Lcx_EvalConstExprEnv(ctx, stmt.child1, env);
return CtVal_Return(val.value);
}
return CtVal_Return(0);
} else if stmt.kind == skExpr {
discard Lcx_EvalConstExprEnv(ctx, stmt.child1, env);
}
stmt = stmt.nextStmt;
}
return CtVal_Make(0);
}
func Lcx_EvalConstExpr(ctx: *LowerCtx, expr: *Expr) -> int {
let env: CtfeEnv = CtfeEnv_New();
let result: CtVal = Lcx_EvalConstExprEnv(ctx, expr, &env);
return result.value;
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -2379,6 +2802,24 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
ctx.genStructs[ctx.genStructCount] = *decl; ctx.genStructs[ctx.genStructCount] = *decl;
ctx.genStructCount = ctx.genStructCount + 1; ctx.genStructCount = ctx.genStructCount + 1;
} }
// Generic impl/extend blocks: methods inherit the impl's type params
if decl.kind == dkImpl && decl.typeParamCount > 0 {
let implTypeName: String = decl.strValue;
var implDecl: *Decl = decl.childDecl1;
while implDecl != null as *Decl {
if implDecl.kind == dkFunc {
let renamed: String = String_Concat(String_Concat(implTypeName, "_"), implDecl.strValue);
var copy: Decl = *implDecl;
copy.strValue = renamed;
copy.typeParam0 = decl.typeParam0;
copy.typeParam1 = decl.typeParam1;
copy.typeParamCount = decl.typeParamCount;
ctx.genFuncs[ctx.genFuncCount] = copy;
ctx.genFuncCount = ctx.genFuncCount + 1;
}
implDecl = implDecl.childDecl2;
}
}
decl = decl.childDecl2; decl = decl.childDecl2;
} }
@@ -2427,6 +2868,11 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
var implDecl: *Decl = decl.childDecl1; var implDecl: *Decl = decl.childDecl1;
while implDecl != null as *Decl { while implDecl != null as *Decl {
if implDecl.kind == dkFunc && implDecl.refBody != null as *Block { if implDecl.kind == dkFunc && implDecl.refBody != null as *Block {
// Generic impl methods are monomorphized on demand; skip direct lowering
if decl.typeParamCount > 0 {
implDecl = implDecl.childDecl2;
continue;
}
let mangled: String = String_Concat(implTypeName, "_"); let mangled: String = String_Concat(implTypeName, "_");
implDecl.strValue = String_Concat(mangled, implDecl.strValue); implDecl.strValue = String_Concat(mangled, implDecl.strValue);
let f: *HirFunc = Lcx_LowerFunc(ctx, implDecl); let f: *HirFunc = Lcx_LowerFunc(ctx, implDecl);
+10 -2
View File
@@ -733,8 +733,8 @@ func parserParseUnary(p: *Parser) -> *Expr {
let col: uint32 = tok.column; let col: uint32 = tok.column;
let kind: int = tok.kind; let kind: int = tok.kind;
// -expr, !expr, *expr, &expr // -expr, !expr, ~expr, *expr, &expr
if kind == tkMinus || kind == tkBang || kind == tkStar || kind == tkAmp { if kind == tkMinus || kind == tkBang || kind == tkTilde || kind == tkStar || kind == tkAmp {
discard parserAdvance(p); discard parserAdvance(p);
let e: *Expr = parserMakeExpr(ekUnary, line, col); let e: *Expr = parserMakeExpr(ekUnary, line, col);
e.intValue = kind; e.intValue = kind;
@@ -1611,6 +1611,14 @@ func parserParseDecl(p: *Parser) -> *Decl {
d.isRelease = isRelease; d.isRelease = isRelease;
return d; return d;
} }
if kind == tkConst && parserPeek(p, 1) == tkFunc {
discard parserAdvance(p); // const
let d: *Decl = parserParseFuncDecl(p, isPublic, false, false);
d.isConst = 1;
d.isChecked = isChecked;
d.isRelease = isRelease;
return d;
}
if kind == tkFunc { if kind == tkFunc {
let d: *Decl = parserParseFuncDecl(p, isPublic, false, false); let d: *Decl = parserParseFuncDecl(p, isPublic, false, false);
d.isChecked = isChecked; d.isChecked = isChecked;
+96 -31
View File
@@ -406,6 +406,16 @@ func Sema_AddCapture(closureExpr: *Expr, name: String, typeKind: int) {
// Expression type checking // Expression type checking
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
func Sema_IsMutRefDeref(target: *Expr) -> bool {
if target == null as *Expr { return false; }
if target.kind != ekUnary { return false; }
if target.intValue != tkStar { return false; }
let operand: *Expr = target.child1;
if operand == null as *Expr { return false; }
if operand.refType == null as *TypeExpr { return false; }
return operand.refType.kind == tekMutRef;
}
func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int { func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
if expr == null as *Expr { return tyUnknown; } if expr == null as *Expr { return tyUnknown; }
let kind: int = expr.kind; let kind: int = expr.kind;
@@ -413,12 +423,18 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
// Literal // Literal
if kind == ekLiteral { if kind == ekLiteral {
let tk: int = expr.tokKind; let tk: int = expr.tokKind;
if tk == tkIntLiteral { return tyInt; } let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
if tk == tkFloatLiteral { return tyFloat64; } te.kind = tekNamed;
if tk == tkStringLiteral { return tyStr; } if tk == tkIntLiteral { te.typeName = "int"; expr.refType = te; return tyInt; }
if tk == tkBoolLiteral { return tyBool; } if tk == tkFloatLiteral { te.typeName = "float64"; expr.refType = te; return tyFloat64; }
if tk == tkCharLiteral { return tyChar32; } if tk == tkStringLiteral { te.typeName = "String"; expr.refType = te; return tyStr; }
if tk == tkNull { return tyPointer; } if tk == tkBoolLiteral { te.typeName = "bool"; expr.refType = te; return tyBool; }
if tk == tkCharLiteral { te.typeName = "char32"; expr.refType = te; return tyChar32; }
if tk == tkNull {
te.typeName = "*void";
expr.refType = te;
return tyPointer;
}
return tyUnknown; return tyUnknown;
} }
@@ -473,13 +489,14 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
let right: int = Sema_CheckExpr(sema, expr.child2); let right: int = Sema_CheckExpr(sema, expr.child2);
let op: int = expr.intValue; let op: int = expr.intValue;
// Borrow check: reject assignment through deref in @[Checked] functions // Borrow check: reject assignment through raw pointer in @[Checked] functions.
if sema.checkedFunc && !sema.releaseFunc && op == tkAssign { // Allow writes through &mut T references.
if expr.child1 != null as *Expr && expr.child1.kind == ekUnary && expr.child1.intValue == tkStar { if sema.checkedFunc && !sema.releaseFunc && op == tkAssign &&
expr.child1 != null as *Expr && expr.child1.kind == ekUnary && expr.child1.intValue == tkStar &&
!Sema_IsMutRefDeref(expr.child1) {
Sema_EmitError(sema, expr.line, expr.column, Sema_EmitError(sema, expr.line, expr.column,
"cannot assign through raw pointer in checked function — use '&mut T' instead"); "cannot assign through raw pointer in checked function — use '&mut T' instead");
} }
}
// Borrow check: reinitialization after move // Borrow check: reinitialization after move
if sema.checkedFunc && !sema.releaseFunc && op == tkAssign { if sema.checkedFunc && !sema.releaseFunc && op == tkAssign {
@@ -552,8 +569,10 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
if kind == ekUnary { if kind == ekUnary {
let operand: int = Sema_CheckExpr(sema, expr.child1); let operand: int = Sema_CheckExpr(sema, expr.child1);
let op: int = expr.intValue; let op: int = expr.intValue;
if op == tkStar {
return tyUnknown;
}
if op == tkBang { return tyBool; } if op == tkBang { return tyBool; }
if op == tkStar { return tyUnknown; } // dereference — resolve pointee
if op == tkAmp { if op == tkAmp {
if expr.child1.refType != null as *TypeExpr { if expr.child1.refType != null as *TypeExpr {
expr.refType = expr.child1.refType; expr.refType = expr.child1.refType;
@@ -567,8 +586,11 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
if kind == ekAssign { if kind == ekAssign {
let target: int = Sema_CheckExpr(sema, expr.child1); let target: int = Sema_CheckExpr(sema, expr.child1);
let value: int = Sema_CheckExpr(sema, expr.child2); let value: int = Sema_CheckExpr(sema, expr.child2);
// Borrow check: reject assignment through deref in @[Checked] functions // Borrow check: reject assignment through raw pointer in @[Checked] functions.
if sema.checkedFunc && !sema.releaseFunc && expr.child1 != null as *Expr && expr.child1.kind == ekUnary && expr.child1.intValue == tkStar { // Allow writes through &mut T references.
if sema.checkedFunc && !sema.releaseFunc &&
expr.child1 != null as *Expr && expr.child1.kind == ekUnary && expr.child1.intValue == tkStar &&
!Sema_IsMutRefDeref(expr.child1) {
Sema_EmitError(sema, expr.line, expr.column, Sema_EmitError(sema, expr.line, expr.column,
"cannot assign through raw pointer in checked function — use '&mut T' instead"); "cannot assign through raw pointer in checked function — use '&mut T' instead");
} }
@@ -619,11 +641,7 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
if sym.kind == skFunc && sym.decl != null as *Decl { if sym.kind == skFunc && sym.decl != null as *Decl {
// Implicit generic type argument inference // Implicit generic type argument inference
if expr.child1.genericTypeArgCount == 0 && sym.decl.typeParamCount > 0 { if expr.child1.genericTypeArgCount == 0 && sym.decl.typeParamCount > 0 {
let inferred: String = Sema_InferGenericArg(sema, sym.decl, expr); Sema_InferGenericArgs(sema, sym.decl, expr);
if !String_Eq(inferred, "") {
expr.child1.genericTypeArgCount = 1;
expr.child1.genericTypeArg0 = inferred;
}
} }
if expr.child1.genericTypeArgCount > 0 { if expr.child1.genericTypeArgCount > 0 {
Sema_CheckTraitBounds(sema, sym.decl, expr.child1.genericTypeArg0, expr.child1.genericTypeArg1, expr.child1.genericTypeArgCount, expr.line, expr.column); Sema_CheckTraitBounds(sema, sym.decl, expr.child1.genericTypeArg0, expr.child1.genericTypeArg1, expr.child1.genericTypeArgCount, expr.line, expr.column);
@@ -672,6 +690,26 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
return inner; // simplified return inner; // simplified
} }
// spawn Callee(args)
if kind == ekSpawn {
discard Sema_CheckExpr(sema, expr.child1);
if expr.child2 != null as *Expr {
discard Sema_CheckExpr(sema, expr.child2);
}
// Determine if callee is async
var calleeName: String = "";
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
calleeName = expr.child1.strValue;
}
if !String_Eq(calleeName, "") {
let sym: Symbol = Scope_Lookup(sema.scope, calleeName);
if sym.decl != null as *Decl && sym.decl.kind == dkFunc && sym.decl.isAsync {
expr.boolValue = true;
}
}
return tyPointer;
}
// Struct init: TypeName { field: value, ... } // Struct init: TypeName { field: value, ... }
if kind == ekStructInit { if kind == ekStructInit {
return tyNamed; return tyNamed;
@@ -1369,21 +1407,48 @@ func Sema_ExtractElemType(te: *TypeExpr) -> String {
// Infer generic type argument from call arguments. // Infer generic type argument from call arguments.
// Supports Array_* and Iter_* stdlib functions. // Supports Array_* and Iter_* stdlib functions.
func Sema_InferGenericArg(sema: *Sema, funcDecl: *Decl, expr: *Expr) -> String { func Sema_InferGenericArgs(sema: *Sema, funcDecl: *Decl, expr: *Expr) {
if expr.callArgs == null as *ExprList { return ""; } if expr.callArgs == null as *ExprList { return; }
let firstArg: *Expr = expr.callArgs.expr; var argList: *ExprList = expr.callArgs;
if firstArg == null as *Expr { return ""; } var pi: int = 0;
// Check first argument's type while argList != null as *ExprList && pi < funcDecl.paramCount {
let argType: *TypeExpr = firstArg.refType; let argExpr: *Expr = argList.expr;
if argType == null as *TypeExpr && firstArg.kind == ekUnary && firstArg.intValue == tkAmp { if argExpr == null as *Expr { argList = argList.next; pi = pi + 1; continue; }
// &x: use x's type var argType: *TypeExpr = argExpr.refType;
argType = firstArg.child1.refType; if argType == null as *TypeExpr && argExpr.kind == ekUnary && argExpr.intValue == tkAmp {
argType = argExpr.child1.refType;
} }
let elemType: String = Sema_ExtractElemType(argType); var paramType: *TypeExpr = null as *TypeExpr;
if !String_Eq(elemType, "") { if pi == 0 { paramType = funcDecl.param0.refParamType; }
return elemType; else if pi == 1 { paramType = funcDecl.param1.refParamType; }
else if pi == 2 { paramType = funcDecl.param2.refParamType; }
else if pi == 3 { paramType = funcDecl.param3.refParamType; }
else if pi == 4 { paramType = funcDecl.param4.refParamType; }
else if pi == 5 { paramType = funcDecl.param5.refParamType; }
else if pi == 6 { paramType = funcDecl.param6.refParamType; }
else if pi == 7 { paramType = funcDecl.param7.refParamType; }
else if pi == 8 { paramType = funcDecl.param8.refParamType; }
if paramType != null as *TypeExpr && paramType.kind == tekNamed && argType != null as *TypeExpr {
let inferred: String = Sema_ExtractElemType(argType);
var typeName: String = inferred;
if String_Eq(typeName, "") && argType.kind == tekNamed {
typeName = argType.typeName;
}
if !String_Eq(typeName, "") {
if funcDecl.typeParamCount >= 1 && String_Eq(paramType.typeName, funcDecl.typeParam0) && String_Eq(expr.child1.genericTypeArg0, "") {
expr.child1.genericTypeArg0 = typeName;
if expr.child1.genericTypeArgCount < 1 { expr.child1.genericTypeArgCount = 1; }
}
if funcDecl.typeParamCount >= 2 && String_Eq(paramType.typeName, funcDecl.typeParam1) && String_Eq(expr.child1.genericTypeArg1, "") {
expr.child1.genericTypeArg1 = typeName;
if expr.child1.genericTypeArgCount < 2 { expr.child1.genericTypeArgCount = 2; }
}
}
}
argList = argList.next;
pi = pi + 1;
} }
return "";
} }
func Sema_CheckTraitBounds(sema: *Sema, funcDecl: *Decl, typeArg0: String, typeArg1: String, typeArgCount: int, line: uint32, col: uint32) { func Sema_CheckTraitBounds(sema: *Sema, funcDecl: *Decl, typeArg0: String, typeArg1: String, typeArgCount: int, line: uint32, col: uint32) {
BIN
View File
Binary file not shown.
File diff suppressed because it is too large Load Diff
+2628 -2592
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2615 -2579
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff