feat: lifetime elision, tooling CI, registry, and LSP locals

Ship the QUALITY_PLAN stretch from ownership through ecosystem: C.1
lifetime elision (bootstrap + selfhost), bux fmt/test/doc CI hooks,
stdlib goldens, package registry (bux search/add), and LSP 0.4
position-sensitive locals with inferred let types. Full-tree format
pass plus Map/Set remove double-free fix.
This commit is contained in:
2026-07-19 16:35:08 +03:00
parent 3eb1ad3a82
commit 53b43b0f79
130 changed files with 20494 additions and 16738 deletions
+54 -3
View File
@@ -3,9 +3,9 @@ SRC := bootstrap/main.nim
OUT := buxc
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 ownership_checked drop_early_return ctfe async concurrency os_time process json iter trait_bounds channel sync jwt stdlib_ergonomics tuples func_ptr map_remove array_iter_extra string_extra multi_closure iter_hof closure_control match_let string_interp iter_generic generic_infer_hof struct_tuple_pat match_block nested_patterns match_guards pattern_shadow
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 ownership_checked drop_early_return lifetime_elision ctfe async concurrency os_time process json iter trait_bounds channel sync jwt stdlib_ergonomics tuples func_ptr map_remove array_iter_extra string_extra multi_closure iter_hof closure_control match_let string_interp iter_generic generic_infer_hof struct_tuple_pat match_block nested_patterns match_guards pattern_shadow
.PHONY: all build dev debug test clean clean-all test-examples selfhost test-golden test-errors selfhost-loop lsp
.PHONY: all build dev debug test clean clean-all test-examples selfhost test-golden test-errors test-stdlib selfhost-loop lsp fmt-check docs
all: build
@@ -19,7 +19,7 @@ dev:
debug: dev
@echo "Debug binary: buxc_debug"
test: build test-examples test-errors
test: build fmt-check test-examples test-errors test-stdlib
@echo "Running lexer tests..."
$(NIM) c -r tests/lexer_test.nim
@echo "Running parser tests..."
@@ -105,6 +105,43 @@ test-errors: build
@chmod +x tests/error_golden/run.sh
@tests/error_golden/run.sh ./$(OUT)
test-stdlib: build
@echo "=== Stdlib golden tests ==="
@chmod +x tests/stdlib_golden/run.sh
@tests/stdlib_golden/run.sh ./$(OUT)
# Generate stdlib API docs from /// comments → docs/api/stdlib.md
docs: build
@mkdir -p docs/api
@./$(OUT) doc --out docs/api/stdlib.md lib/
@echo "docs/api/stdlib.md updated"
# CI: full-tree format check (lib / examples / src / tests / apps) + dirty-path smoke.
fmt-check: build
@echo "=== fmt --check (full tree) ==="
@./$(OUT) fmt --check lib/
@./$(OUT) fmt --check examples/
@./$(OUT) fmt --check src/
@./$(OUT) fmt --check tests/
@./$(OUT) fmt --check apps/
@echo "=== fmt --check dirty-path smoke ==="
@mkdir -p /tmp/bux_fmt_smoke
@printf 'func Main() -> int {\nreturn 0;\n}\n' > /tmp/bux_fmt_smoke/bad.bux
@if ./$(OUT) fmt --check /tmp/bux_fmt_smoke/bad.bux >/dev/null 2>&1; then \
echo "error: expected --check to fail on dirty file"; exit 1; \
fi
@echo "fmt --check passed (tree clean + dirty exits 1)"
# One-shot reformat of the same trees (run before committing style-only fixes)
.PHONY: fmt
fmt: build
@./$(OUT) fmt lib/
@./$(OUT) fmt examples/
@./$(OUT) fmt src/
@./$(OUT) fmt tests/
@./$(OUT) fmt apps/
@echo "Formatted lib/ examples/ src/ tests/ apps/"
selfhost-loop: build
@echo "=== Selfhost loop: bootstrap determinism check ==="
@echo "Build A..."
@@ -145,3 +182,17 @@ lsp: tools/bux-lsp
tools/bux-lsp: tools/lsp_server.nim bootstrap/*.nim
cd tools && $(NIM) c -d:release --opt:size --path:../bootstrap -o:bux-lsp lsp_server.nim
.PHONY: test-lsp
test-lsp: lsp
@echo "=== LSP unit (locals / inference) ==="
$(NIM) r --path:bootstrap tools/test_lsp_locals.nim
@echo "=== LSP hover smoke ==="
@chmod +x tools/smoke_lsp_hover.sh
@tools/smoke_lsp_hover.sh
.PHONY: test-registry
test-registry: build
@echo "=== Registry smoke (E.1) ==="
@chmod +x tools/smoke_registry.sh
@tools/smoke_registry.sh
+3 -3
View File
@@ -432,7 +432,7 @@ func ReadFile(path: String) -> Result<String, IoError> {
| `8.2.1` `own` keyword | ✅ | `own T` parsed and resolves to `T`; ready for borrow checker integration |
| `8.2.2` `borrow` / `&` | ✅ | `&T` shared reference type checked and enforced |
| `8.2.3` `mut` references | ✅ | `&mut T` mutable reference type checked and enforced |
| `8.2.4` Lifetime elision | | Simple rules for common cases; explicit `'a` for complex |
| `8.2.4` Lifetime elision | | Single-input elision + dangling return; explicit `'a` for multi-input |
| `8.2.5` Opt-in checker | ✅ | `@[Checked]` attribute enables borrow checking: writes through `&T` are rejected |
```bux
@@ -681,8 +681,8 @@ buxc2 == buxc3 ✅ (binary-identical)
| `10.2.3` `&mut T` exclusive mutable check | ✅ | No aliasing of mutable refs |
| `10.2.4` Bounds checking on slices | ✅ | `Slice_Get` / `Array_Get` with `bux_bounds_check` |
| `10.2.5` `@[Release]` zero-cost mode | ✅ | Disables borrow + bounds checks, passes `-O3 -flto` |
| `10.2.6` Lifetime elision (simple rules) | | 80% of cases without annotations |
| `10.2.7` Explicit lifetimes `'a` | | Only for complex cases |
| `10.2.6` Lifetime elision (simple rules) | | Single-input elision; multi-input requires `'a` |
| `10.2.7` Explicit lifetimes `'a` | | Parsed + checked; multi-input + mismatch |
### 10.3 — Compiler Architecture Upgrade (v0.6.0 target)
+26 -5
View File
@@ -246,7 +246,7 @@ func Main() -> int {
| **Package Manager** | `bux add`, `bux install`, `bux.lock`, path + git deps |
| **Cross-Compilation** | `--target <triple>` via clang (e.g. `aarch64-linux-gnu`) |
| **Diagnostics** | Rust-style snippets, multi-char underlines, `= help:` hints |
| **Tooling** | `bux new/build/run/test/check/fmt`, LSP (`tools/lsp_server.nim` + `buxc check`) |
| **Tooling** | `bux new/build/run/test/check/fmt/doc`, LSP 0.4.0 (locals + inferred lets) |
---
@@ -299,6 +299,8 @@ bux/
| [`docs/Stdlib.md`](docs/Stdlib.md) | Standard library API |
| [`docs/BuildAndTest.md`](docs/BuildAndTest.md) | Build, test, and tooling |
| [`docs/QUALITY_PLAN.md`](docs/QUALITY_PLAN.md) | Roadmap toward a “good” v1.0 |
| [`docs/Packages.md`](docs/Packages.md) | Package manager + registry |
| [`docs/SEMVER.md`](docs/SEMVER.md) | Versioning policy |
| [`docs/ROADMAP.md`](docs/ROADMAP.md) | Feature status (constructs) |
| [`PLAN.md`](PLAN.md) | Long-form phase plan |
@@ -319,14 +321,33 @@ make test-errors
# Full unit + example suite
make test
# Full-tree format check (lib/ examples/ src/ tests/ apps/)
make fmt-check
# Reformat those trees
make fmt
# Stdlib behavioral goldens (Array / String / collections)
make test-stdlib
# Generate stdlib API docs from /// comments
make docs
# Build self-hosted compiler (Bux → C → native)
make selfhost
# Run all tests
make test
# Package tests (filter + summary table)
./buxc test --filter first _test_runner
# Run example programs
make test-examples
# Format / CI format check
./buxc fmt path/to/file.bux
./buxc fmt --check path/
# API docs
./buxc doc --out docs/api/stdlib.md lib/
# Package registry
./buxc search greet
make test-registry # add greet → install → build temp app
# Verify selfhost binary parity (buxc2 → buxc3, identical)
make selfhost-loop
+42 -42
View File
@@ -5,23 +5,23 @@
// =============================================================================
module Boko {
import Std::Io::{PrintLine, Print, PrintInt};
import Std::Net::{Net_Create, Net_SetReuse, Net_Bind, Net_Listen, Net_Accept, Net_Send, Net_Recv, Net_Close, Net_LastError};
import Std::String::{
import Std::Io::{PrintLine, Print, PrintInt};
import Std::Net::{Net_Create, Net_SetReuse, Net_Bind, Net_Listen, Net_Accept, Net_Send, Net_Recv, Net_Close, Net_LastError};
import Std::String::{
String_Len, String_Eq, String_IsNull,
String_StartsWith, String_Contains,
String_Find, String_Offset, String_Slice,
String_SplitCount, String_SplitPart,
StringBuilder, StringBuilder_New, StringBuilder_Append,
StringBuilder_AppendInt, StringBuilder_Build, StringBuilder_Free
};
import Std::Map::{StringMap, StringMap_New, StringMap_Set, StringMap_Get, StringMap_Has};
};
import Std::Map::{StringMap, StringMap_New, StringMap_Set, StringMap_Get, StringMap_Has};
// =============================================================================
// HTTP Methods
// =============================================================================
enum HttpVerb {
// =============================================================================
// HTTP Methods
// =============================================================================
enum HttpVerb {
GET,
POST,
PUT,
@@ -29,9 +29,9 @@ enum HttpVerb {
PATCH,
HEAD,
OPTIONS,
}
}
func HttpVerb_MethodName(verb: HttpVerb) -> String {
func HttpVerb_MethodName(verb: HttpVerb) -> String {
if verb.tag == HttpVerb_GET { return "GET"; }
if verb.tag == HttpVerb_POST { return "POST"; }
if verb.tag == HttpVerb_PUT { return "PUT"; }
@@ -40,9 +40,9 @@ func HttpVerb_MethodName(verb: HttpVerb) -> String {
if verb.tag == HttpVerb_HEAD { return "HEAD"; }
if verb.tag == HttpVerb_OPTIONS { return "OPTIONS"; }
return "?";
}
}
func HttpVerb_Parse(methodStr: String) -> HttpVerb {
func HttpVerb_Parse(methodStr: String) -> HttpVerb {
if String_Eq(methodStr, "GET") { return HttpVerb { tag: HttpVerb_GET }; }
if String_Eq(methodStr, "POST") { return HttpVerb { tag: HttpVerb_POST }; }
if String_Eq(methodStr, "PUT") { return HttpVerb { tag: HttpVerb_PUT }; }
@@ -51,21 +51,21 @@ func HttpVerb_Parse(methodStr: String) -> HttpVerb {
if String_Eq(methodStr, "HEAD") { return HttpVerb { tag: HttpVerb_HEAD }; }
if String_Eq(methodStr, "OPTIONS") { return HttpVerb { tag: HttpVerb_OPTIONS }; }
return HttpVerb { tag: HttpVerb_GET };
}
}
// =============================================================================
// Request — parsed incoming HTTP request
// =============================================================================
struct Request {
// =============================================================================
// Request — parsed incoming HTTP request
// =============================================================================
struct Request {
method: HttpVerb,
path: String,
body: String,
headers: StringMap<String>,
query: StringMap<String>,
pathParams: StringMap<String>,
}
}
extend Request {
extend Request {
func GetHeader(self: Request, name: String) -> String {
if StringMap_Has<String>(&self.headers, name) {
return StringMap_Get<String>(&self.headers, name);
@@ -90,40 +90,40 @@ extend Request {
}
return "";
}
}
}
// =============================================================================
// Response — outgoing HTTP response
// =============================================================================
struct Response {
// =============================================================================
// Response — outgoing HTTP response
// =============================================================================
struct Response {
statusCode: int,
contentType: String,
body: String,
extraHeaders: String,
}
}
// --- Constructors ---
func Response_New(status: int, contentType: String, body: String) -> Response {
// --- Constructors ---
func Response_New(status: int, contentType: String, body: String) -> Response {
return Response { statusCode: status, contentType: contentType, body: body, extraHeaders: "" };
}
}
func Response_Ok(body: String) -> Response {
func Response_Ok(body: String) -> Response {
return Response_New(200, "text/html; charset=utf-8", body);
}
}
func Response_Html(html: String) -> Response {
func Response_Html(html: String) -> Response {
return Response_New(200, "text/html; charset=utf-8", html);
}
}
func Response_Json(json: String) -> Response {
func Response_Json(json: String) -> Response {
return Response_New(200, "application/json; charset=utf-8", json);
}
}
func Response_Text(text: String) -> Response {
func Response_Text(text: String) -> Response {
return Response_New(200, "text/plain; charset=utf-8", text);
}
}
func Response_Redirect(url: String) -> Response {
func Response_Redirect(url: String) -> Response {
let sb: StringBuilder = StringBuilder_New();
StringBuilder_Append(&sb, "Location: ");
StringBuilder_Append(&sb, url);
@@ -131,13 +131,13 @@ func Response_Redirect(url: String) -> Response {
let headers: String = StringBuilder_Build(&sb);
StringBuilder_Free(&sb);
return Response { statusCode: 302, contentType: "", body: "", extraHeaders: headers };
}
}
func Response_NotFound() -> Response {
func Response_NotFound() -> Response {
return Response_New(404, "application/json; charset=utf-8", "{\"error\":\"not_found\"}");
}
}
func Response_Error(status: int, message: String) -> Response {
func Response_Error(status: int, message: String) -> Response {
let sb: StringBuilder = StringBuilder_New();
StringBuilder_Append(&sb, "{\"error\":\"");
StringBuilder_Append(&sb, message);
+49 -49
View File
@@ -3,24 +3,24 @@
// =============================================================================
module Main {
import Std::Io::{PrintLine, Print};
import Std::String::{
import Std::Io::{PrintLine, Print};
import Std::String::{
String_Eq, String_Len,
StringBuilder, StringBuilder_New, StringBuilder_Append,
StringBuilder_Build, StringBuilder_Free
};
import Boko::{
};
import Boko::{
App, App_New, App_Run,
Request, Response,
Response_Html, Response_Json, Response_NotFound, Response_Redirect,
Path_Match,
HttpVerb
};
};
// =============================================================================
// Boko_Router — user-defined dispatch (called by the framework)
// =============================================================================
func Boko_Router(req: Request) -> Response {
// =============================================================================
// Boko_Router — user-defined dispatch (called by the framework)
// =============================================================================
func Boko_Router(req: Request) -> Response {
// --- GET / ---
if String_Eq(req.path, "/") && req.method.tag == HttpVerb_GET {
return Response_Html(PageHome());
@@ -98,46 +98,46 @@ func Boko_Router(req: Request) -> Response {
// =============================================================================
func PageHome() -> String {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Boko Framework</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:system-ui,sans-serif;background:#0d1117;color:#c9d1d9;min-height:100vh;display:flex;align-items:center;justify-content:center}
.card{background:#161b22;border:1px solid #30363d;border-radius:12px;padding:3rem;max-width:560px;width:100%}
h1{color:#58a6ff;font-size:2rem;margin-bottom:.5rem}
.tag{color:#8b949e;font-size:.9rem;margin-bottom:2rem}
h3{color:#d2a8ff;margin:1.5rem 0 .75rem}
.ep{display:flex;gap:1rem;padding:.35rem 0;font-family:monospace;font-size:.9rem}
.ep .m{color:#3fb950;font-weight:bold;min-width:52px}
.ep .p{color:#c9d1d9}
.ep .d{color:#484f58;margin-left:auto}
a{color:#58a6ff}
</style>
</head>
<body>
<div class="card">
<h1>⚡ Boko</h1>
<p class="tag">Async web framework for Bux — inspired by FastAPI</p>
<h3>Try it</h3>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/hello?name=Bux">/hello?name=Bux</a></span><span class="d">query param</span></div>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/users/42">/users/42</a></span><span class="d">path param</span></div>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/posts/7/comments/3">/posts/7/comments/3</a></span><span class="d">multi params</span></div>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/redirect">/redirect</a></span><span class="d">302 → /</span></div>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/api/health">/api/health</a></span><span class="d">JSON</span></div>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/api/info">/api/info</a></span><span class="d">JSON</span></div>
<h3>Features</h3>
<div class="ep"><span class="m">✓</span><span class="p">Path routing with {params}</span></div>
<div class="ep"><span class="m">✓</span><span class="p">Query parameter extraction</span></div>
<div class="ep"><span class="m">✓</span><span class="p">JSON / HTML / Text responses</span></div>
<div class="ep"><span class="m">✓</span><span class="p">Multi-threaded (configurable)</span></div>
<div class="ep"><span class="m">✓</span><span class="p">Redirects (302)</span></div>
<div class="ep"><span class="m">✓</span><span class="p">POST body access</span></div>
</div>
</body>
</html>`;
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Boko Framework</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:system-ui,sans-serif;background:#0d1117;color:#c9d1d9;min-height:100vh;display:flex;align-items:center;justify-content:center}
.card{background:#161b22;border:1px solid #30363d;border-radius:12px;padding:3rem;max-width:560px;width:100%}
h1{color:#58a6ff;font-size:2rem;margin-bottom:.5rem}
.tag{color:#8b949e;font-size:.9rem;margin-bottom:2rem}
h3{color:#d2a8ff;margin:1.5rem 0 .75rem}
.ep{display:flex;gap:1rem;padding:.35rem 0;font-family:monospace;font-size:.9rem}
.ep .m{color:#3fb950;font-weight:bold;min-width:52px}
.ep .p{color:#c9d1d9}
.ep .d{color:#484f58;margin-left:auto}
a{color:#58a6ff}
</style>
</head>
<body>
<div class="card">
<h1>⚡ Boko</h1>
<p class="tag">Async web framework for Bux — inspired by FastAPI</p>
<h3>Try it</h3>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/hello?name=Bux">/hello?name=Bux</a></span><span class="d">query param</span></div>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/users/42">/users/42</a></span><span class="d">path param</span></div>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/posts/7/comments/3">/posts/7/comments/3</a></span><span class="d">multi params</span></div>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/redirect">/redirect</a></span><span class="d">302 → /</span></div>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/api/health">/api/health</a></span><span class="d">JSON</span></div>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/api/info">/api/info</a></span><span class="d">JSON</span></div>
<h3>Features</h3>
<div class="ep"><span class="m">✓</span><span class="p">Path routing with {params}</span></div>
<div class="ep"><span class="m">✓</span><span class="p">Query parameter extraction</span></div>
<div class="ep"><span class="m">✓</span><span class="p">JSON / HTML / Text responses</span></div>
<div class="ep"><span class="m">✓</span><span class="p">Multi-threaded (configurable)</span></div>
<div class="ep"><span class="m">✓</span><span class="p">Redirects (302)</span></div>
<div class="ep"><span class="m">✓</span><span class="p">POST body access</span></div>
</div>
</body>
</html>`;
}
// =============================================================================
+112 -112
View File
@@ -5,131 +5,131 @@
// =============================================================================
module Main {
import Std::Io::{PrintLine, Print, PrintInt};
import Std::String::{
import Std::Io::{PrintLine, Print, PrintInt};
import Std::String::{
String_Len, String_Eq,
String_SplitCount, String_SplitPart,
StringBuilder, StringBuilder_New, StringBuilder_Append,
StringBuilder_AppendInt, StringBuilder_Build, StringBuilder_Free
};
import Std::Crypto::Jwt::{
};
import Std::Crypto::Jwt::{
JwtAlg,
Jwt_MakeHeader,
Jwt_Encode,
Jwt_Decode,
Jwt_EncodeHS256, Jwt_EncodeHS384, Jwt_EncodeHS512,
Jwt_EncodeRS256, Jwt_EncodeES256, Jwt_EncodeEdDSA
};
import Std::Crypto::Base64::{Base64URL_Decode, Base64_Encode};
import Std::Crypto::Ed25519::{Ed25519_Keypair};
};
import Std::Crypto::Base64::{Base64URL_Decode, Base64_Encode};
import Std::Crypto::Ed25519::{Ed25519_Keypair};
extern func bux_argc() -> int;
extern func bux_argv(index: int) -> String;
extern func bux_alloc(size: uint) -> *void;
extern func bux_read_file(path: String) -> String;
extern func bux_file_exists(path: String) -> int;
extern func bux_base64_encode(data: String, len: int) -> String;
extern func bux_argc() -> int;
extern func bux_argv(index: int) -> String;
extern func bux_alloc(size: uint) -> *void;
extern func bux_read_file(path: String) -> String;
extern func bux_file_exists(path: String) -> int;
extern func bux_base64_encode(data: String, len: int) -> String;
// =============================================================================
// Constants
// =============================================================================
const AppName: String = "jwt-pitbul";
const Version: String = "0.2.0";
// =============================================================================
// Constants
// =============================================================================
const AppName: String = "jwt-pitbul";
const Version: String = "0.2.0";
// =============================================================================
// Algebraic enums for optionals and results
// =============================================================================
enum AlgOption {
// =============================================================================
// Algebraic enums for optionals and results
// =============================================================================
enum AlgOption {
Some(JwtAlg),
None,
}
}
func AlgOption_MakeSome(value: JwtAlg) -> AlgOption {
func AlgOption_MakeSome(value: JwtAlg) -> AlgOption {
let o: AlgOption = AlgOption { tag: AlgOption_Some };
o.data.Some_0 = value;
return o;
}
}
func AlgOption_MakeNone() -> AlgOption {
func AlgOption_MakeNone() -> AlgOption {
return AlgOption { tag: AlgOption_None };
}
}
enum CmdOption {
enum CmdOption {
Some(Cmd),
None,
}
}
func CmdOption_MakeSome(value: Cmd) -> CmdOption {
func CmdOption_MakeSome(value: Cmd) -> CmdOption {
let o: CmdOption = CmdOption { tag: CmdOption_Some };
o.data.Some_0 = value;
return o;
}
}
func CmdOption_MakeNone() -> CmdOption {
func CmdOption_MakeNone() -> CmdOption {
return CmdOption { tag: CmdOption_None };
}
}
enum KeyTypeOption {
enum KeyTypeOption {
Some(KeyType),
None,
}
}
func KeyTypeOption_MakeSome(value: KeyType) -> KeyTypeOption {
func KeyTypeOption_MakeSome(value: KeyType) -> KeyTypeOption {
let o: KeyTypeOption = KeyTypeOption { tag: KeyTypeOption_Some };
o.data.Some_0 = value;
return o;
}
}
func KeyTypeOption_MakeNone() -> KeyTypeOption {
func KeyTypeOption_MakeNone() -> KeyTypeOption {
return KeyTypeOption { tag: KeyTypeOption_None };
}
}
enum KeyResult {
enum KeyResult {
Ok(String),
Err(String),
}
}
func KeyResult_MakeOk(value: String) -> KeyResult {
func KeyResult_MakeOk(value: String) -> KeyResult {
let r: KeyResult = KeyResult { tag: KeyResult_Ok };
r.data.Ok_0 = value;
return r;
}
}
func KeyResult_MakeErr(msg: String) -> KeyResult {
func KeyResult_MakeErr(msg: String) -> KeyResult {
let r: KeyResult = KeyResult { tag: KeyResult_Err };
r.data.Err_0 = msg;
return r;
}
}
enum ExitResult {
enum ExitResult {
Ok(int),
Err(String),
}
}
func ExitResult_MakeOk(value: int) -> ExitResult {
func ExitResult_MakeOk(value: int) -> ExitResult {
let r: ExitResult = ExitResult { tag: ExitResult_Ok };
r.data.Ok_0 = value;
return r;
}
}
func ExitResult_MakeErr(msg: String) -> ExitResult {
func ExitResult_MakeErr(msg: String) -> ExitResult {
let r: ExitResult = ExitResult { tag: ExitResult_Err };
r.data.Err_0 = msg;
return r;
}
}
// =============================================================================
// Commands
// =============================================================================
enum Cmd {
// =============================================================================
// Commands
// =============================================================================
enum Cmd {
Sign,
Verify,
Decode,
Keygen,
Help,
}
}
func ParseCmd(name: String) -> CmdOption {
func ParseCmd(name: String) -> CmdOption {
if String_Eq(name, "sign") { return CmdOption_MakeSome(Cmd { tag: Cmd_Sign }); }
if String_Eq(name, "verify") { return CmdOption_MakeSome(Cmd { tag: Cmd_Verify }); }
if String_Eq(name, "decode") { return CmdOption_MakeSome(Cmd { tag: Cmd_Decode }); }
@@ -138,28 +138,28 @@ func ParseCmd(name: String) -> CmdOption {
return CmdOption_MakeSome(Cmd { tag: Cmd_Help });
}
return CmdOption_MakeNone();
}
}
// =============================================================================
// Key type generation
// =============================================================================
enum KeyType {
// =============================================================================
// Key type generation
// =============================================================================
enum KeyType {
Rsa,
Ecdsa,
Ed25519,
}
}
func ParseKeyType(name: String) -> KeyTypeOption {
func ParseKeyType(name: String) -> KeyTypeOption {
if String_Eq(name, "rsa") { return KeyTypeOption_MakeSome(KeyType { tag: KeyType_Rsa }); }
if String_Eq(name, "ecdsa") { return KeyTypeOption_MakeSome(KeyType { tag: KeyType_Ecdsa }); }
if String_Eq(name, "ed25519") { return KeyTypeOption_MakeSome(KeyType { tag: KeyType_Ed25519 }); }
return KeyTypeOption_MakeNone();
}
}
// =============================================================================
// Extend JwtAlg with parsing / introspection methods
// =============================================================================
extend JwtAlg {
// =============================================================================
// Extend JwtAlg with parsing / introspection methods
// =============================================================================
extend JwtAlg {
func Name(self: JwtAlg) -> String {
match self {
JwtAlg::HS256 => "HS256",
@@ -184,9 +184,9 @@ extend JwtAlg {
self.tag == JwtAlg_ES256 || self.tag == JwtAlg_ES384;
}
}
}
func ParseAlg(name: String) -> AlgOption {
func ParseAlg(name: String) -> AlgOption {
if String_Eq(name, "HS256") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_HS256 }); }
if String_Eq(name, "HS384") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_HS384 }); }
if String_Eq(name, "HS512") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_HS512 }); }
@@ -197,12 +197,12 @@ func ParseAlg(name: String) -> AlgOption {
if String_Eq(name, "ES384") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_ES384 }); }
if String_Eq(name, "EdDSA") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_EdDSA }); }
return AlgOption_MakeNone();
}
}
// =============================================================================
// Help / Usage
// =============================================================================
func PrintUsage() {
// =============================================================================
// Help / Usage
// =============================================================================
func PrintUsage() {
PrintLine("╔══════════════════════════════════════════════════════╗");
PrintLine(f"║ {AppName} — JWT CLI Tool v{Version} ║");
PrintLine("║ Sign, verify, decode JSON Web Tokens ║");
@@ -243,12 +243,12 @@ func PrintUsage() {
PrintLine(f" {AppName} verify eyJh... HS256 'my-secret'");
PrintLine(f" {AppName} decode eyJh...");
PrintLine(f" {AppName} keygen ed25519");
}
}
// =============================================================================
// Key resolution — for RSA/ECDSA, read PEM file; for HMAC/EdDSA, pass through
// =============================================================================
func ResolveKey(alg: JwtAlg, keyArg: String) -> KeyResult {
// =============================================================================
// Key resolution — for RSA/ECDSA, read PEM file; for HMAC/EdDSA, pass through
// =============================================================================
func ResolveKey(alg: JwtAlg, keyArg: String) -> KeyResult {
if !alg.NeedsPemFile() {
return KeyResult_MakeOk(keyArg);
}
@@ -262,12 +262,12 @@ func ResolveKey(alg: JwtAlg, keyArg: String) -> KeyResult {
return KeyResult_MakeErr(f"ERROR: could not read PEM file: {keyArg}");
}
return KeyResult_MakeOk(pem);
}
}
// =============================================================================
// Command: sign
// =============================================================================
func CmdSign(algName: String, keyArg: String, claimsJson: String) -> ExitResult {
// =============================================================================
// Command: sign
// =============================================================================
func CmdSign(algName: String, keyArg: String, claimsJson: String) -> ExitResult {
let algOpt: AlgOption = ParseAlg(algName);
if algOpt.tag != AlgOption_Some {
return ExitResult_MakeErr(f"ERROR: unknown algorithm '{algName}'");
@@ -289,12 +289,12 @@ func CmdSign(algName: String, keyArg: String, claimsJson: String) -> ExitResult
PrintLine(token);
return ExitResult_MakeOk(0);
}
}
// =============================================================================
// Command: verify
// =============================================================================
func CmdVerify(token: String, algName: String, keyArg: String) -> ExitResult {
// =============================================================================
// Command: verify
// =============================================================================
func CmdVerify(token: String, algName: String, keyArg: String) -> ExitResult {
let algOpt: AlgOption = ParseAlg(algName);
if algOpt.tag != AlgOption_Some {
return ExitResult_MakeErr(f"ERROR: unknown algorithm '{algName}'");
@@ -322,12 +322,12 @@ func CmdVerify(token: String, algName: String, keyArg: String) -> ExitResult {
PrintLine("Payload:");
PrintLine(payload);
return ExitResult_MakeOk(0);
}
}
// =============================================================================
// Command: decode (no verification)
// =============================================================================
func CmdDecode(token: String) -> ExitResult {
// =============================================================================
// Command: decode (no verification)
// =============================================================================
func CmdDecode(token: String) -> ExitResult {
let partCount: uint = String_SplitCount(token, ".");
if partCount != 3 {
return ExitResult_MakeErr("ERROR: not a valid JWT (expected 3 parts)");
@@ -352,12 +352,12 @@ func CmdDecode(token: String) -> ExitResult {
PrintLine(sigB64);
return ExitResult_MakeOk(0);
}
}
// =============================================================================
// Command: keygen
// =============================================================================
func CmdKeygen(keyType: String) -> ExitResult {
// =============================================================================
// Command: keygen
// =============================================================================
func CmdKeygen(keyType: String) -> ExitResult {
let ktOpt: KeyTypeOption = ParseKeyType(keyType);
if ktOpt.tag != KeyTypeOption_Some {
return ExitResult_MakeErr(f"ERROR: unknown key type '{keyType}'. Use: rsa, ecdsa, ed25519");
@@ -394,12 +394,12 @@ func CmdKeygen(keyType: String) -> ExitResult {
return ExitResult_MakeOk(0);
}
return ExitResult_MakeErr(f"ERROR: unknown key type '{keyType}'");
}
}
// =============================================================================
// Dispatch a parsed command to its handler
// =============================================================================
func RunCommand(cmd: Cmd, args: Array<String>) -> ExitResult {
// =============================================================================
// Dispatch a parsed command to its handler
// =============================================================================
func RunCommand(cmd: Cmd, args: Array<String>) -> ExitResult {
if cmd.tag == Cmd_Help {
PrintUsage();
return ExitResult_MakeOk(0);
@@ -429,12 +429,12 @@ func RunCommand(cmd: Cmd, args: Array<String>) -> ExitResult {
return CmdKeygen(args[2]);
}
return ExitResult_MakeErr("ERROR: unhandled command");
}
}
// =============================================================================
// Main Entry Point
// =============================================================================
func Main() -> int {
// =============================================================================
// Main Entry Point
// =============================================================================
func Main() -> int {
let argc: int = bux_argc();
// Collect CLI arguments into a generic Array<String>
@@ -463,6 +463,6 @@ func Main() -> int {
return 1;
}
return result.data.Ok_0;
}
}
} // module Main
+4 -4
View File
@@ -1,14 +1,14 @@
module Config {
pub struct ServerConfig {
pub struct ServerConfig {
bindAddr: String;
port: int;
workerCount: int;
publicDir: String;
backlog: int;
}
}
pub const func DefaultConfig() -> ServerConfig {
pub const func DefaultConfig() -> ServerConfig {
return ServerConfig {
bindAddr: "0.0.0.0",
port: 8080,
@@ -16,6 +16,6 @@ pub const func DefaultConfig() -> ServerConfig {
publicDir: "public",
backlog: 128,
};
}
}
}
+23 -23
View File
@@ -1,65 +1,65 @@
module Errors {
import Http::{HttpRequest};
import Http::{HttpRequest};
pub enum HttpError {
pub enum HttpError {
BadRequest,
NotFound,
MethodNotAllowed,
InternalError(String),
}
}
pub enum ParseResult {
pub enum ParseResult {
Ok(HttpRequest),
Err(HttpError),
}
}
pub enum FileResult {
pub enum FileResult {
Ok(String),
Err(HttpError),
}
}
pub func ParseResult_NewOk(req: HttpRequest) -> ParseResult {
pub func ParseResult_NewOk(req: HttpRequest) -> ParseResult {
let r: ParseResult = ParseResult { tag: ParseResult_Ok };
r.data.Ok_0 = req;
return r;
}
}
pub func ParseResult_NewErr(err: HttpError) -> ParseResult {
pub func ParseResult_NewErr(err: HttpError) -> ParseResult {
let r: ParseResult = ParseResult { tag: ParseResult_Err };
r.data.Err_0 = err;
return r;
}
}
pub func FileResult_NewOk(content: String) -> FileResult {
pub func FileResult_NewOk(content: String) -> FileResult {
let r: FileResult = FileResult { tag: FileResult_Ok };
r.data.Ok_0 = content;
return r;
}
}
pub func FileResult_NewErr(err: HttpError) -> FileResult {
pub func FileResult_NewErr(err: HttpError) -> FileResult {
let r: FileResult = FileResult { tag: FileResult_Err };
r.data.Err_0 = err;
return r;
}
}
pub func FileResult_IsOk(r: FileResult) -> bool {
pub func FileResult_IsOk(r: FileResult) -> bool {
return r.tag == FileResult_Ok;
}
}
pub func FileResult_Unwrap(r: FileResult) -> String {
pub func FileResult_Unwrap(r: FileResult) -> String {
return r.data.Ok_0;
}
}
pub func FileResult_UnwrapErr(r: FileResult) -> HttpError {
pub func FileResult_UnwrapErr(r: FileResult) -> HttpError {
return r.data.Err_0;
}
}
pub func HttpError_ToString(err: HttpError) -> String {
pub func HttpError_ToString(err: HttpError) -> String {
if err.tag == HttpError_BadRequest { return "Bad Request"; }
if err.tag == HttpError_NotFound { return "Not Found"; }
if err.tag == HttpError_MethodNotAllowed { return "Method Not Allowed"; }
return err.data.InternalError_0;
}
}
}
+28 -28
View File
@@ -1,28 +1,28 @@
module Handlers {
import Std::String::{String_Eq, String_Contains};
import Http::{HttpMethod, HttpRequest, HttpResponse, Http_NewResponse, Http_MimeType, RequestHeader_Get};
import Errors::{HttpError, FileResult, FileResult_NewOk, FileResult_NewErr, FileResult_IsOk, FileResult_Unwrap, FileResult_UnwrapErr, HttpError_ToString};
import Std::String::{String_Eq, String_Contains};
import Http::{HttpMethod, HttpRequest, HttpResponse, Http_NewResponse, Http_MimeType, RequestHeader_Get};
import Errors::{HttpError, FileResult, FileResult_NewOk, FileResult_NewErr, FileResult_IsOk, FileResult_Unwrap, FileResult_UnwrapErr, HttpError_ToString};
extern func bux_strlen(s: String) -> uint;
extern func bux_file_exists(path: String) -> int;
extern func bux_read_file(path: String) -> String;
extern func bux_path_join(a: String, b: String) -> String;
extern func bux_sb_new(initial_cap: uint) -> *void;
extern func bux_sb_append(sb: *void, s: String);
extern func bux_sb_append_int(sb: *void, n: int64);
extern func bux_sb_build(sb: *void) -> String;
extern func bux_sb_free(sb: *void);
extern func bux_strlen(s: String) -> uint;
extern func bux_file_exists(path: String) -> int;
extern func bux_read_file(path: String) -> String;
extern func bux_path_join(a: String, b: String) -> String;
extern func bux_sb_new(initial_cap: uint) -> *void;
extern func bux_sb_append(sb: *void, s: String);
extern func bux_sb_append_int(sb: *void, n: int64);
extern func bux_sb_build(sb: *void) -> String;
extern func bux_sb_free(sb: *void);
pub func NotFoundResponse() -> HttpResponse {
pub func NotFoundResponse() -> HttpResponse {
return Http_NewResponse(404, "application/json; charset=utf-8", "{\"error\":\"not_found\"}");
}
}
pub func MethodNotAllowedResponse() -> HttpResponse {
pub func MethodNotAllowedResponse() -> HttpResponse {
return Http_NewResponse(405, "text/plain; charset=utf-8", "Method Not Allowed");
}
}
pub func ReadStaticFile(requestPath: String) -> FileResult {
pub func ReadStaticFile(requestPath: String) -> FileResult {
if String_Contains(requestPath, "..") {
return FileResult_NewErr(HttpError { tag: HttpError_NotFound });
}
@@ -39,16 +39,16 @@ pub func ReadStaticFile(requestPath: String) -> FileResult {
let content: String = bux_read_file(fullPath);
return FileResult_NewOk(content);
}
}
func FileErrorResponse(err: HttpError) -> HttpResponse {
func FileErrorResponse(err: HttpError) -> HttpResponse {
match err {
HttpError::NotFound => NotFoundResponse(),
_ => Http_NewResponse(500, "text/plain; charset=utf-8", HttpError_ToString(err)),
}
}
}
pub func ServeStaticFile(req: HttpRequest) -> HttpResponse {
pub func ServeStaticFile(req: HttpRequest) -> HttpResponse {
if req.method != HttpMethod_GET && req.method != HttpMethod_HEAD {
return MethodNotAllowedResponse();
}
@@ -59,19 +59,19 @@ pub func ServeStaticFile(req: HttpRequest) -> HttpResponse {
return Http_NewResponse(200, Http_MimeType(req.path), content);
}
return FileErrorResponse(FileResult_UnwrapErr(result));
}
}
pub func HandleApiHealth() -> HttpResponse {
pub func HandleApiHealth() -> HttpResponse {
return Http_NewResponse(200, "application/json; charset=utf-8",
"{\"status\":\"ok\",\"server\":\"Nexus\",\"version\":\"0.2.0\"}");
}
}
pub func HandleApiInfo() -> HttpResponse {
pub func HandleApiInfo() -> HttpResponse {
return Http_NewResponse(200, "application/json; charset=utf-8",
"{\"name\":\"Nexus\",\"language\":\"Bux\",\"features\":[\"HTTP/1.1\",\"thread-pool\",\"algebraic-enums\"]}");
}
}
pub func HandleWebSocketUpgrade(req: HttpRequest) -> HttpResponse {
pub func HandleWebSocketUpgrade(req: HttpRequest) -> HttpResponse {
let wsKey: String = RequestHeader_Get(req, "Sec-WebSocket-Key");
var resp: HttpResponse;
@@ -89,6 +89,6 @@ pub func HandleWebSocketUpgrade(req: HttpRequest) -> HttpResponse {
bux_sb_free(sb);
return resp;
}
}
}
+20 -20
View File
@@ -1,9 +1,9 @@
module Http {
import Std::Array::{Array};
import Std::String::{String_Eq, String_EndsWith, String_Contains};
import Std::Array::{Array};
import Std::String::{String_Eq, String_EndsWith, String_Contains};
pub enum HttpMethod {
pub enum HttpMethod {
GET,
POST,
PUT,
@@ -12,29 +12,29 @@ pub enum HttpMethod {
HEAD,
OPTIONS,
UNKNOWN,
}
}
pub struct HeaderEntry {
pub struct HeaderEntry {
key: String;
value: String;
}
}
pub struct HttpRequest {
pub struct HttpRequest {
method: HttpMethod;
path: String;
version: String;
body: String;
headers: Array<HeaderEntry>;
}
}
pub struct HttpResponse {
pub struct HttpResponse {
statusCode: int;
contentType: String;
body: String;
extraHeaders: String;
}
}
pub func Http_StatusText(code: int) -> String {
pub func Http_StatusText(code: int) -> String {
if code == 200 { return "OK"; }
if code == 201 { return "Created"; }
if code == 204 { return "No Content"; }
@@ -52,9 +52,9 @@ pub func Http_StatusText(code: int) -> String {
if code == 501 { return "Not Implemented"; }
if code == 503 { return "Service Unavailable"; }
return "Unknown";
}
}
pub func Http_MimeType(path: String) -> String {
pub func Http_MimeType(path: String) -> String {
if String_EndsWith(path, ".html") || String_EndsWith(path, ".htm") { return "text/html; charset=utf-8"; }
if String_EndsWith(path, ".css") { return "text/css; charset=utf-8"; }
if String_EndsWith(path, ".js") { return "application/javascript; charset=utf-8"; }
@@ -71,9 +71,9 @@ pub func Http_MimeType(path: String) -> String {
if String_EndsWith(path, ".woff") { return "font/woff"; }
if String_EndsWith(path, ".wasm") { return "application/wasm"; }
return "application/octet-stream";
}
}
pub func Http_MethodName(m: HttpMethod) -> String {
pub func Http_MethodName(m: HttpMethod) -> String {
match m {
HttpMethod::GET => "GET",
HttpMethod::POST => "POST",
@@ -84,24 +84,24 @@ pub func Http_MethodName(m: HttpMethod) -> String {
HttpMethod::OPTIONS => "OPTIONS",
HttpMethod::UNKNOWN => "UNKNOWN",
}
}
}
pub func Http_NewResponse(code: int, contentType: String, body: String) -> HttpResponse {
pub func Http_NewResponse(code: int, contentType: String, body: String) -> HttpResponse {
var resp: HttpResponse;
resp.statusCode = code;
resp.contentType = contentType;
resp.body = body;
resp.extraHeaders = "";
return resp;
}
}
pub func RequestHeader_Get(req: HttpRequest, key: String) -> String {
pub func RequestHeader_Get(req: HttpRequest, key: String) -> String {
for entry in req.headers {
if String_Eq(entry.key, key) {
return entry.value;
}
}
return "";
}
}
}
+9 -9
View File
@@ -1,12 +1,12 @@
module Main {
import Config::{ServerConfig, DefaultConfig};
import Http::{HttpMethod};
import Router::{Handler, Route, Router};
import Server::{RunServer};
import Std::Array::{Array, Array_New, Array_Push};
import Config::{ServerConfig, DefaultConfig};
import Http::{HttpMethod};
import Router::{Handler, Route, Router};
import Server::{RunServer};
import Std::Array::{Array, Array_New, Array_Push};
func BuildRouter() -> Router {
func BuildRouter() -> Router {
var routes: Array<Route> = Array_New<Route>(8);
Array_Push<Route>(&routes, Route {
@@ -37,13 +37,13 @@ func BuildRouter() -> Router {
routes: routes,
notFound: Handler { tag: Handler_NotFound },
};
}
}
func Main() -> int {
func Main() -> int {
let config: ServerConfig = DefaultConfig();
let router: Router = BuildRouter();
return RunServer(config, router);
}
}
}
+18 -18
View File
@@ -1,16 +1,16 @@
module Parser {
import Std::String::{String_Len, String_Eq, String_Trim};
import Std::Array::{Array, Array_New, Array_Push};
import Http::{HttpMethod, HttpRequest, HeaderEntry};
import Errors::{HttpError, ParseResult, ParseResult_NewOk, ParseResult_NewErr};
import Std::String::{String_Len, String_Eq, String_Trim};
import Std::Array::{Array, Array_New, Array_Push};
import Http::{HttpMethod, HttpRequest, HeaderEntry};
import Errors::{HttpError, ParseResult, ParseResult_NewOk, ParseResult_NewErr};
extern func bux_strlen(s: String) -> uint;
extern func bux_strstr(haystack: String, needle: String) -> String;
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
extern func bux_str_offset(pos: String, base: String) -> uint;
extern func bux_strlen(s: String) -> uint;
extern func bux_strstr(haystack: String, needle: String) -> String;
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
extern func bux_str_offset(pos: String, base: String) -> uint;
pub func ParseMethod(s: String) -> HttpMethod {
pub func ParseMethod(s: String) -> HttpMethod {
if String_Eq(s, "GET") { return HttpMethod { tag: HttpMethod_GET }; }
if String_Eq(s, "POST") { return HttpMethod { tag: HttpMethod_POST }; }
if String_Eq(s, "PUT") { return HttpMethod { tag: HttpMethod_PUT }; }
@@ -19,14 +19,14 @@ pub func ParseMethod(s: String) -> HttpMethod {
if String_Eq(s, "HEAD") { return HttpMethod { tag: HttpMethod_HEAD }; }
if String_Eq(s, "OPTIONS") { return HttpMethod { tag: HttpMethod_OPTIONS }; }
return HttpMethod { tag: HttpMethod_UNKNOWN };
}
}
func Slice(raw: String, start: int, len: int) -> String {
func Slice(raw: String, start: int, len: int) -> String {
if start < 0 || len <= 0 { return ""; }
return bux_str_slice(raw, start as uint, len as uint);
}
}
func FindCrlf(raw: String, start: int) -> int {
func FindCrlf(raw: String, start: int) -> int {
let rawLen: uint = bux_strlen(raw);
if start as uint >= rawLen { return -1; }
let tail: String = bux_str_slice(raw, start as uint, rawLen - start as uint);
@@ -34,9 +34,9 @@ func FindCrlf(raw: String, start: int) -> int {
if String_Len(hit) == 0 { return -1; }
let offset: uint = bux_str_offset(hit, tail);
return start + offset as int;
}
}
func ParseHeaders(raw: String, start: int, end: int) -> Array<HeaderEntry> {
func ParseHeaders(raw: String, start: int, end: int) -> Array<HeaderEntry> {
var headers: Array<HeaderEntry> = Array_New<HeaderEntry>(16);
var pos: int = start;
while pos < end {
@@ -58,9 +58,9 @@ func ParseHeaders(raw: String, start: int, end: int) -> Array<HeaderEntry> {
pos = lineEnd + 2;
}
return headers;
}
}
pub func ParseRequest(raw: String) -> ParseResult {
pub func ParseRequest(raw: String) -> ParseResult {
let rawLen: uint = bux_strlen(raw);
if rawLen == 0 {
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
@@ -125,6 +125,6 @@ pub func ParseRequest(raw: String) -> ParseResult {
headers: headers,
};
return ParseResult_NewOk(req);
}
}
}
+14 -14
View File
@@ -1,30 +1,30 @@
module Router {
import Std::Array::{Array};
import Std::String::{String_Eq};
import Http::{HttpMethod, HttpRequest, HttpResponse, Http_NewResponse};
import Handlers::{ServeStaticFile, HandleApiHealth, HandleApiInfo, HandleWebSocketUpgrade, NotFoundResponse};
import Std::Array::{Array};
import Std::String::{String_Eq};
import Http::{HttpMethod, HttpRequest, HttpResponse, Http_NewResponse};
import Handlers::{ServeStaticFile, HandleApiHealth, HandleApiInfo, HandleWebSocketUpgrade, NotFoundResponse};
pub enum Handler {
pub enum Handler {
StaticFile,
ApiHealth,
ApiInfo,
WsUpgrade,
NotFound,
}
}
pub struct Route {
pub struct Route {
method: HttpMethod;
path: String;
handler: Handler;
}
}
pub struct Router {
pub struct Router {
routes: Array<Route>;
notFound: Handler;
}
}
pub func Handler_Handle(h: Handler, req: HttpRequest) -> HttpResponse {
pub func Handler_Handle(h: Handler, req: HttpRequest) -> HttpResponse {
match h {
Handler::StaticFile => ServeStaticFile(req),
Handler::ApiHealth => HandleApiHealth(),
@@ -32,15 +32,15 @@ pub func Handler_Handle(h: Handler, req: HttpRequest) -> HttpResponse {
Handler::WsUpgrade => HandleWebSocketUpgrade(req),
Handler::NotFound => NotFoundResponse(),
}
}
}
pub func Router_Dispatch(r: Router, req: HttpRequest) -> HttpResponse {
pub func Router_Dispatch(r: Router, req: HttpRequest) -> HttpResponse {
for route in r.routes {
if route.method == req.method && String_Eq(route.path, req.path) {
return Handler_Handle(route.handler, req);
}
}
return Handler_Handle(r.notFound, req);
}
}
}
+31 -31
View File
@@ -1,27 +1,27 @@
module Server {
import Std::Io::{Print, PrintLine, PrintInt};
import Std::Net::{Net_Create, Net_SetReuse, Net_Bind, Net_Listen, Net_Accept, Net_Send, Net_Recv, Net_Close, Net_LastError};
import Std::String::{String_Len, String_StartsWith};
import Std::Channel::{Channel, Channel_New, Channel_Send, Channel_Recv};
import Config::{ServerConfig};
import Http::{HttpRequest, HttpResponse, Http_StatusText, Http_NewResponse};
import Errors::{ParseResult};
import Parser::{ParseRequest};
import Router::{Router, Router_Dispatch};
import Std::Io::{Print, PrintLine, PrintInt};
import Std::Net::{Net_Create, Net_SetReuse, Net_Bind, Net_Listen, Net_Accept, Net_Send, Net_Recv, Net_Close, Net_LastError};
import Std::String::{String_Len, String_StartsWith};
import Std::Channel::{Channel, Channel_New, Channel_Send, Channel_Recv};
import Config::{ServerConfig};
import Http::{HttpRequest, HttpResponse, Http_StatusText, Http_NewResponse};
import Errors::{ParseResult};
import Parser::{ParseRequest};
import Router::{Router, Router_Dispatch};
extern func bux_strlen(s: String) -> uint;
extern func bux_sb_new(initial_cap: uint) -> *void;
extern func bux_sb_append(sb: *void, s: String);
extern func bux_sb_append_int(sb: *void, n: int64);
extern func bux_sb_build(sb: *void) -> String;
extern func bux_sb_free(sb: *void);
extern func bux_strlen(s: String) -> uint;
extern func bux_sb_new(initial_cap: uint) -> *void;
extern func bux_sb_append(sb: *void, s: String);
extern func bux_sb_append_int(sb: *void, n: int64);
extern func bux_sb_build(sb: *void) -> String;
extern func bux_sb_free(sb: *void);
pub struct ConnectionTask {
pub struct ConnectionTask {
fd: int;
}
}
pub func BuildResponse(resp: HttpResponse) -> String {
pub func BuildResponse(resp: HttpResponse) -> String {
let sb: *void = bux_sb_new(4096);
bux_sb_append(sb, "HTTP/1.1 ");
@@ -56,9 +56,9 @@ pub func BuildResponse(resp: HttpResponse) -> String {
let result: String = bux_sb_build(sb);
bux_sb_free(sb);
return result;
}
}
pub func HandleConnection(fd: int, router: Router) {
pub func HandleConnection(fd: int, router: Router) {
let raw: String = Net_Recv(fd, 8192);
if String_Len(raw) == 0 {
return;
@@ -81,27 +81,27 @@ pub func HandleConnection(fd: int, router: Router) {
let resp: HttpResponse = Http_NewResponse(400, "text/plain; charset=utf-8", "Bad Request");
Net_Send(fd, BuildResponse(resp));
}
}
}
pub struct WorkerCtx {
pub struct WorkerCtx {
taskQueue: *Channel<ConnectionTask>;
router: Router;
}
}
pub func Worker(ctx: *WorkerCtx) {
pub func Worker(ctx: *WorkerCtx) {
while true {
let task: ConnectionTask = Channel_Recv<ConnectionTask>(ctx.taskQueue);
HandleConnection(task.fd, ctx.router);
Net_Close(task.fd);
}
}
}
pub struct AcceptorCtx {
pub struct AcceptorCtx {
serverFd: int;
taskQueue: *Channel<ConnectionTask>;
}
}
pub func Acceptor(ctx: *AcceptorCtx) {
pub func Acceptor(ctx: *AcceptorCtx) {
while true {
let fd: int = Net_Accept(ctx.serverFd);
if fd >= 0 {
@@ -109,9 +109,9 @@ pub func Acceptor(ctx: *AcceptorCtx) {
Channel_Send<ConnectionTask>(ctx.taskQueue, task);
}
}
}
}
pub func RunServer(config: ServerConfig, router: Router) -> int {
pub func RunServer(config: ServerConfig, router: Router) -> int {
PrintLine("================================================");
PrintLine(" Nexus HTTP Server v0.2.0");
PrintLine(" Production-ready HTTP/1.1 with thread-pool");
@@ -171,6 +171,6 @@ pub func RunServer(config: ServerConfig, router: Router) -> int {
Worker(&workerCtx);
return 0;
}
}
}
+41 -41
View File
@@ -1,43 +1,43 @@
module Main {
import Std::Io::{PrintLine, Print, PrintInt, ReadFile, WriteFile, FileExists};
import Std::Os::{Os_ArgsCount, Os_Args};
import Std::String::{String_Len, String_Eq, String_StartsWith, String_Find, String_Slice, String_Offset};
import Std::Io::{PrintLine, Print, PrintInt, ReadFile, WriteFile, FileExists};
import Std::Os::{Os_ArgsCount, Os_Args};
import Std::String::{String_Len, String_Eq, String_StartsWith, String_Find, String_Slice, String_Offset};
extern func bux_strlen(s: String) -> uint;
extern func bux_strstr(haystack: String, needle: String) -> String;
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
extern func bux_str_split_count(s: String, delim: String) -> uint;
extern func bux_str_split_part(s: String, delim: String, index: uint) -> String;
extern func bux_sb_new(initial_cap: uint) -> *void;
extern func bux_sb_append(sb: *void, s: String);
extern func bux_sb_append_char(sb: *void, c: char8);
extern func bux_strlen(s: String) -> uint;
extern func bux_strstr(haystack: String, needle: String) -> String;
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
extern func bux_str_split_count(s: String, delim: String) -> uint;
extern func bux_str_split_part(s: String, delim: String, index: uint) -> String;
extern func bux_sb_new(initial_cap: uint) -> *void;
extern func bux_sb_append(sb: *void, s: String);
extern func bux_sb_append_char(sb: *void, c: char8);
func SB_AppendChar(sb: *void, c: int) {
func SB_AppendChar(sb: *void, c: int) {
bux_sb_append_char(sb, c as char8);
}
extern func bux_sb_build(sb: *void) -> String;
extern func bux_sb_free(sb: *void);
extern func bux_alloc(size: uint) -> *void;
extern func bux_free(ptr: *void);
}
extern func bux_sb_build(sb: *void) -> String;
extern func bux_sb_free(sb: *void);
extern func bux_alloc(size: uint) -> *void;
extern func bux_free(ptr: *void);
struct Database {
struct Database {
path: String,
sb: *void,
loaded: bool,
count: uint,
}
}
func DB_New(filePath: String) -> Database {
func DB_New(filePath: String) -> Database {
var db: Database;
db.path = filePath;
db.sb = bux_sb_new(4096);
db.loaded = false;
db.count = 0;
return db;
}
}
func DB_Load(self: *Database) -> bool {
func DB_Load(self: *Database) -> bool {
if self.loaded { return true; }
if !FileExists(self.path) {
@@ -66,9 +66,9 @@ func DB_Load(self: *Database) -> bool {
self.count = n;
self.loaded = true;
return true;
}
}
func DB_Get(self: *Database, key: String) -> String {
func DB_Get(self: *Database, key: String) -> String {
let raw: String = bux_sb_build(self.sb);
let lineCount: uint = bux_str_split_count(raw, "\n");
var i: uint = 0;
@@ -88,9 +88,9 @@ func DB_Get(self: *Database, key: String) -> String {
i = i + 1;
}
return "";
}
}
func DB_Set(self: *Database, key: String, value: String) {
func DB_Set(self: *Database, key: String, value: String) {
let raw: String = bux_sb_build(self.sb);
let lineCount: uint = bux_str_split_count(raw, "\n");
@@ -130,9 +130,9 @@ func DB_Set(self: *Database, key: String, value: String) {
bux_sb_free(self.sb);
self.sb = newSb;
}
}
func DB_Del(self: *Database, key: String) {
func DB_Del(self: *Database, key: String) {
let raw: String = bux_sb_build(self.sb);
let lineCount: uint = bux_str_split_count(raw, "\n");
@@ -164,18 +164,18 @@ func DB_Del(self: *Database, key: String) {
bux_sb_free(self.sb);
self.sb = newSb;
}
}
func DB_Has(self: *Database, key: String) -> bool {
func DB_Has(self: *Database, key: String) -> bool {
let val: String = DB_Get(self, key);
return bux_strlen(val) > 0;
}
}
func DB_Count(self: *Database) -> uint {
func DB_Count(self: *Database) -> uint {
return self.count;
}
}
func DB_Keys(self: *Database) -> *String {
func DB_Keys(self: *Database) -> *String {
if self.count == 0 { return null as *String; }
let raw: String = bux_sb_build(self.sb);
@@ -198,15 +198,15 @@ func DB_Keys(self: *Database) -> *String {
}
return keys;
}
}
func DB_Save(self: *Database) -> bool {
func DB_Save(self: *Database) -> bool {
let content: String = bux_sb_build(self.sb);
let ok: bool = WriteFile(self.path, content);
return ok;
}
}
func PrintUsage() {
func PrintUsage() {
PrintLine("SimpleDB — file-backed key-value database");
PrintLine("Usage:");
PrintLine(" simpledb <dbfile> set <key> <value>");
@@ -215,9 +215,9 @@ func PrintUsage() {
PrintLine(" simpledb <dbfile> has <key>");
PrintLine(" simpledb <dbfile> keys");
PrintLine(" simpledb <dbfile> count");
}
}
func Main() -> int {
func Main() -> int {
let argc: int = Os_ArgsCount();
if argc < 3 {
@@ -334,6 +334,6 @@ func Main() -> int {
Print("unknown command: ");
PrintLine(cmd);
return 1;
}
}
}
+323 -15
View File
@@ -1,6 +1,9 @@
import std/[os, strutils, terminal, strformat, osproc, sets]
import std/[os, strutils, terminal, strformat, osproc, sets, algorithm, tables]
import lexer, parser, ast, sema, manifest, hir_lower, lir_lower, lir_c_backend
import source_location
import fmt
import docgen
import registry
type
ColorMode* = enum
@@ -21,16 +24,25 @@ Usage: bux [options] <command> [command-options]
Commands:
new <name> Create a new Bux package
init Initialize a Bux package in the current directory
add <name> [ver] Add a dependency (--path, --git)
add <name> [ver] Add a dependency (--path, --git, or registry)
install Resolve and install dependencies
search [query] Search the package registry
build Build the current package
run Build and run the current package
test Run tests in tests/ directory
check Type-check the current package
fmt [path] Format .bux sources (default: .)
doc [path] Generate Markdown API docs from /// comments
clean Remove build artifacts
help Show this help message
version Show version
Command options:
test --filter <s> Only run tests whose name contains <s>
fmt --check Exit 1 if any file would be reformatted (CI)
doc --out <file> Write docs to file (default: stdout)
add --path / --git Explicit source; else resolve via registry
Global options:
--color <auto|on|off> Control colored output (default: auto)
-q, --quiet Suppress non-error output
@@ -192,6 +204,14 @@ proc hintForMessage(msg: string): string =
return "provide the missing argument (positional or named)"
if "use of moved value" in m:
return "the value was moved; clone it or restructure ownership"
if "cannot return reference to local" in m:
return "return a value, or return a reference borrowed from a function parameter"
if "lifetime elision failed" in m:
return "add an explicit lifetime, e.g. func F<'a>(x: &'a T, y: &'a U) -> &'a T"
if "lifetime mismatch" in m:
return "returned reference must share a lifetime with the return type (annotate with 'a)"
if "no input reference to borrow from" in m:
return "add a '&T' parameter to borrow from, or return an owned value"
if "shared reference" in m or "checked function" in m:
return "use '&mut T' for mutation, or drop @[Checked] for unchecked code"
if "double mutable borrow" in m or "already mutably borrowed" in m:
@@ -378,7 +398,11 @@ proc cmdAdd*(args: seq[string], opts: GlobalOptions): int =
printError("--git requires a value", useColor)
return 1
else:
if not args[i].startsWith("-"):
version = args[i]
else:
printError(&"unknown add option '{args[i]}'", useColor)
return 1
inc i
# Append to bux.toml
var depLine = ""
@@ -387,7 +411,19 @@ proc cmdAdd*(args: seq[string], opts: GlobalOptions): int =
elif gitUrl.len > 0:
depLine = &"{depName} = {{ Version = \"{version}\", Source = \"{gitUrl}\" }}"
else:
depLine = &"{depName} = \"{version}\""
# Registry resolve (E.1)
let reg = loadRegistry()
if reg.path.len == 0:
printError("no package registry found (set BUX_REGISTRY or install config/registry.toml)", useColor)
return 1
let pkg = registryLookup(reg, depName, version)
if pkg.name.len == 0:
printError(&"package '{depName}' not found in registry ({reg.path})", useColor)
printError("hint: bux search | bux add name --git <url> | bux add name --path <dir>", useColor)
return 1
depLine = formatRegistryDepLine(depName, pkg)
if not opts.quiet:
printInfo(&"Resolved '{depName}' {pkg.version} from registry {reg.path}", useColor)
var content = readFile(manifestPath)
# Ensure [Dependencies] section exists
if content.find("[Dependencies]") < 0:
@@ -399,6 +435,34 @@ proc cmdAdd*(args: seq[string], opts: GlobalOptions): int =
printInfo(&"Added dependency '{depName}' to bux.toml", useColor)
return 0
proc cmdSearch*(args: seq[string], opts: GlobalOptions): int =
let useColor = shouldUseColor(opts)
let query = if args.len > 0: args[0] else: ""
let reg = loadRegistry()
if reg.path.len == 0:
printError("no package registry found (set BUX_REGISTRY)", useColor)
return 1
if not opts.quiet:
echo &"Registry: {reg.path}"
let hits = registrySearch(reg, query)
if hits.len == 0:
if not opts.quiet:
echo "No packages matched."
return 1
# Dedupe by name showing latest version
var seen = initTable[string, RegistryPackage]()
for p in hits:
seen[p.name.toLowerAscii()] = p
var names: seq[string] = @[]
for k in seen.keys:
names.add(k)
names.sort(system.cmp)
for k in names:
let p = seen[k]
let desc = if p.description.len > 0: p.description else: p.source
echo &" {p.name} {p.version} — {desc}"
return 0
proc cmdInstall*(args: seq[string], opts: GlobalOptions): int =
let useColor = shouldUseColor(opts)
let root = getCurrentDir()
@@ -411,6 +475,7 @@ proc cmdInstall*(args: seq[string], opts: GlobalOptions): int =
let cacheDir = getHomeDir() / ".bux" / "packages"
if not dirExists(cacheDir):
createDir(cacheDir)
let reg = loadRegistry()
# Resolve each dependency
for dep in man.dependencies:
case dep.kind
@@ -433,20 +498,43 @@ proc cmdInstall*(args: seq[string], opts: GlobalOptions): int =
if not dirExists(depDir):
if not opts.quiet:
printInfo(&"Cloning '{dep.name}' from {dep.gitUrl}...", useColor)
let (outp, code) = execCmdEx(&"git clone {dep.gitUrl} {depDir} 2>&1")
let (outp, code) = execCmdEx(&"git clone --quiet {quoteShell(dep.gitUrl)} {quoteShell(depDir)} 2>&1")
if code != 0:
printError(&"failed to clone {dep.gitUrl}: {outp}", useColor)
return 1
else:
if not opts.quiet:
printInfo(&"Using cached '{dep.name}' from {depDir}", useColor)
# Lock stores git URL; build loads from cache by name
lock.entries.add(LockEntry(name: dep.name, version: dep.gitVersion, source: dep.gitUrl))
of dkVersion:
# For version-based deps without a registry, we just record them
# TODO: lookup in registry
lock.entries.add(LockEntry(name: dep.name, version: dep.versionReq, source: "registry"))
# Registry lookup (E.1)
if reg.path.len == 0:
printError(&"cannot resolve '{dep.name}': no package registry (set BUX_REGISTRY)", useColor)
return 1
let pkg = registryLookup(reg, dep.name, dep.versionReq)
if pkg.name.len == 0:
printError(&"package '{dep.name}' not found in registry", useColor)
return 1
if pkg.resolvedPath.len > 0 and dirExists(pkg.resolvedPath):
lock.entries.add(LockEntry(name: dep.name, version: pkg.version, source: pkg.resolvedPath))
if not opts.quiet:
printInfo(&"Recorded dependency '{dep.name}' = {dep.versionReq}", useColor)
printInfo(&"Resolved '{dep.name}' {pkg.version} → {pkg.resolvedPath}", useColor)
elif isGitSource(pkg.source):
let depDir = cacheDir / dep.name
if not dirExists(depDir):
if not opts.quiet:
printInfo(&"Cloning '{dep.name}' from {pkg.source}...", useColor)
let (outp, code) = execCmdEx(&"git clone --quiet {quoteShell(pkg.source)} {quoteShell(depDir)} 2>&1")
if code != 0:
printError(&"failed to clone {pkg.source}: {outp}", useColor)
return 1
lock.entries.add(LockEntry(name: dep.name, version: pkg.version, source: pkg.source))
if not opts.quiet:
printInfo(&"Resolved '{dep.name}' {pkg.version} → git {pkg.source}", useColor)
else:
printError(&"registry entry '{dep.name}' has unusable source '{pkg.source}'", useColor)
return 1
# Save lockfile
let lockPath = root / "bux.lock"
saveLockfile(lockPath, lock)
@@ -722,18 +810,93 @@ proc cmdClean*(args: seq[string], opts: GlobalOptions): int =
printInfo("clean: build directory removed", useColor)
return 0
proc parseTestArgs(args: seq[string]): tuple[filter: string, paths: seq[string], ok: bool] =
## Parse `test` args: optional `--filter <s>` / `--filter=<s>`, rest are ignored paths.
result.filter = ""
result.paths = @[]
result.ok = true
var i = 0
while i < args.len:
let a = args[i]
if a == "--filter":
if i + 1 >= args.len:
stderr.writeLine("error: --filter requires an argument")
result.ok = false
return
inc i
result.filter = args[i]
elif a.startsWith("--filter="):
result.filter = a["--filter=".len .. ^1]
elif a == "--help" or a == "-h":
echo "Usage: bux test [--filter <name>] [project-dir]"
echo " --filter <name> Only run tests whose filename contains <name>"
result.ok = false # treat as early exit without error in caller? use special
# Signal help via empty filter and a sentinel path
result.paths = @["__help__"]
return
elif a.startsWith("-"):
stderr.writeLine(&"error: unknown test option '{a}'")
result.ok = false
return
else:
result.paths.add(a)
inc i
proc parseFmtArgs(args: seq[string]): tuple[checkOnly: bool, paths: seq[string], ok: bool, help: bool] =
result.checkOnly = false
result.paths = @[]
result.ok = true
result.help = false
var i = 0
while i < args.len:
let a = args[i]
if a == "--check":
result.checkOnly = true
elif a == "--help" or a == "-h":
result.help = true
return
elif a.startsWith("-"):
stderr.writeLine(&"error: unknown fmt option '{a}'")
result.ok = false
return
else:
result.paths.add(a)
inc i
proc cmdTest*(args: seq[string], opts: GlobalOptions): int =
let useColor = shouldUseColor(opts)
let root = getCurrentDir()
let (filter, paths, ok) = parseTestArgs(args)
if not ok:
if paths.len == 1 and paths[0] == "__help__":
return 0
return 1
let root = if paths.len > 0: absolutePath(paths[0]) else: getCurrentDir()
let testsDir = root / "tests"
var testFiles: seq[string] = @[]
if dirExists(testsDir):
for kind, path in walkDir(testsDir):
if kind == pcFile and path.endsWith(".bux"):
let testName = splitFile(path).name
if filter.len > 0 and filter notin testName:
continue
testFiles.add(path)
testFiles.sort(system.cmp)
if testFiles.len == 0:
if filter.len > 0:
printError(&"no tests matching filter '{filter}' in tests/", useColor)
else:
printError("no tests found in tests/ directory", useColor)
return 1
if not opts.quiet:
if filter.len > 0:
echo &"Running tests (filter: {filter}) in {testsDir}"
else:
echo &"Running tests in {testsDir}"
echo "┌──────────────────────────────┬────────┐"
echo "│ Test │ Status │"
echo "├──────────────────────────────┼────────┤"
var passed = 0
var failed = 0
for testFile in testFiles:
@@ -742,26 +905,168 @@ proc cmdTest*(args: seq[string], opts: GlobalOptions): int =
removeDir(tmpDir)
createDir(tmpDir / "src")
copyFile(testFile, tmpDir / "src" / "Main.bux")
writeFile(tmpDir / "bux.toml", "[package]\nname = \"" & testName & "\"\nversion = \"0.1.0\"\n")
writeFile(tmpDir / "bux.toml",
"[Package]\nName = \"" & testName & "\"\nVersion = \"0.1.0\"\nType = \"bin\"\n\n[Build]\nOutput = \"Bin\"\n")
let buildRes = cmdBuild(@[tmpDir], opts)
var status: string
var statusOk = false
if buildRes != 0:
printError(&" FAIL {testName} (build)", useColor)
status = "FAIL"
failed += 1
continue
else:
var execFile = tmpDir / "build" / testName
if not fileExists(execFile):
execFile = tmpDir / "build" / "bux_out"
let exitCode = execCmd(execFile)
if exitCode == 0:
printInfo(&" PASS {testName}", useColor)
status = "PASS"
statusOk = true
passed += 1
else:
printError(&" FAIL {testName} (exit {exitCode})", useColor)
status = &"FAIL:{exitCode}"
failed += 1
removeDir(tmpDir)
echo &"\nResults: {passed} passed, {failed} failed"
if not opts.quiet:
# Pad name to 28 chars for the table column
var nameCol = testName
if nameCol.len > 28:
nameCol = nameCol[0 .. 24] & "..."
else:
nameCol = nameCol & repeat(' ', 28 - nameCol.len)
var stCol = status
if stCol.len < 6:
stCol = stCol & repeat(' ', 6 - stCol.len)
if useColor:
if statusOk:
stdout.setForegroundColor(fgGreen)
else:
stdout.setForegroundColor(fgRed)
stdout.writeLine(&"│ {nameCol} │ {stCol} │")
stdout.resetAttributes()
else:
echo &"│ {nameCol} │ {stCol} │"
if not opts.quiet:
echo "└──────────────────────────────┴────────┘"
echo &"\nResults: {passed} passed, {failed} failed, {testFiles.len} total"
# CI-friendly exit codes: 0 = all pass, 1 = some failed
return if failed > 0: 1 else: 0
proc cmdFmt*(args: seq[string], opts: GlobalOptions): int =
let useColor = shouldUseColor(opts)
let (checkOnly, paths, ok, help) = parseFmtArgs(args)
if not ok:
return 1
if help:
echo "Usage: bux fmt [--check] [path...]"
echo " --check Do not write; exit 1 if any file would be reformatted"
echo " path File or directory (default: .)"
return 0
let targets = if paths.len > 0: paths else: @["."]
var files: seq[string] = @[]
for t in targets:
let collected = collectBuxFiles(t)
for f in collected:
if f notin files:
files.add(f)
files.sort(system.cmp)
if files.len == 0:
printError("no .bux files found", useColor)
return 1
var changed = 0
var failed = 0
var unchanged = 0
for path in files:
let (okf, didChange, msg) = formatFile(path, checkOnly)
if not okf:
printError(&"{path}: {msg}", useColor)
failed += 1
continue
if didChange:
changed += 1
if not opts.quiet:
if checkOnly:
printError(&" would reformat {path}", useColor)
else:
printInfo(&" formatted {path}", useColor)
else:
unchanged += 1
if opts.verbose and not opts.quiet:
echo &" ok {path}"
if not opts.quiet:
if checkOnly:
echo &"\nfmt --check: {changed} would reformat, {unchanged} ok, {failed} errors"
else:
echo &"\nFormatted {changed}/{files.len} files ({unchanged} already clean)"
if failed > 0:
return 1
if checkOnly and changed > 0:
return 1
return 0
proc cmdDoc*(args: seq[string], opts: GlobalOptions): int =
## Generate Markdown docs from `///` / adjacent `/* */` comments.
var outPath = ""
var paths: seq[string] = @[]
var i = 0
while i < args.len:
let a = args[i]
if a == "--out" or a == "-o":
if i + 1 >= args.len:
stderr.writeLine("error: --out requires a path")
return 1
inc i
outPath = args[i]
elif a.startsWith("--out="):
outPath = a["--out=".len .. ^1]
elif a == "--help" or a == "-h":
echo "Usage: bux doc [--out file.md] [path...]"
echo " Scans .bux files for /// and /* */ docs preceding declarations."
echo " Default path: lib/ (stdlib) when omitted."
return 0
elif a.startsWith("-"):
stderr.writeLine(&"error: unknown doc option '{a}'")
return 1
else:
paths.add(a)
inc i
if paths.len == 0:
# Prefer stdlib if present
if dirExists("lib"):
paths = @["lib"]
else:
paths = @["."]
let items = generateDocs(paths)
let title =
if paths.len == 1 and paths[0] == "lib": "Bux Standard Library"
else: "API Reference"
let md = renderMarkdown(items, title)
if outPath.len > 0:
try:
let parent = parentDir(outPath)
if parent.len > 0 and not dirExists(parent):
createDir(parent)
writeFile(outPath, md)
if not opts.quiet:
echo &"Wrote {items.len} documented items → {outPath}"
except CatchableError as e:
stderr.writeLine("error: " & e.msg)
return 1
else:
stdout.write(md)
if items.len == 0 and not opts.quiet:
stderr.writeLine("warning: no /// or /* */ documented declarations found")
return 0
proc cmdVersion*(args: seq[string], opts: GlobalOptions): int =
echo "bux 0.1.0 (bootstrap)"
return 0
@@ -782,10 +1087,13 @@ proc runCli*(args: seq[string]): int =
of "init": return cmdInit(cmdArgs, opts)
of "add": return cmdAdd(cmdArgs, opts)
of "install": return cmdInstall(cmdArgs, opts)
of "search": return cmdSearch(cmdArgs, opts)
of "build": return cmdBuild(cmdArgs, opts)
of "run": return cmdRun(cmdArgs, opts)
of "check": return cmdCheck(cmdArgs, opts)
of "test": return cmdTest(cmdArgs, opts)
of "fmt": return cmdFmt(cmdArgs, opts)
of "doc": return cmdDoc(cmdArgs, opts)
of "clean": return cmdClean(cmdArgs, opts)
of "version", "--version", "-v": return cmdVersion(cmdArgs, opts)
of "help", "--help", "-h":
+202
View File
@@ -0,0 +1,202 @@
## docgen.nim — Extract `///` (and adjacent `/* */`) docs into Markdown.
## Used by `bux doc [path...]`.
import std/[os, strutils, strformat, algorithm]
type
DocItem* = object
kind*: string ## module | func | struct | enum | interface | extern
name*: string
signature*: string ## first declaration line (trimmed)
docs*: string
file*: string
line*: int
proc isDeclStart(line: string): bool =
let s = line.strip()
if s.len == 0: return false
# Leading attributes @[Checked] etc. — not a decl by themselves
if s.startsWith("@["): return false
if s.startsWith("func ") or s.startsWith("pub func ") or
s.startsWith("extern func ") or s.startsWith("const func ") or
s.startsWith("async func "):
return true
if s.startsWith("struct ") or s.startsWith("pub struct ") or
s.startsWith("enum ") or s.startsWith("pub enum ") or
s.startsWith("union ") or s.startsWith("interface ") or
s.startsWith("module ") or s.startsWith("type "):
return true
return false
proc declKindAndName(line: string): tuple[kind, name: string] =
var s = line.strip()
# Strip leading pub/extern/const/async
for prefix in ["pub ", "extern ", "const ", "async "]:
if s.startsWith(prefix):
s = s[prefix.len .. ^1].strip()
var kind = "item"
if s.startsWith("func "):
kind = "func"
s = s["func ".len .. ^1]
elif s.startsWith("struct "):
kind = "struct"
s = s["struct ".len .. ^1]
elif s.startsWith("enum "):
kind = "enum"
s = s["enum ".len .. ^1]
elif s.startsWith("union "):
kind = "union"
s = s["union ".len .. ^1]
elif s.startsWith("interface "):
kind = "interface"
s = s["interface ".len .. ^1]
elif s.startsWith("module "):
kind = "module"
s = s["module ".len .. ^1]
elif s.startsWith("type "):
kind = "type"
s = s["type ".len .. ^1]
# Name: until `<` `(` `{` `:` space
var name = ""
for ch in s:
if ch in {' ', '<', '(', '{', ':', ';'}:
break
name.add(ch)
if name.len == 0:
name = s
# extern funcs already stripped "extern "
if kind == "func" and line.strip().startsWith("extern"):
kind = "extern"
return (kind, name)
proc extractDocsFromSource*(source, path: string): seq[DocItem] =
result = @[]
var pending: seq[string] = @[]
var inBlockComment = false
var blockDoc: seq[string] = @[]
var lineNo = 0
for rawLine in source.splitLines():
inc lineNo
var line = rawLine
let stripped = line.strip()
# Block comment handling (/* ... */ used in stdlib today)
if inBlockComment:
let endIdx = stripped.find("*/")
if endIdx >= 0:
let before = stripped[0 ..< endIdx].strip()
if before.len > 0:
blockDoc.add(before)
inBlockComment = false
# Treat completed block as pending doc if non-empty
if blockDoc.len > 0:
pending = blockDoc
blockDoc = @[]
continue
else:
blockDoc.add(stripped)
continue
if stripped.startsWith("/*") and not stripped.startsWith("/***"):
let rest = stripped["/*".len .. ^1]
let endIdx = rest.find("*/")
if endIdx >= 0:
let body = rest[0 ..< endIdx].strip()
if body.len > 0:
pending = @[body]
else:
inBlockComment = true
blockDoc = @[]
let body = rest.strip()
if body.len > 0:
blockDoc.add(body)
continue
# Triple-slash doc comments
if stripped.startsWith("///"):
var body = stripped["///".len .. ^1]
if body.startsWith(" "):
body = body[1 .. ^1]
pending.add(body)
continue
# Empty line: keep pending docs (allow blank lines inside doc blocks)
if stripped.len == 0:
continue
# Attributes immediately before decl: keep pending
if stripped.startsWith("@["):
continue
if isDeclStart(line):
if pending.len > 0:
let (kind, name) = declKindAndName(line)
result.add(DocItem(
kind: kind,
name: name,
signature: stripped,
docs: pending.join("\n"),
file: path,
line: lineNo
))
pending = @[]
continue
# Other code clears pending (except plain // comments)
if stripped.startsWith("//"):
continue
pending = @[]
proc collectBuxFilesForDoc*(root: string): seq[string] =
result = @[]
if fileExists(root) and root.endsWith(".bux"):
result.add(root)
return
if not dirExists(root):
return
for path in walkDirRec(root):
if path.endsWith(".bux"):
result.add(path)
result.sort(system.cmp)
proc renderMarkdown*(items: seq[DocItem], title: string = "API Reference"): string =
var sb: string
sb.add(&"# {title}\n\n")
sb.add("Generated by `bux doc` from `///` and `/* */` documentation comments.\n\n")
if items.len == 0:
sb.add("_No documented items found._\n")
return sb
# Group by file
var byFile: seq[string] = @[]
for it in items:
if it.file notin byFile:
byFile.add(it.file)
byFile.sort(system.cmp)
for f in byFile:
let base = splitFile(f).name
sb.add(&"## `{base}`\n\n")
sb.add(&"_Source: `{f}`_\n\n")
for it in items:
if it.file != f:
continue
sb.add(&"### `{it.name}` _{it.kind}_\n\n")
sb.add("```bux\n")
sb.add(it.signature)
sb.add("\n```\n\n")
if it.docs.len > 0:
sb.add(it.docs)
sb.add("\n\n")
return sb
proc generateDocs*(paths: seq[string]): seq[DocItem] =
result = @[]
var files: seq[string] = @[]
for p in paths:
for f in collectBuxFilesForDoc(p):
if f notin files:
files.add(f)
files.sort(system.cmp)
for f in files:
let src = readFile(f)
result.add(extractDocsFromSource(src, f))
+106
View File
@@ -0,0 +1,106 @@
## fmt.nim — Indentation-based Bux source formatter (bootstrap).
## Mirrors selfhost `src/fmt.bux`: re-indent by brace depth, preserve content.
import std/[strutils, os, algorithm]
proc isInStringOrComment(line: string, pos: int): bool =
## Simplified: track `//`, `"..."`, and `'...'` up to `pos`.
var inString = false
var inChar = false
var inComment = false
var i = 0
while i < pos and i < line.len:
let c = line[i]
let n = if i + 1 < line.len: line[i + 1] else: '\0'
if inComment:
inc i
continue
if c == '/' and n == '/':
inComment = true
inc i
continue
if c == '"' and not inChar:
inString = not inString
if c == '\'' and not inString:
inChar = not inChar
inc i
return inString or inChar or inComment
proc countBraceDelta(line: string): int =
var delta = 0
for i in 0 ..< line.len:
if isInStringOrComment(line, i):
continue
let c = line[i]
if c == '{':
inc delta
elif c == '}':
dec delta
return delta
proc formatSource*(source: string): string =
## Re-indent each non-empty line to 4 spaces × brace depth.
## Idempotent: formatting a clean file is a no-op.
var sb: string
var indent = 0
# Nim's splitLines leaves a trailing "" when the source ends with '\n'.
# Drop that artifact so we don't accumulate blank lines on re-format.
var lines = source.splitLines(keepEol = false)
if source.len > 0 and source.endsWith('\n') and lines.len > 0 and lines[^1].len == 0:
lines.setLen(lines.len - 1)
for line in lines:
let trimmed = line.strip(leading = true, trailing = false)
if trimmed.len == 0:
sb.add('\n')
continue
let delta = countBraceDelta(trimmed)
let firstChar = trimmed[0]
if firstChar == '}':
dec indent
if indent < 0:
indent = 0
for _ in 0 ..< indent:
sb.add(" ")
sb.add(trimmed)
sb.add('\n')
if firstChar != '}':
indent = indent + delta
else:
# Net delta after the initial decrease for a leading `}`
indent = indent + delta + 1
if indent < 0:
indent = 0
return sb
proc formatFile*(path: string, checkOnly: bool): tuple[ok: bool, changed: bool, msg: string] =
## Format `path` in place, or only check if reformatting would change it.
if not fileExists(path):
return (false, false, "file not found: " & path)
let source = readFile(path)
let formatted = formatSource(source)
if formatted == source:
return (true, false, "")
if checkOnly:
return (true, true, "would reformat")
try:
writeFile(path, formatted)
return (true, true, "formatted")
except CatchableError as e:
return (false, false, e.msg)
proc collectBuxFiles*(root: string): seq[string] =
## Collect `.bux` files: single file, or recursive directory walk.
result = @[]
if fileExists(root) and root.endsWith(".bux"):
result.add(root)
return
if not dirExists(root):
return
for path in walkDirRec(root):
if path.endsWith(".bux"):
result.add(path)
result.sort(system.cmp)
+167
View File
@@ -0,0 +1,167 @@
## registry.nim — Bux package registry index (E.1)
##
## Index format (TOML-ish, one package per [[package]] table):
##
## [[package]]
## name = "greet"
## version = "0.1.0"
## source = "file:packages/greet" # relative to the registry file
## description = "Hello helpers"
##
## [[package]]
## name = "net"
## version = "1.2.0"
## source = "https://github.com/bux-lang/net.git"
##
## Lookup order for the index file:
## 1. $BUX_REGISTRY (file path)
## 2. ~/.bux/registry.toml
## 3. <repo>/config/registry.toml next to the compiler / cwd
import std/[os, strutils, strformat, algorithm]
type
RegistryPackage* = object
name*: string
version*: string
source*: string ## raw source as written in the index
description*: string
resolvedPath*: string ## absolute path for file: sources (filled on load)
Registry* = object
path*: string ## index file path
packages*: seq[RegistryPackage]
proc resolvePackageSource(pkg: var RegistryPackage, indexDir: string) =
if pkg.source.startsWith("file:"):
var p = pkg.source["file:".len .. ^1]
if p.startsWith("//"):
p = p[2 .. ^1]
if not p.isAbsolute:
p = indexDir / p
pkg.resolvedPath = p.absolutePath
elif pkg.source.startsWith("path:"):
var p = pkg.source["path:".len .. ^1]
if not p.isAbsolute:
p = indexDir / p
pkg.resolvedPath = p.absolutePath
pkg.source = "file:" & pkg.resolvedPath
proc parseRegistryToml(content, indexPath: string): seq[RegistryPackage] =
## Minimal parser for repeated [[package]] blocks with string keys.
result = @[]
var cur: RegistryPackage
var inPkg = false
let indexDir = indexPath.parentDir
for raw in content.splitLines():
let line = raw.strip()
if line.len == 0 or line.startsWith("#"):
continue
if line == "[[package]]" or line == "[[Package]]":
if inPkg and cur.name.len > 0:
resolvePackageSource(cur, indexDir)
result.add(cur)
cur = RegistryPackage()
inPkg = true
continue
if not inPkg:
continue
let eq = line.find('=')
if eq < 0: continue
let key = line[0 ..< eq].strip().toLowerAscii()
var val = line[eq + 1 .. ^1].strip()
if val.len >= 2 and val[0] == '"' and val[^1] == '"':
val = val[1 ..< ^1]
case key
of "name": cur.name = val
of "version": cur.version = val
of "source": cur.source = val
of "description": cur.description = val
else: discard
if inPkg and cur.name.len > 0:
resolvePackageSource(cur, indexDir)
result.add(cur)
proc findRegistryIndex*(): string =
## Locate the registry index file.
let env = getEnv("BUX_REGISTRY")
if env.len > 0 and fileExists(env):
return env.absolutePath
let homeIdx = getHomeDir() / ".bux" / "registry.toml"
if fileExists(homeIdx):
return homeIdx
let candidates = @[
getAppDir() / ".." / "config" / "registry.toml",
getAppDir() / "config" / "registry.toml",
getCurrentDir() / "config" / "registry.toml",
getCurrentDir() / ".." / "config" / "registry.toml",
]
for c in candidates:
if fileExists(c):
return c.absolutePath
return ""
proc loadRegistry*(path: string = ""): Registry =
result.path = if path.len > 0: path else: findRegistryIndex()
result.packages = @[]
if result.path.len == 0 or not fileExists(result.path):
return
try:
let content = readFile(result.path)
result.packages = parseRegistryToml(content, result.path)
except CatchableError:
result.packages = @[]
proc registryLookup*(reg: Registry, name: string, versionReq: string = "*"): RegistryPackage =
## Find a package by name. versionReq `*` picks the last matching entry
## (index order; put newest last). Exact version matches preferred.
result = RegistryPackage()
var candidates: seq[RegistryPackage] = @[]
for p in reg.packages:
if p.name.toLowerAscii() == name.toLowerAscii():
candidates.add(p)
if candidates.len == 0:
return
if versionReq.len == 0 or versionReq == "*":
return candidates[^1]
for p in candidates:
if p.version == versionReq:
return p
# Semver prefix match: "1" matches "1.0.0"
for p in candidates:
if p.version.startsWith(versionReq):
return p
return candidates[^1]
proc registrySearch*(reg: Registry, query: string): seq[RegistryPackage] =
result = @[]
let q = query.toLowerAscii()
for p in reg.packages:
if q.len == 0 or
q in p.name.toLowerAscii() or
q in p.description.toLowerAscii():
result.add(p)
result.sort(proc (a, b: RegistryPackage): int =
cmp(a.name.toLowerAscii(), b.name.toLowerAscii()))
proc isGitSource*(source: string): bool =
source.startsWith("http://") or source.startsWith("https://") or
source.startsWith("git@") or source.startsWith("git://") or
source.startsWith("ssh://")
proc isFileSource*(source: string): bool =
source.startsWith("file:") or source.startsWith("path:")
proc formatRegistryDepLine*(name: string, pkg: RegistryPackage): string =
## Produce a bux.toml Dependencies line for a resolved registry package.
if pkg.resolvedPath.len > 0 and dirExists(pkg.resolvedPath):
return &"{name} = {{ Path = \"{pkg.resolvedPath}\" }}"
if isGitSource(pkg.source):
let ver = if pkg.version.len > 0: pkg.version else: "*"
return &"{name} = {{ Version = \"{ver}\", Source = \"{pkg.source}\" }}"
if isFileSource(pkg.source) and pkg.resolvedPath.len > 0:
return &"{name} = {{ Path = \"{pkg.resolvedPath}\" }}"
# Fallback: version-only (install will re-resolve)
let ver = if pkg.version.len > 0: pkg.version else: "*"
return &"{name} = \"{ver}\""
+178 -5
View File
@@ -51,6 +51,11 @@ type
## When true, ekIdent skips use-while-borrowed (we're forming `&x` itself)
suppressUseWhileBorrow*: bool
currentRetType*: Type ## return type of the function being checked
## Lifetime elision / ref-origin tracking (@[Checked] only)
## Binding name → lifetime id ("'a", "#elided0", "#local", …)
varRefLifetime*: Table[string, string]
## Expected lifetime of the function's returned reference ("" if ret is not a ref)
returnLifetime*: string
closureDepth*: int ## nesting depth inside closures
currentClosureExpr*: Expr ## current closure being analyzed
closureScope*: Scope ## scope at which the current closure was entered
@@ -164,6 +169,138 @@ proc checkTempMutBorrow(sema: var Sema, varName: string, loc: SourceLocation) =
elif sema.activeSharedBorrows.getOrDefault(varName, 0) > 0:
sema.emitError(loc, &"cannot mutably borrow '{varName}' while it is shared-borrowed")
# ---------------------------------------------------------------------------
# Lifetime elision (C.1) — Rust-style simple rules for @[Checked]
# ---------------------------------------------------------------------------
#
# Rules (common cases, no annotations required):
# 1. Each elided input reference (&T / &mut T param) gets a distinct lifetime.
# 2. If there is exactly one input lifetime, it is assigned to all elided outputs.
# 3. If the first param is `self` / `Self`, its lifetime is preferred for outputs.
# 4. Multiple input refs + elided return → error (need explicit `'a`).
# 5. Returning a reference derived from a local (or by-value param) is rejected.
#
const
LifetimeLocal* = "#local" ## ref derived from a local / by-value place
LifetimeOutNone* = "#out" ## return ref with no input to borrow from
LifetimeAmbiguous* = "#ambiguous"
proc isRefTypeExpr(te: TypeExpr): bool =
te != nil and te.kind in {tekRef, tekMutRef}
proc applyLifetimeElision*(sema: var Sema, decl: Decl) =
## Assign elided lifetimes for ref params/return of `decl`. Populates
## `varRefLifetime` (params) and `returnLifetime`.
sema.varRefLifetime = initTable[string, string]()
sema.returnLifetime = ""
if not sema.checkedFunc:
return
var inputLts: seq[string] = @[]
var anon = 0
for p in decl.declFuncParams:
if not isRefTypeExpr(p.ptype):
continue
var lt = p.ptype.refLifetime
if lt.len == 0:
lt = "#elided" & $anon
inc anon
inputLts.add(lt)
sema.varRefLifetime[p.name] = lt
let ret = decl.declFuncReturnType
if not isRefTypeExpr(ret):
return
var rlt = ret.refLifetime
if rlt.len == 0:
if inputLts.len == 1:
rlt = inputLts[0]
elif inputLts.len == 0:
rlt = LifetimeOutNone
elif decl.declFuncParams.len > 0 and
decl.declFuncParams[0].name in ["self", "Self"]:
rlt = inputLts[0]
else:
sema.emitError(decl.loc,
"lifetime elision failed: return type needs an explicit lifetime " &
"(multiple input references); e.g. func F<'a>(a: &'a T, b: &'a U) -> &'a T")
rlt = LifetimeAmbiguous
sema.returnLifetime = rlt
proc exprRefLifetime*(sema: Sema, expr: Expr, scope: Scope): string =
## Best-effort lifetime of a reference-producing expression.
if expr == nil:
return ""
case expr.kind
of ekIdent:
if sema.varRefLifetime.hasKey(expr.exprIdent):
return sema.varRefLifetime[expr.exprIdent]
return ""
of ekUnary:
if expr.exprUnaryOp == tkAmp:
let name = extractBorrowedIdent(expr)
if name.len == 0:
return LifetimeLocal
# Reborrow of an existing ref binding keeps its lifetime
if sema.varRefLifetime.hasKey(name):
return sema.varRefLifetime[name]
# Address-of a by-value local or by-value parameter → local (dangling if returned)
return LifetimeLocal
# Dereference: *r still carries r's lifetime for field/ref purposes
if expr.exprUnaryOp == tkStar:
return sema.exprRefLifetime(expr.exprUnaryOperand, scope)
return ""
of ekBorrow:
# `borrow &x` / `borrow &mut x` — same origin rules as unary &
if expr.exprBorrowOperand != nil:
return sema.exprRefLifetime(expr.exprBorrowOperand, scope)
return LifetimeLocal
of ekField:
# Field projection through a ref keeps the base lifetime: (*p).x or p.x
if expr.exprFieldObj != nil:
let baseLt = sema.exprRefLifetime(expr.exprFieldObj, scope)
if baseLt.len > 0:
return baseLt
# Base is an ident of a struct local — field address would be local
if expr.exprFieldObj.kind == ekIdent:
if sema.varRefLifetime.hasKey(expr.exprFieldObj.exprIdent):
return sema.varRefLifetime[expr.exprFieldObj.exprIdent]
return LifetimeLocal
return ""
else:
return ""
proc checkReturnLifetime*(sema: var Sema, retExpr: Expr, scope: Scope, loc: SourceLocation) =
## Reject dangling returns and explicit lifetime mismatches in @[Checked].
if not sema.checkedFunc or sema.returnLifetime.len == 0 or retExpr == nil:
return
let got = sema.exprRefLifetime(retExpr, scope)
if sema.returnLifetime == LifetimeOutNone:
sema.emitError(loc,
"cannot return a reference: function has no input reference to borrow from")
return
if got == LifetimeLocal:
sema.emitError(loc, "cannot return reference to local variable")
return
if got.len == 0:
# Non-trivial expression (call, etc.) — leave for later analysis
return
if got == LifetimeAmbiguous or sema.returnLifetime == LifetimeAmbiguous:
return
# Explicit lifetime mismatch (both sides named with ')
if got.startsWith("'") and sema.returnLifetime.startsWith("'") and got != sema.returnLifetime:
sema.emitError(loc,
&"lifetime mismatch: returning '{got}' but function returns '{sema.returnLifetime}'")
return
# Distinct elided inputs returned into another elided input's return slot
if got.startsWith("#elided") and sema.returnLifetime.startsWith("#elided") and
got != sema.returnLifetime:
sema.emitError(loc,
"lifetime mismatch: returned reference does not outlive the return type " &
"(multiple input references; annotate with an explicit lifetime)")
# ---------------------------------------------------------------------------
# Generic type inference helpers
# ---------------------------------------------------------------------------
@@ -472,7 +609,7 @@ proc inferTypeArgs(sema: var Sema, funcDecl: Decl, argTypes: seq[Type],
# Type resolution from AST TypeExpr
# ---------------------------------------------------------------------------
proc resolveType(sema: var Sema, te: TypeExpr): Type =
proc resolveType*(sema: var Sema, te: TypeExpr): Type =
if te == nil:
return makeUnknown()
case te.kind
@@ -918,7 +1055,7 @@ proc collectGlobals*(sema: var Sema) =
# Expression type checking
# ---------------------------------------------------------------------------
proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type
proc checkExpr*(sema: var Sema, expr: Expr, scope: Scope): Type
proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type
proc typeImplements(sema: Sema, t: Type, interfaceName: string): bool =
@@ -1099,7 +1236,7 @@ proc resolveCallArgs(sema: var Sema, expr: Expr, calleeDecl: Decl, scope: Scope)
expr.exprCallArgs = newArgs
expr.exprCallArgNames = newNames
proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
proc checkExpr*(sema: var Sema, expr: Expr, scope: Scope): Type =
if expr == nil:
return makeUnknown()
case expr.kind
@@ -1835,6 +1972,11 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type =
# Untyped let + `&x` is typed as &mut by unary lowering
isMut = initType.isMutRef
sema.checkCreateBorrow(bname, isMut, stmt.stmtLetInit.loc)
# Propagate ref lifetime to the new binding (for return-site checks)
if declaredType.isRef or declaredType.isMutRef or initType.isRef or initType.isMutRef:
let lt = sema.exprRefLifetime(stmt.stmtLetInit, scope)
if lt.len > 0:
sema.varRefLifetime[stmt.stmtLetName] = lt
return makeVoid()
of skIf:
let condType = sema.checkExpr(stmt.stmtIfCond, scope)
@@ -1897,6 +2039,8 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type =
let retSym = scope.lookup(stmt.stmtReturnValue.exprIdent)
if retSym != nil and retSym.isOwn:
sema.movedVars.add(stmt.stmtReturnValue.exprIdent)
# Lifetime: reject dangling returns / explicit mismatches
sema.checkReturnLifetime(stmt.stmtReturnValue, scope, stmt.loc)
return makeVoid()
of skBreak, skContinue:
return makeVoid()
@@ -1951,9 +2095,15 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type =
proc checkFunc(sema: var Sema, decl: Decl) =
if decl.declFuncBody == nil:
return
# Skip body type-checking for generic functions — their bodies contain
# Skip body type-checking for type-generic functions — their bodies contain
# type parameters that cannot be fully resolved until monomorphization.
if decl.declFuncTypeParams.len > 0:
# Lifetime-only params (`'a`) are fine: we still check the body for elision.
var hasTypeGeneric = false
for tp in decl.declFuncTypeParams:
if not tp.isLifetime:
hasTypeGeneric = true
break
if hasTypeGeneric:
return
let wasChecked = sema.checkedFunc
let wasAsync = sema.currentFuncIsAsync
@@ -1963,10 +2113,18 @@ proc checkFunc(sema: var Sema, decl: Decl) =
sema.movedVars = @[]
sema.activeMutBorrows = initTable[string, SourceLocation]()
sema.activeSharedBorrows = initTable[string, int]()
# C.1: elide lifetimes on params / return before walking the body
sema.applyLifetimeElision(decl)
else:
sema.varRefLifetime = initTable[string, string]()
sema.returnLifetime = ""
var funcScope = newScope(sema.globalScope)
# Add type parameters to type table for resolution
var addedTypeParams: seq[string] = @[]
for tp in decl.declFuncTypeParams:
if tp.isLifetime:
# Lifetime params are not types; skip typeTable
continue
sema.typeTable[tp.name] = makeTypeParam(tp.name)
addedTypeParams.add(tp.name)
# Add parameters
@@ -1982,6 +2140,8 @@ proc checkFunc(sema: var Sema, decl: Decl) =
sema.typeTable.del(tp)
sema.checkedFunc = wasChecked
sema.currentFuncIsAsync = wasAsync
sema.varRefLifetime = initTable[string, string]()
sema.returnLifetime = ""
# ---------------------------------------------------------------------------
# Second pass: check all function bodies
@@ -2026,3 +2186,16 @@ proc analyzeFull*(modu: Module): tuple[result: SemaResult, sema: Sema] =
sema.collectGlobals()
sema.checkBodies()
result = (SemaResult(diagnostics: sema.diagnostics), sema)
proc checkExprForLsp*(sema: var Sema, expr: Expr, scope: Scope): Type =
## Type-check an expression for IDE use (no borrow/move side effects).
let wasChecked = sema.checkedFunc
let savedMoved = sema.movedVars
let savedMut = sema.activeMutBorrows
let savedShared = sema.activeSharedBorrows
sema.checkedFunc = false
result = sema.checkExpr(expr, scope)
sema.checkedFunc = wasChecked
sema.movedVars = savedMoved
sema.activeMutBorrows = savedMut
sema.activeSharedBorrows = savedShared
+22
View File
@@ -0,0 +1,22 @@
# Bux package registry index (E.1)
# Used by `bux add <name>` and `bux install` when no --path/--git is given.
#
# Override with: export BUX_REGISTRY=/path/to/registry.toml
# Or copy to: ~/.bux/registry.toml
#
# source forms:
# file:relative/or/absolute — local package (relative to this file)
# path:relative/or/absolute — same as file:
# https://...git — git clone into ~/.bux/packages/<name>
[[package]]
name = "greet"
version = "0.1.0"
source = "file:../registry/packages/greet"
description = "Tiny Hello helper library (demo registry package)"
[[package]]
name = "greet"
version = "0.1.1"
source = "file:../registry/packages/greet"
description = "Tiny Hello helper library (demo registry package, patch)"
+49 -4
View File
@@ -152,15 +152,60 @@ This runs:
### Project Tests (`bux test`)
```bash
./buxc test
./buxc test # run all tests/*.bux in the current package
./buxc test --filter first # only tests whose name contains "first"
./buxc test --filter=first _test_runner
```
Builds the project and runs the resulting binary. Reports:
- `Tests passed` on exit code 0
- `Tests failed (exit code N)` on non-zero exit
Discovers `tests/*.bux`, builds each as a temp package, and runs it. Prints a
summary table and exits:
- `0` — all selected tests passed
- `1` — at least one failure, or no tests matched the filter
Use `Std::Test` module for assertions inside test code.
### Format (`bux fmt`)
```bash
./buxc fmt examples/hello.bux # reformat one file
./buxc fmt lib/ # reformat a directory tree
make fmt # reformat lib/ examples/ src/ tests/ apps/
./buxc fmt --check path/ # exit 1 if any file would change
make fmt-check # CI: full-tree clean + dirty smoke
```
Indentation is 4 spaces by brace depth. The formatter is idempotent (safe to re-run).
`make fmt-check` enforces a clean tree under `lib/`, `examples/`, `src/`, `tests/`, and `apps/`.
### Stdlib golden tests
```bash
make test-stdlib
# or: tests/stdlib_golden/run.sh ./buxc
```
Behavioral packages under `tests/stdlib_golden/` (`array`, `string`, `collections`)
assert core Array/String/Map/Set/Result/Option APIs and match expected `PASS` lines.
### API docs (`bux doc`)
```bash
./buxc doc lib/ # Markdown to stdout
./buxc doc --out docs/api/stdlib.md lib/
make docs # writes docs/api/stdlib.md
```
Scans `///` line comments (and bootstrap also accepts adjacent `/* */`) immediately
before `func` / `struct` / `enum` / `interface` / `module` declarations.
### Language Server (`bux-lsp` 0.4.0)
```bash
make lsp # → tools/bux-lsp
nim r --path:bootstrap tools/test_lsp_locals.nim
./tools/smoke_lsp_hover.sh
```
Features: diagnostics (`buxc check`), hover, go-to-def, outline, completion.
**Locals are position-sensitive** (nested scopes / shadowing). **Inferred `let` types**
appear on hover (`let x: int · inferred`).
### Example Programs
```bash
make test-examples
+38
View File
@@ -604,6 +604,44 @@ Moves happen in three contexts:
msg = "reassigned"; // OK: reinitialization
PrintLine(msg);
```
- **No dangling returns**: cannot return a reference to a local (or by-value parameter)
```bux
@[Checked]
func Bad(p: &int) -> &int {
var x: int = 1;
return &x; // ERROR: cannot return reference to local variable
}
```
### Lifetime elision (C.1)
In `@[Checked]` functions, most reference signatures need **no** lifetime annotations.
Elision applies the usual single-input rules:
1. Each elided input `&T` / `&mut T` parameter gets a distinct lifetime.
2. If there is **exactly one** input lifetime, it is assigned to all elided outputs.
3. If the first parameter is named `self` / `Self`, that input lifetime is preferred for outputs.
4. Multiple input references + elided return → **error** (write an explicit lifetime).
```bux
// Elided — one input ref, return shares its lifetime
@[Checked]
func Identity(p: &int) -> &int {
return p; // OK
}
// Explicit — required when several inputs could be returned
@[Checked]
func Pick<'a>(a: &'a int, b: &'a int) -> &'a int {
return a;
}
// Syntax: &'a T and &mut / &'a mut T (lifetime before `mut`)
// Type parameters: func F<'a, T>(...)
```
Unchecked functions ignore lifetime rules (C-like). Explicit `'a` is optional
documentation when a single input would already elide correctly.
---
+75 -27
View File
@@ -1,6 +1,8 @@
# Bux Package Manager
> **Status:** Implemented (Phase 9.1)
> **Status:** Path + git + **local/file registry** (E.1). HTTP registry index URL optional later.
See also: [SEMVER.md](SEMVER.md) for version policy.
---
@@ -20,52 +22,97 @@ License = "MIT"
Output = "Bin"
[Dependencies]
Std = "1.0"
greet = { Path = "/abs/path/to/greet" }
Json = { Version = "2.1", Source = "https://github.com/bux-lang/json" }
Utils = { Path = "../Utils" }
# Registry name-only (resolved by `bux add` / `bux install`):
# greet = "0.1.1"
```
### Dependency Forms
| Form | Example | Description |
|------|---------|-------------|
| Version string | `Std = "1.0"` | Registry dependency |
| Wildcard | `Std = "*"` | Latest version |
| Version string | `greet = "0.1.1"` | Registry dependency |
| Wildcard | `greet = "*"` | Latest registry version |
| Inline table (git) | `{ Version = "1.4", Source = "https://..." }` | Git URL + version |
| Inline table (path) | `{ Path = "../Lib" }` | Local path dependency |
---
## Package registry (E.1)
### Index file
Default locations (first hit wins):
1. `$BUX_REGISTRY` — path to a `registry.toml`
2. `~/.bux/registry.toml`
3. `config/registry.toml` next to the Bux repo / compiler
Format:
```toml
[[package]]
name = "greet"
version = "0.1.1"
source = "file:../registry/packages/greet" # relative to the index file
description = "Hello helpers"
[[package]]
name = "net"
version = "1.0.0"
source = "https://github.com/example/bux-net.git"
description = "TCP helpers"
```
`file:` / `path:` sources are resolved relative to the registry file.
Git URLs are cloned into `~/.bux/packages/<name>/` on install.
### CLI
```bash
# Search the index
bux search
bux search greet
# Add by registry name (writes Path or git Source into bux.toml)
bux add greet
bux add greet 0.1.1
# Explicit sources still work
bux add utils --path "../utils"
bux add network --git "https://github.com/bux-lang/network"
# Resolve + write bux.lock
bux install
```
Demo package in this monorepo: `registry/packages/greet` (registered in
`config/registry.toml`). Smoke test: `tools/smoke_registry.sh`.
---
## CLI Commands
### `bux add <name> [version]`
Add a dependency to `bux.toml`.
Add a dependency to `bux.toml` (registry / `--path` / `--git`).
```bash
# Add registry dependency
bux add json "2.1"
### `bux search [query]`
# Add path-based dependency
bux add utils --path "../utils"
# Add git dependency
bux add network --git "https://github.com/bux-lang/network"
```
List packages in the active registry (filter by name/description).
### `bux install`
Resolve dependencies and generate `bux.lock`.
```bash
bux install
```
What it does:
1. Reads `[Dependencies]` from `bux.toml`
2. Resolves path-based deps (verifies directory exists)
3. Clones/pulls git-based deps to `~/.bux/packages/<name>/`
4. Generates `bux.lock` with exact versions and sources
3. Clones git-based deps to `~/.bux/packages/<name>/`
4. Resolves bare version names via the registry index
5. Generates `bux.lock` with exact versions and sources
### `bux build` / `bux run`
@@ -84,10 +131,9 @@ Auto-generated. **Do not edit manually.**
```toml
[[Package]]
Name = "json"
Version = "2.1.3"
Source = "https://github.com/bux-lang/json"
Checksum = "8dcb2a7f..."
Name = "greet"
Version = "0.1.1"
Source = "/home/user/z-git/bux/bux/registry/packages/greet"
[[Package]]
Name = "utils"
@@ -103,7 +149,7 @@ The lockfile ensures **reproducible builds** — every developer gets the exact
1. **Path-based** deps are resolved relative to the manifest directory
2. **Git-based** deps are cloned to `~/.bux/packages/<name>/`
3. **Version-based** deps (without Source) require a registry (future feature)
3. **Version-based** deps look up `config/registry.toml` (or `$BUX_REGISTRY`)
4. Dependencies are loaded from `<dep>/src/*.bux` at build time
5. Later declarations shadow earlier ones (project > deps > stdlib)
@@ -114,8 +160,10 @@ The lockfile ensures **reproducible builds** — every developer gets the exact
```bash
bux new mylib
cd mylib
# Edit src/Main.bux → module MyLib { pub func Add(...) }
bux build # Builds as library (Type = "lib")
# Edit src/*.bux → module MyLib { func Add(...) }
# Set Type = "lib" in bux.toml
# Register in your registry.toml with source = "file:..."
bux build
```
## Example: Using a Library
+131 -23
View File
@@ -1,7 +1,7 @@
# Bux — План към „добър“ език (v0.5 → v1.0)
> **Дата:** 2026-07-18
> **Текущо:** v0.5.x — selfhost loop, gradual ownership, green threads, **43+ examples**, match + guards + **generic HOF inference** + pattern bindings + **`f"..."` interp** bootstrap+selfhost
> **Текущо:** v0.5.x — selfhost, C.1, tooling, LSP 0.4, full-tree fmt, **package registry (E.1)**
> **Цел:** Език, с който се пишат реални проекти комфортно, безопасно (по избор) и с надежден toolchain.
---
@@ -14,11 +14,11 @@
| Sema / generics | Monomorphization, trait bounds basic | ★★★★☆ |
| HIR → C | Tuples + fat `func` ABI в bootstrap **и** selfhost | ★★★★☆ |
| Selfhost (`src/`) | ~12k LOC, binary-identical loop, closures+tuples | ★★★★★ |
| Gradual ownership | `@[Checked]`, `&`/`&mut`, move, Drop | ★★★☆☆ (basic) |
| Gradual ownership | `@[Checked]`, `&`/`&mut`, move, Drop, **lifetime elision** | ★★★★☆ |
| Concurrency | M:N tasks + channels + async | ★★★★☆ |
| Stdlib | Array/Map/Set/String/Iter HOF разширени | ★★★★☆ |
| Tooling | `test-errors`, LSP diagnostics + hover/def/outline | ★★★★☆ |
| Ecosystem / registry | path+git deps; няма централен registry | ★☆☆☆☆ |
| Ecosystem / registry | path+git + **file registry index** (`bux search/add`) | ★★★☆☆ |
| Документация | README + QUALITY_PLAN синхронизирани (2026-07-15) | ★★★★☆ |
**Силна ниша:** gradual ownership (C-скорост на писане + opt-in Rust-safety).
@@ -69,7 +69,7 @@
| # | Задача | Защо | Статус |
|---|--------|------|--------|
| C.1 | Lifetime elision за common cases | Без `'a` в 90% от API-тата | |
| C.1 | Lifetime elision за common cases | Без `'a` в 90% от API-тата | ✅ bootstrap + selfhost |
| C.2 | Exclusive `&mut` vs shared `&` data-flow | По-малко false negatives | ✅ let-bound + use-while + call conflict |
| C.3 | Auto-drop edge cases (early return, branches) | RAII да е надежден | ✅ bootstrap + selfhost |
| C.4 | `@[Release]` zero-cost path документация + golden tests | Killer story: safe default, free hot path | ✅ partial (unchecked path + goldens) |
@@ -78,21 +78,21 @@
| # | Задача | Защо | Статус |
|---|--------|------|--------|
| D.1 | LSP: hover, go-to-def, diagnostics | IDE = adoption | ✅ hover/def/outline + **sema types on hover** (v0.3.0) + `buxc` diags |
| D.2 | `bux fmt` стабилен + CI check | Единен style | |
| D.3 | `bux test` с `--filter`, exit codes, summary table | CI-friendly | ⏳ partial (`bux test` exists) |
| D.4 | `bux doc` от `///` comments | Самодокументиращ се stdlib | |
| D.5 | Golden tests за stdlib modules | Регресии без изненади | |
| D.1 | LSP: hover, go-to-def, diagnostics | IDE = adoption | ✅ v0.4.0: **position-sensitive locals** + **inferred `let`** + sema hover |
| D.2 | `bux fmt` стабилен + CI check | Единен style | ✅ full-tree format + `make fmt-check` enforce |
| D.3 | `bux test` с `--filter`, exit codes, summary table | CI-friendly | `--filter` / summary / exit 0\|1 |
| D.4 | `bux doc` от `///` comments | Самодокументиращ се stdlib | ✅ bootstrap+selfhost + `make docs` |
| D.5 | Golden tests за stdlib modules | Регресии без изненади | `tests/stdlib_golden/` + `make test-stdlib` |
### E — Ecosystem & v1.0 (P2)
| # | Задача | Защо |
|---|--------|------|
| E.1 | Package registry protocol (git/HTTP) | `bux add foo` без path hacks |
| E.2 | 35 production-quality apps в `apps/` | Showcase |
| E.3 | Language freeze + semver policy | Trust |
| E.4 | Debugger/DWARF basics | Systems audience |
| E.5 | Benchmarks vs C/Zig/Nim (micro + nexus) | Marketing + regression |
| # | Задача | Защо | Статус |
|---|--------|------|--------|
| E.1 | Package registry protocol (git/HTTP) | `bux add foo` без path hacks | ✅ local index + file/git sources + `search` |
| E.2 | 35 production-quality apps в `apps/` | Showcase | ⏳ partial (`nexus`, `boko`, `simpledb`, `jwt-pitbul`) |
| E.3 | Language freeze + semver policy | Trust | ✅ draft `docs/SEMVER.md` |
| E.4 | Debugger/DWARF basics | Systems audience | ⏳ |
| E.5 | Benchmarks vs C/Zig/Nim (micro + nexus) | Marketing + regression | ⏳ |
---
@@ -114,10 +114,10 @@ A (stdlib ergonomics) → B (compiler holes) → C (ownership depth)
- [ ] Всички examples + selfhost-loop + 3 apps минават на CI
- [ ] Array/Map/String/Test API покрива 90% от ежедневните нужди
- [ ] `@[Checked]` хваща use-after-move + double `&mut` в documented subset
- [ ] `bux test` + `bux fmt` + `bux check` са default developer loop
- [ ] LanguageRef синхронизиран с компилатора
- [ ] Поне един външен проект (не в monorepo) build-ва с git dep
- [x] `@[Checked]` хваща use-after-move + double `&mut` + dangling return / elision fail
- [x] `bux test` + `bux fmt` + `bux check` са default developer loop (`--filter` / `--check` shipped)
- [x] LanguageRef синхронизиран с компилатора (incl. C.1 elision)
- [x] Поне един външен/temp проект build-ва с registry dep (`tools/smoke_registry.sh`)
---
@@ -388,8 +388,116 @@ A (stdlib ergonomics) → B (compiler holes) → C (ownership depth)
---
## Сесия 24 (tooling — D.2 fmt --check + D.3 test --filter)
1. **Bootstrap `bux fmt`** (`bootstrap/fmt.nim`):
- Indent-by-brace-depth formatter (parity with `src/fmt.bux`)
- `bux fmt [path...]` writes; `bux fmt --check` exits 1 if any file would change
- Collects single file or recursive `.bux` under directories
2. **Bootstrap `bux test --filter`**:
- `--filter <s>` / `--filter=<s>` — only run `tests/*.bux` whose name contains `s`
- Summary table (`PASS` / `FAIL[:code]`) + `Results: N passed, M failed, T total`
- Exit `0` all pass, `1` failures or no match
3. **Selfhost parity** (`src/cli.bux`, `src/fmt.bux`):
- `Fmt_WouldChange` / `Fmt_CheckFile`; `Cli_Fmt(dir, checkOnly)`
- `Cli_Test(dir, filter)` with summary + skip count; filter skips Main package run
4. **CI hooks:** `make fmt-check` smoke (clean→0, dirty→1); full-tree enforce deferred
until a one-shot format pass on `lib/`/`examples/`
5. **Idempotence fix:** drop trailing split-empty so re-format is a no-op
6. Verified: unit suite + `./buxc test --filter first _test_runner` + selfhost `buxc2`
fmt/test parity
---
## Сесия 25 (Ownership 2.0 — C.1 lifetime elision)
1. **Elision rules** in `@[Checked]` (`bootstrap/sema.nim`):
- Each elided input `&`/`&mut` → distinct `#elidedN`
- One input lifetime → assigned to elided return
- First param `self`/`Self` preferred when multiple inputs
- Multiple inputs + elided return → `lifetime elision failed` (need `'a`)
2. **Return checks:**
- `cannot return reference to local variable` (`return &local` / let-bound local ref)
- `no input reference to borrow from` (return ref with zero input refs)
- Explicit `'a` mismatch between return and value
3. **Body check for lifetime-only generics** (`func F<'a>(...)`) — no longer skipped
4. **Diagnostics hints** for elision / dangling / mismatch
5. **Tests:** 8 new borrow_test cases; goldens `return_local_ref`, `elision_multi_input`
6. **Example:** `examples/lifetime_elision.bux` (Identity / explicit / ViaLet / self)
7. LanguageRef + QUALITY_PLAN updated
8. Verified: borrow_test 24/24, 9 error goldens, example runs
## Сесия 26 (C.1 selfhost parity)
1. **Lexer** (`src/lexer.bux` + `tkLifetime=111`): `'a` vs char `'x'` (same heuristic as bootstrap)
2. **Parser:**
- `&'a T` / `&'a mut T` → `TypeExpr.refLifetime`
- `func F<'a, T>(…)` — lifetime params accepted and **skipped** for mono slots
3. **Sema** lifetime elision (fixed 8-slot maps, same rules as bootstrap):
- single-input elision, `self` preference, multi-input fail
- return-local / no-input-ref / explicit mismatch
- let-bound ref lifetime propagation
4. Fixed `checkFunc` else-branch that wiped `checkedFunc` when retType was void
5. Verified: `buxc2 run lifetime_elision` PASS; goldens on buxc2 show same errors;
bootstrap still green; **selfhost-loop** expected IDENTICAL
---
## Сесия 27 (tooling — D.4 bux doc + D.5 stdlib goldens)
1. **D.5 Stdlib goldens** (`tests/stdlib_golden/`):
- Packages: `array`, `string`, `collections` (Map/Set/Result/Option)
- `run.sh` builds via `buxc run` and matches expected PASS lines
- `make test-stdlib` wired into `make test`
2. **D.4 `bux doc`**:
- Bootstrap: `bootstrap/docgen.nim` — `///` + adjacent `/* */`
- Selfhost: `Cli_Doc` line scanner for `///`
- `bux doc [--out file] [path]` (default path `lib/`)
- `make docs` → `docs/api/stdlib.md`
3. **Stdlib docs:** `///` on Array / String / Test public helpers
4. Verified: `make test-stdlib`, `./buxc doc lib/Array.bux | head`, selfhost build
---
## Сесия 28 (LSP v0.4.0 — position-sensitive locals + inferred lets)
1. **`LocalBinding`** with scope range (`scopeStartLine`…`scopeEndLine`) per let/param
2. **Sema-backed inference** (`checkExprForLsp` / `resolveType`):
- `let x = 42` → hover `let x: int` · inferred
- `let s: String = "…"` → annotated, not inferred
- params: `param a: int` visible for whole function
3. **Position-sensitive** hover / go-to-def / completion (innermost scope wins on shadowing)
4. Nested scopes: if/while/for/match/block arms
5. Version **bux-lsp 0.4.0**; tests: `tools/test_lsp_locals.nim`, `tools/smoke_lsp_hover.sh`
6. Verified: hover shows `let sum: int · inferred`, `param a: int`, `let n: int · inferred`
---
## Сесия 29 (full-tree `bux fmt` + CI enforce)
1. **One-shot format** of `lib/` (33), `examples/` (23), `src/` (15), `tests/` (8), `apps/` (12)
2. **Idempotent:** second `--check` → 0 would reformat on all trees
3. **CI:** `make fmt-check` enforces full tree + dirty-path smoke (exit 1)
4. **`make fmt`** helper to reformat the same roots
5. Verified: `test-stdlib`, key examples, **selfhost + selfhost-loop IDENTICAL ✓**
---
## Сесия 30 (E.1 package registry + E.3 semver draft)
1. **Registry index** (`config/registry.toml`, `$BUX_REGISTRY`, `~/.bux/registry.toml`)
- `[[package]]` with `name` / `version` / `source` / `description`
- `file:` / `path:` (relative to index) or git URL
2. **CLI:** `bux search [q]`, `bux add <name>` resolves registry, `bux install` locks path/git
3. **Demo package:** `registry/packages/greet` (`Greet_Hello`, `Greet_Version`)
4. **Smoke:** `tools/smoke_registry.sh` / `make test-registry` — temp app outside tree
5. **Semver policy:** `docs/SEMVER.md` (0.x vs 1.0, registry version match)
6. Packages.md updated
---
## Следващи стъпки
1. C.1 Lifetime elision
2. Phase D tooling: `bux fmt` CI, `bux test --filter`, golden stdlib tests
3. LSP: position-sensitive locals; inferred `let` types
1. E.2 polish apps / E.5 benchmarks
2. HTTP-fetchable registry index URL (beyond local file)
3. LSP: workspace rename / references (optional)
+76
View File
@@ -0,0 +1,76 @@
# Bux Semantic Versioning Policy
> Status: Draft for v0.x → v1.0 freeze (E.3)
Bux follows [Semantic Versioning 2.0.0](https://semver.org/) with the
clarifications below.
---
## Version numbers
```
MAJOR.MINOR.PATCH[-prerelease]
```
| Component | When it increases |
|-----------|-------------------|
| **MAJOR** | Breaking language / stdlib / CLI changes |
| **MINOR** | Backward-compatible features |
| **PATCH** | Backward-compatible bug fixes |
During **0.x** (pre-1.0):
- `0.MINOR.PATCH` — MINOR may still introduce breaking changes (documented in
the release notes and `MIGRATION_*.sh` when needed).
- Prefer deprecation warnings for at least one MINOR before removal when
practical.
After **1.0.0** (language freeze):
- Breaking changes require a MAJOR bump and a migration guide.
- The Language Reference is the normative spec; compiler bugs that contradict
the ref are fixed without a MAJOR bump.
---
## What counts as “breaking”
- Removing or renaming a public stdlib symbol
- Changing the type or semantics of a public API
- Changing CLI flags that scripts rely on (`build`, `test`, `fmt --check`, …)
- Changing `bux.toml` / `bux.lock` fields in an incompatible way
- Changing the fat `func` / tuple C ABI in a way that breaks linked code
**Not breaking:**
- New keywords that were previously valid identifiers only if reserved carefully
(prefer contextual keywords)
- New diagnostics / stricter `@[Checked]` (document; may be gated)
- Formatter whitespace-only changes
---
## Package versions (registry)
Registry packages use the same MAJOR.MINOR.PATCH scheme.
`bux add foo` / `bux add foo 0.1` resolution:
| Request | Matches |
|---------|---------|
| `*` / omitted | Latest entry for `foo` in the index |
| `0.1.1` | Exact version |
| `0.1` | First version with that prefix (e.g. `0.1.1`) |
Lockfiles pin the **resolved** version and source path/URL.
---
## Release checklist (maintainers)
1. Update `docs/LanguageRef.md` if behaviour changed
2. Update `docs/QUALITY_PLAN.md` / changelog notes
3. Run `make test` (includes `fmt-check`, examples, goldens)
4. Run `make selfhost-loop`
5. Tag `vMAJOR.MINOR.PATCH`
+749
View File
@@ -0,0 +1,749 @@
# API Reference
Generated by `bux doc` from `///` and `/* */` documentation comments.
## `Array`
_Source: `lib/Array.bux`_
### `Array` _struct_
```bux
struct Array<T> {
```
Growable contiguous buffer of `T` (len + capacity).
### `Array_New` _func_
```bux
func Array_New<T>(cap: uint) -> Array<T> {
```
Create an empty array with the given initial capacity.
### `Array_Push` _func_
```bux
func Array_Push<T>(self: *Array<T>, value: T) {
```
Append `value`, growing capacity if needed.
### `Array_Get` _func_
```bux
func Array_Get<T>(self: *Array<T>, index: uint) -> T {
```
Element at `index` (bounds-checked unless `@[Release]`).
### `Array_Set` _func_
```bux
func Array_Set<T>(self: *Array<T>, index: uint, value: T) {
```
Write `value` at `index` (bounds-checked unless `@[Release]`).
### `Array_Len` _func_
```bux
func Array_Len<T>(self: *Array<T>) -> uint {
```
Number of live elements.
### `Array_Free` _func_
```bux
func Array_Free<T>(self: *Array<T>) {
```
Free the backing buffer and reset length/capacity to zero.
### `Array_Drop` _func_
```bux
func Array_Drop<T>(self: *Array<T>) {
```
Drop trait entry — same as `Array_Free`.
### `Array_IsEmpty` _func_
```bux
func Array_IsEmpty<T>(self: *Array<T>) -> bool {
```
True if the array has no elements.
### `Array_Cap` _func_
```bux
func Array_Cap<T>(self: *Array<T>) -> uint {
```
Current capacity (not length).
### `Array_Clear` _func_
```bux
func Array_Clear<T>(self: *Array<T>) {
```
Drop length to zero; keeps allocated capacity.
### `Array_Reserve` _func_
```bux
func Array_Reserve<T>(self: *Array<T>, minCap: uint) {
```
Ensure capacity is at least `minCap` (does not shrink).
### `Array_First` _func_
```bux
func Array_First<T>(self: *Array<T>) -> T {
```
First element (bounds-checked if empty).
### `Array_Last` _func_
```bux
func Array_Last<T>(self: *Array<T>) -> T {
```
Last element (bounds-checked if empty).
### `Array_Pop` _func_
```bux
func Array_Pop<T>(self: *Array<T>) -> T {
```
Remove and return the last element (bounds-checked if empty).
### `Array_Contains` _func_
```bux
func Array_Contains<T>(self: *Array<T>, value: T) -> bool {
```
Linear search: true if `value` is present (uses `==`).
### `Array_IndexOf` _func_
```bux
func Array_IndexOf<T>(self: *Array<T>, value: T) -> int {
```
Index of first equal element, or `-1` if not found.
### `Array_Extend` _func_
```bux
func Array_Extend<T>(self: *Array<T>, other: *Array<T>) {
```
Append all elements of `other` onto `self`.
## `Channel`
_Source: `lib/Channel.bux`_
### `Channel_SendInt` _func_
```bux
func Channel_SendInt(ch: *Channel<int>, value: int) {
```
Convenience wrappers for common types
## `Iter`
_Source: `lib/Iter.bux`_
### `Array_Iter` _func_
```bux
func Array_Iter<T>(arr: *Array<T>) -> Iter<T> {
```
Create an iterator from an Array
### `Iter_HasNext` _func_
```bux
func Iter_HasNext<T>(it: *Iter<T>) -> bool {
```
Check if there are more elements
### `Iter_Next` _func_
```bux
func Iter_Next<T>(it: *Iter<T>) -> T {
```
Get the next element and advance (undefined if HasNext is false)
### `Iter_Peek` _func_
```bux
func Iter_Peek<T>(it: *Iter<T>) -> T {
```
Peek current element without advancing (undefined if HasNext is false)
### `Iter_Reset` _func_
```bux
func Iter_Reset<T>(it: *Iter<T>) {
```
Reset iterator to the beginning
### `Iter_Pos` _func_
```bux
func Iter_Pos<T>(it: *Iter<T>) -> uint {
```
Current position
### `Iter_Len` _func_
```bux
func Iter_Len<T>(it: *Iter<T>) -> uint {
```
Remaining length
### `Iter_Count` _func_
```bux
func Iter_Count<T>(it: *Iter<T>) -> uint {
```
Count remaining elements
### `Iter_Skip` _func_
```bux
func Iter_Skip<T>(it: *Iter<T>, n: uint) {
```
Skip N elements
### `Iter_Take` _func_
```bux
func Iter_Take<T>(it: *Iter<T>, n: uint) -> Iter<T> {
```
Take first N elements (by limiting len)
### `Iter_AnyEq` _func_
```bux
func Iter_AnyEq<T>(it: *Iter<T>, value: T) -> bool {
```
True if any remaining element equals value
### `Iter_AllEq` _func_
```bux
func Iter_AllEq<T>(it: *Iter<T>, value: T) -> bool {
```
True if every remaining element equals value (true if empty)
### `Iter_Collect` _func_
```bux
func Iter_Collect<T>(it: *Iter<T>) -> Array<T> {
```
Collect remaining elements into a new Array
### `Iter_Map` _func_
```bux
func Iter_Map<T, U>(it: *Iter<T>, f: func(T) -> U) -> Array<U> {
```
Map each remaining element through f: T → U, collect into Array<U>
### `Iter_Filter` _func_
```bux
func Iter_Filter<T>(it: *Iter<T>, pred: func(T) -> bool) -> Array<T> {
```
Keep remaining elements for which pred returns true
### `Iter_Fold` _func_
```bux
func Iter_Fold<T, Acc>(it: *Iter<T>, init: Acc, f: func(Acc, T) -> Acc) -> Acc {
```
Left-fold: f(f(...f(init, x0), x1), ...)
### `Iter_ForEach` _func_
```bux
func Iter_ForEach<T>(it: *Iter<T>, f: func(T) -> int) {
```
Call f for each remaining element (return value of f is ignored)
### `Iter_Any` _func_
```bux
func Iter_Any<T>(it: *Iter<T>, pred: func(T) -> bool) -> bool {
```
True if any remaining element satisfies pred
### `Iter_All` _func_
```bux
func Iter_All<T>(it: *Iter<T>, pred: func(T) -> bool) -> bool {
```
True if all remaining elements satisfy pred (true if empty)
### `Iter_SumInt` _func_
```bux
func Iter_SumInt(it: *Iter<int>) -> int {
```
Sum remaining ints (specialized fold)
## `Json`
_Source: `lib/Json.bux`_
### `JsonValue` _struct_
```bux
struct JsonValue {
```
=== Core type ===
### `Json_Null` _func_
```bux
func Json_Null() -> JsonValue {
```
=== Constructors ===
### `Json_ArrayLen` _func_
```bux
func Json_ArrayLen(v: JsonValue) -> uint {
```
=== Array helpers ===
### `Json_ObjectLen` _func_
```bux
func Json_ObjectLen(v: JsonValue) -> uint {
```
=== Object helpers ===
### `Json_IsNull` _func_
```bux
func Json_IsNull(v: JsonValue) -> bool {
```
=== Accessors ===
### `JsonParser` _struct_
```bux
struct JsonParser {
```
=== Parser ===
### `Json_Parse` _func_
```bux
func Json_Parse(s: String) -> JsonValue {
```
=== Public parser ===
### `Json_StringifyImpl` _func_
```bux
func Json_StringifyImpl(sb: *StringBuilder, v: JsonValue) {
```
=== Serializer ===
## `Map`
_Source: `lib/Map.bux`_
### `Map_Remove` _func_
```bux
func Map_Remove<K, V>(m: *Map<K, V>, key: K) -> bool {
```
Remove key if present. Rebuilds the table to keep open-addressing correct.
## `Net`
_Source: `lib/Net.bux`_
### `Net_Create` _func_
```bux
func Net_Create() -> int {
```
Create a TCP socket. Returns -1 on error.
### `Net_SetReuse` _func_
```bux
func Net_SetReuse(fd: int) -> bool {
```
Enable SO_REUSEADDR on a socket.
### `Net_Bind` _func_
```bux
func Net_Bind(fd: int, addr: String, port: int) -> bool {
```
Bind a socket to an address and port.
### `Net_Listen` _func_
```bux
func Net_Listen(fd: int, backlog: int) -> bool {
```
Start listening for connections.
### `Net_Accept` _func_
```bux
func Net_Accept(fd: int) -> int {
```
Accept a connection. Returns new fd or -1 on error.
### `Net_Connect` _func_
```bux
func Net_Connect(fd: int, addr: String, port: int) -> bool {
```
Connect to a remote address and port.
### `Net_Send` _func_
```bux
func Net_Send(fd: int, data: String) -> int {
```
Send data. Returns bytes sent or -1 on error.
### `Net_Recv` _func_
```bux
func Net_Recv(fd: int, maxLen: int) -> String {
```
Receive up to maxLen bytes. Returns empty string on error/EOF.
### `Net_Close` _func_
```bux
func Net_Close(fd: int) -> bool {
```
Close a socket.
### `Net_LastError` _func_
```bux
func Net_LastError() -> String {
```
Get last socket error as a string.
## `Option`
_Source: `lib/Option.bux`_
### `Option_Expect` _func_
```bux
func Option_Expect(o: Option, msg: String) -> int {
```
Unwrap Some or panic with a custom message
### `Option_Or` _func_
```bux
func Option_Or(o: Option, other: Option) -> Option {
```
If o is Some return it, otherwise return other
## `Os`
_Source: `lib/Os.bux`_
### `Os_Exit` _func_
```bux
func Os_Exit(code: int) {
```
Terminate the process with the given exit code
## `Result`
_Source: `lib/Result.bux`_
### `Result_Expect` _func_
```bux
func Result_Expect(r: Result, msg: String) -> int {
```
Unwrap Ok or panic with a custom message
### `Result_UnwrapErr` _func_
```bux
func Result_UnwrapErr(r: Result) -> String {
```
Extract Err payload (panics if Ok)
### `Result_Or` _func_
```bux
func Result_Or(r: Result, other: Result) -> Result {
```
If r is Ok return it, otherwise return other
## `Set`
_Source: `lib/Set.bux`_
### `Set_Remove` _func_
```bux
func Set_Remove<T>(s: *Set<T>, value: T) -> bool {
```
Remove value if present. Rebuilds the table to keep open-addressing correct.
## `String`
_Source: `lib/String.bux`_
### `String_Len` _func_
```bux
func String_Len(s: String) -> uint {
```
Byte length of a C string (`strlen`).
### `String_IsEmpty` _func_
```bux
func String_IsEmpty(s: String) -> bool {
```
True if the string has zero length.
### `String_IsNull` _func_
```bux
func String_IsNull(s: String) -> bool {
```
True if the pointer is null.
### `String_Eq` _func_
```bux
func String_Eq(a: String, b: String) -> bool {
```
Lexicographic equality.
### `String_Concat` _func_
```bux
func String_Concat(a: String, b: String) -> String {
```
Allocate and return `a` concatenated with `b`.
### `String_Copy` _func_
```bux
func String_Copy(s: String) -> String {
```
Heap-copy of `s`.
### `String_StartsWith` _func_
```bux
func String_StartsWith(s: String, prefix: String) -> bool {
```
True if `s` begins with `prefix`.
### `String_EndsWith` _func_
```bux
func String_EndsWith(s: String, suffix: String) -> bool {
```
True if `s` ends with `suffix`.
### `String_Contains` _func_
```bux
func String_Contains(s: String, substr: String) -> bool {
```
True if `substr` occurs anywhere in `s`.
### `String_IsBlank` _func_
```bux
func String_IsBlank(s: String) -> bool {
```
True if empty or only whitespace (space, tab, CR, LF).
### `String_Repeat` _func_
```bux
func String_Repeat(s: String, count: uint) -> String {
```
Repeat `s`, `count` times (`count == 0` → empty string).
### `String_ReplaceAll` _func_
```bux
func String_ReplaceAll(s: String, old: String, new: String) -> String {
```
Replace every non-overlapping occurrence of `old` with `new`.
Empty `old` is a no-op (returns `s` unchanged). Safe if `new` contains `old`.
## `Test`
_Source: `lib/Test.bux`_
### `Test_Exit` _func_
```bux
func Test_Exit(code: int) {
```
Exit the process with `code` (for test runners).
### `Test_Assert` _func_
```bux
func Test_Assert(cond: bool) {
```
Assert `cond` is true; abort on failure.
### `Test_AssertEqInt` _func_
```bux
func Test_AssertEqInt(a: int, b: int) {
```
Assert two ints are equal; print both values and exit 1 on mismatch.
### `Test_AssertNeqInt` _func_
```bux
func Test_AssertNeqInt(a: int, b: int) {
```
Assert two ints differ.
### `Test_AssertEqString` _func_
```bux
func Test_AssertEqString(a: String, b: String) {
```
Assert two strings are equal (`String_Eq`).
### `Test_AssertEqBool` _func_
```bux
func Test_AssertEqBool(a: bool, b: bool) {
```
Assert two bools are equal.
### `Test_AssertTrue` _func_
```bux
func Test_AssertTrue(cond: bool) {
```
Assert `cond` is true.
### `Test_AssertFalse` _func_
```bux
func Test_AssertFalse(cond: bool) {
```
Assert `cond` is false.
### `Test_Fail` _func_
```bux
func Test_Fail(msg: String) {
```
Fail the test with a message and exit 1.
### `Test_Pass` _func_
```bux
func Test_Pass(msg: String) {
```
Print a PASS line (for human-readable runners / goldens).
+51
View File
@@ -0,0 +1,51 @@
// lifetime_elision.bux — C.1: elided lifetimes for common &[Checked] APIs
// No 'a annotations needed when there is a single input reference.
import Std::Io::{PrintLine, PrintInt};
import Std::Test::{Test_AssertEqInt, Test_Pass};
// Elided: param and return share one lifetime automatically
@[Checked]
func Identity(p: &int) -> &int {
return p;
}
// Explicit lifetime for documentation / multi-ref (same lifetime both sides)
@[Checked]
func IdentityNamed<'a>(p: &'a int) -> &'a int {
return p;
}
// Via intermediate let binding — lifetime is propagated
@[Checked]
func ViaLet(p: &int) -> &int {
let r: &int = p;
return r;
}
// self-style first param: elision prefers the first input for the return
@[Checked]
func FirstOf(self: &int, _other: int) -> &int {
return self;
}
@[Checked]
func Main() -> int {
var x: int = 10;
var y: int = 20;
let a: &int = Identity(&x);
Test_AssertEqInt(*a, 10);
let b: &int = IdentityNamed(&y);
Test_AssertEqInt(*b, 20);
let c: &int = ViaLet(&x);
Test_AssertEqInt(*c, 10);
let d: &int = FirstOf(&y, 0);
Test_AssertEqInt(*d, 20);
Test_Pass("lifetime_elision");
PrintLine("lifetime_elision: ok");
return 0;
}
+62 -54
View File
@@ -1,106 +1,114 @@
module Std::Array {
extern func bux_alloc(size: uint) -> *void;
extern func bux_realloc(ptr: *void, size: uint) -> *void;
extern func bux_free(ptr: *void);
extern func bux_bounds_check(index: uint, len: uint);
extern func bux_alloc(size: uint) -> *void;
extern func bux_realloc(ptr: *void, size: uint) -> *void;
extern func bux_free(ptr: *void);
extern func bux_bounds_check(index: uint, len: uint);
struct Array<T> {
/// Growable contiguous buffer of `T` (len + capacity).
struct Array<T> {
data: *T,
len: uint,
cap: uint,
}
}
func Array_New<T>(cap: uint) -> Array<T> {
/// Create an empty array with the given initial capacity.
func Array_New<T>(cap: uint) -> Array<T> {
let data = bux_alloc(cap * sizeof(T)) as *T;
return Array<T> { data: data, len: 0, cap: cap };
}
}
func Array_Push<T>(self: *Array<T>, value: T) {
/// Append `value`, growing capacity if needed.
func Array_Push<T>(self: *Array<T>, value: T) {
if self.len >= self.cap {
self.cap = self.cap * 2;
self.data = bux_realloc(self.data as *void, self.cap * sizeof(T)) as *T;
}
self.data[self.len] = value;
self.len = self.len + 1;
}
}
func Array_Get<T>(self: *Array<T>, index: uint) -> T {
/// Element at `index` (bounds-checked unless `@[Release]`).
func Array_Get<T>(self: *Array<T>, index: uint) -> T {
bux_bounds_check(index, self.len);
return self.data[index];
}
}
func Array_Set<T>(self: *Array<T>, index: uint, value: T) {
/// Write `value` at `index` (bounds-checked unless `@[Release]`).
func Array_Set<T>(self: *Array<T>, index: uint, value: T) {
bux_bounds_check(index, self.len);
self.data[index] = value;
}
}
func Array_Len<T>(self: *Array<T>) -> uint {
/// Number of live elements.
func Array_Len<T>(self: *Array<T>) -> uint {
return self.len;
}
}
func Array_Free<T>(self: *Array<T>) {
/// Free the backing buffer and reset length/capacity to zero.
func Array_Free<T>(self: *Array<T>) {
bux_free(self.data as *void);
self.data = null as *T;
self.len = 0;
self.cap = 0;
}
}
func Array_Drop<T>(self: *Array<T>) {
/// Drop trait entry — same as `Array_Free`.
func Array_Drop<T>(self: *Array<T>) {
Array_Free<T>(self);
}
}
func Array_operator_index_get<T>(self: *Array<T>, idx: uint) -> T {
func Array_operator_index_get<T>(self: *Array<T>, idx: uint) -> T {
return Array_Get<T>(self, idx);
}
}
func Array_operator_index_set<T>(self: *Array<T>, idx: uint, value: T) {
func Array_operator_index_set<T>(self: *Array<T>, idx: uint, value: T) {
Array_Set<T>(self, idx, value);
}
}
/* True if the array has no elements */
func Array_IsEmpty<T>(self: *Array<T>) -> bool {
/// True if the array has no elements.
func Array_IsEmpty<T>(self: *Array<T>) -> bool {
return self.len == 0;
}
}
/* Current capacity (not length) */
func Array_Cap<T>(self: *Array<T>) -> uint {
/// Current capacity (not length).
func Array_Cap<T>(self: *Array<T>) -> uint {
return self.cap;
}
}
/* Drop length to zero; keeps allocated capacity */
func Array_Clear<T>(self: *Array<T>) {
/// Drop length to zero; keeps allocated capacity.
func Array_Clear<T>(self: *Array<T>) {
self.len = 0;
}
}
/* Ensure capacity is at least minCap (does not shrink) */
func Array_Reserve<T>(self: *Array<T>, minCap: uint) {
/// Ensure capacity is at least `minCap` (does not shrink).
func Array_Reserve<T>(self: *Array<T>, minCap: uint) {
if minCap <= self.cap {
return;
}
self.cap = minCap;
self.data = bux_realloc(self.data as *void, self.cap * sizeof(T)) as *T;
}
}
/* First element (panics if empty via bounds check) */
func Array_First<T>(self: *Array<T>) -> T {
/// First element (bounds-checked if empty).
func Array_First<T>(self: *Array<T>) -> T {
return Array_Get<T>(self, 0);
}
}
/* Last element (panics if empty via bounds check) */
func Array_Last<T>(self: *Array<T>) -> T {
/// Last element (bounds-checked if empty).
func Array_Last<T>(self: *Array<T>) -> T {
return Array_Get<T>(self, self.len - 1);
}
}
/* Remove and return the last element (panics if empty) */
func Array_Pop<T>(self: *Array<T>) -> T {
/// Remove and return the last element (bounds-checked if empty).
func Array_Pop<T>(self: *Array<T>) -> T {
bux_bounds_check(0, self.len);
self.len = self.len - 1;
return self.data[self.len];
}
}
/* Linear search: true if value is present (uses ==) */
func Array_Contains<T>(self: *Array<T>, value: T) -> bool {
/// Linear search: true if `value` is present (uses `==`).
func Array_Contains<T>(self: *Array<T>, value: T) -> bool {
var i: uint = 0;
while i < self.len {
if self.data[i] == value {
@@ -109,10 +117,10 @@ func Array_Contains<T>(self: *Array<T>, value: T) -> bool {
i = i + 1;
}
return false;
}
}
/* Index of first equal element, or -1 if not found */
func Array_IndexOf<T>(self: *Array<T>, value: T) -> int {
/// Index of first equal element, or `-1` if not found.
func Array_IndexOf<T>(self: *Array<T>, value: T) -> int {
var i: uint = 0;
while i < self.len {
if self.data[i] == value {
@@ -121,15 +129,15 @@ func Array_IndexOf<T>(self: *Array<T>, value: T) -> int {
i = i + 1;
}
return -1;
}
}
/* Append all elements of other onto self */
func Array_Extend<T>(self: *Array<T>, other: *Array<T>) {
/// Append all elements of `other` onto `self`.
func Array_Extend<T>(self: *Array<T>, other: *Array<T>) {
var i: uint = 0;
while i < other.len {
Array_Push<T>(self, other.data[i]);
i = i + 1;
}
}
}
}
+30 -30
View File
@@ -1,64 +1,64 @@
module Std::Channel {
extern func bux_channel_new(capacity: int64, elem_size: int64) -> *void;
extern func bux_channel_send(handle: *void, elem: *void);
extern func bux_channel_recv(handle: *void, out: *void) -> int;
extern func bux_channel_close(handle: *void);
extern func bux_channel_free(handle: *void);
extern func bux_channel_new(capacity: int64, elem_size: int64) -> *void;
extern func bux_channel_send(handle: *void, elem: *void);
extern func bux_channel_recv(handle: *void, out: *void) -> int;
extern func bux_channel_close(handle: *void);
extern func bux_channel_free(handle: *void);
struct Channel<T> {
struct Channel<T> {
handle: *void;
}
}
func Channel_New<T>(capacity: int64) -> Channel<T> {
func Channel_New<T>(capacity: int64) -> Channel<T> {
return Channel<T> { handle: bux_channel_new(capacity, sizeof(T)) };
}
}
func Channel_Send<T>(ch: *Channel<T>, value: T) {
func Channel_Send<T>(ch: *Channel<T>, value: T) {
bux_channel_send(ch.handle, (&value) as *void);
}
}
func Channel_Recv<T>(ch: *Channel<T>) -> T {
func Channel_Recv<T>(ch: *Channel<T>) -> T {
var result: T;
bux_channel_recv(ch.handle, (&result) as *void);
return result;
}
}
func Channel_Recv_Ok<T>(ch: *Channel<T>, out: *T) -> bool {
func Channel_Recv_Ok<T>(ch: *Channel<T>, out: *T) -> bool {
return bux_channel_recv(ch.handle, out as *void) != 0;
}
}
func Channel_Close<T>(ch: *Channel<T>) {
func Channel_Close<T>(ch: *Channel<T>) {
bux_channel_close(ch.handle);
}
}
func Channel_Free<T>(ch: *Channel<T>) {
func Channel_Free<T>(ch: *Channel<T>) {
bux_channel_free(ch.handle);
}
}
func Channel_Drop<T>(ch: *Channel<T>) {
func Channel_Drop<T>(ch: *Channel<T>) {
Channel_Free<T>(ch);
}
}
/* Convenience wrappers for common types */
func Channel_SendInt(ch: *Channel<int>, value: int) {
/* Convenience wrappers for common types */
func Channel_SendInt(ch: *Channel<int>, value: int) {
bux_channel_send(ch.handle, (&value) as *void);
}
}
func Channel_RecvInt(ch: *Channel<int>) -> int {
func Channel_RecvInt(ch: *Channel<int>) -> int {
var result: int = 0;
bux_channel_recv(ch.handle, (&result) as *void);
return result;
}
}
func Channel_SendFloat64(ch: *Channel<float64>, value: float64) {
func Channel_SendFloat64(ch: *Channel<float64>, value: float64) {
bux_channel_send(ch.handle, (&value) as *void);
}
}
func Channel_RecvFloat64(ch: *Channel<float64>) -> float64 {
func Channel_RecvFloat64(ch: *Channel<float64>) -> float64 {
var result: float64 = 0.0;
bux_channel_recv(ch.handle, (&result) as *void);
return result;
}
}
}
+28 -28
View File
@@ -10,31 +10,31 @@
// =============================================================================
module Std::Crypto {
import Std::Mem::{Alloc, Free};
import Std::String::{String_Len};
import Std::Mem::{Alloc, Free};
import Std::String::{String_Len};
// Re-use the same externs from submodules (merged by compiler)
extern func bux_sha256(data: String, len: int, out: *void);
extern func bux_hmac_sha256(key: String, keylen: int, msg: String, msglen: int, out: *void);
extern func bux_random_bytes(buf: *void, len: int) -> int;
extern func bux_base64_encode(data: String, len: int) -> String;
extern func bux_base64_decode(data: String, len: int, outlen: *int) -> String;
extern func bux_bytes_to_hex(data: *void, len: int) -> String;
// Re-use the same externs from submodules (merged by compiler)
extern func bux_sha256(data: String, len: int, out: *void);
extern func bux_hmac_sha256(key: String, keylen: int, msg: String, msglen: int, out: *void);
extern func bux_random_bytes(buf: *void, len: int) -> int;
extern func bux_base64_encode(data: String, len: int) -> String;
extern func bux_base64_decode(data: String, len: int, outlen: *int) -> String;
extern func bux_bytes_to_hex(data: *void, len: int) -> String;
// --- Legacy function names (delegate to new submodule functions) ---
// --- Legacy function names (delegate to new submodule functions) ---
// SHA-256 → hex
func Crypto_Sha256(data: String) -> String {
// SHA-256 → hex
func Crypto_Sha256(data: String) -> String {
let len: int = String_Len(data) as int;
let hashBuf: *void = Alloc(32);
bux_sha256(data, len, hashBuf);
let result: String = bux_bytes_to_hex(hashBuf as *void, 32);
Free(hashBuf);
return result;
}
}
// HMAC-SHA256 → hex
func Crypto_HmacSha256(key: String, message: String) -> String {
// HMAC-SHA256 → hex
func Crypto_HmacSha256(key: String, message: String) -> String {
let keylen: int = String_Len(key) as int;
let msglen: int = String_Len(message) as int;
let hmacBuf: *void = Alloc(32);
@@ -42,10 +42,10 @@ func Crypto_HmacSha256(key: String, message: String) -> String {
let result: String = bux_bytes_to_hex(hmacBuf as *void, 32);
Free(hmacBuf);
return result;
}
}
// Random bytes → base64
func Crypto_RandomBytes(n: int) -> String {
// Random bytes → base64
func Crypto_RandomBytes(n: int) -> String {
if n <= 0 { return ""; }
let buf: *void = Alloc(n as uint);
if bux_random_bytes(buf, n) != 1 {
@@ -55,15 +55,15 @@ func Crypto_RandomBytes(n: int) -> String {
let result: String = bux_base64_encode(buf as String, n);
Free(buf);
return result;
}
}
// Base64 encode
func Crypto_Base64Encode(s: String) -> String {
// Base64 encode
func Crypto_Base64Encode(s: String) -> String {
return bux_base64_encode(s, String_Len(s) as int);
}
}
// HMAC-SHA256 raw → base64
func Crypto_HmacSha256Raw(key: String, message: String) -> String {
// HMAC-SHA256 raw → base64
func Crypto_HmacSha256Raw(key: String, message: String) -> String {
let keylen: int = String_Len(key) as int;
let msglen: int = String_Len(message) as int;
let hmacBuf: *void = Alloc(32);
@@ -71,11 +71,11 @@ func Crypto_HmacSha256Raw(key: String, message: String) -> String {
let result: String = bux_base64_encode(hmacBuf as String, 32);
Free(hmacBuf);
return result;
}
}
// Base64 decode
func Crypto_Base64Decode(s: String) -> String {
// Base64 decode
func Crypto_Base64Decode(s: String) -> String {
let outlen: int = 0;
return bux_base64_decode(s, String_Len(s) as int, &outlen);
}
}
}
+21 -21
View File
@@ -8,7 +8,7 @@
module Std::Fmt {
import Std::String::{
import Std::String::{
String_Eq,
String_FromInt,
String_FromFloat,
@@ -18,14 +18,14 @@ import Std::String::{
StringBuilder_Append,
StringBuilder_Build,
String_Chars
};
};
extern func bux_strlen(s: String) -> uint;
extern func bux_str_to_int(s: String) -> int64;
extern func bux_alloc(size: uint) -> *void;
extern func bux_strlen(s: String) -> uint;
extern func bux_str_to_int(s: String) -> int64;
extern func bux_alloc(size: uint) -> *void;
// Core formatting engine: replace {0}..{9} in template with args
func Fmt_Format(tmpl: String, argStrs: *String, argCount: int) -> String {
// Core formatting engine: replace {0}..{9} in template with args
func Fmt_Format(tmpl: String, argStrs: *String, argCount: int) -> String {
let sb: StringBuilder = StringBuilder_New();
var i: uint = 0;
let tmplLen: uint = bux_strlen(tmpl);
@@ -58,43 +58,43 @@ func Fmt_Format(tmpl: String, argStrs: *String, argCount: int) -> String {
i = i + 1;
}
return StringBuilder_Build(&sb);
}
}
// Convenience wrappers
func Fmt_Fmt1(tmpl: String, a1: String) -> String {
// Convenience wrappers
func Fmt_Fmt1(tmpl: String, a1: String) -> String {
var args: *String = bux_alloc(sizeof(String)) as *String;
args[0] = a1;
return Fmt_Format(tmpl, args, 1);
}
}
func Fmt_FmtInt(tmpl: String, val: int64) -> String {
func Fmt_FmtInt(tmpl: String, val: int64) -> String {
let s: String = String_FromInt(val);
return Fmt_Fmt1(tmpl, s);
}
}
func Fmt_FmtBool(tmpl: String, val: bool) -> String {
func Fmt_FmtBool(tmpl: String, val: bool) -> String {
let s: String = String_FromBool(val);
return Fmt_Fmt1(tmpl, s);
}
}
func Fmt_FmtFloat(tmpl: String, val: float64) -> String {
func Fmt_FmtFloat(tmpl: String, val: float64) -> String {
let s: String = String_FromFloat(val);
return Fmt_Fmt1(tmpl, s);
}
}
func Fmt_Fmt2(tmpl: String, a1: String, a2: String) -> String {
func Fmt_Fmt2(tmpl: String, a1: String, a2: String) -> String {
var args: *String = bux_alloc(2 * sizeof(String)) as *String;
args[0] = a1;
args[1] = a2;
return Fmt_Format(tmpl, args, 2);
}
}
func Fmt_Fmt3(tmpl: String, a1: String, a2: String, a3: String) -> String {
func Fmt_Fmt3(tmpl: String, a1: String, a2: String, a3: String) -> String {
var args: *String = bux_alloc(3 * sizeof(String)) as *String;
args[0] = a1;
args[1] = a2;
args[2] = a3;
return Fmt_Format(tmpl, args, 3);
}
}
}
+9 -9
View File
@@ -1,19 +1,19 @@
module Std::Fs {
extern func bux_dir_exists(path: String) -> int;
extern func bux_mkdir_if_needed(path: String) -> int;
extern func bux_list_dir(dir: String, ext: String, out_count: *int) -> *String;
extern func bux_dir_exists(path: String) -> int;
extern func bux_mkdir_if_needed(path: String) -> int;
extern func bux_list_dir(dir: String, ext: String, out_count: *int) -> *String;
func DirExists(path: String) -> bool {
func DirExists(path: String) -> bool {
return bux_dir_exists(path) != 0;
}
}
func Mkdir(path: String) -> bool {
func Mkdir(path: String) -> bool {
return bux_mkdir_if_needed(path) != 0;
}
}
func ListDir(dir: String, ext: String, count: *int) -> *String {
func ListDir(dir: String, ext: String, count: *int) -> *String {
return bux_list_dir(dir, ext, count);
}
}
}
+16 -16
View File
@@ -1,28 +1,28 @@
module Std::Io {
extern func PrintLine(s: String);
extern func Print(s: String);
extern func PrintInt(n: int);
extern func PrintInt64(n: int64);
extern func PrintFloat(f: float64);
extern func PrintBool(b: bool);
extern func ReadLine() -> String;
extern func bux_read_file(path: String) -> String;
extern func bux_write_file(path: String, content: String) -> int;
extern func bux_file_exists(path: String) -> int;
extern func PrintLine(s: String);
extern func Print(s: String);
extern func PrintInt(n: int);
extern func PrintInt64(n: int64);
extern func PrintFloat(f: float64);
extern func PrintBool(b: bool);
extern func ReadLine() -> String;
extern func bux_read_file(path: String) -> String;
extern func bux_write_file(path: String, content: String) -> int;
extern func bux_file_exists(path: String) -> int;
func ReadFile(path: String) -> String {
func ReadFile(path: String) -> String {
return bux_read_file(path);
}
}
func WriteFile(path: String, content: String) -> bool {
func WriteFile(path: String, content: String) -> bool {
let r: int = bux_write_file(path, content);
return r != 0;
}
}
func FileExists(path: String) -> bool {
func FileExists(path: String) -> bool {
let r: int = bux_file_exists(path);
return r != 0;
}
}
}
+81 -81
View File
@@ -1,74 +1,74 @@
module Std::Iter {
import Std::Array::*;
import Std::Array::*;
struct Iter<T> {
struct Iter<T> {
data: *T,
len: uint,
pos: uint,
}
}
/* Create an iterator from an Array */
func Array_Iter<T>(arr: *Array<T>) -> Iter<T> {
/* Create an iterator from an Array */
func Array_Iter<T>(arr: *Array<T>) -> Iter<T> {
return Iter<T> { data: arr.data, len: arr.len, pos: 0 };
}
}
/* Check if there are more elements */
func Iter_HasNext<T>(it: *Iter<T>) -> bool {
/* Check if there are more elements */
func Iter_HasNext<T>(it: *Iter<T>) -> bool {
return it.pos < it.len;
}
}
/* Get the next element and advance (undefined if HasNext is false) */
func Iter_Next<T>(it: *Iter<T>) -> T {
/* Get the next element and advance (undefined if HasNext is false) */
func Iter_Next<T>(it: *Iter<T>) -> T {
let val: T = it.data[it.pos];
it.pos = it.pos + 1;
return val;
}
}
/* Peek current element without advancing (undefined if HasNext is false) */
func Iter_Peek<T>(it: *Iter<T>) -> T {
/* Peek current element without advancing (undefined if HasNext is false) */
func Iter_Peek<T>(it: *Iter<T>) -> T {
return it.data[it.pos];
}
}
/* Reset iterator to the beginning */
func Iter_Reset<T>(it: *Iter<T>) {
/* Reset iterator to the beginning */
func Iter_Reset<T>(it: *Iter<T>) {
it.pos = 0;
}
}
/* Current position */
func Iter_Pos<T>(it: *Iter<T>) -> uint {
/* Current position */
func Iter_Pos<T>(it: *Iter<T>) -> uint {
return it.pos;
}
}
/* Remaining length */
func Iter_Len<T>(it: *Iter<T>) -> uint {
/* Remaining length */
func Iter_Len<T>(it: *Iter<T>) -> uint {
return it.len;
}
}
/* Count remaining elements */
func Iter_Count<T>(it: *Iter<T>) -> uint {
/* Count remaining elements */
func Iter_Count<T>(it: *Iter<T>) -> uint {
return it.len - it.pos;
}
}
/* Skip N elements */
func Iter_Skip<T>(it: *Iter<T>, n: uint) {
/* Skip N elements */
func Iter_Skip<T>(it: *Iter<T>, n: uint) {
it.pos = it.pos + n;
if it.pos > it.len {
it.pos = it.len;
}
}
}
/* Take first N elements (by limiting len) */
func Iter_Take<T>(it: *Iter<T>, n: uint) -> Iter<T> {
/* Take first N elements (by limiting len) */
func Iter_Take<T>(it: *Iter<T>, n: uint) -> Iter<T> {
var endPos: uint = it.pos + n;
if endPos > it.len {
endPos = it.len;
}
return Iter<T> { data: it.data, len: endPos, pos: it.pos };
}
}
/* True if any remaining element equals value */
func Iter_AnyEq<T>(it: *Iter<T>, value: T) -> bool {
/* True if any remaining element equals value */
func Iter_AnyEq<T>(it: *Iter<T>, value: T) -> bool {
var i: uint = it.pos;
while i < it.len {
if it.data[i] == value {
@@ -77,10 +77,10 @@ func Iter_AnyEq<T>(it: *Iter<T>, value: T) -> bool {
i = i + 1;
}
return false;
}
}
/* True if every remaining element equals value (true if empty) */
func Iter_AllEq<T>(it: *Iter<T>, value: T) -> bool {
/* True if every remaining element equals value (true if empty) */
func Iter_AllEq<T>(it: *Iter<T>, value: T) -> bool {
var i: uint = it.pos;
while i < it.len {
if it.data[i] != value {
@@ -89,10 +89,10 @@ func Iter_AllEq<T>(it: *Iter<T>, value: T) -> bool {
i = i + 1;
}
return true;
}
}
/* Collect remaining elements into a new Array */
func Iter_Collect<T>(it: *Iter<T>) -> Array<T> {
/* Collect remaining elements into a new Array */
func Iter_Collect<T>(it: *Iter<T>) -> Array<T> {
let remaining: uint = it.len - it.pos;
var cap: uint = remaining;
if cap == 0 {
@@ -105,14 +105,14 @@ func Iter_Collect<T>(it: *Iter<T>) -> Array<T> {
i = i + 1;
}
return arr;
}
}
// ---------------------------------------------------------------------------
// Higher-order helpers (generic; fat func pointers / closures)
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Higher-order helpers (generic; fat func pointers / closures)
// ---------------------------------------------------------------------------
/* Map each remaining element through f: T → U, collect into Array<U> */
func Iter_Map<T, U>(it: *Iter<T>, f: func(T) -> U) -> Array<U> {
/* Map each remaining element through f: T → U, collect into Array<U> */
func Iter_Map<T, U>(it: *Iter<T>, f: func(T) -> U) -> Array<U> {
let remaining: uint = it.len - it.pos;
var cap: uint = remaining;
if cap == 0 {
@@ -126,10 +126,10 @@ func Iter_Map<T, U>(it: *Iter<T>, f: func(T) -> U) -> Array<U> {
i = i + 1;
}
return out;
}
}
/* Keep remaining elements for which pred returns true */
func Iter_Filter<T>(it: *Iter<T>, pred: func(T) -> bool) -> Array<T> {
/* Keep remaining elements for which pred returns true */
func Iter_Filter<T>(it: *Iter<T>, pred: func(T) -> bool) -> Array<T> {
let remaining: uint = it.len - it.pos;
var cap: uint = remaining;
if cap == 0 {
@@ -145,10 +145,10 @@ func Iter_Filter<T>(it: *Iter<T>, pred: func(T) -> bool) -> Array<T> {
i = i + 1;
}
return out;
}
}
/* Left-fold: f(f(...f(init, x0), x1), ...) */
func Iter_Fold<T, Acc>(it: *Iter<T>, init: Acc, f: func(Acc, T) -> Acc) -> Acc {
/* Left-fold: f(f(...f(init, x0), x1), ...) */
func Iter_Fold<T, Acc>(it: *Iter<T>, init: Acc, f: func(Acc, T) -> Acc) -> Acc {
var acc: Acc = init;
var i: uint = it.pos;
while i < it.len {
@@ -156,19 +156,19 @@ func Iter_Fold<T, Acc>(it: *Iter<T>, init: Acc, f: func(Acc, T) -> Acc) -> Acc {
i = i + 1;
}
return acc;
}
}
/* Call f for each remaining element (return value of f is ignored) */
func Iter_ForEach<T>(it: *Iter<T>, f: func(T) -> int) {
/* Call f for each remaining element (return value of f is ignored) */
func Iter_ForEach<T>(it: *Iter<T>, f: func(T) -> int) {
var i: uint = it.pos;
while i < it.len {
let _ignored: int = f(it.data[i]);
i = i + 1;
}
}
}
/* True if any remaining element satisfies pred */
func Iter_Any<T>(it: *Iter<T>, pred: func(T) -> bool) -> bool {
/* True if any remaining element satisfies pred */
func Iter_Any<T>(it: *Iter<T>, pred: func(T) -> bool) -> bool {
var i: uint = it.pos;
while i < it.len {
if pred(it.data[i]) {
@@ -177,10 +177,10 @@ func Iter_Any<T>(it: *Iter<T>, pred: func(T) -> bool) -> bool {
i = i + 1;
}
return false;
}
}
/* True if all remaining elements satisfy pred (true if empty) */
func Iter_All<T>(it: *Iter<T>, pred: func(T) -> bool) -> bool {
/* True if all remaining elements satisfy pred (true if empty) */
func Iter_All<T>(it: *Iter<T>, pred: func(T) -> bool) -> bool {
var i: uint = it.pos;
while i < it.len {
if !pred(it.data[i]) {
@@ -189,10 +189,10 @@ func Iter_All<T>(it: *Iter<T>, pred: func(T) -> bool) -> bool {
i = i + 1;
}
return true;
}
}
/* Sum remaining ints (specialized fold) */
func Iter_SumInt(it: *Iter<int>) -> int {
/* Sum remaining ints (specialized fold) */
func Iter_SumInt(it: *Iter<int>) -> int {
var total: int = 0;
var i: uint = it.pos;
while i < it.len {
@@ -200,34 +200,34 @@ func Iter_SumInt(it: *Iter<int>) -> int {
i = i + 1;
}
return total;
}
}
// ---------------------------------------------------------------------------
// Int-specialized aliases (backward compatible with earlier examples)
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Int-specialized aliases (backward compatible with earlier examples)
// ---------------------------------------------------------------------------
func Iter_MapInt(it: *Iter<int>, f: func(int) -> int) -> Array<int> {
func Iter_MapInt(it: *Iter<int>, f: func(int) -> int) -> Array<int> {
return Iter_Map<int, int>(it, f);
}
}
func Iter_FilterInt(it: *Iter<int>, pred: func(int) -> bool) -> Array<int> {
func Iter_FilterInt(it: *Iter<int>, pred: func(int) -> bool) -> Array<int> {
return Iter_Filter<int>(it, pred);
}
}
func Iter_FoldInt(it: *Iter<int>, init: int, f: func(int, int) -> int) -> int {
func Iter_FoldInt(it: *Iter<int>, init: int, f: func(int, int) -> int) -> int {
return Iter_Fold<int, int>(it, init, f);
}
}
func Iter_ForEachInt(it: *Iter<int>, f: func(int) -> int) {
func Iter_ForEachInt(it: *Iter<int>, f: func(int) -> int) {
Iter_ForEach<int>(it, f);
}
}
func Iter_AnyInt(it: *Iter<int>, pred: func(int) -> bool) -> bool {
func Iter_AnyInt(it: *Iter<int>, pred: func(int) -> bool) -> bool {
return Iter_Any<int>(it, pred);
}
}
func Iter_AllInt(it: *Iter<int>, pred: func(int) -> bool) -> bool {
func Iter_AllInt(it: *Iter<int>, pred: func(int) -> bool) -> bool {
return Iter_All<int>(it, pred);
}
}
}
+80 -80
View File
@@ -1,17 +1,17 @@
module Std::Json {
import Std::Mem::{Alloc, Realloc, Free};
import Std::String;
import Std::Mem::{Alloc, Realloc, Free};
import Std::String;
/* === Tags === */
const JsonTagNull: int = 0;
const JsonTagBool: int = 1;
const JsonTagNumber: int = 2;
const JsonTagString: int = 3;
const JsonTagArray: int = 4;
const JsonTagObject: int = 5;
/* === Tags === */
const JsonTagNull: int = 0;
const JsonTagBool: int = 1;
const JsonTagNumber: int = 2;
const JsonTagString: int = 3;
const JsonTagArray: int = 4;
const JsonTagObject: int = 5;
/* === Core type === */
struct JsonValue {
/* === Core type === */
struct JsonValue {
tag: int,
boolVal: bool,
numVal: float64,
@@ -23,70 +23,70 @@ struct JsonValue {
objValues: *JsonValue,
objLen: uint,
objCap: uint
}
}
/* === Constructors === */
func Json_Null() -> JsonValue {
/* === Constructors === */
func Json_Null() -> JsonValue {
return JsonValue {
tag: JsonTagNull, boolVal: false, numVal: 0.0, strVal: "",
arrData: null, arrLen: 0, arrCap: 0,
objKeys: null, objValues: null, objLen: 0, objCap: 0
};
}
}
func Json_Bool(b: bool) -> JsonValue {
func Json_Bool(b: bool) -> JsonValue {
return JsonValue {
tag: JsonTagBool, boolVal: b, numVal: 0.0, strVal: "",
arrData: null, arrLen: 0, arrCap: 0,
objKeys: null, objValues: null, objLen: 0, objCap: 0
};
}
}
func Json_Number(n: float64) -> JsonValue {
func Json_Number(n: float64) -> JsonValue {
return JsonValue {
tag: JsonTagNumber, boolVal: false, numVal: n, strVal: "",
arrData: null, arrLen: 0, arrCap: 0,
objKeys: null, objValues: null, objLen: 0, objCap: 0
};
}
}
func Json_String(s: String) -> JsonValue {
func Json_String(s: String) -> JsonValue {
return JsonValue {
tag: JsonTagString, boolVal: false, numVal: 0.0, strVal: s,
arrData: null, arrLen: 0, arrCap: 0,
objKeys: null, objValues: null, objLen: 0, objCap: 0
};
}
}
func Json_Array() -> JsonValue {
func Json_Array() -> JsonValue {
return JsonValue {
tag: JsonTagArray, boolVal: false, numVal: 0.0, strVal: "",
arrData: null, arrLen: 0, arrCap: 0,
objKeys: null, objValues: null, objLen: 0, objCap: 0
};
}
}
func Json_Object() -> JsonValue {
func Json_Object() -> JsonValue {
return JsonValue {
tag: JsonTagObject, boolVal: false, numVal: 0.0, strVal: "",
arrData: null, arrLen: 0, arrCap: 0,
objKeys: null, objValues: null, objLen: 0, objCap: 0
};
}
}
/* === Array helpers === */
func Json_ArrayLen(v: JsonValue) -> uint {
/* === Array helpers === */
func Json_ArrayLen(v: JsonValue) -> uint {
if v.tag != JsonTagArray { return 0; }
return v.arrLen;
}
}
func Json_ArrayGet(v: JsonValue, index: uint) -> JsonValue {
func Json_ArrayGet(v: JsonValue, index: uint) -> JsonValue {
if v.tag != JsonTagArray { return Json_Null(); }
if index >= v.arrLen { return Json_Null(); }
return v.arrData[index];
}
}
func Json_ArrayPush(self: *JsonValue, val: JsonValue) {
func Json_ArrayPush(self: *JsonValue, val: JsonValue) {
if self.tag != JsonTagArray { return; }
if self.arrLen >= self.arrCap {
let arrNewCap: uint = self.arrCap;
@@ -101,15 +101,15 @@ func Json_ArrayPush(self: *JsonValue, val: JsonValue) {
}
self.arrData[self.arrLen] = val;
self.arrLen = self.arrLen + 1;
}
}
/* === Object helpers === */
func Json_ObjectLen(v: JsonValue) -> uint {
/* === Object helpers === */
func Json_ObjectLen(v: JsonValue) -> uint {
if v.tag != JsonTagObject { return 0; }
return v.objLen;
}
}
func Json_ObjectGet(v: JsonValue, key: String) -> JsonValue {
func Json_ObjectGet(v: JsonValue, key: String) -> JsonValue {
if v.tag != JsonTagObject { return Json_Null(); }
var i: uint = 0;
while i < v.objLen {
@@ -119,9 +119,9 @@ func Json_ObjectGet(v: JsonValue, key: String) -> JsonValue {
i = i + 1;
}
return Json_Null();
}
}
func Json_ObjectHas(v: JsonValue, key: String) -> bool {
func Json_ObjectHas(v: JsonValue, key: String) -> bool {
if v.tag != JsonTagObject { return false; }
var i: uint = 0;
while i < v.objLen {
@@ -131,9 +131,9 @@ func Json_ObjectHas(v: JsonValue, key: String) -> bool {
i = i + 1;
}
return false;
}
}
func Json_ObjectSet(self: *JsonValue, key: String, val: JsonValue) {
func Json_ObjectSet(self: *JsonValue, key: String, val: JsonValue) {
if self.tag != JsonTagObject { return; }
var i: uint = 0;
while i < self.objLen {
@@ -159,48 +159,48 @@ func Json_ObjectSet(self: *JsonValue, key: String, val: JsonValue) {
self.objKeys[self.objLen] = key;
self.objValues[self.objLen] = val;
self.objLen = self.objLen + 1;
}
}
/* === Accessors === */
func Json_IsNull(v: JsonValue) -> bool {
/* === Accessors === */
func Json_IsNull(v: JsonValue) -> bool {
return v.tag == JsonTagNull;
}
}
func Json_AsBool(v: JsonValue) -> bool {
func Json_AsBool(v: JsonValue) -> bool {
if v.tag == JsonTagBool { return v.boolVal; }
return false;
}
}
func Json_AsNumber(v: JsonValue) -> float64 {
func Json_AsNumber(v: JsonValue) -> float64 {
if v.tag == JsonTagNumber { return v.numVal; }
return 0.0;
}
}
func Json_AsString(v: JsonValue) -> String {
func Json_AsString(v: JsonValue) -> String {
if v.tag == JsonTagString { return v.strVal; }
return "";
}
}
/* === Parser === */
struct JsonParser {
/* === Parser === */
struct JsonParser {
src: String,
pos: uint,
len: uint,
error: String
}
}
func JsonParser_Peek(p: *JsonParser) -> int {
func JsonParser_Peek(p: *JsonParser) -> int {
if p.pos >= p.len { return 0; }
return p.src[p.pos] as int;
}
}
func JsonParser_Advance(p: *JsonParser) {
func JsonParser_Advance(p: *JsonParser) {
if p.pos < p.len {
p.pos = p.pos + 1;
}
}
}
func JsonParser_SkipWhitespace(p: *JsonParser) {
func JsonParser_SkipWhitespace(p: *JsonParser) {
while true {
let c: int = JsonParser_Peek(p);
if c == 32 || c == 9 || c == 10 || c == 13 {
@@ -209,9 +209,9 @@ func JsonParser_SkipWhitespace(p: *JsonParser) {
return;
}
}
}
}
func JsonParser_Match(p: *JsonParser, expected: String) -> bool {
func JsonParser_Match(p: *JsonParser, expected: String) -> bool {
let elen: uint = String_Len(expected);
if p.pos + elen > p.len { return false; }
var i: uint = 0;
@@ -223,11 +223,11 @@ func JsonParser_Match(p: *JsonParser, expected: String) -> bool {
}
p.pos = p.pos + elen;
return true;
}
}
func JsonParser_ParseValue(p: *JsonParser) -> JsonValue;
func JsonParser_ParseValue(p: *JsonParser) -> JsonValue;
func JsonParser_ParseString(p: *JsonParser) -> String {
func JsonParser_ParseString(p: *JsonParser) -> String {
if JsonParser_Peek(p) != 34 {
p.error = "Expected string";
return "";
@@ -274,9 +274,9 @@ func JsonParser_ParseString(p: *JsonParser) -> String {
let result: String = StringBuilder_Build(&sb);
StringBuilder_Free(&sb);
return result;
}
}
func JsonParser_ParseNumber(p: *JsonParser) -> JsonValue {
func JsonParser_ParseNumber(p: *JsonParser) -> JsonValue {
let start: uint = p.pos;
let c0: int = JsonParser_Peek(p);
if c0 == 45 {
@@ -304,9 +304,9 @@ func JsonParser_ParseNumber(p: *JsonParser) -> JsonValue {
let numStr: String = String_Slice(p.src, start, p.pos - start);
let n: float64 = String_ToFloat(numStr);
return Json_Number(n);
}
}
func JsonParser_ParseArray(p: *JsonParser) -> JsonValue {
func JsonParser_ParseArray(p: *JsonParser) -> JsonValue {
JsonParser_Advance(p);
var arr: JsonValue = Json_Array();
JsonParser_SkipWhitespace(p);
@@ -332,9 +332,9 @@ func JsonParser_ParseArray(p: *JsonParser) -> JsonValue {
return Json_Null();
}
}
}
}
func JsonParser_ParseObject(p: *JsonParser) -> JsonValue {
func JsonParser_ParseObject(p: *JsonParser) -> JsonValue {
JsonParser_Advance(p);
var obj: JsonValue = Json_Object();
JsonParser_SkipWhitespace(p);
@@ -369,9 +369,9 @@ func JsonParser_ParseObject(p: *JsonParser) -> JsonValue {
return Json_Null();
}
}
}
}
func JsonParser_ParseValue(p: *JsonParser) -> JsonValue {
func JsonParser_ParseValue(p: *JsonParser) -> JsonValue {
JsonParser_SkipWhitespace(p);
let c: int = JsonParser_Peek(p);
if c == 0 {
@@ -413,10 +413,10 @@ func JsonParser_ParseValue(p: *JsonParser) -> JsonValue {
}
p.error = "Unexpected character";
return Json_Null();
}
}
/* === Public parser === */
func Json_Parse(s: String) -> JsonValue {
/* === Public parser === */
func Json_Parse(s: String) -> JsonValue {
var p: JsonParser = JsonParser { src: s, pos: 0, len: String_Len(s), error: "" };
let result: JsonValue = JsonParser_ParseValue(&p);
JsonParser_SkipWhitespace(&p);
@@ -425,10 +425,10 @@ func Json_Parse(s: String) -> JsonValue {
return Json_Null();
}
return result;
}
}
/* === Serializer === */
func Json_StringifyImpl(sb: *StringBuilder, v: JsonValue) {
/* === Serializer === */
func Json_StringifyImpl(sb: *StringBuilder, v: JsonValue) {
if v.tag == JsonTagNull {
StringBuilder_Append(sb, "null");
return;
@@ -481,12 +481,12 @@ func Json_StringifyImpl(sb: *StringBuilder, v: JsonValue) {
StringBuilder_AppendChar(sb, 125 as char8);
return;
}
}
}
func Json_Stringify(v: JsonValue) -> String {
func Json_Stringify(v: JsonValue) -> String {
let sb: StringBuilder = StringBuilder_New();
Json_StringifyImpl(&sb, v);
return StringBuilder_Build(&sb);
}
}
}
+64 -56
View File
@@ -1,26 +1,26 @@
module Std::Map {
extern func bux_hash_bytes(ptr: *void, size: uint) -> uint;
extern func bux_hash_string(s: String) -> uint;
extern func bux_hash_bytes(ptr: *void, size: uint) -> uint;
extern func bux_hash_string(s: String) -> uint;
// ---------------------------------------------------------------------------
// Generic Map<K, V> — works with value-type keys (int, float, etc.)
// For String keys, use StringMap below.
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Generic Map<K, V> — works with value-type keys (int, float, etc.)
// For String keys, use StringMap below.
// ---------------------------------------------------------------------------
struct MapEntry<K, V> {
struct MapEntry<K, V> {
key: K,
value: V,
occupied: bool,
}
}
struct Map<K, V> {
struct Map<K, V> {
entries: *MapEntry<K, V>,
cap: uint,
len: uint,
}
}
func Map_New<K, V>(cap: uint) -> Map<K, V> {
func Map_New<K, V>(cap: uint) -> Map<K, V> {
let total: uint = cap * sizeof(MapEntry<K, V>);
let data: *MapEntry<K, V> = bux_alloc(total) as *MapEntry<K, V>;
var i: uint = 0;
@@ -29,9 +29,9 @@ func Map_New<K, V>(cap: uint) -> Map<K, V> {
i = i + 1;
}
return Map<K, V> { entries: data, cap: cap, len: 0 };
}
}
func Map_Set<K, V>(m: *Map<K, V>, key: K, value: V) {
func Map_Set<K, V>(m: *Map<K, V>, key: K, value: V) {
var keyPtr: *K = &key;
let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K));
var idx: uint = hash % m.cap;
@@ -46,9 +46,9 @@ func Map_Set<K, V>(m: *Map<K, V>, key: K, value: V) {
m.entries[idx].value = value;
m.entries[idx].occupied = true;
m.len = m.len + 1;
}
}
func Map_Get<K, V>(m: *Map<K, V>, key: K) -> V {
func Map_Get<K, V>(m: *Map<K, V>, key: K) -> V {
var keyPtr: *K = &key;
let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K));
var idx: uint = hash % m.cap;
@@ -61,9 +61,9 @@ func Map_Get<K, V>(m: *Map<K, V>, key: K) -> V {
// Return zero value for missing key
var zero: V = 0 as V;
return zero;
}
}
func Map_Has<K, V>(m: *Map<K, V>, key: K) -> bool {
func Map_Has<K, V>(m: *Map<K, V>, key: K) -> bool {
var keyPtr: *K = &key;
let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K));
var idx: uint = hash % m.cap;
@@ -74,18 +74,18 @@ func Map_Has<K, V>(m: *Map<K, V>, key: K) -> bool {
idx = (idx + 1) % m.cap;
}
return false;
}
}
func Map_Len<K, V>(m: *Map<K, V>) -> uint {
func Map_Len<K, V>(m: *Map<K, V>) -> uint {
return m.len;
}
}
func Map_IsEmpty<K, V>(m: *Map<K, V>) -> bool {
func Map_IsEmpty<K, V>(m: *Map<K, V>) -> bool {
return m.len == 0;
}
}
/* Remove key if present. Rebuilds the table to keep open-addressing correct. */
func Map_Remove<K, V>(m: *Map<K, V>, key: K) -> bool {
/* Remove key if present. Rebuilds the table to keep open-addressing correct. */
func Map_Remove<K, V>(m: *Map<K, V>, key: K) -> bool {
if !Map_Has<K, V>(m, key) {
return false;
}
@@ -103,46 +103,50 @@ func Map_Remove<K, V>(m: *Map<K, V>, key: K) -> bool {
m.entries = fresh.entries;
m.cap = fresh.cap;
m.len = fresh.len;
// Ownership transferred to `m` — clear `fresh` so auto-Drop does not free twice
fresh.entries = null as *MapEntry<K, V>;
fresh.cap = 0;
fresh.len = 0;
return true;
}
}
func Map_Clear<K, V>(m: *Map<K, V>) {
func Map_Clear<K, V>(m: *Map<K, V>) {
var i: uint = 0;
while i < m.cap {
m.entries[i].occupied = false;
i = i + 1;
}
m.len = 0;
}
}
func Map_Free<K, V>(m: *Map<K, V>) {
func Map_Free<K, V>(m: *Map<K, V>) {
bux_free(m.entries as *void);
m.entries = null as *MapEntry<K, V>;
m.cap = 0;
m.len = 0;
}
}
func Map_Drop<K, V>(m: *Map<K, V>) {
func Map_Drop<K, V>(m: *Map<K, V>) {
Map_Free<K, V>(m);
}
}
// ---------------------------------------------------------------------------
// StringMap<V> — specialized Map for String keys, using strcmp
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// StringMap<V> — specialized Map for String keys, using strcmp
// ---------------------------------------------------------------------------
struct StringMapEntry<V> {
struct StringMapEntry<V> {
key: String,
value: V,
occupied: bool,
}
}
struct StringMap<V> {
struct StringMap<V> {
entries: *StringMapEntry<V>,
cap: uint,
len: uint,
}
}
func StringMap_New<V>(cap: uint) -> StringMap<V> {
func StringMap_New<V>(cap: uint) -> StringMap<V> {
let total: uint = cap * sizeof(StringMapEntry<V>);
let data: *StringMapEntry<V> = bux_alloc(total) as *StringMapEntry<V>;
var i: uint = 0;
@@ -151,9 +155,9 @@ func StringMap_New<V>(cap: uint) -> StringMap<V> {
i = i + 1;
}
return StringMap<V> { entries: data, cap: cap, len: 0 };
}
}
func StringMap_Set<V>(m: *StringMap<V>, key: String, value: V) {
func StringMap_Set<V>(m: *StringMap<V>, key: String, value: V) {
let hash: uint = bux_hash_string(key);
var idx: uint = hash % m.cap;
while m.entries[idx].occupied {
@@ -167,9 +171,9 @@ func StringMap_Set<V>(m: *StringMap<V>, key: String, value: V) {
m.entries[idx].value = value;
m.entries[idx].occupied = true;
m.len = m.len + 1;
}
}
func StringMap_Get<V>(m: *StringMap<V>, key: String) -> V {
func StringMap_Get<V>(m: *StringMap<V>, key: String) -> V {
let hash: uint = bux_hash_string(key);
var idx: uint = hash % m.cap;
while m.entries[idx].occupied {
@@ -180,9 +184,9 @@ func StringMap_Get<V>(m: *StringMap<V>, key: String) -> V {
}
var zero: V = 0 as V;
return zero;
}
}
func StringMap_Has<V>(m: *StringMap<V>, key: String) -> bool {
func StringMap_Has<V>(m: *StringMap<V>, key: String) -> bool {
let hash: uint = bux_hash_string(key);
var idx: uint = hash % m.cap;
while m.entries[idx].occupied {
@@ -192,17 +196,17 @@ func StringMap_Has<V>(m: *StringMap<V>, key: String) -> bool {
idx = (idx + 1) % m.cap;
}
return false;
}
}
func StringMap_Len<V>(m: *StringMap<V>) -> uint {
func StringMap_Len<V>(m: *StringMap<V>) -> uint {
return m.len;
}
}
func StringMap_IsEmpty<V>(m: *StringMap<V>) -> bool {
func StringMap_IsEmpty<V>(m: *StringMap<V>) -> bool {
return m.len == 0;
}
}
func StringMap_Remove<V>(m: *StringMap<V>, key: String) -> bool {
func StringMap_Remove<V>(m: *StringMap<V>, key: String) -> bool {
if !StringMap_Has<V>(m, key) {
return false;
}
@@ -220,23 +224,27 @@ func StringMap_Remove<V>(m: *StringMap<V>, key: String) -> bool {
m.entries = fresh.entries;
m.cap = fresh.cap;
m.len = fresh.len;
// Ownership transferred to `m` — clear `fresh` so auto-Drop does not free twice
fresh.entries = null as *StringMapEntry<V>;
fresh.cap = 0;
fresh.len = 0;
return true;
}
}
func StringMap_Clear<V>(m: *StringMap<V>) {
func StringMap_Clear<V>(m: *StringMap<V>) {
var i: uint = 0;
while i < m.cap {
m.entries[i].occupied = false;
i = i + 1;
}
m.len = 0;
}
}
func StringMap_Free<V>(m: *StringMap<V>) {
func StringMap_Free<V>(m: *StringMap<V>) {
bux_free(m.entries as *void);
m.entries = null as *StringMapEntry<V>;
m.cap = 0;
m.len = 0;
}
}
}
+24 -24
View File
@@ -1,44 +1,44 @@
module Std::Math {
extern func bux_sqrt(x: float64) -> float64;
extern func bux_pow(x: float64, y: float64) -> float64;
extern func bux_abs_i64(x: int64) -> int64;
extern func bux_abs_f64(x: float64) -> float64;
extern func bux_min_i64(a: int64, b: int64) -> int64;
extern func bux_max_i64(a: int64, b: int64) -> int64;
extern func bux_min_f64(a: float64, b: float64) -> float64;
extern func bux_max_f64(a: float64, b: float64) -> float64;
extern func bux_sqrt(x: float64) -> float64;
extern func bux_pow(x: float64, y: float64) -> float64;
extern func bux_abs_i64(x: int64) -> int64;
extern func bux_abs_f64(x: float64) -> float64;
extern func bux_min_i64(a: int64, b: int64) -> int64;
extern func bux_max_i64(a: int64, b: int64) -> int64;
extern func bux_min_f64(a: float64, b: float64) -> float64;
extern func bux_max_f64(a: float64, b: float64) -> float64;
func Sqrt(x: float64) -> float64 {
func Sqrt(x: float64) -> float64 {
return bux_sqrt(x);
}
}
func Pow(x: float64, y: float64) -> float64 {
func Pow(x: float64, y: float64) -> float64 {
return bux_pow(x, y);
}
}
func Abs(n: int64) -> int64 {
func Abs(n: int64) -> int64 {
return bux_abs_i64(n);
}
}
func AbsF(f: float64) -> float64 {
func AbsF(f: float64) -> float64 {
return bux_abs_f64(f);
}
}
func Min(a: int64, b: int64) -> int64 {
func Min(a: int64, b: int64) -> int64 {
return bux_min_i64(a, b);
}
}
func Max(a: int64, b: int64) -> int64 {
func Max(a: int64, b: int64) -> int64 {
return bux_max_i64(a, b);
}
}
func MinF(a: float64, b: float64) -> float64 {
func MinF(a: float64, b: float64) -> float64 {
return bux_min_f64(a, b);
}
}
func MaxF(a: float64, b: float64) -> float64 {
func MaxF(a: float64, b: float64) -> float64 {
return bux_max_f64(a, b);
}
}
}
+14 -14
View File
@@ -1,29 +1,29 @@
module Std::Mem {
extern func bux_alloc(size: uint) -> *void;
extern func bux_realloc(ptr: *void, size: uint) -> *void;
extern func bux_free(ptr: *void);
extern func bux_mem_eq(a: *void, b: *void, size: uint) -> int;
extern func bux_alloc(size: uint) -> *void;
extern func bux_realloc(ptr: *void, size: uint) -> *void;
extern func bux_free(ptr: *void);
extern func bux_mem_eq(a: *void, b: *void, size: uint) -> int;
func Alloc(size: uint) -> *void {
func Alloc(size: uint) -> *void {
return bux_alloc(size);
}
}
func Realloc(ptr: *void, size: uint) -> *void {
func Realloc(ptr: *void, size: uint) -> *void {
return bux_realloc(ptr, size);
}
}
func Free(ptr: *void) {
func Free(ptr: *void) {
bux_free(ptr);
}
}
func MemEq(a: *void, b: *void, size: uint) -> bool {
func MemEq(a: *void, b: *void, size: uint) -> bool {
return bux_mem_eq(a, b, size) != 0;
}
}
func New<T>() -> *T {
func New<T>() -> *T {
let sz: uint = sizeof(T);
return bux_alloc(sz) as *T;
}
}
}
+40 -40
View File
@@ -1,62 +1,62 @@
module Std::Net {
extern func bux_socket_create() -> int;
extern func bux_socket_reuse(fd: int) -> int;
extern func bux_socket_bind(fd: int, addr: String, port: int) -> int;
extern func bux_socket_listen(fd: int, backlog: int) -> int;
extern func bux_socket_accept(fd: int) -> int;
extern func bux_socket_connect(fd: int, addr: String, port: int) -> int;
extern func bux_socket_send(fd: int, data: String, len: int) -> int;
extern func bux_socket_recv(fd: int, maxLen: int) -> String;
extern func bux_socket_close(fd: int) -> int;
extern func bux_socket_error() -> String;
extern func bux_socket_create() -> int;
extern func bux_socket_reuse(fd: int) -> int;
extern func bux_socket_bind(fd: int, addr: String, port: int) -> int;
extern func bux_socket_listen(fd: int, backlog: int) -> int;
extern func bux_socket_accept(fd: int) -> int;
extern func bux_socket_connect(fd: int, addr: String, port: int) -> int;
extern func bux_socket_send(fd: int, data: String, len: int) -> int;
extern func bux_socket_recv(fd: int, maxLen: int) -> String;
extern func bux_socket_close(fd: int) -> int;
extern func bux_socket_error() -> String;
/* Create a TCP socket. Returns -1 on error. */
func Net_Create() -> int {
/* Create a TCP socket. Returns -1 on error. */
func Net_Create() -> int {
return bux_socket_create();
}
}
/* Enable SO_REUSEADDR on a socket. */
func Net_SetReuse(fd: int) -> bool {
/* Enable SO_REUSEADDR on a socket. */
func Net_SetReuse(fd: int) -> bool {
return bux_socket_reuse(fd) == 0;
}
}
/* Bind a socket to an address and port. */
func Net_Bind(fd: int, addr: String, port: int) -> bool {
/* Bind a socket to an address and port. */
func Net_Bind(fd: int, addr: String, port: int) -> bool {
return bux_socket_bind(fd, addr, port) == 0;
}
}
/* Start listening for connections. */
func Net_Listen(fd: int, backlog: int) -> bool {
/* Start listening for connections. */
func Net_Listen(fd: int, backlog: int) -> bool {
return bux_socket_listen(fd, backlog) == 0;
}
}
/* Accept a connection. Returns new fd or -1 on error. */
func Net_Accept(fd: int) -> int {
/* Accept a connection. Returns new fd or -1 on error. */
func Net_Accept(fd: int) -> int {
return bux_socket_accept(fd);
}
}
/* Connect to a remote address and port. */
func Net_Connect(fd: int, addr: String, port: int) -> bool {
/* Connect to a remote address and port. */
func Net_Connect(fd: int, addr: String, port: int) -> bool {
return bux_socket_connect(fd, addr, port) == 0;
}
}
/* Send data. Returns bytes sent or -1 on error. */
func Net_Send(fd: int, data: String) -> int {
/* Send data. Returns bytes sent or -1 on error. */
func Net_Send(fd: int, data: String) -> int {
return bux_socket_send(fd, data, bux_strlen(data) as int);
}
}
/* Receive up to maxLen bytes. Returns empty string on error/EOF. */
func Net_Recv(fd: int, maxLen: int) -> String {
/* Receive up to maxLen bytes. Returns empty string on error/EOF. */
func Net_Recv(fd: int, maxLen: int) -> String {
return bux_socket_recv(fd, maxLen);
}
}
/* Close a socket. */
func Net_Close(fd: int) -> bool {
/* Close a socket. */
func Net_Close(fd: int) -> bool {
return bux_socket_close(fd) == 0;
}
}
/* Get last socket error as a string. */
func Net_LastError() -> String {
/* Get last socket error as a string. */
func Net_LastError() -> String {
return bux_socket_error();
}
}
}
+22 -22
View File
@@ -1,61 +1,61 @@
module Std::Option {
import Std::Io::{PrintLine};
import Std::Io::{PrintLine};
extern func bux_exit(code: int);
extern func bux_exit(code: int);
enum Option {
enum Option {
Some(int),
None,
}
}
func Option_NewSome(value: int) -> Option {
func Option_NewSome(value: int) -> Option {
let o: Option = Option { tag: Option_Some };
o.data.Some_0 = value;
return o;
}
}
func Option_NewNone() -> Option {
func Option_NewNone() -> Option {
return Option { tag: Option_None };
}
}
func Option_IsSome(o: Option) -> bool {
func Option_IsSome(o: Option) -> bool {
return o.tag == Option_Some;
}
}
func Option_IsNone(o: Option) -> bool {
func Option_IsNone(o: Option) -> bool {
return o.tag == Option_None;
}
}
func Option_Unwrap(o: Option) -> int {
func Option_Unwrap(o: Option) -> int {
if o.tag != Option_Some {
PrintLine("panic: unwrap on None");
return 0;
}
return o.data.Some_0;
}
}
func Option_UnwrapOr(o: Option, fallback: int) -> int {
func Option_UnwrapOr(o: Option, fallback: int) -> int {
if o.tag == Option_Some {
return o.data.Some_0;
}
return fallback;
}
}
/* Unwrap Some or panic with a custom message */
func Option_Expect(o: Option, msg: String) -> int {
/* Unwrap Some or panic with a custom message */
func Option_Expect(o: Option, msg: String) -> int {
if o.tag != Option_Some {
PrintLine(msg);
bux_exit(1);
}
return o.data.Some_0;
}
}
/* If o is Some return it, otherwise return other */
func Option_Or(o: Option, other: Option) -> Option {
/* If o is Some return it, otherwise return other */
func Option_Or(o: Option, other: Option) -> Option {
if o.tag == Option_Some {
return o;
}
return other;
}
}
}
+22 -22
View File
@@ -1,40 +1,40 @@
module Std::Os {
extern func bux_argc() -> int;
extern func bux_argv(index: int) -> String;
extern func bux_getenv(name: String) -> String;
extern func bux_setenv(name: String, value: String) -> int;
extern func bux_getcwd() -> String;
extern func bux_chdir(path: String) -> int;
extern func bux_exit(code: int);
extern func bux_argc() -> int;
extern func bux_argv(index: int) -> String;
extern func bux_getenv(name: String) -> String;
extern func bux_setenv(name: String, value: String) -> int;
extern func bux_getcwd() -> String;
extern func bux_chdir(path: String) -> int;
extern func bux_exit(code: int);
func Os_ArgsCount() -> int {
func Os_ArgsCount() -> int {
return bux_argc();
}
}
func Os_Args(index: int) -> String {
func Os_Args(index: int) -> String {
return bux_argv(index);
}
}
func Os_GetEnv(name: String) -> String {
func Os_GetEnv(name: String) -> String {
return bux_getenv(name);
}
}
func Os_SetEnv(name: String, value: String) -> bool {
func Os_SetEnv(name: String, value: String) -> bool {
return bux_setenv(name, value) == 0;
}
}
func Os_GetCwd() -> String {
func Os_GetCwd() -> String {
return bux_getcwd();
}
}
func Os_Chdir(path: String) -> bool {
func Os_Chdir(path: String) -> bool {
return bux_chdir(path) == 0;
}
}
/* Terminate the process with the given exit code */
func Os_Exit(code: int) {
/* Terminate the process with the given exit code */
func Os_Exit(code: int) {
bux_exit(code);
}
}
}
+9 -9
View File
@@ -1,19 +1,19 @@
module Std::Path {
extern func bux_path_join(a: String, b: String) -> String;
extern func bux_path_parent(path: String) -> String;
extern func bux_path_ext(path: String) -> String;
extern func bux_path_join(a: String, b: String) -> String;
extern func bux_path_parent(path: String) -> String;
extern func bux_path_ext(path: String) -> String;
func Path_Join(a: String, b: String) -> String {
func Path_Join(a: String, b: String) -> String {
return bux_path_join(a, b);
}
}
func Path_Parent(path: String) -> String {
func Path_Parent(path: String) -> String {
return bux_path_parent(path);
}
}
func Path_Ext(path: String) -> String {
func Path_Ext(path: String) -> String {
return bux_path_ext(path);
}
}
}
+6 -6
View File
@@ -1,14 +1,14 @@
module Std::Process {
extern func bux_process_run(cmd: String) -> int;
extern func bux_process_output(cmd: String) -> String;
extern func bux_process_run(cmd: String) -> int;
extern func bux_process_output(cmd: String) -> String;
func Process_Run(cmd: String) -> int {
func Process_Run(cmd: String) -> int {
return bux_process_run(cmd);
}
}
func Process_Output(cmd: String) -> String {
func Process_Output(cmd: String) -> String {
return bux_process_output(cmd);
}
}
}
+25 -25
View File
@@ -1,72 +1,72 @@
module Std::Result {
import Std::Io::{PrintLine};
import Std::Io::{PrintLine};
extern func bux_exit(code: int);
extern func bux_exit(code: int);
enum Result {
enum Result {
Ok(int),
Err(String),
}
}
func Result_NewOk(value: int) -> Result {
func Result_NewOk(value: int) -> Result {
let r: Result = Result { tag: Result_Ok };
r.data.Ok_0 = value;
return r;
}
}
func Result_NewErr(msg: String) -> Result {
func Result_NewErr(msg: String) -> Result {
let r: Result = Result { tag: Result_Err };
r.data.Err_0 = msg;
return r;
}
}
func Result_IsOk(r: Result) -> bool {
func Result_IsOk(r: Result) -> bool {
return r.tag == Result_Ok;
}
}
func Result_IsErr(r: Result) -> bool {
func Result_IsErr(r: Result) -> bool {
return r.tag == Result_Err;
}
}
func Result_Unwrap(r: Result) -> int {
func Result_Unwrap(r: Result) -> int {
if r.tag != Result_Ok {
PrintLine("panic: unwrap on Err");
return 0;
}
return r.data.Ok_0;
}
}
func Result_UnwrapOr(r: Result, fallback: int) -> int {
func Result_UnwrapOr(r: Result, fallback: int) -> int {
if r.tag == Result_Ok {
return r.data.Ok_0;
}
return fallback;
}
}
/* Unwrap Ok or panic with a custom message */
func Result_Expect(r: Result, msg: String) -> int {
/* Unwrap Ok or panic with a custom message */
func Result_Expect(r: Result, msg: String) -> int {
if r.tag != Result_Ok {
PrintLine(msg);
bux_exit(1);
}
return r.data.Ok_0;
}
}
/* Extract Err payload (panics if Ok) */
func Result_UnwrapErr(r: Result) -> String {
/* Extract Err payload (panics if Ok) */
func Result_UnwrapErr(r: Result) -> String {
if r.tag != Result_Err {
PrintLine("panic: unwrap_err on Ok");
return "";
}
return r.data.Err_0;
}
}
/* If r is Ok return it, otherwise return other */
func Result_Or(r: Result, other: Result) -> Result {
/* If r is Ok return it, otherwise return other */
func Result_Or(r: Result, other: Result) -> Result {
if r.tag == Result_Ok {
return r;
}
return other;
}
}
}
+31 -27
View File
@@ -1,22 +1,22 @@
module Std::Set {
extern func bux_hash_bytes(ptr: *void, size: uint) -> uint;
extern func bux_mem_eq(a: *void, b: *void, size: uint) -> int;
extern func bux_alloc(size: uint) -> *void;
extern func bux_free(ptr: *void);
extern func bux_hash_bytes(ptr: *void, size: uint) -> uint;
extern func bux_mem_eq(a: *void, b: *void, size: uint) -> int;
extern func bux_alloc(size: uint) -> *void;
extern func bux_free(ptr: *void);
struct SetEntry<T> {
struct SetEntry<T> {
value: T,
occupied: bool,
}
}
struct Set<T> {
struct Set<T> {
entries: *SetEntry<T>,
cap: uint,
len: uint,
}
}
func Set_New<T>(cap: uint) -> Set<T> {
func Set_New<T>(cap: uint) -> Set<T> {
let total: uint = cap * sizeof(SetEntry<T>);
let data: *SetEntry<T> = bux_alloc(total) as *SetEntry<T>;
var i: uint = 0;
@@ -25,9 +25,9 @@ func Set_New<T>(cap: uint) -> Set<T> {
i = i + 1;
}
return Set<T> { entries: data, cap: cap, len: 0 };
}
}
func Set_Add<T>(s: *Set<T>, value: T) {
func Set_Add<T>(s: *Set<T>, value: T) {
var valuePtr: *T = &value;
let hash: uint = bux_hash_bytes(valuePtr as *void, sizeof(T));
var idx: uint = hash % s.cap;
@@ -41,9 +41,9 @@ func Set_Add<T>(s: *Set<T>, value: T) {
s.entries[idx].value = value;
s.entries[idx].occupied = true;
s.len = s.len + 1;
}
}
func Set_Has<T>(s: *Set<T>, value: T) -> bool {
func Set_Has<T>(s: *Set<T>, value: T) -> bool {
var valuePtr: *T = &value;
let hash: uint = bux_hash_bytes(valuePtr as *void, sizeof(T));
var idx: uint = hash % s.cap;
@@ -55,18 +55,18 @@ func Set_Has<T>(s: *Set<T>, value: T) -> bool {
idx = (idx + 1) % s.cap;
}
return false;
}
}
func Set_Len<T>(s: *Set<T>) -> uint {
func Set_Len<T>(s: *Set<T>) -> uint {
return s.len;
}
}
func Set_IsEmpty<T>(s: *Set<T>) -> bool {
func Set_IsEmpty<T>(s: *Set<T>) -> bool {
return s.len == 0;
}
}
/* Remove value if present. Rebuilds the table to keep open-addressing correct. */
func Set_Remove<T>(s: *Set<T>, value: T) -> bool {
/* Remove value if present. Rebuilds the table to keep open-addressing correct. */
func Set_Remove<T>(s: *Set<T>, value: T) -> bool {
if !Set_Has<T>(s, value) {
return false;
}
@@ -86,27 +86,31 @@ func Set_Remove<T>(s: *Set<T>, value: T) -> bool {
s.entries = fresh.entries;
s.cap = fresh.cap;
s.len = fresh.len;
// Ownership transferred to `s` — clear `fresh` so auto-Drop does not free twice
fresh.entries = null as *SetEntry<T>;
fresh.cap = 0;
fresh.len = 0;
return true;
}
}
func Set_Clear<T>(s: *Set<T>) {
func Set_Clear<T>(s: *Set<T>) {
var i: uint = 0;
while i < s.cap {
s.entries[i].occupied = false;
i = i + 1;
}
s.len = 0;
}
}
func Set_Free<T>(s: *Set<T>) {
func Set_Free<T>(s: *Set<T>) {
bux_free(s.entries as *void);
s.entries = null as *SetEntry<T>;
s.cap = 0;
s.len = 0;
}
}
func Set_Drop<T>(s: *Set<T>) {
func Set_Drop<T>(s: *Set<T>) {
Set_Free<T>(s);
}
}
}
+15 -15
View File
@@ -1,39 +1,39 @@
module Std::Slice {
extern func bux_bounds_check(index: uint, len: uint);
extern func bux_bounds_check(index: uint, len: uint);
struct Slice<T> {
struct Slice<T> {
data: *T,
len: uint,
}
}
func Slice_FromArray<T>(arr: *Array<T>) -> Slice<T> {
func Slice_FromArray<T>(arr: *Array<T>) -> Slice<T> {
var s: Slice<T>;
s.data = arr.data;
s.len = arr.len;
return s;
}
}
func Slice_Get<T>(self: *Slice<T>, idx: uint) -> T {
func Slice_Get<T>(self: *Slice<T>, idx: uint) -> T {
bux_bounds_check(idx, self.len);
return self.data[idx];
}
}
func Slice_Set<T>(self: *Slice<T>, idx: uint, value: T) {
func Slice_Set<T>(self: *Slice<T>, idx: uint, value: T) {
bux_bounds_check(idx, self.len);
self.data[idx] = value;
}
}
func Slice_Len<T>(self: *Slice<T>) -> uint {
func Slice_Len<T>(self: *Slice<T>) -> uint {
return self.len;
}
}
func Slice_operator_index_get<T>(self: *Slice<T>, idx: uint) -> T {
func Slice_operator_index_get<T>(self: *Slice<T>, idx: uint) -> T {
return Slice_Get<T>(self, idx);
}
}
func Slice_operator_index_set<T>(self: *Slice<T>, idx: uint, value: T) {
func Slice_operator_index_set<T>(self: *Slice<T>, idx: uint, value: T) {
Slice_Set<T>(self, idx, value);
}
}
}
+130 -121
View File
@@ -1,52 +1,57 @@
module Std::String {
extern func bux_strlen(s: String) -> uint;
extern func bux_strcmp(a: String, b: String) -> int;
extern func bux_strncmp(a: String, b: String, n: uint) -> int;
extern func bux_strcpy(dest: *char8, src: String) -> *char8;
extern func bux_strcat(dest: *char8, src: String) -> *char8;
extern func bux_strncpy(dest: *char8, src: String, n: uint) -> *char8;
extern func bux_strstr(haystack: String, needle: String) -> String;
extern func bux_str_contains(haystack: String, needle: String) -> int;
extern func bux_str_offset(pos: String, base: String) -> uint;
extern func bux_str_is_null(s: String) -> int;
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
extern func bux_str_trim_left(s: String) -> String;
extern func bux_str_trim_right(s: String) -> String;
extern func bux_str_trim(s: String) -> String;
extern func bux_int_to_str(n: int64) -> String;
extern func bux_str_to_int(s: String) -> int64;
extern func bux_sb_new(initial_cap: uint) -> *void;
extern func bux_sb_append(sb: *void, s: String);
extern func bux_sb_append_int(sb: *void, n: int64);
extern func bux_sb_append_float(sb: *void, f: float64);
extern func bux_sb_append_char(sb: *void, c: char8);
extern func bux_sb_build(sb: *void) -> String;
extern func bux_sb_free(sb: *void);
extern func bux_str_split_count(s: String, delim: String) -> uint;
extern func bux_str_split_part(s: String, delim: String, index: uint) -> String;
extern func bux_str_join2(a: String, b: String, sep: String) -> String;
extern func bux_float_to_string(f: float64) -> String;
extern func bux_str_format(pattern: String, a0: String, a1: String, a2: String, a3: String, a4: String, a5: String, a6: String, a7: String) -> String;
extern func bux_strlen(s: String) -> uint;
extern func bux_strcmp(a: String, b: String) -> int;
extern func bux_strncmp(a: String, b: String, n: uint) -> int;
extern func bux_strcpy(dest: *char8, src: String) -> *char8;
extern func bux_strcat(dest: *char8, src: String) -> *char8;
extern func bux_strncpy(dest: *char8, src: String, n: uint) -> *char8;
extern func bux_strstr(haystack: String, needle: String) -> String;
extern func bux_str_contains(haystack: String, needle: String) -> int;
extern func bux_str_offset(pos: String, base: String) -> uint;
extern func bux_str_is_null(s: String) -> int;
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
extern func bux_str_trim_left(s: String) -> String;
extern func bux_str_trim_right(s: String) -> String;
extern func bux_str_trim(s: String) -> String;
extern func bux_int_to_str(n: int64) -> String;
extern func bux_str_to_int(s: String) -> int64;
extern func bux_sb_new(initial_cap: uint) -> *void;
extern func bux_sb_append(sb: *void, s: String);
extern func bux_sb_append_int(sb: *void, n: int64);
extern func bux_sb_append_float(sb: *void, f: float64);
extern func bux_sb_append_char(sb: *void, c: char8);
extern func bux_sb_build(sb: *void) -> String;
extern func bux_sb_free(sb: *void);
extern func bux_str_split_count(s: String, delim: String) -> uint;
extern func bux_str_split_part(s: String, delim: String, index: uint) -> String;
extern func bux_str_join2(a: String, b: String, sep: String) -> String;
extern func bux_float_to_string(f: float64) -> String;
extern func bux_str_format(pattern: String, a0: String, a1: String, a2: String, a3: String, a4: String, a5: String, a6: String, a7: String) -> String;
func String_Len(s: String) -> uint {
/// Byte length of a C string (`strlen`).
func String_Len(s: String) -> uint {
return bux_strlen(s);
}
}
func String_IsEmpty(s: String) -> bool {
/// True if the string has zero length.
func String_IsEmpty(s: String) -> bool {
return bux_strlen(s) == 0;
}
}
func String_IsNull(s: String) -> bool {
/// True if the pointer is null.
func String_IsNull(s: String) -> bool {
return bux_str_is_null(s) != 0;
}
}
func String_Eq(a: String, b: String) -> bool {
/// Lexicographic equality.
func String_Eq(a: String, b: String) -> bool {
return bux_strcmp(a, b) == 0;
}
}
func String_Concat(a: String, b: String) -> String {
/// Allocate and return `a` concatenated with `b`.
func String_Concat(a: String, b: String) -> String {
let len_a: uint = bux_strlen(a);
let len_b: uint = bux_strlen(b);
let total: uint = len_a + len_b + 1;
@@ -54,16 +59,18 @@ func String_Concat(a: String, b: String) -> String {
bux_strcpy(buf, a);
bux_strcat(buf, b);
return buf;
}
}
func String_Copy(s: String) -> String {
/// Heap-copy of `s`.
func String_Copy(s: String) -> String {
let len: uint = bux_strlen(s);
let buf: *char8 = bux_alloc(len + 1) as *char8;
bux_strcpy(buf, s);
return buf;
}
}
func String_StartsWith(s: String, prefix: String) -> bool {
/// True if `s` begins with `prefix`.
func String_StartsWith(s: String, prefix: String) -> bool {
let s_len: uint = bux_strlen(s);
let p_len: uint = bux_strlen(prefix);
if p_len > s_len {
@@ -71,9 +78,10 @@ func String_StartsWith(s: String, prefix: String) -> bool {
}
let r: int = bux_strncmp(s, prefix, p_len);
return r == 0;
}
}
func String_EndsWith(s: String, suffix: String) -> bool {
/// True if `s` ends with `suffix`.
func String_EndsWith(s: String, suffix: String) -> bool {
let s_len: uint = bux_strlen(s);
let suf_len: uint = bux_strlen(suffix);
if suf_len > s_len {
@@ -83,76 +91,77 @@ func String_EndsWith(s: String, suffix: String) -> bool {
let tail: String = bux_str_slice(s, start, suf_len);
let eq: bool = bux_strcmp(tail, suffix) == 0;
return eq;
}
}
func String_Contains(s: String, substr: String) -> bool {
/// True if `substr` occurs anywhere in `s`.
func String_Contains(s: String, substr: String) -> bool {
let r: int = bux_str_contains(s, substr);
return r != 0;
}
}
func String_Slice(s: String, start: uint, len: uint) -> String {
func String_Slice(s: String, start: uint, len: uint) -> String {
return bux_str_slice(s, start, len);
}
}
func String_Trim(s: String) -> String {
func String_Trim(s: String) -> String {
return bux_str_trim(s);
}
}
func String_TrimLeft(s: String) -> String {
func String_TrimLeft(s: String) -> String {
return bux_str_trim_left(s);
}
}
func String_TrimRight(s: String) -> String {
func String_TrimRight(s: String) -> String {
return bux_str_trim_right(s);
}
}
func String_FromInt(n: int64) -> String {
func String_FromInt(n: int64) -> String {
return bux_int_to_str(n);
}
}
func String_ToInt(s: String) -> int64 {
func String_ToInt(s: String) -> int64 {
return bux_str_to_int(s);
}
}
// String Builder — efficient string construction
struct StringBuilder {
// String Builder — efficient string construction
struct StringBuilder {
handle: *void,
}
}
func StringBuilder_New() -> StringBuilder {
func StringBuilder_New() -> StringBuilder {
return StringBuilder { handle: bux_sb_new(64) };
}
}
func StringBuilder_NewCap(cap: uint) -> StringBuilder {
func StringBuilder_NewCap(cap: uint) -> StringBuilder {
return StringBuilder { handle: bux_sb_new(cap) };
}
}
func StringBuilder_Append(sb: *StringBuilder, s: String) {
func StringBuilder_Append(sb: *StringBuilder, s: String) {
bux_sb_append(sb.handle, s);
}
}
func StringBuilder_AppendInt(sb: *StringBuilder, n: int64) {
func StringBuilder_AppendInt(sb: *StringBuilder, n: int64) {
bux_sb_append_int(sb.handle, n);
}
}
func StringBuilder_AppendFloat(sb: *StringBuilder, f: float64) {
func StringBuilder_AppendFloat(sb: *StringBuilder, f: float64) {
bux_sb_append_float(sb.handle, f);
}
}
func StringBuilder_AppendChar(sb: *StringBuilder, c: char8) {
func StringBuilder_AppendChar(sb: *StringBuilder, c: char8) {
bux_sb_append_char(sb.handle, c);
}
}
func StringBuilder_Build(sb: *StringBuilder) -> String {
func StringBuilder_Build(sb: *StringBuilder) -> String {
return bux_sb_build(sb.handle);
}
}
func StringBuilder_Free(sb: *StringBuilder) {
func StringBuilder_Free(sb: *StringBuilder) {
bux_sb_free(sb.handle);
}
}
/* True if empty or only whitespace (space, tab, CR, LF) */
func String_IsBlank(s: String) -> bool {
/// True if empty or only whitespace (space, tab, CR, LF).
func String_IsBlank(s: String) -> bool {
let n: uint = bux_strlen(s);
var i: uint = 0;
while i < n {
@@ -163,10 +172,10 @@ func String_IsBlank(s: String) -> bool {
i = i + 1;
}
return true;
}
}
/* Repeat s, count times (count==0 → empty string) */
func String_Repeat(s: String, count: uint) -> String {
/// Repeat `s`, `count` times (`count == 0` → empty string).
func String_Repeat(s: String, count: uint) -> String {
if count == 0 {
return "";
}
@@ -182,42 +191,42 @@ func String_Repeat(s: String, count: uint) -> String {
let result: String = StringBuilder_Build(&sb);
StringBuilder_Free(&sb);
return result;
}
}
// ---------------------------------------------------------------------------
// String split/join
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// String split/join
// ---------------------------------------------------------------------------
func String_SplitCount(s: String, delim: String) -> uint {
func String_SplitCount(s: String, delim: String) -> uint {
return bux_str_split_count(s, delim);
}
}
func String_SplitPart(s: String, delim: String, index: uint) -> String {
func String_SplitPart(s: String, delim: String, index: uint) -> String {
return bux_str_split_part(s, delim, index);
}
}
func String_Join2(a: String, b: String, sep: String) -> String {
func String_Join2(a: String, b: String, sep: String) -> String {
return bux_str_join2(a, b, sep);
}
}
// ---------------------------------------------------------------------------
// String find/replace/format
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// String find/replace/format
// ---------------------------------------------------------------------------
// String_Chars — return single-character string at index (for iteration)
func String_Chars(s: String, index: uint) -> String {
// String_Chars — return single-character string at index (for iteration)
func String_Chars(s: String, index: uint) -> String {
return bux_str_slice(s, index, 1);
}
}
func String_Find(haystack: String, needle: String) -> String {
func String_Find(haystack: String, needle: String) -> String {
return bux_strstr(haystack, needle);
}
}
func String_Offset(pos: String, base: String) -> uint {
func String_Offset(pos: String, base: String) -> uint {
return bux_str_offset(pos, base);
}
}
func String_Replace(s: String, old: String, new: String) -> String {
func String_Replace(s: String, old: String, new: String) -> String {
let pos: String = bux_strstr(s, old);
if String_IsNull(pos) {
return s;
@@ -229,11 +238,11 @@ func String_Replace(s: String, old: String, new: String) -> String {
let temp: String = String_Concat(prefix, new);
let result: String = String_Concat(temp, suffix);
return result;
}
}
/* Replace every non-overlapping occurrence of old with new.
Empty old is a no-op (returns s unchanged). Safe if new contains old. */
func String_ReplaceAll(s: String, old: String, new: String) -> String {
/// Replace every non-overlapping occurrence of `old` with `new`.
/// Empty `old` is a no-op (returns `s` unchanged). Safe if `new` contains `old`.
func String_ReplaceAll(s: String, old: String, new: String) -> String {
let oldLen: uint = bux_strlen(old);
if oldLen == 0 {
return s;
@@ -256,33 +265,33 @@ func String_ReplaceAll(s: String, old: String, new: String) -> String {
let result: String = StringBuilder_Build(&sb);
StringBuilder_Free(&sb);
return result;
}
}
extern func bux_str_to_float(s: String) -> float64;
extern func bux_str_to_float(s: String) -> float64;
func String_ToFloat(s: String) -> float64 {
func String_ToFloat(s: String) -> float64 {
return bux_str_to_float(s);
}
}
func String_FromBool(b: bool) -> String {
func String_FromBool(b: bool) -> String {
if b { return "true"; }
return "false";
}
}
func String_FromFloat(f: float64) -> String {
func String_FromFloat(f: float64) -> String {
return bux_float_to_string(f);
}
}
func String_Format1(pattern: String, a0: String) -> String {
func String_Format1(pattern: String, a0: String) -> String {
return bux_str_format(pattern, a0, "", "", "", "", "", "", "");
}
}
func String_Format2(pattern: String, a0: String, a1: String) -> String {
func String_Format2(pattern: String, a0: String, a1: String) -> String {
return bux_str_format(pattern, a0, a1, "", "", "", "", "", "");
}
}
func String_Format3(pattern: String, a0: String, a1: String, a2: String) -> String {
func String_Format3(pattern: String, a0: String, a1: String, a2: String) -> String {
return bux_str_format(pattern, a0, a1, a2, "", "", "", "", "");
}
}
}
+31 -31
View File
@@ -1,58 +1,58 @@
module Std::Sync {
extern func bux_mutex_new() -> *void;
extern func bux_mutex_lock(handle: *void);
extern func bux_mutex_unlock(handle: *void);
extern func bux_mutex_free(handle: *void);
extern func bux_mutex_new() -> *void;
extern func bux_mutex_lock(handle: *void);
extern func bux_mutex_unlock(handle: *void);
extern func bux_mutex_free(handle: *void);
extern func bux_rwlock_new() -> *void;
extern func bux_rwlock_rdlock(handle: *void);
extern func bux_rwlock_wrlock(handle: *void);
extern func bux_rwlock_unlock(handle: *void);
extern func bux_rwlock_free(handle: *void);
extern func bux_rwlock_new() -> *void;
extern func bux_rwlock_rdlock(handle: *void);
extern func bux_rwlock_wrlock(handle: *void);
extern func bux_rwlock_unlock(handle: *void);
extern func bux_rwlock_free(handle: *void);
struct Mutex {
struct Mutex {
handle: *void;
}
}
struct RwLock {
struct RwLock {
handle: *void;
}
}
func Mutex_New() -> Mutex {
func Mutex_New() -> Mutex {
return Mutex { handle: bux_mutex_new() };
}
}
func Mutex_Lock(m: *Mutex) {
func Mutex_Lock(m: *Mutex) {
bux_mutex_lock(m.handle);
}
}
func Mutex_Unlock(m: *Mutex) {
func Mutex_Unlock(m: *Mutex) {
bux_mutex_unlock(m.handle);
}
}
func Mutex_Free(m: *Mutex) {
func Mutex_Free(m: *Mutex) {
bux_mutex_free(m.handle);
}
}
func RwLock_New() -> RwLock {
func RwLock_New() -> RwLock {
return RwLock { handle: bux_rwlock_new() };
}
}
func RwLock_ReadLock(rw: *RwLock) {
func RwLock_ReadLock(rw: *RwLock) {
bux_rwlock_rdlock(rw.handle);
}
}
func RwLock_WriteLock(rw: *RwLock) {
func RwLock_WriteLock(rw: *RwLock) {
bux_rwlock_wrlock(rw.handle);
}
}
func RwLock_Unlock(rw: *RwLock) {
func RwLock_Unlock(rw: *RwLock) {
bux_rwlock_unlock(rw.handle);
}
}
func RwLock_Free(rw: *RwLock) {
func RwLock_Free(rw: *RwLock) {
bux_rwlock_free(rw.handle);
}
}
}
+23 -23
View File
@@ -1,43 +1,43 @@
module Std::Task {
extern func bux_task_init(num_workers: int);
extern func bux_task_spawn(fn: *void, arg: *void) -> *void;
extern func bux_task_join(handle: *void);
extern func bux_task_sleep(ms: int64);
extern func bux_task_yield();
extern func bux_task_current_id() -> int;
extern func bux_task_shutdown();
extern func bux_task_init(num_workers: int);
extern func bux_task_spawn(fn: *void, arg: *void) -> *void;
extern func bux_task_join(handle: *void);
extern func bux_task_sleep(ms: int64);
extern func bux_task_yield();
extern func bux_task_current_id() -> int;
extern func bux_task_shutdown();
struct TaskHandle {
struct TaskHandle {
handle: *void;
}
}
func Task_Init(num_workers: int) {
func Task_Init(num_workers: int) {
bux_task_init(num_workers);
}
}
func Task_Spawn(fn: *void, arg: *void) -> TaskHandle {
func Task_Spawn(fn: *void, arg: *void) -> TaskHandle {
return TaskHandle { handle: bux_task_spawn(fn, arg) };
}
}
func Task_Wait(t: TaskHandle) {
func Task_Wait(t: TaskHandle) {
bux_task_join(t.handle);
}
}
func Task_Sleep(ms: int64) {
func Task_Sleep(ms: int64) {
bux_task_sleep(ms);
}
}
func Task_Yield() {
func Task_Yield() {
bux_task_yield();
}
}
func Task_CurrentId() -> int {
func Task_CurrentId() -> int {
return bux_task_current_id();
}
}
func Task_Shutdown() {
func Task_Shutdown() {
bux_task_shutdown();
}
}
}
+34 -24
View File
@@ -1,19 +1,22 @@
module Std::Test {
import Std::Io::{PrintLine, PrintInt};
import Std::String::{String_Eq};
import Std::Io::{PrintLine, PrintInt};
import Std::String::{String_Eq};
extern func bux_exit(code: int);
extern func bux_assert(cond: int, file: String, line: int, expr: String);
extern func bux_exit(code: int);
extern func bux_assert(cond: int, file: String, line: int, expr: String);
func Test_Exit(code: int) {
/// Exit the process with `code` (for test runners).
func Test_Exit(code: int) {
bux_exit(code);
}
}
func Test_Assert(cond: bool) {
/// Assert `cond` is true; abort on failure.
func Test_Assert(cond: bool) {
bux_assert(cond as int, "", 0, "");
}
}
func Test_AssertEqInt(a: int, b: int) {
/// Assert two ints are equal; print both values and exit 1 on mismatch.
func Test_AssertEqInt(a: int, b: int) {
if a != b {
PrintLine("ASSERT_EQ_INT FAILED:");
PrintInt(a);
@@ -21,17 +24,19 @@ func Test_AssertEqInt(a: int, b: int) {
PrintInt(b);
bux_exit(1);
}
}
}
func Test_AssertNeqInt(a: int, b: int) {
/// Assert two ints differ.
func Test_AssertNeqInt(a: int, b: int) {
if a == b {
PrintLine("ASSERT_NEQ_INT FAILED: both are");
PrintInt(a);
bux_exit(1);
}
}
}
func Test_AssertEqString(a: String, b: String) {
/// Assert two strings are equal (`String_Eq`).
func Test_AssertEqString(a: String, b: String) {
if !String_Eq(a, b) {
PrintLine("ASSERT_EQ_STRING FAILED:");
PrintLine(a);
@@ -39,38 +44,43 @@ func Test_AssertEqString(a: String, b: String) {
PrintLine(b);
bux_exit(1);
}
}
}
func Test_AssertEqBool(a: bool, b: bool) {
/// Assert two bools are equal.
func Test_AssertEqBool(a: bool, b: bool) {
if a != b {
PrintLine("ASSERT_EQ_BOOL FAILED");
bux_exit(1);
}
}
}
func Test_AssertTrue(cond: bool) {
/// Assert `cond` is true.
func Test_AssertTrue(cond: bool) {
if !cond {
PrintLine("ASSERT_TRUE FAILED");
bux_exit(1);
}
}
}
func Test_AssertFalse(cond: bool) {
/// Assert `cond` is false.
func Test_AssertFalse(cond: bool) {
if cond {
PrintLine("ASSERT_FALSE FAILED");
bux_exit(1);
}
}
}
func Test_Fail(msg: String) {
/// Fail the test with a message and exit 1.
func Test_Fail(msg: String) {
PrintLine("FAIL:");
PrintLine(msg);
bux_exit(1);
}
}
func Test_Pass(msg: String) {
/// Print a PASS line (for human-readable runners / goldens).
func Test_Pass(msg: String) {
PrintLine("PASS:");
PrintLine(msg);
}
}
}
+9 -9
View File
@@ -1,19 +1,19 @@
module Std::Time {
extern func bux_time_ms() -> int64;
extern func bux_time_us() -> int64;
extern func bux_sleep_ms(ms: int64);
extern func bux_time_ms() -> int64;
extern func bux_time_us() -> int64;
extern func bux_sleep_ms(ms: int64);
func Time_NowMs() -> int64 {
func Time_NowMs() -> int64 {
return bux_time_ms();
}
}
func Time_NowUs() -> int64 {
func Time_NowUs() -> int64 {
return bux_time_us();
}
}
func Time_SleepMs(ms: int64) {
func Time_SleepMs(ms: int64) {
bux_sleep_ms(ms);
}
}
}
+31 -31
View File
@@ -3,65 +3,65 @@
// =============================================================================
module Std::Crypto::Aes {
import Std::Mem::{Alloc, Free};
import Std::String::{String_Len};
import Std::Mem::{Alloc, Free};
import Std::String::{String_Len};
extern func bux_random_bytes(buf: *void, len: int) -> int;
extern func bux_aes_256_cbc_encrypt(plain: String, plainlen: int, key: String, iv: String, outlen: *int) -> String;
extern func bux_aes_256_cbc_decrypt(cipher: String, cipherlen: int, key: String, iv: String, outlen: *int) -> String;
extern func bux_aes_256_gcm_encrypt(plain: String, plainlen: int, key: String, iv: String, tag: *void, outlen: *int) -> String;
extern func bux_aes_256_gcm_decrypt(cipher: String, cipherlen: int, key: String, iv: String, tag: String, outlen: *int) -> String;
extern func bux_random_bytes(buf: *void, len: int) -> int;
extern func bux_aes_256_cbc_encrypt(plain: String, plainlen: int, key: String, iv: String, outlen: *int) -> String;
extern func bux_aes_256_cbc_decrypt(cipher: String, cipherlen: int, key: String, iv: String, outlen: *int) -> String;
extern func bux_aes_256_gcm_encrypt(plain: String, plainlen: int, key: String, iv: String, tag: *void, outlen: *int) -> String;
extern func bux_aes_256_gcm_decrypt(cipher: String, cipherlen: int, key: String, iv: String, tag: String, outlen: *int) -> String;
// --- AES-256-CBC ---
// --- AES-256-CBC ---
const AES_KEY_SIZE: int = 32; // 256 bits
const AES_IV_SIZE: int = 16; // 128 bits
const AES_GCM_TAG_SIZE: int = 16;
const AES_KEY_SIZE: int = 32; // 256 bits
const AES_IV_SIZE: int = 16; // 128 bits
const AES_GCM_TAG_SIZE: int = 16;
// Generate a random 256-bit AES key (returns raw 32 bytes)
func Aes_GenerateKey() -> String {
// Generate a random 256-bit AES key (returns raw 32 bytes)
func Aes_GenerateKey() -> String {
let buf: *void = Alloc(AES_KEY_SIZE as uint);
if bux_random_bytes(buf, AES_KEY_SIZE) != 1 {
Free(buf);
return "";
}
return buf as String;
}
}
// Generate a random 128-bit IV (returns raw 16 bytes)
func Aes_GenerateIV() -> String {
// Generate a random 128-bit IV (returns raw 16 bytes)
func Aes_GenerateIV() -> String {
let buf: *void = Alloc(AES_IV_SIZE as uint);
if bux_random_bytes(buf, AES_IV_SIZE) != 1 {
Free(buf);
return "";
}
return buf as String;
}
}
// AES-256-CBC encrypt. plain and key are binary strings, iv is 16 bytes.
// Returns ciphertext (may be longer than plain due to PKCS#7 padding).
func Aes_CbcEncrypt(plain: String, key: String, iv: String) -> String {
// AES-256-CBC encrypt. plain and key are binary strings, iv is 16 bytes.
// Returns ciphertext (may be longer than plain due to PKCS#7 padding).
func Aes_CbcEncrypt(plain: String, key: String, iv: String) -> String {
let outlen: int = 0;
return bux_aes_256_cbc_encrypt(plain, String_Len(plain) as int, key, iv, &outlen);
}
}
// AES-256-CBC decrypt. Returns plaintext.
func Aes_CbcDecrypt(cipher: String, key: String, iv: String) -> String {
// AES-256-CBC decrypt. Returns plaintext.
func Aes_CbcDecrypt(cipher: String, key: String, iv: String) -> String {
let outlen: int = 0;
return bux_aes_256_cbc_decrypt(cipher, String_Len(cipher) as int, key, iv, &outlen);
}
}
// --- AES-256-GCM (Authenticated Encryption) ---
// --- AES-256-GCM (Authenticated Encryption) ---
// AES-256-GCM encrypt. Returns ciphertext. tag receives 16-byte authentication tag.
func Aes_GcmEncrypt(plain: String, key: String, iv: String, tag: *void) -> String {
// AES-256-GCM encrypt. Returns ciphertext. tag receives 16-byte authentication tag.
func Aes_GcmEncrypt(plain: String, key: String, iv: String, tag: *void) -> String {
let outlen: int = 0;
return bux_aes_256_gcm_encrypt(plain, String_Len(plain) as int, key, iv, tag, &outlen);
}
}
// AES-256-GCM decrypt. Returns plaintext. tag must be the 16-byte auth tag from encryption.
func Aes_GcmDecrypt(cipher: String, key: String, iv: String, tag: String) -> String {
// AES-256-GCM decrypt. Returns plaintext. tag must be the 16-byte auth tag from encryption.
func Aes_GcmDecrypt(cipher: String, key: String, iv: String, tag: String) -> String {
let outlen: int = 0;
return bux_aes_256_gcm_decrypt(cipher, String_Len(cipher) as int, key, iv, tag, &outlen);
}
}
}
+15 -15
View File
@@ -3,32 +3,32 @@
// =============================================================================
module Std::Crypto::Base64 {
import Std::String::{String_Len};
import Std::String::{String_Len};
extern func bux_base64_encode(data: String, len: int) -> String;
extern func bux_base64_decode(data: String, len: int, outlen: *int) -> String;
extern func bux_base64url_encode(data: String, len: int) -> String;
extern func bux_base64url_decode(data: String, len: int, outlen: *int) -> String;
extern func bux_base64_encode(data: String, len: int) -> String;
extern func bux_base64_decode(data: String, len: int, outlen: *int) -> String;
extern func bux_base64url_encode(data: String, len: int) -> String;
extern func bux_base64url_decode(data: String, len: int, outlen: *int) -> String;
// --- Standard Base64 ---
// --- Standard Base64 ---
func Base64_Encode(s: String) -> String {
func Base64_Encode(s: String) -> String {
return bux_base64_encode(s, String_Len(s) as int);
}
}
func Base64_Decode(s: String) -> String {
func Base64_Decode(s: String) -> String {
let outlen: int = 0;
return bux_base64_decode(s, String_Len(s) as int, &outlen);
}
}
// --- Base64URL (RFC 4648 §5, uses - and _ instead of + and /, no padding) ---
// --- Base64URL (RFC 4648 §5, uses - and _ instead of + and /, no padding) ---
func Base64URL_Encode(s: String) -> String {
func Base64URL_Encode(s: String) -> String {
return bux_base64url_encode(s, String_Len(s) as int);
}
}
func Base64URL_Decode(s: String) -> String {
func Base64URL_Decode(s: String) -> String {
let outlen: int = 0;
return bux_base64url_decode(s, String_Len(s) as int, &outlen);
}
}
}
+25 -25
View File
@@ -3,56 +3,56 @@
// =============================================================================
module Std::Crypto::Ecdsa {
import Std::Mem::{Alloc, Free};
import Std::String::{String_Len};
import Std::Mem::{Alloc, Free};
import Std::String::{String_Len};
// Extern declarations for the runtime C implementations
extern func bux_ecdsa_sign_p256(key: String, keylen: int, data: String, datalen: int, outlen: *int) -> String;
extern func bux_ecdsa_verify_p256(key: String, keylen: int, data: String, datalen: int, sig: String, siglen: int) -> int;
extern func bux_ecdsa_sign_p384(key: String, keylen: int, data: String, datalen: int, outlen: *int) -> String;
extern func bux_ecdsa_verify_p384(key: String, keylen: int, data: String, datalen: int, sig: String, siglen: int) -> int;
extern func bux_base64_encode(data: String, len: int) -> String;
extern func bux_base64_decode(data: String, len: int, outlen: *int) -> String;
// Extern declarations for the runtime C implementations
extern func bux_ecdsa_sign_p256(key: String, keylen: int, data: String, datalen: int, outlen: *int) -> String;
extern func bux_ecdsa_verify_p256(key: String, keylen: int, data: String, datalen: int, sig: String, siglen: int) -> int;
extern func bux_ecdsa_sign_p384(key: String, keylen: int, data: String, datalen: int, outlen: *int) -> String;
extern func bux_ecdsa_verify_p384(key: String, keylen: int, data: String, datalen: int, sig: String, siglen: int) -> int;
extern func bux_base64_encode(data: String, len: int) -> String;
extern func bux_base64_decode(data: String, len: int, outlen: *int) -> String;
func Ecdsa_SignP256(pemPrivateKey: String, data: String) -> String {
func Ecdsa_SignP256(pemPrivateKey: String, data: String) -> String {
let siglen: int = 0;
return bux_ecdsa_sign_p256(pemPrivateKey, String_Len(pemPrivateKey) as int, data, String_Len(data) as int, &siglen);
}
}
func Ecdsa_SignP256Base64(pemPrivateKey: String, data: String) -> String {
func Ecdsa_SignP256Base64(pemPrivateKey: String, data: String) -> String {
let raw: String = Ecdsa_SignP256(pemPrivateKey, data);
return bux_base64_encode(raw, String_Len(raw) as int);
}
}
func Ecdsa_VerifyP256(pemPublicKey: String, data: String, signature: String) -> bool {
func Ecdsa_VerifyP256(pemPublicKey: String, data: String, signature: String) -> bool {
let r: int = bux_ecdsa_verify_p256(pemPublicKey, String_Len(pemPublicKey) as int, data, String_Len(data) as int, signature, String_Len(signature) as int);
return r == 1;
}
}
func Ecdsa_VerifyP256Base64(pemPublicKey: String, data: String, signatureB64: String) -> bool {
func Ecdsa_VerifyP256Base64(pemPublicKey: String, data: String, signatureB64: String) -> bool {
let outlen: int = 0;
let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
return Ecdsa_VerifyP256(pemPublicKey, data, sig);
}
}
func Ecdsa_SignP384(pemPrivateKey: String, data: String) -> String {
func Ecdsa_SignP384(pemPrivateKey: String, data: String) -> String {
let siglen: int = 0;
return bux_ecdsa_sign_p384(pemPrivateKey, String_Len(pemPrivateKey) as int, data, String_Len(data) as int, &siglen);
}
}
func Ecdsa_SignP384Base64(pemPrivateKey: String, data: String) -> String {
func Ecdsa_SignP384Base64(pemPrivateKey: String, data: String) -> String {
let raw: String = Ecdsa_SignP384(pemPrivateKey, data);
return bux_base64_encode(raw, String_Len(raw) as int);
}
}
func Ecdsa_VerifyP384(pemPublicKey: String, data: String, signature: String) -> bool {
func Ecdsa_VerifyP384(pemPublicKey: String, data: String, signature: String) -> bool {
let r: int = bux_ecdsa_verify_p384(pemPublicKey, String_Len(pemPublicKey) as int, data, String_Len(data) as int, signature, String_Len(signature) as int);
return r == 1;
}
}
func Ecdsa_VerifyP384Base64(pemPublicKey: String, data: String, signatureB64: String) -> bool {
func Ecdsa_VerifyP384Base64(pemPublicKey: String, data: String, signatureB64: String) -> bool {
let outlen: int = 0;
let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
return Ecdsa_VerifyP384(pemPublicKey, data, sig);
}
}
}
+32 -32
View File
@@ -3,30 +3,30 @@
// =============================================================================
module Std::Crypto::Ed25519 {
import Std::Mem::{Alloc, Free};
import Std::String::{String_Len, String_Concat};
import Std::Mem::{Alloc, Free};
import Std::String::{String_Len, String_Concat};
extern func bux_ed25519_keypair(pubKey: *void, privKey: *void) -> int;
extern func bux_ed25519_sign(privKey: String, data: String, datalen: int, sig: *void) -> int;
extern func bux_ed25519_verify(pubKey: String, sig: String, data: String, datalen: int) -> int;
extern func bux_base64_encode(data: String, len: int) -> String;
extern func bux_base64_decode(data: String, len: int, outlen: *int) -> String;
extern func bux_ed25519_keypair(pubKey: *void, privKey: *void) -> int;
extern func bux_ed25519_sign(privKey: String, data: String, datalen: int, sig: *void) -> int;
extern func bux_ed25519_verify(pubKey: String, sig: String, data: String, datalen: int) -> int;
extern func bux_base64_encode(data: String, len: int) -> String;
extern func bux_base64_decode(data: String, len: int, outlen: *int) -> String;
const ED25519_PUBKEY_SIZE: int = 32;
const ED25519_PRIVKEY_SIZE: int = 32;
const ED25519_SIG_SIZE: int = 64;
const ED25519_PUBKEY_SIZE: int = 32;
const ED25519_PRIVKEY_SIZE: int = 32;
const ED25519_SIG_SIZE: int = 64;
// --- Key Generation ---
// --- Key Generation ---
// Ed25519_Keypair: generates a new keypair.
// Returns true on success. pubKey and privKey receive 32-byte raw keys.
func Ed25519_Keypair(pubKey: *void, privKey: *void) -> bool {
// Ed25519_Keypair: generates a new keypair.
// Returns true on success. pubKey and privKey receive 32-byte raw keys.
func Ed25519_Keypair(pubKey: *void, privKey: *void) -> bool {
let r: int = bux_ed25519_keypair(pubKey, privKey);
return r == 1;
}
}
// Convenience: generate and return base64-encoded keypair
func Ed25519_KeypairBase64() -> String {
// Convenience: generate and return base64-encoded keypair
func Ed25519_KeypairBase64() -> String {
let pubBuf: *void = Alloc(ED25519_PUBKEY_SIZE as uint);
let priv: *void = Alloc(ED25519_PRIVKEY_SIZE as uint);
if bux_ed25519_keypair(pubBuf, priv) != 1 {
@@ -41,40 +41,40 @@ func Ed25519_KeypairBase64() -> String {
Free(priv);
let pair: String = String_Concat(pubB64, ":");
return String_Concat(pair, privB64);
}
}
// --- Sign ---
// --- Sign ---
// Ed25519_Sign: sign data with 32-byte raw private key. Returns 64-byte raw signature.
func Ed25519_Sign(privKey: String, data: String) -> String {
// Ed25519_Sign: sign data with 32-byte raw private key. Returns 64-byte raw signature.
func Ed25519_Sign(privKey: String, data: String) -> String {
let sig: *void = Alloc(ED25519_SIG_SIZE as uint);
if bux_ed25519_sign(privKey, data, String_Len(data) as int, sig) != 1 {
Free(sig);
return "";
}
return sig as String;
}
}
// Convenience: sign and return base64-encoded signature
func Ed25519_SignBase64(privKey: String, data: String) -> String {
// Convenience: sign and return base64-encoded signature
func Ed25519_SignBase64(privKey: String, data: String) -> String {
let sig: String = Ed25519_Sign(privKey, data);
if String_Len(sig) == 0 { return ""; }
return bux_base64_encode(sig, ED25519_SIG_SIZE);
}
}
// --- Verify ---
// --- Verify ---
// Ed25519_Verify: verify a 64-byte raw signature against data with 32-byte public key.
func Ed25519_Verify(pubKey: String, signature: String, data: String) -> bool {
// Ed25519_Verify: verify a 64-byte raw signature against data with 32-byte public key.
func Ed25519_Verify(pubKey: String, signature: String, data: String) -> bool {
let r: int = bux_ed25519_verify(pubKey, signature, data, String_Len(data) as int);
return r == 1;
}
}
// Convenience: verify a base64-encoded signature
func Ed25519_VerifyBase64(pubKey: String, signatureB64: String, data: String) -> bool {
// Convenience: verify a base64-encoded signature
func Ed25519_VerifyBase64(pubKey: String, signatureB64: String, data: String) -> bool {
let outlen: int = 0;
let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
if outlen != ED25519_SIG_SIZE { return false; }
return Ed25519_Verify(pubKey, sig, data);
}
}
}
+28 -28
View File
@@ -3,71 +3,71 @@
// =============================================================================
module Std::Crypto::Hash {
import Std::Mem::{Alloc, Free};
import Std::String::{String_Len};
import Std::Mem::{Alloc, Free};
import Std::String::{String_Len};
extern func bux_sha1(data: String, len: int, out: *void);
extern func bux_sha256(data: String, len: int, out: *void);
extern func bux_sha384(data: String, len: int, out: *void);
extern func bux_sha512(data: String, len: int, out: *void);
extern func bux_bytes_to_hex(data: *void, len: int) -> String;
extern func bux_sha1(data: String, len: int, out: *void);
extern func bux_sha256(data: String, len: int, out: *void);
extern func bux_sha384(data: String, len: int, out: *void);
extern func bux_sha512(data: String, len: int, out: *void);
extern func bux_bytes_to_hex(data: *void, len: int) -> String;
// --- Convenience wrappers: hex output ---
// --- Convenience wrappers: hex output ---
func Hash_Sha1(data: String) -> String {
func Hash_Sha1(data: String) -> String {
let len: int = String_Len(data) as int;
let buf: *void = Alloc(20);
bux_sha1(data, len, buf);
let result: String = bux_bytes_to_hex(buf, 20);
Free(buf);
return result;
}
}
func Hash_Sha256(data: String) -> String {
func Hash_Sha256(data: String) -> String {
let len: int = String_Len(data) as int;
let buf: *void = Alloc(32);
bux_sha256(data, len, buf);
let result: String = bux_bytes_to_hex(buf, 32);
Free(buf);
return result;
}
}
func Hash_Sha384(data: String) -> String {
func Hash_Sha384(data: String) -> String {
let len: int = String_Len(data) as int;
let buf: *void = Alloc(48);
bux_sha384(data, len, buf);
let result: String = bux_bytes_to_hex(buf, 48);
Free(buf);
return result;
}
}
func Hash_Sha512(data: String) -> String {
func Hash_Sha512(data: String) -> String {
let len: int = String_Len(data) as int;
let buf: *void = Alloc(64);
bux_sha512(data, len, buf);
let result: String = bux_bytes_to_hex(buf, 64);
Free(buf);
return result;
}
}
// --- Raw binary output (caller must Alloc/Free) ---
// --- Raw binary output (caller must Alloc/Free) ---
func Hash_Sha256Raw(data: String, out: *void) {
func Hash_Sha256Raw(data: String, out: *void) {
bux_sha256(data, String_Len(data) as int, out);
}
}
func Hash_Sha384Raw(data: String, out: *void) {
func Hash_Sha384Raw(data: String, out: *void) {
bux_sha384(data, String_Len(data) as int, out);
}
}
func Hash_Sha512Raw(data: String, out: *void) {
func Hash_Sha512Raw(data: String, out: *void) {
bux_sha512(data, String_Len(data) as int, out);
}
}
// --- Digest sizes ---
// --- Digest sizes ---
func Hash_Sha1Size() -> int { return 20; }
func Hash_Sha256Size() -> int { return 32; }
func Hash_Sha384Size() -> int { return 48; }
func Hash_Sha512Size() -> int { return 64; }
func Hash_Sha1Size() -> int { return 20; }
func Hash_Sha256Size() -> int { return 32; }
func Hash_Sha384Size() -> int { return 48; }
func Hash_Sha512Size() -> int { return 64; }
}
+28 -28
View File
@@ -3,18 +3,18 @@
// =============================================================================
module Std::Crypto::Hmac {
import Std::Mem::{Alloc, Free};
import Std::String::{String_Len};
import Std::Mem::{Alloc, Free};
import Std::String::{String_Len};
extern func bux_hmac_sha256(key: String, keylen: int, msg: String, msglen: int, out: *void);
extern func bux_hmac_sha384(key: String, keylen: int, msg: String, msglen: int, out: *void);
extern func bux_hmac_sha512(key: String, keylen: int, msg: String, msglen: int, out: *void);
extern func bux_bytes_to_hex(data: *void, len: int) -> String;
extern func bux_base64_encode(data: String, len: int) -> String;
extern func bux_hmac_sha256(key: String, keylen: int, msg: String, msglen: int, out: *void);
extern func bux_hmac_sha384(key: String, keylen: int, msg: String, msglen: int, out: *void);
extern func bux_hmac_sha512(key: String, keylen: int, msg: String, msglen: int, out: *void);
extern func bux_bytes_to_hex(data: *void, len: int) -> String;
extern func bux_base64_encode(data: String, len: int) -> String;
// --- HMAC-SHA256 ---
// --- HMAC-SHA256 ---
func Hmac_Sha256(key: String, message: String) -> String {
func Hmac_Sha256(key: String, message: String) -> String {
let kl: int = String_Len(key) as int;
let ml: int = String_Len(message) as int;
let buf: *void = Alloc(32);
@@ -22,13 +22,13 @@ func Hmac_Sha256(key: String, message: String) -> String {
let result: String = bux_bytes_to_hex(buf, 32);
Free(buf);
return result;
}
}
func Hmac_Sha256Raw(key: String, message: String, out: *void) {
func Hmac_Sha256Raw(key: String, message: String, out: *void) {
bux_hmac_sha256(key, String_Len(key) as int, message, String_Len(message) as int, out);
}
}
func Hmac_Sha256Base64(key: String, message: String) -> String {
func Hmac_Sha256Base64(key: String, message: String) -> String {
let kl: int = String_Len(key) as int;
let ml: int = String_Len(message) as int;
let buf: *void = Alloc(32);
@@ -36,11 +36,11 @@ func Hmac_Sha256Base64(key: String, message: String) -> String {
let result: String = bux_base64_encode(buf as String, 32);
Free(buf);
return result;
}
}
// --- HMAC-SHA384 ---
// --- HMAC-SHA384 ---
func Hmac_Sha384(key: String, message: String) -> String {
func Hmac_Sha384(key: String, message: String) -> String {
let kl: int = String_Len(key) as int;
let ml: int = String_Len(message) as int;
let buf: *void = Alloc(48);
@@ -48,13 +48,13 @@ func Hmac_Sha384(key: String, message: String) -> String {
let result: String = bux_bytes_to_hex(buf, 48);
Free(buf);
return result;
}
}
func Hmac_Sha384Raw(key: String, message: String, out: *void) {
func Hmac_Sha384Raw(key: String, message: String, out: *void) {
bux_hmac_sha384(key, String_Len(key) as int, message, String_Len(message) as int, out);
}
}
func Hmac_Sha384Base64(key: String, message: String) -> String {
func Hmac_Sha384Base64(key: String, message: String) -> String {
let kl: int = String_Len(key) as int;
let ml: int = String_Len(message) as int;
let buf: *void = Alloc(48);
@@ -62,11 +62,11 @@ func Hmac_Sha384Base64(key: String, message: String) -> String {
let result: String = bux_base64_encode(buf as String, 48);
Free(buf);
return result;
}
}
// --- HMAC-SHA512 ---
// --- HMAC-SHA512 ---
func Hmac_Sha512(key: String, message: String) -> String {
func Hmac_Sha512(key: String, message: String) -> String {
let kl: int = String_Len(key) as int;
let ml: int = String_Len(message) as int;
let buf: *void = Alloc(64);
@@ -74,13 +74,13 @@ func Hmac_Sha512(key: String, message: String) -> String {
let result: String = bux_bytes_to_hex(buf, 64);
Free(buf);
return result;
}
}
func Hmac_Sha512Raw(key: String, message: String, out: *void) {
func Hmac_Sha512Raw(key: String, message: String, out: *void) {
bux_hmac_sha512(key, String_Len(key) as int, message, String_Len(message) as int, out);
}
}
func Hmac_Sha512Base64(key: String, message: String) -> String {
func Hmac_Sha512Base64(key: String, message: String) -> String {
let kl: int = String_Len(key) as int;
let ml: int = String_Len(message) as int;
let buf: *void = Alloc(64);
@@ -88,5 +88,5 @@ func Hmac_Sha512Base64(key: String, message: String) -> String {
let result: String = bux_base64_encode(buf as String, 64);
Free(buf);
return result;
}
}
}
+58 -58
View File
@@ -4,22 +4,22 @@
// =============================================================================
module Std::Crypto::Jwt {
import Std::Mem::{Alloc, Free};
import Std::String::{String_Len, String_Eq, String_StartsWith, String_Concat};
import Std::Crypto::Base64::{Base64URL_Encode, Base64URL_Decode};
import Std::Crypto::Hash::{Hash_Sha256Raw, Hash_Sha384Raw, Hash_Sha512Raw};
import Std::Crypto::Hmac::{Hmac_Sha256Raw, Hmac_Sha384Raw, Hmac_Sha512Raw};
import Std::Crypto::Rsa::{Rsa_SignSha256, Rsa_SignSha384, Rsa_SignSha512,
import Std::Mem::{Alloc, Free};
import Std::String::{String_Len, String_Eq, String_StartsWith, String_Concat};
import Std::Crypto::Base64::{Base64URL_Encode, Base64URL_Decode};
import Std::Crypto::Hash::{Hash_Sha256Raw, Hash_Sha384Raw, Hash_Sha512Raw};
import Std::Crypto::Hmac::{Hmac_Sha256Raw, Hmac_Sha384Raw, Hmac_Sha512Raw};
import Std::Crypto::Rsa::{Rsa_SignSha256, Rsa_SignSha384, Rsa_SignSha512,
Rsa_VerifySha256, Rsa_VerifySha384, Rsa_VerifySha512};
import Std::Crypto::Ecdsa::{Ecdsa_SignP256, Ecdsa_SignP384, Ecdsa_VerifyP256, Ecdsa_VerifyP384};
import Std::Crypto::Ed25519::{Ed25519_Sign, Ed25519_Verify};
import Std::Crypto::Ecdsa::{Ecdsa_SignP256, Ecdsa_SignP384, Ecdsa_VerifyP256, Ecdsa_VerifyP384};
import Std::Crypto::Ed25519::{Ed25519_Sign, Ed25519_Verify};
extern func bux_base64_encode(data: String, len: int) -> String;
extern func bux_str_split_count(s: String, delim: String) -> uint;
extern func bux_str_split_part(s: String, delim: String, index: uint) -> String;
extern func bux_base64_encode(data: String, len: int) -> String;
extern func bux_str_split_count(s: String, delim: String) -> uint;
extern func bux_str_split_part(s: String, delim: String, index: uint) -> String;
// --- JWT Algorithm enum ---
enum JwtAlg {
// --- JWT Algorithm enum ---
enum JwtAlg {
HS256,
HS384,
HS512,
@@ -29,12 +29,12 @@ enum JwtAlg {
ES256,
ES384,
EdDSA,
}
}
// --- Header ---
// --- Header ---
// Jwt_MakeHeader: build the JWT header JSON string for the given algorithm
func Jwt_MakeHeader(alg: JwtAlg) -> String {
// Jwt_MakeHeader: build the JWT header JSON string for the given algorithm
func Jwt_MakeHeader(alg: JwtAlg) -> String {
if alg.tag == JwtAlg_HS256 { return "{\"alg\":\"HS256\",\"typ\":\"JWT\"}"; }
if alg.tag == JwtAlg_HS384 { return "{\"alg\":\"HS384\",\"typ\":\"JWT\"}"; }
if alg.tag == JwtAlg_HS512 { return "{\"alg\":\"HS512\",\"typ\":\"JWT\"}"; }
@@ -45,12 +45,12 @@ func Jwt_MakeHeader(alg: JwtAlg) -> String {
if alg.tag == JwtAlg_ES384 { return "{\"alg\":\"ES384\",\"typ\":\"JWT\"}"; }
if alg.tag == JwtAlg_EdDSA { return "{\"alg\":\"EdDSA\",\"typ\":\"JWT\"}"; }
return "{\"alg\":\"none\",\"typ\":\"JWT\"}";
}
}
// --- Signing ---
// --- Signing ---
// Sign the JWT signing input with the given algorithm
func Jwt_Sign(alg: JwtAlg, signingInput: String, key: String) -> String {
// Sign the JWT signing input with the given algorithm
func Jwt_Sign(alg: JwtAlg, signingInput: String, key: String) -> String {
// --- HMAC algorithms ---
if alg.tag == JwtAlg_HS256 {
let buf: *void = Alloc(32);
@@ -104,12 +104,12 @@ func Jwt_Sign(alg: JwtAlg, signingInput: String, key: String) -> String {
}
return "";
}
}
// --- Verify ---
// --- Verify ---
// Verify a JWT signature
func Jwt_Verify(alg: JwtAlg, signingInput: String, signatureB64: String, key: String) -> bool {
// Verify a JWT signature
func Jwt_Verify(alg: JwtAlg, signingInput: String, signatureB64: String, key: String) -> bool {
// --- HMAC algorithms ---
if alg.tag == JwtAlg_HS256 {
let expectBuf: *void = Alloc(32);
@@ -146,17 +146,17 @@ func Jwt_Verify(alg: JwtAlg, signingInput: String, signatureB64: String, key: St
if alg.tag == JwtAlg_EdDSA { return Ed25519_Verify(key, signatureB64, signingInput); }
return false;
}
}
// --- Encode ---
// --- Encode ---
// Jwt_Encode: create a signed JWT
// headerJson — JSON header string (use Jwt_MakeHeader or custom)
// payloadJson — JSON payload/claims string
// alg — signing algorithm
// key — signing key (HMAC secret, RSA PEM, ECDSA PEM, or Ed25519 raw privkey)
// Returns the complete "header.payload.signature" JWT string
func Jwt_Encode(headerJson: String, payloadJson: String, alg: JwtAlg, key: String) -> String {
// Jwt_Encode: create a signed JWT
// headerJson — JSON header string (use Jwt_MakeHeader or custom)
// payloadJson — JSON payload/claims string
// alg — signing algorithm
// key — signing key (HMAC secret, RSA PEM, ECDSA PEM, or Ed25519 raw privkey)
// Returns the complete "header.payload.signature" JWT string
func Jwt_Encode(headerJson: String, payloadJson: String, alg: JwtAlg, key: String) -> String {
let headerB64: String = Base64URL_Encode(headerJson);
let payloadB64: String = Base64URL_Encode(payloadJson);
let signingInput: String = String_Concat(headerB64, ".");
@@ -166,18 +166,18 @@ func Jwt_Encode(headerJson: String, payloadJson: String, alg: JwtAlg, key: Strin
let part1: String = String_Concat(signingInputFull, ".");
return String_Concat(part1, sigB64);
}
}
// --- Decode ---
// --- Decode ---
// Jwt_Decode: decode and verify a JWT.
// token — the full "header.payload.signature" string
// alg — expected algorithm
// key — verification key
// headerOut — receives decoded header JSON
// payloadOut — receives decoded payload JSON
// Returns true if signature is valid.
func Jwt_Decode(token: String, alg: JwtAlg, key: String,
// Jwt_Decode: decode and verify a JWT.
// token — the full "header.payload.signature" string
// alg — expected algorithm
// key — verification key
// headerOut — receives decoded header JSON
// payloadOut — receives decoded payload JSON
// Returns true if signature is valid.
func Jwt_Decode(token: String, alg: JwtAlg, key: String,
headerOut: *String, payloadOut: *String) -> bool {
// Split by "."
let partCount: uint = bux_str_split_count(token, ".");
@@ -200,37 +200,37 @@ func Jwt_Decode(token: String, alg: JwtAlg, key: String,
headerOut[0] = Base64URL_Decode(headerB64);
payloadOut[0] = Base64URL_Decode(payloadB64);
return true;
}
}
// --- Convenience: Encode with standard header ---
// --- Convenience: Encode with standard header ---
func Jwt_EncodeHS256(payloadJson: String, secret: String) -> String {
func Jwt_EncodeHS256(payloadJson: String, secret: String) -> String {
let header: String = Jwt_MakeHeader(JwtAlg { tag: JwtAlg_HS256 });
return Jwt_Encode(header, payloadJson, JwtAlg { tag: JwtAlg_HS256 }, secret);
}
}
func Jwt_EncodeHS384(payloadJson: String, secret: String) -> String {
func Jwt_EncodeHS384(payloadJson: String, secret: String) -> String {
let header: String = Jwt_MakeHeader(JwtAlg { tag: JwtAlg_HS384 });
return Jwt_Encode(header, payloadJson, JwtAlg { tag: JwtAlg_HS384 }, secret);
}
}
func Jwt_EncodeHS512(payloadJson: String, secret: String) -> String {
func Jwt_EncodeHS512(payloadJson: String, secret: String) -> String {
let header: String = Jwt_MakeHeader(JwtAlg { tag: JwtAlg_HS512 });
return Jwt_Encode(header, payloadJson, JwtAlg { tag: JwtAlg_HS512 }, secret);
}
}
func Jwt_EncodeRS256(payloadJson: String, pemPrivateKey: String) -> String {
func Jwt_EncodeRS256(payloadJson: String, pemPrivateKey: String) -> String {
let header: String = Jwt_MakeHeader(JwtAlg { tag: JwtAlg_RS256 });
return Jwt_Encode(header, payloadJson, JwtAlg { tag: JwtAlg_RS256 }, pemPrivateKey);
}
}
func Jwt_EncodeES256(payloadJson: String, pemPrivateKey: String) -> String {
func Jwt_EncodeES256(payloadJson: String, pemPrivateKey: String) -> String {
let header: String = Jwt_MakeHeader(JwtAlg { tag: JwtAlg_ES256 });
return Jwt_Encode(header, payloadJson, JwtAlg { tag: JwtAlg_ES256 }, pemPrivateKey);
}
}
func Jwt_EncodeEdDSA(payloadJson: String, rawPrivKey: String) -> String {
func Jwt_EncodeEdDSA(payloadJson: String, rawPrivKey: String) -> String {
let header: String = Jwt_MakeHeader(JwtAlg { tag: JwtAlg_EdDSA });
return Jwt_Encode(header, payloadJson, JwtAlg { tag: JwtAlg_EdDSA }, rawPrivKey);
}
}
}
+16 -16
View File
@@ -3,14 +3,14 @@
// =============================================================================
module Std::Crypto::Random {
import Std::Mem::{Alloc, Free};
import Std::Mem::{Alloc, Free};
extern func bux_random_bytes(buf: *void, len: int) -> int;
extern func bux_base64_encode(data: String, len: int) -> String;
extern func bux_bytes_to_hex(data: *void, len: int) -> String;
extern func bux_random_bytes(buf: *void, len: int) -> int;
extern func bux_base64_encode(data: String, len: int) -> String;
extern func bux_bytes_to_hex(data: *void, len: int) -> String;
// RandomBytes: returns n cryptographically secure random bytes as a raw string
func Random_Bytes(n: int) -> String {
// RandomBytes: returns n cryptographically secure random bytes as a raw string
func Random_Bytes(n: int) -> String {
if n <= 0 { return ""; }
let buf: *void = Alloc(n as uint);
if bux_random_bytes(buf, n) != 1 {
@@ -19,10 +19,10 @@ func Random_Bytes(n: int) -> String {
}
// Return raw buffer as string (binary-safe)
return buf as String;
}
}
// RandomHex: returns n random bytes as lowercase hex
func Random_Hex(n: int) -> String {
// RandomHex: returns n random bytes as lowercase hex
func Random_Hex(n: int) -> String {
if n <= 0 { return ""; }
let buf: *void = Alloc(n as uint);
if bux_random_bytes(buf, n) != 1 {
@@ -32,10 +32,10 @@ func Random_Hex(n: int) -> String {
let result: String = bux_bytes_to_hex(buf, n);
Free(buf);
return result;
}
}
// RandomBase64: returns n random bytes as base64-encoded string
func Random_Base64(n: int) -> String {
// RandomBase64: returns n random bytes as base64-encoded string
func Random_Base64(n: int) -> String {
if n <= 0 { return ""; }
let buf: *void = Alloc(n as uint);
if bux_random_bytes(buf, n) != 1 {
@@ -45,10 +45,10 @@ func Random_Base64(n: int) -> String {
let result: String = bux_base64_encode(buf as String, n);
Free(buf);
return result;
}
}
// RandomUint32: returns a random 32-bit unsigned integer
func Random_Uint32() -> uint {
// RandomUint32: returns a random 32-bit unsigned integer
func Random_Uint32() -> uint {
let buf: *void = Alloc(4);
if bux_random_bytes(buf, 4) != 1 {
Free(buf);
@@ -59,5 +59,5 @@ func Random_Uint32() -> uint {
let val: uint = *ptr;
Free(buf);
return val;
}
}
}
+42 -42
View File
@@ -3,88 +3,88 @@
// =============================================================================
module Std::Crypto::Rsa {
import Std::Mem::{Alloc, Free};
import Std::String::{String_Len};
import Std::Mem::{Alloc, Free};
import Std::String::{String_Len};
// Extern declarations for the runtime C implementations
extern func bux_rsa_sign_sha256(key: String, keylen: int, data: String, datalen: int, outlen: *int) -> String;
extern func bux_rsa_sign_sha384(key: String, keylen: int, data: String, datalen: int, outlen: *int) -> String;
extern func bux_rsa_sign_sha512(key: String, keylen: int, data: String, datalen: int, outlen: *int) -> String;
extern func bux_rsa_verify_sha256(key: String, keylen: int, data: String, datalen: int, sig: String, siglen: int) -> int;
extern func bux_rsa_verify_sha384(key: String, keylen: int, data: String, datalen: int, sig: String, siglen: int) -> int;
extern func bux_rsa_verify_sha512(key: String, keylen: int, data: String, datalen: int, sig: String, siglen: int) -> int;
extern func bux_base64_encode(data: String, len: int) -> String;
extern func bux_base64_decode(data: String, len: int, outlen: *int) -> String;
// Extern declarations for the runtime C implementations
extern func bux_rsa_sign_sha256(key: String, keylen: int, data: String, datalen: int, outlen: *int) -> String;
extern func bux_rsa_sign_sha384(key: String, keylen: int, data: String, datalen: int, outlen: *int) -> String;
extern func bux_rsa_sign_sha512(key: String, keylen: int, data: String, datalen: int, outlen: *int) -> String;
extern func bux_rsa_verify_sha256(key: String, keylen: int, data: String, datalen: int, sig: String, siglen: int) -> int;
extern func bux_rsa_verify_sha384(key: String, keylen: int, data: String, datalen: int, sig: String, siglen: int) -> int;
extern func bux_rsa_verify_sha512(key: String, keylen: int, data: String, datalen: int, sig: String, siglen: int) -> int;
extern func bux_base64_encode(data: String, len: int) -> String;
extern func bux_base64_decode(data: String, len: int, outlen: *int) -> String;
// --- RSA Sign ---
// --- RSA Sign ---
// Rsa_SignSha256: sign data with RSA private key (PEM format), returns raw signature
func Rsa_SignSha256(pemPrivateKey: String, data: String) -> String {
// Rsa_SignSha256: sign data with RSA private key (PEM format), returns raw signature
func Rsa_SignSha256(pemPrivateKey: String, data: String) -> String {
let siglen: int = 0;
return bux_rsa_sign_sha256(pemPrivateKey, String_Len(pemPrivateKey) as int, data, String_Len(data) as int, &siglen);
}
}
func Rsa_SignSha384(pemPrivateKey: String, data: String) -> String {
func Rsa_SignSha384(pemPrivateKey: String, data: String) -> String {
let siglen: int = 0;
return bux_rsa_sign_sha384(pemPrivateKey, String_Len(pemPrivateKey) as int, data, String_Len(data) as int, &siglen);
}
}
func Rsa_SignSha512(pemPrivateKey: String, data: String) -> String {
func Rsa_SignSha512(pemPrivateKey: String, data: String) -> String {
let siglen: int = 0;
return bux_rsa_sign_sha512(pemPrivateKey, String_Len(pemPrivateKey) as int, data, String_Len(data) as int, &siglen);
}
}
// Convenience: sign and return base64-encoded signature
func Rsa_SignSha256Base64(pemPrivateKey: String, data: String) -> String {
// Convenience: sign and return base64-encoded signature
func Rsa_SignSha256Base64(pemPrivateKey: String, data: String) -> String {
let raw: String = Rsa_SignSha256(pemPrivateKey, data);
return bux_base64_encode(raw, String_Len(raw) as int);
}
}
func Rsa_SignSha384Base64(pemPrivateKey: String, data: String) -> String {
func Rsa_SignSha384Base64(pemPrivateKey: String, data: String) -> String {
let raw: String = Rsa_SignSha384(pemPrivateKey, data);
return bux_base64_encode(raw, String_Len(raw) as int);
}
}
func Rsa_SignSha512Base64(pemPrivateKey: String, data: String) -> String {
func Rsa_SignSha512Base64(pemPrivateKey: String, data: String) -> String {
let raw: String = Rsa_SignSha512(pemPrivateKey, data);
return bux_base64_encode(raw, String_Len(raw) as int);
}
}
// --- RSA Verify ---
// --- RSA Verify ---
// Rsa_VerifySha256: verify raw signature against data with RSA public key (PEM)
// Returns true if signature is valid.
func Rsa_VerifySha256(pemPublicKey: String, data: String, signature: String) -> bool {
// Rsa_VerifySha256: verify raw signature against data with RSA public key (PEM)
// Returns true if signature is valid.
func Rsa_VerifySha256(pemPublicKey: String, data: String, signature: String) -> bool {
let r: int = bux_rsa_verify_sha256(pemPublicKey, String_Len(pemPublicKey) as int, data, String_Len(data) as int, signature, String_Len(signature) as int);
return r == 1;
}
}
func Rsa_VerifySha384(pemPublicKey: String, data: String, signature: String) -> bool {
func Rsa_VerifySha384(pemPublicKey: String, data: String, signature: String) -> bool {
let r: int = bux_rsa_verify_sha384(pemPublicKey, String_Len(pemPublicKey) as int, data, String_Len(data) as int, signature, String_Len(signature) as int);
return r == 1;
}
}
func Rsa_VerifySha512(pemPublicKey: String, data: String, signature: String) -> bool {
func Rsa_VerifySha512(pemPublicKey: String, data: String, signature: String) -> bool {
let r: int = bux_rsa_verify_sha512(pemPublicKey, String_Len(pemPublicKey) as int, data, String_Len(data) as int, signature, String_Len(signature) as int);
return r == 1;
}
}
// Convenience: verify base64-encoded signature
func Rsa_VerifySha256Base64(pemPublicKey: String, data: String, signatureB64: String) -> bool {
// Convenience: verify base64-encoded signature
func Rsa_VerifySha256Base64(pemPublicKey: String, data: String, signatureB64: String) -> bool {
let outlen: int = 0;
let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
return Rsa_VerifySha256(pemPublicKey, data, sig);
}
}
func Rsa_VerifySha384Base64(pemPublicKey: String, data: String, signatureB64: String) -> bool {
func Rsa_VerifySha384Base64(pemPublicKey: String, data: String, signatureB64: String) -> bool {
let outlen: int = 0;
let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
return Rsa_VerifySha384(pemPublicKey, data, sig);
}
}
func Rsa_VerifySha512Base64(pemPublicKey: String, data: String, signatureB64: String) -> bool {
func Rsa_VerifySha512Base64(pemPublicKey: String, data: String, signatureB64: String) -> bool {
let outlen: int = 0;
let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
return Rsa_VerifySha512(pemPublicKey, data, sig);
}
}
}
+15
View File
@@ -0,0 +1,15 @@
# greet
Demo package for the Bux registry (`config/registry.toml`).
```bash
bux add greet
bux install
```
```bux
func Main() -> int {
PrintLine(Greet_Hello("Bux"));
return 0;
}
```
+9
View File
@@ -0,0 +1,9 @@
[Package]
Name = "greet"
Version = "0.1.1"
Type = "lib"
Authors = ["Bux Core"]
License = "MIT"
[Build]
Output = "Lib"
+14
View File
@@ -0,0 +1,14 @@
// greet — demo registry package (E.1)
module Greet {
/// Return a greeting for `name`.
func Greet_Hello(name: String) -> String {
return String_Concat("Hello, ", String_Concat(name, "!"));
}
/// Return the package version string.
func Greet_Version() -> String {
return "0.1.1";
}
}
+8 -8
View File
@@ -1,15 +1,15 @@
// main.bux — Entry point for the Bux self-hosting compiler
module Main {
// C runtime for command-line args
extern func bux_argc() -> int;
extern func bux_argv(index: int) -> String;
extern func bux_alloc(size: uint) -> *void;
// C runtime for command-line args
extern func bux_argc() -> int;
extern func bux_argv(index: int) -> String;
extern func bux_alloc(size: uint) -> *void;
// Forward declaration from Cli module
func Cli_Run(args: *String, argCount: int) -> int;
// Forward declaration from Cli module
func Cli_Run(args: *String, argCount: int) -> int;
func Main() -> int {
func Main() -> int {
let count: int = bux_argc();
// Allocate array of String pointers
let args: *String = bux_alloc(count as uint * 8) as *String;
@@ -19,5 +19,5 @@ func Main() -> int {
i = i + 1;
}
return Cli_Run(args, count);
}
}
}
+150 -149
View File
@@ -1,43 +1,43 @@
// ast.bux — AST node types (Expr, Stmt, Decl, Pattern, TypeExpr)
module Ast {
// ---------------------------------------------------------------------------
// SourceLocation (inline for convenience)
// ---------------------------------------------------------------------------
struct SourceLoc {
// ---------------------------------------------------------------------------
// SourceLocation (inline for convenience)
// ---------------------------------------------------------------------------
struct SourceLoc {
line: uint32,
column: uint32,
}
}
// ---------------------------------------------------------------------------
// Token (lightweight inline)
// ---------------------------------------------------------------------------
struct AstToken {
// ---------------------------------------------------------------------------
// Token (lightweight inline)
// ---------------------------------------------------------------------------
struct AstToken {
kind: int,
text: String,
line: uint32,
column: uint32,
}
}
// ---------------------------------------------------------------------------
// TypeExpr — type expressions
// ---------------------------------------------------------------------------
const tekNamed: int = 0;
const tekPath: int = 1;
const tekSlice: int = 2;
const tekPointer: int = 3;
const tekRef: int = 7; // &T — shared reference
const tekMutRef: int = 8; // &mut T — mutable reference
const tekTuple: int = 4;
const tekSelf: int = 5;
const tekFunc: int = 6;
// ---------------------------------------------------------------------------
// TypeExpr — type expressions
// ---------------------------------------------------------------------------
const tekNamed: int = 0;
const tekPath: int = 1;
const tekSlice: int = 2;
const tekPointer: int = 3;
const tekRef: int = 7; // &T — shared reference
const tekMutRef: int = 8; // &mut T — mutable reference
const tekTuple: int = 4;
const tekSelf: int = 5;
const tekFunc: int = 6;
struct TypeExprList {
struct TypeExprList {
te: *TypeExpr,
next: *TypeExprList,
}
}
struct TypeExpr {
struct TypeExpr {
kind: int,
line: uint32,
column: uint32,
@@ -48,27 +48,28 @@ struct TypeExpr {
typeArgName1: String,
typeArgCount: int,
sliceElement: *TypeExpr, // for tekSlice
pointerPointee: *TypeExpr, // for tekPointer
pointerPointee: *TypeExpr, // for tekPointer / tekRef / tekMutRef
refLifetime: String, // for tekRef / tekMutRef: "'a" or "" (elided)
funcParams: *TypeExprList, // for tekFunc
funcRet: *TypeExpr, // for tekFunc
funcParamCount: int, // for tekFunc
tupleElems: *TypeExprList, // for tekTuple
tupleCount: int, // for tekTuple
}
}
// ---------------------------------------------------------------------------
// Pattern — match patterns
// ---------------------------------------------------------------------------
const pkWildcard: int = 0;
const pkLiteral: int = 1;
const pkIdent: int = 2;
const pkRange: int = 3;
const pkEnum: int = 4;
const pkStruct: int = 5;
const pkTuple: int = 6;
const pkGuarded: int = 7; // `p if cond` — patChild1 = inner, patGuardExpr = condition
// ---------------------------------------------------------------------------
// Pattern — match patterns
// ---------------------------------------------------------------------------
const pkWildcard: int = 0;
const pkLiteral: int = 1;
const pkIdent: int = 2;
const pkRange: int = 3;
const pkEnum: int = 4;
const pkStruct: int = 5;
const pkTuple: int = 6;
const pkGuarded: int = 7; // `p if cond` — patChild1 = inner, patGuardExpr = condition
struct Pattern {
struct Pattern {
kind: int,
line: uint32,
column: uint32,
@@ -84,56 +85,56 @@ struct Pattern {
patArgs: *Pattern, // pkEnum/pkTuple/pkStruct field list (head)
patNext: *Pattern, // next sibling in patArgs list
patGuardExpr: *Expr, // for pkGuarded: the `if` condition
}
}
// Match arm: pattern => body
struct MatchArm {
// Match arm: pattern => body
struct MatchArm {
line: uint32,
column: uint32,
pattern: *Pattern,
body: *Expr,
next: *MatchArm,
}
}
// ---------------------------------------------------------------------------
// Expr — expressions (tagged union)
// ---------------------------------------------------------------------------
const ekLiteral: int = 0;
const ekIdent: int = 1;
const ekSelf: int = 2;
const ekPath: int = 3;
const ekSizeOf: int = 4;
const ekUnary: int = 5;
const ekPostfix: int = 6;
const ekBinary: int = 7;
const ekAssign: int = 8;
const ekTernary: int = 9;
const ekRange: int = 10;
const ekCall: int = 11;
const ekGenericCall: int = 12;
const ekIndex: int = 13;
const ekField: int = 14;
const ekStructInit: int = 15;
const ekSlice: int = 16;
const ekTuple: int = 17;
const ekCast: int = 18;
const ekIs: int = 19;
const ekTry: int = 20;
const ekUnwrap: int = 23;
const ekBlock: int = 21;
const ekMatch: int = 22;
const ekSpawn: int = 24;
const ekAwait: int = 25;
const ekStringInterp: int = 26;
const ekClosure: int = 27;
// ---------------------------------------------------------------------------
// Expr — expressions (tagged union)
// ---------------------------------------------------------------------------
const ekLiteral: int = 0;
const ekIdent: int = 1;
const ekSelf: int = 2;
const ekPath: int = 3;
const ekSizeOf: int = 4;
const ekUnary: int = 5;
const ekPostfix: int = 6;
const ekBinary: int = 7;
const ekAssign: int = 8;
const ekTernary: int = 9;
const ekRange: int = 10;
const ekCall: int = 11;
const ekGenericCall: int = 12;
const ekIndex: int = 13;
const ekField: int = 14;
const ekStructInit: int = 15;
const ekSlice: int = 16;
const ekTuple: int = 17;
const ekCast: int = 18;
const ekIs: int = 19;
const ekTry: int = 20;
const ekUnwrap: int = 23;
const ekBlock: int = 21;
const ekMatch: int = 22;
const ekSpawn: int = 24;
const ekAwait: int = 25;
const ekStringInterp: int = 26;
const ekClosure: int = 27;
struct ExprList {
struct ExprList {
expr: *Expr,
next: *ExprList,
argName: String,
}
}
struct Expr {
struct Expr {
kind: int,
line: uint32,
column: uint32,
@@ -184,45 +185,45 @@ struct Expr {
// Match arms (for ekMatch)
matchArms: *MatchArm,
matchArmCount: int,
}
}
// ---------------------------------------------------------------------------
// Block — sequence of statements
// ---------------------------------------------------------------------------
struct Block {
// ---------------------------------------------------------------------------
// Block — sequence of statements
// ---------------------------------------------------------------------------
struct Block {
line: uint32,
column: uint32,
stmtCount: int,
firstStmt: *Stmt,
lastStmt: *Stmt,
}
}
// ---------------------------------------------------------------------------
// Stmt — statements
// ---------------------------------------------------------------------------
const skExpr: int = 0;
const skLet: int = 1;
const skIf: int = 2;
const skWhile: int = 3;
const skDoWhile: int = 4;
const skLoop: int = 5;
const skFor: int = 6;
const skMatch: int = 7;
const skReturn: int = 8;
const skBreak: int = 9;
const skContinue: int = 10;
const skDecl: int = 11;
const skDefer: int = 12;
const skSwitch: int = 13;
// ---------------------------------------------------------------------------
// Stmt — statements
// ---------------------------------------------------------------------------
const skExpr: int = 0;
const skLet: int = 1;
const skIf: int = 2;
const skWhile: int = 3;
const skDoWhile: int = 4;
const skLoop: int = 5;
const skFor: int = 6;
const skMatch: int = 7;
const skReturn: int = 8;
const skBreak: int = 9;
const skContinue: int = 10;
const skDecl: int = 11;
const skDefer: int = 12;
const skSwitch: int = 13;
struct ElseIf {
struct ElseIf {
line: uint32;
column: uint32;
cond: *Expr;
block: *Block;
}
}
struct Stmt {
struct Stmt {
kind: int,
line: uint32,
column: uint32,
@@ -242,51 +243,51 @@ struct Stmt {
elseIfCount: int,
// Linked list
nextStmt: *Stmt,
}
}
// ---------------------------------------------------------------------------
// Decl — declarations
// ---------------------------------------------------------------------------
const dkFunc: int = 0;
const dkStruct: int = 1;
const dkEnum: int = 2;
const dkUnion: int = 3;
const dkInterface: int = 4;
const dkImpl: int = 5;
const dkModule: int = 6;
const dkUse: int = 7;
const dkConst: int = 8;
const dkTypeAlias: int = 9;
const dkExternFunc: int = 10;
const dkExternVar: int = 11;
// ---------------------------------------------------------------------------
// Decl — declarations
// ---------------------------------------------------------------------------
const dkFunc: int = 0;
const dkStruct: int = 1;
const dkEnum: int = 2;
const dkUnion: int = 3;
const dkInterface: int = 4;
const dkImpl: int = 5;
const dkModule: int = 6;
const dkUse: int = 7;
const dkConst: int = 8;
const dkTypeAlias: int = 9;
const dkExternFunc: int = 10;
const dkExternVar: int = 11;
struct Param {
struct Param {
line: uint32;
column: uint32;
name: String;
refParamType: *TypeExpr;
isVariadic: bool;
defaultExpr: *Expr;
}
}
struct StructField {
struct StructField {
line: uint32;
column: uint32;
isPublic: bool;
name: String;
refFieldType: *TypeExpr;
}
}
struct EnumVariant {
struct EnumVariant {
line: uint32;
column: uint32;
name: String;
fieldCount: int;
fieldTypeName0: String;
fieldTypeName1: String;
}
}
struct Decl {
struct Decl {
fieldCount: int,
fields: *StructField,
kind: int,
@@ -352,23 +353,23 @@ struct Decl {
childDecl1: *Decl, // linked list of decls (for module items, impl methods)
childDecl2: *Decl,
// Struct fields (up to 256)
}
}
// ---------------------------------------------------------------------------
// Module — AST root
// ---------------------------------------------------------------------------
struct Module {
// ---------------------------------------------------------------------------
// Module — AST root
// ---------------------------------------------------------------------------
struct Module {
name: String,
path: String, // path segments joined
itemCount: int,
firstItem: *Decl,
}
}
// ---------------------------------------------------------------------------
// Constructor helpers
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Constructor helpers
// ---------------------------------------------------------------------------
func Ast_MakeExpr(kind: int, line: uint32, col: uint32) -> Expr {
func Ast_MakeExpr(kind: int, line: uint32, col: uint32) -> Expr {
return Expr { kind: kind, line: line, column: col,
strValue: "", intValue: 0, boolValue: false,
tokKind: 0, tokText: "",
@@ -377,45 +378,45 @@ func Ast_MakeExpr(kind: int, line: uint32, col: uint32) -> Expr {
genericCallee: "", genericTypeArg0: "", genericTypeArg1: "", genericTypeArgCount: 0,
structName: "", structFieldCount: 0,
callArgs: null as *ExprList, callArgCount: 0 };
}
}
func Ast_MakeIdent(name: String, line: uint32, col: uint32) -> Expr {
func Ast_MakeIdent(name: String, line: uint32, col: uint32) -> Expr {
var e: Expr = Ast_MakeExpr(ekIdent, line, col);
e.strValue = name;
return e;
}
}
func Ast_MakeLiteral(tokKind: int, text: String, line: uint32, col: uint32) -> Expr {
func Ast_MakeLiteral(tokKind: int, text: String, line: uint32, col: uint32) -> Expr {
var e: Expr = Ast_MakeExpr(ekLiteral, line, col);
e.tokKind = tokKind;
e.tokText = text;
return e;
}
}
func Ast_MakeBinary(op: int, left: *Expr, right: *Expr, line: uint32, col: uint32) -> Expr {
func Ast_MakeBinary(op: int, left: *Expr, right: *Expr, line: uint32, col: uint32) -> Expr {
var e: Expr = Ast_MakeExpr(ekBinary, line, col);
e.intValue = op;
e.child1 = left;
e.child2 = right;
return e;
}
}
func Ast_MakeCall(callee: *Expr, line: uint32, col: uint32) -> Expr {
func Ast_MakeCall(callee: *Expr, line: uint32, col: uint32) -> Expr {
var e: Expr = Ast_MakeExpr(ekCall, line, col);
e.child1 = callee;
return e;
}
}
func Ast_MakeStmt(kind: int, line: uint32, col: uint32) -> Stmt {
func Ast_MakeStmt(kind: int, line: uint32, col: uint32) -> Stmt {
return Stmt { kind: kind, line: line, column: col,
strValue: "", boolValue: false,
child1: null as *Expr, child2: null as *Expr, child3: null as *Expr,
refStmtType: null as *TypeExpr, refStmtPattern: null as *Pattern,
refStmtDecl: null as *Decl, refStmtBlock: null as *Block, refStmtElse: null as *Block,
elseIfCount: 0 };
}
}
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,
isAsync: false, isChecked: 0, isDrop: 0, isRelease: 0, isConst: 0,
strValue: "", strValue2: "",
@@ -433,5 +434,5 @@ func Ast_MakeDecl(kind: int, line: uint32, col: uint32) -> Decl {
aliasType: null as *TypeExpr,
extFuncDll: "", extFuncVariadic: false, extFuncRetType: null as *TypeExpr,
childDecl1: null as *Decl, childDecl2: null as *Decl };
}
}
}
+96 -96
View File
@@ -3,27 +3,27 @@
module CBackend {
// ---------------------------------------------------------------------------
// Type → C type name
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Type → C type name
// ---------------------------------------------------------------------------
func CBackend_TypeToC(kind: int) -> String {
func CBackend_TypeToC(kind: int) -> String {
let cName: String = Type_ToCName(kind);
if !String_Eq(cName, "") { return cName; }
if kind == tyNamed { return "int"; }
return "int";
}
}
// Emit a C parameter/variable declaration, embedding the name inside function-pointer syntax.
func CBE_CParamDecl(typeStr: String, name: String) -> String {
// Emit a C parameter/variable declaration, embedding the name inside function-pointer syntax.
func CBE_CParamDecl(typeStr: String, name: String) -> String {
if String_Contains(typeStr, "(*)") {
let replacement: String = String_Concat("(*", String_Concat(name, ")"));
return String_Replace(typeStr, "(*)", replacement);
}
return String_Concat(typeStr, String_Concat(" ", name));
}
}
func CBackend_OpToC(op: int) -> String {
func CBackend_OpToC(op: int) -> String {
if op == tkPlus { return "+"; }
if op == tkMinus { return "-"; }
if op == tkStar { return "*"; }
@@ -46,13 +46,13 @@ func CBackend_OpToC(op: int) -> String {
if op == tkShr { return ">>"; }
if op == tkAssign { return "="; }
return "?";
}
}
// ---------------------------------------------------------------------------
// StringBuilder-based C emitter
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// StringBuilder-based C emitter
// ---------------------------------------------------------------------------
struct CEmitter {
struct CEmitter {
sb: StringBuilder,
indent: int,
mod: *HirModule,
@@ -77,9 +77,9 @@ struct CEmitter {
movedName7: String,
tmpCounter: int,
currentRetType: String,
}
}
func CBE_PushDefer(cbe: *CEmitter, node: *HirNode) {
func CBE_PushDefer(cbe: *CEmitter, node: *HirNode) {
if cbe.deferCount == 0 { cbe.defer0 = node; }
if cbe.deferCount == 1 { cbe.defer1 = node; }
if cbe.deferCount == 2 { cbe.defer2 = node; }
@@ -89,9 +89,9 @@ func CBE_PushDefer(cbe: *CEmitter, node: *HirNode) {
if cbe.deferCount == 6 { cbe.defer6 = node; }
if cbe.deferCount == 7 { cbe.defer7 = node; }
cbe.deferCount = cbe.deferCount + 1;
}
}
func CBE_AddMoved(cbe: *CEmitter, name: String) {
func CBE_AddMoved(cbe: *CEmitter, name: String) {
if cbe.movedCount >= 8 { return; }
if cbe.movedCount == 0 { cbe.movedName0 = name; }
else if cbe.movedCount == 1 { cbe.movedName1 = name; }
@@ -102,9 +102,9 @@ func CBE_AddMoved(cbe: *CEmitter, name: String) {
else if cbe.movedCount == 6 { cbe.movedName6 = name; }
else if cbe.movedCount == 7 { cbe.movedName7 = name; }
cbe.movedCount = cbe.movedCount + 1;
}
}
func CBE_IsMoved(cbe: *CEmitter, name: String) -> bool {
func CBE_IsMoved(cbe: *CEmitter, name: String) -> bool {
if cbe.movedCount > 0 && String_Eq(cbe.movedName0, name) { return true; }
if cbe.movedCount > 1 && String_Eq(cbe.movedName1, name) { return true; }
if cbe.movedCount > 2 && String_Eq(cbe.movedName2, name) { return true; }
@@ -114,9 +114,9 @@ func CBE_IsMoved(cbe: *CEmitter, name: String) -> bool {
if cbe.movedCount > 6 && String_Eq(cbe.movedName6, name) { return true; }
if cbe.movedCount > 7 && String_Eq(cbe.movedName7, name) { return true; }
return false;
}
}
func CBE_RemoveMoved(cbe: *CEmitter, name: String) {
func CBE_RemoveMoved(cbe: *CEmitter, name: String) {
var found: int = -1;
if cbe.movedCount > 0 && String_Eq(cbe.movedName0, name) { found = 0; }
else if cbe.movedCount > 1 && String_Eq(cbe.movedName1, name) { found = 1; }
@@ -140,9 +140,9 @@ func CBE_RemoveMoved(cbe: *CEmitter, name: String) {
}
cbe.movedCount = cbe.movedCount - 1;
}
}
}
func CBE_GetAutoDropVarName(node: *HirNode) -> String {
func CBE_GetAutoDropVarName(node: *HirNode) -> String {
if node == null as *HirNode { return ""; }
// node is the inner expression stored by CBE_PushDefer (hCall for auto-drop)
let callNode: *HirNode = node;
@@ -155,10 +155,10 @@ func CBE_GetAutoDropVarName(node: *HirNode) -> String {
if varNode == null as *HirNode { return ""; }
if varNode.kind != hVar { return ""; }
return varNode.strValue;
}
}
// Emit one defer slot (shared by full-stack and scope-pop emitters).
func CBE_EmitOneDefer(cbe: *CEmitter, i: int) {
// Emit one defer slot (shared by full-stack and scope-pop emitters).
func CBE_EmitOneDefer(cbe: *CEmitter, i: int) {
var dn: *HirNode = null as *HirNode;
if i == 0 { dn = cbe.defer0; }
if i == 1 { dn = cbe.defer1; }
@@ -181,13 +181,13 @@ func CBE_EmitOneDefer(cbe: *CEmitter, i: int) {
}
CBE_EmitExpr(cbe, dn);
StringBuilder_Append(&cbe.sb, ";");
}
}
// Emit all active defers (LIFO) without clearing the stack.
// Must NOT clear: multiple return paths each need the full defer list.
// (Clearing caused Early(flag) { if (0) return; return 1 } to drop only on first exit.)
// Stack is reset at the start of each function emission.
func CBE_EmitDefers(cbe: *CEmitter) -> int {
// Emit all active defers (LIFO) without clearing the stack.
// Must NOT clear: multiple return paths each need the full defer list.
// (Clearing caused Early(flag) { if (0) return; return 1 } to drop only on first exit.)
// Stack is reset at the start of each function emission.
func CBE_EmitDefers(cbe: *CEmitter) -> int {
if cbe.deferCount == 0 { return 0; }
var i: int = cbe.deferCount - 1;
while i >= 0 {
@@ -195,11 +195,11 @@ func CBE_EmitDefers(cbe: *CEmitter) -> int {
i = i - 1;
}
return 1;
}
}
// Emit branch/loop-local defers (indices fromIdx..count-1) then pop them.
// Outer defers stay live so sibling branches and later returns still drop correctly.
func CBE_EmitAndPopDefersFrom(cbe: *CEmitter, fromIdx: int) -> int {
// Emit branch/loop-local defers (indices fromIdx..count-1) then pop them.
// Outer defers stay live so sibling branches and later returns still drop correctly.
func CBE_EmitAndPopDefersFrom(cbe: *CEmitter, fromIdx: int) -> int {
if cbe.deferCount <= fromIdx { return 0; }
var i: int = cbe.deferCount - 1;
while i >= fromIdx {
@@ -208,22 +208,22 @@ func CBE_EmitAndPopDefersFrom(cbe: *CEmitter, fromIdx: int) -> int {
}
cbe.deferCount = fromIdx;
return 1;
}
}
func CBE_Emit(cbe: *CEmitter, text: String) {
func CBE_Emit(cbe: *CEmitter, text: String) {
var i: int = 0;
while i < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
i = i + 1;
}
StringBuilder_Append(&cbe.sb, text);
}
}
// ---------------------------------------------------------------------------
// Emit HIR node
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Emit HIR node
// ---------------------------------------------------------------------------
func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
if node == null as *HirNode { return; }
let kind: int = node.kind;
@@ -808,14 +808,14 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
}
return;
}
}
}
// ---------------------------------------------------------------------------
// Emit function declaration
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Emit function declaration
// ---------------------------------------------------------------------------
// Infer C type for a BuxFn mangled part (int, cstr, uint, void, ...)
func CBE_FatPartToC(part: String) -> String {
// Infer C type for a BuxFn mangled part (int, cstr, uint, void, ...)
func CBE_FatPartToC(part: String) -> String {
if String_Eq(part, "cstr") { return "const char*"; }
if String_Eq(part, "void") { return "void"; }
if String_Eq(part, "bool") { return "bool"; }
@@ -827,10 +827,10 @@ func CBE_FatPartToC(part: String) -> String {
return String_Concat(CBE_FatPartToC(base), "*");
}
return part; // int, etc.
}
}
// Emit typedefs for common BuxFn_* shapes (fat function pointers)
func CBE_EmitFatFuncTypedefs(cbe: *CEmitter, mod: *HirModule) {
// Emit typedefs for common BuxFn_* shapes (fat function pointers)
func CBE_EmitFatFuncTypedefs(cbe: *CEmitter, mod: *HirModule) {
StringBuilder_Append(&cbe.sb, "/* Fat function pointer types (code + env) */\n");
// Always emit core shapes
CBE_EmitOneFatTypedef(cbe, "BuxFn_int_int");
@@ -864,15 +864,15 @@ func CBE_EmitFatFuncTypedefs(cbe: *CEmitter, mod: *HirModule) {
i = i + 1;
}
StringBuilder_Append(&cbe.sb, "\n");
}
}
func CBE_MaybeEmitExtraFat(cbe: *CEmitter, name: String) {
func CBE_MaybeEmitExtraFat(cbe: *CEmitter, name: String) {
if String_Eq(name, "") { return; }
if !String_StartsWith(name, "BuxFn_") { return; }
CBE_EmitOneFatTypedef(cbe, name);
}
}
func CBE_CollectBuxFn(names: *String, count: *int, name: String) {
func CBE_CollectBuxFn(names: *String, count: *int, name: String) {
if String_Eq(name, "") { return; }
if !String_StartsWith(name, "BuxFn_") { return; }
if *count >= 64 { return; }
@@ -883,10 +883,10 @@ func CBE_CollectBuxFn(names: *String, count: *int, name: String) {
}
names[*count] = name;
*count = *count + 1;
}
}
// BuxFn_ret_p0_p1 → typedef with code pointer
func CBE_EmitOneFatTypedef(cbe: *CEmitter, fatName: String) {
// BuxFn_ret_p0_p1 → typedef with code pointer
func CBE_EmitOneFatTypedef(cbe: *CEmitter, fatName: String) {
// Split fatName after "BuxFn_" into parts by '_'
let prefixLen: uint = 6; // "BuxFn_"
let rest: String = String_Slice(fatName, prefixLen, String_Len(fatName) - prefixLen);
@@ -921,9 +921,9 @@ func CBE_EmitOneFatTypedef(cbe: *CEmitter, fatName: String) {
StringBuilder_Append(&cbe.sb, ");\n void* env;\n} ");
StringBuilder_Append(&cbe.sb, fatName);
StringBuilder_Append(&cbe.sb, ";\n#endif\n");
}
}
func CBE_EmitMakerDecl(cbe: *CEmitter, f: *HirFunc) {
func CBE_EmitMakerDecl(cbe: *CEmitter, f: *HirFunc) {
// Infer fat type from thunk: skip __env, use user params + ret
var fatName: String = "BuxFn_";
var retC: String = f.retTypeName;
@@ -971,9 +971,9 @@ func CBE_EmitMakerDecl(cbe: *CEmitter, f: *HirFunc) {
ci = ci + 1;
}
StringBuilder_Append(&cbe.sb, ")");
}
}
func CBE_EmitMakerFunc(cbe: *CEmitter, f: *HirFunc) {
func CBE_EmitMakerFunc(cbe: *CEmitter, f: *HirFunc) {
CBE_EmitMakerDecl(cbe, f);
StringBuilder_Append(&cbe.sb, " {\n");
StringBuilder_Append(&cbe.sb, " ");
@@ -1030,10 +1030,10 @@ func CBE_EmitMakerFunc(cbe: *CEmitter, f: *HirFunc) {
StringBuilder_Append(&cbe.sb, "){ .code = ");
StringBuilder_Append(&cbe.sb, f.name);
StringBuilder_Append(&cbe.sb, ", .env = __e };\n}\n\n");
}
}
// Emit adapters for any non-closure function (used when taken as value)
func CBE_EmitAllAdapters(cbe: *CEmitter, mod: *HirModule) {
// Emit adapters for any non-closure function (used when taken as value)
func CBE_EmitAllAdapters(cbe: *CEmitter, mod: *HirModule) {
StringBuilder_Append(&cbe.sb, "/* Fat-func adapters for named functions */\n");
var i: int = 0;
while i < mod.funcCount {
@@ -1110,9 +1110,9 @@ func CBE_EmitAllAdapters(cbe: *CEmitter, mod: *HirModule) {
StringBuilder_Append(&cbe.sb, ");\n}\n\n");
i = i + 1;
}
}
}
func CBE_EmitFuncDecl(cbe: *CEmitter, f: *HirFunc) {
func CBE_EmitFuncDecl(cbe: *CEmitter, f: *HirFunc) {
// Return type
if String_Eq(f.retTypeName, "") || String_Eq(f.retTypeName, "void") {
StringBuilder_Append(&cbe.sb, "void ");
@@ -1151,13 +1151,13 @@ func CBE_EmitFuncDecl(cbe: *CEmitter, f: *HirFunc) {
}
StringBuilder_Append(&cbe.sb, ")");
}
}
// ---------------------------------------------------------------------------
// Helpers for detecting generic declarations (not yet monomorphized)
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Helpers for detecting generic declarations (not yet monomorphized)
// ---------------------------------------------------------------------------
func CBE_IsGenericTypeName(name: String) -> bool {
func CBE_IsGenericTypeName(name: String) -> bool {
if String_Eq(name, "T") || String_Eq(name, "K") || String_Eq(name, "V") { return true; }
if String_Eq(name, "T*") || String_Eq(name, "K*") || String_Eq(name, "V*") { return true; }
// Any type name containing '<' is a generic instantiation or parameter
@@ -1174,18 +1174,18 @@ func CBE_IsGenericTypeName(name: String) -> bool {
if String_Eq(name, "StringMapEntry") || String_Eq(name, "StringMapEntry*") { return true; }
if String_Eq(name, "Slice") || String_Eq(name, "Slice*") { return true; }
return false;
}
}
func CBE_StructHasGeneric(st: *HirStruct) -> bool {
func CBE_StructHasGeneric(st: *HirStruct) -> bool {
var fi: int = 0;
while fi < st.fieldCount {
if CBE_IsGenericTypeName(st.fields[fi].typeName) { return true; }
fi = fi + 1;
}
return false;
}
}
func CBE_FuncHasGeneric(f: *HirFunc) -> bool {
func CBE_FuncHasGeneric(f: *HirFunc) -> bool {
var pi: int = 0;
while pi < f.paramCount {
var ptype: String = "";
@@ -1203,15 +1203,15 @@ func CBE_FuncHasGeneric(f: *HirFunc) -> bool {
}
if CBE_IsGenericTypeName(f.retTypeName) { return true; }
return false;
}
}
func CBE_IsArrayTypeName(name: String) -> bool {
func CBE_IsArrayTypeName(name: String) -> bool {
if String_Eq(name, "") { return false; }
if String_StartsWith(name, "Array") { return true; }
return false;
}
}
func CBE_IsPrimitiveTypeName(name: String) -> bool {
func CBE_IsPrimitiveTypeName(name: String) -> bool {
if String_Eq(name, "int") || String_Eq(name, "") { return true; }
if String_Eq(name, "String") { return true; }
if String_Eq(name, "bool") { return true; }
@@ -1229,9 +1229,9 @@ func CBE_IsPrimitiveTypeName(name: String) -> bool {
if String_Eq(name, "void") { return true; }
if String_Eq(name, "size_t") { return true; }
return false;
}
}
func CBE_StructHasValueStructField(st: *HirStruct) -> bool {
func CBE_StructHasValueStructField(st: *HirStruct) -> bool {
var fi: int = 0;
while fi < st.fieldCount {
let ft: String = st.fields[fi].typeName;
@@ -1241,9 +1241,9 @@ func CBE_StructHasValueStructField(st: *HirStruct) -> bool {
fi = fi + 1;
}
return false;
}
}
func CBE_EmitStructDef(cbe: *CEmitter, st: *HirStruct) {
func CBE_EmitStructDef(cbe: *CEmitter, st: *HirStruct) {
if String_Eq(st.name, "") { return; }
StringBuilder_Append(&cbe.sb, "struct ");
StringBuilder_Append(&cbe.sb, st.name);
@@ -1269,9 +1269,9 @@ func CBE_EmitStructDef(cbe: *CEmitter, st: *HirStruct) {
fi = fi + 1;
}
StringBuilder_Append(&cbe.sb, "};\n\n");
}
}
func CBE_LookupFieldType(mod: *HirModule, structName: String, fieldName: String) -> String {
func CBE_LookupFieldType(mod: *HirModule, structName: String, fieldName: String) -> String {
var si: int = 0;
while si < mod.structCount {
if String_Eq(mod.structs[si].name, structName) {
@@ -1288,9 +1288,9 @@ func CBE_LookupFieldType(mod: *HirModule, structName: String, fieldName: String)
si = si + 1;
}
return "";
}
}
func CBE_GetExprTypeName(mod: *HirModule, node: *HirNode) -> String {
func CBE_GetExprTypeName(mod: *HirModule, node: *HirNode) -> String {
if node == null as *HirNode { return ""; }
if node.kind == hVar {
if node.typeName != null as String { return node.typeName; }
@@ -1311,13 +1311,13 @@ func CBE_GetExprTypeName(mod: *HirModule, node: *HirNode) -> String {
}
if node.typeName != null as String { return node.typeName; }
return "";
}
}
// ---------------------------------------------------------------------------
// Generate complete C module
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Generate complete C module
// ---------------------------------------------------------------------------
func CBackend_Generate(mod: *HirModule) -> String {
func CBackend_Generate(mod: *HirModule) -> String {
let cbe: *CEmitter = bux_alloc(sizeof(CEmitter)) as *CEmitter;
cbe.sb = StringBuilder_NewCap(8192);
cbe.indent = 0;
@@ -1662,6 +1662,6 @@ func CBackend_Generate(mod: *HirModule) -> String {
}
return StringBuilder_Build(&cbe.sb);
}
}
}
+350 -73
View File
@@ -2,66 +2,66 @@
// Wires together: Lexer → Parser → Sema → HirLower → CBackend
module Cli {
extern func PrintLine(s: String);
extern func Print(s: String);
extern func bux_read_file(path: String) -> String;
extern func bux_write_file(path: String, content: String) -> bool;
extern func bux_file_exists(path: String) -> int;
extern func bux_dir_exists(path: String) -> int;
extern func bux_getcwd() -> String;
extern func bux_path_join(a: String, b: String) -> String;
extern func bux_path_parent(path: String) -> String;
extern func bux_mkdir_if_needed(path: 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_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;
extern func PrintLine(s: String);
extern func Print(s: String);
extern func bux_read_file(path: String) -> String;
extern func bux_write_file(path: String, content: String) -> bool;
extern func bux_file_exists(path: String) -> int;
extern func bux_dir_exists(path: String) -> int;
extern func bux_getcwd() -> String;
extern func bux_path_join(a: String, b: String) -> String;
extern func bux_path_parent(path: String) -> String;
extern func bux_mkdir_if_needed(path: 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_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);
}
}
func WriteFile(path: String, content: String) -> bool {
func WriteFile(path: String, content: String) -> bool {
return bux_write_file(path, content);
}
}
func FileExists(path: String) -> bool {
func FileExists(path: String) -> bool {
return bux_file_exists(path) != 0;
}
}
func DirExists(path: String) -> bool {
func DirExists(path: String) -> bool {
return bux_dir_exists(path) != 0;
}
}
// ---------------------------------------------------------------------------
// Diagnostic formatting (Rust-style errors with snippets)
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Diagnostic formatting (Rust-style errors with snippets)
// ---------------------------------------------------------------------------
struct Diagnostic {
struct Diagnostic {
message: String;
line: uint32;
column: uint32;
severity: int;
}
}
/* Read a single line from a file (1-based). Returns "" on error or EOF. */
func Diagnostic_GetLine(path: String, lineNum: uint32) -> String {
/* Read a single line from a file (1-based). Returns "" on error or EOF. */
func Diagnostic_GetLine(path: String, lineNum: uint32) -> String {
let content: String = bux_read_file(path);
if String_Eq(content, "") { return ""; }
/* bux_str_split_part uses 0-based index */
return bux_str_split_part(content, "\n", lineNum - 1);
}
}
/* Simple substring check for help hints */
func Diagnostic_MsgContains(msg: String, needle: String) -> bool {
/* Simple substring check for help hints */
func Diagnostic_MsgContains(msg: String, needle: String) -> bool {
return bux_str_contains(msg, needle) != 0;
}
}
/* Actionable help for common error messages */
func Diagnostic_Hint(msg: String) -> String {
/* Actionable help for common error messages */
func Diagnostic_Hint(msg: String) -> String {
if Diagnostic_MsgContains(msg, "cannot assign") {
return "ensure the right-hand side type matches the left-hand side";
}
@@ -84,9 +84,9 @@ func Diagnostic_Hint(msg: String) -> String {
return "rename one of the definitions or remove the duplicate";
}
return "";
}
}
/* Print a diagnostic in Rust-style format:
/* Print a diagnostic in Rust-style format:
* error: <message>
* --> <path>:<line>:<col>
* |
@@ -94,7 +94,7 @@ func Diagnostic_Hint(msg: String) -> String {
* | <spaces>^
* = help: <hint>
*/
func Diagnostic_Print(diag: *Diagnostic, sourcePath: String) {
func Diagnostic_Print(diag: *Diagnostic, sourcePath: String) {
/* Severity prefix */
if diag.severity == 0 {
Print("error: ");
@@ -922,12 +922,184 @@ func Cli_Fetch() -> int {
}
// ---------------------------------------------------------------------------
// Fmt command — format source files
// Doc command — Markdown from /// comments (D.4)
// ---------------------------------------------------------------------------
func Cli_Fmt(dir: String) -> int {
// If dir is a file, format just that file
func Cli_DocIsDeclStart(line: String) -> bool {
if String_StartsWith(line, "func ") { return true; }
if String_StartsWith(line, "pub func ") { return true; }
if String_StartsWith(line, "extern func ") { return true; }
if String_StartsWith(line, "const func ") { return true; }
if String_StartsWith(line, "async func ") { return true; }
if String_StartsWith(line, "struct ") { return true; }
if String_StartsWith(line, "pub struct ") { return true; }
if String_StartsWith(line, "enum ") { return true; }
if String_StartsWith(line, "interface ") { return true; }
if String_StartsWith(line, "module ") { return true; }
if String_StartsWith(line, "type ") { return true; }
return false;
}
func Cli_DocExtractName(line: String) -> String {
// Skip leading keywords
var s: String = line;
if String_StartsWith(s, "pub ") { s = bux_str_slice(s, 4, bux_strlen(s) - 4); }
if String_StartsWith(s, "extern ") { s = bux_str_slice(s, 7, bux_strlen(s) - 7); }
if String_StartsWith(s, "const ") { s = bux_str_slice(s, 6, bux_strlen(s) - 6); }
if String_StartsWith(s, "async ") { s = bux_str_slice(s, 6, bux_strlen(s) - 6); }
if String_StartsWith(s, "func ") { s = bux_str_slice(s, 5, bux_strlen(s) - 5); }
else if String_StartsWith(s, "struct ") { s = bux_str_slice(s, 7, bux_strlen(s) - 7); }
else if String_StartsWith(s, "enum ") { s = bux_str_slice(s, 5, bux_strlen(s) - 5); }
else if String_StartsWith(s, "interface ") { s = bux_str_slice(s, 10, bux_strlen(s) - 10); }
else if String_StartsWith(s, "module ") { s = bux_str_slice(s, 7, bux_strlen(s) - 7); }
else if String_StartsWith(s, "type ") { s = bux_str_slice(s, 5, bux_strlen(s) - 5); }
// Take until space, <, (, {, :, ;
var i: uint = 0;
let n: uint = bux_strlen(s);
while i < n {
let ch: String = bux_str_slice(s, i, 1);
if String_Eq(ch, " ") || String_Eq(ch, "<") || String_Eq(ch, "(") ||
String_Eq(ch, "{") || String_Eq(ch, ":") || String_Eq(ch, ";") {
break;
}
i = i + 1;
}
if i == 0 { return s; }
return bux_str_slice(s, 0, i);
}
func Cli_DocProcessFile(path: String, outSb: *StringBuilder) -> int {
let source: String = ReadFile(path);
if source == null as String || String_Eq(source, "") { return 0; }
var itemCount: int = 0;
var pending: String = "";
var hasPending: bool = false;
let lineCount: uint = bux_str_split_count(source, "\n");
// Drop trailing empty split artifact
var nLines: uint = lineCount;
if nLines > 0 {
let last: String = bux_str_split_part(source, "\n", nLines - 1);
if String_Eq(last, "") { nLines = nLines - 1; }
}
var li: uint = 0;
var wroteHeader: bool = false;
while li < nLines {
let raw: String = bux_str_split_part(source, "\n", li);
let line: String = String_Trim(raw);
if String_StartsWith(line, "///") {
var body: String = bux_str_slice(line, 3, bux_strlen(line) - 3);
if String_StartsWith(body, " ") {
body = bux_str_slice(body, 1, bux_strlen(body) - 1);
}
if hasPending {
pending = String_Concat(pending, String_Concat("\n", body));
} else {
pending = body;
hasPending = true;
}
li = li + 1;
continue;
}
if String_Eq(line, "") || String_StartsWith(line, "@[") {
li = li + 1;
continue;
}
if hasPending && Cli_DocIsDeclStart(line) {
if !wroteHeader {
StringBuilder_Append(outSb, "## `");
StringBuilder_Append(outSb, Cli_FileNameFromPath(path));
StringBuilder_Append(outSb, "`\n\n");
StringBuilder_Append(outSb, "_Source: `");
StringBuilder_Append(outSb, path);
StringBuilder_Append(outSb, "`_\n\n");
wroteHeader = true;
}
let name: String = Cli_DocExtractName(line);
StringBuilder_Append(outSb, "### `");
StringBuilder_Append(outSb, name);
StringBuilder_Append(outSb, "`\n\n");
StringBuilder_Append(outSb, "```bux\n");
StringBuilder_Append(outSb, line);
StringBuilder_Append(outSb, "\n```\n\n");
StringBuilder_Append(outSb, pending);
StringBuilder_Append(outSb, "\n\n");
itemCount = itemCount + 1;
hasPending = false;
pending = "";
li = li + 1;
continue;
}
if String_StartsWith(line, "//") {
li = li + 1;
continue;
}
// Other code clears pending
hasPending = false;
pending = "";
li = li + 1;
}
return itemCount;
}
func Cli_Doc(dir: String, outPath: String) -> int {
var sb: StringBuilder = StringBuilder_NewCap(16384);
StringBuilder_Append(&sb, "# API Reference\n\n");
StringBuilder_Append(&sb, "Generated by `bux doc` from `///` documentation comments.\n\n");
var total: int = 0;
if FileExists(dir) {
total = total + Cli_DocProcessFile(dir, &sb);
} else if DirExists(dir) {
var fileCount: int = 0;
let files: *String = bux_list_dir(dir, ".bux", &fileCount);
var i: int = 0;
while i < fileCount {
total = total + Cli_DocProcessFile(files[i], &sb);
i = i + 1;
}
} else {
Print("Error: path not found: ");
PrintLine(dir);
StringBuilder_Free(&sb);
return 1;
}
let md: String = StringBuilder_Build(&sb);
StringBuilder_Free(&sb);
if String_Eq(outPath, "") {
Print(md);
} else {
if !WriteFile(outPath, md) {
Print("Error: cannot write ");
PrintLine(outPath);
return 1;
}
Print("Wrote ");
PrintInt(total as int64);
Print(" documented items → ");
PrintLine(outPath);
}
if total == 0 {
PrintLine("warning: no /// documented declarations found");
}
return 0;
}
// ---------------------------------------------------------------------------
// Fmt command — format source files (write or --check for CI)
// ---------------------------------------------------------------------------
func Cli_Fmt(dir: String, checkOnly: bool) -> int {
// If dir is a file, format/check just that file
if FileExists(dir) {
if checkOnly {
if Fmt_CheckFile(dir) == 0 {
Print(" ok ");
PrintLine(dir);
return 0;
}
Print(" would reformat ");
PrintLine(dir);
return 1;
}
Print("Formatting ");
PrintLine(dir);
if Fmt_FormatFile(dir) {
@@ -936,8 +1108,12 @@ func Cli_Fmt(dir: String) -> int {
}
return 1;
}
// Otherwise format all .bux files in directory
// Otherwise format/check all .bux files in directory
if checkOnly {
Print("Checking format in ");
} else {
Print("Formatting ");
}
PrintLine(dir);
var fileCount: int = 0;
let files: *String = bux_list_dir(dir, ".bux", &fileCount);
@@ -947,14 +1123,34 @@ func Cli_Fmt(dir: String) -> int {
}
var i: int = 0;
var okCount: int = 0;
var changeCount: int = 0;
while i < fileCount {
if checkOnly {
if Fmt_CheckFile(files[i]) == 0 {
okCount = okCount + 1;
} else {
Print(" would reformat ");
PrintLine(files[i]);
changeCount = changeCount + 1;
}
} else {
Print(" ");
PrintLine(files[i]);
if Fmt_FormatFile(files[i]) {
okCount = okCount + 1;
}
}
i = i + 1;
}
if checkOnly {
Print("fmt --check: ");
PrintInt(changeCount as int64);
Print(" would reformat, ");
PrintInt(okCount as int64);
PrintLine(" ok");
if changeCount > 0 { return 1; }
return 0;
}
Print("Formatted "); PrintInt(okCount as int64); Print("/"); PrintInt(fileCount as int64); PrintLine(" files");
return 0;
}
@@ -985,10 +1181,17 @@ func Cli_StripExtension(name: String) -> String {
return name;
}
func Cli_Test(projectDir: String) -> int {
func Cli_Test(projectDir: String, filter: String) -> int {
Print("Testing project: ");
PrintLine(projectDir);
// Build and run the project's own Main first
if !String_Eq(filter, "") {
Print("Filter: ");
PrintLine(filter);
}
// Without --filter, build and run the project's own Main first.
// With --filter, only run matching tests/*.bux files.
if String_Eq(filter, "") {
let mainRc: int = Cli_BuildProject(projectDir, "", false);
if mainRc != 0 {
PrintLine("Main test build failed");
@@ -1011,6 +1214,7 @@ func Cli_Test(projectDir: String) -> int {
return mainResult;
}
PrintLine("Main tests passed");
}
// Propagate the project's stdlib to temp test packages.
let stdlibDir: String = Cli_FindStdlibDir(projectDir);
@@ -1031,15 +1235,31 @@ func Cli_Test(projectDir: String) -> int {
return 0;
}
PrintLine("┌──────────────────────────────┬────────┐");
PrintLine("│ Test │ Status │");
PrintLine("├──────────────────────────────┼────────┤");
var passed: int = 0;
var failed: int = 0;
var skipped: 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: ");
// --filter: only run tests whose name contains the filter substring
if !String_Eq(filter, "") {
if !String_Contains(testName, filter) {
skipped = skipped + 1;
i = i + 1;
continue;
}
}
Print("│ ");
Print(testName);
// Pad status column roughly (name may be long)
Print(" ... ");
// Create temp package for this test file
@@ -1050,14 +1270,14 @@ func Cli_Test(projectDir: String) -> int {
let source: String = ReadFile(testPath);
if String_Eq(source, "") {
PrintLine("FAIL (cannot read test file)");
PrintLine("FAIL ");
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)");
PrintLine("FAIL ");
failed = failed + 1;
i = i + 1;
continue;
@@ -1066,7 +1286,7 @@ func Cli_Test(projectDir: String) -> int {
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)");
PrintLine("FAIL ");
failed = failed + 1;
i = i + 1;
continue;
@@ -1091,36 +1311,48 @@ func Cli_Test(projectDir: String) -> int {
let buildRc: int = Cli_BuildProject(tmpDir, "", false);
if buildRc != 0 {
PrintLine("FAIL (build error)");
PrintLine("FAIL ");
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)");
PrintLine("FAIL ");
failed = failed + 1;
i = i + 1;
continue;
}
let runRc: int = bux_system(testBin);
if runRc == 0 {
PrintLine("PASS");
PrintLine("PASS");
passed = passed + 1;
} else {
Print("FAIL (exit ");
PrintInt(runRc as int64);
PrintLine(")");
Print("FAIL ");
PrintLine("");
failed = failed + 1;
}
i = i + 1;
}
Print("Tests: ");
PrintLine("└──────────────────────────────┴────────┘");
Print("Results: ");
PrintInt(passed as int64);
Print(" passed, ");
PrintInt(failed as int64);
PrintLine(" failed");
Print(" failed");
if skipped > 0 {
Print(", ");
PrintInt(skipped as int64);
Print(" skipped");
}
PrintLine("");
if !String_Eq(filter, "") && passed == 0 && failed == 0 {
Print("No tests matching filter '");
Print(filter);
PrintLine("'");
return 1;
}
if failed > 0 { return 1; }
return 0;
}
@@ -1493,7 +1725,10 @@ func Cli_Run(args: *String, argCount: int) -> int {
if argCount < 2 {
PrintLine("Bux Self-Hosting Compiler v0.2.0");
PrintLine("Usage: buxc <command> [args]");
PrintLine("Commands: build, check, new, init, add, remove, fetch, fmt, test, run, project, help, version");
PrintLine("Commands: build, check, new, init, add, remove, fetch, fmt, doc, test, run, project, help, version");
PrintLine(" test --filter <name> Only run tests/*.bux whose name contains <name>");
PrintLine(" fmt --check [path] Exit 1 if any file would be reformatted (CI)");
PrintLine(" doc --out file.md [path] API docs from /// comments");
return 0;
}
@@ -1506,14 +1741,16 @@ func Cli_Run(args: *String, argCount: int) -> int {
if String_Eq(cmd, "help") || String_Eq(cmd, "--help") || String_Eq(cmd, "-h") {
PrintLine("Bux Self-Hosting Compiler v0.2.0");
PrintLine("Usage: buxc <command> [args]");
PrintLine("Commands: build, check, new, init, add, remove, fetch, fmt, test, run, project, help, version");
PrintLine("Commands: build, check, new, init, add, remove, fetch, fmt, doc, test, run, project, help, version");
PrintLine(" test --filter <name> Only run tests/*.bux whose name contains <name>");
PrintLine(" fmt --check [path] Exit 1 if any file would be reformatted (CI)");
PrintLine(" doc --out file.md [path] API docs from /// comments");
PrintLine("Pipeline modules:");
PrintLine(" Lexer ✅ 695 lines");
PrintLine(" Parser ✅ 1004 lines");
PrintLine(" Sema ✅ 393 lines");
PrintLine(" HirLower ✅ 307 lines");
PrintLine(" CBackend ✅ 585 lines");
PrintLine(" Total: 3830 lines of Bux");
PrintLine(" Lexer ✅");
PrintLine(" Parser ✅");
PrintLine(" Sema ✅");
PrintLine(" HirLower ✅");
PrintLine(" CBackend ✅");
return 0;
}
@@ -1550,9 +1787,36 @@ func Cli_Run(args: *String, argCount: int) -> int {
}
if String_Eq(cmd, "fmt") {
let dir: String = ".";
if argCount >= 3 { dir = args[2]; }
return Cli_Fmt(dir);
var dir: String = ".";
var checkOnly: bool = false;
var fi: int = 2;
while fi < argCount {
if String_Eq(args[fi], "--check") {
checkOnly = true;
} else {
dir = args[fi];
}
fi = fi + 1;
}
return Cli_Fmt(dir, checkOnly);
}
if String_Eq(cmd, "doc") {
var dir: String = "lib";
var outPath: String = "";
var di: int = 2;
while di < argCount {
if String_Eq(args[di], "--out") && di + 1 < argCount {
outPath = args[di + 1];
di = di + 1;
} else if String_StartsWith(args[di], "--out=") {
outPath = bux_str_slice(args[di], 6, bux_strlen(args[di]) - 6);
} else if !String_StartsWith(args[di], "-") {
dir = args[di];
}
di = di + 1;
}
return Cli_Doc(dir, outPath);
}
if String_Eq(cmd, "check") {
@@ -1578,9 +1842,22 @@ func Cli_Run(args: *String, argCount: int) -> int {
}
if String_Eq(cmd, "test") {
let dir: String = ".";
if argCount >= 3 { dir = args[2]; }
return Cli_Test(dir);
var dir: String = ".";
var filter: String = "";
var ti: int = 2;
while ti < argCount {
if String_Eq(args[ti], "--filter") && ti + 1 < argCount {
filter = args[ti + 1];
ti = ti + 1;
} else if String_StartsWith(args[ti], "--filter=") {
// --filter=name form
filter = bux_str_slice(args[ti], 9, bux_strlen(args[ti]) - 9);
} else if !String_StartsWith(args[ti], "-") {
dir = args[ti];
}
ti = ti + 1;
}
return Cli_Test(dir, filter);
}
if String_Eq(cmd, "run") {
+51 -21
View File
@@ -1,12 +1,12 @@
// fmt.bux — Bux source code formatter (indentation-based, preserves line structure)
module Fmt {
extern func bux_read_file(path: String) -> String;
extern func bux_write_file(path: String, content: String) -> bool;
extern func bux_strlen(s: String) -> uint;
extern func bux_read_file(path: String) -> String;
extern func bux_write_file(path: String, content: String) -> bool;
extern func bux_strlen(s: String) -> uint;
// Count leading spaces on a line
func Fmt_CountLeadingSpaces(line: String) -> int {
// Count leading spaces on a line
func Fmt_CountLeadingSpaces(line: String) -> int {
var count: int = 0;
while count < 256 {
let c: int = line[count] as int;
@@ -15,10 +15,10 @@ func Fmt_CountLeadingSpaces(line: String) -> int {
count = count + 1;
}
return count;
}
}
// Skip whitespace from start of line, return rest
func Fmt_TrimLeft(line: String) -> String {
// Skip whitespace from start of line, return rest
func Fmt_TrimLeft(line: String) -> String {
var i: int = 0;
while i < 256 {
let c: int = line[i] as int;
@@ -36,10 +36,10 @@ func Fmt_TrimLeft(line: String) -> String {
}
if i >= len { return ""; }
return bux_str_slice(line, i as uint, (len - i) as uint);
}
}
// Check if char at position is inside a string or comment (simplified)
func Fmt_IsInStringOrComment(line: String, pos: int) -> bool {
// Check if char at position is inside a string or comment (simplified)
func Fmt_IsInStringOrComment(line: String, pos: int) -> bool {
var inString: bool = false;
var inChar: bool = false;
var inComment: bool = false;
@@ -55,10 +55,10 @@ func Fmt_IsInStringOrComment(line: String, pos: int) -> bool {
i = i + 1;
}
return inString || inChar || inComment;
}
}
// Count brace depth change on a line, skipping strings/comments
func Fmt_CountBraceDelta(line: String) -> int {
// Count brace depth change on a line, skipping strings/comments
func Fmt_CountBraceDelta(line: String) -> int {
var delta: int = 0;
var i: int = 0;
while i < 256 {
@@ -71,19 +71,28 @@ func Fmt_CountBraceDelta(line: String) -> int {
i = i + 1;
}
return delta;
}
}
func Fmt_FormatSource(source: String) -> String {
func Fmt_FormatSource(source: String) -> String {
let sb: StringBuilder = StringBuilder_NewCap(8192);
var indent: int = 0;
var i: uint = 0;
let lineCount: uint = bux_str_split_count(source, "\n");
var lineCount: uint = bux_str_split_count(source, "\n");
// Trailing "\n" yields a final empty part (split artifact). Drop it so
// re-formatting is idempotent and does not accumulate blank lines.
if lineCount > 0 {
let last: String = bux_str_split_part(source, "\n", lineCount - 1);
if String_Eq(last, "") {
lineCount = lineCount - 1;
}
}
while i < lineCount {
let line: String = bux_str_split_part(source, "\n", i);
let trimmed: String = Fmt_TrimLeft(line);
// Skip empty lines
// Empty line (intentional blank) — keep a single newline
if String_Eq(trimmed, "") {
StringBuilder_Append(&sb, "\n");
i = i + 1;
@@ -125,14 +134,35 @@ func Fmt_FormatSource(source: String) -> String {
let result: String = StringBuilder_Build(&sb);
StringBuilder_Free(&sb);
return result;
}
}
func Fmt_FormatFile(path: String) -> bool {
func Fmt_FormatFile(path: String) -> bool {
let source: String = bux_read_file(path);
if source == null as String || String_Eq(source, "") { return false; }
let formatted: String = Fmt_FormatSource(source);
if String_Eq(formatted, "") { return false; }
return bux_write_file(path, formatted);
}
}
// Returns true if formatting would change the file (CI --check).
func Fmt_WouldChange(path: String) -> bool {
let source: String = bux_read_file(path);
if source == null as String { return false; }
let formatted: String = Fmt_FormatSource(source);
return !String_Eq(formatted, source);
}
// Check a file without writing. Returns 0 if clean, 1 if would reformat / error.
func Fmt_CheckFile(path: String) -> int {
let source: String = bux_read_file(path);
if source == null as String {
return 1;
}
let formatted: String = Fmt_FormatSource(source);
if String_Eq(formatted, source) {
return 0;
}
return 1;
}
}
+96 -96
View File
@@ -1,55 +1,55 @@
// hir.bux — HIR (High-level Intermediate Representation) node types
module Hir {
// HIR node kinds
const hLit: int = 0;
const hVar: int = 1;
const hSelf: int = 2;
const hUnary: int = 3;
const hBinary: int = 4;
const hAssign: int = 5;
const hIf: int = 6;
const hWhile: int = 7;
const hLoop: int = 8;
const hBreak: int = 9;
const hContinue: int = 10;
const hReturn: int = 11;
const hAlloca: int = 12;
const hLoad: int = 13;
const hStore: int = 14;
const hFieldPtr: int = 15;
const hFieldAccess: int = 16;
const hArrowField: int = 17;
const hIndexPtr: int = 18;
const hCall: int = 32;
const hCallIndirect: int = 33;
const hCast: int = 34;
const hIs: int = 35;
const hSizeOf: int = 36;
const hBlock: int = 37;
const hStructInit: int = 38;
const hSliceInit: int = 39;
const hRange: int = 40;
const hTupleInit: int = 41;
const hMatch: int = 42;
const hSpawn: int = 43;
const hAwait: int = 44;
const hDefer: int = 45;
// HIR node kinds
const hLit: int = 0;
const hVar: int = 1;
const hSelf: int = 2;
const hUnary: int = 3;
const hBinary: int = 4;
const hAssign: int = 5;
const hIf: int = 6;
const hWhile: int = 7;
const hLoop: int = 8;
const hBreak: int = 9;
const hContinue: int = 10;
const hReturn: int = 11;
const hAlloca: int = 12;
const hLoad: int = 13;
const hStore: int = 14;
const hFieldPtr: int = 15;
const hFieldAccess: int = 16;
const hArrowField: int = 17;
const hIndexPtr: int = 18;
const hCall: int = 32;
const hCallIndirect: int = 33;
const hCast: int = 34;
const hIs: int = 35;
const hSizeOf: int = 36;
const hBlock: int = 37;
const hStructInit: int = 38;
const hSliceInit: int = 39;
const hRange: int = 40;
const hTupleInit: int = 41;
const hMatch: int = 42;
const hSpawn: int = 43;
const hAwait: int = 44;
const hDefer: int = 45;
// ---------------------------------------------------------------------------
// HirArgList — linked list for call arguments beyond 2
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// HirArgList — linked list for call arguments beyond 2
// ---------------------------------------------------------------------------
struct HirArgList {
struct HirArgList {
node: *HirNode,
next: *HirArgList,
}
}
// ---------------------------------------------------------------------------
// HirNode — unified struct with tagged union pattern
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// HirNode — unified struct with tagged union pattern
// ---------------------------------------------------------------------------
struct HirNode {
struct HirNode {
kind: int;
line: uint32;
column: uint32;
@@ -66,19 +66,19 @@ struct HirNode {
// Extra data pointer (for children arrays, field lists, etc.)
extraData: *void;
extraCount: int;
}
}
// ---------------------------------------------------------------------------
// HirFunc
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// HirFunc
// ---------------------------------------------------------------------------
struct HirParam {
struct HirParam {
name: String;
typeKind: int;
typeName: String;
}
}
struct HirFunc {
struct HirFunc {
name: String;
paramCount: int;
param0: *HirParam;
@@ -115,13 +115,13 @@ struct HirFunc {
envStructName: String;
envInstanceName: String;
checkedFunc: bool;
}
}
// ---------------------------------------------------------------------------
// HirEnumVariant
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// HirEnumVariant
// ---------------------------------------------------------------------------
struct HirEnumVariant {
struct HirEnumVariant {
name: String;
fieldCount: int;
fieldType0: int;
@@ -130,35 +130,35 @@ struct HirEnumVariant {
fieldType1: int;
fieldName1: String;
fieldTypeName1: String;
}
}
// ---------------------------------------------------------------------------
// HirModule
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// HirModule
// ---------------------------------------------------------------------------
struct HirStructField {
struct HirStructField {
name: String;
typeName: String;
}
}
struct HirStruct {
struct HirStruct {
name: String;
fieldCount: int;
fields: *HirStructField;
}
}
struct HirConst {
struct HirConst {
name: String;
value: int;
}
}
struct HirEnum {
struct HirEnum {
name: String;
variantCount: int;
variants: *HirEnumVariant;
}
}
struct HirModule {
struct HirModule {
funcCount: int;
funcs: *HirFunc;
externCount: int;
@@ -169,88 +169,88 @@ struct HirModule {
enums: *HirEnum;
constCount: int;
consts: *HirConst;
}
}
// ---------------------------------------------------------------------------
// Constructor helpers
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Constructor helpers
// ---------------------------------------------------------------------------
func Hir_MakeNode(kind: int, line: uint32, column: uint32) -> HirNode {
func Hir_MakeNode(kind: int, line: uint32, column: uint32) -> HirNode {
return HirNode { kind: kind, line: line, column: column,
typeKind: 0, typeName: "",
strValue: "", intValue: 0, boolValue: false,
child1: null as *HirNode, child2: null as *HirNode, child3: null as *HirNode,
extraData: null as *void, extraCount: 0 };
}
}
func Hir_MakeLit(tokKind: int, tokText: String, line: uint32, col: uint32) -> HirNode {
func Hir_MakeLit(tokKind: int, tokText: String, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hLit, line, col);
n.intValue = tokKind;
n.strValue = tokText;
return n;
}
}
func Hir_MakeVar(name: String, line: uint32, col: uint32) -> HirNode {
func Hir_MakeVar(name: String, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hVar, line, col);
n.strValue = name;
return n;
}
}
func Hir_MakeBinary(op: int, left: *HirNode, right: *HirNode, line: uint32, col: uint32) -> HirNode {
func Hir_MakeBinary(op: int, left: *HirNode, right: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hBinary, line, col);
n.intValue = op;
n.child1 = left;
n.child2 = right;
return n;
}
}
func Hir_MakeCall(callee: String, line: uint32, col: uint32) -> HirNode {
func Hir_MakeCall(callee: String, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hCall, line, col);
n.strValue = callee;
return n;
}
}
func Hir_MakeReturn(value: *HirNode, line: uint32, col: uint32) -> HirNode {
func Hir_MakeReturn(value: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hReturn, line, col);
n.child1 = value;
return n;
}
}
func Hir_MakeBlock(line: uint32, col: uint32) -> HirNode {
func Hir_MakeBlock(line: uint32, col: uint32) -> HirNode {
return Hir_MakeNode(hBlock, line, col);
}
}
func Hir_MakeIf(cond: *HirNode, thenBody: *HirNode, elseBody: *HirNode, line: uint32, col: uint32) -> HirNode {
func Hir_MakeIf(cond: *HirNode, thenBody: *HirNode, elseBody: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hIf, line, col);
n.child1 = cond;
n.child2 = thenBody;
n.child3 = elseBody;
return n;
}
}
func Hir_MakeWhile(cond: *HirNode, body: *HirNode, line: uint32, col: uint32) -> HirNode {
func Hir_MakeWhile(cond: *HirNode, body: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hWhile, line, col);
n.child1 = cond;
n.child2 = body;
return n;
}
}
func Hir_MakeAlloca(name: String, line: uint32, col: uint32) -> HirNode {
func Hir_MakeAlloca(name: String, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hAlloca, line, col);
n.strValue = name;
return n;
}
}
func Hir_MakeLoad(ptr: *HirNode, line: uint32, col: uint32) -> HirNode {
func Hir_MakeLoad(ptr: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hLoad, line, col);
n.child1 = ptr;
return n;
}
}
func Hir_MakeStore(ptr: *HirNode, value: *HirNode, line: uint32, col: uint32) -> HirNode {
func Hir_MakeStore(ptr: *HirNode, value: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hStore, line, col);
n.child1 = ptr;
n.child2 = value;
return n;
}
}
}
+169 -169
View File
@@ -2,13 +2,13 @@
// Transforms the typed AST into a lower-level IR suitable for code generation.
module HirLower {
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
extern func bux_strlen(s: String) -> uint;
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
extern func bux_strlen(s: String) -> uint;
// ---------------------------------------------------------------------------
// Lowering context
// ---------------------------------------------------------------------------
struct LowerCtx {
// ---------------------------------------------------------------------------
// Lowering context
// ---------------------------------------------------------------------------
struct LowerCtx {
module: *Module,
scope: *Scope,
funcs: *HirFunc,
@@ -52,9 +52,9 @@ struct LowerCtx {
patMapTo6: String,
patMapFrom7: String,
patMapTo7: String,
}
}
func Lcx_PatLookup(ctx: *LowerCtx, src: String) -> String {
func Lcx_PatLookup(ctx: *LowerCtx, src: String) -> String {
// Most recent rename wins (scan from end)
var i: int = ctx.patMapCount - 1;
while i >= 0 {
@@ -72,9 +72,9 @@ func Lcx_PatLookup(ctx: *LowerCtx, src: String) -> String {
i = i - 1;
}
return "";
}
}
func Lcx_PatPush(ctx: *LowerCtx, src: String, dst: String) {
func Lcx_PatPush(ctx: *LowerCtx, src: String, dst: String) {
if ctx.patMapCount >= 8 { return; }
let i: int = ctx.patMapCount;
if i == 0 { ctx.patMapFrom0 = src; ctx.patMapTo0 = dst; }
@@ -86,18 +86,18 @@ func Lcx_PatPush(ctx: *LowerCtx, src: String, dst: String) {
else if i == 6 { ctx.patMapFrom6 = src; ctx.patMapTo6 = dst; }
else if i == 7 { ctx.patMapFrom7 = src; ctx.patMapTo7 = dst; }
ctx.patMapCount = ctx.patMapCount + 1;
}
}
func Lcx_FreshPatName(ctx: *LowerCtx, src: String) -> String {
func Lcx_FreshPatName(ctx: *LowerCtx, src: String) -> String {
ctx.varCounter = ctx.varCounter + 1;
var safe: String = src;
if String_Eq(src, "") || String_Eq(src, "_") { safe = "x"; }
return String_Concat(String_Concat("__p", String_FromInt(ctx.varCounter as int64)),
String_Concat("_", safe));
}
}
// Alloca unique C local + store + scope define + rename map for pattern binding.
func Lcx_BindPatIdent(ctx: *LowerCtx, src: String, ty: String, value: *HirNode,
// Alloca unique C local + store + scope define + rename map for pattern binding.
func Lcx_BindPatIdent(ctx: *LowerCtx, src: String, ty: String, value: *HirNode,
line: uint32, col: uint32) -> *HirNode {
if String_Eq(src, "") || String_Eq(src, "_") { return null as *HirNode; }
let cName: String = Lcx_FreshPatName(ctx, src);
@@ -129,19 +129,19 @@ func Lcx_BindPatIdent(ctx: *LowerCtx, src: String, ty: String, value: *HirNode,
bsym.decl = null as *Decl;
discard Scope_Define(ctx.scope, bsym);
return alloca;
}
}
// ---------------------------------------------------------------------------
// TypeExpr.kind → Type.kind resolver
// TypeExpr.kind values (0-5) overlap with Type.kind values — this
// resolves the correct Type.kind for codegen.
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// TypeExpr.kind → Type.kind resolver
// TypeExpr.kind values (0-5) overlap with Type.kind values — this
// resolves the correct Type.kind for codegen.
// ---------------------------------------------------------------------------
func Lcx_ResolveTypeKindFromName(name: String) -> int {
func Lcx_ResolveTypeKindFromName(name: String) -> int {
return Type_FromName(name);
}
}
func Lcx_TypeKindToName(kind: int) -> String {
func Lcx_TypeKindToName(kind: int) -> String {
if kind == tyVoid { return "void"; }
if kind == tyBool || kind == tyBool8 || kind == tyBool16 || kind == tyBool32 { return "bool"; }
if kind == tyChar8 { return "char"; }
@@ -162,9 +162,9 @@ func Lcx_TypeKindToName(kind: int) -> String {
if kind == tyFloat64 { return "float64"; }
if kind == tyPointer { return "void*"; }
return "int";
}
}
func Lcx_ResolveTypeKind(te: *TypeExpr) -> int {
func Lcx_ResolveTypeKind(te: *TypeExpr) -> int {
if te == null as *TypeExpr { return tyUnknown; }
if te.kind == tekPointer || te.kind == tekRef || te.kind == tekMutRef { return tyPointer; }
@@ -173,13 +173,13 @@ func Lcx_ResolveTypeKind(te: *TypeExpr) -> int {
if te.kind == tekFunc { return tyFunc; }
return Lcx_ResolveTypeKindFromName(te.typeName);
}
}
// ---------------------------------------------------------------------------
// Type substitution for generic monomorphization
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Type substitution for generic monomorphization
// ---------------------------------------------------------------------------
func Lcx_SubstituteType(ctx: *LowerCtx, te: *TypeExpr) -> *TypeExpr {
func Lcx_SubstituteType(ctx: *LowerCtx, te: *TypeExpr) -> *TypeExpr {
if te == null as *TypeExpr { return te; }
// Generic named type with type args: check if concrete or parametric
@@ -277,10 +277,10 @@ func Lcx_SubstituteType(ctx: *LowerCtx, te: *TypeExpr) -> *TypeExpr {
}
return te;
}
}
// Sanitize a C type fragment for use inside BuxFn_* mangled names
func Lcx_SanitizeFatPart(s: String) -> String {
// Sanitize a C type fragment for use inside BuxFn_* mangled names
func Lcx_SanitizeFatPart(s: String) -> String {
var r: String = s;
if String_Eq(r, "String") || String_Eq(r, "str") || String_Eq(r, "const char*") {
return "cstr";
@@ -294,9 +294,9 @@ func Lcx_SanitizeFatPart(s: String) -> String {
r = String_ReplaceAll(r, ",", "_");
if String_Eq(r, "") { return "int"; }
return r;
}
}
func Lcx_TypeExprFatPart(te: *TypeExpr) -> String {
func Lcx_TypeExprFatPart(te: *TypeExpr) -> String {
if te == null as *TypeExpr { return "void"; }
if te.kind == tekPointer && te.pointerPointee != null as *TypeExpr {
return Lcx_SanitizeFatPart(String_Concat(te.pointerPointee.typeName, "Ptr"));
@@ -307,11 +307,11 @@ func Lcx_TypeExprFatPart(te: *TypeExpr) -> String {
var n: String = te.typeName;
if String_Eq(n, "") { n = "int"; }
return Lcx_SanitizeFatPart(n);
}
}
// Fat function-pointer type name: BuxFn_<ret>_<p0>_<p1>...
// Enables multi-instance closures (code + env).
func Lcx_BuildFuncTypeName(te: *TypeExpr) -> String {
// Fat function-pointer type name: BuxFn_<ret>_<p0>_<p1>...
// Enables multi-instance closures (code + env).
func Lcx_BuildFuncTypeName(te: *TypeExpr) -> String {
if te == null as *TypeExpr || te.kind != tekFunc {
return "BuxFn_void_void";
}
@@ -332,13 +332,13 @@ func Lcx_BuildFuncTypeName(te: *TypeExpr) -> String {
result = String_Concat(result, "_void");
}
return result;
}
}
// ---------------------------------------------------------------------------
// Generic monomorphization helpers
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Generic monomorphization helpers
// ---------------------------------------------------------------------------
func Lcx_FindGenericFunc(ctx: *LowerCtx, name: String) -> *Decl {
func Lcx_FindGenericFunc(ctx: *LowerCtx, name: String) -> *Decl {
var i: int = 0;
while i < ctx.genFuncCount {
if String_Eq(ctx.genFuncs[i].strValue, name) {
@@ -347,9 +347,9 @@ func Lcx_FindGenericFunc(ctx: *LowerCtx, name: String) -> *Decl {
i = i + 1;
}
return null as *Decl;
}
}
func Lcx_FindGenericStruct(ctx: *LowerCtx, name: String) -> *Decl {
func Lcx_FindGenericStruct(ctx: *LowerCtx, name: String) -> *Decl {
var i: int = 0;
while i < ctx.genStructCount {
if String_Eq(ctx.genStructs[i].strValue, name) {
@@ -358,10 +358,10 @@ func Lcx_FindGenericStruct(ctx: *LowerCtx, name: String) -> *Decl {
i = i + 1;
}
return null as *Decl;
}
}
// Extract element type from mangled collection name: Array_int → int, Iter_String → String
func Lcx_ExtractElemFromName(typeName: String) -> String {
// Extract element type from mangled collection name: Array_int → int, Iter_String → String
func Lcx_ExtractElemFromName(typeName: String) -> String {
if String_Eq(typeName, "") { return ""; }
let len: uint = bux_strlen(typeName);
// "Array_" prefix (6 chars)
@@ -379,9 +379,9 @@ func Lcx_ExtractElemFromName(typeName: String) -> String {
}
}
return "";
}
}
func Lcx_MangleName(base: String, typeArg0: String, typeArg1: String, typeArgCount: int) -> String {
func Lcx_MangleName(base: String, typeArg0: String, typeArg1: String, typeArgCount: int) -> String {
let r: String = String_Concat(base, "_");
r = String_Concat(r, typeArg0);
if typeArgCount > 1 && !String_Eq(typeArg1, "") {
@@ -389,9 +389,9 @@ func Lcx_MangleName(base: String, typeArg0: String, typeArg1: String, typeArgCou
r = String_Concat(r, typeArg1);
}
return r;
}
}
func Lcx_GenerateStructInstance(ctx: *LowerCtx, genDecl: *Decl, typeArg0: String, typeArg1: String, typeArgCount: int) -> String {
func Lcx_GenerateStructInstance(ctx: *LowerCtx, genDecl: *Decl, typeArg0: String, typeArg1: String, typeArgCount: int) -> String {
if String_Eq(genDecl.strValue, "") { return ""; }
let mangled: String = Lcx_MangleName(genDecl.strValue, typeArg0, typeArg1, typeArgCount);
@@ -448,9 +448,9 @@ func Lcx_GenerateStructInstance(ctx: *LowerCtx, genDecl: *Decl, typeArg0: String
ctx.substArg1 = oldArg1;
return mangled;
}
}
func Lcx_GenerateFuncInstance(ctx: *LowerCtx, genDecl: *Decl, typeArg0: String, typeArg1: String, typeArgCount: int) -> String {
func Lcx_GenerateFuncInstance(ctx: *LowerCtx, genDecl: *Decl, typeArg0: String, typeArg1: String, typeArgCount: int) -> String {
let mangled: String = Lcx_MangleName(genDecl.strValue, typeArg0, typeArg1, typeArgCount);
// Check if already generated (linear search in ctx.funcs)
@@ -489,11 +489,11 @@ func Lcx_GenerateFuncInstance(ctx: *LowerCtx, genDecl: *Decl, typeArg0: String,
ctx.substArg1 = oldArg1;
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 {
// 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, "") {
@@ -509,13 +509,13 @@ func Lcx_StripTypeArgs(typeName: String, typeArg0: String, typeArg1: String, typ
}
}
return typeName;
}
}
// ---------------------------------------------------------------------------
// Array type helpers for bounds-checking desugaring
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Array type helpers for bounds-checking desugaring
// ---------------------------------------------------------------------------
func Lcx_IsArrayTypeExpr(te: *TypeExpr) -> bool {
func Lcx_IsArrayTypeExpr(te: *TypeExpr) -> bool {
if te == null as *TypeExpr { return false; }
if te.kind == tekPointer && te.pointerPointee != null as *TypeExpr {
te = te.pointerPointee;
@@ -528,9 +528,9 @@ func Lcx_IsArrayTypeExpr(te: *TypeExpr) -> bool {
}
}
return false;
}
}
func Lcx_GetArrayElemType(te: *TypeExpr) -> String {
func Lcx_GetArrayElemType(te: *TypeExpr) -> String {
if te == null as *TypeExpr { return ""; }
if te.kind == tekPointer && te.pointerPointee != null as *TypeExpr {
te = te.pointerPointee;
@@ -549,13 +549,13 @@ func Lcx_GetArrayElemType(te: *TypeExpr) -> String {
}
}
return "";
}
}
// ---------------------------------------------------------------------------
// Match lowering helpers
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Match lowering helpers
// ---------------------------------------------------------------------------
func Lcx_EnumHasData(ctx: *LowerCtx, enumName: String) -> bool {
func Lcx_EnumHasData(ctx: *LowerCtx, enumName: String) -> bool {
if String_Eq(enumName, "") { return false; }
let sym: Symbol = Scope_Lookup(ctx.scope, enumName);
if sym.decl == null as *Decl || sym.decl.kind != dkEnum { return false; }
@@ -569,9 +569,9 @@ func Lcx_EnumHasData(ctx: *LowerCtx, enumName: String) -> bool {
if sym.decl.variantCount > 7 && sym.decl.variant7.fieldCount > 0 { return true; }
if sym.decl.variantCount > 8 && sym.decl.variant8.fieldCount > 0 { return true; }
return false;
}
}
func Lcx_MakeLitHir(litKind: int, litText: String, line: uint32, col: uint32) -> *HirNode {
func Lcx_MakeLitHir(litKind: int, litText: String, line: uint32, col: uint32) -> *HirNode {
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
n.kind = hLit;
n.line = line;
@@ -579,9 +579,9 @@ func Lcx_MakeLitHir(litKind: int, litText: String, line: uint32, col: uint32) ->
n.intValue = litKind;
n.strValue = litText;
return n;
}
}
func Lcx_MakeBinHir(op: int, left: *HirNode, right: *HirNode, line: uint32, col: uint32) -> *HirNode {
func Lcx_MakeBinHir(op: int, left: *HirNode, right: *HirNode, line: uint32, col: uint32) -> *HirNode {
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
n.kind = hBinary;
n.line = line;
@@ -590,14 +590,14 @@ func Lcx_MakeBinHir(op: int, left: *HirNode, right: *HirNode, line: uint32, col:
n.child1 = left;
n.child2 = right;
return n;
}
}
func Lcx_MakeTrueHir(line: uint32, col: uint32) -> *HirNode {
func Lcx_MakeTrueHir(line: uint32, col: uint32) -> *HirNode {
return Lcx_MakeLitHir(tkBoolLiteral, "true", line, col);
}
}
// Build condition HirNode for a match pattern. Returns null = always-true.
func Lcx_PatternCond(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
// Build condition HirNode for a match pattern. Returns null = always-true.
func Lcx_PatternCond(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
subjectEnumName: String, subjectHasData: bool,
line: uint32, col: uint32) -> *HirNode {
if pat == null as *Pattern { return null as *HirNode; }
@@ -681,11 +681,11 @@ func Lcx_PatternCond(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
}
return null as *HirNode;
}
}
// Emit binding stmts for pattern payload: Option::Some(value) → alloca value; value = subject.data.Some_0
// Returns head of child3-linked list of HirNodes (may be null).
func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
// Emit binding stmts for pattern payload: Option::Some(value) → alloca value; value = subject.data.Some_0
// Returns head of child3-linked list of HirNodes (may be null).
func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
subjectEnumName: String, subjectHasData: bool,
line: uint32, col: uint32) -> *HirNode {
if pat == null as *Pattern { return null as *HirNode; }
@@ -909,27 +909,27 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
ai = ai + 1;
}
return head;
}
}
// True when n is a multi-stmt yield block (match result, etc.)
func Lcx_IsMatchYield(n: *HirNode) -> bool {
// True when n is a multi-stmt yield block (match result, etc.)
func Lcx_IsMatchYield(n: *HirNode) -> bool {
if n == null as *HirNode { return false; }
if n.kind != hBlock { return false; }
// strValue must be a real temp name — null/"" is a plain statement block
if n.strValue == null as String { return false; }
return !String_Eq(n.strValue, "");
}
}
func Lcx_YieldVarOf(n: *HirNode) -> *HirNode {
func Lcx_YieldVarOf(n: *HirNode) -> *HirNode {
let v: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
v.kind = hVar;
v.strValue = n.strValue;
v.typeName = n.typeName;
return v;
}
}
// Append `node` at the end of a child3-linked chain starting at `head` (or its child1 if head is hBlock).
func Lcx_AppendToChain(head: *HirNode, node: *HirNode) {
// Append `node` at the end of a child3-linked chain starting at `head` (or its child1 if head is hBlock).
func Lcx_AppendToChain(head: *HirNode, node: *HirNode) {
if head == null as *HirNode || node == null as *HirNode { return; }
var cur: *HirNode = head;
if head.kind == hBlock && head.child1 != null as *HirNode {
@@ -939,12 +939,12 @@ func Lcx_AppendToChain(head: *HirNode, node: *HirNode) {
cur = cur.child3;
}
cur.child3 = node;
}
}
// Lower match expr → sequential ifs with a found flag (no shared HIR DAG).
// Each arm: if (!found) { if (cond) { binds; if (guard) { result=body; found=true; } } }
// Guards see pattern bindings. Supports pkGuarded (`p if cond`).
func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
// Lower match expr → sequential ifs with a found flag (no shared HIR DAG).
// Each arm: if (!found) { if (cond) { binds; if (guard) { result=body; found=true; } } }
// Guards see pattern bindings. Supports pkGuarded (`p if cond`).
func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
let line: uint32 = expr.line;
let col: uint32 = expr.column;
let subject: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
@@ -1135,13 +1135,13 @@ func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
block.strValue = resultName;
block.typeName = typeName;
return block;
}
}
// ---------------------------------------------------------------------------
// Expression lowering
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Expression lowering
// ---------------------------------------------------------------------------
func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
if expr == null as *Expr { return null as *HirNode; }
let line: uint32 = expr.line;
@@ -2347,13 +2347,13 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
}
return n;
}
}
// ---------------------------------------------------------------------------
// Statement lowering
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Statement lowering
// ---------------------------------------------------------------------------
func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
if stmt == null as *Stmt { return null as *HirNode; }
let line: uint32 = stmt.line;
@@ -3236,13 +3236,13 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
}
return n;
}
}
// ---------------------------------------------------------------------------
// Block lowering
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Block lowering
// ---------------------------------------------------------------------------
func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode {
func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode {
if block == null as *Block { return null as *HirNode; }
if block.stmtCount == 0 { return null as *HirNode; }
@@ -3379,13 +3379,13 @@ func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode
n.typeName = "int";
}
return n;
}
}
// ---------------------------------------------------------------------------
// Param → HirParam conversion
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Param → HirParam conversion
// ---------------------------------------------------------------------------
func Lcx_LowerParam(out: *HirParam, p: *Param, ctx: *LowerCtx) {
func Lcx_LowerParam(out: *HirParam, p: *Param, ctx: *LowerCtx) {
out.name = p.name;
var te: *TypeExpr = p.refParamType;
if ctx != null as *LowerCtx {
@@ -3407,13 +3407,13 @@ func Lcx_LowerParam(out: *HirParam, p: *Param, ctx: *LowerCtx) {
out.typeKind = 0;
out.typeName = "";
}
}
}
// ---------------------------------------------------------------------------
// Function lowering
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Function lowering
// ---------------------------------------------------------------------------
func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
let oldChecked: bool = ctx.checkedFunc;
ctx.checkedFunc = decl.isChecked != 0;
let oldRelease: bool = ctx.releaseFunc;
@@ -3514,13 +3514,13 @@ func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
ctx.releaseFunc = oldRelease;
return f;
}
}
// ---------------------------------------------------------------------------
// Closure lowering — generate a global function for a closure expression
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Closure lowering — generate a global function for a closure expression
// ---------------------------------------------------------------------------
func Lcx_LowerClosureFunc(ctx: *LowerCtx, expr: *Expr) -> *HirFunc {
func Lcx_LowerClosureFunc(ctx: *LowerCtx, expr: *Expr) -> *HirFunc {
let f: *HirFunc = bux_alloc(sizeof(HirFunc)) as *HirFunc;
// Generate unique name
@@ -3653,35 +3653,35 @@ func Lcx_LowerClosureFunc(ctx: *LowerCtx, expr: *Expr) -> *HirFunc {
ctx.funcCount = ctx.funcCount + 1;
return f;
}
}
// ---------------------------------------------------------------------------
// Compile-Time Function Execution (CTFE) — constant expression evaluator
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Compile-Time Function Execution (CTFE) — constant expression evaluator
// ---------------------------------------------------------------------------
const CTFE_MAX_LOCALS: int = 64;
const CTFE_MAX_LOCALS: int = 64;
struct CtfeLocal {
struct CtfeLocal {
name: String,
value: int,
}
}
struct CtfeEnv {
struct CtfeEnv {
locals: *CtfeLocal,
count: int,
}
}
struct CtVal {
struct CtVal {
value: int,
isReturn: bool,
}
}
func CtfeEnv_New() -> CtfeEnv {
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 {
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) {
@@ -3690,16 +3690,16 @@ func CtfeEnv_Get(env: *CtfeEnv, name: String) -> int {
i = i - 1;
}
return 0;
}
}
func CtfeEnv_Set(env: *CtfeEnv, name: String, value: int) {
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 {
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) {
@@ -3708,9 +3708,9 @@ func Lcx_FindConstFunc(ctx: *LowerCtx, name: String) -> *Decl {
decl = decl.childDecl2;
}
return null as *Decl;
}
}
func Lcx_ParamName(fd: *Decl, idx: int) -> String {
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; }
@@ -3722,17 +3722,17 @@ func Lcx_ParamName(fd: *Decl, idx: int) -> String {
if idx == 7 { return fd.param7.name; }
if idx == 8 { return fd.param8.name; }
return "";
}
}
func CtVal_Make(value: int) -> CtVal {
func CtVal_Make(value: int) -> CtVal {
return CtVal { value: value, isReturn: false };
}
}
func CtVal_Return(value: int) -> CtVal {
func CtVal_Return(value: int) -> CtVal {
return CtVal { value: value, isReturn: true };
}
}
func Lcx_EvalConstExprEnv(ctx: *LowerCtx, expr: *Expr, env: *CtfeEnv) -> CtVal {
func Lcx_EvalConstExprEnv(ctx: *LowerCtx, expr: *Expr, env: *CtfeEnv) -> CtVal {
if expr == null as *Expr {
return CtVal_Make(0);
}
@@ -3855,9 +3855,9 @@ func Lcx_EvalConstExprEnv(ctx: *LowerCtx, expr: *Expr, env: *CtfeEnv) -> CtVal {
}
return CtVal_Make(0);
}
}
func Lcx_EvalConstBlock(ctx: *LowerCtx, block: *Block, env: *CtfeEnv) -> CtVal {
func Lcx_EvalConstBlock(ctx: *LowerCtx, block: *Block, env: *CtfeEnv) -> CtVal {
if block == null as *Block {
return CtVal_Make(0);
}
@@ -3887,19 +3887,19 @@ func Lcx_EvalConstBlock(ctx: *LowerCtx, block: *Block, env: *CtfeEnv) -> CtVal {
stmt = stmt.nextStmt;
}
return CtVal_Make(0);
}
}
func Lcx_EvalConstExpr(ctx: *LowerCtx, expr: *Expr) -> int {
func Lcx_EvalConstExpr(ctx: *LowerCtx, expr: *Expr) -> int {
let env: CtfeEnv = CtfeEnv_New();
let result: CtVal = Lcx_EvalConstExprEnv(ctx, expr, &env);
return result.value;
}
}
// ---------------------------------------------------------------------------
// Auto-Drop: build the Free function name for a given type
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Auto-Drop: build the Free function name for a given type
// ---------------------------------------------------------------------------
func Lcx_BuildAutoDropFree(ctx: *LowerCtx, typeName: String) -> String {
func Lcx_BuildAutoDropFree(ctx: *LowerCtx, typeName: String) -> String {
if String_StartsWith(typeName, "Array_") {
let elemType: String = bux_str_slice(typeName, 6, bux_strlen(typeName) - 6);
// Ensure inner free is also monomorphized since Drop calls Free
@@ -3974,13 +3974,13 @@ func Lcx_BuildAutoDropFree(ctx: *LowerCtx, typeName: String) -> String {
}
}
return "";
}
}
// ---------------------------------------------------------------------------
// Module lowering — main entry point
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Module lowering — main entry point
// ---------------------------------------------------------------------------
func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
let ctx: *LowerCtx = bux_alloc(sizeof(LowerCtx)) as *LowerCtx;
ctx.module = mod;
ctx.scope = sema.scope;
@@ -4190,6 +4190,6 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
return hm;
}
}
}
+122 -109
View File
@@ -2,56 +2,56 @@
// Tokenizes Bux source into a stream of tokens.
module Lexer {
extern func bux_strlen(s: String) -> uint;
extern func bux_strlen(s: String) -> uint;
// ---------------------------------------------------------------------------
// Character helpers (wrap C ctype)
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Character helpers (wrap C ctype)
// ---------------------------------------------------------------------------
func Lex_IsDigit(c: uint32) -> bool {
func Lex_IsDigit(c: uint32) -> bool {
return c >= 48 && c <= 57; // '0'..'9'
}
}
func Lex_IsHexDigit(c: uint32) -> bool {
func Lex_IsHexDigit(c: uint32) -> bool {
if c >= 48 && c <= 57 { return true; } // 0-9
if c >= 65 && c <= 70 { return true; } // A-F
if c >= 97 && c <= 102 { return true; } // a-f
return false;
}
}
func Lex_IsBinDigit(c: uint32) -> bool {
func Lex_IsBinDigit(c: uint32) -> bool {
return c == 48 || c == 49; // '0' or '1'
}
}
func Lex_IsOctDigit(c: uint32) -> bool {
func Lex_IsOctDigit(c: uint32) -> bool {
return c >= 48 && c <= 55; // '0'..'7'
}
}
func Lex_IsIdentStart(c: uint32) -> bool {
func Lex_IsIdentStart(c: uint32) -> bool {
if c >= 97 && c <= 122 { return true; } // a-z
if c >= 65 && c <= 90 { return true; } // A-Z
return c == 95; // '_'
}
}
func Lex_IsIdentChar(c: uint32) -> bool {
func Lex_IsIdentChar(c: uint32) -> bool {
if Lex_IsIdentStart(c) { return true; }
return Lex_IsDigit(c);
}
}
// ---------------------------------------------------------------------------
// Lexer state
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Lexer state
// ---------------------------------------------------------------------------
const maxTokens: int = 32768;
const maxDiags: int = 128;
const maxTokens: int = 32768;
const maxDiags: int = 128;
struct LexerDiag {
struct LexerDiag {
line: uint32;
column: uint32;
message: String;
}
}
struct Lexer {
struct Lexer {
source: String;
sourceLen: int;
pos: int;
@@ -64,32 +64,32 @@ struct Lexer {
tokens: *LexToken;
diagCount: int;
diags: *LexerDiag;
}
}
struct LexToken {
struct LexToken {
kind: int;
text: String;
line: uint32;
column: uint32;
}
}
// ---------------------------------------------------------------------------
// Core primitives
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Core primitives
// ---------------------------------------------------------------------------
func lexIsAtEnd(lex: *Lexer) -> bool {
func lexIsAtEnd(lex: *Lexer) -> bool {
return lex.pos >= lex.sourceLen;
}
}
func lexPeek(lex: *Lexer, ahead: int) -> uint32 {
func lexPeek(lex: *Lexer, ahead: int) -> uint32 {
let i: int = lex.pos + ahead;
if i < lex.sourceLen {
return (lex.source[i] as uint32) & 255;
}
return 0;
}
}
func lexAdvance(lex: *Lexer) -> uint32 {
func lexAdvance(lex: *Lexer) -> uint32 {
let c: uint32 = lexPeek(lex, 0);
if !lexIsAtEnd(lex) {
lex.pos = lex.pos + 1;
@@ -101,16 +101,16 @@ func lexAdvance(lex: *Lexer) -> uint32 {
}
}
return c;
}
}
func lexMatch(lex: *Lexer, expected: uint32) -> bool {
func lexMatch(lex: *Lexer, expected: uint32) -> bool {
if lexIsAtEnd(lex) { return false; }
if lexPeek(lex, 0) != expected { return false; }
discard lexAdvance(lex);
return true;
}
}
func lexMatchStr(lex: *Lexer, s: String) -> bool {
func lexMatchStr(lex: *Lexer, s: String) -> bool {
let len: uint = bux_strlen(s);
var i: int = 0;
while i < (len as int) {
@@ -125,19 +125,19 @@ func lexMatchStr(lex: *Lexer, s: String) -> bool {
i = i + 1;
}
return true;
}
}
// ---------------------------------------------------------------------------
// Token emission
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Token emission
// ---------------------------------------------------------------------------
func lexMarkStart(lex: *Lexer) {
func lexMarkStart(lex: *Lexer) {
lex.startLine = lex.line;
lex.startColumn = lex.column;
lex.startPos = lex.pos;
}
}
func lexMakeToken(lex: *Lexer, kind: int) -> LexToken {
func lexMakeToken(lex: *Lexer, kind: int) -> LexToken {
var text: String = "";
let endPos: int = lex.pos;
let startPos: int = lex.startPos;
@@ -158,42 +158,42 @@ func lexMakeToken(lex: *Lexer, kind: int) -> LexToken {
kind: kind, text: text,
line: lex.startLine, column: lex.startColumn
};
}
}
func lexEmitToken(lex: *Lexer, kind: int) {
func lexEmitToken(lex: *Lexer, kind: int) {
let tok: LexToken = lexMakeToken(lex, kind);
if lex.tokenCount < maxTokens {
lex.tokens[lex.tokenCount] = tok;
lex.tokenCount = lex.tokenCount + 1;
}
}
}
func lexSetLastTokenText(lex: *Lexer, text: String) {
func lexSetLastTokenText(lex: *Lexer, text: String) {
if lex.tokenCount > 0 {
lex.tokens[lex.tokenCount - 1].text = text;
}
}
}
func lexEmitDiag(lex: *Lexer, msg: String) {
func lexEmitDiag(lex: *Lexer, msg: String) {
if lex.diagCount < maxDiags {
lex.diags[lex.diagCount] = LexerDiag {
line: lex.line, column: lex.column, message: msg
};
lex.diagCount = lex.diagCount + 1;
}
}
}
// ---------------------------------------------------------------------------
// Whitespace / comments
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Whitespace / comments
// ---------------------------------------------------------------------------
func lexSkipLineComment(lex: *Lexer) {
func lexSkipLineComment(lex: *Lexer) {
while !lexIsAtEnd(lex) && lexPeek(lex, 0) != 10 { // '\n'
discard lexAdvance(lex);
}
}
}
func lexSkipBlockComment(lex: *Lexer) {
func lexSkipBlockComment(lex: *Lexer) {
var depth: int = 1;
while !lexIsAtEnd(lex) && depth > 0 {
if lexPeek(lex, 0) == 47 && lexPeek(lex, 1) == 42 { // /*
@@ -211,9 +211,9 @@ func lexSkipBlockComment(lex: *Lexer) {
if depth > 0 {
lexEmitDiag(lex, "unterminated block comment");
}
}
}
func lexSkipWhitespace(lex: *Lexer) {
func lexSkipWhitespace(lex: *Lexer) {
while !lexIsAtEnd(lex) {
let c: uint32 = lexPeek(lex, 0);
if c == 32 || c == 9 || c == 13 {
@@ -232,13 +232,13 @@ func lexSkipWhitespace(lex: *Lexer) {
}
}
}
}
}
// ---------------------------------------------------------------------------
// Identifiers
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Identifiers
// ---------------------------------------------------------------------------
func lexKeywordKind(text: String) -> int {
func lexKeywordKind(text: String) -> int {
if String_Eq(text, "true") { return tkBoolLiteral; }
if String_Eq(text, "false") { return tkBoolLiteral; }
if String_Eq(text, "func") { return tkFunc; }
@@ -282,9 +282,9 @@ func lexKeywordKind(text: String) -> int {
if String_Eq(text, "await") { return tkAwait; }
if String_Eq(text, "spawn") { return tkSpawn; }
return tkIdent;
}
}
func lexScanIdent(lex: *Lexer) {
func lexScanIdent(lex: *Lexer) {
lexMarkStart(lex);
while !lexIsAtEnd(lex) && Lex_IsIdentChar(lexPeek(lex, 0)) {
discard lexAdvance(lex);
@@ -296,25 +296,25 @@ func lexScanIdent(lex: *Lexer) {
lex.tokens[lex.tokenCount] = tok;
lex.tokenCount = lex.tokenCount + 1;
}
}
}
// ---------------------------------------------------------------------------
// Numbers
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Numbers
// ---------------------------------------------------------------------------
func lexScanDigits(lex: *Lexer) {
func lexScanDigits(lex: *Lexer) {
while !lexIsAtEnd(lex) && Lex_IsDigit(lexPeek(lex, 0)) {
discard lexAdvance(lex);
}
}
}
func lexScanHexDigits(lex: *Lexer) {
func lexScanHexDigits(lex: *Lexer) {
while !lexIsAtEnd(lex) && Lex_IsHexDigit(lexPeek(lex, 0)) {
discard lexAdvance(lex);
}
}
}
func lexScanNumber(lex: *Lexer) {
func lexScanNumber(lex: *Lexer) {
lexMarkStart(lex);
var isFloat: bool = false;
@@ -361,13 +361,13 @@ func lexScanNumber(lex: *Lexer) {
} else {
lexEmitToken(lex, tkIntLiteral);
}
}
}
// ---------------------------------------------------------------------------
// Strings and chars
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Strings and chars
// ---------------------------------------------------------------------------
func lexScanBacktickString(lex: *Lexer) {
func lexScanBacktickString(lex: *Lexer) {
lexMarkStart(lex);
if lexPeek(lex, 0) == 96 { discard lexAdvance(lex); } // opening backtick
while !lexIsAtEnd(lex) && lexPeek(lex, 0) != 96 {
@@ -379,10 +379,10 @@ func lexScanBacktickString(lex: *Lexer) {
discard lexAdvance(lex); // closing backtick
}
lexEmitToken(lex, tkStringLiteral);
}
}
// Assumes lex.startPos already marked (may include f/c8/… prefix before the quote).
func lexScanStringFrom(lex: *Lexer) {
// Assumes lex.startPos already marked (may include f/c8/… prefix before the quote).
func lexScanStringFrom(lex: *Lexer) {
// Collect the prefix (before opening quote) for the token text
var prefix: String = "";
var prefixLen: int = 0;
@@ -456,14 +456,14 @@ func lexScanStringFrom(lex: *Lexer) {
finalBuf[fi] = 34 as char8; fi = fi + 1; // closing "
finalBuf[fi] = 0 as char8;
lexSetLastTokenText(lex, finalBuf);
}
}
func lexScanString(lex: *Lexer) {
func lexScanString(lex: *Lexer) {
lexMarkStart(lex);
lexScanStringFrom(lex);
}
}
func lexScanChar(lex: *Lexer) {
func lexScanChar(lex: *Lexer) {
lexMarkStart(lex);
// Collect the prefix for the token text
var prefix: String = "";
@@ -522,13 +522,13 @@ func lexScanChar(lex: *Lexer) {
finalBuf[fi] = 39 as char8; fi = fi + 1; // closing '
finalBuf[fi] = 0 as char8;
lexSetLastTokenText(lex, finalBuf);
}
}
// ---------------------------------------------------------------------------
// Symbols / operators
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Symbols / operators
// ---------------------------------------------------------------------------
func lexScanSymbol(lex: *Lexer) {
func lexScanSymbol(lex: *Lexer) {
lexMarkStart(lex);
let c: uint32 = lexAdvance(lex);
@@ -667,13 +667,13 @@ func lexScanSymbol(lex: *Lexer) {
lexEmitDiag(lex, "unexpected character");
lexEmitToken(lex, tkUnknown);
}
}
// ---------------------------------------------------------------------------
// Next token
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Next token
// ---------------------------------------------------------------------------
func lexNextToken(lex: *Lexer) {
func lexNextToken(lex: *Lexer) {
lexSkipWhitespace(lex);
if lexIsAtEnd(lex) {
@@ -741,6 +741,19 @@ func lexNextToken(lex: *Lexer) {
}
if c == 39 { // '
// Lifetime 'a vs char literal 'x' / '\n'
// Lifetime: ' + ident-start, and the char after that is NOT closing '
let n1: uint32 = lexPeek(lex, 1);
let n2: uint32 = lexPeek(lex, 2);
if Lex_IsIdentStart(n1) && n2 != 39 && n2 != 0 {
lexMarkStart(lex);
discard lexAdvance(lex); // '
while !lexIsAtEnd(lex) && Lex_IsIdentChar(lexPeek(lex, 0)) {
discard lexAdvance(lex);
}
lexEmitToken(lex, tkLifetime);
return;
}
lexScanChar(lex); return;
}
@@ -753,13 +766,13 @@ func lexNextToken(lex: *Lexer) {
}
lexScanSymbol(lex);
}
}
// ---------------------------------------------------------------------------
// Tokenize — main entry point
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Tokenize — main entry point
// ---------------------------------------------------------------------------
func Lexer_Tokenize(source: String) -> *Lexer {
func Lexer_Tokenize(source: String) -> *Lexer {
let lex: *Lexer = bux_alloc(sizeof(Lexer)) as *Lexer;
lex.source = source;
lex.sourceLen = bux_strlen(source) as int;
@@ -787,15 +800,15 @@ func Lexer_Tokenize(source: String) -> *Lexer {
}
}
return lex;
}
}
func Lexer_DiagCount(lex: *Lexer) -> int {
func Lexer_DiagCount(lex: *Lexer) -> int {
return lex.diagCount;
}
}
func Lexer_Free(lex: *Lexer) {
func Lexer_Free(lex: *Lexer) {
bux_free(lex.tokens as *void);
bux_free(lex.diags as *void);
bux_free(lex as *void);
}
}
}
+32 -32
View File
@@ -2,12 +2,12 @@
// Parses package metadata: name, version, type, build output.
module Manifest {
extern func bux_strlen(s: String) -> uint;
extern func bux_strlen(s: String) -> uint;
// ---------------------------------------------------------------------------
// Manifest struct
// ---------------------------------------------------------------------------
struct Manifest {
// ---------------------------------------------------------------------------
// Manifest struct
// ---------------------------------------------------------------------------
struct Manifest {
name: String;
version: String;
pkgType: String;
@@ -29,13 +29,13 @@ struct Manifest {
depUrl6: String;
depName7: String;
depUrl7: String;
}
}
// ---------------------------------------------------------------------------
// Simple TOML parser (handles [Package] and [Build] sections)
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Simple TOML parser (handles [Package] and [Build] sections)
// ---------------------------------------------------------------------------
func Manifest_Parse(content: String) -> Manifest {
func Manifest_Parse(content: String) -> Manifest {
var m: Manifest;
m.name = "";
m.version = "0.1.0";
@@ -108,13 +108,13 @@ func Manifest_Parse(content: String) -> Manifest {
}
return m;
}
}
// ---------------------------------------------------------------------------
// Dependency helpers
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Dependency helpers
// ---------------------------------------------------------------------------
func Manifest_HasDep(m: Manifest, name: String) -> bool {
func Manifest_HasDep(m: Manifest, name: String) -> bool {
var i: int = 0;
while i < m.depCount {
var depName: String = "";
@@ -130,9 +130,9 @@ func Manifest_HasDep(m: Manifest, name: String) -> bool {
i = i + 1;
}
return false;
}
}
func Manifest_GetDepUrl(m: Manifest, name: String) -> String {
func Manifest_GetDepUrl(m: Manifest, name: String) -> String {
var i: int = 0;
while i < m.depCount {
var depName: String = "";
@@ -149,9 +149,9 @@ func Manifest_GetDepUrl(m: Manifest, name: String) -> String {
i = i + 1;
}
return "";
}
}
func Manifest_AddDep(m: *Manifest, name: String, url: String) -> bool {
func Manifest_AddDep(m: *Manifest, name: String, url: String) -> bool {
if Manifest_HasDep(*m, name) { return false; }
if m.depCount >= 8 { return false; }
if m.depCount == 0 { m.depName0 = name; m.depUrl0 = url; }
@@ -164,9 +164,9 @@ func Manifest_AddDep(m: *Manifest, name: String, url: String) -> bool {
else if m.depCount == 7 { m.depName7 = name; m.depUrl7 = url; }
m.depCount = m.depCount + 1;
return true;
}
}
func Manifest_RemoveDep(m: *Manifest, name: String) -> bool {
func Manifest_RemoveDep(m: *Manifest, name: String) -> bool {
var found: int = -1;
var i: int = 0;
while i < m.depCount {
@@ -196,13 +196,13 @@ func Manifest_RemoveDep(m: *Manifest, name: String) -> bool {
}
m.depCount = m.depCount - 1;
return true;
}
}
// ---------------------------------------------------------------------------
// Serialize manifest back to TOML
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Serialize manifest back to TOML
// ---------------------------------------------------------------------------
func Manifest_ToString(m: Manifest) -> String {
func Manifest_ToString(m: Manifest) -> String {
let sb: StringBuilder = StringBuilder_New();
StringBuilder_Append(&sb, "[Package]\n");
StringBuilder_Append(&sb, "Name = \"");
@@ -240,14 +240,14 @@ func Manifest_ToString(m: Manifest) -> String {
}
}
return StringBuilder_Build(&sb);
}
}
// ---------------------------------------------------------------------------
// Load manifest from file
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Load manifest from file
// ---------------------------------------------------------------------------
func Manifest_Load(path: String) -> Manifest {
func Manifest_Load(path: String) -> Manifest {
let content: String = ReadFile(path);
return Manifest_Parse(content);
}
}
}
+223 -188
View File
@@ -2,45 +2,45 @@
// Parses Bux source tokens into an AST.
module Parser {
extern func bux_strlen(s: String) -> uint;
extern func bux_str_to_int(s: String) -> int64;
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
extern func bux_strlen(s: String) -> uint;
extern func bux_str_to_int(s: String) -> int64;
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
// Forward declarations for mutual recursion
func parserParseExpr(p: *Parser) -> *Expr;
func parserParseStmt(p: *Parser) -> *Stmt;
func parserParseBlock(p: *Parser) -> *Block;
func parserParsePrimary(p: *Parser) -> *Expr;
func parserParsePostfixExpr(p: *Parser) -> *Expr;
func parserParseUnary(p: *Parser) -> *Expr;
func parserParseBinaryPrec(p: *Parser, minPrec: int) -> *Expr;
func parserParsePattern(p: *Parser) -> *Pattern;
func parserParseMatchExpr(p: *Parser) -> *Expr;
// Forward declarations for mutual recursion
func parserParseExpr(p: *Parser) -> *Expr;
func parserParseStmt(p: *Parser) -> *Stmt;
func parserParseBlock(p: *Parser) -> *Block;
func parserParsePrimary(p: *Parser) -> *Expr;
func parserParsePostfixExpr(p: *Parser) -> *Expr;
func parserParseUnary(p: *Parser) -> *Expr;
func parserParseBinaryPrec(p: *Parser, minPrec: int) -> *Expr;
func parserParsePattern(p: *Parser) -> *Pattern;
func parserParseMatchExpr(p: *Parser) -> *Expr;
// ---------------------------------------------------------------------------
// Parser state
// ---------------------------------------------------------------------------
struct Parser {
// ---------------------------------------------------------------------------
// Parser state
// ---------------------------------------------------------------------------
struct Parser {
tokens: *LexToken,
tokenCount: int,
pos: int,
diagCount: int,
diags: *ParserDiag,
structInitAllowed: bool,
}
}
struct ParserDiag {
struct ParserDiag {
line: uint32,
column: uint32,
message: String,
severity: int, /* 0=error (fatal), 1=warning (recoverable) */
}
}
// ---------------------------------------------------------------------------
// Token helpers
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Token helpers
// ---------------------------------------------------------------------------
func parserCurToken(p: *Parser) -> LexToken {
func parserCurToken(p: *Parser) -> LexToken {
if p.pos < p.tokenCount {
return p.tokens[p.pos];
}
@@ -48,20 +48,20 @@ func parserCurToken(p: *Parser) -> LexToken {
eof.kind = tkEndOfFile;
eof.text = "";
return eof;
}
}
func parserPeek(p: *Parser, ahead: int) -> int {
func parserPeek(p: *Parser, ahead: int) -> int {
let i: int = p.pos + ahead;
if i >= 0 && i < p.tokenCount {
return p.tokens[i].kind;
}
return tkEndOfFile;
}
}
// Lookahead to determine if '<' starts a type argument list (`Foo<int>`).
// Must not treat value comparisons `x < 0` as generics when a later `x > 0`
// exists (e.g. multiple match arm guards).
func parserIsTypeArgListAhead(p: *Parser) -> bool {
// Lookahead to determine if '<' starts a type argument list (`Foo<int>`).
// Must not treat value comparisons `x < 0` as generics when a later `x > 0`
// exists (e.g. multiple match arm guards).
func parserIsTypeArgListAhead(p: *Parser) -> bool {
if !parserCheck(p, tkLt) { return false; }
var depth: int = 0;
var ahead: int = 0;
@@ -92,29 +92,29 @@ func parserIsTypeArgListAhead(p: *Parser) -> bool {
ahead = ahead + 1;
}
return false;
}
}
func parserAdvance(p: *Parser) -> LexToken {
func parserAdvance(p: *Parser) -> LexToken {
let tok: LexToken = parserCurToken(p);
if p.pos < p.tokenCount {
p.pos = p.pos + 1;
}
return tok;
}
}
func parserCheck(p: *Parser, kind: int) -> bool {
func parserCheck(p: *Parser, kind: int) -> bool {
return parserPeek(p, 0) == kind;
}
}
func parserMatch(p: *Parser, kind: int) -> bool {
func parserMatch(p: *Parser, kind: int) -> bool {
if parserCheck(p, kind) {
discard parserAdvance(p);
return true;
}
return false;
}
}
func parserExpect(p: *Parser, kind: int, msg: String) -> LexToken {
func parserExpect(p: *Parser, kind: int, msg: String) -> LexToken {
if parserCheck(p, kind) {
return parserAdvance(p);
}
@@ -126,24 +126,24 @@ func parserExpect(p: *Parser, kind: int, msg: String) -> LexToken {
p.diagCount = p.diagCount + 1;
}
return tok;
}
}
func parserEmitDiag(p: *Parser, line: uint32, col: uint32, msg: String) {
func parserEmitDiag(p: *Parser, line: uint32, col: uint32, msg: String) {
if p.diagCount < 256 {
p.diags[p.diagCount] = ParserDiag {
line: line, column: col, message: msg, severity: 1
};
p.diagCount = p.diagCount + 1;
}
}
}
func parserIsKeyword(kind: int) -> bool {
func parserIsKeyword(kind: int) -> bool {
if kind >= tkIf && kind <= tkSuper { return true; }
if kind == tkSizeOf { return true; }
return false;
}
}
func parserExpectIdentOrKeyword(p: *Parser, msg: String) -> LexToken {
func parserExpectIdentOrKeyword(p: *Parser, msg: String) -> LexToken {
let tok: LexToken = parserCurToken(p);
if tok.kind == tkIdent || parserIsKeyword(tok.kind) {
return parserAdvance(p);
@@ -155,14 +155,14 @@ func parserExpectIdentOrKeyword(p: *Parser, msg: String) -> LexToken {
p.diagCount = p.diagCount + 1;
}
return tok;
}
}
// ---------------------------------------------------------------------------
// Type parsing
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Type parsing
// ---------------------------------------------------------------------------
// C-friendly type name from a TypeExpr (named, tuple, pointer, …).
func parserTypeExprCName(te: *TypeExpr) -> String {
// C-friendly type name from a TypeExpr (named, tuple, pointer, …).
func parserTypeExprCName(te: *TypeExpr) -> String {
if te == null as *TypeExpr { return "int"; }
if te.kind == tekTuple {
if !String_Eq(te.typeName, "") { return te.typeName; }
@@ -181,16 +181,23 @@ func parserTypeExprCName(te: *TypeExpr) -> String {
return te.typeName;
}
return "int";
}
}
func parserParseType(p: *Parser) -> *TypeExpr {
func parserParseType(p: *Parser) -> *TypeExpr {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
let kindTok: int = parserPeek(p, 0);
// &T (shared reference) and &mut T (mutable reference)
// &T / &'a T (shared) and &mut T / &'a mut T (mutable)
if kindTok == tkAmp {
discard parserAdvance(p); // &
var lt: String = "";
// Optional lifetime: &'a or &'a mut
if parserCheck(p, tkLifetime) {
let ltTok: LexToken = parserCurToken(p);
lt = ltTok.text;
discard parserAdvance(p);
}
var isMut: bool = false;
// Check for "mut" keyword
if parserCheck(p, tkIdent) {
@@ -208,6 +215,7 @@ func parserParseType(p: *Parser) -> *TypeExpr {
}
te.line = line;
te.column = col;
te.refLifetime = lt;
te.pointerPointee = parserParseType(p);
if te.pointerPointee != null as *TypeExpr {
te.typeName = String_Concat(te.pointerPointee.typeName, "*");
@@ -351,17 +359,17 @@ func parserParseType(p: *Parser) -> *TypeExpr {
discard parserExpect(p, tkGt, "expected '>' to close type arguments");
}
return te;
}
}
// ---------------------------------------------------------------------------
// Forward declarations and helpers
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Forward declarations and helpers
// ---------------------------------------------------------------------------
func parserParseExpr(p: *Parser) -> *Expr;
func parserParseStmt(p: *Parser) -> *Stmt;
func parserParseBlock(p: *Parser) -> *Block;
func parserParseExpr(p: *Parser) -> *Expr;
func parserParseStmt(p: *Parser) -> *Stmt;
func parserParseBlock(p: *Parser) -> *Block;
func parserMakeExpr(kind: int, line: uint32, col: uint32) -> *Expr {
func parserMakeExpr(kind: int, line: uint32, col: uint32) -> *Expr {
let e: *Expr = bux_alloc(sizeof(Expr)) as *Expr;
e.kind = kind;
e.line = line;
@@ -387,18 +395,18 @@ func parserMakeExpr(kind: int, line: uint32, col: uint32) -> *Expr {
e.matchArms = null as *MatchArm;
e.matchArmCount = 0;
return e;
}
}
func parserMakeStringLitExpr(text: String, line: uint32, col: uint32) -> *Expr {
func parserMakeStringLitExpr(text: String, line: uint32, col: uint32) -> *Expr {
let quoted: String = String_Concat(String_Concat("\"", text), "\"");
let e: *Expr = parserMakeExpr(ekLiteral, line, col);
e.tokKind = tkStringLiteral;
e.tokText = quoted;
return e;
}
}
func parserParseInterpFragment(exprStr: String) -> *Expr {
func parserParseInterpFragment(exprStr: String) -> *Expr {
let lex: *Lexer = Lexer_Tokenize(exprStr);
var sub: Parser;
sub.tokens = lex.tokens;
@@ -408,15 +416,15 @@ func parserParseInterpFragment(exprStr: String) -> *Expr {
sub.diags = null as *ParserDiag;
sub.structInitAllowed = true;
return parserParseExpr(&sub);
}
}
func parserAppendPart(head: *ExprList, tail: *ExprList, e: *Expr) -> *ExprList {
func parserAppendPart(head: *ExprList, tail: *ExprList, e: *Expr) -> *ExprList {
// returns new tail; head updated via pointer trick not possible — return pair as side effect on first arg using double pointer?
// simpler: just inline in main
return tail;
}
}
func parserParseStringInterp(p: *Parser, tok: LexToken) -> *Expr {
func parserParseStringInterp(p: *Parser, tok: LexToken) -> *Expr {
let text: String = tok.text;
let tlen: uint = bux_strlen(text);
if tlen < 3 as uint {
@@ -552,13 +560,13 @@ func parserParseStringInterp(p: *Parser, tok: LexToken) -> *Expr {
e.callArgs = head;
e.callArgCount = partCount;
return e;
}
}
// ---------------------------------------------------------------------------
// Primary expressions
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Primary expressions
// ---------------------------------------------------------------------------
func parserParsePrimary(p: *Parser) -> *Expr {
func parserParsePrimary(p: *Parser) -> *Expr {
while parserCheck(p, tkNewLine) {
discard parserAdvance(p);
}
@@ -720,13 +728,13 @@ func parserParsePrimary(p: *Parser) -> *Expr {
parserEmitDiag(p, line, col, "expected expression");
return parserMakeExpr(ekLiteral, line, col);
}
}
// ---------------------------------------------------------------------------
// Patterns (for match arms)
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Patterns (for match arms)
// ---------------------------------------------------------------------------
func parserMakePattern(kind: int, line: uint32, col: uint32) -> *Pattern {
func parserMakePattern(kind: int, line: uint32, col: uint32) -> *Pattern {
let pat: *Pattern = bux_alloc(sizeof(Pattern)) as *Pattern;
pat.kind = kind;
pat.line = line;
@@ -744,9 +752,9 @@ func parserMakePattern(kind: int, line: uint32, col: uint32) -> *Pattern {
pat.patNext = null as *Pattern;
pat.patGuardExpr = null as *Expr;
return pat;
}
}
func parserParsePrimaryPattern(p: *Parser) -> *Pattern {
func parserParsePrimaryPattern(p: *Parser) -> *Pattern {
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
let tok: LexToken = parserCurToken(p);
let line: uint32 = tok.line;
@@ -902,9 +910,9 @@ func parserParsePrimaryPattern(p: *Parser) -> *Pattern {
parserEmitDiag(p, line, col, "expected pattern");
return parserMakePattern(pkWildcard, line, col);
}
}
func parserParsePattern(p: *Parser) -> *Pattern {
func parserParsePattern(p: *Parser) -> *Pattern {
let locTok: LexToken = parserCurToken(p);
let line: uint32 = locTok.line;
let col: uint32 = locTok.column;
@@ -939,10 +947,10 @@ func parserParsePattern(p: *Parser) -> *Pattern {
return pat;
}
return left;
}
}
// match subject { pat => body, ... }
func parserParseMatchExpr(p: *Parser) -> *Expr {
// match subject { pat => body, ... }
func parserParseMatchExpr(p: *Parser) -> *Expr {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
discard parserAdvance(p); // match
@@ -990,14 +998,14 @@ func parserParseMatchExpr(p: *Parser) -> *Expr {
e.matchArms = firstArm;
e.matchArmCount = armCount;
return e;
}
}
// ---------------------------------------------------------------------------
// Closure: |params| -> Ret { body }
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Closure: |params| -> Ret { body }
// ---------------------------------------------------------------------------
// Zero-param closure when written as `||` (single tkPipePipe token from lexer)
func parserParseEmptyClosure(p: *Parser) -> *Expr {
// Zero-param closure when written as `||` (single tkPipePipe token from lexer)
func parserParseEmptyClosure(p: *Parser) -> *Expr {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
discard parserAdvance(p); // ||
@@ -1012,9 +1020,9 @@ func parserParseEmptyClosure(p: *Parser) -> *Expr {
}
e.refBlock = parserParseBlock(p);
return e;
}
}
func parserParseClosure(p: *Parser) -> *Expr {
func parserParseClosure(p: *Parser) -> *Expr {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
discard parserExpect(p, tkPipe, "expected '|' to start closure params");
@@ -1082,13 +1090,13 @@ func parserParseClosure(p: *Parser) -> *Expr {
// Body: { ... }
e.refBlock = parserParseBlock(p);
return e;
}
}
// ---------------------------------------------------------------------------
// Postfix: call, index, field access, as, is, ?
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Postfix: call, index, field access, as, is, ?
// ---------------------------------------------------------------------------
func parserParsePostfixExpr(p: *Parser) -> *Expr {
func parserParsePostfixExpr(p: *Parser) -> *Expr {
var left: *Expr = parserParsePrimary(p);
while true {
let kind: int = parserPeek(p, 0);
@@ -1317,14 +1325,14 @@ func parserParsePostfixExpr(p: *Parser) -> *Expr {
break;
}
return left;
}
}
// ---------------------------------------------------------------------------
// Binary expression (precedence climbing)
// All binary operators: arithmetic, comparison, logical, bitwise, assignment
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Binary expression (precedence climbing)
// All binary operators: arithmetic, comparison, logical, bitwise, assignment
// ---------------------------------------------------------------------------
func parserPrecedence(op: int) -> int {
func parserPrecedence(op: int) -> int {
// Assignment operators are parsed by parserParseAssign, not here
if op == tkPipePipe { return 2; }
if op == tkAmpAmp { return 3; }
@@ -1338,9 +1346,9 @@ func parserPrecedence(op: int) -> int {
if op == tkStar || op == tkSlash || op == tkPercent { return 11; }
if op == tkStarStar { return 12; }
return 0;
}
}
func parserParseUnary(p: *Parser) -> *Expr {
func parserParseUnary(p: *Parser) -> *Expr {
while parserCheck(p, tkNewLine) {
discard parserAdvance(p);
}
@@ -1359,9 +1367,9 @@ func parserParseUnary(p: *Parser) -> *Expr {
}
return parserParsePostfixExpr(p);
}
}
func parserParseBinaryPrec(p: *Parser, minPrec: int) -> *Expr {
func parserParseBinaryPrec(p: *Parser, minPrec: int) -> *Expr {
var left: *Expr = parserParseUnary(p);
while true {
while parserCheck(p, tkNewLine) {
@@ -1382,17 +1390,17 @@ func parserParseBinaryPrec(p: *Parser, minPrec: int) -> *Expr {
left = e;
}
return left;
}
}
func parserParseBinary(p: *Parser) -> *Expr {
func parserParseBinary(p: *Parser) -> *Expr {
return parserParseBinaryPrec(p, 1);
}
}
// ---------------------------------------------------------------------------
// Range: lo .. hi or lo ..= hi
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Range: lo .. hi or lo ..= hi
// ---------------------------------------------------------------------------
func parserParseRange(p: *Parser) -> *Expr {
func parserParseRange(p: *Parser) -> *Expr {
var left: *Expr = parserParseBinary(p);
if parserCheck(p, tkDotDot) || parserCheck(p, tkDotDotEqual) {
let inclusive: bool = parserCheck(p, tkDotDotEqual);
@@ -1405,13 +1413,13 @@ func parserParseRange(p: *Parser) -> *Expr {
return e;
}
return left;
}
}
// ---------------------------------------------------------------------------
// Ternary: cond ? then : else
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Ternary: cond ? then : else
// ---------------------------------------------------------------------------
func parserParseTernary(p: *Parser) -> *Expr {
func parserParseTernary(p: *Parser) -> *Expr {
var left: *Expr = parserParseRange(p);
if parserMatch(p, tkQuestion) {
let thenExpr: *Expr = parserParseExpr(p);
@@ -1424,13 +1432,13 @@ func parserParseTernary(p: *Parser) -> *Expr {
return e;
}
return left;
}
}
// ---------------------------------------------------------------------------
// Assignment: target = value (right-associative)
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Assignment: target = value (right-associative)
// ---------------------------------------------------------------------------
func parserParseAssign(p: *Parser) -> *Expr {
func parserParseAssign(p: *Parser) -> *Expr {
let left: *Expr = parserParseTernary(p);
let op: int = parserPeek(p, 0);
if op == tkAssign || op == tkPlusAssign || op == tkMinusAssign || op == tkStarAssign || op == tkSlashAssign || op == tkPercentAssign || op == tkAmpAssign || op == tkPipeAssign || op == tkCaretAssign || op == tkShlAssign || op == tkShrAssign {
@@ -1443,21 +1451,21 @@ func parserParseAssign(p: *Parser) -> *Expr {
return e;
}
return left;
}
}
// ---------------------------------------------------------------------------
// Top-level expression
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Top-level expression
// ---------------------------------------------------------------------------
func parserParseExpr(p: *Parser) -> *Expr {
func parserParseExpr(p: *Parser) -> *Expr {
return parserParseAssign(p);
}
}
// ---------------------------------------------------------------------------
// Statements
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Statements
// ---------------------------------------------------------------------------
func parserParseStmt(p: *Parser) -> *Stmt {
func parserParseStmt(p: *Parser) -> *Stmt {
let tok: LexToken = parserCurToken(p);
let line: uint32 = tok.line;
let col: uint32 = tok.column;
@@ -1731,13 +1739,13 @@ func parserParseStmt(p: *Parser) -> *Stmt {
s.column = col;
s.child1 = expr;
return s;
}
}
// ---------------------------------------------------------------------------
// Block: { stmt* }
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Block: { stmt* }
// ---------------------------------------------------------------------------
func parserParseBlock(p: *Parser) -> *Block {
func parserParseBlock(p: *Parser) -> *Block {
discard parserExpect(p, tkLBrace, "expected '{'");
let b: *Block = bux_alloc(sizeof(Block)) as *Block;
b.line = parserCurToken(p).line;
@@ -1782,13 +1790,13 @@ func parserParseBlock(p: *Parser) -> *Block {
discard parserAdvance(p);
}
return b;
}
}
// ---------------------------------------------------------------------------
// Function parameters
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Function parameters
// ---------------------------------------------------------------------------
func parserParseParamList(p: *Parser) -> *Decl {
func parserParseParamList(p: *Parser) -> *Decl {
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
d.kind = dkFunc;
d.paramCount = 0;
@@ -1854,39 +1862,66 @@ func parserParseParamList(p: *Parser) -> *Decl {
}
discard parserExpect(p, tkRParen, "expected ')'");
return d;
}
}
// ---------------------------------------------------------------------------
// Type parameters: <T: Bound, U: Bound2>
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Type parameters: <T: Bound, U: Bound2>
// ---------------------------------------------------------------------------
func parserParseTypeParams(p: *Parser, d: *Decl) {
func parserParseTypeParams(p: *Parser, d: *Decl) {
// Lifetime params ('a) are accepted and skipped for monomorphization —
// they only annotate &/'a T on parameters/returns (stored in TypeExpr.refLifetime).
if !parserCheck(p, tkLt) { return; }
discard parserAdvance(p);
let tp0: LexToken = parserExpect(p, tkIdent, "expected type param");
d.typeParam0 = tp0.text;
d.typeParamCount = 1;
var typeCount: int = 0;
var first: bool = true;
while !parserCheck(p, tkGt) && parserPeek(p, 0) != tkEndOfFile {
if !first {
if !parserMatch(p, tkComma) { break; }
}
first = false;
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
if parserCheck(p, tkGt) { break; }
// Lifetime type param: 'a — parse and ignore for mono slots
if parserCheck(p, tkLifetime) {
discard parserAdvance(p);
// optional trait bound is nonsense for lifetimes; skip : Bound if present
if parserMatch(p, tkColon) {
discard parserExpect(p, tkIdent, "expected trait bound name");
}
continue;
}
let tpTok: LexToken = parserExpect(p, tkIdent, "expected type param");
if typeCount == 0 {
d.typeParam0 = tpTok.text;
typeCount = 1;
if parserMatch(p, tkColon) {
let bound0: LexToken = parserExpect(p, tkIdent, "expected trait bound name");
d.typeParam0Bound = bound0.text;
}
if parserMatch(p, tkComma) {
let tp1: LexToken = parserExpect(p, tkIdent, "expected type param");
d.typeParam1 = tp1.text;
d.typeParamCount = 2;
} else if typeCount == 1 {
d.typeParam1 = tpTok.text;
typeCount = 2;
if parserMatch(p, tkColon) {
let bound1: LexToken = parserExpect(p, tkIdent, "expected trait bound name");
d.typeParam1Bound = bound1.text;
}
} else {
// Extra type params beyond 2 — consume and ignore
if parserMatch(p, tkColon) {
discard parserExpect(p, tkIdent, "expected trait bound name");
}
}
}
d.typeParamCount = typeCount;
discard parserExpect(p, tkGt, "expected '>'");
}
}
// ---------------------------------------------------------------------------
// Declarations
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Declarations
// ---------------------------------------------------------------------------
func parserParseFuncDecl(p: *Parser, isPublic: bool, isExtern: bool, isAsync: bool) -> *Decl {
func parserParseFuncDecl(p: *Parser, isPublic: bool, isExtern: bool, isAsync: bool) -> *Decl {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
discard parserExpect(p, tkFunc, "expected 'func'");
@@ -1929,9 +1964,9 @@ func parserParseFuncDecl(p: *Parser, isPublic: bool, isExtern: bool, isAsync: bo
}
return d;
}
}
func parserParseStructDecl(p: *Parser, isPublic: bool) -> *Decl {
func parserParseStructDecl(p: *Parser, isPublic: bool) -> *Decl {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
discard parserExpect(p, tkStruct, "expected 'struct'");
@@ -1980,9 +2015,9 @@ func parserParseStructDecl(p: *Parser, isPublic: bool) -> *Decl {
d.fieldCount = fieldCount;
discard parserExpect(p, tkRBrace, "expected '}'");
return d;
}
}
func parserParseEnumDecl(p: *Parser, isPublic: bool) -> *Decl {
func parserParseEnumDecl(p: *Parser, isPublic: bool) -> *Decl {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
discard parserExpect(p, tkEnum, "expected 'enum'");
@@ -2036,9 +2071,9 @@ func parserParseEnumDecl(p: *Parser, isPublic: bool) -> *Decl {
}
discard parserExpect(p, tkRBrace, "expected '}'");
return d;
}
}
func parserParseImportDecl(p: *Parser, isPublic: bool) -> *Decl {
func parserParseImportDecl(p: *Parser, isPublic: bool) -> *Decl {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
discard parserExpect(p, tkImport, "expected 'import'");
@@ -2099,9 +2134,9 @@ func parserParseImportDecl(p: *Parser, isPublic: bool) -> *Decl {
parserMatch(p, tkSemicolon);
return d;
}
}
func parserParseExternDecl(p: *Parser, isPublic: bool) -> *Decl {
func parserParseExternDecl(p: *Parser, isPublic: bool) -> *Decl {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
discard parserExpect(p, tkExtern, "expected 'extern'");
@@ -2118,13 +2153,13 @@ func parserParseExternDecl(p: *Parser, isPublic: bool) -> *Decl {
d.column = col;
d.isPublic = isPublic;
return d;
}
}
// ---------------------------------------------------------------------------
// Interface declaration
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Interface declaration
// ---------------------------------------------------------------------------
func parserParseInterfaceDecl(p: *Parser, isPublic: bool) -> *Decl {
func parserParseInterfaceDecl(p: *Parser, isPublic: bool) -> *Decl {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
discard parserExpect(p, tkInterface, "expected 'interface'");
@@ -2159,13 +2194,13 @@ func parserParseInterfaceDecl(p: *Parser, isPublic: bool) -> *Decl {
d.childDecl1 = methods;
discard parserExpect(p, tkRBrace, "expected '}' to close interface");
return d;
}
}
// ---------------------------------------------------------------------------
// Top-level declaration
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Top-level declaration
// ---------------------------------------------------------------------------
func parserParseDecl(p: *Parser) -> *Decl {
func parserParseDecl(p: *Parser) -> *Decl {
// Skip newlines before declaration (matching bootstrap behavior)
while parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) {
discard parserAdvance(p);
@@ -2349,13 +2384,13 @@ func parserParseDecl(p: *Parser) -> *Decl {
parserEmitDiag(p, parserCurToken(p).line, parserCurToken(p).column, "skipping unknown declaration");
discard parserAdvance(p);
return null as *Decl;
}
}
// ---------------------------------------------------------------------------
// Module parsing
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Module parsing
// ---------------------------------------------------------------------------
func Parser_Parse(tokens: *LexToken, tokenCount: int) -> *Module {
func Parser_Parse(tokens: *LexToken, tokenCount: int) -> *Module {
let p: *Parser = bux_alloc(sizeof(Parser)) as *Parser;
p.tokens = tokens;
p.tokenCount = tokenCount;
@@ -2419,6 +2454,6 @@ func Parser_Parse(tokens: *LexToken, tokenCount: int) -> *Module {
}
return mod;
}
}
}
+29 -29
View File
@@ -1,17 +1,17 @@
// scope.bux — Symbol table with parent-chain lookup
module Scope {
// Symbol kinds
const skVar: int = 0;
const skFunc: int = 1;
const skType: int = 2;
const skConst: int = 3;
const skModule: int = 4;
// Symbol kinds
const skVar: int = 0;
const skFunc: int = 1;
const skType: int = 2;
const skConst: int = 3;
const skModule: int = 4;
// Maximum symbols per scope
const maxSymbols: int = 8192;
// Maximum symbols per scope
const maxSymbols: int = 8192;
struct Symbol {
struct Symbol {
kind: int;
name: String;
typeKind: int;
@@ -20,31 +20,31 @@ struct Symbol {
isMutable: bool;
isPublic: bool;
decl: *Decl; // associated declaration (for funcs, structs, enums)
}
}
struct Scope {
struct Scope {
symbols: *Symbol;
count: int;
parent: *Scope;
}
}
// ---------------------------------------------------------------------------
// Scope operations
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Scope operations
// ---------------------------------------------------------------------------
func Scope_New() -> Scope {
func Scope_New() -> Scope {
let sz: uint = maxSymbols as uint * sizeof(Symbol);
let data: *Symbol = bux_alloc(sz) as *Symbol;
return Scope { symbols: data, count: 0, parent: null as *Scope };
}
}
func Scope_NewChild(parent: *Scope) -> Scope {
func Scope_NewChild(parent: *Scope) -> Scope {
let sz: uint = maxSymbols as uint * sizeof(Symbol);
let data: *Symbol = bux_alloc(sz) as *Symbol;
return Scope { symbols: data, count: 0, parent: parent };
}
}
func Scope_Define(scope: *Scope, sym: Symbol) -> bool {
func Scope_Define(scope: *Scope, sym: Symbol) -> bool {
// Check local scope for duplicates
var i: int = 0;
while i < scope.count {
@@ -59,9 +59,9 @@ func Scope_Define(scope: *Scope, sym: Symbol) -> bool {
return true;
}
return false;
}
}
func Scope_Lookup(scope: *Scope, name: String) -> Symbol {
func Scope_Lookup(scope: *Scope, name: String) -> Symbol {
var cur: *Scope = scope;
while cur != null as *Scope {
var i: int = 0;
@@ -75,9 +75,9 @@ func Scope_Lookup(scope: *Scope, name: String) -> Symbol {
}
var empty: Symbol = Symbol { kind: 0, name: "", typeKind: 0, typeName: "", refType: null as *TypeExpr, isMutable: false, isPublic: false, decl: null as *Decl };
return empty;
}
}
func Scope_LookupLocal(scope: *Scope, name: String) -> Symbol {
func Scope_LookupLocal(scope: *Scope, name: String) -> Symbol {
var i: int = 0;
while i < scope.count {
if String_Eq(scope.symbols[i].name, name) {
@@ -87,9 +87,9 @@ func Scope_LookupLocal(scope: *Scope, name: String) -> Symbol {
}
var empty: Symbol = Symbol { kind: 0, name: "", typeKind: 0, typeName: "", refType: null as *TypeExpr, isMutable: false, isPublic: false, decl: null as *Decl };
return empty;
}
}
func Scope_LookupUpTo(scope: *Scope, name: String, limit: *Scope) -> Symbol {
func Scope_LookupUpTo(scope: *Scope, name: String, limit: *Scope) -> Symbol {
var cur: *Scope = scope;
while cur != null as *Scope {
var i: int = 0;
@@ -106,10 +106,10 @@ func Scope_LookupUpTo(scope: *Scope, name: String, limit: *Scope) -> Symbol {
}
var empty: Symbol = Symbol { kind: 0, name: "", typeKind: 0, typeName: "", refType: null as *TypeExpr, isMutable: false, isPublic: false, decl: null as *Decl };
return empty;
}
}
func Scope_Free(scope: *Scope) {
func Scope_Free(scope: *Scope) {
bux_free(scope.symbols as *void);
}
}
}
+385 -135
View File
@@ -2,15 +2,15 @@
// Validates types, resolves identifiers, checks function calls.
module Sema {
extern func bux_string_concat(a: String, b: String) -> String;
extern func bux_int_to_str(n: int64) -> String;
extern func bux_strlen(s: String) -> uint;
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
extern func bux_string_concat(a: String, b: String) -> String;
extern func bux_int_to_str(n: int64) -> String;
extern func bux_strlen(s: String) -> uint;
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
// ---------------------------------------------------------------------------
// Sema context
// ---------------------------------------------------------------------------
struct Sema {
// ---------------------------------------------------------------------------
// Sema context
// ---------------------------------------------------------------------------
struct Sema {
module: *Module;
scope: *Scope;
typeTable: *void;
@@ -30,6 +30,26 @@ struct Sema {
movedName5: String;
movedName6: String;
movedName7: String;
// Lifetime elision (C.1) — binding name → lifetime id (up to 8)
ltCount: int;
ltName0: String;
ltName1: String;
ltName2: String;
ltName3: String;
ltName4: String;
ltName5: String;
ltName6: String;
ltName7: String;
ltVal0: String;
ltVal1: String;
ltVal2: String;
ltVal3: String;
ltVal4: String;
ltVal5: String;
ltVal6: String;
ltVal7: String;
returnLifetime: String; // expected return ref lifetime ("" if not a ref return)
ltAnon: int; // next #elidedN counter
closureDepth: int; // nesting depth inside closures
currentClosureExpr: *Expr; // current closure being analyzed (for capture tracking)
closureScope: *Scope; // scope at which the current closure was entered
@@ -38,46 +58,46 @@ struct Sema {
interfaceCount: int;
methodEntries: *MethodEntry;
methodCount: int;
}
}
struct SemaDiag {
struct SemaDiag {
line: uint32;
column: uint32;
message: String;
}
}
// ---------------------------------------------------------------------------
// Interface / Method tables for trait bounds checking
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Interface / Method tables for trait bounds checking
// ---------------------------------------------------------------------------
struct InterfaceEntry {
struct InterfaceEntry {
name: String,
decl: *Decl,
}
}
struct MethodEntry {
struct MethodEntry {
typeName: String,
methodName: String,
decl: *Decl,
}
}
// ---------------------------------------------------------------------------
// Diagnostics
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Diagnostics
// ---------------------------------------------------------------------------
func Sema_EmitError(sema: *Sema, line: uint32, col: uint32, msg: String) {
func Sema_EmitError(sema: *Sema, line: uint32, col: uint32, msg: String) {
if sema.diagCount < 256 {
sema.diags[sema.diagCount] = SemaDiag { line: line, column: col, message: msg };
sema.diagCount = sema.diagCount + 1;
}
sema.hasError = true;
}
}
// ---------------------------------------------------------------------------
// Symbol zero-init helper (bootstrap C backend does not zero-init structs)
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Symbol zero-init helper (bootstrap C backend does not zero-init structs)
// ---------------------------------------------------------------------------
func Sema_ZeroInitSymbol(sym: *Symbol) {
func Sema_ZeroInitSymbol(sym: *Symbol) {
sym.kind = 0;
sym.name = "";
sym.typeKind = 0;
@@ -86,13 +106,13 @@ func Sema_ZeroInitSymbol(sym: *Symbol) {
sym.isMutable = false;
sym.isPublic = false;
sym.decl = null as *Decl;
}
}
// ---------------------------------------------------------------------------
// Build a tekFunc TypeExpr from a function declaration
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Build a tekFunc TypeExpr from a function declaration
// ---------------------------------------------------------------------------
func Sema_BuildFuncTypeExprFromDecl(decl: *Decl) -> *TypeExpr {
func Sema_BuildFuncTypeExprFromDecl(decl: *Decl) -> *TypeExpr {
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
te.kind = tekFunc;
te.line = decl.line;
@@ -128,13 +148,13 @@ func Sema_BuildFuncTypeExprFromDecl(decl: *Decl) -> *TypeExpr {
}
te.funcParams = head;
return te;
}
}
// ---------------------------------------------------------------------------
// Type resolution from TypeExpr → Type constants
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Type resolution from TypeExpr → Type constants
// ---------------------------------------------------------------------------
func Sema_ResolveType(sema: *Sema, te: *TypeExpr) -> int {
func Sema_ResolveType(sema: *Sema, te: *TypeExpr) -> int {
if te == null as *TypeExpr { return tyUnknown; }
if te.kind == tekPointer {
@@ -151,29 +171,29 @@ func Sema_ResolveType(sema: *Sema, te: *TypeExpr) -> int {
}
return Type_FromName(te.typeName);
}
}
// ---------------------------------------------------------------------------
// Type predicates
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Type predicates
// ---------------------------------------------------------------------------
func Sema_IsNumeric(kind: int) -> bool {
func Sema_IsNumeric(kind: int) -> bool {
if kind == tyUnknown || kind == tyNamed || kind == tyTypeParam { return true; }
if kind == tyInt8 || kind == tyInt16 || kind == tyInt32 || kind == tyInt64 || kind == tyInt { return true; }
if kind == tyUInt8 || kind == tyUInt16 || kind == tyUInt32 || kind == tyUInt64 || kind == tyUInt { return true; }
if kind == tyFloat32 || kind == tyFloat64 { return true; }
return false;
}
}
func Sema_IsBool(kind: int) -> bool {
func Sema_IsBool(kind: int) -> bool {
return kind == tyBool || kind == tyBool8 || kind == tyBool16 || kind == tyBool32;
}
}
// ---------------------------------------------------------------------------
// Block checking helper
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Block checking helper
// ---------------------------------------------------------------------------
func Sema_CheckBlock(sema: *Sema, block: *Block) {
func Sema_CheckBlock(sema: *Sema, block: *Block) {
if block == null as *Block { return; }
// Create child scope for block (matching Nim bootstrap behavior)
var blockScope: Scope = Scope_NewChild(sema.scope);
@@ -185,13 +205,13 @@ func Sema_CheckBlock(sema: *Sema, block: *Block) {
stmt = stmt.nextStmt;
}
sema.scope = prevScope;
}
}
// ---------------------------------------------------------------------------
// Call argument resolution: inject defaults and reorder named args
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Call argument resolution: inject defaults and reorder named args
// ---------------------------------------------------------------------------
func Sema_ResolveCallArgs(sema: *Sema, expr: *Expr) {
func Sema_ResolveCallArgs(sema: *Sema, expr: *Expr) {
if expr == null as *Expr || expr.kind != ekCall { return; }
if expr.child1 == null as *Expr || expr.child1.kind != ekIdent { return; }
let sym: Symbol = Scope_Lookup(sema.scope, expr.child1.strValue);
@@ -284,13 +304,13 @@ func Sema_ResolveCallArgs(sema: *Sema, expr: *Expr) {
expr.callArgs = newFirst;
expr.callArgCount = newCount;
}
}
// ---------------------------------------------------------------------------
// Borrow checker helpers (inline array, matching Decl param pattern)
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Borrow checker helpers (inline array, matching Decl param pattern)
// ---------------------------------------------------------------------------
func Sema_AddMoved(sema: *Sema, name: String) {
func Sema_AddMoved(sema: *Sema, name: String) {
if sema == null as *Sema { return; }
if sema.movedCount >= 8 { return; }
if sema.movedCount == 0 { sema.movedName0 = name; }
@@ -302,9 +322,9 @@ func Sema_AddMoved(sema: *Sema, name: String) {
else if sema.movedCount == 6 { sema.movedName6 = name; }
else if sema.movedCount == 7 { sema.movedName7 = name; }
sema.movedCount = sema.movedCount + 1;
}
}
func Sema_IsMoved(sema: *Sema, name: String) -> bool {
func Sema_IsMoved(sema: *Sema, name: String) -> bool {
if sema == null as *Sema { return false; }
if sema.movedCount > 0 && String_Eq(sema.movedName0, name) { return true; }
if sema.movedCount > 1 && String_Eq(sema.movedName1, name) { return true; }
@@ -315,9 +335,9 @@ func Sema_IsMoved(sema: *Sema, name: String) -> bool {
if sema.movedCount > 6 && String_Eq(sema.movedName6, name) { return true; }
if sema.movedCount > 7 && String_Eq(sema.movedName7, name) { return true; }
return false;
}
}
func Sema_RemoveMoved(sema: *Sema, name: String) {
func Sema_RemoveMoved(sema: *Sema, name: String) {
if sema == null as *Sema { return; }
var found: int = -1;
if sema.movedCount > 0 && String_Eq(sema.movedName0, name) { found = 0; }
@@ -342,13 +362,215 @@ func Sema_RemoveMoved(sema: *Sema, name: String) {
}
sema.movedCount = sema.movedCount - 1;
}
}
}
// ---------------------------------------------------------------------------
// Capture tracking for closures
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Lifetime elision helpers (C.1) — selfhost parity with bootstrap
// ---------------------------------------------------------------------------
func Sema_AddCapture(closureExpr: *Expr, name: String, typeKind: int) {
func Sema_ClearLifetimes(sema: *Sema) {
if sema == null as *Sema { return; }
sema.ltCount = 0;
sema.returnLifetime = "";
sema.ltAnon = 0;
}
func Sema_SetVarLifetime(sema: *Sema, name: String, lt: String) {
if sema == null as *Sema { return; }
if String_Eq(name, "") { return; }
// Update existing binding
if sema.ltCount > 0 && String_Eq(sema.ltName0, name) { sema.ltVal0 = lt; return; }
if sema.ltCount > 1 && String_Eq(sema.ltName1, name) { sema.ltVal1 = lt; return; }
if sema.ltCount > 2 && String_Eq(sema.ltName2, name) { sema.ltVal2 = lt; return; }
if sema.ltCount > 3 && String_Eq(sema.ltName3, name) { sema.ltVal3 = lt; return; }
if sema.ltCount > 4 && String_Eq(sema.ltName4, name) { sema.ltVal4 = lt; return; }
if sema.ltCount > 5 && String_Eq(sema.ltName5, name) { sema.ltVal5 = lt; return; }
if sema.ltCount > 6 && String_Eq(sema.ltName6, name) { sema.ltVal6 = lt; return; }
if sema.ltCount > 7 && String_Eq(sema.ltName7, name) { sema.ltVal7 = lt; return; }
if sema.ltCount >= 8 { return; }
if sema.ltCount == 0 { sema.ltName0 = name; sema.ltVal0 = lt; }
else if sema.ltCount == 1 { sema.ltName1 = name; sema.ltVal1 = lt; }
else if sema.ltCount == 2 { sema.ltName2 = name; sema.ltVal2 = lt; }
else if sema.ltCount == 3 { sema.ltName3 = name; sema.ltVal3 = lt; }
else if sema.ltCount == 4 { sema.ltName4 = name; sema.ltVal4 = lt; }
else if sema.ltCount == 5 { sema.ltName5 = name; sema.ltVal5 = lt; }
else if sema.ltCount == 6 { sema.ltName6 = name; sema.ltVal6 = lt; }
else if sema.ltCount == 7 { sema.ltName7 = name; sema.ltVal7 = lt; }
sema.ltCount = sema.ltCount + 1;
}
func Sema_GetVarLifetime(sema: *Sema, name: String) -> String {
if sema == null as *Sema { return ""; }
if sema.ltCount > 0 && String_Eq(sema.ltName0, name) { return sema.ltVal0; }
if sema.ltCount > 1 && String_Eq(sema.ltName1, name) { return sema.ltVal1; }
if sema.ltCount > 2 && String_Eq(sema.ltName2, name) { return sema.ltVal2; }
if sema.ltCount > 3 && String_Eq(sema.ltName3, name) { return sema.ltVal3; }
if sema.ltCount > 4 && String_Eq(sema.ltName4, name) { return sema.ltVal4; }
if sema.ltCount > 5 && String_Eq(sema.ltName5, name) { return sema.ltVal5; }
if sema.ltCount > 6 && String_Eq(sema.ltName6, name) { return sema.ltVal6; }
if sema.ltCount > 7 && String_Eq(sema.ltName7, name) { return sema.ltVal7; }
return "";
}
func Sema_DeclParam(decl: *Decl, i: int) -> *Param {
if decl == null as *Decl { return null as *Param; }
if i == 0 { return &decl.param0; }
if i == 1 { return &decl.param1; }
if i == 2 { return &decl.param2; }
if i == 3 { return &decl.param3; }
if i == 4 { return &decl.param4; }
if i == 5 { return &decl.param5; }
if i == 6 { return &decl.param6; }
if i == 7 { return &decl.param7; }
if i == 8 { return &decl.param8; }
return null as *Param;
}
func Sema_ApplyLifetimeElision(sema: *Sema, decl: *Decl) {
// Rust-style elision for @[Checked] functions (single-input + self).
Sema_ClearLifetimes(sema);
if sema == null as *Sema || decl == null as *Decl { return; }
if !sema.checkedFunc || sema.releaseFunc { return; }
var inputLt0: String = "";
var inputCount: int = 0;
var firstParamName: String = "";
var i: int = 0;
while i < decl.paramCount {
let p: *Param = Sema_DeclParam(decl, i);
if p != null as *Param && p.refParamType != null as *TypeExpr {
let pk: int = p.refParamType.kind;
if pk == tekRef || pk == tekMutRef {
var lt: String = p.refParamType.refLifetime;
if String_Eq(lt, "") {
lt = String_Concat("#elided", bux_int_to_str(sema.ltAnon as int64));
sema.ltAnon = sema.ltAnon + 1;
}
Sema_SetVarLifetime(sema, p.name, lt);
if inputCount == 0 { inputLt0 = lt; }
inputCount = inputCount + 1;
}
}
if i == 0 && p != null as *Param {
firstParamName = p.name;
}
i = i + 1;
}
if decl.retType == null as *TypeExpr { return; }
let rk: int = decl.retType.kind;
if rk != tekRef && rk != tekMutRef { return; }
var rlt: String = decl.retType.refLifetime;
if String_Eq(rlt, "") {
if inputCount == 1 {
rlt = inputLt0;
} else if inputCount == 0 {
rlt = "#out";
} else if String_Eq(firstParamName, "self") || String_Eq(firstParamName, "Self") {
rlt = inputLt0;
} else {
Sema_EmitError(sema, decl.line, decl.column,
"lifetime elision failed: return type needs an explicit lifetime (multiple input references); e.g. func F<'a>(a: &'a T, b: &'a U) -> &'a T");
rlt = "#ambiguous";
}
}
sema.returnLifetime = rlt;
}
func Sema_ExtractBorrowedIdent(expr: *Expr) -> String {
// Identify source var of &x
if expr == null as *Expr { return ""; }
if expr.kind == ekUnary && expr.intValue == tkAmp {
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
return expr.child1.strValue;
}
if expr.child1 != null as *Expr && expr.child1.kind == ekUnary &&
expr.child1.intValue == tkAmp && expr.child1.child1 != null as *Expr &&
expr.child1.child1.kind == ekIdent {
return expr.child1.child1.strValue;
}
}
return "";
}
func Sema_ExprRefLifetime(sema: *Sema, expr: *Expr) -> String {
if sema == null as *Sema || expr == null as *Expr { return ""; }
if expr.kind == ekIdent {
return Sema_GetVarLifetime(sema, expr.strValue);
}
if expr.kind == ekUnary && expr.intValue == tkAmp {
let name: String = Sema_ExtractBorrowedIdent(expr);
if String_Eq(name, "") { return "#local"; }
let existing: String = Sema_GetVarLifetime(sema, name);
if !String_Eq(existing, "") {
// Reborrow of an existing ref binding keeps its lifetime
let sym: Symbol = Scope_Lookup(sema.scope, name);
if sym.refType != null as *TypeExpr {
if sym.refType.kind == tekRef || sym.refType.kind == tekMutRef {
return existing;
}
}
// Named as input lifetime but not a ref type? still use it
return existing;
}
return "#local";
}
if expr.kind == ekUnary && expr.intValue == tkStar {
return Sema_ExprRefLifetime(sema, expr.child1);
}
if expr.kind == ekField {
let baseLt: String = Sema_ExprRefLifetime(sema, expr.child1);
if !String_Eq(baseLt, "") { return baseLt; }
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
let bl: String = Sema_GetVarLifetime(sema, expr.child1.strValue);
if !String_Eq(bl, "") { return bl; }
return "#local";
}
return "";
}
return "";
}
func Sema_CheckReturnLifetime(sema: *Sema, retExpr: *Expr, line: uint32, col: uint32) {
if sema == null as *Sema { return; }
if !sema.checkedFunc || sema.releaseFunc { return; }
if String_Eq(sema.returnLifetime, "") { return; }
if retExpr == null as *Expr { return; }
let got: String = Sema_ExprRefLifetime(sema, retExpr);
if String_Eq(sema.returnLifetime, "#out") {
Sema_EmitError(sema, line, col,
"cannot return a reference: function has no input reference to borrow from");
return;
}
if String_Eq(got, "#local") {
Sema_EmitError(sema, line, col, "cannot return reference to local variable");
return;
}
if String_Eq(got, "") { return; }
if String_Eq(got, "#ambiguous") || String_Eq(sema.returnLifetime, "#ambiguous") { return; }
// Explicit lifetime mismatch
if String_StartsWith(got, "'") && String_StartsWith(sema.returnLifetime, "'") &&
!String_Eq(got, sema.returnLifetime) {
Sema_EmitError(sema, line, col,
String_Concat("lifetime mismatch: returning '",
String_Concat(got, String_Concat("' but function returns '",
String_Concat(sema.returnLifetime, "'")))));
return;
}
if String_StartsWith(got, "#elided") && String_StartsWith(sema.returnLifetime, "#elided") &&
!String_Eq(got, sema.returnLifetime) {
Sema_EmitError(sema, line, col,
"lifetime mismatch: returned reference does not outlive the return type (multiple input references; annotate with an explicit lifetime)");
}
}
// ---------------------------------------------------------------------------
// Capture tracking for closures
// ---------------------------------------------------------------------------
func Sema_AddCapture(closureExpr: *Expr, name: String, typeKind: int) {
if closureExpr == null as *Expr { return; }
// Check if already captured
var i: int = 0;
@@ -378,15 +600,15 @@ func Sema_AddCapture(closureExpr: *Expr, name: String, typeKind: int) {
else if idx == 7 { closureExpr.captureName7 = name; closureExpr.captureType7 = typeKind; }
closureExpr.captureCount = closureExpr.captureCount + 1;
}
}
}
// ---------------------------------------------------------------------------
// Expression type checking
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Expression type checking
// ---------------------------------------------------------------------------
// Bind identifiers from a match pattern into the current scope.
// Enum payloads: Option::Some(value) → value:int (from variant field type).
func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
// Bind identifiers from a match pattern into the current scope.
// Enum payloads: Option::Some(value) → value:int (from variant field type).
func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
if pat == null as *Pattern { return; }
// Guarded: bind from inner pattern only (`p if cond`)
if pat.kind == pkGuarded {
@@ -582,9 +804,9 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
}
return;
}
}
}
func Sema_IsMutRefDeref(target: *Expr) -> bool {
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; }
@@ -592,9 +814,9 @@ func Sema_IsMutRefDeref(target: *Expr) -> bool {
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; }
let kind: int = expr.kind;
@@ -1176,13 +1398,13 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
}
return tyUnknown;
}
}
// ---------------------------------------------------------------------------
// Statement checking
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Statement checking
// ---------------------------------------------------------------------------
func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) {
func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) {
if stmt == null as *Stmt { return; }
let kind: int = stmt.kind;
@@ -1221,6 +1443,30 @@ func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) {
sym.isPublic = false;
sym.decl = null as *Decl;
discard Scope_Define(sema.scope, sym);
// Propagate ref lifetime for return-site checks
if sema.checkedFunc && !sema.releaseFunc && stmt.child1 != null as *Expr {
var isRefBind: bool = false;
if stmt.refStmtType != null as *TypeExpr {
if stmt.refStmtType.kind == tekRef || stmt.refStmtType.kind == tekMutRef {
isRefBind = true;
}
}
if !isRefBind && stmt.child1.refType != null as *TypeExpr {
if stmt.child1.refType.kind == tekRef || stmt.child1.refType.kind == tekMutRef {
isRefBind = true;
}
}
// Also treat address-of as creating a ref binding
if !isRefBind && stmt.child1.kind == ekUnary && stmt.child1.intValue == tkAmp {
isRefBind = true;
}
if isRefBind {
let lt: String = Sema_ExprRefLifetime(sema, stmt.child1);
if !String_Eq(lt, "") {
Sema_SetVarLifetime(sema, stmt.strValue, lt);
}
}
}
return;
}
@@ -1241,6 +1487,8 @@ func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) {
}
}
}
// C.1 lifetime: reject dangling returns / elision mismatches
Sema_CheckReturnLifetime(sema, stmt.child1, stmt.line, stmt.column);
} else {
if sema.currentRetType != tyVoid && sema.currentRetType != tyUnknown {
Sema_EmitError(sema, stmt.line, stmt.column, "missing return value");
@@ -1357,13 +1605,13 @@ func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) {
}
return;
}
}
}
// ---------------------------------------------------------------------------
// Collect globals (register functions, structs, enums in scope)
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Collect globals (register functions, structs, enums in scope)
// ---------------------------------------------------------------------------
func Sema_CollectGlobals(sema: *Sema) {
func Sema_CollectGlobals(sema: *Sema) {
var decl: *Decl = sema.module.firstItem;
var funcCount: int = 0;
var lastDecl: *Decl = null as *Decl;
@@ -1628,13 +1876,13 @@ func Sema_CollectGlobals(sema: *Sema) {
}
decl = decl.childDecl2;
}
}
}
// ---------------------------------------------------------------------------
// Trait bounds checking
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Trait bounds checking
// ---------------------------------------------------------------------------
func Sema_FindInterface(sema: *Sema, name: String) -> *Decl {
func Sema_FindInterface(sema: *Sema, name: String) -> *Decl {
var i: int = 0;
while i < sema.interfaceCount {
if String_Eq(sema.interfaceTable[i].name, name) {
@@ -1643,9 +1891,9 @@ func Sema_FindInterface(sema: *Sema, name: String) -> *Decl {
i = i + 1;
}
return null as *Decl;
}
}
func Sema_TypeHasMethod(sema: *Sema, typeName: String, methodName: String) -> bool {
func Sema_TypeHasMethod(sema: *Sema, typeName: String, methodName: String) -> bool {
var i: int = 0;
while i < sema.methodCount {
if String_Eq(sema.methodEntries[i].typeName, typeName) && String_Eq(sema.methodEntries[i].methodName, methodName) {
@@ -1654,9 +1902,9 @@ func Sema_TypeHasMethod(sema: *Sema, typeName: String, methodName: String) -> bo
i = i + 1;
}
return false;
}
}
func Sema_TypeImplements(sema: *Sema, typeName: String, interfaceName: String) -> bool {
func Sema_TypeImplements(sema: *Sema, typeName: String, interfaceName: String) -> bool {
let iface: *Decl = Sema_FindInterface(sema, interfaceName);
if iface == null as *Decl {
return true; // Unknown interface — be permissive
@@ -1671,11 +1919,11 @@ func Sema_TypeImplements(sema: *Sema, typeName: String, interfaceName: String) -
req = req.childDecl2;
}
return true;
}
}
// Extract element type name from a collection TypeExpr.
// Handles both explicit generic types (Array<int>) and mangled types (Array_int).
func Sema_ExtractElemType(te: *TypeExpr) -> String {
// Extract element type name from a collection TypeExpr.
// Handles both explicit generic types (Array<int>) and mangled types (Array_int).
func Sema_ExtractElemType(te: *TypeExpr) -> String {
if te == null as *TypeExpr { return ""; }
// Pointer: unwrap and recurse
if te.kind == tekPointer && te.pointerPointee != null as *TypeExpr {
@@ -1701,11 +1949,11 @@ func Sema_ExtractElemType(te: *TypeExpr) -> String {
}
}
return "";
}
}
// Substitute type params in a TypeExpr (shallow clone). Used for call return types
// after inference: Array<U> + U=String → Array with typeArgName0=String / Array_String.
func Sema_SubstTypeExpr(te: *TypeExpr, p0: String, a0: String, p1: String, a1: String, argc: int) -> *TypeExpr {
// Substitute type params in a TypeExpr (shallow clone). Used for call return types
// after inference: Array<U> + U=String → Array with typeArgName0=String / Array_String.
func Sema_SubstTypeExpr(te: *TypeExpr, p0: String, a0: String, p1: String, a1: String, argc: int) -> *TypeExpr {
if te == null as *TypeExpr { return null as *TypeExpr; }
let r: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
r.kind = te.kind;
@@ -1771,10 +2019,10 @@ func Sema_SubstTypeExpr(te: *TypeExpr, p0: String, a0: String, p1: String, a1: S
return r;
}
return r;
}
}
// Bind a type param name on the call callee if not already set.
func Sema_BindInferredArg(expr: *Expr, funcDecl: *Decl, tpName: String, typeName: String) {
// Bind a type param name on the call callee if not already set.
func Sema_BindInferredArg(expr: *Expr, funcDecl: *Decl, tpName: String, typeName: String) {
if String_Eq(tpName, "") || String_Eq(typeName, "") { return; }
if expr.child1 == null as *Expr { return; }
if funcDecl.typeParamCount >= 1 && String_Eq(tpName, funcDecl.typeParam0) {
@@ -1789,10 +2037,10 @@ func Sema_BindInferredArg(expr: *Expr, funcDecl: *Decl, tpName: String, typeName
}
if expr.child1.genericTypeArgCount < 2 { expr.child1.genericTypeArgCount = 2; }
}
}
}
// Resolve concrete type name for a value expression (for bare type-param params).
func Sema_ArgTypeName(argExpr: *Expr) -> String {
// Resolve concrete type name for a value expression (for bare type-param params).
func Sema_ArgTypeName(argExpr: *Expr) -> String {
if argExpr == null as *Expr { return ""; }
var argType: *TypeExpr = argExpr.refType;
if argType == null as *TypeExpr && argExpr.kind == ekUnary && argExpr.intValue == tkAmp {
@@ -1806,10 +2054,10 @@ func Sema_ArgTypeName(argExpr: *Expr) -> String {
}
}
return "";
}
}
// Infer type args from a param TypeExpr pattern against a concrete arg TypeExpr.
func Sema_UnifyInfer(expr: *Expr, funcDecl: *Decl, pattern: *TypeExpr, concrete: *TypeExpr) {
// Infer type args from a param TypeExpr pattern against a concrete arg TypeExpr.
func Sema_UnifyInfer(expr: *Expr, funcDecl: *Decl, pattern: *TypeExpr, concrete: *TypeExpr) {
if pattern == null as *TypeExpr || concrete == null as *TypeExpr { return; }
// Bare type param: T / Acc / U
@@ -1897,11 +2145,11 @@ func Sema_UnifyInfer(expr: *Expr, funcDecl: *Decl, pattern: *TypeExpr, concrete:
}
return;
}
}
}
// Infer generic type arguments from call arguments (structural).
// Handles *Array<T>, *Iter<T>, func(T)->U, bare Acc, etc.
func Sema_InferGenericArgs(sema: *Sema, funcDecl: *Decl, expr: *Expr) {
// Infer generic type arguments from call arguments (structural).
// Handles *Array<T>, *Iter<T>, func(T)->U, bare Acc, etc.
func Sema_InferGenericArgs(sema: *Sema, funcDecl: *Decl, expr: *Expr) {
if expr.callArgs == null as *ExprList { return; }
if expr.child1 == null as *Expr { return; }
var argList: *ExprList = expr.callArgs;
@@ -1950,9 +2198,9 @@ func Sema_InferGenericArgs(sema: *Sema, funcDecl: *Decl, expr: *Expr) {
argList = argList.next;
pi = pi + 1;
}
}
}
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) {
// Trait bounds checking
if funcDecl.typeParamCount >= 1 && typeArgCount >= 1 && !String_Eq(funcDecl.typeParam0Bound, "") {
if !Sema_TypeImplements(sema, typeArg0, funcDecl.typeParam0Bound) {
@@ -1968,13 +2216,13 @@ func Sema_CheckTraitBounds(sema: *Sema, funcDecl: *Decl, typeArg0: String, typeA
Sema_EmitError(sema, line, col, errMsg2);
}
}
}
}
// ---------------------------------------------------------------------------
// Analyze — main entry point
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Analyze — main entry point
// ---------------------------------------------------------------------------
func Sema_Analyze(mod: *Module) -> *Sema {
func Sema_Analyze(mod: *Module) -> *Sema {
let s: *Sema = bux_alloc(sizeof(Sema)) as *Sema;
s.module = mod;
s.scope = bux_alloc(sizeof(Scope)) as *Scope;
@@ -2075,8 +2323,6 @@ func Sema_Analyze(mod: *Module) -> *Sema {
s.currentRetType = Sema_ResolveType(s, decl.retType);
} else {
s.currentRetType = tyVoid;
s.checkedFunc = false;
s.movedCount = 0;
}
// Enable borrow checking for @[Checked] functions
@@ -2084,6 +2330,9 @@ func Sema_Analyze(mod: *Module) -> *Sema {
s.checkedFunc = decl.isChecked != 0;
let wasRelease: bool = s.releaseFunc;
s.releaseFunc = decl.isRelease != 0;
s.movedCount = 0;
// C.1: lifetime elision before walking the body
Sema_ApplyLifetimeElision(s, decl);
// Check body statements
var stmt: *Stmt = decl.refBody.firstStmt;
@@ -2094,27 +2343,28 @@ func Sema_Analyze(mod: *Module) -> *Sema {
s.checkedFunc = wasChecked;
s.releaseFunc = wasRelease;
Sema_ClearLifetimes(s);
s.scope = prevScope;
}
decl = decl.childDecl2;
}
return s;
}
}
func Sema_HasError(sema: *Sema) -> bool {
func Sema_HasError(sema: *Sema) -> bool {
return sema.hasError;
}
}
func Sema_DiagCount(sema: *Sema) -> int {
func Sema_DiagCount(sema: *Sema) -> int {
return sema.diagCount;
}
}
func Sema_Free(sema: *Sema) {
func Sema_Free(sema: *Sema) {
bux_free(sema.scope.symbols as *void);
bux_free(sema.scope as *void);
bux_free(sema.diags as *void);
bux_free(sema as *void);
}
}
}
+4 -4
View File
@@ -1,13 +1,13 @@
// source_location.bux — Source position tracking
module SourceLocation {
struct SourceLocation {
struct SourceLocation {
line: uint32;
column: uint32;
offset: uint32;
}
}
func SourceLocation_New(line: uint32, column: uint32, offset: uint32) -> SourceLocation {
func SourceLocation_New(line: uint32, column: uint32, offset: uint32) -> SourceLocation {
return SourceLocation { line: line, column: column, offset: offset };
}
}
}
+152 -148
View File
@@ -1,166 +1,169 @@
// token.bux — Token kinds and helpers
module Token {
// ---------------------------------------------------------------------------
// TokenKind enum
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// TokenKind enum
// ---------------------------------------------------------------------------
// Literals
const tkIntLiteral: int = 0;
const tkFloatLiteral: int = 1;
const tkStringLiteral: int = 2;
const tkCharLiteral: int = 3;
const tkBoolLiteral: int = 4;
// Literals
const tkIntLiteral: int = 0;
const tkFloatLiteral: int = 1;
const tkStringLiteral: int = 2;
const tkCharLiteral: int = 3;
const tkBoolLiteral: int = 4;
// Identifiers
const tkIdent: int = 5;
const tkUnderscore: int = 6;
// Identifiers
const tkIdent: int = 5;
const tkUnderscore: int = 6;
// Control flow keywords
const tkIf: int = 7;
const tkElse: int = 8;
const tkWhile: int = 9;
const tkDo: int = 10;
const tkLoop: int = 11;
const tkFor: int = 12;
const tkIn: int = 13;
const tkBreak: int = 14;
const tkContinue: int = 15;
const tkReturn: int = 16;
const tkMatch: int = 17;
// Control flow keywords
const tkIf: int = 7;
const tkElse: int = 8;
const tkWhile: int = 9;
const tkDo: int = 10;
const tkLoop: int = 11;
const tkFor: int = 12;
const tkIn: int = 13;
const tkBreak: int = 14;
const tkContinue: int = 15;
const tkReturn: int = 16;
const tkMatch: int = 17;
// Declaration keywords
const tkFunc: int = 18;
const tkLet: int = 19;
const tkVar: int = 20;
const tkConst: int = 21;
const tkType: int = 22;
const tkStruct: int = 23;
const tkEnum: int = 24;
const tkUnion: int = 25;
const tkInterface: int = 26;
const tkExtend: int = 27;
const tkModule: int = 28;
const tkImport: int = 29;
const tkPub: int = 30;
const tkExtern: int = 31;
// Declaration keywords
const tkFunc: int = 18;
const tkLet: int = 19;
const tkVar: int = 20;
const tkConst: int = 21;
const tkType: int = 22;
const tkStruct: int = 23;
const tkEnum: int = 24;
const tkUnion: int = 25;
const tkInterface: int = 26;
const tkExtend: int = 27;
const tkModule: int = 28;
const tkImport: int = 29;
const tkPub: int = 30;
const tkExtern: int = 31;
// Other keywords
const tkAs: int = 32;
const tkIs: int = 33;
const tkNull: int = 34;
const tkSelf: int = 35;
const tkSuper: int = 36;
const tkSizeOf: int = 37;
// Other keywords
const tkAs: int = 32;
const tkIs: int = 33;
const tkNull: int = 34;
const tkSelf: int = 35;
const tkSuper: int = 36;
const tkSizeOf: int = 37;
// Punctuation
const tkLParen: int = 38;
const tkRParen: int = 39;
const tkLBrace: int = 40;
const tkRBrace: int = 41;
const tkLBracket: int = 42;
const tkRBracket: int = 43;
const tkComma: int = 44;
const tkSemicolon: int = 45;
const tkColon: int = 46;
const tkColonColon: int = 47;
const tkDot: int = 48;
const tkDotDot: int = 49;
const tkDotDotDot: int = 50;
const tkDotDotEqual: int = 51;
const tkArrow: int = 52;
const tkFatArrow: int = 53;
const tkAt: int = 54;
const tkHash: int = 55;
const tkQuestion: int = 56;
// Punctuation
const tkLParen: int = 38;
const tkRParen: int = 39;
const tkLBrace: int = 40;
const tkRBrace: int = 41;
const tkLBracket: int = 42;
const tkRBracket: int = 43;
const tkComma: int = 44;
const tkSemicolon: int = 45;
const tkColon: int = 46;
const tkColonColon: int = 47;
const tkDot: int = 48;
const tkDotDot: int = 49;
const tkDotDotDot: int = 50;
const tkDotDotEqual: int = 51;
const tkArrow: int = 52;
const tkFatArrow: int = 53;
const tkAt: int = 54;
const tkHash: int = 55;
const tkQuestion: int = 56;
// Arithmetic operators
const tkPlus: int = 57;
const tkMinus: int = 58;
const tkStar: int = 59;
const tkSlash: int = 60;
const tkPercent: int = 61;
const tkStarStar: int = 62;
const tkPlusPlus: int = 63;
const tkMinusMinus: int = 64;
// Arithmetic operators
const tkPlus: int = 57;
const tkMinus: int = 58;
const tkStar: int = 59;
const tkSlash: int = 60;
const tkPercent: int = 61;
const tkStarStar: int = 62;
const tkPlusPlus: int = 63;
const tkMinusMinus: int = 64;
// Bitwise operators
const tkAmp: int = 65;
const tkPipe: int = 66;
const tkCaret: int = 67;
const tkTilde: int = 68;
const tkShl: int = 69;
const tkShr: int = 70;
// Bitwise operators
const tkAmp: int = 65;
const tkPipe: int = 66;
const tkCaret: int = 67;
const tkTilde: int = 68;
const tkShl: int = 69;
const tkShr: int = 70;
// Logical operators
const tkAmpAmp: int = 71;
const tkPipePipe: int = 72;
const tkBang: int = 73;
// Logical operators
const tkAmpAmp: int = 71;
const tkPipePipe: int = 72;
const tkBang: int = 73;
// Comparison operators
const tkEq: int = 74;
const tkNe: int = 75;
const tkLt: int = 76;
const tkLe: int = 77;
const tkGt: int = 78;
const tkGe: int = 79;
// Comparison operators
const tkEq: int = 74;
const tkNe: int = 75;
const tkLt: int = 76;
const tkLe: int = 77;
const tkGt: int = 78;
const tkGe: int = 79;
// Assignment operators
const tkAssign: int = 80;
const tkPlusAssign: int = 81;
const tkMinusAssign: int = 82;
const tkStarAssign: int = 83;
const tkSlashAssign: int = 84;
const tkPercentAssign: int = 85;
const tkAmpAssign: int = 86;
const tkPipeAssign: int = 87;
const tkCaretAssign: int = 88;
const tkShlAssign: int = 89;
const tkShrAssign: int = 90;
// Assignment operators
const tkAssign: int = 80;
const tkPlusAssign: int = 81;
const tkMinusAssign: int = 82;
const tkStarAssign: int = 83;
const tkSlashAssign: int = 84;
const tkPercentAssign: int = 85;
const tkAmpAssign: int = 86;
const tkPipeAssign: int = 87;
const tkCaretAssign: int = 88;
const tkShlAssign: int = 89;
const tkShrAssign: int = 90;
// Compile-time intrinsics
const tkHashLine: int = 91;
const tkHashColumn: int = 92;
const tkHashFile: int = 93;
const tkHashFunction: int = 94;
const tkHashDate: int = 95;
const tkHashTime: int = 96;
const tkHashModule: int = 97;
// Compile-time intrinsics
const tkHashLine: int = 91;
const tkHashColumn: int = 92;
const tkHashFile: int = 93;
const tkHashFunction: int = 94;
const tkHashDate: int = 95;
const tkHashTime: int = 96;
const tkHashModule: int = 97;
// Special
const tkOwn: int = 98;
const tkNewLine: int = 99;
const tkEndOfFile: int = 100;
const tkUnknown: int = 101;
// Special
const tkOwn: int = 98;
const tkNewLine: int = 99;
const tkEndOfFile: int = 100;
const tkUnknown: int = 101;
// Async / concurrency
const tkAsync: int = 102;
const tkAwait: int = 103;
const tkSpawn: int = 104;
const tkDiscard: int = 105;
const tkDefer: int = 106;
const tkSwitch: int = 107;
const tkCase: int = 108;
const tkDefault: int = 109;
const tkUnsafe: int = 110;
// Async / concurrency
const tkAsync: int = 102;
const tkAwait: int = 103;
const tkSpawn: int = 104;
const tkDiscard: int = 105;
const tkDefer: int = 106;
const tkSwitch: int = 107;
const tkCase: int = 108;
const tkDefault: int = 109;
const tkUnsafe: int = 110;
// ---------------------------------------------------------------------------
// Token struct
// ---------------------------------------------------------------------------
// Lifetime parameter token: 'a, 'b, ... (not a char literal)
const tkLifetime: int = 111;
struct Token {
// ---------------------------------------------------------------------------
// Token struct
// ---------------------------------------------------------------------------
struct Token {
kind: int;
text: String;
line: uint32;
column: uint32;
offset: uint32;
}
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
func Token_IsKeyword(kind: int) -> bool {
func Token_IsKeyword(kind: int) -> bool {
if kind >= tkIf && kind <= tkIn { return true; }
if kind >= tkBreak && kind <= tkMatch { return true; }
if kind >= tkFunc && kind <= tkExtern { return true; }
@@ -169,23 +172,23 @@ func Token_IsKeyword(kind: int) -> bool {
if kind >= tkDefer && kind <= tkUnsafe { return true; }
if kind >= tkAsync && kind <= tkSpawn { return true; }
return false;
}
}
func Token_IsLiteral(kind: int) -> bool {
func Token_IsLiteral(kind: int) -> bool {
if kind >= tkIntLiteral && kind <= tkBoolLiteral { return true; }
return false;
}
}
func Token_IsOperator(kind: int) -> bool {
func Token_IsOperator(kind: int) -> bool {
if kind >= tkPlus && kind <= tkShrAssign { return true; }
return false;
}
}
func Token_IsEof(kind: int) -> bool {
func Token_IsEof(kind: int) -> bool {
return kind == tkEndOfFile;
}
}
func Token_KeywordKind(text: String) -> int {
func Token_KeywordKind(text: String) -> int {
if String_Eq(text, "func") { return tkFunc; }
if String_Eq(text, "let") { return tkLet; }
if String_Eq(text, "var") { return tkVar; }
@@ -229,9 +232,9 @@ func Token_KeywordKind(text: String) -> int {
if String_Eq(text, "true") { return tkBoolLiteral; }
if String_Eq(text, "false") { return tkBoolLiteral; }
return tkIdent;
}
}
func Token_KindName(kind: int) -> String {
func Token_KindName(kind: int) -> String {
if kind == tkIntLiteral { return "integer literal"; }
if kind == tkFloatLiteral { return "float literal"; }
if kind == tkStringLiteral { return "string literal"; }
@@ -339,8 +342,9 @@ func Token_KindName(kind: int) -> String {
if kind == tkHashDate { return "#date"; }
if kind == tkHashTime { return "#time"; }
if kind == tkHashModule { return "#module"; }
if kind == tkLifetime { return "lifetime"; }
if kind == tkNewLine { return "newline"; }
if kind == tkEndOfFile { return "end of file"; }
return "unknown token";
}
}
}
+109 -109
View File
@@ -1,45 +1,45 @@
// types.bux — Type system definitions and factories
module Types {
// ---------------------------------------------------------------------------
// TypeKind constants
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// TypeKind constants
// ---------------------------------------------------------------------------
const tyUnknown: int = 0;
const tyVoid: int = 1;
const tyBool: int = 2;
const tyBool8: int = 3;
const tyBool16: int = 4;
const tyBool32: int = 5;
const tyChar8: int = 6;
const tyChar16: int = 7;
const tyChar32: int = 8;
const tyStr: int = 9;
const tyInt8: int = 10;
const tyInt16: int = 11;
const tyInt32: int = 12;
const tyInt64: int = 13;
const tyInt: int = 14;
const tyUInt8: int = 15;
const tyUInt16: int = 16;
const tyUInt32: int = 17;
const tyUInt64: int = 18;
const tyUInt: int = 19;
const tyFloat32: int = 20;
const tyFloat64: int = 21;
const tyPointer: int = 22;
const tySlice: int = 23;
const tyRange: int = 24;
const tyTuple: int = 25;
const tyNamed: int = 26;
const tyTypeParam: int = 27;
const tyFunc: int = 28;
const tyUnknown: int = 0;
const tyVoid: int = 1;
const tyBool: int = 2;
const tyBool8: int = 3;
const tyBool16: int = 4;
const tyBool32: int = 5;
const tyChar8: int = 6;
const tyChar16: int = 7;
const tyChar32: int = 8;
const tyStr: int = 9;
const tyInt8: int = 10;
const tyInt16: int = 11;
const tyInt32: int = 12;
const tyInt64: int = 13;
const tyInt: int = 14;
const tyUInt8: int = 15;
const tyUInt16: int = 16;
const tyUInt32: int = 17;
const tyUInt64: int = 18;
const tyUInt: int = 19;
const tyFloat32: int = 20;
const tyFloat64: int = 21;
const tyPointer: int = 22;
const tySlice: int = 23;
const tyRange: int = 24;
const tyTuple: int = 25;
const tyNamed: int = 26;
const tyTypeParam: int = 27;
const tyFunc: int = 28;
// ---------------------------------------------------------------------------
// Type struct
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Type struct
// ---------------------------------------------------------------------------
struct Type {
struct Type {
kind: int;
name: String;
// inner types stored as array of pointers (simplified)
@@ -50,129 +50,129 @@ struct Type {
innerKind3: int;
innerName3: String;
innerCount: int;
}
}
// ---------------------------------------------------------------------------
// Factories
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Factories
// ---------------------------------------------------------------------------
func Type_MakeUnknown() -> Type {
func Type_MakeUnknown() -> Type {
return Type { kind: tyUnknown, name: "", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
}
func Type_MakeVoid() -> Type {
func Type_MakeVoid() -> Type {
return Type { kind: tyVoid, name: "void", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
}
func Type_MakeBool() -> Type {
func Type_MakeBool() -> Type {
return Type { kind: tyBool, name: "bool", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
}
func Type_MakeInt() -> Type {
func Type_MakeInt() -> Type {
return Type { kind: tyInt, name: "int", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
}
func Type_MakeInt64() -> Type {
func Type_MakeInt64() -> Type {
return Type { kind: tyInt64, name: "int64", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
}
func Type_MakeUInt() -> Type {
func Type_MakeUInt() -> Type {
return Type { kind: tyUInt, name: "uint", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
}
func Type_MakeFloat64() -> Type {
func Type_MakeFloat64() -> Type {
return Type { kind: tyFloat64, name: "float64", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
}
func Type_MakeStr() -> Type {
func Type_MakeStr() -> Type {
return Type { kind: tyStr, name: "String", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
}
func Type_MakePointer(pointee: Type) -> Type {
func Type_MakePointer(pointee: Type) -> Type {
return Type { kind: tyPointer, name: "", innerCount: 1,
innerKind1: pointee.kind, innerName1: pointee.name,
innerKind2: 0, innerName2: "", innerKind3: 0, innerName3: "" };
}
}
func Type_MakeNamed(name: String) -> Type {
func Type_MakeNamed(name: String) -> Type {
return Type { kind: tyNamed, name: name, innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
}
func Type_MakeTypeParam(name: String) -> Type {
func Type_MakeTypeParam(name: String) -> Type {
return Type { kind: tyTypeParam, name: name, innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
}
// ---------------------------------------------------------------------------
// Predicates
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Predicates
// ---------------------------------------------------------------------------
func Type_IsNumeric(t: Type) -> bool {
func Type_IsNumeric(t: Type) -> bool {
let k: int = t.kind;
if k == tyInt8 || k == tyInt16 || k == tyInt32 || k == tyInt64 || k == tyInt { return true; }
if k == tyUInt8 || k == tyUInt16 || k == tyUInt32 || k == tyUInt64 || k == tyUInt { return true; }
if k == tyFloat32 || k == tyFloat64 { return true; }
if k == tyUnknown || k == tyNamed || k == tyTypeParam { return true; }
return false;
}
}
func Type_IsInteger(t: Type) -> bool {
func Type_IsInteger(t: Type) -> bool {
let k: int = t.kind;
if k == tyInt8 || k == tyInt16 || k == tyInt32 || k == tyInt64 || k == tyInt { return true; }
if k == tyUInt8 || k == tyUInt16 || k == tyUInt32 || k == tyUInt64 || k == tyUInt { return true; }
if k == tyUnknown || k == tyNamed || k == tyTypeParam { return true; }
return false;
}
}
func Type_IsBool(t: Type) -> bool {
func Type_IsBool(t: Type) -> bool {
let k: int = t.kind;
return k == tyBool || k == tyBool8 || k == tyBool16 || k == tyBool32;
}
}
func Type_IsPointer(t: Type) -> bool {
func Type_IsPointer(t: Type) -> bool {
return t.kind == tyPointer;
}
}
func Type_IsSlice(t: Type) -> bool {
func Type_IsSlice(t: Type) -> bool {
return t.kind == tySlice;
}
}
// ---------------------------------------------------------------------------
// Comparison (structural, limited to kind + name for simplicity)
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Comparison (structural, limited to kind + name for simplicity)
// ---------------------------------------------------------------------------
func Type_Eq(a: Type, b: Type) -> bool {
func Type_Eq(a: Type, b: Type) -> bool {
if a.kind != b.kind { return false; }
if a.kind == tyNamed || a.kind == tyTypeParam {
return String_Eq(a.name, b.name);
}
return true;
}
}
// ---------------------------------------------------------------------------
// toString
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// toString
// ---------------------------------------------------------------------------
func Type_ToString(t: Type) -> String {
func Type_ToString(t: Type) -> String {
if t.kind == tyVoid { return "void"; }
if t.kind == tyBool { return "bool"; }
if t.kind == tyStr { return "String"; }
@@ -185,13 +185,13 @@ func Type_ToString(t: Type) -> String {
if t.kind == tyPointer { return String_Concat("*", t.innerName1); }
if t.kind == tyFunc { return t.name; }
return "?";
}
}
// ---------------------------------------------------------------------------
// Type_FromName — central type-name → kind mapping (used by sema, hir_lower)
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Type_FromName — central type-name → kind mapping (used by sema, hir_lower)
// ---------------------------------------------------------------------------
func Type_FromName(name: String) -> int {
func Type_FromName(name: String) -> int {
if String_Eq(name, "void") { return tyVoid; }
if String_Eq(name, "bool") { return tyBool; }
if String_Eq(name, "bool8") { return tyBool8; }
@@ -216,13 +216,13 @@ func Type_FromName(name: String) -> int {
if String_Eq(name, "float64") { return tyFloat64; }
if String_Eq(name, "float") { return tyFloat64; }
return tyNamed;
}
}
// ---------------------------------------------------------------------------
// Type_ToCName — type kind → C type name (used by C backend)
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Type_ToCName — type kind → C type name (used by C backend)
// ---------------------------------------------------------------------------
func Type_ToCName(kind: int) -> String {
func Type_ToCName(kind: int) -> String {
if kind == tyVoid { return "void"; }
if kind == tyBool || kind == tyBool8 || kind == tyBool16 || kind == tyBool32 { return "bool"; }
if kind == tyChar8 { return "char"; }
@@ -245,29 +245,29 @@ func Type_ToCName(kind: int) -> String {
// Fat function pointer — concrete BuxFn_* name comes from typeName field
if kind == tyFunc { return "BuxFn"; }
return "";
}
}
// ---------------------------------------------------------------------------
// Signed / Unsigned / Float predicates
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Signed / Unsigned / Float predicates
// ---------------------------------------------------------------------------
func Type_IsSigned(kind: int) -> bool {
func Type_IsSigned(kind: int) -> bool {
return kind == tyInt8 || kind == tyInt16 || kind == tyInt32 || kind == tyInt64 || kind == tyInt;
}
}
func Type_IsUnsigned(kind: int) -> bool {
func Type_IsUnsigned(kind: int) -> bool {
return kind == tyUInt8 || kind == tyUInt16 || kind == tyUInt32 || kind == tyUInt64 || kind == tyUInt;
}
}
func Type_IsFloat(kind: int) -> bool {
func Type_IsFloat(kind: int) -> bool {
return kind == tyFloat32 || kind == tyFloat64;
}
}
// ---------------------------------------------------------------------------
// Type_SizeOf — byte size of a primitive type (0 for non-primitive)
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Type_SizeOf — byte size of a primitive type (0 for non-primitive)
// ---------------------------------------------------------------------------
func Type_SizeOf(kind: int) -> int {
func Type_SizeOf(kind: int) -> int {
if kind == tyBool || kind == tyBool8 || kind == tyChar8 || kind == tyInt8 || kind == tyUInt8 { return 1; }
if kind == tyBool16 || kind == tyChar16 || kind == tyInt16 || kind == tyUInt16 { return 2; }
if kind == tyBool32 || kind == tyChar32 || kind == tyInt32 || kind == tyUInt32 || kind == tyFloat32 { return 4; }
@@ -275,5 +275,5 @@ func Type_SizeOf(kind: int) -> int {
if kind == tyInt || kind == tyUInt { return 8; }
if kind == tyPointer { return 8; }
return 0;
}
}
}
+121
View File
@@ -248,5 +248,126 @@ func Main() -> int {
let val: int = (*r).x;
return val;
}
""")
check(not res.hasErrors)
# --- C.1 Lifetime elision ---
test "@[Checked] elided lifetime: return param ref is OK":
let res = checkSource("""
@[Checked]
func Identity(p: &int) -> &int {
return p;
}
@[Checked]
func Main() -> int {
var x: int = 7;
let r: &int = Identity(&x);
return *r;
}
""")
check(not res.hasErrors)
test "@[Checked] explicit lifetime 'a works":
let res = checkSource("""
@[Checked]
func Identity<'a>(p: &'a int) -> &'a int {
return p;
}
@[Checked]
func Main() -> int {
var x: int = 3;
let r: &int = Identity(&x);
return *r;
}
""")
check(not res.hasErrors)
test "@[Checked] rejects return of reference to local":
let res = checkSource("""
@[Checked]
func Dangle(p: &int) -> &int {
var x: int = 1;
return &x;
}
@[Checked]
func Main() -> int {
return 0;
}
""")
check(res.hasErrors)
check(res.diagnostics[0].message.contains("local"))
test "@[Checked] rejects return ref with no input reference":
let res = checkSource("""
@[Checked]
func Bad() -> &int {
var x: int = 1;
return &x;
}
@[Checked]
func Main() -> int {
return 0;
}
""")
check(res.hasErrors)
check(res.diagnostics[0].message.contains("no input reference") or
res.diagnostics[0].message.contains("local"))
test "@[Checked] elision fails with multiple input refs":
let res = checkSource("""
@[Checked]
func Pick(a: &int, b: &int) -> &int {
return a;
}
@[Checked]
func Main() -> int {
return 0;
}
""")
check(res.hasErrors)
check(res.diagnostics[0].message.contains("lifetime elision failed") or
res.diagnostics[0].message.contains("lifetime mismatch"))
test "@[Checked] multiple inputs OK with explicit lifetime":
let res = checkSource("""
@[Checked]
func Pick<'a>(a: &'a int, b: &'a int) -> &'a int {
return a;
}
@[Checked]
func Main() -> int {
var x: int = 1;
var y: int = 2;
let r: &int = Pick(&x, &y);
return *r;
}
""")
check(not res.hasErrors)
test "@[Checked] let-bound reborrow of param may be returned":
let res = checkSource("""
@[Checked]
func ViaLet(p: &int) -> &int {
let r: &int = p;
return r;
}
@[Checked]
func Main() -> int {
var x: int = 9;
return *ViaLet(&x);
}
""")
check(not res.hasErrors)
test "unchecked may return &local (no lifetime checks)":
let res = checkSource("""
func Dangle() -> &int {
var x: int = 1;
return &x;
}
func Main() -> int {
return 0;
}
""")
check(not res.hasErrors)
@@ -0,0 +1,7 @@
[Package]
Name = "elision_multi_input"
Version = "0.1.0"
Type = "bin"
[Build]
Output = "Bin"
@@ -0,0 +1,7 @@
error: type errors in project
error: lifetime elision failed: return type needs an explicit lifetime (multiple input references); e.g. func F<'a>(a: &'a T, b: &'a U) -> &'a T
--> FILE:2:1
|
2 | func Pick(a: &int, b: &int) -> &int {
| ^^^^
= help: add an explicit lifetime, e.g. func F<'a>(x: &'a T, y: &'a U) -> &'a T
@@ -0,0 +1,9 @@
@[Checked]
func Pick(a: &int, b: &int) -> &int {
return a;
}
@[Checked]
func Main() -> int {
return 0;
}
@@ -0,0 +1,7 @@
[Package]
Name = "return_local_ref"
Version = "0.1.0"
Type = "bin"
[Build]
Output = "Bin"
@@ -0,0 +1,7 @@
error: type errors in project
error: cannot return reference to local variable
--> FILE:4:5
|
4 | return &x;
| ^^^^^^
= help: return a value, or return a reference borrowed from a function parameter
@@ -0,0 +1,10 @@
@[Checked]
func Dangle(p: &int) -> &int {
var x: int = 42;
return &x;
}
@[Checked]
func Main() -> int {
return 0;
}
+12 -12
View File
@@ -1,31 +1,31 @@
module Main {
import Std::Array::{Array, Array_New, Array_Push};
import Std::Array::{Array, Array_New, Array_Push};
struct Record {
struct Record {
name: String;
value: int;
}
}
struct Box {
struct Box {
items: Array<Record>;
}
}
enum Payload {
enum Payload {
Ok(Record),
Err(String),
}
}
enum Color { Red, Green }
enum Color { Red, Green }
func Name(c: Color) -> String {
func Name(c: Color) -> String {
match c {
Color::Red => "red",
Color::Green => "green",
}
}
}
func Main() -> int {
func Main() -> int {
var box: Box;
box.items = Array_New<Record>(4);
Array_Push<Record>(&box.items, Record { name: "x", value: 10 });
@@ -44,6 +44,6 @@ func Main() -> int {
}
return 1;
}
}
}
+7
View File
@@ -0,0 +1,7 @@
[Package]
Name = "stdlib_array"
Version = "0.1.0"
Type = "bin"
[Build]
Output = "Bin"
+3
View File
@@ -0,0 +1,3 @@
stdlib_array: ok
PASS:
stdlib_array
+50
View File
@@ -0,0 +1,50 @@
// Stdlib golden: Array helpers + Contains/IndexOf/Extend
import Std::Io::{PrintLine};
import Std::Array::{
Array, Array_New, Array_Push, Array_Pop, Array_Clear, Array_IsEmpty,
Array_First, Array_Last, Array_Cap, Array_Reserve, Array_Len, Array_Get,
Array_Contains, Array_IndexOf, Array_Extend, Array_Free
};
import Std::Test::{
Test_AssertTrue, Test_AssertFalse, Test_AssertEqInt, Test_Pass
};
func Main() -> int {
var arr: Array<int> = Array_New<int>(2);
Array_Reserve<int>(&arr, 8);
Test_AssertTrue(Array_Cap<int>(&arr) >= 8);
Test_AssertTrue(Array_IsEmpty<int>(&arr));
Array_Push<int>(&arr, 10);
Array_Push<int>(&arr, 20);
Array_Push<int>(&arr, 30);
Test_AssertFalse(Array_IsEmpty<int>(&arr));
Test_AssertEqInt(Array_Len<int>(&arr) as int, 3);
Test_AssertEqInt(Array_First<int>(&arr), 10);
Test_AssertEqInt(Array_Last<int>(&arr), 30);
Test_AssertTrue(Array_Contains<int>(&arr, 20));
Test_AssertFalse(Array_Contains<int>(&arr, 99));
Test_AssertEqInt(Array_IndexOf<int>(&arr, 30), 2);
let popped: int = Array_Pop<int>(&arr);
Test_AssertEqInt(popped, 30);
Test_AssertEqInt(Array_Len<int>(&arr) as int, 2);
var extra: Array<int> = Array_New<int>(2);
Array_Push<int>(&extra, 40);
Array_Push<int>(&extra, 50);
Array_Extend<int>(&arr, &extra);
Test_AssertEqInt(Array_Len<int>(&arr) as int, 4);
Test_AssertEqInt(Array_Get<int>(&arr, 3), 50);
Array_Clear<int>(&arr);
Test_AssertTrue(Array_IsEmpty<int>(&arr));
Test_AssertTrue(Array_Cap<int>(&arr) >= 8);
Array_Free<int>(&arr);
Array_Free<int>(&extra);
PrintLine("stdlib_array: ok");
Test_Pass("stdlib_array");
return 0;
}
+7
View File
@@ -0,0 +1,7 @@
[Package]
Name = "stdlib_collections"
Version = "0.1.0"
Type = "bin"
[Build]
Output = "Bin"
@@ -0,0 +1,3 @@
stdlib_collections: ok
PASS:
stdlib_collections
@@ -0,0 +1,67 @@
// Stdlib golden: Map / Set / Result / Option helpers
import Std::Io::{PrintLine};
import Std::Map::{
Map, Map_New, Map_Set, Map_Get, Map_Has, Map_Remove, Map_Clear,
Map_Len, Map_IsEmpty, Map_Free
};
import Std::Set::{
Set, Set_New, Set_Add, Set_Has, Set_Remove, Set_Len, Set_IsEmpty, Set_Free
};
import Std::Result::{
Result, Result_NewOk, Result_NewErr, Result_IsOk, Result_IsErr,
Result_UnwrapOr, Result_Or, Result_UnwrapErr
};
import Std::Option::{
Option, Option_NewSome, Option_NewNone, Option_IsSome, Option_Or, Option_UnwrapOr
};
import Std::String::{String_Eq};
import Std::Test::{
Test_AssertTrue, Test_AssertFalse, Test_AssertEqInt, Test_Pass
};
func Main() -> int {
var m: Map<int, int> = Map_New<int, int>(16);
Map_Set<int, int>(&m, 1, 100);
Map_Set<int, int>(&m, 2, 200);
Map_Set<int, int>(&m, 3, 300);
Test_AssertEqInt(Map_Len<int, int>(&m) as int, 3);
Test_AssertTrue(Map_Has<int, int>(&m, 2));
Test_AssertTrue(Map_Remove<int, int>(&m, 2));
Test_AssertFalse(Map_Has<int, int>(&m, 2));
Test_AssertEqInt(Map_Get<int, int>(&m, 1), 100);
Test_AssertFalse(Map_Remove<int, int>(&m, 99));
Map_Clear<int, int>(&m);
Test_AssertTrue(Map_IsEmpty<int, int>(&m));
Map_Free<int, int>(&m);
var s: Set<int> = Set_New<int>(16);
Set_Add<int>(&s, 10);
Set_Add<int>(&s, 20);
Set_Add<int>(&s, 30);
Test_AssertTrue(Set_Remove<int>(&s, 20));
Test_AssertFalse(Set_Has<int>(&s, 20));
Test_AssertTrue(Set_Has<int>(&s, 10));
Test_AssertEqInt(Set_Len<int>(&s) as int, 2);
Test_AssertFalse(Set_IsEmpty<int>(&s));
Set_Free<int>(&s);
let ok: Result = Result_NewOk(42);
let err: Result = Result_NewErr("boom");
Test_AssertTrue(Result_IsOk(ok));
Test_AssertTrue(Result_IsErr(err));
Test_AssertEqInt(Result_UnwrapOr(err, -1), -1);
let recovered: Result = Result_Or(err, Result_NewOk(7));
Test_AssertEqInt(Result_UnwrapOr(recovered, 0), 7);
Test_AssertTrue(String_Eq(Result_UnwrapErr(err), "boom"));
let some: Option = Option_NewSome(5);
let none: Option = Option_NewNone();
Test_AssertTrue(Option_IsSome(some));
Test_AssertEqInt(Option_UnwrapOr(none, 9), 9);
let filled: Option = Option_Or(none, Option_NewSome(3));
Test_AssertEqInt(Option_UnwrapOr(filled, 0), 3);
PrintLine("stdlib_collections: ok");
Test_Pass("stdlib_collections");
return 0;
}
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env bash
# Golden behavioral tests for stdlib modules.
# Usage: from repo root: tests/stdlib_golden/run.sh [path/to/buxc]
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
BUXC_ARG="${1:-$ROOT/buxc}"
DIR="$(cd "$(dirname "$0")" && pwd)"
# Resolve to absolute path so `cd` into test packages still finds the binary.
if [[ "$BUXC_ARG" = /* ]]; then
BUXC="$BUXC_ARG"
else
BUXC="$(cd "$(dirname "$BUXC_ARG")" && pwd)/$(basename "$BUXC_ARG")"
fi
if [[ ! -x "$BUXC" && ! -f "$BUXC" ]]; then
echo "error: buxc not found at $BUXC (run make build first)"
exit 1
fi
passed=0
failed=0
skipped=0
normalize_out() {
# Drop absolute paths; trim trailing whitespace/blank lines
sed -E \
-e "s|$ROOT|ROOT|g" \
-e "s|$DIR|DIR|g" \
-e 's/[[:space:]]+$//' \
| sed -e :a -e '/^\n*$/{$d;N;ba' -e '}'
}
for case_dir in "$DIR"/*/; do
name="$(basename "$case_dir")"
[[ -f "$case_dir/bux.toml" ]] || continue
[[ -f "$case_dir/src/Main.bux" ]] || continue
if [[ ! -f "$case_dir/expected.out" ]]; then
echo " SKIP $name (no expected.out)"
skipped=$((skipped + 1))
continue
fi
# Build + run; capture stdout+stderr
out=""
if ! out="$(cd "$case_dir" && "$BUXC" run . 2>&1)"; then
echo " FAIL $name (build/run non-zero)"
printf '%s\n' "$out" | head -40
failed=$((failed + 1))
continue
fi
got="$(printf '%s\n' "$out" | normalize_out)"
exp="$(cat "$case_dir/expected.out" | normalize_out)"
# Match on key status lines (tests may also print build noise)
if printf '%s\n' "$got" | grep -Fqx "$(printf '%s' "$exp" | head -1)" 2>/dev/null; then
# Prefer full expected lines all present
all_ok=1
while IFS= read -r line; do
[[ -z "$line" ]] && continue
if ! printf '%s\n' "$got" | grep -Fqx "$line"; then
all_ok=0
break
fi
done <<< "$exp"
if [[ $all_ok -eq 1 ]]; then
echo " PASS $name"
passed=$((passed + 1))
continue
fi
fi
# Fallback: every non-empty expected line appears as substring
all_ok=1
while IFS= read -r line; do
[[ -z "$line" ]] && continue
if ! printf '%s\n' "$got" | grep -Fq "$line"; then
all_ok=0
break
fi
done <<< "$exp"
if [[ $all_ok -eq 1 ]]; then
echo " PASS $name"
passed=$((passed + 1))
else
echo " FAIL $name"
echo "---- expected lines ----"
printf '%s\n' "$exp"
echo "---- got (tail) ----"
printf '%s\n' "$got" | tail -20
echo "--------------"
failed=$((failed + 1))
fi
done
echo "Stdlib golden tests: $passed passed, $failed failed, $skipped skipped"
if [[ $failed -gt 0 ]]; then
exit 1
fi
+7
View File
@@ -0,0 +1,7 @@
[Package]
Name = "stdlib_string"
Version = "0.1.0"
Type = "bin"
[Build]
Output = "Bin"
+3
View File
@@ -0,0 +1,3 @@
stdlib_string: ok
PASS:
stdlib_string
+35
View File
@@ -0,0 +1,35 @@
// Stdlib golden: String_IsEmpty / IsBlank / Repeat / ReplaceAll
import Std::Io::{PrintLine};
import Std::String::{
String_IsEmpty, String_IsBlank, String_Repeat, String_ReplaceAll,
String_Eq, String_Len, String_Contains, String_StartsWith, String_EndsWith
};
import Std::Test::{
Test_AssertTrue, Test_AssertFalse, Test_AssertEqInt, Test_AssertEqString, Test_Pass
};
func Main() -> int {
Test_AssertTrue(String_IsEmpty(""));
Test_AssertFalse(String_IsEmpty("x"));
Test_AssertTrue(String_IsBlank(""));
Test_AssertTrue(String_IsBlank(" \t\n"));
Test_AssertFalse(String_IsBlank(" x "));
Test_AssertEqString(String_Repeat(".", 5), ".....");
Test_AssertEqInt(String_Len(String_Repeat("ab", 3)) as int, 6);
Test_AssertEqString(String_Repeat("x", 0), "");
Test_AssertEqString(String_Repeat("ok", 1), "ok");
let multi: String = String_ReplaceAll("a-b-a-b-a", "a", "X");
Test_AssertEqString(multi, "X-b-X-b-X");
let safe: String = String_ReplaceAll("..", ".", "x.");
Test_AssertEqString(safe, "x.x.");
Test_AssertTrue(String_Contains("hello", "ell"));
Test_AssertTrue(String_StartsWith("hello", "he"));
Test_AssertTrue(String_EndsWith("hello", "lo"));
PrintLine("stdlib_string: ok");
Test_Pass("stdlib_string");
return 0;
}
+259 -43
View File
@@ -4,10 +4,10 @@
# Usage: bux-lsp
# The editor spawns this binary and communicates via stdin/stdout.
#
# Hover uses real bootstrap sema types when possible (globals + stdlib);
# completion/outline still use a fast lightweight scan.
# Hover uses real bootstrap sema types when possible (globals + stdlib).
# Locals are position-sensitive (scoped) and include inferred `let` types (v0.4.0).
import std/[json, os, strutils, streams, tables, osproc, sequtils]
import std/[json, os, strutils, streams, tables, osproc, sequtils, sets]
import lexer, parser, ast, sema, types, scope, source_location
# ---------------------------------------------------------------------------
@@ -88,6 +88,17 @@ type
detail: string ## signature / type annotation
container: string ## optional parent (module / type)
fromSema: bool ## detail came from real type checker
## Scoped local binding for position-sensitive hover / go-to-def
LocalBinding = object
name: string
detail: string ## e.g. "let x: int" (inferred or annotated)
kind: string ## variable | parameter
declLine: int ## 0-based declaration line
declCol: int ## 0-based start of name
scopeStartLine: int ## first line where name is visible
scopeEndLine: int ## last line where name is visible (inclusive)
container: string ## enclosing function name
inferred: bool ## type came from initializer, not annotation
DocumentState = ref object
uri: string
content: string
@@ -97,6 +108,8 @@ type
## Full-project type index for hover (includes stdlib after sema enrich)
typeIndex: Table[string, string] ## name → type / signature string
kindIndex: Table[string, string] ## name → kind label
## Position-sensitive locals (filled by enrichWithSema)
locals: seq[LocalBinding]
var
documents = initTable[string, DocumentState]()
@@ -355,8 +368,14 @@ proc typeExprToStr(te: TypeExpr): string =
of tekOwn:
result = "own " & typeExprToStr(te.pointerPointee)
of tekRef:
if te.refLifetime.len > 0:
result = "&" & te.refLifetime & " " & typeExprToStr(te.pointerPointee)
else:
result = "&" & typeExprToStr(te.pointerPointee)
of tekMutRef:
if te.refLifetime.len > 0:
result = "&" & te.refLifetime & " mut " & typeExprToStr(te.pointerPointee)
else:
result = "&mut " & typeExprToStr(te.pointerPointee)
of tekSlice:
result = typeExprToStr(te.sliceElement) & "[]"
@@ -590,56 +609,173 @@ proc enrichWithSema(doc: DocumentState) =
else:
indexDecl(d)
# Walk this file's AST for local lets with explicit types (function bodies)
proc walkBlock(blk: Block, container: string) =
# --- Position-sensitive locals + inferred let types ---
doc.locals = @[]
proc blockEndLine(blk: Block): int =
## Last 0-based line covered by statements in `blk` (best-effort).
if blk == nil: return 0
result = max(0, int(blk.loc.line) - 1)
for stmt in blk.stmts:
result = max(result, max(0, int(stmt.loc.line) - 1))
case stmt.kind
of skIf:
result = max(result, blockEndLine(stmt.stmtIfThen))
result = max(result, blockEndLine(stmt.stmtIfElse))
for br in stmt.stmtIfElseIfs:
result = max(result, blockEndLine(br.blk))
of skWhile:
result = max(result, blockEndLine(stmt.stmtWhileBody))
of skDoWhile:
result = max(result, blockEndLine(stmt.stmtDoWhileBody))
of skLoop:
result = max(result, blockEndLine(stmt.stmtLoopBody))
of skFor:
result = max(result, blockEndLine(stmt.stmtForBody))
of skMatch:
for arm in stmt.stmtMatchArms:
if arm.body != nil and arm.body.kind == ekBlock:
result = max(result, blockEndLine(arm.body.exprBlock))
elif arm.body != nil:
result = max(result, max(0, int(arm.body.loc.line) - 1))
of skExpr:
if stmt.stmtExpr != nil and stmt.stmtExpr.kind == ekBlock:
result = max(result, blockEndLine(stmt.stmtExpr.exprBlock))
else:
discard
proc collectLocals(sema: var Sema, blk: Block, sc: Scope, scopeEnd: int,
container: string) =
if blk == nil: return
let endLine = max(scopeEnd, blockEndLine(blk))
for stmt in blk.stmts:
case stmt.kind
of skLet:
let n = stmt.stmtLetName
if n.len == 0: continue
var typStr = ""
var typ: Type = makeUnknown()
var inferred = false
if stmt.stmtLetType != nil:
typStr = typeExprToStr(stmt.stmtLetType)
typ = sema.resolveType(stmt.stmtLetType)
if (typ == nil or typ.isUnknown) and stmt.stmtLetInit != nil:
typ = sema.checkExprForLsp(stmt.stmtLetInit, sc)
inferred = true
elif stmt.stmtLetType == nil and stmt.stmtLetInit != nil:
# Explicit absence of annotation — still type the initializer
typ = sema.checkExprForLsp(stmt.stmtLetInit, sc)
inferred = true
let kw = if stmt.stmtLetMut: "var" else: "let"
let detail = if typStr.len > 0: kw & " " & n & ": " & typStr else: kw & " " & n
let loc = stmt.loc
let line = max(0, int(loc.line) - 1)
let col = max(0, int(loc.column) - 1)
# Prefer sema-enriched detail if name already global; else add local
if not doc.symbols.hasKey(n) or not doc.symbols[n].fromSema:
let typStr = if typ != nil and not typ.isUnknown: typ.toString else: ""
let detail =
if typStr.len > 0: kw & " " & n & ": " & typStr
else: kw & " " & n
let line = max(0, int(stmt.loc.line) - 1)
let col = max(0, int(stmt.loc.column) - 1)
doc.locals.add(LocalBinding(
name: n, detail: detail, kind: "variable",
declLine: line, declCol: col,
scopeStartLine: line, scopeEndLine: endLine,
container: container, inferred: inferred and typStr.len > 0))
# Also keep latest flat entry for outline (position lookup prefers locals)
doc.symbols[n] = SymbolInfo(
line: line, col: col, kind: "variable", detail: detail,
container: container, fromSema: typStr.len > 0)
if n notin doc.ordered:
doc.ordered.add(n)
if typStr.len > 0:
doc.typeIndex[n] = detail
doc.kindIndex[n] = "variable"
# Define in scope for subsequent inference
let sym = Symbol(kind: skVar, name: n, typ: typ,
isMutable: stmt.stmtLetMut, isOwn: false)
discard sc.define(sym)
of skExpr:
if stmt.stmtExpr != nil and stmt.stmtExpr.kind == ekBlock:
walkBlock(stmt.stmtExpr.exprBlock, container)
var child = newScope(sc)
collectLocals(sema, stmt.stmtExpr.exprBlock, child,
blockEndLine(stmt.stmtExpr.exprBlock), container)
of skIf:
walkBlock(stmt.stmtIfThen, container)
walkBlock(stmt.stmtIfElse, container)
var thenSc = newScope(sc)
collectLocals(sema, stmt.stmtIfThen, thenSc,
blockEndLine(stmt.stmtIfThen), container)
for br in stmt.stmtIfElseIfs:
walkBlock(br.blk, container)
var elifSc = newScope(sc)
collectLocals(sema, br.blk, elifSc, blockEndLine(br.blk), container)
if stmt.stmtIfElse != nil:
var elseSc = newScope(sc)
collectLocals(sema, stmt.stmtIfElse, elseSc,
blockEndLine(stmt.stmtIfElse), container)
of skWhile:
walkBlock(stmt.stmtWhileBody, container)
of skFor:
walkBlock(stmt.stmtForBody, container)
var wSc = newScope(sc)
collectLocals(sema, stmt.stmtWhileBody, wSc,
blockEndLine(stmt.stmtWhileBody), container)
of skDoWhile:
var dSc = newScope(sc)
collectLocals(sema, stmt.stmtDoWhileBody, dSc,
blockEndLine(stmt.stmtDoWhileBody), container)
of skLoop:
walkBlock(stmt.stmtLoopBody, container)
var lSc = newScope(sc)
collectLocals(sema, stmt.stmtLoopBody, lSc,
blockEndLine(stmt.stmtLoopBody), container)
of skFor:
var fSc = newScope(sc)
if stmt.stmtForVar.len > 0:
let fline = max(0, int(stmt.loc.line) - 1)
let fcol = max(0, int(stmt.loc.column) - 1)
let fend = blockEndLine(stmt.stmtForBody)
# Best-effort: element type unknown without iterator typing
let detail = "for " & stmt.stmtForVar
doc.locals.add(LocalBinding(
name: stmt.stmtForVar, detail: detail, kind: "variable",
declLine: fline, declCol: fcol,
scopeStartLine: fline, scopeEndLine: fend,
container: container, inferred: false))
discard fSc.define(Symbol(kind: skVar, name: stmt.stmtForVar,
typ: makeUnknown(), isMutable: false))
collectLocals(sema, stmt.stmtForBody, fSc,
blockEndLine(stmt.stmtForBody), container)
of skMatch:
for arm in stmt.stmtMatchArms:
if arm.body != nil and arm.body.kind == ekBlock:
var mSc = newScope(sc)
collectLocals(sema, arm.body.exprBlock, mSc,
blockEndLine(arm.body.exprBlock), container)
else:
discard
proc collectFuncLocals(sema: var Sema, d: Decl) =
if d == nil or d.kind != dkFunc or d.declFuncBody == nil:
return
let fname = d.declFuncName
let bodyEnd = blockEndLine(d.declFuncBody)
var funcScope = newScope(sema.globalScope)
# Parameters — visible for entire function body
let funcStart = max(0, int(d.loc.line) - 1)
for p in d.declFuncParams:
if p.name.len == 0: continue
var pType = makeUnknown()
if p.ptype != nil:
pType = sema.resolveType(p.ptype)
let typStr = if pType != nil and not pType.isUnknown: pType.toString else: ""
let detail =
if typStr.len > 0: "param " & p.name & ": " & typStr
else: "param " & p.name
let pline = max(0, int(p.loc.line) - 1)
let pcol = max(0, int(p.loc.column) - 1)
doc.locals.add(LocalBinding(
name: p.name, detail: detail, kind: "parameter",
declLine: pline, declCol: pcol,
scopeStartLine: funcStart, scopeEndLine: bodyEnd,
container: fname, inferred: false))
discard funcScope.define(Symbol(kind: skVar, name: p.name, typ: pType,
isMutable: false))
collectLocals(sema, d.declFuncBody, funcScope, bodyEnd, fname)
var semaMut = semaCtx
for d in parseRes.module.items:
if d.kind == dkFunc and d.declFuncBody != nil:
walkBlock(d.declFuncBody, d.declFuncName)
if d.kind == dkFunc:
collectFuncLocals(semaMut, d)
elif d.kind == dkModule:
for sub in d.declModuleItems:
if sub.kind == dkFunc and sub.declFuncBody != nil:
walkBlock(sub.declFuncBody, sub.declFuncName)
if sub.kind == dkFunc:
collectFuncLocals(semaMut, sub)
except:
discard # sema failures must not crash the LSP
@@ -862,27 +998,55 @@ proc handleCompletion(stream: FileStream, id: JsonNode, paramsNode: JsonNode) =
return
ensureAnalyzed(doc)
if doc.locals.len == 0 and doc.content.len > 0:
enrichWithSema(doc)
let prefix = findWordAt(doc.content, lineNum, col)
var items = newJArray()
var offered = initHashSet[string]()
# Position-sensitive locals / params first (highest priority)
for b in doc.locals:
if lineNum < b.scopeStartLine or lineNum > b.scopeEndLine: continue
if prefix != "" and not b.name.toLowerAscii().startsWith(prefix.toLowerAscii()):
continue
# Prefer later/narrower binding for same name
if offered.contains(b.name):
continue
offered.incl(b.name)
let k = if b.kind == "parameter": 6 else: completionKind("variable")
items.add(%*{
"label": b.name,
"kind": k,
"detail": b.detail,
"sortText": "0_" & b.name,
"documentation": {"kind": "markdown",
"value": "```bux\n" & b.detail & "\n```\n\n_" & b.kind &
(if b.inferred: " · inferred" else: "") & "_"}
})
for name, info in doc.symbols.pairs:
if offered.contains(name): continue
if prefix == "" or name.toLowerAscii().startsWith(prefix.toLowerAscii()):
offered.incl(name)
items.add(%*{
"label": name,
"kind": completionKind(info.kind),
"detail": info.detail,
"sortText": "1_" & name,
"documentation": {"kind": "markdown", "value": "```bux\n" & info.detail & "\n```\n\n_" & info.kind & "_"}
})
# Also offer workspace symbols (other open / scanned files)
for name, ws in workspaceSymbols.pairs:
if doc.symbols.hasKey(name):
continue
if offered.contains(name): continue
if prefix == "" or name.toLowerAscii().startsWith(prefix.toLowerAscii()):
offered.incl(name)
items.add(%*{
"label": name,
"kind": completionKind(ws.info.kind),
"detail": ws.info.detail & " (workspace)",
"sortText": "2_" & name,
"documentation": {"kind": "markdown", "value": "```bux\n" & ws.info.detail & "\n```"}
})
@@ -896,11 +1060,32 @@ proc handleCompletion(stream: FileStream, id: JsonNode, paramsNode: JsonNode) =
items.add(%*{
"label": kw,
"kind": 14,
"detail": "keyword"
"detail": "keyword",
"sortText": "3_" & kw
})
sendResponse(stream, id, %*{"isIncomplete": false, "items": items})
# ---------------------------------------------------------------------------
# Position-sensitive local lookup
# ---------------------------------------------------------------------------
proc lookupLocalAt*(doc: DocumentState, name: string, line: int): tuple[ok: bool, b: LocalBinding] =
## Innermost local/parameter binding for `name` visible at `line` (0-based).
result.ok = false
var bestSpan = high(int)
var bestStart = -1
for b in doc.locals:
if b.name != name: continue
if line < b.scopeStartLine or line > b.scopeEndLine: continue
let span = b.scopeEndLine - b.scopeStartLine
# Prefer narrower scope; on ties prefer later declaration (shadowing)
if span < bestSpan or (span == bestSpan and b.scopeStartLine >= bestStart):
bestSpan = span
bestStart = b.scopeStartLine
result.b = b
result.ok = true
# ---------------------------------------------------------------------------
# Go-to-definition
# ---------------------------------------------------------------------------
@@ -917,13 +1102,26 @@ proc handleDefinition(stream: FileStream, id: JsonNode, paramsNode: JsonNode) =
return
ensureAnalyzed(doc)
if doc.locals.len == 0 and doc.content.len > 0:
enrichWithSema(doc)
let word = findWordAt(doc.content, lineNum, col)
if word.len == 0:
sendResponse(stream, id, %*[])
return
var locs = newJArray()
if doc.symbols.hasKey(word):
# Position-sensitive local first
let (lok, lb) = lookupLocalAt(doc, word, lineNum)
if lok:
locs.add(%*{
"uri": uri,
"range": {
"start": {"line": lb.declLine, "character": lb.declCol},
"end": {"line": lb.declLine, "character": lb.declCol + word.len}
}
})
elif doc.symbols.hasKey(word):
let info = doc.symbols[word]
locs.add(%*{
"uri": uri,
@@ -949,6 +1147,7 @@ proc handleDefinition(stream: FileStream, id: JsonNode, paramsNode: JsonNode) =
proc handleHover(stream: FileStream, id: JsonNode, paramsNode: JsonNode) =
## Hover with accurate range; prefer real sema types when available.
## Locals are resolved by position (shadowing / nested scopes).
let uri = paramsNode["textDocument"]["uri"].getStr()
let position = paramsNode["position"]
let lineNum = position["line"].getInt()
@@ -961,7 +1160,7 @@ proc handleHover(stream: FileStream, id: JsonNode, paramsNode: JsonNode) =
ensureAnalyzed(doc)
# Lazy sema enrich on first hover if not yet run (e.g. only didChange so far)
if doc.typeIndex.len == 0 and doc.content.len > 0:
if (doc.typeIndex.len == 0 or doc.locals.len == 0) and doc.content.len > 0:
enrichWithSema(doc)
let lines = doc.content.split("\n")
@@ -983,23 +1182,39 @@ proc handleHover(stream: FileStream, id: JsonNode, paramsNode: JsonNode) =
var detail = ""
var kind = ""
var found = false
var fromSema = false
var inferred = false
var scopeNote = ""
# Prefer file-local symbol (may be sema-upgraded)
if doc.symbols.hasKey(word):
# 1) Position-sensitive local / parameter
let (lok, lb) = lookupLocalAt(doc, word, lineNum)
if lok:
detail = lb.detail
kind = lb.kind
found = true
fromSema = true
inferred = lb.inferred
if lb.container.len > 0:
scopeNote = " in `" & lb.container & "`"
# 2) File-level / global symbols (functions, types, …)
if not found and doc.symbols.hasKey(word):
let info = doc.symbols[word]
detail = info.detail
kind = info.kind
found = true
# Prefer pure sema typeIndex when richer
fromSema = info.fromSema
if doc.typeIndex.hasKey(word) and doc.typeIndex[word].len >= detail.len:
detail = doc.typeIndex[word]
if doc.kindIndex.hasKey(word):
kind = doc.kindIndex[word]
elif doc.typeIndex.hasKey(word):
fromSema = true
elif not found and doc.typeIndex.hasKey(word):
detail = doc.typeIndex[word]
kind = if doc.kindIndex.hasKey(word): doc.kindIndex[word] else: "symbol"
found = true
elif workspaceSymbols.hasKey(word):
fromSema = true
elif not found and workspaceSymbols.hasKey(word):
let info = workspaceSymbols[word].info
detail = info.detail
kind = info.kind
@@ -1010,10 +1225,12 @@ proc handleHover(stream: FileStream, id: JsonNode, paramsNode: JsonNode) =
return
var md = "```bux\n" & detail & "\n```\n\n_" & kind & "_"
if doc.symbols.hasKey(word) and doc.symbols[word].fromSema:
md &= " · sema"
elif doc.typeIndex.hasKey(word):
if scopeNote.len > 0:
md &= scopeNote
if fromSema:
md &= " · sema"
if inferred:
md &= " · inferred"
sendResponse(stream, id, %*{
"contents": {"kind": "markdown", "value": md},
@@ -1022,7 +1239,6 @@ proc handleHover(stream: FileStream, id: JsonNode, paramsNode: JsonNode) =
"end": {"line": lineNum, "character": endC}
}
})
# ---------------------------------------------------------------------------
# Document symbols (outline)
# ---------------------------------------------------------------------------
@@ -1088,7 +1304,7 @@ proc handleMessage(stream: FileStream, msg: JsonNode) =
"hoverProvider": true,
"documentSymbolProvider": true
},
"serverInfo": {"name": "bux-lsp", "version": "0.3.0"}
"serverInfo": {"name": "bux-lsp", "version": "0.4.0"}
})
if paramsNode.hasKey("rootPath") and paramsNode["rootPath"].kind != JNull:
rootPath = paramsNode["rootPath"].getStr()
+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env bash
# Smoke: hover on inferred let + parameter via bux-lsp JSON-RPC.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
LSP="$ROOT/tools/bux-lsp"
TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT
if [[ ! -x "$LSP" ]]; then
echo "building bux-lsp..."
(cd "$ROOT" && make lsp >/dev/null)
fi
cat > "$TMP/Main.bux" <<'EOF'
func Add(a: int, b: int) -> int {
let sum = a + b;
return sum;
}
func Main() -> int {
let n = 10;
return Add(n, 2);
}
EOF
rpc() {
local body="$1"
local len
len=$(printf '%s' "$body" | wc -c)
printf 'Content-Length: %s\r\n\r\n%s' "$len" "$body"
}
CONTENT_JSON=$(python3 -c 'import json,sys; print(json.dumps(open(sys.argv[1]).read()))' "$TMP/Main.bux")
URI="file://$TMP/Main.bux"
{
rpc '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{},"rootUri":"file://'"$TMP"'"}}'
rpc '{"jsonrpc":"2.0","method":"initialized","params":{}}'
rpc '{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"'"$URI"'","languageId":"bux","version":1,"text":'"$CONTENT_JSON"'}}}'
# hover on `sum` (line 1)
rpc '{"jsonrpc":"2.0","id":2,"method":"textDocument/hover","params":{"textDocument":{"uri":"'"$URI"'"},"position":{"line":1,"character":8}}}'
# hover on param a (line 0)
rpc '{"jsonrpc":"2.0","id":3,"method":"textDocument/hover","params":{"textDocument":{"uri":"'"$URI"'"},"position":{"line":0,"character":9}}}'
# hover on n in Main (line 5)
rpc '{"jsonrpc":"2.0","id":4,"method":"textDocument/hover","params":{"textDocument":{"uri":"'"$URI"'"},"position":{"line":5,"character":8}}}'
rpc '{"jsonrpc":"2.0","id":5,"method":"shutdown","params":null}'
rpc '{"jsonrpc":"2.0","method":"exit","params":null}'
} | "$LSP" 2>/dev/null | tr '\r' '\n' > "$TMP/out.txt"
echo "---- hover responses (excerpt) ----"
grep -o '"value":"[^"]*"' "$TMP/out.txt" | head -20 || true
# Must have typed sum / n and param a somewhere in output
ok=1
if ! grep -q 'sum' "$TMP/out.txt"; then
echo "FAIL: no hover for sum"
ok=0
fi
if ! grep -Eq 'let sum: int|sum: int' "$TMP/out.txt"; then
echo "WARN: sum type not clearly int (may still pass if detail present)"
# Soft fail only if completely missing inferred path
if ! grep -q 'inferred' "$TMP/out.txt" && ! grep -q 'let sum' "$TMP/out.txt"; then
ok=0
fi
fi
if ! grep -Eq 'param a|a: int' "$TMP/out.txt"; then
echo "FAIL: expected param a hover"
ok=0
fi
if ! grep -Eq 'let n: int|n: int' "$TMP/out.txt"; then
echo "WARN: n type not clearly int"
fi
if [[ $ok -eq 0 ]]; then
echo "---- full output ----"
cat "$TMP/out.txt"
exit 1
fi
echo "PASS: LSP hover smoke (locals + params + inferred lets)"
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
# Smoke: registry search + add + install + build with greet package (E.1)
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
BUXC="$ROOT/buxc"
export BUX_REGISTRY="$ROOT/config/registry.toml"
if [[ ! -x "$BUXC" ]]; then
(cd "$ROOT" && make build >/dev/null)
fi
TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT
echo "=== bux search greet ==="
"$BUXC" search greet | tee "$TMP/search.out"
grep -q greet "$TMP/search.out"
echo "=== create consumer project ==="
mkdir -p "$TMP/app/src"
cat > "$TMP/app/bux.toml" <<'EOF'
[Package]
Name = "registry_consumer"
Version = "0.1.0"
Type = "bin"
[Build]
Output = "Bin"
EOF
cat > "$TMP/app/src/Main.bux" <<'EOF'
import Std::Io::{PrintLine};
import Std::String::{String_Eq};
import Std::Test::{Test_AssertTrue, Test_Pass};
func Main() -> int {
let msg: String = Greet_Hello("Bux");
Test_AssertTrue(String_Eq(msg, "Hello, Bux!"));
Test_AssertTrue(String_Eq(Greet_Version(), "0.1.1"));
PrintLine(msg);
Test_Pass("registry_consumer");
return 0;
}
EOF
cd "$TMP/app"
export BUX_STDLIB="$ROOT/lib"
echo "=== bux add greet ==="
"$BUXC" add greet
grep -q greet bux.toml
cat bux.toml
echo "=== bux install ==="
"$BUXC" install
test -f bux.lock
grep -q greet bux.lock
cat bux.lock
echo "=== bux run ==="
"$BUXC" run . | tee "$TMP/run.out"
grep -q "Hello, Bux!" "$TMP/run.out"
echo "PASS: registry smoke (search + add + install + build)"

Some files were not shown because too many files have changed in this diff Show More