Phase 5-7: generic inference, extend Type<T>, String stdlib, Map<K,V>, self-hosting audit, docs update
This commit is contained in:
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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] = @[]
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user