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:
@@ -3,9 +3,9 @@ SRC := bootstrap/main.nim
|
|||||||
OUT := buxc
|
OUT := buxc
|
||||||
BUILD_DIR := build
|
BUILD_DIR := build
|
||||||
|
|
||||||
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership 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
|
all: build
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ dev:
|
|||||||
debug: dev
|
debug: dev
|
||||||
@echo "Debug binary: buxc_debug"
|
@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..."
|
@echo "Running lexer tests..."
|
||||||
$(NIM) c -r tests/lexer_test.nim
|
$(NIM) c -r tests/lexer_test.nim
|
||||||
@echo "Running parser tests..."
|
@echo "Running parser tests..."
|
||||||
@@ -105,6 +105,43 @@ test-errors: build
|
|||||||
@chmod +x tests/error_golden/run.sh
|
@chmod +x tests/error_golden/run.sh
|
||||||
@tests/error_golden/run.sh ./$(OUT)
|
@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
|
selfhost-loop: build
|
||||||
@echo "=== Selfhost loop: bootstrap determinism check ==="
|
@echo "=== Selfhost loop: bootstrap determinism check ==="
|
||||||
@echo "Build A..."
|
@echo "Build A..."
|
||||||
@@ -145,3 +182,17 @@ lsp: tools/bux-lsp
|
|||||||
|
|
||||||
tools/bux-lsp: tools/lsp_server.nim bootstrap/*.nim
|
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
|
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
|
||||||
|
|||||||
@@ -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.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.2` `borrow` / `&` | ✅ | `&T` shared reference type checked and enforced |
|
||||||
| `8.2.3` `mut` references | ✅ | `&mut T` mutable 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 |
|
| `8.2.5` Opt-in checker | ✅ | `@[Checked]` attribute enables borrow checking: writes through `&T` are rejected |
|
||||||
|
|
||||||
```bux
|
```bux
|
||||||
@@ -681,8 +681,8 @@ buxc2 == buxc3 ✅ (binary-identical)
|
|||||||
| `10.2.3` `&mut T` exclusive mutable check | ✅ | No aliasing of mutable refs |
|
| `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.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.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.6` Lifetime elision (simple rules) | ✅ | Single-input elision; multi-input requires `'a` |
|
||||||
| `10.2.7` Explicit lifetimes `'a` | ⏳ | Only for complex cases |
|
| `10.2.7` Explicit lifetimes `'a` | ✅ | Parsed + checked; multi-input + mismatch |
|
||||||
|
|
||||||
### 10.3 — Compiler Architecture Upgrade (v0.6.0 target)
|
### 10.3 — Compiler Architecture Upgrade (v0.6.0 target)
|
||||||
|
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ func Main() -> int {
|
|||||||
| **Package Manager** | `bux add`, `bux install`, `bux.lock`, path + git deps |
|
| **Package Manager** | `bux add`, `bux install`, `bux.lock`, path + git deps |
|
||||||
| **Cross-Compilation** | `--target <triple>` via clang (e.g. `aarch64-linux-gnu`) |
|
| **Cross-Compilation** | `--target <triple>` via clang (e.g. `aarch64-linux-gnu`) |
|
||||||
| **Diagnostics** | Rust-style snippets, multi-char underlines, `= help:` hints |
|
| **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/Stdlib.md`](docs/Stdlib.md) | Standard library API |
|
||||||
| [`docs/BuildAndTest.md`](docs/BuildAndTest.md) | Build, test, and tooling |
|
| [`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/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) |
|
| [`docs/ROADMAP.md`](docs/ROADMAP.md) | Feature status (constructs) |
|
||||||
| [`PLAN.md`](PLAN.md) | Long-form phase plan |
|
| [`PLAN.md`](PLAN.md) | Long-form phase plan |
|
||||||
|
|
||||||
@@ -319,14 +321,33 @@ make test-errors
|
|||||||
# Full unit + example suite
|
# Full unit + example suite
|
||||||
make test
|
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)
|
# Build self-hosted compiler (Bux → C → native)
|
||||||
make selfhost
|
make selfhost
|
||||||
|
|
||||||
# Run all tests
|
# Package tests (filter + summary table)
|
||||||
make test
|
./buxc test --filter first _test_runner
|
||||||
|
|
||||||
# Run example programs
|
# Format / CI format check
|
||||||
make test-examples
|
./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)
|
# Verify selfhost binary parity (buxc2 → buxc3, identical)
|
||||||
make selfhost-loop
|
make selfhost-loop
|
||||||
|
|||||||
+116
-116
@@ -5,143 +5,143 @@
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
module Boko {
|
module Boko {
|
||||||
|
|
||||||
import Std::Io::{PrintLine, Print, PrintInt};
|
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::Net::{Net_Create, Net_SetReuse, Net_Bind, Net_Listen, Net_Accept, Net_Send, Net_Recv, Net_Close, Net_LastError};
|
||||||
import Std::String::{
|
import Std::String::{
|
||||||
String_Len, String_Eq, String_IsNull,
|
String_Len, String_Eq, String_IsNull,
|
||||||
String_StartsWith, String_Contains,
|
String_StartsWith, String_Contains,
|
||||||
String_Find, String_Offset, String_Slice,
|
String_Find, String_Offset, String_Slice,
|
||||||
String_SplitCount, String_SplitPart,
|
String_SplitCount, String_SplitPart,
|
||||||
StringBuilder, StringBuilder_New, StringBuilder_Append,
|
StringBuilder, StringBuilder_New, StringBuilder_Append,
|
||||||
StringBuilder_AppendInt, StringBuilder_Build, StringBuilder_Free
|
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
|
// HTTP Methods
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
enum HttpVerb {
|
enum HttpVerb {
|
||||||
GET,
|
GET,
|
||||||
POST,
|
POST,
|
||||||
PUT,
|
PUT,
|
||||||
DELETE,
|
DELETE,
|
||||||
PATCH,
|
PATCH,
|
||||||
HEAD,
|
HEAD,
|
||||||
OPTIONS,
|
OPTIONS,
|
||||||
}
|
}
|
||||||
|
|
||||||
func HttpVerb_MethodName(verb: HttpVerb) -> String {
|
func HttpVerb_MethodName(verb: HttpVerb) -> String {
|
||||||
if verb.tag == HttpVerb_GET { return "GET"; }
|
if verb.tag == HttpVerb_GET { return "GET"; }
|
||||||
if verb.tag == HttpVerb_POST { return "POST"; }
|
if verb.tag == HttpVerb_POST { return "POST"; }
|
||||||
if verb.tag == HttpVerb_PUT { return "PUT"; }
|
if verb.tag == HttpVerb_PUT { return "PUT"; }
|
||||||
if verb.tag == HttpVerb_DELETE { return "DELETE"; }
|
if verb.tag == HttpVerb_DELETE { return "DELETE"; }
|
||||||
if verb.tag == HttpVerb_PATCH { return "PATCH"; }
|
if verb.tag == HttpVerb_PATCH { return "PATCH"; }
|
||||||
if verb.tag == HttpVerb_HEAD { return "HEAD"; }
|
if verb.tag == HttpVerb_HEAD { return "HEAD"; }
|
||||||
if verb.tag == HttpVerb_OPTIONS { return "OPTIONS"; }
|
if verb.tag == HttpVerb_OPTIONS { return "OPTIONS"; }
|
||||||
return "?";
|
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, "GET") { return HttpVerb { tag: HttpVerb_GET }; }
|
||||||
if String_Eq(methodStr, "POST") { return HttpVerb { tag: HttpVerb_POST }; }
|
if String_Eq(methodStr, "POST") { return HttpVerb { tag: HttpVerb_POST }; }
|
||||||
if String_Eq(methodStr, "PUT") { return HttpVerb { tag: HttpVerb_PUT }; }
|
if String_Eq(methodStr, "PUT") { return HttpVerb { tag: HttpVerb_PUT }; }
|
||||||
if String_Eq(methodStr, "DELETE") { return HttpVerb { tag: HttpVerb_DELETE }; }
|
if String_Eq(methodStr, "DELETE") { return HttpVerb { tag: HttpVerb_DELETE }; }
|
||||||
if String_Eq(methodStr, "PATCH") { return HttpVerb { tag: HttpVerb_PATCH }; }
|
if String_Eq(methodStr, "PATCH") { return HttpVerb { tag: HttpVerb_PATCH }; }
|
||||||
if String_Eq(methodStr, "HEAD") { return HttpVerb { tag: HttpVerb_HEAD }; }
|
if String_Eq(methodStr, "HEAD") { return HttpVerb { tag: HttpVerb_HEAD }; }
|
||||||
if String_Eq(methodStr, "OPTIONS") { return HttpVerb { tag: HttpVerb_OPTIONS }; }
|
if String_Eq(methodStr, "OPTIONS") { return HttpVerb { tag: HttpVerb_OPTIONS }; }
|
||||||
return HttpVerb { tag: HttpVerb_GET };
|
return HttpVerb { tag: HttpVerb_GET };
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Request — parsed incoming HTTP request
|
// Request — parsed incoming HTTP request
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
struct Request {
|
struct Request {
|
||||||
method: HttpVerb,
|
method: HttpVerb,
|
||||||
path: String,
|
path: String,
|
||||||
body: String,
|
body: String,
|
||||||
headers: StringMap<String>,
|
headers: StringMap<String>,
|
||||||
query: StringMap<String>,
|
query: StringMap<String>,
|
||||||
pathParams: StringMap<String>,
|
pathParams: StringMap<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
extend Request {
|
extend Request {
|
||||||
func GetHeader(self: Request, name: String) -> String {
|
func GetHeader(self: Request, name: String) -> String {
|
||||||
if StringMap_Has<String>(&self.headers, name) {
|
if StringMap_Has<String>(&self.headers, name) {
|
||||||
return StringMap_Get<String>(&self.headers, name);
|
return StringMap_Get<String>(&self.headers, name);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetQuery(self: Request, name: String) -> String {
|
func GetQuery(self: Request, name: String) -> String {
|
||||||
if StringMap_Has<String>(&self.query, name) {
|
if StringMap_Has<String>(&self.query, name) {
|
||||||
return StringMap_Get<String>(&self.query, name);
|
return StringMap_Get<String>(&self.query, name);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
func HasQuery(self: Request, name: String) -> bool {
|
func HasQuery(self: Request, name: String) -> bool {
|
||||||
return StringMap_Has<String>(&self.query, name);
|
return StringMap_Has<String>(&self.query, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPathParam(self: Request, name: String) -> String {
|
func GetPathParam(self: Request, name: String) -> String {
|
||||||
if StringMap_Has<String>(&self.pathParams, name) {
|
if StringMap_Has<String>(&self.pathParams, name) {
|
||||||
return StringMap_Get<String>(&self.pathParams, name);
|
return StringMap_Get<String>(&self.pathParams, name);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Response — outgoing HTTP response
|
// Response — outgoing HTTP response
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
struct Response {
|
struct Response {
|
||||||
statusCode: int,
|
statusCode: int,
|
||||||
contentType: String,
|
contentType: String,
|
||||||
body: String,
|
body: String,
|
||||||
extraHeaders: String,
|
extraHeaders: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Constructors ---
|
// --- Constructors ---
|
||||||
func Response_New(status: int, contentType: String, body: String) -> Response {
|
func Response_New(status: int, contentType: String, body: String) -> Response {
|
||||||
return Response { statusCode: status, contentType: contentType, body: body, extraHeaders: "" };
|
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);
|
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);
|
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);
|
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);
|
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();
|
let sb: StringBuilder = StringBuilder_New();
|
||||||
StringBuilder_Append(&sb, "Location: ");
|
StringBuilder_Append(&sb, "Location: ");
|
||||||
StringBuilder_Append(&sb, url);
|
StringBuilder_Append(&sb, url);
|
||||||
StringBuilder_Append(&sb, "\r\n");
|
StringBuilder_Append(&sb, "\r\n");
|
||||||
let headers: String = StringBuilder_Build(&sb);
|
let headers: String = StringBuilder_Build(&sb);
|
||||||
StringBuilder_Free(&sb);
|
StringBuilder_Free(&sb);
|
||||||
return Response { statusCode: 302, contentType: "", body: "", extraHeaders: headers };
|
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\"}");
|
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();
|
let sb: StringBuilder = StringBuilder_New();
|
||||||
StringBuilder_Append(&sb, "{\"error\":\"");
|
StringBuilder_Append(&sb, "{\"error\":\"");
|
||||||
StringBuilder_Append(&sb, message);
|
StringBuilder_Append(&sb, message);
|
||||||
StringBuilder_Append(&sb, "\"}");
|
StringBuilder_Append(&sb, "\"}");
|
||||||
let body: String = StringBuilder_Build(&sb);
|
let body: String = StringBuilder_Build(&sb);
|
||||||
StringBuilder_Free(&sb);
|
StringBuilder_Free(&sb);
|
||||||
return Response_New(status, "application/json; charset=utf-8", body);
|
return Response_New(status, "application/json; charset=utf-8", body);
|
||||||
|
|||||||
@@ -3,56 +3,56 @@
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
module Main {
|
module Main {
|
||||||
|
|
||||||
import Std::Io::{PrintLine, Print};
|
import Std::Io::{PrintLine, Print};
|
||||||
import Std::String::{
|
import Std::String::{
|
||||||
String_Eq, String_Len,
|
String_Eq, String_Len,
|
||||||
StringBuilder, StringBuilder_New, StringBuilder_Append,
|
StringBuilder, StringBuilder_New, StringBuilder_Append,
|
||||||
StringBuilder_Build, StringBuilder_Free
|
StringBuilder_Build, StringBuilder_Free
|
||||||
};
|
};
|
||||||
import Boko::{
|
import Boko::{
|
||||||
App, App_New, App_Run,
|
App, App_New, App_Run,
|
||||||
Request, Response,
|
Request, Response,
|
||||||
Response_Html, Response_Json, Response_NotFound, Response_Redirect,
|
Response_Html, Response_Json, Response_NotFound, Response_Redirect,
|
||||||
Path_Match,
|
Path_Match,
|
||||||
HttpVerb
|
HttpVerb
|
||||||
};
|
};
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Boko_Router — user-defined dispatch (called by the framework)
|
// Boko_Router — user-defined dispatch (called by the framework)
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
func Boko_Router(req: Request) -> Response {
|
func Boko_Router(req: Request) -> Response {
|
||||||
// --- GET / ---
|
// --- GET / ---
|
||||||
if String_Eq(req.path, "/") && req.method.tag == HttpVerb_GET {
|
if String_Eq(req.path, "/") && req.method.tag == HttpVerb_GET {
|
||||||
return Response_Html(PageHome());
|
return Response_Html(PageHome());
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- GET /api/health ---
|
// --- GET /api/health ---
|
||||||
if String_Eq(req.path, "/api/health") {
|
if String_Eq(req.path, "/api/health") {
|
||||||
return Response_Json("{\"status\":\"ok\",\"framework\":\"Boko\",\"version\":\"0.2.0\"}");
|
return Response_Json("{\"status\":\"ok\",\"framework\":\"Boko\",\"version\":\"0.2.0\"}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- GET /api/info ---
|
// --- GET /api/info ---
|
||||||
if String_Eq(req.path, "/api/info") {
|
if String_Eq(req.path, "/api/info") {
|
||||||
return Response_Json("{\"name\":\"Boko\",\"language\":\"Bux\",\"inspiration\":\"FastAPI\",\"features\":[\"routing\",\"path-params\",\"query-params\",\"json\"]}");
|
return Response_Json("{\"name\":\"Boko\",\"language\":\"Bux\",\"inspiration\":\"FastAPI\",\"features\":[\"routing\",\"path-params\",\"query-params\",\"json\"]}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- GET /hello?name=World ---
|
// --- GET /hello?name=World ---
|
||||||
if String_Eq(req.path, "/hello") {
|
if String_Eq(req.path, "/hello") {
|
||||||
var name: String = req.GetQuery("name");
|
var name: String = req.GetQuery("name");
|
||||||
if String_Len(name) == 0 { name = "World"; }
|
if String_Len(name) == 0 { name = "World"; }
|
||||||
let html: String = f"<h1>Hello, {name}!</h1>";
|
let html: String = f"<h1>Hello, {name}!</h1>";
|
||||||
return Response_Html(html);
|
return Response_Html(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- GET /users/{id} ---
|
// --- GET /users/{id} ---
|
||||||
if Path_Match("/users/{id}", req.path, &req) {
|
if Path_Match("/users/{id}", req.path, &req) {
|
||||||
let id: String = req.GetPathParam("id");
|
let id: String = req.GetPathParam("id");
|
||||||
let sb: StringBuilder = StringBuilder_New();
|
let sb: StringBuilder = StringBuilder_New();
|
||||||
StringBuilder_Append(&sb, "{\"id\":");
|
StringBuilder_Append(&sb, "{\"id\":");
|
||||||
StringBuilder_Append(&sb, id);
|
StringBuilder_Append(&sb, id);
|
||||||
StringBuilder_Append(&sb, ",\"name\":\"User ");
|
StringBuilder_Append(&sb, ",\"name\":\"User ");
|
||||||
StringBuilder_Append(&sb, id);
|
StringBuilder_Append(&sb, id);
|
||||||
StringBuilder_Append(&sb, "\"}");
|
StringBuilder_Append(&sb, "\"}");
|
||||||
let json: String = StringBuilder_Build(&sb);
|
let json: String = StringBuilder_Build(&sb);
|
||||||
StringBuilder_Free(&sb);
|
StringBuilder_Free(&sb);
|
||||||
return Response_Json(json);
|
return Response_Json(json);
|
||||||
@@ -98,46 +98,46 @@ func Boko_Router(req: Request) -> Response {
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
func PageHome() -> String {
|
func PageHome() -> String {
|
||||||
return `<!DOCTYPE html>
|
return `<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Boko Framework</title>
|
<title>Boko Framework</title>
|
||||||
<style>
|
<style>
|
||||||
*{margin:0;padding:0;box-sizing:border-box}
|
*{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}
|
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%}
|
.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}
|
h1{color:#58a6ff;font-size:2rem;margin-bottom:.5rem}
|
||||||
.tag{color:#8b949e;font-size:.9rem;margin-bottom:2rem}
|
.tag{color:#8b949e;font-size:.9rem;margin-bottom:2rem}
|
||||||
h3{color:#d2a8ff;margin:1.5rem 0 .75rem}
|
h3{color:#d2a8ff;margin:1.5rem 0 .75rem}
|
||||||
.ep{display:flex;gap:1rem;padding:.35rem 0;font-family:monospace;font-size:.9rem}
|
.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 .m{color:#3fb950;font-weight:bold;min-width:52px}
|
||||||
.ep .p{color:#c9d1d9}
|
.ep .p{color:#c9d1d9}
|
||||||
.ep .d{color:#484f58;margin-left:auto}
|
.ep .d{color:#484f58;margin-left:auto}
|
||||||
a{color:#58a6ff}
|
a{color:#58a6ff}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h1>⚡ Boko</h1>
|
<h1>⚡ Boko</h1>
|
||||||
<p class="tag">Async web framework for Bux — inspired by FastAPI</p>
|
<p class="tag">Async web framework for Bux — inspired by FastAPI</p>
|
||||||
<h3>Try it</h3>
|
<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="/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="/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="/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="/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/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>
|
<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>
|
<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">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">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">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">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">Redirects (302)</span></div>
|
||||||
<div class="ep"><span class="m">✓</span><span class="p">POST body access</span></div>
|
<div class="ep"><span class="m">✓</span><span class="p">POST body access</span></div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>`;
|
</html>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|||||||
+425
-425
@@ -5,464 +5,464 @@
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
module Main {
|
module Main {
|
||||||
|
|
||||||
import Std::Io::{PrintLine, Print, PrintInt};
|
import Std::Io::{PrintLine, Print, PrintInt};
|
||||||
import Std::String::{
|
import Std::String::{
|
||||||
String_Len, String_Eq,
|
String_Len, String_Eq,
|
||||||
String_SplitCount, String_SplitPart,
|
String_SplitCount, String_SplitPart,
|
||||||
StringBuilder, StringBuilder_New, StringBuilder_Append,
|
StringBuilder, StringBuilder_New, StringBuilder_Append,
|
||||||
StringBuilder_AppendInt, StringBuilder_Build, StringBuilder_Free
|
StringBuilder_AppendInt, StringBuilder_Build, StringBuilder_Free
|
||||||
};
|
};
|
||||||
import Std::Crypto::Jwt::{
|
import Std::Crypto::Jwt::{
|
||||||
JwtAlg,
|
JwtAlg,
|
||||||
Jwt_MakeHeader,
|
Jwt_MakeHeader,
|
||||||
Jwt_Encode,
|
Jwt_Encode,
|
||||||
Jwt_Decode,
|
Jwt_Decode,
|
||||||
Jwt_EncodeHS256, Jwt_EncodeHS384, Jwt_EncodeHS512,
|
Jwt_EncodeHS256, Jwt_EncodeHS384, Jwt_EncodeHS512,
|
||||||
Jwt_EncodeRS256, Jwt_EncodeES256, Jwt_EncodeEdDSA
|
Jwt_EncodeRS256, Jwt_EncodeES256, Jwt_EncodeEdDSA
|
||||||
};
|
};
|
||||||
import Std::Crypto::Base64::{Base64URL_Decode, Base64_Encode};
|
import Std::Crypto::Base64::{Base64URL_Decode, Base64_Encode};
|
||||||
import Std::Crypto::Ed25519::{Ed25519_Keypair};
|
import Std::Crypto::Ed25519::{Ed25519_Keypair};
|
||||||
|
|
||||||
extern func bux_argc() -> int;
|
extern func bux_argc() -> int;
|
||||||
extern func bux_argv(index: int) -> String;
|
extern func bux_argv(index: int) -> String;
|
||||||
extern func bux_alloc(size: uint) -> *void;
|
extern func bux_alloc(size: uint) -> *void;
|
||||||
extern func bux_read_file(path: String) -> String;
|
extern func bux_read_file(path: String) -> String;
|
||||||
extern func bux_file_exists(path: String) -> int;
|
extern func bux_file_exists(path: String) -> int;
|
||||||
extern func bux_base64_encode(data: String, len: int) -> String;
|
extern func bux_base64_encode(data: String, len: int) -> String;
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Constants
|
// Constants
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
const AppName: String = "jwt-pitbul";
|
const AppName: String = "jwt-pitbul";
|
||||||
const Version: String = "0.2.0";
|
const Version: String = "0.2.0";
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Algebraic enums for optionals and results
|
// Algebraic enums for optionals and results
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
enum AlgOption {
|
enum AlgOption {
|
||||||
Some(JwtAlg),
|
Some(JwtAlg),
|
||||||
None,
|
None,
|
||||||
}
|
|
||||||
|
|
||||||
func AlgOption_MakeSome(value: JwtAlg) -> AlgOption {
|
|
||||||
let o: AlgOption = AlgOption { tag: AlgOption_Some };
|
|
||||||
o.data.Some_0 = value;
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
|
|
||||||
func AlgOption_MakeNone() -> AlgOption {
|
|
||||||
return AlgOption { tag: AlgOption_None };
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CmdOption {
|
|
||||||
Some(Cmd),
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
func CmdOption_MakeSome(value: Cmd) -> CmdOption {
|
|
||||||
let o: CmdOption = CmdOption { tag: CmdOption_Some };
|
|
||||||
o.data.Some_0 = value;
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
|
|
||||||
func CmdOption_MakeNone() -> CmdOption {
|
|
||||||
return CmdOption { tag: CmdOption_None };
|
|
||||||
}
|
|
||||||
|
|
||||||
enum KeyTypeOption {
|
|
||||||
Some(KeyType),
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
func KeyTypeOption_MakeSome(value: KeyType) -> KeyTypeOption {
|
|
||||||
let o: KeyTypeOption = KeyTypeOption { tag: KeyTypeOption_Some };
|
|
||||||
o.data.Some_0 = value;
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
|
|
||||||
func KeyTypeOption_MakeNone() -> KeyTypeOption {
|
|
||||||
return KeyTypeOption { tag: KeyTypeOption_None };
|
|
||||||
}
|
|
||||||
|
|
||||||
enum KeyResult {
|
|
||||||
Ok(String),
|
|
||||||
Err(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
let r: KeyResult = KeyResult { tag: KeyResult_Err };
|
|
||||||
r.data.Err_0 = msg;
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ExitResult {
|
|
||||||
Ok(int),
|
|
||||||
Err(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
let r: ExitResult = ExitResult { tag: ExitResult_Err };
|
|
||||||
r.data.Err_0 = msg;
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Commands
|
|
||||||
// =============================================================================
|
|
||||||
enum Cmd {
|
|
||||||
Sign,
|
|
||||||
Verify,
|
|
||||||
Decode,
|
|
||||||
Keygen,
|
|
||||||
Help,
|
|
||||||
}
|
|
||||||
|
|
||||||
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 }); }
|
|
||||||
if String_Eq(name, "keygen") { return CmdOption_MakeSome(Cmd { tag: Cmd_Keygen }); }
|
|
||||||
if String_Eq(name, "help") || String_Eq(name, "--help") || String_Eq(name, "-h") {
|
|
||||||
return CmdOption_MakeSome(Cmd { tag: Cmd_Help });
|
|
||||||
}
|
}
|
||||||
return CmdOption_MakeNone();
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
func AlgOption_MakeSome(value: JwtAlg) -> AlgOption {
|
||||||
// Key type generation
|
let o: AlgOption = AlgOption { tag: AlgOption_Some };
|
||||||
// =============================================================================
|
o.data.Some_0 = value;
|
||||||
enum KeyType {
|
return o;
|
||||||
Rsa,
|
}
|
||||||
Ecdsa,
|
|
||||||
Ed25519,
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseKeyType(name: String) -> KeyTypeOption {
|
func AlgOption_MakeNone() -> AlgOption {
|
||||||
if String_Eq(name, "rsa") { return KeyTypeOption_MakeSome(KeyType { tag: KeyType_Rsa }); }
|
return AlgOption { tag: AlgOption_None };
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
enum CmdOption {
|
||||||
// Extend JwtAlg with parsing / introspection methods
|
Some(Cmd),
|
||||||
// =============================================================================
|
None,
|
||||||
extend JwtAlg {
|
}
|
||||||
func Name(self: JwtAlg) -> String {
|
|
||||||
match self {
|
func CmdOption_MakeSome(value: Cmd) -> CmdOption {
|
||||||
JwtAlg::HS256 => "HS256",
|
let o: CmdOption = CmdOption { tag: CmdOption_Some };
|
||||||
JwtAlg::HS384 => "HS384",
|
o.data.Some_0 = value;
|
||||||
JwtAlg::HS512 => "HS512",
|
return o;
|
||||||
JwtAlg::RS256 => "RS256",
|
}
|
||||||
JwtAlg::RS384 => "RS384",
|
|
||||||
JwtAlg::RS512 => "RS512",
|
func CmdOption_MakeNone() -> CmdOption {
|
||||||
JwtAlg::ES256 => "ES256",
|
return CmdOption { tag: CmdOption_None };
|
||||||
JwtAlg::ES384 => "ES384",
|
}
|
||||||
JwtAlg::EdDSA => "EdDSA",
|
|
||||||
_ => "HS256",
|
enum KeyTypeOption {
|
||||||
|
Some(KeyType),
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
func KeyTypeOption_MakeSome(value: KeyType) -> KeyTypeOption {
|
||||||
|
let o: KeyTypeOption = KeyTypeOption { tag: KeyTypeOption_Some };
|
||||||
|
o.data.Some_0 = value;
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
func KeyTypeOption_MakeNone() -> KeyTypeOption {
|
||||||
|
return KeyTypeOption { tag: KeyTypeOption_None };
|
||||||
|
}
|
||||||
|
|
||||||
|
enum KeyResult {
|
||||||
|
Ok(String),
|
||||||
|
Err(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
let r: KeyResult = KeyResult { tag: KeyResult_Err };
|
||||||
|
r.data.Err_0 = msg;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ExitResult {
|
||||||
|
Ok(int),
|
||||||
|
Err(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
let r: ExitResult = ExitResult { tag: ExitResult_Err };
|
||||||
|
r.data.Err_0 = msg;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Commands
|
||||||
|
// =============================================================================
|
||||||
|
enum Cmd {
|
||||||
|
Sign,
|
||||||
|
Verify,
|
||||||
|
Decode,
|
||||||
|
Keygen,
|
||||||
|
Help,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 }); }
|
||||||
|
if String_Eq(name, "keygen") { return CmdOption_MakeSome(Cmd { tag: Cmd_Keygen }); }
|
||||||
|
if String_Eq(name, "help") || String_Eq(name, "--help") || String_Eq(name, "-h") {
|
||||||
|
return CmdOption_MakeSome(Cmd { tag: Cmd_Help });
|
||||||
}
|
}
|
||||||
|
return CmdOption_MakeNone();
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsHmac(self: JwtAlg) -> bool {
|
// =============================================================================
|
||||||
return self.tag == JwtAlg_HS256 || self.tag == JwtAlg_HS384 || self.tag == JwtAlg_HS512;
|
// Key type generation
|
||||||
|
// =============================================================================
|
||||||
|
enum KeyType {
|
||||||
|
Rsa,
|
||||||
|
Ecdsa,
|
||||||
|
Ed25519,
|
||||||
}
|
}
|
||||||
|
|
||||||
func NeedsPemFile(self: JwtAlg) -> bool {
|
func ParseKeyType(name: String) -> KeyTypeOption {
|
||||||
return self.tag == JwtAlg_RS256 || self.tag == JwtAlg_RS384 || self.tag == JwtAlg_RS512 ||
|
if String_Eq(name, "rsa") { return KeyTypeOption_MakeSome(KeyType { tag: KeyType_Rsa }); }
|
||||||
self.tag == JwtAlg_ES256 || self.tag == JwtAlg_ES384;
|
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
|
||||||
func ParseAlg(name: String) -> AlgOption {
|
// =============================================================================
|
||||||
if String_Eq(name, "HS256") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_HS256 }); }
|
extend JwtAlg {
|
||||||
if String_Eq(name, "HS384") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_HS384 }); }
|
func Name(self: JwtAlg) -> String {
|
||||||
if String_Eq(name, "HS512") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_HS512 }); }
|
match self {
|
||||||
if String_Eq(name, "RS256") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_RS256 }); }
|
JwtAlg::HS256 => "HS256",
|
||||||
if String_Eq(name, "RS384") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_RS384 }); }
|
JwtAlg::HS384 => "HS384",
|
||||||
if String_Eq(name, "RS512") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_RS512 }); }
|
JwtAlg::HS512 => "HS512",
|
||||||
if String_Eq(name, "ES256") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_ES256 }); }
|
JwtAlg::RS256 => "RS256",
|
||||||
if String_Eq(name, "ES384") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_ES384 }); }
|
JwtAlg::RS384 => "RS384",
|
||||||
if String_Eq(name, "EdDSA") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_EdDSA }); }
|
JwtAlg::RS512 => "RS512",
|
||||||
return AlgOption_MakeNone();
|
JwtAlg::ES256 => "ES256",
|
||||||
}
|
JwtAlg::ES384 => "ES384",
|
||||||
|
JwtAlg::EdDSA => "EdDSA",
|
||||||
// =============================================================================
|
_ => "HS256",
|
||||||
// Help / Usage
|
}
|
||||||
// =============================================================================
|
|
||||||
func PrintUsage() {
|
|
||||||
PrintLine("╔══════════════════════════════════════════════════════╗");
|
|
||||||
PrintLine(f"║ {AppName} — JWT CLI Tool v{Version} ║");
|
|
||||||
PrintLine("║ Sign, verify, decode JSON Web Tokens ║");
|
|
||||||
PrintLine("╚══════════════════════════════════════════════════════╝");
|
|
||||||
PrintLine("");
|
|
||||||
PrintLine("Usage:");
|
|
||||||
PrintLine(f" {AppName} sign <alg> <key> <claims>");
|
|
||||||
PrintLine(f" {AppName} verify <token> <alg> <key>");
|
|
||||||
PrintLine(f" {AppName} decode <token>");
|
|
||||||
PrintLine(f" {AppName} keygen <type>");
|
|
||||||
PrintLine("");
|
|
||||||
PrintLine("Commands:");
|
|
||||||
PrintLine(" sign Create a signed JWT from claims JSON");
|
|
||||||
PrintLine(" verify Verify a JWT signature and print payload");
|
|
||||||
PrintLine(" decode Decode a JWT without verification");
|
|
||||||
PrintLine(" keygen Generate cryptographic keys");
|
|
||||||
PrintLine("");
|
|
||||||
PrintLine("Algorithms:");
|
|
||||||
PrintLine(" HS256, HS384, HS512 — HMAC (symmetric)");
|
|
||||||
PrintLine(" RS256, RS384, RS512 — RSA PKCS#1 v1.5");
|
|
||||||
PrintLine(" ES256, ES384 — ECDSA P-256 / P-384");
|
|
||||||
PrintLine(" EdDSA — Ed25519");
|
|
||||||
PrintLine("");
|
|
||||||
PrintLine("Key formats:");
|
|
||||||
PrintLine(" HMAC: raw secret string");
|
|
||||||
PrintLine(" RSA: path to PEM private/public key file");
|
|
||||||
PrintLine(" ECDSA: path to PEM private/public key file");
|
|
||||||
PrintLine(" EdDSA: base64-encoded 32-byte raw key");
|
|
||||||
PrintLine("");
|
|
||||||
PrintLine("Key generation:");
|
|
||||||
PrintLine(f" {AppName} keygen rsa # RSA 2048-bit (PEM)");
|
|
||||||
PrintLine(f" {AppName} keygen ecdsa # ECDSA P-256 (PEM)");
|
|
||||||
PrintLine(f" {AppName} keygen ed25519 # Ed25519 (raw base64)");
|
|
||||||
PrintLine("");
|
|
||||||
PrintLine("Examples:");
|
|
||||||
Print(f" {AppName} sign HS256 'my-secret' ");
|
|
||||||
PrintLine("'{\"sub\":\"123\"}'");
|
|
||||||
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 {
|
|
||||||
if !alg.NeedsPemFile() {
|
|
||||||
return KeyResult_MakeOk(keyArg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if bux_file_exists(keyArg) == 0 {
|
|
||||||
return KeyResult_MakeErr(f"ERROR: PEM file not found: {keyArg}");
|
|
||||||
}
|
|
||||||
|
|
||||||
let pem: String = bux_read_file(keyArg);
|
|
||||||
if String_Len(pem) == 0 {
|
|
||||||
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 {
|
|
||||||
let algOpt: AlgOption = ParseAlg(algName);
|
|
||||||
if algOpt.tag != AlgOption_Some {
|
|
||||||
return ExitResult_MakeErr(f"ERROR: unknown algorithm '{algName}'");
|
|
||||||
}
|
|
||||||
let alg: JwtAlg = algOpt.data.Some_0;
|
|
||||||
|
|
||||||
let keyRes: KeyResult = ResolveKey(alg, keyArg);
|
|
||||||
if keyRes.tag != KeyResult_Ok {
|
|
||||||
return ExitResult_MakeErr(keyRes.data.Err_0);
|
|
||||||
}
|
|
||||||
let key: String = keyRes.data.Ok_0;
|
|
||||||
|
|
||||||
let header: String = Jwt_MakeHeader(alg);
|
|
||||||
let token: String = Jwt_Encode(header, claimsJson, alg, key);
|
|
||||||
|
|
||||||
if String_Len(token) == 0 {
|
|
||||||
return ExitResult_MakeErr("ERROR: signing failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
PrintLine(token);
|
|
||||||
return ExitResult_MakeOk(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// 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}'");
|
|
||||||
}
|
|
||||||
let alg: JwtAlg = algOpt.data.Some_0;
|
|
||||||
|
|
||||||
let keyRes: KeyResult = ResolveKey(alg, keyArg);
|
|
||||||
if keyRes.tag != KeyResult_Ok {
|
|
||||||
return ExitResult_MakeErr(keyRes.data.Err_0);
|
|
||||||
}
|
|
||||||
let key: String = keyRes.data.Ok_0;
|
|
||||||
|
|
||||||
var header: String = "";
|
|
||||||
var payload: String = "";
|
|
||||||
|
|
||||||
if !Jwt_Decode(token, alg, key, &header, &payload) {
|
|
||||||
return ExitResult_MakeErr("✗ Signature INVALID (or malformed token)");
|
|
||||||
}
|
|
||||||
|
|
||||||
PrintLine("✓ Signature valid");
|
|
||||||
PrintLine("");
|
|
||||||
PrintLine("Header:");
|
|
||||||
PrintLine(header);
|
|
||||||
PrintLine("");
|
|
||||||
PrintLine("Payload:");
|
|
||||||
PrintLine(payload);
|
|
||||||
return ExitResult_MakeOk(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// 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)");
|
|
||||||
}
|
|
||||||
|
|
||||||
let headerB64: String = String_SplitPart(token, ".", 0);
|
|
||||||
let payloadB64: String = String_SplitPart(token, ".", 1);
|
|
||||||
let sigB64: String = String_SplitPart(token, ".", 2);
|
|
||||||
|
|
||||||
let headerJson: String = Base64URL_Decode(headerB64);
|
|
||||||
let payloadJson: String = Base64URL_Decode(payloadB64);
|
|
||||||
|
|
||||||
PrintLine("Decoded (no verification):");
|
|
||||||
PrintLine("");
|
|
||||||
PrintLine("Header:");
|
|
||||||
PrintLine(headerJson);
|
|
||||||
PrintLine("");
|
|
||||||
PrintLine("Payload:");
|
|
||||||
PrintLine(payloadJson);
|
|
||||||
PrintLine("");
|
|
||||||
Print("Signature (base64url): ");
|
|
||||||
PrintLine(sigB64);
|
|
||||||
|
|
||||||
return ExitResult_MakeOk(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// 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");
|
|
||||||
}
|
|
||||||
let kt: KeyType = ktOpt.data.Some_0;
|
|
||||||
|
|
||||||
if kt.tag == KeyType_Ed25519 {
|
|
||||||
let pubBuf: *void = bux_alloc(32);
|
|
||||||
let priv: *void = bux_alloc(32);
|
|
||||||
if !Ed25519_Keypair(pubBuf, priv) {
|
|
||||||
return ExitResult_MakeErr("ERROR: Ed25519 key generation failed (OpenSSL 1.1.1+ required)");
|
|
||||||
}
|
}
|
||||||
let pubB64: String = bux_base64_encode(pubBuf as String, 32);
|
|
||||||
let privB64: String = bux_base64_encode(priv as String, 32);
|
func IsHmac(self: JwtAlg) -> bool {
|
||||||
PrintLine("Ed25519 keypair (base64):");
|
return self.tag == JwtAlg_HS256 || self.tag == JwtAlg_HS384 || self.tag == JwtAlg_HS512;
|
||||||
PrintLine(f" Public: {pubB64}");
|
}
|
||||||
PrintLine(f" Private: {privB64}");
|
|
||||||
return ExitResult_MakeOk(0);
|
func NeedsPemFile(self: JwtAlg) -> bool {
|
||||||
|
return self.tag == JwtAlg_RS256 || self.tag == JwtAlg_RS384 || self.tag == JwtAlg_RS512 ||
|
||||||
|
self.tag == JwtAlg_ES256 || self.tag == JwtAlg_ES384;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if kt.tag == KeyType_Rsa {
|
|
||||||
PrintLine("RSA key generation requires OpenSSL CLI:");
|
func ParseAlg(name: String) -> AlgOption {
|
||||||
PrintLine(" openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048");
|
if String_Eq(name, "HS256") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_HS256 }); }
|
||||||
PrintLine(" openssl rsa -in private.pem -pubout -out public.pem");
|
if String_Eq(name, "HS384") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_HS384 }); }
|
||||||
|
if String_Eq(name, "HS512") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_HS512 }); }
|
||||||
|
if String_Eq(name, "RS256") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_RS256 }); }
|
||||||
|
if String_Eq(name, "RS384") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_RS384 }); }
|
||||||
|
if String_Eq(name, "RS512") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_RS512 }); }
|
||||||
|
if String_Eq(name, "ES256") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_ES256 }); }
|
||||||
|
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() {
|
||||||
|
PrintLine("╔══════════════════════════════════════════════════════╗");
|
||||||
|
PrintLine(f"║ {AppName} — JWT CLI Tool v{Version} ║");
|
||||||
|
PrintLine("║ Sign, verify, decode JSON Web Tokens ║");
|
||||||
|
PrintLine("╚══════════════════════════════════════════════════════╝");
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
PrintLine("(Key generation from within Bux requires PEM write support — coming soon)");
|
PrintLine("Usage:");
|
||||||
return ExitResult_MakeOk(0);
|
PrintLine(f" {AppName} sign <alg> <key> <claims>");
|
||||||
}
|
PrintLine(f" {AppName} verify <token> <alg> <key>");
|
||||||
if kt.tag == KeyType_Ecdsa {
|
PrintLine(f" {AppName} decode <token>");
|
||||||
PrintLine("ECDSA key generation requires OpenSSL CLI:");
|
PrintLine(f" {AppName} keygen <type>");
|
||||||
PrintLine(" openssl ecparam -genkey -name prime256v1 -noout -out ec_private.pem");
|
|
||||||
PrintLine(" openssl ec -in ec_private.pem -pubout -out ec_public.pem");
|
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
PrintLine("(Key generation from within Bux requires PEM write support — coming soon)");
|
PrintLine("Commands:");
|
||||||
|
PrintLine(" sign Create a signed JWT from claims JSON");
|
||||||
|
PrintLine(" verify Verify a JWT signature and print payload");
|
||||||
|
PrintLine(" decode Decode a JWT without verification");
|
||||||
|
PrintLine(" keygen Generate cryptographic keys");
|
||||||
|
PrintLine("");
|
||||||
|
PrintLine("Algorithms:");
|
||||||
|
PrintLine(" HS256, HS384, HS512 — HMAC (symmetric)");
|
||||||
|
PrintLine(" RS256, RS384, RS512 — RSA PKCS#1 v1.5");
|
||||||
|
PrintLine(" ES256, ES384 — ECDSA P-256 / P-384");
|
||||||
|
PrintLine(" EdDSA — Ed25519");
|
||||||
|
PrintLine("");
|
||||||
|
PrintLine("Key formats:");
|
||||||
|
PrintLine(" HMAC: raw secret string");
|
||||||
|
PrintLine(" RSA: path to PEM private/public key file");
|
||||||
|
PrintLine(" ECDSA: path to PEM private/public key file");
|
||||||
|
PrintLine(" EdDSA: base64-encoded 32-byte raw key");
|
||||||
|
PrintLine("");
|
||||||
|
PrintLine("Key generation:");
|
||||||
|
PrintLine(f" {AppName} keygen rsa # RSA 2048-bit (PEM)");
|
||||||
|
PrintLine(f" {AppName} keygen ecdsa # ECDSA P-256 (PEM)");
|
||||||
|
PrintLine(f" {AppName} keygen ed25519 # Ed25519 (raw base64)");
|
||||||
|
PrintLine("");
|
||||||
|
PrintLine("Examples:");
|
||||||
|
Print(f" {AppName} sign HS256 'my-secret' ");
|
||||||
|
PrintLine("'{\"sub\":\"123\"}'");
|
||||||
|
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 {
|
||||||
|
if !alg.NeedsPemFile() {
|
||||||
|
return KeyResult_MakeOk(keyArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if bux_file_exists(keyArg) == 0 {
|
||||||
|
return KeyResult_MakeErr(f"ERROR: PEM file not found: {keyArg}");
|
||||||
|
}
|
||||||
|
|
||||||
|
let pem: String = bux_read_file(keyArg);
|
||||||
|
if String_Len(pem) == 0 {
|
||||||
|
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 {
|
||||||
|
let algOpt: AlgOption = ParseAlg(algName);
|
||||||
|
if algOpt.tag != AlgOption_Some {
|
||||||
|
return ExitResult_MakeErr(f"ERROR: unknown algorithm '{algName}'");
|
||||||
|
}
|
||||||
|
let alg: JwtAlg = algOpt.data.Some_0;
|
||||||
|
|
||||||
|
let keyRes: KeyResult = ResolveKey(alg, keyArg);
|
||||||
|
if keyRes.tag != KeyResult_Ok {
|
||||||
|
return ExitResult_MakeErr(keyRes.data.Err_0);
|
||||||
|
}
|
||||||
|
let key: String = keyRes.data.Ok_0;
|
||||||
|
|
||||||
|
let header: String = Jwt_MakeHeader(alg);
|
||||||
|
let token: String = Jwt_Encode(header, claimsJson, alg, key);
|
||||||
|
|
||||||
|
if String_Len(token) == 0 {
|
||||||
|
return ExitResult_MakeErr("ERROR: signing failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintLine(token);
|
||||||
return ExitResult_MakeOk(0);
|
return ExitResult_MakeOk(0);
|
||||||
}
|
}
|
||||||
return ExitResult_MakeErr(f"ERROR: unknown key type '{keyType}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Dispatch a parsed command to its handler
|
// Command: verify
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
func RunCommand(cmd: Cmd, args: Array<String>) -> ExitResult {
|
func CmdVerify(token: String, algName: String, keyArg: String) -> ExitResult {
|
||||||
if cmd.tag == Cmd_Help {
|
let algOpt: AlgOption = ParseAlg(algName);
|
||||||
PrintUsage();
|
if algOpt.tag != AlgOption_Some {
|
||||||
|
return ExitResult_MakeErr(f"ERROR: unknown algorithm '{algName}'");
|
||||||
|
}
|
||||||
|
let alg: JwtAlg = algOpt.data.Some_0;
|
||||||
|
|
||||||
|
let keyRes: KeyResult = ResolveKey(alg, keyArg);
|
||||||
|
if keyRes.tag != KeyResult_Ok {
|
||||||
|
return ExitResult_MakeErr(keyRes.data.Err_0);
|
||||||
|
}
|
||||||
|
let key: String = keyRes.data.Ok_0;
|
||||||
|
|
||||||
|
var header: String = "";
|
||||||
|
var payload: String = "";
|
||||||
|
|
||||||
|
if !Jwt_Decode(token, alg, key, &header, &payload) {
|
||||||
|
return ExitResult_MakeErr("✗ Signature INVALID (or malformed token)");
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintLine("✓ Signature valid");
|
||||||
|
PrintLine("");
|
||||||
|
PrintLine("Header:");
|
||||||
|
PrintLine(header);
|
||||||
|
PrintLine("");
|
||||||
|
PrintLine("Payload:");
|
||||||
|
PrintLine(payload);
|
||||||
return ExitResult_MakeOk(0);
|
return ExitResult_MakeOk(0);
|
||||||
}
|
}
|
||||||
if cmd.tag == Cmd_Sign {
|
|
||||||
if args.len < 5 {
|
// =============================================================================
|
||||||
return ExitResult_MakeErr("ERROR: 'sign' requires: <alg> <key> <claims_json>");
|
// 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)");
|
||||||
}
|
}
|
||||||
return CmdSign(args[2], args[3], args[4]);
|
|
||||||
|
let headerB64: String = String_SplitPart(token, ".", 0);
|
||||||
|
let payloadB64: String = String_SplitPart(token, ".", 1);
|
||||||
|
let sigB64: String = String_SplitPart(token, ".", 2);
|
||||||
|
|
||||||
|
let headerJson: String = Base64URL_Decode(headerB64);
|
||||||
|
let payloadJson: String = Base64URL_Decode(payloadB64);
|
||||||
|
|
||||||
|
PrintLine("Decoded (no verification):");
|
||||||
|
PrintLine("");
|
||||||
|
PrintLine("Header:");
|
||||||
|
PrintLine(headerJson);
|
||||||
|
PrintLine("");
|
||||||
|
PrintLine("Payload:");
|
||||||
|
PrintLine(payloadJson);
|
||||||
|
PrintLine("");
|
||||||
|
Print("Signature (base64url): ");
|
||||||
|
PrintLine(sigB64);
|
||||||
|
|
||||||
|
return ExitResult_MakeOk(0);
|
||||||
}
|
}
|
||||||
if cmd.tag == Cmd_Verify {
|
|
||||||
if args.len < 5 {
|
// =============================================================================
|
||||||
return ExitResult_MakeErr("ERROR: 'verify' requires: <token> <alg> <key>");
|
// 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");
|
||||||
}
|
}
|
||||||
return CmdVerify(args[2], args[3], args[4]);
|
let kt: KeyType = ktOpt.data.Some_0;
|
||||||
}
|
|
||||||
if cmd.tag == Cmd_Decode {
|
if kt.tag == KeyType_Ed25519 {
|
||||||
if args.len < 3 {
|
let pubBuf: *void = bux_alloc(32);
|
||||||
return ExitResult_MakeErr("ERROR: 'decode' requires: <token>");
|
let priv: *void = bux_alloc(32);
|
||||||
|
if !Ed25519_Keypair(pubBuf, priv) {
|
||||||
|
return ExitResult_MakeErr("ERROR: Ed25519 key generation failed (OpenSSL 1.1.1+ required)");
|
||||||
|
}
|
||||||
|
let pubB64: String = bux_base64_encode(pubBuf as String, 32);
|
||||||
|
let privB64: String = bux_base64_encode(priv as String, 32);
|
||||||
|
PrintLine("Ed25519 keypair (base64):");
|
||||||
|
PrintLine(f" Public: {pubB64}");
|
||||||
|
PrintLine(f" Private: {privB64}");
|
||||||
|
return ExitResult_MakeOk(0);
|
||||||
}
|
}
|
||||||
return CmdDecode(args[2]);
|
if kt.tag == KeyType_Rsa {
|
||||||
}
|
PrintLine("RSA key generation requires OpenSSL CLI:");
|
||||||
if cmd.tag == Cmd_Keygen {
|
PrintLine(" openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048");
|
||||||
if args.len < 3 {
|
PrintLine(" openssl rsa -in private.pem -pubout -out public.pem");
|
||||||
return ExitResult_MakeErr("ERROR: 'keygen' requires: <type>");
|
PrintLine("");
|
||||||
|
PrintLine("(Key generation from within Bux requires PEM write support — coming soon)");
|
||||||
|
return ExitResult_MakeOk(0);
|
||||||
}
|
}
|
||||||
return CmdKeygen(args[2]);
|
if kt.tag == KeyType_Ecdsa {
|
||||||
}
|
PrintLine("ECDSA key generation requires OpenSSL CLI:");
|
||||||
return ExitResult_MakeErr("ERROR: unhandled command");
|
PrintLine(" openssl ecparam -genkey -name prime256v1 -noout -out ec_private.pem");
|
||||||
}
|
PrintLine(" openssl ec -in ec_private.pem -pubout -out ec_public.pem");
|
||||||
|
PrintLine("");
|
||||||
// =============================================================================
|
PrintLine("(Key generation from within Bux requires PEM write support — coming soon)");
|
||||||
// Main Entry Point
|
return ExitResult_MakeOk(0);
|
||||||
// =============================================================================
|
}
|
||||||
func Main() -> int {
|
return ExitResult_MakeErr(f"ERROR: unknown key type '{keyType}'");
|
||||||
let argc: int = bux_argc();
|
|
||||||
|
|
||||||
// Collect CLI arguments into a generic Array<String>
|
|
||||||
let args: Array<String> = Array_New<String>(argc as uint);
|
|
||||||
for i in 0..argc {
|
|
||||||
Array_Push<String>(&args, bux_argv(i));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.len < 2 {
|
// =============================================================================
|
||||||
PrintUsage();
|
// Dispatch a parsed command to its handler
|
||||||
return 0;
|
// =============================================================================
|
||||||
|
func RunCommand(cmd: Cmd, args: Array<String>) -> ExitResult {
|
||||||
|
if cmd.tag == Cmd_Help {
|
||||||
|
PrintUsage();
|
||||||
|
return ExitResult_MakeOk(0);
|
||||||
|
}
|
||||||
|
if cmd.tag == Cmd_Sign {
|
||||||
|
if args.len < 5 {
|
||||||
|
return ExitResult_MakeErr("ERROR: 'sign' requires: <alg> <key> <claims_json>");
|
||||||
|
}
|
||||||
|
return CmdSign(args[2], args[3], args[4]);
|
||||||
|
}
|
||||||
|
if cmd.tag == Cmd_Verify {
|
||||||
|
if args.len < 5 {
|
||||||
|
return ExitResult_MakeErr("ERROR: 'verify' requires: <token> <alg> <key>");
|
||||||
|
}
|
||||||
|
return CmdVerify(args[2], args[3], args[4]);
|
||||||
|
}
|
||||||
|
if cmd.tag == Cmd_Decode {
|
||||||
|
if args.len < 3 {
|
||||||
|
return ExitResult_MakeErr("ERROR: 'decode' requires: <token>");
|
||||||
|
}
|
||||||
|
return CmdDecode(args[2]);
|
||||||
|
}
|
||||||
|
if cmd.tag == Cmd_Keygen {
|
||||||
|
if args.len < 3 {
|
||||||
|
return ExitResult_MakeErr("ERROR: 'keygen' requires: <type>");
|
||||||
|
}
|
||||||
|
return CmdKeygen(args[2]);
|
||||||
|
}
|
||||||
|
return ExitResult_MakeErr("ERROR: unhandled command");
|
||||||
}
|
}
|
||||||
|
|
||||||
let command: String = args[1];
|
// =============================================================================
|
||||||
let cmdOpt: CmdOption = ParseCmd(command);
|
// Main Entry Point
|
||||||
if cmdOpt.tag != CmdOption_Some {
|
// =============================================================================
|
||||||
PrintLine(f"ERROR: unknown command '{command}'");
|
func Main() -> int {
|
||||||
PrintLine(f"Run '{AppName} help' for usage.");
|
let argc: int = bux_argc();
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
let cmd: Cmd = cmdOpt.data.Some_0;
|
|
||||||
|
|
||||||
let result: ExitResult = RunCommand(cmd, args);
|
// Collect CLI arguments into a generic Array<String>
|
||||||
if result.tag == ExitResult_Err {
|
let args: Array<String> = Array_New<String>(argc as uint);
|
||||||
PrintLine(result.data.Err_0);
|
for i in 0..argc {
|
||||||
return 1;
|
Array_Push<String>(&args, bux_argv(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.len < 2 {
|
||||||
|
PrintUsage();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let command: String = args[1];
|
||||||
|
let cmdOpt: CmdOption = ParseCmd(command);
|
||||||
|
if cmdOpt.tag != CmdOption_Some {
|
||||||
|
PrintLine(f"ERROR: unknown command '{command}'");
|
||||||
|
PrintLine(f"Run '{AppName} help' for usage.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
let cmd: Cmd = cmdOpt.data.Some_0;
|
||||||
|
|
||||||
|
let result: ExitResult = RunCommand(cmd, args);
|
||||||
|
if result.tag == ExitResult_Err {
|
||||||
|
PrintLine(result.data.Err_0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return result.data.Ok_0;
|
||||||
}
|
}
|
||||||
return result.data.Ok_0;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // module Main
|
} // module Main
|
||||||
|
|||||||
+16
-16
@@ -1,21 +1,21 @@
|
|||||||
module Config {
|
module Config {
|
||||||
|
|
||||||
pub struct ServerConfig {
|
pub struct ServerConfig {
|
||||||
bindAddr: String;
|
bindAddr: String;
|
||||||
port: int;
|
port: int;
|
||||||
workerCount: int;
|
workerCount: int;
|
||||||
publicDir: String;
|
publicDir: String;
|
||||||
backlog: int;
|
backlog: int;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const func DefaultConfig() -> ServerConfig {
|
pub const func DefaultConfig() -> ServerConfig {
|
||||||
return ServerConfig {
|
return ServerConfig {
|
||||||
bindAddr: "0.0.0.0",
|
bindAddr: "0.0.0.0",
|
||||||
port: 8080,
|
port: 8080,
|
||||||
workerCount: 4,
|
workerCount: 4,
|
||||||
publicDir: "public",
|
publicDir: "public",
|
||||||
backlog: 128,
|
backlog: 128,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-50
@@ -1,65 +1,65 @@
|
|||||||
module Errors {
|
module Errors {
|
||||||
|
|
||||||
import Http::{HttpRequest};
|
import Http::{HttpRequest};
|
||||||
|
|
||||||
pub enum HttpError {
|
pub enum HttpError {
|
||||||
BadRequest,
|
BadRequest,
|
||||||
NotFound,
|
NotFound,
|
||||||
MethodNotAllowed,
|
MethodNotAllowed,
|
||||||
InternalError(String),
|
InternalError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ParseResult {
|
pub enum ParseResult {
|
||||||
Ok(HttpRequest),
|
Ok(HttpRequest),
|
||||||
Err(HttpError),
|
Err(HttpError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum FileResult {
|
pub enum FileResult {
|
||||||
Ok(String),
|
Ok(String),
|
||||||
Err(HttpError),
|
Err(HttpError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub func ParseResult_NewOk(req: HttpRequest) -> ParseResult {
|
pub func ParseResult_NewOk(req: HttpRequest) -> ParseResult {
|
||||||
let r: ParseResult = ParseResult { tag: ParseResult_Ok };
|
let r: ParseResult = ParseResult { tag: ParseResult_Ok };
|
||||||
r.data.Ok_0 = req;
|
r.data.Ok_0 = req;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub func ParseResult_NewErr(err: HttpError) -> ParseResult {
|
pub func ParseResult_NewErr(err: HttpError) -> ParseResult {
|
||||||
let r: ParseResult = ParseResult { tag: ParseResult_Err };
|
let r: ParseResult = ParseResult { tag: ParseResult_Err };
|
||||||
r.data.Err_0 = err;
|
r.data.Err_0 = err;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub func FileResult_NewOk(content: String) -> FileResult {
|
pub func FileResult_NewOk(content: String) -> FileResult {
|
||||||
let r: FileResult = FileResult { tag: FileResult_Ok };
|
let r: FileResult = FileResult { tag: FileResult_Ok };
|
||||||
r.data.Ok_0 = content;
|
r.data.Ok_0 = content;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub func FileResult_NewErr(err: HttpError) -> FileResult {
|
pub func FileResult_NewErr(err: HttpError) -> FileResult {
|
||||||
let r: FileResult = FileResult { tag: FileResult_Err };
|
let r: FileResult = FileResult { tag: FileResult_Err };
|
||||||
r.data.Err_0 = err;
|
r.data.Err_0 = err;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub func FileResult_IsOk(r: FileResult) -> bool {
|
pub func FileResult_IsOk(r: FileResult) -> bool {
|
||||||
return r.tag == FileResult_Ok;
|
return r.tag == FileResult_Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub func FileResult_Unwrap(r: FileResult) -> String {
|
pub func FileResult_Unwrap(r: FileResult) -> String {
|
||||||
return r.data.Ok_0;
|
return r.data.Ok_0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub func FileResult_UnwrapErr(r: FileResult) -> HttpError {
|
pub func FileResult_UnwrapErr(r: FileResult) -> HttpError {
|
||||||
return r.data.Err_0;
|
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_BadRequest { return "Bad Request"; }
|
||||||
if err.tag == HttpError_NotFound { return "Not Found"; }
|
if err.tag == HttpError_NotFound { return "Not Found"; }
|
||||||
if err.tag == HttpError_MethodNotAllowed { return "Method Not Allowed"; }
|
if err.tag == HttpError_MethodNotAllowed { return "Method Not Allowed"; }
|
||||||
return err.data.InternalError_0;
|
return err.data.InternalError_0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+72
-72
@@ -1,94 +1,94 @@
|
|||||||
module Handlers {
|
module Handlers {
|
||||||
|
|
||||||
import Std::String::{String_Eq, String_Contains};
|
import Std::String::{String_Eq, String_Contains};
|
||||||
import Http::{HttpMethod, HttpRequest, HttpResponse, Http_NewResponse, Http_MimeType, RequestHeader_Get};
|
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 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_strlen(s: String) -> uint;
|
||||||
extern func bux_file_exists(path: String) -> int;
|
extern func bux_file_exists(path: String) -> int;
|
||||||
extern func bux_read_file(path: String) -> String;
|
extern func bux_read_file(path: String) -> String;
|
||||||
extern func bux_path_join(a: String, b: 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_new(initial_cap: uint) -> *void;
|
||||||
extern func bux_sb_append(sb: *void, s: String);
|
extern func bux_sb_append(sb: *void, s: String);
|
||||||
extern func bux_sb_append_int(sb: *void, n: int64);
|
extern func bux_sb_append_int(sb: *void, n: int64);
|
||||||
extern func bux_sb_build(sb: *void) -> String;
|
extern func bux_sb_build(sb: *void) -> String;
|
||||||
extern func bux_sb_free(sb: *void);
|
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\"}");
|
return Http_NewResponse(404, "application/json; charset=utf-8", "{\"error\":\"not_found\"}");
|
||||||
}
|
|
||||||
|
|
||||||
pub func MethodNotAllowedResponse() -> HttpResponse {
|
|
||||||
return Http_NewResponse(405, "text/plain; charset=utf-8", "Method Not Allowed");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub func ReadStaticFile(requestPath: String) -> FileResult {
|
|
||||||
if String_Contains(requestPath, "..") {
|
|
||||||
return FileResult_NewErr(HttpError { tag: HttpError_NotFound });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var filePath: String = requestPath;
|
pub func MethodNotAllowedResponse() -> HttpResponse {
|
||||||
if String_Eq(filePath, "/") {
|
return Http_NewResponse(405, "text/plain; charset=utf-8", "Method Not Allowed");
|
||||||
filePath = "/index.html";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let fullPath: String = bux_path_join("public", filePath);
|
pub func ReadStaticFile(requestPath: String) -> FileResult {
|
||||||
if bux_file_exists(fullPath) == 0 {
|
if String_Contains(requestPath, "..") {
|
||||||
return FileResult_NewErr(HttpError { tag: HttpError_NotFound });
|
return FileResult_NewErr(HttpError { tag: HttpError_NotFound });
|
||||||
|
}
|
||||||
|
|
||||||
|
var filePath: String = requestPath;
|
||||||
|
if String_Eq(filePath, "/") {
|
||||||
|
filePath = "/index.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
let fullPath: String = bux_path_join("public", filePath);
|
||||||
|
if bux_file_exists(fullPath) == 0 {
|
||||||
|
return FileResult_NewErr(HttpError { tag: HttpError_NotFound });
|
||||||
|
}
|
||||||
|
|
||||||
|
let content: String = bux_read_file(fullPath);
|
||||||
|
return FileResult_NewOk(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
let content: String = bux_read_file(fullPath);
|
func FileErrorResponse(err: HttpError) -> HttpResponse {
|
||||||
return FileResult_NewOk(content);
|
match err {
|
||||||
}
|
HttpError::NotFound => NotFoundResponse(),
|
||||||
|
_ => Http_NewResponse(500, "text/plain; charset=utf-8", HttpError_ToString(err)),
|
||||||
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 {
|
|
||||||
if req.method != HttpMethod_GET && req.method != HttpMethod_HEAD {
|
|
||||||
return MethodNotAllowedResponse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let result: FileResult = ReadStaticFile(req.path);
|
pub func ServeStaticFile(req: HttpRequest) -> HttpResponse {
|
||||||
if FileResult_IsOk(result) {
|
if req.method != HttpMethod_GET && req.method != HttpMethod_HEAD {
|
||||||
let content: String = FileResult_Unwrap(result);
|
return MethodNotAllowedResponse();
|
||||||
return Http_NewResponse(200, Http_MimeType(req.path), content);
|
}
|
||||||
}
|
|
||||||
return FileErrorResponse(FileResult_UnwrapErr(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub func HandleApiHealth() -> HttpResponse {
|
let result: FileResult = ReadStaticFile(req.path);
|
||||||
return Http_NewResponse(200, "application/json; charset=utf-8",
|
if FileResult_IsOk(result) {
|
||||||
|
let content: String = FileResult_Unwrap(result);
|
||||||
|
return Http_NewResponse(200, Http_MimeType(req.path), content);
|
||||||
|
}
|
||||||
|
return FileErrorResponse(FileResult_UnwrapErr(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub func HandleApiHealth() -> HttpResponse {
|
||||||
|
return Http_NewResponse(200, "application/json; charset=utf-8",
|
||||||
"{\"status\":\"ok\",\"server\":\"Nexus\",\"version\":\"0.2.0\"}");
|
"{\"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",
|
return Http_NewResponse(200, "application/json; charset=utf-8",
|
||||||
"{\"name\":\"Nexus\",\"language\":\"Bux\",\"features\":[\"HTTP/1.1\",\"thread-pool\",\"algebraic-enums\"]}");
|
"{\"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");
|
let wsKey: String = RequestHeader_Get(req, "Sec-WebSocket-Key");
|
||||||
|
|
||||||
var resp: HttpResponse;
|
var resp: HttpResponse;
|
||||||
resp.statusCode = 101;
|
resp.statusCode = 101;
|
||||||
resp.contentType = "";
|
resp.contentType = "";
|
||||||
resp.body = "";
|
resp.body = "";
|
||||||
|
|
||||||
let sb: *void = bux_sb_new(256);
|
let sb: *void = bux_sb_new(256);
|
||||||
bux_sb_append(sb, "Upgrade: websocket\r\n");
|
bux_sb_append(sb, "Upgrade: websocket\r\n");
|
||||||
bux_sb_append(sb, "Connection: Upgrade\r\n");
|
bux_sb_append(sb, "Connection: Upgrade\r\n");
|
||||||
bux_sb_append(sb, "Sec-WebSocket-Accept: ");
|
bux_sb_append(sb, "Sec-WebSocket-Accept: ");
|
||||||
bux_sb_append(sb, wsKey);
|
bux_sb_append(sb, wsKey);
|
||||||
bux_sb_append(sb, "\r\n");
|
bux_sb_append(sb, "\r\n");
|
||||||
resp.extraHeaders = bux_sb_build(sb);
|
resp.extraHeaders = bux_sb_build(sb);
|
||||||
bux_sb_free(sb);
|
bux_sb_free(sb);
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+97
-97
@@ -1,107 +1,107 @@
|
|||||||
module Http {
|
module Http {
|
||||||
|
|
||||||
import Std::Array::{Array};
|
import Std::Array::{Array};
|
||||||
import Std::String::{String_Eq, String_EndsWith, String_Contains};
|
import Std::String::{String_Eq, String_EndsWith, String_Contains};
|
||||||
|
|
||||||
pub enum HttpMethod {
|
pub enum HttpMethod {
|
||||||
GET,
|
GET,
|
||||||
POST,
|
POST,
|
||||||
PUT,
|
PUT,
|
||||||
DELETE,
|
DELETE,
|
||||||
PATCH,
|
PATCH,
|
||||||
HEAD,
|
HEAD,
|
||||||
OPTIONS,
|
OPTIONS,
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
}
|
|
||||||
|
|
||||||
pub struct HeaderEntry {
|
|
||||||
key: String;
|
|
||||||
value: String;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct HttpRequest {
|
|
||||||
method: HttpMethod;
|
|
||||||
path: String;
|
|
||||||
version: String;
|
|
||||||
body: String;
|
|
||||||
headers: Array<HeaderEntry>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct HttpResponse {
|
|
||||||
statusCode: int;
|
|
||||||
contentType: String;
|
|
||||||
body: String;
|
|
||||||
extraHeaders: String;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub func Http_StatusText(code: int) -> String {
|
|
||||||
if code == 200 { return "OK"; }
|
|
||||||
if code == 201 { return "Created"; }
|
|
||||||
if code == 204 { return "No Content"; }
|
|
||||||
if code == 301 { return "Moved Permanently"; }
|
|
||||||
if code == 302 { return "Found"; }
|
|
||||||
if code == 304 { return "Not Modified"; }
|
|
||||||
if code == 400 { return "Bad Request"; }
|
|
||||||
if code == 401 { return "Unauthorized"; }
|
|
||||||
if code == 403 { return "Forbidden"; }
|
|
||||||
if code == 404 { return "Not Found"; }
|
|
||||||
if code == 405 { return "Method Not Allowed"; }
|
|
||||||
if code == 413 { return "Payload Too Large"; }
|
|
||||||
if code == 414 { return "URI Too Long"; }
|
|
||||||
if code == 500 { return "Internal Server Error"; }
|
|
||||||
if code == 501 { return "Not Implemented"; }
|
|
||||||
if code == 503 { return "Service Unavailable"; }
|
|
||||||
return "Unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
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"; }
|
|
||||||
if String_EndsWith(path, ".json") { return "application/json; charset=utf-8"; }
|
|
||||||
if String_EndsWith(path, ".xml") { return "application/xml; charset=utf-8"; }
|
|
||||||
if String_EndsWith(path, ".txt") { return "text/plain; charset=utf-8"; }
|
|
||||||
if String_EndsWith(path, ".png") { return "image/png"; }
|
|
||||||
if String_EndsWith(path, ".jpg") || String_EndsWith(path, ".jpeg") { return "image/jpeg"; }
|
|
||||||
if String_EndsWith(path, ".gif") { return "image/gif"; }
|
|
||||||
if String_EndsWith(path, ".svg") { return "image/svg+xml"; }
|
|
||||||
if String_EndsWith(path, ".ico") { return "image/x-icon"; }
|
|
||||||
if String_EndsWith(path, ".webp") { return "image/webp"; }
|
|
||||||
if String_EndsWith(path, ".woff2") { return "font/woff2"; }
|
|
||||||
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 {
|
|
||||||
match m {
|
|
||||||
HttpMethod::GET => "GET",
|
|
||||||
HttpMethod::POST => "POST",
|
|
||||||
HttpMethod::PUT => "PUT",
|
|
||||||
HttpMethod::DELETE => "DELETE",
|
|
||||||
HttpMethod::PATCH => "PATCH",
|
|
||||||
HttpMethod::HEAD => "HEAD",
|
|
||||||
HttpMethod::OPTIONS => "OPTIONS",
|
|
||||||
HttpMethod::UNKNOWN => "UNKNOWN",
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub func Http_NewResponse(code: int, contentType: String, body: String) -> HttpResponse {
|
pub struct HeaderEntry {
|
||||||
var resp: HttpResponse;
|
key: String;
|
||||||
resp.statusCode = code;
|
value: String;
|
||||||
resp.contentType = contentType;
|
}
|
||||||
resp.body = body;
|
|
||||||
resp.extraHeaders = "";
|
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub func RequestHeader_Get(req: HttpRequest, key: String) -> String {
|
pub struct HttpRequest {
|
||||||
for entry in req.headers {
|
method: HttpMethod;
|
||||||
if String_Eq(entry.key, key) {
|
path: String;
|
||||||
return entry.value;
|
version: String;
|
||||||
|
body: String;
|
||||||
|
headers: Array<HeaderEntry>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HttpResponse {
|
||||||
|
statusCode: int;
|
||||||
|
contentType: String;
|
||||||
|
body: String;
|
||||||
|
extraHeaders: String;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub func Http_StatusText(code: int) -> String {
|
||||||
|
if code == 200 { return "OK"; }
|
||||||
|
if code == 201 { return "Created"; }
|
||||||
|
if code == 204 { return "No Content"; }
|
||||||
|
if code == 301 { return "Moved Permanently"; }
|
||||||
|
if code == 302 { return "Found"; }
|
||||||
|
if code == 304 { return "Not Modified"; }
|
||||||
|
if code == 400 { return "Bad Request"; }
|
||||||
|
if code == 401 { return "Unauthorized"; }
|
||||||
|
if code == 403 { return "Forbidden"; }
|
||||||
|
if code == 404 { return "Not Found"; }
|
||||||
|
if code == 405 { return "Method Not Allowed"; }
|
||||||
|
if code == 413 { return "Payload Too Large"; }
|
||||||
|
if code == 414 { return "URI Too Long"; }
|
||||||
|
if code == 500 { return "Internal Server Error"; }
|
||||||
|
if code == 501 { return "Not Implemented"; }
|
||||||
|
if code == 503 { return "Service Unavailable"; }
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
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"; }
|
||||||
|
if String_EndsWith(path, ".json") { return "application/json; charset=utf-8"; }
|
||||||
|
if String_EndsWith(path, ".xml") { return "application/xml; charset=utf-8"; }
|
||||||
|
if String_EndsWith(path, ".txt") { return "text/plain; charset=utf-8"; }
|
||||||
|
if String_EndsWith(path, ".png") { return "image/png"; }
|
||||||
|
if String_EndsWith(path, ".jpg") || String_EndsWith(path, ".jpeg") { return "image/jpeg"; }
|
||||||
|
if String_EndsWith(path, ".gif") { return "image/gif"; }
|
||||||
|
if String_EndsWith(path, ".svg") { return "image/svg+xml"; }
|
||||||
|
if String_EndsWith(path, ".ico") { return "image/x-icon"; }
|
||||||
|
if String_EndsWith(path, ".webp") { return "image/webp"; }
|
||||||
|
if String_EndsWith(path, ".woff2") { return "font/woff2"; }
|
||||||
|
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 {
|
||||||
|
match m {
|
||||||
|
HttpMethod::GET => "GET",
|
||||||
|
HttpMethod::POST => "POST",
|
||||||
|
HttpMethod::PUT => "PUT",
|
||||||
|
HttpMethod::DELETE => "DELETE",
|
||||||
|
HttpMethod::PATCH => "PATCH",
|
||||||
|
HttpMethod::HEAD => "HEAD",
|
||||||
|
HttpMethod::OPTIONS => "OPTIONS",
|
||||||
|
HttpMethod::UNKNOWN => "UNKNOWN",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "";
|
|
||||||
}
|
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 {
|
||||||
|
for entry in req.headers {
|
||||||
|
if String_Eq(entry.key, key) {
|
||||||
|
return entry.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-37
@@ -1,49 +1,49 @@
|
|||||||
module Main {
|
module Main {
|
||||||
|
|
||||||
import Config::{ServerConfig, DefaultConfig};
|
import Config::{ServerConfig, DefaultConfig};
|
||||||
import Http::{HttpMethod};
|
import Http::{HttpMethod};
|
||||||
import Router::{Handler, Route, Router};
|
import Router::{Handler, Route, Router};
|
||||||
import Server::{RunServer};
|
import Server::{RunServer};
|
||||||
import Std::Array::{Array, Array_New, Array_Push};
|
import Std::Array::{Array, Array_New, Array_Push};
|
||||||
|
|
||||||
func BuildRouter() -> Router {
|
func BuildRouter() -> Router {
|
||||||
var routes: Array<Route> = Array_New<Route>(8);
|
var routes: Array<Route> = Array_New<Route>(8);
|
||||||
|
|
||||||
Array_Push<Route>(&routes, Route {
|
Array_Push<Route>(&routes, Route {
|
||||||
method: HttpMethod { tag: HttpMethod_GET },
|
method: HttpMethod { tag: HttpMethod_GET },
|
||||||
path: "/api/health",
|
path: "/api/health",
|
||||||
handler: Handler { tag: Handler_ApiHealth },
|
handler: Handler { tag: Handler_ApiHealth },
|
||||||
});
|
});
|
||||||
|
|
||||||
Array_Push<Route>(&routes, Route {
|
Array_Push<Route>(&routes, Route {
|
||||||
method: HttpMethod { tag: HttpMethod_GET },
|
method: HttpMethod { tag: HttpMethod_GET },
|
||||||
path: "/api/info",
|
path: "/api/info",
|
||||||
handler: Handler { tag: Handler_ApiInfo },
|
handler: Handler { tag: Handler_ApiInfo },
|
||||||
});
|
});
|
||||||
|
|
||||||
Array_Push<Route>(&routes, Route {
|
Array_Push<Route>(&routes, Route {
|
||||||
method: HttpMethod { tag: HttpMethod_GET },
|
method: HttpMethod { tag: HttpMethod_GET },
|
||||||
path: "/ws",
|
path: "/ws",
|
||||||
handler: Handler { tag: Handler_WsUpgrade },
|
handler: Handler { tag: Handler_WsUpgrade },
|
||||||
});
|
});
|
||||||
|
|
||||||
Array_Push<Route>(&routes, Route {
|
Array_Push<Route>(&routes, Route {
|
||||||
method: HttpMethod { tag: HttpMethod_GET },
|
method: HttpMethod { tag: HttpMethod_GET },
|
||||||
path: "/",
|
path: "/",
|
||||||
handler: Handler { tag: Handler_StaticFile },
|
handler: Handler { tag: Handler_StaticFile },
|
||||||
});
|
});
|
||||||
|
|
||||||
return Router {
|
return Router {
|
||||||
routes: routes,
|
routes: routes,
|
||||||
notFound: Handler { tag: Handler_NotFound },
|
notFound: Handler { tag: Handler_NotFound },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
func Main() -> int {
|
func Main() -> int {
|
||||||
let config: ServerConfig = DefaultConfig();
|
let config: ServerConfig = DefaultConfig();
|
||||||
let router: Router = BuildRouter();
|
let router: Router = BuildRouter();
|
||||||
|
|
||||||
return RunServer(config, router);
|
return RunServer(config, router);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+110
-110
@@ -1,130 +1,130 @@
|
|||||||
module Parser {
|
module Parser {
|
||||||
|
|
||||||
import Std::String::{String_Len, String_Eq, String_Trim};
|
import Std::String::{String_Len, String_Eq, String_Trim};
|
||||||
import Std::Array::{Array, Array_New, Array_Push};
|
import Std::Array::{Array, Array_New, Array_Push};
|
||||||
import Http::{HttpMethod, HttpRequest, HeaderEntry};
|
import Http::{HttpMethod, HttpRequest, HeaderEntry};
|
||||||
import Errors::{HttpError, ParseResult, ParseResult_NewOk, ParseResult_NewErr};
|
import Errors::{HttpError, ParseResult, ParseResult_NewOk, ParseResult_NewErr};
|
||||||
|
|
||||||
extern func bux_strlen(s: String) -> uint;
|
extern func bux_strlen(s: String) -> uint;
|
||||||
extern func bux_strstr(haystack: String, needle: String) -> String;
|
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_slice(s: String, start: uint, len: uint) -> String;
|
||||||
extern func bux_str_offset(pos: String, base: String) -> uint;
|
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, "GET") { return HttpMethod { tag: HttpMethod_GET }; }
|
||||||
if String_Eq(s, "POST") { return HttpMethod { tag: HttpMethod_POST }; }
|
if String_Eq(s, "POST") { return HttpMethod { tag: HttpMethod_POST }; }
|
||||||
if String_Eq(s, "PUT") { return HttpMethod { tag: HttpMethod_PUT }; }
|
if String_Eq(s, "PUT") { return HttpMethod { tag: HttpMethod_PUT }; }
|
||||||
if String_Eq(s, "DELETE") { return HttpMethod { tag: HttpMethod_DELETE }; }
|
if String_Eq(s, "DELETE") { return HttpMethod { tag: HttpMethod_DELETE }; }
|
||||||
if String_Eq(s, "PATCH") { return HttpMethod { tag: HttpMethod_PATCH }; }
|
if String_Eq(s, "PATCH") { return HttpMethod { tag: HttpMethod_PATCH }; }
|
||||||
if String_Eq(s, "HEAD") { return HttpMethod { tag: HttpMethod_HEAD }; }
|
if String_Eq(s, "HEAD") { return HttpMethod { tag: HttpMethod_HEAD }; }
|
||||||
if String_Eq(s, "OPTIONS") { return HttpMethod { tag: HttpMethod_OPTIONS }; }
|
if String_Eq(s, "OPTIONS") { return HttpMethod { tag: HttpMethod_OPTIONS }; }
|
||||||
return HttpMethod { tag: HttpMethod_UNKNOWN };
|
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 ""; }
|
if start < 0 || len <= 0 { return ""; }
|
||||||
return bux_str_slice(raw, start as uint, len as uint);
|
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);
|
let rawLen: uint = bux_strlen(raw);
|
||||||
if start as uint >= rawLen { return -1; }
|
if start as uint >= rawLen { return -1; }
|
||||||
let tail: String = bux_str_slice(raw, start as uint, rawLen - start as uint);
|
let tail: String = bux_str_slice(raw, start as uint, rawLen - start as uint);
|
||||||
let hit: String = bux_strstr(tail, "\r\n");
|
let hit: String = bux_strstr(tail, "\r\n");
|
||||||
if String_Len(hit) == 0 { return -1; }
|
if String_Len(hit) == 0 { return -1; }
|
||||||
let offset: uint = bux_str_offset(hit, tail);
|
let offset: uint = bux_str_offset(hit, tail);
|
||||||
return start + offset as int;
|
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 headers: Array<HeaderEntry> = Array_New<HeaderEntry>(16);
|
||||||
var pos: int = start;
|
var pos: int = start;
|
||||||
while pos < end {
|
while pos < end {
|
||||||
let lineEnd: int = FindCrlf(raw, pos);
|
let lineEnd: int = FindCrlf(raw, pos);
|
||||||
if lineEnd < 0 || lineEnd >= end { break; }
|
if lineEnd < 0 || lineEnd >= end { break; }
|
||||||
let lineLen: int = lineEnd - pos;
|
let lineLen: int = lineEnd - pos;
|
||||||
if lineLen > 0 {
|
if lineLen > 0 {
|
||||||
let line: String = Slice(raw, pos, lineLen);
|
let line: String = Slice(raw, pos, lineLen);
|
||||||
let colon: String = bux_strstr(line, ":");
|
let colon: String = bux_strstr(line, ":");
|
||||||
if String_Len(colon) > 0 {
|
if String_Len(colon) > 0 {
|
||||||
let keyLen: uint = bux_str_offset(colon, line);
|
let keyLen: uint = bux_str_offset(colon, line);
|
||||||
let key: String = String_Trim(Slice(line, 0, keyLen as int));
|
let key: String = String_Trim(Slice(line, 0, keyLen as int));
|
||||||
let valStart: int = keyLen as int + 1;
|
let valStart: int = keyLen as int + 1;
|
||||||
let val: String = String_Trim(Slice(line, valStart, lineLen - valStart));
|
let val: String = String_Trim(Slice(line, valStart, lineLen - valStart));
|
||||||
let entry: HeaderEntry = HeaderEntry { key: key, value: val };
|
let entry: HeaderEntry = HeaderEntry { key: key, value: val };
|
||||||
Array_Push<HeaderEntry>(&headers, entry);
|
Array_Push<HeaderEntry>(&headers, entry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
pos = lineEnd + 2;
|
||||||
}
|
}
|
||||||
pos = lineEnd + 2;
|
return headers;
|
||||||
}
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub func ParseRequest(raw: String) -> ParseResult {
|
|
||||||
let rawLen: uint = bux_strlen(raw);
|
|
||||||
if rawLen == 0 {
|
|
||||||
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find end of request line
|
pub func ParseRequest(raw: String) -> ParseResult {
|
||||||
let lineEnd: int = FindCrlf(raw, 0);
|
let rawLen: uint = bux_strlen(raw);
|
||||||
if lineEnd < 0 {
|
if rawLen == 0 {
|
||||||
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
|
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split request line: METHOD PATH VERSION
|
// Find end of request line
|
||||||
var methodEnd: int = -1;
|
let lineEnd: int = FindCrlf(raw, 0);
|
||||||
var pathStart: int = -1;
|
if lineEnd < 0 {
|
||||||
var pathEnd: int = -1;
|
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
|
||||||
var i: int = 0;
|
}
|
||||||
while i < lineEnd {
|
|
||||||
if raw[i] as int == 32 { // space
|
// Split request line: METHOD PATH VERSION
|
||||||
if methodEnd < 0 {
|
var methodEnd: int = -1;
|
||||||
methodEnd = i;
|
var pathStart: int = -1;
|
||||||
pathStart = i + 1;
|
var pathEnd: int = -1;
|
||||||
} else if pathEnd < 0 {
|
var i: int = 0;
|
||||||
pathEnd = i;
|
while i < lineEnd {
|
||||||
break;
|
if raw[i] as int == 32 { // space
|
||||||
|
if methodEnd < 0 {
|
||||||
|
methodEnd = i;
|
||||||
|
pathStart = i + 1;
|
||||||
|
} else if pathEnd < 0 {
|
||||||
|
pathEnd = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
i = i + 1;
|
||||||
}
|
}
|
||||||
i = i + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if methodEnd < 0 || pathStart < 0 || pathEnd < 0 {
|
if methodEnd < 0 || pathStart < 0 || pathEnd < 0 {
|
||||||
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
|
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
|
||||||
}
|
|
||||||
|
|
||||||
let methodStr: String = Slice(raw, 0, methodEnd);
|
|
||||||
let path: String = Slice(raw, pathStart, pathEnd - pathStart);
|
|
||||||
let version: String = Slice(raw, pathEnd + 1, lineEnd - pathEnd - 1);
|
|
||||||
|
|
||||||
// Find header/body boundary
|
|
||||||
let boundary: String = bux_strstr(raw, "\r\n\r\n");
|
|
||||||
var headers: Array<HeaderEntry> = Array_New<HeaderEntry>(16);
|
|
||||||
var body: String = "";
|
|
||||||
if String_Len(boundary) > 0 {
|
|
||||||
let headerEnd: uint = bux_str_offset(boundary, raw);
|
|
||||||
headers = ParseHeaders(raw, lineEnd + 2, headerEnd as int);
|
|
||||||
let bodyStart: uint = headerEnd + 4;
|
|
||||||
if bodyStart < rawLen {
|
|
||||||
body = bux_str_slice(raw, bodyStart, rawLen - bodyStart);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
headers = ParseHeaders(raw, lineEnd + 2, rawLen as int);
|
|
||||||
}
|
|
||||||
|
|
||||||
if String_Eq(path, "") {
|
let methodStr: String = Slice(raw, 0, methodEnd);
|
||||||
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
|
let path: String = Slice(raw, pathStart, pathEnd - pathStart);
|
||||||
}
|
let version: String = Slice(raw, pathEnd + 1, lineEnd - pathEnd - 1);
|
||||||
|
|
||||||
let req: HttpRequest = HttpRequest {
|
// Find header/body boundary
|
||||||
method: ParseMethod(methodStr),
|
let boundary: String = bux_strstr(raw, "\r\n\r\n");
|
||||||
path: path,
|
var headers: Array<HeaderEntry> = Array_New<HeaderEntry>(16);
|
||||||
version: version,
|
var body: String = "";
|
||||||
body: body,
|
if String_Len(boundary) > 0 {
|
||||||
headers: headers,
|
let headerEnd: uint = bux_str_offset(boundary, raw);
|
||||||
};
|
headers = ParseHeaders(raw, lineEnd + 2, headerEnd as int);
|
||||||
return ParseResult_NewOk(req);
|
let bodyStart: uint = headerEnd + 4;
|
||||||
}
|
if bodyStart < rawLen {
|
||||||
|
body = bux_str_slice(raw, bodyStart, rawLen - bodyStart);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
headers = ParseHeaders(raw, lineEnd + 2, rawLen as int);
|
||||||
|
}
|
||||||
|
|
||||||
|
if String_Eq(path, "") {
|
||||||
|
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
|
||||||
|
}
|
||||||
|
|
||||||
|
let req: HttpRequest = HttpRequest {
|
||||||
|
method: ParseMethod(methodStr),
|
||||||
|
path: path,
|
||||||
|
version: version,
|
||||||
|
body: body,
|
||||||
|
headers: headers,
|
||||||
|
};
|
||||||
|
return ParseResult_NewOk(req);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-37
@@ -1,46 +1,46 @@
|
|||||||
module Router {
|
module Router {
|
||||||
|
|
||||||
import Std::Array::{Array};
|
import Std::Array::{Array};
|
||||||
import Std::String::{String_Eq};
|
import Std::String::{String_Eq};
|
||||||
import Http::{HttpMethod, HttpRequest, HttpResponse, Http_NewResponse};
|
import Http::{HttpMethod, HttpRequest, HttpResponse, Http_NewResponse};
|
||||||
import Handlers::{ServeStaticFile, HandleApiHealth, HandleApiInfo, HandleWebSocketUpgrade, NotFoundResponse};
|
import Handlers::{ServeStaticFile, HandleApiHealth, HandleApiInfo, HandleWebSocketUpgrade, NotFoundResponse};
|
||||||
|
|
||||||
pub enum Handler {
|
pub enum Handler {
|
||||||
StaticFile,
|
StaticFile,
|
||||||
ApiHealth,
|
ApiHealth,
|
||||||
ApiInfo,
|
ApiInfo,
|
||||||
WsUpgrade,
|
WsUpgrade,
|
||||||
NotFound,
|
NotFound,
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Route {
|
|
||||||
method: HttpMethod;
|
|
||||||
path: String;
|
|
||||||
handler: Handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Router {
|
|
||||||
routes: Array<Route>;
|
|
||||||
notFound: Handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub func Handler_Handle(h: Handler, req: HttpRequest) -> HttpResponse {
|
|
||||||
match h {
|
|
||||||
Handler::StaticFile => ServeStaticFile(req),
|
|
||||||
Handler::ApiHealth => HandleApiHealth(),
|
|
||||||
Handler::ApiInfo => HandleApiInfo(),
|
|
||||||
Handler::WsUpgrade => HandleWebSocketUpgrade(req),
|
|
||||||
Handler::NotFound => NotFoundResponse(),
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub func Router_Dispatch(r: Router, req: HttpRequest) -> HttpResponse {
|
pub struct Route {
|
||||||
for route in r.routes {
|
method: HttpMethod;
|
||||||
if route.method == req.method && String_Eq(route.path, req.path) {
|
path: String;
|
||||||
return Handler_Handle(route.handler, req);
|
handler: Handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Router {
|
||||||
|
routes: Array<Route>;
|
||||||
|
notFound: Handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub func Handler_Handle(h: Handler, req: HttpRequest) -> HttpResponse {
|
||||||
|
match h {
|
||||||
|
Handler::StaticFile => ServeStaticFile(req),
|
||||||
|
Handler::ApiHealth => HandleApiHealth(),
|
||||||
|
Handler::ApiInfo => HandleApiInfo(),
|
||||||
|
Handler::WsUpgrade => HandleWebSocketUpgrade(req),
|
||||||
|
Handler::NotFound => NotFoundResponse(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Handler_Handle(r.notFound, req);
|
|
||||||
}
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+148
-148
@@ -1,176 +1,176 @@
|
|||||||
module Server {
|
module Server {
|
||||||
|
|
||||||
import Std::Io::{Print, PrintLine, PrintInt};
|
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::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::String::{String_Len, String_StartsWith};
|
||||||
import Std::Channel::{Channel, Channel_New, Channel_Send, Channel_Recv};
|
import Std::Channel::{Channel, Channel_New, Channel_Send, Channel_Recv};
|
||||||
import Config::{ServerConfig};
|
import Config::{ServerConfig};
|
||||||
import Http::{HttpRequest, HttpResponse, Http_StatusText, Http_NewResponse};
|
import Http::{HttpRequest, HttpResponse, Http_StatusText, Http_NewResponse};
|
||||||
import Errors::{ParseResult};
|
import Errors::{ParseResult};
|
||||||
import Parser::{ParseRequest};
|
import Parser::{ParseRequest};
|
||||||
import Router::{Router, Router_Dispatch};
|
import Router::{Router, Router_Dispatch};
|
||||||
|
|
||||||
extern func bux_strlen(s: String) -> uint;
|
extern func bux_strlen(s: String) -> uint;
|
||||||
extern func bux_sb_new(initial_cap: uint) -> *void;
|
extern func bux_sb_new(initial_cap: uint) -> *void;
|
||||||
extern func bux_sb_append(sb: *void, s: String);
|
extern func bux_sb_append(sb: *void, s: String);
|
||||||
extern func bux_sb_append_int(sb: *void, n: int64);
|
extern func bux_sb_append_int(sb: *void, n: int64);
|
||||||
extern func bux_sb_build(sb: *void) -> String;
|
extern func bux_sb_build(sb: *void) -> String;
|
||||||
extern func bux_sb_free(sb: *void);
|
extern func bux_sb_free(sb: *void);
|
||||||
|
|
||||||
pub struct ConnectionTask {
|
pub struct ConnectionTask {
|
||||||
fd: int;
|
fd: int;
|
||||||
}
|
|
||||||
|
|
||||||
pub func BuildResponse(resp: HttpResponse) -> String {
|
|
||||||
let sb: *void = bux_sb_new(4096);
|
|
||||||
|
|
||||||
bux_sb_append(sb, "HTTP/1.1 ");
|
|
||||||
bux_sb_append_int(sb, resp.statusCode as int64);
|
|
||||||
bux_sb_append(sb, " ");
|
|
||||||
bux_sb_append(sb, Http_StatusText(resp.statusCode));
|
|
||||||
bux_sb_append(sb, "\r\n");
|
|
||||||
|
|
||||||
bux_sb_append(sb, "Server: Nexus/0.2.0 (Bux)\r\n");
|
|
||||||
|
|
||||||
if bux_strlen(resp.extraHeaders) > 0 {
|
|
||||||
bux_sb_append(sb, resp.extraHeaders);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if bux_strlen(resp.contentType) > 0 {
|
pub func BuildResponse(resp: HttpResponse) -> String {
|
||||||
bux_sb_append(sb, "Content-Type: ");
|
let sb: *void = bux_sb_new(4096);
|
||||||
bux_sb_append(sb, resp.contentType);
|
|
||||||
|
bux_sb_append(sb, "HTTP/1.1 ");
|
||||||
|
bux_sb_append_int(sb, resp.statusCode as int64);
|
||||||
|
bux_sb_append(sb, " ");
|
||||||
|
bux_sb_append(sb, Http_StatusText(resp.statusCode));
|
||||||
bux_sb_append(sb, "\r\n");
|
bux_sb_append(sb, "\r\n");
|
||||||
|
|
||||||
|
bux_sb_append(sb, "Server: Nexus/0.2.0 (Bux)\r\n");
|
||||||
|
|
||||||
|
if bux_strlen(resp.extraHeaders) > 0 {
|
||||||
|
bux_sb_append(sb, resp.extraHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
|
if bux_strlen(resp.contentType) > 0 {
|
||||||
|
bux_sb_append(sb, "Content-Type: ");
|
||||||
|
bux_sb_append(sb, resp.contentType);
|
||||||
|
bux_sb_append(sb, "\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
let bodyLen: uint = bux_strlen(resp.body);
|
||||||
|
bux_sb_append(sb, "Content-Length: ");
|
||||||
|
bux_sb_append_int(sb, bodyLen as int64);
|
||||||
|
bux_sb_append(sb, "\r\n");
|
||||||
|
bux_sb_append(sb, "Connection: close\r\n");
|
||||||
|
bux_sb_append(sb, "\r\n");
|
||||||
|
|
||||||
|
if bodyLen > 0 {
|
||||||
|
bux_sb_append(sb, resp.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: String = bux_sb_build(sb);
|
||||||
|
bux_sb_free(sb);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
let bodyLen: uint = bux_strlen(resp.body);
|
pub func HandleConnection(fd: int, router: Router) {
|
||||||
bux_sb_append(sb, "Content-Length: ");
|
let raw: String = Net_Recv(fd, 8192);
|
||||||
bux_sb_append_int(sb, bodyLen as int64);
|
if String_Len(raw) == 0 {
|
||||||
bux_sb_append(sb, "\r\n");
|
return;
|
||||||
bux_sb_append(sb, "Connection: close\r\n");
|
}
|
||||||
bux_sb_append(sb, "\r\n");
|
|
||||||
|
|
||||||
if bodyLen > 0 {
|
// HTTP/2 preface detection
|
||||||
bux_sb_append(sb, resp.body);
|
if String_StartsWith(raw, "PRI * HTTP/2.0") {
|
||||||
}
|
let resp: HttpResponse = Http_NewResponse(200, "text/plain; charset=utf-8",
|
||||||
|
|
||||||
let result: String = bux_sb_build(sb);
|
|
||||||
bux_sb_free(sb);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub func HandleConnection(fd: int, router: Router) {
|
|
||||||
let raw: String = Net_Recv(fd, 8192);
|
|
||||||
if String_Len(raw) == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP/2 preface detection
|
|
||||||
if String_StartsWith(raw, "PRI * HTTP/2.0") {
|
|
||||||
let resp: HttpResponse = Http_NewResponse(200, "text/plain; charset=utf-8",
|
|
||||||
"HTTP/2 detected — full support planned for future release.\r\n");
|
"HTTP/2 detected — full support planned for future release.\r\n");
|
||||||
Net_Send(fd, BuildResponse(resp));
|
Net_Send(fd, BuildResponse(resp));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let parsed: ParseResult = ParseRequest(raw);
|
let parsed: ParseResult = ParseRequest(raw);
|
||||||
if parsed.tag == ParseResult_Ok {
|
if parsed.tag == ParseResult_Ok {
|
||||||
let req: HttpRequest = parsed.data.Ok_0;
|
let req: HttpRequest = parsed.data.Ok_0;
|
||||||
let resp: HttpResponse = Router_Dispatch(router, req);
|
let resp: HttpResponse = Router_Dispatch(router, req);
|
||||||
Net_Send(fd, BuildResponse(resp));
|
Net_Send(fd, BuildResponse(resp));
|
||||||
} else {
|
} else {
|
||||||
let resp: HttpResponse = Http_NewResponse(400, "text/plain; charset=utf-8", "Bad Request");
|
let resp: HttpResponse = Http_NewResponse(400, "text/plain; charset=utf-8", "Bad Request");
|
||||||
Net_Send(fd, BuildResponse(resp));
|
Net_Send(fd, BuildResponse(resp));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct WorkerCtx {
|
|
||||||
taskQueue: *Channel<ConnectionTask>;
|
|
||||||
router: Router;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
serverFd: int;
|
|
||||||
taskQueue: *Channel<ConnectionTask>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub func Acceptor(ctx: *AcceptorCtx) {
|
|
||||||
while true {
|
|
||||||
let fd: int = Net_Accept(ctx.serverFd);
|
|
||||||
if fd >= 0 {
|
|
||||||
let task: ConnectionTask = ConnectionTask { fd: fd };
|
|
||||||
Channel_Send<ConnectionTask>(ctx.taskQueue, task);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub func RunServer(config: ServerConfig, router: Router) -> int {
|
pub struct WorkerCtx {
|
||||||
PrintLine("================================================");
|
taskQueue: *Channel<ConnectionTask>;
|
||||||
PrintLine(" Nexus HTTP Server v0.2.0");
|
router: Router;
|
||||||
PrintLine(" Production-ready HTTP/1.1 with thread-pool");
|
|
||||||
PrintLine(" Built with Bux");
|
|
||||||
PrintLine("================================================");
|
|
||||||
PrintLine("");
|
|
||||||
|
|
||||||
let serverFd: int = Net_Create();
|
|
||||||
if serverFd < 0 {
|
|
||||||
PrintLine("FATAL: socket() failed");
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !Net_SetReuse(serverFd) {
|
pub func Worker(ctx: *WorkerCtx) {
|
||||||
PrintLine("WARN: SO_REUSEADDR failed");
|
while true {
|
||||||
|
let task: ConnectionTask = Channel_Recv<ConnectionTask>(ctx.taskQueue);
|
||||||
|
HandleConnection(task.fd, ctx.router);
|
||||||
|
Net_Close(task.fd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !Net_Bind(serverFd, config.bindAddr, config.port) {
|
pub struct AcceptorCtx {
|
||||||
Print("FATAL: bind failed: ");
|
serverFd: int;
|
||||||
PrintLine(Net_LastError());
|
taskQueue: *Channel<ConnectionTask>;
|
||||||
Net_Close(serverFd);
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !Net_Listen(serverFd, config.backlog) {
|
pub func Acceptor(ctx: *AcceptorCtx) {
|
||||||
PrintLine("FATAL: listen() failed");
|
while true {
|
||||||
Net_Close(serverFd);
|
let fd: int = Net_Accept(ctx.serverFd);
|
||||||
return 1;
|
if fd >= 0 {
|
||||||
|
let task: ConnectionTask = ConnectionTask { fd: fd };
|
||||||
|
Channel_Send<ConnectionTask>(ctx.taskQueue, task);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Print("Listening on http://");
|
pub func RunServer(config: ServerConfig, router: Router) -> int {
|
||||||
Print(config.bindAddr);
|
PrintLine("================================================");
|
||||||
Print(":");
|
PrintLine(" Nexus HTTP Server v0.2.0");
|
||||||
PrintInt(config.port);
|
PrintLine(" Production-ready HTTP/1.1 with thread-pool");
|
||||||
PrintLine("");
|
PrintLine(" Built with Bux");
|
||||||
PrintInt(config.workerCount);
|
PrintLine("================================================");
|
||||||
PrintLine(" worker threads | static: ./public/");
|
PrintLine("");
|
||||||
PrintLine("Endpoints: / /api/health /api/info /ws");
|
|
||||||
PrintLine("Press Ctrl+C to stop.");
|
|
||||||
PrintLine("");
|
|
||||||
|
|
||||||
let taskQueue: Channel<ConnectionTask> = Channel_New<ConnectionTask>(config.backlog as int64);
|
let serverFd: int = Net_Create();
|
||||||
let workerCtx: WorkerCtx = WorkerCtx { taskQueue: &taskQueue, router: router };
|
if serverFd < 0 {
|
||||||
let acceptorCtx: AcceptorCtx = AcceptorCtx { serverFd: serverFd, taskQueue: &taskQueue };
|
PrintLine("FATAL: socket() failed");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Spawn workers (main thread will also become one)
|
if !Net_SetReuse(serverFd) {
|
||||||
var i: int = 0;
|
PrintLine("WARN: SO_REUSEADDR failed");
|
||||||
while i < config.workerCount - 1 {
|
}
|
||||||
spawn Worker(&workerCtx);
|
|
||||||
i = i + 1;
|
if !Net_Bind(serverFd, config.bindAddr, config.port) {
|
||||||
|
Print("FATAL: bind failed: ");
|
||||||
|
PrintLine(Net_LastError());
|
||||||
|
Net_Close(serverFd);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Net_Listen(serverFd, config.backlog) {
|
||||||
|
PrintLine("FATAL: listen() failed");
|
||||||
|
Net_Close(serverFd);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Print("Listening on http://");
|
||||||
|
Print(config.bindAddr);
|
||||||
|
Print(":");
|
||||||
|
PrintInt(config.port);
|
||||||
|
PrintLine("");
|
||||||
|
PrintInt(config.workerCount);
|
||||||
|
PrintLine(" worker threads | static: ./public/");
|
||||||
|
PrintLine("Endpoints: / /api/health /api/info /ws");
|
||||||
|
PrintLine("Press Ctrl+C to stop.");
|
||||||
|
PrintLine("");
|
||||||
|
|
||||||
|
let taskQueue: Channel<ConnectionTask> = Channel_New<ConnectionTask>(config.backlog as int64);
|
||||||
|
let workerCtx: WorkerCtx = WorkerCtx { taskQueue: &taskQueue, router: router };
|
||||||
|
let acceptorCtx: AcceptorCtx = AcceptorCtx { serverFd: serverFd, taskQueue: &taskQueue };
|
||||||
|
|
||||||
|
// Spawn workers (main thread will also become one)
|
||||||
|
var i: int = 0;
|
||||||
|
while i < config.workerCount - 1 {
|
||||||
|
spawn Worker(&workerCtx);
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn acceptor
|
||||||
|
spawn Acceptor(&acceptorCtx);
|
||||||
|
|
||||||
|
// Main thread works too
|
||||||
|
Worker(&workerCtx);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spawn acceptor
|
|
||||||
spawn Acceptor(&acceptorCtx);
|
|
||||||
|
|
||||||
// Main thread works too
|
|
||||||
Worker(&workerCtx);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+302
-302
@@ -1,339 +1,339 @@
|
|||||||
module Main {
|
module Main {
|
||||||
|
|
||||||
import Std::Io::{PrintLine, Print, PrintInt, ReadFile, WriteFile, FileExists};
|
import Std::Io::{PrintLine, Print, PrintInt, ReadFile, WriteFile, FileExists};
|
||||||
import Std::Os::{Os_ArgsCount, Os_Args};
|
import Std::Os::{Os_ArgsCount, Os_Args};
|
||||||
import Std::String::{String_Len, String_Eq, String_StartsWith, String_Find, String_Slice, String_Offset};
|
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_strlen(s: String) -> uint;
|
||||||
extern func bux_strstr(haystack: String, needle: String) -> String;
|
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_slice(s: String, start: uint, len: uint) -> String;
|
||||||
extern func bux_str_split_count(s: String, delim: String) -> uint;
|
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_split_part(s: String, delim: String, index: uint) -> String;
|
||||||
extern func bux_sb_new(initial_cap: uint) -> *void;
|
extern func bux_sb_new(initial_cap: uint) -> *void;
|
||||||
extern func bux_sb_append(sb: *void, s: String);
|
extern func bux_sb_append(sb: *void, s: String);
|
||||||
extern func bux_sb_append_char(sb: *void, c: char8);
|
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);
|
bux_sb_append_char(sb, c as char8);
|
||||||
}
|
}
|
||||||
extern func bux_sb_build(sb: *void) -> String;
|
extern func bux_sb_build(sb: *void) -> String;
|
||||||
extern func bux_sb_free(sb: *void);
|
extern func bux_sb_free(sb: *void);
|
||||||
extern func bux_alloc(size: uint) -> *void;
|
extern func bux_alloc(size: uint) -> *void;
|
||||||
extern func bux_free(ptr: *void);
|
extern func bux_free(ptr: *void);
|
||||||
|
|
||||||
struct Database {
|
struct Database {
|
||||||
path: String,
|
path: String,
|
||||||
sb: *void,
|
sb: *void,
|
||||||
loaded: bool,
|
loaded: bool,
|
||||||
count: uint,
|
count: uint,
|
||||||
}
|
}
|
||||||
|
|
||||||
func DB_New(filePath: String) -> Database {
|
func DB_New(filePath: String) -> Database {
|
||||||
var db: Database;
|
var db: Database;
|
||||||
db.path = filePath;
|
db.path = filePath;
|
||||||
db.sb = bux_sb_new(4096);
|
db.sb = bux_sb_new(4096);
|
||||||
db.loaded = false;
|
db.loaded = false;
|
||||||
db.count = 0;
|
db.count = 0;
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
func DB_Load(self: *Database) -> bool {
|
func DB_Load(self: *Database) -> bool {
|
||||||
if self.loaded { return true; }
|
if self.loaded { return true; }
|
||||||
|
|
||||||
if !FileExists(self.path) {
|
if !FileExists(self.path) {
|
||||||
|
self.loaded = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let raw: String = ReadFile(self.path);
|
||||||
|
if String_Len(raw) == 0 || bux_strlen(raw) == 0 {
|
||||||
|
self.loaded = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bux_sb_append(self.sb, raw);
|
||||||
|
|
||||||
|
let lineCount: uint = bux_str_split_count(raw, "\n");
|
||||||
|
var i: uint = 0;
|
||||||
|
var n: uint = 0;
|
||||||
|
while i < lineCount {
|
||||||
|
let line: String = bux_str_split_part(raw, "\n", i);
|
||||||
|
if bux_strlen(line) > 0 {
|
||||||
|
n = n + 1;
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
self.count = n;
|
||||||
self.loaded = true;
|
self.loaded = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let raw: String = ReadFile(self.path);
|
func DB_Get(self: *Database, key: String) -> String {
|
||||||
if String_Len(raw) == 0 || bux_strlen(raw) == 0 {
|
let raw: String = bux_sb_build(self.sb);
|
||||||
self.loaded = true;
|
let lineCount: uint = bux_str_split_count(raw, "\n");
|
||||||
return true;
|
var i: uint = 0;
|
||||||
}
|
while i < lineCount {
|
||||||
|
let line: String = bux_str_split_part(raw, "\n", i);
|
||||||
bux_sb_append(self.sb, raw);
|
|
||||||
|
|
||||||
let lineCount: uint = bux_str_split_count(raw, "\n");
|
|
||||||
var i: uint = 0;
|
|
||||||
var n: uint = 0;
|
|
||||||
while i < lineCount {
|
|
||||||
let line: String = bux_str_split_part(raw, "\n", i);
|
|
||||||
if bux_strlen(line) > 0 {
|
|
||||||
n = n + 1;
|
|
||||||
}
|
|
||||||
i = i + 1;
|
|
||||||
}
|
|
||||||
self.count = n;
|
|
||||||
self.loaded = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
while i < lineCount {
|
|
||||||
let line: String = bux_str_split_part(raw, "\n", i);
|
|
||||||
if String_StartsWith(line, key) {
|
|
||||||
let eqPos: String = String_Find(line, "=");
|
|
||||||
if String_Len(eqPos) > 0 {
|
|
||||||
let keyLen: uint = bux_strlen(key);
|
|
||||||
let valStart: uint = keyLen + 1;
|
|
||||||
let lineLen: uint = bux_strlen(line);
|
|
||||||
if valStart < lineLen {
|
|
||||||
return bux_str_slice(line, valStart, lineLen - valStart);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i = i + 1;
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
|
|
||||||
var newSb: *void = bux_sb_new(4096);
|
|
||||||
var found: bool = false;
|
|
||||||
var i: uint = 0;
|
|
||||||
while i < lineCount {
|
|
||||||
let line: String = bux_str_split_part(raw, "\n", i);
|
|
||||||
if bux_strlen(line) > 0 {
|
|
||||||
if String_StartsWith(line, key) {
|
if String_StartsWith(line, key) {
|
||||||
let eqPos: String = String_Find(line, "=");
|
let eqPos: String = String_Find(line, "=");
|
||||||
if String_Len(eqPos) > 0 {
|
if String_Len(eqPos) > 0 {
|
||||||
bux_sb_append(newSb, key);
|
let keyLen: uint = bux_strlen(key);
|
||||||
SB_AppendChar(newSb, 61);
|
let valStart: uint = keyLen + 1;
|
||||||
bux_sb_append(newSb, value);
|
let lineLen: uint = bux_strlen(line);
|
||||||
SB_AppendChar(newSb, 10);
|
if valStart < lineLen {
|
||||||
found = true;
|
return bux_str_slice(line, valStart, lineLen - valStart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
var newSb: *void = bux_sb_new(4096);
|
||||||
|
var found: bool = false;
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < lineCount {
|
||||||
|
let line: String = bux_str_split_part(raw, "\n", i);
|
||||||
|
if bux_strlen(line) > 0 {
|
||||||
|
if String_StartsWith(line, key) {
|
||||||
|
let eqPos: String = String_Find(line, "=");
|
||||||
|
if String_Len(eqPos) > 0 {
|
||||||
|
bux_sb_append(newSb, key);
|
||||||
|
SB_AppendChar(newSb, 61);
|
||||||
|
bux_sb_append(newSb, value);
|
||||||
|
SB_AppendChar(newSb, 10);
|
||||||
|
found = true;
|
||||||
|
} else {
|
||||||
|
bux_sb_append(newSb, line);
|
||||||
|
SB_AppendChar(newSb, 10);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
bux_sb_append(newSb, line);
|
bux_sb_append(newSb, line);
|
||||||
SB_AppendChar(newSb, 10);
|
SB_AppendChar(newSb, 10);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
bux_sb_append(newSb, line);
|
|
||||||
SB_AppendChar(newSb, 10);
|
|
||||||
}
|
}
|
||||||
|
i = i + 1;
|
||||||
}
|
}
|
||||||
i = i + 1;
|
|
||||||
|
if !found {
|
||||||
|
bux_sb_append(newSb, key);
|
||||||
|
SB_AppendChar(newSb, 61);
|
||||||
|
bux_sb_append(newSb, value);
|
||||||
|
SB_AppendChar(newSb, 10);
|
||||||
|
self.count = self.count + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bux_sb_free(self.sb);
|
||||||
|
self.sb = newSb;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
func DB_Del(self: *Database, key: String) {
|
||||||
bux_sb_append(newSb, key);
|
let raw: String = bux_sb_build(self.sb);
|
||||||
SB_AppendChar(newSb, 61);
|
let lineCount: uint = bux_str_split_count(raw, "\n");
|
||||||
bux_sb_append(newSb, value);
|
|
||||||
SB_AppendChar(newSb, 10);
|
|
||||||
self.count = self.count + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
bux_sb_free(self.sb);
|
var newSb: *void = bux_sb_new(4096);
|
||||||
self.sb = newSb;
|
var i: uint = 0;
|
||||||
}
|
var delCount: uint = 0;
|
||||||
|
while i < lineCount {
|
||||||
func DB_Del(self: *Database, key: String) {
|
let line: String = bux_str_split_part(raw, "\n", i);
|
||||||
let raw: String = bux_sb_build(self.sb);
|
if bux_strlen(line) > 0 {
|
||||||
let lineCount: uint = bux_str_split_count(raw, "\n");
|
if String_StartsWith(line, key) {
|
||||||
|
let eqPos: String = String_Find(line, "=");
|
||||||
var newSb: *void = bux_sb_new(4096);
|
if String_Len(eqPos) > 0 {
|
||||||
var i: uint = 0;
|
delCount = delCount + 1;
|
||||||
var delCount: uint = 0;
|
} else {
|
||||||
while i < lineCount {
|
bux_sb_append(newSb, line);
|
||||||
let line: String = bux_str_split_part(raw, "\n", i);
|
SB_AppendChar(newSb, 10);
|
||||||
if bux_strlen(line) > 0 {
|
}
|
||||||
if String_StartsWith(line, key) {
|
|
||||||
let eqPos: String = String_Find(line, "=");
|
|
||||||
if String_Len(eqPos) > 0 {
|
|
||||||
delCount = delCount + 1;
|
|
||||||
} else {
|
} else {
|
||||||
bux_sb_append(newSb, line);
|
bux_sb_append(newSb, line);
|
||||||
SB_AppendChar(newSb, 10);
|
SB_AppendChar(newSb, 10);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if delCount > 0 {
|
||||||
|
self.count = self.count - delCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
bux_sb_free(self.sb);
|
||||||
|
self.sb = newSb;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return self.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
func DB_Keys(self: *Database) -> *String {
|
||||||
|
if self.count == 0 { return null as *String; }
|
||||||
|
|
||||||
|
let raw: String = bux_sb_build(self.sb);
|
||||||
|
let lineCount: uint = bux_str_split_count(raw, "\n");
|
||||||
|
let keys: *String = bux_alloc(self.count * sizeof(String)) as *String;
|
||||||
|
|
||||||
|
var found: uint = 0;
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < lineCount && found < self.count {
|
||||||
|
let line: String = bux_str_split_part(raw, "\n", i);
|
||||||
|
if bux_strlen(line) > 0 {
|
||||||
|
let eqPos: String = String_Find(line, "=");
|
||||||
|
if String_Len(eqPos) > 0 {
|
||||||
|
let keyLen: uint = String_Offset(eqPos, line);
|
||||||
|
keys[found] = bux_str_slice(line, 0, keyLen);
|
||||||
|
found = found + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
PrintLine("SimpleDB — file-backed key-value database");
|
||||||
|
PrintLine("Usage:");
|
||||||
|
PrintLine(" simpledb <dbfile> set <key> <value>");
|
||||||
|
PrintLine(" simpledb <dbfile> get <key>");
|
||||||
|
PrintLine(" simpledb <dbfile> del <key>");
|
||||||
|
PrintLine(" simpledb <dbfile> has <key>");
|
||||||
|
PrintLine(" simpledb <dbfile> keys");
|
||||||
|
PrintLine(" simpledb <dbfile> count");
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
let argc: int = Os_ArgsCount();
|
||||||
|
|
||||||
|
if argc < 3 {
|
||||||
|
PrintUsage();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dbFile: String = Os_Args(1);
|
||||||
|
let cmd: String = Os_Args(2);
|
||||||
|
|
||||||
|
var db: Database = DB_New(dbFile);
|
||||||
|
|
||||||
|
if String_Eq(cmd, "get") {
|
||||||
|
DB_Load(&db);
|
||||||
|
if argc < 4 {
|
||||||
|
PrintLine("get: missing key");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
let key: String = Os_Args(3);
|
||||||
|
let val: String = DB_Get(&db, key);
|
||||||
|
if bux_strlen(val) > 0 {
|
||||||
|
PrintLine(val);
|
||||||
} else {
|
} else {
|
||||||
bux_sb_append(newSb, line);
|
Print("(not found) ");
|
||||||
SB_AppendChar(newSb, 10);
|
PrintLine(key);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
i = i + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if delCount > 0 {
|
|
||||||
self.count = self.count - delCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
bux_sb_free(self.sb);
|
|
||||||
self.sb = newSb;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
return self.count;
|
|
||||||
}
|
|
||||||
|
|
||||||
func DB_Keys(self: *Database) -> *String {
|
|
||||||
if self.count == 0 { return null as *String; }
|
|
||||||
|
|
||||||
let raw: String = bux_sb_build(self.sb);
|
|
||||||
let lineCount: uint = bux_str_split_count(raw, "\n");
|
|
||||||
let keys: *String = bux_alloc(self.count * sizeof(String)) as *String;
|
|
||||||
|
|
||||||
var found: uint = 0;
|
|
||||||
var i: uint = 0;
|
|
||||||
while i < lineCount && found < self.count {
|
|
||||||
let line: String = bux_str_split_part(raw, "\n", i);
|
|
||||||
if bux_strlen(line) > 0 {
|
|
||||||
let eqPos: String = String_Find(line, "=");
|
|
||||||
if String_Len(eqPos) > 0 {
|
|
||||||
let keyLen: uint = String_Offset(eqPos, line);
|
|
||||||
keys[found] = bux_str_slice(line, 0, keyLen);
|
|
||||||
found = found + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i = i + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
PrintLine("SimpleDB — file-backed key-value database");
|
|
||||||
PrintLine("Usage:");
|
|
||||||
PrintLine(" simpledb <dbfile> set <key> <value>");
|
|
||||||
PrintLine(" simpledb <dbfile> get <key>");
|
|
||||||
PrintLine(" simpledb <dbfile> del <key>");
|
|
||||||
PrintLine(" simpledb <dbfile> has <key>");
|
|
||||||
PrintLine(" simpledb <dbfile> keys");
|
|
||||||
PrintLine(" simpledb <dbfile> count");
|
|
||||||
}
|
|
||||||
|
|
||||||
func Main() -> int {
|
|
||||||
let argc: int = Os_ArgsCount();
|
|
||||||
|
|
||||||
if argc < 3 {
|
|
||||||
PrintUsage();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let dbFile: String = Os_Args(1);
|
|
||||||
let cmd: String = Os_Args(2);
|
|
||||||
|
|
||||||
var db: Database = DB_New(dbFile);
|
|
||||||
|
|
||||||
if String_Eq(cmd, "get") {
|
|
||||||
DB_Load(&db);
|
|
||||||
if argc < 4 {
|
|
||||||
PrintLine("get: missing key");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
let key: String = Os_Args(3);
|
|
||||||
let val: String = DB_Get(&db, key);
|
|
||||||
if bux_strlen(val) > 0 {
|
|
||||||
PrintLine(val);
|
|
||||||
} else {
|
|
||||||
Print("(not found) ");
|
|
||||||
PrintLine(key);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if String_Eq(cmd, "set") {
|
|
||||||
DB_Load(&db);
|
|
||||||
if argc < 5 {
|
|
||||||
PrintLine("set: missing key or value");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
let key: String = Os_Args(3);
|
|
||||||
let val: String = Os_Args(4);
|
|
||||||
DB_Set(&db, key, val);
|
|
||||||
let saved: bool = DB_Save(&db);
|
|
||||||
if saved {
|
|
||||||
Print("OK ");
|
|
||||||
Print(key);
|
|
||||||
Print(" = ");
|
|
||||||
PrintLine(val);
|
|
||||||
} else {
|
|
||||||
PrintLine("set: save failed");
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if String_Eq(cmd, "del") {
|
|
||||||
DB_Load(&db);
|
|
||||||
if argc < 4 {
|
|
||||||
PrintLine("del: missing key");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
let key: String = Os_Args(3);
|
|
||||||
DB_Del(&db, key);
|
|
||||||
DB_Save(&db);
|
|
||||||
Print("DEL ");
|
|
||||||
PrintLine(key);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if String_Eq(cmd, "has") {
|
|
||||||
DB_Load(&db);
|
|
||||||
if argc < 4 {
|
|
||||||
PrintLine("has: missing key");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
let key: String = Os_Args(3);
|
|
||||||
let found: bool = DB_Has(&db, key);
|
|
||||||
if found {
|
|
||||||
Print("true ");
|
|
||||||
PrintLine(key);
|
|
||||||
} else {
|
|
||||||
Print("false ");
|
|
||||||
PrintLine(key);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if String_Eq(cmd, "keys") {
|
|
||||||
DB_Load(&db);
|
|
||||||
let n: uint = DB_Count(&db);
|
|
||||||
Print("keys: ");
|
|
||||||
PrintInt(n as int);
|
|
||||||
PrintLine("");
|
|
||||||
|
|
||||||
if n == 0 {
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let list: *String = DB_Keys(&db);
|
if String_Eq(cmd, "set") {
|
||||||
var i: uint = 0;
|
DB_Load(&db);
|
||||||
while i < n {
|
if argc < 5 {
|
||||||
Print(" ");
|
PrintLine("set: missing key or value");
|
||||||
PrintLine(list[i]);
|
return 1;
|
||||||
i = i + 1;
|
}
|
||||||
|
let key: String = Os_Args(3);
|
||||||
|
let val: String = Os_Args(4);
|
||||||
|
DB_Set(&db, key, val);
|
||||||
|
let saved: bool = DB_Save(&db);
|
||||||
|
if saved {
|
||||||
|
Print("OK ");
|
||||||
|
Print(key);
|
||||||
|
Print(" = ");
|
||||||
|
PrintLine(val);
|
||||||
|
} else {
|
||||||
|
PrintLine("set: save failed");
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
bux_free(list as *void);
|
|
||||||
return 0;
|
if String_Eq(cmd, "del") {
|
||||||
|
DB_Load(&db);
|
||||||
|
if argc < 4 {
|
||||||
|
PrintLine("del: missing key");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
let key: String = Os_Args(3);
|
||||||
|
DB_Del(&db, key);
|
||||||
|
DB_Save(&db);
|
||||||
|
Print("DEL ");
|
||||||
|
PrintLine(key);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if String_Eq(cmd, "has") {
|
||||||
|
DB_Load(&db);
|
||||||
|
if argc < 4 {
|
||||||
|
PrintLine("has: missing key");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
let key: String = Os_Args(3);
|
||||||
|
let found: bool = DB_Has(&db, key);
|
||||||
|
if found {
|
||||||
|
Print("true ");
|
||||||
|
PrintLine(key);
|
||||||
|
} else {
|
||||||
|
Print("false ");
|
||||||
|
PrintLine(key);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if String_Eq(cmd, "keys") {
|
||||||
|
DB_Load(&db);
|
||||||
|
let n: uint = DB_Count(&db);
|
||||||
|
Print("keys: ");
|
||||||
|
PrintInt(n as int);
|
||||||
|
PrintLine("");
|
||||||
|
|
||||||
|
if n == 0 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let list: *String = DB_Keys(&db);
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < n {
|
||||||
|
Print(" ");
|
||||||
|
PrintLine(list[i]);
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
bux_free(list as *void);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if String_Eq(cmd, "count") {
|
||||||
|
DB_Load(&db);
|
||||||
|
let n: uint = DB_Count(&db);
|
||||||
|
Print("count: ");
|
||||||
|
PrintInt(n as int);
|
||||||
|
PrintLine("");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Print("unknown command: ");
|
||||||
|
PrintLine(cmd);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if String_Eq(cmd, "count") {
|
|
||||||
DB_Load(&db);
|
|
||||||
let n: uint = DB_Count(&db);
|
|
||||||
Print("count: ");
|
|
||||||
PrintInt(n as int);
|
|
||||||
PrintLine("");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Print("unknown command: ");
|
|
||||||
PrintLine(cmd);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+334
-26
@@ -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 lexer, parser, ast, sema, manifest, hir_lower, lir_lower, lir_c_backend
|
||||||
import source_location
|
import source_location
|
||||||
|
import fmt
|
||||||
|
import docgen
|
||||||
|
import registry
|
||||||
|
|
||||||
type
|
type
|
||||||
ColorMode* = enum
|
ColorMode* = enum
|
||||||
@@ -21,16 +24,25 @@ Usage: bux [options] <command> [command-options]
|
|||||||
Commands:
|
Commands:
|
||||||
new <name> Create a new Bux package
|
new <name> Create a new Bux package
|
||||||
init Initialize a Bux package in the current directory
|
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
|
install Resolve and install dependencies
|
||||||
|
search [query] Search the package registry
|
||||||
build Build the current package
|
build Build the current package
|
||||||
run Build and run the current package
|
run Build and run the current package
|
||||||
test Run tests in tests/ directory
|
test Run tests in tests/ directory
|
||||||
check Type-check the current package
|
check Type-check the current package
|
||||||
|
fmt [path] Format .bux sources (default: .)
|
||||||
|
doc [path] Generate Markdown API docs from /// comments
|
||||||
clean Remove build artifacts
|
clean Remove build artifacts
|
||||||
help Show this help message
|
help Show this help message
|
||||||
version Show version
|
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:
|
Global options:
|
||||||
--color <auto|on|off> Control colored output (default: auto)
|
--color <auto|on|off> Control colored output (default: auto)
|
||||||
-q, --quiet Suppress non-error output
|
-q, --quiet Suppress non-error output
|
||||||
@@ -192,6 +204,14 @@ proc hintForMessage(msg: string): string =
|
|||||||
return "provide the missing argument (positional or named)"
|
return "provide the missing argument (positional or named)"
|
||||||
if "use of moved value" in m:
|
if "use of moved value" in m:
|
||||||
return "the value was moved; clone it or restructure ownership"
|
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:
|
if "shared reference" in m or "checked function" in m:
|
||||||
return "use '&mut T' for mutation, or drop @[Checked] for unchecked code"
|
return "use '&mut T' for mutation, or drop @[Checked] for unchecked code"
|
||||||
if "double mutable borrow" in m or "already mutably borrowed" in m:
|
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)
|
printError("--git requires a value", useColor)
|
||||||
return 1
|
return 1
|
||||||
else:
|
else:
|
||||||
version = args[i]
|
if not args[i].startsWith("-"):
|
||||||
|
version = args[i]
|
||||||
|
else:
|
||||||
|
printError(&"unknown add option '{args[i]}'", useColor)
|
||||||
|
return 1
|
||||||
inc i
|
inc i
|
||||||
# Append to bux.toml
|
# Append to bux.toml
|
||||||
var depLine = ""
|
var depLine = ""
|
||||||
@@ -387,7 +411,19 @@ proc cmdAdd*(args: seq[string], opts: GlobalOptions): int =
|
|||||||
elif gitUrl.len > 0:
|
elif gitUrl.len > 0:
|
||||||
depLine = &"{depName} = {{ Version = \"{version}\", Source = \"{gitUrl}\" }}"
|
depLine = &"{depName} = {{ Version = \"{version}\", Source = \"{gitUrl}\" }}"
|
||||||
else:
|
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)
|
var content = readFile(manifestPath)
|
||||||
# Ensure [Dependencies] section exists
|
# Ensure [Dependencies] section exists
|
||||||
if content.find("[Dependencies]") < 0:
|
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)
|
printInfo(&"Added dependency '{depName}' to bux.toml", useColor)
|
||||||
return 0
|
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 =
|
proc cmdInstall*(args: seq[string], opts: GlobalOptions): int =
|
||||||
let useColor = shouldUseColor(opts)
|
let useColor = shouldUseColor(opts)
|
||||||
let root = getCurrentDir()
|
let root = getCurrentDir()
|
||||||
@@ -411,6 +475,7 @@ proc cmdInstall*(args: seq[string], opts: GlobalOptions): int =
|
|||||||
let cacheDir = getHomeDir() / ".bux" / "packages"
|
let cacheDir = getHomeDir() / ".bux" / "packages"
|
||||||
if not dirExists(cacheDir):
|
if not dirExists(cacheDir):
|
||||||
createDir(cacheDir)
|
createDir(cacheDir)
|
||||||
|
let reg = loadRegistry()
|
||||||
# Resolve each dependency
|
# Resolve each dependency
|
||||||
for dep in man.dependencies:
|
for dep in man.dependencies:
|
||||||
case dep.kind
|
case dep.kind
|
||||||
@@ -433,20 +498,43 @@ proc cmdInstall*(args: seq[string], opts: GlobalOptions): int =
|
|||||||
if not dirExists(depDir):
|
if not dirExists(depDir):
|
||||||
if not opts.quiet:
|
if not opts.quiet:
|
||||||
printInfo(&"Cloning '{dep.name}' from {dep.gitUrl}...", useColor)
|
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:
|
if code != 0:
|
||||||
printError(&"failed to clone {dep.gitUrl}: {outp}", useColor)
|
printError(&"failed to clone {dep.gitUrl}: {outp}", useColor)
|
||||||
return 1
|
return 1
|
||||||
else:
|
else:
|
||||||
if not opts.quiet:
|
if not opts.quiet:
|
||||||
printInfo(&"Using cached '{dep.name}' from {depDir}", useColor)
|
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))
|
lock.entries.add(LockEntry(name: dep.name, version: dep.gitVersion, source: dep.gitUrl))
|
||||||
of dkVersion:
|
of dkVersion:
|
||||||
# For version-based deps without a registry, we just record them
|
# Registry lookup (E.1)
|
||||||
# TODO: lookup in registry
|
if reg.path.len == 0:
|
||||||
lock.entries.add(LockEntry(name: dep.name, version: dep.versionReq, source: "registry"))
|
printError(&"cannot resolve '{dep.name}': no package registry (set BUX_REGISTRY)", useColor)
|
||||||
if not opts.quiet:
|
return 1
|
||||||
printInfo(&"Recorded dependency '{dep.name}' = {dep.versionReq}", useColor)
|
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(&"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
|
# Save lockfile
|
||||||
let lockPath = root / "bux.lock"
|
let lockPath = root / "bux.lock"
|
||||||
saveLockfile(lockPath, lock)
|
saveLockfile(lockPath, lock)
|
||||||
@@ -722,18 +810,93 @@ proc cmdClean*(args: seq[string], opts: GlobalOptions): int =
|
|||||||
printInfo("clean: build directory removed", useColor)
|
printInfo("clean: build directory removed", useColor)
|
||||||
return 0
|
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 =
|
proc cmdTest*(args: seq[string], opts: GlobalOptions): int =
|
||||||
let useColor = shouldUseColor(opts)
|
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"
|
let testsDir = root / "tests"
|
||||||
var testFiles: seq[string] = @[]
|
var testFiles: seq[string] = @[]
|
||||||
if dirExists(testsDir):
|
if dirExists(testsDir):
|
||||||
for kind, path in walkDir(testsDir):
|
for kind, path in walkDir(testsDir):
|
||||||
if kind == pcFile and path.endsWith(".bux"):
|
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.add(path)
|
||||||
|
testFiles.sort(system.cmp)
|
||||||
if testFiles.len == 0:
|
if testFiles.len == 0:
|
||||||
printError("no tests found in tests/ directory", useColor)
|
if filter.len > 0:
|
||||||
|
printError(&"no tests matching filter '{filter}' in tests/", useColor)
|
||||||
|
else:
|
||||||
|
printError("no tests found in tests/ directory", useColor)
|
||||||
return 1
|
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 passed = 0
|
||||||
var failed = 0
|
var failed = 0
|
||||||
for testFile in testFiles:
|
for testFile in testFiles:
|
||||||
@@ -742,25 +905,167 @@ proc cmdTest*(args: seq[string], opts: GlobalOptions): int =
|
|||||||
removeDir(tmpDir)
|
removeDir(tmpDir)
|
||||||
createDir(tmpDir / "src")
|
createDir(tmpDir / "src")
|
||||||
copyFile(testFile, tmpDir / "src" / "Main.bux")
|
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)
|
let buildRes = cmdBuild(@[tmpDir], opts)
|
||||||
|
var status: string
|
||||||
|
var statusOk = false
|
||||||
if buildRes != 0:
|
if buildRes != 0:
|
||||||
printError(&" FAIL {testName} (build)", useColor)
|
status = "FAIL"
|
||||||
|
failed += 1
|
||||||
|
else:
|
||||||
|
var execFile = tmpDir / "build" / testName
|
||||||
|
if not fileExists(execFile):
|
||||||
|
execFile = tmpDir / "build" / "bux_out"
|
||||||
|
let exitCode = execCmd(execFile)
|
||||||
|
if exitCode == 0:
|
||||||
|
status = "PASS"
|
||||||
|
statusOk = true
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
status = &"FAIL:{exitCode}"
|
||||||
|
failed += 1
|
||||||
|
removeDir(tmpDir)
|
||||||
|
|
||||||
|
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
|
failed += 1
|
||||||
continue
|
continue
|
||||||
var execFile = tmpDir / "build" / testName
|
if didChange:
|
||||||
if not fileExists(execFile):
|
changed += 1
|
||||||
execFile = tmpDir / "build" / "bux_out"
|
if not opts.quiet:
|
||||||
let exitCode = execCmd(execFile)
|
if checkOnly:
|
||||||
if exitCode == 0:
|
printError(&" would reformat {path}", useColor)
|
||||||
printInfo(&" PASS {testName}", useColor)
|
else:
|
||||||
passed += 1
|
printInfo(&" formatted {path}", useColor)
|
||||||
else:
|
else:
|
||||||
printError(&" FAIL {testName} (exit {exitCode})", useColor)
|
unchanged += 1
|
||||||
failed += 1
|
if opts.verbose and not opts.quiet:
|
||||||
removeDir(tmpDir)
|
echo &" ok {path}"
|
||||||
echo &"\nResults: {passed} passed, {failed} failed"
|
|
||||||
return if failed > 0: 1 else: 0
|
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 =
|
proc cmdVersion*(args: seq[string], opts: GlobalOptions): int =
|
||||||
echo "bux 0.1.0 (bootstrap)"
|
echo "bux 0.1.0 (bootstrap)"
|
||||||
@@ -782,10 +1087,13 @@ proc runCli*(args: seq[string]): int =
|
|||||||
of "init": return cmdInit(cmdArgs, opts)
|
of "init": return cmdInit(cmdArgs, opts)
|
||||||
of "add": return cmdAdd(cmdArgs, opts)
|
of "add": return cmdAdd(cmdArgs, opts)
|
||||||
of "install": return cmdInstall(cmdArgs, opts)
|
of "install": return cmdInstall(cmdArgs, opts)
|
||||||
|
of "search": return cmdSearch(cmdArgs, opts)
|
||||||
of "build": return cmdBuild(cmdArgs, opts)
|
of "build": return cmdBuild(cmdArgs, opts)
|
||||||
of "run": return cmdRun(cmdArgs, opts)
|
of "run": return cmdRun(cmdArgs, opts)
|
||||||
of "check": return cmdCheck(cmdArgs, opts)
|
of "check": return cmdCheck(cmdArgs, opts)
|
||||||
of "test": return cmdTest(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 "clean": return cmdClean(cmdArgs, opts)
|
||||||
of "version", "--version", "-v": return cmdVersion(cmdArgs, opts)
|
of "version", "--version", "-v": return cmdVersion(cmdArgs, opts)
|
||||||
of "help", "--help", "-h":
|
of "help", "--help", "-h":
|
||||||
|
|||||||
@@ -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))
|
||||||
@@ -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)
|
||||||
@@ -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
@@ -51,6 +51,11 @@ type
|
|||||||
## When true, ekIdent skips use-while-borrowed (we're forming `&x` itself)
|
## When true, ekIdent skips use-while-borrowed (we're forming `&x` itself)
|
||||||
suppressUseWhileBorrow*: bool
|
suppressUseWhileBorrow*: bool
|
||||||
currentRetType*: Type ## return type of the function being checked
|
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
|
closureDepth*: int ## nesting depth inside closures
|
||||||
currentClosureExpr*: Expr ## current closure being analyzed
|
currentClosureExpr*: Expr ## current closure being analyzed
|
||||||
closureScope*: Scope ## scope at which the current closure was entered
|
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:
|
elif sema.activeSharedBorrows.getOrDefault(varName, 0) > 0:
|
||||||
sema.emitError(loc, &"cannot mutably borrow '{varName}' while it is shared-borrowed")
|
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
|
# Generic type inference helpers
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -472,7 +609,7 @@ proc inferTypeArgs(sema: var Sema, funcDecl: Decl, argTypes: seq[Type],
|
|||||||
# Type resolution from AST TypeExpr
|
# Type resolution from AST TypeExpr
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
proc resolveType(sema: var Sema, te: TypeExpr): Type =
|
proc resolveType*(sema: var Sema, te: TypeExpr): Type =
|
||||||
if te == nil:
|
if te == nil:
|
||||||
return makeUnknown()
|
return makeUnknown()
|
||||||
case te.kind
|
case te.kind
|
||||||
@@ -918,7 +1055,7 @@ proc collectGlobals*(sema: var Sema) =
|
|||||||
# Expression type checking
|
# 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 checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type
|
||||||
|
|
||||||
proc typeImplements(sema: Sema, t: Type, interfaceName: string): bool =
|
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.exprCallArgs = newArgs
|
||||||
expr.exprCallArgNames = newNames
|
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:
|
if expr == nil:
|
||||||
return makeUnknown()
|
return makeUnknown()
|
||||||
case expr.kind
|
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
|
# Untyped let + `&x` is typed as &mut by unary lowering
|
||||||
isMut = initType.isMutRef
|
isMut = initType.isMutRef
|
||||||
sema.checkCreateBorrow(bname, isMut, stmt.stmtLetInit.loc)
|
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()
|
return makeVoid()
|
||||||
of skIf:
|
of skIf:
|
||||||
let condType = sema.checkExpr(stmt.stmtIfCond, scope)
|
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)
|
let retSym = scope.lookup(stmt.stmtReturnValue.exprIdent)
|
||||||
if retSym != nil and retSym.isOwn:
|
if retSym != nil and retSym.isOwn:
|
||||||
sema.movedVars.add(stmt.stmtReturnValue.exprIdent)
|
sema.movedVars.add(stmt.stmtReturnValue.exprIdent)
|
||||||
|
# Lifetime: reject dangling returns / explicit mismatches
|
||||||
|
sema.checkReturnLifetime(stmt.stmtReturnValue, scope, stmt.loc)
|
||||||
return makeVoid()
|
return makeVoid()
|
||||||
of skBreak, skContinue:
|
of skBreak, skContinue:
|
||||||
return makeVoid()
|
return makeVoid()
|
||||||
@@ -1951,9 +2095,15 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type =
|
|||||||
proc checkFunc(sema: var Sema, decl: Decl) =
|
proc checkFunc(sema: var Sema, decl: Decl) =
|
||||||
if decl.declFuncBody == nil:
|
if decl.declFuncBody == nil:
|
||||||
return
|
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.
|
# 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
|
return
|
||||||
let wasChecked = sema.checkedFunc
|
let wasChecked = sema.checkedFunc
|
||||||
let wasAsync = sema.currentFuncIsAsync
|
let wasAsync = sema.currentFuncIsAsync
|
||||||
@@ -1963,10 +2113,18 @@ proc checkFunc(sema: var Sema, decl: Decl) =
|
|||||||
sema.movedVars = @[]
|
sema.movedVars = @[]
|
||||||
sema.activeMutBorrows = initTable[string, SourceLocation]()
|
sema.activeMutBorrows = initTable[string, SourceLocation]()
|
||||||
sema.activeSharedBorrows = initTable[string, int]()
|
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)
|
var funcScope = newScope(sema.globalScope)
|
||||||
# Add type parameters to type table for resolution
|
# Add type parameters to type table for resolution
|
||||||
var addedTypeParams: seq[string] = @[]
|
var addedTypeParams: seq[string] = @[]
|
||||||
for tp in decl.declFuncTypeParams:
|
for tp in decl.declFuncTypeParams:
|
||||||
|
if tp.isLifetime:
|
||||||
|
# Lifetime params are not types; skip typeTable
|
||||||
|
continue
|
||||||
sema.typeTable[tp.name] = makeTypeParam(tp.name)
|
sema.typeTable[tp.name] = makeTypeParam(tp.name)
|
||||||
addedTypeParams.add(tp.name)
|
addedTypeParams.add(tp.name)
|
||||||
# Add parameters
|
# Add parameters
|
||||||
@@ -1982,6 +2140,8 @@ proc checkFunc(sema: var Sema, decl: Decl) =
|
|||||||
sema.typeTable.del(tp)
|
sema.typeTable.del(tp)
|
||||||
sema.checkedFunc = wasChecked
|
sema.checkedFunc = wasChecked
|
||||||
sema.currentFuncIsAsync = wasAsync
|
sema.currentFuncIsAsync = wasAsync
|
||||||
|
sema.varRefLifetime = initTable[string, string]()
|
||||||
|
sema.returnLifetime = ""
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Second pass: check all function bodies
|
# Second pass: check all function bodies
|
||||||
@@ -2026,3 +2186,16 @@ proc analyzeFull*(modu: Module): tuple[result: SemaResult, sema: Sema] =
|
|||||||
sema.collectGlobals()
|
sema.collectGlobals()
|
||||||
sema.checkBodies()
|
sema.checkBodies()
|
||||||
result = (SemaResult(diagnostics: sema.diagnostics), sema)
|
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
|
||||||
|
|||||||
@@ -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
@@ -152,15 +152,60 @@ This runs:
|
|||||||
|
|
||||||
### Project Tests (`bux test`)
|
### Project Tests (`bux test`)
|
||||||
```bash
|
```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:
|
Discovers `tests/*.bux`, builds each as a temp package, and runs it. Prints a
|
||||||
- `Tests passed` on exit code 0
|
summary table and exits:
|
||||||
- `Tests failed (exit code N)` on non-zero exit
|
- `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.
|
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
|
### Example Programs
|
||||||
```bash
|
```bash
|
||||||
make test-examples
|
make test-examples
|
||||||
|
|||||||
@@ -604,6 +604,44 @@ Moves happen in three contexts:
|
|||||||
msg = "reassigned"; // OK: reinitialization
|
msg = "reassigned"; // OK: reinitialization
|
||||||
PrintLine(msg);
|
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+76
-28
@@ -1,6 +1,8 @@
|
|||||||
# Bux Package Manager
|
# 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"
|
Output = "Bin"
|
||||||
|
|
||||||
[Dependencies]
|
[Dependencies]
|
||||||
Std = "1.0"
|
greet = { Path = "/abs/path/to/greet" }
|
||||||
Json = { Version = "2.1", Source = "https://github.com/bux-lang/json" }
|
Json = { Version = "2.1", Source = "https://github.com/bux-lang/json" }
|
||||||
Utils = { Path = "../Utils" }
|
Utils = { Path = "../Utils" }
|
||||||
|
# Registry name-only (resolved by `bux add` / `bux install`):
|
||||||
|
# greet = "0.1.1"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Dependency Forms
|
### Dependency Forms
|
||||||
|
|
||||||
| Form | Example | Description |
|
| Form | Example | Description |
|
||||||
|------|---------|-------------|
|
|------|---------|-------------|
|
||||||
| Version string | `Std = "1.0"` | Registry dependency |
|
| Version string | `greet = "0.1.1"` | Registry dependency |
|
||||||
| Wildcard | `Std = "*"` | Latest version |
|
| Wildcard | `greet = "*"` | Latest registry version |
|
||||||
| Inline table (git) | `{ Version = "1.4", Source = "https://..." }` | Git URL + version |
|
| Inline table (git) | `{ Version = "1.4", Source = "https://..." }` | Git URL + version |
|
||||||
| Inline table (path) | `{ Path = "../Lib" }` | Local path dependency |
|
| 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
|
## CLI Commands
|
||||||
|
|
||||||
### `bux add <name> [version]`
|
### `bux add <name> [version]`
|
||||||
|
|
||||||
Add a dependency to `bux.toml`.
|
Add a dependency to `bux.toml` (registry / `--path` / `--git`).
|
||||||
|
|
||||||
```bash
|
### `bux search [query]`
|
||||||
# Add registry dependency
|
|
||||||
bux add json "2.1"
|
|
||||||
|
|
||||||
# Add path-based dependency
|
List packages in the active registry (filter by name/description).
|
||||||
bux add utils --path "../utils"
|
|
||||||
|
|
||||||
# Add git dependency
|
|
||||||
bux add network --git "https://github.com/bux-lang/network"
|
|
||||||
```
|
|
||||||
|
|
||||||
### `bux install`
|
### `bux install`
|
||||||
|
|
||||||
Resolve dependencies and generate `bux.lock`.
|
Resolve dependencies and generate `bux.lock`.
|
||||||
|
|
||||||
```bash
|
|
||||||
bux install
|
|
||||||
```
|
|
||||||
|
|
||||||
What it does:
|
What it does:
|
||||||
1. Reads `[Dependencies]` from `bux.toml`
|
1. Reads `[Dependencies]` from `bux.toml`
|
||||||
2. Resolves path-based deps (verifies directory exists)
|
2. Resolves path-based deps (verifies directory exists)
|
||||||
3. Clones/pulls git-based deps to `~/.bux/packages/<name>/`
|
3. Clones git-based deps to `~/.bux/packages/<name>/`
|
||||||
4. Generates `bux.lock` with exact versions and sources
|
4. Resolves bare version names via the registry index
|
||||||
|
5. Generates `bux.lock` with exact versions and sources
|
||||||
|
|
||||||
### `bux build` / `bux run`
|
### `bux build` / `bux run`
|
||||||
|
|
||||||
@@ -84,10 +131,9 @@ Auto-generated. **Do not edit manually.**
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[[Package]]
|
[[Package]]
|
||||||
Name = "json"
|
Name = "greet"
|
||||||
Version = "2.1.3"
|
Version = "0.1.1"
|
||||||
Source = "https://github.com/bux-lang/json"
|
Source = "/home/user/z-git/bux/bux/registry/packages/greet"
|
||||||
Checksum = "8dcb2a7f..."
|
|
||||||
|
|
||||||
[[Package]]
|
[[Package]]
|
||||||
Name = "utils"
|
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
|
1. **Path-based** deps are resolved relative to the manifest directory
|
||||||
2. **Git-based** deps are cloned to `~/.bux/packages/<name>/`
|
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
|
4. Dependencies are loaded from `<dep>/src/*.bux` at build time
|
||||||
5. Later declarations shadow earlier ones (project > deps > stdlib)
|
5. Later declarations shadow earlier ones (project > deps > stdlib)
|
||||||
|
|
||||||
@@ -114,8 +160,10 @@ The lockfile ensures **reproducible builds** — every developer gets the exact
|
|||||||
```bash
|
```bash
|
||||||
bux new mylib
|
bux new mylib
|
||||||
cd mylib
|
cd mylib
|
||||||
# Edit src/Main.bux → module MyLib { pub func Add(...) }
|
# Edit src/*.bux → module MyLib { func Add(...) }
|
||||||
bux build # Builds as library (Type = "lib")
|
# Set Type = "lib" in bux.toml
|
||||||
|
# Register in your registry.toml with source = "file:..."
|
||||||
|
bux build
|
||||||
```
|
```
|
||||||
|
|
||||||
## Example: Using a Library
|
## Example: Using a Library
|
||||||
|
|||||||
+131
-23
@@ -1,7 +1,7 @@
|
|||||||
# Bux — План към „добър“ език (v0.5 → v1.0)
|
# Bux — План към „добър“ език (v0.5 → v1.0)
|
||||||
|
|
||||||
> **Дата:** 2026-07-18
|
> **Дата:** 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.
|
> **Цел:** Език, с който се пишат реални проекти комфортно, безопасно (по избор) и с надежден toolchain.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -14,11 +14,11 @@
|
|||||||
| Sema / generics | Monomorphization, trait bounds basic | ★★★★☆ |
|
| Sema / generics | Monomorphization, trait bounds basic | ★★★★☆ |
|
||||||
| HIR → C | Tuples + fat `func` ABI в bootstrap **и** selfhost | ★★★★☆ |
|
| HIR → C | Tuples + fat `func` ABI в bootstrap **и** selfhost | ★★★★☆ |
|
||||||
| Selfhost (`src/`) | ~12k LOC, binary-identical loop, closures+tuples | ★★★★★ |
|
| 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 | ★★★★☆ |
|
| Concurrency | M:N tasks + channels + async | ★★★★☆ |
|
||||||
| Stdlib | Array/Map/Set/String/Iter HOF разширени | ★★★★☆ |
|
| Stdlib | Array/Map/Set/String/Iter HOF разширени | ★★★★☆ |
|
||||||
| Tooling | `test-errors`, LSP diagnostics + hover/def/outline | ★★★★☆ |
|
| 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) | ★★★★☆ |
|
| Документация | README + QUALITY_PLAN синхронизирани (2026-07-15) | ★★★★☆ |
|
||||||
|
|
||||||
**Силна ниша:** gradual ownership (C-скорост на писане + opt-in Rust-safety).
|
**Силна ниша:** 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.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.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) |
|
| 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.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 | ⏳ |
|
| 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 | ⏳ partial (`bux test` exists) |
|
| D.3 | `bux test` с `--filter`, exit codes, summary table | CI-friendly | ✅ `--filter` / summary / exit 0\|1 |
|
||||||
| D.4 | `bux doc` от `///` comments | Самодокументиращ се stdlib | ⏳ |
|
| D.4 | `bux doc` от `///` comments | Самодокументиращ се stdlib | ✅ bootstrap+selfhost + `make docs` |
|
||||||
| D.5 | Golden tests за stdlib modules | Регресии без изненади | ⏳ |
|
| D.5 | Golden tests за stdlib modules | Регресии без изненади | ✅ `tests/stdlib_golden/` + `make test-stdlib` |
|
||||||
|
|
||||||
### E — Ecosystem & v1.0 (P2)
|
### E — Ecosystem & v1.0 (P2)
|
||||||
|
|
||||||
| # | Задача | Защо |
|
| # | Задача | Защо | Статус |
|
||||||
|---|--------|------|
|
|---|--------|------|--------|
|
||||||
| E.1 | Package registry protocol (git/HTTP) | `bux add foo` без path hacks |
|
| E.1 | Package registry protocol (git/HTTP) | `bux add foo` без path hacks | ✅ local index + file/git sources + `search` |
|
||||||
| E.2 | 3–5 production-quality apps в `apps/` | Showcase |
|
| E.2 | 3–5 production-quality apps в `apps/` | Showcase | ⏳ partial (`nexus`, `boko`, `simpledb`, `jwt-pitbul`) |
|
||||||
| E.3 | Language freeze + semver policy | Trust |
|
| E.3 | Language freeze + semver policy | Trust | ✅ draft `docs/SEMVER.md` |
|
||||||
| E.4 | Debugger/DWARF basics | Systems audience |
|
| E.4 | Debugger/DWARF basics | Systems audience | ⏳ |
|
||||||
| E.5 | Benchmarks vs C/Zig/Nim (micro + nexus) | Marketing + regression |
|
| 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
|
- [ ] Всички examples + selfhost-loop + 3 apps минават на CI
|
||||||
- [ ] Array/Map/String/Test API покрива 90% от ежедневните нужди
|
- [ ] Array/Map/String/Test API покрива 90% от ежедневните нужди
|
||||||
- [ ] `@[Checked]` хваща use-after-move + double `&mut` в documented subset
|
- [x] `@[Checked]` хваща use-after-move + double `&mut` + dangling return / elision fail
|
||||||
- [ ] `bux test` + `bux fmt` + `bux check` са default developer loop
|
- [x] `bux test` + `bux fmt` + `bux check` са default developer loop (`--filter` / `--check` shipped)
|
||||||
- [ ] LanguageRef синхронизиран с компилатора
|
- [x] LanguageRef синхронизиран с компилатора (incl. C.1 elision)
|
||||||
- [ ] Поне един външен проект (не в monorepo) build-ва с git dep
|
- [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
|
1. E.2 polish apps / E.5 benchmarks
|
||||||
2. Phase D tooling: `bux fmt` CI, `bux test --filter`, golden stdlib tests
|
2. HTTP-fetchable registry index URL (beyond local file)
|
||||||
3. LSP: position-sensitive locals; inferred `let` types
|
3. LSP: workspace rename / references (optional)
|
||||||
|
|||||||
@@ -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`
|
||||||
@@ -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).
|
||||||
|
|
||||||
@@ -10,20 +10,20 @@ enum Result {
|
|||||||
func Main() -> int {
|
func Main() -> int {
|
||||||
let r1: Result = Result { tag: Result_Ok };
|
let r1: Result = Result { tag: Result_Ok };
|
||||||
r1.data.Ok_0 = 42;
|
r1.data.Ok_0 = 42;
|
||||||
|
|
||||||
let r2: Result = Result { tag: Result_Err };
|
let r2: Result = Result { tag: Result_Err };
|
||||||
r2.data.Err_0 = "error message";
|
r2.data.Err_0 = "error message";
|
||||||
|
|
||||||
if r1.tag == Result_Ok {
|
if r1.tag == Result_Ok {
|
||||||
PrintLine("r1 is Ok:");
|
PrintLine("r1 is Ok:");
|
||||||
PrintInt(r1.data.Ok_0);
|
PrintInt(r1.data.Ok_0);
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
}
|
}
|
||||||
|
|
||||||
if r2.tag == Result_Err {
|
if r2.tag == Result_Err {
|
||||||
PrintLine("r2 is Err:");
|
PrintLine("r2 is Err:");
|
||||||
PrintLine(r2.data.Err_0);
|
PrintLine(r2.data.Err_0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
// borrow.bux — Test explicit borrow expressions
|
// borrow.bux — Test explicit borrow expressions
|
||||||
|
|
||||||
struct Point {
|
struct Point {
|
||||||
x: int;
|
x: int;
|
||||||
y: int;
|
y: int;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOT @[Checked] — borrow keyword available everywhere
|
// NOT @[Checked] — borrow keyword available everywhere
|
||||||
|
|||||||
+2
-2
@@ -23,12 +23,12 @@ func ColorName(c: Color) -> String {
|
|||||||
|
|
||||||
func Main() -> int {
|
func Main() -> int {
|
||||||
let myColor: Color = Color::Green;
|
let myColor: Color = Color::Green;
|
||||||
|
|
||||||
PrintLine("My color is:");
|
PrintLine("My color is:");
|
||||||
PrintLine(ColorName(myColor));
|
PrintLine(ColorName(myColor));
|
||||||
PrintLine("Color value:");
|
PrintLine("Color value:");
|
||||||
PrintInt(myColor as int);
|
PrintInt(myColor as int);
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,4 +27,4 @@ func Main() -> int {
|
|||||||
PrintLine("");
|
PrintLine("");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ func Factorial(n: int) -> int {
|
|||||||
|
|
||||||
func Main() -> int {
|
func Main() -> int {
|
||||||
PrintLine("Factorials:");
|
PrintLine("Factorials:");
|
||||||
|
|
||||||
var i: int = 1;
|
var i: int = 1;
|
||||||
while i <= 10 {
|
while i <= 10 {
|
||||||
let fact: int = Factorial(i);
|
let fact: int = Factorial(i);
|
||||||
PrintLine(Fmt_Fmt2("{0}! = {1}", String_FromInt(i), String_FromInt(fact)));
|
PrintLine(Fmt_Fmt2("{0}! = {1}", String_FromInt(i), String_FromInt(fact)));
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ func Fibonacci(n: int) -> int {
|
|||||||
|
|
||||||
func Main() -> int {
|
func Main() -> int {
|
||||||
PrintLine("Fibonacci sequence:");
|
PrintLine("Fibonacci sequence:");
|
||||||
|
|
||||||
var i: int = 0;
|
var i: int = 0;
|
||||||
while i < 10 {
|
while i < 10 {
|
||||||
let fib: int = Fibonacci(i);
|
let fib: int = Fibonacci(i);
|
||||||
PrintLine(Fmt_Fmt1("{0}", String_FromInt(fib)));
|
PrintLine(Fmt_Fmt1("{0}", String_FromInt(fib)));
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ func Main() -> int {
|
|||||||
// Inferred: T = int
|
// Inferred: T = int
|
||||||
let m1: int = Max(10, 20);
|
let m1: int = Max(10, 20);
|
||||||
let m2: int = Max(5, 3);
|
let m2: int = Max(5, 3);
|
||||||
|
|
||||||
PrintLine("Max(10, 20) = ");
|
PrintLine("Max(10, 20) = ");
|
||||||
PrintInt(m1);
|
PrintInt(m1);
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
|
|
||||||
PrintLine("Max(5, 3) = ");
|
PrintLine("Max(5, 3) = ");
|
||||||
PrintInt(m2);
|
PrintInt(m2);
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,4 +20,4 @@ func Main() -> int {
|
|||||||
PrintInt(p.GetFirst());
|
PrintInt(p.GetFirst());
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ func Max<T>(a: T, b: T) -> T {
|
|||||||
func Main() -> int {
|
func Main() -> int {
|
||||||
let m1: int = Max<int>(10, 20);
|
let m1: int = Max<int>(10, 20);
|
||||||
let m2: int = Max<int>(5, 3);
|
let m2: int = Max<int>(5, 3);
|
||||||
|
|
||||||
PrintLine("Max(10, 20) = ");
|
PrintLine("Max(10, 20) = ");
|
||||||
PrintInt(m1);
|
PrintInt(m1);
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
|
|
||||||
PrintLine("Max(5, 3) = ");
|
PrintLine("Max(5, 3) = ");
|
||||||
PrintInt(m2);
|
PrintInt(m2);
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,21 +31,21 @@ func Main() -> int {
|
|||||||
PrintLine("Box value:");
|
PrintLine("Box value:");
|
||||||
PrintInt(b.Get());
|
PrintInt(b.Get());
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
|
|
||||||
b.Set(100);
|
b.Set(100);
|
||||||
PrintLine("Box after Set(100):");
|
PrintLine("Box after Set(100):");
|
||||||
PrintInt(b.Get());
|
PrintInt(b.Get());
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
|
|
||||||
let p: Pair<int, String> = Pair<int, String> { first: 10, second: "hello" };
|
let p: Pair<int, String> = Pair<int, String> { first: 10, second: "hello" };
|
||||||
PrintLine("Pair first:");
|
PrintLine("Pair first:");
|
||||||
PrintInt(p.GetFirst());
|
PrintInt(p.GetFirst());
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
|
|
||||||
let bp: *Box<int> = &b;
|
let bp: *Box<int> = &b;
|
||||||
PrintLine("Box via pointer:");
|
PrintLine("Box via pointer:");
|
||||||
PrintInt(bp.Get());
|
PrintInt(bp.Get());
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -5,20 +5,20 @@ import Std::Fmt::{Fmt_Fmt1, Fmt_FmtInt};
|
|||||||
|
|
||||||
func Main() -> int {
|
func Main() -> int {
|
||||||
PrintLine("=== Json Demo ===");
|
PrintLine("=== Json Demo ===");
|
||||||
|
|
||||||
let s: String = "{\"name\":\"Bux\",\"count\":42,\"items\":[\"a\",\"b\",\"c\"]}";
|
let s: String = "{\"name\":\"Bux\",\"count\":42,\"items\":[\"a\",\"b\",\"c\"]}";
|
||||||
let j: JsonValue = Json_Parse(s);
|
let j: JsonValue = Json_Parse(s);
|
||||||
PrintLine("Parsed and stringified:");
|
PrintLine("Parsed and stringified:");
|
||||||
PrintLine(Json_Stringify(j));
|
PrintLine(Json_Stringify(j));
|
||||||
|
|
||||||
PrintLine("\nAccess fields:");
|
PrintLine("\nAccess fields:");
|
||||||
PrintLine(Fmt_Fmt1("name = {0}", Json_AsString(Json_ObjectGet(j, "name"))));
|
PrintLine(Fmt_Fmt1("name = {0}", Json_AsString(Json_ObjectGet(j, "name"))));
|
||||||
PrintLine(Fmt_FmtInt("count = {0}", Json_AsNumber(Json_ObjectGet(j, "count")) as int64));
|
PrintLine(Fmt_FmtInt("count = {0}", Json_AsNumber(Json_ObjectGet(j, "count")) as int64));
|
||||||
|
|
||||||
let items: JsonValue = Json_ObjectGet(j, "items");
|
let items: JsonValue = Json_ObjectGet(j, "items");
|
||||||
PrintLine(Fmt_Fmt1("items[0] = {0}", Json_AsString(Json_ArrayGet(items, 0))));
|
PrintLine(Fmt_Fmt1("items[0] = {0}", Json_AsString(Json_ArrayGet(items, 0))));
|
||||||
PrintLine(Fmt_Fmt1("items[1] = {0}", Json_AsString(Json_ArrayGet(items, 1))));
|
PrintLine(Fmt_Fmt1("items[1] = {0}", Json_AsString(Json_ArrayGet(items, 1))));
|
||||||
|
|
||||||
PrintLine("\nBuild JSON programmatically:");
|
PrintLine("\nBuild JSON programmatically:");
|
||||||
var obj: JsonValue = Json_Object();
|
var obj: JsonValue = Json_Object();
|
||||||
Json_ObjectSet(&obj, "lang", Json_String("Bux"));
|
Json_ObjectSet(&obj, "lang", Json_String("Bux"));
|
||||||
@@ -28,6 +28,6 @@ func Main() -> int {
|
|||||||
Json_ArrayPush(&arr, Json_String("safe"));
|
Json_ArrayPush(&arr, Json_String("safe"));
|
||||||
Json_ObjectSet(&obj, "tags", arr);
|
Json_ObjectSet(&obj, "tags", arr);
|
||||||
PrintLine(Json_Stringify(obj));
|
PrintLine(Json_Stringify(obj));
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-7
@@ -29,30 +29,30 @@ func Jwt_Create(payload: JsonValue, secret: String) -> String {
|
|||||||
let header: JsonValue = Json_Object();
|
let header: JsonValue = Json_Object();
|
||||||
Json_ObjectSet(&header, "alg", Json_String("HS256"));
|
Json_ObjectSet(&header, "alg", Json_String("HS256"));
|
||||||
Json_ObjectSet(&header, "typ", Json_String("JWT"));
|
Json_ObjectSet(&header, "typ", Json_String("JWT"));
|
||||||
|
|
||||||
let headerB64: String = Base64UrlEncode(Json_Stringify(header));
|
let headerB64: String = Base64UrlEncode(Json_Stringify(header));
|
||||||
let payloadB64: String = Base64UrlEncode(Json_Stringify(payload));
|
let payloadB64: String = Base64UrlEncode(Json_Stringify(payload));
|
||||||
|
|
||||||
let signingInput: String = String_Concat(headerB64, ".");
|
let signingInput: String = String_Concat(headerB64, ".");
|
||||||
signingInput = String_Concat(signingInput, payloadB64);
|
signingInput = String_Concat(signingInput, payloadB64);
|
||||||
|
|
||||||
let sigRaw: String = Crypto_HmacSha256Raw(secret, signingInput);
|
let sigRaw: String = Crypto_HmacSha256Raw(secret, signingInput);
|
||||||
let sigB64: String = Base64UrlEncodeSig(sigRaw);
|
let sigB64: String = Base64UrlEncodeSig(sigRaw);
|
||||||
|
|
||||||
return String_Concat(String_Concat(signingInput, "."), sigB64);
|
return String_Concat(String_Concat(signingInput, "."), sigB64);
|
||||||
}
|
}
|
||||||
|
|
||||||
func Main() -> int {
|
func Main() -> int {
|
||||||
PrintLine("=== JWT Demo ===");
|
PrintLine("=== JWT Demo ===");
|
||||||
|
|
||||||
let payload: JsonValue = Json_Object();
|
let payload: JsonValue = Json_Object();
|
||||||
Json_ObjectSet(&payload, "sub", Json_String("user123"));
|
Json_ObjectSet(&payload, "sub", Json_String("user123"));
|
||||||
Json_ObjectSet(&payload, "name", Json_String("Bux Developer"));
|
Json_ObjectSet(&payload, "name", Json_String("Bux Developer"));
|
||||||
Json_ObjectSet(&payload, "admin", Json_Bool(true));
|
Json_ObjectSet(&payload, "admin", Json_Bool(true));
|
||||||
|
|
||||||
let token: String = Jwt_Create(payload, "my-secret-key");
|
let token: String = Jwt_Create(payload, "my-secret-key");
|
||||||
PrintLine("Token:");
|
PrintLine("Token:");
|
||||||
PrintLine(token);
|
PrintLine(token);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
+7
-7
@@ -5,26 +5,26 @@ import Std::Map::{Map, Map_New, Map_Set, Map_Get, Map_Has, Map_Free};
|
|||||||
func Main() -> int {
|
func Main() -> int {
|
||||||
PrintLine("=== Map<int, String> ===");
|
PrintLine("=== Map<int, String> ===");
|
||||||
let m: Map<int, String> = Map_New<int, String>(16);
|
let m: Map<int, String> = Map_New<int, String>(16);
|
||||||
|
|
||||||
Map_Set<int, String>(&m, 1, "one");
|
Map_Set<int, String>(&m, 1, "one");
|
||||||
Map_Set<int, String>(&m, 2, "two");
|
Map_Set<int, String>(&m, 2, "two");
|
||||||
Map_Set<int, String>(&m, 3, "three");
|
Map_Set<int, String>(&m, 3, "three");
|
||||||
|
|
||||||
PrintLine("Get 1:");
|
PrintLine("Get 1:");
|
||||||
PrintLine(Map_Get<int, String>(&m, 1));
|
PrintLine(Map_Get<int, String>(&m, 1));
|
||||||
|
|
||||||
PrintLine("Get 2:");
|
PrintLine("Get 2:");
|
||||||
PrintLine(Map_Get<int, String>(&m, 2));
|
PrintLine(Map_Get<int, String>(&m, 2));
|
||||||
|
|
||||||
if Map_Has<int, String>(&m, 1) {
|
if Map_Has<int, String>(&m, 1) {
|
||||||
PrintLine("Has 1: yes");
|
PrintLine("Has 1: yes");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test update
|
// Test update
|
||||||
Map_Set<int, String>(&m, 2, "updated");
|
Map_Set<int, String>(&m, 2, "updated");
|
||||||
PrintLine("Updated 2:");
|
PrintLine("Updated 2:");
|
||||||
PrintLine(Map_Get<int, String>(&m, 2));
|
PrintLine(Map_Get<int, String>(&m, 2));
|
||||||
|
|
||||||
Map_Free<int, String>(&m);
|
Map_Free<int, String>(&m);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ extend Rectangle {
|
|||||||
func Area(self: Rectangle) -> int {
|
func Area(self: Rectangle) -> int {
|
||||||
return self.width * self.height;
|
return self.width * self.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
func Perimeter(self: Rectangle) -> int {
|
func Perimeter(self: Rectangle) -> int {
|
||||||
return 2 * (self.width + self.height);
|
return 2 * (self.width + self.height);
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@ extend Rectangle {
|
|||||||
|
|
||||||
func Main() -> int {
|
func Main() -> int {
|
||||||
let rect: Rectangle = Rectangle { width: 10, height: 5 };
|
let rect: Rectangle = Rectangle { width: 10, height: 5 };
|
||||||
|
|
||||||
PrintLine("Rectangle:");
|
PrintLine("Rectangle:");
|
||||||
PrintLine("Width = ");
|
PrintLine("Width = ");
|
||||||
PrintInt(rect.width);
|
PrintInt(rect.width);
|
||||||
@@ -33,6 +33,6 @@ func Main() -> int {
|
|||||||
PrintLine("Perimeter = ");
|
PrintLine("Perimeter = ");
|
||||||
PrintInt(rect.Perimeter());
|
PrintInt(rect.Perimeter());
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,16 +9,16 @@ func Main() -> int {
|
|||||||
PrintLine(Fmt_Fmt1("Current directory: {0}", Os_GetCwd()));
|
PrintLine(Fmt_Fmt1("Current directory: {0}", Os_GetCwd()));
|
||||||
PrintLine(Fmt_FmtInt("Command-line args: {0}", Os_ArgsCount() as int64));
|
PrintLine(Fmt_FmtInt("Command-line args: {0}", Os_ArgsCount() as int64));
|
||||||
PrintLine(Fmt_Fmt1("HOME env: {0}", Os_GetEnv("HOME")));
|
PrintLine(Fmt_Fmt1("HOME env: {0}", Os_GetEnv("HOME")));
|
||||||
|
|
||||||
PrintLine("\n=== Time Demo ===");
|
PrintLine("\n=== Time Demo ===");
|
||||||
let start: int64 = Time_NowMs();
|
let start: int64 = Time_NowMs();
|
||||||
PrintLine(Fmt_FmtInt("Start: {0} ms", start));
|
PrintLine(Fmt_FmtInt("Start: {0} ms", start));
|
||||||
PrintLine("Sleeping 150ms...");
|
PrintLine("Sleeping 150ms...");
|
||||||
Time_SleepMs(150);
|
Time_SleepMs(150);
|
||||||
|
|
||||||
let end: int64 = Time_NowMs();
|
let end: int64 = Time_NowMs();
|
||||||
PrintLine(Fmt_FmtInt("End: {0} ms", end));
|
PrintLine(Fmt_FmtInt("End: {0} ms", end));
|
||||||
PrintLine(Fmt_FmtInt("Elapsed: {0} ms", end - start));
|
PrintLine(Fmt_FmtInt("Elapsed: {0} ms", end - start));
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,17 +23,17 @@ func UncheckedSwap(a: *int, b: *int) {
|
|||||||
|
|
||||||
func Main() -> int {
|
func Main() -> int {
|
||||||
var x: int = 10;
|
var x: int = 10;
|
||||||
|
|
||||||
// &mut allows mutation
|
// &mut allows mutation
|
||||||
ScaleInPlace(&x, 3);
|
ScaleInPlace(&x, 3);
|
||||||
PrintInt(x); // 30
|
PrintInt(x); // 30
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
|
|
||||||
// & allows reading
|
// & allows reading
|
||||||
let y: int = GetValue(&x);
|
let y: int = GetValue(&x);
|
||||||
PrintInt(y); // 30
|
PrintInt(y); // 30
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
|
|
||||||
// Unchecked: raw pointers work like C
|
// Unchecked: raw pointers work like C
|
||||||
var a: int = 5;
|
var a: int = 5;
|
||||||
var b: int = 7;
|
var b: int = 7;
|
||||||
@@ -42,6 +42,6 @@ func Main() -> int {
|
|||||||
PrintLine("");
|
PrintLine("");
|
||||||
PrintInt(b); // 5
|
PrintInt(b); // 5
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import Std::Fmt::{Fmt_Fmt1, Fmt_FmtInt};
|
|||||||
|
|
||||||
func Main() -> int {
|
func Main() -> int {
|
||||||
PrintLine("Process demo:");
|
PrintLine("Process demo:");
|
||||||
|
|
||||||
let output: String = Process_Output("echo Hello from subprocess");
|
let output: String = Process_Output("echo Hello from subprocess");
|
||||||
PrintLine(Fmt_Fmt1("Captured: {0}", output));
|
PrintLine(Fmt_Fmt1("Captured: {0}", output));
|
||||||
|
|
||||||
let rc: int = Process_Run("true");
|
let rc: int = Process_Run("true");
|
||||||
PrintLine(Fmt_FmtInt("Exit code: {0}", rc));
|
PrintLine(Fmt_FmtInt("Exit code: {0}", rc));
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,14 +82,14 @@ func SafeDivide(a: int, b: int) -> Option {
|
|||||||
|
|
||||||
func Main() -> int {
|
func Main() -> int {
|
||||||
PrintLine("Result demo:");
|
PrintLine("Result demo:");
|
||||||
|
|
||||||
let r1: Result = Divide(10, 2);
|
let r1: Result = Divide(10, 2);
|
||||||
if Result_IsOk(r1) {
|
if Result_IsOk(r1) {
|
||||||
PrintLine("10 / 2 = ");
|
PrintLine("10 / 2 = ");
|
||||||
PrintInt(Result_Unwrap(r1));
|
PrintInt(Result_Unwrap(r1));
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
}
|
}
|
||||||
|
|
||||||
let r2: Result = Divide(10, 0);
|
let r2: Result = Divide(10, 0);
|
||||||
if Result_IsErr(r2) {
|
if Result_IsErr(r2) {
|
||||||
PrintLine("10 / 0 failed");
|
PrintLine("10 / 0 failed");
|
||||||
@@ -97,20 +97,20 @@ func Main() -> int {
|
|||||||
PrintLine("unwrap_or = ");
|
PrintLine("unwrap_or = ");
|
||||||
PrintInt(Result_UnwrapOr(r2, -1));
|
PrintInt(Result_UnwrapOr(r2, -1));
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
|
|
||||||
PrintLine("Option demo:");
|
PrintLine("Option demo:");
|
||||||
|
|
||||||
let o1: Option = SafeDivide(20, 4);
|
let o1: Option = SafeDivide(20, 4);
|
||||||
if Option_IsSome(o1) {
|
if Option_IsSome(o1) {
|
||||||
PrintLine("20 / 4 = ");
|
PrintLine("20 / 4 = ");
|
||||||
PrintInt(Option_UnwrapOr(o1, 0));
|
PrintInt(Option_UnwrapOr(o1, 0));
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
}
|
}
|
||||||
|
|
||||||
let o2: Option = SafeDivide(20, 0);
|
let o2: Option = SafeDivide(20, 0);
|
||||||
PrintLine("20 / 0 fallback = ");
|
PrintLine("20 / 0 fallback = ");
|
||||||
PrintInt(Option_UnwrapOr(o2, -1));
|
PrintInt(Option_UnwrapOr(o2, -1));
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,30 +5,30 @@ import Std::String::{String_Len, String_Eq, String_Concat};
|
|||||||
func Main() -> int {
|
func Main() -> int {
|
||||||
let hello: String = "Hello";
|
let hello: String = "Hello";
|
||||||
let world: String = "World";
|
let world: String = "World";
|
||||||
|
|
||||||
PrintLine("String operations:");
|
PrintLine("String operations:");
|
||||||
|
|
||||||
// Length
|
// Length
|
||||||
PrintLine("Length of 'Hello':");
|
PrintLine("Length of 'Hello':");
|
||||||
PrintInt(String_Len(hello));
|
PrintInt(String_Len(hello));
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
|
|
||||||
// Concatenation
|
// Concatenation
|
||||||
let greeting: String = String_Concat(hello, ", ");
|
let greeting: String = String_Concat(hello, ", ");
|
||||||
let greeting2: String = String_Concat(greeting, world);
|
let greeting2: String = String_Concat(greeting, world);
|
||||||
let full: String = String_Concat(greeting2, "!");
|
let full: String = String_Concat(greeting2, "!");
|
||||||
|
|
||||||
PrintLine("Concatenated:");
|
PrintLine("Concatenated:");
|
||||||
PrintLine(full);
|
PrintLine(full);
|
||||||
|
|
||||||
// Equality
|
// Equality
|
||||||
if String_Eq(hello, "Hello") {
|
if String_Eq(hello, "Hello") {
|
||||||
PrintLine("'Hello' equals 'Hello'");
|
PrintLine("'Hello' equals 'Hello'");
|
||||||
}
|
}
|
||||||
|
|
||||||
if !String_Eq(hello, world) {
|
if !String_Eq(hello, world) {
|
||||||
PrintLine("'Hello' does not equal 'World'");
|
PrintLine("'Hello' does not equal 'World'");
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,10 +65,10 @@ func Main() -> int {
|
|||||||
StringBuilder_Append(&sb, "World");
|
StringBuilder_Append(&sb, "World");
|
||||||
StringBuilder_Append(&sb, "! #");
|
StringBuilder_Append(&sb, "! #");
|
||||||
StringBuilder_AppendInt(&sb, 42);
|
StringBuilder_AppendInt(&sb, 42);
|
||||||
|
|
||||||
Print("StringBuilder: ");
|
Print("StringBuilder: ");
|
||||||
PrintLine(StringBuilder_Build(&sb));
|
PrintLine(StringBuilder_Build(&sb));
|
||||||
StringBuilder_Free(&sb);
|
StringBuilder_Free(&sb);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func Main() -> int {
|
|||||||
let p1: Point = Point { x: 10, y: 20 };
|
let p1: Point = Point { x: 10, y: 20 };
|
||||||
let p2: Point = Point { x: 5, y: 15 };
|
let p2: Point = Point { x: 5, y: 15 };
|
||||||
let sum: Point = AddPoints(p1, p2);
|
let sum: Point = AddPoints(p1, p2);
|
||||||
|
|
||||||
PrintLine("Point sum:");
|
PrintLine("Point sum:");
|
||||||
PrintLine("x = ");
|
PrintLine("x = ");
|
||||||
PrintInt(sum.x);
|
PrintInt(sum.x);
|
||||||
@@ -24,6 +24,6 @@ func Main() -> int {
|
|||||||
PrintLine("y = ");
|
PrintLine("y = ");
|
||||||
PrintInt(sum.y);
|
PrintInt(sum.y);
|
||||||
PrintLine("");
|
PrintLine("");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func ComputeWithError() -> Result {
|
|||||||
|
|
||||||
func Main() -> int {
|
func Main() -> int {
|
||||||
PrintLine("Try operator demo:");
|
PrintLine("Try operator demo:");
|
||||||
|
|
||||||
let r1: Result = Compute();
|
let r1: Result = Compute();
|
||||||
if r1.tag == Result_Ok {
|
if r1.tag == Result_Ok {
|
||||||
PrintLine("Compute() = ");
|
PrintLine("Compute() = ");
|
||||||
@@ -48,11 +48,11 @@ func Main() -> int {
|
|||||||
} else {
|
} else {
|
||||||
PrintLine("Compute() failed");
|
PrintLine("Compute() failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
let r2: Result = ComputeWithError();
|
let r2: Result = ComputeWithError();
|
||||||
if r2.tag == Result_Err {
|
if r2.tag == Result_Err {
|
||||||
PrintLine("ComputeWithError() failed as expected");
|
PrintLine("ComputeWithError() failed as expected");
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+122
-114
@@ -1,135 +1,143 @@
|
|||||||
module Std::Array {
|
module Std::Array {
|
||||||
|
|
||||||
extern func bux_alloc(size: uint) -> *void;
|
extern func bux_alloc(size: uint) -> *void;
|
||||||
extern func bux_realloc(ptr: *void, size: uint) -> *void;
|
extern func bux_realloc(ptr: *void, size: uint) -> *void;
|
||||||
extern func bux_free(ptr: *void);
|
extern func bux_free(ptr: *void);
|
||||||
extern func bux_bounds_check(index: uint, len: uint);
|
extern func bux_bounds_check(index: uint, len: uint);
|
||||||
|
|
||||||
struct Array<T> {
|
/// Growable contiguous buffer of `T` (len + capacity).
|
||||||
data: *T,
|
struct Array<T> {
|
||||||
len: uint,
|
data: *T,
|
||||||
cap: uint,
|
len: uint,
|
||||||
}
|
cap: uint,
|
||||||
|
}
|
||||||
|
|
||||||
func Array_New<T>(cap: uint) -> Array<T> {
|
/// Create an empty array with the given initial capacity.
|
||||||
let data = bux_alloc(cap * sizeof(T)) as *T;
|
func Array_New<T>(cap: uint) -> Array<T> {
|
||||||
return Array<T> { data: data, len: 0, cap: cap };
|
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.
|
||||||
if self.len >= self.cap {
|
func Array_Push<T>(self: *Array<T>, value: T) {
|
||||||
self.cap = self.cap * 2;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Number of live elements.
|
||||||
|
func Array_Len<T>(self: *Array<T>) -> uint {
|
||||||
|
return self.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
return Array_Get<T>(self, idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return self.len == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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>) {
|
||||||
|
self.len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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;
|
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 {
|
/// First element (bounds-checked if empty).
|
||||||
bux_bounds_check(index, self.len);
|
func Array_First<T>(self: *Array<T>) -> T {
|
||||||
return self.data[index];
|
return Array_Get<T>(self, 0);
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
return self.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
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>) {
|
|
||||||
Array_Free<T>(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
Array_Set<T>(self, idx, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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 {
|
|
||||||
return self.cap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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) {
|
|
||||||
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) */
|
/// Last element (bounds-checked if empty).
|
||||||
func Array_First<T>(self: *Array<T>) -> T {
|
func Array_Last<T>(self: *Array<T>) -> T {
|
||||||
return Array_Get<T>(self, 0);
|
return Array_Get<T>(self, self.len - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Last element (panics if empty via bounds check) */
|
/// Remove and return the last element (bounds-checked if empty).
|
||||||
func Array_Last<T>(self: *Array<T>) -> T {
|
func Array_Pop<T>(self: *Array<T>) -> T {
|
||||||
return Array_Get<T>(self, self.len - 1);
|
bux_bounds_check(0, self.len);
|
||||||
}
|
self.len = self.len - 1;
|
||||||
|
return self.data[self.len];
|
||||||
|
}
|
||||||
|
|
||||||
/* Remove and return the last element (panics if empty) */
|
/// Linear search: true if `value` is present (uses `==`).
|
||||||
func Array_Pop<T>(self: *Array<T>) -> T {
|
func Array_Contains<T>(self: *Array<T>, value: T) -> bool {
|
||||||
bux_bounds_check(0, self.len);
|
var i: uint = 0;
|
||||||
self.len = self.len - 1;
|
while i < self.len {
|
||||||
return self.data[self.len];
|
if self.data[i] == value {
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
/* Linear search: true if value is present (uses ==) */
|
i = i + 1;
|
||||||
func Array_Contains<T>(self: *Array<T>, value: T) -> bool {
|
|
||||||
var i: uint = 0;
|
|
||||||
while i < self.len {
|
|
||||||
if self.data[i] == value {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
i = i + 1;
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Index of first equal element, or -1 if not found */
|
/// Index of first equal element, or `-1` if not found.
|
||||||
func Array_IndexOf<T>(self: *Array<T>, value: T) -> int {
|
func Array_IndexOf<T>(self: *Array<T>, value: T) -> int {
|
||||||
var i: uint = 0;
|
var i: uint = 0;
|
||||||
while i < self.len {
|
while i < self.len {
|
||||||
if self.data[i] == value {
|
if self.data[i] == value {
|
||||||
return i as int;
|
return i as int;
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
}
|
}
|
||||||
i = i + 1;
|
return -1;
|
||||||
}
|
}
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Append all elements of other onto self */
|
/// Append all elements of `other` onto `self`.
|
||||||
func Array_Extend<T>(self: *Array<T>, other: *Array<T>) {
|
func Array_Extend<T>(self: *Array<T>, other: *Array<T>) {
|
||||||
var i: uint = 0;
|
var i: uint = 0;
|
||||||
while i < other.len {
|
while i < other.len {
|
||||||
Array_Push<T>(self, other.data[i]);
|
Array_Push<T>(self, other.data[i]);
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-48
@@ -1,64 +1,64 @@
|
|||||||
module Std::Channel {
|
module Std::Channel {
|
||||||
|
|
||||||
extern func bux_channel_new(capacity: int64, elem_size: int64) -> *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_send(handle: *void, elem: *void);
|
||||||
extern func bux_channel_recv(handle: *void, out: *void) -> int;
|
extern func bux_channel_recv(handle: *void, out: *void) -> int;
|
||||||
extern func bux_channel_close(handle: *void);
|
extern func bux_channel_close(handle: *void);
|
||||||
extern func bux_channel_free(handle: *void);
|
extern func bux_channel_free(handle: *void);
|
||||||
|
|
||||||
struct Channel<T> {
|
struct Channel<T> {
|
||||||
handle: *void;
|
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)) };
|
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);
|
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;
|
var result: T;
|
||||||
bux_channel_recv(ch.handle, (&result) as *void);
|
bux_channel_recv(ch.handle, (&result) as *void);
|
||||||
return result;
|
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;
|
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);
|
bux_channel_close(ch.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
func Channel_Free<T>(ch: *Channel<T>) {
|
func Channel_Free<T>(ch: *Channel<T>) {
|
||||||
bux_channel_free(ch.handle);
|
bux_channel_free(ch.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
func Channel_Drop<T>(ch: *Channel<T>) {
|
func Channel_Drop<T>(ch: *Channel<T>) {
|
||||||
Channel_Free<T>(ch);
|
Channel_Free<T>(ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Convenience wrappers for common types */
|
/* Convenience wrappers for common types */
|
||||||
func Channel_SendInt(ch: *Channel<int>, value: int) {
|
func Channel_SendInt(ch: *Channel<int>, value: int) {
|
||||||
bux_channel_send(ch.handle, (&value) as *void);
|
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;
|
var result: int = 0;
|
||||||
bux_channel_recv(ch.handle, (&result) as *void);
|
bux_channel_recv(ch.handle, (&result) as *void);
|
||||||
return result;
|
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);
|
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;
|
var result: float64 = 0.0;
|
||||||
bux_channel_recv(ch.handle, (&result) as *void);
|
bux_channel_recv(ch.handle, (&result) as *void);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+61
-61
@@ -10,72 +10,72 @@
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
module Std::Crypto {
|
module Std::Crypto {
|
||||||
|
|
||||||
import Std::Mem::{Alloc, Free};
|
import Std::Mem::{Alloc, Free};
|
||||||
import Std::String::{String_Len};
|
import Std::String::{String_Len};
|
||||||
|
|
||||||
// Re-use the same externs from submodules (merged by compiler)
|
// Re-use the same externs from submodules (merged by compiler)
|
||||||
extern func bux_sha256(data: String, len: int, out: *void);
|
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_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_random_bytes(buf: *void, len: int) -> int;
|
||||||
extern func bux_base64_encode(data: String, len: 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_base64_decode(data: String, len: int, outlen: *int) -> String;
|
||||||
extern func bux_bytes_to_hex(data: *void, len: 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
|
// SHA-256 → hex
|
||||||
func Crypto_Sha256(data: String) -> String {
|
func Crypto_Sha256(data: String) -> String {
|
||||||
let len: int = String_Len(data) as int;
|
let len: int = String_Len(data) as int;
|
||||||
let hashBuf: *void = Alloc(32);
|
let hashBuf: *void = Alloc(32);
|
||||||
bux_sha256(data, len, hashBuf);
|
bux_sha256(data, len, hashBuf);
|
||||||
let result: String = bux_bytes_to_hex(hashBuf as *void, 32);
|
let result: String = bux_bytes_to_hex(hashBuf as *void, 32);
|
||||||
Free(hashBuf);
|
Free(hashBuf);
|
||||||
return result;
|
return result;
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
bux_hmac_sha256(key, keylen, message, msglen, hmacBuf);
|
|
||||||
let result: String = bux_bytes_to_hex(hmacBuf as *void, 32);
|
|
||||||
Free(hmacBuf);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
Free(buf);
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
let result: String = bux_base64_encode(buf as String, n);
|
|
||||||
Free(buf);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Base64 encode
|
// HMAC-SHA256 → hex
|
||||||
func Crypto_Base64Encode(s: String) -> String {
|
func Crypto_HmacSha256(key: String, message: String) -> String {
|
||||||
return bux_base64_encode(s, String_Len(s) as int);
|
let keylen: int = String_Len(key) as int;
|
||||||
}
|
let msglen: int = String_Len(message) as int;
|
||||||
|
let hmacBuf: *void = Alloc(32);
|
||||||
|
bux_hmac_sha256(key, keylen, message, msglen, hmacBuf);
|
||||||
|
let result: String = bux_bytes_to_hex(hmacBuf as *void, 32);
|
||||||
|
Free(hmacBuf);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// HMAC-SHA256 raw → base64
|
// Random bytes → base64
|
||||||
func Crypto_HmacSha256Raw(key: String, message: String) -> String {
|
func Crypto_RandomBytes(n: int) -> String {
|
||||||
let keylen: int = String_Len(key) as int;
|
if n <= 0 { return ""; }
|
||||||
let msglen: int = String_Len(message) as int;
|
let buf: *void = Alloc(n as uint);
|
||||||
let hmacBuf: *void = Alloc(32);
|
if bux_random_bytes(buf, n) != 1 {
|
||||||
bux_hmac_sha256(key, keylen, message, msglen, hmacBuf);
|
Free(buf);
|
||||||
let result: String = bux_base64_encode(hmacBuf as String, 32);
|
return "";
|
||||||
Free(hmacBuf);
|
}
|
||||||
return result;
|
let result: String = bux_base64_encode(buf as String, n);
|
||||||
}
|
Free(buf);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// Base64 decode
|
// Base64 encode
|
||||||
func Crypto_Base64Decode(s: String) -> String {
|
func Crypto_Base64Encode(s: String) -> String {
|
||||||
let outlen: int = 0;
|
return bux_base64_encode(s, String_Len(s) as int);
|
||||||
return bux_base64_decode(s, String_Len(s) as int, &outlen);
|
}
|
||||||
}
|
|
||||||
|
// 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);
|
||||||
|
bux_hmac_sha256(key, keylen, message, msglen, hmacBuf);
|
||||||
|
let result: String = bux_base64_encode(hmacBuf as String, 32);
|
||||||
|
Free(hmacBuf);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base64 decode
|
||||||
|
func Crypto_Base64Decode(s: String) -> String {
|
||||||
|
let outlen: int = 0;
|
||||||
|
return bux_base64_decode(s, String_Len(s) as int, &outlen);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+74
-74
@@ -8,93 +8,93 @@
|
|||||||
|
|
||||||
module Std::Fmt {
|
module Std::Fmt {
|
||||||
|
|
||||||
import Std::String::{
|
import Std::String::{
|
||||||
String_Eq,
|
String_Eq,
|
||||||
String_FromInt,
|
String_FromInt,
|
||||||
String_FromFloat,
|
String_FromFloat,
|
||||||
String_FromBool,
|
String_FromBool,
|
||||||
StringBuilder,
|
StringBuilder,
|
||||||
StringBuilder_New,
|
StringBuilder_New,
|
||||||
StringBuilder_Append,
|
StringBuilder_Append,
|
||||||
StringBuilder_Build,
|
StringBuilder_Build,
|
||||||
String_Chars
|
String_Chars
|
||||||
};
|
};
|
||||||
|
|
||||||
extern func bux_strlen(s: String) -> uint;
|
extern func bux_strlen(s: String) -> uint;
|
||||||
extern func bux_str_to_int(s: String) -> int64;
|
extern func bux_str_to_int(s: String) -> int64;
|
||||||
extern func bux_alloc(size: uint) -> *void;
|
extern func bux_alloc(size: uint) -> *void;
|
||||||
|
|
||||||
// Core formatting engine: replace {0}..{9} in template with args
|
// Core formatting engine: replace {0}..{9} in template with args
|
||||||
func Fmt_Format(tmpl: String, argStrs: *String, argCount: int) -> String {
|
func Fmt_Format(tmpl: String, argStrs: *String, argCount: int) -> String {
|
||||||
let sb: StringBuilder = StringBuilder_New();
|
let sb: StringBuilder = StringBuilder_New();
|
||||||
var i: uint = 0;
|
var i: uint = 0;
|
||||||
let tmplLen: uint = bux_strlen(tmpl);
|
let tmplLen: uint = bux_strlen(tmpl);
|
||||||
while i < tmplLen {
|
while i < tmplLen {
|
||||||
// Check for {
|
// Check for {
|
||||||
let ch: String = String_Chars(tmpl, i);
|
let ch: String = String_Chars(tmpl, i);
|
||||||
if String_Eq(ch, "{") {
|
if String_Eq(ch, "{") {
|
||||||
let digitIdx: uint = i + 1;
|
let digitIdx: uint = i + 1;
|
||||||
if digitIdx < tmplLen {
|
if digitIdx < tmplLen {
|
||||||
let digitCh: String = String_Chars(tmpl, digitIdx);
|
let digitCh: String = String_Chars(tmpl, digitIdx);
|
||||||
let d: int64 = bux_str_to_int(digitCh);
|
let d: int64 = bux_str_to_int(digitCh);
|
||||||
if d >= 0 && d < argCount as int64 {
|
if d >= 0 && d < argCount as int64 {
|
||||||
// Consume {d}
|
// Consume {d}
|
||||||
i = i + 2; // skip past digit
|
i = i + 2; // skip past digit
|
||||||
// Check for closing }
|
// Check for closing }
|
||||||
if i < tmplLen {
|
if i < tmplLen {
|
||||||
let closeCh: String = String_Chars(tmpl, i);
|
let closeCh: String = String_Chars(tmpl, i);
|
||||||
if String_Eq(closeCh, "}") {
|
if String_Eq(closeCh, "}") {
|
||||||
i = i + 1; // skip }
|
i = i + 1; // skip }
|
||||||
let argStr: String = argStrs[d as uint];
|
let argStr: String = argStrs[d as uint];
|
||||||
StringBuilder_Append(&sb, argStr);
|
StringBuilder_Append(&sb, argStr);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Normal character: append it
|
||||||
|
StringBuilder_Append(&sb, ch);
|
||||||
|
i = i + 1;
|
||||||
}
|
}
|
||||||
// Normal character: append it
|
return StringBuilder_Build(&sb);
|
||||||
StringBuilder_Append(&sb, ch);
|
|
||||||
i = i + 1;
|
|
||||||
}
|
}
|
||||||
return StringBuilder_Build(&sb);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convenience wrappers
|
// Convenience wrappers
|
||||||
func Fmt_Fmt1(tmpl: String, a1: String) -> String {
|
func Fmt_Fmt1(tmpl: String, a1: String) -> String {
|
||||||
var args: *String = bux_alloc(sizeof(String)) as *String;
|
var args: *String = bux_alloc(sizeof(String)) as *String;
|
||||||
args[0] = a1;
|
args[0] = a1;
|
||||||
return Fmt_Format(tmpl, args, 1);
|
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);
|
let s: String = String_FromInt(val);
|
||||||
return Fmt_Fmt1(tmpl, s);
|
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);
|
let s: String = String_FromBool(val);
|
||||||
return Fmt_Fmt1(tmpl, s);
|
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);
|
let s: String = String_FromFloat(val);
|
||||||
return Fmt_Fmt1(tmpl, s);
|
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;
|
var args: *String = bux_alloc(2 * sizeof(String)) as *String;
|
||||||
args[0] = a1;
|
args[0] = a1;
|
||||||
args[1] = a2;
|
args[1] = a2;
|
||||||
return Fmt_Format(tmpl, args, 2);
|
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;
|
var args: *String = bux_alloc(3 * sizeof(String)) as *String;
|
||||||
args[0] = a1;
|
args[0] = a1;
|
||||||
args[1] = a2;
|
args[1] = a2;
|
||||||
args[2] = a3;
|
args[2] = a3;
|
||||||
return Fmt_Format(tmpl, args, 3);
|
return Fmt_Format(tmpl, args, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-12
@@ -1,19 +1,19 @@
|
|||||||
module Std::Fs {
|
module Std::Fs {
|
||||||
|
|
||||||
extern func bux_dir_exists(path: String) -> int;
|
extern func bux_dir_exists(path: String) -> int;
|
||||||
extern func bux_mkdir_if_needed(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_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;
|
return bux_dir_exists(path) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
func Mkdir(path: String) -> bool {
|
func Mkdir(path: String) -> bool {
|
||||||
return bux_mkdir_if_needed(path) != 0;
|
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);
|
return bux_list_dir(dir, ext, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-21
@@ -1,28 +1,28 @@
|
|||||||
module Std::Io {
|
module Std::Io {
|
||||||
|
|
||||||
extern func PrintLine(s: String);
|
extern func PrintLine(s: String);
|
||||||
extern func Print(s: String);
|
extern func Print(s: String);
|
||||||
extern func PrintInt(n: int);
|
extern func PrintInt(n: int);
|
||||||
extern func PrintInt64(n: int64);
|
extern func PrintInt64(n: int64);
|
||||||
extern func PrintFloat(f: float64);
|
extern func PrintFloat(f: float64);
|
||||||
extern func PrintBool(b: bool);
|
extern func PrintBool(b: bool);
|
||||||
extern func ReadLine() -> String;
|
extern func ReadLine() -> String;
|
||||||
extern func bux_read_file(path: String) -> String;
|
extern func bux_read_file(path: String) -> String;
|
||||||
extern func bux_write_file(path: String, content: String) -> int;
|
extern func bux_write_file(path: String, content: String) -> int;
|
||||||
extern func bux_file_exists(path: 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);
|
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);
|
let r: int = bux_write_file(path, content);
|
||||||
return r != 0;
|
return r != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
func FileExists(path: String) -> bool {
|
func FileExists(path: String) -> bool {
|
||||||
let r: int = bux_file_exists(path);
|
let r: int = bux_file_exists(path);
|
||||||
return r != 0;
|
return r != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+201
-201
@@ -1,233 +1,233 @@
|
|||||||
module Std::Iter {
|
module Std::Iter {
|
||||||
|
|
||||||
import Std::Array::*;
|
import Std::Array::*;
|
||||||
|
|
||||||
struct Iter<T> {
|
struct Iter<T> {
|
||||||
data: *T,
|
data: *T,
|
||||||
len: uint,
|
len: uint,
|
||||||
pos: uint,
|
pos: uint,
|
||||||
}
|
|
||||||
|
|
||||||
/* 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 {
|
|
||||||
return it.pos < it.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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 {
|
|
||||||
return it.data[it.pos];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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 {
|
|
||||||
return it.pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Remaining length */
|
|
||||||
func Iter_Len<T>(it: *Iter<T>) -> uint {
|
|
||||||
return it.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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) {
|
|
||||||
it.pos = it.pos + n;
|
|
||||||
if it.pos > it.len {
|
|
||||||
it.pos = it.len;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* Take first N elements (by limiting len) */
|
/* Create an iterator from an Array */
|
||||||
func Iter_Take<T>(it: *Iter<T>, n: uint) -> Iter<T> {
|
func Array_Iter<T>(arr: *Array<T>) -> Iter<T> {
|
||||||
var endPos: uint = it.pos + n;
|
return Iter<T> { data: arr.data, len: arr.len, pos: 0 };
|
||||||
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 */
|
/* Check if there are more elements */
|
||||||
func Iter_AnyEq<T>(it: *Iter<T>, value: T) -> bool {
|
func Iter_HasNext<T>(it: *Iter<T>) -> bool {
|
||||||
var i: uint = it.pos;
|
return it.pos < it.len;
|
||||||
while i < it.len {
|
}
|
||||||
if it.data[i] == value {
|
|
||||||
return true;
|
/* 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 {
|
||||||
|
return it.data[it.pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 {
|
||||||
|
return it.pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remaining length */
|
||||||
|
func Iter_Len<T>(it: *Iter<T>) -> uint {
|
||||||
|
return it.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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) {
|
||||||
|
it.pos = it.pos + n;
|
||||||
|
if it.pos > it.len {
|
||||||
|
it.pos = it.len;
|
||||||
}
|
}
|
||||||
i = i + 1;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* True if every remaining element equals value (true if empty) */
|
/* Take first N elements (by limiting len) */
|
||||||
func Iter_AllEq<T>(it: *Iter<T>, value: T) -> bool {
|
func Iter_Take<T>(it: *Iter<T>, n: uint) -> Iter<T> {
|
||||||
var i: uint = it.pos;
|
var endPos: uint = it.pos + n;
|
||||||
while i < it.len {
|
if endPos > it.len {
|
||||||
if it.data[i] != value {
|
endPos = it.len;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
i = i + 1;
|
return Iter<T> { data: it.data, len: endPos, pos: it.pos };
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Collect remaining elements into a new Array */
|
/* True if any remaining element equals value */
|
||||||
func Iter_Collect<T>(it: *Iter<T>) -> Array<T> {
|
func Iter_AnyEq<T>(it: *Iter<T>, value: T) -> bool {
|
||||||
let remaining: uint = it.len - it.pos;
|
var i: uint = it.pos;
|
||||||
var cap: uint = remaining;
|
while i < it.len {
|
||||||
if cap == 0 {
|
if it.data[i] == value {
|
||||||
cap = 1;
|
return true;
|
||||||
}
|
}
|
||||||
var arr: Array<T> = Array_New<T>(cap);
|
i = i + 1;
|
||||||
var i: uint = it.pos;
|
|
||||||
while i < it.len {
|
|
||||||
Array_Push<T>(&arr, it.data[i]);
|
|
||||||
i = i + 1;
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// 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> {
|
|
||||||
let remaining: uint = it.len - it.pos;
|
|
||||||
var cap: uint = remaining;
|
|
||||||
if cap == 0 {
|
|
||||||
cap = 1;
|
|
||||||
}
|
|
||||||
var out: Array<U> = Array_New<U>(cap);
|
|
||||||
var i: uint = it.pos;
|
|
||||||
while i < it.len {
|
|
||||||
let mapped: U = f(it.data[i]);
|
|
||||||
Array_Push<U>(&out, mapped);
|
|
||||||
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> {
|
|
||||||
let remaining: uint = it.len - it.pos;
|
|
||||||
var cap: uint = remaining;
|
|
||||||
if cap == 0 {
|
|
||||||
cap = 1;
|
|
||||||
}
|
|
||||||
var out: Array<T> = Array_New<T>(cap);
|
|
||||||
var i: uint = it.pos;
|
|
||||||
while i < it.len {
|
|
||||||
let v: T = it.data[i];
|
|
||||||
if pred(v) {
|
|
||||||
Array_Push<T>(&out, v);
|
|
||||||
}
|
}
|
||||||
i = i + 1;
|
return false;
|
||||||
}
|
}
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Left-fold: f(f(...f(init, x0), x1), ...) */
|
/* True if every remaining element equals value (true if empty) */
|
||||||
func Iter_Fold<T, Acc>(it: *Iter<T>, init: Acc, f: func(Acc, T) -> Acc) -> Acc {
|
func Iter_AllEq<T>(it: *Iter<T>, value: T) -> bool {
|
||||||
var acc: Acc = init;
|
var i: uint = it.pos;
|
||||||
var i: uint = it.pos;
|
while i < it.len {
|
||||||
while i < it.len {
|
if it.data[i] != value {
|
||||||
acc = f(acc, it.data[i]);
|
return false;
|
||||||
i = i + 1;
|
}
|
||||||
}
|
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) {
|
|
||||||
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 {
|
|
||||||
var i: uint = it.pos;
|
|
||||||
while i < it.len {
|
|
||||||
if pred(it.data[i]) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
i = i + 1;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* True if all remaining elements satisfy pred (true if empty) */
|
/* Collect remaining elements into a new Array */
|
||||||
func Iter_All<T>(it: *Iter<T>, pred: func(T) -> bool) -> bool {
|
func Iter_Collect<T>(it: *Iter<T>) -> Array<T> {
|
||||||
var i: uint = it.pos;
|
let remaining: uint = it.len - it.pos;
|
||||||
while i < it.len {
|
var cap: uint = remaining;
|
||||||
if !pred(it.data[i]) {
|
if cap == 0 {
|
||||||
return false;
|
cap = 1;
|
||||||
}
|
}
|
||||||
i = i + 1;
|
var arr: Array<T> = Array_New<T>(cap);
|
||||||
|
var i: uint = it.pos;
|
||||||
|
while i < it.len {
|
||||||
|
Array_Push<T>(&arr, it.data[i]);
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sum remaining ints (specialized fold) */
|
// ---------------------------------------------------------------------------
|
||||||
func Iter_SumInt(it: *Iter<int>) -> int {
|
// Higher-order helpers (generic; fat func pointers / closures)
|
||||||
var total: int = 0;
|
// ---------------------------------------------------------------------------
|
||||||
var i: uint = it.pos;
|
|
||||||
while i < it.len {
|
/* Map each remaining element through f: T → U, collect into Array<U> */
|
||||||
total = total + it.data[i];
|
func Iter_Map<T, U>(it: *Iter<T>, f: func(T) -> U) -> Array<U> {
|
||||||
i = i + 1;
|
let remaining: uint = it.len - it.pos;
|
||||||
|
var cap: uint = remaining;
|
||||||
|
if cap == 0 {
|
||||||
|
cap = 1;
|
||||||
|
}
|
||||||
|
var out: Array<U> = Array_New<U>(cap);
|
||||||
|
var i: uint = it.pos;
|
||||||
|
while i < it.len {
|
||||||
|
let mapped: U = f(it.data[i]);
|
||||||
|
Array_Push<U>(&out, mapped);
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
/* Keep remaining elements for which pred returns true */
|
||||||
// Int-specialized aliases (backward compatible with earlier examples)
|
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 {
|
||||||
|
cap = 1;
|
||||||
|
}
|
||||||
|
var out: Array<T> = Array_New<T>(cap);
|
||||||
|
var i: uint = it.pos;
|
||||||
|
while i < it.len {
|
||||||
|
let v: T = it.data[i];
|
||||||
|
if pred(v) {
|
||||||
|
Array_Push<T>(&out, v);
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
func Iter_MapInt(it: *Iter<int>, f: func(int) -> int) -> Array<int> {
|
/* Left-fold: f(f(...f(init, x0), x1), ...) */
|
||||||
return Iter_Map<int, int>(it, f);
|
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 {
|
||||||
|
acc = f(acc, it.data[i]);
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
func Iter_FilterInt(it: *Iter<int>, pred: func(int) -> bool) -> Array<int> {
|
/* Call f for each remaining element (return value of f is ignored) */
|
||||||
return Iter_Filter<int>(it, pred);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Iter_FoldInt(it: *Iter<int>, init: int, f: func(int, int) -> int) -> int {
|
/* True if any remaining element satisfies pred */
|
||||||
return Iter_Fold<int, int>(it, init, f);
|
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]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
func Iter_ForEachInt(it: *Iter<int>, f: func(int) -> int) {
|
/* True if all remaining elements satisfy pred (true if empty) */
|
||||||
Iter_ForEach<int>(it, f);
|
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]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
func Iter_AnyInt(it: *Iter<int>, pred: func(int) -> bool) -> bool {
|
/* Sum remaining ints (specialized fold) */
|
||||||
return Iter_Any<int>(it, pred);
|
func Iter_SumInt(it: *Iter<int>) -> int {
|
||||||
}
|
var total: int = 0;
|
||||||
|
var i: uint = it.pos;
|
||||||
|
while i < it.len {
|
||||||
|
total = total + it.data[i];
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
func Iter_AllInt(it: *Iter<int>, pred: func(int) -> bool) -> bool {
|
// ---------------------------------------------------------------------------
|
||||||
return Iter_All<int>(it, pred);
|
// Int-specialized aliases (backward compatible with earlier examples)
|
||||||
}
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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> {
|
||||||
|
return Iter_Filter<int>(it, pred);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
Iter_ForEach<int>(it, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return Iter_All<int>(it, pred);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+408
-408
@@ -1,297 +1,287 @@
|
|||||||
module Std::Json {
|
module Std::Json {
|
||||||
import Std::Mem::{Alloc, Realloc, Free};
|
import Std::Mem::{Alloc, Realloc, Free};
|
||||||
import Std::String;
|
import Std::String;
|
||||||
|
|
||||||
/* === Tags === */
|
/* === Tags === */
|
||||||
const JsonTagNull: int = 0;
|
const JsonTagNull: int = 0;
|
||||||
const JsonTagBool: int = 1;
|
const JsonTagBool: int = 1;
|
||||||
const JsonTagNumber: int = 2;
|
const JsonTagNumber: int = 2;
|
||||||
const JsonTagString: int = 3;
|
const JsonTagString: int = 3;
|
||||||
const JsonTagArray: int = 4;
|
const JsonTagArray: int = 4;
|
||||||
const JsonTagObject: int = 5;
|
const JsonTagObject: int = 5;
|
||||||
|
|
||||||
/* === Core type === */
|
/* === Core type === */
|
||||||
struct JsonValue {
|
struct JsonValue {
|
||||||
tag: int,
|
tag: int,
|
||||||
boolVal: bool,
|
boolVal: bool,
|
||||||
numVal: float64,
|
numVal: float64,
|
||||||
strVal: String,
|
strVal: String,
|
||||||
arrData: *JsonValue,
|
arrData: *JsonValue,
|
||||||
arrLen: uint,
|
arrLen: uint,
|
||||||
arrCap: uint,
|
arrCap: uint,
|
||||||
objKeys: *String,
|
objKeys: *String,
|
||||||
objValues: *JsonValue,
|
objValues: *JsonValue,
|
||||||
objLen: uint,
|
objLen: uint,
|
||||||
objCap: uint
|
objCap: uint
|
||||||
}
|
}
|
||||||
|
|
||||||
/* === Constructors === */
|
/* === Constructors === */
|
||||||
func Json_Null() -> JsonValue {
|
func Json_Null() -> JsonValue {
|
||||||
return JsonValue {
|
return JsonValue {
|
||||||
tag: JsonTagNull, boolVal: false, numVal: 0.0, strVal: "",
|
tag: JsonTagNull, boolVal: false, numVal: 0.0, strVal: "",
|
||||||
arrData: null, arrLen: 0, arrCap: 0,
|
arrData: null, arrLen: 0, arrCap: 0,
|
||||||
objKeys: null, objValues: null, objLen: 0, objCap: 0
|
objKeys: null, objValues: null, objLen: 0, objCap: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
func Json_Bool(b: bool) -> JsonValue {
|
func Json_Bool(b: bool) -> JsonValue {
|
||||||
return JsonValue {
|
return JsonValue {
|
||||||
tag: JsonTagBool, boolVal: b, numVal: 0.0, strVal: "",
|
tag: JsonTagBool, boolVal: b, numVal: 0.0, strVal: "",
|
||||||
arrData: null, arrLen: 0, arrCap: 0,
|
arrData: null, arrLen: 0, arrCap: 0,
|
||||||
objKeys: null, objValues: null, objLen: 0, objCap: 0
|
objKeys: null, objValues: null, objLen: 0, objCap: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
func Json_Number(n: float64) -> JsonValue {
|
func Json_Number(n: float64) -> JsonValue {
|
||||||
return JsonValue {
|
return JsonValue {
|
||||||
tag: JsonTagNumber, boolVal: false, numVal: n, strVal: "",
|
tag: JsonTagNumber, boolVal: false, numVal: n, strVal: "",
|
||||||
arrData: null, arrLen: 0, arrCap: 0,
|
arrData: null, arrLen: 0, arrCap: 0,
|
||||||
objKeys: null, objValues: null, objLen: 0, objCap: 0
|
objKeys: null, objValues: null, objLen: 0, objCap: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
func Json_String(s: String) -> JsonValue {
|
func Json_String(s: String) -> JsonValue {
|
||||||
return JsonValue {
|
return JsonValue {
|
||||||
tag: JsonTagString, boolVal: false, numVal: 0.0, strVal: s,
|
tag: JsonTagString, boolVal: false, numVal: 0.0, strVal: s,
|
||||||
arrData: null, arrLen: 0, arrCap: 0,
|
arrData: null, arrLen: 0, arrCap: 0,
|
||||||
objKeys: null, objValues: null, objLen: 0, objCap: 0
|
objKeys: null, objValues: null, objLen: 0, objCap: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
func Json_Array() -> JsonValue {
|
func Json_Array() -> JsonValue {
|
||||||
return JsonValue {
|
return JsonValue {
|
||||||
tag: JsonTagArray, boolVal: false, numVal: 0.0, strVal: "",
|
tag: JsonTagArray, boolVal: false, numVal: 0.0, strVal: "",
|
||||||
arrData: null, arrLen: 0, arrCap: 0,
|
arrData: null, arrLen: 0, arrCap: 0,
|
||||||
objKeys: null, objValues: null, objLen: 0, objCap: 0
|
objKeys: null, objValues: null, objLen: 0, objCap: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
func Json_Object() -> JsonValue {
|
func Json_Object() -> JsonValue {
|
||||||
return JsonValue {
|
return JsonValue {
|
||||||
tag: JsonTagObject, boolVal: false, numVal: 0.0, strVal: "",
|
tag: JsonTagObject, boolVal: false, numVal: 0.0, strVal: "",
|
||||||
arrData: null, arrLen: 0, arrCap: 0,
|
arrData: null, arrLen: 0, arrCap: 0,
|
||||||
objKeys: null, objValues: null, objLen: 0, objCap: 0
|
objKeys: null, objValues: null, objLen: 0, objCap: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* === Array helpers === */
|
/* === Array helpers === */
|
||||||
func Json_ArrayLen(v: JsonValue) -> uint {
|
func Json_ArrayLen(v: JsonValue) -> uint {
|
||||||
if v.tag != JsonTagArray { return 0; }
|
if v.tag != JsonTagArray { return 0; }
|
||||||
return v.arrLen;
|
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 v.tag != JsonTagArray { return Json_Null(); }
|
||||||
if index >= v.arrLen { return Json_Null(); }
|
if index >= v.arrLen { return Json_Null(); }
|
||||||
return v.arrData[index];
|
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.tag != JsonTagArray { return; }
|
||||||
if self.arrLen >= self.arrCap {
|
if self.arrLen >= self.arrCap {
|
||||||
let arrNewCap: uint = self.arrCap;
|
let arrNewCap: uint = self.arrCap;
|
||||||
if arrNewCap == 0 {
|
if arrNewCap == 0 {
|
||||||
self.arrCap = 4;
|
self.arrCap = 4;
|
||||||
self.arrData = Alloc(4 * sizeof(JsonValue)) as *JsonValue;
|
self.arrData = Alloc(4 * sizeof(JsonValue)) as *JsonValue;
|
||||||
} else {
|
} else {
|
||||||
let doubleCap: uint = arrNewCap * 2;
|
let doubleCap: uint = arrNewCap * 2;
|
||||||
self.arrCap = doubleCap;
|
self.arrCap = doubleCap;
|
||||||
self.arrData = Realloc(self.arrData as *void, doubleCap * sizeof(JsonValue)) as *JsonValue;
|
self.arrData = Realloc(self.arrData as *void, doubleCap * sizeof(JsonValue)) as *JsonValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
self.arrData[self.arrLen] = val;
|
||||||
|
self.arrLen = self.arrLen + 1;
|
||||||
}
|
}
|
||||||
self.arrData[self.arrLen] = val;
|
|
||||||
self.arrLen = self.arrLen + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* === Object helpers === */
|
/* === Object helpers === */
|
||||||
func Json_ObjectLen(v: JsonValue) -> uint {
|
func Json_ObjectLen(v: JsonValue) -> uint {
|
||||||
if v.tag != JsonTagObject { return 0; }
|
if v.tag != JsonTagObject { return 0; }
|
||||||
return v.objLen;
|
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(); }
|
if v.tag != JsonTagObject { return Json_Null(); }
|
||||||
var i: uint = 0;
|
var i: uint = 0;
|
||||||
while i < v.objLen {
|
while i < v.objLen {
|
||||||
if String_Eq(v.objKeys[i], key) {
|
if String_Eq(v.objKeys[i], key) {
|
||||||
return v.objValues[i];
|
return v.objValues[i];
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
}
|
}
|
||||||
i = i + 1;
|
return Json_Null();
|
||||||
}
|
}
|
||||||
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; }
|
if v.tag != JsonTagObject { return false; }
|
||||||
var i: uint = 0;
|
var i: uint = 0;
|
||||||
while i < v.objLen {
|
while i < v.objLen {
|
||||||
if String_Eq(v.objKeys[i], key) {
|
if String_Eq(v.objKeys[i], key) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
}
|
}
|
||||||
i = i + 1;
|
return false;
|
||||||
}
|
}
|
||||||
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; }
|
if self.tag != JsonTagObject { return; }
|
||||||
var i: uint = 0;
|
var i: uint = 0;
|
||||||
while i < self.objLen {
|
while i < self.objLen {
|
||||||
if String_Eq(self.objKeys[i], key) {
|
if String_Eq(self.objKeys[i], key) {
|
||||||
self.objValues[i] = val;
|
self.objValues[i] = val;
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
}
|
}
|
||||||
i = i + 1;
|
if self.objLen >= self.objCap {
|
||||||
}
|
let objNewCap: uint = self.objCap;
|
||||||
if self.objLen >= self.objCap {
|
if objNewCap == 0 {
|
||||||
let objNewCap: uint = self.objCap;
|
self.objCap = 4;
|
||||||
if objNewCap == 0 {
|
self.objKeys = Alloc(4 * sizeof(String)) as *String;
|
||||||
self.objCap = 4;
|
self.objValues = Alloc(4 * sizeof(JsonValue)) as *JsonValue;
|
||||||
self.objKeys = Alloc(4 * sizeof(String)) as *String;
|
} else {
|
||||||
self.objValues = Alloc(4 * sizeof(JsonValue)) as *JsonValue;
|
let doubleCap: uint = objNewCap * 2;
|
||||||
} else {
|
self.objCap = doubleCap;
|
||||||
let doubleCap: uint = objNewCap * 2;
|
self.objKeys = Realloc(self.objKeys as *void, doubleCap * sizeof(String)) as *String;
|
||||||
self.objCap = doubleCap;
|
self.objValues = Realloc(self.objValues as *void, doubleCap * sizeof(JsonValue)) as *JsonValue;
|
||||||
self.objKeys = Realloc(self.objKeys as *void, doubleCap * sizeof(String)) as *String;
|
}
|
||||||
self.objValues = Realloc(self.objValues as *void, doubleCap * sizeof(JsonValue)) as *JsonValue;
|
|
||||||
}
|
}
|
||||||
|
self.objKeys[self.objLen] = key;
|
||||||
|
self.objValues[self.objLen] = val;
|
||||||
|
self.objLen = self.objLen + 1;
|
||||||
}
|
}
|
||||||
self.objKeys[self.objLen] = key;
|
|
||||||
self.objValues[self.objLen] = val;
|
|
||||||
self.objLen = self.objLen + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* === Accessors === */
|
/* === Accessors === */
|
||||||
func Json_IsNull(v: JsonValue) -> bool {
|
func Json_IsNull(v: JsonValue) -> bool {
|
||||||
return v.tag == JsonTagNull;
|
return v.tag == JsonTagNull;
|
||||||
}
|
|
||||||
|
|
||||||
func Json_AsBool(v: JsonValue) -> bool {
|
|
||||||
if v.tag == JsonTagBool { return v.boolVal; }
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
func Json_AsNumber(v: JsonValue) -> float64 {
|
|
||||||
if v.tag == JsonTagNumber { return v.numVal; }
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
func Json_AsString(v: JsonValue) -> String {
|
|
||||||
if v.tag == JsonTagString { return v.strVal; }
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* === Parser === */
|
|
||||||
struct JsonParser {
|
|
||||||
src: String,
|
|
||||||
pos: uint,
|
|
||||||
len: uint,
|
|
||||||
error: String
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
if p.pos < p.len {
|
|
||||||
p.pos = p.pos + 1;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func JsonParser_SkipWhitespace(p: *JsonParser) {
|
func Json_AsBool(v: JsonValue) -> bool {
|
||||||
while true {
|
if v.tag == JsonTagBool { return v.boolVal; }
|
||||||
let c: int = JsonParser_Peek(p);
|
return false;
|
||||||
if c == 32 || c == 9 || c == 10 || c == 13 {
|
|
||||||
JsonParser_Advance(p);
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func JsonParser_Match(p: *JsonParser, expected: String) -> bool {
|
func Json_AsNumber(v: JsonValue) -> float64 {
|
||||||
let elen: uint = String_Len(expected);
|
if v.tag == JsonTagNumber { return v.numVal; }
|
||||||
if p.pos + elen > p.len { return false; }
|
return 0.0;
|
||||||
var i: uint = 0;
|
|
||||||
while i < elen {
|
|
||||||
if p.src[p.pos + i] != expected[i] {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
i = i + 1;
|
|
||||||
}
|
}
|
||||||
p.pos = p.pos + elen;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
func JsonParser_ParseValue(p: *JsonParser) -> JsonValue;
|
func Json_AsString(v: JsonValue) -> String {
|
||||||
|
if v.tag == JsonTagString { return v.strVal; }
|
||||||
func JsonParser_ParseString(p: *JsonParser) -> String {
|
|
||||||
if JsonParser_Peek(p) != 34 {
|
|
||||||
p.error = "Expected string";
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
JsonParser_Advance(p);
|
|
||||||
let sb: StringBuilder = StringBuilder_New();
|
/* === Parser === */
|
||||||
while true {
|
struct JsonParser {
|
||||||
let c: int = JsonParser_Peek(p);
|
src: String,
|
||||||
if c == 0 || c == 34 {
|
pos: uint,
|
||||||
break;
|
len: uint,
|
||||||
}
|
error: String
|
||||||
if c == 92 {
|
}
|
||||||
JsonParser_Advance(p);
|
|
||||||
let esc: int = JsonParser_Peek(p);
|
func JsonParser_Peek(p: *JsonParser) -> int {
|
||||||
if esc == 0 {
|
if p.pos >= p.len { return 0; }
|
||||||
p.error = "Unterminated string escape";
|
return p.src[p.pos] as int;
|
||||||
StringBuilder_Free(&sb);
|
}
|
||||||
return "";
|
|
||||||
}
|
func JsonParser_Advance(p: *JsonParser) {
|
||||||
if esc == 110 { StringBuilder_AppendChar(&sb, 10 as char8); } // \n
|
if p.pos < p.len {
|
||||||
else if esc == 116 { StringBuilder_AppendChar(&sb, 9 as char8); } // \t
|
p.pos = p.pos + 1;
|
||||||
else if esc == 114 { StringBuilder_AppendChar(&sb, 13 as char8); } // \r
|
|
||||||
else if esc == 98 { StringBuilder_AppendChar(&sb, 8 as char8); } // \b
|
|
||||||
else if esc == 102 { StringBuilder_AppendChar(&sb, 12 as char8); } // \f
|
|
||||||
else if esc == 34 { StringBuilder_AppendChar(&sb, 34 as char8); } // \"
|
|
||||||
else if esc == 92 { StringBuilder_AppendChar(&sb, 92 as char8); } // \\
|
|
||||||
else {
|
|
||||||
// Unknown escape — keep literal
|
|
||||||
StringBuilder_AppendChar(&sb, 92 as char8);
|
|
||||||
StringBuilder_AppendChar(&sb, esc as char8);
|
|
||||||
}
|
|
||||||
JsonParser_Advance(p);
|
|
||||||
} else {
|
|
||||||
StringBuilder_AppendChar(&sb, c as char8);
|
|
||||||
JsonParser_Advance(p);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if JsonParser_Peek(p) != 34 {
|
|
||||||
p.error = "Unterminated string";
|
func JsonParser_SkipWhitespace(p: *JsonParser) {
|
||||||
|
while true {
|
||||||
|
let c: int = JsonParser_Peek(p);
|
||||||
|
if c == 32 || c == 9 || c == 10 || c == 13 {
|
||||||
|
JsonParser_Advance(p);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
while i < elen {
|
||||||
|
if p.src[p.pos + i] != expected[i] {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
p.pos = p.pos + elen;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
func JsonParser_ParseValue(p: *JsonParser) -> JsonValue;
|
||||||
|
|
||||||
|
func JsonParser_ParseString(p: *JsonParser) -> String {
|
||||||
|
if JsonParser_Peek(p) != 34 {
|
||||||
|
p.error = "Expected string";
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
JsonParser_Advance(p);
|
||||||
|
let sb: StringBuilder = StringBuilder_New();
|
||||||
|
while true {
|
||||||
|
let c: int = JsonParser_Peek(p);
|
||||||
|
if c == 0 || c == 34 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if c == 92 {
|
||||||
|
JsonParser_Advance(p);
|
||||||
|
let esc: int = JsonParser_Peek(p);
|
||||||
|
if esc == 0 {
|
||||||
|
p.error = "Unterminated string escape";
|
||||||
|
StringBuilder_Free(&sb);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if esc == 110 { StringBuilder_AppendChar(&sb, 10 as char8); } // \n
|
||||||
|
else if esc == 116 { StringBuilder_AppendChar(&sb, 9 as char8); } // \t
|
||||||
|
else if esc == 114 { StringBuilder_AppendChar(&sb, 13 as char8); } // \r
|
||||||
|
else if esc == 98 { StringBuilder_AppendChar(&sb, 8 as char8); } // \b
|
||||||
|
else if esc == 102 { StringBuilder_AppendChar(&sb, 12 as char8); } // \f
|
||||||
|
else if esc == 34 { StringBuilder_AppendChar(&sb, 34 as char8); } // \"
|
||||||
|
else if esc == 92 { StringBuilder_AppendChar(&sb, 92 as char8); } // \\
|
||||||
|
else {
|
||||||
|
// Unknown escape — keep literal
|
||||||
|
StringBuilder_AppendChar(&sb, 92 as char8);
|
||||||
|
StringBuilder_AppendChar(&sb, esc as char8);
|
||||||
|
}
|
||||||
|
JsonParser_Advance(p);
|
||||||
|
} else {
|
||||||
|
StringBuilder_AppendChar(&sb, c as char8);
|
||||||
|
JsonParser_Advance(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if JsonParser_Peek(p) != 34 {
|
||||||
|
p.error = "Unterminated string";
|
||||||
|
StringBuilder_Free(&sb);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
JsonParser_Advance(p);
|
||||||
|
let result: String = StringBuilder_Build(&sb);
|
||||||
StringBuilder_Free(&sb);
|
StringBuilder_Free(&sb);
|
||||||
return "";
|
return result;
|
||||||
}
|
}
|
||||||
JsonParser_Advance(p);
|
|
||||||
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 start: uint = p.pos;
|
||||||
let c0: int = JsonParser_Peek(p);
|
let c0: int = JsonParser_Peek(p);
|
||||||
if c0 == 45 {
|
if c0 == 45 {
|
||||||
JsonParser_Advance(p);
|
|
||||||
}
|
|
||||||
while true {
|
|
||||||
let c: int = JsonParser_Peek(p);
|
|
||||||
if c >= 48 && c <= 57 {
|
|
||||||
JsonParser_Advance(p);
|
JsonParser_Advance(p);
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if JsonParser_Peek(p) == 46 {
|
|
||||||
JsonParser_Advance(p);
|
|
||||||
while true {
|
while true {
|
||||||
let c: int = JsonParser_Peek(p);
|
let c: int = JsonParser_Peek(p);
|
||||||
if c >= 48 && c <= 57 {
|
if c >= 48 && c <= 57 {
|
||||||
@@ -300,193 +290,203 @@ func JsonParser_ParseNumber(p: *JsonParser) -> JsonValue {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if JsonParser_Peek(p) == 46 {
|
||||||
|
JsonParser_Advance(p);
|
||||||
|
while true {
|
||||||
|
let c: int = JsonParser_Peek(p);
|
||||||
|
if c >= 48 && c <= 57 {
|
||||||
|
JsonParser_Advance(p);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let numStr: String = String_Slice(p.src, start, p.pos - start);
|
||||||
|
let n: float64 = String_ToFloat(numStr);
|
||||||
|
return Json_Number(n);
|
||||||
}
|
}
|
||||||
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);
|
|
||||||
if JsonParser_Peek(p) == 93 {
|
|
||||||
JsonParser_Advance(p);
|
JsonParser_Advance(p);
|
||||||
return arr;
|
var arr: JsonValue = Json_Array();
|
||||||
}
|
|
||||||
while true {
|
|
||||||
JsonParser_SkipWhitespace(p);
|
JsonParser_SkipWhitespace(p);
|
||||||
let val: JsonValue = JsonParser_ParseValue(p);
|
if JsonParser_Peek(p) == 93 {
|
||||||
if p.error != "" { return Json_Null(); }
|
|
||||||
Json_ArrayPush(&arr, val);
|
|
||||||
JsonParser_SkipWhitespace(p);
|
|
||||||
let c: int = JsonParser_Peek(p);
|
|
||||||
if c == 93 {
|
|
||||||
JsonParser_Advance(p);
|
JsonParser_Advance(p);
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
if c == 44 {
|
while true {
|
||||||
JsonParser_Advance(p);
|
JsonParser_SkipWhitespace(p);
|
||||||
} else {
|
let val: JsonValue = JsonParser_ParseValue(p);
|
||||||
p.error = "Expected ',' or ']' in array";
|
if p.error != "" { return Json_Null(); }
|
||||||
return Json_Null();
|
Json_ArrayPush(&arr, val);
|
||||||
|
JsonParser_SkipWhitespace(p);
|
||||||
|
let c: int = JsonParser_Peek(p);
|
||||||
|
if c == 93 {
|
||||||
|
JsonParser_Advance(p);
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
if c == 44 {
|
||||||
|
JsonParser_Advance(p);
|
||||||
|
} else {
|
||||||
|
p.error = "Expected ',' or ']' in array";
|
||||||
|
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);
|
|
||||||
if JsonParser_Peek(p) == 125 {
|
|
||||||
JsonParser_Advance(p);
|
JsonParser_Advance(p);
|
||||||
return obj;
|
var obj: JsonValue = Json_Object();
|
||||||
}
|
|
||||||
while true {
|
|
||||||
JsonParser_SkipWhitespace(p);
|
JsonParser_SkipWhitespace(p);
|
||||||
let key: String = JsonParser_ParseString(p);
|
if JsonParser_Peek(p) == 125 {
|
||||||
if p.error != "" { return Json_Null(); }
|
|
||||||
JsonParser_SkipWhitespace(p);
|
|
||||||
if JsonParser_Peek(p) != 58 {
|
|
||||||
p.error = "Expected ':' after object key";
|
|
||||||
return Json_Null();
|
|
||||||
}
|
|
||||||
JsonParser_Advance(p);
|
|
||||||
JsonParser_SkipWhitespace(p);
|
|
||||||
let val: JsonValue = JsonParser_ParseValue(p);
|
|
||||||
if p.error != "" { return Json_Null(); }
|
|
||||||
Json_ObjectSet(&obj, key, val);
|
|
||||||
JsonParser_SkipWhitespace(p);
|
|
||||||
let c: int = JsonParser_Peek(p);
|
|
||||||
if c == 125 {
|
|
||||||
JsonParser_Advance(p);
|
JsonParser_Advance(p);
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
if c == 44 {
|
while true {
|
||||||
|
JsonParser_SkipWhitespace(p);
|
||||||
|
let key: String = JsonParser_ParseString(p);
|
||||||
|
if p.error != "" { return Json_Null(); }
|
||||||
|
JsonParser_SkipWhitespace(p);
|
||||||
|
if JsonParser_Peek(p) != 58 {
|
||||||
|
p.error = "Expected ':' after object key";
|
||||||
|
return Json_Null();
|
||||||
|
}
|
||||||
JsonParser_Advance(p);
|
JsonParser_Advance(p);
|
||||||
} else {
|
JsonParser_SkipWhitespace(p);
|
||||||
p.error = "Expected ',' or '}' in object";
|
let val: JsonValue = JsonParser_ParseValue(p);
|
||||||
|
if p.error != "" { return Json_Null(); }
|
||||||
|
Json_ObjectSet(&obj, key, val);
|
||||||
|
JsonParser_SkipWhitespace(p);
|
||||||
|
let c: int = JsonParser_Peek(p);
|
||||||
|
if c == 125 {
|
||||||
|
JsonParser_Advance(p);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
if c == 44 {
|
||||||
|
JsonParser_Advance(p);
|
||||||
|
} else {
|
||||||
|
p.error = "Expected ',' or '}' in object";
|
||||||
|
return Json_Null();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func JsonParser_ParseValue(p: *JsonParser) -> JsonValue {
|
||||||
|
JsonParser_SkipWhitespace(p);
|
||||||
|
let c: int = JsonParser_Peek(p);
|
||||||
|
if c == 0 {
|
||||||
|
p.error = "Unexpected end of input";
|
||||||
return Json_Null();
|
return Json_Null();
|
||||||
}
|
}
|
||||||
}
|
if c == 34 {
|
||||||
}
|
return Json_String(JsonParser_ParseString(p));
|
||||||
|
|
||||||
func JsonParser_ParseValue(p: *JsonParser) -> JsonValue {
|
|
||||||
JsonParser_SkipWhitespace(p);
|
|
||||||
let c: int = JsonParser_Peek(p);
|
|
||||||
if c == 0 {
|
|
||||||
p.error = "Unexpected end of input";
|
|
||||||
return Json_Null();
|
|
||||||
}
|
|
||||||
if c == 34 {
|
|
||||||
return Json_String(JsonParser_ParseString(p));
|
|
||||||
}
|
|
||||||
if c == 123 {
|
|
||||||
return JsonParser_ParseObject(p);
|
|
||||||
}
|
|
||||||
if c == 91 {
|
|
||||||
return JsonParser_ParseArray(p);
|
|
||||||
}
|
|
||||||
if c == 116 {
|
|
||||||
if JsonParser_Match(p, "true") {
|
|
||||||
return Json_Bool(true);
|
|
||||||
}
|
}
|
||||||
p.error = "Expected 'true'";
|
if c == 123 {
|
||||||
return Json_Null();
|
return JsonParser_ParseObject(p);
|
||||||
}
|
|
||||||
if c == 102 {
|
|
||||||
if JsonParser_Match(p, "false") {
|
|
||||||
return Json_Bool(false);
|
|
||||||
}
|
}
|
||||||
p.error = "Expected 'false'";
|
if c == 91 {
|
||||||
return Json_Null();
|
return JsonParser_ParseArray(p);
|
||||||
}
|
}
|
||||||
if c == 110 {
|
if c == 116 {
|
||||||
if JsonParser_Match(p, "null") {
|
if JsonParser_Match(p, "true") {
|
||||||
|
return Json_Bool(true);
|
||||||
|
}
|
||||||
|
p.error = "Expected 'true'";
|
||||||
return Json_Null();
|
return Json_Null();
|
||||||
}
|
}
|
||||||
p.error = "Expected 'null'";
|
if c == 102 {
|
||||||
|
if JsonParser_Match(p, "false") {
|
||||||
|
return Json_Bool(false);
|
||||||
|
}
|
||||||
|
p.error = "Expected 'false'";
|
||||||
|
return Json_Null();
|
||||||
|
}
|
||||||
|
if c == 110 {
|
||||||
|
if JsonParser_Match(p, "null") {
|
||||||
|
return Json_Null();
|
||||||
|
}
|
||||||
|
p.error = "Expected 'null'";
|
||||||
|
return Json_Null();
|
||||||
|
}
|
||||||
|
if (c >= 48 && c <= 57) || c == 45 {
|
||||||
|
return JsonParser_ParseNumber(p);
|
||||||
|
}
|
||||||
|
p.error = "Unexpected character";
|
||||||
return Json_Null();
|
return Json_Null();
|
||||||
}
|
}
|
||||||
if (c >= 48 && c <= 57) || c == 45 {
|
|
||||||
return JsonParser_ParseNumber(p);
|
|
||||||
}
|
|
||||||
p.error = "Unexpected character";
|
|
||||||
return Json_Null();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* === Public parser === */
|
/* === Public parser === */
|
||||||
func Json_Parse(s: String) -> JsonValue {
|
func Json_Parse(s: String) -> JsonValue {
|
||||||
var p: JsonParser = JsonParser { src: s, pos: 0, len: String_Len(s), error: "" };
|
var p: JsonParser = JsonParser { src: s, pos: 0, len: String_Len(s), error: "" };
|
||||||
let result: JsonValue = JsonParser_ParseValue(&p);
|
let result: JsonValue = JsonParser_ParseValue(&p);
|
||||||
JsonParser_SkipWhitespace(&p);
|
JsonParser_SkipWhitespace(&p);
|
||||||
if p.error == "" && p.pos != p.len {
|
if p.error == "" && p.pos != p.len {
|
||||||
p.error = "Trailing data after JSON value";
|
p.error = "Trailing data after JSON value";
|
||||||
return Json_Null();
|
return Json_Null();
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* === Serializer === */
|
|
||||||
func Json_StringifyImpl(sb: *StringBuilder, v: JsonValue) {
|
|
||||||
if v.tag == JsonTagNull {
|
|
||||||
StringBuilder_Append(sb, "null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if v.tag == JsonTagBool {
|
|
||||||
if v.boolVal {
|
|
||||||
StringBuilder_Append(sb, "true");
|
|
||||||
} else {
|
|
||||||
StringBuilder_Append(sb, "false");
|
|
||||||
}
|
}
|
||||||
return;
|
return result;
|
||||||
}
|
}
|
||||||
if v.tag == JsonTagNumber {
|
|
||||||
StringBuilder_AppendFloat(sb, v.numVal);
|
/* === Serializer === */
|
||||||
return;
|
func Json_StringifyImpl(sb: *StringBuilder, v: JsonValue) {
|
||||||
}
|
if v.tag == JsonTagNull {
|
||||||
if v.tag == JsonTagString {
|
StringBuilder_Append(sb, "null");
|
||||||
StringBuilder_AppendChar(sb, 34 as char8);
|
return;
|
||||||
StringBuilder_Append(sb, v.strVal);
|
}
|
||||||
StringBuilder_AppendChar(sb, 34 as char8);
|
if v.tag == JsonTagBool {
|
||||||
return;
|
if v.boolVal {
|
||||||
}
|
StringBuilder_Append(sb, "true");
|
||||||
if v.tag == JsonTagArray {
|
} else {
|
||||||
StringBuilder_AppendChar(sb, 91 as char8);
|
StringBuilder_Append(sb, "false");
|
||||||
var i: uint = 0;
|
|
||||||
while i < v.arrLen {
|
|
||||||
if i > 0 {
|
|
||||||
StringBuilder_AppendChar(sb, 44 as char8);
|
|
||||||
}
|
}
|
||||||
Json_StringifyImpl(sb, v.arrData[i]);
|
return;
|
||||||
i = i + 1;
|
|
||||||
}
|
}
|
||||||
StringBuilder_AppendChar(sb, 93 as char8);
|
if v.tag == JsonTagNumber {
|
||||||
return;
|
StringBuilder_AppendFloat(sb, v.numVal);
|
||||||
}
|
return;
|
||||||
if v.tag == JsonTagObject {
|
}
|
||||||
StringBuilder_AppendChar(sb, 123 as char8);
|
if v.tag == JsonTagString {
|
||||||
var i: uint = 0;
|
StringBuilder_AppendChar(sb, 34 as char8);
|
||||||
while i < v.objLen {
|
StringBuilder_Append(sb, v.strVal);
|
||||||
if i > 0 {
|
StringBuilder_AppendChar(sb, 34 as char8);
|
||||||
StringBuilder_AppendChar(sb, 44 as char8);
|
return;
|
||||||
|
}
|
||||||
|
if v.tag == JsonTagArray {
|
||||||
|
StringBuilder_AppendChar(sb, 91 as char8);
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < v.arrLen {
|
||||||
|
if i > 0 {
|
||||||
|
StringBuilder_AppendChar(sb, 44 as char8);
|
||||||
|
}
|
||||||
|
Json_StringifyImpl(sb, v.arrData[i]);
|
||||||
|
i = i + 1;
|
||||||
}
|
}
|
||||||
StringBuilder_AppendChar(sb, 34 as char8);
|
StringBuilder_AppendChar(sb, 93 as char8);
|
||||||
StringBuilder_Append(sb, v.objKeys[i]);
|
return;
|
||||||
StringBuilder_AppendChar(sb, 34 as char8);
|
}
|
||||||
StringBuilder_AppendChar(sb, 58 as char8);
|
if v.tag == JsonTagObject {
|
||||||
Json_StringifyImpl(sb, v.objValues[i]);
|
StringBuilder_AppendChar(sb, 123 as char8);
|
||||||
i = i + 1;
|
var i: uint = 0;
|
||||||
|
while i < v.objLen {
|
||||||
|
if i > 0 {
|
||||||
|
StringBuilder_AppendChar(sb, 44 as char8);
|
||||||
|
}
|
||||||
|
StringBuilder_AppendChar(sb, 34 as char8);
|
||||||
|
StringBuilder_Append(sb, v.objKeys[i]);
|
||||||
|
StringBuilder_AppendChar(sb, 34 as char8);
|
||||||
|
StringBuilder_AppendChar(sb, 58 as char8);
|
||||||
|
Json_StringifyImpl(sb, v.objValues[i]);
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
StringBuilder_AppendChar(sb, 125 as char8);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
StringBuilder_AppendChar(sb, 125 as char8);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func Json_Stringify(v: JsonValue) -> String {
|
func Json_Stringify(v: JsonValue) -> String {
|
||||||
let sb: StringBuilder = StringBuilder_New();
|
let sb: StringBuilder = StringBuilder_New();
|
||||||
Json_StringifyImpl(&sb, v);
|
Json_StringifyImpl(&sb, v);
|
||||||
return StringBuilder_Build(&sb);
|
return StringBuilder_Build(&sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+211
-203
@@ -1,242 +1,250 @@
|
|||||||
module Std::Map {
|
module Std::Map {
|
||||||
|
|
||||||
extern func bux_hash_bytes(ptr: *void, size: uint) -> uint;
|
extern func bux_hash_bytes(ptr: *void, size: uint) -> uint;
|
||||||
extern func bux_hash_string(s: String) -> uint;
|
extern func bux_hash_string(s: String) -> uint;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Generic Map<K, V> — works with value-type keys (int, float, etc.)
|
// Generic Map<K, V> — works with value-type keys (int, float, etc.)
|
||||||
// For String keys, use StringMap below.
|
// For String keys, use StringMap below.
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
struct MapEntry<K, V> {
|
struct MapEntry<K, V> {
|
||||||
key: K,
|
key: K,
|
||||||
value: V,
|
value: V,
|
||||||
occupied: bool,
|
occupied: bool,
|
||||||
}
|
|
||||||
|
|
||||||
struct Map<K, V> {
|
|
||||||
entries: *MapEntry<K, V>,
|
|
||||||
cap: uint,
|
|
||||||
len: uint,
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
while i < cap {
|
|
||||||
data[i].occupied = false;
|
|
||||||
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) {
|
struct Map<K, V> {
|
||||||
var keyPtr: *K = &key;
|
entries: *MapEntry<K, V>,
|
||||||
let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K));
|
cap: uint,
|
||||||
var idx: uint = hash % m.cap;
|
len: uint,
|
||||||
while m.entries[idx].occupied {
|
}
|
||||||
if m.entries[idx].key == key {
|
|
||||||
m.entries[idx].value = value;
|
func Map_New<K, V>(cap: uint) -> Map<K, V> {
|
||||||
return;
|
let total: uint = cap * sizeof(MapEntry<K, V>);
|
||||||
|
let data: *MapEntry<K, V> = bux_alloc(total) as *MapEntry<K, V>;
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < cap {
|
||||||
|
data[i].occupied = false;
|
||||||
|
i = i + 1;
|
||||||
}
|
}
|
||||||
idx = (idx + 1) % m.cap;
|
return Map<K, V> { entries: data, cap: cap, len: 0 };
|
||||||
}
|
}
|
||||||
m.entries[idx].key = key;
|
|
||||||
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_Set<K, V>(m: *Map<K, V>, key: K, value: V) {
|
||||||
var keyPtr: *K = &key;
|
var keyPtr: *K = &key;
|
||||||
let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K));
|
let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K));
|
||||||
var idx: uint = hash % m.cap;
|
var idx: uint = hash % m.cap;
|
||||||
while m.entries[idx].occupied {
|
while m.entries[idx].occupied {
|
||||||
if m.entries[idx].key == key {
|
if m.entries[idx].key == key {
|
||||||
return m.entries[idx].value;
|
m.entries[idx].value = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
idx = (idx + 1) % m.cap;
|
||||||
}
|
}
|
||||||
idx = (idx + 1) % m.cap;
|
m.entries[idx].key = key;
|
||||||
|
m.entries[idx].value = value;
|
||||||
|
m.entries[idx].occupied = true;
|
||||||
|
m.len = m.len + 1;
|
||||||
}
|
}
|
||||||
// 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_Get<K, V>(m: *Map<K, V>, key: K) -> V {
|
||||||
var keyPtr: *K = &key;
|
var keyPtr: *K = &key;
|
||||||
let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K));
|
let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K));
|
||||||
var idx: uint = hash % m.cap;
|
var idx: uint = hash % m.cap;
|
||||||
while m.entries[idx].occupied {
|
while m.entries[idx].occupied {
|
||||||
if m.entries[idx].key == key {
|
if m.entries[idx].key == key {
|
||||||
return true;
|
return m.entries[idx].value;
|
||||||
|
}
|
||||||
|
idx = (idx + 1) % m.cap;
|
||||||
}
|
}
|
||||||
idx = (idx + 1) % m.cap;
|
// Return zero value for missing key
|
||||||
|
var zero: V = 0 as V;
|
||||||
|
return zero;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
func Map_Len<K, V>(m: *Map<K, V>) -> uint {
|
func Map_Has<K, V>(m: *Map<K, V>, key: K) -> bool {
|
||||||
return m.len;
|
var keyPtr: *K = &key;
|
||||||
}
|
let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K));
|
||||||
|
var idx: uint = hash % m.cap;
|
||||||
func Map_IsEmpty<K, V>(m: *Map<K, V>) -> bool {
|
while m.entries[idx].occupied {
|
||||||
return m.len == 0;
|
if m.entries[idx].key == key {
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
/* Remove key if present. Rebuilds the table to keep open-addressing correct. */
|
idx = (idx + 1) % m.cap;
|
||||||
func Map_Remove<K, V>(m: *Map<K, V>, key: K) -> bool {
|
}
|
||||||
if !Map_Has<K, V>(m, key) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var fresh: Map<K, V> = Map_New<K, V>(m.cap);
|
|
||||||
var i: uint = 0;
|
func Map_Len<K, V>(m: *Map<K, V>) -> uint {
|
||||||
while i < m.cap {
|
return m.len;
|
||||||
if m.entries[i].occupied {
|
}
|
||||||
if m.entries[i].key != key {
|
|
||||||
Map_Set<K, V>(&fresh, m.entries[i].key, m.entries[i].value);
|
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 {
|
||||||
|
if !Map_Has<K, V>(m, key) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var fresh: Map<K, V> = Map_New<K, V>(m.cap);
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < m.cap {
|
||||||
|
if m.entries[i].occupied {
|
||||||
|
if m.entries[i].key != key {
|
||||||
|
Map_Set<K, V>(&fresh, m.entries[i].key, m.entries[i].value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
i = i + 1;
|
||||||
}
|
}
|
||||||
i = i + 1;
|
bux_free(m.entries as *void);
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
bux_free(m.entries as *void);
|
|
||||||
m.entries = fresh.entries;
|
|
||||||
m.cap = fresh.cap;
|
|
||||||
m.len = fresh.len;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
func Map_Clear<K, V>(m: *Map<K, V>) {
|
func Map_Clear<K, V>(m: *Map<K, V>) {
|
||||||
var i: uint = 0;
|
var i: uint = 0;
|
||||||
while i < m.cap {
|
while i < m.cap {
|
||||||
m.entries[i].occupied = false;
|
m.entries[i].occupied = false;
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
}
|
|
||||||
m.len = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
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>) {
|
|
||||||
Map_Free<K, V>(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// StringMap<V> — specialized Map for String keys, using strcmp
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
struct StringMapEntry<V> {
|
|
||||||
key: String,
|
|
||||||
value: V,
|
|
||||||
occupied: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct StringMap<V> {
|
|
||||||
entries: *StringMapEntry<V>,
|
|
||||||
cap: uint,
|
|
||||||
len: uint,
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
while i < cap {
|
|
||||||
data[i].occupied = false;
|
|
||||||
i = i + 1;
|
|
||||||
}
|
|
||||||
return StringMap<V> { entries: data, cap: cap, len: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
if String_Eq(m.entries[idx].key, key) {
|
|
||||||
m.entries[idx].value = value;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
idx = (idx + 1) % m.cap;
|
m.len = 0;
|
||||||
}
|
}
|
||||||
m.entries[idx].key = key;
|
|
||||||
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 Map_Free<K, V>(m: *Map<K, V>) {
|
||||||
let hash: uint = bux_hash_string(key);
|
bux_free(m.entries as *void);
|
||||||
var idx: uint = hash % m.cap;
|
m.entries = null as *MapEntry<K, V>;
|
||||||
while m.entries[idx].occupied {
|
m.cap = 0;
|
||||||
if String_Eq(m.entries[idx].key, key) {
|
m.len = 0;
|
||||||
return m.entries[idx].value;
|
}
|
||||||
|
|
||||||
|
func Map_Drop<K, V>(m: *Map<K, V>) {
|
||||||
|
Map_Free<K, V>(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// StringMap<V> — specialized Map for String keys, using strcmp
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
struct StringMapEntry<V> {
|
||||||
|
key: String,
|
||||||
|
value: V,
|
||||||
|
occupied: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StringMap<V> {
|
||||||
|
entries: *StringMapEntry<V>,
|
||||||
|
cap: uint,
|
||||||
|
len: uint,
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
while i < cap {
|
||||||
|
data[i].occupied = false;
|
||||||
|
i = i + 1;
|
||||||
}
|
}
|
||||||
idx = (idx + 1) % m.cap;
|
return StringMap<V> { entries: data, cap: cap, len: 0 };
|
||||||
}
|
}
|
||||||
var zero: V = 0 as V;
|
|
||||||
return zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
func StringMap_Has<V>(m: *StringMap<V>, key: String) -> bool {
|
func StringMap_Set<V>(m: *StringMap<V>, key: String, value: V) {
|
||||||
let hash: uint = bux_hash_string(key);
|
let hash: uint = bux_hash_string(key);
|
||||||
var idx: uint = hash % m.cap;
|
var idx: uint = hash % m.cap;
|
||||||
while m.entries[idx].occupied {
|
while m.entries[idx].occupied {
|
||||||
if String_Eq(m.entries[idx].key, key) {
|
if String_Eq(m.entries[idx].key, key) {
|
||||||
return true;
|
m.entries[idx].value = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
idx = (idx + 1) % m.cap;
|
||||||
}
|
}
|
||||||
idx = (idx + 1) % m.cap;
|
m.entries[idx].key = key;
|
||||||
|
m.entries[idx].value = value;
|
||||||
|
m.entries[idx].occupied = true;
|
||||||
|
m.len = m.len + 1;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
func StringMap_Len<V>(m: *StringMap<V>) -> uint {
|
func StringMap_Get<V>(m: *StringMap<V>, key: String) -> V {
|
||||||
return m.len;
|
let hash: uint = bux_hash_string(key);
|
||||||
}
|
var idx: uint = hash % m.cap;
|
||||||
|
while m.entries[idx].occupied {
|
||||||
|
if String_Eq(m.entries[idx].key, key) {
|
||||||
|
return m.entries[idx].value;
|
||||||
|
}
|
||||||
|
idx = (idx + 1) % m.cap;
|
||||||
|
}
|
||||||
|
var zero: V = 0 as V;
|
||||||
|
return zero;
|
||||||
|
}
|
||||||
|
|
||||||
func StringMap_IsEmpty<V>(m: *StringMap<V>) -> bool {
|
func StringMap_Has<V>(m: *StringMap<V>, key: String) -> bool {
|
||||||
return m.len == 0;
|
let hash: uint = bux_hash_string(key);
|
||||||
}
|
var idx: uint = hash % m.cap;
|
||||||
|
while m.entries[idx].occupied {
|
||||||
func StringMap_Remove<V>(m: *StringMap<V>, key: String) -> bool {
|
if String_Eq(m.entries[idx].key, key) {
|
||||||
if !StringMap_Has<V>(m, key) {
|
return true;
|
||||||
|
}
|
||||||
|
idx = (idx + 1) % m.cap;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var fresh: StringMap<V> = StringMap_New<V>(m.cap);
|
|
||||||
var i: uint = 0;
|
func StringMap_Len<V>(m: *StringMap<V>) -> uint {
|
||||||
while i < m.cap {
|
return m.len;
|
||||||
if m.entries[i].occupied {
|
}
|
||||||
if !String_Eq(m.entries[i].key, key) {
|
|
||||||
StringMap_Set<V>(&fresh, m.entries[i].key, m.entries[i].value);
|
func StringMap_IsEmpty<V>(m: *StringMap<V>) -> bool {
|
||||||
}
|
return m.len == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringMap_Remove<V>(m: *StringMap<V>, key: String) -> bool {
|
||||||
|
if !StringMap_Has<V>(m, key) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
i = i + 1;
|
var fresh: StringMap<V> = StringMap_New<V>(m.cap);
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < m.cap {
|
||||||
|
if m.entries[i].occupied {
|
||||||
|
if !String_Eq(m.entries[i].key, key) {
|
||||||
|
StringMap_Set<V>(&fresh, m.entries[i].key, m.entries[i].value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
bux_free(m.entries as *void);
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
bux_free(m.entries as *void);
|
|
||||||
m.entries = fresh.entries;
|
|
||||||
m.cap = fresh.cap;
|
|
||||||
m.len = fresh.len;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
func StringMap_Clear<V>(m: *StringMap<V>) {
|
func StringMap_Clear<V>(m: *StringMap<V>) {
|
||||||
var i: uint = 0;
|
var i: uint = 0;
|
||||||
while i < m.cap {
|
while i < m.cap {
|
||||||
m.entries[i].occupied = false;
|
m.entries[i].occupied = false;
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
|
}
|
||||||
|
m.len = 0;
|
||||||
}
|
}
|
||||||
m.len = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
func StringMap_Free<V>(m: *StringMap<V>) {
|
func StringMap_Free<V>(m: *StringMap<V>) {
|
||||||
bux_free(m.entries as *void);
|
bux_free(m.entries as *void);
|
||||||
m.entries = null as *StringMapEntry<V>;
|
m.entries = null as *StringMapEntry<V>;
|
||||||
m.cap = 0;
|
m.cap = 0;
|
||||||
m.len = 0;
|
m.len = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-40
@@ -1,44 +1,44 @@
|
|||||||
module Std::Math {
|
module Std::Math {
|
||||||
|
|
||||||
extern func bux_sqrt(x: float64) -> float64;
|
extern func bux_sqrt(x: float64) -> float64;
|
||||||
extern func bux_pow(x: float64, y: float64) -> float64;
|
extern func bux_pow(x: float64, y: float64) -> float64;
|
||||||
extern func bux_abs_i64(x: int64) -> int64;
|
extern func bux_abs_i64(x: int64) -> int64;
|
||||||
extern func bux_abs_f64(x: float64) -> float64;
|
extern func bux_abs_f64(x: float64) -> float64;
|
||||||
extern func bux_min_i64(a: int64, b: int64) -> int64;
|
extern func bux_min_i64(a: int64, b: int64) -> int64;
|
||||||
extern func bux_max_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_min_f64(a: float64, b: float64) -> float64;
|
||||||
extern func bux_max_f64(a: float64, b: float64) -> float64;
|
extern func bux_max_f64(a: float64, b: float64) -> float64;
|
||||||
|
|
||||||
|
func Sqrt(x: float64) -> float64 {
|
||||||
|
return bux_sqrt(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
func Pow(x: float64, y: float64) -> float64 {
|
||||||
|
return bux_pow(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
func Abs(n: int64) -> int64 {
|
||||||
|
return bux_abs_i64(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
func AbsF(f: float64) -> float64 {
|
||||||
|
return bux_abs_f64(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
func Min(a: int64, b: int64) -> int64 {
|
||||||
|
return bux_min_i64(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
func Max(a: int64, b: int64) -> int64 {
|
||||||
|
return bux_max_i64(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
func MinF(a: float64, b: float64) -> float64 {
|
||||||
|
return bux_min_f64(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
func MaxF(a: float64, b: float64) -> float64 {
|
||||||
|
return bux_max_f64(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
func Sqrt(x: float64) -> float64 {
|
|
||||||
return bux_sqrt(x);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Pow(x: float64, y: float64) -> float64 {
|
|
||||||
return bux_pow(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
func Abs(n: int64) -> int64 {
|
|
||||||
return bux_abs_i64(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
func AbsF(f: float64) -> float64 {
|
|
||||||
return bux_abs_f64(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
func Min(a: int64, b: int64) -> int64 {
|
|
||||||
return bux_min_i64(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
func Max(a: int64, b: int64) -> int64 {
|
|
||||||
return bux_max_i64(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
func MinF(a: float64, b: float64) -> float64 {
|
|
||||||
return bux_min_f64(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
func MaxF(a: float64, b: float64) -> float64 {
|
|
||||||
return bux_max_f64(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+20
-20
@@ -1,29 +1,29 @@
|
|||||||
module Std::Mem {
|
module Std::Mem {
|
||||||
|
|
||||||
extern func bux_alloc(size: uint) -> *void;
|
extern func bux_alloc(size: uint) -> *void;
|
||||||
extern func bux_realloc(ptr: *void, size: uint) -> *void;
|
extern func bux_realloc(ptr: *void, size: uint) -> *void;
|
||||||
extern func bux_free(ptr: *void);
|
extern func bux_free(ptr: *void);
|
||||||
extern func bux_mem_eq(a: *void, b: *void, size: uint) -> int;
|
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);
|
return bux_alloc(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
func Realloc(ptr: *void, size: uint) -> *void {
|
func Realloc(ptr: *void, size: uint) -> *void {
|
||||||
return bux_realloc(ptr, size);
|
return bux_realloc(ptr, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
func Free(ptr: *void) {
|
func Free(ptr: *void) {
|
||||||
bux_free(ptr);
|
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;
|
return bux_mem_eq(a, b, size) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
func New<T>() -> *T {
|
func New<T>() -> *T {
|
||||||
let sz: uint = sizeof(T);
|
let sz: uint = sizeof(T);
|
||||||
return bux_alloc(sz) as *T;
|
return bux_alloc(sz) as *T;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-50
@@ -1,62 +1,62 @@
|
|||||||
module Std::Net {
|
module Std::Net {
|
||||||
extern func bux_socket_create() -> int;
|
extern func bux_socket_create() -> int;
|
||||||
extern func bux_socket_reuse(fd: int) -> 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_bind(fd: int, addr: String, port: int) -> int;
|
||||||
extern func bux_socket_listen(fd: int, backlog: int) -> int;
|
extern func bux_socket_listen(fd: int, backlog: int) -> int;
|
||||||
extern func bux_socket_accept(fd: 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_connect(fd: int, addr: String, port: int) -> int;
|
||||||
extern func bux_socket_send(fd: int, data: String, len: 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_recv(fd: int, maxLen: int) -> String;
|
||||||
extern func bux_socket_close(fd: int) -> int;
|
extern func bux_socket_close(fd: int) -> int;
|
||||||
extern func bux_socket_error() -> String;
|
extern func bux_socket_error() -> String;
|
||||||
|
|
||||||
/* Create a TCP socket. Returns -1 on error. */
|
/* Create a TCP socket. Returns -1 on error. */
|
||||||
func Net_Create() -> int {
|
func Net_Create() -> int {
|
||||||
return bux_socket_create();
|
return bux_socket_create();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Enable SO_REUSEADDR on a socket. */
|
/* Enable SO_REUSEADDR on a socket. */
|
||||||
func Net_SetReuse(fd: int) -> bool {
|
func Net_SetReuse(fd: int) -> bool {
|
||||||
return bux_socket_reuse(fd) == 0;
|
return bux_socket_reuse(fd) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bind a socket to an address and port. */
|
/* Bind a socket to an address and port. */
|
||||||
func Net_Bind(fd: int, addr: String, port: int) -> bool {
|
func Net_Bind(fd: int, addr: String, port: int) -> bool {
|
||||||
return bux_socket_bind(fd, addr, port) == 0;
|
return bux_socket_bind(fd, addr, port) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Start listening for connections. */
|
/* Start listening for connections. */
|
||||||
func Net_Listen(fd: int, backlog: int) -> bool {
|
func Net_Listen(fd: int, backlog: int) -> bool {
|
||||||
return bux_socket_listen(fd, backlog) == 0;
|
return bux_socket_listen(fd, backlog) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Accept a connection. Returns new fd or -1 on error. */
|
/* Accept a connection. Returns new fd or -1 on error. */
|
||||||
func Net_Accept(fd: int) -> int {
|
func Net_Accept(fd: int) -> int {
|
||||||
return bux_socket_accept(fd);
|
return bux_socket_accept(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Connect to a remote address and port. */
|
/* Connect to a remote address and port. */
|
||||||
func Net_Connect(fd: int, addr: String, port: int) -> bool {
|
func Net_Connect(fd: int, addr: String, port: int) -> bool {
|
||||||
return bux_socket_connect(fd, addr, port) == 0;
|
return bux_socket_connect(fd, addr, port) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Send data. Returns bytes sent or -1 on error. */
|
/* Send data. Returns bytes sent or -1 on error. */
|
||||||
func Net_Send(fd: int, data: String) -> int {
|
func Net_Send(fd: int, data: String) -> int {
|
||||||
return bux_socket_send(fd, data, bux_strlen(data) as int);
|
return bux_socket_send(fd, data, bux_strlen(data) as int);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Receive up to maxLen bytes. Returns empty string on error/EOF. */
|
/* Receive up to maxLen bytes. Returns empty string on error/EOF. */
|
||||||
func Net_Recv(fd: int, maxLen: int) -> String {
|
func Net_Recv(fd: int, maxLen: int) -> String {
|
||||||
return bux_socket_recv(fd, maxLen);
|
return bux_socket_recv(fd, maxLen);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Close a socket. */
|
/* Close a socket. */
|
||||||
func Net_Close(fd: int) -> bool {
|
func Net_Close(fd: int) -> bool {
|
||||||
return bux_socket_close(fd) == 0;
|
return bux_socket_close(fd) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get last socket error as a string. */
|
/* Get last socket error as a string. */
|
||||||
func Net_LastError() -> String {
|
func Net_LastError() -> String {
|
||||||
return bux_socket_error();
|
return bux_socket_error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-52
@@ -1,61 +1,61 @@
|
|||||||
module Std::Option {
|
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),
|
Some(int),
|
||||||
None,
|
None,
|
||||||
}
|
|
||||||
|
|
||||||
func Option_NewSome(value: int) -> Option {
|
|
||||||
let o: Option = Option { tag: Option_Some };
|
|
||||||
o.data.Some_0 = value;
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
|
|
||||||
func Option_NewNone() -> Option {
|
|
||||||
return Option { tag: Option_None };
|
|
||||||
}
|
|
||||||
|
|
||||||
func Option_IsSome(o: Option) -> bool {
|
|
||||||
return o.tag == Option_Some;
|
|
||||||
}
|
|
||||||
|
|
||||||
func Option_IsNone(o: Option) -> bool {
|
|
||||||
return o.tag == Option_None;
|
|
||||||
}
|
|
||||||
|
|
||||||
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_NewSome(value: int) -> Option {
|
||||||
if o.tag == Option_Some {
|
let o: Option = Option { tag: Option_Some };
|
||||||
return o.data.Some_0;
|
o.data.Some_0 = value;
|
||||||
}
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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.tag == Option_Some {
|
|
||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
return other;
|
|
||||||
}
|
func Option_NewNone() -> Option {
|
||||||
|
return Option { tag: Option_None };
|
||||||
|
}
|
||||||
|
|
||||||
|
func Option_IsSome(o: Option) -> bool {
|
||||||
|
return o.tag == Option_Some;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Option_IsNone(o: Option) -> bool {
|
||||||
|
return o.tag == Option_None;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
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.tag == Option_Some {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
return other;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-29
@@ -1,40 +1,40 @@
|
|||||||
module Std::Os {
|
module Std::Os {
|
||||||
|
|
||||||
extern func bux_argc() -> int;
|
extern func bux_argc() -> int;
|
||||||
extern func bux_argv(index: int) -> String;
|
extern func bux_argv(index: int) -> String;
|
||||||
extern func bux_getenv(name: String) -> String;
|
extern func bux_getenv(name: String) -> String;
|
||||||
extern func bux_setenv(name: String, value: String) -> int;
|
extern func bux_setenv(name: String, value: String) -> int;
|
||||||
extern func bux_getcwd() -> String;
|
extern func bux_getcwd() -> String;
|
||||||
extern func bux_chdir(path: String) -> int;
|
extern func bux_chdir(path: String) -> int;
|
||||||
extern func bux_exit(code: int);
|
extern func bux_exit(code: int);
|
||||||
|
|
||||||
func Os_ArgsCount() -> int {
|
func Os_ArgsCount() -> int {
|
||||||
return bux_argc();
|
return bux_argc();
|
||||||
}
|
}
|
||||||
|
|
||||||
func Os_Args(index: int) -> String {
|
func Os_Args(index: int) -> String {
|
||||||
return bux_argv(index);
|
return bux_argv(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
func Os_GetEnv(name: String) -> String {
|
func Os_GetEnv(name: String) -> String {
|
||||||
return bux_getenv(name);
|
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;
|
return bux_setenv(name, value) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
func Os_GetCwd() -> String {
|
func Os_GetCwd() -> String {
|
||||||
return bux_getcwd();
|
return bux_getcwd();
|
||||||
}
|
}
|
||||||
|
|
||||||
func Os_Chdir(path: String) -> bool {
|
func Os_Chdir(path: String) -> bool {
|
||||||
return bux_chdir(path) == 0;
|
return bux_chdir(path) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Terminate the process with the given exit code */
|
/* Terminate the process with the given exit code */
|
||||||
func Os_Exit(code: int) {
|
func Os_Exit(code: int) {
|
||||||
bux_exit(code);
|
bux_exit(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-15
@@ -1,19 +1,19 @@
|
|||||||
module Std::Path {
|
module Std::Path {
|
||||||
|
|
||||||
extern func bux_path_join(a: String, b: String) -> String;
|
extern func bux_path_join(a: String, b: String) -> String;
|
||||||
extern func bux_path_parent(path: String) -> String;
|
extern func bux_path_parent(path: String) -> String;
|
||||||
extern func bux_path_ext(path: String) -> String;
|
extern func bux_path_ext(path: String) -> String;
|
||||||
|
|
||||||
|
func Path_Join(a: String, b: String) -> String {
|
||||||
|
return bux_path_join(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
func Path_Parent(path: String) -> String {
|
||||||
|
return bux_path_parent(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
func Path_Ext(path: String) -> String {
|
||||||
|
return bux_path_ext(path);
|
||||||
|
}
|
||||||
|
|
||||||
func Path_Join(a: String, b: String) -> String {
|
|
||||||
return bux_path_join(a, b);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Path_Parent(path: String) -> String {
|
|
||||||
return bux_path_parent(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
func Path_Ext(path: String) -> String {
|
|
||||||
return bux_path_ext(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+8
-8
@@ -1,14 +1,14 @@
|
|||||||
module Std::Process {
|
module Std::Process {
|
||||||
|
|
||||||
extern func bux_process_run(cmd: String) -> int;
|
extern func bux_process_run(cmd: String) -> int;
|
||||||
extern func bux_process_output(cmd: String) -> String;
|
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);
|
return bux_process_run(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
func Process_Output(cmd: String) -> String {
|
func Process_Output(cmd: String) -> String {
|
||||||
return bux_process_output(cmd);
|
return bux_process_output(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+63
-63
@@ -1,72 +1,72 @@
|
|||||||
module Std::Result {
|
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),
|
Ok(int),
|
||||||
Err(String),
|
Err(String),
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
let r: Result = Result { tag: Result_Err };
|
|
||||||
r.data.Err_0 = msg;
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
func Result_IsOk(r: Result) -> bool {
|
|
||||||
return r.tag == Result_Ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
func Result_IsErr(r: Result) -> bool {
|
|
||||||
return r.tag == Result_Err;
|
|
||||||
}
|
|
||||||
|
|
||||||
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_NewOk(value: int) -> Result {
|
||||||
if r.tag == Result_Ok {
|
let r: Result = Result { tag: Result_Ok };
|
||||||
return r.data.Ok_0;
|
r.data.Ok_0 = value;
|
||||||
}
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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 {
|
|
||||||
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.tag == Result_Ok {
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
return other;
|
|
||||||
}
|
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 {
|
||||||
|
return r.tag == Result_Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Result_IsErr(r: Result) -> bool {
|
||||||
|
return r.tag == Result_Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
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.tag == Result_Ok {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
return other;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+95
-91
@@ -1,112 +1,116 @@
|
|||||||
module Std::Set {
|
module Std::Set {
|
||||||
|
|
||||||
extern func bux_hash_bytes(ptr: *void, size: uint) -> uint;
|
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_mem_eq(a: *void, b: *void, size: uint) -> int;
|
||||||
extern func bux_alloc(size: uint) -> *void;
|
extern func bux_alloc(size: uint) -> *void;
|
||||||
extern func bux_free(ptr: *void);
|
extern func bux_free(ptr: *void);
|
||||||
|
|
||||||
struct SetEntry<T> {
|
struct SetEntry<T> {
|
||||||
value: T,
|
value: T,
|
||||||
occupied: bool,
|
occupied: bool,
|
||||||
}
|
|
||||||
|
|
||||||
struct Set<T> {
|
|
||||||
entries: *SetEntry<T>,
|
|
||||||
cap: uint,
|
|
||||||
len: uint,
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
while i < cap {
|
|
||||||
data[i].occupied = false;
|
|
||||||
i = i + 1;
|
|
||||||
}
|
}
|
||||||
return Set<T> { entries: data, cap: cap, len: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
func Set_Add<T>(s: *Set<T>, value: T) {
|
struct Set<T> {
|
||||||
var valuePtr: *T = &value;
|
entries: *SetEntry<T>,
|
||||||
let hash: uint = bux_hash_bytes(valuePtr as *void, sizeof(T));
|
cap: uint,
|
||||||
var idx: uint = hash % s.cap;
|
len: uint,
|
||||||
while s.entries[idx].occupied {
|
}
|
||||||
var entryPtr: *T = &s.entries[idx].value;
|
|
||||||
if bux_mem_eq(entryPtr as *void, valuePtr as *void, sizeof(T)) != 0 {
|
func Set_New<T>(cap: uint) -> Set<T> {
|
||||||
return;
|
let total: uint = cap * sizeof(SetEntry<T>);
|
||||||
|
let data: *SetEntry<T> = bux_alloc(total) as *SetEntry<T>;
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < cap {
|
||||||
|
data[i].occupied = false;
|
||||||
|
i = i + 1;
|
||||||
}
|
}
|
||||||
idx = (idx + 1) % s.cap;
|
return Set<T> { entries: data, cap: cap, len: 0 };
|
||||||
}
|
}
|
||||||
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_Add<T>(s: *Set<T>, value: T) {
|
||||||
var valuePtr: *T = &value;
|
var valuePtr: *T = &value;
|
||||||
let hash: uint = bux_hash_bytes(valuePtr as *void, sizeof(T));
|
let hash: uint = bux_hash_bytes(valuePtr as *void, sizeof(T));
|
||||||
var idx: uint = hash % s.cap;
|
var idx: uint = hash % s.cap;
|
||||||
while s.entries[idx].occupied {
|
while s.entries[idx].occupied {
|
||||||
var entryPtr: *T = &s.entries[idx].value;
|
var entryPtr: *T = &s.entries[idx].value;
|
||||||
if bux_mem_eq(entryPtr as *void, valuePtr as *void, sizeof(T)) != 0 {
|
if bux_mem_eq(entryPtr as *void, valuePtr as *void, sizeof(T)) != 0 {
|
||||||
return true;
|
return;
|
||||||
|
}
|
||||||
|
idx = (idx + 1) % s.cap;
|
||||||
}
|
}
|
||||||
idx = (idx + 1) % s.cap;
|
s.entries[idx].value = value;
|
||||||
|
s.entries[idx].occupied = true;
|
||||||
|
s.len = s.len + 1;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
func Set_Len<T>(s: *Set<T>) -> uint {
|
func Set_Has<T>(s: *Set<T>, value: T) -> bool {
|
||||||
return s.len;
|
var valuePtr: *T = &value;
|
||||||
}
|
let hash: uint = bux_hash_bytes(valuePtr as *void, sizeof(T));
|
||||||
|
var idx: uint = hash % s.cap;
|
||||||
func Set_IsEmpty<T>(s: *Set<T>) -> bool {
|
while s.entries[idx].occupied {
|
||||||
return s.len == 0;
|
var entryPtr: *T = &s.entries[idx].value;
|
||||||
}
|
if bux_mem_eq(entryPtr as *void, valuePtr as *void, sizeof(T)) != 0 {
|
||||||
|
return true;
|
||||||
/* Remove value if present. Rebuilds the table to keep open-addressing correct. */
|
}
|
||||||
func Set_Remove<T>(s: *Set<T>, value: T) -> bool {
|
idx = (idx + 1) % s.cap;
|
||||||
if !Set_Has<T>(s, value) {
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var fresh: Set<T> = Set_New<T>(s.cap);
|
|
||||||
var i: uint = 0;
|
func Set_Len<T>(s: *Set<T>) -> uint {
|
||||||
while i < s.cap {
|
return s.len;
|
||||||
if s.entries[i].occupied {
|
}
|
||||||
var entryPtr: *T = &s.entries[i].value;
|
|
||||||
var valuePtr: *T = &value;
|
func Set_IsEmpty<T>(s: *Set<T>) -> bool {
|
||||||
if bux_mem_eq(entryPtr as *void, valuePtr as *void, sizeof(T)) == 0 {
|
return s.len == 0;
|
||||||
Set_Add<T>(&fresh, s.entries[i].value);
|
}
|
||||||
}
|
|
||||||
|
/* 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;
|
||||||
}
|
}
|
||||||
i = i + 1;
|
var fresh: Set<T> = Set_New<T>(s.cap);
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < s.cap {
|
||||||
|
if s.entries[i].occupied {
|
||||||
|
var entryPtr: *T = &s.entries[i].value;
|
||||||
|
var valuePtr: *T = &value;
|
||||||
|
if bux_mem_eq(entryPtr as *void, valuePtr as *void, sizeof(T)) == 0 {
|
||||||
|
Set_Add<T>(&fresh, s.entries[i].value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
bux_free(s.entries as *void);
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
bux_free(s.entries as *void);
|
|
||||||
s.entries = fresh.entries;
|
|
||||||
s.cap = fresh.cap;
|
|
||||||
s.len = fresh.len;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
func Set_Clear<T>(s: *Set<T>) {
|
func Set_Clear<T>(s: *Set<T>) {
|
||||||
var i: uint = 0;
|
var i: uint = 0;
|
||||||
while i < s.cap {
|
while i < s.cap {
|
||||||
s.entries[i].occupied = false;
|
s.entries[i].occupied = false;
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
|
}
|
||||||
|
s.len = 0;
|
||||||
}
|
}
|
||||||
s.len = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
func Set_Free<T>(s: *Set<T>) {
|
func Set_Free<T>(s: *Set<T>) {
|
||||||
bux_free(s.entries as *void);
|
bux_free(s.entries as *void);
|
||||||
s.entries = null as *SetEntry<T>;
|
s.entries = null as *SetEntry<T>;
|
||||||
s.cap = 0;
|
s.cap = 0;
|
||||||
s.len = 0;
|
s.len = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
func Set_Drop<T>(s: *Set<T>) {
|
func Set_Drop<T>(s: *Set<T>) {
|
||||||
Set_Free<T>(s);
|
Set_Free<T>(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-28
@@ -1,39 +1,39 @@
|
|||||||
module Std::Slice {
|
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,
|
data: *T,
|
||||||
len: uint,
|
len: uint,
|
||||||
}
|
}
|
||||||
|
|
||||||
func Slice_FromArray<T>(arr: *Array<T>) -> Slice<T> {
|
func Slice_FromArray<T>(arr: *Array<T>) -> Slice<T> {
|
||||||
var s: Slice<T>;
|
var s: Slice<T>;
|
||||||
s.data = arr.data;
|
s.data = arr.data;
|
||||||
s.len = arr.len;
|
s.len = arr.len;
|
||||||
return s;
|
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);
|
bux_bounds_check(idx, self.len);
|
||||||
return self.data[idx];
|
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);
|
bux_bounds_check(idx, self.len);
|
||||||
self.data[idx] = value;
|
self.data[idx] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
func Slice_Len<T>(self: *Slice<T>) -> uint {
|
func Slice_Len<T>(self: *Slice<T>) -> uint {
|
||||||
return self.len;
|
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);
|
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);
|
Slice_Set<T>(self, idx, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+270
-261
@@ -1,288 +1,297 @@
|
|||||||
module Std::String {
|
module Std::String {
|
||||||
|
|
||||||
extern func bux_strlen(s: String) -> uint;
|
extern func bux_strlen(s: String) -> uint;
|
||||||
extern func bux_strcmp(a: String, b: String) -> int;
|
extern func bux_strcmp(a: String, b: String) -> int;
|
||||||
extern func bux_strncmp(a: String, b: String, n: uint) -> int;
|
extern func bux_strncmp(a: String, b: String, n: uint) -> int;
|
||||||
extern func bux_strcpy(dest: *char8, src: String) -> *char8;
|
extern func bux_strcpy(dest: *char8, src: String) -> *char8;
|
||||||
extern func bux_strcat(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_strncpy(dest: *char8, src: String, n: uint) -> *char8;
|
||||||
extern func bux_strstr(haystack: String, needle: String) -> String;
|
extern func bux_strstr(haystack: String, needle: String) -> String;
|
||||||
extern func bux_str_contains(haystack: String, needle: String) -> int;
|
extern func bux_str_contains(haystack: String, needle: String) -> int;
|
||||||
extern func bux_str_offset(pos: String, base: String) -> uint;
|
extern func bux_str_offset(pos: String, base: String) -> uint;
|
||||||
extern func bux_str_is_null(s: String) -> int;
|
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_slice(s: String, start: uint, len: uint) -> String;
|
||||||
extern func bux_str_trim_left(s: String) -> String;
|
extern func bux_str_trim_left(s: String) -> String;
|
||||||
extern func bux_str_trim_right(s: String) -> String;
|
extern func bux_str_trim_right(s: String) -> String;
|
||||||
extern func bux_str_trim(s: String) -> String;
|
extern func bux_str_trim(s: String) -> String;
|
||||||
extern func bux_int_to_str(n: int64) -> String;
|
extern func bux_int_to_str(n: int64) -> String;
|
||||||
extern func bux_str_to_int(s: String) -> int64;
|
extern func bux_str_to_int(s: String) -> int64;
|
||||||
extern func bux_sb_new(initial_cap: uint) -> *void;
|
extern func bux_sb_new(initial_cap: uint) -> *void;
|
||||||
extern func bux_sb_append(sb: *void, s: String);
|
extern func bux_sb_append(sb: *void, s: String);
|
||||||
extern func bux_sb_append_int(sb: *void, n: int64);
|
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_float(sb: *void, f: float64);
|
||||||
extern func bux_sb_append_char(sb: *void, c: char8);
|
extern func bux_sb_append_char(sb: *void, c: char8);
|
||||||
extern func bux_sb_build(sb: *void) -> String;
|
extern func bux_sb_build(sb: *void) -> String;
|
||||||
extern func bux_sb_free(sb: *void);
|
extern func bux_sb_free(sb: *void);
|
||||||
extern func bux_str_split_count(s: String, delim: String) -> uint;
|
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_split_part(s: String, delim: String, index: uint) -> String;
|
||||||
extern func bux_str_join2(a: String, b: String, sep: String) -> 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_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_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`).
|
||||||
return bux_strlen(s);
|
func String_Len(s: String) -> uint {
|
||||||
}
|
return bux_strlen(s);
|
||||||
|
|
||||||
func String_IsEmpty(s: String) -> bool {
|
|
||||||
return bux_strlen(s) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
func String_IsNull(s: String) -> bool {
|
|
||||||
return bux_str_is_null(s) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
func String_Eq(a: String, b: String) -> bool {
|
|
||||||
return bux_strcmp(a, b) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
let buf: *char8 = bux_alloc(total) as *char8;
|
|
||||||
bux_strcpy(buf, a);
|
|
||||||
bux_strcat(buf, b);
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
let s_len: uint = bux_strlen(s);
|
|
||||||
let p_len: uint = bux_strlen(prefix);
|
|
||||||
if p_len > s_len {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
let r: int = bux_strncmp(s, prefix, p_len);
|
|
||||||
return r == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
func String_EndsWith(s: String, suffix: String) -> bool {
|
/// True if the string has zero length.
|
||||||
let s_len: uint = bux_strlen(s);
|
func String_IsEmpty(s: String) -> bool {
|
||||||
let suf_len: uint = bux_strlen(suffix);
|
return bux_strlen(s) == 0;
|
||||||
if suf_len > s_len {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
let start: uint = s_len - suf_len;
|
|
||||||
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 the pointer is null.
|
||||||
let r: int = bux_str_contains(s, substr);
|
func String_IsNull(s: String) -> bool {
|
||||||
return r != 0;
|
return bux_str_is_null(s) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
func String_Slice(s: String, start: uint, len: uint) -> String {
|
/// Lexicographic equality.
|
||||||
return bux_str_slice(s, start, len);
|
func String_Eq(a: String, b: String) -> bool {
|
||||||
}
|
return bux_strcmp(a, b) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
func String_Trim(s: String) -> String {
|
/// Allocate and return `a` concatenated with `b`.
|
||||||
return bux_str_trim(s);
|
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;
|
||||||
|
let buf: *char8 = bux_alloc(total) as *char8;
|
||||||
|
bux_strcpy(buf, a);
|
||||||
|
bux_strcat(buf, b);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
func String_TrimLeft(s: String) -> String {
|
/// Heap-copy of `s`.
|
||||||
return bux_str_trim_left(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_TrimRight(s: String) -> String {
|
/// True if `s` begins with `prefix`.
|
||||||
return bux_str_trim_right(s);
|
func String_StartsWith(s: String, prefix: String) -> bool {
|
||||||
}
|
let s_len: uint = bux_strlen(s);
|
||||||
|
let p_len: uint = bux_strlen(prefix);
|
||||||
func String_FromInt(n: int64) -> String {
|
if p_len > s_len {
|
||||||
return bux_int_to_str(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
func String_ToInt(s: String) -> int64 {
|
|
||||||
return bux_str_to_int(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
// String Builder — efficient string construction
|
|
||||||
struct StringBuilder {
|
|
||||||
handle: *void,
|
|
||||||
}
|
|
||||||
|
|
||||||
func StringBuilder_New() -> StringBuilder {
|
|
||||||
return StringBuilder { handle: bux_sb_new(64) };
|
|
||||||
}
|
|
||||||
|
|
||||||
func StringBuilder_NewCap(cap: uint) -> StringBuilder {
|
|
||||||
return StringBuilder { handle: bux_sb_new(cap) };
|
|
||||||
}
|
|
||||||
|
|
||||||
func StringBuilder_Append(sb: *StringBuilder, s: String) {
|
|
||||||
bux_sb_append(sb.handle, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
func StringBuilder_AppendInt(sb: *StringBuilder, n: int64) {
|
|
||||||
bux_sb_append_int(sb.handle, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
func StringBuilder_AppendFloat(sb: *StringBuilder, f: float64) {
|
|
||||||
bux_sb_append_float(sb.handle, f);
|
|
||||||
}
|
|
||||||
|
|
||||||
func StringBuilder_AppendChar(sb: *StringBuilder, c: char8) {
|
|
||||||
bux_sb_append_char(sb.handle, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
func StringBuilder_Build(sb: *StringBuilder) -> String {
|
|
||||||
return bux_sb_build(sb.handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
let n: uint = bux_strlen(s);
|
|
||||||
var i: uint = 0;
|
|
||||||
while i < n {
|
|
||||||
let ch: String = bux_str_slice(s, i, 1);
|
|
||||||
if !(String_Eq(ch, " ") || String_Eq(ch, "\t") || String_Eq(ch, "\n") || String_Eq(ch, "\r")) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
i = i + 1;
|
let r: int = bux_strncmp(s, prefix, p_len);
|
||||||
|
return r == 0;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Repeat s, count times (count==0 → empty string) */
|
/// True if `s` ends with `suffix`.
|
||||||
func String_Repeat(s: String, count: uint) -> String {
|
func String_EndsWith(s: String, suffix: String) -> bool {
|
||||||
if count == 0 {
|
let s_len: uint = bux_strlen(s);
|
||||||
return "";
|
let suf_len: uint = bux_strlen(suffix);
|
||||||
}
|
if suf_len > s_len {
|
||||||
if count == 1 {
|
return false;
|
||||||
return s;
|
|
||||||
}
|
|
||||||
var sb: StringBuilder = StringBuilder_New();
|
|
||||||
var i: uint = 0;
|
|
||||||
while i < count {
|
|
||||||
StringBuilder_Append(&sb, s);
|
|
||||||
i = i + 1;
|
|
||||||
}
|
|
||||||
let result: String = StringBuilder_Build(&sb);
|
|
||||||
StringBuilder_Free(&sb);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// String split/join
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
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 {
|
|
||||||
return bux_str_split_part(s, delim, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
func String_Join2(a: String, b: String, sep: String) -> String {
|
|
||||||
return bux_str_join2(a, b, sep);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// String find/replace/format
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
return bux_strstr(haystack, needle);
|
|
||||||
}
|
|
||||||
|
|
||||||
func String_Offset(pos: String, base: String) -> uint {
|
|
||||||
return bux_str_offset(pos, base);
|
|
||||||
}
|
|
||||||
|
|
||||||
func String_Replace(s: String, old: String, new: String) -> String {
|
|
||||||
let pos: String = bux_strstr(s, old);
|
|
||||||
if String_IsNull(pos) {
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
let oldLen: uint = bux_strlen(old);
|
|
||||||
let prefixLen: uint = String_Offset(pos, s);
|
|
||||||
let prefix: String = bux_str_slice(s, 0, prefixLen);
|
|
||||||
let suffix: String = bux_str_slice(s, prefixLen + oldLen, bux_strlen(s) - prefixLen - oldLen);
|
|
||||||
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 {
|
|
||||||
let oldLen: uint = bux_strlen(old);
|
|
||||||
if oldLen == 0 {
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
var sb: StringBuilder = StringBuilder_New();
|
|
||||||
var remaining: String = s;
|
|
||||||
while true {
|
|
||||||
let pos: String = bux_strstr(remaining, old);
|
|
||||||
if String_IsNull(pos) {
|
|
||||||
StringBuilder_Append(&sb, remaining);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
let prefixLen: uint = String_Offset(pos, remaining);
|
let start: uint = s_len - suf_len;
|
||||||
let prefix: String = bux_str_slice(remaining, 0, prefixLen);
|
let tail: String = bux_str_slice(s, start, suf_len);
|
||||||
StringBuilder_Append(&sb, prefix);
|
let eq: bool = bux_strcmp(tail, suffix) == 0;
|
||||||
StringBuilder_Append(&sb, new);
|
return eq;
|
||||||
let remLen: uint = bux_strlen(remaining);
|
|
||||||
remaining = bux_str_slice(remaining, prefixLen + oldLen, remLen - prefixLen - oldLen);
|
|
||||||
}
|
}
|
||||||
let result: String = StringBuilder_Build(&sb);
|
|
||||||
StringBuilder_Free(&sb);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern func bux_str_to_float(s: String) -> float64;
|
/// 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_ToFloat(s: String) -> float64 {
|
func String_Slice(s: String, start: uint, len: uint) -> String {
|
||||||
return bux_str_to_float(s);
|
return bux_str_slice(s, start, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
func String_FromBool(b: bool) -> String {
|
func String_Trim(s: String) -> String {
|
||||||
if b { return "true"; }
|
return bux_str_trim(s);
|
||||||
return "false";
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func String_FromFloat(f: float64) -> String {
|
func String_TrimLeft(s: String) -> String {
|
||||||
return bux_float_to_string(f);
|
return bux_str_trim_left(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
func String_Format1(pattern: String, a0: String) -> String {
|
func String_TrimRight(s: String) -> String {
|
||||||
return bux_str_format(pattern, a0, "", "", "", "", "", "", "");
|
return bux_str_trim_right(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
func String_Format2(pattern: String, a0: String, a1: String) -> String {
|
func String_FromInt(n: int64) -> String {
|
||||||
return bux_str_format(pattern, a0, a1, "", "", "", "", "", "");
|
return bux_int_to_str(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
func String_Format3(pattern: String, a0: String, a1: String, a2: String) -> String {
|
func String_ToInt(s: String) -> int64 {
|
||||||
return bux_str_format(pattern, a0, a1, a2, "", "", "", "", "");
|
return bux_str_to_int(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String Builder — efficient string construction
|
||||||
|
struct StringBuilder {
|
||||||
|
handle: *void,
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringBuilder_New() -> StringBuilder {
|
||||||
|
return StringBuilder { handle: bux_sb_new(64) };
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringBuilder_NewCap(cap: uint) -> StringBuilder {
|
||||||
|
return StringBuilder { handle: bux_sb_new(cap) };
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringBuilder_Append(sb: *StringBuilder, s: String) {
|
||||||
|
bux_sb_append(sb.handle, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringBuilder_AppendInt(sb: *StringBuilder, n: int64) {
|
||||||
|
bux_sb_append_int(sb.handle, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringBuilder_AppendFloat(sb: *StringBuilder, f: float64) {
|
||||||
|
bux_sb_append_float(sb.handle, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringBuilder_AppendChar(sb: *StringBuilder, c: char8) {
|
||||||
|
bux_sb_append_char(sb.handle, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringBuilder_Build(sb: *StringBuilder) -> String {
|
||||||
|
return bux_sb_build(sb.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
let n: uint = bux_strlen(s);
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < n {
|
||||||
|
let ch: String = bux_str_slice(s, i, 1);
|
||||||
|
if !(String_Eq(ch, " ") || String_Eq(ch, "\t") || String_Eq(ch, "\n") || String_Eq(ch, "\r")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Repeat `s`, `count` times (`count == 0` → empty string).
|
||||||
|
func String_Repeat(s: String, count: uint) -> String {
|
||||||
|
if count == 0 {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if count == 1 {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
var sb: StringBuilder = StringBuilder_New();
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < count {
|
||||||
|
StringBuilder_Append(&sb, s);
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
let result: String = StringBuilder_Build(&sb);
|
||||||
|
StringBuilder_Free(&sb);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// String split/join
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return bux_str_split_part(s, delim, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
func String_Join2(a: String, b: String, sep: String) -> String {
|
||||||
|
return bux_str_join2(a, b, sep);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// String find/replace/format
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return bux_strstr(haystack, needle);
|
||||||
|
}
|
||||||
|
|
||||||
|
func String_Offset(pos: String, base: String) -> uint {
|
||||||
|
return bux_str_offset(pos, base);
|
||||||
|
}
|
||||||
|
|
||||||
|
func String_Replace(s: String, old: String, new: String) -> String {
|
||||||
|
let pos: String = bux_strstr(s, old);
|
||||||
|
if String_IsNull(pos) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
let oldLen: uint = bux_strlen(old);
|
||||||
|
let prefixLen: uint = String_Offset(pos, s);
|
||||||
|
let prefix: String = bux_str_slice(s, 0, prefixLen);
|
||||||
|
let suffix: String = bux_str_slice(s, prefixLen + oldLen, bux_strlen(s) - prefixLen - oldLen);
|
||||||
|
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 {
|
||||||
|
let oldLen: uint = bux_strlen(old);
|
||||||
|
if oldLen == 0 {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
var sb: StringBuilder = StringBuilder_New();
|
||||||
|
var remaining: String = s;
|
||||||
|
while true {
|
||||||
|
let pos: String = bux_strstr(remaining, old);
|
||||||
|
if String_IsNull(pos) {
|
||||||
|
StringBuilder_Append(&sb, remaining);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let prefixLen: uint = String_Offset(pos, remaining);
|
||||||
|
let prefix: String = bux_str_slice(remaining, 0, prefixLen);
|
||||||
|
StringBuilder_Append(&sb, prefix);
|
||||||
|
StringBuilder_Append(&sb, new);
|
||||||
|
let remLen: uint = bux_strlen(remaining);
|
||||||
|
remaining = bux_str_slice(remaining, prefixLen + oldLen, remLen - prefixLen - oldLen);
|
||||||
|
}
|
||||||
|
let result: String = StringBuilder_Build(&sb);
|
||||||
|
StringBuilder_Free(&sb);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern func bux_str_to_float(s: String) -> float64;
|
||||||
|
|
||||||
|
func String_ToFloat(s: String) -> float64 {
|
||||||
|
return bux_str_to_float(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
func String_FromBool(b: bool) -> String {
|
||||||
|
if b { return "true"; }
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
|
||||||
|
func String_FromFloat(f: float64) -> String {
|
||||||
|
return bux_float_to_string(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
func String_Format1(pattern: String, a0: String) -> String {
|
||||||
|
return bux_str_format(pattern, a0, "", "", "", "", "", "", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return bux_str_format(pattern, a0, a1, a2, "", "", "", "", "");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-42
@@ -1,58 +1,58 @@
|
|||||||
module Std::Sync {
|
module Std::Sync {
|
||||||
|
|
||||||
extern func bux_mutex_new() -> *void;
|
extern func bux_mutex_new() -> *void;
|
||||||
extern func bux_mutex_lock(handle: *void);
|
extern func bux_mutex_lock(handle: *void);
|
||||||
extern func bux_mutex_unlock(handle: *void);
|
extern func bux_mutex_unlock(handle: *void);
|
||||||
extern func bux_mutex_free(handle: *void);
|
extern func bux_mutex_free(handle: *void);
|
||||||
|
|
||||||
extern func bux_rwlock_new() -> *void;
|
extern func bux_rwlock_new() -> *void;
|
||||||
extern func bux_rwlock_rdlock(handle: *void);
|
extern func bux_rwlock_rdlock(handle: *void);
|
||||||
extern func bux_rwlock_wrlock(handle: *void);
|
extern func bux_rwlock_wrlock(handle: *void);
|
||||||
extern func bux_rwlock_unlock(handle: *void);
|
extern func bux_rwlock_unlock(handle: *void);
|
||||||
extern func bux_rwlock_free(handle: *void);
|
extern func bux_rwlock_free(handle: *void);
|
||||||
|
|
||||||
struct Mutex {
|
struct Mutex {
|
||||||
handle: *void;
|
handle: *void;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RwLock {
|
struct RwLock {
|
||||||
handle: *void;
|
handle: *void;
|
||||||
}
|
}
|
||||||
|
|
||||||
func Mutex_New() -> Mutex {
|
func Mutex_New() -> Mutex {
|
||||||
return Mutex { handle: bux_mutex_new() };
|
return Mutex { handle: bux_mutex_new() };
|
||||||
}
|
}
|
||||||
|
|
||||||
func Mutex_Lock(m: *Mutex) {
|
func Mutex_Lock(m: *Mutex) {
|
||||||
bux_mutex_lock(m.handle);
|
bux_mutex_lock(m.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
func Mutex_Unlock(m: *Mutex) {
|
func Mutex_Unlock(m: *Mutex) {
|
||||||
bux_mutex_unlock(m.handle);
|
bux_mutex_unlock(m.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
func Mutex_Free(m: *Mutex) {
|
func Mutex_Free(m: *Mutex) {
|
||||||
bux_mutex_free(m.handle);
|
bux_mutex_free(m.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
func RwLock_New() -> RwLock {
|
func RwLock_New() -> RwLock {
|
||||||
return RwLock { handle: bux_rwlock_new() };
|
return RwLock { handle: bux_rwlock_new() };
|
||||||
}
|
}
|
||||||
|
|
||||||
func RwLock_ReadLock(rw: *RwLock) {
|
func RwLock_ReadLock(rw: *RwLock) {
|
||||||
bux_rwlock_rdlock(rw.handle);
|
bux_rwlock_rdlock(rw.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
func RwLock_WriteLock(rw: *RwLock) {
|
func RwLock_WriteLock(rw: *RwLock) {
|
||||||
bux_rwlock_wrlock(rw.handle);
|
bux_rwlock_wrlock(rw.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
func RwLock_Unlock(rw: *RwLock) {
|
func RwLock_Unlock(rw: *RwLock) {
|
||||||
bux_rwlock_unlock(rw.handle);
|
bux_rwlock_unlock(rw.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
func RwLock_Free(rw: *RwLock) {
|
func RwLock_Free(rw: *RwLock) {
|
||||||
bux_rwlock_free(rw.handle);
|
bux_rwlock_free(rw.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-31
@@ -1,43 +1,43 @@
|
|||||||
module Std::Task {
|
module Std::Task {
|
||||||
|
|
||||||
extern func bux_task_init(num_workers: int);
|
extern func bux_task_init(num_workers: int);
|
||||||
extern func bux_task_spawn(fn: *void, arg: *void) -> *void;
|
extern func bux_task_spawn(fn: *void, arg: *void) -> *void;
|
||||||
extern func bux_task_join(handle: *void);
|
extern func bux_task_join(handle: *void);
|
||||||
extern func bux_task_sleep(ms: int64);
|
extern func bux_task_sleep(ms: int64);
|
||||||
extern func bux_task_yield();
|
extern func bux_task_yield();
|
||||||
extern func bux_task_current_id() -> int;
|
extern func bux_task_current_id() -> int;
|
||||||
extern func bux_task_shutdown();
|
extern func bux_task_shutdown();
|
||||||
|
|
||||||
struct TaskHandle {
|
struct TaskHandle {
|
||||||
handle: *void;
|
handle: *void;
|
||||||
}
|
}
|
||||||
|
|
||||||
func Task_Init(num_workers: int) {
|
func Task_Init(num_workers: int) {
|
||||||
bux_task_init(num_workers);
|
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) };
|
return TaskHandle { handle: bux_task_spawn(fn, arg) };
|
||||||
}
|
}
|
||||||
|
|
||||||
func Task_Wait(t: TaskHandle) {
|
func Task_Wait(t: TaskHandle) {
|
||||||
bux_task_join(t.handle);
|
bux_task_join(t.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
func Task_Sleep(ms: int64) {
|
func Task_Sleep(ms: int64) {
|
||||||
bux_task_sleep(ms);
|
bux_task_sleep(ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
func Task_Yield() {
|
func Task_Yield() {
|
||||||
bux_task_yield();
|
bux_task_yield();
|
||||||
}
|
}
|
||||||
|
|
||||||
func Task_CurrentId() -> int {
|
func Task_CurrentId() -> int {
|
||||||
return bux_task_current_id();
|
return bux_task_current_id();
|
||||||
}
|
}
|
||||||
|
|
||||||
func Task_Shutdown() {
|
func Task_Shutdown() {
|
||||||
bux_task_shutdown();
|
bux_task_shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+75
-65
@@ -1,76 +1,86 @@
|
|||||||
module Std::Test {
|
module Std::Test {
|
||||||
import Std::Io::{PrintLine, PrintInt};
|
import Std::Io::{PrintLine, PrintInt};
|
||||||
import Std::String::{String_Eq};
|
import Std::String::{String_Eq};
|
||||||
|
|
||||||
extern func bux_exit(code: int);
|
extern func bux_exit(code: int);
|
||||||
extern func bux_assert(cond: int, file: String, line: int, expr: String);
|
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).
|
||||||
bux_exit(code);
|
func Test_Exit(code: int) {
|
||||||
}
|
bux_exit(code);
|
||||||
|
}
|
||||||
|
|
||||||
func Test_Assert(cond: bool) {
|
/// Assert `cond` is true; abort on failure.
|
||||||
bux_assert(cond as int, "", 0, "");
|
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.
|
||||||
if a != b {
|
func Test_AssertEqInt(a: int, b: int) {
|
||||||
PrintLine("ASSERT_EQ_INT FAILED:");
|
if a != b {
|
||||||
PrintInt(a);
|
PrintLine("ASSERT_EQ_INT FAILED:");
|
||||||
PrintLine(" != ");
|
PrintInt(a);
|
||||||
PrintInt(b);
|
PrintLine(" != ");
|
||||||
|
PrintInt(b);
|
||||||
|
bux_exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
PrintLine(" != ");
|
||||||
|
PrintLine(b);
|
||||||
|
bux_exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assert two bools are equal.
|
||||||
|
func Test_AssertEqBool(a: bool, b: bool) {
|
||||||
|
if a != b {
|
||||||
|
PrintLine("ASSERT_EQ_BOOL FAILED");
|
||||||
|
bux_exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assert `cond` is true.
|
||||||
|
func Test_AssertTrue(cond: bool) {
|
||||||
|
if !cond {
|
||||||
|
PrintLine("ASSERT_TRUE FAILED");
|
||||||
|
bux_exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assert `cond` is false.
|
||||||
|
func Test_AssertFalse(cond: bool) {
|
||||||
|
if cond {
|
||||||
|
PrintLine("ASSERT_FALSE FAILED");
|
||||||
|
bux_exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fail the test with a message and exit 1.
|
||||||
|
func Test_Fail(msg: String) {
|
||||||
|
PrintLine("FAIL:");
|
||||||
|
PrintLine(msg);
|
||||||
bux_exit(1);
|
bux_exit(1);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func Test_AssertNeqInt(a: int, b: int) {
|
/// Print a PASS line (for human-readable runners / goldens).
|
||||||
if a == b {
|
func Test_Pass(msg: String) {
|
||||||
PrintLine("ASSERT_NEQ_INT FAILED: both are");
|
PrintLine("PASS:");
|
||||||
PrintInt(a);
|
PrintLine(msg);
|
||||||
bux_exit(1);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func Test_AssertEqString(a: String, b: String) {
|
|
||||||
if !String_Eq(a, b) {
|
|
||||||
PrintLine("ASSERT_EQ_STRING FAILED:");
|
|
||||||
PrintLine(a);
|
|
||||||
PrintLine(" != ");
|
|
||||||
PrintLine(b);
|
|
||||||
bux_exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_AssertEqBool(a: bool, b: bool) {
|
|
||||||
if a != b {
|
|
||||||
PrintLine("ASSERT_EQ_BOOL FAILED");
|
|
||||||
bux_exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_AssertTrue(cond: bool) {
|
|
||||||
if !cond {
|
|
||||||
PrintLine("ASSERT_TRUE FAILED");
|
|
||||||
bux_exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_AssertFalse(cond: bool) {
|
|
||||||
if cond {
|
|
||||||
PrintLine("ASSERT_FALSE FAILED");
|
|
||||||
bux_exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_Fail(msg: String) {
|
|
||||||
PrintLine("FAIL:");
|
|
||||||
PrintLine(msg);
|
|
||||||
bux_exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_Pass(msg: String) {
|
|
||||||
PrintLine("PASS:");
|
|
||||||
PrintLine(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-12
@@ -1,19 +1,19 @@
|
|||||||
module Std::Time {
|
module Std::Time {
|
||||||
|
|
||||||
extern func bux_time_ms() -> int64;
|
extern func bux_time_ms() -> int64;
|
||||||
extern func bux_time_us() -> int64;
|
extern func bux_time_us() -> int64;
|
||||||
extern func bux_sleep_ms(ms: int64);
|
extern func bux_sleep_ms(ms: int64);
|
||||||
|
|
||||||
func Time_NowMs() -> int64 {
|
func Time_NowMs() -> int64 {
|
||||||
return bux_time_ms();
|
return bux_time_ms();
|
||||||
}
|
}
|
||||||
|
|
||||||
func Time_NowUs() -> int64 {
|
func Time_NowUs() -> int64 {
|
||||||
return bux_time_us();
|
return bux_time_us();
|
||||||
}
|
}
|
||||||
|
|
||||||
func Time_SleepMs(ms: int64) {
|
func Time_SleepMs(ms: int64) {
|
||||||
bux_sleep_ms(ms);
|
bux_sleep_ms(ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-49
@@ -3,65 +3,65 @@
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
module Std::Crypto::Aes {
|
module Std::Crypto::Aes {
|
||||||
|
|
||||||
import Std::Mem::{Alloc, Free};
|
import Std::Mem::{Alloc, Free};
|
||||||
import Std::String::{String_Len};
|
import Std::String::{String_Len};
|
||||||
|
|
||||||
extern func bux_random_bytes(buf: *void, len: int) -> int;
|
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_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_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_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_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_KEY_SIZE: int = 32; // 256 bits
|
||||||
const AES_IV_SIZE: int = 16; // 128 bits
|
const AES_IV_SIZE: int = 16; // 128 bits
|
||||||
const AES_GCM_TAG_SIZE: int = 16;
|
const AES_GCM_TAG_SIZE: int = 16;
|
||||||
|
|
||||||
// Generate a random 256-bit AES key (returns raw 32 bytes)
|
// Generate a random 256-bit AES key (returns raw 32 bytes)
|
||||||
func Aes_GenerateKey() -> String {
|
func Aes_GenerateKey() -> String {
|
||||||
let buf: *void = Alloc(AES_KEY_SIZE as uint);
|
let buf: *void = Alloc(AES_KEY_SIZE as uint);
|
||||||
if bux_random_bytes(buf, AES_KEY_SIZE) != 1 {
|
if bux_random_bytes(buf, AES_KEY_SIZE) != 1 {
|
||||||
Free(buf);
|
Free(buf);
|
||||||
return "";
|
return "";
|
||||||
|
}
|
||||||
|
return buf as String;
|
||||||
}
|
}
|
||||||
return buf as String;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a random 128-bit IV (returns raw 16 bytes)
|
// Generate a random 128-bit IV (returns raw 16 bytes)
|
||||||
func Aes_GenerateIV() -> String {
|
func Aes_GenerateIV() -> String {
|
||||||
let buf: *void = Alloc(AES_IV_SIZE as uint);
|
let buf: *void = Alloc(AES_IV_SIZE as uint);
|
||||||
if bux_random_bytes(buf, AES_IV_SIZE) != 1 {
|
if bux_random_bytes(buf, AES_IV_SIZE) != 1 {
|
||||||
Free(buf);
|
Free(buf);
|
||||||
return "";
|
return "";
|
||||||
|
}
|
||||||
|
return buf as String;
|
||||||
}
|
}
|
||||||
return buf as String;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AES-256-CBC encrypt. plain and key are binary strings, iv is 16 bytes.
|
// 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).
|
// Returns ciphertext (may be longer than plain due to PKCS#7 padding).
|
||||||
func Aes_CbcEncrypt(plain: String, key: String, iv: String) -> String {
|
func Aes_CbcEncrypt(plain: String, key: String, iv: String) -> String {
|
||||||
let outlen: int = 0;
|
let outlen: int = 0;
|
||||||
return bux_aes_256_cbc_encrypt(plain, String_Len(plain) as int, key, iv, &outlen);
|
return bux_aes_256_cbc_encrypt(plain, String_Len(plain) as int, key, iv, &outlen);
|
||||||
}
|
}
|
||||||
|
|
||||||
// AES-256-CBC decrypt. Returns plaintext.
|
// AES-256-CBC decrypt. Returns plaintext.
|
||||||
func Aes_CbcDecrypt(cipher: String, key: String, iv: String) -> String {
|
func Aes_CbcDecrypt(cipher: String, key: String, iv: String) -> String {
|
||||||
let outlen: int = 0;
|
let outlen: int = 0;
|
||||||
return bux_aes_256_cbc_decrypt(cipher, String_Len(cipher) as int, key, iv, &outlen);
|
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.
|
// AES-256-GCM encrypt. Returns ciphertext. tag receives 16-byte authentication tag.
|
||||||
func Aes_GcmEncrypt(plain: String, key: String, iv: String, tag: *void) -> String {
|
func Aes_GcmEncrypt(plain: String, key: String, iv: String, tag: *void) -> String {
|
||||||
let outlen: int = 0;
|
let outlen: int = 0;
|
||||||
return bux_aes_256_gcm_encrypt(plain, String_Len(plain) as int, key, iv, tag, &outlen);
|
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.
|
// 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 {
|
func Aes_GcmDecrypt(cipher: String, key: String, iv: String, tag: String) -> String {
|
||||||
let outlen: int = 0;
|
let outlen: int = 0;
|
||||||
return bux_aes_256_gcm_decrypt(cipher, String_Len(cipher) as int, key, iv, tag, &outlen);
|
return bux_aes_256_gcm_decrypt(cipher, String_Len(cipher) as int, key, iv, tag, &outlen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-25
@@ -3,32 +3,32 @@
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
module Std::Crypto::Base64 {
|
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_encode(data: String, len: int) -> String;
|
||||||
extern func bux_base64_decode(data: String, len: int, outlen: *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_encode(data: String, len: int) -> String;
|
||||||
extern func bux_base64url_decode(data: String, len: int, outlen: *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);
|
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;
|
let outlen: int = 0;
|
||||||
return bux_base64_decode(s, String_Len(s) as int, &outlen);
|
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);
|
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;
|
let outlen: int = 0;
|
||||||
return bux_base64url_decode(s, String_Len(s) as int, &outlen);
|
return bux_base64url_decode(s, String_Len(s) as int, &outlen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+43
-43
@@ -3,56 +3,56 @@
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
module Std::Crypto::Ecdsa {
|
module Std::Crypto::Ecdsa {
|
||||||
|
|
||||||
import Std::Mem::{Alloc, Free};
|
import Std::Mem::{Alloc, Free};
|
||||||
import Std::String::{String_Len};
|
import Std::String::{String_Len};
|
||||||
|
|
||||||
// Extern declarations for the runtime C implementations
|
// 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_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_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_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_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_encode(data: String, len: int) -> String;
|
||||||
extern func bux_base64_decode(data: String, len: int, outlen: *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;
|
let siglen: int = 0;
|
||||||
return bux_ecdsa_sign_p256(pemPrivateKey, String_Len(pemPrivateKey) as int, data, String_Len(data) as int, &siglen);
|
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);
|
let raw: String = Ecdsa_SignP256(pemPrivateKey, data);
|
||||||
return bux_base64_encode(raw, String_Len(raw) as int);
|
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);
|
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;
|
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 outlen: int = 0;
|
||||||
let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
|
let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
|
||||||
return Ecdsa_VerifyP256(pemPublicKey, data, sig);
|
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;
|
let siglen: int = 0;
|
||||||
return bux_ecdsa_sign_p384(pemPrivateKey, String_Len(pemPrivateKey) as int, data, String_Len(data) as int, &siglen);
|
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);
|
let raw: String = Ecdsa_SignP384(pemPrivateKey, data);
|
||||||
return bux_base64_encode(raw, String_Len(raw) as int);
|
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);
|
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;
|
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 outlen: int = 0;
|
||||||
let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
|
let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
|
||||||
return Ecdsa_VerifyP384(pemPublicKey, data, sig);
|
return Ecdsa_VerifyP384(pemPublicKey, data, sig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+59
-59
@@ -3,78 +3,78 @@
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
module Std::Crypto::Ed25519 {
|
module Std::Crypto::Ed25519 {
|
||||||
|
|
||||||
import Std::Mem::{Alloc, Free};
|
import Std::Mem::{Alloc, Free};
|
||||||
import Std::String::{String_Len, String_Concat};
|
import Std::String::{String_Len, String_Concat};
|
||||||
|
|
||||||
extern func bux_ed25519_keypair(pubKey: *void, privKey: *void) -> int;
|
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_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_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_encode(data: String, len: int) -> String;
|
||||||
extern func bux_base64_decode(data: String, len: int, outlen: *int) -> String;
|
extern func bux_base64_decode(data: String, len: int, outlen: *int) -> String;
|
||||||
|
|
||||||
const ED25519_PUBKEY_SIZE: int = 32;
|
const ED25519_PUBKEY_SIZE: int = 32;
|
||||||
const ED25519_PRIVKEY_SIZE: int = 32;
|
const ED25519_PRIVKEY_SIZE: int = 32;
|
||||||
const ED25519_SIG_SIZE: int = 64;
|
const ED25519_SIG_SIZE: int = 64;
|
||||||
|
|
||||||
// --- Key Generation ---
|
// --- Key Generation ---
|
||||||
|
|
||||||
// Ed25519_Keypair: generates a new keypair.
|
// Ed25519_Keypair: generates a new keypair.
|
||||||
// Returns true on success. pubKey and privKey receive 32-byte raw keys.
|
// Returns true on success. pubKey and privKey receive 32-byte raw keys.
|
||||||
func Ed25519_Keypair(pubKey: *void, privKey: *void) -> bool {
|
func Ed25519_Keypair(pubKey: *void, privKey: *void) -> bool {
|
||||||
let r: int = bux_ed25519_keypair(pubKey, privKey);
|
let r: int = bux_ed25519_keypair(pubKey, privKey);
|
||||||
return r == 1;
|
return r == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience: generate and return base64-encoded keypair
|
// Convenience: generate and return base64-encoded keypair
|
||||||
func Ed25519_KeypairBase64() -> String {
|
func Ed25519_KeypairBase64() -> String {
|
||||||
let pubBuf: *void = Alloc(ED25519_PUBKEY_SIZE as uint);
|
let pubBuf: *void = Alloc(ED25519_PUBKEY_SIZE as uint);
|
||||||
let priv: *void = Alloc(ED25519_PRIVKEY_SIZE as uint);
|
let priv: *void = Alloc(ED25519_PRIVKEY_SIZE as uint);
|
||||||
if bux_ed25519_keypair(pubBuf, priv) != 1 {
|
if bux_ed25519_keypair(pubBuf, priv) != 1 {
|
||||||
|
Free(pubBuf);
|
||||||
|
Free(priv);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
// Return "pub_b64:priv_b64"
|
||||||
|
let pubB64: String = bux_base64_encode(pubBuf as String, ED25519_PUBKEY_SIZE);
|
||||||
|
let privB64: String = bux_base64_encode(priv as String, ED25519_PRIVKEY_SIZE);
|
||||||
Free(pubBuf);
|
Free(pubBuf);
|
||||||
Free(priv);
|
Free(priv);
|
||||||
return "";
|
let pair: String = String_Concat(pubB64, ":");
|
||||||
|
return String_Concat(pair, privB64);
|
||||||
}
|
}
|
||||||
// Return "pub_b64:priv_b64"
|
|
||||||
let pubB64: String = bux_base64_encode(pubBuf as String, ED25519_PUBKEY_SIZE);
|
|
||||||
let privB64: String = bux_base64_encode(priv as String, ED25519_PRIVKEY_SIZE);
|
|
||||||
Free(pubBuf);
|
|
||||||
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.
|
// Ed25519_Sign: sign data with 32-byte raw private key. Returns 64-byte raw signature.
|
||||||
func Ed25519_Sign(privKey: String, data: String) -> String {
|
func Ed25519_Sign(privKey: String, data: String) -> String {
|
||||||
let sig: *void = Alloc(ED25519_SIG_SIZE as uint);
|
let sig: *void = Alloc(ED25519_SIG_SIZE as uint);
|
||||||
if bux_ed25519_sign(privKey, data, String_Len(data) as int, sig) != 1 {
|
if bux_ed25519_sign(privKey, data, String_Len(data) as int, sig) != 1 {
|
||||||
Free(sig);
|
Free(sig);
|
||||||
return "";
|
return "";
|
||||||
|
}
|
||||||
|
return sig as String;
|
||||||
}
|
}
|
||||||
return sig as String;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convenience: sign and return base64-encoded signature
|
// Convenience: sign and return base64-encoded signature
|
||||||
func Ed25519_SignBase64(privKey: String, data: String) -> String {
|
func Ed25519_SignBase64(privKey: String, data: String) -> String {
|
||||||
let sig: String = Ed25519_Sign(privKey, data);
|
let sig: String = Ed25519_Sign(privKey, data);
|
||||||
if String_Len(sig) == 0 { return ""; }
|
if String_Len(sig) == 0 { return ""; }
|
||||||
return bux_base64_encode(sig, ED25519_SIG_SIZE);
|
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.
|
// 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 {
|
func Ed25519_Verify(pubKey: String, signature: String, data: String) -> bool {
|
||||||
let r: int = bux_ed25519_verify(pubKey, signature, data, String_Len(data) as int);
|
let r: int = bux_ed25519_verify(pubKey, signature, data, String_Len(data) as int);
|
||||||
return r == 1;
|
return r == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience: verify a base64-encoded signature
|
// Convenience: verify a base64-encoded signature
|
||||||
func Ed25519_VerifyBase64(pubKey: String, signatureB64: String, data: String) -> bool {
|
func Ed25519_VerifyBase64(pubKey: String, signatureB64: String, data: String) -> bool {
|
||||||
let outlen: int = 0;
|
let outlen: int = 0;
|
||||||
let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
|
let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
|
||||||
if outlen != ED25519_SIG_SIZE { return false; }
|
if outlen != ED25519_SIG_SIZE { return false; }
|
||||||
return Ed25519_Verify(pubKey, sig, data);
|
return Ed25519_Verify(pubKey, sig, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+64
-64
@@ -3,71 +3,71 @@
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
module Std::Crypto::Hash {
|
module Std::Crypto::Hash {
|
||||||
|
|
||||||
import Std::Mem::{Alloc, Free};
|
import Std::Mem::{Alloc, Free};
|
||||||
import Std::String::{String_Len};
|
import Std::String::{String_Len};
|
||||||
|
|
||||||
extern func bux_sha1(data: String, len: int, out: *void);
|
extern func bux_sha1(data: String, len: int, out: *void);
|
||||||
extern func bux_sha256(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_sha384(data: String, len: int, out: *void);
|
||||||
extern func bux_sha512(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_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 len: int = String_Len(data) as int;
|
||||||
let buf: *void = Alloc(20);
|
let buf: *void = Alloc(20);
|
||||||
bux_sha1(data, len, buf);
|
bux_sha1(data, len, buf);
|
||||||
let result: String = bux_bytes_to_hex(buf, 20);
|
let result: String = bux_bytes_to_hex(buf, 20);
|
||||||
Free(buf);
|
Free(buf);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
func Hash_Sha256(data: String) -> String {
|
func Hash_Sha256(data: String) -> String {
|
||||||
let len: int = String_Len(data) as int;
|
let len: int = String_Len(data) as int;
|
||||||
let buf: *void = Alloc(32);
|
let buf: *void = Alloc(32);
|
||||||
bux_sha256(data, len, buf);
|
bux_sha256(data, len, buf);
|
||||||
let result: String = bux_bytes_to_hex(buf, 32);
|
let result: String = bux_bytes_to_hex(buf, 32);
|
||||||
Free(buf);
|
Free(buf);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
func Hash_Sha384(data: String) -> String {
|
func Hash_Sha384(data: String) -> String {
|
||||||
let len: int = String_Len(data) as int;
|
let len: int = String_Len(data) as int;
|
||||||
let buf: *void = Alloc(48);
|
let buf: *void = Alloc(48);
|
||||||
bux_sha384(data, len, buf);
|
bux_sha384(data, len, buf);
|
||||||
let result: String = bux_bytes_to_hex(buf, 48);
|
let result: String = bux_bytes_to_hex(buf, 48);
|
||||||
Free(buf);
|
Free(buf);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
func Hash_Sha512(data: String) -> String {
|
func Hash_Sha512(data: String) -> String {
|
||||||
let len: int = String_Len(data) as int;
|
let len: int = String_Len(data) as int;
|
||||||
let buf: *void = Alloc(64);
|
let buf: *void = Alloc(64);
|
||||||
bux_sha512(data, len, buf);
|
bux_sha512(data, len, buf);
|
||||||
let result: String = bux_bytes_to_hex(buf, 64);
|
let result: String = bux_bytes_to_hex(buf, 64);
|
||||||
Free(buf);
|
Free(buf);
|
||||||
return result;
|
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);
|
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);
|
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);
|
bux_sha512(data, String_Len(data) as int, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Digest sizes ---
|
// --- Digest sizes ---
|
||||||
|
|
||||||
func Hash_Sha1Size() -> int { return 20; }
|
func Hash_Sha1Size() -> int { return 20; }
|
||||||
func Hash_Sha256Size() -> int { return 32; }
|
func Hash_Sha256Size() -> int { return 32; }
|
||||||
func Hash_Sha384Size() -> int { return 48; }
|
func Hash_Sha384Size() -> int { return 48; }
|
||||||
func Hash_Sha512Size() -> int { return 64; }
|
func Hash_Sha512Size() -> int { return 64; }
|
||||||
}
|
}
|
||||||
|
|||||||
+83
-83
@@ -3,90 +3,90 @@
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
module Std::Crypto::Hmac {
|
module Std::Crypto::Hmac {
|
||||||
|
|
||||||
import Std::Mem::{Alloc, Free};
|
import Std::Mem::{Alloc, Free};
|
||||||
import Std::String::{String_Len};
|
import Std::String::{String_Len};
|
||||||
|
|
||||||
extern func bux_hmac_sha256(key: String, keylen: int, msg: String, msglen: int, out: *void);
|
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_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_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_bytes_to_hex(data: *void, len: int) -> String;
|
||||||
extern func bux_base64_encode(data: String, 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 kl: int = String_Len(key) as int;
|
||||||
let ml: int = String_Len(message) as int;
|
let ml: int = String_Len(message) as int;
|
||||||
let buf: *void = Alloc(32);
|
let buf: *void = Alloc(32);
|
||||||
bux_hmac_sha256(key, kl, message, ml, buf);
|
bux_hmac_sha256(key, kl, message, ml, buf);
|
||||||
let result: String = bux_bytes_to_hex(buf, 32);
|
let result: String = bux_bytes_to_hex(buf, 32);
|
||||||
Free(buf);
|
Free(buf);
|
||||||
return result;
|
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);
|
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 kl: int = String_Len(key) as int;
|
||||||
let ml: int = String_Len(message) as int;
|
let ml: int = String_Len(message) as int;
|
||||||
let buf: *void = Alloc(32);
|
let buf: *void = Alloc(32);
|
||||||
bux_hmac_sha256(key, kl, message, ml, buf);
|
bux_hmac_sha256(key, kl, message, ml, buf);
|
||||||
let result: String = bux_base64_encode(buf as String, 32);
|
let result: String = bux_base64_encode(buf as String, 32);
|
||||||
Free(buf);
|
Free(buf);
|
||||||
return result;
|
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 kl: int = String_Len(key) as int;
|
||||||
let ml: int = String_Len(message) as int;
|
let ml: int = String_Len(message) as int;
|
||||||
let buf: *void = Alloc(48);
|
let buf: *void = Alloc(48);
|
||||||
bux_hmac_sha384(key, kl, message, ml, buf);
|
bux_hmac_sha384(key, kl, message, ml, buf);
|
||||||
let result: String = bux_bytes_to_hex(buf, 48);
|
let result: String = bux_bytes_to_hex(buf, 48);
|
||||||
Free(buf);
|
Free(buf);
|
||||||
return result;
|
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);
|
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 kl: int = String_Len(key) as int;
|
||||||
let ml: int = String_Len(message) as int;
|
let ml: int = String_Len(message) as int;
|
||||||
let buf: *void = Alloc(48);
|
let buf: *void = Alloc(48);
|
||||||
bux_hmac_sha384(key, kl, message, ml, buf);
|
bux_hmac_sha384(key, kl, message, ml, buf);
|
||||||
let result: String = bux_base64_encode(buf as String, 48);
|
let result: String = bux_base64_encode(buf as String, 48);
|
||||||
Free(buf);
|
Free(buf);
|
||||||
return result;
|
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 kl: int = String_Len(key) as int;
|
||||||
let ml: int = String_Len(message) as int;
|
let ml: int = String_Len(message) as int;
|
||||||
let buf: *void = Alloc(64);
|
let buf: *void = Alloc(64);
|
||||||
bux_hmac_sha512(key, kl, message, ml, buf);
|
bux_hmac_sha512(key, kl, message, ml, buf);
|
||||||
let result: String = bux_bytes_to_hex(buf, 64);
|
let result: String = bux_bytes_to_hex(buf, 64);
|
||||||
Free(buf);
|
Free(buf);
|
||||||
return result;
|
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);
|
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 kl: int = String_Len(key) as int;
|
||||||
let ml: int = String_Len(message) as int;
|
let ml: int = String_Len(message) as int;
|
||||||
let buf: *void = Alloc(64);
|
let buf: *void = Alloc(64);
|
||||||
bux_hmac_sha512(key, kl, message, ml, buf);
|
bux_hmac_sha512(key, kl, message, ml, buf);
|
||||||
let result: String = bux_base64_encode(buf as String, 64);
|
let result: String = bux_base64_encode(buf as String, 64);
|
||||||
Free(buf);
|
Free(buf);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+206
-206
@@ -4,233 +4,233 @@
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
module Std::Crypto::Jwt {
|
module Std::Crypto::Jwt {
|
||||||
|
|
||||||
import Std::Mem::{Alloc, Free};
|
import Std::Mem::{Alloc, Free};
|
||||||
import Std::String::{String_Len, String_Eq, String_StartsWith, String_Concat};
|
import Std::String::{String_Len, String_Eq, String_StartsWith, String_Concat};
|
||||||
import Std::Crypto::Base64::{Base64URL_Encode, Base64URL_Decode};
|
import Std::Crypto::Base64::{Base64URL_Encode, Base64URL_Decode};
|
||||||
import Std::Crypto::Hash::{Hash_Sha256Raw, Hash_Sha384Raw, Hash_Sha512Raw};
|
import Std::Crypto::Hash::{Hash_Sha256Raw, Hash_Sha384Raw, Hash_Sha512Raw};
|
||||||
import Std::Crypto::Hmac::{Hmac_Sha256Raw, Hmac_Sha384Raw, Hmac_Sha512Raw};
|
import Std::Crypto::Hmac::{Hmac_Sha256Raw, Hmac_Sha384Raw, Hmac_Sha512Raw};
|
||||||
import Std::Crypto::Rsa::{Rsa_SignSha256, Rsa_SignSha384, Rsa_SignSha512,
|
import Std::Crypto::Rsa::{Rsa_SignSha256, Rsa_SignSha384, Rsa_SignSha512,
|
||||||
Rsa_VerifySha256, Rsa_VerifySha384, Rsa_VerifySha512};
|
Rsa_VerifySha256, Rsa_VerifySha384, Rsa_VerifySha512};
|
||||||
import Std::Crypto::Ecdsa::{Ecdsa_SignP256, Ecdsa_SignP384, Ecdsa_VerifyP256, Ecdsa_VerifyP384};
|
import Std::Crypto::Ecdsa::{Ecdsa_SignP256, Ecdsa_SignP384, Ecdsa_VerifyP256, Ecdsa_VerifyP384};
|
||||||
import Std::Crypto::Ed25519::{Ed25519_Sign, Ed25519_Verify};
|
import Std::Crypto::Ed25519::{Ed25519_Sign, Ed25519_Verify};
|
||||||
|
|
||||||
extern func bux_base64_encode(data: String, len: int) -> 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_count(s: String, delim: String) -> uint;
|
||||||
extern func bux_str_split_part(s: String, delim: String, index: uint) -> String;
|
extern func bux_str_split_part(s: String, delim: String, index: uint) -> String;
|
||||||
|
|
||||||
// --- JWT Algorithm enum ---
|
// --- JWT Algorithm enum ---
|
||||||
enum JwtAlg {
|
enum JwtAlg {
|
||||||
HS256,
|
HS256,
|
||||||
HS384,
|
HS384,
|
||||||
HS512,
|
HS512,
|
||||||
RS256,
|
RS256,
|
||||||
RS384,
|
RS384,
|
||||||
RS512,
|
RS512,
|
||||||
ES256,
|
ES256,
|
||||||
ES384,
|
ES384,
|
||||||
EdDSA,
|
EdDSA,
|
||||||
}
|
|
||||||
|
|
||||||
// --- Header ---
|
|
||||||
|
|
||||||
// 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\"}"; }
|
|
||||||
if alg.tag == JwtAlg_RS256 { return "{\"alg\":\"RS256\",\"typ\":\"JWT\"}"; }
|
|
||||||
if alg.tag == JwtAlg_RS384 { return "{\"alg\":\"RS384\",\"typ\":\"JWT\"}"; }
|
|
||||||
if alg.tag == JwtAlg_RS512 { return "{\"alg\":\"RS512\",\"typ\":\"JWT\"}"; }
|
|
||||||
if alg.tag == JwtAlg_ES256 { return "{\"alg\":\"ES256\",\"typ\":\"JWT\"}"; }
|
|
||||||
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 ---
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
Hmac_Sha256Raw(key, signingInput, buf);
|
|
||||||
let result: String = bux_base64_encode(buf as String, 32);
|
|
||||||
Free(buf);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
if alg.tag == JwtAlg_HS384 {
|
|
||||||
let buf: *void = Alloc(48);
|
|
||||||
Hmac_Sha384Raw(key, signingInput, buf);
|
|
||||||
let result: String = bux_base64_encode(buf as String, 48);
|
|
||||||
Free(buf);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
if alg.tag == JwtAlg_HS512 {
|
|
||||||
let buf: *void = Alloc(64);
|
|
||||||
Hmac_Sha512Raw(key, signingInput, buf);
|
|
||||||
let result: String = bux_base64_encode(buf as String, 64);
|
|
||||||
Free(buf);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- RSA algorithms (key is PEM private key) ---
|
// --- Header ---
|
||||||
if alg.tag == JwtAlg_RS256 {
|
|
||||||
let raw: String = Rsa_SignSha256(key, signingInput);
|
// Jwt_MakeHeader: build the JWT header JSON string for the given algorithm
|
||||||
return bux_base64_encode(raw, String_Len(raw) as int);
|
func Jwt_MakeHeader(alg: JwtAlg) -> String {
|
||||||
}
|
if alg.tag == JwtAlg_HS256 { return "{\"alg\":\"HS256\",\"typ\":\"JWT\"}"; }
|
||||||
if alg.tag == JwtAlg_RS384 {
|
if alg.tag == JwtAlg_HS384 { return "{\"alg\":\"HS384\",\"typ\":\"JWT\"}"; }
|
||||||
let raw: String = Rsa_SignSha384(key, signingInput);
|
if alg.tag == JwtAlg_HS512 { return "{\"alg\":\"HS512\",\"typ\":\"JWT\"}"; }
|
||||||
return bux_base64_encode(raw, String_Len(raw) as int);
|
if alg.tag == JwtAlg_RS256 { return "{\"alg\":\"RS256\",\"typ\":\"JWT\"}"; }
|
||||||
}
|
if alg.tag == JwtAlg_RS384 { return "{\"alg\":\"RS384\",\"typ\":\"JWT\"}"; }
|
||||||
if alg.tag == JwtAlg_RS512 {
|
if alg.tag == JwtAlg_RS512 { return "{\"alg\":\"RS512\",\"typ\":\"JWT\"}"; }
|
||||||
let raw: String = Rsa_SignSha512(key, signingInput);
|
if alg.tag == JwtAlg_ES256 { return "{\"alg\":\"ES256\",\"typ\":\"JWT\"}"; }
|
||||||
return bux_base64_encode(raw, String_Len(raw) as int);
|
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\"}";
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- ECDSA algorithms (key is PEM private key) ---
|
// --- Signing ---
|
||||||
if alg.tag == JwtAlg_ES256 {
|
|
||||||
let raw: String = Ecdsa_SignP256(key, signingInput);
|
// Sign the JWT signing input with the given algorithm
|
||||||
return bux_base64_encode(raw, String_Len(raw) as int);
|
func Jwt_Sign(alg: JwtAlg, signingInput: String, key: String) -> String {
|
||||||
}
|
// --- HMAC algorithms ---
|
||||||
if alg.tag == JwtAlg_ES384 {
|
if alg.tag == JwtAlg_HS256 {
|
||||||
let raw: String = Ecdsa_SignP384(key, signingInput);
|
let buf: *void = Alloc(32);
|
||||||
return bux_base64_encode(raw, String_Len(raw) as int);
|
Hmac_Sha256Raw(key, signingInput, buf);
|
||||||
|
let result: String = bux_base64_encode(buf as String, 32);
|
||||||
|
Free(buf);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if alg.tag == JwtAlg_HS384 {
|
||||||
|
let buf: *void = Alloc(48);
|
||||||
|
Hmac_Sha384Raw(key, signingInput, buf);
|
||||||
|
let result: String = bux_base64_encode(buf as String, 48);
|
||||||
|
Free(buf);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if alg.tag == JwtAlg_HS512 {
|
||||||
|
let buf: *void = Alloc(64);
|
||||||
|
Hmac_Sha512Raw(key, signingInput, buf);
|
||||||
|
let result: String = bux_base64_encode(buf as String, 64);
|
||||||
|
Free(buf);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- RSA algorithms (key is PEM private key) ---
|
||||||
|
if alg.tag == JwtAlg_RS256 {
|
||||||
|
let raw: String = Rsa_SignSha256(key, signingInput);
|
||||||
|
return bux_base64_encode(raw, String_Len(raw) as int);
|
||||||
|
}
|
||||||
|
if alg.tag == JwtAlg_RS384 {
|
||||||
|
let raw: String = Rsa_SignSha384(key, signingInput);
|
||||||
|
return bux_base64_encode(raw, String_Len(raw) as int);
|
||||||
|
}
|
||||||
|
if alg.tag == JwtAlg_RS512 {
|
||||||
|
let raw: String = Rsa_SignSha512(key, signingInput);
|
||||||
|
return bux_base64_encode(raw, String_Len(raw) as int);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ECDSA algorithms (key is PEM private key) ---
|
||||||
|
if alg.tag == JwtAlg_ES256 {
|
||||||
|
let raw: String = Ecdsa_SignP256(key, signingInput);
|
||||||
|
return bux_base64_encode(raw, String_Len(raw) as int);
|
||||||
|
}
|
||||||
|
if alg.tag == JwtAlg_ES384 {
|
||||||
|
let raw: String = Ecdsa_SignP384(key, signingInput);
|
||||||
|
return bux_base64_encode(raw, String_Len(raw) as int);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- EdDSA (key is 32-byte raw private key) ---
|
||||||
|
if alg.tag == JwtAlg_EdDSA {
|
||||||
|
return Ed25519_Sign(key, signingInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- EdDSA (key is 32-byte raw private key) ---
|
// --- Verify ---
|
||||||
if alg.tag == JwtAlg_EdDSA {
|
|
||||||
return Ed25519_Sign(key, signingInput);
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
// 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);
|
||||||
|
Hmac_Sha256Raw(key, signingInput, expectBuf);
|
||||||
|
let expectB64: String = bux_base64_encode(expectBuf as String, 32);
|
||||||
|
Free(expectBuf);
|
||||||
|
return String_Eq(expectB64, signatureB64);
|
||||||
|
}
|
||||||
|
if alg.tag == JwtAlg_HS384 {
|
||||||
|
let expectBuf: *void = Alloc(48);
|
||||||
|
Hmac_Sha384Raw(key, signingInput, expectBuf);
|
||||||
|
let expectB64: String = bux_base64_encode(expectBuf as String, 48);
|
||||||
|
Free(expectBuf);
|
||||||
|
return String_Eq(expectB64, signatureB64);
|
||||||
|
}
|
||||||
|
if alg.tag == JwtAlg_HS512 {
|
||||||
|
let expectBuf: *void = Alloc(64);
|
||||||
|
Hmac_Sha512Raw(key, signingInput, expectBuf);
|
||||||
|
let expectB64: String = bux_base64_encode(expectBuf as String, 64);
|
||||||
|
Free(expectBuf);
|
||||||
|
return String_Eq(expectB64, signatureB64);
|
||||||
|
}
|
||||||
|
|
||||||
// --- Verify ---
|
// --- RSA algorithms ---
|
||||||
|
if alg.tag == JwtAlg_RS256 { return Rsa_VerifySha256(key, signingInput, signatureB64); }
|
||||||
|
if alg.tag == JwtAlg_RS384 { return Rsa_VerifySha384(key, signingInput, signatureB64); }
|
||||||
|
if alg.tag == JwtAlg_RS512 { return Rsa_VerifySha512(key, signingInput, signatureB64); }
|
||||||
|
|
||||||
// Verify a JWT signature
|
// --- ECDSA algorithms ---
|
||||||
func Jwt_Verify(alg: JwtAlg, signingInput: String, signatureB64: String, key: String) -> bool {
|
if alg.tag == JwtAlg_ES256 { return Ecdsa_VerifyP256(key, signingInput, signatureB64); }
|
||||||
// --- HMAC algorithms ---
|
if alg.tag == JwtAlg_ES384 { return Ecdsa_VerifyP384(key, signingInput, signatureB64); }
|
||||||
if alg.tag == JwtAlg_HS256 {
|
|
||||||
let expectBuf: *void = Alloc(32);
|
|
||||||
Hmac_Sha256Raw(key, signingInput, expectBuf);
|
|
||||||
let expectB64: String = bux_base64_encode(expectBuf as String, 32);
|
|
||||||
Free(expectBuf);
|
|
||||||
return String_Eq(expectB64, signatureB64);
|
|
||||||
}
|
|
||||||
if alg.tag == JwtAlg_HS384 {
|
|
||||||
let expectBuf: *void = Alloc(48);
|
|
||||||
Hmac_Sha384Raw(key, signingInput, expectBuf);
|
|
||||||
let expectB64: String = bux_base64_encode(expectBuf as String, 48);
|
|
||||||
Free(expectBuf);
|
|
||||||
return String_Eq(expectB64, signatureB64);
|
|
||||||
}
|
|
||||||
if alg.tag == JwtAlg_HS512 {
|
|
||||||
let expectBuf: *void = Alloc(64);
|
|
||||||
Hmac_Sha512Raw(key, signingInput, expectBuf);
|
|
||||||
let expectB64: String = bux_base64_encode(expectBuf as String, 64);
|
|
||||||
Free(expectBuf);
|
|
||||||
return String_Eq(expectB64, signatureB64);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- RSA algorithms ---
|
// --- EdDSA ---
|
||||||
if alg.tag == JwtAlg_RS256 { return Rsa_VerifySha256(key, signingInput, signatureB64); }
|
if alg.tag == JwtAlg_EdDSA { return Ed25519_Verify(key, signatureB64, signingInput); }
|
||||||
if alg.tag == JwtAlg_RS384 { return Rsa_VerifySha384(key, signingInput, signatureB64); }
|
|
||||||
if alg.tag == JwtAlg_RS512 { return Rsa_VerifySha512(key, signingInput, signatureB64); }
|
|
||||||
|
|
||||||
// --- ECDSA algorithms ---
|
|
||||||
if alg.tag == JwtAlg_ES256 { return Ecdsa_VerifyP256(key, signingInput, signatureB64); }
|
|
||||||
if alg.tag == JwtAlg_ES384 { return Ecdsa_VerifyP384(key, signingInput, signatureB64); }
|
|
||||||
|
|
||||||
// --- EdDSA ---
|
|
||||||
if alg.tag == JwtAlg_EdDSA { return Ed25519_Verify(key, signatureB64, signingInput); }
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- 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 {
|
|
||||||
let headerB64: String = Base64URL_Encode(headerJson);
|
|
||||||
let payloadB64: String = Base64URL_Encode(payloadJson);
|
|
||||||
let signingInput: String = String_Concat(headerB64, ".");
|
|
||||||
let signingInputFull: String = String_Concat(signingInput, payloadB64);
|
|
||||||
|
|
||||||
let sigB64: String = Jwt_Sign(alg, signingInputFull, key);
|
|
||||||
|
|
||||||
let part1: String = String_Concat(signingInputFull, ".");
|
|
||||||
return String_Concat(part1, sigB64);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- 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,
|
|
||||||
headerOut: *String, payloadOut: *String) -> bool {
|
|
||||||
// Split by "."
|
|
||||||
let partCount: uint = bux_str_split_count(token, ".");
|
|
||||||
if partCount != 3 { return false; }
|
|
||||||
|
|
||||||
let headerB64: String = bux_str_split_part(token, ".", 0);
|
|
||||||
let payloadB64: String = bux_str_split_part(token, ".", 1);
|
|
||||||
let sigB64: String = bux_str_split_part(token, ".", 2);
|
|
||||||
|
|
||||||
// Build signing input
|
|
||||||
let input: String = String_Concat(headerB64, ".");
|
|
||||||
let signingInput: String = String_Concat(input, payloadB64);
|
|
||||||
|
|
||||||
// Verify signature
|
|
||||||
if !Jwt_Verify(alg, signingInput, sigB64, key) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode
|
// --- Encode ---
|
||||||
headerOut[0] = Base64URL_Decode(headerB64);
|
|
||||||
payloadOut[0] = Base64URL_Decode(payloadB64);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Convenience: Encode with standard header ---
|
// 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, ".");
|
||||||
|
let signingInputFull: String = String_Concat(signingInput, payloadB64);
|
||||||
|
|
||||||
func Jwt_EncodeHS256(payloadJson: String, secret: String) -> String {
|
let sigB64: String = Jwt_Sign(alg, signingInputFull, key);
|
||||||
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 {
|
let part1: String = String_Concat(signingInputFull, ".");
|
||||||
let header: String = Jwt_MakeHeader(JwtAlg { tag: JwtAlg_HS384 });
|
return String_Concat(part1, sigB64);
|
||||||
return Jwt_Encode(header, payloadJson, JwtAlg { tag: JwtAlg_HS384 }, secret);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func Jwt_EncodeHS512(payloadJson: String, secret: String) -> String {
|
// --- Decode ---
|
||||||
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 {
|
// Jwt_Decode: decode and verify a JWT.
|
||||||
let header: String = Jwt_MakeHeader(JwtAlg { tag: JwtAlg_RS256 });
|
// token — the full "header.payload.signature" string
|
||||||
return Jwt_Encode(header, payloadJson, JwtAlg { tag: JwtAlg_RS256 }, pemPrivateKey);
|
// 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, ".");
|
||||||
|
if partCount != 3 { return false; }
|
||||||
|
|
||||||
func Jwt_EncodeES256(payloadJson: String, pemPrivateKey: String) -> String {
|
let headerB64: String = bux_str_split_part(token, ".", 0);
|
||||||
let header: String = Jwt_MakeHeader(JwtAlg { tag: JwtAlg_ES256 });
|
let payloadB64: String = bux_str_split_part(token, ".", 1);
|
||||||
return Jwt_Encode(header, payloadJson, JwtAlg { tag: JwtAlg_ES256 }, pemPrivateKey);
|
let sigB64: String = bux_str_split_part(token, ".", 2);
|
||||||
}
|
|
||||||
|
|
||||||
func Jwt_EncodeEdDSA(payloadJson: String, rawPrivKey: String) -> String {
|
// Build signing input
|
||||||
let header: String = Jwt_MakeHeader(JwtAlg { tag: JwtAlg_EdDSA });
|
let input: String = String_Concat(headerB64, ".");
|
||||||
return Jwt_Encode(header, payloadJson, JwtAlg { tag: JwtAlg_EdDSA }, rawPrivKey);
|
let signingInput: String = String_Concat(input, payloadB64);
|
||||||
}
|
|
||||||
|
// Verify signature
|
||||||
|
if !Jwt_Verify(alg, signingInput, sigB64, key) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode
|
||||||
|
headerOut[0] = Base64URL_Decode(headerB64);
|
||||||
|
payloadOut[0] = Base64URL_Decode(payloadB64);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Convenience: Encode with standard header ---
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
let header: String = Jwt_MakeHeader(JwtAlg { tag: JwtAlg_EdDSA });
|
||||||
|
return Jwt_Encode(header, payloadJson, JwtAlg { tag: JwtAlg_EdDSA }, rawPrivKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-45
@@ -3,61 +3,61 @@
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
module Std::Crypto::Random {
|
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_random_bytes(buf: *void, len: int) -> int;
|
||||||
extern func bux_base64_encode(data: String, len: int) -> String;
|
extern func bux_base64_encode(data: String, len: int) -> String;
|
||||||
extern func bux_bytes_to_hex(data: *void, 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
|
// RandomBytes: returns n cryptographically secure random bytes as a raw string
|
||||||
func Random_Bytes(n: int) -> String {
|
func Random_Bytes(n: int) -> String {
|
||||||
if n <= 0 { return ""; }
|
if n <= 0 { return ""; }
|
||||||
let buf: *void = Alloc(n as uint);
|
let buf: *void = Alloc(n as uint);
|
||||||
if bux_random_bytes(buf, n) != 1 {
|
if bux_random_bytes(buf, n) != 1 {
|
||||||
Free(buf);
|
Free(buf);
|
||||||
return "";
|
return "";
|
||||||
|
}
|
||||||
|
// Return raw buffer as string (binary-safe)
|
||||||
|
return buf as String;
|
||||||
}
|
}
|
||||||
// Return raw buffer as string (binary-safe)
|
|
||||||
return buf as String;
|
|
||||||
}
|
|
||||||
|
|
||||||
// RandomHex: returns n random bytes as lowercase hex
|
// RandomHex: returns n random bytes as lowercase hex
|
||||||
func Random_Hex(n: int) -> String {
|
func Random_Hex(n: int) -> String {
|
||||||
if n <= 0 { return ""; }
|
if n <= 0 { return ""; }
|
||||||
let buf: *void = Alloc(n as uint);
|
let buf: *void = Alloc(n as uint);
|
||||||
if bux_random_bytes(buf, n) != 1 {
|
if bux_random_bytes(buf, n) != 1 {
|
||||||
|
Free(buf);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
let result: String = bux_bytes_to_hex(buf, n);
|
||||||
Free(buf);
|
Free(buf);
|
||||||
return "";
|
return result;
|
||||||
}
|
}
|
||||||
let result: String = bux_bytes_to_hex(buf, n);
|
|
||||||
Free(buf);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// RandomBase64: returns n random bytes as base64-encoded string
|
// RandomBase64: returns n random bytes as base64-encoded string
|
||||||
func Random_Base64(n: int) -> String {
|
func Random_Base64(n: int) -> String {
|
||||||
if n <= 0 { return ""; }
|
if n <= 0 { return ""; }
|
||||||
let buf: *void = Alloc(n as uint);
|
let buf: *void = Alloc(n as uint);
|
||||||
if bux_random_bytes(buf, n) != 1 {
|
if bux_random_bytes(buf, n) != 1 {
|
||||||
|
Free(buf);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
let result: String = bux_base64_encode(buf as String, n);
|
||||||
Free(buf);
|
Free(buf);
|
||||||
return "";
|
return result;
|
||||||
}
|
}
|
||||||
let result: String = bux_base64_encode(buf as String, n);
|
|
||||||
Free(buf);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// RandomUint32: returns a random 32-bit unsigned integer
|
// RandomUint32: returns a random 32-bit unsigned integer
|
||||||
func Random_Uint32() -> uint {
|
func Random_Uint32() -> uint {
|
||||||
let buf: *void = Alloc(4);
|
let buf: *void = Alloc(4);
|
||||||
if bux_random_bytes(buf, 4) != 1 {
|
if bux_random_bytes(buf, 4) != 1 {
|
||||||
|
Free(buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// Interpret first 4 bytes as uint (native endian)
|
||||||
|
let ptr: *uint = buf as *uint;
|
||||||
|
let val: uint = *ptr;
|
||||||
Free(buf);
|
Free(buf);
|
||||||
return 0;
|
return val;
|
||||||
}
|
}
|
||||||
// Interpret first 4 bytes as uint (native endian)
|
|
||||||
let ptr: *uint = buf as *uint;
|
|
||||||
let val: uint = *ptr;
|
|
||||||
Free(buf);
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+81
-81
@@ -3,88 +3,88 @@
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
module Std::Crypto::Rsa {
|
module Std::Crypto::Rsa {
|
||||||
|
|
||||||
import Std::Mem::{Alloc, Free};
|
import Std::Mem::{Alloc, Free};
|
||||||
import Std::String::{String_Len};
|
import Std::String::{String_Len};
|
||||||
|
|
||||||
// Extern declarations for the runtime C implementations
|
// 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_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_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_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_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_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_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_encode(data: String, len: int) -> String;
|
||||||
extern func bux_base64_decode(data: String, len: int, outlen: *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
|
// Rsa_SignSha256: sign data with RSA private key (PEM format), returns raw signature
|
||||||
func Rsa_SignSha256(pemPrivateKey: String, data: String) -> String {
|
func Rsa_SignSha256(pemPrivateKey: String, data: String) -> String {
|
||||||
let siglen: int = 0;
|
let siglen: int = 0;
|
||||||
return bux_rsa_sign_sha256(pemPrivateKey, String_Len(pemPrivateKey) as int, data, String_Len(data) as int, &siglen);
|
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;
|
let siglen: int = 0;
|
||||||
return bux_rsa_sign_sha384(pemPrivateKey, String_Len(pemPrivateKey) as int, data, String_Len(data) as int, &siglen);
|
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;
|
let siglen: int = 0;
|
||||||
return bux_rsa_sign_sha512(pemPrivateKey, String_Len(pemPrivateKey) as int, data, String_Len(data) as int, &siglen);
|
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
|
// Convenience: sign and return base64-encoded signature
|
||||||
func Rsa_SignSha256Base64(pemPrivateKey: String, data: String) -> String {
|
func Rsa_SignSha256Base64(pemPrivateKey: String, data: String) -> String {
|
||||||
let raw: String = Rsa_SignSha256(pemPrivateKey, data);
|
let raw: String = Rsa_SignSha256(pemPrivateKey, data);
|
||||||
return bux_base64_encode(raw, String_Len(raw) as int);
|
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);
|
let raw: String = Rsa_SignSha384(pemPrivateKey, data);
|
||||||
return bux_base64_encode(raw, String_Len(raw) as int);
|
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);
|
let raw: String = Rsa_SignSha512(pemPrivateKey, data);
|
||||||
return bux_base64_encode(raw, String_Len(raw) as int);
|
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)
|
// Rsa_VerifySha256: verify raw signature against data with RSA public key (PEM)
|
||||||
// Returns true if signature is valid.
|
// Returns true if signature is valid.
|
||||||
func Rsa_VerifySha256(pemPublicKey: String, data: String, signature: String) -> bool {
|
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);
|
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;
|
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);
|
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;
|
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);
|
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;
|
return r == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience: verify base64-encoded signature
|
// Convenience: verify base64-encoded signature
|
||||||
func Rsa_VerifySha256Base64(pemPublicKey: String, data: String, signatureB64: String) -> bool {
|
func Rsa_VerifySha256Base64(pemPublicKey: String, data: String, signatureB64: String) -> bool {
|
||||||
let outlen: int = 0;
|
let outlen: int = 0;
|
||||||
let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
|
let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
|
||||||
return Rsa_VerifySha256(pemPublicKey, data, sig);
|
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 outlen: int = 0;
|
||||||
let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
|
let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
|
||||||
return Rsa_VerifySha384(pemPublicKey, data, sig);
|
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 outlen: int = 0;
|
||||||
let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
|
let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
|
||||||
return Rsa_VerifySha512(pemPublicKey, data, sig);
|
return Rsa_VerifySha512(pemPublicKey, data, sig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[Package]
|
||||||
|
Name = "greet"
|
||||||
|
Version = "0.1.1"
|
||||||
|
Type = "lib"
|
||||||
|
Authors = ["Bux Core"]
|
||||||
|
License = "MIT"
|
||||||
|
|
||||||
|
[Build]
|
||||||
|
Output = "Lib"
|
||||||
@@ -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";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+16
-16
@@ -1,23 +1,23 @@
|
|||||||
// main.bux — Entry point for the Bux self-hosting compiler
|
// main.bux — Entry point for the Bux self-hosting compiler
|
||||||
module Main {
|
module Main {
|
||||||
|
|
||||||
// C runtime for command-line args
|
// C runtime for command-line args
|
||||||
extern func bux_argc() -> int;
|
extern func bux_argc() -> int;
|
||||||
extern func bux_argv(index: int) -> String;
|
extern func bux_argv(index: int) -> String;
|
||||||
extern func bux_alloc(size: uint) -> *void;
|
extern func bux_alloc(size: uint) -> *void;
|
||||||
|
|
||||||
// Forward declaration from Cli module
|
// Forward declaration from Cli module
|
||||||
func Cli_Run(args: *String, argCount: int) -> int;
|
func Cli_Run(args: *String, argCount: int) -> int;
|
||||||
|
|
||||||
func Main() -> int {
|
func Main() -> int {
|
||||||
let count: int = bux_argc();
|
let count: int = bux_argc();
|
||||||
// Allocate array of String pointers
|
// Allocate array of String pointers
|
||||||
let args: *String = bux_alloc(count as uint * 8) as *String;
|
let args: *String = bux_alloc(count as uint * 8) as *String;
|
||||||
var i: int = 0;
|
var i: int = 0;
|
||||||
while i < count {
|
while i < count {
|
||||||
args[i] = bux_argv(i);
|
args[i] = bux_argv(i);
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
|
}
|
||||||
|
return Cli_Run(args, count);
|
||||||
}
|
}
|
||||||
return Cli_Run(args, count);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+434
-433
@@ -1,437 +1,438 @@
|
|||||||
// ast.bux — AST node types (Expr, Stmt, Decl, Pattern, TypeExpr)
|
// ast.bux — AST node types (Expr, Stmt, Decl, Pattern, TypeExpr)
|
||||||
module Ast {
|
module Ast {
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// SourceLocation (inline for convenience)
|
// SourceLocation (inline for convenience)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
struct SourceLoc {
|
struct SourceLoc {
|
||||||
line: uint32,
|
line: uint32,
|
||||||
column: uint32,
|
column: uint32,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Token (lightweight inline)
|
// Token (lightweight inline)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
struct AstToken {
|
struct AstToken {
|
||||||
kind: int,
|
kind: int,
|
||||||
text: String,
|
text: String,
|
||||||
line: uint32,
|
line: uint32,
|
||||||
column: uint32,
|
column: uint32,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// TypeExpr — type expressions
|
// TypeExpr — type expressions
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
const tekNamed: int = 0;
|
const tekNamed: int = 0;
|
||||||
const tekPath: int = 1;
|
const tekPath: int = 1;
|
||||||
const tekSlice: int = 2;
|
const tekSlice: int = 2;
|
||||||
const tekPointer: int = 3;
|
const tekPointer: int = 3;
|
||||||
const tekRef: int = 7; // &T — shared reference
|
const tekRef: int = 7; // &T — shared reference
|
||||||
const tekMutRef: int = 8; // &mut T — mutable reference
|
const tekMutRef: int = 8; // &mut T — mutable reference
|
||||||
const tekTuple: int = 4;
|
const tekTuple: int = 4;
|
||||||
const tekSelf: int = 5;
|
const tekSelf: int = 5;
|
||||||
const tekFunc: int = 6;
|
const tekFunc: int = 6;
|
||||||
|
|
||||||
struct TypeExprList {
|
struct TypeExprList {
|
||||||
te: *TypeExpr,
|
te: *TypeExpr,
|
||||||
next: *TypeExprList,
|
next: *TypeExprList,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TypeExpr {
|
struct TypeExpr {
|
||||||
kind: int,
|
kind: int,
|
||||||
line: uint32,
|
line: uint32,
|
||||||
column: uint32,
|
column: uint32,
|
||||||
typeName: String, // for tekNamed / diagnostic name for tekFunc
|
typeName: String, // for tekNamed / diagnostic name for tekFunc
|
||||||
pathStr: String, // for tekPath (segments joined with ::)
|
pathStr: String, // for tekPath (segments joined with ::)
|
||||||
pathCount: int, // number of path segments
|
pathCount: int, // number of path segments
|
||||||
typeArgName0: String, // up to 2 type args
|
typeArgName0: String, // up to 2 type args
|
||||||
typeArgName1: String,
|
typeArgName1: String,
|
||||||
typeArgCount: int,
|
typeArgCount: int,
|
||||||
sliceElement: *TypeExpr, // for tekSlice
|
sliceElement: *TypeExpr, // for tekSlice
|
||||||
pointerPointee: *TypeExpr, // for tekPointer
|
pointerPointee: *TypeExpr, // for tekPointer / tekRef / tekMutRef
|
||||||
funcParams: *TypeExprList, // for tekFunc
|
refLifetime: String, // for tekRef / tekMutRef: "'a" or "" (elided)
|
||||||
funcRet: *TypeExpr, // for tekFunc
|
funcParams: *TypeExprList, // for tekFunc
|
||||||
funcParamCount: int, // for tekFunc
|
funcRet: *TypeExpr, // for tekFunc
|
||||||
tupleElems: *TypeExprList, // for tekTuple
|
funcParamCount: int, // for tekFunc
|
||||||
tupleCount: int, // for tekTuple
|
tupleElems: *TypeExprList, // for tekTuple
|
||||||
}
|
tupleCount: int, // for tekTuple
|
||||||
|
}
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Pattern — match patterns
|
// ---------------------------------------------------------------------------
|
||||||
// ---------------------------------------------------------------------------
|
// Pattern — match patterns
|
||||||
const pkWildcard: int = 0;
|
// ---------------------------------------------------------------------------
|
||||||
const pkLiteral: int = 1;
|
const pkWildcard: int = 0;
|
||||||
const pkIdent: int = 2;
|
const pkLiteral: int = 1;
|
||||||
const pkRange: int = 3;
|
const pkIdent: int = 2;
|
||||||
const pkEnum: int = 4;
|
const pkRange: int = 3;
|
||||||
const pkStruct: int = 5;
|
const pkEnum: int = 4;
|
||||||
const pkTuple: int = 6;
|
const pkStruct: int = 5;
|
||||||
const pkGuarded: int = 7; // `p if cond` — patChild1 = inner, patGuardExpr = condition
|
const pkTuple: int = 6;
|
||||||
|
const pkGuarded: int = 7; // `p if cond` — patChild1 = inner, patGuardExpr = condition
|
||||||
struct Pattern {
|
|
||||||
kind: int,
|
struct Pattern {
|
||||||
line: uint32,
|
kind: int,
|
||||||
column: uint32,
|
line: uint32,
|
||||||
patIdent: String, // for pkIdent
|
column: uint32,
|
||||||
patLitKind: int, // for pkLiteral (token kind)
|
patIdent: String, // for pkIdent
|
||||||
patLitText: String, // for pkLiteral (token text)
|
patLitKind: int, // for pkLiteral (token kind)
|
||||||
patRangeInclusive: bool, // for pkRange
|
patLitText: String, // for pkLiteral (token text)
|
||||||
patEnumPath: String, // for pkEnum: "Enum::Variant"
|
patRangeInclusive: bool, // for pkRange
|
||||||
patStructName: String, // for pkStruct (type name)
|
patEnumPath: String, // for pkEnum: "Enum::Variant"
|
||||||
patFieldName: String, // for struct field entry: field name in Point { x: a }
|
patStructName: String, // for pkStruct (type name)
|
||||||
patChild1: *Pattern, // range lo / nested / guarded inner
|
patFieldName: String, // for struct field entry: field name in Point { x: a }
|
||||||
patChild2: *Pattern, // range hi / nested
|
patChild1: *Pattern, // range lo / nested / guarded inner
|
||||||
patArgs: *Pattern, // pkEnum/pkTuple/pkStruct field list (head)
|
patChild2: *Pattern, // range hi / nested
|
||||||
patNext: *Pattern, // next sibling in patArgs list
|
patArgs: *Pattern, // pkEnum/pkTuple/pkStruct field list (head)
|
||||||
patGuardExpr: *Expr, // for pkGuarded: the `if` condition
|
patNext: *Pattern, // next sibling in patArgs list
|
||||||
}
|
patGuardExpr: *Expr, // for pkGuarded: the `if` condition
|
||||||
|
}
|
||||||
// Match arm: pattern => body
|
|
||||||
struct MatchArm {
|
// Match arm: pattern => body
|
||||||
line: uint32,
|
struct MatchArm {
|
||||||
column: uint32,
|
line: uint32,
|
||||||
pattern: *Pattern,
|
column: uint32,
|
||||||
body: *Expr,
|
pattern: *Pattern,
|
||||||
next: *MatchArm,
|
body: *Expr,
|
||||||
}
|
next: *MatchArm,
|
||||||
|
}
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Expr — expressions (tagged union)
|
// ---------------------------------------------------------------------------
|
||||||
// ---------------------------------------------------------------------------
|
// Expr — expressions (tagged union)
|
||||||
const ekLiteral: int = 0;
|
// ---------------------------------------------------------------------------
|
||||||
const ekIdent: int = 1;
|
const ekLiteral: int = 0;
|
||||||
const ekSelf: int = 2;
|
const ekIdent: int = 1;
|
||||||
const ekPath: int = 3;
|
const ekSelf: int = 2;
|
||||||
const ekSizeOf: int = 4;
|
const ekPath: int = 3;
|
||||||
const ekUnary: int = 5;
|
const ekSizeOf: int = 4;
|
||||||
const ekPostfix: int = 6;
|
const ekUnary: int = 5;
|
||||||
const ekBinary: int = 7;
|
const ekPostfix: int = 6;
|
||||||
const ekAssign: int = 8;
|
const ekBinary: int = 7;
|
||||||
const ekTernary: int = 9;
|
const ekAssign: int = 8;
|
||||||
const ekRange: int = 10;
|
const ekTernary: int = 9;
|
||||||
const ekCall: int = 11;
|
const ekRange: int = 10;
|
||||||
const ekGenericCall: int = 12;
|
const ekCall: int = 11;
|
||||||
const ekIndex: int = 13;
|
const ekGenericCall: int = 12;
|
||||||
const ekField: int = 14;
|
const ekIndex: int = 13;
|
||||||
const ekStructInit: int = 15;
|
const ekField: int = 14;
|
||||||
const ekSlice: int = 16;
|
const ekStructInit: int = 15;
|
||||||
const ekTuple: int = 17;
|
const ekSlice: int = 16;
|
||||||
const ekCast: int = 18;
|
const ekTuple: int = 17;
|
||||||
const ekIs: int = 19;
|
const ekCast: int = 18;
|
||||||
const ekTry: int = 20;
|
const ekIs: int = 19;
|
||||||
const ekUnwrap: int = 23;
|
const ekTry: int = 20;
|
||||||
const ekBlock: int = 21;
|
const ekUnwrap: int = 23;
|
||||||
const ekMatch: int = 22;
|
const ekBlock: int = 21;
|
||||||
const ekSpawn: int = 24;
|
const ekMatch: int = 22;
|
||||||
const ekAwait: int = 25;
|
const ekSpawn: int = 24;
|
||||||
const ekStringInterp: int = 26;
|
const ekAwait: int = 25;
|
||||||
const ekClosure: int = 27;
|
const ekStringInterp: int = 26;
|
||||||
|
const ekClosure: int = 27;
|
||||||
struct ExprList {
|
|
||||||
expr: *Expr,
|
struct ExprList {
|
||||||
next: *ExprList,
|
expr: *Expr,
|
||||||
argName: String,
|
next: *ExprList,
|
||||||
}
|
argName: String,
|
||||||
|
}
|
||||||
struct Expr {
|
|
||||||
kind: int,
|
struct Expr {
|
||||||
line: uint32,
|
kind: int,
|
||||||
column: uint32,
|
line: uint32,
|
||||||
// Common fields
|
column: uint32,
|
||||||
strValue: String, // ident name, path segments, field name, callee
|
// Common fields
|
||||||
intValue: int, // operator kind, intrinsic kind
|
strValue: String, // ident name, path segments, field name, callee
|
||||||
boolValue: bool, // range inclusive
|
intValue: int, // operator kind, intrinsic kind
|
||||||
tokKind: int, // literal token kind
|
boolValue: bool, // range inclusive
|
||||||
tokText: String, // literal token text
|
tokKind: int, // literal token kind
|
||||||
// Children (up to 3 sub-expressions)
|
tokText: String, // literal token text
|
||||||
child1: *Expr, // left, operand, callee, cond, subject
|
// Children (up to 3 sub-expressions)
|
||||||
child2: *Expr, // right, index, then, value
|
child1: *Expr, // left, operand, callee, cond, subject
|
||||||
child3: *Expr, // else, third
|
child2: *Expr, // right, index, then, value
|
||||||
// Extra references
|
child3: *Expr, // else, third
|
||||||
refType: *TypeExpr, // cast type, is type, sizeof type
|
// Extra references
|
||||||
refBlock: *Block, // for ekBlock
|
refType: *TypeExpr, // cast type, is type, sizeof type
|
||||||
// Generic call
|
refBlock: *Block, // for ekBlock
|
||||||
genericCallee: String,
|
// Generic call
|
||||||
genericTypeArg0: String,
|
genericCallee: String,
|
||||||
genericTypeArg1: String,
|
genericTypeArg0: String,
|
||||||
genericTypeArgCount: int,
|
genericTypeArg1: String,
|
||||||
// Struct init fields
|
genericTypeArgCount: int,
|
||||||
structName: String,
|
// Struct init fields
|
||||||
structFieldCount: int,
|
structName: String,
|
||||||
// Closure params (for ekClosure)
|
structFieldCount: int,
|
||||||
closureParams: *Decl,
|
// Closure params (for ekClosure)
|
||||||
// Captures (for ekClosure)
|
closureParams: *Decl,
|
||||||
captureCount: int,
|
// Captures (for ekClosure)
|
||||||
captureName0: String,
|
captureCount: int,
|
||||||
captureName1: String,
|
captureName0: String,
|
||||||
captureName2: String,
|
captureName1: String,
|
||||||
captureName3: String,
|
captureName2: String,
|
||||||
captureName4: String,
|
captureName3: String,
|
||||||
captureName5: String,
|
captureName4: String,
|
||||||
captureName6: String,
|
captureName5: String,
|
||||||
captureName7: String,
|
captureName6: String,
|
||||||
captureType0: int,
|
captureName7: String,
|
||||||
captureType1: int,
|
captureType0: int,
|
||||||
captureType2: int,
|
captureType1: int,
|
||||||
captureType3: int,
|
captureType2: int,
|
||||||
captureType4: int,
|
captureType3: int,
|
||||||
captureType5: int,
|
captureType4: int,
|
||||||
captureType6: int,
|
captureType5: int,
|
||||||
captureType7: int,
|
captureType6: int,
|
||||||
// Call arguments (linked list for multi-arg support)
|
captureType7: int,
|
||||||
callArgs: *ExprList,
|
// Call arguments (linked list for multi-arg support)
|
||||||
callArgCount: int,
|
callArgs: *ExprList,
|
||||||
// Match arms (for ekMatch)
|
callArgCount: int,
|
||||||
matchArms: *MatchArm,
|
// Match arms (for ekMatch)
|
||||||
matchArmCount: int,
|
matchArms: *MatchArm,
|
||||||
}
|
matchArmCount: int,
|
||||||
|
}
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Block — sequence of statements
|
// ---------------------------------------------------------------------------
|
||||||
// ---------------------------------------------------------------------------
|
// Block — sequence of statements
|
||||||
struct Block {
|
// ---------------------------------------------------------------------------
|
||||||
line: uint32,
|
struct Block {
|
||||||
column: uint32,
|
line: uint32,
|
||||||
stmtCount: int,
|
column: uint32,
|
||||||
firstStmt: *Stmt,
|
stmtCount: int,
|
||||||
lastStmt: *Stmt,
|
firstStmt: *Stmt,
|
||||||
}
|
lastStmt: *Stmt,
|
||||||
|
}
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Stmt — statements
|
// ---------------------------------------------------------------------------
|
||||||
// ---------------------------------------------------------------------------
|
// Stmt — statements
|
||||||
const skExpr: int = 0;
|
// ---------------------------------------------------------------------------
|
||||||
const skLet: int = 1;
|
const skExpr: int = 0;
|
||||||
const skIf: int = 2;
|
const skLet: int = 1;
|
||||||
const skWhile: int = 3;
|
const skIf: int = 2;
|
||||||
const skDoWhile: int = 4;
|
const skWhile: int = 3;
|
||||||
const skLoop: int = 5;
|
const skDoWhile: int = 4;
|
||||||
const skFor: int = 6;
|
const skLoop: int = 5;
|
||||||
const skMatch: int = 7;
|
const skFor: int = 6;
|
||||||
const skReturn: int = 8;
|
const skMatch: int = 7;
|
||||||
const skBreak: int = 9;
|
const skReturn: int = 8;
|
||||||
const skContinue: int = 10;
|
const skBreak: int = 9;
|
||||||
const skDecl: int = 11;
|
const skContinue: int = 10;
|
||||||
const skDefer: int = 12;
|
const skDecl: int = 11;
|
||||||
const skSwitch: int = 13;
|
const skDefer: int = 12;
|
||||||
|
const skSwitch: int = 13;
|
||||||
struct ElseIf {
|
|
||||||
line: uint32;
|
struct ElseIf {
|
||||||
column: uint32;
|
line: uint32;
|
||||||
cond: *Expr;
|
column: uint32;
|
||||||
block: *Block;
|
cond: *Expr;
|
||||||
}
|
block: *Block;
|
||||||
|
}
|
||||||
struct Stmt {
|
|
||||||
kind: int,
|
struct Stmt {
|
||||||
line: uint32,
|
kind: int,
|
||||||
column: uint32,
|
line: uint32,
|
||||||
// Common fields
|
column: uint32,
|
||||||
strValue: String, // let name, pattern ident, label, for var
|
// Common fields
|
||||||
boolValue: bool, // let mutable
|
strValue: String, // let name, pattern ident, label, for var
|
||||||
// Children
|
boolValue: bool, // let mutable
|
||||||
child1: *Expr, // init expr, condition, iter expr, return value
|
// Children
|
||||||
child2: *Expr, // match subject
|
child1: *Expr, // init expr, condition, iter expr, return value
|
||||||
child3: *Expr, // extra
|
child2: *Expr, // match subject
|
||||||
refStmtType: *TypeExpr, // let type annotation
|
child3: *Expr, // extra
|
||||||
refStmtPattern: *Pattern,// let pattern
|
refStmtType: *TypeExpr, // let type annotation
|
||||||
refStmtDecl: *Decl, // for skDecl
|
refStmtPattern: *Pattern,// let pattern
|
||||||
refStmtBlock: *Block, // then/body block
|
refStmtDecl: *Decl, // for skDecl
|
||||||
refStmtElse: *Block, // else block
|
refStmtBlock: *Block, // then/body block
|
||||||
// Else-if chain
|
refStmtElse: *Block, // else block
|
||||||
elseIfCount: int,
|
// Else-if chain
|
||||||
// Linked list
|
elseIfCount: int,
|
||||||
nextStmt: *Stmt,
|
// Linked list
|
||||||
}
|
nextStmt: *Stmt,
|
||||||
|
}
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Decl — declarations
|
// ---------------------------------------------------------------------------
|
||||||
// ---------------------------------------------------------------------------
|
// Decl — declarations
|
||||||
const dkFunc: int = 0;
|
// ---------------------------------------------------------------------------
|
||||||
const dkStruct: int = 1;
|
const dkFunc: int = 0;
|
||||||
const dkEnum: int = 2;
|
const dkStruct: int = 1;
|
||||||
const dkUnion: int = 3;
|
const dkEnum: int = 2;
|
||||||
const dkInterface: int = 4;
|
const dkUnion: int = 3;
|
||||||
const dkImpl: int = 5;
|
const dkInterface: int = 4;
|
||||||
const dkModule: int = 6;
|
const dkImpl: int = 5;
|
||||||
const dkUse: int = 7;
|
const dkModule: int = 6;
|
||||||
const dkConst: int = 8;
|
const dkUse: int = 7;
|
||||||
const dkTypeAlias: int = 9;
|
const dkConst: int = 8;
|
||||||
const dkExternFunc: int = 10;
|
const dkTypeAlias: int = 9;
|
||||||
const dkExternVar: int = 11;
|
const dkExternFunc: int = 10;
|
||||||
|
const dkExternVar: int = 11;
|
||||||
struct Param {
|
|
||||||
line: uint32;
|
struct Param {
|
||||||
column: uint32;
|
line: uint32;
|
||||||
name: String;
|
column: uint32;
|
||||||
refParamType: *TypeExpr;
|
name: String;
|
||||||
isVariadic: bool;
|
refParamType: *TypeExpr;
|
||||||
defaultExpr: *Expr;
|
isVariadic: bool;
|
||||||
}
|
defaultExpr: *Expr;
|
||||||
|
}
|
||||||
struct StructField {
|
|
||||||
line: uint32;
|
struct StructField {
|
||||||
column: uint32;
|
line: uint32;
|
||||||
isPublic: bool;
|
column: uint32;
|
||||||
name: String;
|
isPublic: bool;
|
||||||
refFieldType: *TypeExpr;
|
name: String;
|
||||||
}
|
refFieldType: *TypeExpr;
|
||||||
|
}
|
||||||
struct EnumVariant {
|
|
||||||
line: uint32;
|
struct EnumVariant {
|
||||||
column: uint32;
|
line: uint32;
|
||||||
name: String;
|
column: uint32;
|
||||||
fieldCount: int;
|
name: String;
|
||||||
fieldTypeName0: String;
|
fieldCount: int;
|
||||||
fieldTypeName1: String;
|
fieldTypeName0: String;
|
||||||
}
|
fieldTypeName1: String;
|
||||||
|
}
|
||||||
struct Decl {
|
|
||||||
fieldCount: int,
|
struct Decl {
|
||||||
fields: *StructField,
|
fieldCount: int,
|
||||||
kind: int,
|
fields: *StructField,
|
||||||
line: uint32,
|
kind: int,
|
||||||
column: uint32,
|
line: uint32,
|
||||||
isPublic: bool,
|
column: uint32,
|
||||||
isAsync: bool,
|
isPublic: bool,
|
||||||
isChecked: int, // @[Checked] attribute (0/1)
|
isAsync: bool,
|
||||||
isDrop: int, // @[Drop] attribute (0/1)
|
isChecked: int, // @[Checked] attribute (0/1)
|
||||||
isRelease: int, // @[Release] attribute (0/1)
|
isDrop: int, // @[Drop] attribute (0/1)
|
||||||
isConst: int, // const func (0/1)
|
isRelease: int, // @[Release] attribute (0/1)
|
||||||
// Names
|
isConst: int, // const func (0/1)
|
||||||
strValue: String, // decl name
|
// Names
|
||||||
strValue2: String, // interface name, dll name, module path
|
strValue: String, // decl name
|
||||||
// Type params (up to 2)
|
strValue2: String, // interface name, dll name, module path
|
||||||
typeParam0: String,
|
// Type params (up to 2)
|
||||||
typeParam1: String,
|
typeParam0: String,
|
||||||
typeParamCount: int,
|
typeParam1: String,
|
||||||
// Trait bounds for type params (e.g. <T: Comparable>)
|
typeParamCount: int,
|
||||||
typeParam0Bound: String,
|
// Trait bounds for type params (e.g. <T: Comparable>)
|
||||||
typeParam1Bound: String,
|
typeParam0Bound: String,
|
||||||
// Params (for functions)
|
typeParam1Bound: String,
|
||||||
paramCount: int,
|
// Params (for functions)
|
||||||
param0: Param,
|
paramCount: int,
|
||||||
param1: Param,
|
param0: Param,
|
||||||
param2: Param,
|
param1: Param,
|
||||||
param3: Param,
|
param2: Param,
|
||||||
param4: Param,
|
param3: Param,
|
||||||
param5: Param,
|
param4: Param,
|
||||||
param6: Param,
|
param5: Param,
|
||||||
param7: Param,
|
param6: Param,
|
||||||
param8: Param,
|
param7: Param,
|
||||||
retType: *TypeExpr,
|
param8: Param,
|
||||||
// Body
|
retType: *TypeExpr,
|
||||||
refBody: *Block,
|
// Body
|
||||||
// Enum variants (up to 8)
|
refBody: *Block,
|
||||||
variantCount: int,
|
// Enum variants (up to 8)
|
||||||
variant0: EnumVariant,
|
variantCount: int,
|
||||||
variant1: EnumVariant,
|
variant0: EnumVariant,
|
||||||
variant2: EnumVariant,
|
variant1: EnumVariant,
|
||||||
variant3: EnumVariant,
|
variant2: EnumVariant,
|
||||||
variant4: EnumVariant,
|
variant3: EnumVariant,
|
||||||
variant5: EnumVariant,
|
variant4: EnumVariant,
|
||||||
variant6: EnumVariant,
|
variant5: EnumVariant,
|
||||||
variant7: EnumVariant,
|
variant6: EnumVariant,
|
||||||
variant8: EnumVariant,
|
variant7: EnumVariant,
|
||||||
// Impl methods (up to 4)
|
variant8: EnumVariant,
|
||||||
methodCount: int,
|
// Impl methods (up to 4)
|
||||||
// Use/import
|
methodCount: int,
|
||||||
useKind: int,
|
// Use/import
|
||||||
usePath: String,
|
useKind: int,
|
||||||
useNames: String, // joined names for multi-import
|
usePath: String,
|
||||||
// Const
|
useNames: String, // joined names for multi-import
|
||||||
constType: *TypeExpr,
|
// Const
|
||||||
constValue: *Expr,
|
constType: *TypeExpr,
|
||||||
// Type alias
|
constValue: *Expr,
|
||||||
aliasType: *TypeExpr,
|
// Type alias
|
||||||
// Extern func
|
aliasType: *TypeExpr,
|
||||||
extFuncDll: String,
|
// Extern func
|
||||||
extFuncVariadic: bool,
|
extFuncDll: String,
|
||||||
extFuncRetType: *TypeExpr,
|
extFuncVariadic: bool,
|
||||||
// Children
|
extFuncRetType: *TypeExpr,
|
||||||
childDecl1: *Decl, // linked list of decls (for module items, impl methods)
|
// Children
|
||||||
childDecl2: *Decl,
|
childDecl1: *Decl, // linked list of decls (for module items, impl methods)
|
||||||
// Struct fields (up to 256)
|
childDecl2: *Decl,
|
||||||
}
|
// Struct fields (up to 256)
|
||||||
|
}
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Module — AST root
|
// ---------------------------------------------------------------------------
|
||||||
// ---------------------------------------------------------------------------
|
// Module — AST root
|
||||||
struct Module {
|
// ---------------------------------------------------------------------------
|
||||||
name: String,
|
struct Module {
|
||||||
path: String, // path segments joined
|
name: String,
|
||||||
itemCount: int,
|
path: String, // path segments joined
|
||||||
firstItem: *Decl,
|
itemCount: int,
|
||||||
}
|
firstItem: *Decl,
|
||||||
|
}
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Constructor helpers
|
// ---------------------------------------------------------------------------
|
||||||
// ---------------------------------------------------------------------------
|
// Constructor helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
func Ast_MakeExpr(kind: int, line: uint32, col: uint32) -> Expr {
|
|
||||||
return Expr { kind: kind, line: line, column: col,
|
func Ast_MakeExpr(kind: int, line: uint32, col: uint32) -> Expr {
|
||||||
strValue: "", intValue: 0, boolValue: false,
|
return Expr { kind: kind, line: line, column: col,
|
||||||
tokKind: 0, tokText: "",
|
strValue: "", intValue: 0, boolValue: false,
|
||||||
child1: null as *Expr, child2: null as *Expr, child3: null as *Expr,
|
tokKind: 0, tokText: "",
|
||||||
refType: null as *TypeExpr, refBlock: null as *Block,
|
child1: null as *Expr, child2: null as *Expr, child3: null as *Expr,
|
||||||
genericCallee: "", genericTypeArg0: "", genericTypeArg1: "", genericTypeArgCount: 0,
|
refType: null as *TypeExpr, refBlock: null as *Block,
|
||||||
structName: "", structFieldCount: 0,
|
genericCallee: "", genericTypeArg0: "", genericTypeArg1: "", genericTypeArgCount: 0,
|
||||||
callArgs: null as *ExprList, callArgCount: 0 };
|
structName: "", structFieldCount: 0,
|
||||||
}
|
callArgs: null as *ExprList, callArgCount: 0 };
|
||||||
|
}
|
||||||
func Ast_MakeIdent(name: String, line: uint32, col: uint32) -> Expr {
|
|
||||||
var e: Expr = Ast_MakeExpr(ekIdent, line, col);
|
func Ast_MakeIdent(name: String, line: uint32, col: uint32) -> Expr {
|
||||||
e.strValue = name;
|
var e: Expr = Ast_MakeExpr(ekIdent, line, col);
|
||||||
return e;
|
e.strValue = name;
|
||||||
}
|
return e;
|
||||||
|
}
|
||||||
func Ast_MakeLiteral(tokKind: int, text: String, line: uint32, col: uint32) -> Expr {
|
|
||||||
var e: Expr = Ast_MakeExpr(ekLiteral, line, col);
|
func Ast_MakeLiteral(tokKind: int, text: String, line: uint32, col: uint32) -> Expr {
|
||||||
e.tokKind = tokKind;
|
var e: Expr = Ast_MakeExpr(ekLiteral, line, col);
|
||||||
e.tokText = text;
|
e.tokKind = tokKind;
|
||||||
return e;
|
e.tokText = text;
|
||||||
}
|
return e;
|
||||||
|
}
|
||||||
func Ast_MakeBinary(op: int, left: *Expr, right: *Expr, line: uint32, col: uint32) -> Expr {
|
|
||||||
var e: Expr = Ast_MakeExpr(ekBinary, line, col);
|
func Ast_MakeBinary(op: int, left: *Expr, right: *Expr, line: uint32, col: uint32) -> Expr {
|
||||||
e.intValue = op;
|
var e: Expr = Ast_MakeExpr(ekBinary, line, col);
|
||||||
e.child1 = left;
|
e.intValue = op;
|
||||||
e.child2 = right;
|
e.child1 = left;
|
||||||
return e;
|
e.child2 = right;
|
||||||
}
|
return e;
|
||||||
|
}
|
||||||
func Ast_MakeCall(callee: *Expr, line: uint32, col: uint32) -> Expr {
|
|
||||||
var e: Expr = Ast_MakeExpr(ekCall, line, col);
|
func Ast_MakeCall(callee: *Expr, line: uint32, col: uint32) -> Expr {
|
||||||
e.child1 = callee;
|
var e: Expr = Ast_MakeExpr(ekCall, line, col);
|
||||||
return e;
|
e.child1 = callee;
|
||||||
}
|
return e;
|
||||||
|
}
|
||||||
func Ast_MakeStmt(kind: int, line: uint32, col: uint32) -> Stmt {
|
|
||||||
return Stmt { kind: kind, line: line, column: col,
|
func Ast_MakeStmt(kind: int, line: uint32, col: uint32) -> Stmt {
|
||||||
strValue: "", boolValue: false,
|
return Stmt { kind: kind, line: line, column: col,
|
||||||
child1: null as *Expr, child2: null as *Expr, child3: null as *Expr,
|
strValue: "", boolValue: false,
|
||||||
refStmtType: null as *TypeExpr, refStmtPattern: null as *Pattern,
|
child1: null as *Expr, child2: null as *Expr, child3: null as *Expr,
|
||||||
refStmtDecl: null as *Decl, refStmtBlock: null as *Block, refStmtElse: null as *Block,
|
refStmtType: null as *TypeExpr, refStmtPattern: null as *Pattern,
|
||||||
elseIfCount: 0 };
|
refStmtDecl: null as *Decl, refStmtBlock: null as *Block, refStmtElse: null as *Block,
|
||||||
}
|
elseIfCount: 0 };
|
||||||
|
}
|
||||||
func Ast_MakeDecl(kind: int, line: uint32, col: uint32) -> Decl {
|
|
||||||
return Decl { kind: kind, line: line, column: col, isPublic: false,
|
func Ast_MakeDecl(kind: int, line: uint32, col: uint32) -> Decl {
|
||||||
isAsync: false, isChecked: 0, isDrop: 0, isRelease: 0, isConst: 0,
|
return Decl { kind: kind, line: line, column: col, isPublic: false,
|
||||||
strValue: "", strValue2: "",
|
isAsync: false, isChecked: 0, isDrop: 0, isRelease: 0, isConst: 0,
|
||||||
typeParam0: "", typeParam1: "", typeParamCount: 0,
|
strValue: "", strValue2: "",
|
||||||
typeParam0Bound: "", typeParam1Bound: ""
|
typeParam0: "", typeParam1: "", typeParamCount: 0,
|
||||||
,
|
typeParam0Bound: "", typeParam1Bound: ""
|
||||||
paramCount: 0,
|
,
|
||||||
retType: null as *TypeExpr,
|
paramCount: 0,
|
||||||
refBody: null as *Block,
|
retType: null as *TypeExpr,
|
||||||
fieldCount: 0,
|
refBody: null as *Block,
|
||||||
variantCount: 0,
|
fieldCount: 0,
|
||||||
methodCount: 0,
|
variantCount: 0,
|
||||||
useKind: 0, usePath: "", useNames: "",
|
methodCount: 0,
|
||||||
constType: null as *TypeExpr, constValue: null as *Expr,
|
useKind: 0, usePath: "", useNames: "",
|
||||||
aliasType: null as *TypeExpr,
|
constType: null as *TypeExpr, constValue: null as *Expr,
|
||||||
extFuncDll: "", extFuncVariadic: false, extFuncRetType: null as *TypeExpr,
|
aliasType: null as *TypeExpr,
|
||||||
childDecl1: null as *Decl, childDecl2: null as *Decl };
|
extFuncDll: "", extFuncVariadic: false, extFuncRetType: null as *TypeExpr,
|
||||||
}
|
childDecl1: null as *Decl, childDecl2: null as *Decl };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1534
-1534
File diff suppressed because it is too large
Load Diff
+463
-186
@@ -2,144 +2,144 @@
|
|||||||
// Wires together: Lexer → Parser → Sema → HirLower → CBackend
|
// Wires together: Lexer → Parser → Sema → HirLower → CBackend
|
||||||
module Cli {
|
module Cli {
|
||||||
|
|
||||||
extern func PrintLine(s: String);
|
extern func PrintLine(s: String);
|
||||||
extern func Print(s: String);
|
extern func Print(s: String);
|
||||||
extern func bux_read_file(path: String) -> String;
|
extern func bux_read_file(path: String) -> String;
|
||||||
extern func bux_write_file(path: String, content: String) -> bool;
|
extern func bux_write_file(path: String, content: String) -> bool;
|
||||||
extern func bux_file_exists(path: String) -> int;
|
extern func bux_file_exists(path: String) -> int;
|
||||||
extern func bux_dir_exists(path: String) -> int;
|
extern func bux_dir_exists(path: String) -> int;
|
||||||
extern func bux_getcwd() -> String;
|
extern func bux_getcwd() -> String;
|
||||||
extern func bux_path_join(a: String, b: String) -> String;
|
extern func bux_path_join(a: String, b: String) -> String;
|
||||||
extern func bux_path_parent(path: String) -> String;
|
extern func bux_path_parent(path: String) -> String;
|
||||||
extern func bux_mkdir_if_needed(path: String) -> int;
|
extern func bux_mkdir_if_needed(path: String) -> int;
|
||||||
extern func bux_run_nim(nim_file: String, out_bin: String) -> int;
|
extern func bux_run_nim(nim_file: String, out_bin: String) -> int;
|
||||||
extern func bux_list_dir(dir: String, ext: String, out_count: *int) -> *String;
|
extern func bux_list_dir(dir: String, ext: String, out_count: *int) -> *String;
|
||||||
extern func bux_system(cmd: String) -> int;
|
extern func bux_system(cmd: String) -> int;
|
||||||
extern func bux_getenv(name: String) -> String;
|
extern func bux_getenv(name: String) -> String;
|
||||||
extern func bux_setenv(name: String, value: String) -> int;
|
extern func bux_setenv(name: String, value: String) -> int;
|
||||||
extern func bux_strlen(s: String) -> uint;
|
extern func bux_strlen(s: String) -> uint;
|
||||||
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
|
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
|
||||||
|
|
||||||
func ReadFile(path: String) -> String {
|
func ReadFile(path: String) -> String {
|
||||||
return bux_read_file(path);
|
return bux_read_file(path);
|
||||||
}
|
|
||||||
|
|
||||||
func WriteFile(path: String, content: String) -> bool {
|
|
||||||
return bux_write_file(path, content);
|
|
||||||
}
|
|
||||||
|
|
||||||
func FileExists(path: String) -> bool {
|
|
||||||
return bux_file_exists(path) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
func DirExists(path: String) -> bool {
|
|
||||||
return bux_dir_exists(path) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Diagnostic formatting (Rust-style errors with snippets)
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
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 {
|
|
||||||
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 {
|
|
||||||
return bux_str_contains(msg, needle) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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";
|
|
||||||
}
|
}
|
||||||
if Diagnostic_MsgContains(msg, "undeclared identifier") {
|
|
||||||
return "check the spelling, or import the symbol from the right module";
|
|
||||||
}
|
|
||||||
if Diagnostic_MsgContains(msg, "too few arguments") {
|
|
||||||
return "compare the call with the function's parameter list";
|
|
||||||
}
|
|
||||||
if Diagnostic_MsgContains(msg, "too many arguments") {
|
|
||||||
return "compare the call with the function's parameter list";
|
|
||||||
}
|
|
||||||
if Diagnostic_MsgContains(msg, "use of moved value") {
|
|
||||||
return "the value was moved; clone it or restructure ownership";
|
|
||||||
}
|
|
||||||
if Diagnostic_MsgContains(msg, "expected expression") {
|
|
||||||
return "the previous statement may be incomplete (missing value or ';')";
|
|
||||||
}
|
|
||||||
if Diagnostic_MsgContains(msg, "duplicate symbol") {
|
|
||||||
return "rename one of the definitions or remove the duplicate";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Print a diagnostic in Rust-style format:
|
func WriteFile(path: String, content: String) -> bool {
|
||||||
* error: <message>
|
return bux_write_file(path, content);
|
||||||
* --> <path>:<line>:<col>
|
|
||||||
* |
|
|
||||||
* 42 | <source_line>
|
|
||||||
* | <spaces>^
|
|
||||||
* = help: <hint>
|
|
||||||
*/
|
|
||||||
func Diagnostic_Print(diag: *Diagnostic, sourcePath: String) {
|
|
||||||
/* Severity prefix */
|
|
||||||
if diag.severity == 0 {
|
|
||||||
Print("error: ");
|
|
||||||
} else if diag.severity == 1 {
|
|
||||||
Print("warning: ");
|
|
||||||
} else {
|
|
||||||
Print("note: ");
|
|
||||||
}
|
}
|
||||||
PrintLine(diag.message);
|
|
||||||
|
|
||||||
/* Location header */
|
func FileExists(path: String) -> bool {
|
||||||
Print(" --> ");
|
return bux_file_exists(path) != 0;
|
||||||
Print(sourcePath);
|
}
|
||||||
Print(":");
|
|
||||||
PrintInt(diag.line as int64);
|
|
||||||
Print(":");
|
|
||||||
PrintInt(diag.column as int64);
|
|
||||||
PrintLine("");
|
|
||||||
|
|
||||||
/* Source snippet */
|
func DirExists(path: String) -> bool {
|
||||||
let lineText: String = Diagnostic_GetLine(sourcePath, diag.line);
|
return bux_dir_exists(path) != 0;
|
||||||
if !String_Eq(lineText, "") {
|
}
|
||||||
let lineNumStr: String = String_FromInt(diag.line as int64);
|
|
||||||
|
|
||||||
Print(" |");
|
// ---------------------------------------------------------------------------
|
||||||
PrintLine("");
|
// Diagnostic formatting (Rust-style errors with snippets)
|
||||||
Print(" ");
|
// ---------------------------------------------------------------------------
|
||||||
Print(lineNumStr);
|
|
||||||
Print(" | ");
|
|
||||||
PrintLine(lineText);
|
|
||||||
|
|
||||||
/* Underline (multi-char for identifiers/string tokens) */
|
struct Diagnostic {
|
||||||
Print(" | ");
|
message: String;
|
||||||
var i: uint32 = 0;
|
line: uint32;
|
||||||
while i < diag.column - 1 && i < 120 {
|
column: uint32;
|
||||||
Print(" ");
|
severity: int;
|
||||||
i = i + 1;
|
}
|
||||||
|
|
||||||
|
/* 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 {
|
||||||
|
return bux_str_contains(msg, needle) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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";
|
||||||
}
|
}
|
||||||
/* Estimate token length from the source line */
|
if Diagnostic_MsgContains(msg, "undeclared identifier") {
|
||||||
var ulen: uint = 1;
|
return "check the spelling, or import the symbol from the right module";
|
||||||
let col0: uint = diag.column - 1;
|
}
|
||||||
let lineLen: uint = String_Len(lineText);
|
if Diagnostic_MsgContains(msg, "too few arguments") {
|
||||||
if col0 < lineLen {
|
return "compare the call with the function's parameter list";
|
||||||
let first: String = String_Chars(lineText, col0);
|
}
|
||||||
if String_Eq(first, "\"") || String_Eq(first, "`") || String_Eq(first, "'") {
|
if Diagnostic_MsgContains(msg, "too many arguments") {
|
||||||
|
return "compare the call with the function's parameter list";
|
||||||
|
}
|
||||||
|
if Diagnostic_MsgContains(msg, "use of moved value") {
|
||||||
|
return "the value was moved; clone it or restructure ownership";
|
||||||
|
}
|
||||||
|
if Diagnostic_MsgContains(msg, "expected expression") {
|
||||||
|
return "the previous statement may be incomplete (missing value or ';')";
|
||||||
|
}
|
||||||
|
if Diagnostic_MsgContains(msg, "duplicate symbol") {
|
||||||
|
return "rename one of the definitions or remove the duplicate";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Print a diagnostic in Rust-style format:
|
||||||
|
* error: <message>
|
||||||
|
* --> <path>:<line>:<col>
|
||||||
|
* |
|
||||||
|
* 42 | <source_line>
|
||||||
|
* | <spaces>^
|
||||||
|
* = help: <hint>
|
||||||
|
*/
|
||||||
|
func Diagnostic_Print(diag: *Diagnostic, sourcePath: String) {
|
||||||
|
/* Severity prefix */
|
||||||
|
if diag.severity == 0 {
|
||||||
|
Print("error: ");
|
||||||
|
} else if diag.severity == 1 {
|
||||||
|
Print("warning: ");
|
||||||
|
} else {
|
||||||
|
Print("note: ");
|
||||||
|
}
|
||||||
|
PrintLine(diag.message);
|
||||||
|
|
||||||
|
/* Location header */
|
||||||
|
Print(" --> ");
|
||||||
|
Print(sourcePath);
|
||||||
|
Print(":");
|
||||||
|
PrintInt(diag.line as int64);
|
||||||
|
Print(":");
|
||||||
|
PrintInt(diag.column as int64);
|
||||||
|
PrintLine("");
|
||||||
|
|
||||||
|
/* Source snippet */
|
||||||
|
let lineText: String = Diagnostic_GetLine(sourcePath, diag.line);
|
||||||
|
if !String_Eq(lineText, "") {
|
||||||
|
let lineNumStr: String = String_FromInt(diag.line as int64);
|
||||||
|
|
||||||
|
Print(" |");
|
||||||
|
PrintLine("");
|
||||||
|
Print(" ");
|
||||||
|
Print(lineNumStr);
|
||||||
|
Print(" | ");
|
||||||
|
PrintLine(lineText);
|
||||||
|
|
||||||
|
/* Underline (multi-char for identifiers/string tokens) */
|
||||||
|
Print(" | ");
|
||||||
|
var i: uint32 = 0;
|
||||||
|
while i < diag.column - 1 && i < 120 {
|
||||||
|
Print(" ");
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
/* Estimate token length from the source line */
|
||||||
|
var ulen: uint = 1;
|
||||||
|
let col0: uint = diag.column - 1;
|
||||||
|
let lineLen: uint = String_Len(lineText);
|
||||||
|
if col0 < lineLen {
|
||||||
|
let first: String = String_Chars(lineText, col0);
|
||||||
|
if String_Eq(first, "\"") || String_Eq(first, "`") || String_Eq(first, "'") {
|
||||||
var j: uint = col0 + 1;
|
var j: uint = col0 + 1;
|
||||||
while j < lineLen {
|
while j < lineLen {
|
||||||
let cj: String = String_Chars(lineText, j);
|
let cj: String = String_Chars(lineText, j);
|
||||||
@@ -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 {
|
func Cli_DocIsDeclStart(line: String) -> bool {
|
||||||
// If dir is a file, format just that file
|
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) {
|
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 ");
|
Print("Formatting ");
|
||||||
PrintLine(dir);
|
PrintLine(dir);
|
||||||
if Fmt_FormatFile(dir) {
|
if Fmt_FormatFile(dir) {
|
||||||
@@ -936,8 +1108,12 @@ func Cli_Fmt(dir: String) -> int {
|
|||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
// Otherwise format all .bux files in directory
|
// Otherwise format/check all .bux files in directory
|
||||||
Print("Formatting ");
|
if checkOnly {
|
||||||
|
Print("Checking format in ");
|
||||||
|
} else {
|
||||||
|
Print("Formatting ");
|
||||||
|
}
|
||||||
PrintLine(dir);
|
PrintLine(dir);
|
||||||
var fileCount: int = 0;
|
var fileCount: int = 0;
|
||||||
let files: *String = bux_list_dir(dir, ".bux", &fileCount);
|
let files: *String = bux_list_dir(dir, ".bux", &fileCount);
|
||||||
@@ -947,14 +1123,34 @@ func Cli_Fmt(dir: String) -> int {
|
|||||||
}
|
}
|
||||||
var i: int = 0;
|
var i: int = 0;
|
||||||
var okCount: int = 0;
|
var okCount: int = 0;
|
||||||
|
var changeCount: int = 0;
|
||||||
while i < fileCount {
|
while i < fileCount {
|
||||||
Print(" ");
|
if checkOnly {
|
||||||
PrintLine(files[i]);
|
if Fmt_CheckFile(files[i]) == 0 {
|
||||||
if Fmt_FormatFile(files[i]) {
|
okCount = okCount + 1;
|
||||||
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;
|
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");
|
Print("Formatted "); PrintInt(okCount as int64); Print("/"); PrintInt(fileCount as int64); PrintLine(" files");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -985,32 +1181,40 @@ func Cli_StripExtension(name: String) -> String {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
func Cli_Test(projectDir: String) -> int {
|
func Cli_Test(projectDir: String, filter: String) -> int {
|
||||||
Print("Testing project: ");
|
Print("Testing project: ");
|
||||||
PrintLine(projectDir);
|
PrintLine(projectDir);
|
||||||
// Build and run the project's own Main first
|
if !String_Eq(filter, "") {
|
||||||
let mainRc: int = Cli_BuildProject(projectDir, "", false);
|
Print("Filter: ");
|
||||||
if mainRc != 0 {
|
PrintLine(filter);
|
||||||
PrintLine("Main test build failed");
|
|
||||||
return mainRc;
|
|
||||||
}
|
}
|
||||||
let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml"));
|
|
||||||
var mainName: String = man.name;
|
// Without --filter, build and run the project's own Main first.
|
||||||
if String_Eq(mainName, "") { mainName = "bux_out"; }
|
// With --filter, only run matching tests/*.bux files.
|
||||||
let mainBin: String = bux_path_join(bux_path_join(projectDir, "build"), mainName);
|
if String_Eq(filter, "") {
|
||||||
if !FileExists(mainBin) {
|
let mainRc: int = Cli_BuildProject(projectDir, "", false);
|
||||||
Print("Error: test binary not found: ");
|
if mainRc != 0 {
|
||||||
PrintLine(mainBin);
|
PrintLine("Main test build failed");
|
||||||
return 1;
|
return mainRc;
|
||||||
|
}
|
||||||
|
let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml"));
|
||||||
|
var mainName: String = man.name;
|
||||||
|
if String_Eq(mainName, "") { mainName = "bux_out"; }
|
||||||
|
let mainBin: String = bux_path_join(bux_path_join(projectDir, "build"), mainName);
|
||||||
|
if !FileExists(mainBin) {
|
||||||
|
Print("Error: test binary not found: ");
|
||||||
|
PrintLine(mainBin);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
let mainResult: int = bux_system(mainBin);
|
||||||
|
if mainResult != 0 {
|
||||||
|
Print("Main tests failed (exit code ");
|
||||||
|
PrintInt(mainResult as int64);
|
||||||
|
PrintLine(")");
|
||||||
|
return mainResult;
|
||||||
|
}
|
||||||
|
PrintLine("Main tests passed");
|
||||||
}
|
}
|
||||||
let mainResult: int = bux_system(mainBin);
|
|
||||||
if mainResult != 0 {
|
|
||||||
Print("Main tests failed (exit code ");
|
|
||||||
PrintInt(mainResult as int64);
|
|
||||||
PrintLine(")");
|
|
||||||
return mainResult;
|
|
||||||
}
|
|
||||||
PrintLine("Main tests passed");
|
|
||||||
|
|
||||||
// Propagate the project's stdlib to temp test packages.
|
// Propagate the project's stdlib to temp test packages.
|
||||||
let stdlibDir: String = Cli_FindStdlibDir(projectDir);
|
let stdlibDir: String = Cli_FindStdlibDir(projectDir);
|
||||||
@@ -1031,15 +1235,31 @@ func Cli_Test(projectDir: String) -> int {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PrintLine("┌──────────────────────────────┬────────┐");
|
||||||
|
PrintLine("│ Test │ Status │");
|
||||||
|
PrintLine("├──────────────────────────────┼────────┤");
|
||||||
|
|
||||||
var passed: int = 0;
|
var passed: int = 0;
|
||||||
var failed: int = 0;
|
var failed: int = 0;
|
||||||
|
var skipped: int = 0;
|
||||||
var i: int = 0;
|
var i: int = 0;
|
||||||
while i < testCount {
|
while i < testCount {
|
||||||
let testPath: String = testFiles[i];
|
let testPath: String = testFiles[i];
|
||||||
let fileName: String = Cli_FileNameFromPath(testPath);
|
let fileName: String = Cli_FileNameFromPath(testPath);
|
||||||
let testName: String = Cli_StripExtension(fileName);
|
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);
|
Print(testName);
|
||||||
|
// Pad status column roughly (name may be long)
|
||||||
Print(" ... ");
|
Print(" ... ");
|
||||||
|
|
||||||
// Create temp package for this test file
|
// Create temp package for this test file
|
||||||
@@ -1050,14 +1270,14 @@ func Cli_Test(projectDir: String) -> int {
|
|||||||
|
|
||||||
let source: String = ReadFile(testPath);
|
let source: String = ReadFile(testPath);
|
||||||
if String_Eq(source, "") {
|
if String_Eq(source, "") {
|
||||||
PrintLine("FAIL (cannot read test file)");
|
PrintLine("FAIL │");
|
||||||
failed = failed + 1;
|
failed = failed + 1;
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let tmpMain: String = bux_path_join(tmpSrc, "Main.bux");
|
let tmpMain: String = bux_path_join(tmpSrc, "Main.bux");
|
||||||
if !WriteFile(tmpMain, source) {
|
if !WriteFile(tmpMain, source) {
|
||||||
PrintLine("FAIL (cannot write temp Main.bux)");
|
PrintLine("FAIL │");
|
||||||
failed = failed + 1;
|
failed = failed + 1;
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
continue;
|
continue;
|
||||||
@@ -1066,7 +1286,7 @@ func Cli_Test(projectDir: String) -> int {
|
|||||||
let tmpToml: String = bux_path_join(tmpDir, "bux.toml");
|
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";
|
var tomlContent: String = "[Package]\nName = \"_test_tmp\"\nVersion = \"0.1.0\"\nType = \"bin\"\n\n[Build]\nOutput = \"Bin\"\n";
|
||||||
if !WriteFile(tmpToml, tomlContent) {
|
if !WriteFile(tmpToml, tomlContent) {
|
||||||
PrintLine("FAIL (cannot write temp bux.toml)");
|
PrintLine("FAIL │");
|
||||||
failed = failed + 1;
|
failed = failed + 1;
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
continue;
|
continue;
|
||||||
@@ -1091,36 +1311,48 @@ func Cli_Test(projectDir: String) -> int {
|
|||||||
|
|
||||||
let buildRc: int = Cli_BuildProject(tmpDir, "", false);
|
let buildRc: int = Cli_BuildProject(tmpDir, "", false);
|
||||||
if buildRc != 0 {
|
if buildRc != 0 {
|
||||||
PrintLine("FAIL (build error)");
|
PrintLine("FAIL │");
|
||||||
failed = failed + 1;
|
failed = failed + 1;
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let testBin: String = bux_path_join(bux_path_join(tmpDir, "build"), "_test_tmp");
|
let testBin: String = bux_path_join(bux_path_join(tmpDir, "build"), "_test_tmp");
|
||||||
if !FileExists(testBin) {
|
if !FileExists(testBin) {
|
||||||
PrintLine("FAIL (test binary not found)");
|
PrintLine("FAIL │");
|
||||||
failed = failed + 1;
|
failed = failed + 1;
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let runRc: int = bux_system(testBin);
|
let runRc: int = bux_system(testBin);
|
||||||
if runRc == 0 {
|
if runRc == 0 {
|
||||||
PrintLine("PASS");
|
PrintLine("PASS │");
|
||||||
passed = passed + 1;
|
passed = passed + 1;
|
||||||
} else {
|
} else {
|
||||||
Print("FAIL (exit ");
|
Print("FAIL │");
|
||||||
PrintInt(runRc as int64);
|
PrintLine("");
|
||||||
PrintLine(")");
|
|
||||||
failed = failed + 1;
|
failed = failed + 1;
|
||||||
}
|
}
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Print("Tests: ");
|
PrintLine("└──────────────────────────────┴────────┘");
|
||||||
|
Print("Results: ");
|
||||||
PrintInt(passed as int64);
|
PrintInt(passed as int64);
|
||||||
Print(" passed, ");
|
Print(" passed, ");
|
||||||
PrintInt(failed as int64);
|
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; }
|
if failed > 0 { return 1; }
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1493,7 +1725,10 @@ func Cli_Run(args: *String, argCount: int) -> int {
|
|||||||
if argCount < 2 {
|
if argCount < 2 {
|
||||||
PrintLine("Bux Self-Hosting Compiler v0.2.0");
|
PrintLine("Bux Self-Hosting Compiler v0.2.0");
|
||||||
PrintLine("Usage: buxc <command> [args]");
|
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;
|
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") {
|
if String_Eq(cmd, "help") || String_Eq(cmd, "--help") || String_Eq(cmd, "-h") {
|
||||||
PrintLine("Bux Self-Hosting Compiler v0.2.0");
|
PrintLine("Bux Self-Hosting Compiler v0.2.0");
|
||||||
PrintLine("Usage: buxc <command> [args]");
|
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("Pipeline modules:");
|
||||||
PrintLine(" Lexer ✅ 695 lines");
|
PrintLine(" Lexer ✅");
|
||||||
PrintLine(" Parser ✅ 1004 lines");
|
PrintLine(" Parser ✅");
|
||||||
PrintLine(" Sema ✅ 393 lines");
|
PrintLine(" Sema ✅");
|
||||||
PrintLine(" HirLower ✅ 307 lines");
|
PrintLine(" HirLower ✅");
|
||||||
PrintLine(" CBackend ✅ 585 lines");
|
PrintLine(" CBackend ✅");
|
||||||
PrintLine(" Total: 3830 lines of Bux");
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1550,9 +1787,36 @@ func Cli_Run(args: *String, argCount: int) -> int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if String_Eq(cmd, "fmt") {
|
if String_Eq(cmd, "fmt") {
|
||||||
let dir: String = ".";
|
var dir: String = ".";
|
||||||
if argCount >= 3 { dir = args[2]; }
|
var checkOnly: bool = false;
|
||||||
return Cli_Fmt(dir);
|
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") {
|
if String_Eq(cmd, "check") {
|
||||||
@@ -1578,9 +1842,22 @@ func Cli_Run(args: *String, argCount: int) -> int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if String_Eq(cmd, "test") {
|
if String_Eq(cmd, "test") {
|
||||||
let dir: String = ".";
|
var dir: String = ".";
|
||||||
if argCount >= 3 { dir = args[2]; }
|
var filter: String = "";
|
||||||
return Cli_Test(dir);
|
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") {
|
if String_Eq(cmd, "run") {
|
||||||
|
|||||||
+153
-123
@@ -1,138 +1,168 @@
|
|||||||
// fmt.bux — Bux source code formatter (indentation-based, preserves line structure)
|
// fmt.bux — Bux source code formatter (indentation-based, preserves line structure)
|
||||||
module Fmt {
|
module Fmt {
|
||||||
|
|
||||||
extern func bux_read_file(path: String) -> String;
|
extern func bux_read_file(path: String) -> String;
|
||||||
extern func bux_write_file(path: String, content: String) -> bool;
|
extern func bux_write_file(path: String, content: String) -> bool;
|
||||||
extern func bux_strlen(s: String) -> uint;
|
extern func bux_strlen(s: String) -> uint;
|
||||||
|
|
||||||
// Count leading spaces on a line
|
// Count leading spaces on a line
|
||||||
func Fmt_CountLeadingSpaces(line: String) -> int {
|
func Fmt_CountLeadingSpaces(line: String) -> int {
|
||||||
var count: int = 0;
|
var count: int = 0;
|
||||||
while count < 256 {
|
while count < 256 {
|
||||||
let c: int = line[count] as int;
|
let c: int = line[count] as int;
|
||||||
if c == 0 { break; }
|
if c == 0 { break; }
|
||||||
if c != 32 { break; } // space
|
if c != 32 { break; } // space
|
||||||
count = count + 1;
|
count = count + 1;
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
if c == 0 { break; }
|
|
||||||
if c != 32 && c != 9 { break; }
|
|
||||||
i = i + 1;
|
|
||||||
}
|
|
||||||
if i == 0 { return line; }
|
|
||||||
// Extract substring from i
|
|
||||||
var len: int = 0;
|
|
||||||
while len < 256 {
|
|
||||||
let c: int = line[len] as int;
|
|
||||||
if c == 0 { break; }
|
|
||||||
len = len + 1;
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
var inString: bool = false;
|
|
||||||
var inChar: bool = false;
|
|
||||||
var inComment: bool = false;
|
|
||||||
var i: int = 0;
|
|
||||||
while i < pos {
|
|
||||||
let c: int = line[i] as int;
|
|
||||||
let n: int = 0;
|
|
||||||
if i + 1 < 256 { n = line[i + 1] as int; }
|
|
||||||
if inComment { i = i + 1; continue; }
|
|
||||||
if c == 47 && n == 47 { inComment = true; i = i + 1; continue; } // //
|
|
||||||
if c == 34 && !inChar { inString = !inString; }
|
|
||||||
if c == 39 && !inString { inChar = !inChar; }
|
|
||||||
i = i + 1;
|
|
||||||
}
|
|
||||||
return inString || inChar || inComment;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
let c: int = line[i] as int;
|
|
||||||
if c == 0 { break; }
|
|
||||||
if !Fmt_IsInStringOrComment(line, i) {
|
|
||||||
if c == 123 { delta = delta + 1; } // {
|
|
||||||
if c == 125 { delta = delta - 1; } // }
|
|
||||||
}
|
}
|
||||||
i = i + 1;
|
return count;
|
||||||
}
|
}
|
||||||
return delta;
|
|
||||||
}
|
|
||||||
|
|
||||||
func Fmt_FormatSource(source: String) -> String {
|
// Skip whitespace from start of line, return rest
|
||||||
let sb: StringBuilder = StringBuilder_NewCap(8192);
|
func Fmt_TrimLeft(line: String) -> String {
|
||||||
var indent: int = 0;
|
var i: int = 0;
|
||||||
var i: uint = 0;
|
while i < 256 {
|
||||||
let lineCount: uint = bux_str_split_count(source, "\n");
|
let c: int = line[i] as int;
|
||||||
|
if c == 0 { break; }
|
||||||
while i < lineCount {
|
if c != 32 && c != 9 { break; }
|
||||||
let line: String = bux_str_split_part(source, "\n", i);
|
|
||||||
let trimmed: String = Fmt_TrimLeft(line);
|
|
||||||
|
|
||||||
// Skip empty lines
|
|
||||||
if String_Eq(trimmed, "") {
|
|
||||||
StringBuilder_Append(&sb, "\n");
|
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
if i == 0 { return line; }
|
||||||
// Adjust indent for closing braces on this line
|
// Extract substring from i
|
||||||
let delta: int = Fmt_CountBraceDelta(trimmed);
|
var len: int = 0;
|
||||||
// If line starts with }, decrease indent before emitting
|
while len < 256 {
|
||||||
let firstChar: int = trimmed[0] as int;
|
let c: int = line[len] as int;
|
||||||
if firstChar == 125 { // }
|
if c == 0 { break; }
|
||||||
indent = indent - 1;
|
len = len + 1;
|
||||||
if indent < 0 { indent = 0; }
|
|
||||||
}
|
}
|
||||||
|
if i >= len { return ""; }
|
||||||
// Emit indentation
|
return bux_str_slice(line, i as uint, (len - i) as uint);
|
||||||
var si: int = 0;
|
|
||||||
while si < indent {
|
|
||||||
StringBuilder_Append(&sb, " ");
|
|
||||||
si = si + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emit the trimmed line
|
|
||||||
StringBuilder_Append(&sb, trimmed);
|
|
||||||
StringBuilder_Append(&sb, "\n");
|
|
||||||
|
|
||||||
// Adjust indent for opening braces
|
|
||||||
if firstChar != 125 {
|
|
||||||
indent = indent + delta;
|
|
||||||
} else {
|
|
||||||
// For lines starting with }, apply the net delta after the initial decrease
|
|
||||||
indent = indent + delta + 1;
|
|
||||||
if indent < 0 { indent = 0; }
|
|
||||||
}
|
|
||||||
|
|
||||||
i = i + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let result: String = StringBuilder_Build(&sb);
|
// Check if char at position is inside a string or comment (simplified)
|
||||||
StringBuilder_Free(&sb);
|
func Fmt_IsInStringOrComment(line: String, pos: int) -> bool {
|
||||||
return result;
|
var inString: bool = false;
|
||||||
}
|
var inChar: bool = false;
|
||||||
|
var inComment: bool = false;
|
||||||
|
var i: int = 0;
|
||||||
|
while i < pos {
|
||||||
|
let c: int = line[i] as int;
|
||||||
|
let n: int = 0;
|
||||||
|
if i + 1 < 256 { n = line[i + 1] as int; }
|
||||||
|
if inComment { i = i + 1; continue; }
|
||||||
|
if c == 47 && n == 47 { inComment = true; i = i + 1; continue; } // //
|
||||||
|
if c == 34 && !inChar { inString = !inString; }
|
||||||
|
if c == 39 && !inString { inChar = !inChar; }
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
return inString || inChar || inComment;
|
||||||
|
}
|
||||||
|
|
||||||
func Fmt_FormatFile(path: String) -> bool {
|
// Count brace depth change on a line, skipping strings/comments
|
||||||
let source: String = bux_read_file(path);
|
func Fmt_CountBraceDelta(line: String) -> int {
|
||||||
if source == null as String || String_Eq(source, "") { return false; }
|
var delta: int = 0;
|
||||||
let formatted: String = Fmt_FormatSource(source);
|
var i: int = 0;
|
||||||
if String_Eq(formatted, "") { return false; }
|
while i < 256 {
|
||||||
return bux_write_file(path, formatted);
|
let c: int = line[i] as int;
|
||||||
}
|
if c == 0 { break; }
|
||||||
|
if !Fmt_IsInStringOrComment(line, i) {
|
||||||
|
if c == 123 { delta = delta + 1; } // {
|
||||||
|
if c == 125 { delta = delta - 1; } // }
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
return delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fmt_FormatSource(source: String) -> String {
|
||||||
|
let sb: StringBuilder = StringBuilder_NewCap(8192);
|
||||||
|
var indent: int = 0;
|
||||||
|
var i: uint = 0;
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Empty line (intentional blank) — keep a single newline
|
||||||
|
if String_Eq(trimmed, "") {
|
||||||
|
StringBuilder_Append(&sb, "\n");
|
||||||
|
i = i + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust indent for closing braces on this line
|
||||||
|
let delta: int = Fmt_CountBraceDelta(trimmed);
|
||||||
|
// If line starts with }, decrease indent before emitting
|
||||||
|
let firstChar: int = trimmed[0] as int;
|
||||||
|
if firstChar == 125 { // }
|
||||||
|
indent = indent - 1;
|
||||||
|
if indent < 0 { indent = 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit indentation
|
||||||
|
var si: int = 0;
|
||||||
|
while si < indent {
|
||||||
|
StringBuilder_Append(&sb, " ");
|
||||||
|
si = si + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit the trimmed line
|
||||||
|
StringBuilder_Append(&sb, trimmed);
|
||||||
|
StringBuilder_Append(&sb, "\n");
|
||||||
|
|
||||||
|
// Adjust indent for opening braces
|
||||||
|
if firstChar != 125 {
|
||||||
|
indent = indent + delta;
|
||||||
|
} else {
|
||||||
|
// For lines starting with }, apply the net delta after the initial decrease
|
||||||
|
indent = indent + delta + 1;
|
||||||
|
if indent < 0 { indent = 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: String = StringBuilder_Build(&sb);
|
||||||
|
StringBuilder_Free(&sb);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+250
-250
@@ -1,256 +1,256 @@
|
|||||||
// hir.bux — HIR (High-level Intermediate Representation) node types
|
// hir.bux — HIR (High-level Intermediate Representation) node types
|
||||||
module Hir {
|
module Hir {
|
||||||
|
|
||||||
// HIR node kinds
|
// HIR node kinds
|
||||||
const hLit: int = 0;
|
const hLit: int = 0;
|
||||||
const hVar: int = 1;
|
const hVar: int = 1;
|
||||||
const hSelf: int = 2;
|
const hSelf: int = 2;
|
||||||
const hUnary: int = 3;
|
const hUnary: int = 3;
|
||||||
const hBinary: int = 4;
|
const hBinary: int = 4;
|
||||||
const hAssign: int = 5;
|
const hAssign: int = 5;
|
||||||
const hIf: int = 6;
|
const hIf: int = 6;
|
||||||
const hWhile: int = 7;
|
const hWhile: int = 7;
|
||||||
const hLoop: int = 8;
|
const hLoop: int = 8;
|
||||||
const hBreak: int = 9;
|
const hBreak: int = 9;
|
||||||
const hContinue: int = 10;
|
const hContinue: int = 10;
|
||||||
const hReturn: int = 11;
|
const hReturn: int = 11;
|
||||||
const hAlloca: int = 12;
|
const hAlloca: int = 12;
|
||||||
const hLoad: int = 13;
|
const hLoad: int = 13;
|
||||||
const hStore: int = 14;
|
const hStore: int = 14;
|
||||||
const hFieldPtr: int = 15;
|
const hFieldPtr: int = 15;
|
||||||
const hFieldAccess: int = 16;
|
const hFieldAccess: int = 16;
|
||||||
const hArrowField: int = 17;
|
const hArrowField: int = 17;
|
||||||
const hIndexPtr: int = 18;
|
const hIndexPtr: int = 18;
|
||||||
const hCall: int = 32;
|
const hCall: int = 32;
|
||||||
const hCallIndirect: int = 33;
|
const hCallIndirect: int = 33;
|
||||||
const hCast: int = 34;
|
const hCast: int = 34;
|
||||||
const hIs: int = 35;
|
const hIs: int = 35;
|
||||||
const hSizeOf: int = 36;
|
const hSizeOf: int = 36;
|
||||||
const hBlock: int = 37;
|
const hBlock: int = 37;
|
||||||
const hStructInit: int = 38;
|
const hStructInit: int = 38;
|
||||||
const hSliceInit: int = 39;
|
const hSliceInit: int = 39;
|
||||||
const hRange: int = 40;
|
const hRange: int = 40;
|
||||||
const hTupleInit: int = 41;
|
const hTupleInit: int = 41;
|
||||||
const hMatch: int = 42;
|
const hMatch: int = 42;
|
||||||
const hSpawn: int = 43;
|
const hSpawn: int = 43;
|
||||||
const hAwait: int = 44;
|
const hAwait: int = 44;
|
||||||
const hDefer: int = 45;
|
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,
|
node: *HirNode,
|
||||||
next: *HirArgList,
|
next: *HirArgList,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// HirNode — unified struct with tagged union pattern
|
// HirNode — unified struct with tagged union pattern
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
struct HirNode {
|
struct HirNode {
|
||||||
kind: int;
|
kind: int;
|
||||||
line: uint32;
|
line: uint32;
|
||||||
column: uint32;
|
column: uint32;
|
||||||
typeKind: int;
|
typeKind: int;
|
||||||
typeName: String;
|
typeName: String;
|
||||||
// Common fields (used by multiple kinds)
|
// Common fields (used by multiple kinds)
|
||||||
strValue: String; // var name; callee name; field name; label
|
strValue: String; // var name; callee name; field name; label
|
||||||
intValue: int; // token kind (for lit; unary op; binary op)
|
intValue: int; // token kind (for lit; unary op; binary op)
|
||||||
boolValue: bool; // range inclusive; isScope
|
boolValue: bool; // range inclusive; isScope
|
||||||
// Child nodes (up to 3)
|
// Child nodes (up to 3)
|
||||||
child1: *HirNode; // left/operand/condition/base
|
child1: *HirNode; // left/operand/condition/base
|
||||||
child2: *HirNode; // right/value/then/body
|
child2: *HirNode; // right/value/then/body
|
||||||
child3: *HirNode; // else/third
|
child3: *HirNode; // else/third
|
||||||
// Extra data pointer (for children arrays, field lists, etc.)
|
// Extra data pointer (for children arrays, field lists, etc.)
|
||||||
extraData: *void;
|
extraData: *void;
|
||||||
extraCount: int;
|
extraCount: int;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// HirFunc
|
// HirFunc
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
struct HirParam {
|
struct HirParam {
|
||||||
name: String;
|
name: String;
|
||||||
typeKind: int;
|
typeKind: int;
|
||||||
typeName: String;
|
typeName: String;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HirFunc {
|
struct HirFunc {
|
||||||
name: String;
|
name: String;
|
||||||
paramCount: int;
|
paramCount: int;
|
||||||
param0: *HirParam;
|
param0: *HirParam;
|
||||||
param1: *HirParam;
|
param1: *HirParam;
|
||||||
param2: *HirParam;
|
param2: *HirParam;
|
||||||
param3: *HirParam;
|
param3: *HirParam;
|
||||||
param4: *HirParam;
|
param4: *HirParam;
|
||||||
param5: *HirParam;
|
param5: *HirParam;
|
||||||
param6: *HirParam;
|
param6: *HirParam;
|
||||||
param7: *HirParam;
|
param7: *HirParam;
|
||||||
param8: *HirParam;
|
param8: *HirParam;
|
||||||
retTypeKind: int;
|
retTypeKind: int;
|
||||||
retTypeName: String;
|
retTypeName: String;
|
||||||
body: *HirNode;
|
body: *HirNode;
|
||||||
isPublic: bool;
|
isPublic: bool;
|
||||||
// Closure capture metadata
|
// Closure capture metadata
|
||||||
captureCount: int;
|
captureCount: int;
|
||||||
captureName0: String;
|
captureName0: String;
|
||||||
captureName1: String;
|
captureName1: String;
|
||||||
captureName2: String;
|
captureName2: String;
|
||||||
captureName3: String;
|
captureName3: String;
|
||||||
captureName4: String;
|
captureName4: String;
|
||||||
captureName5: String;
|
captureName5: String;
|
||||||
captureName6: String;
|
captureName6: String;
|
||||||
captureName7: String;
|
captureName7: String;
|
||||||
captureType0: int;
|
captureType0: int;
|
||||||
captureType1: int;
|
captureType1: int;
|
||||||
captureType2: int;
|
captureType2: int;
|
||||||
captureType3: int;
|
captureType3: int;
|
||||||
captureType4: int;
|
captureType4: int;
|
||||||
captureType5: int;
|
captureType5: int;
|
||||||
captureType6: int;
|
captureType6: int;
|
||||||
captureType7: int;
|
captureType7: int;
|
||||||
envStructName: String;
|
envStructName: String;
|
||||||
envInstanceName: String;
|
envInstanceName: String;
|
||||||
checkedFunc: bool;
|
checkedFunc: bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// HirEnumVariant
|
// HirEnumVariant
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
struct HirEnumVariant {
|
struct HirEnumVariant {
|
||||||
name: String;
|
name: String;
|
||||||
fieldCount: int;
|
fieldCount: int;
|
||||||
fieldType0: int;
|
fieldType0: int;
|
||||||
fieldName0: String;
|
fieldName0: String;
|
||||||
fieldTypeName0: String; // C type name (e.g. "int", "Tuple_int_int")
|
fieldTypeName0: String; // C type name (e.g. "int", "Tuple_int_int")
|
||||||
fieldType1: int;
|
fieldType1: int;
|
||||||
fieldName1: String;
|
fieldName1: String;
|
||||||
fieldTypeName1: String;
|
fieldTypeName1: String;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// HirModule
|
// HirModule
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
struct HirStructField {
|
struct HirStructField {
|
||||||
name: String;
|
name: String;
|
||||||
typeName: String;
|
typeName: String;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HirStruct {
|
struct HirStruct {
|
||||||
name: String;
|
name: String;
|
||||||
fieldCount: int;
|
fieldCount: int;
|
||||||
fields: *HirStructField;
|
fields: *HirStructField;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HirConst {
|
struct HirConst {
|
||||||
name: String;
|
name: String;
|
||||||
value: int;
|
value: int;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HirEnum {
|
struct HirEnum {
|
||||||
name: String;
|
name: String;
|
||||||
variantCount: int;
|
variantCount: int;
|
||||||
variants: *HirEnumVariant;
|
variants: *HirEnumVariant;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HirModule {
|
struct HirModule {
|
||||||
funcCount: int;
|
funcCount: int;
|
||||||
funcs: *HirFunc;
|
funcs: *HirFunc;
|
||||||
externCount: int;
|
externCount: int;
|
||||||
externFuncs: *HirFunc;
|
externFuncs: *HirFunc;
|
||||||
structCount: int;
|
structCount: int;
|
||||||
structs: *HirStruct;
|
structs: *HirStruct;
|
||||||
enumCount: int;
|
enumCount: int;
|
||||||
enums: *HirEnum;
|
enums: *HirEnum;
|
||||||
constCount: int;
|
constCount: int;
|
||||||
consts: *HirConst;
|
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,
|
return HirNode { kind: kind, line: line, column: column,
|
||||||
typeKind: 0, typeName: "",
|
typeKind: 0, typeName: "",
|
||||||
strValue: "", intValue: 0, boolValue: false,
|
strValue: "", intValue: 0, boolValue: false,
|
||||||
child1: null as *HirNode, child2: null as *HirNode, child3: null as *HirNode,
|
child1: null as *HirNode, child2: null as *HirNode, child3: null as *HirNode,
|
||||||
extraData: null as *void, extraCount: 0 };
|
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);
|
var n: HirNode = Hir_MakeNode(hLit, line, col);
|
||||||
n.intValue = tokKind;
|
n.intValue = tokKind;
|
||||||
n.strValue = tokText;
|
n.strValue = tokText;
|
||||||
return n;
|
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);
|
var n: HirNode = Hir_MakeNode(hVar, line, col);
|
||||||
n.strValue = name;
|
n.strValue = name;
|
||||||
return n;
|
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);
|
var n: HirNode = Hir_MakeNode(hBinary, line, col);
|
||||||
n.intValue = op;
|
n.intValue = op;
|
||||||
n.child1 = left;
|
n.child1 = left;
|
||||||
n.child2 = right;
|
n.child2 = right;
|
||||||
return n;
|
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);
|
var n: HirNode = Hir_MakeNode(hCall, line, col);
|
||||||
n.strValue = callee;
|
n.strValue = callee;
|
||||||
return n;
|
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);
|
var n: HirNode = Hir_MakeNode(hReturn, line, col);
|
||||||
n.child1 = value;
|
n.child1 = value;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
func Hir_MakeBlock(line: uint32, col: uint32) -> HirNode {
|
func Hir_MakeBlock(line: uint32, col: uint32) -> HirNode {
|
||||||
return Hir_MakeNode(hBlock, line, col);
|
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);
|
var n: HirNode = Hir_MakeNode(hIf, line, col);
|
||||||
n.child1 = cond;
|
n.child1 = cond;
|
||||||
n.child2 = thenBody;
|
n.child2 = thenBody;
|
||||||
n.child3 = elseBody;
|
n.child3 = elseBody;
|
||||||
return n;
|
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);
|
var n: HirNode = Hir_MakeNode(hWhile, line, col);
|
||||||
n.child1 = cond;
|
n.child1 = cond;
|
||||||
n.child2 = body;
|
n.child2 = body;
|
||||||
return n;
|
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);
|
var n: HirNode = Hir_MakeNode(hAlloca, line, col);
|
||||||
n.strValue = name;
|
n.strValue = name;
|
||||||
return n;
|
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);
|
var n: HirNode = Hir_MakeNode(hLoad, line, col);
|
||||||
n.child1 = ptr;
|
n.child1 = ptr;
|
||||||
return n;
|
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);
|
var n: HirNode = Hir_MakeNode(hStore, line, col);
|
||||||
n.child1 = ptr;
|
n.child1 = ptr;
|
||||||
n.child2 = value;
|
n.child2 = value;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3960
-3960
File diff suppressed because it is too large
Load Diff
+725
-712
File diff suppressed because it is too large
Load Diff
+213
-213
@@ -2,224 +2,137 @@
|
|||||||
// Parses package metadata: name, version, type, build output.
|
// Parses package metadata: name, version, type, build output.
|
||||||
module Manifest {
|
module Manifest {
|
||||||
|
|
||||||
extern func bux_strlen(s: String) -> uint;
|
extern func bux_strlen(s: String) -> uint;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Manifest struct
|
// Manifest struct
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
struct Manifest {
|
struct Manifest {
|
||||||
name: String;
|
name: String;
|
||||||
version: String;
|
version: String;
|
||||||
pkgType: String;
|
pkgType: String;
|
||||||
output: String;
|
output: String;
|
||||||
depCount: int;
|
depCount: int;
|
||||||
depName0: String;
|
depName0: String;
|
||||||
depUrl0: String;
|
depUrl0: String;
|
||||||
depName1: String;
|
depName1: String;
|
||||||
depUrl1: String;
|
depUrl1: String;
|
||||||
depName2: String;
|
depName2: String;
|
||||||
depUrl2: String;
|
depUrl2: String;
|
||||||
depName3: String;
|
depName3: String;
|
||||||
depUrl3: String;
|
depUrl3: String;
|
||||||
depName4: String;
|
depName4: String;
|
||||||
depUrl4: String;
|
depUrl4: String;
|
||||||
depName5: String;
|
depName5: String;
|
||||||
depUrl5: String;
|
depUrl5: String;
|
||||||
depName6: String;
|
depName6: String;
|
||||||
depUrl6: String;
|
depUrl6: String;
|
||||||
depName7: String;
|
depName7: String;
|
||||||
depUrl7: 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;
|
var m: Manifest;
|
||||||
m.name = "";
|
m.name = "";
|
||||||
m.version = "0.1.0";
|
m.version = "0.1.0";
|
||||||
m.pkgType = "bin";
|
m.pkgType = "bin";
|
||||||
m.output = "Bin";
|
m.output = "Bin";
|
||||||
m.depCount = 0;
|
m.depCount = 0;
|
||||||
|
|
||||||
if String_Eq(content, "") { return m; }
|
if String_Eq(content, "") { return m; }
|
||||||
|
|
||||||
var currentSection: String = "";
|
var currentSection: String = "";
|
||||||
let count: uint = String_SplitCount(content, "\n");
|
let count: uint = String_SplitCount(content, "\n");
|
||||||
var i: uint = 0;
|
var i: uint = 0;
|
||||||
while i < count {
|
while i < count {
|
||||||
let line: String = String_SplitPart(content, "\n", i);
|
let line: String = String_SplitPart(content, "\n", i);
|
||||||
|
|
||||||
// Skip empty lines and comments
|
// Skip empty lines and comments
|
||||||
if String_Eq(line, "") { i = i + 1; continue; }
|
if String_Eq(line, "") { i = i + 1; continue; }
|
||||||
if String_StartsWith(line, "#") { i = i + 1; continue; }
|
if String_StartsWith(line, "#") { i = i + 1; continue; }
|
||||||
|
|
||||||
// Section header: [Section]
|
// Section header: [Section]
|
||||||
if String_StartsWith(line, "[") {
|
if String_StartsWith(line, "[") {
|
||||||
if String_StartsWith(line, "[Package]") {
|
if String_StartsWith(line, "[Package]") {
|
||||||
currentSection = "Package";
|
currentSection = "Package";
|
||||||
} else if String_StartsWith(line, "[Build]") {
|
} else if String_StartsWith(line, "[Build]") {
|
||||||
currentSection = "Build";
|
currentSection = "Build";
|
||||||
} else if String_StartsWith(line, "[dependencies]") {
|
} else if String_StartsWith(line, "[dependencies]") {
|
||||||
currentSection = "dependencies";
|
currentSection = "dependencies";
|
||||||
} else {
|
} else {
|
||||||
currentSection = "";
|
currentSection = "";
|
||||||
|
}
|
||||||
|
i = i + 1; continue;
|
||||||
}
|
}
|
||||||
i = i + 1; continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key = Value
|
// Key = Value
|
||||||
let eqCount: uint = String_SplitCount(line, "=");
|
let eqCount: uint = String_SplitCount(line, "=");
|
||||||
if eqCount >= 2 {
|
if eqCount >= 2 {
|
||||||
let key: String = String_Trim(String_SplitPart(line, "=", 0));
|
let key: String = String_Trim(String_SplitPart(line, "=", 0));
|
||||||
let rawVal: String = String_Trim(String_SplitPart(line, "=", 1));
|
let rawVal: String = String_Trim(String_SplitPart(line, "=", 1));
|
||||||
|
|
||||||
// Strip quotes from value
|
// Strip quotes from value
|
||||||
var val: String = rawVal;
|
var val: String = rawVal;
|
||||||
if String_StartsWith(val, "\"") && String_EndsWith(val, "\"") {
|
if String_StartsWith(val, "\"") && String_EndsWith(val, "\"") {
|
||||||
let vlen: uint = bux_strlen(val);
|
let vlen: uint = bux_strlen(val);
|
||||||
if vlen >= 2 {
|
if vlen >= 2 {
|
||||||
val = String_Slice(val, 1, vlen - 2);
|
val = String_Slice(val, 1, vlen - 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if String_Eq(currentSection, "Package") {
|
if String_Eq(currentSection, "Package") {
|
||||||
if String_Eq(key, "Name") { m.name = val; }
|
if String_Eq(key, "Name") { m.name = val; }
|
||||||
if String_Eq(key, "Version") { m.version = val; }
|
if String_Eq(key, "Version") { m.version = val; }
|
||||||
if String_Eq(key, "Type") { m.pkgType = val; }
|
if String_Eq(key, "Type") { m.pkgType = val; }
|
||||||
} else if String_Eq(currentSection, "Build") {
|
} else if String_Eq(currentSection, "Build") {
|
||||||
if String_Eq(key, "Output") { m.output = val; }
|
if String_Eq(key, "Output") { m.output = val; }
|
||||||
} else if String_Eq(currentSection, "dependencies") {
|
} else if String_Eq(currentSection, "dependencies") {
|
||||||
if m.depCount < 8 {
|
if m.depCount < 8 {
|
||||||
if m.depCount == 0 { m.depName0 = key; m.depUrl0 = val; }
|
if m.depCount == 0 { m.depName0 = key; m.depUrl0 = val; }
|
||||||
else if m.depCount == 1 { m.depName1 = key; m.depUrl1 = val; }
|
else if m.depCount == 1 { m.depName1 = key; m.depUrl1 = val; }
|
||||||
else if m.depCount == 2 { m.depName2 = key; m.depUrl2 = val; }
|
else if m.depCount == 2 { m.depName2 = key; m.depUrl2 = val; }
|
||||||
else if m.depCount == 3 { m.depName3 = key; m.depUrl3 = val; }
|
else if m.depCount == 3 { m.depName3 = key; m.depUrl3 = val; }
|
||||||
else if m.depCount == 4 { m.depName4 = key; m.depUrl4 = val; }
|
else if m.depCount == 4 { m.depName4 = key; m.depUrl4 = val; }
|
||||||
else if m.depCount == 5 { m.depName5 = key; m.depUrl5 = val; }
|
else if m.depCount == 5 { m.depName5 = key; m.depUrl5 = val; }
|
||||||
else if m.depCount == 6 { m.depName6 = key; m.depUrl6 = val; }
|
else if m.depCount == 6 { m.depName6 = key; m.depUrl6 = val; }
|
||||||
else if m.depCount == 7 { m.depName7 = key; m.depUrl7 = val; }
|
else if m.depCount == 7 { m.depName7 = key; m.depUrl7 = val; }
|
||||||
m.depCount = m.depCount + 1;
|
m.depCount = m.depCount + 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
i = i + 1;
|
||||||
}
|
}
|
||||||
i = i + 1;
|
|
||||||
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
return m;
|
// ---------------------------------------------------------------------------
|
||||||
}
|
// Dependency helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
func Manifest_HasDep(m: Manifest, name: String) -> bool {
|
||||||
// Dependency helpers
|
var i: int = 0;
|
||||||
// ---------------------------------------------------------------------------
|
while i < m.depCount {
|
||||||
|
var depName: String = "";
|
||||||
func Manifest_HasDep(m: Manifest, name: String) -> bool {
|
if i == 0 { depName = m.depName0; }
|
||||||
var i: int = 0;
|
else if i == 1 { depName = m.depName1; }
|
||||||
while i < m.depCount {
|
else if i == 2 { depName = m.depName2; }
|
||||||
var depName: String = "";
|
else if i == 3 { depName = m.depName3; }
|
||||||
if i == 0 { depName = m.depName0; }
|
else if i == 4 { depName = m.depName4; }
|
||||||
else if i == 1 { depName = m.depName1; }
|
else if i == 5 { depName = m.depName5; }
|
||||||
else if i == 2 { depName = m.depName2; }
|
else if i == 6 { depName = m.depName6; }
|
||||||
else if i == 3 { depName = m.depName3; }
|
else if i == 7 { depName = m.depName7; }
|
||||||
else if i == 4 { depName = m.depName4; }
|
if String_Eq(depName, name) { return true; }
|
||||||
else if i == 5 { depName = m.depName5; }
|
i = i + 1;
|
||||||
else if i == 6 { depName = m.depName6; }
|
}
|
||||||
else if i == 7 { depName = m.depName7; }
|
return false;
|
||||||
if String_Eq(depName, name) { return true; }
|
|
||||||
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 = "";
|
|
||||||
var depUrl: String = "";
|
|
||||||
if i == 0 { depName = m.depName0; depUrl = m.depUrl0; }
|
|
||||||
else if i == 1 { depName = m.depName1; depUrl = m.depUrl1; }
|
|
||||||
else if i == 2 { depName = m.depName2; depUrl = m.depUrl2; }
|
|
||||||
else if i == 3 { depName = m.depName3; depUrl = m.depUrl3; }
|
|
||||||
else if i == 4 { depName = m.depName4; depUrl = m.depUrl4; }
|
|
||||||
else if i == 5 { depName = m.depName5; depUrl = m.depUrl5; }
|
|
||||||
else if i == 6 { depName = m.depName6; depUrl = m.depUrl6; }
|
|
||||||
else if i == 7 { depName = m.depName7; depUrl = m.depUrl7; }
|
|
||||||
if String_Eq(depName, name) { return depUrl; }
|
|
||||||
i = i + 1;
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
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; }
|
|
||||||
else if m.depCount == 1 { m.depName1 = name; m.depUrl1 = url; }
|
|
||||||
else if m.depCount == 2 { m.depName2 = name; m.depUrl2 = url; }
|
|
||||||
else if m.depCount == 3 { m.depName3 = name; m.depUrl3 = url; }
|
|
||||||
else if m.depCount == 4 { m.depName4 = name; m.depUrl4 = url; }
|
|
||||||
else if m.depCount == 5 { m.depName5 = name; m.depUrl5 = url; }
|
|
||||||
else if m.depCount == 6 { m.depName6 = name; m.depUrl6 = url; }
|
|
||||||
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 {
|
|
||||||
var found: int = -1;
|
|
||||||
var i: int = 0;
|
|
||||||
while i < m.depCount {
|
|
||||||
var depName: String = "";
|
|
||||||
if i == 0 { depName = m.depName0; }
|
|
||||||
else if i == 1 { depName = m.depName1; }
|
|
||||||
else if i == 2 { depName = m.depName2; }
|
|
||||||
else if i == 3 { depName = m.depName3; }
|
|
||||||
else if i == 4 { depName = m.depName4; }
|
|
||||||
else if i == 5 { depName = m.depName5; }
|
|
||||||
else if i == 6 { depName = m.depName6; }
|
|
||||||
else if i == 7 { depName = m.depName7; }
|
|
||||||
if String_Eq(depName, name) { found = i; break; }
|
|
||||||
i = i + 1;
|
|
||||||
}
|
|
||||||
if found < 0 { return false; }
|
|
||||||
i = found;
|
|
||||||
while i < m.depCount - 1 {
|
|
||||||
if i == 0 { m.depName0 = m.depName1; m.depUrl0 = m.depUrl1; }
|
|
||||||
else if i == 1 { m.depName1 = m.depName2; m.depUrl1 = m.depUrl2; }
|
|
||||||
else if i == 2 { m.depName2 = m.depName3; m.depUrl2 = m.depUrl3; }
|
|
||||||
else if i == 3 { m.depName3 = m.depName4; m.depUrl3 = m.depUrl4; }
|
|
||||||
else if i == 4 { m.depName4 = m.depName5; m.depUrl4 = m.depUrl5; }
|
|
||||||
else if i == 5 { m.depName5 = m.depName6; m.depUrl5 = m.depUrl6; }
|
|
||||||
else if i == 6 { m.depName6 = m.depName7; m.depUrl6 = m.depUrl7; }
|
|
||||||
i = i + 1;
|
|
||||||
}
|
|
||||||
m.depCount = m.depCount - 1;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Serialize manifest back to TOML
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
func Manifest_ToString(m: Manifest) -> String {
|
|
||||||
let sb: StringBuilder = StringBuilder_New();
|
|
||||||
StringBuilder_Append(&sb, "[Package]\n");
|
|
||||||
StringBuilder_Append(&sb, "Name = \"");
|
|
||||||
StringBuilder_Append(&sb, m.name);
|
|
||||||
StringBuilder_Append(&sb, "\"\n");
|
|
||||||
StringBuilder_Append(&sb, "Version = \"");
|
|
||||||
StringBuilder_Append(&sb, m.version);
|
|
||||||
StringBuilder_Append(&sb, "\"\n");
|
|
||||||
StringBuilder_Append(&sb, "Type = \"");
|
|
||||||
StringBuilder_Append(&sb, m.pkgType);
|
|
||||||
StringBuilder_Append(&sb, "\"\n\n");
|
|
||||||
StringBuilder_Append(&sb, "[Build]\n");
|
|
||||||
StringBuilder_Append(&sb, "Output = \"");
|
|
||||||
StringBuilder_Append(&sb, m.output);
|
|
||||||
StringBuilder_Append(&sb, "\"\n");
|
|
||||||
if m.depCount > 0 {
|
|
||||||
StringBuilder_Append(&sb, "\n[dependencies]\n");
|
|
||||||
var i: int = 0;
|
var i: int = 0;
|
||||||
while i < m.depCount {
|
while i < m.depCount {
|
||||||
var depName: String = "";
|
var depName: String = "";
|
||||||
@@ -232,22 +145,109 @@ func Manifest_ToString(m: Manifest) -> String {
|
|||||||
else if i == 5 { depName = m.depName5; depUrl = m.depUrl5; }
|
else if i == 5 { depName = m.depName5; depUrl = m.depUrl5; }
|
||||||
else if i == 6 { depName = m.depName6; depUrl = m.depUrl6; }
|
else if i == 6 { depName = m.depName6; depUrl = m.depUrl6; }
|
||||||
else if i == 7 { depName = m.depName7; depUrl = m.depUrl7; }
|
else if i == 7 { depName = m.depName7; depUrl = m.depUrl7; }
|
||||||
StringBuilder_Append(&sb, depName);
|
if String_Eq(depName, name) { return depUrl; }
|
||||||
StringBuilder_Append(&sb, " = \"");
|
|
||||||
StringBuilder_Append(&sb, depUrl);
|
|
||||||
StringBuilder_Append(&sb, "\"\n");
|
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
}
|
}
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
return StringBuilder_Build(&sb);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
func Manifest_AddDep(m: *Manifest, name: String, url: String) -> bool {
|
||||||
// Load manifest from file
|
if Manifest_HasDep(*m, name) { return false; }
|
||||||
// ---------------------------------------------------------------------------
|
if m.depCount >= 8 { return false; }
|
||||||
|
if m.depCount == 0 { m.depName0 = name; m.depUrl0 = url; }
|
||||||
|
else if m.depCount == 1 { m.depName1 = name; m.depUrl1 = url; }
|
||||||
|
else if m.depCount == 2 { m.depName2 = name; m.depUrl2 = url; }
|
||||||
|
else if m.depCount == 3 { m.depName3 = name; m.depUrl3 = url; }
|
||||||
|
else if m.depCount == 4 { m.depName4 = name; m.depUrl4 = url; }
|
||||||
|
else if m.depCount == 5 { m.depName5 = name; m.depUrl5 = url; }
|
||||||
|
else if m.depCount == 6 { m.depName6 = name; m.depUrl6 = url; }
|
||||||
|
else if m.depCount == 7 { m.depName7 = name; m.depUrl7 = url; }
|
||||||
|
m.depCount = m.depCount + 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
func Manifest_Load(path: String) -> Manifest {
|
func Manifest_RemoveDep(m: *Manifest, name: String) -> bool {
|
||||||
let content: String = ReadFile(path);
|
var found: int = -1;
|
||||||
return Manifest_Parse(content);
|
var i: int = 0;
|
||||||
}
|
while i < m.depCount {
|
||||||
|
var depName: String = "";
|
||||||
|
if i == 0 { depName = m.depName0; }
|
||||||
|
else if i == 1 { depName = m.depName1; }
|
||||||
|
else if i == 2 { depName = m.depName2; }
|
||||||
|
else if i == 3 { depName = m.depName3; }
|
||||||
|
else if i == 4 { depName = m.depName4; }
|
||||||
|
else if i == 5 { depName = m.depName5; }
|
||||||
|
else if i == 6 { depName = m.depName6; }
|
||||||
|
else if i == 7 { depName = m.depName7; }
|
||||||
|
if String_Eq(depName, name) { found = i; break; }
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
if found < 0 { return false; }
|
||||||
|
i = found;
|
||||||
|
while i < m.depCount - 1 {
|
||||||
|
if i == 0 { m.depName0 = m.depName1; m.depUrl0 = m.depUrl1; }
|
||||||
|
else if i == 1 { m.depName1 = m.depName2; m.depUrl1 = m.depUrl2; }
|
||||||
|
else if i == 2 { m.depName2 = m.depName3; m.depUrl2 = m.depUrl3; }
|
||||||
|
else if i == 3 { m.depName3 = m.depName4; m.depUrl3 = m.depUrl4; }
|
||||||
|
else if i == 4 { m.depName4 = m.depName5; m.depUrl4 = m.depUrl5; }
|
||||||
|
else if i == 5 { m.depName5 = m.depName6; m.depUrl5 = m.depUrl6; }
|
||||||
|
else if i == 6 { m.depName6 = m.depName7; m.depUrl6 = m.depUrl7; }
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
m.depCount = m.depCount - 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Serialize manifest back to TOML
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func Manifest_ToString(m: Manifest) -> String {
|
||||||
|
let sb: StringBuilder = StringBuilder_New();
|
||||||
|
StringBuilder_Append(&sb, "[Package]\n");
|
||||||
|
StringBuilder_Append(&sb, "Name = \"");
|
||||||
|
StringBuilder_Append(&sb, m.name);
|
||||||
|
StringBuilder_Append(&sb, "\"\n");
|
||||||
|
StringBuilder_Append(&sb, "Version = \"");
|
||||||
|
StringBuilder_Append(&sb, m.version);
|
||||||
|
StringBuilder_Append(&sb, "\"\n");
|
||||||
|
StringBuilder_Append(&sb, "Type = \"");
|
||||||
|
StringBuilder_Append(&sb, m.pkgType);
|
||||||
|
StringBuilder_Append(&sb, "\"\n\n");
|
||||||
|
StringBuilder_Append(&sb, "[Build]\n");
|
||||||
|
StringBuilder_Append(&sb, "Output = \"");
|
||||||
|
StringBuilder_Append(&sb, m.output);
|
||||||
|
StringBuilder_Append(&sb, "\"\n");
|
||||||
|
if m.depCount > 0 {
|
||||||
|
StringBuilder_Append(&sb, "\n[dependencies]\n");
|
||||||
|
var i: int = 0;
|
||||||
|
while i < m.depCount {
|
||||||
|
var depName: String = "";
|
||||||
|
var depUrl: String = "";
|
||||||
|
if i == 0 { depName = m.depName0; depUrl = m.depUrl0; }
|
||||||
|
else if i == 1 { depName = m.depName1; depUrl = m.depUrl1; }
|
||||||
|
else if i == 2 { depName = m.depName2; depUrl = m.depUrl2; }
|
||||||
|
else if i == 3 { depName = m.depName3; depUrl = m.depUrl3; }
|
||||||
|
else if i == 4 { depName = m.depName4; depUrl = m.depUrl4; }
|
||||||
|
else if i == 5 { depName = m.depName5; depUrl = m.depUrl5; }
|
||||||
|
else if i == 6 { depName = m.depName6; depUrl = m.depUrl6; }
|
||||||
|
else if i == 7 { depName = m.depName7; depUrl = m.depUrl7; }
|
||||||
|
StringBuilder_Append(&sb, depName);
|
||||||
|
StringBuilder_Append(&sb, " = \"");
|
||||||
|
StringBuilder_Append(&sb, depUrl);
|
||||||
|
StringBuilder_Append(&sb, "\"\n");
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return StringBuilder_Build(&sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Load manifest from file
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func Manifest_Load(path: String) -> Manifest {
|
||||||
|
let content: String = ReadFile(path);
|
||||||
|
return Manifest_Parse(content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2284
-2249
File diff suppressed because it is too large
Load Diff
+93
-93
@@ -1,115 +1,115 @@
|
|||||||
// scope.bux — Symbol table with parent-chain lookup
|
// scope.bux — Symbol table with parent-chain lookup
|
||||||
module Scope {
|
module Scope {
|
||||||
|
|
||||||
// Symbol kinds
|
// Symbol kinds
|
||||||
const skVar: int = 0;
|
const skVar: int = 0;
|
||||||
const skFunc: int = 1;
|
const skFunc: int = 1;
|
||||||
const skType: int = 2;
|
const skType: int = 2;
|
||||||
const skConst: int = 3;
|
const skConst: int = 3;
|
||||||
const skModule: int = 4;
|
const skModule: int = 4;
|
||||||
|
|
||||||
// Maximum symbols per scope
|
// Maximum symbols per scope
|
||||||
const maxSymbols: int = 8192;
|
const maxSymbols: int = 8192;
|
||||||
|
|
||||||
struct Symbol {
|
struct Symbol {
|
||||||
kind: int;
|
kind: int;
|
||||||
name: String;
|
name: String;
|
||||||
typeKind: int;
|
typeKind: int;
|
||||||
typeName: String;
|
typeName: String;
|
||||||
refType: *TypeExpr; // original type expression (for func types, etc.)
|
refType: *TypeExpr; // original type expression (for func types, etc.)
|
||||||
isMutable: bool;
|
isMutable: bool;
|
||||||
isPublic: bool;
|
isPublic: bool;
|
||||||
decl: *Decl; // associated declaration (for funcs, structs, enums)
|
decl: *Decl; // associated declaration (for funcs, structs, enums)
|
||||||
}
|
|
||||||
|
|
||||||
struct Scope {
|
|
||||||
symbols: *Symbol;
|
|
||||||
count: int;
|
|
||||||
parent: *Scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Scope operations
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
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 {
|
|
||||||
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 {
|
|
||||||
// Check local scope for duplicates
|
|
||||||
var i: int = 0;
|
|
||||||
while i < scope.count {
|
|
||||||
if String_Eq(scope.symbols[i].name, sym.name) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
i = i + 1;
|
|
||||||
}
|
}
|
||||||
if scope.count < maxSymbols {
|
|
||||||
scope.symbols[scope.count] = sym;
|
|
||||||
scope.count = scope.count + 1;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
func Scope_Lookup(scope: *Scope, name: String) -> Symbol {
|
struct Scope {
|
||||||
var cur: *Scope = scope;
|
symbols: *Symbol;
|
||||||
while cur != null as *Scope {
|
count: int;
|
||||||
|
parent: *Scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Scope operations
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
// Check local scope for duplicates
|
||||||
var i: int = 0;
|
var i: int = 0;
|
||||||
while i < cur.count {
|
while i < scope.count {
|
||||||
if String_Eq(cur.symbols[i].name, name) {
|
if String_Eq(scope.symbols[i].name, sym.name) {
|
||||||
return cur.symbols[i];
|
return false;
|
||||||
}
|
}
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
}
|
}
|
||||||
cur = cur.parent;
|
if scope.count < maxSymbols {
|
||||||
}
|
scope.symbols[scope.count] = sym;
|
||||||
var empty: Symbol = Symbol { kind: 0, name: "", typeKind: 0, typeName: "", refType: null as *TypeExpr, isMutable: false, isPublic: false, decl: null as *Decl };
|
scope.count = scope.count + 1;
|
||||||
return empty;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
func Scope_LookupLocal(scope: *Scope, name: String) -> Symbol {
|
|
||||||
var i: int = 0;
|
|
||||||
while i < scope.count {
|
|
||||||
if String_Eq(scope.symbols[i].name, name) {
|
|
||||||
return scope.symbols[i];
|
|
||||||
}
|
}
|
||||||
i = i + 1;
|
return false;
|
||||||
}
|
}
|
||||||
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_Lookup(scope: *Scope, name: String) -> Symbol {
|
||||||
var cur: *Scope = scope;
|
var cur: *Scope = scope;
|
||||||
while cur != null as *Scope {
|
while cur != null as *Scope {
|
||||||
|
var i: int = 0;
|
||||||
|
while i < cur.count {
|
||||||
|
if String_Eq(cur.symbols[i].name, name) {
|
||||||
|
return cur.symbols[i];
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
cur = cur.parent;
|
||||||
|
}
|
||||||
|
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 {
|
||||||
var i: int = 0;
|
var i: int = 0;
|
||||||
while i < cur.count {
|
while i < scope.count {
|
||||||
if String_Eq(cur.symbols[i].name, name) {
|
if String_Eq(scope.symbols[i].name, name) {
|
||||||
return cur.symbols[i];
|
return scope.symbols[i];
|
||||||
}
|
}
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
}
|
}
|
||||||
if cur == limit {
|
var empty: Symbol = Symbol { kind: 0, name: "", typeKind: 0, typeName: "", refType: null as *TypeExpr, isMutable: false, isPublic: false, decl: null as *Decl };
|
||||||
break;
|
return empty;
|
||||||
}
|
|
||||||
cur = cur.parent;
|
|
||||||
}
|
}
|
||||||
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_LookupUpTo(scope: *Scope, name: String, limit: *Scope) -> Symbol {
|
||||||
bux_free(scope.symbols as *void);
|
var cur: *Scope = scope;
|
||||||
}
|
while cur != null as *Scope {
|
||||||
|
var i: int = 0;
|
||||||
|
while i < cur.count {
|
||||||
|
if String_Eq(cur.symbols[i].name, name) {
|
||||||
|
return cur.symbols[i];
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
if cur == limit {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cur = cur.parent;
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
bux_free(scope.symbols as *void);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2231
-1981
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,13 @@
|
|||||||
// source_location.bux — Source position tracking
|
// source_location.bux — Source position tracking
|
||||||
module SourceLocation {
|
module SourceLocation {
|
||||||
|
|
||||||
struct SourceLocation {
|
struct SourceLocation {
|
||||||
line: uint32;
|
line: uint32;
|
||||||
column: uint32;
|
column: uint32;
|
||||||
offset: 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 };
|
return SourceLocation { line: line, column: column, offset: offset };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+330
-326
@@ -1,346 +1,350 @@
|
|||||||
// token.bux — Token kinds and helpers
|
// token.bux — Token kinds and helpers
|
||||||
module Token {
|
module Token {
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// TokenKind enum
|
// TokenKind enum
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
// Literals
|
// Literals
|
||||||
const tkIntLiteral: int = 0;
|
const tkIntLiteral: int = 0;
|
||||||
const tkFloatLiteral: int = 1;
|
const tkFloatLiteral: int = 1;
|
||||||
const tkStringLiteral: int = 2;
|
const tkStringLiteral: int = 2;
|
||||||
const tkCharLiteral: int = 3;
|
const tkCharLiteral: int = 3;
|
||||||
const tkBoolLiteral: int = 4;
|
const tkBoolLiteral: int = 4;
|
||||||
|
|
||||||
// Identifiers
|
// Identifiers
|
||||||
const tkIdent: int = 5;
|
const tkIdent: int = 5;
|
||||||
const tkUnderscore: int = 6;
|
const tkUnderscore: int = 6;
|
||||||
|
|
||||||
// Control flow keywords
|
// Control flow keywords
|
||||||
const tkIf: int = 7;
|
const tkIf: int = 7;
|
||||||
const tkElse: int = 8;
|
const tkElse: int = 8;
|
||||||
const tkWhile: int = 9;
|
const tkWhile: int = 9;
|
||||||
const tkDo: int = 10;
|
const tkDo: int = 10;
|
||||||
const tkLoop: int = 11;
|
const tkLoop: int = 11;
|
||||||
const tkFor: int = 12;
|
const tkFor: int = 12;
|
||||||
const tkIn: int = 13;
|
const tkIn: int = 13;
|
||||||
const tkBreak: int = 14;
|
const tkBreak: int = 14;
|
||||||
const tkContinue: int = 15;
|
const tkContinue: int = 15;
|
||||||
const tkReturn: int = 16;
|
const tkReturn: int = 16;
|
||||||
const tkMatch: int = 17;
|
const tkMatch: int = 17;
|
||||||
|
|
||||||
// Declaration keywords
|
// Declaration keywords
|
||||||
const tkFunc: int = 18;
|
const tkFunc: int = 18;
|
||||||
const tkLet: int = 19;
|
const tkLet: int = 19;
|
||||||
const tkVar: int = 20;
|
const tkVar: int = 20;
|
||||||
const tkConst: int = 21;
|
const tkConst: int = 21;
|
||||||
const tkType: int = 22;
|
const tkType: int = 22;
|
||||||
const tkStruct: int = 23;
|
const tkStruct: int = 23;
|
||||||
const tkEnum: int = 24;
|
const tkEnum: int = 24;
|
||||||
const tkUnion: int = 25;
|
const tkUnion: int = 25;
|
||||||
const tkInterface: int = 26;
|
const tkInterface: int = 26;
|
||||||
const tkExtend: int = 27;
|
const tkExtend: int = 27;
|
||||||
const tkModule: int = 28;
|
const tkModule: int = 28;
|
||||||
const tkImport: int = 29;
|
const tkImport: int = 29;
|
||||||
const tkPub: int = 30;
|
const tkPub: int = 30;
|
||||||
const tkExtern: int = 31;
|
const tkExtern: int = 31;
|
||||||
|
|
||||||
// Other keywords
|
// Other keywords
|
||||||
const tkAs: int = 32;
|
const tkAs: int = 32;
|
||||||
const tkIs: int = 33;
|
const tkIs: int = 33;
|
||||||
const tkNull: int = 34;
|
const tkNull: int = 34;
|
||||||
const tkSelf: int = 35;
|
const tkSelf: int = 35;
|
||||||
const tkSuper: int = 36;
|
const tkSuper: int = 36;
|
||||||
const tkSizeOf: int = 37;
|
const tkSizeOf: int = 37;
|
||||||
|
|
||||||
// Punctuation
|
// Punctuation
|
||||||
const tkLParen: int = 38;
|
const tkLParen: int = 38;
|
||||||
const tkRParen: int = 39;
|
const tkRParen: int = 39;
|
||||||
const tkLBrace: int = 40;
|
const tkLBrace: int = 40;
|
||||||
const tkRBrace: int = 41;
|
const tkRBrace: int = 41;
|
||||||
const tkLBracket: int = 42;
|
const tkLBracket: int = 42;
|
||||||
const tkRBracket: int = 43;
|
const tkRBracket: int = 43;
|
||||||
const tkComma: int = 44;
|
const tkComma: int = 44;
|
||||||
const tkSemicolon: int = 45;
|
const tkSemicolon: int = 45;
|
||||||
const tkColon: int = 46;
|
const tkColon: int = 46;
|
||||||
const tkColonColon: int = 47;
|
const tkColonColon: int = 47;
|
||||||
const tkDot: int = 48;
|
const tkDot: int = 48;
|
||||||
const tkDotDot: int = 49;
|
const tkDotDot: int = 49;
|
||||||
const tkDotDotDot: int = 50;
|
const tkDotDotDot: int = 50;
|
||||||
const tkDotDotEqual: int = 51;
|
const tkDotDotEqual: int = 51;
|
||||||
const tkArrow: int = 52;
|
const tkArrow: int = 52;
|
||||||
const tkFatArrow: int = 53;
|
const tkFatArrow: int = 53;
|
||||||
const tkAt: int = 54;
|
const tkAt: int = 54;
|
||||||
const tkHash: int = 55;
|
const tkHash: int = 55;
|
||||||
const tkQuestion: int = 56;
|
const tkQuestion: int = 56;
|
||||||
|
|
||||||
// Arithmetic operators
|
// Arithmetic operators
|
||||||
const tkPlus: int = 57;
|
const tkPlus: int = 57;
|
||||||
const tkMinus: int = 58;
|
const tkMinus: int = 58;
|
||||||
const tkStar: int = 59;
|
const tkStar: int = 59;
|
||||||
const tkSlash: int = 60;
|
const tkSlash: int = 60;
|
||||||
const tkPercent: int = 61;
|
const tkPercent: int = 61;
|
||||||
const tkStarStar: int = 62;
|
const tkStarStar: int = 62;
|
||||||
const tkPlusPlus: int = 63;
|
const tkPlusPlus: int = 63;
|
||||||
const tkMinusMinus: int = 64;
|
const tkMinusMinus: int = 64;
|
||||||
|
|
||||||
// Bitwise operators
|
// Bitwise operators
|
||||||
const tkAmp: int = 65;
|
const tkAmp: int = 65;
|
||||||
const tkPipe: int = 66;
|
const tkPipe: int = 66;
|
||||||
const tkCaret: int = 67;
|
const tkCaret: int = 67;
|
||||||
const tkTilde: int = 68;
|
const tkTilde: int = 68;
|
||||||
const tkShl: int = 69;
|
const tkShl: int = 69;
|
||||||
const tkShr: int = 70;
|
const tkShr: int = 70;
|
||||||
|
|
||||||
// Logical operators
|
// Logical operators
|
||||||
const tkAmpAmp: int = 71;
|
const tkAmpAmp: int = 71;
|
||||||
const tkPipePipe: int = 72;
|
const tkPipePipe: int = 72;
|
||||||
const tkBang: int = 73;
|
const tkBang: int = 73;
|
||||||
|
|
||||||
// Comparison operators
|
// Comparison operators
|
||||||
const tkEq: int = 74;
|
const tkEq: int = 74;
|
||||||
const tkNe: int = 75;
|
const tkNe: int = 75;
|
||||||
const tkLt: int = 76;
|
const tkLt: int = 76;
|
||||||
const tkLe: int = 77;
|
const tkLe: int = 77;
|
||||||
const tkGt: int = 78;
|
const tkGt: int = 78;
|
||||||
const tkGe: int = 79;
|
const tkGe: int = 79;
|
||||||
|
|
||||||
// Assignment operators
|
// Assignment operators
|
||||||
const tkAssign: int = 80;
|
const tkAssign: int = 80;
|
||||||
const tkPlusAssign: int = 81;
|
const tkPlusAssign: int = 81;
|
||||||
const tkMinusAssign: int = 82;
|
const tkMinusAssign: int = 82;
|
||||||
const tkStarAssign: int = 83;
|
const tkStarAssign: int = 83;
|
||||||
const tkSlashAssign: int = 84;
|
const tkSlashAssign: int = 84;
|
||||||
const tkPercentAssign: int = 85;
|
const tkPercentAssign: int = 85;
|
||||||
const tkAmpAssign: int = 86;
|
const tkAmpAssign: int = 86;
|
||||||
const tkPipeAssign: int = 87;
|
const tkPipeAssign: int = 87;
|
||||||
const tkCaretAssign: int = 88;
|
const tkCaretAssign: int = 88;
|
||||||
const tkShlAssign: int = 89;
|
const tkShlAssign: int = 89;
|
||||||
const tkShrAssign: int = 90;
|
const tkShrAssign: int = 90;
|
||||||
|
|
||||||
// Compile-time intrinsics
|
// Compile-time intrinsics
|
||||||
const tkHashLine: int = 91;
|
const tkHashLine: int = 91;
|
||||||
const tkHashColumn: int = 92;
|
const tkHashColumn: int = 92;
|
||||||
const tkHashFile: int = 93;
|
const tkHashFile: int = 93;
|
||||||
const tkHashFunction: int = 94;
|
const tkHashFunction: int = 94;
|
||||||
const tkHashDate: int = 95;
|
const tkHashDate: int = 95;
|
||||||
const tkHashTime: int = 96;
|
const tkHashTime: int = 96;
|
||||||
const tkHashModule: int = 97;
|
const tkHashModule: int = 97;
|
||||||
|
|
||||||
// Special
|
// Special
|
||||||
const tkOwn: int = 98;
|
const tkOwn: int = 98;
|
||||||
const tkNewLine: int = 99;
|
const tkNewLine: int = 99;
|
||||||
const tkEndOfFile: int = 100;
|
const tkEndOfFile: int = 100;
|
||||||
const tkUnknown: int = 101;
|
const tkUnknown: int = 101;
|
||||||
|
|
||||||
// Async / concurrency
|
// Async / concurrency
|
||||||
const tkAsync: int = 102;
|
const tkAsync: int = 102;
|
||||||
const tkAwait: int = 103;
|
const tkAwait: int = 103;
|
||||||
const tkSpawn: int = 104;
|
const tkSpawn: int = 104;
|
||||||
const tkDiscard: int = 105;
|
const tkDiscard: int = 105;
|
||||||
const tkDefer: int = 106;
|
const tkDefer: int = 106;
|
||||||
const tkSwitch: int = 107;
|
const tkSwitch: int = 107;
|
||||||
const tkCase: int = 108;
|
const tkCase: int = 108;
|
||||||
const tkDefault: int = 109;
|
const tkDefault: int = 109;
|
||||||
const tkUnsafe: int = 110;
|
const tkUnsafe: int = 110;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// Lifetime parameter token: 'a, 'b, ... (not a char literal)
|
||||||
// Token struct
|
const tkLifetime: int = 111;
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
struct Token {
|
// ---------------------------------------------------------------------------
|
||||||
kind: int;
|
// Token struct
|
||||||
text: String;
|
// ---------------------------------------------------------------------------
|
||||||
line: uint32;
|
|
||||||
column: uint32;
|
struct Token {
|
||||||
offset: uint32;
|
kind: int;
|
||||||
}
|
text: String;
|
||||||
|
line: uint32;
|
||||||
// ---------------------------------------------------------------------------
|
column: uint32;
|
||||||
// Helpers
|
offset: uint32;
|
||||||
// ---------------------------------------------------------------------------
|
}
|
||||||
|
|
||||||
func Token_IsKeyword(kind: int) -> bool {
|
// ---------------------------------------------------------------------------
|
||||||
if kind >= tkIf && kind <= tkIn { return true; }
|
// Helpers
|
||||||
if kind >= tkBreak && kind <= tkMatch { return true; }
|
// ---------------------------------------------------------------------------
|
||||||
if kind >= tkFunc && kind <= tkExtern { return true; }
|
|
||||||
if kind >= tkAs && kind <= tkSuper { return true; }
|
func Token_IsKeyword(kind: int) -> bool {
|
||||||
if kind == tkSizeOf { return true; }
|
if kind >= tkIf && kind <= tkIn { return true; }
|
||||||
if kind >= tkDefer && kind <= tkUnsafe { return true; }
|
if kind >= tkBreak && kind <= tkMatch { return true; }
|
||||||
if kind >= tkAsync && kind <= tkSpawn { return true; }
|
if kind >= tkFunc && kind <= tkExtern { return true; }
|
||||||
return false;
|
if kind >= tkAs && kind <= tkSuper { return true; }
|
||||||
}
|
if kind == tkSizeOf { return true; }
|
||||||
|
if kind >= tkDefer && kind <= tkUnsafe { return true; }
|
||||||
func Token_IsLiteral(kind: int) -> bool {
|
if kind >= tkAsync && kind <= tkSpawn { return true; }
|
||||||
if kind >= tkIntLiteral && kind <= tkBoolLiteral { return true; }
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
func Token_IsLiteral(kind: int) -> bool {
|
||||||
func Token_IsOperator(kind: int) -> bool {
|
if kind >= tkIntLiteral && kind <= tkBoolLiteral { return true; }
|
||||||
if kind >= tkPlus && kind <= tkShrAssign { return true; }
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
func Token_IsOperator(kind: int) -> bool {
|
||||||
func Token_IsEof(kind: int) -> bool {
|
if kind >= tkPlus && kind <= tkShrAssign { return true; }
|
||||||
return kind == tkEndOfFile;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
func Token_KeywordKind(text: String) -> int {
|
func Token_IsEof(kind: int) -> bool {
|
||||||
if String_Eq(text, "func") { return tkFunc; }
|
return kind == tkEndOfFile;
|
||||||
if String_Eq(text, "let") { return tkLet; }
|
}
|
||||||
if String_Eq(text, "var") { return tkVar; }
|
|
||||||
if String_Eq(text, "const") { return tkConst; }
|
func Token_KeywordKind(text: String) -> int {
|
||||||
if String_Eq(text, "type") { return tkType; }
|
if String_Eq(text, "func") { return tkFunc; }
|
||||||
if String_Eq(text, "struct") { return tkStruct; }
|
if String_Eq(text, "let") { return tkLet; }
|
||||||
if String_Eq(text, "enum") { return tkEnum; }
|
if String_Eq(text, "var") { return tkVar; }
|
||||||
if String_Eq(text, "union") { return tkUnion; }
|
if String_Eq(text, "const") { return tkConst; }
|
||||||
if String_Eq(text, "interface") { return tkInterface; }
|
if String_Eq(text, "type") { return tkType; }
|
||||||
if String_Eq(text, "extend") { return tkExtend; }
|
if String_Eq(text, "struct") { return tkStruct; }
|
||||||
if String_Eq(text, "module") { return tkModule; }
|
if String_Eq(text, "enum") { return tkEnum; }
|
||||||
if String_Eq(text, "import") { return tkImport; }
|
if String_Eq(text, "union") { return tkUnion; }
|
||||||
if String_Eq(text, "pub") { return tkPub; }
|
if String_Eq(text, "interface") { return tkInterface; }
|
||||||
if String_Eq(text, "extern") { return tkExtern; }
|
if String_Eq(text, "extend") { return tkExtend; }
|
||||||
if String_Eq(text, "if") { return tkIf; }
|
if String_Eq(text, "module") { return tkModule; }
|
||||||
if String_Eq(text, "else") { return tkElse; }
|
if String_Eq(text, "import") { return tkImport; }
|
||||||
if String_Eq(text, "while") { return tkWhile; }
|
if String_Eq(text, "pub") { return tkPub; }
|
||||||
if String_Eq(text, "do") { return tkDo; }
|
if String_Eq(text, "extern") { return tkExtern; }
|
||||||
if String_Eq(text, "loop") { return tkLoop; }
|
if String_Eq(text, "if") { return tkIf; }
|
||||||
if String_Eq(text, "for") { return tkFor; }
|
if String_Eq(text, "else") { return tkElse; }
|
||||||
if String_Eq(text, "in") { return tkIn; }
|
if String_Eq(text, "while") { return tkWhile; }
|
||||||
if String_Eq(text, "break") { return tkBreak; }
|
if String_Eq(text, "do") { return tkDo; }
|
||||||
if String_Eq(text, "continue") { return tkContinue; }
|
if String_Eq(text, "loop") { return tkLoop; }
|
||||||
if String_Eq(text, "return") { return tkReturn; }
|
if String_Eq(text, "for") { return tkFor; }
|
||||||
if String_Eq(text, "match") { return tkMatch; }
|
if String_Eq(text, "in") { return tkIn; }
|
||||||
if String_Eq(text, "as") { return tkAs; }
|
if String_Eq(text, "break") { return tkBreak; }
|
||||||
if String_Eq(text, "is") { return tkIs; }
|
if String_Eq(text, "continue") { return tkContinue; }
|
||||||
if String_Eq(text, "null") { return tkNull; }
|
if String_Eq(text, "return") { return tkReturn; }
|
||||||
if String_Eq(text, "self") { return tkSelf; }
|
if String_Eq(text, "match") { return tkMatch; }
|
||||||
if String_Eq(text, "super") { return tkSuper; }
|
if String_Eq(text, "as") { return tkAs; }
|
||||||
if String_Eq(text, "sizeof") { return tkSizeOf; }
|
if String_Eq(text, "is") { return tkIs; }
|
||||||
if String_Eq(text, "defer") { return tkDefer; }
|
if String_Eq(text, "null") { return tkNull; }
|
||||||
if String_Eq(text, "switch") { return tkSwitch; }
|
if String_Eq(text, "self") { return tkSelf; }
|
||||||
if String_Eq(text, "case") { return tkCase; }
|
if String_Eq(text, "super") { return tkSuper; }
|
||||||
if String_Eq(text, "default") { return tkDefault; }
|
if String_Eq(text, "sizeof") { return tkSizeOf; }
|
||||||
if String_Eq(text, "unsafe") { return tkUnsafe; }
|
if String_Eq(text, "defer") { return tkDefer; }
|
||||||
if String_Eq(text, "discard") { return tkDiscard; }
|
if String_Eq(text, "switch") { return tkSwitch; }
|
||||||
if String_Eq(text, "async") { return tkAsync; }
|
if String_Eq(text, "case") { return tkCase; }
|
||||||
if String_Eq(text, "await") { return tkAwait; }
|
if String_Eq(text, "default") { return tkDefault; }
|
||||||
if String_Eq(text, "spawn") { return tkSpawn; }
|
if String_Eq(text, "unsafe") { return tkUnsafe; }
|
||||||
if String_Eq(text, "true") { return tkBoolLiteral; }
|
if String_Eq(text, "discard") { return tkDiscard; }
|
||||||
if String_Eq(text, "false") { return tkBoolLiteral; }
|
if String_Eq(text, "async") { return tkAsync; }
|
||||||
return tkIdent;
|
if String_Eq(text, "await") { return tkAwait; }
|
||||||
}
|
if String_Eq(text, "spawn") { return tkSpawn; }
|
||||||
|
if String_Eq(text, "true") { return tkBoolLiteral; }
|
||||||
func Token_KindName(kind: int) -> String {
|
if String_Eq(text, "false") { return tkBoolLiteral; }
|
||||||
if kind == tkIntLiteral { return "integer literal"; }
|
return tkIdent;
|
||||||
if kind == tkFloatLiteral { return "float literal"; }
|
}
|
||||||
if kind == tkStringLiteral { return "string literal"; }
|
|
||||||
if kind == tkCharLiteral { return "char literal"; }
|
func Token_KindName(kind: int) -> String {
|
||||||
if kind == tkBoolLiteral { return "boolean literal"; }
|
if kind == tkIntLiteral { return "integer literal"; }
|
||||||
if kind == tkIdent { return "identifier"; }
|
if kind == tkFloatLiteral { return "float literal"; }
|
||||||
if kind == tkUnderscore { return "_"; }
|
if kind == tkStringLiteral { return "string literal"; }
|
||||||
if kind == tkSizeOf { return "sizeof"; }
|
if kind == tkCharLiteral { return "char literal"; }
|
||||||
if kind == tkIf { return "if"; }
|
if kind == tkBoolLiteral { return "boolean literal"; }
|
||||||
if kind == tkElse { return "else"; }
|
if kind == tkIdent { return "identifier"; }
|
||||||
if kind == tkWhile { return "while"; }
|
if kind == tkUnderscore { return "_"; }
|
||||||
if kind == tkDo { return "do"; }
|
if kind == tkSizeOf { return "sizeof"; }
|
||||||
if kind == tkLoop { return "loop"; }
|
if kind == tkIf { return "if"; }
|
||||||
if kind == tkFor { return "for"; }
|
if kind == tkElse { return "else"; }
|
||||||
if kind == tkIn { return "in"; }
|
if kind == tkWhile { return "while"; }
|
||||||
if kind == tkBreak { return "break"; }
|
if kind == tkDo { return "do"; }
|
||||||
if kind == tkContinue { return "continue"; }
|
if kind == tkLoop { return "loop"; }
|
||||||
if kind == tkReturn { return "return"; }
|
if kind == tkFor { return "for"; }
|
||||||
if kind == tkMatch { return "match"; }
|
if kind == tkIn { return "in"; }
|
||||||
if kind == tkFunc { return "func"; }
|
if kind == tkBreak { return "break"; }
|
||||||
if kind == tkLet { return "let"; }
|
if kind == tkContinue { return "continue"; }
|
||||||
if kind == tkVar { return "var"; }
|
if kind == tkReturn { return "return"; }
|
||||||
if kind == tkConst { return "const"; }
|
if kind == tkMatch { return "match"; }
|
||||||
if kind == tkType { return "type"; }
|
if kind == tkFunc { return "func"; }
|
||||||
if kind == tkStruct { return "struct"; }
|
if kind == tkLet { return "let"; }
|
||||||
if kind == tkEnum { return "enum"; }
|
if kind == tkVar { return "var"; }
|
||||||
if kind == tkUnion { return "union"; }
|
if kind == tkConst { return "const"; }
|
||||||
if kind == tkInterface { return "interface"; }
|
if kind == tkType { return "type"; }
|
||||||
if kind == tkExtend { return "extend"; }
|
if kind == tkStruct { return "struct"; }
|
||||||
if kind == tkModule { return "module"; }
|
if kind == tkEnum { return "enum"; }
|
||||||
if kind == tkImport { return "import"; }
|
if kind == tkUnion { return "union"; }
|
||||||
if kind == tkPub { return "pub"; }
|
if kind == tkInterface { return "interface"; }
|
||||||
if kind == tkExtern { return "extern"; }
|
if kind == tkExtend { return "extend"; }
|
||||||
if kind == tkAs { return "as"; }
|
if kind == tkModule { return "module"; }
|
||||||
if kind == tkIs { return "is"; }
|
if kind == tkImport { return "import"; }
|
||||||
if kind == tkNull { return "null"; }
|
if kind == tkPub { return "pub"; }
|
||||||
if kind == tkSelf { return "self"; }
|
if kind == tkExtern { return "extern"; }
|
||||||
if kind == tkSuper { return "super"; }
|
if kind == tkAs { return "as"; }
|
||||||
if kind == tkUnsafe { return "unsafe"; }
|
if kind == tkIs { return "is"; }
|
||||||
if kind == tkDefer { return "defer"; }
|
if kind == tkNull { return "null"; }
|
||||||
if kind == tkSwitch { return "switch"; }
|
if kind == tkSelf { return "self"; }
|
||||||
if kind == tkCase { return "case"; }
|
if kind == tkSuper { return "super"; }
|
||||||
if kind == tkDefault { return "default"; }
|
if kind == tkUnsafe { return "unsafe"; }
|
||||||
if kind == tkAsync { return "async"; }
|
if kind == tkDefer { return "defer"; }
|
||||||
if kind == tkAwait { return "await"; }
|
if kind == tkSwitch { return "switch"; }
|
||||||
if kind == tkSpawn { return "spawn"; }
|
if kind == tkCase { return "case"; }
|
||||||
if kind == tkDiscard { return "discard"; }
|
if kind == tkDefault { return "default"; }
|
||||||
if kind == tkLParen { return "("; }
|
if kind == tkAsync { return "async"; }
|
||||||
if kind == tkRParen { return ")"; }
|
if kind == tkAwait { return "await"; }
|
||||||
if kind == tkLBrace { return "{"; }
|
if kind == tkSpawn { return "spawn"; }
|
||||||
if kind == tkRBrace { return "}"; }
|
if kind == tkDiscard { return "discard"; }
|
||||||
if kind == tkLBracket { return "["; }
|
if kind == tkLParen { return "("; }
|
||||||
if kind == tkRBracket { return "]"; }
|
if kind == tkRParen { return ")"; }
|
||||||
if kind == tkComma { return ","; }
|
if kind == tkLBrace { return "{"; }
|
||||||
if kind == tkSemicolon { return ";"; }
|
if kind == tkRBrace { return "}"; }
|
||||||
if kind == tkColon { return ":"; }
|
if kind == tkLBracket { return "["; }
|
||||||
if kind == tkColonColon { return "::"; }
|
if kind == tkRBracket { return "]"; }
|
||||||
if kind == tkDot { return "."; }
|
if kind == tkComma { return ","; }
|
||||||
if kind == tkDotDot { return ".."; }
|
if kind == tkSemicolon { return ";"; }
|
||||||
if kind == tkDotDotDot { return "..."; }
|
if kind == tkColon { return ":"; }
|
||||||
if kind == tkDotDotEqual { return "..="; }
|
if kind == tkColonColon { return "::"; }
|
||||||
if kind == tkArrow { return "->"; }
|
if kind == tkDot { return "."; }
|
||||||
if kind == tkFatArrow { return "=>"; }
|
if kind == tkDotDot { return ".."; }
|
||||||
if kind == tkAt { return "@"; }
|
if kind == tkDotDotDot { return "..."; }
|
||||||
if kind == tkHash { return "#"; }
|
if kind == tkDotDotEqual { return "..="; }
|
||||||
if kind == tkQuestion { return "?"; }
|
if kind == tkArrow { return "->"; }
|
||||||
if kind == tkPlus { return "+"; }
|
if kind == tkFatArrow { return "=>"; }
|
||||||
if kind == tkMinus { return "-"; }
|
if kind == tkAt { return "@"; }
|
||||||
if kind == tkStar { return "*"; }
|
if kind == tkHash { return "#"; }
|
||||||
if kind == tkSlash { return "/"; }
|
if kind == tkQuestion { return "?"; }
|
||||||
if kind == tkPercent { return "%"; }
|
if kind == tkPlus { return "+"; }
|
||||||
if kind == tkStarStar { return "**"; }
|
if kind == tkMinus { return "-"; }
|
||||||
if kind == tkPlusPlus { return "++"; }
|
if kind == tkStar { return "*"; }
|
||||||
if kind == tkMinusMinus { return "--"; }
|
if kind == tkSlash { return "/"; }
|
||||||
if kind == tkAmp { return "&"; }
|
if kind == tkPercent { return "%"; }
|
||||||
if kind == tkPipe { return "|"; }
|
if kind == tkStarStar { return "**"; }
|
||||||
if kind == tkCaret { return "^"; }
|
if kind == tkPlusPlus { return "++"; }
|
||||||
if kind == tkTilde { return "~"; }
|
if kind == tkMinusMinus { return "--"; }
|
||||||
if kind == tkShl { return "<<"; }
|
if kind == tkAmp { return "&"; }
|
||||||
if kind == tkShr { return ">>"; }
|
if kind == tkPipe { return "|"; }
|
||||||
if kind == tkAmpAmp { return "&&"; }
|
if kind == tkCaret { return "^"; }
|
||||||
if kind == tkPipePipe { return "||"; }
|
if kind == tkTilde { return "~"; }
|
||||||
if kind == tkBang { return "!"; }
|
if kind == tkShl { return "<<"; }
|
||||||
if kind == tkEq { return "=="; }
|
if kind == tkShr { return ">>"; }
|
||||||
if kind == tkNe { return "!="; }
|
if kind == tkAmpAmp { return "&&"; }
|
||||||
if kind == tkLt { return "<"; }
|
if kind == tkPipePipe { return "||"; }
|
||||||
if kind == tkLe { return "<="; }
|
if kind == tkBang { return "!"; }
|
||||||
if kind == tkGt { return ">"; }
|
if kind == tkEq { return "=="; }
|
||||||
if kind == tkGe { return ">="; }
|
if kind == tkNe { return "!="; }
|
||||||
if kind == tkAssign { return "="; }
|
if kind == tkLt { return "<"; }
|
||||||
if kind == tkPlusAssign { return "+="; }
|
if kind == tkLe { return "<="; }
|
||||||
if kind == tkMinusAssign { return "-="; }
|
if kind == tkGt { return ">"; }
|
||||||
if kind == tkStarAssign { return "*="; }
|
if kind == tkGe { return ">="; }
|
||||||
if kind == tkSlashAssign { return "/="; }
|
if kind == tkAssign { return "="; }
|
||||||
if kind == tkPercentAssign { return "%="; }
|
if kind == tkPlusAssign { return "+="; }
|
||||||
if kind == tkAmpAssign { return "&="; }
|
if kind == tkMinusAssign { return "-="; }
|
||||||
if kind == tkPipeAssign { return "|="; }
|
if kind == tkStarAssign { return "*="; }
|
||||||
if kind == tkCaretAssign { return "^="; }
|
if kind == tkSlashAssign { return "/="; }
|
||||||
if kind == tkShlAssign { return "<<="; }
|
if kind == tkPercentAssign { return "%="; }
|
||||||
if kind == tkShrAssign { return ">>="; }
|
if kind == tkAmpAssign { return "&="; }
|
||||||
if kind == tkHashLine { return "#line"; }
|
if kind == tkPipeAssign { return "|="; }
|
||||||
if kind == tkHashColumn { return "#column"; }
|
if kind == tkCaretAssign { return "^="; }
|
||||||
if kind == tkHashFile { return "#file"; }
|
if kind == tkShlAssign { return "<<="; }
|
||||||
if kind == tkHashFunction { return "#function"; }
|
if kind == tkShrAssign { return ">>="; }
|
||||||
if kind == tkHashDate { return "#date"; }
|
if kind == tkHashLine { return "#line"; }
|
||||||
if kind == tkHashTime { return "#time"; }
|
if kind == tkHashColumn { return "#column"; }
|
||||||
if kind == tkHashModule { return "#module"; }
|
if kind == tkHashFile { return "#file"; }
|
||||||
if kind == tkNewLine { return "newline"; }
|
if kind == tkHashFunction { return "#function"; }
|
||||||
if kind == tkEndOfFile { return "end of file"; }
|
if kind == tkHashDate { return "#date"; }
|
||||||
return "unknown token";
|
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";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user