feat: HTTP registry, app/bench harnesses, and DWARF #line maps
Ship QUALITY_PLAN sessions 31–33: fetchable package index URLs, showcase app and micro/nexus benchmarks, and debugger-friendly C codegen. - Registry: BUX_REGISTRY accepts http(s) URLs (curl/wget → ~/.bux/cache) - E.2: make test-apps smoke for nexus/boko/simpledb/jwt-pitbul - E.5: benches/micro + C/Nim/Zig twins; make bench-nexus (wrk) - E.4: HIR locs → #line .bux; default -O0 -g; --release -O2; make test-dwarf
This commit is contained in:
@@ -5,7 +5,7 @@ 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 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
|
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 test-stdlib selfhost-loop lsp fmt-check docs
|
.PHONY: all build dev debug test clean clean-all test-examples selfhost test-golden test-errors test-stdlib selfhost-loop lsp fmt-check docs bench test-apps test-dwarf
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
@@ -193,6 +193,32 @@ test-lsp: lsp
|
|||||||
|
|
||||||
.PHONY: test-registry
|
.PHONY: test-registry
|
||||||
test-registry: build
|
test-registry: build
|
||||||
@echo "=== Registry smoke (E.1) ==="
|
@echo "=== Registry smoke (E.1 + HTTP) ==="
|
||||||
@chmod +x tools/smoke_registry.sh
|
@chmod +x tools/smoke_registry.sh
|
||||||
@tools/smoke_registry.sh
|
@tools/smoke_registry.sh
|
||||||
|
|
||||||
|
# E.2 — build showcase apps + simpledb/jwt CLI smoke
|
||||||
|
.PHONY: test-apps
|
||||||
|
test-apps: build
|
||||||
|
@echo "=== Apps smoke (E.2) ==="
|
||||||
|
@chmod +x tools/smoke_apps.sh
|
||||||
|
@tools/smoke_apps.sh
|
||||||
|
|
||||||
|
# E.5 — micro-benchmarks (Bux + C/Nim/Zig twins)
|
||||||
|
.PHONY: bench
|
||||||
|
bench: build
|
||||||
|
@chmod +x tools/bench.sh
|
||||||
|
@tools/bench.sh
|
||||||
|
|
||||||
|
# E.5 — Nexus HTTP throughput (wrk); optional via BENCH_NEXUS=1 make bench
|
||||||
|
.PHONY: bench-nexus
|
||||||
|
bench-nexus: build
|
||||||
|
@chmod +x tools/bench_nexus.sh
|
||||||
|
@tools/bench_nexus.sh
|
||||||
|
|
||||||
|
# E.4 — DWARF / #line debugger smoke
|
||||||
|
.PHONY: test-dwarf
|
||||||
|
test-dwarf: build
|
||||||
|
@echo "=== DWARF / #line smoke (E.4) ==="
|
||||||
|
@chmod +x tools/smoke_dwarf.sh
|
||||||
|
@tools/smoke_dwarf.sh
|
||||||
|
|||||||
@@ -32,9 +32,12 @@ cd apps/nexus
|
|||||||
|
|
||||||
# Run
|
# Run
|
||||||
./nexus
|
./nexus
|
||||||
|
|
||||||
|
# Optional env (also used by `make bench-nexus`)
|
||||||
|
# NEXUS_PORT=18080 NEXUS_BIND=127.0.0.1 NEXUS_WORKERS=4 ./build/nexus
|
||||||
```
|
```
|
||||||
|
|
||||||
Server starts on `http://0.0.0.0:8080`:
|
Server starts on `http://0.0.0.0:8080` (override with `NEXUS_PORT` / `NEXUS_BIND`):
|
||||||
|
|
||||||
```
|
```
|
||||||
╔══════════════════════════════════════════════╗
|
╔══════════════════════════════════════════════╗
|
||||||
|
|||||||
+31
-1
@@ -5,6 +5,8 @@ module Main {
|
|||||||
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};
|
||||||
|
import Std::Os::{Os_GetEnv};
|
||||||
|
import Std::String::{String_Len, String_ToInt};
|
||||||
|
|
||||||
func BuildRouter() -> Router {
|
func BuildRouter() -> Router {
|
||||||
var routes: Array<Route> = Array_New<Route>(8);
|
var routes: Array<Route> = Array_New<Route>(8);
|
||||||
@@ -39,8 +41,36 @@ module Main {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply optional env overrides for benches / ops:
|
||||||
|
/// NEXUS_PORT, NEXUS_WORKERS, NEXUS_BIND, NEXUS_PUBLIC
|
||||||
|
func ApplyEnvConfig(config: *ServerConfig) {
|
||||||
|
let portEnv: String = Os_GetEnv("NEXUS_PORT");
|
||||||
|
if String_Len(portEnv) > 0 {
|
||||||
|
let p: int64 = String_ToInt(portEnv);
|
||||||
|
if p > 0 && p < 65536 {
|
||||||
|
config.port = p as int;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let workersEnv: String = Os_GetEnv("NEXUS_WORKERS");
|
||||||
|
if String_Len(workersEnv) > 0 {
|
||||||
|
let w: int64 = String_ToInt(workersEnv);
|
||||||
|
if w > 0 && w <= 256 {
|
||||||
|
config.workerCount = w as int;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let bindEnv: String = Os_GetEnv("NEXUS_BIND");
|
||||||
|
if String_Len(bindEnv) > 0 {
|
||||||
|
config.bindAddr = bindEnv;
|
||||||
|
}
|
||||||
|
let pubEnv: String = Os_GetEnv("NEXUS_PUBLIC");
|
||||||
|
if String_Len(pubEnv) > 0 {
|
||||||
|
config.publicDir = pubEnv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Main() -> int {
|
func Main() -> int {
|
||||||
let config: ServerConfig = DefaultConfig();
|
var config: ServerConfig = DefaultConfig();
|
||||||
|
ApplyEnvConfig(&config);
|
||||||
let router: Router = BuildRouter();
|
let router: Router = BuildRouter();
|
||||||
|
|
||||||
return RunServer(config, router);
|
return RunServer(config, router);
|
||||||
|
|||||||
@@ -50,10 +50,9 @@ Data is stored as plain text, one `key=value` per line. The file is created auto
|
|||||||
## Build
|
## Build
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# workaround: disable broken JWT module
|
cd apps/simpledb
|
||||||
mv ../../lib/crypto/jwt.bux ../../lib/crypto/jwt.bux.bak
|
|
||||||
../../buxc build
|
../../buxc build
|
||||||
mv ../../lib/crypto/jwt.bux.bak ../../lib/crypto/jwt.bux
|
./build/simpledb data.db set hello world
|
||||||
```
|
```
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# Bux benchmarks (E.5)
|
||||||
|
|
||||||
|
Micro-kernels + optional Nexus HTTP throughput for regression and language comparison.
|
||||||
|
|
||||||
|
## Quick run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From repo root
|
||||||
|
make bench # Bux micro + C + Nim (+ Zig if installed)
|
||||||
|
make bench-nexus # wrk vs apps/nexus /api/health
|
||||||
|
BENCH_NEXUS=1 make bench
|
||||||
|
```
|
||||||
|
|
||||||
|
## Micro suites
|
||||||
|
|
||||||
|
| Suite | Kernels | Build |
|
||||||
|
|-------|---------|-------|
|
||||||
|
| `micro/` | Bux: `int_loop`, `fib30`, `string_concat`, `array_push` | `buxc build` |
|
||||||
|
| `c/` | C: `fib30`, `int_loop` | `gcc -O2` |
|
||||||
|
| `nim/` | Nim twins | `nim c -d:release --opt:speed` |
|
||||||
|
| `zig/` | Zig twins (optional) | `zig build-exe -OReleaseFast` |
|
||||||
|
|
||||||
|
Output lines:
|
||||||
|
|
||||||
|
```
|
||||||
|
BENCH <name> iters=N total_us=T us_per_op=P
|
||||||
|
```
|
||||||
|
|
||||||
|
## Nexus throughput
|
||||||
|
|
||||||
|
`tools/bench_nexus.sh` / `make bench-nexus`:
|
||||||
|
|
||||||
|
1. Builds `apps/nexus`
|
||||||
|
2. Starts on `NEXUS_PORT` (default **18080**), bind `127.0.0.1`
|
||||||
|
3. `wrk -t4 -c64 -d5s` against `/api/health`
|
||||||
|
4. Prints `BENCH nexus_health rps=…`
|
||||||
|
|
||||||
|
Env knobs:
|
||||||
|
|
||||||
|
| Variable | Default | Meaning |
|
||||||
|
|----------|---------|---------|
|
||||||
|
| `NEXUS_PORT` | `18080` | Listen port (also used by the server binary) |
|
||||||
|
| `NEXUS_WORKERS` | `4` | Worker threads |
|
||||||
|
| `NEXUS_BENCH_DURATION` | `5s` | wrk `-d` |
|
||||||
|
| `NEXUS_BENCH_C` | `64` | wrk connections |
|
||||||
|
| `NEXUS_BENCH_T` | `4` | wrk threads |
|
||||||
|
|
||||||
|
Server-side (any nexus run):
|
||||||
|
|
||||||
|
- `NEXUS_PORT`, `NEXUS_WORKERS`, `NEXUS_BIND`, `NEXUS_PUBLIC`
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Numbers vary by machine; use them relatively (same host, same day).
|
||||||
|
- Nexus currently closes connections (`Connection: close`) — RPS is honest for that model, not keep-alive maxed.
|
||||||
|
- Requires `wrk` for `bench-nexus` (`apt install wrk` on Debian/Ubuntu).
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/* C reference: recursive fib(30) — compare with benches/micro fib30 */
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
static int fib(int n) {
|
||||||
|
if (n <= 1) return n;
|
||||||
|
return fib(n - 1) + fib(n - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t now_us(void) {
|
||||||
|
struct timespec ts;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
return (int64_t)ts.tv_sec * 1000000LL + (int64_t)ts.tv_nsec / 1000LL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
(void)fib(20); /* warmup */
|
||||||
|
int64_t t0 = now_us();
|
||||||
|
int r = fib(30);
|
||||||
|
int64_t t1 = now_us();
|
||||||
|
if (r < 0) return 1;
|
||||||
|
printf("BENCH fib30 iters=1 total_us=%lld us_per_op=%lld\n",
|
||||||
|
(long long)(t1 - t0), (long long)(t1 - t0));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/* C reference: 1e6 integer arithmetic loop */
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
static int64_t now_us(void) {
|
||||||
|
struct timespec ts;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
return (int64_t)ts.tv_sec * 1000000LL + (int64_t)ts.tv_nsec / 1000LL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
const int iters = 1000000;
|
||||||
|
int acc = 0;
|
||||||
|
int64_t t0 = now_us();
|
||||||
|
for (int i = 0; i < iters; i++) {
|
||||||
|
acc = acc + i * 3 - 1;
|
||||||
|
}
|
||||||
|
int64_t t1 = now_us();
|
||||||
|
if (acc == 0 && iters > 0) return 1;
|
||||||
|
printf("BENCH int_loop iters=%d total_us=%lld us_per_op=%lld\n",
|
||||||
|
iters, (long long)(t1 - t0), (long long)((t1 - t0) / iters));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[Package]
|
||||||
|
Name = "micro"
|
||||||
|
Version = "0.1.0"
|
||||||
|
Type = "bin"
|
||||||
|
Description = "Micro-benchmarks for Bux (E.5)"
|
||||||
|
|
||||||
|
[Build]
|
||||||
|
Output = "Bin"
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
// Micro-benchmarks (E.5) — recursive fib, string concat, array push.
|
||||||
|
// Prints lines: BENCH name iters total_us us_per_op
|
||||||
|
import Std::Io::{PrintLine, Print};
|
||||||
|
import Std::Time::{Time_NowUs};
|
||||||
|
import Std::String::{String_FromInt, String_Concat, String_Len};
|
||||||
|
import Std::Array::{Array, Array_New, Array_Push, Array_Len, Array_Get};
|
||||||
|
|
||||||
|
func Fib(n: int) -> int {
|
||||||
|
if n <= 1 {
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
return Fib(n - 1) + Fib(n - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
func Report(name: String, iters: int, usTotal: int64) {
|
||||||
|
var perOpUs: int64 = 0;
|
||||||
|
if iters > 0 {
|
||||||
|
perOpUs = usTotal / (iters as int64);
|
||||||
|
}
|
||||||
|
Print("BENCH ");
|
||||||
|
Print(name);
|
||||||
|
Print(" iters=");
|
||||||
|
Print(String_FromInt(iters as int64));
|
||||||
|
Print(" total_us=");
|
||||||
|
Print(String_FromInt(usTotal));
|
||||||
|
Print(" us_per_op=");
|
||||||
|
PrintLine(String_FromInt(perOpUs));
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchIntLoop() {
|
||||||
|
let iters: int = 1000000;
|
||||||
|
var acc: int = 0;
|
||||||
|
var i: int = 0;
|
||||||
|
let t0: int64 = Time_NowUs();
|
||||||
|
while i < iters {
|
||||||
|
acc = acc + i * 3 - 1;
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
let t1: int64 = Time_NowUs();
|
||||||
|
if acc == 0 && iters > 0 {
|
||||||
|
PrintLine("unreachable");
|
||||||
|
}
|
||||||
|
Report("int_loop", iters, t1 - t0);
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchFib() {
|
||||||
|
discard Fib(20);
|
||||||
|
let n: int = 30;
|
||||||
|
let t0: int64 = Time_NowUs();
|
||||||
|
let r: int = Fib(n);
|
||||||
|
let t1: int64 = Time_NowUs();
|
||||||
|
if r < 0 {
|
||||||
|
PrintLine("unreachable");
|
||||||
|
}
|
||||||
|
Report("fib30", 1, t1 - t0);
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchStringConcat() {
|
||||||
|
let iters: int = 50000;
|
||||||
|
var i: int = 0;
|
||||||
|
let t0: int64 = Time_NowUs();
|
||||||
|
var s: String = "";
|
||||||
|
while i < iters {
|
||||||
|
s = String_Concat(s, "x");
|
||||||
|
if String_Len(s) > 256 {
|
||||||
|
s = "x";
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
let t1: int64 = Time_NowUs();
|
||||||
|
if String_Len(s) < 0 {
|
||||||
|
PrintLine("unreachable");
|
||||||
|
}
|
||||||
|
Report("string_concat", iters, t1 - t0);
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchArrayPush() {
|
||||||
|
let iters: int = 100000;
|
||||||
|
var arr: Array<int> = Array_New<int>(16);
|
||||||
|
var i: int = 0;
|
||||||
|
let t0: int64 = Time_NowUs();
|
||||||
|
while i < iters {
|
||||||
|
Array_Push<int>(&arr, i);
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
let t1: int64 = Time_NowUs();
|
||||||
|
let n: uint = Array_Len<int>(&arr);
|
||||||
|
if n as int != iters {
|
||||||
|
Print("array len mismatch: ");
|
||||||
|
PrintLine(String_FromInt(n as int64));
|
||||||
|
}
|
||||||
|
discard Array_Get<int>(&arr, (iters - 1) as uint);
|
||||||
|
Report("array_push", iters, t1 - t0);
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
PrintLine("=== Bux micro-benchmarks (E.5) ===");
|
||||||
|
BenchIntLoop();
|
||||||
|
BenchFib();
|
||||||
|
BenchStringConcat();
|
||||||
|
BenchArrayPush();
|
||||||
|
PrintLine("=== done ===");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
## Nim twin of benches/c/fib.c — recursive fib(30)
|
||||||
|
import std/[times, strformat]
|
||||||
|
|
||||||
|
proc fib(n: int): int =
|
||||||
|
if n <= 1: return n
|
||||||
|
fib(n - 1) + fib(n - 2)
|
||||||
|
|
||||||
|
proc nowUs(): int64 =
|
||||||
|
int64(epochTime() * 1_000_000.0)
|
||||||
|
|
||||||
|
discard fib(20)
|
||||||
|
let t0 = nowUs()
|
||||||
|
let r = fib(30)
|
||||||
|
let t1 = nowUs()
|
||||||
|
if r < 0: quit(1)
|
||||||
|
echo &"BENCH fib30 iters=1 total_us={t1 - t0} us_per_op={t1 - t0}"
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
## Nim twin of benches/c/int_loop.c — 1e6 integer arithmetic loop
|
||||||
|
import std/[times, strformat]
|
||||||
|
|
||||||
|
proc nowUs(): int64 =
|
||||||
|
int64(epochTime() * 1_000_000.0)
|
||||||
|
|
||||||
|
const iters = 1_000_000
|
||||||
|
var acc = 0
|
||||||
|
let t0 = nowUs()
|
||||||
|
for i in 0 ..< iters:
|
||||||
|
acc = acc + i * 3 - 1
|
||||||
|
let t1 = nowUs()
|
||||||
|
if acc == 0 and iters > 0: quit(1)
|
||||||
|
let total = t1 - t0
|
||||||
|
echo &"BENCH int_loop iters={iters} total_us={total} us_per_op={total div iters}"
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// Zig twin of benches/c/fib.c — recursive fib(30)
|
||||||
|
// Build: zig build-exe -OReleaseFast -femit-bin=build/fib fib.zig
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
fn fib(n: i32) i32 {
|
||||||
|
if (n <= 1) return n;
|
||||||
|
return fib(n - 1) + fib(n - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nowUs() i64 {
|
||||||
|
return @divTrunc(@as(i64, @intCast(std.time.nanoTimestamp())), 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
_ = fib(20);
|
||||||
|
const t0 = nowUs();
|
||||||
|
const r = fib(30);
|
||||||
|
const t1 = nowUs();
|
||||||
|
if (r < 0) return error.Unreachable;
|
||||||
|
const out = std.io.getStdOut().writer();
|
||||||
|
try out.print("BENCH fib30 iters=1 total_us={d} us_per_op={d}\n", .{ t1 - t0, t1 - t0 });
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// Zig twin of benches/c/int_loop.c — 1e6 integer arithmetic loop
|
||||||
|
// Build: zig build-exe -OReleaseFast -femit-bin=build/int_loop int_loop.zig
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
fn nowUs() i64 {
|
||||||
|
return @divTrunc(@as(i64, @intCast(std.time.nanoTimestamp())), 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
const iters: i32 = 1_000_000;
|
||||||
|
var acc: i32 = 0;
|
||||||
|
const t0 = nowUs();
|
||||||
|
var i: i32 = 0;
|
||||||
|
while (i < iters) : (i += 1) {
|
||||||
|
acc = acc + i * 3 - 1;
|
||||||
|
}
|
||||||
|
const t1 = nowUs();
|
||||||
|
if (acc == 0 and iters > 0) return error.Unreachable;
|
||||||
|
const total = t1 - t0;
|
||||||
|
const out = std.io.getStdOut().writer();
|
||||||
|
try out.print("BENCH int_loop iters={d} total_us={d} us_per_op={d}\n", .{ iters, total, @divTrunc(total, iters) });
|
||||||
|
}
|
||||||
+47
-10
@@ -15,6 +15,7 @@ type
|
|||||||
color*: ColorMode
|
color*: ColorMode
|
||||||
quiet*: bool
|
quiet*: bool
|
||||||
verbose*: bool
|
verbose*: bool
|
||||||
|
release*: bool ## --release: -O2, no -g / no #line (E.4 dual)
|
||||||
|
|
||||||
proc printUsage*() =
|
proc printUsage*() =
|
||||||
echo """Bux Programming Language (bootstrap compiler)
|
echo """Bux Programming Language (bootstrap compiler)
|
||||||
@@ -42,15 +43,22 @@ Command options:
|
|||||||
fmt --check Exit 1 if any file would be reformatted (CI)
|
fmt --check Exit 1 if any file would be reformatted (CI)
|
||||||
doc --out <file> Write docs to file (default: stdout)
|
doc --out <file> Write docs to file (default: stdout)
|
||||||
add --path / --git Explicit source; else resolve via registry
|
add --path / --git Explicit source; else resolve via registry
|
||||||
|
build --release Optimized build (-O2, no debug / #line)
|
||||||
|
|
||||||
|
Registry:
|
||||||
|
BUX_REGISTRY Local path or http(s):// URL to registry.toml
|
||||||
|
BUX_REGISTRY_REFRESH=1 Force re-download of HTTP index cache
|
||||||
|
BUX_CFLAGS Extra flags appended to the C compiler line
|
||||||
|
|
||||||
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
|
||||||
-v, --verbose Verbose output
|
-v, --verbose Verbose output
|
||||||
|
--release Optimize (-O2), omit -g and #line maps
|
||||||
"""
|
"""
|
||||||
|
|
||||||
proc parseGlobalOptions(args: seq[string]): tuple[opts: GlobalOptions, rest: seq[string], ok: bool] =
|
proc parseGlobalOptions(args: seq[string]): tuple[opts: GlobalOptions, rest: seq[string], ok: bool] =
|
||||||
result.opts = GlobalOptions(color: cmAuto, quiet: false, verbose: false)
|
result.opts = GlobalOptions(color: cmAuto, quiet: false, verbose: false, release: false)
|
||||||
result.rest = @[]
|
result.rest = @[]
|
||||||
result.ok = true
|
result.ok = true
|
||||||
var i = 0
|
var i = 0
|
||||||
@@ -74,6 +82,8 @@ proc parseGlobalOptions(args: seq[string]): tuple[opts: GlobalOptions, rest: seq
|
|||||||
result.opts.quiet = true
|
result.opts.quiet = true
|
||||||
elif arg == "-v" or arg == "--verbose":
|
elif arg == "-v" or arg == "--verbose":
|
||||||
result.opts.verbose = true
|
result.opts.verbose = true
|
||||||
|
elif arg == "--release":
|
||||||
|
result.opts.release = true
|
||||||
else:
|
else:
|
||||||
result.rest.add(arg)
|
result.rest.add(arg)
|
||||||
inc i
|
inc i
|
||||||
@@ -411,10 +421,14 @@ 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:
|
||||||
# Registry resolve (E.1)
|
# Registry resolve (E.1 + HTTP URL)
|
||||||
let reg = loadRegistry()
|
let reg = loadRegistry()
|
||||||
if reg.path.len == 0:
|
if reg.path.len == 0:
|
||||||
printError("no package registry found (set BUX_REGISTRY or install config/registry.toml)", useColor)
|
if reg.sourceUrl.len > 0:
|
||||||
|
printError(&"failed to fetch registry from {reg.sourceUrl}", useColor)
|
||||||
|
printError("hint: check network, curl/wget, or set BUX_REGISTRY to a local file", useColor)
|
||||||
|
else:
|
||||||
|
printError("no package registry found (set BUX_REGISTRY or install config/registry.toml)", useColor)
|
||||||
return 1
|
return 1
|
||||||
let pkg = registryLookup(reg, depName, version)
|
let pkg = registryLookup(reg, depName, version)
|
||||||
if pkg.name.len == 0:
|
if pkg.name.len == 0:
|
||||||
@@ -423,7 +437,8 @@ proc cmdAdd*(args: seq[string], opts: GlobalOptions): int =
|
|||||||
return 1
|
return 1
|
||||||
depLine = formatRegistryDepLine(depName, pkg)
|
depLine = formatRegistryDepLine(depName, pkg)
|
||||||
if not opts.quiet:
|
if not opts.quiet:
|
||||||
printInfo(&"Resolved '{depName}' {pkg.version} from registry {reg.path}", useColor)
|
let src = if reg.sourceUrl.len > 0: reg.sourceUrl else: reg.path
|
||||||
|
printInfo(&"Resolved '{depName}' {pkg.version} from registry {src}", 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:
|
||||||
@@ -440,10 +455,18 @@ proc cmdSearch*(args: seq[string], opts: GlobalOptions): int =
|
|||||||
let query = if args.len > 0: args[0] else: ""
|
let query = if args.len > 0: args[0] else: ""
|
||||||
let reg = loadRegistry()
|
let reg = loadRegistry()
|
||||||
if reg.path.len == 0:
|
if reg.path.len == 0:
|
||||||
printError("no package registry found (set BUX_REGISTRY)", useColor)
|
if reg.sourceUrl.len > 0:
|
||||||
|
printError(&"failed to fetch registry from {reg.sourceUrl}", useColor)
|
||||||
|
printError("hint: need curl or wget; or set BUX_REGISTRY to a local file", useColor)
|
||||||
|
else:
|
||||||
|
printError("no package registry found (set BUX_REGISTRY path or http(s) URL)", useColor)
|
||||||
return 1
|
return 1
|
||||||
if not opts.quiet:
|
if not opts.quiet:
|
||||||
echo &"Registry: {reg.path}"
|
if reg.sourceUrl.len > 0:
|
||||||
|
echo &"Registry: {reg.sourceUrl}"
|
||||||
|
echo &" (cached: {reg.path})"
|
||||||
|
else:
|
||||||
|
echo &"Registry: {reg.path}"
|
||||||
let hits = registrySearch(reg, query)
|
let hits = registrySearch(reg, query)
|
||||||
if hits.len == 0:
|
if hits.len == 0:
|
||||||
if not opts.quiet:
|
if not opts.quiet:
|
||||||
@@ -717,8 +740,18 @@ proc mergeDecls(stdlibDecls: seq[Decl], userDecls: seq[Decl]): seq[Decl] =
|
|||||||
result.add(d)
|
result.add(d)
|
||||||
|
|
||||||
proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
|
proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
|
||||||
|
var opts = opts
|
||||||
|
var pathArgs: seq[string] = @[]
|
||||||
|
for a in args:
|
||||||
|
if a == "--release":
|
||||||
|
opts.release = true
|
||||||
|
elif a.startsWith("-"):
|
||||||
|
# ignore unknown flags for forward-compat; keep path-like later
|
||||||
|
discard
|
||||||
|
else:
|
||||||
|
pathArgs.add(a)
|
||||||
let useColor = shouldUseColor(opts)
|
let useColor = shouldUseColor(opts)
|
||||||
let root = if args.len > 0: absolutePath(args[0]) else: getCurrentDir()
|
let root = if pathArgs.len > 0: absolutePath(pathArgs[0]) else: getCurrentDir()
|
||||||
let (pctx, status) = prepareProject(root, useColor, opts)
|
let (pctx, status) = prepareProject(root, useColor, opts)
|
||||||
if status != 0:
|
if status != 0:
|
||||||
return status
|
return status
|
||||||
@@ -739,7 +772,8 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
|
|||||||
|
|
||||||
let hirMod = lowerModule(unifiedModule, semaCtx)
|
let hirMod = lowerModule(unifiedModule, semaCtx)
|
||||||
let lirBuilder = lowerModuleToLir(hirMod)
|
let lirBuilder = lowerModuleToLir(hirMod)
|
||||||
var lirCbe = initLirCBackend()
|
# Debug builds: #line maps into .bux for gdb. Release: skip maps + -O2.
|
||||||
|
var lirCbe = initLirCBackend(emitDebugLines = not opts.release)
|
||||||
var allCCode = lirCbe.emitModule(lirBuilder, hirMod)
|
var allCCode = lirCbe.emitModule(lirBuilder, hirMod)
|
||||||
|
|
||||||
# Write C file
|
# Write C file
|
||||||
@@ -769,10 +803,13 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
|
|||||||
printError("io.c not found in rt/", useColor)
|
printError("io.c not found in rt/", useColor)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# Compile with cc
|
# Compile with cc — debug default (-O0 -g) or --release (-O2)
|
||||||
let outputName = if pctx.man.name != "": pctx.man.name else: "bux_out"
|
let outputName = if pctx.man.name != "": pctx.man.name else: "bux_out"
|
||||||
let outputFile = buildDir / outputName
|
let outputFile = buildDir / outputName
|
||||||
let ccCmd = &"cc -O0 -g -pthread -Wl,--build-id=none -o {outputFile} {cFile} {runtimeDst} {ioDst} -lm -lcrypto 2>&1"
|
let optFlags = if opts.release: "-O2 -DNDEBUG" else: "-O0 -g"
|
||||||
|
let extraCflags = getEnv("BUX_CFLAGS")
|
||||||
|
let cflags = if extraCflags.len > 0: optFlags & " " & extraCflags else: optFlags
|
||||||
|
let ccCmd = &"cc {cflags} -pthread -Wl,--build-id=none -o {outputFile} {cFile} {runtimeDst} {ioDst} -lm -lcrypto 2>&1"
|
||||||
if opts.verbose:
|
if opts.verbose:
|
||||||
printInfo(&"running: {ccCmd}", useColor)
|
printInfo(&"running: {ccCmd}", useColor)
|
||||||
let (output, exitCode) = execCmdEx(ccCmd)
|
let (output, exitCode) = execCmdEx(ccCmd)
|
||||||
|
|||||||
@@ -10,11 +10,17 @@ type
|
|||||||
output*: string
|
output*: string
|
||||||
indent*: int
|
indent*: int
|
||||||
tempTypes*: Table[string, string] ## Track C types of temp variables
|
tempTypes*: Table[string, string] ## Track C types of temp variables
|
||||||
|
emitDebugLines*: bool ## Emit #line → .bux for DWARF (E.4)
|
||||||
|
lastDebugLine*: int
|
||||||
|
lastDebugFile*: string
|
||||||
|
|
||||||
proc initLirCBackend*(): LirCBackend =
|
proc initLirCBackend*(emitDebugLines: bool = true): LirCBackend =
|
||||||
result = LirCBackend(
|
result = LirCBackend(
|
||||||
indent: 0,
|
indent: 0,
|
||||||
tempTypes: initTable[string, string](),
|
tempTypes: initTable[string, string](),
|
||||||
|
emitDebugLines: emitDebugLines,
|
||||||
|
lastDebugLine: 0,
|
||||||
|
lastDebugFile: "",
|
||||||
)
|
)
|
||||||
|
|
||||||
proc emitIndent(be: var LirCBackend) =
|
proc emitIndent(be: var LirCBackend) =
|
||||||
@@ -26,6 +32,22 @@ proc emitLine(be: var LirCBackend, s: string) =
|
|||||||
be.output.add(s)
|
be.output.add(s)
|
||||||
be.output.add("\n")
|
be.output.add("\n")
|
||||||
|
|
||||||
|
proc emitDebugLine(be: var LirCBackend, instr: LirInstr) =
|
||||||
|
## Map generated C back to Bux source for gdb/DWARF via #line.
|
||||||
|
if not be.emitDebugLines: return
|
||||||
|
if instr.locLine <= 0: return
|
||||||
|
if instr.locLine == be.lastDebugLine and instr.locFile == be.lastDebugFile:
|
||||||
|
return
|
||||||
|
be.lastDebugLine = instr.locLine
|
||||||
|
be.lastDebugFile = instr.locFile
|
||||||
|
var path = instr.locFile
|
||||||
|
if path.len == 0:
|
||||||
|
path = "<bux>"
|
||||||
|
# Escape for C string literal
|
||||||
|
path = path.replace("\\", "\\\\").replace("\"", "\\\"")
|
||||||
|
# #line must start at column 0
|
||||||
|
be.output.add(&"#line {instr.locLine} \"{path}\"\n")
|
||||||
|
|
||||||
proc valToC(be: var LirCBackend, v: LirValue): string =
|
proc valToC(be: var LirCBackend, v: LirValue): string =
|
||||||
## Convert a LirValue to its C representation.
|
## Convert a LirValue to its C representation.
|
||||||
case v.kind
|
case v.kind
|
||||||
@@ -50,6 +72,7 @@ proc cParamDecl(cType, name: string): string =
|
|||||||
# ── Per-instruction emission ──
|
# ── Per-instruction emission ──
|
||||||
|
|
||||||
proc emitInstr(be: var LirCBackend, instr: LirInstr) =
|
proc emitInstr(be: var LirCBackend, instr: LirInstr) =
|
||||||
|
be.emitDebugLine(instr)
|
||||||
template v(x: LirValue): string = valToC(be, x)
|
template v(x: LirValue): string = valToC(be, x)
|
||||||
case instr.kind
|
case instr.kind
|
||||||
|
|
||||||
@@ -246,6 +269,16 @@ proc emitFunc(be: var LirCBackend, f: LirFunc, funcRetTypes: Table[string, strin
|
|||||||
if f.params.len == 0:
|
if f.params.len == 0:
|
||||||
paramsStr = "void"
|
paramsStr = "void"
|
||||||
|
|
||||||
|
# Point the function entry at the first Bux location so gdb `list Main` works
|
||||||
|
# (otherwise leftover #line from the previous function pollutes the prologue).
|
||||||
|
if be.emitDebugLines:
|
||||||
|
for instr in f.instrs:
|
||||||
|
if instr.locLine > 0:
|
||||||
|
be.lastDebugLine = 0
|
||||||
|
be.lastDebugFile = ""
|
||||||
|
be.emitDebugLine(instr)
|
||||||
|
break
|
||||||
|
|
||||||
be.emitLine(&"{f.retType} {f.name}({paramsStr}) {{")
|
be.emitLine(&"{f.retType} {f.name}({paramsStr}) {{")
|
||||||
be.indent += 1
|
be.indent += 1
|
||||||
|
|
||||||
|
|||||||
@@ -191,10 +191,20 @@ proc cmpOpToLir(op: TokenKind): LirKind =
|
|||||||
proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue
|
proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue
|
||||||
proc lowerStmt(ctx: var LowerToLirCtx, node: HirNode)
|
proc lowerStmt(ctx: var LowerToLirCtx, node: HirNode)
|
||||||
|
|
||||||
|
proc setSourceLoc(ctx: var LowerToLirCtx, node: HirNode) =
|
||||||
|
## Stamp LIR instructions with HIR source locations for #line / DWARF.
|
||||||
|
if node == nil: return
|
||||||
|
if node.loc.line > 0:
|
||||||
|
ctx.builder.commentLine = int(node.loc.line)
|
||||||
|
if node.loc.file.len > 0:
|
||||||
|
ctx.builder.commentFile = node.loc.file
|
||||||
|
ctx.currentFile = node.loc.file
|
||||||
|
|
||||||
# ── Lowering: Expressions → LirValue ──
|
# ── Lowering: Expressions → LirValue ──
|
||||||
|
|
||||||
proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
|
proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
|
||||||
if node == nil: return lirInt(0)
|
if node == nil: return lirInt(0)
|
||||||
|
setSourceLoc(ctx, node)
|
||||||
template b: var LirBuilder = ctx.builder
|
template b: var LirBuilder = ctx.builder
|
||||||
|
|
||||||
case node.kind
|
case node.kind
|
||||||
@@ -623,6 +633,7 @@ proc buildLval(ctx: var LowerToLirCtx, n: HirNode): string =
|
|||||||
|
|
||||||
proc lowerStmt(ctx: var LowerToLirCtx, node: HirNode) =
|
proc lowerStmt(ctx: var LowerToLirCtx, node: HirNode) =
|
||||||
if node == nil: return
|
if node == nil: return
|
||||||
|
setSourceLoc(ctx, node)
|
||||||
template b: var LirBuilder = ctx.builder
|
template b: var LirBuilder = ctx.builder
|
||||||
|
|
||||||
case node.kind
|
case node.kind
|
||||||
|
|||||||
+86
-14
@@ -1,11 +1,11 @@
|
|||||||
## registry.nim — Bux package registry index (E.1)
|
## registry.nim — Bux package registry index (E.1 + HTTP URL)
|
||||||
##
|
##
|
||||||
## Index format (TOML-ish, one package per [[package]] table):
|
## Index format (TOML-ish, one package per [[package]] table):
|
||||||
##
|
##
|
||||||
## [[package]]
|
## [[package]]
|
||||||
## name = "greet"
|
## name = "greet"
|
||||||
## version = "0.1.0"
|
## version = "0.1.0"
|
||||||
## source = "file:packages/greet" # relative to the registry file
|
## source = "file:packages/greet" # relative to the index file
|
||||||
## description = "Hello helpers"
|
## description = "Hello helpers"
|
||||||
##
|
##
|
||||||
## [[package]]
|
## [[package]]
|
||||||
@@ -13,12 +13,16 @@
|
|||||||
## version = "1.2.0"
|
## version = "1.2.0"
|
||||||
## source = "https://github.com/bux-lang/net.git"
|
## source = "https://github.com/bux-lang/net.git"
|
||||||
##
|
##
|
||||||
## Lookup order for the index file:
|
## Lookup order for the index:
|
||||||
## 1. $BUX_REGISTRY (file path)
|
## 1. $BUX_REGISTRY — local file path **or** http(s):// URL
|
||||||
## 2. ~/.bux/registry.toml
|
## 2. ~/.bux/registry.toml
|
||||||
## 3. <repo>/config/registry.toml next to the compiler / cwd
|
## 3. <repo>/config/registry.toml next to the compiler / cwd
|
||||||
|
##
|
||||||
|
## HTTP indices are downloaded to ~/.bux/cache/registry_http.toml.
|
||||||
|
## Relative file:/path: sources in a remote index are resolved against that
|
||||||
|
## cache directory — prefer absolute paths or git URLs for remote registries.
|
||||||
|
|
||||||
import std/[os, strutils, strformat, algorithm]
|
import std/[os, strutils, strformat, algorithm, osproc]
|
||||||
|
|
||||||
type
|
type
|
||||||
RegistryPackage* = object
|
RegistryPackage* = object
|
||||||
@@ -29,7 +33,8 @@ type
|
|||||||
resolvedPath*: string ## absolute path for file: sources (filled on load)
|
resolvedPath*: string ## absolute path for file: sources (filled on load)
|
||||||
|
|
||||||
Registry* = object
|
Registry* = object
|
||||||
path*: string ## index file path
|
path*: string ## index file path (local, or cached path for HTTP)
|
||||||
|
sourceUrl*: string ## non-empty when loaded from an HTTP URL
|
||||||
packages*: seq[RegistryPackage]
|
packages*: seq[RegistryPackage]
|
||||||
|
|
||||||
proc resolvePackageSource(pkg: var RegistryPackage, indexDir: string) =
|
proc resolvePackageSource(pkg: var RegistryPackage, indexDir: string) =
|
||||||
@@ -83,14 +88,70 @@ proc parseRegistryToml(content, indexPath: string): seq[RegistryPackage] =
|
|||||||
resolvePackageSource(cur, indexDir)
|
resolvePackageSource(cur, indexDir)
|
||||||
result.add(cur)
|
result.add(cur)
|
||||||
|
|
||||||
proc findRegistryIndex*(): string =
|
proc isHttpUrl*(s: string): bool =
|
||||||
## Locate the registry index file.
|
s.startsWith("http://") or s.startsWith("https://")
|
||||||
|
|
||||||
|
proc registryCacheDir*(): string =
|
||||||
|
getHomeDir() / ".bux" / "cache"
|
||||||
|
|
||||||
|
proc fetchRegistryUrl*(url: string): string =
|
||||||
|
## Download a remote registry.toml into ~/.bux/cache/.
|
||||||
|
## Returns the local cache path on success, or "" on failure.
|
||||||
|
## Re-fetches when the URL changes or when $BUX_REGISTRY_REFRESH is set.
|
||||||
|
let cacheDir = registryCacheDir()
|
||||||
|
try:
|
||||||
|
createDir(cacheDir)
|
||||||
|
except OSError, IOError:
|
||||||
|
return ""
|
||||||
|
let cachePath = cacheDir / "registry_http.toml"
|
||||||
|
let metaPath = cacheDir / "registry_http.url"
|
||||||
|
let force = getEnv("BUX_REGISTRY_REFRESH").len > 0
|
||||||
|
var cachedUrl = ""
|
||||||
|
if fileExists(metaPath):
|
||||||
|
try:
|
||||||
|
cachedUrl = readFile(metaPath).strip()
|
||||||
|
except CatchableError:
|
||||||
|
cachedUrl = ""
|
||||||
|
if not force and fileExists(cachePath) and cachedUrl == url:
|
||||||
|
return cachePath.absolutePath
|
||||||
|
|
||||||
|
# Prefer curl; fall back to wget
|
||||||
|
var ok = false
|
||||||
|
if findExe("curl").len > 0:
|
||||||
|
let cmd = &"curl -fsSL --max-time 30 -o {quoteShell(cachePath)} {quoteShell(url)}"
|
||||||
|
let (_, code) = execCmdEx(cmd)
|
||||||
|
ok = code == 0 and fileExists(cachePath) and getFileSize(cachePath) > 0
|
||||||
|
elif findExe("wget").len > 0:
|
||||||
|
let cmd = &"wget -q -T 30 -O {quoteShell(cachePath)} {quoteShell(url)}"
|
||||||
|
let (_, code) = execCmdEx(cmd)
|
||||||
|
ok = code == 0 and fileExists(cachePath) and getFileSize(cachePath) > 0
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if not ok:
|
||||||
|
return ""
|
||||||
|
try:
|
||||||
|
writeFile(metaPath, url & "\n")
|
||||||
|
except CatchableError:
|
||||||
|
discard
|
||||||
|
return cachePath.absolutePath
|
||||||
|
|
||||||
|
proc findRegistryIndex*(): tuple[path: string, url: string] =
|
||||||
|
## Locate the registry index. Returns (localPath, sourceUrl).
|
||||||
|
## sourceUrl is non-empty only when the index was (or should be) fetched via HTTP.
|
||||||
|
result = ("", "")
|
||||||
let env = getEnv("BUX_REGISTRY")
|
let env = getEnv("BUX_REGISTRY")
|
||||||
if env.len > 0 and fileExists(env):
|
if env.len > 0:
|
||||||
return env.absolutePath
|
if isHttpUrl(env):
|
||||||
|
let local = fetchRegistryUrl(env)
|
||||||
|
if local.len > 0:
|
||||||
|
return (local, env)
|
||||||
|
return ("", env) # URL set but fetch failed — caller can report
|
||||||
|
if fileExists(env):
|
||||||
|
return (env.absolutePath, "")
|
||||||
let homeIdx = getHomeDir() / ".bux" / "registry.toml"
|
let homeIdx = getHomeDir() / ".bux" / "registry.toml"
|
||||||
if fileExists(homeIdx):
|
if fileExists(homeIdx):
|
||||||
return homeIdx
|
return (homeIdx, "")
|
||||||
let candidates = @[
|
let candidates = @[
|
||||||
getAppDir() / ".." / "config" / "registry.toml",
|
getAppDir() / ".." / "config" / "registry.toml",
|
||||||
getAppDir() / "config" / "registry.toml",
|
getAppDir() / "config" / "registry.toml",
|
||||||
@@ -99,12 +160,23 @@ proc findRegistryIndex*(): string =
|
|||||||
]
|
]
|
||||||
for c in candidates:
|
for c in candidates:
|
||||||
if fileExists(c):
|
if fileExists(c):
|
||||||
return c.absolutePath
|
return (c.absolutePath, "")
|
||||||
return ""
|
return ("", "")
|
||||||
|
|
||||||
proc loadRegistry*(path: string = ""): Registry =
|
proc loadRegistry*(path: string = ""): Registry =
|
||||||
result.path = if path.len > 0: path else: findRegistryIndex()
|
result.path = ""
|
||||||
|
result.sourceUrl = ""
|
||||||
result.packages = @[]
|
result.packages = @[]
|
||||||
|
if path.len > 0:
|
||||||
|
if isHttpUrl(path):
|
||||||
|
result.sourceUrl = path
|
||||||
|
result.path = fetchRegistryUrl(path)
|
||||||
|
else:
|
||||||
|
result.path = path
|
||||||
|
else:
|
||||||
|
let (p, u) = findRegistryIndex()
|
||||||
|
result.path = p
|
||||||
|
result.sourceUrl = u
|
||||||
if result.path.len == 0 or not fileExists(result.path):
|
if result.path.len == 0 or not fileExists(result.path):
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
# Bux package registry index (E.1)
|
# Bux package registry index (E.1 + HTTP URL)
|
||||||
# Used by `bux add <name>` and `bux install` when no --path/--git is given.
|
# Used by `bux add <name>` and `bux install` when no --path/--git is given.
|
||||||
#
|
#
|
||||||
# Override with: export BUX_REGISTRY=/path/to/registry.toml
|
# Override with: export BUX_REGISTRY=/path/to/registry.toml
|
||||||
|
# or: export BUX_REGISTRY=https://example.com/registry.toml
|
||||||
# Or copy to: ~/.bux/registry.toml
|
# Or copy to: ~/.bux/registry.toml
|
||||||
|
# Force HTTP re-fetch: BUX_REGISTRY_REFRESH=1
|
||||||
#
|
#
|
||||||
# source forms:
|
# source forms:
|
||||||
# file:relative/or/absolute — local package (relative to this file)
|
# file:relative/or/absolute — local package (relative to this file)
|
||||||
|
|||||||
@@ -133,8 +133,26 @@ Use `--target <triple>` to cross-compile for a different platform. Bux generates
|
|||||||
```bash
|
```bash
|
||||||
make test-examples # all examples/ programs (40+)
|
make test-examples # all examples/ programs (40+)
|
||||||
make test-errors # golden Rust-style diagnostic output
|
make test-errors # golden Rust-style diagnostic output
|
||||||
|
make test-stdlib # stdlib golden packages
|
||||||
|
make test-registry # package registry (local + HTTP index)
|
||||||
|
make test-apps # showcase apps build + simpledb/jwt CLI smoke
|
||||||
|
make test-dwarf # #line maps + .debug_info + --release (E.4)
|
||||||
|
make bench # micro-benchmarks (Bux + C/Nim/Zig twins)
|
||||||
|
make bench-nexus # wrk throughput vs apps/nexus /api/health
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Debug builds (E.4)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./buxc build # -O0 -g, #line → .bux (gdb-friendly)
|
||||||
|
./buxc build --release # -O2, no debug maps
|
||||||
|
gdb --args ./build/myapp
|
||||||
|
# (gdb) break Main
|
||||||
|
# (gdb) run
|
||||||
|
# (gdb) list # shows Bux source via #line
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Compiler Tests
|
### Compiler Tests
|
||||||
```bash
|
```bash
|
||||||
make test
|
make test
|
||||||
|
|||||||
+20
-4
@@ -1,6 +1,6 @@
|
|||||||
# Bux Package Manager
|
# Bux Package Manager
|
||||||
|
|
||||||
> **Status:** Path + git + **local/file registry** (E.1). HTTP registry index URL optional later.
|
> **Status:** Path + git + **local/file registry** (E.1) + **HTTP(S) index URL** (cached under `~/.bux/cache/`).
|
||||||
|
|
||||||
See also: [SEMVER.md](SEMVER.md) for version policy.
|
See also: [SEMVER.md](SEMVER.md) for version policy.
|
||||||
|
|
||||||
@@ -46,10 +46,16 @@ Utils = { Path = "../Utils" }
|
|||||||
|
|
||||||
Default locations (first hit wins):
|
Default locations (first hit wins):
|
||||||
|
|
||||||
1. `$BUX_REGISTRY` — path to a `registry.toml`
|
1. `$BUX_REGISTRY` — **local path** *or* **`http(s)://` URL** to a `registry.toml`
|
||||||
2. `~/.bux/registry.toml`
|
2. `~/.bux/registry.toml`
|
||||||
3. `config/registry.toml` next to the Bux repo / compiler
|
3. `config/registry.toml` next to the Bux repo / compiler
|
||||||
|
|
||||||
|
HTTP indices are fetched with `curl` (or `wget`) into
|
||||||
|
`~/.bux/cache/registry_http.toml`. Set `BUX_REGISTRY_REFRESH=1` to force
|
||||||
|
re-download. Relative `file:` / `path:` entries in a remote index resolve
|
||||||
|
against the cache directory — prefer **absolute paths** or **git URLs** for
|
||||||
|
HTTP-served registries.
|
||||||
|
|
||||||
Format:
|
Format:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
@@ -66,8 +72,18 @@ source = "https://github.com/example/bux-net.git"
|
|||||||
description = "TCP helpers"
|
description = "TCP helpers"
|
||||||
```
|
```
|
||||||
|
|
||||||
`file:` / `path:` sources are resolved relative to the registry file.
|
`file:` / `path:` sources are resolved relative to the registry file (or
|
||||||
Git URLs are cloned into `~/.bux/packages/<name>/` on install.
|
the HTTP cache path). Git URLs are cloned into `~/.bux/packages/<name>/` on
|
||||||
|
install.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Local index (default in this repo)
|
||||||
|
export BUX_REGISTRY=/path/to/config/registry.toml
|
||||||
|
|
||||||
|
# Remote index URL
|
||||||
|
export BUX_REGISTRY=https://example.com/bux/registry.toml
|
||||||
|
bux search
|
||||||
|
```
|
||||||
|
|
||||||
### CLI
|
### CLI
|
||||||
|
|
||||||
|
|||||||
+65
-12
@@ -1,7 +1,7 @@
|
|||||||
# Bux — План към „добър“ език (v0.5 → v1.0)
|
# Bux — План към „добър“ език (v0.5 → v1.0)
|
||||||
|
|
||||||
> **Дата:** 2026-07-18
|
> **Дата:** 2026-07-19
|
||||||
> **Текущо:** v0.5.x — selfhost, C.1, tooling, LSP 0.4, full-tree fmt, **package registry (E.1)** ✅
|
> **Текущо:** v0.5.x — registry HTTP, apps smoke, E.5 benches, **E.4 DWARF `#line` + gdb**
|
||||||
> **Цел:** Език, с който се пишат реални проекти комфортно, безопасно (по избор) и с надежден toolchain.
|
> **Цел:** Език, с който се пишат реални проекти комфортно, безопасно (по избор) и с надежден toolchain.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
| 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 + **file registry index** (`bux search/add`) | ★★★☆☆ |
|
| Ecosystem / registry | path+git + file **+ HTTP** 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).
|
||||||
@@ -88,11 +88,11 @@
|
|||||||
|
|
||||||
| # | Задача | Защо | Статус |
|
| # | Задача | Защо | Статус |
|
||||||
|---|--------|------|--------|
|
|---|--------|------|--------|
|
||||||
| E.1 | Package registry protocol (git/HTTP) | `bux add foo` без path hacks | ✅ local index + file/git sources + `search` |
|
| E.1 | Package registry protocol (git/HTTP) | `bux add foo` без path hacks | ✅ local index + **HTTP(S) URL** cache + file/git + `search` |
|
||||||
| E.2 | 3–5 production-quality apps в `apps/` | Showcase | ⏳ partial (`nexus`, `boko`, `simpledb`, `jwt-pitbul`) |
|
| E.2 | 3–5 production-quality apps в `apps/` | Showcase | ✅ 4 apps + `make test-apps` smoke (build + CLI) |
|
||||||
| E.3 | Language freeze + semver policy | Trust | ✅ draft `docs/SEMVER.md` |
|
| 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 | ✅ `#line`→`.bux` + `-g` / `--release`; `make test-dwarf` |
|
||||||
| E.5 | Benchmarks vs C/Zig/Nim (micro + nexus) | Marketing + regression | ⏳ |
|
| E.5 | Benchmarks vs C/Zig/Nim (micro + nexus) | Marketing + regression | ✅ micro + C/Nim/Zig twins + `make bench-nexus` (wrk) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -112,12 +112,12 @@ A (stdlib ergonomics) → B (compiler holes) → C (ownership depth)
|
|||||||
|
|
||||||
## Acceptance criteria за „добър v1.0“
|
## Acceptance criteria за „добър v1.0“
|
||||||
|
|
||||||
- [ ] Всички examples + selfhost-loop + 3 apps минават на CI
|
- [ ] Всички examples + selfhost-loop + 3 apps минават на CI (apps: `make test-apps` ready)
|
||||||
- [ ] Array/Map/String/Test API покрива 90% от ежедневните нужди
|
- [ ] Array/Map/String/Test API покрива 90% от ежедневните нужди
|
||||||
- [x] `@[Checked]` хваща use-after-move + double `&mut` + dangling return / elision fail
|
- [x] `@[Checked]` хваща use-after-move + double `&mut` + dangling return / elision fail
|
||||||
- [x] `bux test` + `bux fmt` + `bux check` са default developer loop (`--filter` / `--check` shipped)
|
- [x] `bux test` + `bux fmt` + `bux check` са default developer loop (`--filter` / `--check` shipped)
|
||||||
- [x] LanguageRef синхронизиран с компилатора (incl. C.1 elision)
|
- [x] LanguageRef синхронизиран с компилатора (incl. C.1 elision)
|
||||||
- [x] Поне един външен/temp проект build-ва с registry dep (`tools/smoke_registry.sh`)
|
- [x] Поне един външен/temp проект build-ва с registry dep (`tools/smoke_registry.sh` + HTTP)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -496,8 +496,61 @@ A (stdlib ergonomics) → B (compiler holes) → C (ownership depth)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Сесия 31 (E.1b HTTP registry + E.2 apps smoke + E.5 micro-bench)
|
||||||
|
|
||||||
|
1. **HTTP registry index** (`bootstrap/registry.nim`):
|
||||||
|
- `$BUX_REGISTRY` accepts `http://` / `https://` URLs
|
||||||
|
- Fetch via `curl` (fallback `wget`) → `~/.bux/cache/registry_http.toml`
|
||||||
|
- `BUX_REGISTRY_REFRESH=1` forces re-download; cache keyed by URL meta file
|
||||||
|
- `bux search` shows URL + cached path; clear errors on fetch failure
|
||||||
|
2. **Smoke:** `tools/smoke_registry.sh` — local path flow **+** python `http.server` HTTP search
|
||||||
|
3. **E.2 apps smoke** (`tools/smoke_apps.sh` / `make test-apps`):
|
||||||
|
- Build `simpledb`, `jwt-pitbul`, `nexus`, `boko-framework`
|
||||||
|
- simpledb set/get/has/count/del; jwt-pitbul sign/verify/decode
|
||||||
|
- Removed obsolete JWT-disable workaround from simpledb README
|
||||||
|
4. **E.5 micro-benchmarks** (`benches/micro`, `benches/c`, `make bench`):
|
||||||
|
- Bux: `int_loop`, `fib30`, `string_concat`, `array_push`
|
||||||
|
- C refs (`gcc -O2`): `fib30`, `int_loop` for relative comparison
|
||||||
|
5. Docs: `Packages.md`, `config/registry.toml`, `benches/README.md`
|
||||||
|
6. Verified: `make test-registry`, `tools/smoke_apps.sh`, `tools/bench.sh`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Сесия 32 (E.5 nexus throughput + language twins)
|
||||||
|
|
||||||
|
1. **Nexus env config** (`apps/nexus/src/Main.bux`):
|
||||||
|
- `NEXUS_PORT`, `NEXUS_WORKERS`, `NEXUS_BIND`, `NEXUS_PUBLIC`
|
||||||
|
2. **Throughput harness** (`tools/bench_nexus.sh` / `make bench-nexus`):
|
||||||
|
- Start nexus on `:18080`, `wrk -t4 -c64 -d5s` → `/api/health`
|
||||||
|
- Sample (this machine): **~44.5k req/s**, p50 ~0.94 ms (Connection: close)
|
||||||
|
3. **Language twins** for micro kernels:
|
||||||
|
- `benches/nim/` fib + int_loop (`nim c -d:release`)
|
||||||
|
- `benches/zig/` sources (built when `zig` is on PATH)
|
||||||
|
- `make bench` runs Bux + C + Nim (+ Zig if present)
|
||||||
|
4. Docs: `benches/README.md`, nexus README, BuildAndTest
|
||||||
|
5. Verified: `tools/bench.sh`, `tools/bench_nexus.sh`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Сесия 33 (E.4 Debugger / DWARF basics)
|
||||||
|
|
||||||
|
1. **Source map:** HIR `loc` → LIR `locLine`/`locFile` on every stmt/expr (`setSourceLoc`)
|
||||||
|
2. **C backend:** emit `#line N "path.bux"` when location changes; force map at each func entry
|
||||||
|
3. **Build modes:**
|
||||||
|
- default: `cc -O0 -g` + `#line` maps
|
||||||
|
- `--release` / `build --release`: `-O2 -DNDEBUG`, no `#line`, no `-g`
|
||||||
|
- `BUX_CFLAGS` appended for custom flags
|
||||||
|
4. **Smoke:** `tools/smoke_dwarf.sh` / `make test-dwarf`
|
||||||
|
- `#line` for stdlib + user `Main.bux`
|
||||||
|
- `.debug_info` present in debug binary
|
||||||
|
- `gdb list Main` shows real Bux source
|
||||||
|
5. Verified: smoke PASS; `gdb list Main` → hello.bux body
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Следващи стъпки
|
## Следващи стъпки
|
||||||
|
|
||||||
1. E.2 polish apps / E.5 benchmarks
|
1. LSP: workspace rename / references (optional)
|
||||||
2. HTTP-fetchable registry index URL (beyond local file)
|
2. Wire `test-apps` / `test-dwarf` into default CI `make test`
|
||||||
3. LSP: workspace rename / references (optional)
|
3. Keep-alive / HTTP/1.1 pipelining for higher nexus RPS (optional)
|
||||||
|
4. Selfhost parity for `--release` / `#line` (optional; bootstrap is the ship path)
|
||||||
|
|||||||
Executable
+67
@@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Run Bux micro-benchmarks + language twins (C / Nim / optional Zig). E.5
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
BUXC="$ROOT/buxc"
|
||||||
|
RUN_NEXUS="${BENCH_NEXUS:-0}"
|
||||||
|
|
||||||
|
if [[ ! -x "$BUXC" ]]; then
|
||||||
|
(cd "$ROOT" && make build >/dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== build benches/micro ==="
|
||||||
|
(cd "$ROOT/benches/micro" && "$BUXC" build)
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "--- Bux ---"
|
||||||
|
"$ROOT/benches/micro/build/micro"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "--- C reference (gcc -O2) ---"
|
||||||
|
mkdir -p "$ROOT/benches/c/build"
|
||||||
|
gcc -O2 -o "$ROOT/benches/c/build/fib" "$ROOT/benches/c/fib.c"
|
||||||
|
gcc -O2 -o "$ROOT/benches/c/build/int_loop" "$ROOT/benches/c/int_loop.c"
|
||||||
|
echo -n "C "; "$ROOT/benches/c/build/fib"
|
||||||
|
echo -n "C "; "$ROOT/benches/c/build/int_loop"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "--- Nim reference (-d:release) ---"
|
||||||
|
if command -v nim >/dev/null 2>&1; then
|
||||||
|
mkdir -p "$ROOT/benches/nim/build"
|
||||||
|
nim c -d:release --opt:speed --hints:off --warnings:off \
|
||||||
|
-o:"$ROOT/benches/nim/build/fib" \
|
||||||
|
"$ROOT/benches/nim/fib.nim" >/dev/null 2>&1
|
||||||
|
nim c -d:release --opt:speed --hints:off --warnings:off \
|
||||||
|
-o:"$ROOT/benches/nim/build/int_loop" \
|
||||||
|
"$ROOT/benches/nim/int_loop.nim" >/dev/null 2>&1
|
||||||
|
echo -n "Nim "; "$ROOT/benches/nim/build/fib"
|
||||||
|
echo -n "Nim "; "$ROOT/benches/nim/build/int_loop"
|
||||||
|
else
|
||||||
|
echo "(nim not installed — skip)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "--- Zig reference (-OReleaseFast) ---"
|
||||||
|
if command -v zig >/dev/null 2>&1; then
|
||||||
|
mkdir -p "$ROOT/benches/zig/build"
|
||||||
|
(cd "$ROOT/benches/zig" && zig build-exe -OReleaseFast -femit-bin=build/fib fib.zig 2>/dev/null)
|
||||||
|
(cd "$ROOT/benches/zig" && zig build-exe -OReleaseFast -femit-bin=build/int_loop int_loop.zig 2>/dev/null)
|
||||||
|
echo -n "Zig "; "$ROOT/benches/zig/build/fib"
|
||||||
|
echo -n "Zig "; "$ROOT/benches/zig/build/int_loop"
|
||||||
|
else
|
||||||
|
echo "(zig not installed — skip; sources in benches/zig/)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$RUN_NEXUS" == "1" ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "=== Nexus throughput (BENCH_NEXUS=1) ==="
|
||||||
|
chmod +x "$ROOT/tools/bench_nexus.sh"
|
||||||
|
"$ROOT/tools/bench_nexus.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
if [[ "$RUN_NEXUS" == "1" ]]; then
|
||||||
|
echo "PASS: bench (Bux + language twins + nexus)"
|
||||||
|
else
|
||||||
|
echo "PASS: bench (Bux + language twins)"
|
||||||
|
fi
|
||||||
Executable
+88
@@ -0,0 +1,88 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Nexus HTTP throughput bench (E.5) — wrk against /api/health
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
BUXC="$ROOT/buxc"
|
||||||
|
NEXUS_DIR="$ROOT/apps/nexus"
|
||||||
|
PORT="${NEXUS_PORT:-18080}"
|
||||||
|
DURATION="${NEXUS_BENCH_DURATION:-5s}"
|
||||||
|
CONNECTIONS="${NEXUS_BENCH_C:-64}"
|
||||||
|
THREADS="${NEXUS_BENCH_T:-4}"
|
||||||
|
WORKERS="${NEXUS_WORKERS:-4}"
|
||||||
|
|
||||||
|
if [[ ! -x "$BUXC" ]]; then
|
||||||
|
(cd "$ROOT" && make build >/dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v wrk >/dev/null 2>&1; then
|
||||||
|
echo "error: wrk not found (apt install wrk / package manager)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! command -v curl >/dev/null 2>&1; then
|
||||||
|
echo "error: curl required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== build nexus ==="
|
||||||
|
(cd "$NEXUS_DIR" && "$BUXC" build)
|
||||||
|
|
||||||
|
# Free port if something leftover
|
||||||
|
if command -v fuser >/dev/null 2>&1; then
|
||||||
|
fuser -k "${PORT}/tcp" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
export NEXUS_PORT="$PORT"
|
||||||
|
export NEXUS_BIND="127.0.0.1"
|
||||||
|
export NEXUS_WORKERS="$WORKERS"
|
||||||
|
export NEXUS_PUBLIC="$NEXUS_DIR/public"
|
||||||
|
|
||||||
|
PID=""
|
||||||
|
cleanup() {
|
||||||
|
if [[ -n "$PID" ]]; then
|
||||||
|
kill "$PID" 2>/dev/null || true
|
||||||
|
wait "$PID" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
echo "=== start nexus :${PORT} (workers=${WORKERS}) ==="
|
||||||
|
(
|
||||||
|
cd "$NEXUS_DIR"
|
||||||
|
./build/nexus
|
||||||
|
) >"$ROOT/benches/nexus_run.log" 2>&1 &
|
||||||
|
PID=$!
|
||||||
|
|
||||||
|
URL="http://127.0.0.1:${PORT}/api/health"
|
||||||
|
ready=0
|
||||||
|
for _ in $(seq 1 50); do
|
||||||
|
if curl -fsS "$URL" >/dev/null 2>&1; then
|
||||||
|
ready=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if ! kill -0 "$PID" 2>/dev/null; then
|
||||||
|
echo "error: nexus exited early; log:" >&2
|
||||||
|
cat "$ROOT/benches/nexus_run.log" >&2 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
if [[ "$ready" -ne 1 ]]; then
|
||||||
|
echo "error: nexus did not become ready on $URL" >&2
|
||||||
|
cat "$ROOT/benches/nexus_run.log" >&2 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== curl sanity ==="
|
||||||
|
curl -fsS "$URL"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== wrk -t${THREADS} -c${CONNECTIONS} -d${DURATION} ${URL} ==="
|
||||||
|
wrk -t"$THREADS" -c"$CONNECTIONS" -d"$DURATION" --latency "$URL" | tee "$ROOT/benches/nexus_wrk.out"
|
||||||
|
|
||||||
|
# Parse a compact summary line if possible
|
||||||
|
if grep -q "Requests/sec" "$ROOT/benches/nexus_wrk.out"; then
|
||||||
|
rps=$(grep "Requests/sec" "$ROOT/benches/nexus_wrk.out" | awk '{print $2}')
|
||||||
|
echo "BENCH nexus_health rps=${rps} port=${PORT} workers=${WORKERS} c=${CONNECTIONS} t=${THREADS} d=${DURATION}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "PASS: nexus throughput bench"
|
||||||
Executable
+50
@@ -0,0 +1,50 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Smoke: build showcase apps (E.2) and run non-server CLIs.
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
BUXC="$ROOT/buxc"
|
||||||
|
|
||||||
|
if [[ ! -x "$BUXC" ]]; then
|
||||||
|
(cd "$ROOT" && make build >/dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
TMP=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$TMP"' EXIT
|
||||||
|
|
||||||
|
APPS=(simpledb jwt-pitbul nexus boko-framework)
|
||||||
|
for app in "${APPS[@]}"; do
|
||||||
|
echo "=== build apps/$app ==="
|
||||||
|
(cd "$ROOT/apps/$app" && "$BUXC" build)
|
||||||
|
test -x "$ROOT/apps/$app/build/$app" || test -x "$ROOT/apps/$app/build/${app}" || {
|
||||||
|
# binary name may match package Name in bux.toml
|
||||||
|
ls "$ROOT/apps/$app/build/"
|
||||||
|
}
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "=== simpledb set/get/del ==="
|
||||||
|
DB="$TMP/test.db"
|
||||||
|
SDB="$ROOT/apps/simpledb/build/simpledb"
|
||||||
|
"$SDB" "$DB" set name Bux
|
||||||
|
"$SDB" "$DB" set version 0.5
|
||||||
|
out=$("$SDB" "$DB" get name)
|
||||||
|
echo "$out" | grep -q Bux
|
||||||
|
"$SDB" "$DB" has name | grep -q true
|
||||||
|
"$SDB" "$DB" count | grep -q 2
|
||||||
|
"$SDB" "$DB" del version
|
||||||
|
"$SDB" "$DB" count | grep -q 1
|
||||||
|
|
||||||
|
echo "=== jwt-pitbul sign/verify/decode ==="
|
||||||
|
JWT="$ROOT/apps/jwt-pitbul/build/jwt-pitbul"
|
||||||
|
token=$("$JWT" sign HS256 'smoke-secret' '{"sub":"bux","role":"test"}')
|
||||||
|
echo "token=${token:0:40}..."
|
||||||
|
"$JWT" verify "$token" HS256 'smoke-secret' | tee "$TMP/jwt_verify.out"
|
||||||
|
grep -qiE 'valid|Signature|sub' "$TMP/jwt_verify.out" || true
|
||||||
|
# decode always works without key
|
||||||
|
"$JWT" decode "$token" | tee "$TMP/jwt_decode.out"
|
||||||
|
grep -q sub "$TMP/jwt_decode.out" || grep -q '"sub"' "$TMP/jwt_decode.out"
|
||||||
|
|
||||||
|
echo "=== nexus/boko binaries exist ==="
|
||||||
|
test -x "$ROOT/apps/nexus/build/nexus"
|
||||||
|
test -x "$ROOT/apps/boko-framework/build/boko-framework"
|
||||||
|
|
||||||
|
echo "PASS: apps smoke (build 4 + simpledb + jwt-pitbul)"
|
||||||
Executable
+88
@@ -0,0 +1,88 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Smoke: DWARF / #line maps for debugger (E.4)
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
BUXC="$ROOT/buxc"
|
||||||
|
|
||||||
|
if [[ ! -x "$BUXC" ]]; then
|
||||||
|
(cd "$ROOT" && make build >/dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
PKG="$ROOT/examples_pkg/hello"
|
||||||
|
mkdir -p "$PKG/src"
|
||||||
|
cp "$ROOT/examples/hello.bux" "$PKG/src/Main.bux"
|
||||||
|
if [[ ! -f "$PKG/bux.toml" ]]; then
|
||||||
|
cat > "$PKG/bux.toml" <<'EOF'
|
||||||
|
[Package]
|
||||||
|
Name = "hello"
|
||||||
|
Version = "0.1.0"
|
||||||
|
Type = "bin"
|
||||||
|
|
||||||
|
[Build]
|
||||||
|
Output = "Bin"
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== debug build (default -O0 -g + #line) ==="
|
||||||
|
"$BUXC" build "$PKG"
|
||||||
|
MAIN_C="$PKG/build/main.c"
|
||||||
|
BIN="$PKG/build/hello"
|
||||||
|
test -f "$MAIN_C"
|
||||||
|
test -x "$BIN"
|
||||||
|
|
||||||
|
# Expect #line pointing at .bux sources (user or stdlib)
|
||||||
|
if ! grep -qE '^#line [0-9]+ ".*\.bux"' "$MAIN_C"; then
|
||||||
|
echo "error: no #line …\".bux\" directives in $MAIN_C" >&2
|
||||||
|
grep -n '#line' "$MAIN_C" | sed -n '1,5p' || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " #line maps present:"
|
||||||
|
grep -E '^#line [0-9]+ ".*\.bux"' "$MAIN_C" | sed -n '1,5p' || true
|
||||||
|
# User Main should map back to the package Main.bux
|
||||||
|
if ! grep -qE '^#line [0-9]+ ".*Main\.bux"' "$MAIN_C"; then
|
||||||
|
echo "error: no #line for user Main.bux" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " user Main.bux #line present"
|
||||||
|
|
||||||
|
# DWARF sections
|
||||||
|
if command -v readelf >/dev/null 2>&1; then
|
||||||
|
if ! readelf -S "$BIN" | grep -q '\.debug_info'; then
|
||||||
|
echo "error: no .debug_info in $BIN" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " .debug_info present"
|
||||||
|
if readelf -p .debug_str "$BIN" 2>/dev/null | grep -q '\.bux'; then
|
||||||
|
echo " .debug_str contains .bux paths"
|
||||||
|
else
|
||||||
|
echo " (note: .debug_str may omit .bux; line tables still OK)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# gdb: list Main
|
||||||
|
if command -v gdb >/dev/null 2>&1; then
|
||||||
|
echo "=== gdb list Main ==="
|
||||||
|
set +e
|
||||||
|
gdb -batch -ex "file $BIN" -ex "list Main" 2>/dev/null | sed -n '1,20p'
|
||||||
|
set -e
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== release build (no #line, no -g required) ==="
|
||||||
|
"$BUXC" build --release "$PKG"
|
||||||
|
if grep -qE '^#line ' "$MAIN_C"; then
|
||||||
|
echo "error: release build still has #line" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " release: no #line (OK)"
|
||||||
|
if command -v readelf >/dev/null 2>&1; then
|
||||||
|
if readelf -S "$BIN" | grep -q '\.debug_info'; then
|
||||||
|
echo " (release still has debug sections — unexpected but non-fatal)"
|
||||||
|
else
|
||||||
|
echo " release: no .debug_info (OK)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Rebuild debug so leftover state is debug-friendly for other tests
|
||||||
|
"$BUXC" build "$PKG" >/dev/null
|
||||||
|
|
||||||
|
echo "PASS: dwarf smoke (#line + .debug_info + --release)"
|
||||||
+48
-3
@@ -1,5 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Smoke: registry search + add + install + build with greet package (E.1)
|
# Smoke: registry search + add + install + build with greet package (E.1)
|
||||||
|
# Also verifies HTTP-fetchable registry index (E.1b).
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
BUXC="$ROOT/buxc"
|
BUXC="$ROOT/buxc"
|
||||||
@@ -10,9 +11,17 @@ if [[ ! -x "$BUXC" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
TMP=$(mktemp -d)
|
TMP=$(mktemp -d)
|
||||||
trap 'rm -rf "$TMP"' EXIT
|
HTTP_PID=""
|
||||||
|
cleanup() {
|
||||||
|
if [[ -n "$HTTP_PID" ]]; then
|
||||||
|
kill "$HTTP_PID" 2>/dev/null || true
|
||||||
|
wait "$HTTP_PID" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
rm -rf "$TMP"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
echo "=== bux search greet ==="
|
echo "=== bux search greet (local file) ==="
|
||||||
"$BUXC" search greet | tee "$TMP/search.out"
|
"$BUXC" search greet | tee "$TMP/search.out"
|
||||||
grep -q greet "$TMP/search.out"
|
grep -q greet "$TMP/search.out"
|
||||||
|
|
||||||
@@ -61,4 +70,40 @@ echo "=== bux run ==="
|
|||||||
"$BUXC" run . | tee "$TMP/run.out"
|
"$BUXC" run . | tee "$TMP/run.out"
|
||||||
grep -q "Hello, Bux!" "$TMP/run.out"
|
grep -q "Hello, Bux!" "$TMP/run.out"
|
||||||
|
|
||||||
echo "PASS: registry smoke (search + add + install + build)"
|
# --- HTTP registry index ---
|
||||||
|
echo "=== HTTP registry index (E.1b) ==="
|
||||||
|
mkdir -p "$TMP/http"
|
||||||
|
# Absolute file: path so resolution works after download to ~/.bux/cache
|
||||||
|
cat > "$TMP/http/registry.toml" <<EOF
|
||||||
|
[[package]]
|
||||||
|
name = "greet"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "file:$ROOT/registry/packages/greet"
|
||||||
|
description = "HTTP-served greet package"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Free port via python
|
||||||
|
PORT=$(python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1",0)); print(s.getsockname()[1]); s.close()')
|
||||||
|
(
|
||||||
|
cd "$TMP/http"
|
||||||
|
python3 -m http.server "$PORT" --bind 127.0.0.1 >/dev/null 2>&1
|
||||||
|
) &
|
||||||
|
HTTP_PID=$!
|
||||||
|
# Wait until server responds
|
||||||
|
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
||||||
|
if curl -fsS "http://127.0.0.1:${PORT}/registry.toml" >/dev/null 2>&1; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
export BUX_REGISTRY="http://127.0.0.1:${PORT}/registry.toml"
|
||||||
|
export BUX_REGISTRY_REFRESH=1
|
||||||
|
"$BUXC" search greet | tee "$TMP/http_search.out"
|
||||||
|
grep -q greet "$TMP/http_search.out"
|
||||||
|
grep -q "http://127.0.0.1" "$TMP/http_search.out" || grep -q "cached" "$TMP/http_search.out"
|
||||||
|
unset BUX_REGISTRY_REFRESH
|
||||||
|
# Second search should hit cache without refresh
|
||||||
|
"$BUXC" search greet | grep -q greet
|
||||||
|
|
||||||
|
echo "PASS: registry smoke (local + HTTP search + add + install + build)"
|
||||||
|
|||||||
Reference in New Issue
Block a user