From a23860be3e0513f700d66c4479b6b41b0ea56334 Mon Sep 17 00:00:00 2001 From: dimgigov Date: Mon, 27 Jul 2026 21:49:12 +0300 Subject: [PATCH] release: Bux v1.0.0 language freeze Bump compiler banners and package version to 1.0.0, activate SEMVER policy, and add RELEASE_v1.0.0 notes. Fix closure auto-Drop leaking outer Array drops into nested capture bodies (iter_hof). Fmt-clean examples/src for CI. --- README.md | 3 +- apps/nexus/src/Main.bux | 2 +- bootstrap/cli.nim | 2 +- bootstrap/hir_lower.nim | 21 +- bux.toml | 2 +- docs/QUALITY_PLAN.md | 36 ++- docs/RELEASE_v1.0.0.md | 55 +++++ docs/ROADMAP.md | 4 +- docs/SEMVER.md | 33 +-- examples/http_health.bux | 10 +- examples/macro_nested.bux | 4 +- src/cli.bux | 24 +- src/macroexpand.bux | 12 +- src/manifest.bux | 2 +- src/parser.bux | 8 +- src/registry.bux | 474 +++++++++++++++++++------------------- 16 files changed, 384 insertions(+), 308 deletions(-) create mode 100644 docs/RELEASE_v1.0.0.md diff --git a/README.md b/README.md index 8496a28..9b85a6f 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,14 @@ ![Bux Language](bux-lang-01.jpeg) -> **Status:** v0.5.x — Bootstrap (`buxc`, Nim) and self-hosted (`buxc2`, Bux) both compile `.bux` → C → native binary. +> **Status:** **v1.0.0** — language freeze. Bootstrap (`buxc`, Nim) and self-hosted (`buxc2`, Bux) both compile `.bux` → C → native binary. > **Selfhost loop:** deterministic C codegen + ELF verified. > **Gradual Ownership:** `@[Checked]` borrow checker, `@[Release]` zero-cost mode, `borrow &mut` expressions. > **Closures:** multi-instance capturing closures via fat function pointers (`BuxFn { code, env }`) in both compilers. > **Tuples:** `(T, U)` types and `.0`/`.1` field access (bootstrap + selfhost). > **Green Threads:** M:N scheduler with channels (Go-style goroutines without GC). > **Examples:** 40+ programs pass (`make test-examples`). Apps: `boko-framework`, `jwt-pitbul`, `nexus`, `simpledb`. +> **Semver:** after 1.0, breaking changes require MAJOR (`docs/SEMVER.md`). Bux is a fast, compiled, strongly-typed systems programming language. Features a C backend for native code generation, raw multi-line strings, gradual ownership (opt-in borrow checking), multi-instance closures, async/await, generics, algebraic enums, and a package manager. diff --git a/apps/nexus/src/Main.bux b/apps/nexus/src/Main.bux index 9ecf1ca..8202da8 100644 --- a/apps/nexus/src/Main.bux +++ b/apps/nexus/src/Main.bux @@ -87,7 +87,7 @@ module Main { let tlsEnv: String = Os_GetEnv("NEXUS_TLS"); if String_Len(tlsEnv) > 0 { if String_Eq(tlsEnv, "1") || String_Eq(tlsEnv, "true") || String_Eq(tlsEnv, "on") || - String_Eq(tlsEnv, "https") { + String_Eq(tlsEnv, "https") { config.tlsEnabled = true; } } diff --git a/bootstrap/cli.nim b/bootstrap/cli.nim index c290dd3..79e28d0 100644 --- a/bootstrap/cli.nim +++ b/bootstrap/cli.nim @@ -1345,7 +1345,7 @@ proc cmdDoc*(args: seq[string], opts: GlobalOptions): int = return 0 proc cmdVersion*(args: seq[string], opts: GlobalOptions): int = - echo "bux 0.1.0 (bootstrap)" + echo "bux 1.0.0 (bootstrap)" return 0 proc runCli*(args: seq[string]): int = diff --git a/bootstrap/hir_lower.nim b/bootstrap/hir_lower.nim index bb34d4f..3a24f78 100644 --- a/bootstrap/hir_lower.nim +++ b/bootstrap/hir_lower.nim @@ -2734,18 +2734,37 @@ proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc = f.retType = ctx.resolveTypeExpr(expr.exprClosureReturnType) else: f.retType = makeVoid() - # Body with closure rewriting + # Body with closure rewriting — isolate Drop/defer stack like lowerFunc. + # Nested closure lowering previously kept the outer function's deferStmts, + # so `return` inside a capture emitted Array_Drop for outer locals (session 88). let savedDepth = ctx.closureDepth let savedExpr = ctx.currentClosureExpr let savedEnv = ctx.envInstanceName + let oldDefers = ctx.deferStmts + let oldMovedOut = ctx.movedOutLocals + let oldPartialMoved = ctx.partialMovedFields + ctx.deferStmts = @[] + ctx.movedOutLocals = initHashSet[string]() + ctx.partialMovedFields = initTable[string, HashSet[string]]() ctx.closureDepth = ctx.closureDepth + 1 ctx.currentClosureExpr = expr ctx.envInstanceName = f.envInstanceName if expr.exprClosureBody != nil: f.body = ctx.lowerBlock(expr.exprClosureBody) + # Emit pending auto-drops for locals allocated *inside* the closure body only + if ctx.deferStmts.len > 0 and f.body != nil and f.body.kind == hBlock: + var lastIsReturn = false + if f.body.blockStmts.len > 0 and f.body.blockStmts[^1].kind == hReturn: + lastIsReturn = true + if not lastIsReturn: + for i in countdown(ctx.deferStmts.len - 1, 0): + ctx.emitDropOrPartial(f.body.blockStmts, ctx.deferStmts[i], "") ctx.closureDepth = savedDepth ctx.currentClosureExpr = savedExpr ctx.envInstanceName = savedEnv + ctx.deferStmts = oldDefers + ctx.movedOutLocals = oldMovedOut + ctx.partialMovedFields = oldPartialMoved ctx.extraFuncs.add(f) return f diff --git a/bux.toml b/bux.toml index 1418e5e..57098f5 100644 --- a/bux.toml +++ b/bux.toml @@ -1,6 +1,6 @@ [Package] Name = "buxc" -Version = "0.3.0" +Version = "1.0.0" Type = "bin" [Build] diff --git a/docs/QUALITY_PLAN.md b/docs/QUALITY_PLAN.md index a2a585f..316b832 100644 --- a/docs/QUALITY_PLAN.md +++ b/docs/QUALITY_PLAN.md @@ -1,10 +1,11 @@ -# Bux — План към „добър“ език (v0.5 → v1.0) +# Bux — План към „добър“ език (v0.5 → **v1.0.0** ✅) -> **Дата:** 2026-07-23 -> **Текущо:** v0.5.x — `:type` macros + Array_Reverse (session 87) +> **Дата:** 2026-07-27 +> **Текущо:** **v1.0.0** — language freeze (session 88 / release) > **Цел:** Език, с който се пишат реални проекти комфортно, безопасно (по избор) и с надежден toolchain. > **Платформен фокус:** **Linux** (primary) · **cloud-native** (servers, containers, HTTP) · **embedded** (cross, freestanding-ish, CTFE). -> **Не-цел:** MS Windows като product platform (исторически CI/hello smoke остават; няма roadmap investment). +> **Не-цел:** MS Windows като product platform (исторически CI/hello smoke остават; няма roadmap investment). +> **Release notes:** `docs/RELEASE_v1.0.0.md` · **Semver:** `docs/SEMVER.md` (active). --- @@ -113,7 +114,7 @@ A (stdlib ergonomics) → B (compiler holes) → C (ownership depth) --- -## Acceptance criteria за „добър v1.0“ +## Acceptance criteria за „добър v1.0“ — **met; tagged v1.0.0** - [x] Всички examples + apps + selfhost smoke на CI (`make test` via `.github/workflows/ci.yml`); selfhost-loop optional - [x] Array/Map/String/Test API покрива 90% от ежедневните нужди (+ Insert/Remove/Clone/case/GetOr) @@ -121,6 +122,7 @@ A (stdlib ergonomics) → B (compiler holes) → C (ownership depth) - [x] `bux test` + `bux fmt` + `bux check` са default developer loop (`--filter` / `--check` shipped) - [x] LanguageRef синхронизиран с компилатора (incl. C.1 elision) - [x] Поне един външен/temp проект build-ва с registry dep (`tools/smoke_registry.sh` + HTTP) +- [x] Version strings + SEMVER active + `docs/RELEASE_v1.0.0.md` (session 88) --- @@ -1461,22 +1463,18 @@ bootstrap + **buxc2** `macro_tt_raw` (incl. slice) PASS. --- -## Следващи стъпки +## Сесия 88 (v1.0.0 language freeze) -### P0 — Compiler / language +1. Version banners: bootstrap + selfhost CLI → **1.0.0**; root `bux.toml` → 1.0.0 +2. `docs/SEMVER.md` — **Active** (post-1.0 MAJOR = breaking) +3. `docs/RELEASE_v1.0.0.md` — freeze notes + verify commands +4. README / QUALITY_PLAN / ROADMAP status → v1.0.0 +5. Tag `v1.0.0` after `make test` gate -1. ~~… through session 86~~ ✅ -2. ~~**`:type` macro fragments**~~ ✅ session 87 -3. Optional: richer free-form (operators-only tt); generics in `:type` (`Array`) - -### P1 — Linux / cloud-native - -4. ~~(sessions 75–81)~~ ✅ - -### P2 — Embedded / cross - -5. ~~riscv64 smoke + freestanding notes~~ ✅ session 85 -6. Optional: real `runtime_freestanding.c` + Cortex-M qemu — not v1.0 +**Post-1.0 backlog (MINOR, not freeze blockers):** +- Generics in `:type` (`Array`); operators-only tt paste +- `runtime_freestanding.c` + Cortex-M research +- LSP / IDE versioning independent of language MAJOR ### Изрично **не** правим diff --git a/docs/RELEASE_v1.0.0.md b/docs/RELEASE_v1.0.0.md new file mode 100644 index 0000000..b956b0f --- /dev/null +++ b/docs/RELEASE_v1.0.0.md @@ -0,0 +1,55 @@ +# Bux v1.0.0 — Language Freeze + +**Date:** 2026-07-27 +**Tag:** `v1.0.0` + +## What 1.0 means + +- **Language freeze** for public surface: syntax, stdlib APIs, and CLI that + scripts depend on follow `docs/SEMVER.md`. +- **Normative spec:** `docs/LanguageRef.md`. +- **Compilers:** bootstrap (`buxc`, Nim) and selfhost (`buxc2`, Bux) both target + the same language for the shipped feature set. +- **Platform focus:** Linux primary; cloud/containers; cross/static/minimal + runtime. Windows is not a product platform. + +## Shipped surface (summary) + +| Area | Highlights | +|------|------------| +| Frontend | Pratt parser, recovery, macros (`macro!`, fragments, juxta/`tt`/`type`) | +| Types | Generics (mono), tuples, fat `func` ABI, algebraic enums | +| Ownership | `@[Checked]`, `@[Release]`, Drop/RAII, field-move + remaining Drop | +| Concurrency | M:N tasks, channels, async | +| Stdlib | Array/Map/Set/String/Iter HOF, Net/TLS, registry, Test | +| Tooling | `fmt`, `test`, `doc`, `check`, LSP (separate versioning), DWARF `#line` | +| Ecosystem | path/git/HTTP registry, lock + `--locked`, 4 apps, benches | + +## Explicitly *not* 1.0 blockers + +- True freestanding / bare-metal (`runtime_freestanding.c`, Cortex-M) +- Operators-only token paste; full `Array` in `:type` fragments +- Windows product investment +- LLVM / non-C backends + +## Upgrade notes from 0.5.x + +No mandatory source migration for projects that already build on late 0.5.x. +New package scaffolds still default to package version `0.1.0` (package semver, +not language version). + +### Fix included in 1.0.0 + +- **Closure Drop isolation:** lowering a nested closure no longer emits + outer-function `Array_Drop` / auto-Drop on the closure’s return path + (broke `iter_hof` and any capturing HOF over droppable locals). + +## Verify + +```bash +make build +./buxc --version # bux 1.0.0 (bootstrap) +make test # full gate +make selfhost # buxc2 +# optional: make selfhost-loop +``` diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index d46c5c1..d85e1ff 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -1,7 +1,7 @@ # Bux Language Roadmap — New Constructs -> **Updated:** 2026-07-15 | **Status:** In Progress -> Recent: multi-instance closures (fat `BuxFn`), tuples in selfhost, Iter map/filter/fold, Rust-style diagnostics. +> **Updated:** 2026-07-27 | **Status:** ✅ Constructs for v1.0 shipped (language freeze) +> Recent: macros (`tt`/`type`/juxta), stdlib daily APIs, Linux/riscv smoke; see `docs/RELEASE_v1.0.0.md`. This document tracks planned language constructs beyond Phase 8 strategy. diff --git a/docs/SEMVER.md b/docs/SEMVER.md index 06a03d8..17f24f0 100644 --- a/docs/SEMVER.md +++ b/docs/SEMVER.md @@ -1,6 +1,6 @@ # Bux Semantic Versioning Policy -> Status: Draft for v0.x → v1.0 freeze (E.3) +> Status: **Active** as of **v1.0.0** (language freeze) Bux follows [Semantic Versioning 2.0.0](https://semver.org/) with the clarifications below. @@ -19,18 +19,18 @@ MAJOR.MINOR.PATCH[-prerelease] | **MINOR** | Backward-compatible features | | **PATCH** | Backward-compatible bug fixes | -During **0.x** (pre-1.0): +### Before 1.0 (historical 0.x) -- `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. +During **0.x**, MINOR could still introduce breaking changes (documented in +release notes and `MIGRATION_*.sh` when needed). -After **1.0.0** (language freeze): +### From **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. +- Breaking changes require a **MAJOR** bump and a migration guide. +- The **Language Reference** (`docs/LanguageRef.md`) is the normative spec; + compiler bugs that contradict the ref are fixed without a MAJOR bump. +- New diagnostics and stricter `@[Checked]` are allowed in MINOR when + documented; prefer gating with attributes when practical. --- @@ -48,12 +48,14 @@ After **1.0.0** (language freeze): (prefer contextual keywords) - New diagnostics / stricter `@[Checked]` (document; may be gated) - Formatter whitespace-only changes +- New optional CLI flags and commands --- ## Package versions (registry) -Registry packages use the same MAJOR.MINOR.PATCH scheme. +Registry packages use the same MAJOR.MINOR.PATCH scheme (independent of the +compiler version). `bux add foo` / `bux add foo 0.1` resolution: @@ -70,7 +72,8 @@ 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` +2. Update `docs/QUALITY_PLAN.md` / release notes +3. Bump compiler version strings (`bootstrap/cli.nim`, `src/cli.bux`, root `bux.toml`) +4. Run `make test` (includes `fmt-check`, examples, goldens) +5. Run `make selfhost` and preferably `make selfhost-loop` +6. Tag `vMAJOR.MINOR.PATCH` and push the tag diff --git a/examples/http_health.bux b/examples/http_health.bux index 4c03a04..e97fdef 100644 --- a/examples/http_health.bux +++ b/examples/http_health.bux @@ -12,11 +12,11 @@ func HealthBody() -> String { func BuildHttp(body: String) -> String { let n: int = String_Len(body) as int; return String_Concat( - String_Concat( - "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: ", - String_FromInt(n as int64) - ), - String_Concat("\r\nConnection: close\r\n\r\n", body) + String_Concat( + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: ", + String_FromInt(n as int64) + ), + String_Concat("\r\nConnection: close\r\n\r\n", body) ); } diff --git a/examples/macro_nested.bux b/examples/macro_nested.bux index 2e8b2ad..ae341f8 100644 --- a/examples/macro_nested.bux +++ b/examples/macro_nested.bux @@ -28,8 +28,8 @@ macro! double_each_sum { ( $($x:expr),* ) => { var __t: int = 0; $( - $( __t = __t + $x; )* - $( __t = __t + $x; )* + $( __t = __t + $x; )* + $( __t = __t + $x; )* )* __t } diff --git a/src/cli.bux b/src/cli.bux index cdee1ae..8345a6e 100644 --- a/src/cli.bux +++ b/src/cli.bux @@ -59,8 +59,8 @@ module Cli { return "rt/runtime_win.c"; } if String_Eq(env, "minimal") || String_Eq(env, "thin") || - String_Eq(env, "embed") || String_Eq(env, "embedded") || - String_Eq(env, "freestanding") { + String_Eq(env, "embed") || String_Eq(env, "embedded") || + String_Eq(env, "freestanding") { return "rt/runtime_minimal.c"; } if String_Eq(env, "full") || String_Eq(env, "posix") { @@ -141,7 +141,7 @@ module Cli { } func Cli_LinkProgram(cFile: String, outBin: String, projectDir: String, - targetTriple: String, isRelease: bool, isStatic: bool) -> int { + targetTriple: String, isRelease: bool, isStatic: bool) -> int { let relRt: String = Cli_RuntimeRel(isStatic, targetTriple); let rtPath: String = Cli_FindRtFile(projectDir, relRt); let ioPath: String = Cli_FindRtFile(projectDir, "rt/io.c"); @@ -1179,8 +1179,8 @@ func Cli_PackageChecksum(dir: String) -> String { return ""; } let cmd: String = String_Concat( - "find \"", - String_Concat(dir, "\" -name '*.bux' -type f 2>/dev/null | LC_ALL=C sort | xargs cat 2>/dev/null | sha1sum | awk '{print $1}'") + "find \"", + String_Concat(dir, "\" -name '*.bux' -type f 2>/dev/null | LC_ALL=C sort | xargs cat 2>/dev/null | sha1sum | awk '{print $1}'") ); let out: String = bux_process_output(cmd); if out == null as String { return ""; } @@ -1205,7 +1205,7 @@ func Cli_DepResolvedPath(projectDir: String, depName: String, depUrl: String) -> return depUrl; } if String_StartsWith(depUrl, "http://") || String_StartsWith(depUrl, "https://") || - String_EndsWith(depUrl, ".git") { + String_EndsWith(depUrl, ".git") { return bux_path_join(bux_path_join(projectDir, "deps"), depName); } // relative path @@ -1245,7 +1245,7 @@ func Cli_InstallLocked(projectDir: String) -> int { path = bux_path_join(projectDir, source); } if String_StartsWith(source, "http://") || String_StartsWith(source, "https://") || - String_EndsWith(source, ".git") { + String_EndsWith(source, ".git") { path = bux_path_join(bux_path_join(projectDir, "deps"), name); } if !DirExists(path) { @@ -1342,7 +1342,7 @@ func Cli_Install(projectDir: String, lockedOnly: bool) -> int { // Fetch git deps into deps/ when missing if String_StartsWith(depUrl, "http://") || String_StartsWith(depUrl, "https://") || - String_EndsWith(depUrl, ".git") { + String_EndsWith(depUrl, ".git") { let depsDir: String = bux_path_join(projectDir, "deps"); discard bux_mkdir_if_needed(depsDir); let depPath: String = bux_path_join(depsDir, depName); @@ -2254,7 +2254,7 @@ func Cli_Run(args: *String, argCount: int) -> int { } if argCount < 2 { - PrintLine("Bux Self-Hosting Compiler v0.2.0"); + PrintLine("Bux Self-Hosting Compiler v1.0.0"); PrintLine("Usage: buxc [args]"); PrintLine("Commands: build, check, new, init, add, remove, fetch, install, search, fmt, doc, test, run, project, help, version"); PrintLine(" test --filter Only run tests/*.bux whose name contains "); @@ -2272,12 +2272,12 @@ func Cli_Run(args: *String, argCount: int) -> int { let cmd: String = args[1]; if String_Eq(cmd, "version") || String_Eq(cmd, "--version") || String_Eq(cmd, "-v") { - PrintLine("Bux 0.2.0 (self-hosting bootstrap)"); + PrintLine("Bux 1.0.0 (self-hosting)"); return 0; } 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 v1.0.0"); PrintLine("Usage: buxc [args]"); PrintLine("Commands: build, check, new, init, add, remove, fetch, install, search, fmt, doc, test, run, project, help, version"); PrintLine(" test --filter Only run tests/*.bux whose name contains "); @@ -2330,7 +2330,7 @@ func Cli_Run(args: *String, argCount: int) -> int { if argCount >= 4 { let a3: String = args[3]; if !String_Contains(a3, "/") && !String_StartsWith(a3, "http") && - !String_EndsWith(a3, ".git") { + !String_EndsWith(a3, ".git") { return Cli_AddFromRegistry(args[2], a3); } return Cli_Add(args[2], a3); diff --git a/src/macroexpand.bux b/src/macroexpand.bux index 0af1963..2d13048 100644 --- a/src/macroexpand.bux +++ b/src/macroexpand.bux @@ -467,7 +467,7 @@ module MacroExpand { wrap.child1 = aexp; // group flag: tuple or non-empty slice lit wrap.boolValue = aexp.kind == ekTuple || - (aexp.kind == ekSlice && aexp.callArgCount > 0); + (aexp.kind == ekSlice && aexp.callArgCount > 0); return wrap; } // type — session 87: named / pointer type from call-site expr shape @@ -845,7 +845,7 @@ module MacroExpand { // Build callArgs for a call/macro-call, flattening MacroRep and MacroTt groups. func Macro_SubstCallArgs(oldArgs: *ExprList, env: *MacroEnv, file: String, line: uint32, col: uint32, - outCount: *int) -> *ExprList { + outCount: *int) -> *ExprList { var first: *ExprList = null as *ExprList; var last: *ExprList = null as *ExprList; var count: int = 0; @@ -895,8 +895,8 @@ module MacroExpand { let bound: *Expr = Env_Lookup(env, a.strValue); // Bare `$args:tt` group → flatten tuple/slice elements as call args if bound != null as *Expr && bound.kind == ekMacroTt && bound.boolValue && - bound.child1 != null as *Expr && - (bound.child1.kind == ekTuple || bound.child1.kind == ekSlice) { + bound.child1 != null as *Expr && + (bound.child1.kind == ekTuple || bound.child1.kind == ekSlice) { var tel: *ExprList = bound.child1.callArgs; while tel != null as *ExprList { let ce: *Expr = Ast_CloneExpr(tel.expr); @@ -1197,8 +1197,8 @@ module MacroExpand { let k1: String = Macro_KindAt(kinds, 1); let only: *Expr = expArgs.expr; if String_Eq(k0, "ident") && String_Eq(k1, "tt") && - only != null as *Expr && only.kind == ekCall && - only.child1 != null as *Expr && only.child1.kind == ekIdent { + only != null as *Expr && only.kind == ekCall && + only.child1 != null as *Expr && only.child1.kind == ekIdent { let callee: *Expr = only.child1; // Build MacroTt group from call args (tuple of elements) let inner: *Expr = bux_alloc(sizeof(Expr)) as *Expr; diff --git a/src/manifest.bux b/src/manifest.bux index 0a693a1..6200929 100644 --- a/src/manifest.bux +++ b/src/manifest.bux @@ -92,7 +92,7 @@ module Manifest { while pi + 4 < pl { // match ASCII 'P','a','t','h' if line[pi] == 80 as char8 && line[pi + 1] == 97 as char8 && - line[pi + 2] == 116 as char8 && line[pi + 3] == 104 as char8 { + line[pi + 2] == 116 as char8 && line[pi + 3] == 104 as char8 { var q1: int = -1; var qj: uint = pi; while qj < pl { diff --git a/src/parser.bux b/src/parser.bux index 3785118..93e8350 100644 --- a/src/parser.bux +++ b/src/parser.bux @@ -2465,8 +2465,8 @@ module Parser { if String_Eq(kname, "lit") { kname = "literal"; } if String_Eq(kname, "pattern") { kname = "pat"; } if !(String_Eq(kname, "expr") || String_Eq(kname, "ident") || String_Eq(kname, "tt") - || String_Eq(kname, "literal") || String_Eq(kname, "block") - || String_Eq(kname, "stmt") || String_Eq(kname, "pat") || String_Eq(kname, "type")) { + || String_Eq(kname, "literal") || String_Eq(kname, "block") + || String_Eq(kname, "stmt") || String_Eq(kname, "pat") || String_Eq(kname, "type")) { kname = "expr"; } if nIn == 0 { @@ -2519,8 +2519,8 @@ module Parser { if String_Eq(kname, "lit") { kname = "literal"; } if String_Eq(kname, "pattern") { kname = "pat"; } if !(String_Eq(kname, "expr") || String_Eq(kname, "ident") || String_Eq(kname, "tt") - || String_Eq(kname, "literal") || String_Eq(kname, "block") - || String_Eq(kname, "stmt") || String_Eq(kname, "pat") || String_Eq(kname, "type")) { + || String_Eq(kname, "literal") || String_Eq(kname, "block") + || String_Eq(kname, "stmt") || String_Eq(kname, "pat") || String_Eq(kname, "type")) { kname = "expr"; } if rule.paramCount < 9 { diff --git a/src/registry.bux b/src/registry.bux index e278f79..6622682 100644 --- a/src/registry.bux +++ b/src/registry.bux @@ -100,8 +100,8 @@ module Registry { } // prefer curl let curlCmd: String = String_Concat( - "curl -fsSL", - String_Concat(kflag, String_Concat(" --max-time 30 -o \"", String_Concat(cachePath, String_Concat("\" \"", String_Concat(url, "\""))))) + "curl -fsSL", + String_Concat(kflag, String_Concat(" --max-time 30 -o \"", String_Concat(cachePath, String_Concat("\" \"", String_Concat(url, "\""))))) ); var ok: bool = false; if bux_system("command -v curl >/dev/null 2>&1") == 0 { @@ -112,8 +112,8 @@ module Registry { nflag = " --no-check-certificate"; } let wgetCmd: String = String_Concat( - "wget -q", - String_Concat(nflag, String_Concat(" -T 30 -O \"", String_Concat(cachePath, String_Concat("\" \"", String_Concat(url, "\""))))) + "wget -q", + String_Concat(nflag, String_Concat(" -T 30 -O \"", String_Concat(cachePath, String_Concat("\" \"", String_Concat(url, "\""))))) ); ok = bux_system(wgetCmd) == 0 && Reg_FileExists(cachePath); } @@ -141,240 +141,240 @@ module Registry { if String_StartsWith(src, "file:") { p = String_Slice(src, 5, bux_strlen(src) - 5); if String_StartsWith(p, "//") { - p = String_Slice(p, 2, bux_strlen(p) - 2); - } - } else if String_StartsWith(src, "path:") { - p = String_Slice(src, 5, bux_strlen(src) - 5); - } else { - return ""; + p = String_Slice(p, 2, bux_strlen(p) - 2); } - if String_StartsWith(p, "/") { - return p; - } - return bux_path_join(indexDir, p); + } else if String_StartsWith(src, "path:") { + p = String_Slice(src, 5, bux_strlen(src) - 5); + } else { + return ""; } - - func Reg_SetPkg(reg: *Registry, idx: int, pkg: RegistryPackage) { - if idx == 0 { reg.p0 = pkg; } - else if idx == 1 { reg.p1 = pkg; } - else if idx == 2 { reg.p2 = pkg; } - else if idx == 3 { reg.p3 = pkg; } - else if idx == 4 { reg.p4 = pkg; } - else if idx == 5 { reg.p5 = pkg; } - else if idx == 6 { reg.p6 = pkg; } - else if idx == 7 { reg.p7 = pkg; } - else if idx == 8 { reg.p8 = pkg; } - else if idx == 9 { reg.p9 = pkg; } - else if idx == 10 { reg.p10 = pkg; } - else if idx == 11 { reg.p11 = pkg; } - else if idx == 12 { reg.p12 = pkg; } - else if idx == 13 { reg.p13 = pkg; } - else if idx == 14 { reg.p14 = pkg; } - else if idx == 15 { reg.p15 = pkg; } - } - - func Reg_GetPkg(reg: Registry, idx: int) -> RegistryPackage { - if idx == 0 { return reg.p0; } - if idx == 1 { return reg.p1; } - if idx == 2 { return reg.p2; } - if idx == 3 { return reg.p3; } - if idx == 4 { return reg.p4; } - if idx == 5 { return reg.p5; } - if idx == 6 { return reg.p6; } - if idx == 7 { return reg.p7; } - if idx == 8 { return reg.p8; } - if idx == 9 { return reg.p9; } - if idx == 10 { return reg.p10; } - if idx == 11 { return reg.p11; } - if idx == 12 { return reg.p12; } - if idx == 13 { return reg.p13; } - if idx == 14 { return reg.p14; } - return reg.p15; - } - - func Reg_ParseContent(content: String, indexPath: String) -> Registry { - var reg: Registry; - reg.path = indexPath; - reg.sourceUrl = ""; - reg.count = 0; - let indexDir: String = bux_path_parent(indexPath); - var cur: RegistryPackage; - var inPkg: bool = false; - let nlines: uint = String_SplitCount(content, "\n"); - var i: uint = 0; - while i <= nlines { - var line: String = ""; - if i < nlines { - line = String_Trim(String_SplitPart(content, "\n", i)); - } - let flush: bool = (i == nlines) || String_Eq(line, "[[package]]") || String_Eq(line, "[[Package]]"); - if flush && inPkg && !String_Eq(cur.name, "") { - cur.resolvedPath = Reg_ResolveSource(cur.source, indexDir); - if reg.count < 16 { - Reg_SetPkg(®, reg.count, cur); - reg.count = reg.count + 1; - } - cur.name = ""; - cur.version = ""; - cur.source = ""; - cur.description = ""; - cur.resolvedPath = ""; - } - if i == nlines { break; } - if String_Eq(line, "") || String_StartsWith(line, "#") { - i = i + 1; - continue; - } - if String_Eq(line, "[[package]]") || String_Eq(line, "[[Package]]") { - inPkg = true; - i = i + 1; - continue; - } - if !inPkg { - i = i + 1; - continue; - } - let eqc: uint = String_SplitCount(line, "="); - if eqc >= 2 { - let key: String = String_Trim(String_SplitPart(line, "=", 0)); - let val: String = Reg_StripQuotes(String_SplitPart(line, "=", 1)); - // lowercase-ish compare for common keys - if String_Eq(key, "name") || String_Eq(key, "Name") { - cur.name = val; - } else if String_Eq(key, "version") || String_Eq(key, "Version") { - cur.version = val; - } else if String_Eq(key, "source") || String_Eq(key, "Source") { - cur.source = val; - } else if String_Eq(key, "description") || String_Eq(key, "Description") { - cur.description = val; - } - } - i = i + 1; - } - return reg; - } - - func Reg_FindIndex() -> Registry { - var reg: Registry; - reg.path = ""; - reg.sourceUrl = ""; - reg.count = 0; - let env: String = bux_getenv("BUX_REGISTRY"); - if env != null as String && !String_Eq(env, "") { - if Reg_IsHttpUrl(env) { - let local: String = Reg_FetchHttp(env); - if String_Eq(local, "") { - reg.sourceUrl = env; - return reg; - } - let content: String = bux_read_file(local); - reg = Reg_ParseContent(content, local); - reg.sourceUrl = env; - reg.path = local; - return reg; - } - if Reg_FileExists(env) { - let content: String = bux_read_file(env); - reg = Reg_ParseContent(content, env); - reg.path = env; - return reg; - } - } - let home: String = bux_getenv("HOME"); - if home != null as String && !String_Eq(home, "") { - let homeIdx: String = bux_path_join(bux_path_join(home, ".bux"), "registry.toml"); - if Reg_FileExists(homeIdx) { - let content: String = bux_read_file(homeIdx); - reg = Reg_ParseContent(content, homeIdx); - reg.path = homeIdx; - return reg; - } - } - // cwd-relative candidates - let cwd: String = bux_getcwd(); - var c0: String = bux_path_join(cwd, "config/registry.toml"); - if Reg_FileExists(c0) { - let content: String = bux_read_file(c0); - reg = Reg_ParseContent(content, c0); - reg.path = c0; - return reg; - } - c0 = bux_path_join(cwd, "../config/registry.toml"); - if Reg_FileExists(c0) { - let content: String = bux_read_file(c0); - reg = Reg_ParseContent(content, c0); - reg.path = c0; - return reg; - } - c0 = bux_path_join(cwd, "../../config/registry.toml"); - if Reg_FileExists(c0) { - let content: String = bux_read_file(c0); - reg = Reg_ParseContent(content, c0); - reg.path = c0; - return reg; - } - return reg; - } - - func Reg_VersionOk(have: String, req: String) -> bool { - if String_Eq(req, "") || String_Eq(req, "*") { return true; } - return String_Eq(have, req); - } - - // Lookup by name; versionReq "*" = last matching entry (highest listed last). - func Reg_Lookup(reg: Registry, name: String, versionReq: String) -> RegistryPackage { - var found: RegistryPackage; - found.name = ""; - var i: int = 0; - while i < reg.count { - let p: RegistryPackage = Reg_GetPkg(reg, i); - if String_Eq(p.name, name) && Reg_VersionOk(p.version, versionReq) { - found = p; - } - i = i + 1; - } - return found; - } - - func Reg_Search(reg: Registry, query: String) -> int { - // print hits; return count - var hits: int = 0; - // Dedupe by name: keep last version - // Simple O(n^2): for each pkg, if last occurrence of name, print - var i: int = 0; - while i < reg.count { - let p: RegistryPackage = Reg_GetPkg(reg, i); - var isLast: bool = true; - var j: int = i + 1; - while j < reg.count { - let q: RegistryPackage = Reg_GetPkg(reg, j); - if String_Eq(q.name, p.name) { - isLast = false; - break; - } - j = j + 1; - } - if isLast { - var ok: bool = true; - if !String_Eq(query, "") { - ok = String_Contains(p.name, query) || String_Contains(p.description, query); - } - if ok { - Print(" "); - Print(p.name); - Print(" "); - Print(p.version); - Print(" — "); - if !String_Eq(p.description, "") { - PrintLine(p.description); - } else { - PrintLine(p.source); - } - hits = hits + 1; - } - } - i = i + 1; - } - return hits; + if String_StartsWith(p, "/") { + return p; } + return bux_path_join(indexDir, p); +} + +func Reg_SetPkg(reg: *Registry, idx: int, pkg: RegistryPackage) { + if idx == 0 { reg.p0 = pkg; } + else if idx == 1 { reg.p1 = pkg; } + else if idx == 2 { reg.p2 = pkg; } + else if idx == 3 { reg.p3 = pkg; } + else if idx == 4 { reg.p4 = pkg; } + else if idx == 5 { reg.p5 = pkg; } + else if idx == 6 { reg.p6 = pkg; } + else if idx == 7 { reg.p7 = pkg; } + else if idx == 8 { reg.p8 = pkg; } + else if idx == 9 { reg.p9 = pkg; } + else if idx == 10 { reg.p10 = pkg; } + else if idx == 11 { reg.p11 = pkg; } + else if idx == 12 { reg.p12 = pkg; } + else if idx == 13 { reg.p13 = pkg; } + else if idx == 14 { reg.p14 = pkg; } + else if idx == 15 { reg.p15 = pkg; } +} + +func Reg_GetPkg(reg: Registry, idx: int) -> RegistryPackage { + if idx == 0 { return reg.p0; } + if idx == 1 { return reg.p1; } + if idx == 2 { return reg.p2; } + if idx == 3 { return reg.p3; } + if idx == 4 { return reg.p4; } + if idx == 5 { return reg.p5; } + if idx == 6 { return reg.p6; } + if idx == 7 { return reg.p7; } + if idx == 8 { return reg.p8; } + if idx == 9 { return reg.p9; } + if idx == 10 { return reg.p10; } + if idx == 11 { return reg.p11; } + if idx == 12 { return reg.p12; } + if idx == 13 { return reg.p13; } + if idx == 14 { return reg.p14; } + return reg.p15; +} + +func Reg_ParseContent(content: String, indexPath: String) -> Registry { + var reg: Registry; + reg.path = indexPath; + reg.sourceUrl = ""; + reg.count = 0; + let indexDir: String = bux_path_parent(indexPath); + var cur: RegistryPackage; + var inPkg: bool = false; + let nlines: uint = String_SplitCount(content, "\n"); + var i: uint = 0; + while i <= nlines { + var line: String = ""; + if i < nlines { + line = String_Trim(String_SplitPart(content, "\n", i)); + } + let flush: bool = (i == nlines) || String_Eq(line, "[[package]]") || String_Eq(line, "[[Package]]"); + if flush && inPkg && !String_Eq(cur.name, "") { + cur.resolvedPath = Reg_ResolveSource(cur.source, indexDir); + if reg.count < 16 { + Reg_SetPkg(®, reg.count, cur); + reg.count = reg.count + 1; + } + cur.name = ""; + cur.version = ""; + cur.source = ""; + cur.description = ""; + cur.resolvedPath = ""; + } + if i == nlines { break; } + if String_Eq(line, "") || String_StartsWith(line, "#") { + i = i + 1; + continue; + } + if String_Eq(line, "[[package]]") || String_Eq(line, "[[Package]]") { + inPkg = true; + i = i + 1; + continue; + } + if !inPkg { + i = i + 1; + continue; + } + let eqc: uint = String_SplitCount(line, "="); + if eqc >= 2 { + let key: String = String_Trim(String_SplitPart(line, "=", 0)); + let val: String = Reg_StripQuotes(String_SplitPart(line, "=", 1)); + // lowercase-ish compare for common keys + if String_Eq(key, "name") || String_Eq(key, "Name") { + cur.name = val; + } else if String_Eq(key, "version") || String_Eq(key, "Version") { + cur.version = val; + } else if String_Eq(key, "source") || String_Eq(key, "Source") { + cur.source = val; + } else if String_Eq(key, "description") || String_Eq(key, "Description") { + cur.description = val; + } + } + i = i + 1; + } + return reg; +} + +func Reg_FindIndex() -> Registry { + var reg: Registry; + reg.path = ""; + reg.sourceUrl = ""; + reg.count = 0; + let env: String = bux_getenv("BUX_REGISTRY"); + if env != null as String && !String_Eq(env, "") { + if Reg_IsHttpUrl(env) { + let local: String = Reg_FetchHttp(env); + if String_Eq(local, "") { + reg.sourceUrl = env; + return reg; + } + let content: String = bux_read_file(local); + reg = Reg_ParseContent(content, local); + reg.sourceUrl = env; + reg.path = local; + return reg; + } + if Reg_FileExists(env) { + let content: String = bux_read_file(env); + reg = Reg_ParseContent(content, env); + reg.path = env; + return reg; + } + } + let home: String = bux_getenv("HOME"); + if home != null as String && !String_Eq(home, "") { + let homeIdx: String = bux_path_join(bux_path_join(home, ".bux"), "registry.toml"); + if Reg_FileExists(homeIdx) { + let content: String = bux_read_file(homeIdx); + reg = Reg_ParseContent(content, homeIdx); + reg.path = homeIdx; + return reg; + } + } + // cwd-relative candidates + let cwd: String = bux_getcwd(); + var c0: String = bux_path_join(cwd, "config/registry.toml"); + if Reg_FileExists(c0) { + let content: String = bux_read_file(c0); + reg = Reg_ParseContent(content, c0); + reg.path = c0; + return reg; + } + c0 = bux_path_join(cwd, "../config/registry.toml"); + if Reg_FileExists(c0) { + let content: String = bux_read_file(c0); + reg = Reg_ParseContent(content, c0); + reg.path = c0; + return reg; + } + c0 = bux_path_join(cwd, "../../config/registry.toml"); + if Reg_FileExists(c0) { + let content: String = bux_read_file(c0); + reg = Reg_ParseContent(content, c0); + reg.path = c0; + return reg; + } + return reg; +} + +func Reg_VersionOk(have: String, req: String) -> bool { + if String_Eq(req, "") || String_Eq(req, "*") { return true; } + return String_Eq(have, req); +} + +// Lookup by name; versionReq "*" = last matching entry (highest listed last). +func Reg_Lookup(reg: Registry, name: String, versionReq: String) -> RegistryPackage { + var found: RegistryPackage; + found.name = ""; + var i: int = 0; + while i < reg.count { + let p: RegistryPackage = Reg_GetPkg(reg, i); + if String_Eq(p.name, name) && Reg_VersionOk(p.version, versionReq) { + found = p; + } + i = i + 1; + } + return found; +} + +func Reg_Search(reg: Registry, query: String) -> int { + // print hits; return count + var hits: int = 0; + // Dedupe by name: keep last version + // Simple O(n^2): for each pkg, if last occurrence of name, print + var i: int = 0; + while i < reg.count { + let p: RegistryPackage = Reg_GetPkg(reg, i); + var isLast: bool = true; + var j: int = i + 1; + while j < reg.count { + let q: RegistryPackage = Reg_GetPkg(reg, j); + if String_Eq(q.name, p.name) { + isLast = false; + break; + } + j = j + 1; + } + if isLast { + var ok: bool = true; + if !String_Eq(query, "") { + ok = String_Contains(p.name, query) || String_Contains(p.description, query); + } + if ok { + Print(" "); + Print(p.name); + Print(" "); + Print(p.version); + Print(" — "); + if !String_Eq(p.description, "") { + PrintLine(p.description); + } else { + PrintLine(p.source); + } + hits = hits + 1; + } + } + i = i + 1; + } + return hits; +} }