fix(v1.0.2): Array grow from 0, Map/Set rehash, checked div/mod
ci / build (ubuntu) (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
ci / build (ubuntu) (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
- Array_Push grows from cap 0 (same min as Insert) to avoid segfault - Map/StringMap/Set: default min cap 8 and auto-rehash at ~50% load - Integer / and % emit bux_div_i64 / bux_mod_i64 (panic instead of SIGFPE) - Bump version to 1.0.2; stdlib golden regressions; fmt clean
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
> **Status:** **v1.0.0** — language freeze. Bootstrap (`buxc`, Nim) and self-hosted (`buxc2`, Bux) both compile `.bux` → C → native binary.
|
> **Status:** **v1.0.2** — language freeze (1.0) + patch fixes. Bootstrap (`buxc`, Nim) and self-hosted (`buxc2`, Bux) both compile `.bux` → C → native binary.
|
||||||
> **Selfhost loop:** deterministic C codegen + ELF verified.
|
> **Selfhost loop:** deterministic C codegen + ELF verified.
|
||||||
> **Gradual Ownership:** `@[Checked]` borrow checker, `@[Release]` zero-cost mode, `borrow &mut` expressions.
|
> **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.
|
> **Closures:** multi-instance capturing closures via fat function pointers (`BuxFn { code, env }`) in both compilers.
|
||||||
@@ -316,6 +316,7 @@ make vscode # VS Code extension (syntax + client; auto-finds tools/bux
|
|||||||
| [`docs/Packages.md`](docs/Packages.md) | Package manager + registry |
|
| [`docs/Packages.md`](docs/Packages.md) | Package manager + registry |
|
||||||
| [`docs/SEMVER.md`](docs/SEMVER.md) | Semver policy (**active** post-1.0) |
|
| [`docs/SEMVER.md`](docs/SEMVER.md) | Semver policy (**active** post-1.0) |
|
||||||
| [`docs/RELEASE_v1.0.0.md`](docs/RELEASE_v1.0.0.md) | v1.0.0 freeze notes |
|
| [`docs/RELEASE_v1.0.0.md`](docs/RELEASE_v1.0.0.md) | v1.0.0 freeze notes |
|
||||||
|
| [`docs/RELEASE_v1.0.2.md`](docs/RELEASE_v1.0.2.md) | v1.0.2 patch (stdlib grow + checked div) |
|
||||||
| [`docs/ROADMAP.md`](docs/ROADMAP.md) | Language construct status |
|
| [`docs/ROADMAP.md`](docs/ROADMAP.md) | Language construct status |
|
||||||
| [`docs/QUALITY_PLAN.md`](docs/QUALITY_PLAN.md) | Session history + path to v1.0 (archive) |
|
| [`docs/QUALITY_PLAN.md`](docs/QUALITY_PLAN.md) | Session history + path to v1.0 (archive) |
|
||||||
| [`vscode/README.md`](vscode/README.md) | VS Code extension install & settings |
|
| [`vscode/README.md`](vscode/README.md) | VS Code extension install & settings |
|
||||||
|
|||||||
@@ -256,6 +256,12 @@ proc emitExpr(be: var CBackend, node: HirNode): string =
|
|||||||
of hBinary:
|
of hBinary:
|
||||||
let left = be.emitExpr(node.binaryLeft)
|
let left = be.emitExpr(node.binaryLeft)
|
||||||
let right = be.emitExpr(node.binaryRight)
|
let right = be.emitExpr(node.binaryRight)
|
||||||
|
# Checked integer / and % (float keeps raw C operators)
|
||||||
|
let isFloat = node.typ != nil and node.typ.kind in {tkFloat32, tkFloat64}
|
||||||
|
if node.binaryOp == tkSlash and not isFloat:
|
||||||
|
return &"bux_div_i64((int64_t)({left}), (int64_t)({right}))"
|
||||||
|
if node.binaryOp == tkPercent and not isFloat:
|
||||||
|
return &"bux_mod_i64((int64_t)({left}), (int64_t)({right}))"
|
||||||
let op = operatorToC(node.binaryOp)
|
let op = operatorToC(node.binaryOp)
|
||||||
return &"({left} {op} {right})"
|
return &"({left} {op} {right})"
|
||||||
|
|
||||||
@@ -658,6 +664,9 @@ proc emitModule*(be: var CBackend, module: HirModule): string =
|
|||||||
be.emitLine("#include <stdbool.h>")
|
be.emitLine("#include <stdbool.h>")
|
||||||
be.emitLine("#include <string.h>")
|
be.emitLine("#include <string.h>")
|
||||||
be.emitLine("")
|
be.emitLine("")
|
||||||
|
be.emitLine("extern int64_t bux_div_i64(int64_t a, int64_t b);")
|
||||||
|
be.emitLine("extern int64_t bux_mod_i64(int64_t a, int64_t b);")
|
||||||
|
be.emitLine("")
|
||||||
|
|
||||||
# Pre-collect slice types so we can emit forward declarations early
|
# Pre-collect slice types so we can emit forward declarations early
|
||||||
let sliceTypes = collectSliceTypes(module)
|
let sliceTypes = collectSliceTypes(module)
|
||||||
|
|||||||
+1
-1
@@ -1350,7 +1350,7 @@ proc cmdDoc*(args: seq[string], opts: GlobalOptions): int =
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
proc cmdVersion*(args: seq[string], opts: GlobalOptions): int =
|
proc cmdVersion*(args: seq[string], opts: GlobalOptions): int =
|
||||||
echo "bux 1.0.0 (bootstrap)"
|
echo "bux 1.0.2 (bootstrap)"
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
proc runCli*(args: seq[string]): int =
|
proc runCli*(args: seq[string]): int =
|
||||||
|
|||||||
@@ -106,14 +106,17 @@ proc emitInstr(be: var LirCBackend, instr: LirInstr) =
|
|||||||
be.emitLine(&"{v(instr.dst)} = {v(instr.src)};")
|
be.emitLine(&"{v(instr.dst)} = {v(instr.src)};")
|
||||||
|
|
||||||
# ── Arithmetic ──
|
# ── Arithmetic ──
|
||||||
of lirAdd, lirSub, lirMul, lirDiv, lirMod,
|
of lirDiv:
|
||||||
|
# Checked integer division (runtime panics on divisor 0 instead of SIGFPE).
|
||||||
|
be.emitLine(&"{v(instr.dst)} = bux_div_i64((int64_t)({v(instr.src)}), (int64_t)({v(instr.src2)}));")
|
||||||
|
of lirMod:
|
||||||
|
be.emitLine(&"{v(instr.dst)} = bux_mod_i64((int64_t)({v(instr.src)}), (int64_t)({v(instr.src2)}));")
|
||||||
|
of lirAdd, lirSub, lirMul,
|
||||||
lirAnd, lirOr, lirXor, lirShl, lirShr:
|
lirAnd, lirOr, lirXor, lirShl, lirShr:
|
||||||
let op = case instr.kind
|
let op = case instr.kind
|
||||||
of lirAdd: "+"
|
of lirAdd: "+"
|
||||||
of lirSub: "-"
|
of lirSub: "-"
|
||||||
of lirMul: "*"
|
of lirMul: "*"
|
||||||
of lirDiv: "/"
|
|
||||||
of lirMod: "%"
|
|
||||||
of lirAnd: "&"
|
of lirAnd: "&"
|
||||||
of lirOr: "|"
|
of lirOr: "|"
|
||||||
of lirXor: "^"
|
of lirXor: "^"
|
||||||
@@ -629,6 +632,10 @@ proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): s
|
|||||||
be.emitLine("#include <stdbool.h>")
|
be.emitLine("#include <stdbool.h>")
|
||||||
be.emitLine("#include <string.h>")
|
be.emitLine("#include <string.h>")
|
||||||
be.emitLine("")
|
be.emitLine("")
|
||||||
|
# Checked integer div/mod (rt/runtime*.c) — always available
|
||||||
|
be.emitLine("extern int64_t bux_div_i64(int64_t a, int64_t b);")
|
||||||
|
be.emitLine("extern int64_t bux_mod_i64(int64_t a, int64_t b);")
|
||||||
|
be.emitLine("")
|
||||||
|
|
||||||
# Forward struct declarations
|
# Forward struct declarations
|
||||||
for s in module.structs:
|
for s in module.structs:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[Package]
|
[Package]
|
||||||
Name = "buxc"
|
Name = "buxc"
|
||||||
Version = "1.0.0"
|
Version = "1.0.2"
|
||||||
Type = "bin"
|
Type = "bin"
|
||||||
|
|
||||||
[Build]
|
[Build]
|
||||||
|
|||||||
+12
-1
@@ -1,10 +1,21 @@
|
|||||||
# Bux — План за подобрения (post-v1.0.0)
|
# Bux — План за подобрения (post-v1.0.0)
|
||||||
|
|
||||||
> **Дата:** 2026-07-28
|
> **Дата:** 2026-07-28
|
||||||
> **Статус:** Всички приоритетни задачи изпълнени ✅ · follow-up DX/correctness shipped
|
> **Статус:** Всички приоритетни задачи изпълнени ✅ · follow-up DX/correctness shipped · **v1.0.2** patch
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Сесия 6 — v1.0.2 runtime correctness (2026-07-28)
|
||||||
|
|
||||||
|
| # | Задача | Файлове |
|
||||||
|
|---|--------|---------|
|
||||||
|
| S.1 | `Array_Push` grow from cap 0 (no segfault) | `lib/Array.bux` |
|
||||||
|
| S.2 | `Map`/`StringMap`/`Set` min cap + auto-rehash (no hang / FPE) | `lib/Map.bux`, `lib/Set.bux` |
|
||||||
|
| S.3 | Integer `/` `%` → `bux_div_i64` / `bux_mod_i64` | `bootstrap/lir_c_backend.nim`, `bootstrap/c_backend.nim`, `src/c_backend.bux` |
|
||||||
|
| S.4 | Version **1.0.2** + golden regressions | `bootstrap/cli.nim`, `src/cli.bux`, `bux.toml`, `tests/stdlib_golden/` |
|
||||||
|
|
||||||
|
**Verified:** stdlib golden PASS; `Array_New(0)+Push` OK; `Map_New(2)+5 inserts` OK; div-by-zero → `bux panic: division by zero`; examples PASS; fmt-check PASS.
|
||||||
|
|
||||||
## Сесия 4 — Try payload type + LSP formatting (2026-07-28)
|
## Сесия 4 — Try payload type + LSP formatting (2026-07-28)
|
||||||
|
|
||||||
| # | Задача | Файлове |
|
| # | Задача | Файлове |
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Bux v1.0.2 — Patch
|
||||||
|
|
||||||
|
**Date:** 2026-07-28
|
||||||
|
**Tag:** `v1.0.2`
|
||||||
|
|
||||||
|
Patch release on the **v1.0 language freeze**. No language-surface breakage;
|
||||||
|
stdlib + codegen correctness only.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
| Area | Issue | Fix |
|
||||||
|
|------|--------|-----|
|
||||||
|
| `Array_Push` | `Array_New(0)` + push → segfault (`cap * 2 == 0`) | Grow to at least 4 when capacity was 0 (same as `Array_Insert`) |
|
||||||
|
| `Map` / `StringMap` | Full table → infinite loop in open-addressing probe; `cap == 0` → SIGFPE on `%` | Default min cap 8; auto-rehash when load ≥ 50% |
|
||||||
|
| `Set` | Same as Map | Same grow / min-cap policy |
|
||||||
|
| Integer `/` and `%` | Divisor 0 → raw SIGFPE | Emit `bux_div_i64` / `bux_mod_i64` (bootstrap LIR + C backends, selfhost C backend) |
|
||||||
|
|
||||||
|
## Verify
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make build
|
||||||
|
./buxc --version # bux 1.0.2 (bootstrap)
|
||||||
|
make test-stdlib test-examples
|
||||||
|
```
|
||||||
+5
-1
@@ -21,7 +21,11 @@ module Std::Array {
|
|||||||
/// Append `value`, growing capacity if needed.
|
/// Append `value`, growing capacity if needed.
|
||||||
func Array_Push<T>(self: *Array<T>, value: T) {
|
func Array_Push<T>(self: *Array<T>, value: T) {
|
||||||
if self.len >= self.cap {
|
if self.len >= self.cap {
|
||||||
self.cap = self.cap * 2;
|
var newCap: uint = self.cap * 2;
|
||||||
|
if newCap == 0 {
|
||||||
|
newCap = 4;
|
||||||
|
}
|
||||||
|
self.cap = newCap;
|
||||||
self.data = bux_realloc(self.data as *void, self.cap * sizeof(T)) as *T;
|
self.data = bux_realloc(self.data as *void, self.cap * sizeof(T)) as *T;
|
||||||
}
|
}
|
||||||
self.data[self.len] = value;
|
self.data[self.len] = value;
|
||||||
|
|||||||
+115
-12
@@ -2,6 +2,8 @@ module Std::Map {
|
|||||||
|
|
||||||
extern func bux_hash_bytes(ptr: *void, size: uint) -> uint;
|
extern func bux_hash_bytes(ptr: *void, size: uint) -> uint;
|
||||||
extern func bux_hash_string(s: String) -> uint;
|
extern func bux_hash_string(s: String) -> uint;
|
||||||
|
extern func bux_alloc(size: uint) -> *void;
|
||||||
|
extern func bux_free(ptr: *void);
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Generic Map<K, V> — works with value-type keys (int, float, etc.)
|
// Generic Map<K, V> — works with value-type keys (int, float, etc.)
|
||||||
@@ -20,18 +22,48 @@ module Std::Map {
|
|||||||
len: uint,
|
len: uint,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a map with at least `cap` slots (open addressing).
|
||||||
|
/// `cap == 0` defaults to 8 so inserts never hit `% 0` or a full table with no growth room.
|
||||||
func Map_New<K, V>(cap: uint) -> Map<K, V> {
|
func Map_New<K, V>(cap: uint) -> Map<K, V> {
|
||||||
let total: uint = cap * sizeof(MapEntry<K, V>);
|
var c: uint = cap;
|
||||||
|
if c == 0 {
|
||||||
|
c = 8;
|
||||||
|
}
|
||||||
|
let total: uint = c * sizeof(MapEntry<K, V>);
|
||||||
let data: *MapEntry<K, V> = bux_alloc(total) as *MapEntry<K, V>;
|
let data: *MapEntry<K, V> = bux_alloc(total) as *MapEntry<K, V>;
|
||||||
var i: uint = 0;
|
var i: uint = 0;
|
||||||
while i < cap {
|
while i < c {
|
||||||
data[i].occupied = false;
|
data[i].occupied = false;
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
}
|
}
|
||||||
return Map<K, V> { entries: data, cap: cap, len: 0 };
|
return Map<K, V> { entries: data, cap: c, len: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
func Map_Set<K, V>(m: *Map<K, V>, key: K, value: V) {
|
/// Grow / rehash to `newCap` (must be > 0). Transfers ownership; does not Drop `fresh`.
|
||||||
|
func Map_Rehash<K, V>(m: *Map<K, V>, newCap: uint) {
|
||||||
|
var nc: uint = newCap;
|
||||||
|
if nc == 0 {
|
||||||
|
nc = 8;
|
||||||
|
}
|
||||||
|
var fresh: Map<K, V> = Map_New<K, V>(nc);
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < m.cap {
|
||||||
|
if m.entries[i].occupied {
|
||||||
|
Map_SetInsertOnly<K, V>(&fresh, m.entries[i].key, m.entries[i].value);
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
bux_free(m.entries as *void);
|
||||||
|
m.entries = fresh.entries;
|
||||||
|
m.cap = fresh.cap;
|
||||||
|
m.len = fresh.len;
|
||||||
|
fresh.entries = null as *MapEntry<K, V>;
|
||||||
|
fresh.cap = 0;
|
||||||
|
fresh.len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert assuming free slots exist (no grow). Used by rehash.
|
||||||
|
func Map_SetInsertOnly<K, V>(m: *Map<K, V>, key: K, value: V) {
|
||||||
var keyPtr: *K = &key;
|
var keyPtr: *K = &key;
|
||||||
let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K));
|
let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K));
|
||||||
var idx: uint = hash % m.cap;
|
var idx: uint = hash % m.cap;
|
||||||
@@ -48,7 +80,23 @@ module Std::Map {
|
|||||||
m.len = m.len + 1;
|
m.len = m.len + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Map_Set<K, V>(m: *Map<K, V>, key: K, value: V) {
|
||||||
|
// Keep load factor under ~50% for open addressing (also handles cap==0).
|
||||||
|
if m.cap == 0 || m.len * 2 >= m.cap {
|
||||||
|
var nc: uint = m.cap * 2;
|
||||||
|
if nc < 8 {
|
||||||
|
nc = 8;
|
||||||
|
}
|
||||||
|
Map_Rehash<K, V>(m, nc);
|
||||||
|
}
|
||||||
|
Map_SetInsertOnly<K, V>(m, key, value);
|
||||||
|
}
|
||||||
|
|
||||||
func Map_Get<K, V>(m: *Map<K, V>, key: K) -> V {
|
func Map_Get<K, V>(m: *Map<K, V>, key: K) -> V {
|
||||||
|
if m.cap == 0 {
|
||||||
|
var zero: V = 0 as V;
|
||||||
|
return zero;
|
||||||
|
}
|
||||||
var keyPtr: *K = &key;
|
var keyPtr: *K = &key;
|
||||||
let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K));
|
let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K));
|
||||||
var idx: uint = hash % m.cap;
|
var idx: uint = hash % m.cap;
|
||||||
@@ -72,6 +120,9 @@ module Std::Map {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Map_Has<K, V>(m: *Map<K, V>, key: K) -> bool {
|
func Map_Has<K, V>(m: *Map<K, V>, key: K) -> bool {
|
||||||
|
if m.cap == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
var keyPtr: *K = &key;
|
var keyPtr: *K = &key;
|
||||||
let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K));
|
let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K));
|
||||||
var idx: uint = hash % m.cap;
|
var idx: uint = hash % m.cap;
|
||||||
@@ -97,12 +148,16 @@ module Std::Map {
|
|||||||
if !Map_Has<K, V>(m, key) {
|
if !Map_Has<K, V>(m, key) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var fresh: Map<K, V> = Map_New<K, V>(m.cap);
|
var keepCap: uint = m.cap;
|
||||||
|
if keepCap == 0 {
|
||||||
|
keepCap = 8;
|
||||||
|
}
|
||||||
|
var fresh: Map<K, V> = Map_New<K, V>(keepCap);
|
||||||
var i: uint = 0;
|
var i: uint = 0;
|
||||||
while i < m.cap {
|
while i < m.cap {
|
||||||
if m.entries[i].occupied {
|
if m.entries[i].occupied {
|
||||||
if m.entries[i].key != key {
|
if m.entries[i].key != key {
|
||||||
Map_Set<K, V>(&fresh, m.entries[i].key, m.entries[i].value);
|
Map_SetInsertOnly<K, V>(&fresh, m.entries[i].key, m.entries[i].value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
@@ -155,17 +210,43 @@ module Std::Map {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func StringMap_New<V>(cap: uint) -> StringMap<V> {
|
func StringMap_New<V>(cap: uint) -> StringMap<V> {
|
||||||
let total: uint = cap * sizeof(StringMapEntry<V>);
|
var c: uint = cap;
|
||||||
|
if c == 0 {
|
||||||
|
c = 8;
|
||||||
|
}
|
||||||
|
let total: uint = c * sizeof(StringMapEntry<V>);
|
||||||
let data: *StringMapEntry<V> = bux_alloc(total) as *StringMapEntry<V>;
|
let data: *StringMapEntry<V> = bux_alloc(total) as *StringMapEntry<V>;
|
||||||
var i: uint = 0;
|
var i: uint = 0;
|
||||||
while i < cap {
|
while i < c {
|
||||||
data[i].occupied = false;
|
data[i].occupied = false;
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
}
|
}
|
||||||
return StringMap<V> { entries: data, cap: cap, len: 0 };
|
return StringMap<V> { entries: data, cap: c, len: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
func StringMap_Set<V>(m: *StringMap<V>, key: String, value: V) {
|
func StringMap_Rehash<V>(m: *StringMap<V>, newCap: uint) {
|
||||||
|
var nc: uint = newCap;
|
||||||
|
if nc == 0 {
|
||||||
|
nc = 8;
|
||||||
|
}
|
||||||
|
var fresh: StringMap<V> = StringMap_New<V>(nc);
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < m.cap {
|
||||||
|
if m.entries[i].occupied {
|
||||||
|
StringMap_SetInsertOnly<V>(&fresh, m.entries[i].key, m.entries[i].value);
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
bux_free(m.entries as *void);
|
||||||
|
m.entries = fresh.entries;
|
||||||
|
m.cap = fresh.cap;
|
||||||
|
m.len = fresh.len;
|
||||||
|
fresh.entries = null as *StringMapEntry<V>;
|
||||||
|
fresh.cap = 0;
|
||||||
|
fresh.len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringMap_SetInsertOnly<V>(m: *StringMap<V>, key: String, value: V) {
|
||||||
let hash: uint = bux_hash_string(key);
|
let hash: uint = bux_hash_string(key);
|
||||||
var idx: uint = hash % m.cap;
|
var idx: uint = hash % m.cap;
|
||||||
while m.entries[idx].occupied {
|
while m.entries[idx].occupied {
|
||||||
@@ -181,7 +262,22 @@ module Std::Map {
|
|||||||
m.len = m.len + 1;
|
m.len = m.len + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StringMap_Set<V>(m: *StringMap<V>, key: String, value: V) {
|
||||||
|
if m.cap == 0 || m.len * 2 >= m.cap {
|
||||||
|
var nc: uint = m.cap * 2;
|
||||||
|
if nc < 8 {
|
||||||
|
nc = 8;
|
||||||
|
}
|
||||||
|
StringMap_Rehash<V>(m, nc);
|
||||||
|
}
|
||||||
|
StringMap_SetInsertOnly<V>(m, key, value);
|
||||||
|
}
|
||||||
|
|
||||||
func StringMap_Get<V>(m: *StringMap<V>, key: String) -> V {
|
func StringMap_Get<V>(m: *StringMap<V>, key: String) -> V {
|
||||||
|
if m.cap == 0 {
|
||||||
|
var zero: V = 0 as V;
|
||||||
|
return zero;
|
||||||
|
}
|
||||||
let hash: uint = bux_hash_string(key);
|
let hash: uint = bux_hash_string(key);
|
||||||
var idx: uint = hash % m.cap;
|
var idx: uint = hash % m.cap;
|
||||||
while m.entries[idx].occupied {
|
while m.entries[idx].occupied {
|
||||||
@@ -203,6 +299,9 @@ module Std::Map {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func StringMap_Has<V>(m: *StringMap<V>, key: String) -> bool {
|
func StringMap_Has<V>(m: *StringMap<V>, key: String) -> bool {
|
||||||
|
if m.cap == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
let hash: uint = bux_hash_string(key);
|
let hash: uint = bux_hash_string(key);
|
||||||
var idx: uint = hash % m.cap;
|
var idx: uint = hash % m.cap;
|
||||||
while m.entries[idx].occupied {
|
while m.entries[idx].occupied {
|
||||||
@@ -226,12 +325,16 @@ module Std::Map {
|
|||||||
if !StringMap_Has<V>(m, key) {
|
if !StringMap_Has<V>(m, key) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var fresh: StringMap<V> = StringMap_New<V>(m.cap);
|
var keepCap: uint = m.cap;
|
||||||
|
if keepCap == 0 {
|
||||||
|
keepCap = 8;
|
||||||
|
}
|
||||||
|
var fresh: StringMap<V> = StringMap_New<V>(keepCap);
|
||||||
var i: uint = 0;
|
var i: uint = 0;
|
||||||
while i < m.cap {
|
while i < m.cap {
|
||||||
if m.entries[i].occupied {
|
if m.entries[i].occupied {
|
||||||
if !String_Eq(m.entries[i].key, key) {
|
if !String_Eq(m.entries[i].key, key) {
|
||||||
StringMap_Set<V>(&fresh, m.entries[i].key, m.entries[i].value);
|
StringMap_SetInsertOnly<V>(&fresh, m.entries[i].key, m.entries[i].value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
|
|||||||
+51
-6
@@ -16,18 +16,45 @@ module Std::Set {
|
|||||||
len: uint,
|
len: uint,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a set with at least `cap` slots. `cap == 0` defaults to 8.
|
||||||
func Set_New<T>(cap: uint) -> Set<T> {
|
func Set_New<T>(cap: uint) -> Set<T> {
|
||||||
let total: uint = cap * sizeof(SetEntry<T>);
|
var c: uint = cap;
|
||||||
|
if c == 0 {
|
||||||
|
c = 8;
|
||||||
|
}
|
||||||
|
let total: uint = c * sizeof(SetEntry<T>);
|
||||||
let data: *SetEntry<T> = bux_alloc(total) as *SetEntry<T>;
|
let data: *SetEntry<T> = bux_alloc(total) as *SetEntry<T>;
|
||||||
var i: uint = 0;
|
var i: uint = 0;
|
||||||
while i < cap {
|
while i < c {
|
||||||
data[i].occupied = false;
|
data[i].occupied = false;
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
}
|
}
|
||||||
return Set<T> { entries: data, cap: cap, len: 0 };
|
return Set<T> { entries: data, cap: c, len: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
func Set_Add<T>(s: *Set<T>, value: T) {
|
func Set_Rehash<T>(s: *Set<T>, newCap: uint) {
|
||||||
|
var nc: uint = newCap;
|
||||||
|
if nc == 0 {
|
||||||
|
nc = 8;
|
||||||
|
}
|
||||||
|
var fresh: Set<T> = Set_New<T>(nc);
|
||||||
|
var i: uint = 0;
|
||||||
|
while i < s.cap {
|
||||||
|
if s.entries[i].occupied {
|
||||||
|
Set_AddInsertOnly<T>(&fresh, s.entries[i].value);
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
bux_free(s.entries as *void);
|
||||||
|
s.entries = fresh.entries;
|
||||||
|
s.cap = fresh.cap;
|
||||||
|
s.len = fresh.len;
|
||||||
|
fresh.entries = null as *SetEntry<T>;
|
||||||
|
fresh.cap = 0;
|
||||||
|
fresh.len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Set_AddInsertOnly<T>(s: *Set<T>, value: T) {
|
||||||
var valuePtr: *T = &value;
|
var valuePtr: *T = &value;
|
||||||
let hash: uint = bux_hash_bytes(valuePtr as *void, sizeof(T));
|
let hash: uint = bux_hash_bytes(valuePtr as *void, sizeof(T));
|
||||||
var idx: uint = hash % s.cap;
|
var idx: uint = hash % s.cap;
|
||||||
@@ -43,7 +70,21 @@ module Std::Set {
|
|||||||
s.len = s.len + 1;
|
s.len = s.len + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Set_Add<T>(s: *Set<T>, value: T) {
|
||||||
|
if s.cap == 0 || s.len * 2 >= s.cap {
|
||||||
|
var nc: uint = s.cap * 2;
|
||||||
|
if nc < 8 {
|
||||||
|
nc = 8;
|
||||||
|
}
|
||||||
|
Set_Rehash<T>(s, nc);
|
||||||
|
}
|
||||||
|
Set_AddInsertOnly<T>(s, value);
|
||||||
|
}
|
||||||
|
|
||||||
func Set_Has<T>(s: *Set<T>, value: T) -> bool {
|
func Set_Has<T>(s: *Set<T>, value: T) -> bool {
|
||||||
|
if s.cap == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
var valuePtr: *T = &value;
|
var valuePtr: *T = &value;
|
||||||
let hash: uint = bux_hash_bytes(valuePtr as *void, sizeof(T));
|
let hash: uint = bux_hash_bytes(valuePtr as *void, sizeof(T));
|
||||||
var idx: uint = hash % s.cap;
|
var idx: uint = hash % s.cap;
|
||||||
@@ -70,14 +111,18 @@ module Std::Set {
|
|||||||
if !Set_Has<T>(s, value) {
|
if !Set_Has<T>(s, value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var fresh: Set<T> = Set_New<T>(s.cap);
|
var keepCap: uint = s.cap;
|
||||||
|
if keepCap == 0 {
|
||||||
|
keepCap = 8;
|
||||||
|
}
|
||||||
|
var fresh: Set<T> = Set_New<T>(keepCap);
|
||||||
var i: uint = 0;
|
var i: uint = 0;
|
||||||
while i < s.cap {
|
while i < s.cap {
|
||||||
if s.entries[i].occupied {
|
if s.entries[i].occupied {
|
||||||
var entryPtr: *T = &s.entries[i].value;
|
var entryPtr: *T = &s.entries[i].value;
|
||||||
var valuePtr: *T = &value;
|
var valuePtr: *T = &value;
|
||||||
if bux_mem_eq(entryPtr as *void, valuePtr as *void, sizeof(T)) == 0 {
|
if bux_mem_eq(entryPtr as *void, valuePtr as *void, sizeof(T)) == 0 {
|
||||||
Set_Add<T>(&fresh, s.entries[i].value);
|
Set_AddInsertOnly<T>(&fresh, s.entries[i].value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
|
|||||||
@@ -996,7 +996,24 @@ module CBackend {
|
|||||||
|
|
||||||
// Binary — always parenthesize so C precedence cannot rewrite the AST.
|
// Binary — always parenthesize so C precedence cannot rewrite the AST.
|
||||||
// Without parens, Mul(Add(a,b), c) emits `a + b * c` (= a+(b*c)) instead of (a+b)*c.
|
// Without parens, Mul(Add(a,b), c) emits `a + b * c` (= a+(b*c)) instead of (a+b)*c.
|
||||||
|
// Integer / and % use checked runtime helpers (panic on zero divisor).
|
||||||
if kind == hBinary {
|
if kind == hBinary {
|
||||||
|
if node.intValue == tkSlash {
|
||||||
|
StringBuilder_Append(&cbe.sb, "bux_div_i64((int64_t)(");
|
||||||
|
CBE_EmitExpr(cbe, node.child1);
|
||||||
|
StringBuilder_Append(&cbe.sb, "), (int64_t)(");
|
||||||
|
CBE_EmitExpr(cbe, node.child2);
|
||||||
|
StringBuilder_Append(&cbe.sb, "))");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if node.intValue == tkPercent {
|
||||||
|
StringBuilder_Append(&cbe.sb, "bux_mod_i64((int64_t)(");
|
||||||
|
CBE_EmitExpr(cbe, node.child1);
|
||||||
|
StringBuilder_Append(&cbe.sb, "), (int64_t)(");
|
||||||
|
CBE_EmitExpr(cbe, node.child2);
|
||||||
|
StringBuilder_Append(&cbe.sb, "))");
|
||||||
|
return;
|
||||||
|
}
|
||||||
StringBuilder_Append(&cbe.sb, "(");
|
StringBuilder_Append(&cbe.sb, "(");
|
||||||
CBE_EmitExpr(cbe, node.child1);
|
CBE_EmitExpr(cbe, node.child1);
|
||||||
StringBuilder_Append(&cbe.sb, " ");
|
StringBuilder_Append(&cbe.sb, " ");
|
||||||
@@ -2108,6 +2125,8 @@ module CBackend {
|
|||||||
StringBuilder_Append(&cbe.sb, "#include <string.h>\n");
|
StringBuilder_Append(&cbe.sb, "#include <string.h>\n");
|
||||||
StringBuilder_Append(&cbe.sb, "#include <stdio.h>\n");
|
StringBuilder_Append(&cbe.sb, "#include <stdio.h>\n");
|
||||||
StringBuilder_Append(&cbe.sb, "#include <stdlib.h>\n\n");
|
StringBuilder_Append(&cbe.sb, "#include <stdlib.h>\n\n");
|
||||||
|
StringBuilder_Append(&cbe.sb, "extern int64_t bux_div_i64(int64_t a, int64_t b);\n");
|
||||||
|
StringBuilder_Append(&cbe.sb, "extern int64_t bux_mod_i64(int64_t a, int64_t b);\n\n");
|
||||||
// Type aliases
|
// Type aliases
|
||||||
StringBuilder_Append(&cbe.sb, "typedef const char* String;\n");
|
StringBuilder_Append(&cbe.sb, "typedef const char* String;\n");
|
||||||
StringBuilder_Append(&cbe.sb, "typedef unsigned char uint8;\n");
|
StringBuilder_Append(&cbe.sb, "typedef unsigned char uint8;\n");
|
||||||
|
|||||||
+3
-3
@@ -2337,7 +2337,7 @@ func Cli_Run(args: *String, argCount: int) -> int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if argCount < 2 {
|
if argCount < 2 {
|
||||||
PrintLine("Bux Self-Hosting Compiler v1.0.0");
|
PrintLine("Bux Self-Hosting Compiler v1.0.2");
|
||||||
PrintLine("Usage: buxc <command> [args]");
|
PrintLine("Usage: buxc <command> [args]");
|
||||||
PrintLine("Commands: build, check, new, init, add, remove, fetch, install, search, fmt, doc, test, run, project, help, version");
|
PrintLine("Commands: build, check, new, init, add, remove, fetch, install, search, fmt, doc, test, run, project, help, version");
|
||||||
PrintLine(" test --filter <name> Only run tests/*.bux whose name contains <name>");
|
PrintLine(" test --filter <name> Only run tests/*.bux whose name contains <name>");
|
||||||
@@ -2355,12 +2355,12 @@ func Cli_Run(args: *String, argCount: int) -> int {
|
|||||||
|
|
||||||
let cmd: String = args[1];
|
let cmd: String = args[1];
|
||||||
if String_Eq(cmd, "version") || String_Eq(cmd, "--version") || String_Eq(cmd, "-v") {
|
if String_Eq(cmd, "version") || String_Eq(cmd, "--version") || String_Eq(cmd, "-v") {
|
||||||
PrintLine("Bux 1.0.0 (self-hosting)");
|
PrintLine("Bux 1.0.2 (self-hosting)");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if String_Eq(cmd, "help") || String_Eq(cmd, "--help") || String_Eq(cmd, "-h") {
|
if String_Eq(cmd, "help") || String_Eq(cmd, "--help") || String_Eq(cmd, "-h") {
|
||||||
PrintLine("Bux Self-Hosting Compiler v1.0.0");
|
PrintLine("Bux Self-Hosting Compiler v1.0.2");
|
||||||
PrintLine("Usage: buxc <command> [args]");
|
PrintLine("Usage: buxc <command> [args]");
|
||||||
PrintLine("Commands: build, check, new, init, add, remove, fetch, install, search, fmt, doc, test, run, project, help, version");
|
PrintLine("Commands: build, check, new, init, add, remove, fetch, install, search, fmt, doc, test, run, project, help, version");
|
||||||
PrintLine(" test --filter <name> Only run tests/*.bux whose name contains <name>");
|
PrintLine(" test --filter <name> Only run tests/*.bux whose name contains <name>");
|
||||||
|
|||||||
@@ -11,6 +11,15 @@ import Std::Test::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
func Main() -> int {
|
func Main() -> int {
|
||||||
|
// Zero-capacity grow (v1.0.2): must not segfault
|
||||||
|
var z: Array<int> = Array_New<int>(0);
|
||||||
|
Array_Push<int>(&z, 7);
|
||||||
|
Array_Push<int>(&z, 8);
|
||||||
|
Test_AssertEqInt(Array_Len<int>(&z) as int, 2);
|
||||||
|
Test_AssertEqInt(Array_Get<int>(&z, 0), 7);
|
||||||
|
Test_AssertEqInt(Array_Get<int>(&z, 1), 8);
|
||||||
|
Array_Free<int>(&z);
|
||||||
|
|
||||||
var arr: Array<int> = Array_New<int>(2);
|
var arr: Array<int> = Array_New<int>(2);
|
||||||
Array_Reserve<int>(&arr, 8);
|
Array_Reserve<int>(&arr, 8);
|
||||||
Test_AssertTrue(Array_Cap<int>(&arr) >= 8);
|
Test_AssertTrue(Array_Cap<int>(&arr) >= 8);
|
||||||
|
|||||||
@@ -34,6 +34,23 @@ func Main() -> int {
|
|||||||
Test_AssertTrue(Map_IsEmpty<int, int>(&m));
|
Test_AssertTrue(Map_IsEmpty<int, int>(&m));
|
||||||
Map_Free<int, int>(&m);
|
Map_Free<int, int>(&m);
|
||||||
|
|
||||||
|
// Auto-grow past initial cap (v1.0.2): must not hang
|
||||||
|
var tiny: Map<int, int> = Map_New<int, int>(2);
|
||||||
|
Map_Set<int, int>(&tiny, 1, 10);
|
||||||
|
Map_Set<int, int>(&tiny, 2, 20);
|
||||||
|
Map_Set<int, int>(&tiny, 3, 30);
|
||||||
|
Map_Set<int, int>(&tiny, 4, 40);
|
||||||
|
Map_Set<int, int>(&tiny, 5, 50);
|
||||||
|
Test_AssertEqInt(Map_Len<int, int>(&tiny) as int, 5);
|
||||||
|
Test_AssertEqInt(Map_Get<int, int>(&tiny, 5), 50);
|
||||||
|
Map_Free<int, int>(&tiny);
|
||||||
|
|
||||||
|
// Zero-cap request defaults to usable table (v1.0.2)
|
||||||
|
var z: Map<int, int> = Map_New<int, int>(0);
|
||||||
|
Map_Set<int, int>(&z, 9, 99);
|
||||||
|
Test_AssertEqInt(Map_Get<int, int>(&z, 9), 99);
|
||||||
|
Map_Free<int, int>(&z);
|
||||||
|
|
||||||
var s: Set<int> = Set_New<int>(16);
|
var s: Set<int> = Set_New<int>(16);
|
||||||
Set_Add<int>(&s, 10);
|
Set_Add<int>(&s, 10);
|
||||||
Set_Add<int>(&s, 20);
|
Set_Add<int>(&s, 20);
|
||||||
@@ -45,6 +62,16 @@ func Main() -> int {
|
|||||||
Test_AssertFalse(Set_IsEmpty<int>(&s));
|
Test_AssertFalse(Set_IsEmpty<int>(&s));
|
||||||
Set_Free<int>(&s);
|
Set_Free<int>(&s);
|
||||||
|
|
||||||
|
// Set grow (v1.0.2)
|
||||||
|
var s2: Set<int> = Set_New<int>(2);
|
||||||
|
Set_Add<int>(&s2, 1);
|
||||||
|
Set_Add<int>(&s2, 2);
|
||||||
|
Set_Add<int>(&s2, 3);
|
||||||
|
Set_Add<int>(&s2, 4);
|
||||||
|
Test_AssertEqInt(Set_Len<int>(&s2) as int, 4);
|
||||||
|
Test_AssertTrue(Set_Has<int>(&s2, 4));
|
||||||
|
Set_Free<int>(&s2);
|
||||||
|
|
||||||
let ok: Result<int, String> = Result_NewOk<int, String>(42);
|
let ok: Result<int, String> = Result_NewOk<int, String>(42);
|
||||||
let err: Result<int, String> = Result_NewErr<int, String>("boom");
|
let err: Result<int, String> = Result_NewErr<int, String>("boom");
|
||||||
Test_AssertTrue(Result_IsOk<int, String>(ok));
|
Test_AssertTrue(Result_IsOk<int, String>(ok));
|
||||||
|
|||||||
Reference in New Issue
Block a user