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

- 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:
2026-07-29 00:03:22 +03:00
parent f21002d258
commit d8c21609fd
16 changed files with 296 additions and 37 deletions
+2 -1
View File
@@ -2,7 +2,7 @@
![Bux Language](bux-lang-01.jpeg) ![Bux Language](bux-lang-01.jpeg)
> **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 |
+9
View File
@@ -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
View File
@@ -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 =
+10 -3
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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)
| # | Задача | Файлове | | # | Задача | Файлове |
+24
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+19
View File
@@ -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
View File
@@ -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>");
+1 -1
View File
@@ -2579,7 +2579,7 @@ module HirLower {
} }
// Fallback: type annotation on the is-expression operand // Fallback: type annotation on the is-expression operand
if String_Eq(enumName, "") && expr.child1 != null as *Expr && if String_Eq(enumName, "") && expr.child1 != null as *Expr &&
expr.child1.refType != null as *TypeExpr { expr.child1.refType != null as *TypeExpr {
let te: *TypeExpr = Lcx_SubstituteType(ctx, expr.child1.refType); let te: *TypeExpr = Lcx_SubstituteType(ctx, expr.child1.refType);
if te != null as *TypeExpr && !String_Eq(te.typeName, "") { if te != null as *TypeExpr && !String_Eq(te.typeName, "") {
enumName = te.typeName; enumName = te.typeName;
+7 -7
View File
@@ -358,7 +358,7 @@ module Sema {
} }
if !foundName { if !foundName {
Sema_EmitError(sema, expr.line, expr.column, Sema_EmitError(sema, expr.line, expr.column,
String_Concat("unknown argument name '", String_Concat(checkArg.argName, "'"))); String_Concat("unknown argument name '", String_Concat(checkArg.argName, "'")));
} }
} }
checkArg = checkArg.next; checkArg = checkArg.next;
@@ -1079,8 +1079,8 @@ module Sema {
// Arity check (runs after Sema_ResolveCallArgs so defaults/named args align) // Arity check (runs after Sema_ResolveCallArgs so defaults/named args align)
if callDecl != null as *Decl && expr.callArgCount != callDecl.paramCount && sema.diagCount == diagsBeforeCallArgs { if callDecl != null as *Decl && expr.callArgCount != callDecl.paramCount && sema.diagCount == diagsBeforeCallArgs {
Sema_EmitError(sema, expr.line, expr.column, Sema_EmitError(sema, expr.line, expr.column,
String_Concat("expected ", String_Concat(bux_int_to_str(callDecl.paramCount as int64), String_Concat("expected ", String_Concat(bux_int_to_str(callDecl.paramCount as int64),
String_Concat(" arguments, got ", bux_int_to_str(expr.callArgCount as int64))))); String_Concat(" arguments, got ", bux_int_to_str(expr.callArgCount as int64)))));
callDecl = null as *Decl; callDecl = null as *Decl;
} }
var argIdx: int = 0; var argIdx: int = 0;
@@ -1109,9 +1109,9 @@ module Sema {
let wantName: String = Sema_TypeNameForDiag(cp.refParamType, Sema_ResolveType(sema, cp.refParamType)); let wantName: String = Sema_TypeNameForDiag(cp.refParamType, Sema_ResolveType(sema, cp.refParamType));
let gotName: String = Sema_TypeNameForDiag(arg.expr.refType, argKind); let gotName: String = Sema_TypeNameForDiag(arg.expr.refType, argKind);
Sema_EmitError(sema, arg.expr.line, arg.expr.column, Sema_EmitError(sema, arg.expr.line, arg.expr.column,
String_Concat("argument ", String_Concat(bux_int_to_str((argIdx + 1) as int64), String_Concat("argument ", String_Concat(bux_int_to_str((argIdx + 1) as int64),
String_Concat(": expected ", String_Concat(wantName, String_Concat(": expected ", String_Concat(wantName,
String_Concat(", got ", gotName)))))); String_Concat(", got ", gotName))))));
} }
} }
} }
@@ -1565,7 +1565,7 @@ module Sema {
let gotName: String = Sema_TypeNameForDiag(stmt.child1.refType, initType); let gotName: String = Sema_TypeNameForDiag(stmt.child1.refType, initType);
let wantName: String = Sema_TypeNameForDiag(stmt.refStmtType, Sema_ResolveType(sema, stmt.refStmtType)); let wantName: String = Sema_TypeNameForDiag(stmt.refStmtType, Sema_ResolveType(sema, stmt.refStmtType));
let msg: String = String_Concat("cannot assign ", let msg: String = String_Concat("cannot assign ",
String_Concat(gotName, String_Concat(" to ", wantName))); String_Concat(gotName, String_Concat(" to ", wantName)));
Sema_EmitError(sema, stmt.line, stmt.column, msg); Sema_EmitError(sema, stmt.line, stmt.column, msg);
} }
} }
+9
View File
@@ -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));