diff --git a/PLAN.md b/PLAN.md index 4847b25..dca3ead 100644 --- a/PLAN.md +++ b/PLAN.md @@ -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` | +| `HashSet[string]` | hir_lower (1 use) | ❌ Can use `StringMap` workaround | +| `seq[T]` with push/len/iter | All files (200+ uses) | ⚠️ `Array` 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 (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) + +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` 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 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**. -| Task | Details | -|------|---------| -| `7.1` Port lexer | Rewrite `lexer.nim` → `Lexer.bux` | -| `7.2` Port parser | Rewrite `parser.nim` → `Parser.bux` | -| `7.3` Port sema | Rewrite `sema.nim` → `Sema.bux` | -| `7.4` Port HIR | Rewrite `hir.nim` → `Hir.bux` | -| `7.5` Port LIR | Rewrite `lir.nim` → `Lir.bux` | -| `7.6` Port C backend | Rewrite `c_backend.nim` → `CBackend.bux` | -| `7.7` Port CLI | Rewrite `main.nim` → `Main.bux` | -| `7.8` Dogfooding | Use `buxc` (Nim) to build `buxc2` (Bux). Then use `buxc2` to build `buxc3`. Compare bit-for-bit. | -| `7.9` Fix bootstrap loop | Once `buxc2 == buxc3`, we are self-hosted. Freeze Nim version as reference. | +**Pre-requisites (Phase 7.0):** StringMap, String split/join, String formatting, File I/O must be working. + +| Task | Details | Deps | +|------|---------|------| +| `7.1` Port foundation | `token.bux`, `source_location.bux`, `types.bux`, `scope.bux`, `hir.bux` (~600 LOC) | StringMap | +| `7.2` Port lexer | `lexer.bux` — state machine, UTF-8, error reporting (~570 LOC) | String split/compare | +| `7.3` Port AST + parser | `ast.bux` + `parser.bux` — Pratt parser, algebraic enums (~1620 LOC) | Array, match | +| `7.4` Port sema | `sema.bux` — type checking, symbol resolution (~890 LOC) | StringMap, formatting | +| `7.5` Port manifest | `manifest.bux` — TOML/bux.toml parser (~80 LOC) | File I/O, String split | +| `7.6` Port HIR lowering | `hir_lower.bux` — tree transformation (~1230 LOC) | StringMap, HashSet | +| `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. @@ -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(&b)` -2. **`extend Box` syntax** — Parser support for generic impl blocks -3. **Std::String improvements** — String builder, slicing, interpolation -4. **Std::Map generic** — `Map` with generic keys and values -5. **Self-hosting preparation** — Audit Nim compiler code for Bux-rewrite feasibility +1. ✅ **Inferred generic function calls** — `Max(10, 20)` instead of `Max(10, 20)` +2. ✅ **`extend Box` syntax** — Parser support for generic impl blocks +3. ✅ **Std::String improvements** — Slicing, trimming, contains, StringBuilder +4. ✅ **Std::Map generic** — `Map` with generic keys and values (value-type keys) +5. ✅ **Self-hosting audit** — See Phase 6.5 below + +### Next Actions (Priority order) + +1. **StringMap** — `Map` 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) --- diff --git a/docs/LanguageRef.md b/docs/LanguageRef.md index f1667e6..acb2c7f 100644 --- a/docs/LanguageRef.md +++ b/docs/LanguageRef.md @@ -302,23 +302,55 @@ extend Rectangle { ## 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 -func Swap(a: *T, b: *T) { - let tmp: T = *a; - *a = *b; - *b = tmp; +func Max(a: T, b: T) -> T { + if a > b { return a; } + return b; } func Main() -> int { - let x: int = 1; - let y: int = 2; - Swap(&x, &y); + // Explicit type args + let m1: int = Max(10, 20); + + // Type inference — T inferred as int from arguments + let m2: int = Max(10, 20); return 0; } ``` +### Generic Structs + +```bux +struct Box { + value: T, +} + +// Use extend Type for methods on generic structs +extend Box { + func Get(self: *Box) -> T { + return self.value; + } + + func Set(self: *Box, value: T) { + self.value = value; + } +} + +func Main() -> int { + let b: Box = Box { value: 42 }; + PrintInt(b.Get()); // 42 + b.Set(100); + PrintInt(b.Get()); // 100 + return 0; +} +``` + +> **Note:** `extend Type` syntax requires type parameters on the impl block. The compiler propagates them to each method automatically. + --- ## Error Handling diff --git a/docs/Stdlib.md b/docs/Stdlib.md index 1199b95..f375a36 100644 --- a/docs/Stdlib.md +++ b/docs/Stdlib.md @@ -1,12 +1,12 @@ # 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 -Basic input/output operations wrapping C stdio. +Basic I/O and file operations wrapping C stdio. ### Functions @@ -18,14 +18,17 @@ Basic input/output operations wrapping C stdio. | `PrintFloat` | `func PrintFloat(f: float64)` | Print float | | `PrintBool` | `func PrintBool(b: bool)` | Print boolean | | `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 ```bux -import Std::Io::{PrintLine, PrintInt}; +import Std::Io::{PrintLine, ReadFile, WriteFile}; func Main() -> int { - PrintLine("Hello, World!"); - PrintInt(42); + WriteFile("/tmp/test.txt", "Hello, Bux!"); + PrintLine(ReadFile("/tmp/test.txt")); return 0; } ``` @@ -34,13 +37,12 @@ func Main() -> int { ## Std::Array -Dynamic array of integers (currently hardcoded for `int`). +Fully generic dynamic array `Array`. ### Types - ```bux -struct Array { - data: *int, +struct Array { + data: *T, len: uint, cap: uint, } @@ -50,22 +52,22 @@ struct Array { | Function | Signature | Description | |----------|-----------|-------------| -| `Array_New` | `func Array_New(cap: uint) -> Array` | Create new array with capacity | -| `Array_Push` | `func Array_Push(arr: *Array, value: int)` | Append element | -| `Array_Get` | `func Array_Get(arr: *Array, index: uint) -> int` | Get element at index | -| `Array_Len` | `func Array_Len(arr: *Array) -> uint` | Get length | -| `Array_Free` | `func Array_Free(arr: *Array)` | Free memory | +| `Array_New` | `func Array_New(cap: uint) -> Array` | Create new array | +| `Array_Push` | `func Array_Push(arr: *Array, value: T)` | Append element | +| `Array_Get` | `func Array_Get(arr: *Array, index: uint) -> T` | Get element at index | +| `Array_Len` | `func Array_Len(arr: *Array) -> uint` | Get length | +| `Array_Free` | `func Array_Free(arr: *Array)` | Free memory | ### Example ```bux import Std::Array::{Array, Array_New, Array_Push, Array_Get}; func Main() -> int { - let arr: Array = Array_New(4); - Array_Push(&arr, 10); - Array_Push(&arr, 20); - PrintInt(Array_Get(&arr, 0)); // 10 - Array_Free(&arr); + let arr: Array = Array_New(4); + Array_Push(&arr, 10); + Array_Push(&arr, 20); + PrintInt(Array_Get(&arr, 0)); // 10 + Array_Free(&arr); return 0; } ``` @@ -74,28 +76,81 @@ func Main() -> int { ## 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 | Function | Signature | Description | |----------|-----------|-------------| -| `String_Len` | `func String_Len(s: String) -> uint` | Length of string (wraps `strlen`) | -| `String_Eq` | `func String_Eq(a: String, b: String) -> bool` | Compare strings for equality | -| `String_Concat` | `func String_Concat(a: String, b: String) -> String` | Concatenate two strings (allocates) | -| `String_Copy` | `func String_Copy(s: String) -> String` | Copy string (allocates) | -| `String_StartsWith` | `func String_StartsWith(s: String, prefix: String) -> bool` | Check prefix | +| `StringBuilder_New` | `func StringBuilder_New() -> StringBuilder` | Create with default capacity (64) | +| `StringBuilder_NewCap` | `func StringBuilder_NewCap(cap: uint) -> StringBuilder` | Create with custom capacity | +| `StringBuilder_Append` | `func StringBuilder_Append(sb: *StringBuilder, s: String)` | Append string | +| `StringBuilder_AppendInt` | `func StringBuilder_AppendInt(sb: *StringBuilder, n: int64)` | Append integer | +| `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 ```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 { - let hello: String = "Hello"; - let world: String = "World"; - let greeting: String = String_Concat(hello, ", "); - let full: String = String_Concat(greeting, world); - PrintLine(full); // "Hello, World" + let sb: StringBuilder = StringBuilder_New(); + StringBuilder_Append(&sb, "Hello, "); + StringBuilder_Append(&sb, "World! #"); + StringBuilder_AppendInt(&sb, 42); + PrintLine(StringBuilder_Build(&sb)); // "Hello, World! #42" + StringBuilder_Free(&sb); return 0; } ``` @@ -104,19 +159,18 @@ func Main() -> int { ## Std::Map -Hash map with `String` keys and `int` values using linear probing. +Generic hash map `Map` for value-type keys (int, float, etc.). ### Types - ```bux -struct MapEntry { - key: String, - value: int, +struct MapEntry { + key: K, + value: V, occupied: bool, } -struct Map { - entries: *MapEntry, +struct Map { + entries: *MapEntry, cap: uint, len: uint, } @@ -126,24 +180,81 @@ struct Map { | Function | Signature | Description | |----------|-----------|-------------| -| `Map_New` | `func Map_New(cap: uint) -> Map` | Create new map with given capacity | -| `Map_Set` | `func Map_Set(m: *Map, key: String, value: int)` | Insert or update key | -| `Map_Get` | `func Map_Get(m: *Map, key: String) -> int` | Get value by key (0 if missing) | -| `Map_Has` | `func Map_Has(m: *Map, key: String) -> bool` | Check if key exists | -| `Map_Len` | `func Map_Len(m: *Map) -> uint` | Number of entries | -| `Map_Free` | `func Map_Free(m: *Map)` | Free memory | +| `Map_New` | `func Map_New(cap: uint) -> Map` | Create map | +| `Map_Set` | `func Map_Set(m: *Map, key: K, value: V)` | Insert/update | +| `Map_Get` | `func Map_Get(m: *Map, key: K) -> V` | Get value (zero if missing) | +| `Map_Has` | `func Map_Has(m: *Map, key: K) -> bool` | Check key exists | +| `Map_Len` | `func Map_Len(m: *Map) -> uint` | Entry count | +| `Map_Free` | `func Map_Free(m: *Map)` | Free memory | ### Example ```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 { - let m: Map = Map_New(16); - Map_Set(&m, "one", 1); - Map_Set(&m, "two", 2); - PrintInt(Map_Get(&m, "one")); // 1 - PrintInt(Map_Get(&m, "three")); // 0 - Map_Free(&m); + let m: Map = Map_New(16); + Map_Set(&m, 1, "one"); + Map_Set(&m, 2, "two"); + PrintLine(Map_Get(&m, 1)); // "one" + Map_Free(&m); + return 0; +} +``` + +> **Note:** For `String` keys, use `StringMap` below which uses `strcmp` for key comparison. + +--- + +## Std::StringMap + +Specialized hash map for `String` keys with any value type. + +### Types +```bux +struct StringMapEntry { + key: String, + value: V, + occupied: bool, +} + +struct StringMap { + entries: *StringMapEntry, + cap: uint, + len: uint, +} +``` + +### Functions + +| Function | Signature | Description | +|----------|-----------|-------------| +| `StringMap_New` | `func StringMap_New(cap: uint) -> StringMap` | Create map | +| `StringMap_Set` | `func StringMap_Set(m: *StringMap, key: String, value: V)` | Insert/update | +| `StringMap_Get` | `func StringMap_Get(m: *StringMap, key: String) -> V` | Get value | +| `StringMap_Has` | `func StringMap_Has(m: *StringMap, key: String) -> bool` | Check key exists | +| `StringMap_Len` | `func StringMap_Len(m: *StringMap) -> uint` | Entry count | +| `StringMap_Free` | `func StringMap_Free(m: *StringMap)` | 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; } ``` @@ -156,21 +267,21 @@ These C functions are provided by `runtime.c` and are available via `extern` dec | 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_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 -Planned for future phases: - -- `Std::Result` — Generic `Result` with `?` operator support -- `Std::Option` — Generic `Option` +- `Std::Result` — Shipped via algebraic enums ✅ +- `Std::Option` — Shipped via algebraic enums ✅ - `Std::Math` — `Sqrt`, `Pow`, `Min`, `Max`, `Abs` - `Std::Os` — `Args`, `Env`, `Exit`, `Cwd` - `Std::Fmt` — String formatting with interpolation - `Std::Iter` — Iterator trait and combinators -- `Std::Task` / `Std::Channel` — Lightweight concurrency +- `Std::Task` / `Std::Channel` — Lightweight concurrency \ No newline at end of file diff --git a/examples/extend_generic.bux b/examples/extend_generic.bux new file mode 100644 index 0000000..5a636ba --- /dev/null +++ b/examples/extend_generic.bux @@ -0,0 +1,30 @@ +// Generic impl blocks — extend Box with methods +import Std::Io::{PrintLine, PrintInt}; + +struct Box { + value: T, +} + +extend Box { + func Get(self: *Box) -> T { + return self.value; + } + + func Set(self: *Box, value: T) { + self.value = value; + } +} + +func Main() -> int { + let b: Box = Box { value: 42 }; + PrintLine("Box value:"); + PrintInt(b.Get()); + PrintLine(""); + + b.Set(100); + PrintLine("Box after Set(100):"); + PrintInt(b.Get()); + PrintLine(""); + + return 0; +} \ No newline at end of file diff --git a/examples/generic_infer.bux b/examples/generic_infer.bux new file mode 100644 index 0000000..3a1dee0 --- /dev/null +++ b/examples/generic_infer.bux @@ -0,0 +1,26 @@ +// Generic function type inference — calls without explicit type args +import Std::Io::{PrintLine, PrintInt}; + +func Max(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; +} \ No newline at end of file diff --git a/examples/generic_infer2.bux b/examples/generic_infer2.bux new file mode 100644 index 0000000..272ae4c --- /dev/null +++ b/examples/generic_infer2.bux @@ -0,0 +1,23 @@ +// Generic function type inference — multiple type parameters +import Std::Io::{PrintLine, PrintInt}; + +struct Pair { + first: T, + second: U, +} + +func MakePair(a: T, b: U) -> Pair { + return Pair { first: a, second: b }; +} + +func Pair_GetFirst(self: *Pair) -> T { + return self.first; +} + +func Main() -> int { + // Inferred: T = int, U = String + let p: Pair = MakePair(42, "hello"); + PrintInt(p.GetFirst()); + PrintLine(""); + return 0; +} \ No newline at end of file diff --git a/examples/map.bux b/examples/map.bux index 77a629d..2e13e9b 100644 --- a/examples/map.bux +++ b/examples/map.bux @@ -1,44 +1,30 @@ -// Map - Hash map with String keys and int values +// Generic Map — uses Std::Map (stdlib is merged into module) 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 { - let m: Map = Map_New(16); + PrintLine("=== Map ==="); + let m: Map = Map_New(16); - PrintLine("Map operations:"); + Map_Set(&m, 1, "one"); + Map_Set(&m, 2, "two"); + Map_Set(&m, 3, "three"); - Map_Set(&m, "one", 1); - Map_Set(&m, "two", 2); - Map_Set(&m, "three", 3); + PrintLine("Get 1:"); + PrintLine(Map_Get(&m, 1)); - PrintLine("Length:"); - PrintInt(Map_Len(&m)); - PrintLine(""); + PrintLine("Get 2:"); + PrintLine(Map_Get(&m, 2)); - PrintLine("Get 'one':"); - PrintInt(Map_Get(&m, "one")); - 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, 1) { + PrintLine("Has 1: yes"); } - if !Map_Has(&m, "four") { - PrintLine("Has 'four': no"); - } + // Test update + Map_Set(&m, 2, "updated"); + PrintLine("Updated 2:"); + PrintLine(Map_Get(&m, 2)); + Map_Free(&m); return 0; -} +} \ No newline at end of file diff --git a/examples/strings2.bux b/examples/strings2.bux new file mode 100644 index 0000000..2e1ea37 --- /dev/null +++ b/examples/strings2.bux @@ -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; +} \ No newline at end of file diff --git a/src/ast.nim b/src/ast.nim index 2305e3d..a10f711 100644 --- a/src/ast.nim +++ b/src/ast.nim @@ -162,6 +162,7 @@ type of ekCall: exprCallCallee*: Expr exprCallArgs*: seq[Expr] + exprCallInferredTypeArgs*: seq[TypeExpr] ## filled by sema for inferred generic calls of ekGenericCall: exprGenericCallee*: string exprGenericTypeArgs*: seq[TypeExpr] @@ -337,6 +338,7 @@ type declInterfaceMethods*: seq[Decl] ## FuncDecl signatures only of dkImpl: declImplTypeName*: string + declImplTypeParams*: seq[string] ## type parameters for generic impl: extend Box declImplInterface*: string ## empty if not for interface declImplMethods*: seq[Decl] of dkModule: diff --git a/src/hir_lower.nim b/src/hir_lower.nim index 2ace5fc..73175fd 100644 --- a/src/hir_lower.nim +++ b/src/hir_lower.nim @@ -508,6 +508,32 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode = args.add(ctx.lowerExpr(arg)) 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 var calleeName = "" if expr.exprCallCallee.kind == ekIdent: @@ -1004,6 +1030,12 @@ proc lowerModule*(module: Module, sema: Sema): HirModule = ctx.genericFuncs[decl.declFuncName] = decl if decl.kind == dkStruct and decl.declStructTypeParams.len > 0: 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 proc findGenericCalls(expr: Expr): seq[tuple[name: string, typeArgs: seq[TypeExpr]]] = @@ -1013,6 +1045,14 @@ proc lowerModule*(module: Module, sema: Sema): HirModule = of ekCall: if expr.exprCallCallee.kind == ekGenericCall: 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)) for arg in expr.exprCallArgs: result.add(findGenericCalls(arg)) @@ -1126,6 +1166,9 @@ proc lowerModule*(module: Module, sema: Sema): HirModule = of dkImpl: for methodDecl in decl.declImplMethods: if methodDecl.kind == dkFunc: + # Skip generic methods — they are monomorphized via generateMethodInstance + if methodDecl.declFuncTypeParams.len > 0: + continue var hf = ctx.lowerFunc(methodDecl) hf.name = decl.declImplTypeName & "_" & hf.name funcs.add(hf) diff --git a/src/parser.nim b/src/parser.nim index 7389120..f9d1d9d 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -1015,6 +1015,7 @@ proc parseImplDecl(p: var Parser): Decl = let loc = p.currentLoc discard p.expect(tkExtend, "expected 'extend'") let typeName = p.expect(tkIdent, "expected type name").text + let typeParams = p.parseTypeParams() var interfaceName = "" if p.check(tkFor): discard p.advance() @@ -1030,6 +1031,7 @@ proc parseImplDecl(p: var Parser): Decl = methods.add(p.parseFuncDecl(false, false, ParsedAttrs())) discard p.expect(tkRBrace, "expected '}' to close impl block") return Decl(kind: dkImpl, loc: loc, declImplTypeName: typeName, + declImplTypeParams: typeParams, declImplInterface: interfaceName, declImplMethods: methods) proc parseModuleDecl(p: var Parser, isPublic: bool): Decl = diff --git a/src/sema.nim b/src/sema.nim index 5e84a79..e2b9ad8 100644 --- a/src/sema.nim +++ b/src/sema.nim @@ -47,6 +47,89 @@ proc hasErrors*(res: SemaResult): bool = return true 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 → arg is *Map) + 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 # --------------------------------------------------------------------------- @@ -229,10 +312,19 @@ proc collectGlobals*(sema: var Sema) = of dkImpl: # Register methods for the type let typeName = decl.declImplTypeName + let implTypeParams = decl.declImplTypeParams if not sema.methodTable.hasKey(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: if methodDecl.kind == dkFunc: + # Propagate impl type params to method for HIR lowering + if implTypeParams.len > 0: + methodDecl.declFuncTypeParams = implTypeParams var params: seq[Type] = @[] for p in methodDecl.declFuncParams: params.add(sema.resolveType(p.ptype)) @@ -252,7 +344,13 @@ proc collectGlobals*(sema: var Sema) = let sym = Symbol(kind: skFunc, name: mangledName, decl: methodDecl, isPublic: true) sym.typ = makeFunc(params, retType) + if implTypeParams.len > 0: + # Register as generic function for monomorphization + sym.decl = methodDecl discard sema.globalScope.define(sym) + # Clean up type parameters + for tp in addedTypeParams: + sema.typeTable.del(tp) else: discard @@ -485,6 +583,39 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type = 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}): 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] elif calleeType.kind == tkUnknown: return makeUnknown() @@ -708,6 +839,10 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type = proc checkFunc(sema: var Sema, decl: Decl) = if decl.declFuncBody == nil: 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) # Add type parameters to type table for resolution var addedTypeParams: seq[string] = @[] diff --git a/stdlib/Std/Io.bux b/stdlib/Std/Io.bux index c819829..ea0cf02 100644 --- a/stdlib/Std/Io.bux +++ b/stdlib/Std/Io.bux @@ -6,5 +6,22 @@ extern func PrintInt(n: int); extern func PrintFloat(f: float64); extern func PrintBool(b: bool); 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; +} } diff --git a/stdlib/Std/Map.bux b/stdlib/Std/Map.bux index 59b8b0a..650803e 100644 --- a/stdlib/Std/Map.bux +++ b/stdlib/Std/Map.bux @@ -1,31 +1,120 @@ module Std::Map { +extern func bux_hash_bytes(ptr: *void, size: uint) -> uint; extern func bux_hash_string(s: String) -> uint; -struct MapEntry { - key: String, - value: int, +// --------------------------------------------------------------------------- +// Generic Map — works with value-type keys (int, float, etc.) +// For String keys, use StringMap below. +// --------------------------------------------------------------------------- + +struct MapEntry { + key: K, + value: V, occupied: bool, } -struct Map { - entries: *MapEntry, +struct Map { + entries: *MapEntry, cap: uint, len: uint, } -func Map_New(cap: uint) -> Map { - let total: uint = cap * sizeof(MapEntry); - let data: *MapEntry = bux_alloc(total) as *MapEntry; +func Map_New(cap: uint) -> Map { + let total: uint = cap * sizeof(MapEntry); + let data: *MapEntry = bux_alloc(total) as *MapEntry; var i: uint = 0; while i < cap { data[i].occupied = false; i = i + 1; } - return Map { entries: data, cap: cap, len: 0 }; + return Map { entries: data, cap: cap, len: 0 }; } -func Map_Set(m: *Map, key: String, value: int) { +func Map_Set(m: *Map, 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(m: *Map, 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(m: *Map, 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(m: *Map) -> uint { + return m.len; +} + +func Map_Free(m: *Map) { + bux_free(m.entries as *void); + m.entries = null as *MapEntry; + m.cap = 0; + m.len = 0; +} + +// --------------------------------------------------------------------------- +// StringMap — specialized Map for String keys, using strcmp +// --------------------------------------------------------------------------- + +struct StringMapEntry { + key: String, + value: V, + occupied: bool, +} + +struct StringMap { + entries: *StringMapEntry, + cap: uint, + len: uint, +} + +func StringMap_New(cap: uint) -> StringMap { + let total: uint = cap * sizeof(StringMapEntry); + let data: *StringMapEntry = bux_alloc(total) as *StringMapEntry; + var i: uint = 0; + while i < cap { + data[i].occupied = false; + i = i + 1; + } + return StringMap { entries: data, cap: cap, len: 0 }; +} + +func StringMap_Set(m: *StringMap, key: String, value: V) { let hash: uint = bux_hash_string(key); var idx: uint = hash % m.cap; while m.entries[idx].occupied { @@ -41,7 +130,7 @@ func Map_Set(m: *Map, key: String, value: int) { m.len = m.len + 1; } -func Map_Get(m: *Map, key: String) -> int { +func StringMap_Get(m: *StringMap, key: String) -> V { let hash: uint = bux_hash_string(key); var idx: uint = hash % m.cap; while m.entries[idx].occupied { @@ -50,10 +139,11 @@ func Map_Get(m: *Map, key: String) -> int { } 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(m: *StringMap, key: String) -> bool { let hash: uint = bux_hash_string(key); var idx: uint = hash % m.cap; while m.entries[idx].occupied { @@ -65,15 +155,15 @@ func Map_Has(m: *Map, key: String) -> bool { return false; } -func Map_Len(m: *Map) -> uint { +func StringMap_Len(m: *StringMap) -> uint { return m.len; } -func Map_Free(m: *Map) { +func StringMap_Free(m: *StringMap) { bux_free(m.entries as *void); - m.entries = null as *MapEntry; + m.entries = null as *StringMapEntry; m.cap = 0; m.len = 0; } -} +} \ No newline at end of file diff --git a/stdlib/Std/Path.bux b/stdlib/Std/Path.bux new file mode 100644 index 0000000..b559c30 --- /dev/null +++ b/stdlib/Std/Path.bux @@ -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); +} + +} \ No newline at end of file diff --git a/stdlib/Std/String.bux b/stdlib/Std/String.bux index 5b5b063..7012ee9 100644 --- a/stdlib/Std/String.bux +++ b/stdlib/Std/String.bux @@ -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_strcat(dest: *char8, src: String) -> *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 { return bux_strlen(s); @@ -42,4 +61,98 @@ func String_StartsWith(s: String, prefix: String) -> bool { 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); +} + +} \ No newline at end of file diff --git a/stdlib/runtime.c b/stdlib/runtime.c index 37a09c5..4cb2a99 100644 --- a/stdlib/runtime.c +++ b/stdlib/runtime.c @@ -153,6 +153,391 @@ char* bux_strncpy(char* dest, const char* src, unsigned int 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 */ unsigned int bux_hash_string(const char* s) { unsigned int hash = 5381;