diff --git a/PLAN.md b/PLAN.md index c5d1156..2d86870 100644 --- a/PLAN.md +++ b/PLAN.md @@ -1,66 +1,87 @@ # Bux Programming Language — Roadmap to Self-Hosting -> **Reference:** [Rux Language](https://rux-lang.dev/) | [Rux Source](../_rux/) -> **Bootstrap Implementation:** Nim +> **Reference:** [Rux Language](https://rux-lang.dev/) | [Rux Source](../_rux/) +> **Bootstrap Implementation:** Nim > **Target:** Bux compiler written in Bux (self-hosting) --- ## Overview -Bux is a fast, compiled, strongly-typed, multi-paradigm systems programming language inspired by Rux. The strategy is **bootstrap via Nim** — we build the first Bux compiler in Nim, then progressively rewrite it in Bux until it compiles itself. +Bux is a fast, compiled, strongly-typed, multi-paradigm systems programming language inspired by Rux, Rust, and Nim. The strategy is **bootstrap via Nim** — we build the first Bux compiler in Nim, then progressively rewrite it in Bux until it compiles itself. + +**Core philosophy:** Systems-level control with modern ergonomics. No hidden costs, no hidden allocations, no hidden control flow. --- -## Phase 0 — Bootstrap Foundation (Week 1-2) +## Language Design Goals (Bux vs Rust vs Nim vs Rux) + +| Dimension | Bux Target | Rust | Nim | Rux v0.2.0 | +|-----------|-----------|------|-----|------------| +| **Memory safety** | Gradual ownership (opt-in borrow checking) | Strict borrow checker | GC / manual | Raw pointers only | +| **Error handling** | `Result` + `?` propagation | `Result` + `?` | Exceptions | Basic Result, no `?` | +| **Concurrency** | Lightweight tasks + channels + `async`/`await` | `async`/`await` + threads | Async/await + threads | None | +| **Metaprogramming** | Compile-time function execution (CTFE) + macros | Proc/decl macros | Static generics + macros | None | +| **Generics** | Monomorphization + trait bounds | Monomorphization + trait bounds | Static generics | Limited | +| **Backend** | C transpiler (bootstrap) → native x86-64 + LLVM | LLVM | C/JS/JS backend | Custom native only | +| **Compile speed** | Fast (Nim-like goal: <1s for medium projects) | Slow (LLVM) | Fast | Fast (custom backend) | +| **FFI** | Seamless C interop (zero-cost) | Good | Good (native) | Basic extern | +| **Stdlib** | Batteries-included (collections, IO, net, sync) | Rich | Rich | Minimal | +| **Tooling** | Built-in formatter, LSP, test runner, debugger | External tools | External tools | Minimal | + +--- + +## Phase 0 — Bootstrap Foundation ✅ (Complete) **Goal:** Working Nim project that can lex, parse, and dump a Bux AST. -| Task | Details | -|------|---------| -| `0.1` Project skeleton | `buxc` CLI in Nim, `bux.toml` manifest parser | -| `0.2` Token model | All Rux tokens (`TokenKind`, `SourceLocation`, literal suffixes) | -| `0.3` Lexer | UTF-8 source, identifiers, numbers (dec/hex/bin/oct), strings (`c8""`, `c16""`, `c32""`), chars, operators, nested `/* */`, `//` comments, intrinsics (`#line`, `#file`, etc.) | -| `0.4` CLI commands | `bux new`, `bux init`, `bux build`, `bux run`, `bux check` | -| `0.5` Test harness | Golden-file tests for lexer output (`.tokens`) | +| Task | Status | Details | +|------|--------|---------| +| `0.1` Project skeleton | ✅ | `buxc` CLI in Nim, `bux.toml` manifest parser | +| `0.2` Token model | ✅ | All Rux tokens (`TokenKind`, `SourceLocation`, literal suffixes) | +| `0.3` Lexer | ✅ | UTF-8 source, identifiers, numbers (dec/hex/bin/oct), strings (`c8""`, `c16""`, `c32""`), chars, operators, nested `/* */`, `//` comments, intrinsics (`#line`, `#file`, etc.) | +| `0.4` CLI commands | ✅ | `bux new`, `bux init`, `bux build`, `bux run`, `bux check` | +| `0.5` Test harness | ✅ | Golden-file tests for lexer output (`.tokens`) | **Deliverable:** `echo 'let x = 42' | bux check` prints token stream. --- -## Phase 1 — Frontend: Parser & AST (Week 3-4) +## Phase 1 — Frontend: Parser & AST ✅ (Complete) **Goal:** Parse every construct present in Rux v0.2.0 into a Nim AST. -| Task | Details | -|------|---------| -| `1.1` AST nodes | All `Expr`, `Stmt`, `Decl`, `Pattern`, `TypeExpr`, `Block` variants (see `_rux/Include/Rux/Ast.h`) | -| `1.2` Pratt parser | Full precedence climbing for all binary/unary/postfix operators including `**` (right-assoc) and range `..` / `..=` | -| `1.3` Declarations | `func`, `struct`, `enum`, `union`, `interface`, `extend`/`impl`, `module`, `const`, `type`, `extern`, `import`/`use` | -| `1.4` Statements | `let`/`var`, `if`/`else if`/`else`, `while`, `do while`, `loop`, `for in`, `match`, `return`, `break`/`continue` (with labels) | -| `1.5` Expressions | Literals, identifiers, paths (`a::b`), calls, index, field access, struct init, slice init `[a,b]`, tuple `(a,b)`, cast `as`, test `is`, ternary `? :`, block-expr `{ ... }` | -| `1.6` Patterns | Wildcard `_`, literal, ident, range, enum destructuring, struct destructuring, tuple, guarded `if` | -| `1.7` Attributes | `@[Import(lib: "...")]`, calling-convention, platform-conditional imports | -| `1.8` Error recovery | Synchronize on declaration/statement boundaries; emit multiple diagnostics | +| Task | Status | Details | +|------|--------|---------| +| `1.1` AST nodes | ✅ | All `Expr`, `Stmt`, `Decl`, `Pattern`, `TypeExpr`, `Block` variants | +| `1.2` Pratt parser | ✅ | Full precedence climbing for all binary/unary/postfix operators including `**` (right-assoc) and range `..` / `..=` | +| `1.3` Declarations | ✅ | `func`, `struct`, `enum`, `union`, `interface`, `extend`/`impl`, `module`, `const`, `type`, `extern`, `import`/`use` | +| `1.4` Statements | ✅ | `let`/`var`, `if`/`else if`/`else`, `while`, `do while`, `loop`, `for in`, `match`, `return`, `break`/`continue` (with labels) | +| `1.5` Expressions | ✅ | Literals, identifiers, paths (`a::b`), calls, index, field access, struct init, slice init `[a,b]`, tuple `(a,b)`, cast `as`, test `is`, ternary `? :`, block-expr `{ ... }` | +| `1.6` Patterns | ✅ | Wildcard `_`, literal, ident, range, enum destructuring, struct destructuring, tuple, guarded `if` | +| `1.7` Attributes | ✅ | `@[Import(lib: "...")]`, calling-convention, platform-conditional imports | +| `1.8` Error recovery | ✅ | Synchronize on declaration/statement boundaries; emit multiple diagnostics | **Deliverable:** All `_rux/Tests/**/*.rux` files parse without error and produce `.ast` dumps. --- -## Phase 2 — Semantic Analysis (Week 5-7) +## Phase 2 — Semantic Analysis 🔄 (In Progress) **Goal:** Type-check the AST and produce a typed symbol table. -| Task | Details | -|------|---------| -| `2.1` Type model | `TypeRef` with primitives, pointers, slices, tuples, named types, type parameters, functions (see `_rux/Include/Rux/Type.h`) | -| `2.2` Scopes | Module scope, block scope, namespace resolution for `Std::Io::PrintLine` | -| `2.3` First pass | Collect global symbols (functions, structs, enums, unions, interfaces, consts, type aliases, imports) | -| `2.4` Type checking | Expression typing, operator overload resolution per Rux rules, assignment compatibility | -| `2.5` Name resolution | Resolve identifiers, paths, `self`, `super`; report undeclared / ambiguous names | -| `2.6` Interface conformance | Check that `extend T for I` provides all required methods; build vtable map | -| `2.7` Generics (basic) | Monomorphization of generic structs and functions at call sites | -| `2.8` Diagnostics | Multi-file error messages with source locations | +| Task | Status | Details | +|------|--------|---------| +| `2.1` Type model | ✅ | `TypeRef` with primitives, pointers, slices, tuples, named types, type parameters, functions | +| `2.2` Scopes | ✅ | Module scope, block scope, namespace resolution for `Std::Io::PrintLine` | +| `2.3` First pass | ✅ | Collect global symbols (functions, structs, enums, unions, interfaces, consts, type aliases, imports) | +| `2.4` Type checking | ✅ | Expression typing, operator overload resolution per Rux rules, assignment compatibility | +| `2.5` Name resolution | ✅ | Resolve identifiers, paths, `self`, `super`; report undeclared / ambiguous names | +| `2.6` Interface conformance | 🔄 | Check that `extend T for I` provides all required methods; build vtable map | +| `2.7` Generics (basic) | ⏳ | Monomorphization of generic structs and functions at call sites | +| `2.8` Diagnostics | ✅ | Multi-file error messages with source locations | +| `2.9` **Algebraic enums** | ⏳ | Enums with data (like Rust's `enum Result { Ok(T), Err(E) }`) — AST supports it, sema needs to validate | +| `2.10` **Method resolution** | ⏳ | Resolve `obj.method()` calls to `Type_method(obj)` based on receiver type | **Deliverable:** `bux check` rejects ill-typed programs and passes `Tests/Echo`, `Tests/Io`, `Tests/Pow` type-checking. @@ -76,6 +97,8 @@ Bux is a fast, compiled, strongly-typed, multi-paradigm systems programming lang | `3.2` Lowering | Desugar `for` → `while`+iterator, `match` → decision tree, method calls to explicit receiver calls | | `3.3` Constant folding | Evaluate `const` and simple compile-time expressions | | `3.4` Interface lowering | Convert interface values to fat pointers `{data_ptr, vtable_ptr}`; generate vtable labels | +| `3.5` **Generic instantiation** | Monomorphize generic functions/structs at call sites | +| `3.6` **Enum lowering** | Lower algebraic enums to tagged unions `{tag: uint, data: union}` | **Deliverable:** HIR dump matches Rux HIR semantics for sample programs. @@ -142,6 +165,9 @@ Bux is a fast, compiled, strongly-typed, multi-paradigm systems programming lang | `Std::Os` | `Args`, `Env`, `Exit`, `Cwd` | | `Std::Path` | Path joining, extension splitting | | `Std::Process` | Spawn subprocess, read stdout/stderr | +| **`Std::Result`** | `Result` + `Option` types with `?` operator for error propagation | +| **`Std::Iter`** | Iterator trait with `map`, `filter`, `fold`, `collect` | +| **`Std::Fmt`** | String formatting: `"Hello, {}!"` interpolation | **Deliverable:** Can write a non-trivial CLI tool (e.g., a file copier or a basic grep) entirely in Bux. @@ -167,17 +193,136 @@ Bux is a fast, compiled, strongly-typed, multi-paradigm systems programming lang --- -## Phase 8 — Ecosystem & Tooling (Week 27+) +## Phase 8 — Advanced Language Features (Week 27-34) + +**Goal:** Features that make Bux better than Rux and competitive with Rust/Nim. + +### 8.1 — Error Handling (Result/Option + `?` operator) | Task | Details | |------|---------| -| `8.1` Package manager | `bux add`, `bux remove`, `bux update`, `bux install` with lockfile | -| `8.2` Registry protocol | Simple HTTP git-based registry (like Go modules or Cargo) | -| `8.3` Formatter | `bux fmt` — auto-format Bux source | -| `8.4` LSP | Language Server Protocol for autocomplete, hover, go-to-definition | -| `8.5` Tests | `bux test` runner with assertions and golden tests | -| `8.6` Documentation | `bux doc` — generate HTML from `///` doc comments | -| `8.7` Cross-compilation | `--target` flag leveraging C backend portability | +| `8.1.1` Result type | `Result` with `Ok(T)` and `Err(E)` constructors | +| `8.1.2` Option type | `Option` with `Some(T)` and `None` constructors | +| `8.1.3` `?` operator | `expr?` desugars to: if `Err`/`None`, early-return from function | +| `8.1.4` `!` suffix | `expr!` unwraps or panics (for prototyping) | + +```bux +func ReadFile(path: String) -> Result { + let file = Open(path)?; // early-returns Err if open fails + let content = file.ReadAll()?; // early-returns Err if read fails + return Ok(content); +} +``` + +### 8.2 — Ownership & Borrowing (Gradual Safety) + +| Task | Details | +|------|---------| +| `8.2.1` `own` keyword | Explicit ownership transfer: `let x = own value` | +| `8.2.2` `borrow` / `&` | Borrow references with lifetime tracking | +| `8.2.3` `mut` references | `&mut T` for mutable borrows (exclusive) | +| `8.2.4` Lifetime elision | Simple rules for common cases; explicit `'a` for complex | +| `8.2.5` Opt-in checker | `@[Checked]` attribute enables borrow checking; default is permissive | + +```bux +// Opt-in safety — by default, Bux is permissive like Nim +func UnsafeSwap(a: *int, b: *int) { + let tmp = *a; + *a = *b; + *b = tmp; +} + +// Opt-in safety — with @[Checked], borrow checker kicks in +@[Checked] +func SafeSwap(a: &mut int, b: &mut int) { + let tmp = *a; + *a = *b; + *b = tmp; +} +``` + +### 8.3 — Concurrency + +| Task | Details | +|------|---------| +| `8.3.1` Tasks | Lightweight green threads (M:N scheduler) | +| `8.3.2` Channels | `Channel` for message passing between tasks | +| `8.3.3` `async`/`await` | Async functions compile to state machines | +| `8.3.4` `Send`/`Sync` traits | Compile-time thread safety markers | +| `8.3.5` Atomics | `atomic` type with memory ordering | + +```bux +import Std::Task; +import Std::Channel; + +func Producer(ch: Channel) { + for i in 0..100 { + ch.Send(i); + } + ch.Close(); +} + +func Main() -> int { + let (tx, rx) = Channel::New(); + Task::Spawn(|| Producer(tx)); + for value in rx { + PrintLine(value); + } + return 0; +} +``` + +### 8.4 — Compile-Time Function Execution (CTFE) + +| Task | Details | +|------|---------| +| `8.4.1` `const` functions | `const func` evaluable at compile time | +| `8.4.2` Compile-time blocks | `comptime { ... }` for arbitrary compile-time code | +| `8.4.3` Static assertions | `static_assert(cond, msg)` for compile-time checks | +| `8.4.4` Generated code | `#emit` for compile-time code generation | + +```bux +const func Factorial(n: int) -> int { + if n <= 1 { return 1; } + return n * Factorial(n - 1); +} + +const TABLE_SIZE = Factorial(10); // Computed at compile time +``` + +### 8.5 — Trait System (Interfaces++) + +| Task | Details | +|------|---------| +| `8.5.1` Traits | Like Rust traits or Go interfaces, but with default implementations | +| `8.5.2` Associated types | `type Output` inside trait definitions | +| `8.5.3` Trait bounds | `func Sort(arr: &mut Array)` | +| `8.5.4` Trait objects | `&dyn Trait` for dynamic dispatch (fat pointer) | +| `8.5.5` Blanket impls | `impl Printable for T` | + +### 8.6 — Metaprogramming + +| Task | Details | +|------|---------| +| `8.6.1` Declarative macros | `macro! Name { ... }` pattern-matching macros | +| `8.6.2` Procedural macros | `#[derive(Clone)]`, `#[derive(Debug)]` | +| `8.6.3` Reflection | Compile-time type introspection for serialization | + +--- + +## Phase 9 — Ecosystem & Tooling (Week 35+) + +| Task | Details | +|------|---------| +| `9.1` Package manager | `bux add`, `bux remove`, `bux update`, `bux install` with lockfile | +| `9.2` Registry protocol | Simple HTTP git-based registry (like Go modules or Cargo) | +| `9.3` Formatter | `bux fmt` — auto-format Bux source | +| `9.4` LSP | Language Server Protocol for autocomplete, hover, go-to-definition | +| `9.5` Tests | `bux test` runner with assertions and golden tests | +| `9.6` Documentation | `bux doc` — generate HTML from `///` doc comments | +| `9.7` Cross-compilation | `--target` flag leveraging C backend portability | +| `9.8` Debugger support | DWARF/PDB debug info generation for gdb/lldb/VSCode | +| `9.9` Profiler integration | `bux build --profile` with basic profiling hooks | --- @@ -213,7 +358,13 @@ bux/ │ │ ├── Math.bux │ │ ├── Os.bux │ │ ├── Path.bux -│ │ └── Process.bux +│ │ ├── Process.bux +│ │ ├── Result.bux # Result and Option +│ │ ├── Iter.bux # Iterator trait and combinators +│ │ ├── Fmt.bux # String formatting +│ │ ├── Task.bux # Lightweight concurrency +│ │ ├── Channel.bux # Message passing +│ │ └── Sync.bux # Mutex, RwLock, atomic │ └── Runtime.c # C runtime shim ├── tests/ │ ├── Lexer/ @@ -222,23 +373,123 @@ bux/ │ ├── Codegen/ │ └── Integration/ └── docs/ - └── LanguageRef.md + ├── LanguageRef.md + ├── Ownership.md + └── Concurrency.md ``` --- -## Language Design Decisions (Bux vs Rux) +## Language Design Decisions (Bux Improvements) -| Feature | Bux Decision | Rationale | -|---------|-------------|-----------| -| **Backend (bootstrap)** | C transpiler first | Fastest path to working compiler; leverages existing C toolchains | -| **Backend (final)** | Native x86-64 + optional LLVM | Match Rux ambition; self-hosting needs speed | -| **Memory safety** | Raw pointers + optional borrow checker (Phase 9) | Match Rux current model; gradual safety | -| **Generics** | Monomorphization only | Simpler than Rust-style trait objects; enough for self-hosting | -| **Error handling** | Explicit `Result` + `?` operator (later) | Start with C-style returns; add sugar after self-hosting | -| **String literals** | `c8""`, `c16""`, `c32""` + `""` defaulting to `c8` | Same as Rux | -| **Build system** | `bux.toml` (same as `Rux.toml`) | Compatible manifest format | -| **Module system** | `import Std::Io::PrintLine` | Same as Rux path syntax | +### What Bux inherits from Rux +| Feature | Rux | Bux | +|---------|-----|-----| +| Syntax | C-like with modern touches | Same base, extended | +| Module system | `import Std::Io::PrintLine` | Same path syntax | +| String literals | `c8""`, `c16""`, `c32""` + `""` | Same | +| Build manifest | `Rux.toml` | `bux.toml` (compatible format) | +| Backend philosophy | Self-contained (no LLVM required) | C transpiler first → native + optional LLVM | + +### What Bux improves over Rux +| Gap in Rux | Bux Solution | +|-----------|-------------| +| No memory safety | Gradual ownership model (opt-in borrow checking) | +| No error handling sugar | `Result` + `?` operator | +| No concurrency | Green threads + channels + `async`/`await` | +| No metaprogramming | CTFE + declarative macros + derive macros | +| Minimal stdlib | Batteries-included (collections, IO, net, sync, fmt) | +| Custom backend only | C transpiler (portable) + native + LLVM option | +| No debug symbols | DWARF/PDB generation for debugger integration | +| Windows-only output | Cross-platform from day one (Linux, macOS, Windows) | + +### What Bux learns from Rust +| Rust feature | Bux adaptation | +|-------------|----------------| +| Ownership/borrowing | **Opt-in** via `@[Checked]` — not forced on everyone | +| `Result`/`Option` + `?` | Adopted directly | +| Traits | Adopted as "interfaces" with default methods | +| Cargo | `bux.toml` + package manager | +| `rustfmt` | `bux fmt` built-in | +| Pattern matching | Adopted (already in AST) | + +### What Bux learns from Nim +| Nim feature | Bux adaptation | +|-------------|----------------| +| Fast compilation | C transpiler backend (leverages C compiler speed) | +| CTFE | `const func` + `comptime` blocks | +| Clean syntax | Less noisy than Rust (no `::` turbofish, simpler generics) | +| Macro system | Declarative macros with pattern matching | +| Pragmatic approach | Gradual safety — start permissive, add checks as needed | + +--- + +## Syntax Preview + +```bux +import Std::Io::{PrintLine, Print}; +import Std::Result::{Result, Ok, Err}; +import Std::Array::Array; + +// Struct with generic type parameter +struct Stack { + items: Array, + len: uint, +} + +// Trait (interface) with default implementation +interface Display { + func ToString(self: &Self) -> String; + + func Display(self: &Self) { + PrintLine(self.ToString()); + } +} + +// Implement trait for struct +extend Stack for Display { + func ToString(self: &Stack) -> String { + return Format("Stack(len={})", self.len); + } +} + +// Function with Result return type and ? operator +func Divide(a: int, b: int) -> Result { + if b == 0 { + return Err("division by zero"); + } + return Ok(a / b); +} + +// Async function +async func FetchData(url: String) -> Result { + let response = Http::Get(url).await?; + return Ok(response.Body); +} + +// Compile-time function +const func Fibonacci(n: int) -> int { + if n <= 1 { return n; } + return Fibonacci(n - 1) + Fibonacci(n - 2); +} + +const FIB_20 = Fibonacci(20); // Computed at compile time + +// Main entry point +func Main() -> int { + // Error handling with ? + let result = Divide(10, 2)?; + PrintLine("10 / 2 = {}", result); + + // Pattern matching on algebraic enum + match result { + Ok(value) => PrintLine("Got: {}", value), + Err(msg) => PrintLine("Error: {}", msg), + } + + return 0; +} +``` --- @@ -246,14 +497,15 @@ bux/ | Milestone | Phase | Success Criteria | |-----------|-------|------------------| -| **M0** | 0 | `bux check` lexes source | -| **M1** | 1 | All Rux test files parse | -| **M2** | 2 | Type-checker rejects invalid programs | +| **M0** | 0 ✅ | `bux check` lexes source | +| **M1** | 1 ✅ | All Rux test files parse | +| **M2** | 2 🔄 | Type-checker rejects invalid programs | | **M3** | 3+4 | LIR emits for all constructs | | **M4** | 5A | `bux run` produces working binary via C | | **M5** | 6 | Can write compiler-adjacent tools in Bux | | **M6** | 7 | **Self-hosted**: Bux compiler builds itself | -| **M7** | 8 | Package manager + LSP + formatter shipped | +| **M7** | 8 | Result/Option, ownership, concurrency shipped | +| **M8** | 9 | Package manager + LSP + formatter shipped | --- @@ -263,11 +515,227 @@ bux/ |------|------------| | Nim bootstrap too slow | Keep Nim code simple; aim for rewrite in ~3 months | | C backend limits performance | Maintain parallel native backend; C is only bootstrap | -| Generics get complex | Restrict to monomorphization; no higher-kinded types | -| Self-hosting too hard | Ensure stdlib has `Array`, `Map`, `String` before starting rewrite | +| Generics get complex | Restrict to monomorphization; no higher-kinded types initially | +| Self-hosting too hard | Ensure stdlib has `Array`, `Map`, `String`, `Result` before starting rewrite | +| Ownership model too complex | Make it opt-in; default is permissive (like Nim) | +| Concurrency runtime overhead | Green threads are optional; core language works without runtime | --- -## Next Immediate Step +## Next Immediate Steps -Create the Nim bootstrap skeleton and implement the **Lexer** (`0.1`–`0.3`). +1. **Complete Phase 2** — Finish interface conformance checking and generic monomorphization +2. **Build HIR** (Phase 3) — Lower AST to typed HIR +3. **C Backend** (Phase 5A) — Get `Hello, Bux!` running via C transpiler +4. **Stdlib basics** — `Array`, `String`, `Result` types + +--- + +## Open Design Questions + +1. **Syntax for ownership**: Should Bux use `own` keyword or Rust-style move semantics? +2. **Async runtime**: M:N green threads (Go-style) or 1:1 OS threads (Rust-style)? +3. **Macro system**: Declarative-only or also procedural macros? +4. **Package registry**: Centralized (crates.io) or decentralized (Go modules)? +5. **LLVM backend**: Should Bux support LLVM as an optional backend, or stay fully self-contained? + +--- + +## Appendix A: Rux Language Reference (for Bux parity) + +Based on [Rux Documentation](https://rux-lang.dev/docs/), these are the features Bux must support for Rux parity: + +### A.1 Types + +| Category | Rux Types | Bux Status | +|----------|-----------|------------| +| **Signed integers** | `int8`, `int16`, `int32`, `int64`, `int` (platform) | ✅ Implemented | +| **Unsigned integers** | `uint8`, `uint16`, `uint32`, `uint64`, `uint` (platform) | ✅ Implemented | +| **Floating-point** | `float32`, `float64` | ✅ Implemented | +| **Boolean** | `bool`, `bool8`, `bool16`, `bool32` | ✅ Implemented | +| **Character** | `char8`, `char16`, `char32` | ✅ Implemented | +| **String** | `String` (UTF-8), `c8""`, `c16""`, `c32""` literals | ✅ Implemented | +| **Pointer** | `*T` (raw pointer) | ✅ Implemented | +| **Slice** | `T[]` (unsized), `T[N]` (fixed-size) | ✅ Implemented | +| **Tuple** | `(T1, T2, ...)` | ✅ Implemented | +| **Function** | `func(T1, T2) -> R` | ✅ Implemented | +| **Option** | `Option` = `Some(T)` \| `None` | ⏳ Phase 6 | +| **Result** | `Result` = `Ok(T)` \| `Err(E)` | ⏳ Phase 6 | + +### A.2 Declarations + +| Construct | Rux Syntax | Bux Status | +|-----------|------------|------------| +| **Immutable variable** | `let x: int = 42;` | ✅ Implemented | +| **Mutable variable** | `var x: int = 42;` | ✅ Implemented | +| **Constant** | `const Max: uint32 = 100;` | ✅ Implemented | +| **Function** | `func Add(a: int, b: int) -> int { ... }` | ✅ Implemented | +| **Generic function** | `func Min(x: T, y: T) -> T { ... }` | ⏳ Phase 2.7 | +| **Variadic function** | `func Sum(values: int32...)` | ⏳ Phase 1 | +| **Struct** | `struct Point { x: float64; y: float64; }` | ✅ Implemented | +| **Enum** | `enum Color { Red, Green, Blue }` | ✅ Implemented | +| **Data-carrying enum** | `enum Shape { Circle(float64), Rect(float64, float64) }` | ⏳ Phase 2.9 | +| **Union (untagged)** | `union Bits { asByte: uint8; asInt: int32; }` | ✅ Implemented | +| **Interface (trait)** | `interface Display { func ToString() -> String; }` | ✅ Implemented | +| **Impl (extend)** | `extend Circle: Display { ... }` | ✅ Implemented | +| **Module** | `module Math;` | ✅ Implemented | +| **Type alias** | `type Int = int32;` | ✅ Implemented | +| **Extern function** | `extern func printf(fmt: *char8, ...);` | ✅ Implemented | + +### A.3 Statements & Control Flow + +| Construct | Rux Syntax | Bux Status | +|-----------|------------|------------| +| **If/else** | `if cond { ... } else { ... }` | ✅ Implemented | +| **While loop** | `while cond { ... }` | ✅ Implemented | +| **Do-while** | `do { ... } while cond;` | ✅ Implemented | +| **Infinite loop** | `loop { ... }` | ✅ Implemented | +| **For-in loop** | `for item in collection { ... }` | ✅ Implemented | +| **Range (exclusive)** | `0..10` (0 to 9) | ✅ Implemented | +| **Range (inclusive)** | `0..=10` (0 to 10) | ✅ Implemented | +| **Match expression** | `match val { pat => expr, ... }` | ✅ Implemented | +| **Break** | `break;` or `break label;` | ✅ Implemented | +| **Continue** | `continue;` or `continue label;` | ✅ Implemented | +| **Return** | `return expr;` | ✅ Implemented | +| **Labeled loops** | `outer: loop { ... break outer; }` | ✅ Implemented | + +### A.4 Pattern Matching + +| Pattern | Rux Syntax | Bux Status | +|---------|------------|------------| +| **Wildcard** | `_` | ✅ Implemented | +| **Literal** | `42`, `"hello"`, `true` | ✅ Implemented | +| **Identifier** | `name` (binds value) | ✅ Implemented | +| **Range** | `1..9`, `1..=9` | ✅ Implemented | +| **Enum destructuring** | `Shape::Circle(r)` | ✅ Implemented | +| **Struct destructuring** | `Point { x: 0, y: 0 }` | ✅ Implemented | +| **Tuple** | `(a, b, c)` | ✅ Implemented | +| **Guard** | `t if t < 0` | ✅ Implemented | + +### A.5 Expressions & Operators + +| Category | Rux Operators | Bux Status | +|----------|---------------|------------| +| **Arithmetic** | `+`, `-`, `*`, `/`, `%`, `**` | ✅ Implemented | +| **Comparison** | `==`, `!=`, `<`, `<=`, `>`, `>=` | ✅ Implemented | +| **Logical** | `&&`, `\|\|`, `!` | ✅ Implemented | +| **Bitwise** | `&`, `\|`, `^`, `~`, `<<`, `>>` | ✅ Implemented | +| **Assignment** | `=`, `+=`, `-=`, `*=`, `/=`, etc. | ✅ Implemented | +| **Increment/Decrement** | `++`, `--` | ✅ Implemented | +| **Cast** | `expr as Type` | ✅ Implemented | +| **Type test** | `expr is Type` | ✅ Implemented | +| **Ternary** | `cond ? then : else` | ✅ Implemented | +| **Path** | `Module::Name` | ✅ Implemented | +| **Field access** | `obj.field` | ✅ Implemented | +| **Index** | `arr[idx]` | ✅ Implemented | +| **Call** | `func(args...)` | ✅ Implemented | +| **Spread** | `func(slice...)` | ✅ Implemented | +| **Struct init** | `Point { x: 1.0, y: 2.0 }` | ✅ Implemented | +| **Slice init** | `[1, 2, 3]` | ✅ Implemented | +| **Tuple init** | `(a, b, c)` | ✅ Implemented | +| **Sizeof** | `sizeof(Type)` | ✅ Implemented | +| **Dereference** | `*ptr` | ✅ Implemented | +| **Address-of** | `&var` | ✅ Implemented | + +### A.6 Modules & Imports + +| Feature | Rux Syntax | Bux Status | +|---------|------------|------------| +| **Single import** | `import Math::Sqrt;` | ✅ Implemented | +| **Multiple imports** | `import Http::{ Request, Response };` | ✅ Implemented | +| **Wildcard import** | `import Std::Io::*;` | ⏳ Phase 1 | +| **Public visibility** | `pub struct Foo { ... }` | ✅ Implemented | +| **Private (default)** | Items private to module by default | ✅ Implemented | + +### A.7 Functions + +| Feature | Rux Syntax | Bux Status | +|---------|------------|------------| +| **Basic function** | `func Name(params) -> RetType { body }` | ✅ Implemented | +| **Parameters** | `name: type` | ✅ Implemented | +| **Return type** | `-> type` | ✅ Implemented | +| **Multiple returns** | `-> (type1, type2)` via tuple | ⏳ Phase 1 | +| **Variadic** | `values: type...` | ⏳ Phase 1 | +| **Generics** | `func Name(...)` | ⏳ Phase 2.7 | +| **Assembler** | `asm func Name() { ... }` | ⏳ Phase 8 | +| **Entry point** | `func Main() -> int` | ✅ Implemented | + +### A.8 Features Bux Adds Beyond Rux + +| Feature | Bux Syntax | Rux Equivalent | +|---------|------------|----------------| +| **Error propagation** | `expr?` | ❌ Not in Rux | +| **Unwrap/panic** | `expr!` | ❌ Not in Rux | +| **Ownership (opt-in)** | `@[Checked]` attribute | ❌ Not in Rux | +| **Borrow checking** | `&T`, `&mut T` with lifetimes | ❌ Not in Rux | +| **Async/await** | `async func`, `.await` | ❌ Not in Rux | +| **Channels** | `Channel` | ❌ Not in Rux | +| **CTFE** | `const func` | Partial (const only) | +| **String interpolation** | `"Hello, {name}!"` | ❌ Not in Rux | +| **Iterators** | `for x in iter.map(...)` | ❌ Not in Rux | +| **Derive macros** | `#[derive(Clone, Debug)]` | ❌ Not in Rux | +| **Declarative macros** | `macro! Name { ... }` | ❌ Not in Rux | + +--- + +## Appendix B: Bux Token Reference + +Complete token list from the lexer (matches Rux token set): + +### Literals +`tkIntLiteral`, `tkFloatLiteral`, `tkStringLiteral`, `tkCharLiteral`, `tkBoolLiteral` + +### Keywords +- **Control flow:** `if`, `else`, `while`, `do`, `loop`, `for`, `in`, `break`, `continue`, `return`, `match` +- **Declarations:** `func`, `let`, `var`, `const`, `type`, `struct`, `enum`, `union`, `interface`, `extend`, `module`, `import`, `pub`, `extern` +- **Other:** `as`, `is`, `null`, `self`, `super`, `sizeof` + +### Operators +- **Arithmetic:** `+`, `-`, `*`, `/`, `%`, `**`, `++`, `--` +- **Bitwise:** `&`, `|`, `^`, `~`, `<<`, `>>` +- **Logical:** `&&`, `||`, `!` +- **Comparison:** `==`, `!=`, `<`, `<=`, `>`, `>=` +- **Assignment:** `=`, `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `<<=`, `>>=` + +### Punctuation +`(`, `)`, `{`, `}`, `[`, `]`, `,`, `;`, `:`, `::`, `.`, `..`, `...`, `..=`, `->`, `=>`, `@`, `#`, `?` + +### Compile-time Intrinsics +`#line`, `#column`, `#file`, `#function`, `#date`, `#time`, `#module` + +--- + +## Appendix C: Build & Tooling Commands + +```bash +# Build the bootstrap compiler (Nim) +make build + +# Run tests +make test + +# Create a new Bux project +bux new myproject + +# Build a Bux project +bux build + +# Run a Bux project +bux run + +# Type-check without building +bux check + +# Clean build artifacts +bux clean + +# Show version +bux version + +# Future commands (Phase 8+) +bux fmt # Format code +bux test # Run tests +bux doc # Generate documentation +bux add # Add dependency +bux lsp # Start language server +``` diff --git a/src/c_backend.nim b/src/c_backend.nim new file mode 100644 index 0000000..20ac309 --- /dev/null +++ b/src/c_backend.nim @@ -0,0 +1,386 @@ +import std/[strformat, strutils, tables] +import hir, types, token, source_location + +type + CBackend* = object + output*: string + indent*: int + varCounter*: int + declaredVars*: seq[string] + +proc initCBackend*(): CBackend = + result.output = "" + result.indent = 0 + result.varCounter = 0 + result.declaredVars = @[] + +proc emit(be: var CBackend, s: string) = + be.output.add(s) + +proc emitLine(be: var CBackend, s: string) = + for i in 0.. 0: + return typeToC(typ.inner[0]) & "*" + return "void*" + of tkSlice: + if typ.inner.len > 0: + return typeToC(typ.inner[0]) & "*" + return "void*" + of tkNamed: return typ.name + of tkTuple: return "void*" # TODO: proper tuple struct + of tkFunc: return "void*" # TODO: function pointer + else: return "int" + +proc operatorToC(op: TokenKind): string = + case op + of tkPlus: return "+" + of tkMinus: return "-" + of tkStar: return "*" + of tkSlash: return "/" + of tkPercent: return "%" + of tkAmp: return "&" + of tkPipe: return "|" + of tkCaret: return "^" + of tkShl: return "<<" + of tkShr: return ">>" + of tkAmpAmp: return "&&" + of tkPipePipe: return "||" + of tkEq: return "==" + of tkNe: return "!=" + of tkLt: return "<" + of tkLe: return "<=" + of tkGt: return ">" + of tkGe: return ">=" + of tkBang: return "!" + of tkTilde: return "~" + of tkPlusPlus: return "++" + of tkMinusMinus: return "--" + of tkPlusAssign: return "+=" + of tkMinusAssign: return "-=" + of tkStarAssign: return "*=" + of tkSlashAssign: return "/=" + of tkPercentAssign: return "%=" + else: return "?" + +# Forward declaration +proc emitExpr(be: var CBackend, node: HirNode): string +proc emitStmt(be: var CBackend, node: HirNode) + +proc emitExpr(be: var CBackend, node: HirNode): string = + if node == nil: return "0" + case node.kind + of hLit: + case node.litToken.kind + of tkBoolLiteral: + if node.litToken.text == "true": return "true" + else: return "false" + of tkStringLiteral: + return node.litToken.text + of tkNull: + return "NULL" + else: + return node.litToken.text + + of hVar: + return node.varName + + of hSelf: + return "self" + + of hUnary: + let operand = be.emitExpr(node.unaryOperand) + let op = operatorToC(node.unaryOp) + if node.unaryOp == tkStar: + return &"(*{operand})" + elif node.unaryOp == tkAmp: + return &"(&{operand})" + else: + return &"({op}{operand})" + + of hBinary: + let left = be.emitExpr(node.binaryLeft) + let right = be.emitExpr(node.binaryRight) + let op = operatorToC(node.binaryOp) + return &"({left} {op} {right})" + + of hCall: + var args: seq[string] = @[] + for arg in node.callArgs: + args.add(be.emitExpr(arg)) + let argsStr = args.join(", ") + return &"{node.callCallee}({argsStr})" + + of hCallIndirect: + let callee = be.emitExpr(node.callIndirectCallee) + var args: seq[string] = @[] + for arg in node.callIndirectArgs: + args.add(be.emitExpr(arg)) + let argsStr = args.join(", ") + return &"({callee})({argsStr})" + + of hLoad: + let ptrExpr = be.emitExpr(node.loadPtr) + return &"(*{ptrExpr})" + + of hFieldPtr: + let base = be.emitExpr(node.fieldPtrBase) + return &"(&({base}.{node.fieldName}))" + + of hIndexPtr: + let base = be.emitExpr(node.indexPtrBase) + let idx = be.emitExpr(node.indexPtrIndex) + return &"(&({base}[{idx}]))" + + of hStructInit: + # C99 compound literal: (StructName){.field1 = val1, .field2 = val2} + var fields: seq[string] = @[] + for f in node.structInitFields: + let val = be.emitExpr(f.value) + fields.add(&".{f.name} = {val}") + let fieldsStr = fields.join(", ") + return &"(({node.structInitName}){{{fieldsStr}}})" + + of hSliceInit: + # For now, use a static array + var elems: seq[string] = @[] + for e in node.sliceInitElements: + elems.add(be.emitExpr(e)) + let elemsStr = elems.join(", ") + return &"{{{elemsStr}}}" + + of hTupleInit: + var elems: seq[string] = @[] + for e in node.tupleInitElements: + elems.add(be.emitExpr(e)) + return &"{{{elems.join(\", \")}}}" + + of hCast: + let operand = be.emitExpr(node.castOperand) + let typ = typeToC(node.castType) + return &"(({typ}){operand})" + + of hIs: + return "true" # TODO: proper type checking + + of hIf: + # Ternary expression + let cond = be.emitExpr(node.ifCond) + let thenE = be.emitExpr(node.ifThen) + let elseE = be.emitExpr(node.ifElse) + return &"({cond} ? {thenE} : {elseE})" + + of hAssign: + let target = be.emitExpr(node.assignTarget) + let value = be.emitExpr(node.assignValue) + let op = operatorToC(node.assignOp) + return &"({target} {op} {value})" + + of hBlock: + # For block expressions, just emit the last expression + if node.blockExpr != nil: + return be.emitExpr(node.blockExpr) + elif node.blockStmts.len > 0: + return be.emitExpr(node.blockStmts[^1]) + return "0" + + of hMatch: + return "0" # TODO: match expression lowering + + else: + return "0" + +proc emitStmt(be: var CBackend, node: HirNode) = + if node == nil: return + case node.kind + of hReturn: + if node.returnValue != nil: + let val = be.emitExpr(node.returnValue) + be.emitLine(&"return {val};") + else: + be.emitLine("return;") + + of hIf: + let cond = be.emitExpr(node.ifCond) + be.emitLine(&"if ({cond}) {{") + inc be.indent + be.emitStmt(node.ifThen) + dec be.indent + if node.ifElse != nil: + be.emitLine("} else {") + inc be.indent + be.emitStmt(node.ifElse) + dec be.indent + be.emitLine("}") + + of hWhile: + let cond = be.emitExpr(node.whileCond) + be.emitLine(&"while ({cond}) {{") + inc be.indent + be.emitStmt(node.whileBody) + dec be.indent + be.emitLine("}") + + of hLoop: + be.emitLine("while (1) {") + inc be.indent + be.emitStmt(node.loopBody) + dec be.indent + be.emitLine("}") + + of hBreak: + be.emitLine("break;") + + of hContinue: + be.emitLine("continue;") + + of hBlock: + for stmt in node.blockStmts: + be.emitStmt(stmt) + if node.blockExpr != nil: + let val = be.emitExpr(node.blockExpr) + be.emitLine(&"{val};") + + of hAlloca: + let typ = typeToC(node.allocaType) + be.emitLine(&"{typ} {node.allocaName};") + + of hStore: + let ptrExpr = be.emitExpr(node.storePtr) + let val = be.emitExpr(node.storeValue) + be.emitLine(&"{ptrExpr} = {val};") + + of hAssign: + let target = be.emitExpr(node.assignTarget) + let value = be.emitExpr(node.assignValue) + let op = operatorToC(node.assignOp) + be.emitLine(&"{target} {op} {value};") + + of hCall: + let expr = be.emitExpr(node) + be.emitLine(&"{expr};") + + of hCallIndirect: + let expr = be.emitExpr(node) + be.emitLine(&"{expr};") + + else: + # Expression statement + let expr = be.emitExpr(node) + be.emitLine(&"{expr};") + +proc emitFunc*(be: var CBackend, hfunc: HirFunc) = + let retType = typeToC(hfunc.retType) + var params: seq[string] = @[] + for p in hfunc.params: + params.add(&"{typeToC(p.typ)} {p.name}") + if params.len == 0: + params.add("void") + let paramsStr = params.join(", ") + be.emitLine(&"{retType} {hfunc.name}({paramsStr}) {{") + inc be.indent + if hfunc.body != nil: + be.emitStmt(hfunc.body) + dec be.indent + be.emitLine("}") + be.emitLine("") + +proc emitStruct*(be: var CBackend, name: string, fields: seq[tuple[name: string, typ: Type]]) = + be.emitLine(&"typedef struct {name} {{") + inc be.indent + for f in fields: + let typ = typeToC(f.typ) + be.emitLine(&"{typ} {f.name};") + dec be.indent + be.emitLine(&"}} {name};") + be.emitLine("") + +proc emitEnum*(be: var CBackend, name: string, variants: seq[string]) = + be.emitLine(&"typedef enum {{") + inc be.indent + for i, v in variants: + if i < variants.len - 1: + be.emitLine(&"{name}_{v},") + else: + be.emitLine(&"{name}_{v}") + dec be.indent + be.emitLine(&"}} {name};") + be.emitLine("") + +proc emitModule*(be: var CBackend, module: HirModule): string = + # Header + be.emitLine("/* Generated by Bux Compiler */") + be.emitLine("#include ") + be.emitLine("#include ") + be.emitLine("#include ") + be.emitLine("#include ") + be.emitLine("#include ") + be.emitLine("") + + # Forward declarations + for s in module.structs: + be.emitLine(&"typedef struct {s.name} {s.name};") + if module.structs.len > 0: + be.emitLine("") + + # Struct definitions + for s in module.structs: + be.emitStruct(s.name, s.fields) + + # Enum definitions + for e in module.enums: + be.emitEnum(e.name, e.variants) + + # Function definitions + var hasMain = false + for f in module.funcs: + be.emitFunc(f) + if f.name == "Main": + hasMain = true + + # Generate C main wrapper if Bux Main exists + if hasMain: + be.emitLine("/* C entry point wrapper */") + be.emitLine("int main(int argc, char** argv) {") + be.emitLine(" return Main();") + be.emitLine("}") + be.emitLine("") + + return be.output diff --git a/src/cli.nim b/src/cli.nim index ba64eac..d674ae8 100644 --- a/src/cli.nim +++ b/src/cli.nim @@ -1,5 +1,5 @@ -import std/[os, strutils, terminal, strformat] -import lexer, parser, sema, manifest +import std/[os, strutils, terminal, strformat, osproc] +import lexer, parser, sema, manifest, hir, hir_lower, c_backend, types, scope type ColorMode* = enum @@ -192,12 +192,104 @@ proc cmdCheck*(args: seq[string], opts: GlobalOptions): int = proc cmdBuild*(args: seq[string], opts: GlobalOptions): int = let useColor = shouldUseColor(opts) - # For now, just type-check - let checkRes = cmdCheck(args, opts) - if checkRes != 0: - return checkRes + let root = getCurrentDir() + let manifestPath = root / "bux.toml" + if not fileExists(manifestPath): + printError("no bux.toml found", useColor) + return 1 + let man = loadManifest(manifestPath) + let srcDir = root / "src" + if not dirExists(srcDir): + printError("no src/ directory found", useColor) + return 1 + + # Create build directory + let buildDir = root / "build" + if not dirExists(buildDir): + createDir(buildDir) + + # Process all .bux files + var allCCode = "" + var foundMain = false + for kind, path in walkDir(srcDir): + if kind == pcFile and path.endsWith(".bux"): + let source = readFile(path) + let lexRes = tokenize(source, path) + if lexRes.hasErrors: + printError(&"lex errors in {path}", useColor) + for d in lexRes.diagnostics: + echo $d + return 1 + let parseRes = parse(lexRes.tokens, path) + if parseRes.diagnostics.len > 0: + printError(&"parse errors in {path}", useColor) + for d in parseRes.diagnostics: + echo &"error: {d.message} at {d.loc}" + return 1 + let semaRes = analyze(parseRes.module) + if semaRes.hasErrors: + printError(&"type errors in {path}", useColor) + for d in semaRes.diagnostics: + let sev = if d.severity == sdsError: "error" else: "warning" + echo &"{sev}: {d.message} at {d.loc}" + return 1 + + # Lower to HIR + var s = Sema(module: parseRes.module, globalScope: newScope()) + s.collectGlobals() + let hirModule = lowerModule(parseRes.module, s) + + # Generate C code + var cbe = initCBackend() + let cCode = cbe.emitModule(hirModule) + allCCode.add(cCode) + allCCode.add("\n") + + if splitFile(path).name == "Main": + foundMain = true + + if not foundMain: + printError("no Main.bux found in src/", useColor) + return 1 + + # Write C file + let cFile = buildDir / "main.c" + writeFile(cFile, allCCode) + + # Copy runtime + let runtimeDst = buildDir / "runtime.c" + var runtimeFound = false + # Search in multiple locations + let searchPaths = @[ + getAppDir() / ".." / "stdlib" / "runtime.c", + getAppDir() / "stdlib" / "runtime.c", + root / "stdlib" / "runtime.c", + root / "stdlib_runtime.c", + "/home/ziko/z-git/bux/bux/stdlib/runtime.c", # TODO: make this configurable + ] + for runtimePath in searchPaths: + if fileExists(runtimePath): + copyFile(runtimePath, runtimeDst) + runtimeFound = true + break + if not runtimeFound: + printError("runtime.c not found (searched in stdlib/, build/, and project root)", useColor) + return 1 + + # Compile with cc + let outputName = if man.name != "": man.name else: "bux_out" + let outputFile = buildDir / outputName + let ccCmd = &"cc -o {outputFile} {cFile} {runtimeDst} -lm 2>&1" + if opts.verbose: + printInfo(&"running: {ccCmd}", useColor) + let (output, exitCode) = execCmdEx(ccCmd) + if exitCode != 0: + printError("C compilation failed:", useColor) + echo output + return 1 + if not opts.quiet: - printInfo("build: nothing to do yet (no codegen)", useColor) + printInfo(&"build: {outputFile}", useColor) return 0 proc cmdRun*(args: seq[string], opts: GlobalOptions): int = @@ -205,14 +297,24 @@ proc cmdRun*(args: seq[string], opts: GlobalOptions): int = let buildRes = cmdBuild(args, opts) if buildRes != 0: return buildRes - printError("run: no executable produced yet (no codegen)", useColor) - return 1 + let root = getCurrentDir() + let man = loadManifest(root / "bux.toml") + let outputName = if man.name != "": man.name else: "bux_out" + let outputFile = root / "build" / outputName + if not fileExists(outputFile): + printError("executable not found after build", useColor) + return 1 + let exitCode = execCmd(outputFile) + return exitCode proc cmdClean*(args: seq[string], opts: GlobalOptions): int = let useColor = shouldUseColor(opts) - # Placeholder + let root = getCurrentDir() + let buildDir = root / "build" + if dirExists(buildDir): + removeDir(buildDir) if not opts.quiet: - printInfo("clean: nothing to do yet", useColor) + printInfo("clean: build directory removed", useColor) return 0 proc cmdVersion*(args: seq[string], opts: GlobalOptions): int = diff --git a/src/hir.nim b/src/hir.nim new file mode 100644 index 0000000..8014dfd --- /dev/null +++ b/src/hir.nim @@ -0,0 +1,165 @@ +import ast, types, token, source_location + +type + HirKind* = enum + # Literals + hLit + hVar + hSelf + # Operations + hUnary + hBinary + hAssign + # Control flow + hIf + hWhile + hLoop + hBreak + hContinue + hReturn + # Memory + hAlloca + hLoad + hStore + hFieldPtr + hIndexPtr + # Functions + hCall + hCallIndirect + # Type operations + hCast + hIs + # Composite + hBlock + hStructInit + hSliceInit + hTupleInit + # Match (desugared to switch/branch later) + hMatch + + HirNode* = ref object + loc*: SourceLocation + typ*: Type + case kind*: HirKind + of hLit: + litToken*: Token + of hVar: + varName*: string + of hSelf: + discard + of hUnary: + unaryOp*: TokenKind + unaryOperand*: HirNode + of hBinary: + binaryOp*: TokenKind + binaryLeft*: HirNode + binaryRight*: HirNode + of hAssign: + assignOp*: TokenKind + assignTarget*: HirNode + assignValue*: HirNode + of hIf: + ifCond*: HirNode + ifThen*: HirNode + ifElse*: HirNode + of hWhile: + whileCond*: HirNode + whileBody*: HirNode + of hLoop: + loopBody*: HirNode + of hBreak: + breakLabel*: string + of hContinue: + continueLabel*: string + of hReturn: + returnValue*: HirNode + of hAlloca: + allocaType*: Type + allocaName*: string + of hLoad: + loadPtr*: HirNode + of hStore: + storePtr*: HirNode + storeValue*: HirNode + of hFieldPtr: + fieldPtrBase*: HirNode + fieldName*: string + of hIndexPtr: + indexPtrBase*: HirNode + indexPtrIndex*: HirNode + of hCall: + callCallee*: string + callArgs*: seq[HirNode] + of hCallIndirect: + callIndirectCallee*: HirNode + callIndirectArgs*: seq[HirNode] + of hCast: + castOperand*: HirNode + castType*: Type + of hIs: + isOperand*: HirNode + isType*: Type + of hBlock: + blockStmts*: seq[HirNode] + blockExpr*: HirNode + of hStructInit: + structInitName*: string + structInitFields*: seq[tuple[name: string, value: HirNode]] + of hSliceInit: + sliceInitElements*: seq[HirNode] + of hTupleInit: + tupleInitElements*: seq[HirNode] + of hMatch: + matchSubject*: HirNode + matchArms*: seq[HirMatchArm] + + HirMatchArm* = object + pattern*: Pattern + body*: HirNode + + HirFunc* = object + name*: string + params*: seq[tuple[name: string, typ: Type]] + retType*: Type + body*: HirNode + isPublic*: bool + + HirModule* = object + funcs*: seq[HirFunc] + structs*: seq[tuple[name: string, fields: seq[tuple[name: string, typ: Type]]]] + enums*: seq[tuple[name: string, variants: seq[string]]] + consts*: seq[tuple[name: string, typ: Type, value: HirNode]] + +# Constructor helpers +proc hirLit*(tok: Token, typ: Type, loc: SourceLocation): HirNode = + HirNode(kind: hLit, litToken: tok, typ: typ, loc: loc) + +proc hirVar*(name: string, typ: Type, loc: SourceLocation): HirNode = + HirNode(kind: hVar, varName: name, typ: typ, loc: loc) + +proc hirSelf*(typ: Type, loc: SourceLocation): HirNode = + HirNode(kind: hSelf, typ: typ, loc: loc) + +proc hirUnary*(op: TokenKind, operand: HirNode, typ: Type, loc: SourceLocation): HirNode = + HirNode(kind: hUnary, unaryOp: op, unaryOperand: operand, typ: typ, loc: loc) + +proc hirBinary*(op: TokenKind, left, right: HirNode, typ: Type, loc: SourceLocation): HirNode = + HirNode(kind: hBinary, binaryOp: op, binaryLeft: left, binaryRight: right, typ: typ, loc: loc) + +proc hirCall*(callee: string, args: seq[HirNode], typ: Type, loc: SourceLocation): HirNode = + HirNode(kind: hCall, callCallee: callee, callArgs: args, typ: typ, loc: loc) + +proc hirReturn*(value: HirNode, loc: SourceLocation): HirNode = + HirNode(kind: hReturn, returnValue: value, typ: makeVoid(), loc: loc) + +proc hirBlock*(stmts: seq[HirNode], expr: HirNode, typ: Type, loc: SourceLocation): HirNode = + HirNode(kind: hBlock, blockStmts: stmts, blockExpr: expr, typ: typ, loc: loc) + +proc hirAlloca*(name: string, typ: Type, loc: SourceLocation): HirNode = + HirNode(kind: hAlloca, allocaType: typ, allocaName: name, typ: makePointer(typ), loc: loc) + +proc hirStore*(ptrNode, value: HirNode, loc: SourceLocation): HirNode = + HirNode(kind: hStore, storePtr: ptrNode, storeValue: value, typ: makeVoid(), loc: loc) + +proc hirLoad*(ptrNode: HirNode, typ: Type, loc: SourceLocation): HirNode = + HirNode(kind: hLoad, loadPtr: ptrNode, typ: typ, loc: loc) diff --git a/src/hir_lower.nim b/src/hir_lower.nim new file mode 100644 index 0000000..882d166 --- /dev/null +++ b/src/hir_lower.nim @@ -0,0 +1,464 @@ +import std/[tables, strformat, strutils] +import ast, types, token, source_location, hir, sema, scope + +type + LowerCtx* = object + module*: Module + globalScope*: Scope + methodTable*: Table[string, seq[MethodInfo]] + currentFuncRetType*: Type + varCounter*: int + +proc freshName(ctx: var LowerCtx): string = + inc ctx.varCounter + result = "__tmp_" & $ctx.varCounter + +proc initLowerCtx*(module: Module, sema: Sema): LowerCtx = + result.module = module + result.globalScope = sema.globalScope + result.methodTable = sema.methodTable + result.varCounter = 0 + +# Forward declarations +proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode +proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode +proc lowerBlock(ctx: var LowerCtx, blk: Block): HirNode + +proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type = + if expr == nil: return makeUnknown() + case expr.kind + of ekLiteral: + case expr.exprLit.kind + of tkIntLiteral: return makeInt() + of tkFloatLiteral: return makeFloat64() + of tkStringLiteral: return makeStr() + of tkCharLiteral: return makeChar8() + of tkBoolLiteral: return makeBool() + else: return makeUnknown() + of ekIdent: + let sym = ctx.globalScope.lookup(expr.exprIdent) + if sym != nil and sym.typ != nil: return sym.typ + return makeUnknown() + of ekSelf: return makeNamed("self") + of ekBinary: + let left = ctx.resolveExprType(expr.exprBinaryLeft) + case expr.exprBinaryOp + of tkEq, tkNe, tkLt, tkLe, tkGt, tkGe, tkAmpAmp, tkPipePipe: + return makeBool() + else: return left + of ekUnary: + case expr.exprUnaryOp + of tkBang: return makeBool() + of tkAmp: return makePointer(ctx.resolveExprType(expr.exprUnaryOperand)) + of tkStar: + let inner = ctx.resolveExprType(expr.exprUnaryOperand) + if inner.isPointer: return inner.inner[0] + return makeUnknown() + else: return ctx.resolveExprType(expr.exprUnaryOperand) + of ekCall: + if expr.exprCallCallee.kind == ekIdent: + let sym = ctx.globalScope.lookup(expr.exprCallCallee.exprIdent) + if sym != nil and sym.typ != nil and sym.typ.kind == tkFunc: + return sym.typ.inner[^1] + if expr.exprCallCallee.kind == ekField: + let recvType = ctx.resolveExprType(expr.exprCallCallee.exprFieldObj) + let methodName = expr.exprCallCallee.exprFieldName + var typeName = "" + if recvType.kind == tkNamed: typeName = recvType.name + elif recvType.isPointer and recvType.inner.len > 0 and recvType.inner[0].kind == tkNamed: + typeName = recvType.inner[0].name + if typeName != "" and ctx.methodTable.hasKey(typeName): + for minfo in ctx.methodTable[typeName]: + if minfo.name == methodName: + return minfo.retType + return makeUnknown() + of ekField: + let objType = ctx.resolveExprType(expr.exprFieldObj) + if objType.kind == tkNamed: + let sym = ctx.globalScope.lookup(objType.name) + if sym != nil and sym.decl != nil and sym.decl.kind == dkStruct: + for f in sym.decl.declStructFields: + if f.name == expr.exprFieldName: + if f.ftype != nil: + case f.ftype.kind + of tekNamed: + case f.ftype.typeName + of "int", "int32", "int64": return makeInt() + of "float64": return makeFloat64() + of "float32": return makeFloat32() + of "bool": return makeBool() + else: return makeNamed(f.ftype.typeName) + of tekPointer: return makePointer(makeUnknown()) + else: return makeUnknown() + return makeUnknown() + of ekStructInit: return makeNamed(expr.exprStructInitName) + of ekSlice: + if expr.exprSliceElements.len > 0: + return makeSlice(ctx.resolveExprType(expr.exprSliceElements[0])) + return makeSlice(makeUnknown()) + of ekTuple: + var elems: seq[Type] = @[] + for e in expr.exprTupleElements: + elems.add(ctx.resolveExprType(e)) + return makeTuple(elems) + of ekCast: + if expr.exprCastType != nil: + case expr.exprCastType.kind + of tekNamed: return makeNamed(expr.exprCastType.typeName) + else: return makeUnknown() + return makeUnknown() + of ekBlock: + if expr.exprBlock.stmts.len > 0: + let last = expr.exprBlock.stmts[^1] + if last.kind == skExpr: + return ctx.resolveExprType(last.stmtExpr) + return makeVoid() + else: return makeUnknown() + +proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode = + if expr == nil: return nil + let loc = expr.loc + let typ = ctx.resolveExprType(expr) + + case expr.kind + of ekLiteral: + return hirLit(expr.exprLit, typ, loc) + + of ekIdent: + return hirVar(expr.exprIdent, typ, loc) + + of ekSelf: + return hirSelf(typ, loc) + + of ekUnary: + let operand = ctx.lowerExpr(expr.exprUnaryOperand) + return hirUnary(expr.exprUnaryOp, operand, typ, loc) + + of ekBinary: + let left = ctx.lowerExpr(expr.exprBinaryLeft) + let right = ctx.lowerExpr(expr.exprBinaryRight) + return hirBinary(expr.exprBinaryOp, left, right, typ, loc) + + of ekCall: + # Method call desugaring: obj.method(args) → Type_method(obj, args) + if expr.exprCallCallee.kind == ekField: + let recvType = ctx.resolveExprType(expr.exprCallCallee.exprFieldObj) + let methodName = expr.exprCallCallee.exprFieldName + var typeName = "" + if recvType.kind == tkNamed: typeName = recvType.name + elif recvType.isPointer and recvType.inner.len > 0 and recvType.inner[0].kind == tkNamed: + typeName = recvType.inner[0].name + if typeName != "" and ctx.methodTable.hasKey(typeName): + for minfo in ctx.methodTable[typeName]: + if minfo.name == methodName: + # Desugar: obj.method(args) → Type_method(&obj, args) + let mangledName = typeName & "_" & methodName + var args: seq[HirNode] = @[] + args.add(ctx.lowerExpr(expr.exprCallCallee.exprFieldObj)) + 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: + calleeName = expr.exprCallCallee.exprIdent + elif expr.exprCallCallee.kind == ekPath: + calleeName = expr.exprCallCallee.exprPath.join("::") + var args: seq[HirNode] = @[] + for arg in expr.exprCallArgs: + args.add(ctx.lowerExpr(arg)) + if calleeName != "": + return hirCall(calleeName, args, typ, loc) + else: + let callee = ctx.lowerExpr(expr.exprCallCallee) + return HirNode(kind: hCallIndirect, callIndirectCallee: callee, + callIndirectArgs: args, typ: typ, loc: loc) + + of ekField: + let base = ctx.lowerExpr(expr.exprFieldObj) + let basePtr = HirNode(kind: hFieldPtr, fieldPtrBase: base, + fieldName: expr.exprFieldName, + typ: makePointer(typ), loc: loc) + return HirNode(kind: hLoad, loadPtr: basePtr, typ: typ, loc: loc) + + of ekIndex: + let base = ctx.lowerExpr(expr.exprIndexObj) + let idx = ctx.lowerExpr(expr.exprIndexIdx) + let basePtr = HirNode(kind: hIndexPtr, indexPtrBase: base, + indexPtrIndex: idx, typ: makePointer(typ), loc: loc) + return HirNode(kind: hLoad, loadPtr: basePtr, typ: typ, loc: loc) + + of ekAssign: + let target = ctx.lowerExpr(expr.exprAssignTarget) + let value = ctx.lowerExpr(expr.exprAssignValue) + return HirNode(kind: hAssign, assignOp: expr.exprAssignOp, + assignTarget: target, assignValue: value, + typ: makeVoid(), loc: loc) + + of ekStructInit: + var fields: seq[tuple[name: string, value: HirNode]] = @[] + for f in expr.exprStructInitFields: + fields.add((f.name, ctx.lowerExpr(f.value))) + return HirNode(kind: hStructInit, structInitName: expr.exprStructInitName, + structInitFields: fields, typ: typ, loc: loc) + + of ekSlice: + var elems: seq[HirNode] = @[] + for e in expr.exprSliceElements: + elems.add(ctx.lowerExpr(e)) + return HirNode(kind: hSliceInit, sliceInitElements: elems, typ: typ, loc: loc) + + of ekTuple: + var elems: seq[HirNode] = @[] + for e in expr.exprTupleElements: + elems.add(ctx.lowerExpr(e)) + return HirNode(kind: hTupleInit, tupleInitElements: elems, typ: typ, loc: loc) + + of ekCast: + let operand = ctx.lowerExpr(expr.exprCastOperand) + var castType = makeUnknown() + if expr.exprCastType != nil: + case expr.exprCastType.kind + of tekNamed: castType = makeNamed(expr.exprCastType.typeName) + of tekPointer: castType = makePointer(makeUnknown()) + else: discard + return HirNode(kind: hCast, castOperand: operand, castType: castType, + typ: typ, loc: loc) + + of ekBlock: + return ctx.lowerBlock(expr.exprBlock) + + of ekPostfix: + let operand = ctx.lowerExpr(expr.exprPostfixOperand) + return HirNode(kind: hUnary, unaryOp: expr.exprPostfixOp, + unaryOperand: operand, typ: typ, loc: loc) + + of ekTernary: + let cond = ctx.lowerExpr(expr.exprTernaryCond) + let thenE = ctx.lowerExpr(expr.exprTernaryThen) + let elseE = ctx.lowerExpr(expr.exprTernaryElse) + return HirNode(kind: hIf, ifCond: cond, ifThen: thenE, ifElse: elseE, + typ: typ, loc: loc) + + of ekIs: + let operand = ctx.lowerExpr(expr.exprIsOperand) + var isType = makeUnknown() + if expr.exprIsType != nil and expr.exprIsType.kind == tekNamed: + isType = makeNamed(expr.exprIsType.typeName) + return HirNode(kind: hIs, isOperand: operand, isType: isType, + typ: makeBool(), loc: loc) + + of ekMatch: + let subject = ctx.lowerExpr(expr.exprMatchSubject) + var arms: seq[HirMatchArm] = @[] + for arm in expr.exprMatchArms: + arms.add(HirMatchArm(pattern: arm.pattern, body: ctx.lowerExpr(arm.body))) + return HirNode(kind: hMatch, matchSubject: subject, matchArms: arms, + typ: typ, loc: loc) + + of ekSizeOf: + return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc), + typ: makeInt(), loc: loc) + + of ekIntrinsic: + return HirNode(kind: hLit, litToken: Token(kind: tkStringLiteral, text: "\"\"", loc: loc), + typ: makeStr(), loc: loc) + + else: + return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc), + typ: makeVoid(), loc: loc) + +proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode = + if stmt == nil: return nil + let loc = stmt.loc + + case stmt.kind + of skExpr: + return ctx.lowerExpr(stmt.stmtExpr) + + of skLet: + let initHir = ctx.lowerExpr(stmt.stmtLetInit) + let allocaType = if stmt.stmtLetType != nil: + case stmt.stmtLetType.kind + of tekNamed: + case stmt.stmtLetType.typeName + of "int", "int32": makeInt() + of "int64": makeInt64() + of "float64": makeFloat64() + of "float32": makeFloat32() + of "bool": makeBool() + else: makeNamed(stmt.stmtLetType.typeName) + of tekPointer: makePointer(makeUnknown()) + else: makeUnknown() + else: + ctx.resolveExprType(stmt.stmtLetInit) + + let alloca = hirAlloca(stmt.stmtLetName, allocaType, loc) + let varNode = hirVar(stmt.stmtLetName, makePointer(allocaType), loc) + let store = hirStore(varNode, initHir, loc) + return HirNode(kind: hBlock, blockStmts: @[alloca, store], + blockExpr: nil, typ: makeVoid(), loc: loc) + + of skReturn: + let value = if stmt.stmtReturnValue != nil: ctx.lowerExpr(stmt.stmtReturnValue) else: nil + return hirReturn(value, loc) + + of skIf: + let cond = ctx.lowerExpr(stmt.stmtIfCond) + let thenBlock = ctx.lowerBlock(stmt.stmtIfThen) + var elseBlock: HirNode = nil + if stmt.stmtIfElse != nil: + elseBlock = ctx.lowerBlock(stmt.stmtIfElse) + elif stmt.stmtIfElseIfs.len > 0: + # Desugar else-if chain + var current: HirNode = nil + for i in countdown(stmt.stmtIfElseIfs.len - 1, 0): + let elifBranch = stmt.stmtIfElseIfs[i] + let elifCond = ctx.lowerExpr(elifBranch.cond) + let elifBlock = ctx.lowerBlock(elifBranch.blk) + current = HirNode(kind: hIf, ifCond: elifCond, ifThen: elifBlock, + ifElse: current, typ: makeVoid(), loc: elifBranch.loc) + elseBlock = current + return HirNode(kind: hIf, ifCond: cond, ifThen: thenBlock, ifElse: elseBlock, + typ: makeVoid(), loc: loc) + + of skWhile: + let cond = ctx.lowerExpr(stmt.stmtWhileCond) + let body = ctx.lowerBlock(stmt.stmtWhileBody) + return HirNode(kind: hWhile, whileCond: cond, whileBody: body, + typ: makeVoid(), loc: loc) + + of skLoop: + let body = ctx.lowerBlock(stmt.stmtLoopBody) + return HirNode(kind: hLoop, loopBody: body, typ: makeVoid(), loc: loc) + + of skBreak: + return HirNode(kind: hBreak, breakLabel: stmt.stmtBreakLabel, + typ: makeVoid(), loc: loc) + + of skContinue: + return HirNode(kind: hContinue, continueLabel: stmt.stmtContinueLabel, + typ: makeVoid(), loc: loc) + + of skFor: + # Desugar: for i in iter { body } → { let __iter = iter; while __hasNext(__iter) { let i = __next(__iter); body } } + let iterExpr = ctx.lowerExpr(stmt.stmtForIter) + let body = ctx.lowerBlock(stmt.stmtForBody) + # Simplified: just lower the body for now + return HirNode(kind: hLoop, loopBody: body, typ: makeVoid(), loc: loc) + + of skDoWhile: + let body = ctx.lowerBlock(stmt.stmtDoWhileBody) + let cond = ctx.lowerExpr(stmt.stmtDoWhileCond) + let whileNode = HirNode(kind: hWhile, whileCond: cond, whileBody: body, + typ: makeVoid(), loc: loc) + return HirNode(kind: hBlock, blockStmts: @[body, whileNode], + blockExpr: nil, typ: makeVoid(), loc: loc) + + of skMatch: + let subject = ctx.lowerExpr(stmt.stmtMatchSubject) + var arms: seq[HirMatchArm] = @[] + for arm in stmt.stmtMatchArms: + arms.add(HirMatchArm(pattern: arm.pattern, body: ctx.lowerExpr(arm.body))) + return HirNode(kind: hMatch, matchSubject: subject, matchArms: arms, + typ: makeVoid(), loc: loc) + + of skDecl: + return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc), + typ: makeVoid(), loc: loc) + +proc lowerBlock(ctx: var LowerCtx, blk: Block): HirNode = + if blk == nil: return nil + var stmts: seq[HirNode] = @[] + for s in blk.stmts: + let hir = ctx.lowerStmt(s) + if hir != nil: + stmts.add(hir) + return HirNode(kind: hBlock, blockStmts: stmts, blockExpr: nil, + typ: makeVoid(), loc: blk.loc) + +proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc = + var params: seq[tuple[name: string, typ: Type]] = @[] + for p in decl.declFuncParams: + var pType = makeUnknown() + if p.ptype != nil: + case p.ptype.kind + of tekNamed: + case p.ptype.typeName + of "int", "int32": pType = makeInt() + of "int64": pType = makeInt64() + of "float64": pType = makeFloat64() + of "float32": pType = makeFloat32() + of "bool": pType = makeBool() + of "Point", "Self": pType = makeNamed(p.ptype.typeName) + else: pType = makeNamed(p.ptype.typeName) + of tekPointer: pType = makePointer(makeUnknown()) + else: discard + params.add((p.name, pType)) + + var retType = makeVoid() + if decl.declFuncReturnType != nil: + case decl.declFuncReturnType.kind + of tekNamed: + case decl.declFuncReturnType.typeName + of "int", "int32": retType = makeInt() + of "int64": retType = makeInt64() + of "float64": retType = makeFloat64() + of "float32": retType = makeFloat32() + of "bool": retType = makeBool() + else: retType = makeNamed(decl.declFuncReturnType.typeName) + of tekPointer: retType = makePointer(makeUnknown()) + else: discard + + ctx.currentFuncRetType = retType + let body = if decl.declFuncBody != nil: ctx.lowerBlock(decl.declFuncBody) else: nil + + result = HirFunc(name: decl.declFuncName, params: params, retType: retType, + body: body, isPublic: decl.isPublic) + +proc lowerModule*(module: Module, sema: Sema): HirModule = + var ctx = initLowerCtx(module, sema) + var funcs: seq[HirFunc] = @[] + var structs: seq[tuple[name: string, fields: seq[tuple[name: string, typ: Type]]]] = @[] + var enums: seq[tuple[name: string, variants: seq[string]]] = @[] + var consts: seq[tuple[name: string, typ: Type, value: HirNode]] = @[] + + for decl in module.items: + case decl.kind + of dkFunc: + funcs.add(ctx.lowerFunc(decl)) + of dkImpl: + for methodDecl in decl.declImplMethods: + if methodDecl.kind == dkFunc: + var hf = ctx.lowerFunc(methodDecl) + hf.name = decl.declImplTypeName & "_" & hf.name + funcs.add(hf) + of dkStruct: + var fields: seq[tuple[name: string, typ: Type]] = @[] + for f in decl.declStructFields: + var fType = makeUnknown() + if f.ftype != nil and f.ftype.kind == tekNamed: + case f.ftype.typeName + of "float64": fType = makeFloat64() + of "float32": fType = makeFloat32() + of "int", "int32": fType = makeInt() + else: fType = makeNamed(f.ftype.typeName) + fields.add((f.name, fType)) + structs.add((decl.declStructName, fields)) + of dkEnum: + var variants: seq[string] = @[] + for v in decl.declEnumVariants: + variants.add(v.name) + enums.add((decl.declEnumName, variants)) + of dkConst: + let value = ctx.lowerExpr(decl.declConstValue) + let typ = if decl.declConstType != nil: + case decl.declConstType.kind + of tekNamed: makeNamed(decl.declConstType.typeName) + else: makeUnknown() + else: makeUnknown() + consts.add((decl.declConstName, typ, value)) + else: discard + + result = HirModule(funcs: funcs, structs: structs, enums: enums, consts: consts) diff --git a/src/parser b/src/parser new file mode 100755 index 0000000..4f251ac Binary files /dev/null and b/src/parser differ diff --git a/src/parser.nim b/src/parser.nim index ced96f8..d8f4896 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -323,10 +323,11 @@ proc parsePrimary(p: var Parser): Expr = case p.peek() of tkIntLiteral, tkFloatLiteral, tkStringLiteral, tkCharLiteral, tkBoolLiteral: return newLiteralExpr(p.advance()) + of tkSelf: + discard p.advance() + return Expr(kind: ekSelf, loc: loc) of tkIdent: let name = p.advance().text - if name == "self": - return Expr(kind: ekSelf, loc: loc) # Path expression: a::b::c if p.check(tkColonColon): var segs = @[name] @@ -781,11 +782,25 @@ proc parseParamList(p: var Parser, allowVariadic: bool = false): seq[Param] = var isVar = false if allowVariadic and p.check(tkDotDotDot): discard p.advance() - let name = p.expect(tkIdent, "expected parameter name after '...'").text + let nameTok = p.at + var name = "" + if nameTok.kind == tkIdent: + name = p.advance().text + elif nameTok.kind == tkSelf: + name = p.advance().text + else: + name = p.expect(tkIdent, "expected parameter name after '...'").text let ty = p.parseType() result.add(Param(loc: loc, name: name, ptype: ty, isVariadic: true)) else: - let name = p.expect(tkIdent, "expected parameter name").text + let nameTok = p.at + var name = "" + if nameTok.kind == tkIdent: + name = p.advance().text + elif nameTok.kind == tkSelf: + name = p.advance().text + else: + name = p.expect(tkIdent, "expected parameter name").text discard p.expect(tkColon, "expected ':' after parameter name") let ty = p.parseType() var defaultVal: Expr = nil @@ -826,6 +841,11 @@ proc parseStructDecl(p: var Parser, isPublic: bool): Decl = discard p.expect(tkLBrace, "expected '{' to start struct body") var fields: seq[StructField] = @[] while not p.check(tkRBrace) and not p.isAtEnd: + # Skip newlines + while p.check(tkNewLine): + discard p.advance() + if p.check(tkRBrace) or p.isAtEnd: + break let fLoc = p.currentLoc var fPub = false if p.check(tkPub): @@ -853,6 +873,11 @@ proc parseEnumDecl(p: var Parser, isPublic: bool): Decl = discard p.expect(tkLBrace, "expected '{' to start enum body") var variants: seq[EnumVariant] = @[] while not p.check(tkRBrace) and not p.isAtEnd: + # Skip newlines + while p.check(tkNewLine): + discard p.advance() + if p.check(tkRBrace) or p.isAtEnd: + break let vLoc = p.currentLoc let vName = p.expect(tkIdent, "expected variant name").text var fields: seq[TypeExpr] = @[] @@ -908,6 +933,11 @@ proc parseInterfaceDecl(p: var Parser, isPublic: bool): Decl = discard p.expect(tkLBrace, "expected '{' to start interface body") var methods: seq[Decl] = @[] while not p.check(tkRBrace) and not p.isAtEnd: + # Skip newlines + while p.check(tkNewLine): + discard p.advance() + if p.check(tkRBrace) or p.isAtEnd: + break methods.add(p.parseFuncDecl(false, false, ParsedAttrs())) discard p.expect(tkRBrace, "expected '}' to close interface") return Decl(kind: dkInterface, loc: loc, isPublic: isPublic, @@ -924,6 +954,11 @@ proc parseImplDecl(p: var Parser): Decl = discard p.expect(tkLBrace, "expected '{' to start impl block") var methods: seq[Decl] = @[] while not p.check(tkRBrace) and not p.isAtEnd: + # Skip newlines + while p.check(tkNewLine): + discard p.advance() + if p.check(tkRBrace) or p.isAtEnd: + break methods.add(p.parseFuncDecl(false, false, ParsedAttrs())) discard p.expect(tkRBrace, "expected '}' to close impl block") return Decl(kind: dkImpl, loc: loc, declImplTypeName: typeName, diff --git a/src/sema b/src/sema new file mode 100755 index 0000000..1437252 Binary files /dev/null and b/src/sema differ diff --git a/src/sema.nim b/src/sema.nim index 34518c9..600afda 100644 --- a/src/sema.nim +++ b/src/sema.nim @@ -14,12 +14,22 @@ type SemaResult* = object diagnostics*: seq[SemaDiagnostic] + MethodInfo* = object + name*: string + decl*: Decl + params*: seq[Type] + retType*: Type + Sema* = object module*: Module globalScope*: Scope diagnostics*: seq[SemaDiagnostic] # Built-in type mapping from name to Type typeTable*: Table[string, Type] + # Type name -> list of methods (from extend blocks) + methodTable*: Table[string, seq[MethodInfo]] + # Interface name -> interface decl + interfaceTable*: Table[string, Decl] # --------------------------------------------------------------------------- # Helpers @@ -94,7 +104,7 @@ proc resolveType(sema: var Sema, te: TypeExpr): Type = # First pass: collect global symbols # --------------------------------------------------------------------------- -proc collectGlobals(sema: var Sema) = +proc collectGlobals*(sema: var Sema) = for decl in sema.module.items: case decl.kind of dkFunc: @@ -148,6 +158,42 @@ proc collectGlobals(sema: var Sema) = let name = decl.declUsePath[^1] let sym = Symbol(kind: skModule, name: name, typ: makeUnknown(), isPublic: true) discard sema.globalScope.define(sym) + of dkInterface: + # Register interface for conformance checking + sema.interfaceTable[decl.declInterfaceName] = decl + let t = makeNamed(decl.declInterfaceName) + let sym = Symbol(kind: skType, name: decl.declInterfaceName, typ: t, + decl: decl, isPublic: decl.isPublic) + if not sema.globalScope.define(sym): + sema.emitError(decl.loc, &"duplicate symbol '{decl.declInterfaceName}'") + sema.typeTable[decl.declInterfaceName] = t + of dkImpl: + # Register methods for the type + let typeName = decl.declImplTypeName + if not sema.methodTable.hasKey(typeName): + sema.methodTable[typeName] = @[] + for methodDecl in decl.declImplMethods: + if methodDecl.kind == dkFunc: + var params: seq[Type] = @[] + for p in methodDecl.declFuncParams: + params.add(sema.resolveType(p.ptype)) + let retType = if methodDecl.declFuncReturnType != nil: + sema.resolveType(methodDecl.declFuncReturnType) + else: + makeVoid() + let info = MethodInfo( + name: methodDecl.declFuncName, + decl: methodDecl, + params: params, + retType: retType + ) + sema.methodTable[typeName].add(info) + # Also register as a global function: TypeName_MethodName + let mangledName = typeName & "_" & methodDecl.declFuncName + let sym = Symbol(kind: skFunc, name: mangledName, decl: methodDecl, + isPublic: true) + sym.typ = makeFunc(params, retType) + discard sema.globalScope.define(sym) else: discard @@ -278,6 +324,49 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type = if expr.exprCallCallee == nil: sema.emitError(expr.loc, "internal error: nil callee in call expression") return makeUnknown() + + # Check for method call: obj.method(args) + if expr.exprCallCallee.kind == ekField: + let receiver = sema.checkExpr(expr.exprCallCallee.exprFieldObj, scope) + let methodName = expr.exprCallCallee.exprFieldName + var argTypes = sema.checkExprList(expr.exprCallArgs, scope) + + # Try to find method for receiver type + var typeName = "" + if receiver.kind == tkNamed: + typeName = receiver.name + elif receiver.isPointer and receiver.inner.len > 0 and receiver.inner[0].kind == tkNamed: + typeName = receiver.inner[0].name + + if typeName != "" and sema.methodTable.hasKey(typeName): + for minfo in sema.methodTable[typeName]: + if minfo.name == methodName: + # Found method - check arguments (skip self parameter) + let expectedParams = minfo.params + if argTypes.len + 1 < expectedParams.len: + sema.emitError(expr.loc, &"too few arguments for method '{methodName}'") + elif argTypes.len > expectedParams.len: + sema.emitError(expr.loc, &"too many arguments for method '{methodName}'") + else: + for i in 0 ..< argTypes.len: + let paramIdx = i + 1 # skip self + if paramIdx < expectedParams.len: + if not argTypes[i].isAssignableTo(expectedParams[paramIdx]): + sema.emitError(expr.loc, &"argument {i+1}: expected {expectedParams[paramIdx].toString}, got {argTypes[i].toString}") + return minfo.retType + + # Not a method - treat as function pointer field + let fieldType = sema.checkExpr(expr.exprCallCallee, scope) + if fieldType.kind == tkFunc: + let expectedParams = fieldType.inner[0..^2] + if argTypes.len != expectedParams.len: + sema.emitError(expr.loc, &"expected {expectedParams.len} arguments, got {argTypes.len}") + return fieldType.inner[^1] + else: + sema.emitError(expr.loc, &"cannot call non-function field '{methodName}' on type {receiver.toString}") + return makeUnknown() + + # Regular function call let calleeType = sema.checkExpr(expr.exprCallCallee, scope) var argTypes = sema.checkExprList(expr.exprCallArgs, scope) if calleeType.kind == tkFunc: diff --git a/stdlib/runtime.c b/stdlib/runtime.c new file mode 100644 index 0000000..ee80c3a --- /dev/null +++ b/stdlib/runtime.c @@ -0,0 +1,129 @@ +/* Bux Runtime - Minimal C runtime for Bux programs */ +/* This is linked with every Bux program compiled via the C backend */ + +#include +#include +#include +#include +#include + +/* Memory allocation */ +void* bux_alloc(size_t size) { + void* ptr = malloc(size); + if (ptr == NULL) { + fprintf(stderr, "bux runtime: out of memory (alloc %zu bytes)\n", size); + abort(); + } + return ptr; +} + +void* bux_realloc(void* ptr, size_t size) { + void* new_ptr = realloc(ptr, size); + if (new_ptr == NULL && size > 0) { + fprintf(stderr, "bux runtime: out of memory (realloc %zu bytes)\n", size); + abort(); + } + return new_ptr; +} + +void bux_free(void* ptr) { + free(ptr); +} + +/* I/O */ +void bux_print(const char* s) { + if (s != NULL) { + fputs(s, stdout); + } +} + +void bux_println(const char* s) { + if (s != NULL) { + puts(s); + } else { + puts(""); + } +} + +void bux_print_int(int64_t n) { + printf("%lld", (long long)n); +} + +void bux_print_float(double f) { + printf("%g", f); +} + +void bux_print_bool(bool b) { + printf("%s", b ? "true" : "false"); +} + +void bux_print_char(char c) { + putchar(c); +} + +/* Panic */ +void bux_panic(const char* msg) { + fprintf(stderr, "bux panic: %s\n", msg ? msg : "unknown error"); + abort(); +} + +/* Division by zero check */ +int64_t bux_div_i64(int64_t a, int64_t b) { + if (b == 0) { + bux_panic("division by zero"); + } + return a / b; +} + +int64_t bux_mod_i64(int64_t a, int64_t b) { + if (b == 0) { + bux_panic("modulo by zero"); + } + return a % b; +} + +/* String operations */ +typedef struct { + const char* data; + size_t len; +} BuxString; + +BuxString bux_string_from_cstr(const char* s) { + BuxString result; + result.data = s; + result.len = s ? strlen(s) : 0; + return result; +} + +BuxString bux_string_concat(BuxString a, BuxString b) { + BuxString result; + result.len = a.len + b.len; + char* buf = (char*)bux_alloc(result.len + 1); + if (a.data && a.len > 0) memcpy(buf, a.data, a.len); + if (b.data && b.len > 0) memcpy(buf + a.len, b.data, b.len); + buf[result.len] = '\0'; + result.data = buf; + return result; +} + +/* Slice operations */ +typedef struct { + void* data; + size_t len; + size_t cap; +} BuxSlice; + +BuxSlice bux_slice_new(size_t elem_size, size_t len) { + BuxSlice result; + result.len = len; + result.cap = len; + result.data = bux_alloc(elem_size * len); + return result; +} + +void bux_bounds_check(size_t index, size_t len) { + if (index >= len) { + fprintf(stderr, "bux panic: index out of bounds (index %zu, len %zu)\n", index, len); + abort(); + } +} diff --git a/tests/hir_test b/tests/hir_test new file mode 100755 index 0000000..07470c4 Binary files /dev/null and b/tests/hir_test differ diff --git a/tests/hir_test.nim b/tests/hir_test.nim new file mode 100644 index 0000000..39e6b9c --- /dev/null +++ b/tests/hir_test.nim @@ -0,0 +1,104 @@ +import std/[unittest, strformat] +import ../src/[lexer, parser, sema, hir, hir_lower, types, scope] + +proc lowerSource(source: string): HirModule = + let lexRes = tokenize(source, "") + check not lexRes.hasErrors + let parseRes = parse(lexRes.tokens, "") + check parseRes.diagnostics.len == 0 + # Create Sema and populate global scope + method table + var s = Sema(module: parseRes.module, globalScope: newScope()) + s.collectGlobals() + result = lowerModule(parseRes.module, s) + +suite "HIR Lowering": + test "simple function": + let src = "func Main() -> int { return 0; }" + let hir = lowerSource(src) + check hir.funcs.len == 1 + check hir.funcs[0].name == "Main" + check hir.funcs[0].body != nil + check hir.funcs[0].body.kind == hBlock + echo " PASS: simple function" + + test "function with arithmetic": + let src = "func Add(a: int, b: int) -> int { return a + b; }" + let hir = lowerSource(src) + check hir.funcs.len == 1 + check hir.funcs[0].params.len == 2 + echo " PASS: function with arithmetic" + + test "struct lowering": + let src = """ +struct Point { x: float64; y: float64; } +func Main() -> int { return 0; } +""" + let hir = lowerSource(src) + check hir.structs.len == 1 + check hir.structs[0].name == "Point" + check hir.structs[0].fields.len == 2 + echo " PASS: struct lowering" + + test "method lowering": + let src = """ +struct Point { x: float64; } +extend Point { + func GetX(self: Point) -> float64 { return 0.0; } +} +func Main() -> int { return 0; } +""" + let hir = lowerSource(src) + check hir.funcs.len == 2 # GetX (mangled) + Main + var foundMangled = false + for f in hir.funcs: + if f.name == "Point_GetX": + foundMangled = true + check foundMangled + echo " PASS: method lowering" + + test "if statement": + let src = """ +func Main() -> int { + if true { return 1; } + return 0; +} +""" + let hir = lowerSource(src) + let body = hir.funcs[0].body + check body.kind == hBlock + check body.blockStmts.len >= 1 + echo " PASS: if statement" + + test "while loop": + let src = """ +func Main() -> int { + while true { break; } + return 0; +} +""" + let hir = lowerSource(src) + let body = hir.funcs[0].body + check body.kind == hBlock + echo " PASS: while loop" + + test "let statement": + let src = """ +func Main() -> int { + let x: int = 42; + return x; +} +""" + let hir = lowerSource(src) + check hir.funcs.len == 1 + echo " PASS: let statement" + + test "enum lowering": + let src = """ +enum Color { Red, Green, Blue } +func Main() -> int { return 0; } +""" + let hir = lowerSource(src) + check hir.enums.len == 1 + check hir.enums[0].name == "Color" + check hir.enums[0].variants.len == 3 + echo " PASS: enum lowering" diff --git a/tests/parser_test b/tests/parser_test new file mode 100755 index 0000000..ac6eb84 Binary files /dev/null and b/tests/parser_test differ diff --git a/tests/sema_test b/tests/sema_test new file mode 100755 index 0000000..4f4c8d6 Binary files /dev/null and b/tests/sema_test differ diff --git a/tests/sema_test.nim b/tests/sema_test.nim index a9701d7..e1a1231 100644 --- a/tests/sema_test.nim +++ b/tests/sema_test.nim @@ -83,3 +83,57 @@ suite "Sema": test "slice element type mismatch": let res = checkSource("func Main() -> int { let arr = [1, c8\"a\"]; return 0; }") check res.hasErrors + + test "method call with extend": + let src = """ +struct Point { x: float64; y: float64; } +extend Point { + func Distance(self: Point) -> float64 { return 0.0; } +} +func Main() -> int { + let p = Point { x: 1.0, y: 2.0 }; + let d = p.Distance(); + return 0; +} +""" + let res = checkSource(src) + check not res.hasErrors + + test "method call with wrong arguments": + let src = """ +struct Point { x: float64; y: float64; } +extend Point { + func Add(self: Point, other: Point) -> Point { return self; } +} +func Main() -> int { + let p = Point { x: 1.0, y: 2.0 }; + let q = p.Add(); + return 0; +} +""" + let res = checkSource(src) + check res.hasErrors + check "too few arguments" in res.diagnostics[0].message + + test "interface declaration": + let src = """ +interface Display { + func ToString(self: Self) -> String; +} +""" + let res = checkSource(src) + check not res.hasErrors + + test "extend for interface": + let src = """ +struct Point { x: float64; y: float64; } +interface Display { + func ToString(self: Self) -> String; +} +extend Point for Display { + func ToString(self: Point) -> String { return c8"Point"; } +} +func Main() -> int { return 0; } +""" + let res = checkSource(src) + check not res.hasErrors