diff --git a/PLAN.md b/PLAN.md index baa34bd..b3a38e4 100644 --- a/PLAN.md +++ b/PLAN.md @@ -157,12 +157,15 @@ Bux is a fast, compiled, strongly-typed, multi-paradigm systems programming lang | Module | Status | Requirements | |--------|--------|-------------| | `Std::Io` | ✅ | `Print`, `PrintLine`, `PrintInt`, `ReadLine` (wrap C stdio) | -| `Std::Memory` | ✅ | `bux_alloc`, `bux_realloc`, `bux_free` (wrap `malloc`/`free`) | -| `Std::String` | ✅ | Full API: `String_Len`, `String_Eq`, `String_Concat`, `String_Copy`, `String_StartsWith`, `String_EndsWith`, `String_Contains`, `String_Slice`, `String_Trim`, `String_TrimLeft`, `String_TrimRight`, `String_FromInt`, `String_ToInt`, `StringBuilder`; C wrappers in `runtime.c` | +| `Std::Mem` | ✅ | `Alloc`, `Realloc`, `Free`, `MemEq`, `New` — wrappers around C runtime | +| `Std::String` | ✅ | Full API: `String_Len`, `String_Eq`, `String_Concat`, `String_Copy`, `String_StartsWith`, `String_EndsWith`, `String_Contains`, `String_Slice`, `String_Trim`, `String_TrimLeft`, `String_TrimRight`, `String_FromInt`, `String_ToInt`, `StringBuilder`; plus `String_Find`, `String_Replace`, `String_Format1/2/3`; C wrappers in `runtime.c` | | `Std::Array` | ✅ | Fully generic `Array` with `Array_New`, `Array_Push`, `Array_Get`, `Array_Len`, `Array_Free`; generic struct methods with auto-addressing | | `Std::Map` | ✅ | Generic `Map` with `Map_New`, `Map_Set`, `Map_Get`, `Map_Has`, `Map_Len`, `Map_Free`; value-type keys with strcmp | +| `Std::StringMap` | ✅ | Specialized `StringMap` for String keys using `strcmp` | +| `Std::Set` | ✅ | Generic `Set` with `Set_New`, `Set_Add`, `Set_Has`, `Set_Len`, `Set_Free` | | `Std::Math` | ✅ | `Sqrt`, `Pow`, `Min`, `Max`, `Abs`, `MinF`, `MaxF`, `AbsF` (float64 + int64 variants, C runtime wrappers) | -| `Std::Path` | ✅ | File exists, basic path operations | +| `Std::Path` | ✅ | `Path_Join`, `Path_Parent`, `Path_Ext` | +| `Std::Fs` | ✅ | `DirExists`, `Mkdir`, `ListDir` | | `Std::Os` | ⏳ | `Args`, `Env`, `Exit`, `Cwd` | | `Std::Process` | ⏳ | Spawn subprocess, read stdout/stderr | | **`Std::Result`** | ✅ | Algebraic enums `Result` and `Option` with `NewOk`/`NewErr`/`NewSome`/`NewNone`; `?` try operator desugared in HIR | @@ -173,9 +176,13 @@ Bux is a fast, compiled, strongly-typed, multi-paradigm systems programming lang - ✅ Generic type inference: `Max(10, 20)` instead of `Max(10, 20)` — compiler infers `T` from argument types - ✅ `extend Box` syntax: parser support for generic impl blocks - ✅ String slicing, trimming, contains, StringBuilder (`strings2` example) +- ✅ String find, replace, format (`String_Find`, `String_Replace`, `String_Format`) - ✅ Generic `Map` with value-type keys +- ✅ Generic `Set` for deduplication +- ✅ File system operations (`Std::Fs`) +- ✅ Memory management wrappers (`Std::Mem`) -**Deliverable:** Can write a non-trivial CLI tool entirely in Bux. ✅ 18 example programs working: `hello`, `fibonacci`, `factorial`, `structs`, `enums`, `methods`, `algebraic_enums`, `generics`, `generics_struct`, `generic_infer`, `generic_infer2`, `extend_generic`, `pattern_matching`, `strings`, `strings2`, `map`, `result_option`, `try_operator`. +**Deliverable:** Can write a non-trivial CLI tool entirely in Bux. ✅ 20+ example programs working. --- @@ -205,25 +212,29 @@ Bux is a fast, compiled, strongly-typed, multi-paradigm systems programming lang | 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 | +| `HashSet[string]` | hir_lower (1 use) | ✅ `Set` available | | `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 | +| `&"..."` / `fmt"..."` | sema, c_backend (119 uses) | ✅ `String_Format1/2/3` available | +| `split()`, `join()` | lexer, parser, cli | ✅ `String_SplitCount`, `String_SplitPart`, `String_Join2` | | `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 | +| File read/write | cli | ✅ `ReadFile`, `WriteFile` in `Std::Io` | +| OS path operations | cli, manifest | ✅ `Path_Join`, `DirExists`, `Mkdir` in `Std::Path`/`Std::Fs` | ### 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.0 — Stdlib blockers (all resolved ✅): + ├── StringMap ✅ + ├── String split/join ✅ + ├── String formatting ✅ + ├── File I/O (readFile, writeFile, fileExists) ✅ + └── OS path (joinPath, parentDir) ✅ + +Remaining gaps for self-host polish: + ├── `Std::Os` — `Args`, `Env`, `Exit`, `Cwd` + └── `Std::Process` — spawn subprocess Phase 7.1 — Foundation (no internal deps): ├── token.bux (enum + helpers) @@ -257,8 +268,8 @@ Phase 7.5 — Driver (depends on all): | 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 | +| `Map_Len` / `Set_Len` monomorphization bug | **Low** | C backend issue — use explicit type args or avoid; QBE backend unaffected | +| String formatting | **Medium** | `String_Format1/2/3` available via `bux_str_format` | | Array API gaps | **Low** | Extend Array module as needed during porting | ### Estimated Effort diff --git a/README.md b/README.md index 9d0d767..3e055e9 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ func Main() -> int { | **Methods** | `extend` blocks for struct methods | | **Interfaces** | `interface` + `extend` for trait-like behavior | | **Error Handling** | `Result`, `Option`, and the `?` operator | -| **Standard Library** | `Io`, `Array`, `String`, `Map` | +| **Standard Library** | `Io`, `Array`, `String`, `Map`, `Fs`, `Mem`, `Set`, `Path`, `Math`, `Task`, `Channel` | | **Backend** | QBE native codegen (self-hosting), C transpiler (bootstrap) | | **Strings** | Raw multi-line backtick strings (`...`), C-string interop | | **Gradual Ownership** | `@[Checked]` + `&T`/`&mut T` borrow checking | @@ -168,6 +168,11 @@ bux/ ├── src_bux/ # Self-hosting compiler source (Bux) ├── _selfhost/ # Self-hosting build artifacts ├── stdlib/ # Standard library (.bux + .c runtime) +│ └── Std/ +│ ├── Io.bux, String.bux, Array.bux, Map.bux +│ ├── Fs.bux, Mem.bux, Set.bux +│ ├── Path.bux, Math.bux +│ └── Task.bux, Channel.bux ├── examples/ # Example programs ├── tests/ # Unit tests (Nim) ├── docs/ # Documentation diff --git a/_selfhost/src/c_backend.bux b/_selfhost/src/c_backend.bux new file mode 100644 index 0000000..502d9b5 --- /dev/null +++ b/_selfhost/src/c_backend.bux @@ -0,0 +1,585 @@ +// c_backend.bux — C transpiler backend (ported from c_backend.nim) +// Generates C code from the HIR. +module CBackend { + +// --------------------------------------------------------------------------- +// Type → C type name +// --------------------------------------------------------------------------- + +func CBackend_TypeToC(kind: int) -> String { + if kind == tyVoid { return "void"; } + if kind == tyBool { return "bool"; } + if kind == tyBool8 { return "bool"; } + if kind == tyBool16 { return "bool"; } + if kind == tyBool32 { return "bool"; } + if kind == tyChar8 { return "char"; } + if kind == tyChar16 { return "uint16"; } + if kind == tyChar32 { return "uint32"; } + if kind == tyStr { return "String"; } + if kind == tyInt8 { return "int8"; } + if kind == tyInt16 { return "int16"; } + if kind == tyInt32 { return "int32"; } + if kind == tyInt64 { return "int64"; } + if kind == tyInt{ return "int"; } + if kind == tyUInt8 { return "uint8"; } + if kind == tyUInt16 { return "uint16"; } + if kind == tyUInt32 { return "uint32"; } + if kind == tyUInt64 { return "uint64"; } + if kind == tyUInt { return "uint"; } + if kind == tyFloat32 { return "float32"; } + if kind == tyFloat64 { return "float64"; } + if kind == tyPointer { return "void*"; } + if kind == tyNamed { return "int"; } + return "int"; +} + +func CBackend_OpToC(op: int) -> String { + if op == tkPlus { return "+"; } + if op == tkMinus { return "-"; } + if op == tkStar { return "*"; } + if op == tkSlash { return "/"; } + if op == tkPercent { return "%"; } + if op == tkEq { return "=="; } + if op == tkNe { return "!="; } + if op == tkLt { return "<"; } + if op == tkLe { return "<="; } + if op == tkGt { return ">"; } + if op == tkGe { return ">="; } + if op == tkAmpAmp { return "&&"; } + if op == tkPipePipe { return "||"; } + if op == tkBang { return "!"; } + if op == tkAmp { return "&"; } + if op == tkPipe { return "|"; } + if op == tkCaret { return "^"; } + if op == tkShl { return "<<"; } + if op == tkShr { return ">>"; } + if op == tkAssign { return "="; } + return "?"; +} + +// --------------------------------------------------------------------------- +// StringBuilder-based C emitter +// --------------------------------------------------------------------------- + +struct CEmitter { + sb: StringBuilder, + indent: int, +} + +func CBE_Init() -> CEmitter { + var sb: StringBuilder = StringBuilder_NewCap(4096); + return CEmitter { sb: sb, indent: 0 }; +} + +func CBE_Emit(cbe: *CEmitter, text: String) { + var i: int = 0; + while i < cbe.indent { + StringBuilder_Append(&cbe.sb, " "); + i = i + 1; + } + StringBuilder_Append(&cbe.sb, text); +} + +func CBE_EmitLine(cbe: *CEmitter, text: String) { + CBE_Emit(cbe, text); + StringBuilder_Append(&cbe.sb, "\n"); +} + +func CBE_Build(cbe: *CEmitter) -> String { + return StringBuilder_Build(&cbe.sb); +} + +// --------------------------------------------------------------------------- +// Emit HIR node +// --------------------------------------------------------------------------- + +func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) { + if node == null as *HirNode { return; } + let kind: int = node.kind; + + // Literal + if kind == hLit { + if node.intValue == tkNull { + StringBuilder_Append(&cbe.sb, "0"); + } else { + StringBuilder_Append(&cbe.sb, node.strValue); + } + return; + } + + // Variable + if kind == hVar { + StringBuilder_Append(&cbe.sb, node.strValue); + return; + } + + // Binary + if kind == hBinary { + CBE_EmitExpr(cbe, node.child1); + StringBuilder_Append(&cbe.sb, " "); + StringBuilder_Append(&cbe.sb, CBackend_OpToC(node.intValue)); + StringBuilder_Append(&cbe.sb, " "); + CBE_EmitExpr(cbe, node.child2); + return; + } + + // Unary + if kind == hUnary { + StringBuilder_Append(&cbe.sb, CBackend_OpToC(node.intValue)); + CBE_EmitExpr(cbe, node.child1); + return; + } + + // Call + if kind == hCall { + StringBuilder_Append(&cbe.sb, node.strValue); + StringBuilder_Append(&cbe.sb, "("); + if node.child1 != null as *HirNode { + CBE_EmitExpr(cbe, node.child1); + } + if node.child2 != null as *HirNode { + StringBuilder_Append(&cbe.sb, ", "); + CBE_EmitExpr(cbe, node.child2); + } + StringBuilder_Append(&cbe.sb, ")"); + return; + } + + // spawn Callee(args) + if kind == hSpawn { + StringBuilder_Append(&cbe.sb, "bux_async_spawn("); + StringBuilder_Append(&cbe.sb, node.strValue); + if node.child1 != null as *HirNode { + StringBuilder_Append(&cbe.sb, ", (void*)"); + CBE_EmitExpr(cbe, node.child1); + } + StringBuilder_Append(&cbe.sb, ")"); + return; + } + + // await + if kind == hAwait { + StringBuilder_Append(&cbe.sb, "bux_async_await("); + CBE_EmitExpr(cbe, node.child1); + StringBuilder_Append(&cbe.sb, ")"); + return; + } + + // Return + if kind == hReturn { + StringBuilder_Append(&cbe.sb, "return"); + if node.child1 != null as *HirNode { + StringBuilder_Append(&cbe.sb, " "); + CBE_EmitExpr(cbe, node.child1); + } + return; + } + + // Alloca + if kind == hAlloca { + if !String_Eq(node.typeName, "") { + StringBuilder_Append(&cbe.sb, node.typeName); + } else { + StringBuilder_Append(&cbe.sb, "int"); + } + StringBuilder_Append(&cbe.sb, " "); + StringBuilder_Append(&cbe.sb, node.strValue); + return; + } + + // Store: combine alloca + value into single declaration + if kind == hStore { + if node.child1 != null as *HirNode && node.child1.kind == hAlloca { + // Declaration with initializer: Type x = value; + if !String_Eq(node.child1.typeName, "") { + StringBuilder_Append(&cbe.sb, node.child1.typeName); + } else { + StringBuilder_Append(&cbe.sb, CBackend_TypeToC(node.child1.intValue)); + } + StringBuilder_Append(&cbe.sb, " "); + StringBuilder_Append(&cbe.sb, node.child1.strValue); + StringBuilder_Append(&cbe.sb, " = "); + CBE_EmitExpr(cbe, node.child2); + return; + } + // Plain assignment + CBE_EmitExpr(cbe, node.child1); + StringBuilder_Append(&cbe.sb, " = "); + CBE_EmitExpr(cbe, node.child2); + return; + } + + // If + if kind == hIf { + StringBuilder_Append(&cbe.sb, "if ("); + CBE_EmitExpr(cbe, node.child1); + StringBuilder_Append(&cbe.sb, ") {\n"); + if node.child2 != null as *HirNode { + cbe.indent = cbe.indent + 1; + CBE_EmitExpr(cbe, node.child2); + cbe.indent = cbe.indent - 1; + } + var sp: int = 0; + while sp < cbe.indent { + StringBuilder_Append(&cbe.sb, " "); + sp = sp + 1; + } + StringBuilder_Append(&cbe.sb, "}"); + let elseBlock: *HirNode = node.extraData as *HirNode; + if elseBlock != null as *HirNode { + StringBuilder_Append(&cbe.sb, " else {\n"); + cbe.indent = cbe.indent + 1; + CBE_EmitExpr(cbe, elseBlock); + cbe.indent = cbe.indent - 1; + sp = 0; + while sp < cbe.indent { + StringBuilder_Append(&cbe.sb, " "); + sp = sp + 1; + } + StringBuilder_Append(&cbe.sb, "}\n"); + } else { + StringBuilder_Append(&cbe.sb, "\n"); + } + return; + } + + // While + if kind == hWhile { + StringBuilder_Append(&cbe.sb, "while ("); + CBE_EmitExpr(cbe, node.child1); + StringBuilder_Append(&cbe.sb, ") {\n"); + if node.child2 != null as *HirNode { + cbe.indent = cbe.indent + 1; + CBE_EmitExpr(cbe, node.child2); + cbe.indent = cbe.indent - 1; + } + var sp: int = 0; + while sp < cbe.indent { + StringBuilder_Append(&cbe.sb, " "); + sp = sp + 1; + } + StringBuilder_Append(&cbe.sb, "}"); + return; + } + + // Loop (infinite) + if kind == hLoop { + StringBuilder_Append(&cbe.sb, "while (1) {\n"); + if node.child1 != null as *HirNode { + cbe.indent = cbe.indent + 1; + CBE_EmitExpr(cbe, node.child1); + cbe.indent = cbe.indent - 1; + } + var sp: int = 0; + while sp < cbe.indent { + StringBuilder_Append(&cbe.sb, " "); + sp = sp + 1; + } + StringBuilder_Append(&cbe.sb, "}"); + return; + } + + // Field access: obj.field — emit as obj->field (since most are pointers) + if kind == hFieldPtr { + CBE_EmitExpr(cbe, node.child1); + StringBuilder_Append(&cbe.sb, "->"); + StringBuilder_Append(&cbe.sb, node.strValue); + return; + } + + // Sizeof: sizeof(Type) — emit as sizeof(Type) + if kind == hSizeOf { + StringBuilder_Append(&cbe.sb, "sizeof("); + if !String_Eq(node.typeName, "") { + StringBuilder_Append(&cbe.sb, node.typeName); + } else { + StringBuilder_Append(&cbe.sb, "int"); + } + StringBuilder_Append(&cbe.sb, ")"); + return; + } + + // Index: arr[idx] — emit as arr[idx] + if kind == hIndexPtr { + CBE_EmitExpr(cbe, node.child1); + StringBuilder_Append(&cbe.sb, "["); + CBE_EmitExpr(cbe, node.child2); + StringBuilder_Append(&cbe.sb, "]"); + return; + } + + // Load: *ptr — emit as *ptr (dereference) + if kind == hLoad { + StringBuilder_Append(&cbe.sb, "(*"); + CBE_EmitExpr(cbe, node.child1); + StringBuilder_Append(&cbe.sb, ")"); + return; + } + + // Assign: target = value + if kind == hAssign { + CBE_EmitExpr(cbe, node.child1); + StringBuilder_Append(&cbe.sb, " = "); + CBE_EmitExpr(cbe, node.child2); + return; + } + + // Cast + if kind == hCast { + StringBuilder_Append(&cbe.sb, "(("); + if !String_Eq(node.typeName, "") { + StringBuilder_Append(&cbe.sb, node.typeName); + } else { + StringBuilder_Append(&cbe.sb, CBackend_TypeToC(node.typeKind)); + } + StringBuilder_Append(&cbe.sb, ")"); + CBE_EmitExpr(cbe, node.child1); + StringBuilder_Append(&cbe.sb, ")"); + return; + } + + // Struct init: ((TypeName){.field = value, ...}) + if kind == hStructInit { + StringBuilder_Append(&cbe.sb, "(("); + StringBuilder_Append(&cbe.sb, node.strValue); + StringBuilder_Append(&cbe.sb, "){"); + // Emit fields (chained via child3) + var field: *HirNode = node.child1; + var first: bool = true; + while field != null as *HirNode { + if !first { + StringBuilder_Append(&cbe.sb, ", "); + } + StringBuilder_Append(&cbe.sb, "."); + StringBuilder_Append(&cbe.sb, field.strValue); + StringBuilder_Append(&cbe.sb, " = "); + CBE_EmitExpr(cbe, field.child1); + first = false; + field = field.child3; + } + StringBuilder_Append(&cbe.sb, "})"); + return; + } + + // Block — emit each statement via child3 linked list + if kind == hBlock { + var child: *HirNode = node.child1; + while child != null as *HirNode { + // Indent + var sp: int = 0; + while sp < cbe.indent { + StringBuilder_Append(&cbe.sb, " "); + sp = sp + 1; + } + CBE_EmitExpr(cbe, child); + // Add semicolon for statements that need it + if child.kind != hBlock && child.kind != hIf && child.kind != hWhile && child.kind != hLoop { + StringBuilder_Append(&cbe.sb, ";"); + } + StringBuilder_Append(&cbe.sb, "\n"); + child = child.child3; + } + return; + } +} + +// --------------------------------------------------------------------------- +// Emit function declaration +// --------------------------------------------------------------------------- + +func CBE_EmitFuncDecl(cbe: *CEmitter, f: *HirFunc) { + // Return type + if String_Eq(f.retTypeName, "") || String_Eq(f.retTypeName, "void") { + StringBuilder_Append(&cbe.sb, "void "); + } else { + StringBuilder_Append(&cbe.sb, f.retTypeName); + StringBuilder_Append(&cbe.sb, " "); + } + + StringBuilder_Append(&cbe.sb, f.name); + StringBuilder_Append(&cbe.sb, "("); + + // Parameters + var i: int = 0; + while i < f.paramCount { + if i > 0 { + StringBuilder_Append(&cbe.sb, ", "); + } + // Use param type info + var pname: String = ""; + var ptype: String = ""; + if i == 0 { pname = f.param0.name; ptype = f.param0.typeName; } + if i == 1 { pname = f.param1.name; ptype = f.param1.typeName; } + if i == 2 { pname = f.param2.name; ptype = f.param2.typeName; } + if i == 3 { pname = f.param3.name; ptype = f.param3.typeName; } + if i == 4 { pname = f.param4.name; ptype = f.param4.typeName; } + if i == 5 { pname = f.param5.name; ptype = f.param5.typeName; } + // Emit type + if String_Eq(ptype, "String") || String_Eq(ptype, "str") { + StringBuilder_Append(&cbe.sb, "String "); + } else if String_Eq(ptype, "bool") { + StringBuilder_Append(&cbe.sb, "bool "); + } else if String_Eq(ptype, "int") { + StringBuilder_Append(&cbe.sb, "int "); + } else if String_Eq(ptype, "int64") { + StringBuilder_Append(&cbe.sb, "int64 "); + } else if String_Eq(ptype, "uint") { + StringBuilder_Append(&cbe.sb, "uint "); + } else if String_Eq(ptype, "float64") { + StringBuilder_Append(&cbe.sb, "float64 "); + } else if !String_Eq(ptype, "") { + StringBuilder_Append(&cbe.sb, ptype); + StringBuilder_Append(&cbe.sb, " "); + } else { + StringBuilder_Append(&cbe.sb, "int "); // fallback + } + StringBuilder_Append(&cbe.sb, pname); + i = i + 1; + } + + StringBuilder_Append(&cbe.sb, ")"); +} + +// --------------------------------------------------------------------------- +// Generate complete C module +// --------------------------------------------------------------------------- + +func CBackend_Generate(mod: *HirModule) -> String { + let cbe: *CEmitter = bux_alloc(sizeof(CEmitter)) as *CEmitter; + cbe.sb = StringBuilder_NewCap(8192); + cbe.indent = 0; + + // Header + StringBuilder_Append(&cbe.sb, "// Generated by Bux C Backend\n"); + StringBuilder_Append(&cbe.sb, "#include \n"); + StringBuilder_Append(&cbe.sb, "#include \n"); + StringBuilder_Append(&cbe.sb, "#include \n"); + StringBuilder_Append(&cbe.sb, "#include \n"); + StringBuilder_Append(&cbe.sb, "#include \n\n"); + // Type aliases + StringBuilder_Append(&cbe.sb, "typedef const char* String;\n"); + StringBuilder_Append(&cbe.sb, "typedef unsigned char uint8;\n"); + StringBuilder_Append(&cbe.sb, "typedef unsigned short uint16;\n"); + StringBuilder_Append(&cbe.sb, "typedef unsigned int uint32;\n"); + StringBuilder_Append(&cbe.sb, "typedef unsigned long long uint64;\n"); + StringBuilder_Append(&cbe.sb, "typedef signed char int8;\n"); + StringBuilder_Append(&cbe.sb, "typedef short int16;\n"); + StringBuilder_Append(&cbe.sb, "typedef long long int64;\n"); + StringBuilder_Append(&cbe.sb, "typedef float float32;\n"); + StringBuilder_Append(&cbe.sb, "typedef double float64;\n\n"); + // Runtime declarations + StringBuilder_Append(&cbe.sb, "void* bux_alloc(unsigned int size);\n"); + StringBuilder_Append(&cbe.sb, "void bux_free(void* ptr);\n\n"); + + // Forward declare all struct types + var si: int = 0; + while si < mod.structCount { + StringBuilder_Append(&cbe.sb, "typedef struct "); + StringBuilder_Append(&cbe.sb, mod.structs[si].name); + StringBuilder_Append(&cbe.sb, " "); + StringBuilder_Append(&cbe.sb, mod.structs[si].name); + StringBuilder_Append(&cbe.sb, ";\n"); + si = si + 1; + } + StringBuilder_Append(&cbe.sb, "\n"); + + // Struct definitions + si = 0; + while si < mod.structCount { + StringBuilder_Append(&cbe.sb, "typedef struct {\n"); + var fi: int = 0; + while fi < mod.structs[si].fieldCount { + StringBuilder_Append(&cbe.sb, " "); + var ft: String = mod.structs[si].fields[fi].typeName; + if String_Eq(ft, "int") || String_Eq(ft, "") { + StringBuilder_Append(&cbe.sb, "int"); + } else if String_Eq(ft, "String") { + StringBuilder_Append(&cbe.sb, "String"); + } else if String_Eq(ft, "bool") { + StringBuilder_Append(&cbe.sb, "bool"); + } else if String_Eq(ft, "uint32") { + StringBuilder_Append(&cbe.sb, "uint32"); + } else { + // Use the type name directly (handles "Foo*", "Bar", etc.) + StringBuilder_Append(&cbe.sb, ft); + } + StringBuilder_Append(&cbe.sb, " "); + StringBuilder_Append(&cbe.sb, mod.structs[si].fields[fi].name); + StringBuilder_Append(&cbe.sb, ";\n"); + fi = fi + 1; + } + StringBuilder_Append(&cbe.sb, "} "); + StringBuilder_Append(&cbe.sb, mod.structs[si].name); + StringBuilder_Append(&cbe.sb, ";\n\n"); + si = si + 1; + } + + // Forward declarations for all functions + var i: int = 0; + while i < mod.funcCount { + CBE_EmitFuncDecl(cbe, &mod.funcs[i]); + StringBuilder_Append(&cbe.sb, ";\n"); + i = i + 1; + } + StringBuilder_Append(&cbe.sb, "\n"); + + // Extern declarations + i = 0; + while i < mod.externCount { + CBE_EmitFuncDecl(cbe, &mod.externFuncs[i]); + StringBuilder_Append(&cbe.sb, ";\n"); + i = i + 1; + } + StringBuilder_Append(&cbe.sb, "\n"); + + // Function definitions + var hasMain: bool = false; + i = 0; + while i < mod.funcCount { + if String_Eq(mod.funcs[i].name, "Main") { hasMain = true; } + // Skip forward declarations (functions without body) + let body: *HirNode = mod.funcs[i].body; + if body == null as *HirNode { + i = i + 1; + continue; + } + CBE_EmitFuncDecl(cbe, &mod.funcs[i]); + StringBuilder_Append(&cbe.sb, " {\n"); + // Body + var hasReturn: bool = false; + cbe.indent = 1; + CBE_EmitExpr(cbe, body); + cbe.indent = 0; + // Check if body has a return statement + var stmt: *HirNode = body.child1; + while stmt != null as *HirNode { + if stmt.kind == hReturn { hasReturn = true; } + stmt = stmt.child3; + } + // Add default return 0 only if function returns int and has no explicit return + if String_Eq(mod.funcs[i].retTypeName, "int") && !hasReturn { + StringBuilder_Append(&cbe.sb, " return 0;\n"); + } + StringBuilder_Append(&cbe.sb, "}\n\n"); + i = i + 1; + } + + // Generate C main wrapper if Main function exists + if hasMain { + StringBuilder_Append(&cbe.sb, "extern int g_argc;\n"); + StringBuilder_Append(&cbe.sb, "extern char** g_argv;\n"); + StringBuilder_Append(&cbe.sb, "int main(int argc, char** argv) {\n"); + StringBuilder_Append(&cbe.sb, " g_argc = argc;\n"); + StringBuilder_Append(&cbe.sb, " g_argv = argv;\n"); + StringBuilder_Append(&cbe.sb, " return Main();\n"); + StringBuilder_Append(&cbe.sb, "}\n"); + } + + return StringBuilder_Build(&cbe.sb); +} + +func CBackend_Free(cbe: *CEmitter) { + StringBuilder_Free(&cbe.sb); + bux_free(cbe as *void); +} +} diff --git a/_selfhost/src/nim_backend.bux b/_selfhost/src/nim_backend.bux new file mode 100644 index 0000000..8484771 --- /dev/null +++ b/_selfhost/src/nim_backend.bux @@ -0,0 +1,812 @@ +// nim_backend.bux — Nim transpiler backend +// Generates Nim code from the HIR. Much simpler than C backend. + +import Std::String; +import Std::Io::{Print, PrintLine}; + +module NimBackend { + +// --------------------------------------------------------------------------- +// Type → Nim type name +// --------------------------------------------------------------------------- + +func NimBackend_TypeToNim(kind: int) -> String { + if kind == tyVoid { return "void"; } + if kind == tyBool || kind == tyBool8 || kind == tyBool16 || kind == tyBool32 { return "bool"; } + if kind == tyChar8 { return "char"; } + if kind == tyChar16 { return "uint16"; } + if kind == tyChar32 { return "uint32"; } + if kind == tyStr { return "string"; } + if kind == tyInt8 { return "int8"; } + if kind == tyInt16 { return "int16"; } + if kind == tyInt32 { return "int32"; } + if kind == tyInt64 { return "int64"; } + if kind == tyInt { return "int"; } + if kind == tyUInt8 { return "uint8"; } + if kind == tyUInt16 { return "uint16"; } + if kind == tyUInt32 { return "uint32"; } + if kind == tyUInt64 { return "uint64"; } + if kind == tyUInt { return "uint"; } + if kind == tyFloat32 { return "float32"; } + if kind == tyFloat64 { return "float64"; } + if kind == tyPointer { return "pointer"; } + return "int"; +} + +// --------------------------------------------------------------------------- +// Operator → Nim +// --------------------------------------------------------------------------- + +func NimBackend_OpToNim(op: int) -> String { + if op == tkPlus { return "+"; } + if op == tkMinus { return "-"; } + if op == tkStar { return "*"; } + if op == tkSlash { return "/"; } + if op == tkPercent { return "%"; } + if op == tkEq { return "=="; } + if op == tkNe { return "!="; } + if op == tkLt { return "<"; } + if op == tkLe { return "<="; } + if op == tkGt { return ">"; } + if op == tkGe { return ">="; } + if op == tkAmpAmp { return "and"; } + if op == tkPipePipe { return "or"; } + if op == tkBang { return "not"; } + if op == tkAmp { return "and"; } + if op == tkPipe { return "or"; } + if op == tkCaret { return "xor"; } + if op == tkShl { return "shl"; } + if op == tkShr { return "shr"; } + if op == tkAssign { return "="; } + return "?"; +} + +// --------------------------------------------------------------------------- +// Emitter +// --------------------------------------------------------------------------- + +struct NBEmitter { + sb: StringBuilder; + indent: int; +} + +func NBE_Init() -> NBEmitter { + var sb: StringBuilder = StringBuilder_NewCap(4096); + return NBEmitter { sb: sb, indent: 0 }; +} + +func NBE_EmitIndent(nbe: *NBEmitter) { + var i: int = 0; + while i < nbe.indent { + StringBuilder_Append(&nbe.sb, " "); + i = i + 1; + } +} + +func NBE_Emit(nbe: *NBEmitter, text: String) { + NBE_EmitIndent(nbe); + StringBuilder_Append(&nbe.sb, text); +} + +func NBE_EmitLine(nbe: *NBEmitter, text: String) { + NBE_Emit(nbe, text); + StringBuilder_Append(&nbe.sb, "\n"); +} + +func NBE_Build(nbe: *NBEmitter) -> String { + return StringBuilder_Build(&nbe.sb); +} + +func NBE_Append(nbe: *NBEmitter, text: String) { + StringBuilder_Append(&nbe.sb, text); +} + +func NBE_AppendLine(nbe: *NBEmitter, text: String) { + StringBuilder_Append(&nbe.sb, text); + StringBuilder_Append(&nbe.sb, "\n"); +} + +func NBE_AppendChar(nbe: *NBEmitter, c: char8) { + var tmp: *char8 = bux_alloc(2) as *char8; + tmp[0] = c; + tmp[1] = 0 as char8; + StringBuilder_Append(&nbe.sb, tmp); +} + +// --------------------------------------------------------------------------- +// Expression emission +// --------------------------------------------------------------------------- + +func NBE_EmitExpr(nbe: *NBEmitter, node: *HirNode) { + if node == null as *HirNode { return; } + let kind: int = node.kind; + + // Literal + if kind == hLit { + if node.intValue == tkNull { + NBE_Append(nbe, "nil"); + } else if node.intValue == tkStringLiteral { + // Nim string: emit quoted with escapes + NBE_Append(nbe, "\""); + var s: String = node.strValue; + if s != null as String { + var slen: int = bux_strlen(s) as int; + var start: int = 0; + var end: int = slen; + // Strip outer quotes if present + if slen >= 2 { + var fc: char8 = s[0]; + var lc: char8 = s[slen - 1]; + if fc == 34 as char8 && lc == 34 as char8 { + start = 1; + end = slen - 1; + } + } + var i: int = start; + while i < end { + var c: char8 = s[i]; + if c == 10 as char8 { NBE_Append(nbe, "\\n"); } // \n + else if c == 13 as char8 { NBE_Append(nbe, "\\r"); } // \r + else if c == 9 as char8 { NBE_Append(nbe, "\\t"); } // \t + else if c == 34 as char8 { NBE_Append(nbe, "\\\""); } // \" + else if c == 92 as char8 { NBE_Append(nbe, "\\\\"); } // \\ + else { NBE_AppendChar(nbe, c); } + i = i + 1; + } + } + NBE_Append(nbe, "\""); + } else if node.intValue == tkBoolLiteral { + NBE_Append(nbe, node.strValue); + } else { + NBE_Append(nbe, node.strValue); + } + return; + } + + // Variable + if kind == hVar { + NBE_Append(nbe, NimBackend_EscapeIdent(node.strValue)); + return; + } + + // Binary + if kind == hBinary { + // Special case: String == null → check for empty string + if node.intValue == tkEq || node.intValue == tkNe { + var isNullCmp: bool = false; + var other: *HirNode = null as *HirNode; + if node.child1 != null as *HirNode && node.child1.kind == hLit && node.child1.intValue == tkNull { + isNullCmp = true; + other = node.child2; + } + if !isNullCmp && node.child2 != null as *HirNode && node.child2.kind == hLit && node.child2.intValue == tkNull { + isNullCmp = true; + other = node.child1; + } + if isNullCmp && other != null as *HirNode && other.typeKind == tyStr { + if node.intValue == tkEq { + NBE_EmitExpr(nbe, other); + NBE_Append(nbe, " == \"\""); + } else { + NBE_EmitExpr(nbe, other); + NBE_Append(nbe, " != \"\""); + } + return; + } + } + if node.child1 != null as *HirNode { + NBE_EmitExpr(nbe, node.child1); + NBE_Append(nbe, " "); + } + // Emit operator only if we have a left operand + if node.child1 != null as *HirNode { + NBE_Append(nbe, NimBackend_OpToNim(node.intValue)); + NBE_Append(nbe, " "); + } + NBE_EmitExpr(nbe, node.child2); + return; + } + + // Unary + if kind == hUnary { + // Skip address-of operator (&) — Nim uses automatic dereference + if node.intValue == tkAmp { + NBE_EmitExpr(nbe, node.child1); + return; + } + NBE_Append(nbe, NimBackend_OpToNim(node.intValue)); + NBE_Append(nbe, "("); + NBE_EmitExpr(nbe, node.child1); + NBE_Append(nbe, ")"); + return; + } + + // Call + if kind == hCall { + var fname: String = node.strValue; + // Translate StringBuilder calls to Nim native operations + if String_Eq(fname, "StringBuilder_Append") { + // sb = sb & text (string concatenation) + NBE_EmitExpr(nbe, node.child1); + NBE_Append(nbe, " = "); + NBE_EmitExpr(nbe, node.child1); + NBE_Append(nbe, " & "); + NBE_EmitExpr(nbe, node.child2); + return; + } + if String_Eq(fname, "StringBuilder_NewCap") { + // Just empty string + NBE_Append(nbe, "\"\""); + return; + } + if String_Eq(fname, "StringBuilder_Build") { + // Just return the string field + NBE_EmitExpr(nbe, node.child1); + return; + } + NBE_Append(nbe, NimBackend_EscapeIdent(fname)); + NBE_Append(nbe, "("); + if node.child1 != null as *HirNode { + NBE_EmitExpr(nbe, node.child1); + } + if node.child2 != null as *HirNode { + NBE_Append(nbe, ", "); + NBE_EmitExpr(nbe, node.child2); + } + NBE_Append(nbe, ")"); + return; + } + + // Field access: obj.field → obj.field (Nim auto-dereferences) + if kind == hFieldPtr || kind == hArrowField { + NBE_EmitExpr(nbe, node.child1); + NBE_Append(nbe, "."); + NBE_Append(nbe, node.strValue); + return; + } + + // Index: arr[idx] + if kind == hIndexPtr { + NBE_EmitExpr(nbe, node.child1); + NBE_Append(nbe, "["); + NBE_EmitExpr(nbe, node.child2); + NBE_Append(nbe, "]"); + return; + } + + // Cast: expr as Type + if kind == hCast { + // Special case: null as Type → appropriate default + if node.child1 != null as *HirNode && node.child1.kind == hLit && node.child1.intValue == tkNull { + var typeName: String = node.typeName; + if !String_Eq(typeName, "") { + var converted: String = NimBackend_ConvertTypeName(typeName); + if String_Eq(converted, "string") { + NBE_Append(nbe, "\"\""); + return; + } + if String_Eq(converted, "pointer") { + NBE_Append(nbe, "nil"); + return; + } + } + // For pointer types (ending in * or "ptr "), emit nil + var tlen: int = bux_strlen(typeName) as int; + if tlen > 1 { + var lastCh: char8 = typeName[tlen - 1]; + if lastCh == 42 as char8 { // '*' + NBE_Append(nbe, "nil"); + return; + } + } + } + NBE_Append(nbe, "cast["); + if !String_Eq(node.typeName, "") { + NBE_Append(nbe, NimBackend_ConvertTypeName(node.typeName)); + } else { + NBE_Append(nbe, NimBackend_TypeToNim(node.typeKind)); + } + NBE_Append(nbe, "]("); + NBE_EmitExpr(nbe, node.child1); + NBE_Append(nbe, ")"); + return; + } + + // Is + if kind == hIs { + NBE_EmitExpr(nbe, node.child1); + NBE_Append(nbe, " is "); + if !String_Eq(node.typeName, "") { + NBE_Append(nbe, node.typeName); + } + return; + } + + // SizeOf + if kind == hSizeOf { + NBE_Append(nbe, "sizeof("); + if !String_Eq(node.typeName, "") { + NBE_Append(nbe, node.typeName); + } else { + NBE_Append(nbe, "int"); + } + NBE_Append(nbe, ")"); + return; + } + + // Alloca: variable declaration (used within Store for full decl) + if kind == hAlloca { + if !String_Eq(node.typeName, "") { + NBE_Append(nbe, NimBackend_ConvertTypeNameParam(node.typeName)); + } else { + NBE_Append(nbe, NimBackend_TypeToNim(node.typeKind)); + } + NBE_Append(nbe, " "); + NBE_Append(nbe, NimBackend_EscapeIdent(node.strValue)); + return; + } + + // Assign: target = value + if kind == hAssign { + NBE_EmitExpr(nbe, node.child1); + NBE_Append(nbe, " = "); + NBE_EmitExpr(nbe, node.child2); + return; + } + + // Store: assignment or declaration with init + if kind == hStore { + if node.child1 != null as *HirNode && node.child1.kind == hAlloca { + // Declaration: var x: Type = value + NBE_Append(nbe, "var "); + NBE_Append(nbe, NimBackend_EscapeIdent(node.child1.strValue)); + if !String_Eq(node.child1.typeName, "") { + NBE_Append(nbe, ": "); + NBE_Append(nbe, NimBackend_ConvertTypeNameParam(node.child1.typeName)); + } + if node.child2 != null as *HirNode { + NBE_Append(nbe, " = "); + NBE_EmitExpr(nbe, node.child2); + } + return; + } + // Plain assignment + NBE_EmitExpr(nbe, node.child1); + NBE_Append(nbe, " = "); + NBE_EmitExpr(nbe, node.child2); + return; + } + + // If + if kind == hIf { + NBE_Append(nbe, "if "); + NBE_EmitExpr(nbe, node.child1); + NBE_AppendLine(nbe, ":"); + if node.child2 != null as *HirNode { + nbe.indent = nbe.indent + 1; + if node.child2.kind != hBlock { NBE_EmitIndent(nbe); } + NBE_EmitExpr(nbe, node.child2); + nbe.indent = nbe.indent - 1; + } else { + nbe.indent = nbe.indent + 1; + NBE_EmitLine(nbe, "discard"); + nbe.indent = nbe.indent - 1; + } + if node.child3 != null as *HirNode { + NBE_EmitIndent(nbe); // indent else at same level as if + NBE_AppendLine(nbe, "else:"); + nbe.indent = nbe.indent + 1; + if node.child3.kind != hBlock { NBE_EmitIndent(nbe); } + NBE_EmitExpr(nbe, node.child3); + nbe.indent = nbe.indent - 1; + } + return; + } + + // While + if kind == hWhile { + NBE_Append(nbe, "while "); + NBE_EmitExpr(nbe, node.child1); + NBE_AppendLine(nbe, ":"); + if node.child2 != null as *HirNode { + nbe.indent = nbe.indent + 1; + if node.child2.kind != hBlock { NBE_EmitIndent(nbe); } + NBE_EmitExpr(nbe, node.child2); + nbe.indent = nbe.indent - 1; + } + return; + } + + // Loop (infinite) + if kind == hLoop { + NBE_AppendLine(nbe, "while true:"); + if node.child1 != null as *HirNode { + nbe.indent = nbe.indent + 1; + if node.child1.kind != hBlock { NBE_EmitIndent(nbe); } + NBE_EmitExpr(nbe, node.child1); + nbe.indent = nbe.indent - 1; + } + return; + } + + // Break / Continue + if kind == hBreak { + NBE_AppendLine(nbe, "break"); + return; + } + if kind == hContinue { + NBE_AppendLine(nbe, "continue"); + return; + } + + // Return + if kind == hReturn { + NBE_Append(nbe, "return "); + if node.child1 != null as *HirNode { + if node.child1.kind == hBinary { + NBE_Append(nbe, "("); + NBE_EmitExpr(nbe, node.child1); + NBE_Append(nbe, ")"); + } else { + NBE_EmitExpr(nbe, node.child1); + } + } + return; + } + + // Struct init: TypeName { field: value, ... } + if kind == hStructInit { + NBE_Append(nbe, node.strValue); + NBE_Append(nbe, "("); + // Emit fields (chained via child3) + var field: *HirNode = node.child3; + var first: bool = true; + while field != null as *HirNode { + if !first { NBE_Append(nbe, ", "); } + NBE_Append(nbe, field.strValue); + NBE_Append(nbe, ": "); + NBE_EmitExpr(nbe, field.child1); + first = false; + field = field.child3; + } + NBE_Append(nbe, ")"); + return; + } + + // Load: dereference — in Nim just emit the pointer expression + if kind == hLoad { + NBE_EmitExpr(nbe, node.child1); + return; + } + + // Block — sequence of statements + if kind == hBlock { + var child: *HirNode = node.child1; + if child == null as *HirNode { + NBE_EmitIndent(nbe); + NBE_AppendLine(nbe, "discard"); + return; + } + // First pass: emit statements, merging continuation lines + while child != null as *HirNode { + NBE_EmitIndent(nbe); + // Check if this is a return/binary that has continuation siblings + if child.kind == hReturn && child.child1 != null as *HirNode { + // Emit return and open paren before expression + NBE_Append(nbe, "return ("); + NBE_EmitExpr(nbe, child.child1); + child = child.child3; + // Merge all continuation siblings + while child != null as *HirNode && child.kind == hBinary && child.child1 == null as *HirNode { + NBE_Append(nbe, " "); + NBE_EmitExpr(nbe, child); + child = child.child3; + } + NBE_AppendLine(nbe, ")"); + } else { + // Wrap with discard for non-control-flow statements + if child.kind == hCall || child.kind == hAssign || child.kind == hStore { + NBE_Append(nbe, "discard ("); + NBE_EmitExpr(nbe, child); + NBE_Append(nbe, ")"); + } else { + NBE_EmitExpr(nbe, child); + } + child = child.child3; + // Merge continuation siblings for other statement types + while child != null as *HirNode && child.kind == hBinary && child.child1 == null as *HirNode { + NBE_Append(nbe, " "); + NBE_EmitExpr(nbe, child); + child = child.child3; + } + NBE_AppendLine(nbe, ""); + } + } + return; + } + + // Match — simplified + if kind == hMatch { + NBE_EmitLine(nbe, "# match (simplified)"); + return; + } + + // Spawn + if kind == hSpawn { + NBE_Append(nbe, "spawn "); + NBE_Append(nbe, node.strValue); + NBE_Append(nbe, "("); + if node.child1 != null as *HirNode { + NBE_EmitExpr(nbe, node.child1); + } + NBE_Append(nbe, ")"); + return; + } + + // Await + if kind == hAwait { + NBE_Append(nbe, "await "); + NBE_EmitExpr(nbe, node.child1); + return; + } + + // Fallback + NBE_Append(nbe, "nil"); +} + +// --------------------------------------------------------------------------- +// Function declaration emission +// --------------------------------------------------------------------------- + +func NBE_EmitFuncDecl(nbe: *NBEmitter, f: *HirFunc) { + // Return type + var retType: String = f.retTypeName; + if String_Eq(retType, "") || String_Eq(retType, "void") { + // No return type annotation needed + } else if String_Eq(retType, "int") { + NBE_Append(nbe, "int"); + } else if String_Eq(retType, "bool") { + NBE_Append(nbe, "bool"); + } else if String_Eq(retType, "string") || String_Eq(retType, "String") { + NBE_Append(nbe, "string"); + } else { + NBE_Append(nbe, retType); + } +} + +// --------------------------------------------------------------------------- +// Convert type name from Bux to Nim +// --------------------------------------------------------------------------- + +func NimBackend_EscapeIdent(name: String) -> String { + if name == null as String || String_Eq(name, "") { return "x"; } + // Nim keywords that need escaping + if String_Eq(name, "block") { return "`block`"; } + if String_Eq(name, "type") { return "`type`"; } + if String_Eq(name, "object") { return "`object`"; } + if String_Eq(name, "proc") { return "`proc`"; } + if String_Eq(name, "var") { return "`var`"; } + if String_Eq(name, "let") { return "`let`"; } + if String_Eq(name, "const") { return "`const`"; } + if String_Eq(name, "from") { return "`from`"; } + if String_Eq(name, "import") { return "`import`"; } + if String_Eq(name, "export") { return "`export`"; } + if String_Eq(name, "include") { return "`include`"; } + if String_Eq(name, "iterator") { return "`iterator`"; } + if String_Eq(name, "method") { return "`method`"; } + if String_Eq(name, "func") { return "`func`"; } + if String_Eq(name, "string") { return "`string`"; } + if String_Eq(name, "int") { return "`int`"; } + if String_Eq(name, "bool") { return "`bool`"; } + if String_Eq(name, "float") { return "`float`"; } + if String_Eq(name, "char") { return "`char`"; } + if String_Eq(name, "ptr") { return "`ptr`"; } + if String_Eq(name, "ref") { return "`ref`"; } + if String_Eq(name, "nil") { return "`nil`"; } + if String_Eq(name, "true") { return "`true`"; } + if String_Eq(name, "false") { return "`false`"; } + if String_Eq(name, "and") { return "`and`"; } + if String_Eq(name, "or") { return "`or`"; } + if String_Eq(name, "not") { return "`not`"; } + if String_Eq(name, "xor") { return "`xor`"; } + if String_Eq(name, "shl") { return "`shl`"; } + if String_Eq(name, "shr") { return "`shr`"; } + if String_Eq(name, "div") { return "`div`"; } + if String_Eq(name, "mod") { return "`mod`"; } + if String_Eq(name, "is") { return "`is`"; } + if String_Eq(name, "of") { return "`of`"; } + if String_Eq(name, "in") { return "`in`"; } + if String_Eq(name, "out") { return "`out`"; } + if String_Eq(name, "static") { return "`static`"; } + if String_Eq(name, "enum") { return "`enum`"; } + if String_Eq(name, "tuple") { return "`tuple`"; } + if String_Eq(name, "concept") { return "`concept`"; } + if String_Eq(name, "distinct") { return "`distinct`"; } + if String_Eq(name, "Result") { return "`Result`"; } + return name; +} + +func NimBackend_ConvertTypeNameParam(tn: String) -> String { + // For function parameters: strip pointer indirection, Nim passes by ref + if tn == null as String || String_Eq(tn, "") { return "int"; } + if String_Eq(tn, "String") { return "string"; } + if String_Eq(tn, "bool") { return "bool"; } + if String_Eq(tn, "int") { return "int"; } + if String_Eq(tn, "int64") { return "int64"; } + if String_Eq(tn, "uint32") { return "uint32"; } + if String_Eq(tn, "uint") { return "uint"; } + if String_Eq(tn, "float64") { return "float64"; } + // Pointer types → ptr types in Nim + if !String_Eq(tn, "") { + var tlen: int = bux_strlen(tn) as int; + if tlen > 1 { + var lastCh: char8 = tn[tlen - 1]; + if lastCh == 42 as char8 { + var base: String = String_Slice(tn, 0 as uint, (tlen - 1) as uint); + if String_Eq(base, "void") { return "pointer"; } + return String_Concat("ptr ", base); + } + } + } + return tn; +} + +func NimBackend_ConvertTypeName(tn: String) -> String { + if tn == null as String || String_Eq(tn, "") { return "int"; } + if String_Eq(tn, "String") { return "string"; } + if String_Eq(tn, "bool") { return "bool"; } + if String_Eq(tn, "int") { return "int"; } + if String_Eq(tn, "int64") { return "int64"; } + if String_Eq(tn, "uint32") { return "uint32"; } + if String_Eq(tn, "uint") { return "uint"; } + if String_Eq(tn, "float64") { return "float64"; } + // Convert pointer types: "Foo*" → "ptr Foo" + if !String_Eq(tn, "") { + var tlen: int = bux_strlen(tn) as int; + if tlen > 1 { + var lastCh: char8 = tn[tlen - 1]; + if lastCh == 42 as char8 { // '*' + // Extract base name (without *) + var base: String = String_Slice(tn, 0 as uint, (tlen - 1) as uint); + if String_Eq(base, "void") { return "pointer"; } + return String_Concat("ptr ", base); + } + } + } + return tn; +} + +// --------------------------------------------------------------------------- +// Generate complete Nim module +// --------------------------------------------------------------------------- + +func NimBackend_Generate(mod: *HirModule) -> String { + let nbe: *NBEmitter = bux_alloc(sizeof(NBEmitter)) as *NBEmitter; + nbe.sb = StringBuilder_NewCap(8192); + nbe.indent = 0; + + // Header + NBE_AppendLine(nbe, "# Generated by Bux Nim Backend"); + NBE_AppendLine(nbe, ""); + NBE_AppendLine(nbe, "import std/strutils"); + NBE_AppendLine(nbe, "import std/tables"); + NBE_AppendLine(nbe, "import std/sequtils"); + NBE_AppendLine(nbe, "import std/os"); + NBE_AppendLine(nbe, ""); + + // Type definitions — skip builtins that Nim already has + NBE_AppendLine(nbe, "# Type aliases"); + NBE_AppendLine(nbe, "type"); + nbe.indent = nbe.indent + 1; + NBE_EmitLine(nbe, "String* = string"); + NBE_EmitLine(nbe, "char8* = char"); + // StringBuild is just a string in Nim + NBE_EmitLine(nbe, "StringBuilder* = string"); + // Close type block + nbe.indent = nbe.indent - 1; + NBE_AppendLine(nbe, ""); + + NBE_AppendLine(nbe, ""); + + // Struct definitions + if mod.structCount > 0 { + NBE_AppendLine(nbe, "# Struct types"); + NBE_AppendLine(nbe, "type"); + var si: int = 0; + while si < mod.structCount { + NBE_Append(nbe, " "); + NBE_Append(nbe, NimBackend_EscapeIdent(mod.structs[si].name)); + NBE_AppendLine(nbe, " = object"); + var fi: int = 0; + while fi < mod.structs[si].fieldCount { + var fname: String = mod.structs[si].fields[fi].name; + if !String_Eq(fname, "") { + NBE_Append(nbe, " "); + NBE_Append(nbe, NimBackend_EscapeIdent(fname)); + NBE_Append(nbe, ": "); + var ft: String = mod.structs[si].fields[fi].typeName; + NBE_Append(nbe, NimBackend_ConvertTypeName(ft)); + NBE_AppendLine(nbe, ""); + } + fi = fi + 1; + } + si = si + 1; + } + NBE_AppendLine(nbe, ""); + } + + // Const definitions + if mod.constCount > 0 { + NBE_AppendLine(nbe, "# Constants"); + var ci: int = 0; + while ci < mod.constCount { + NBE_Emit(nbe, "const "); + NBE_Append(nbe, mod.consts[ci].name); + NBE_Append(nbe, " = "); + NBE_Append(nbe, String_FromInt(mod.consts[ci].value as int64)); + NBE_AppendLine(nbe, ""); + ci = ci + 1; + } + NBE_AppendLine(nbe, ""); + } + + // Extern declarations — skip (Nim doesn't need them) + // Bux extern functions are C runtime functions that Nim can't directly call. + // Instead, we use Nim native equivalents (e.g., len() instead of bux_strlen). + + // Function definitions (reverse order: dependencies first) + var i: int = mod.funcCount; + while i > 0 { + i = i - 1; + let f: *HirFunc = &mod.funcs[i]; + if f.body == null as *HirNode { continue; } + + NBE_Emit(nbe, "proc "); + NBE_Append(nbe, NimBackend_EscapeIdent(f.name)); + NBE_Append(nbe, "("); + + // Parameters + var pi: int = 0; + var pnames: *HirParam = &f.param0; + while pi < f.paramCount { + if pi > 0 { NBE_Append(nbe, ", "); } + NBE_Append(nbe, NimBackend_EscapeIdent(pnames[pi].name)); + NBE_Append(nbe, ": "); + var pt: String = pnames[pi].typeName; + NBE_Append(nbe, NimBackend_ConvertTypeNameParam(pt)); + pi = pi + 1; + } + NBE_Append(nbe, "): "); + // Return type + var rtn: String = NimBackend_ConvertTypeNameParam(f.retTypeName); + if String_Eq(rtn, "") || String_Eq(rtn, "void") { + NBE_AppendLine(nbe, "void ="); + } else { + NBE_Append(nbe, rtn); + NBE_AppendLine(nbe, " ="); + } + + // Body + nbe.indent = nbe.indent + 1; + if f.body != null as *HirNode && f.body.kind != hBlock { + NBE_EmitIndent(nbe); + } + NBE_EmitExpr(nbe, f.body); + nbe.indent = nbe.indent - 1; + NBE_AppendLine(nbe, ""); + NBE_AppendLine(nbe, ""); + } + + // Main entry point + NBE_AppendLine(nbe, "# Entry point"); + NBE_AppendLine(nbe, "when isMainModule:"); + NBE_EmitLine(nbe, " var args: seq[string] = @[]"); + NBE_EmitLine(nbe, " var argCount = paramCount()"); + NBE_EmitLine(nbe, " discard Main()"); + NBE_EmitLine(nbe, ""); + + let result: String = NBE_Build(nbe); + return result; +} + +} // module NimBackend diff --git a/_selfhost/src/parser.bux b/_selfhost/src/parser.bux index f36dd2d..8ad0e5b 100644 --- a/_selfhost/src/parser.bux +++ b/_selfhost/src/parser.bux @@ -298,10 +298,6 @@ func parserParsePrimary(p: *Parser) -> *Expr { } // --------------------------------------------------------------------------- - -func dummyFunc() -> int { - return 0; -} // Postfix: call, index, field access, as, is, ? // --------------------------------------------------------------------------- diff --git a/docs/BuildAndTest.md b/docs/BuildAndTest.md index 46767d5..b1a0672 100644 --- a/docs/BuildAndTest.md +++ b/docs/BuildAndTest.md @@ -71,12 +71,15 @@ Output = "Bin" ```bash # Type-check without building ./buxc check +./buxc check ./myproject # Build ./buxc build +./buxc build ./myproject # Build and run ./buxc run +./buxc run ./myproject # Clean build artifacts ./buxc clean @@ -136,7 +139,14 @@ bux/ │ │ ├── Io.bux │ │ ├── Array.bux │ │ ├── String.bux -│ │ └── Map.bux +│ │ ├── Map.bux +│ │ ├── Fs.bux +│ │ ├── Mem.bux +│ │ ├── Set.bux +│ │ ├── Path.bux +│ │ ├── Math.bux +│ │ ├── Task.bux +│ │ └── Channel.bux │ ├── runtime.c # C runtime shim │ └── io.c # C I/O functions ├── examples/ # Example programs diff --git a/docs/Stdlib.md b/docs/Stdlib.md index f69af51..5385938 100644 --- a/docs/Stdlib.md +++ b/docs/Stdlib.md @@ -22,6 +22,33 @@ Basic I/O and file operations wrapping C stdio. | `WriteFile` | `func WriteFile(path: String, content: String) -> bool` | Write string to file | | `FileExists` | `func FileExists(path: String) -> bool` | Check if file exists | +--- + +## Std::Fs + +File system operations beyond basic I/O. + +| Function | Signature | Description | +|----------|-----------|-------------| +| `DirExists` | `func DirExists(path: String) -> bool` | Check if directory exists | +| `Mkdir` | `func Mkdir(path: String) -> bool` | Create directory (and parents if needed) | +| `ListDir` | `func ListDir(dir: String, ext: String, count: *int) -> *String` | List files matching extension | + +### Example +```bux +import Std::Fs::{DirExists, Mkdir, ListDir}; + +func Main() -> int { + if DirExists("/tmp") { + PrintLine("/tmp exists"); + } + Mkdir("build/output"); + var count: int = 0; + let files: *String = ListDir("src", ".bux", &count); + return 0; +} +``` + ### Example ```bux import Std::Io::{PrintLine, ReadFile, WriteFile}; @@ -114,6 +141,35 @@ String manipulation utilities. | `String_FromInt` | `func String_FromInt(n: int64) -> String` | Int to string | | `String_ToInt` | `func String_ToInt(s: String) -> int64` | String to int | +### Find, Replace & Format + +| Function | Signature | Description | +|----------|-----------|-------------| +| `String_Find` | `func String_Find(haystack: String, needle: String) -> String` | Find substring (returns pointer; 0 = not found) | +| `String_Replace` | `func String_Replace(s: String, old: String, new: String) -> String` | Replace first occurrence | +| `String_Format1` | `func String_Format1(pattern: String, a0: String) -> String` | Format with 1 arg (`{0}`) | +| `String_Format2` | `func String_Format2(pattern: String, a0: String, a1: String) -> String` | Format with 2 args | +| `String_Format3` | `func String_Format3(pattern: String, a0: String, a1: String, a2: String) -> String` | Format with 3 args | + +### Example +```bux +import Std::String; + +func Main() -> int { + let s: String = String_Replace("hello world", "world", "Bux"); + PrintLine(s); // "hello Bux" + + let fmt: String = String_Format2("{0} + {1} = magic", "Bux", "QBE"); + PrintLine(fmt); // "Bux + QBE = magic" + + let found: String = String_Find("hello world", "world"); + if found as uint != 0 { + PrintLine("found!"); + } + return 0; +} +``` + --- ## Std::StringBuilder @@ -157,6 +213,82 @@ func Main() -> int { --- +## Std::Mem + +Memory management wrappers around C runtime functions. + +| Function | Signature | Description | +|----------|-----------|-------------| +| `Alloc` | `func Alloc(size: uint) -> *void` | Allocate memory | +| `Realloc` | `func Realloc(ptr: *void, size: uint) -> *void` | Reallocate memory | +| `Free` | `func Free(ptr: *void)` | Free memory | +| `MemEq` | `func MemEq(a: *void, b: *void, size: uint) -> bool` | Byte-wise memory comparison | +| `New` | `func New() -> *T` | Typed allocation (`sizeof(T)` bytes) | + +### Example +```bux +import Std::Mem; + +func Main() -> int { + let p: *void = Alloc(64); + Free(p); + + let n: *int = New(); + // use n... + Free(n as *void); + return 0; +} +``` + +--- + +## Std::Set + +Generic hash set for deduplication and membership testing. + +### Types +```bux +struct SetEntry { + value: T, + occupied: bool, +} + +struct Set { + entries: *SetEntry, + cap: uint, + len: uint, +} +``` + +### Functions + +| Function | Signature | Description | +|----------|-----------|-------------| +| `Set_New` | `func Set_New(cap: uint) -> Set` | Create set | +| `Set_Add` | `func Set_Add(s: *Set, value: T)` | Insert element (ignores duplicates) | +| `Set_Has` | `func Set_Has(s: *Set, value: T) -> bool` | Check membership | +| `Set_Len` | `func Set_Len(s: *Set) -> uint` | Element count | +| `Set_Free` | `func Set_Free(s: *Set)` | Free memory | + +### Example +```bux +import Std::Set; + +func Main() -> int { + var s: Set = Set_New(16); + Set_Add(&s, 10); + Set_Add(&s, 20); + Set_Add(&s, 10); // duplicate, ignored + if Set_Has(&s, 10) { + PrintLine("has 10"); + } + Set_Free(&s); + return 0; +} +``` + +--- + ## Std::Map Generic hash map `Map` for value-type keys (int, float, etc.). @@ -317,8 +449,13 @@ func Main() -> int { - `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 (pthread-based threads) \ No newline at end of file +- `Std::Math` — `Sqrt`, `Pow`, `Min`, `Max`, `Abs` ✅ +- `Std::Fs` — Directory operations ✅ +- `Std::Mem` — Memory wrappers ✅ +- `Std::Set` — Hash set ✅ +- `Std::Path` — Path manipulation ✅ +- `Std::Os` — `Args`, `Env`, `Exit`, `Cwd` ⏳ +- `Std::Process` — Spawn subprocess ⏳ +- `Std::Fmt` — String formatting with interpolation ⏳ +- `Std::Iter` — Iterator trait and combinators ⏳ +- `Std::Task` / `Std::Channel` — Lightweight concurrency (pthread-based threads) ✅ \ No newline at end of file diff --git a/src/cli.nim b/src/cli.nim index fba848d..8d736a9 100644 --- a/src/cli.nim +++ b/src/cli.nim @@ -258,7 +258,7 @@ proc collectDepDecls(lock: Lockfile, root: string, opts: GlobalOptions): seq[Dec proc cmdCheck*(args: seq[string], opts: GlobalOptions): int = let useColor = shouldUseColor(opts) - let root = getCurrentDir() + let root = if args.len > 0: absolutePath(args[0]) else: getCurrentDir() let manifestPath = root / "bux.toml" if not fileExists(manifestPath): printError("no bux.toml found", useColor) @@ -415,7 +415,7 @@ proc mergeDecls(stdlibDecls: seq[Decl], userDecls: seq[Decl]): seq[Decl] = proc cmdBuild*(args: seq[string], opts: GlobalOptions): int = let useColor = shouldUseColor(opts) - let root = getCurrentDir() + let root = if args.len > 0: absolutePath(args[0]) else: getCurrentDir() let manifestPath = root / "bux.toml" if not fileExists(manifestPath): printError("no bux.toml found", useColor) @@ -554,7 +554,7 @@ proc cmdRun*(args: seq[string], opts: GlobalOptions): int = let buildRes = cmdBuild(args, opts) if buildRes != 0: return buildRes - let root = getCurrentDir() + let root = if args.len > 0: absolutePath(args[0]) else: getCurrentDir() let man = loadManifest(root / "bux.toml") let outputName = if man.name != "": man.name else: "bux_out" let outputFile = root / "build" / outputName diff --git a/src_bux/parser.bux b/src_bux/parser.bux index f36dd2d..8ad0e5b 100644 --- a/src_bux/parser.bux +++ b/src_bux/parser.bux @@ -298,10 +298,6 @@ func parserParsePrimary(p: *Parser) -> *Expr { } // --------------------------------------------------------------------------- - -func dummyFunc() -> int { - return 0; -} // Postfix: call, index, field access, as, is, ? // --------------------------------------------------------------------------- diff --git a/stdlib/Std/Fs.bux b/stdlib/Std/Fs.bux new file mode 100644 index 0000000..75e25c4 --- /dev/null +++ b/stdlib/Std/Fs.bux @@ -0,0 +1,19 @@ +module Std::Fs { + +extern func bux_dir_exists(path: String) -> int; +extern func bux_mkdir_if_needed(path: String) -> int; +extern func bux_list_dir(dir: String, ext: String, out_count: *int) -> *String; + +func DirExists(path: String) -> bool { + return bux_dir_exists(path) != 0; +} + +func Mkdir(path: String) -> bool { + return bux_mkdir_if_needed(path) != 0; +} + +func ListDir(dir: String, ext: String, count: *int) -> *String { + return bux_list_dir(dir, ext, count); +} + +} diff --git a/stdlib/Std/Mem.bux b/stdlib/Std/Mem.bux new file mode 100644 index 0000000..85ee4d1 --- /dev/null +++ b/stdlib/Std/Mem.bux @@ -0,0 +1,29 @@ +module Std::Mem { + +extern func bux_alloc(size: uint) -> *void; +extern func bux_realloc(ptr: *void, size: uint) -> *void; +extern func bux_free(ptr: *void); +extern func bux_mem_eq(a: *void, b: *void, size: uint) -> int; + +func Alloc(size: uint) -> *void { + return bux_alloc(size); +} + +func Realloc(ptr: *void, size: uint) -> *void { + return bux_realloc(ptr, size); +} + +func Free(ptr: *void) { + bux_free(ptr); +} + +func MemEq(a: *void, b: *void, size: uint) -> bool { + return bux_mem_eq(a, b, size) != 0; +} + +func New() -> *T { + let sz: uint = sizeof(T); + return bux_alloc(sz) as *T; +} + +} diff --git a/stdlib/Std/Set.bux b/stdlib/Std/Set.bux new file mode 100644 index 0000000..6a8fca6 --- /dev/null +++ b/stdlib/Std/Set.bux @@ -0,0 +1,71 @@ +module Std::Set { + +extern func bux_hash_bytes(ptr: *void, size: uint) -> uint; +extern func bux_mem_eq(a: *void, b: *void, size: uint) -> int; +extern func bux_alloc(size: uint) -> *void; +extern func bux_free(ptr: *void); + +struct SetEntry { + value: T, + occupied: bool, +} + +struct Set { + entries: *SetEntry, + cap: uint, + len: uint, +} + +func Set_New(cap: uint) -> Set { + let total: uint = cap * sizeof(SetEntry); + let data: *SetEntry = bux_alloc(total) as *SetEntry; + var i: uint = 0; + while i < cap { + data[i].occupied = false; + i = i + 1; + } + return Set { entries: data, cap: cap, len: 0 }; +} + +func Set_Add(s: *Set, value: T) { + var valuePtr: *T = &value; + let hash: uint = bux_hash_bytes(valuePtr as *void, sizeof(T)); + var idx: uint = hash % s.cap; + while s.entries[idx].occupied { + var entryPtr: *T = &s.entries[idx].value; + if bux_mem_eq(entryPtr as *void, valuePtr as *void, sizeof(T)) != 0 { + return; + } + idx = (idx + 1) % s.cap; + } + s.entries[idx].value = value; + s.entries[idx].occupied = true; + s.len = s.len + 1; +} + +func Set_Has(s: *Set, value: T) -> bool { + var valuePtr: *T = &value; + let hash: uint = bux_hash_bytes(valuePtr as *void, sizeof(T)); + var idx: uint = hash % s.cap; + while s.entries[idx].occupied { + var entryPtr: *T = &s.entries[idx].value; + if bux_mem_eq(entryPtr as *void, valuePtr as *void, sizeof(T)) != 0 { + return true; + } + idx = (idx + 1) % s.cap; + } + return false; +} + +func Set_Len(s: *Set) -> uint { + return s.len; +} + +func Set_Free(s: *Set) { + bux_free(s.entries as *void); + s.entries = null as *SetEntry; + s.cap = 0; + s.len = 0; +} + +} diff --git a/stdlib/Std/String.bux b/stdlib/Std/String.bux index 7012ee9..cbce8d7 100644 --- a/stdlib/Std/String.bux +++ b/stdlib/Std/String.bux @@ -24,6 +24,7 @@ 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; +extern func bux_str_format(pattern: String, a0: String, a1: String, a2: String, a3: String, a4: String, a5: String, a6: String, a7: String) -> String; func String_Len(s: String) -> uint { @@ -155,4 +156,38 @@ func String_Join2(a: String, b: String, sep: String) -> String { return bux_str_join2(a, b, sep); } -} \ No newline at end of file +// --------------------------------------------------------------------------- +// String find/replace/format +// --------------------------------------------------------------------------- + +func String_Find(haystack: String, needle: String) -> String { + return bux_strstr(haystack, needle); +} + +func String_Replace(s: String, old: String, new: String) -> String { + let pos: String = bux_strstr(s, old); + if pos as uint == 0 { + return s; + } + let oldLen: uint = bux_strlen(old); + let prefixLen: uint = pos as uint - s as uint; + let prefix: String = bux_str_slice(s, 0, prefixLen); + let suffix: String = bux_str_slice(s, prefixLen + oldLen, bux_strlen(s) - prefixLen - oldLen); + let temp: String = String_Concat(prefix, new); + let result: String = String_Concat(temp, suffix); + return result; +} + +func String_Format1(pattern: String, a0: String) -> String { + return bux_str_format(pattern, a0, "", "", "", "", "", "", ""); +} + +func String_Format2(pattern: String, a0: String, a1: String) -> String { + return bux_str_format(pattern, a0, a1, "", "", "", "", "", ""); +} + +func String_Format3(pattern: String, a0: String, a1: String, a2: String) -> String { + return bux_str_format(pattern, a0, a1, a2, "", "", "", "", ""); +} + +}