feat: add HIR, C backend, and end-to-end compilation
- Phase 3: High-Level IR (HIR) with lowering from AST - Method call desugaring (obj.method() → Type_method(obj)) - if/else, while, loop, break, continue lowering - struct, enum, function lowering - 8 HIR tests passing - Phase 5A: C backend code generation - Type mapping (Bux types → C11 types) - Expression and statement emission - Struct, enum, function generation - C main() wrapper for Bux Main() - Runtime shim (stdlib/runtime.c) - bux_alloc, bux_free, bux_print, bux_panic - BuxString, BuxSlice types - Bounds checking, division by zero - Build integration - bux build: lex → parse → sema → HIR → C → cc - bux run: build + execute - bux clean: remove build directory - Parser fixes - Newline handling in struct, enum, extend, interface blocks - self keyword as expression and parameter name - Sema improvements - Method resolution (extend blocks) - Interface conformance checking - collectGlobals made public - All 70 tests passing (25 lexer + 16 parser + 21 sema + 8 HIR) - End-to-end: Bux programs compile to native ELF64 binaries
This commit is contained in:
@@ -8,59 +8,80 @@
|
|||||||
|
|
||||||
## Overview
|
## 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<T,E>` + `?` propagation | `Result<T,E>` + `?` | 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.
|
**Goal:** Working Nim project that can lex, parse, and dump a Bux AST.
|
||||||
|
|
||||||
| Task | Details |
|
| Task | Status | Details |
|
||||||
|------|---------|
|
|------|--------|---------|
|
||||||
| `0.1` Project skeleton | `buxc` CLI in Nim, `bux.toml` manifest parser |
|
| `0.1` Project skeleton | ✅ | `buxc` CLI in Nim, `bux.toml` manifest parser |
|
||||||
| `0.2` Token model | All Rux tokens (`TokenKind`, `SourceLocation`, literal suffixes) |
|
| `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.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.4` CLI commands | ✅ | `bux new`, `bux init`, `bux build`, `bux run`, `bux check` |
|
||||||
| `0.5` Test harness | Golden-file tests for lexer output (`.tokens`) |
|
| `0.5` Test harness | ✅ | Golden-file tests for lexer output (`.tokens`) |
|
||||||
|
|
||||||
**Deliverable:** `echo 'let x = 42' | bux check` prints token stream.
|
**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.
|
**Goal:** Parse every construct present in Rux v0.2.0 into a Nim AST.
|
||||||
|
|
||||||
| Task | Details |
|
| Task | Status | Details |
|
||||||
|------|---------|
|
|------|--------|---------|
|
||||||
| `1.1` AST nodes | All `Expr`, `Stmt`, `Decl`, `Pattern`, `TypeExpr`, `Block` variants (see `_rux/Include/Rux/Ast.h`) |
|
| `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.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.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.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.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.6` Patterns | ✅ | Wildcard `_`, literal, ident, range, enum destructuring, struct destructuring, tuple, guarded `if` |
|
||||||
| `1.7` Attributes | `@[Import(lib: "...")]`, calling-convention, platform-conditional imports |
|
| `1.7` Attributes | ✅ | `@[Import(lib: "...")]`, calling-convention, platform-conditional imports |
|
||||||
| `1.8` Error recovery | Synchronize on declaration/statement boundaries; emit multiple diagnostics |
|
| `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.
|
**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.
|
**Goal:** Type-check the AST and produce a typed symbol table.
|
||||||
|
|
||||||
| Task | Details |
|
| Task | Status | Details |
|
||||||
|------|---------|
|
|------|--------|---------|
|
||||||
| `2.1` Type model | `TypeRef` with primitives, pointers, slices, tuples, named types, type parameters, functions (see `_rux/Include/Rux/Type.h`) |
|
| `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.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.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.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.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.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.7` Generics (basic) | ⏳ | Monomorphization of generic structs and functions at call sites |
|
||||||
| `2.8` Diagnostics | Multi-file error messages with source locations |
|
| `2.8` Diagnostics | ✅ | Multi-file error messages with source locations |
|
||||||
|
| `2.9` **Algebraic enums** | ⏳ | Enums with data (like Rust's `enum Result<T,E> { 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.
|
**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.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.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.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.
|
**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::Os` | `Args`, `Env`, `Exit`, `Cwd` |
|
||||||
| `Std::Path` | Path joining, extension splitting |
|
| `Std::Path` | Path joining, extension splitting |
|
||||||
| `Std::Process` | Spawn subprocess, read stdout/stderr |
|
| `Std::Process` | Spawn subprocess, read stdout/stderr |
|
||||||
|
| **`Std::Result`** | `Result<T, E>` + `Option<T>` 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.
|
**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 |
|
| Task | Details |
|
||||||
|------|---------|
|
|------|---------|
|
||||||
| `8.1` Package manager | `bux add`, `bux remove`, `bux update`, `bux install` with lockfile |
|
| `8.1.1` Result type | `Result<T, E>` with `Ok(T)` and `Err(E)` constructors |
|
||||||
| `8.2` Registry protocol | Simple HTTP git-based registry (like Go modules or Cargo) |
|
| `8.1.2` Option type | `Option<T>` with `Some(T)` and `None` constructors |
|
||||||
| `8.3` Formatter | `bux fmt` — auto-format Bux source |
|
| `8.1.3` `?` operator | `expr?` desugars to: if `Err`/`None`, early-return from function |
|
||||||
| `8.4` LSP | Language Server Protocol for autocomplete, hover, go-to-definition |
|
| `8.1.4` `!` suffix | `expr!` unwraps or panics (for prototyping) |
|
||||||
| `8.5` Tests | `bux test` runner with assertions and golden tests |
|
|
||||||
| `8.6` Documentation | `bux doc` — generate HTML from `///` doc comments |
|
```bux
|
||||||
| `8.7` Cross-compilation | `--target` flag leveraging C backend portability |
|
func ReadFile(path: String) -> Result<String, IoError> {
|
||||||
|
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<T>` 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<T>` type with memory ordering |
|
||||||
|
|
||||||
|
```bux
|
||||||
|
import Std::Task;
|
||||||
|
import Std::Channel;
|
||||||
|
|
||||||
|
func Producer(ch: Channel<int>) {
|
||||||
|
for i in 0..100 {
|
||||||
|
ch.Send(i);
|
||||||
|
}
|
||||||
|
ch.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
let (tx, rx) = Channel::New<int>();
|
||||||
|
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<T: Comparable>(arr: &mut Array<T>)` |
|
||||||
|
| `8.5.4` Trait objects | `&dyn Trait` for dynamic dispatch (fat pointer) |
|
||||||
|
| `8.5.5` Blanket impls | `impl<T: Display> 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
|
│ │ ├── Math.bux
|
||||||
│ │ ├── Os.bux
|
│ │ ├── Os.bux
|
||||||
│ │ ├── Path.bux
|
│ │ ├── Path.bux
|
||||||
│ │ └── Process.bux
|
│ │ ├── Process.bux
|
||||||
|
│ │ ├── Result.bux # Result<T,E> and Option<T>
|
||||||
|
│ │ ├── 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
|
│ └── Runtime.c # C runtime shim
|
||||||
├── tests/
|
├── tests/
|
||||||
│ ├── Lexer/
|
│ ├── Lexer/
|
||||||
@@ -222,23 +373,123 @@ bux/
|
|||||||
│ ├── Codegen/
|
│ ├── Codegen/
|
||||||
│ └── Integration/
|
│ └── Integration/
|
||||||
└── docs/
|
└── docs/
|
||||||
└── LanguageRef.md
|
├── LanguageRef.md
|
||||||
|
├── Ownership.md
|
||||||
|
└── Concurrency.md
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Language Design Decisions (Bux vs Rux)
|
## Language Design Decisions (Bux Improvements)
|
||||||
|
|
||||||
| Feature | Bux Decision | Rationale |
|
### What Bux inherits from Rux
|
||||||
|---------|-------------|-----------|
|
| Feature | Rux | Bux |
|
||||||
| **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 |
|
| Syntax | C-like with modern touches | Same base, extended |
|
||||||
| **Memory safety** | Raw pointers + optional borrow checker (Phase 9) | Match Rux current model; gradual safety |
|
| Module system | `import Std::Io::PrintLine` | Same path syntax |
|
||||||
| **Generics** | Monomorphization only | Simpler than Rust-style trait objects; enough for self-hosting |
|
| String literals | `c8""`, `c16""`, `c32""` + `""` | Same |
|
||||||
| **Error handling** | Explicit `Result<T, E>` + `?` operator (later) | Start with C-style returns; add sugar after self-hosting |
|
| Build manifest | `Rux.toml` | `bux.toml` (compatible format) |
|
||||||
| **String literals** | `c8""`, `c16""`, `c32""` + `""` defaulting to `c8` | Same as Rux |
|
| Backend philosophy | Self-contained (no LLVM required) | C transpiler first → native + optional LLVM |
|
||||||
| **Build system** | `bux.toml` (same as `Rux.toml`) | Compatible manifest format |
|
|
||||||
| **Module system** | `import Std::Io::PrintLine` | Same as Rux path syntax |
|
### 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<T,E>` + `?` 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<T> {
|
||||||
|
items: Array<T>,
|
||||||
|
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<T> for Display {
|
||||||
|
func ToString(self: &Stack<T>) -> String {
|
||||||
|
return Format("Stack(len={})", self.len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function with Result return type and ? operator
|
||||||
|
func Divide(a: int, b: int) -> Result<int, String> {
|
||||||
|
if b == 0 {
|
||||||
|
return Err("division by zero");
|
||||||
|
}
|
||||||
|
return Ok(a / b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Async function
|
||||||
|
async func FetchData(url: String) -> Result<String, IoError> {
|
||||||
|
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 |
|
| Milestone | Phase | Success Criteria |
|
||||||
|-----------|-------|------------------|
|
|-----------|-------|------------------|
|
||||||
| **M0** | 0 | `bux check` lexes source |
|
| **M0** | 0 ✅ | `bux check` lexes source |
|
||||||
| **M1** | 1 | All Rux test files parse |
|
| **M1** | 1 ✅ | All Rux test files parse |
|
||||||
| **M2** | 2 | Type-checker rejects invalid programs |
|
| **M2** | 2 🔄 | Type-checker rejects invalid programs |
|
||||||
| **M3** | 3+4 | LIR emits for all constructs |
|
| **M3** | 3+4 | LIR emits for all constructs |
|
||||||
| **M4** | 5A | `bux run` produces working binary via C |
|
| **M4** | 5A | `bux run` produces working binary via C |
|
||||||
| **M5** | 6 | Can write compiler-adjacent tools in Bux |
|
| **M5** | 6 | Can write compiler-adjacent tools in Bux |
|
||||||
| **M6** | 7 | **Self-hosted**: Bux compiler builds itself |
|
| **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 |
|
| 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 |
|
| C backend limits performance | Maintain parallel native backend; C is only bootstrap |
|
||||||
| Generics get complex | Restrict to monomorphization; no higher-kinded types |
|
| Generics get complex | Restrict to monomorphization; no higher-kinded types initially |
|
||||||
| Self-hosting too hard | Ensure stdlib has `Array`, `Map`, `String` before starting rewrite |
|
| 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<T>` = `Some(T)` \| `None` | ⏳ Phase 6 |
|
||||||
|
| **Result** | `Result<T, E>` = `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<T>(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<T>(...)` | ⏳ 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<T>` | ❌ 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 <pkg> # Add dependency
|
||||||
|
bux lsp # Start language server
|
||||||
|
```
|
||||||
|
|||||||
@@ -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..<be.indent:
|
||||||
|
be.output.add(" ")
|
||||||
|
be.output.add(s)
|
||||||
|
be.output.add("\n")
|
||||||
|
|
||||||
|
proc emitIndent(be: var CBackend) =
|
||||||
|
for i in 0..<be.indent:
|
||||||
|
be.output.add(" ")
|
||||||
|
|
||||||
|
proc freshVar(be: var CBackend): string =
|
||||||
|
inc be.varCounter
|
||||||
|
result = &"__tmp_{be.varCounter}"
|
||||||
|
|
||||||
|
# Type conversion: Bux Type → C type string
|
||||||
|
proc typeToC*(typ: Type): string =
|
||||||
|
if typ == nil: return "void"
|
||||||
|
case typ.kind
|
||||||
|
of tkVoid: return "void"
|
||||||
|
of tkBool: return "bool"
|
||||||
|
of tkBool8: return "bool"
|
||||||
|
of tkBool16: return "bool"
|
||||||
|
of tkBool32: return "bool"
|
||||||
|
of tkChar8: return "char"
|
||||||
|
of tkChar16: return "char16_t"
|
||||||
|
of tkChar32: return "char32_t"
|
||||||
|
of tkStr: return "const char*"
|
||||||
|
of tkInt8: return "int8_t"
|
||||||
|
of tkInt16: return "int16_t"
|
||||||
|
of tkInt32: return "int32_t"
|
||||||
|
of tkInt64: return "int64_t"
|
||||||
|
of tkInt: return "int"
|
||||||
|
of tkUInt8: return "uint8_t"
|
||||||
|
of tkUInt16: return "uint16_t"
|
||||||
|
of tkUInt32: return "uint32_t"
|
||||||
|
of tkUInt64: return "uint64_t"
|
||||||
|
of tkUInt: return "unsigned int"
|
||||||
|
of tkFloat32: return "float"
|
||||||
|
of tkFloat64: return "double"
|
||||||
|
of tkPointer:
|
||||||
|
if typ.inner.len > 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 <stdio.h>")
|
||||||
|
be.emitLine("#include <stdlib.h>")
|
||||||
|
be.emitLine("#include <stdint.h>")
|
||||||
|
be.emitLine("#include <stdbool.h>")
|
||||||
|
be.emitLine("#include <string.h>")
|
||||||
|
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
|
||||||
+112
-10
@@ -1,5 +1,5 @@
|
|||||||
import std/[os, strutils, terminal, strformat]
|
import std/[os, strutils, terminal, strformat, osproc]
|
||||||
import lexer, parser, sema, manifest
|
import lexer, parser, sema, manifest, hir, hir_lower, c_backend, types, scope
|
||||||
|
|
||||||
type
|
type
|
||||||
ColorMode* = enum
|
ColorMode* = enum
|
||||||
@@ -192,12 +192,104 @@ proc cmdCheck*(args: seq[string], opts: GlobalOptions): int =
|
|||||||
|
|
||||||
proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
|
proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
|
||||||
let useColor = shouldUseColor(opts)
|
let useColor = shouldUseColor(opts)
|
||||||
# For now, just type-check
|
let root = getCurrentDir()
|
||||||
let checkRes = cmdCheck(args, opts)
|
let manifestPath = root / "bux.toml"
|
||||||
if checkRes != 0:
|
if not fileExists(manifestPath):
|
||||||
return checkRes
|
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:
|
if not opts.quiet:
|
||||||
printInfo("build: nothing to do yet (no codegen)", useColor)
|
printInfo(&"build: {outputFile}", useColor)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
proc cmdRun*(args: seq[string], opts: GlobalOptions): int =
|
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)
|
let buildRes = cmdBuild(args, opts)
|
||||||
if buildRes != 0:
|
if buildRes != 0:
|
||||||
return buildRes
|
return buildRes
|
||||||
printError("run: no executable produced yet (no codegen)", useColor)
|
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
|
return 1
|
||||||
|
let exitCode = execCmd(outputFile)
|
||||||
|
return exitCode
|
||||||
|
|
||||||
proc cmdClean*(args: seq[string], opts: GlobalOptions): int =
|
proc cmdClean*(args: seq[string], opts: GlobalOptions): int =
|
||||||
let useColor = shouldUseColor(opts)
|
let useColor = shouldUseColor(opts)
|
||||||
# Placeholder
|
let root = getCurrentDir()
|
||||||
|
let buildDir = root / "build"
|
||||||
|
if dirExists(buildDir):
|
||||||
|
removeDir(buildDir)
|
||||||
if not opts.quiet:
|
if not opts.quiet:
|
||||||
printInfo("clean: nothing to do yet", useColor)
|
printInfo("clean: build directory removed", useColor)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
proc cmdVersion*(args: seq[string], opts: GlobalOptions): int =
|
proc cmdVersion*(args: seq[string], opts: GlobalOptions): int =
|
||||||
|
|||||||
+165
@@ -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)
|
||||||
@@ -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)
|
||||||
Executable
BIN
Binary file not shown.
+39
-4
@@ -323,10 +323,11 @@ proc parsePrimary(p: var Parser): Expr =
|
|||||||
case p.peek()
|
case p.peek()
|
||||||
of tkIntLiteral, tkFloatLiteral, tkStringLiteral, tkCharLiteral, tkBoolLiteral:
|
of tkIntLiteral, tkFloatLiteral, tkStringLiteral, tkCharLiteral, tkBoolLiteral:
|
||||||
return newLiteralExpr(p.advance())
|
return newLiteralExpr(p.advance())
|
||||||
|
of tkSelf:
|
||||||
|
discard p.advance()
|
||||||
|
return Expr(kind: ekSelf, loc: loc)
|
||||||
of tkIdent:
|
of tkIdent:
|
||||||
let name = p.advance().text
|
let name = p.advance().text
|
||||||
if name == "self":
|
|
||||||
return Expr(kind: ekSelf, loc: loc)
|
|
||||||
# Path expression: a::b::c
|
# Path expression: a::b::c
|
||||||
if p.check(tkColonColon):
|
if p.check(tkColonColon):
|
||||||
var segs = @[name]
|
var segs = @[name]
|
||||||
@@ -781,11 +782,25 @@ proc parseParamList(p: var Parser, allowVariadic: bool = false): seq[Param] =
|
|||||||
var isVar = false
|
var isVar = false
|
||||||
if allowVariadic and p.check(tkDotDotDot):
|
if allowVariadic and p.check(tkDotDotDot):
|
||||||
discard p.advance()
|
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()
|
let ty = p.parseType()
|
||||||
result.add(Param(loc: loc, name: name, ptype: ty, isVariadic: true))
|
result.add(Param(loc: loc, name: name, ptype: ty, isVariadic: true))
|
||||||
else:
|
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")
|
discard p.expect(tkColon, "expected ':' after parameter name")
|
||||||
let ty = p.parseType()
|
let ty = p.parseType()
|
||||||
var defaultVal: Expr = nil
|
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")
|
discard p.expect(tkLBrace, "expected '{' to start struct body")
|
||||||
var fields: seq[StructField] = @[]
|
var fields: seq[StructField] = @[]
|
||||||
while not p.check(tkRBrace) and not p.isAtEnd:
|
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
|
let fLoc = p.currentLoc
|
||||||
var fPub = false
|
var fPub = false
|
||||||
if p.check(tkPub):
|
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")
|
discard p.expect(tkLBrace, "expected '{' to start enum body")
|
||||||
var variants: seq[EnumVariant] = @[]
|
var variants: seq[EnumVariant] = @[]
|
||||||
while not p.check(tkRBrace) and not p.isAtEnd:
|
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 vLoc = p.currentLoc
|
||||||
let vName = p.expect(tkIdent, "expected variant name").text
|
let vName = p.expect(tkIdent, "expected variant name").text
|
||||||
var fields: seq[TypeExpr] = @[]
|
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")
|
discard p.expect(tkLBrace, "expected '{' to start interface body")
|
||||||
var methods: seq[Decl] = @[]
|
var methods: seq[Decl] = @[]
|
||||||
while not p.check(tkRBrace) and not p.isAtEnd:
|
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()))
|
methods.add(p.parseFuncDecl(false, false, ParsedAttrs()))
|
||||||
discard p.expect(tkRBrace, "expected '}' to close interface")
|
discard p.expect(tkRBrace, "expected '}' to close interface")
|
||||||
return Decl(kind: dkInterface, loc: loc, isPublic: isPublic,
|
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")
|
discard p.expect(tkLBrace, "expected '{' to start impl block")
|
||||||
var methods: seq[Decl] = @[]
|
var methods: seq[Decl] = @[]
|
||||||
while not p.check(tkRBrace) and not p.isAtEnd:
|
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()))
|
methods.add(p.parseFuncDecl(false, false, ParsedAttrs()))
|
||||||
discard p.expect(tkRBrace, "expected '}' to close impl block")
|
discard p.expect(tkRBrace, "expected '}' to close impl block")
|
||||||
return Decl(kind: dkImpl, loc: loc, declImplTypeName: typeName,
|
return Decl(kind: dkImpl, loc: loc, declImplTypeName: typeName,
|
||||||
|
|||||||
+90
-1
@@ -14,12 +14,22 @@ type
|
|||||||
SemaResult* = object
|
SemaResult* = object
|
||||||
diagnostics*: seq[SemaDiagnostic]
|
diagnostics*: seq[SemaDiagnostic]
|
||||||
|
|
||||||
|
MethodInfo* = object
|
||||||
|
name*: string
|
||||||
|
decl*: Decl
|
||||||
|
params*: seq[Type]
|
||||||
|
retType*: Type
|
||||||
|
|
||||||
Sema* = object
|
Sema* = object
|
||||||
module*: Module
|
module*: Module
|
||||||
globalScope*: Scope
|
globalScope*: Scope
|
||||||
diagnostics*: seq[SemaDiagnostic]
|
diagnostics*: seq[SemaDiagnostic]
|
||||||
# Built-in type mapping from name to Type
|
# Built-in type mapping from name to Type
|
||||||
typeTable*: Table[string, 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
|
# Helpers
|
||||||
@@ -94,7 +104,7 @@ proc resolveType(sema: var Sema, te: TypeExpr): Type =
|
|||||||
# First pass: collect global symbols
|
# First pass: collect global symbols
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
proc collectGlobals(sema: var Sema) =
|
proc collectGlobals*(sema: var Sema) =
|
||||||
for decl in sema.module.items:
|
for decl in sema.module.items:
|
||||||
case decl.kind
|
case decl.kind
|
||||||
of dkFunc:
|
of dkFunc:
|
||||||
@@ -148,6 +158,42 @@ proc collectGlobals(sema: var Sema) =
|
|||||||
let name = decl.declUsePath[^1]
|
let name = decl.declUsePath[^1]
|
||||||
let sym = Symbol(kind: skModule, name: name, typ: makeUnknown(), isPublic: true)
|
let sym = Symbol(kind: skModule, name: name, typ: makeUnknown(), isPublic: true)
|
||||||
discard sema.globalScope.define(sym)
|
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:
|
else:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
@@ -278,6 +324,49 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
|||||||
if expr.exprCallCallee == nil:
|
if expr.exprCallCallee == nil:
|
||||||
sema.emitError(expr.loc, "internal error: nil callee in call expression")
|
sema.emitError(expr.loc, "internal error: nil callee in call expression")
|
||||||
return makeUnknown()
|
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)
|
let calleeType = sema.checkExpr(expr.exprCallCallee, scope)
|
||||||
var argTypes = sema.checkExprList(expr.exprCallArgs, scope)
|
var argTypes = sema.checkExprList(expr.exprCallArgs, scope)
|
||||||
if calleeType.kind == tkFunc:
|
if calleeType.kind == tkFunc:
|
||||||
|
|||||||
@@ -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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
BIN
Binary file not shown.
@@ -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, "<test>")
|
||||||
|
check not lexRes.hasErrors
|
||||||
|
let parseRes = parse(lexRes.tokens, "<test>")
|
||||||
|
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"
|
||||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -83,3 +83,57 @@ suite "Sema":
|
|||||||
test "slice element type mismatch":
|
test "slice element type mismatch":
|
||||||
let res = checkSource("func Main() -> int { let arr = [1, c8\"a\"]; return 0; }")
|
let res = checkSource("func Main() -> int { let arr = [1, c8\"a\"]; return 0; }")
|
||||||
check res.hasErrors
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user