Phase 5-7: generic inference, extend Type<T>, String stdlib, Map<K,V>, self-hosting audit, docs update

This commit is contained in:
2026-05-31 13:06:29 +03:00
parent 25f846bb00
commit 5c1a00cbd6
17 changed files with 1329 additions and 134 deletions
+125 -18
View File
@@ -173,21 +173,120 @@ Bux is a fast, compiled, strongly-typed, multi-paradigm systems programming lang
--- ---
## Phase 7 — Self-Hosting: The Great Rewrite (Week 19-26) ## Phase 6.5 — Self-Hosting Audit (Completed 2026-05-31)
### Source File Analysis
| File | Lines | Procs | Complexity | Bux Readiness |
|------|-------|-------|------------|---------------|
| `source_location.nim` | 8 | 0 | Trivial struct | ✅ Ready |
| `main.nim` | 6 | 0 | CLI entry | ✅ Ready |
| `scope.nim` | 47 | 4 | Simple | ✅ Ready |
| `manifest.nim` | 79 | 2 | TOML parser | ⚠️ Needs TOML/INI parser |
| `hir.nim` | 184 | 0 | Type defs | ✅ Ready |
| `types.nim` | 185 | 44 | Factories | ✅ Ready |
| `token.nim` | 305 | 12 | Enum + helpers | ✅ Ready |
| `cli.nim` | 390 | 15 | File I/O, process | ⚠️ Needs File I/O, path ops |
| `ast.nim` | 400 | 6 | Complex case-object | ✅ Ready (algebraic enums) |
| `c_backend.nim` | 519 | 16 | Code generation | ⚠️ Needs String formatting |
| `lexer.nim` | 567 | 37 | State machine | ⚠️ Needs String split/compare |
| `sema.nim` | 892 | 27 | Type checking | ⚠️ Needs Table[String,...] |
| `parser.nim` | 1220 | 81 | Pratt parser | ⚠️ Needs seq/array ops |
| `hir_lower.nim` | 1233 | 29 | Tree transform | ⚠️ Needs Table, HashSet |
### Nim Patterns → Bux Equivalents
| Nim Pattern | Used In | Bux Status |
|-------------|---------|------------|
| `Table[string, T]` | sema, hir_lower, c_backend (23 uses) | ❌ **Blocker** — need `StringMap<V>` |
| `HashSet[string]` | hir_lower (1 use) | ❌ Can use `StringMap<bool>` workaround |
| `seq[T]` with push/len/iter | All files (200+ uses) | ⚠️ `Array<T>` exists, needs richer API |
| `&"..."` / `fmt"..."` | sema, c_backend (119 uses) | ❌ **Blocker** — need string formatting |
| `split()`, `join()` | lexer, parser, cli | ❌ **Blocker** — need String split/join |
| `case obj.kind of...` | All files (90+ uses) | ✅ `match` with algebraic enums |
| `for x in collection` | All files (200+ uses) | ✅ Supported |
| `var` parameters | Multiple | ✅ Use pointers (`*T`) |
| File read/write | cli | ❌ Need `readFile`, `writeFile` |
| OS path operations | cli, manifest | ❌ Need path join, exists |
### Rewrite Order (Dependency-driven)
```
Phase 7.0 — Stdlib blockers:
├── StringMap<V> (blocker #1 — needed by all modules)
├── String split/join (blocker #2 — needed by lexer, parser)
├── String formatting (blocker #3 — needed by sema, c_backend)
├── File I/O (readFile, writeFile, fileExists)
└── OS path (joinPath, parentDir)
Phase 7.1 — Foundation (no internal deps):
├── token.bux (enum + helpers)
├── source_location.bux (struct)
├── types.bux (enum + factories)
├── scope.bux (symbol table — needs StringMap)
└── hir.bux (type definitions)
Phase 7.2 — Frontend (depends on 7.1):
├── lexer.bux (needs String split/compare)
├── ast.bux (algebraic enums)
└── parser.bux (Pratt parser, needs Array<T>)
Phase 7.3 — Analysis (depends on 7.2):
├── sema.bux (type checking, needs StringMap, formatting)
└── manifest.bux (TOML parser)
Phase 7.4 — Backend (depends on 7.3):
├── hir_lower.bux (tree transform, needs StringMap, HashSet)
└── c_backend.bux (code gen, needs String formatting)
Phase 7.5 — Driver (depends on all):
├── cli.bux (file I/O, argument parsing)
└── main.bux (entry point)
```
### Risk Assessment
| Risk | Severity | Mitigation |
|------|----------|------------|
| StringMap not working for String keys | **High** | Already have working `StringMap<V>` in stdlib using strcmp |
| `&key as *void` precedence bug | **Medium** | Workaround: use intermediate `*K` variable |
| Cross-module generics not working | **Medium** | All compiler code will be in one package (merged via stdlib mechanism) |
| `Map_Len` monomorphization bug | **Low** | Avoid calling `Map_Len` with explicit type args; inline the body |
| String formatting complexity | **Medium** | Use StringBuilder pattern instead of printf-style formatting |
| Array<T> API gaps | **Low** | Extend Array module as needed during porting |
### Estimated Effort
| Phase | Bux LOC | Effort |
|-------|---------|--------|
| 7.0 Stdlib blockers | ~300 | 1-2 sessions |
| 7.1 Foundation | ~600 | 1-2 sessions |
| 7.2 Frontend | ~1800 | 3-4 sessions |
| 7.3 Analysis | ~900 | 2-3 sessions |
| 7.4 Backend | ~1700 | 3-4 sessions |
| 7.5 Driver | ~400 | 1 session |
| **Total** | **~5700** | **11-16 sessions** |
---
## Phase 7 — Self-Hosting: The Great Rewrite
**Goal:** Bux compiler compiles itself. This is the **main milestone**. **Goal:** Bux compiler compiles itself. This is the **main milestone**.
| Task | Details | **Pre-requisites (Phase 7.0):** StringMap, String split/join, String formatting, File I/O must be working.
|------|---------|
| `7.1` Port lexer | Rewrite `lexer.nim``Lexer.bux` | | Task | Details | Deps |
| `7.2` Port parser | Rewrite `parser.nim``Parser.bux` | |------|---------|------|
| `7.3` Port sema | Rewrite `sema.nim``Sema.bux` | | `7.1` Port foundation | `token.bux`, `source_location.bux`, `types.bux`, `scope.bux`, `hir.bux` (~600 LOC) | StringMap |
| `7.4` Port HIR | Rewrite `hir.nim``Hir.bux` | | `7.2` Port lexer | `lexer.bux` — state machine, UTF-8, error reporting (~570 LOC) | String split/compare |
| `7.5` Port LIR | Rewrite `lir.nim` `Lir.bux` | | `7.3` Port AST + parser | `ast.bux` + `parser.bux` — Pratt parser, algebraic enums (~1620 LOC) | Array<T>, match |
| `7.6` Port C backend | Rewrite `c_backend.nim``CBackend.bux` | | `7.4` Port sema | `sema.bux` — type checking, symbol resolution (~890 LOC) | StringMap, formatting |
| `7.7` Port CLI | Rewrite `main.nim``Main.bux` | | `7.5` Port manifest | `manifest.bux` — TOML/bux.toml parser (~80 LOC) | File I/O, String split |
| `7.8` Dogfooding | Use `buxc` (Nim) to build `buxc2` (Bux). Then use `buxc2` to build `buxc3`. Compare bit-for-bit. | | `7.6` Port HIR lowering | `hir_lower.bux` — tree transformation (~1230 LOC) | StringMap, HashSet |
| `7.9` Fix bootstrap loop | Once `buxc2 == buxc3`, we are self-hosted. Freeze Nim version as reference. | | `7.7` Port C backend | `c_backend.bux` — C code generator (~520 LOC) | String formatting |
| `7.8` Port CLI | `cli.bux` + `main.bux` — command dispatch (~400 LOC) | File I/O, path ops |
| `7.9` Dogfooding | Use `buxc` (Nim) to build `buxc2` (Bux). Then use `buxc2` to build `buxc3`. Compare bit-for-bit. | All of above |
| `7.10` Fix bootstrap loop | Once `buxc2 == buxc3`, we are self-hosted. Freeze Nim version as reference. | 7.9 |
**Deliverable:** `make selfhost` succeeds; Bux compiler is written entirely in Bux. **Deliverable:** `make selfhost` succeeds; Bux compiler is written entirely in Bux.
@@ -522,13 +621,21 @@ func Main() -> int {
--- ---
## Next Immediate Steps ## Next Immediate Steps ✅ (Completed 2026-05-31)
1. **Inferred generic function calls**`UseBox(&b)` instead of `UseBox<int>(&b)` 1. **Inferred generic function calls**`Max(10, 20)` instead of `Max<int>(10, 20)`
2. **`extend Box<T>` syntax** — Parser support for generic impl blocks 2. **`extend Box<T>` syntax** — Parser support for generic impl blocks
3. **Std::String improvements** — String builder, slicing, interpolation 3. **Std::String improvements** — Slicing, trimming, contains, StringBuilder
4. **Std::Map generic**`Map<K,V>` with generic keys and values 4. **Std::Map generic**`Map<K,V>` with generic keys and values (value-type keys)
5. **Self-hosting preparation** — Audit Nim compiler code for Bux-rewrite feasibility 5. **Self-hosting audit** — See Phase 6.5 below
### Next Actions (Priority order)
1. **StringMap**`Map<String, V>` with strcmp-based key comparison (blocker #1)
2. **String split/join**`String_Split(s, sep)`, `String_Join(parts, sep)` for parsing
3. **String formatting** — Simple `String_Format(pattern, args...)` or `sb.Append(...)` pattern
4. **File I/O**`readFile`, `writeFile`, `fileExists` via C stdio
5. **Begin porting lexer** — Start with `token.bux` + `lexer.bux` (most self-contained modules)
--- ---
+40 -8
View File
@@ -302,23 +302,55 @@ extend Rectangle {
## Generics ## Generics
Generic functions are monomorphized at compile time: ### Generic Functions
Generic functions are monomorphized at compile time. Type parameters can be specified explicitly or inferred from arguments:
```bux ```bux
func Swap<T>(a: *T, b: *T) { func Max<T>(a: T, b: T) -> T {
let tmp: T = *a; if a > b { return a; }
*a = *b; return b;
*b = tmp;
} }
func Main() -> int { func Main() -> int {
let x: int = 1; // Explicit type args
let y: int = 2; let m1: int = Max<int>(10, 20);
Swap<int>(&x, &y);
// Type inference — T inferred as int from arguments
let m2: int = Max(10, 20);
return 0; return 0;
} }
``` ```
### Generic Structs
```bux
struct Box<T> {
value: T,
}
// Use extend Type<T> for methods on generic structs
extend Box<T> {
func Get(self: *Box<T>) -> T {
return self.value;
}
func Set(self: *Box<T>, value: T) {
self.value = value;
}
}
func Main() -> int {
let b: Box<int> = Box<int> { value: 42 };
PrintInt(b.Get()); // 42
b.Set(100);
PrintInt(b.Get()); // 100
return 0;
}
```
> **Note:** `extend Type<T>` syntax requires type parameters on the impl block. The compiler propagates them to each method automatically.
--- ---
## Error Handling ## Error Handling
+168 -57
View File
@@ -1,12 +1,12 @@
# Bux Standard Library # Bux Standard Library
The Bux standard library provides core functionality for systems programming. It is intentionally minimal for the bootstrap phase and will grow as the language matures. The Bux standard library provides core functionality for systems programming. All modules are merged into every compilation, so no explicit linking is needed.
--- ---
## Std::Io ## Std::Io
Basic input/output operations wrapping C stdio. Basic I/O and file operations wrapping C stdio.
### Functions ### Functions
@@ -18,14 +18,17 @@ Basic input/output operations wrapping C stdio.
| `PrintFloat` | `func PrintFloat(f: float64)` | Print float | | `PrintFloat` | `func PrintFloat(f: float64)` | Print float |
| `PrintBool` | `func PrintBool(b: bool)` | Print boolean | | `PrintBool` | `func PrintBool(b: bool)` | Print boolean |
| `ReadLine` | `func ReadLine() -> String` | Read line from stdin | | `ReadLine` | `func ReadLine() -> String` | Read line from stdin |
| `ReadFile` | `func ReadFile(path: String) -> String` | Read entire file into string |
| `WriteFile` | `func WriteFile(path: String, content: String) -> bool` | Write string to file |
| `FileExists` | `func FileExists(path: String) -> bool` | Check if file exists |
### Example ### Example
```bux ```bux
import Std::Io::{PrintLine, PrintInt}; import Std::Io::{PrintLine, ReadFile, WriteFile};
func Main() -> int { func Main() -> int {
PrintLine("Hello, World!"); WriteFile("/tmp/test.txt", "Hello, Bux!");
PrintInt(42); PrintLine(ReadFile("/tmp/test.txt"));
return 0; return 0;
} }
``` ```
@@ -34,13 +37,12 @@ func Main() -> int {
## Std::Array ## Std::Array
Dynamic array of integers (currently hardcoded for `int`). Fully generic dynamic array `Array<T>`.
### Types ### Types
```bux ```bux
struct Array { struct Array<T> {
data: *int, data: *T,
len: uint, len: uint,
cap: uint, cap: uint,
} }
@@ -50,22 +52,22 @@ struct Array {
| Function | Signature | Description | | Function | Signature | Description |
|----------|-----------|-------------| |----------|-----------|-------------|
| `Array_New` | `func Array_New(cap: uint) -> Array` | Create new array with capacity | | `Array_New<T>` | `func Array_New<T>(cap: uint) -> Array<T>` | Create new array |
| `Array_Push` | `func Array_Push(arr: *Array, value: int)` | Append element | | `Array_Push<T>` | `func Array_Push<T>(arr: *Array<T>, value: T)` | Append element |
| `Array_Get` | `func Array_Get(arr: *Array, index: uint) -> int` | Get element at index | | `Array_Get<T>` | `func Array_Get<T>(arr: *Array<T>, index: uint) -> T` | Get element at index |
| `Array_Len` | `func Array_Len(arr: *Array) -> uint` | Get length | | `Array_Len<T>` | `func Array_Len<T>(arr: *Array<T>) -> uint` | Get length |
| `Array_Free` | `func Array_Free(arr: *Array)` | Free memory | | `Array_Free<T>` | `func Array_Free<T>(arr: *Array<T>)` | Free memory |
### Example ### Example
```bux ```bux
import Std::Array::{Array, Array_New, Array_Push, Array_Get}; import Std::Array::{Array, Array_New, Array_Push, Array_Get};
func Main() -> int { func Main() -> int {
let arr: Array = Array_New(4); let arr: Array<int> = Array_New<int>(4);
Array_Push(&arr, 10); Array_Push<int>(&arr, 10);
Array_Push(&arr, 20); Array_Push<int>(&arr, 20);
PrintInt(Array_Get(&arr, 0)); // 10 PrintInt(Array_Get<int>(&arr, 0)); // 10
Array_Free(&arr); Array_Free<int>(&arr);
return 0; return 0;
} }
``` ```
@@ -74,28 +76,81 @@ func Main() -> int {
## Std::String ## Std::String
String manipulation utilities for the built-in `String` type (`const char*` in C). String manipulation utilities.
### Core Functions
| Function | Signature | Description |
|----------|-----------|-------------|
| `String_Len` | `func String_Len(s: String) -> uint` | Length of string |
| `String_Eq` | `func String_Eq(a: String, b: String) -> bool` | Compare strings |
| `String_Concat` | `func String_Concat(a: String, b: String) -> String` | Concatenate (allocates) |
| `String_Copy` | `func String_Copy(s: String) -> String` | Copy string (allocates) |
| `String_StartsWith` | `func String_StartsWith(s: String, prefix: String) -> bool` | Check prefix |
| `String_EndsWith` | `func String_EndsWith(s: String, suffix: String) -> bool` | Check suffix |
| `String_Contains` | `func String_Contains(s: String, substr: String) -> bool` | Check substring |
### Slicing & Trimming
| Function | Signature | Description |
|----------|-----------|-------------|
| `String_Slice` | `func String_Slice(s: String, start: uint, len: uint) -> String` | Extract substring |
| `String_Trim` | `func String_Trim(s: String) -> String` | Trim both sides |
| `String_TrimLeft` | `func String_TrimLeft(s: String) -> String` | Trim left side |
| `String_TrimRight` | `func String_TrimRight(s: String) -> String` | Trim right side |
### Split & Join
| Function | Signature | Description |
|----------|-----------|-------------|
| `String_SplitCount` | `func String_SplitCount(s: String, delim: String) -> uint` | Count parts |
| `String_SplitPart` | `func String_SplitPart(s: String, delim: String, index: uint) -> String` | Get nth part (0-indexed) |
| `String_Join2` | `func String_Join2(a: String, b: String, sep: String) -> String` | Join two strings with separator |
### Conversion
| Function | Signature | Description |
|----------|-----------|-------------|
| `String_FromInt` | `func String_FromInt(n: int64) -> String` | Int to string |
| `String_ToInt` | `func String_ToInt(s: String) -> int64` | String to int |
---
## Std::StringBuilder
Efficient string construction using a dynamic buffer.
### Types
```bux
struct StringBuilder {
handle: *void,
}
```
### Functions ### Functions
| Function | Signature | Description | | Function | Signature | Description |
|----------|-----------|-------------| |----------|-----------|-------------|
| `String_Len` | `func String_Len(s: String) -> uint` | Length of string (wraps `strlen`) | | `StringBuilder_New` | `func StringBuilder_New() -> StringBuilder` | Create with default capacity (64) |
| `String_Eq` | `func String_Eq(a: String, b: String) -> bool` | Compare strings for equality | | `StringBuilder_NewCap` | `func StringBuilder_NewCap(cap: uint) -> StringBuilder` | Create with custom capacity |
| `String_Concat` | `func String_Concat(a: String, b: String) -> String` | Concatenate two strings (allocates) | | `StringBuilder_Append` | `func StringBuilder_Append(sb: *StringBuilder, s: String)` | Append string |
| `String_Copy` | `func String_Copy(s: String) -> String` | Copy string (allocates) | | `StringBuilder_AppendInt` | `func StringBuilder_AppendInt(sb: *StringBuilder, n: int64)` | Append integer |
| `String_StartsWith` | `func String_StartsWith(s: String, prefix: String) -> bool` | Check prefix | | `StringBuilder_AppendFloat` | `func StringBuilder_AppendFloat(sb: *StringBuilder, f: float64)` | Append float |
| `StringBuilder_AppendChar` | `func StringBuilder_AppendChar(sb: *StringBuilder, c: char8)` | Append char |
| `StringBuilder_Build` | `func StringBuilder_Build(sb: *StringBuilder) -> String` | Build result string |
| `StringBuilder_Free` | `func StringBuilder_Free(sb: *StringBuilder)` | Free memory |
### Example ### Example
```bux ```bux
import Std::String::{String_Len, String_Concat, String_Eq}; import Std::String::{StringBuilder, StringBuilder_New, StringBuilder_Append, StringBuilder_AppendInt, StringBuilder_Build, StringBuilder_Free};
func Main() -> int { func Main() -> int {
let hello: String = "Hello"; let sb: StringBuilder = StringBuilder_New();
let world: String = "World"; StringBuilder_Append(&sb, "Hello, ");
let greeting: String = String_Concat(hello, ", "); StringBuilder_Append(&sb, "World! #");
let full: String = String_Concat(greeting, world); StringBuilder_AppendInt(&sb, 42);
PrintLine(full); // "Hello, World" PrintLine(StringBuilder_Build(&sb)); // "Hello, World! #42"
StringBuilder_Free(&sb);
return 0; return 0;
} }
``` ```
@@ -104,19 +159,18 @@ func Main() -> int {
## Std::Map ## Std::Map
Hash map with `String` keys and `int` values using linear probing. Generic hash map `Map<K, V>` for value-type keys (int, float, etc.).
### Types ### Types
```bux ```bux
struct MapEntry { struct MapEntry<K, V> {
key: String, key: K,
value: int, value: V,
occupied: bool, occupied: bool,
} }
struct Map { struct Map<K, V> {
entries: *MapEntry, entries: *MapEntry<K, V>,
cap: uint, cap: uint,
len: uint, len: uint,
} }
@@ -126,24 +180,81 @@ struct Map {
| Function | Signature | Description | | Function | Signature | Description |
|----------|-----------|-------------| |----------|-----------|-------------|
| `Map_New` | `func Map_New(cap: uint) -> Map` | Create new map with given capacity | | `Map_New<K,V>` | `func Map_New<K,V>(cap: uint) -> Map<K,V>` | Create map |
| `Map_Set` | `func Map_Set(m: *Map, key: String, value: int)` | Insert or update key | | `Map_Set<K,V>` | `func Map_Set<K,V>(m: *Map<K,V>, key: K, value: V)` | Insert/update |
| `Map_Get` | `func Map_Get(m: *Map, key: String) -> int` | Get value by key (0 if missing) | | `Map_Get<K,V>` | `func Map_Get<K,V>(m: *Map<K,V>, key: K) -> V` | Get value (zero if missing) |
| `Map_Has` | `func Map_Has(m: *Map, key: String) -> bool` | Check if key exists | | `Map_Has<K,V>` | `func Map_Has<K,V>(m: *Map<K,V>, key: K) -> bool` | Check key exists |
| `Map_Len` | `func Map_Len(m: *Map) -> uint` | Number of entries | | `Map_Len<K,V>` | `func Map_Len<K,V>(m: *Map<K,V>) -> uint` | Entry count |
| `Map_Free` | `func Map_Free(m: *Map)` | Free memory | | `Map_Free<K,V>` | `func Map_Free<K,V>(m: *Map<K,V>)` | Free memory |
### Example ### Example
```bux ```bux
import Std::Map::{Map, Map_New, Map_Set, Map_Get, Map_Has}; import Std::Map::{Map, Map_New, Map_Set, Map_Get, Map_Free};
func Main() -> int { func Main() -> int {
let m: Map = Map_New(16); let m: Map<int, String> = Map_New<int, String>(16);
Map_Set(&m, "one", 1); Map_Set<int, String>(&m, 1, "one");
Map_Set(&m, "two", 2); Map_Set<int, String>(&m, 2, "two");
PrintInt(Map_Get(&m, "one")); // 1 PrintLine(Map_Get<int, String>(&m, 1)); // "one"
PrintInt(Map_Get(&m, "three")); // 0 Map_Free<int, String>(&m);
Map_Free(&m); return 0;
}
```
> **Note:** For `String` keys, use `StringMap<V>` below which uses `strcmp` for key comparison.
---
## Std::StringMap
Specialized hash map for `String` keys with any value type.
### Types
```bux
struct StringMapEntry<V> {
key: String,
value: V,
occupied: bool,
}
struct StringMap<V> {
entries: *StringMapEntry<V>,
cap: uint,
len: uint,
}
```
### Functions
| Function | Signature | Description |
|----------|-----------|-------------|
| `StringMap_New<V>` | `func StringMap_New<V>(cap: uint) -> StringMap<V>` | Create map |
| `StringMap_Set<V>` | `func StringMap_Set<V>(m: *StringMap<V>, key: String, value: V)` | Insert/update |
| `StringMap_Get<V>` | `func StringMap_Get<V>(m: *StringMap<V>, key: String) -> V` | Get value |
| `StringMap_Has<V>` | `func StringMap_Has<V>(m: *StringMap<V>, key: String) -> bool` | Check key exists |
| `StringMap_Len<V>` | `func StringMap_Len<V>(m: *StringMap<V>) -> uint` | Entry count |
| `StringMap_Free<V>` | `func StringMap_Free<V>(m: *StringMap<V>)` | Free memory |
---
## Std::Path
Path manipulation utilities.
| Function | Signature | Description |
|----------|-----------|-------------|
| `Path_Join` | `func Path_Join(a: String, b: String) -> String` | Join path segments |
| `Path_Parent` | `func Path_Parent(path: String) -> String` | Get parent directory |
| `Path_Ext` | `func Path_Ext(path: String) -> String` | Get file extension |
### Example
```bux
import Std::Path::{Path_Join, Path_Parent, Path_Ext};
func Main() -> int {
PrintLine(Path_Join("/home", "docs")); // "/home/docs"
PrintLine(Path_Parent("/a/b/c.txt")); // "/a/b"
PrintLine(Path_Ext("main.bux")); // ".bux"
return 0; return 0;
} }
``` ```
@@ -156,19 +267,19 @@ These C functions are provided by `runtime.c` and are available via `extern` dec
| Function | Signature | Description | | Function | Signature | Description |
|----------|-----------|-------------| |----------|-----------|-------------|
| `bux_alloc` | `func bux_alloc(size: uint) -> *void` | Allocate memory (wraps `malloc`) | | `bux_alloc` | `func bux_alloc(size: uint) -> *void` | Allocate memory |
| `bux_realloc` | `func bux_realloc(ptr: *void, size: uint) -> *void` | Reallocate memory | | `bux_realloc` | `func bux_realloc(ptr: *void, size: uint) -> *void` | Reallocate memory |
| `bux_free` | `func bux_free(ptr: *void)` | Free memory | | `bux_free` | `func bux_free(ptr: *void)` | Free memory |
| `bux_bounds_check` | `func bux_bounds_check(index: uint, len: uint)` | Panic on out-of-bounds | | `bux_bounds_check` | `func bux_bounds_check(index: uint, len: uint)` | Panic on OOB |
| `bux_hash_string` | `func bux_hash_string(s: String) -> uint` | DJB2 hash for strings |
| `bux_hash_bytes` | `func bux_hash_bytes(ptr: *void, size: uint) -> uint` | DJB2 hash for raw bytes |
--- ---
## Future Modules ## Future Modules
Planned for future phases: - `Std::Result` — Shipped via algebraic enums ✅
- `Std::Option` — Shipped via algebraic enums ✅
- `Std::Result` — Generic `Result<T, E>` with `?` operator support
- `Std::Option` — Generic `Option<T>`
- `Std::Math``Sqrt`, `Pow`, `Min`, `Max`, `Abs` - `Std::Math``Sqrt`, `Pow`, `Min`, `Max`, `Abs`
- `Std::Os``Args`, `Env`, `Exit`, `Cwd` - `Std::Os``Args`, `Env`, `Exit`, `Cwd`
- `Std::Fmt` — String formatting with interpolation - `Std::Fmt` — String formatting with interpolation
+30
View File
@@ -0,0 +1,30 @@
// Generic impl blocks — extend Box<T> with methods
import Std::Io::{PrintLine, PrintInt};
struct Box<T> {
value: T,
}
extend Box<T> {
func Get(self: *Box<T>) -> T {
return self.value;
}
func Set(self: *Box<T>, value: T) {
self.value = value;
}
}
func Main() -> int {
let b: Box<int> = Box<int> { value: 42 };
PrintLine("Box value:");
PrintInt(b.Get());
PrintLine("");
b.Set(100);
PrintLine("Box after Set(100):");
PrintInt(b.Get());
PrintLine("");
return 0;
}
+26
View File
@@ -0,0 +1,26 @@
// Generic function type inference — calls without explicit type args
import Std::Io::{PrintLine, PrintInt};
func Max<T>(a: T, b: T) -> T {
if a > b {
return a;
} else {
return b;
}
}
func Main() -> int {
// Inferred: T = int
let m1: int = Max(10, 20);
let m2: int = Max(5, 3);
PrintLine("Max(10, 20) = ");
PrintInt(m1);
PrintLine("");
PrintLine("Max(5, 3) = ");
PrintInt(m2);
PrintLine("");
return 0;
}
+23
View File
@@ -0,0 +1,23 @@
// Generic function type inference — multiple type parameters
import Std::Io::{PrintLine, PrintInt};
struct Pair<T, U> {
first: T,
second: U,
}
func MakePair<T, U>(a: T, b: U) -> Pair<T, U> {
return Pair<T, U> { first: a, second: b };
}
func Pair_GetFirst<T, U>(self: *Pair<T, U>) -> T {
return self.first;
}
func Main() -> int {
// Inferred: T = int, U = String
let p: Pair<int, String> = MakePair(42, "hello");
PrintInt(p.GetFirst());
PrintLine("");
return 0;
}
+18 -32
View File
@@ -1,44 +1,30 @@
// Map - Hash map with String keys and int values // Generic Map<K, V> — uses Std::Map (stdlib is merged into module)
import Std::Io::{PrintLine, PrintInt}; import Std::Io::{PrintLine, PrintInt};
import Std::Map::{Map, Map_New, Map_Set, Map_Get, Map_Has, Map_Len}; import Std::Map::{Map, Map_New, Map_Set, Map_Get, Map_Has, Map_Free};
func Main() -> int { func Main() -> int {
let m: Map = Map_New(16); PrintLine("=== Map<int, String> ===");
let m: Map<int, String> = Map_New<int, String>(16);
PrintLine("Map operations:"); Map_Set<int, String>(&m, 1, "one");
Map_Set<int, String>(&m, 2, "two");
Map_Set<int, String>(&m, 3, "three");
Map_Set(&m, "one", 1); PrintLine("Get 1:");
Map_Set(&m, "two", 2); PrintLine(Map_Get<int, String>(&m, 1));
Map_Set(&m, "three", 3);
PrintLine("Length:"); PrintLine("Get 2:");
PrintInt(Map_Len(&m)); PrintLine(Map_Get<int, String>(&m, 2));
PrintLine("");
PrintLine("Get 'one':"); if Map_Has<int, String>(&m, 1) {
PrintInt(Map_Get(&m, "one")); PrintLine("Has 1: yes");
PrintLine("");
PrintLine("Get 'two':");
PrintInt(Map_Get(&m, "two"));
PrintLine("");
PrintLine("Get 'three':");
PrintInt(Map_Get(&m, "three"));
PrintLine("");
Map_Set(&m, "two", 22);
PrintLine("Updated 'two':");
PrintInt(Map_Get(&m, "two"));
PrintLine("");
if Map_Has(&m, "one") {
PrintLine("Has 'one': yes");
} }
if !Map_Has(&m, "four") { // Test update
PrintLine("Has 'four': no"); Map_Set<int, String>(&m, 2, "updated");
} PrintLine("Updated 2:");
PrintLine(Map_Get<int, String>(&m, 2));
Map_Free<int, String>(&m);
return 0; return 0;
} }
+74
View File
@@ -0,0 +1,74 @@
// String improvements — slicing, trimming, contains, StringBuilder
import Std::Io::{Print, PrintLine, PrintInt, PrintBool};
import Std::String::{String_Len, String_Slice, String_Contains, String_EndsWith, String_StartsWith};
import Std::String::{String_Trim, String_TrimLeft, String_TrimRight, String_FromInt, String_ToInt};
import Std::String::{StringBuilder, StringBuilder_New, StringBuilder_Append, StringBuilder_AppendInt, StringBuilder_Build, StringBuilder_Free};
func Main() -> int {
// --- Slicing ---
let hello: String = "Hello, World!";
Print("Slice(0,5): ");
PrintLine(String_Slice(hello, 0, 5));
Print("Slice(7,5): ");
PrintLine(String_Slice(hello, 7, 5));
// --- Contains ---
Print("Contains 'World': ");
PrintBool(String_Contains(hello, "World"));
PrintLine("");
Print("Contains 'xyz': ");
PrintBool(String_Contains(hello, "xyz"));
PrintLine("");
// --- EndsWith ---
Print("EndsWith '!': ");
PrintBool(String_EndsWith(hello, "!"));
PrintLine("");
// --- StartsWith ---
Print("StartsWith 'Hello': ");
PrintBool(String_StartsWith(hello, "Hello"));
PrintLine("");
// --- Trim ---
let spaced: String = " foo bar ";
Print("Trim: '");
Print(String_Trim(spaced));
PrintLine("'");
Print("TrimLeft: '");
Print(String_TrimLeft(spaced));
PrintLine("'");
Print("TrimRight: '");
Print(String_TrimRight(spaced));
PrintLine("'");
// --- Int conversion ---
Print("FromInt(42): ");
PrintLine(String_FromInt(42));
Print("FromInt(-100): ");
PrintLine(String_FromInt(-100));
let n: int64 = String_ToInt("123");
Print("ToInt('123'): ");
PrintInt(n as int);
PrintLine("");
// --- StringBuilder ---
let sb: StringBuilder = StringBuilder_New();
StringBuilder_Append(&sb, "Hello");
StringBuilder_Append(&sb, ", ");
StringBuilder_Append(&sb, "World");
StringBuilder_Append(&sb, "! #");
StringBuilder_AppendInt(&sb, 42);
Print("StringBuilder: ");
PrintLine(StringBuilder_Build(&sb));
StringBuilder_Free(&sb);
return 0;
}
+2
View File
@@ -162,6 +162,7 @@ type
of ekCall: of ekCall:
exprCallCallee*: Expr exprCallCallee*: Expr
exprCallArgs*: seq[Expr] exprCallArgs*: seq[Expr]
exprCallInferredTypeArgs*: seq[TypeExpr] ## filled by sema for inferred generic calls
of ekGenericCall: of ekGenericCall:
exprGenericCallee*: string exprGenericCallee*: string
exprGenericTypeArgs*: seq[TypeExpr] exprGenericTypeArgs*: seq[TypeExpr]
@@ -337,6 +338,7 @@ type
declInterfaceMethods*: seq[Decl] ## FuncDecl signatures only declInterfaceMethods*: seq[Decl] ## FuncDecl signatures only
of dkImpl: of dkImpl:
declImplTypeName*: string declImplTypeName*: string
declImplTypeParams*: seq[string] ## type parameters for generic impl: extend Box<T>
declImplInterface*: string ## empty if not for interface declImplInterface*: string ## empty if not for interface
declImplMethods*: seq[Decl] declImplMethods*: seq[Decl]
of dkModule: of dkModule:
+43
View File
@@ -508,6 +508,32 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
args.add(ctx.lowerExpr(arg)) args.add(ctx.lowerExpr(arg))
return hirCall(mangledName, args, typ, loc) return hirCall(mangledName, args, typ, loc)
# Inferred generic function call: Max(10, 20) → Max_int(10, 20)
if expr.exprCallInferredTypeArgs.len > 0:
var calleeName = ""
case expr.exprCallCallee.kind
of ekIdent:
calleeName = expr.exprCallCallee.exprIdent
if ctx.importTable.hasKey(calleeName):
calleeName = ctx.importTable[calleeName]
of ekPath:
calleeName = expr.exprCallCallee.exprPath.join("_")
else: discard
if calleeName != "":
var typeSuffix = ""
for i, targ in expr.exprCallInferredTypeArgs:
if i > 0:
typeSuffix.add("_")
if targ.kind == tekNamed:
typeSuffix.add(targ.typeName)
else:
typeSuffix.add("unknown")
let mangledName = calleeName & "_" & typeSuffix
var args: seq[HirNode] = @[]
for arg in expr.exprCallArgs:
args.add(ctx.lowerExpr(arg))
return hirCall(mangledName, args, typ, loc)
# Regular function call # Regular function call
var calleeName = "" var calleeName = ""
if expr.exprCallCallee.kind == ekIdent: if expr.exprCallCallee.kind == ekIdent:
@@ -1004,6 +1030,12 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
ctx.genericFuncs[decl.declFuncName] = decl ctx.genericFuncs[decl.declFuncName] = decl
if decl.kind == dkStruct and decl.declStructTypeParams.len > 0: if decl.kind == dkStruct and decl.declStructTypeParams.len > 0:
ctx.genericStructs[decl.declStructName] = decl ctx.genericStructs[decl.declStructName] = decl
if decl.kind == dkImpl and decl.declImplTypeParams.len > 0:
let typeName = decl.declImplTypeName
for methodDecl in decl.declImplMethods:
if methodDecl.kind == dkFunc:
let mangledName = typeName & "_" & methodDecl.declFuncName
ctx.genericFuncs[mangledName] = methodDecl
# Second pass: find all generic calls and monomorphize # Second pass: find all generic calls and monomorphize
proc findGenericCalls(expr: Expr): seq[tuple[name: string, typeArgs: seq[TypeExpr]]] = proc findGenericCalls(expr: Expr): seq[tuple[name: string, typeArgs: seq[TypeExpr]]] =
@@ -1013,6 +1045,14 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
of ekCall: of ekCall:
if expr.exprCallCallee.kind == ekGenericCall: if expr.exprCallCallee.kind == ekGenericCall:
result.add((expr.exprCallCallee.exprGenericCallee, expr.exprCallCallee.exprGenericTypeArgs)) result.add((expr.exprCallCallee.exprGenericCallee, expr.exprCallCallee.exprGenericTypeArgs))
elif expr.exprCallInferredTypeArgs.len > 0:
var calleeName = ""
case expr.exprCallCallee.kind
of ekIdent: calleeName = expr.exprCallCallee.exprIdent
of ekPath: calleeName = expr.exprCallCallee.exprPath.join("::")
else: discard
if calleeName != "":
result.add((calleeName, expr.exprCallInferredTypeArgs))
result.add(findGenericCalls(expr.exprCallCallee)) result.add(findGenericCalls(expr.exprCallCallee))
for arg in expr.exprCallArgs: for arg in expr.exprCallArgs:
result.add(findGenericCalls(arg)) result.add(findGenericCalls(arg))
@@ -1126,6 +1166,9 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
of dkImpl: of dkImpl:
for methodDecl in decl.declImplMethods: for methodDecl in decl.declImplMethods:
if methodDecl.kind == dkFunc: if methodDecl.kind == dkFunc:
# Skip generic methods — they are monomorphized via generateMethodInstance
if methodDecl.declFuncTypeParams.len > 0:
continue
var hf = ctx.lowerFunc(methodDecl) var hf = ctx.lowerFunc(methodDecl)
hf.name = decl.declImplTypeName & "_" & hf.name hf.name = decl.declImplTypeName & "_" & hf.name
funcs.add(hf) funcs.add(hf)
+2
View File
@@ -1015,6 +1015,7 @@ proc parseImplDecl(p: var Parser): Decl =
let loc = p.currentLoc let loc = p.currentLoc
discard p.expect(tkExtend, "expected 'extend'") discard p.expect(tkExtend, "expected 'extend'")
let typeName = p.expect(tkIdent, "expected type name").text let typeName = p.expect(tkIdent, "expected type name").text
let typeParams = p.parseTypeParams()
var interfaceName = "" var interfaceName = ""
if p.check(tkFor): if p.check(tkFor):
discard p.advance() discard p.advance()
@@ -1030,6 +1031,7 @@ proc parseImplDecl(p: var Parser): Decl =
methods.add(p.parseFuncDecl(false, false, ParsedAttrs())) methods.add(p.parseFuncDecl(false, false, ParsedAttrs()))
discard p.expect(tkRBrace, "expected '}' to close impl block") discard p.expect(tkRBrace, "expected '}' to close impl block")
return Decl(kind: dkImpl, loc: loc, declImplTypeName: typeName, return Decl(kind: dkImpl, loc: loc, declImplTypeName: typeName,
declImplTypeParams: typeParams,
declImplInterface: interfaceName, declImplMethods: methods) declImplInterface: interfaceName, declImplMethods: methods)
proc parseModuleDecl(p: var Parser, isPublic: bool): Decl = proc parseModuleDecl(p: var Parser, isPublic: bool): Decl =
+135
View File
@@ -47,6 +47,89 @@ proc hasErrors*(res: SemaResult): bool =
return true return true
return false return false
# ---------------------------------------------------------------------------
# Generic type inference helpers
# ---------------------------------------------------------------------------
proc typeExprReferencesTypeParam(te: TypeExpr, name: string): bool =
## Recursively check if a TypeExpr tree references a given type parameter name.
if te == nil: return false
case te.kind
of tekNamed:
if te.typeName == name: return true
for arg in te.typeArgs:
if typeExprReferencesTypeParam(arg, name): return true
of tekPath:
return false
of tekSlice:
return typeExprReferencesTypeParam(te.sliceElement, name)
of tekPointer:
return typeExprReferencesTypeParam(te.pointerPointee, name)
of tekTuple:
for elem in te.tupleElements:
if typeExprReferencesTypeParam(elem, name): return true
of tekSelf:
return false
proc typeToTypeExpr(t: Type): TypeExpr =
## Convert a resolved Type back to a TypeExpr for storage in inferred type args.
case t.kind
of tkInt: TypeExpr(kind: tekNamed, typeName: "int")
of tkInt8: TypeExpr(kind: tekNamed, typeName: "int8")
of tkInt16: TypeExpr(kind: tekNamed, typeName: "int16")
of tkInt32: TypeExpr(kind: tekNamed, typeName: "int32")
of tkInt64: TypeExpr(kind: tekNamed, typeName: "int64")
of tkUInt: TypeExpr(kind: tekNamed, typeName: "uint")
of tkUInt8: TypeExpr(kind: tekNamed, typeName: "uint8")
of tkUInt16: TypeExpr(kind: tekNamed, typeName: "uint16")
of tkUInt32: TypeExpr(kind: tekNamed, typeName: "uint32")
of tkUInt64: TypeExpr(kind: tekNamed, typeName: "uint64")
of tkFloat32: TypeExpr(kind: tekNamed, typeName: "float32")
of tkFloat64: TypeExpr(kind: tekNamed, typeName: "float64")
of tkBool: TypeExpr(kind: tekNamed, typeName: "bool")
of tkStr: TypeExpr(kind: tekNamed, typeName: "String")
of tkNamed: TypeExpr(kind: tekNamed, typeName: t.name)
of tkPointer:
if t.inner.len > 0:
TypeExpr(kind: tekPointer, pointerPointee: typeToTypeExpr(t.inner[0]))
else:
TypeExpr(kind: tekNamed, typeName: "void")
of tkVoid: TypeExpr(kind: tekNamed, typeName: "void")
else: TypeExpr(kind: tekNamed, typeName: t.toString)
proc inferTypeArgs(sema: var Sema, funcDecl: Decl, argTypes: seq[Type],
loc: SourceLocation): seq[TypeExpr] =
## Infer type arguments from argument types for a generic function call.
## Returns empty seq if inference fails for any type parameter.
result = @[]
for tpName in funcDecl.declFuncTypeParams:
var inferred: Type = nil
for i, param in funcDecl.declFuncParams:
if i >= argTypes.len: break
# Skip pointer params — type param is inside the pointee and we cannot
# structurally extract it (e.g., *Map<K,V> → arg is *Map<int,String>)
if param.ptype.kind == tekPointer:
continue
if typeExprReferencesTypeParam(param.ptype, tpName):
if inferred == nil:
inferred = argTypes[i]
elif inferred != argTypes[i]:
# Check if one is assignable to the other (wider type wins)
if argTypes[i].isAssignableTo(inferred):
discard # inferred stays the same
elif inferred.isAssignableTo(argTypes[i]):
inferred = argTypes[i]
else:
sema.emitError(loc,
&"conflicting types for type parameter '{tpName}': " &
&"{inferred.toString} vs {argTypes[i].toString}")
return @[]
if inferred != nil and not inferred.isUnknown:
result.add(typeToTypeExpr(inferred))
else:
# Cannot infer this type parameter from arguments
return @[]
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Type resolution from AST TypeExpr # Type resolution from AST TypeExpr
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -229,10 +312,19 @@ proc collectGlobals*(sema: var Sema) =
of dkImpl: of dkImpl:
# Register methods for the type # Register methods for the type
let typeName = decl.declImplTypeName let typeName = decl.declImplTypeName
let implTypeParams = decl.declImplTypeParams
if not sema.methodTable.hasKey(typeName): if not sema.methodTable.hasKey(typeName):
sema.methodTable[typeName] = @[] sema.methodTable[typeName] = @[]
# If impl has type params, temporarily add them to type table
var addedTypeParams: seq[string] = @[]
for tp in implTypeParams:
sema.typeTable[tp] = makeTypeParam(tp)
addedTypeParams.add(tp)
for methodDecl in decl.declImplMethods: for methodDecl in decl.declImplMethods:
if methodDecl.kind == dkFunc: if methodDecl.kind == dkFunc:
# Propagate impl type params to method for HIR lowering
if implTypeParams.len > 0:
methodDecl.declFuncTypeParams = implTypeParams
var params: seq[Type] = @[] var params: seq[Type] = @[]
for p in methodDecl.declFuncParams: for p in methodDecl.declFuncParams:
params.add(sema.resolveType(p.ptype)) params.add(sema.resolveType(p.ptype))
@@ -252,7 +344,13 @@ proc collectGlobals*(sema: var Sema) =
let sym = Symbol(kind: skFunc, name: mangledName, decl: methodDecl, let sym = Symbol(kind: skFunc, name: mangledName, decl: methodDecl,
isPublic: true) isPublic: true)
sym.typ = makeFunc(params, retType) sym.typ = makeFunc(params, retType)
if implTypeParams.len > 0:
# Register as generic function for monomorphization
sym.decl = methodDecl
discard sema.globalScope.define(sym) discard sema.globalScope.define(sym)
# Clean up type parameters
for tp in addedTypeParams:
sema.typeTable.del(tp)
else: else:
discard discard
@@ -485,6 +583,39 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
for i in 0 ..< argTypes.len: for i in 0 ..< argTypes.len:
if not argTypes[i].isAssignableTo(expectedParams[i]) and not (argTypes[i].kind in {TypeKind.tkUnknown, TypeKind.tkNamed, TypeKind.tkTypeParam}): if not argTypes[i].isAssignableTo(expectedParams[i]) and not (argTypes[i].kind in {TypeKind.tkUnknown, TypeKind.tkNamed, TypeKind.tkTypeParam}):
sema.emitError(expr.loc, &"argument {i+1}: expected {expectedParams[i].toString}, got {argTypes[i].toString}") sema.emitError(expr.loc, &"argument {i+1}: expected {expectedParams[i].toString}, got {argTypes[i].toString}")
# Check for inferred generic function call (no explicit type args)
var calleeDecl: Decl = nil
case expr.exprCallCallee.kind
of ekIdent:
let sym = scope.lookup(expr.exprCallCallee.exprIdent)
if sym != nil: calleeDecl = sym.decl
of ekPath:
let fullName = expr.exprCallCallee.exprPath.join("::")
let sym = scope.lookup(fullName)
if sym != nil: calleeDecl = sym.decl
else: discard
if calleeDecl != nil and calleeDecl.kind == dkFunc and
calleeDecl.declFuncTypeParams.len > 0 and
expr.exprCallInferredTypeArgs.len == 0 and
expr.exprCallCallee.kind != ekGenericCall:
let inferred = sema.inferTypeArgs(calleeDecl, argTypes, expr.loc)
if inferred.len == calleeDecl.declFuncTypeParams.len:
expr.exprCallInferredTypeArgs = inferred
# Substitute return type using inferred type args
if calleeDecl.declFuncReturnType != nil:
var added: seq[string] = @[]
for i, tp in calleeDecl.declFuncTypeParams:
if i < inferred.len:
let concrete = sema.resolveType(inferred[i])
sema.typeTable[tp] = concrete
added.add(tp)
let retType = sema.resolveType(calleeDecl.declFuncReturnType)
for tp in added:
sema.typeTable.del(tp)
return retType
return calleeType.inner[^1] return calleeType.inner[^1]
elif calleeType.kind == tkUnknown: elif calleeType.kind == tkUnknown:
return makeUnknown() return makeUnknown()
@@ -708,6 +839,10 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type =
proc checkFunc(sema: var Sema, decl: Decl) = proc checkFunc(sema: var Sema, decl: Decl) =
if decl.declFuncBody == nil: if decl.declFuncBody == nil:
return return
# Skip body type-checking for generic functions — their bodies contain
# type parameters that cannot be fully resolved until monomorphization.
if decl.declFuncTypeParams.len > 0:
return
var funcScope = newScope(sema.globalScope) var funcScope = newScope(sema.globalScope)
# Add type parameters to type table for resolution # Add type parameters to type table for resolution
var addedTypeParams: seq[string] = @[] var addedTypeParams: seq[string] = @[]
+17
View File
@@ -6,5 +6,22 @@ extern func PrintInt(n: int);
extern func PrintFloat(f: float64); extern func PrintFloat(f: float64);
extern func PrintBool(b: bool); extern func PrintBool(b: bool);
extern func ReadLine() -> String; extern func ReadLine() -> String;
extern func bux_read_file(path: String) -> String;
extern func bux_write_file(path: String, content: String) -> int;
extern func bux_file_exists(path: String) -> int;
func ReadFile(path: String) -> String {
return bux_read_file(path);
}
func WriteFile(path: String, content: String) -> bool {
let r: int = bux_write_file(path, content);
return r != 0;
}
func FileExists(path: String) -> bool {
let r: int = bux_file_exists(path);
return r != 0;
}
} }
+106 -16
View File
@@ -1,31 +1,120 @@
module Std::Map { module Std::Map {
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;
struct MapEntry { // ---------------------------------------------------------------------------
key: String, // Generic Map<K, V> — works with value-type keys (int, float, etc.)
value: int, // For String keys, use StringMap below.
// ---------------------------------------------------------------------------
struct MapEntry<K, V> {
key: K,
value: V,
occupied: bool, occupied: bool,
} }
struct Map { struct Map<K, V> {
entries: *MapEntry, entries: *MapEntry<K, V>,
cap: uint, cap: uint,
len: uint, len: uint,
} }
func Map_New(cap: uint) -> Map { func Map_New<K, V>(cap: uint) -> Map<K, V> {
let total: uint = cap * sizeof(MapEntry); let total: uint = cap * sizeof(MapEntry<K, V>);
let data: *MapEntry = bux_alloc(total) as *MapEntry; 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 < cap {
data[i].occupied = false; data[i].occupied = false;
i = i + 1; i = i + 1;
} }
return Map { entries: data, cap: cap, len: 0 }; return Map<K, V> { entries: data, cap: cap, len: 0 };
} }
func Map_Set(m: *Map, key: String, value: int) { func Map_Set<K, V>(m: *Map<K, V>, key: K, value: V) {
var keyPtr: *K = &key;
let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K));
var idx: uint = hash % m.cap;
while m.entries[idx].occupied {
if m.entries[idx].key == key {
m.entries[idx].value = value;
return;
}
idx = (idx + 1) % m.cap;
}
m.entries[idx].key = key;
m.entries[idx].value = value;
m.entries[idx].occupied = true;
m.len = m.len + 1;
}
func Map_Get<K, V>(m: *Map<K, V>, key: K) -> V {
var keyPtr: *K = &key;
let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K));
var idx: uint = hash % m.cap;
while m.entries[idx].occupied {
if m.entries[idx].key == key {
return m.entries[idx].value;
}
idx = (idx + 1) % m.cap;
}
// Return zero value for missing key
var zero: V = 0 as V;
return zero;
}
func Map_Has<K, V>(m: *Map<K, V>, key: K) -> bool {
var keyPtr: *K = &key;
let hash: uint = bux_hash_bytes(keyPtr as *void, sizeof(K));
var idx: uint = hash % m.cap;
while m.entries[idx].occupied {
if m.entries[idx].key == key {
return true;
}
idx = (idx + 1) % m.cap;
}
return false;
}
func Map_Len<K, V>(m: *Map<K, V>) -> uint {
return m.len;
}
func Map_Free<K, V>(m: *Map<K, V>) {
bux_free(m.entries as *void);
m.entries = null as *MapEntry<K, V>;
m.cap = 0;
m.len = 0;
}
// ---------------------------------------------------------------------------
// StringMap<V> — specialized Map for String keys, using strcmp
// ---------------------------------------------------------------------------
struct StringMapEntry<V> {
key: String,
value: V,
occupied: bool,
}
struct StringMap<V> {
entries: *StringMapEntry<V>,
cap: uint,
len: uint,
}
func StringMap_New<V>(cap: uint) -> StringMap<V> {
let total: uint = cap * sizeof(StringMapEntry<V>);
let data: *StringMapEntry<V> = bux_alloc(total) as *StringMapEntry<V>;
var i: uint = 0;
while i < cap {
data[i].occupied = false;
i = i + 1;
}
return StringMap<V> { entries: data, cap: cap, len: 0 };
}
func StringMap_Set<V>(m: *StringMap<V>, key: String, value: V) {
let hash: uint = bux_hash_string(key); 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 {
@@ -41,7 +130,7 @@ func Map_Set(m: *Map, key: String, value: int) {
m.len = m.len + 1; m.len = m.len + 1;
} }
func Map_Get(m: *Map, key: String) -> int { func StringMap_Get<V>(m: *StringMap<V>, key: String) -> 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 {
@@ -50,10 +139,11 @@ func Map_Get(m: *Map, key: String) -> int {
} }
idx = (idx + 1) % m.cap; idx = (idx + 1) % m.cap;
} }
return 0; var zero: V = 0 as V;
return zero;
} }
func Map_Has(m: *Map, key: String) -> bool { func StringMap_Has<V>(m: *StringMap<V>, key: String) -> bool {
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 {
@@ -65,13 +155,13 @@ func Map_Has(m: *Map, key: String) -> bool {
return false; return false;
} }
func Map_Len(m: *Map) -> uint { func StringMap_Len<V>(m: *StringMap<V>) -> uint {
return m.len; return m.len;
} }
func Map_Free(m: *Map) { func StringMap_Free<V>(m: *StringMap<V>) {
bux_free(m.entries as *void); bux_free(m.entries as *void);
m.entries = null as *MapEntry; m.entries = null as *StringMapEntry<V>;
m.cap = 0; m.cap = 0;
m.len = 0; m.len = 0;
} }
+19
View File
@@ -0,0 +1,19 @@
module Std::Path {
extern func bux_path_join(a: String, b: String) -> String;
extern func bux_path_parent(path: String) -> String;
extern func bux_path_ext(path: String) -> String;
func Path_Join(a: String, b: String) -> String {
return bux_path_join(a, b);
}
func Path_Parent(path: String) -> String {
return bux_path_parent(path);
}
func Path_Ext(path: String) -> String {
return bux_path_ext(path);
}
}
+113
View File
@@ -6,6 +6,25 @@ extern func bux_strncmp(a: String, b: String, n: uint) -> int;
extern func bux_strcpy(dest: *char8, src: String) -> *char8; extern func bux_strcpy(dest: *char8, src: String) -> *char8;
extern func bux_strcat(dest: *char8, src: String) -> *char8; extern func bux_strcat(dest: *char8, src: String) -> *char8;
extern func bux_strncpy(dest: *char8, src: String, n: uint) -> *char8; extern func bux_strncpy(dest: *char8, src: String, n: uint) -> *char8;
extern func bux_strstr(haystack: String, needle: String) -> String;
extern func bux_str_contains(haystack: String, needle: String) -> int;
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
extern func bux_str_trim_left(s: String) -> String;
extern func bux_str_trim_right(s: String) -> String;
extern func bux_str_trim(s: String) -> String;
extern func bux_int_to_str(n: int64) -> String;
extern func bux_str_to_int(s: String) -> int64;
extern func bux_sb_new(initial_cap: uint) -> *void;
extern func bux_sb_append(sb: *void, s: String);
extern func bux_sb_append_int(sb: *void, n: int64);
extern func bux_sb_append_float(sb: *void, f: float64);
extern func bux_sb_append_char(sb: *void, c: char8);
extern func bux_sb_build(sb: *void) -> String;
extern func bux_sb_free(sb: *void);
extern func bux_str_split_count(s: String, delim: String) -> uint;
extern func bux_str_split_part(s: String, delim: String, index: uint) -> String;
extern func bux_str_join2(a: String, b: String, sep: String) -> String;
func String_Len(s: String) -> uint { func String_Len(s: String) -> uint {
return bux_strlen(s); return bux_strlen(s);
@@ -42,4 +61,98 @@ func String_StartsWith(s: String, prefix: String) -> bool {
return r == 0; return r == 0;
} }
func String_EndsWith(s: String, suffix: String) -> bool {
let s_len: uint = bux_strlen(s);
let suf_len: uint = bux_strlen(suffix);
if suf_len > s_len {
return false;
}
let start: uint = s_len - suf_len;
let tail: String = bux_str_slice(s, start, suf_len);
let eq: bool = bux_strcmp(tail, suffix) == 0;
return eq;
}
func String_Contains(s: String, substr: String) -> bool {
let r: int = bux_str_contains(s, substr);
return r != 0;
}
func String_Slice(s: String, start: uint, len: uint) -> String {
return bux_str_slice(s, start, len);
}
func String_Trim(s: String) -> String {
return bux_str_trim(s);
}
func String_TrimLeft(s: String) -> String {
return bux_str_trim_left(s);
}
func String_TrimRight(s: String) -> String {
return bux_str_trim_right(s);
}
func String_FromInt(n: int64) -> String {
return bux_int_to_str(n);
}
func String_ToInt(s: String) -> int64 {
return bux_str_to_int(s);
}
// String Builder — efficient string construction
struct StringBuilder {
handle: *void,
}
func StringBuilder_New() -> StringBuilder {
return StringBuilder { handle: bux_sb_new(64) };
}
func StringBuilder_NewCap(cap: uint) -> StringBuilder {
return StringBuilder { handle: bux_sb_new(cap) };
}
func StringBuilder_Append(sb: *StringBuilder, s: String) {
bux_sb_append(sb.handle, s);
}
func StringBuilder_AppendInt(sb: *StringBuilder, n: int64) {
bux_sb_append_int(sb.handle, n);
}
func StringBuilder_AppendFloat(sb: *StringBuilder, f: float64) {
bux_sb_append_float(sb.handle, f);
}
func StringBuilder_AppendChar(sb: *StringBuilder, c: char8) {
bux_sb_append_char(sb.handle, c);
}
func StringBuilder_Build(sb: *StringBuilder) -> String {
return bux_sb_build(sb.handle);
}
func StringBuilder_Free(sb: *StringBuilder) {
bux_sb_free(sb.handle);
}
// ---------------------------------------------------------------------------
// String split/join
// ---------------------------------------------------------------------------
func String_SplitCount(s: String, delim: String) -> uint {
return bux_str_split_count(s, delim);
}
func String_SplitPart(s: String, delim: String, index: uint) -> String {
return bux_str_split_part(s, delim, index);
}
func String_Join2(a: String, b: String, sep: String) -> String {
return bux_str_join2(a, b, sep);
}
} }
+385
View File
@@ -153,6 +153,391 @@ char* bux_strncpy(char* dest, const char* src, unsigned int n) {
return strncpy(dest, src, (size_t)n); return strncpy(dest, src, (size_t)n);
} }
/* String find: returns pointer to first occurrence of needle in haystack, or NULL */
const char* bux_strstr(const char* haystack, const char* needle) {
if (!haystack || !needle) return NULL;
return strstr(haystack, needle);
}
/* String contains: returns 1 if haystack contains needle, 0 otherwise */
int bux_str_contains(const char* haystack, const char* needle) {
return bux_strstr(haystack, needle) != NULL;
}
/* String slice: extract substring from start, length len */
char* bux_str_slice(const char* s, unsigned int start, unsigned int len) {
if (!s) return NULL;
unsigned int s_len = (unsigned int)strlen(s);
if (start >= s_len) {
char* empty = (char*)bux_alloc(1);
empty[0] = '\0';
return empty;
}
unsigned int avail = s_len - start;
if (len > avail) len = avail;
char* result = (char*)bux_alloc(len + 1);
memcpy(result, s + start, len);
result[len] = '\0';
return result;
}
/* String trim left: remove leading whitespace */
char* bux_str_trim_left(const char* s) {
if (!s) return NULL;
while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++;
unsigned int len = (unsigned int)strlen(s);
char* result = (char*)bux_alloc(len + 1);
memcpy(result, s, len + 1);
return result;
}
/* String trim right: remove trailing whitespace */
char* bux_str_trim_right(const char* s) {
if (!s) return NULL;
unsigned int len = (unsigned int)strlen(s);
while (len > 0 && (s[len-1] == ' ' || s[len-1] == '\t' || s[len-1] == '\n' || s[len-1] == '\r')) {
len--;
}
char* result = (char*)bux_alloc(len + 1);
memcpy(result, s, len);
result[len] = '\0';
return result;
}
/* String trim: remove both leading and trailing whitespace */
char* bux_str_trim(const char* s) {
char* left = bux_str_trim_left(s);
char* result = bux_str_trim_right(left);
bux_free(left);
return result;
}
/* Int to string conversion */
char* bux_int_to_str(int64_t n) {
char* result = (char*)bux_alloc(32);
snprintf(result, 32, "%lld", (long long)n);
return result;
}
/* String to int conversion */
int64_t bux_str_to_int(const char* s) {
if (!s) return 0;
return (int64_t)atoll(s);
}
/* String builder */
typedef struct {
char* buf;
unsigned int len;
unsigned int cap;
} BuxStringBuilder;
BuxStringBuilder* bux_sb_new(unsigned int initial_cap) {
BuxStringBuilder* sb = (BuxStringBuilder*)bux_alloc(sizeof(BuxStringBuilder));
sb->cap = initial_cap > 0 ? initial_cap : 64;
sb->buf = (char*)bux_alloc(sb->cap);
sb->buf[0] = '\0';
sb->len = 0;
return sb;
}
void bux_sb_append(BuxStringBuilder* sb, const char* s) {
if (!sb || !s) return;
unsigned int s_len = (unsigned int)strlen(s);
unsigned int new_len = sb->len + s_len;
if (new_len + 1 > sb->cap) {
while (sb->cap < new_len + 1) sb->cap *= 2;
sb->buf = (char*)bux_realloc(sb->buf, sb->cap);
}
memcpy(sb->buf + sb->len, s, s_len);
sb->len = new_len;
sb->buf[sb->len] = '\0';
}
void bux_sb_append_int(BuxStringBuilder* sb, int64_t n) {
char tmp[32];
snprintf(tmp, sizeof(tmp), "%lld", (long long)n);
bux_sb_append(sb, tmp);
}
void bux_sb_append_float(BuxStringBuilder* sb, double f) {
char tmp[32];
snprintf(tmp, sizeof(tmp), "%g", f);
bux_sb_append(sb, tmp);
}
void bux_sb_append_char(BuxStringBuilder* sb, char c) {
if (!sb) return;
if (sb->len + 2 > sb->cap) {
sb->cap *= 2;
sb->buf = (char*)bux_realloc(sb->buf, sb->cap);
}
sb->buf[sb->len++] = c;
sb->buf[sb->len] = '\0';
}
const char* bux_sb_build(BuxStringBuilder* sb) {
if (!sb) return "";
return sb->buf;
}
void bux_sb_free(BuxStringBuilder* sb) {
if (!sb) return;
bux_free(sb->buf);
bux_free(sb);
}
/* String split: count parts separated by delimiter */
unsigned int bux_str_split_count(const char* s, const char* delim) {
if (!s || !delim || !*delim) return 1;
unsigned int count = 1;
size_t delim_len = strlen(delim);
const char* p = s;
while ((p = strstr(p, delim)) != NULL) {
count++;
p += delim_len;
}
return count;
}
/* String split: get the n-th part (0-indexed) */
char* bux_str_split_part(const char* s, const char* delim, unsigned int index) {
if (!s || !delim || !*delim) {
if (index == 0) {
unsigned int len = s ? (unsigned int)strlen(s) : 0;
char* result = (char*)bux_alloc(len + 1);
if (s) memcpy(result, s, len);
result[len] = '\0';
return result;
}
char* empty = (char*)bux_alloc(1);
empty[0] = '\0';
return empty;
}
size_t delim_len = strlen(delim);
const char* start = s;
const char* end;
unsigned int current = 0;
while (current < index) {
end = strstr(start, delim);
if (!end) {
char* empty = (char*)bux_alloc(1);
empty[0] = '\0';
return empty;
}
start = end + delim_len;
current++;
}
end = strstr(start, delim);
size_t part_len;
if (end) {
part_len = (size_t)(end - start);
} else {
part_len = strlen(start);
}
char* result = (char*)bux_alloc(part_len + 1);
memcpy(result, start, part_len);
result[part_len] = '\0';
return result;
}
/* String join: join two strings with separator */
char* bux_str_join2(const char* a, const char* b, const char* sep) {
if (!a && !b) {
char* empty = (char*)bux_alloc(1);
empty[0] = '\0';
return empty;
}
unsigned int len_a = a ? (unsigned int)strlen(a) : 0;
unsigned int len_b = b ? (unsigned int)strlen(b) : 0;
unsigned int len_sep = sep ? (unsigned int)strlen(sep) : 0;
unsigned int total = len_a + len_sep + len_b;
char* result = (char*)bux_alloc(total + 1);
if (a) memcpy(result, a, len_a);
if (sep && len_a > 0 && len_b > 0) memcpy(result + len_a, sep, len_sep);
if (b) memcpy(result + len_a + (len_a > 0 && len_b > 0 ? len_sep : 0), b, len_b);
result[total] = '\0';
return result;
}
/* Simple string format: replace {0}, {1}, ... with string arguments.
Returns formatted string. Supports up to 8 arguments. */
char* bux_str_format(const char* pattern,
const char* a0, const char* a1, const char* a2, const char* a3,
const char* a4, const char* a5, const char* a6, const char* a7) {
if (!pattern) {
char* empty = (char*)bux_alloc(1);
empty[0] = '\0';
return empty;
}
const char* args[8] = { a0, a1, a2, a3, a4, a5, a6, a7 };
/* Calculate result size */
size_t total = 0;
const char* p = pattern;
while (*p) {
if (*p == '{' && p[1] >= '0' && p[1] <= '7' && p[2] == '}') {
int idx = p[1] - '0';
if (args[idx]) total += strlen(args[idx]);
p += 3;
} else {
total++;
p++;
}
}
char* result = (char*)bux_alloc(total + 1);
char* w = result;
p = pattern;
while (*p) {
if (*p == '{' && p[1] >= '0' && p[1] <= '7' && p[2] == '}') {
int idx = p[1] - '0';
if (args[idx]) {
size_t len = strlen(args[idx]);
memcpy(w, args[idx], len);
w += len;
}
p += 3;
} else {
*w++ = *p++;
}
}
*w = '\0';
return result;
}
/* File I/O — read entire file into string */
char* bux_read_file(const char* path) {
if (!path) return NULL;
FILE* f = fopen(path, "rb");
if (!f) return NULL;
fseek(f, 0, SEEK_END);
long size = ftell(f);
fseek(f, 0, SEEK_SET);
char* buf = (char*)bux_alloc((size_t)size + 1);
size_t read = fread(buf, 1, (size_t)size, f);
fclose(f);
buf[read] = '\0';
return buf;
}
/* File I/O — write string to file */
int bux_write_file(const char* path, const char* content) {
if (!path || !content) return 0;
FILE* f = fopen(path, "wb");
if (!f) return 0;
size_t len = strlen(content);
size_t written = fwrite(content, 1, len, f);
fclose(f);
return written == len ? 1 : 0;
}
/* File I/O — check if file exists */
int bux_file_exists(const char* path) {
if (!path) return 0;
FILE* f = fopen(path, "rb");
if (f) {
fclose(f);
return 1;
}
return 0;
}
/* Path operations */
char* bux_path_join(const char* a, const char* b) {
if (!a && !b) {
char* empty = (char*)bux_alloc(1);
empty[0] = '\0';
return empty;
}
if (!a) {
unsigned int len = (unsigned int)strlen(b);
char* result = (char*)bux_alloc(len + 1);
memcpy(result, b, len + 1);
return result;
}
if (!b) {
unsigned int len = (unsigned int)strlen(a);
char* result = (char*)bux_alloc(len + 1);
memcpy(result, a, len + 1);
return result;
}
unsigned int len_a = (unsigned int)strlen(a);
unsigned int len_b = (unsigned int)strlen(b);
int need_sep = (len_a > 0 && a[len_a-1] != '/') ? 1 : 0;
unsigned int total = len_a + (need_sep ? 1 : 0) + len_b;
char* result = (char*)bux_alloc(total + 1);
memcpy(result, a, len_a);
if (need_sep) result[len_a] = '/';
memcpy(result + len_a + (need_sep ? 1 : 0), b, len_b);
result[total] = '\0';
return result;
}
char* bux_path_parent(const char* path) {
if (!path) {
char* empty = (char*)bux_alloc(1);
empty[0] = '\0';
return empty;
}
int len = (int)strlen(path);
while (len > 0 && path[len-1] == '/') len--;
while (len > 0 && path[len-1] != '/') len--;
while (len > 0 && path[len-1] == '/') len--;
if (len == 0) {
char* dot = (char*)bux_alloc(2);
dot[0] = '.';
dot[1] = '\0';
return dot;
}
char* result = (char*)bux_alloc((unsigned int)len + 1);
memcpy(result, path, (unsigned int)len);
result[len] = '\0';
return result;
}
char* bux_path_ext(const char* path) {
if (!path) {
char* empty = (char*)bux_alloc(1);
empty[0] = '\0';
return empty;
}
const char* dot = strrchr(path, '.');
if (!dot) {
char* empty = (char*)bux_alloc(1);
empty[0] = '\0';
return empty;
}
const char* slash = strrchr(path, '/');
if (slash && slash > dot) {
char* empty = (char*)bux_alloc(1);
empty[0] = '\0';
return empty;
}
unsigned int len = (unsigned int)strlen(dot);
char* result = (char*)bux_alloc(len + 1);
memcpy(result, dot, len + 1);
return result;
}
/* Hash function (djb2) over raw bytes — for generic key types */
unsigned int bux_hash_bytes(const void* ptr, size_t size) {
if (!ptr) return 0;
unsigned int hash = 5381;
const unsigned char* bytes = (const unsigned char*)ptr;
for (size_t i = 0; i < size; i++) {
hash = ((hash << 5) + hash) + bytes[i]; /* hash * 33 + byte */
}
return hash;
}
/* Byte equality check — for generic key comparison */
int bux_mem_eq(const void* a, const void* b, size_t size) {
if (a == b) return 1;
if (!a || !b) return 0;
return memcmp(a, b, size) == 0;
}
/* Hash function (djb2) for string keys */ /* Hash function (djb2) for string keys */
unsigned int bux_hash_string(const char* s) { unsigned int bux_hash_string(const char* s) {
unsigned int hash = 5381; unsigned int hash = 5381;