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:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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) {
|
||||||
|
|||||||
Binary file not shown.
+2640
-2604
File diff suppressed because it is too large
Load Diff
+2628
-2592
File diff suppressed because it is too large
Load Diff
+2641
-2605
File diff suppressed because it is too large
Load Diff
+2621
-2585
File diff suppressed because it is too large
Load Diff
+2615
-2579
File diff suppressed because it is too large
Load Diff
+2641
-2605
File diff suppressed because it is too large
Load Diff
+2635
-2599
File diff suppressed because it is too large
Load Diff
+2645
-2609
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user