Compare commits

..

10 Commits

Author SHA1 Message Date
dimgigov 0bdef58182 selfhost: fix pointer field access, algebraic enums, string literal escaping
- hir_lower: use per-function scope instead of global scope for params
  fixes p->error emission for pointer parameters
- parser: assign variant4..variant7 in enum decls (was only 0..3)
- c_backend: escape quotes, backslash, newline, tab, cr in string literals
- runtime: bux_write_file now writes raw bytes (was un-escaping sequences)
- c_backend: emit algebraic enums as tagged unions (tag enum + data union + struct)
- hir_lower: add hBreak/hContinue lowering
- sema: skip empty variant names when collecting enum globals
2026-06-06 23:54:09 +03:00
dimgigov 3ae8c60ebb fix(bootstrap): resolve lir lower crashes and struct-copy bugs
- Fix lirValToC crash on lvkInt in hStore raw C emission
- Add hAlloca handling to lowerExpr (returns &name, avoids unhandled fallback)
- Fix nested comment syntax in unhandled expr fallback
- Rewrite buildLval to handle hIndexPtr and nested hFieldPtr/hArrowField
  so assignments like arr[i].field = val emit direct C lvalues
- Make &&/|| temporaries unique (__and_tmp_N/__or_tmp_N) to avoid
  duplicate declarations in the same function
- Selfhost null-deref fix in sema.bux (unchained nested conditions)
2026-06-06 21:46:25 +03:00
dimgigov 9416c486b2 docs: update PLAN.md — Phase 11 roadmap, 26 examples, M10 completed 2026-06-06 19:53:24 +03:00
dimgigov ba0a55f467 docs: update README — 26 examples passing, selfhost working 2026-06-06 19:49:52 +03:00
dimgigov 797ca7c80c fix(lir): resolve all LIR backend regressions — 26/26 examples + selfhost passing
- hir_lower: add isScope=true to lowerBlock; fix resolveExprType for ekIndex,
  ekField on monomorphized structs, and ekTry/ekUnwrap tmpVar typing
- lir_lower: add loop label stack for break/continue; pass null arg to
  bux_task_spawn when spawn has no arguments
- lir_c_backend: multi-pass type inference for temps; include params in
  varTypes; handle lvkTemp in lirLoad; use inferred type in lirAlloca
2026-06-06 19:49:24 +03:00
dimgigov 4f7653a410 docs: update project status — buxc2 is now a working compiler, not a PoC 2026-06-06 19:02:54 +03:00
dimgigov 62a9c8b84a feat: LIR backend replaces direct HIR→C emission (v0.3.0)
- 42 LIR instructions in bootstrap/lir.nim

- HIR→LIR lowering in bootstrap/lir_lower.nim

- LIR→C emission with full struct/enum/vtable support

- All 22 examples + 5 Nim unit tests passing

- Updated README status and backend description
2026-06-06 18:55:59 +03:00
dimgigov aff03ffb5a fix(selfhost): resolve all sema errors — 0 undeclared identifiers
- Add forward declarations for mutually-recursive parser functions
  (parserParseExpr, parserParseStmt, parserParseBlock,
   parserParsePrimary, parserParsePostfixExpr, parserParseBinaryPrec)
- Fix HIR lowering: skip forward decls (refBody==null) to avoid
  duplicate function entries and array overflow
- Bump funcs/externFuncs array size 256→512 to handle large modules
- Update PLAN.md with progress

Sema: 0 errors (was 7, then 2, now 0)
Remaining: C backend struct ordering (pre-existing, Iter/StringBuilder)
2026-06-06 05:34:19 +03:00
dimgigov a8b909726b v0.4.0: selfhost loop progress
- Fix stdlib merge: merge ALL lib/*.bux via bux_list_dir (was 0)
- Fix Cli_FindStdlibDir: search for lib/ not stdlib/
- Fix stdlib path: remove Std/ subdir (now flat in lib/)
- Fix runtime paths: add ../../ fallback for build/selfhost depth
- Remove hardcoded /home/ziko paths from src/cli.bux
- Fix Json.bux: avoid var-in-if-block scoping bug (5 errors)
- Add improved sema error messages (reverted due to crash)
- Remaining: 2 parserParsePostfix sema forward-ref errors
- Update PLAN.md with v0.3.0-v1.0.0 roadmap
2026-06-06 05:15:04 +03:00
dimgigov 2698ca92b7 fix(selfhost): stdlib path + merge all modules, not just imports
- Fix Cli_FindStdlibDir: search for lib/ instead of stdlib/
- Fix Cli_CollectStdlibImports → merge ALL lib/*.bux via bux_list_dir
- Fix Cli_CollectStdlibImports path: remove Std/ subdir (now flat)
- Add ../../ fallback for rt/ paths (build/selfhost depth)
- Update PLAN.md with v0.3.0 structure + v1.0.0 roadmap
- Remaining: 7 sema cross-module resolution bugs (pre-existing)
2026-06-06 05:02:48 +03:00
43 changed files with 2565 additions and 6981 deletions
+1
View File
@@ -1,5 +1,6 @@
# Compiled binary # Compiled binary
buxc buxc
buxc_lir
src/main src/main
# Nim cache # Nim cache
+15 -14
View File
@@ -1,5 +1,5 @@
NIM := nim NIM := nim
SRC := compiler/bootstrap/main.nim SRC := bootstrap/main.nim
OUT := buxc OUT := buxc
BUILD_DIR := build BUILD_DIR := build
@@ -11,7 +11,7 @@ all: build
build: build:
$(NIM) c -o:$(OUT) -d:release --opt:size $(SRC) $(NIM) c -o:$(OUT) -d:release --opt:size $(SRC)
strip $(OUT) # strip $(OUT)
dev: dev:
$(NIM) c -o:buxc_debug -d:debug --stackTrace:on --lineTrace:on $(SRC) $(NIM) c -o:buxc_debug -d:debug --stackTrace:on --lineTrace:on $(SRC)
@@ -21,15 +21,15 @@ debug: dev
test: build test-examples test: build test-examples
@echo "Running lexer tests..." @echo "Running lexer tests..."
$(NIM) c -r compiler/tests/lexer_test.nim $(NIM) c -r tests/lexer_test.nim
@echo "Running parser tests..." @echo "Running parser tests..."
$(NIM) c -r compiler/tests/parser_test.nim $(NIM) c -r tests/parser_test.nim
@echo "Running sema tests..." @echo "Running sema tests..."
$(NIM) c -r compiler/tests/sema_test.nim $(NIM) c -r tests/sema_test.nim
@echo "Running HIR tests..." @echo "Running HIR tests..."
$(NIM) c -r compiler/tests/hir_test.nim $(NIM) c -r tests/hir_test.nim
@echo "Running borrow checker tests..." @echo "Running borrow checker tests..."
$(NIM) c -r compiler/tests/borrow_test.nim $(NIM) c -r tests/borrow_test.nim
@echo "Running integration tests..." @echo "Running integration tests..."
rm -rf _test_tmp_pkg rm -rf _test_tmp_pkg
./$(OUT) new _test_tmp_pkg ./$(OUT) new _test_tmp_pkg
@@ -62,14 +62,15 @@ clean:
rm -rf _test_cast _test_cast2 _test_cast3 _test_channel rm -rf _test_cast _test_cast2 _test_cast3 _test_channel
clean-all: clean clean-all: clean
rm -rf _selfhost/src _selfhost/build rm -rf build/selfhost
selfhost: build selfhost: build
@echo "=== Building self-hosted compiler ===" @echo "=== Building self-hosted compiler ==="
@rm -rf _selfhost/src _selfhost/build @rm -rf build/selfhost
@mkdir -p _selfhost/src @mkdir -p build/selfhost/src
@cp compiler/selfhost/*.bux _selfhost/src/ @cp src/*.bux build/selfhost/src/
@mv _selfhost/src/main.bux _selfhost/src/Main.bux 2>/dev/null || true @cp src/bux.toml build/selfhost/
@cd _selfhost && ../$(OUT) build @mv build/selfhost/src/main.bux build/selfhost/src/Main.bux 2>/dev/null || true
@strip _selfhost/build/buxc2 @cd build/selfhost && ../../$(OUT) build
# strip removed for debug
@echo "=== Self-hosted compiler built successfully ===" @echo "=== Self-hosted compiler built successfully ==="
+243 -62
View File
@@ -1,16 +1,20 @@
# Bux Programming Language — Roadmap to Self-Hosting # Bux Programming Language — Roadmap to v1.0.0
> **Bootstrap Implementation:** Nim > **Version:** 0.3.1 (2026-06-06)
> **Target:** Bux compiler written in Bux (self-hosting) > **Bootstrap:** Nim (`bootstrap/`) — compiles `src/` → `buxc`
> **Self-host:** Bux (`src/`) — compiles via `buxc` → `buxc2`
> **Target:** Bux v1.0.0 — fully self-hosting, gradual ownership, tooling
--- ---
## Overview ## Overview
Bux is a fast, compiled, strongly-typed, multi-paradigm systems programming language. 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. The strategy is **bootstrap via Nim** — we built the first Bux compiler in Nim, then rewrote it in Bux. The Nim bootstrap now exists only as a build scaffold.
**Core philosophy:** Systems-level control with modern ergonomics. No hidden costs, no hidden allocations, no hidden control flow. **Core philosophy:** Systems-level control with modern ergonomics. No hidden costs, no hidden allocations, no hidden control flow.
**Killer feature:** Gradual ownership — write fast like C, add safety like Rust, but only where you choose (`@[Checked]`).
--- ---
## Language Design Goals (Bux vs Rust vs Nim vs Zig) ## Language Design Goals (Bux vs Rust vs Nim vs Zig)
@@ -286,11 +290,11 @@ Phase 7.5 — Driver (depends on all):
--- ---
## Phase 7 — Self-Hosting: The Great Rewrite 🔄 (In Progress) ## Phase 7 — Self-Hosting: The Great Rewrite (Complete)
**Goal:** Bux compiler compiles itself. This is the **main milestone**. **Goal:** Bux compiler compiles itself. This is the **main milestone**.
**All 14 modules ported** in `compiler/selfhost/` (4094 LOC total). Self-hosted project structure in `_selfhost/`. **All 14 modules ported** in `src/` (4094 LOC total). Built via `make selfhost`.
| Task | Status | Details | LOC | | Task | Status | Details | LOC |
|------|--------|---------|-----| |------|--------|---------|-----|
@@ -342,7 +346,7 @@ Phase 7.5 — Driver (depends on all):
**Self-hosted compiler stats:** **Self-hosted compiler stats:**
``` ```
$ _selfhost/build/buxc2 version $ src/build/buxc2 version
Bux 0.2.0 (self-hosting bootstrap) Bux 0.2.0 (self-hosting bootstrap)
Pipeline modules: Pipeline modules:
Lexer ✅ 695 lines Lexer ✅ 695 lines
@@ -355,8 +359,8 @@ Pipeline modules:
**Bootstrap loop goal:** **Bootstrap loop goal:**
``` ```
buxc (Nim) → compile compiler/selfhost/*.bux → buxc2 (Bux binary) buxc (Nim) → compile src/*.bux → buxc2 (Bux binary)
buxc2 (Bux) → compile compiler/selfhost/*.bux → buxc3 (Bux binary) buxc2 (Bux) → compile src/*.bux → buxc3 (Bux binary)
compare buxc2 == buxc3 → SELF-HOSTED ✅ compare buxc2 == buxc3 → SELF-HOSTED ✅
``` ```
@@ -534,60 +538,187 @@ const TABLE_SIZE = Factorial(10); // Computed at compile time
--- ---
## File Structure (Target) ## File Structure (v0.3.0 — Current)
``` ```
bux/ bux/
├── bux.toml # Compiler package manifest ├── bux.toml # Bootstrap compiler manifest (v0.3.0)
├── README.md ├── README.md
├── PLAN.md ├── PLAN.md # This file
├── Makefile # build, test, selfhost ├── Makefile # build, test, selfhost
├── src/ ├── src/ # 🎯 CANONICAL: Bux compiler source
│ ├── Main.bux # CLI entry point │ ├── bux.toml # Self-host compiler manifest
│ ├── Lexer.bux │ ├── main.bux # Entry point (renamed → Main.bux at build)
│ ├── Parser.bux │ ├── lexer.bux # Tokenizer (UTF-8 state machine, 697 LOC)
│ ├── Ast.bux │ ├── parser.bux # Pratt parser (1004 LOC)
│ ├── Sema.bux │ ├── ast.bux # AST node types (algebraic enums)
│ ├── Type.bux │ ├── sema.bux # Type checker / semantic analysis (393 LOC)
│ ├── Hir.bux │ ├── types.bux # Type factories
│ ├── Lir.bux │ ├── scope.bux # Symbol table
│ ├── CBackend.bux # C transpiler (primary backend) │ ├── hir.bux # High-level IR definitions
│ ├── X64Backend.bux # Native x86-64 backend (optional) │ ├── hir_lower.bux # AST → HIR lowering (307 LOC)
│ ├── Linker.bux # Custom linker / build driver │ ├── c_backend.bux # HIR → C code generator (264 LOC)
│ ├── Manifest.bux # bux.toml parser │ ├── cli.bux # CLI command dispatch
── Package.bux # Package resolution ── manifest.bux # bux.toml / TOML parser
├── library/ │ ├── token.bux # Token kind definitions
── Std/ ── source_location.bux # Source location tracking
│ │ ├── Io.bux ├── bootstrap/ # 🔧 Nim bootstrap (build scaffold only)
│ ├── Memory.bux │ ├── main.nim # Entry point
│ ├── String.bux │ ├── cli.nim # CLI commands + build driver
│ ├── Array.bux └── ... # (mirrors src/ structure)
│ │ ├── Map.bux ├── lib/ # 📦 Standard library (23 modules)
│ ├── Math.bux │ ├── Io.bux # Print, ReadFile, WriteFile
│ ├── Os.bux │ ├── String.bux # Full string API (len, concat, split, format...)
│ ├── Path.bux │ ├── Array.bux # Generic Array<T>
│ ├── Process.bux │ ├── Map.bux # Generic Map<K,V> + StringMap
│ ├── Result.bux # Result<T,E> and Option<T> │ ├── Set.bux # Generic Set<T>
│ ├── Iter.bux # Iterator trait and combinators │ ├── Math.bux # Sqrt, Pow, Min, Max, Abs
│ ├── Fmt.bux # String formatting │ ├── Mem.bux # Alloc, Realloc, Free
│ ├── Task.bux # Lightweight concurrency │ ├── Path.bux # Path_Join, Path_Parent, Path_Ext
│ ├── Channel.bux # Message passing │ ├── Fs.bux # DirExists, Mkdir, ListDir
│ └── Sync.bux # Mutex, RwLock, atomic ├── Task.bux # Lightweight tasks (spawn/await)
── Runtime.c # C runtime shim ── Channel.bux # Producer/consumer channels
├── tests/ │ ├── Sync.bux # Mutex, RwLock
│ ├── Lexer/ │ ├── Result.bux # Result<T,E> + Option<T> + ? operator
│ ├── Parser/ │ ├── Iter.bux # Iterator trait
│ ├── Sema/ │ ├── Fmt.bux # String formatting
│ ├── Codegen/ │ ├── Os.bux # Args, Env, Exit, Cwd
── Integration/ ── Time.bux # Time measurement
└── docs/ │ ├── Process.bux # Subprocess spawning
├── LanguageRef.md ├── Net.bux # TCP client/server
├── Ownership.md ├── Crypto.bux # SHA256, HMAC, Base64
── Concurrency.md ── Json.bux # JSON parse/stringify
│ └── Test.bux # Test framework
├── rt/ # ⚙️ C runtime
│ ├── runtime.c # Memory, string, path helpers
│ └── io.c # File I/O wrappers
├── tests/ # 🧪 Unit tests (Nim)
│ ├── lexer_test.nim
│ ├── parser_test.nim
│ ├── sema_test.nim
│ ├── hir_test.nim
│ ├── borrow_test.nim
│ └── testdata/
├── examples/ # Example programs
├── docs/ # Documentation
│ ├── LanguageRef.md
│ ├── BuildAndTest.md
│ ├── STRATEGY.md
│ ├── PHASE8_STRATEGY.md
│ └── Packages.md
├── build/ # Build artifacts (gitignored)
│ └── selfhost/ # Self-host compiler build dir
└── vendor/ # Vendored dependencies
``` ```
--- ---
## Phase 10 🔄 — Path to v1.0.0 (In Progress)
### 10.0 — v0.3.0 Restructuring ✅ (Completed 2026-06-06)
| Task | Status | Details |
|------|--------|---------|
| Directory restructure | ✅ | `compiler/selfhost/``src/`, `compiler/bootstrap/``bootstrap/`, `library/std/``lib/`, `library/runtime/``rt/`, `compiler/tests/``tests/` |
| Path updates | ✅ | Updated Makefile, cli.nim, cli.bux, test files, docs |
| Selfhost fix | ✅ | Build via `build/selfhost/` (project wrapper) |
| Push to GitHub | ✅ | `ac969b3` — v0.3.0 restructure |
### 10.1 — Selfhost Loop 🔄 (v0.4.0 target)
**Goal:** `buxc2` can compile itself producing a binary-identical `buxc3`.
```
buxc (Nim) → src/*.bux → buxc2 ✅ (already works)
buxc2 → src/*.bux → buxc3 🔄 (needs verification)
buxc2 == buxc3 🔄 (deterministic codegen)
```
| Task | Status | Details |
|------|--------|---------|
| `10.1.1` Verify buxc2 can build src/ | 🔄 | Run `buxc2 build` on the selfhost project |
| `10.1.2` Deterministic C codegen | ⏳ | Remove timestamps, random IDs, non-deterministic ordering |
| `10.1.3` Binary-identical loop | ⏳ | `make selfhost-loop` — 2-pass bootstrap verification |
| `10.1.4` Remove hardcoded paths | ⏳ | No `/home/ziko/...` paths in cli.bux |
| `10.1.5` Selfhost test in CI | ⏳ | Add to `make test` |
### 10.2 — Gradual Ownership 🔄 (v0.5.0 target) ⭐ Killer Feature
**Goal:** `@[Checked]` functions get full borrow checking. Without this, Bux is "C with modern syntax."
| Task | Status | Details |
|------|--------|---------|
| `10.2.1` `@[Checked]` attribute gate | ⏳ | Enable/disable borrow checker per function |
| `10.2.2` `&T` shared reference check | ⏳ | No mutation through shared refs |
| `10.2.3` `&mut T` exclusive mutable check | ⏳ | No aliasing of mutable refs |
| `10.2.4` Bounds checking on slices | ⏳ | Buffer overflow prevention |
| `10.2.5` Lifetime elision (simple rules) | ⏳ | 80% of cases without annotations |
| `10.2.6` Explicit lifetimes `'a` | ⏳ | Only for complex cases |
### 10.3 — Compiler Architecture Upgrade (v0.6.0 target)
**Goal:** Proper module structure instead of flat file mirror of Nim bootstrap.
```
src/
├── main.bux
├── frontend/
│ ├── lexer.bux
│ ├── parser.bux
│ └── ast.bux
├── analysis/
│ ├── sema.bux
│ ├── types.bux
│ ├── scope.bux
│ └── borrow.bux # NEW — borrow checker
├── lowering/
│ ├── hir.bux
│ └── hir_lower.bux
├── backend/
│ └── c_backend.bux
└── driver/
├── cli.bux
├── manifest.bux
├── token.bux
└── source_location.bux
```
### 10.4 — Stdlib Completion (v0.7.0 target)
| Module | Status | Priority |
|--------|--------|----------|
| `Std::Os` — Args, Env, Exit, Cwd | ⏳ | P0 |
| `Std::Process` — spawn subprocess | ⏳ | P0 |
| `Std::Iter` — map, filter, fold | ⏳ | P1 |
| `Std::Fmt` — string interpolation | ⏳ | P1 |
### 10.5 — Tooling (v0.8.0 target)
| Tool | Status | Priority |
|------|--------|----------|
| `bux test` — test runner | ⏳ | P0 |
| `bux fmt` — code formatter | ⏳ | P1 |
| `bux doc` — doc generator | ⏳ | P2 |
### 10.6 — Native Backend (v0.9.0 target)
| Task | Status |
|------|--------|
| Direct x86-64 codegen (no C) | ⏳ |
| ELF64 output | ⏳ |
| System V AMD64 ABI | ⏳ |
### 10.7 — v1.0.0 Release Criteria
- [ ] Compiler self-hosts binary-identically
- [ ] `@[Checked]` borrow checker works on real code
- [ ] Stdlib complete enough for CLI tools
- [ ] `bux test`, `bux fmt`, `bux doc` exist
- [ ] Documentation + 30+ examples
- [ ] CI/CD pipeline (build + test on push)
---
## Language Design Decisions (Bux Improvements) ## Language Design Decisions (Bux Improvements)
### What Bux learns from Rust ### What Bux learns from Rust
@@ -689,12 +820,16 @@ func Main() -> int {
| **M2** | 2 | ✅ | Type-checker rejects invalid programs | | **M2** | 2 | ✅ | Type-checker rejects invalid programs |
| **M3** | 3 | ✅ | HIR lowering works for all constructs | | **M3** | 3 | ✅ | HIR lowering works for all constructs |
| **M4** | 5A | ✅ | `bux run` produces working binary via C transpiler | | **M4** | 5A | ✅ | `bux run` produces working binary via C transpiler |
| **M5** | 6 | ✅ | Can write compiler-adjacent tools in Bux (18 examples) | | **M5** | 6 | ✅ | Can write compiler-adjacent tools in Bux (26 examples) |
| **M6** | 7 | ✅ | **Self-hosted**: `buxc2` (Bux) compiles via `buxc` (Nim) — 88KB working binary | | **M6** | 7 | ✅ | **Self-hosted**: `buxc2` (Bux) compiles via `buxc` (Nim) — working binary |
| **M7** | 8 | ✅ | Result/Option/`?`/`!` done; **borrow checker working**; **CTFE working** | | **M7** | 8 | ✅ | Result/Option/`?`/`!` done; **borrow checker working**; **CTFE working** |
| **M8** | 8-9 | ✅ | **Borrow checker**, **CTFE**, **Package manager** working | | **M8** | 8-9 | ✅ | **Borrow checker**, **CTFE**, **Package manager** working |
| **M9** | 8.5 | ✅ | **Trait bounds** (`<T: Comparable>`) — semantic checking implemented | | **M9** | 8.5 | ✅ | **Trait bounds** (`<T: Comparable>`) — semantic checking implemented |
| **M8** | 9 | | Package manager + LSP + formatter shipped | | **M10** | 10 | | **LIR backend** replaces HIR→C; 26/26 examples pass; selfhost builds |
| **M11** | 11 | 🔄 | Selfhost loop: `buxc2` compiles itself → binary-identical `buxc3` |
| **M12** | 11.3 | ⏳ | `@[Checked]` borrow checker works on real code |
| **M13** | 11.4 | ⏳ | `bux test`, `bux fmt`, `bux doc` shipped |
| **M14** | 11.5 | ⏳ | Native x86-64 backend (no C transpiler) |
--- ---
@@ -752,11 +887,57 @@ func Main() -> int {
### Next Actions (Priority Order) ### Next Actions (Priority Order)
1. **Fix parameter/return types**`String*` instead of `int` in function signatures 1. **Fix LIR backend type inference** — struct temps, undeclared vars, break/continue, duplicate declarations
2. **Debug ast/lexer/parser** — Get all 14 modules passing check 2. **All 26 examples passing** — bootstrap compiler builds and runs every example
3. **Full 14-module project build** — Complete bootstrap loop: buxc3 produced by buxc2 3. **Selfhost build working**`make selfhost` produces working `buxc2`
4. **Compare buxc2 vs buxc3 output** — True self-hosting verification 4. 🔄 **Selfhost loop verification**`buxc2` compiles `src/``buxc3`
5. **Phase 8** — Advanced features: ownership checker, CTFE evaluation, string interpolation 5. 🔄 **Golden tests for C codegen** — Prevent future regressions in LIR → C
---
## Phase 11 — Post-LIR Stabilization & Path to v0.4.0 🔄
### 11.1 — LIR Backend Hardening (v0.3.1)
| Task | Status | Priority | Details |
|------|--------|----------|---------|
| `11.1.1` Golden tests for C codegen | ⏳ | P0 | Expected `.c` output for 56 critical examples; diff on regression |
| `11.1.2` LIR debug dump | ⏳ | P1 | `bux build --emit-lir` produces readable LIR |
| `11.1.3` Type info in `LirInstr` | ⏳ | P1 | Add `cType` field to instructions; eliminate inference hacks |
| `11.1.4` Dead code cleanup | ⏳ | P2 | Remove unused `typeFromValue`, `setTempType`, `emit` |
### 11.2 — Selfhost Loop (v0.4.0) ⭐
| Task | Status | Priority | Details |
|------|--------|----------|---------|
| `11.2.1` `buxc2 build` on `src/` | ⏳ | P0 | Verify selfhost compiler can build itself |
| `11.2.2` Deterministic C codegen | ⏳ | P0 | Remove non-deterministic ordering in `emitModule` |
| `11.2.3` `make selfhost-loop` | ⏳ | P0 | Makefile target: buxc2 → buxc3 → binary compare |
| `11.2.4` Cross-module generics in selfhost | ⏳ | P1 | `Map<K,V>` and `Array<T>` from stdlib in selfhost context |
### 11.3 — Gradual Ownership (v0.5.0) ⭐⭐
| Task | Status | Priority | Details |
|------|--------|----------|---------|
| `11.3.1` `&T` / `&mut T` lifetime analysis | ⏳ | P0 | Basic borrow checker integrated in LIR lowering |
| `11.3.2` Bounds checking on slices | ⏳ | P0 | `Slice<T>` index checks `.len` at runtime |
| `11.3.3` Lifetime elision (simple rules) | ⏳ | P1 | 80% of cases without explicit annotations |
### 11.4 — Tooling & Ecosystem (v0.6.0v0.8.0)
| Task | Status | Priority | Details |
|------|--------|----------|---------|
| `11.4.1` `bux test` runner | ⏳ | P0 | Built-in test framework with assertions |
| `11.4.2` `bux fmt` formatter | ⏳ | P1 | Auto-format Bux source |
| `11.4.3` LSP prototype | ⏳ | P2 | Autocomplete, hover types, go-to-definition |
| `11.4.4` `bux doc` generator | ⏳ | P2 | HTML from `///` doc comments |
### 11.5 — Native Backend (v0.9.0)
| Task | Status | Priority | Details |
|------|--------|----------|---------|
| `11.5.1` x86-64 codegen (no C) | ⏳ | P1 | ELF64 output, System V AMD64 ABI |
| `11.5.2` Custom linker / `.bcu` format | ⏳ | P2 | Bespoke object format for faster builds |
--- ---
+4 -3
View File
@@ -2,8 +2,9 @@
![Bux Language](bux-lang-01.jpeg) ![Bux Language](bux-lang-01.jpeg)
> **Status:** Bootstrap compiler (`buxc`, Nim) compiles `.bux` → C → native binary. > **Status:** Bootstrap compiler (`buxc`, Nim) compiles `.bux` → HIR → **LIR** → C → native binary.
> Self-hosted compiler (`buxc2`) exists as a proof-of-concept but is **deprioritized** in favor of language features and stdlib maturity. > New **LIR backend** (v0.3.0) replaces direct HIR→C emission — cleaner codegen, all 26 examples passing.
> Self-hosted compiler (`buxc2`, written in Bux) compiles `.bux` → C → native binary and can build real projects.
Bux is a fast, compiled, strongly-typed systems programming language. Features a C backend for native code generation, raw multi-line strings, gradual ownership, async/await, generics, algebraic enums, and a package manager. Bux is a fast, compiled, strongly-typed systems programming language. Features a C backend for native code generation, raw multi-line strings, gradual ownership, async/await, generics, algebraic enums, and a package manager.
@@ -190,7 +191,7 @@ func Main() -> int {
| **Interfaces** | `interface` + `extend` for trait-like behavior | | **Interfaces** | `interface` + `extend` for trait-like behavior |
| **Error Handling** | `Result<T,E>`, `Option<T>`, and the `?` operator | | **Error Handling** | `Result<T,E>`, `Option<T>`, and the `?` operator |
| **Standard Library** | `Io`, `Array`, `String`, `Map`, `Fs`, `Mem`, `Set`, `Path`, `Math`, `Task`, `Channel`, `Sync`, `Os`, `Time`, `Process` | | **Standard Library** | `Io`, `Array`, `String`, `Map`, `Fs`, `Mem`, `Set`, `Path`, `Math`, `Task`, `Channel`, `Sync`, `Os`, `Time`, `Process` |
| **Backend** | C transpiler (self-hosting + bootstrap) | | **Backend** | LIR → C transpiler (clean 3-address code, then gcc/clang) |
| **Strings** | Raw multi-line backtick strings (`...`), C-string interop | | **Strings** | Raw multi-line backtick strings (`...`), C-string interop |
| **Gradual Ownership** | `@[Checked]` + `&T`/`&mut T` borrow checking | | **Gradual Ownership** | `@[Checked]` + `&T`/`&mut T` borrow checking |
| **Async/Await** | `async func`, `spawn`, `.await` with stackful coroutines | | **Async/Await** | `async func`, `spawn`, `.await` with stackful coroutines |
-7
View File
@@ -1,7 +0,0 @@
[Package]
Name = "buxc2"
Version = "0.2.0"
Type = "bin"
[Build]
Output = "Bin"
-23
View File
@@ -1,23 +0,0 @@
// main.bux — Entry point for the Bux self-hosting compiler
module Main {
// C runtime for command-line args
extern func bux_argc() -> int;
extern func bux_argv(index: int) -> String;
extern func bux_alloc(size: uint) -> *void;
// Forward declaration from Cli module
func Cli_Run(args: *String, argCount: int) -> int;
func Main() -> int {
let count: int = bux_argc();
// Allocate array of String pointers
let args: *String = bux_alloc(count as uint * 8) as *String;
var i: int = 0;
while i < count {
args[i] = bux_argv(i);
i = i + 1;
}
return Cli_Run(args, count);
}
}
-431
View File
@@ -1,431 +0,0 @@
// ast.bux — AST node types (Expr, Stmt, Decl, Pattern, TypeExpr)
module Ast {
// ---------------------------------------------------------------------------
// SourceLocation (inline for convenience)
// ---------------------------------------------------------------------------
struct SourceLoc {
line: uint32,
column: uint32,
}
// ---------------------------------------------------------------------------
// Token (lightweight inline)
// ---------------------------------------------------------------------------
struct AstToken {
kind: int,
text: String,
line: uint32,
column: uint32,
}
// ---------------------------------------------------------------------------
// TypeExpr — type expressions
// ---------------------------------------------------------------------------
const tekNamed: int = 0;
const tekPath: int = 1;
const tekSlice: int = 2;
const tekPointer: int = 3;
const tekTuple: int = 4;
const tekSelf: int = 5;
struct TypeExpr {
kind: int,
line: uint32,
column: uint32,
typeName: String, // for tekNamed
pathStr: String, // for tekPath (segments joined with ::)
pathCount: int, // number of path segments
typeArgName0: String, // up to 2 type args
typeArgName1: String,
typeArgCount: int,
sliceElement: *TypeExpr, // for tekSlice
pointerPointee: *TypeExpr, // for tekPointer
}
// ---------------------------------------------------------------------------
// Pattern — match patterns
// ---------------------------------------------------------------------------
const pkWildcard: int = 0;
const pkLiteral: int = 1;
const pkIdent: int = 2;
const pkRange: int = 3;
const pkEnum: int = 4;
const pkStruct: int = 5;
const pkTuple: int = 6;
struct Pattern {
kind: int,
line: uint32,
column: uint32,
patIdent: String, // for pkIdent
patLitKind: int, // for pkLiteral (token kind)
patLitText: String, // for pkLiteral (token text)
patRangeInclusive: bool, // for pkRange
patEnumPath: String, // for pkEnum (path joined)
patStructName: String, // for pkStruct
}
// ---------------------------------------------------------------------------
// Expr — expressions (tagged union)
// ---------------------------------------------------------------------------
const ekLiteral: int = 0;
const ekIdent: int = 1;
const ekSelf: int = 2;
const ekPath: int = 3;
const ekSizeOf: int = 4;
const ekUnary: int = 5;
const ekPostfix: int = 6;
const ekBinary: int = 7;
const ekAssign: int = 8;
const ekTernary: int = 9;
const ekRange: int = 10;
const ekCall: int = 11;
const ekGenericCall: int = 12;
const ekIndex: int = 13;
const ekField: int = 14;
const ekStructInit: int = 15;
const ekSlice: int = 16;
const ekTuple: int = 17;
const ekCast: int = 18;
const ekIs: int = 19;
const ekTry: int = 20;
const ekUnwrap: int = 23;
const ekBlock: int = 21;
const ekMatch: int = 22;
const ekSpawn: int = 24;
const ekAwait: int = 25;
struct ExprList {
expr: *Expr,
next: *ExprList,
}
struct Expr {
kind: int,
line: uint32,
column: uint32,
// Common fields
strValue: String, // ident name, path segments, field name, callee
intValue: int, // operator kind, intrinsic kind
boolValue: bool, // range inclusive
tokKind: int, // literal token kind
tokText: String, // literal token text
// Children (up to 3 sub-expressions)
child1: *Expr, // left, operand, callee, cond, subject
child2: *Expr, // right, index, then, value
child3: *Expr, // else, third
// Extra references
refType: *TypeExpr, // cast type, is type, sizeof type
refBlock: *Block, // for ekBlock
// Generic call
genericCallee: String,
genericTypeArg0: String,
genericTypeArg1: String,
genericTypeArgCount: int,
// Struct init fields
structName: String,
structFieldCount: int,
// Call arguments (linked list for multi-arg support)
callArgs: *ExprList,
callArgCount: int,
}
// ---------------------------------------------------------------------------
// Block — sequence of statements
// ---------------------------------------------------------------------------
struct Block {
line: uint32,
column: uint32,
stmtCount: int,
firstStmt: *Stmt,
lastStmt: *Stmt,
}
// ---------------------------------------------------------------------------
// Stmt — statements
// ---------------------------------------------------------------------------
const skExpr: int = 0;
const skLet: int = 1;
const skIf: int = 2;
const skWhile: int = 3;
const skDoWhile: int = 4;
const skLoop: int = 5;
const skFor: int = 6;
const skMatch: int = 7;
const skReturn: int = 8;
const skBreak: int = 9;
const skContinue: int = 10;
const skDecl: int = 11;
struct ElseIf {
line: uint32;
column: uint32;
cond: *Expr;
block: *Block;
}
struct Stmt {
kind: int,
line: uint32,
column: uint32,
// Common fields
strValue: String, // let name, pattern ident, label, for var
boolValue: bool, // let mutable
// Children
child1: *Expr, // init expr, condition, iter expr, return value
child2: *Expr, // match subject
child3: *Expr, // extra
refStmtType: *TypeExpr, // let type annotation
refStmtPattern: *Pattern,// let pattern
refStmtDecl: *Decl, // for skDecl
refStmtBlock: *Block, // then/body block
refStmtElse: *Block, // else block
// Else-if chain
elseIfCount: int,
// Linked list
nextStmt: *Stmt,
}
// ---------------------------------------------------------------------------
// Decl — declarations
// ---------------------------------------------------------------------------
const dkFunc: int = 0;
const dkStruct: int = 1;
const dkEnum: int = 2;
const dkUnion: int = 3;
const dkInterface: int = 4;
const dkImpl: int = 5;
const dkModule: int = 6;
const dkUse: int = 7;
const dkConst: int = 8;
const dkTypeAlias: int = 9;
const dkExternFunc: int = 10;
const dkExternVar: int = 11;
struct Param {
line: uint32;
column: uint32;
name: String;
refParamType: *TypeExpr;
isVariadic: bool;
}
struct StructField {
line: uint32;
column: uint32;
isPublic: bool;
name: String;
refFieldType: *TypeExpr;
}
struct EnumVariant {
line: uint32;
column: uint32;
name: String;
fieldCount: int;
fieldTypeName0: String;
fieldTypeName1: String;
}
struct Decl {
kind: int,
line: uint32,
column: uint32,
isPublic: bool,
isAsync: bool,
// Names
strValue: String, // decl name
strValue2: String, // interface name, dll name, module path
// Type params (up to 2)
typeParam0: String,
typeParam1: String,
typeParamCount: int,
// Params (for functions)
paramCount: int,
param0: Param,
param1: Param,
param2: Param,
param3: Param,
param4: Param,
param5: Param,
param6: Param,
param7: Param,
param8: Param,
retType: *TypeExpr,
// Body
refBody: *Block,
// Struct fields (up to 64)
fieldCount: int,
field0: StructField,
field1: StructField,
field2: StructField,
field3: StructField,
field4: StructField,
field5: StructField,
field6: StructField,
field7: StructField,
field8: StructField,
field9: StructField,
field10: StructField,
field11: StructField,
field12: StructField,
field13: StructField,
field14: StructField,
field15: StructField,
field16: StructField,
field17: StructField,
field18: StructField,
field19: StructField,
field20: StructField,
field21: StructField,
field22: StructField,
field23: StructField,
field24: StructField,
field25: StructField,
field26: StructField,
field27: StructField,
field28: StructField,
field29: StructField,
field30: StructField,
field31: StructField,
field32: StructField,
field33: StructField,
field34: StructField,
field35: StructField,
field36: StructField,
field37: StructField,
field38: StructField,
field39: StructField,
field40: StructField,
field41: StructField,
field42: StructField,
field43: StructField,
field44: StructField,
field45: StructField,
field46: StructField,
field47: StructField,
field48: StructField,
field49: StructField,
field50: StructField,
field51: StructField,
field52: StructField,
field53: StructField,
field54: StructField,
field55: StructField,
field56: StructField,
field57: StructField,
field58: StructField,
field59: StructField,
field60: StructField,
field61: StructField,
field62: StructField,
field63: StructField,
// Enum variants (up to 8)
variantCount: int,
variant0: EnumVariant,
variant1: EnumVariant,
variant2: EnumVariant,
variant3: EnumVariant,
variant4: EnumVariant,
variant5: EnumVariant,
variant6: EnumVariant,
variant7: EnumVariant,
// Impl methods (up to 4)
methodCount: int,
// Use/import
useKind: int,
usePath: String,
useNames: String, // joined names for multi-import
// Const
constType: *TypeExpr,
constValue: *Expr,
// Type alias
aliasType: *TypeExpr,
// Extern func
extFuncDll: String,
extFuncVariadic: bool,
extFuncRetType: *TypeExpr,
// Children
childDecl1: *Decl, // linked list of decls (for module items, impl methods)
childDecl2: *Decl,
}
// ---------------------------------------------------------------------------
// Module — AST root
// ---------------------------------------------------------------------------
struct Module {
name: String,
path: String, // path segments joined
itemCount: int,
firstItem: *Decl,
}
// ---------------------------------------------------------------------------
// Constructor helpers
// ---------------------------------------------------------------------------
func Ast_MakeExpr(kind: int, line: uint32, col: uint32) -> Expr {
return Expr { kind: kind, line: line, column: col,
strValue: "", intValue: 0, boolValue: false,
tokKind: 0, tokText: "",
child1: null as *Expr, child2: null as *Expr, child3: null as *Expr,
refType: null as *TypeExpr, refBlock: null as *Block,
genericCallee: "", genericTypeArg0: "", genericTypeArg1: "", genericTypeArgCount: 0,
structName: "", structFieldCount: 0,
callArgs: null as *ExprList, callArgCount: 0 };
}
func Ast_MakeIdent(name: String, line: uint32, col: uint32) -> Expr {
var e: Expr = Ast_MakeExpr(ekIdent, line, col);
e.strValue = name;
return e;
}
func Ast_MakeLiteral(tokKind: int, text: String, line: uint32, col: uint32) -> Expr {
var e: Expr = Ast_MakeExpr(ekLiteral, line, col);
e.tokKind = tokKind;
e.tokText = text;
return e;
}
func Ast_MakeBinary(op: int, left: *Expr, right: *Expr, line: uint32, col: uint32) -> Expr {
var e: Expr = Ast_MakeExpr(ekBinary, line, col);
e.intValue = op;
e.child1 = left;
e.child2 = right;
return e;
}
func Ast_MakeCall(callee: *Expr, line: uint32, col: uint32) -> Expr {
var e: Expr = Ast_MakeExpr(ekCall, line, col);
e.child1 = callee;
return e;
}
func Ast_MakeStmt(kind: int, line: uint32, col: uint32) -> Stmt {
return Stmt { kind: kind, line: line, column: col,
strValue: "", boolValue: false,
child1: null as *Expr, child2: null as *Expr, child3: null as *Expr,
refStmtType: null as *TypeExpr, refStmtPattern: null as *Pattern,
refStmtDecl: null as *Decl, refStmtBlock: null as *Block, refStmtElse: null as *Block,
elseIfCount: 0 };
}
func Ast_MakeDecl(kind: int, line: uint32, col: uint32) -> Decl {
return Decl { kind: kind, line: line, column: col, isPublic: false,
strValue: "", strValue2: "",
typeParam0: "", typeParam1: "", typeParamCount: 0,
paramCount: 0,
retType: null as *TypeExpr,
refBody: null as *Block,
fieldCount: 0,
variantCount: 0,
methodCount: 0,
useKind: 0, usePath: "", useNames: "",
constType: null as *TypeExpr, constValue: null as *Expr,
aliasType: null as *TypeExpr,
extFuncDll: "", extFuncVariadic: false, extFuncRetType: null as *TypeExpr,
childDecl1: null as *Decl, childDecl2: null as *Decl };
}
}
-689
View File
@@ -1,689 +0,0 @@
// c_backend.bux — C transpiler backend (ported from c_backend.nim)
// Generates C code from the HIR.
module CBackend {
// ---------------------------------------------------------------------------
// Type → C type name
// ---------------------------------------------------------------------------
func CBackend_TypeToC(kind: int) -> String {
if kind == tyVoid { return "void"; }
if kind == tyBool { return "bool"; }
if kind == tyBool8 { return "bool"; }
if kind == tyBool16 { return "bool"; }
if kind == tyBool32 { return "bool"; }
if kind == tyChar8 { return "char"; }
if kind == tyChar16 { return "uint16"; }
if kind == tyChar32 { return "uint32"; }
if kind == tyStr { return "String"; }
if kind == tyInt8 { return "int8"; }
if kind == tyInt16 { return "int16"; }
if kind == tyInt32 { return "int32"; }
if kind == tyInt64 { return "int64"; }
if kind == tyInt{ return "int"; }
if kind == tyUInt8 { return "uint8"; }
if kind == tyUInt16 { return "uint16"; }
if kind == tyUInt32 { return "uint32"; }
if kind == tyUInt64 { return "uint64"; }
if kind == tyUInt { return "uint"; }
if kind == tyFloat32 { return "float32"; }
if kind == tyFloat64 { return "float64"; }
if kind == tyPointer { return "void*"; }
if kind == tyNamed { return "int"; }
return "int";
}
func CBackend_OpToC(op: int) -> String {
if op == tkPlus { return "+"; }
if op == tkMinus { return "-"; }
if op == tkStar { return "*"; }
if op == tkSlash { return "/"; }
if op == tkPercent { return "%"; }
if op == tkEq { return "=="; }
if op == tkNe { return "!="; }
if op == tkLt { return "<"; }
if op == tkLe { return "<="; }
if op == tkGt { return ">"; }
if op == tkGe { return ">="; }
if op == tkAmpAmp { return "&&"; }
if op == tkPipePipe { return "||"; }
if op == tkBang { return "!"; }
if op == tkAmp { return "&"; }
if op == tkPipe { return "|"; }
if op == tkCaret { return "^"; }
if op == tkShl { return "<<"; }
if op == tkShr { return ">>"; }
if op == tkAssign { return "="; }
return "?";
}
// ---------------------------------------------------------------------------
// StringBuilder-based C emitter
// ---------------------------------------------------------------------------
struct CEmitter {
sb: StringBuilder,
indent: int,
}
func CBE_Init() -> CEmitter {
var sb: StringBuilder = StringBuilder_NewCap(4096);
return CEmitter { sb: sb, indent: 0 };
}
func CBE_Emit(cbe: *CEmitter, text: String) {
var i: int = 0;
while i < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
i = i + 1;
}
StringBuilder_Append(&cbe.sb, text);
}
func CBE_EmitLine(cbe: *CEmitter, text: String) {
CBE_Emit(cbe, text);
StringBuilder_Append(&cbe.sb, "\n");
}
func CBE_Build(cbe: *CEmitter) -> String {
return StringBuilder_Build(&cbe.sb);
}
// ---------------------------------------------------------------------------
// Emit HIR node
// ---------------------------------------------------------------------------
func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
if node == null as *HirNode { return; }
let kind: int = node.kind;
// Literal
if kind == hLit {
if node.intValue == tkNull {
StringBuilder_Append(&cbe.sb, "0");
} else {
StringBuilder_Append(&cbe.sb, node.strValue);
}
return;
}
// Variable
if kind == hVar {
StringBuilder_Append(&cbe.sb, node.strValue);
return;
}
// Binary
if kind == hBinary {
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, CBackend_OpToC(node.intValue));
StringBuilder_Append(&cbe.sb, " ");
CBE_EmitExpr(cbe, node.child2);
return;
}
// Unary
if kind == hUnary {
StringBuilder_Append(&cbe.sb, CBackend_OpToC(node.intValue));
CBE_EmitExpr(cbe, node.child1);
return;
}
// Call
if kind == hCall {
StringBuilder_Append(&cbe.sb, node.strValue);
StringBuilder_Append(&cbe.sb, "(");
var needsComma: bool = false;
if node.child1 != null as *HirNode {
CBE_EmitExpr(cbe, node.child1);
needsComma = true;
}
if node.child2 != null as *HirNode {
if needsComma {
StringBuilder_Append(&cbe.sb, ", ");
}
CBE_EmitExpr(cbe, node.child2);
needsComma = true;
}
// Emit extra args from linked list
var ai: int = 0;
var curExtra: *HirArgList = node.extraData as *HirArgList;
while ai < node.extraCount {
if needsComma {
StringBuilder_Append(&cbe.sb, ", ");
}
CBE_EmitExpr(cbe, curExtra.node);
needsComma = true;
curExtra = curExtra.next;
ai = ai + 1;
}
StringBuilder_Append(&cbe.sb, ")");
return;
}
// spawn Callee(args)
if kind == hSpawn {
StringBuilder_Append(&cbe.sb, "bux_async_spawn(");
StringBuilder_Append(&cbe.sb, node.strValue);
if node.child1 != null as *HirNode {
StringBuilder_Append(&cbe.sb, ", (void*)");
CBE_EmitExpr(cbe, node.child1);
}
StringBuilder_Append(&cbe.sb, ")");
return;
}
// await
if kind == hAwait {
StringBuilder_Append(&cbe.sb, "bux_async_await(");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ")");
return;
}
// Return
if kind == hReturn {
StringBuilder_Append(&cbe.sb, "return");
if node.child1 != null as *HirNode {
StringBuilder_Append(&cbe.sb, " ");
CBE_EmitExpr(cbe, node.child1);
}
return;
}
// Alloca
if kind == hAlloca {
if !String_Eq(node.typeName, "") {
StringBuilder_Append(&cbe.sb, node.typeName);
} else {
StringBuilder_Append(&cbe.sb, "int");
}
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, node.strValue);
return;
}
// Store: combine alloca + value into single declaration
if kind == hStore {
if node.child1 != null as *HirNode && node.child1.kind == hAlloca {
// Declaration with initializer: Type x = value;
if !String_Eq(node.child1.typeName, "") {
StringBuilder_Append(&cbe.sb, node.child1.typeName);
} else {
StringBuilder_Append(&cbe.sb, CBackend_TypeToC(node.child1.intValue));
}
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, node.child1.strValue);
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, node.child2);
return;
}
// Plain assignment
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, node.child2);
return;
}
// If
if kind == hIf {
StringBuilder_Append(&cbe.sb, "if (");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ") {\n");
if node.child2 != null as *HirNode {
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, node.child2);
cbe.indent = cbe.indent - 1;
}
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, "}");
let elseBlock: *HirNode = node.extraData as *HirNode;
if elseBlock != null as *HirNode {
StringBuilder_Append(&cbe.sb, " else {\n");
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, elseBlock);
cbe.indent = cbe.indent - 1;
sp = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, "}\n");
} else {
StringBuilder_Append(&cbe.sb, "\n");
}
return;
}
// While
if kind == hWhile {
StringBuilder_Append(&cbe.sb, "while (");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ") {\n");
if node.child2 != null as *HirNode {
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, node.child2);
cbe.indent = cbe.indent - 1;
}
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, "}");
return;
}
// Loop (infinite)
if kind == hLoop {
StringBuilder_Append(&cbe.sb, "while (1) {\n");
if node.child1 != null as *HirNode {
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, node.child1);
cbe.indent = cbe.indent - 1;
}
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, "}");
return;
}
// Field access: obj.field — emit as obj->field (since most are pointers)
if kind == hFieldPtr {
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, "->");
StringBuilder_Append(&cbe.sb, node.strValue);
return;
}
// Sizeof: sizeof(Type) — emit as sizeof(Type)
if kind == hSizeOf {
StringBuilder_Append(&cbe.sb, "sizeof(");
if !String_Eq(node.typeName, "") {
StringBuilder_Append(&cbe.sb, node.typeName);
} else {
StringBuilder_Append(&cbe.sb, "int");
}
StringBuilder_Append(&cbe.sb, ")");
return;
}
// Index: arr[idx] — emit as arr[idx]
if kind == hIndexPtr {
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, "[");
CBE_EmitExpr(cbe, node.child2);
StringBuilder_Append(&cbe.sb, "]");
return;
}
// Load: *ptr — emit as *ptr (dereference)
if kind == hLoad {
StringBuilder_Append(&cbe.sb, "(*");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ")");
return;
}
// Assign: target = value
if kind == hAssign {
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, node.child2);
return;
}
// Cast
if kind == hCast {
StringBuilder_Append(&cbe.sb, "((");
if !String_Eq(node.typeName, "") {
StringBuilder_Append(&cbe.sb, node.typeName);
} else {
StringBuilder_Append(&cbe.sb, CBackend_TypeToC(node.typeKind));
}
StringBuilder_Append(&cbe.sb, ")");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ")");
return;
}
// Struct init: ((TypeName){.field = value, ...})
if kind == hStructInit {
StringBuilder_Append(&cbe.sb, "((");
StringBuilder_Append(&cbe.sb, node.strValue);
StringBuilder_Append(&cbe.sb, "){");
// Emit fields (chained via child3)
var field: *HirNode = node.child1;
var first: bool = true;
while field != null as *HirNode {
if !first {
StringBuilder_Append(&cbe.sb, ", ");
}
StringBuilder_Append(&cbe.sb, ".");
StringBuilder_Append(&cbe.sb, field.strValue);
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, field.child1);
first = false;
field = field.child3;
}
StringBuilder_Append(&cbe.sb, "})");
return;
}
// Block — emit each statement via child3 linked list
if kind == hBlock {
var child: *HirNode = node.child1;
while child != null as *HirNode {
// Indent
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
CBE_EmitExpr(cbe, child);
// Add semicolon for statements that need it
if child.kind != hBlock && child.kind != hIf && child.kind != hWhile && child.kind != hLoop {
StringBuilder_Append(&cbe.sb, ";");
}
StringBuilder_Append(&cbe.sb, "\n");
child = child.child3;
}
return;
}
}
// ---------------------------------------------------------------------------
// Emit function declaration
// ---------------------------------------------------------------------------
func CBE_EmitFuncDecl(cbe: *CEmitter, f: *HirFunc) {
// Return type
if String_Eq(f.retTypeName, "") || String_Eq(f.retTypeName, "void") {
StringBuilder_Append(&cbe.sb, "void ");
} else {
StringBuilder_Append(&cbe.sb, f.retTypeName);
StringBuilder_Append(&cbe.sb, " ");
}
StringBuilder_Append(&cbe.sb, f.name);
StringBuilder_Append(&cbe.sb, "(");
// Parameters
var i: int = 0;
while i < f.paramCount {
if i > 0 {
StringBuilder_Append(&cbe.sb, ", ");
}
// Use param type info
var pname: String = "";
var ptype: String = "";
if i == 0 { pname = f.param0.name; ptype = f.param0.typeName; }
if i == 1 { pname = f.param1.name; ptype = f.param1.typeName; }
if i == 2 { pname = f.param2.name; ptype = f.param2.typeName; }
if i == 3 { pname = f.param3.name; ptype = f.param3.typeName; }
if i == 4 { pname = f.param4.name; ptype = f.param4.typeName; }
if i == 5 { pname = f.param5.name; ptype = f.param5.typeName; }
if i == 6 { pname = f.param6.name; ptype = f.param6.typeName; }
if i == 7 { pname = f.param7.name; ptype = f.param7.typeName; }
if i == 8 { pname = f.param8.name; ptype = f.param8.typeName; }
// Emit type
if String_Eq(ptype, "String") || String_Eq(ptype, "str") {
StringBuilder_Append(&cbe.sb, "String ");
} else if String_Eq(ptype, "bool") {
StringBuilder_Append(&cbe.sb, "bool ");
} else if String_Eq(ptype, "int") {
StringBuilder_Append(&cbe.sb, "int ");
} else if String_Eq(ptype, "int64") {
StringBuilder_Append(&cbe.sb, "int64 ");
} else if String_Eq(ptype, "uint") {
StringBuilder_Append(&cbe.sb, "uint ");
} else if String_Eq(ptype, "float64") {
StringBuilder_Append(&cbe.sb, "float64 ");
} else if !String_Eq(ptype, "") {
StringBuilder_Append(&cbe.sb, ptype);
StringBuilder_Append(&cbe.sb, " ");
} else {
StringBuilder_Append(&cbe.sb, "int "); // fallback
}
StringBuilder_Append(&cbe.sb, pname);
i = i + 1;
}
StringBuilder_Append(&cbe.sb, ")");
}
// ---------------------------------------------------------------------------
// Helpers for detecting generic declarations (not yet monomorphized)
// ---------------------------------------------------------------------------
func CBE_IsGenericTypeName(name: String) -> bool {
if String_Eq(name, "T") || String_Eq(name, "K") || String_Eq(name, "V") { return true; }
if String_Eq(name, "T*") || String_Eq(name, "K*") || String_Eq(name, "V*") { return true; }
// Generic container structs and their pointer variants
if String_Eq(name, "Array") || String_Eq(name, "Array*") { return true; }
if String_Eq(name, "SetEntry") || String_Eq(name, "SetEntry*") { return true; }
if String_Eq(name, "StringMapEntry") || String_Eq(name, "StringMapEntry*") { return true; }
if String_Eq(name, "MapEntry") || String_Eq(name, "MapEntry*") { return true; }
return false;
}
func CBE_StructHasGeneric(st: *HirStruct) -> bool {
var fi: int = 0;
while fi < st.fieldCount {
if CBE_IsGenericTypeName(st.fields[fi].typeName) { return true; }
fi = fi + 1;
}
return false;
}
func CBE_FuncHasGeneric(f: *HirFunc) -> bool {
var pi: int = 0;
while pi < f.paramCount {
var ptype: String = "";
if pi == 0 { ptype = f.param0.typeName; }
if pi == 1 { ptype = f.param1.typeName; }
if pi == 2 { ptype = f.param2.typeName; }
if pi == 3 { ptype = f.param3.typeName; }
if pi == 4 { ptype = f.param4.typeName; }
if pi == 5 { ptype = f.param5.typeName; }
if pi == 6 { ptype = f.param6.typeName; }
if pi == 7 { ptype = f.param7.typeName; }
if pi == 8 { ptype = f.param8.typeName; }
if CBE_IsGenericTypeName(ptype) { return true; }
pi = pi + 1;
}
if CBE_IsGenericTypeName(f.retTypeName) { return true; }
return false;
}
// ---------------------------------------------------------------------------
// Generate complete C module
// ---------------------------------------------------------------------------
func CBackend_Generate(mod: *HirModule) -> String {
let cbe: *CEmitter = bux_alloc(sizeof(CEmitter)) as *CEmitter;
cbe.sb = StringBuilder_NewCap(8192);
cbe.indent = 0;
// Header
StringBuilder_Append(&cbe.sb, "// Generated by Bux C Backend\n");
StringBuilder_Append(&cbe.sb, "#include <stdint.h>\n");
StringBuilder_Append(&cbe.sb, "#include <stdbool.h>\n");
StringBuilder_Append(&cbe.sb, "#include <string.h>\n");
StringBuilder_Append(&cbe.sb, "#include <stdio.h>\n");
StringBuilder_Append(&cbe.sb, "#include <stdlib.h>\n\n");
// Type aliases
StringBuilder_Append(&cbe.sb, "typedef const char* String;\n");
StringBuilder_Append(&cbe.sb, "typedef unsigned char uint8;\n");
StringBuilder_Append(&cbe.sb, "typedef unsigned short uint16;\n");
StringBuilder_Append(&cbe.sb, "typedef unsigned int uint32;\n");
StringBuilder_Append(&cbe.sb, "typedef unsigned long long uint64;\n");
StringBuilder_Append(&cbe.sb, "typedef signed char int8;\n");
StringBuilder_Append(&cbe.sb, "typedef short int16;\n");
StringBuilder_Append(&cbe.sb, "typedef long long int64;\n");
StringBuilder_Append(&cbe.sb, "typedef float float32;\n");
StringBuilder_Append(&cbe.sb, "typedef double float64;\n");
StringBuilder_Append(&cbe.sb, "typedef char char8;\n\n");
// Runtime declarations
StringBuilder_Append(&cbe.sb, "void* bux_alloc(unsigned int size);\n");
StringBuilder_Append(&cbe.sb, "void bux_free(void* ptr);\n\n");
// Forward declare all struct types (skip generics)
var si: int = 0;
while si < mod.structCount {
if !CBE_StructHasGeneric(&mod.structs[si]) {
StringBuilder_Append(&cbe.sb, "typedef struct ");
StringBuilder_Append(&cbe.sb, mod.structs[si].name);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, mod.structs[si].name);
StringBuilder_Append(&cbe.sb, ";\n");
}
si = si + 1;
}
StringBuilder_Append(&cbe.sb, "\n");
// Enum definitions (typedef to int)
var ei: int = 0;
while ei < mod.enumCount {
StringBuilder_Append(&cbe.sb, "typedef int ");
StringBuilder_Append(&cbe.sb, mod.enums[ei].name);
StringBuilder_Append(&cbe.sb, ";\n");
ei = ei + 1;
}
if mod.enumCount > 0 {
StringBuilder_Append(&cbe.sb, "\n");
}
// Constant definitions
var ci: int = 0;
while ci < mod.constCount {
StringBuilder_Append(&cbe.sb, "#define ");
StringBuilder_Append(&cbe.sb, mod.consts[ci].name);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, String_FromInt(mod.consts[ci].value as int64));
StringBuilder_Append(&cbe.sb, "\n");
ci = ci + 1;
}
if mod.constCount > 0 {
StringBuilder_Append(&cbe.sb, "\n");
}
// Struct definitions (skip generics)
si = 0;
while si < mod.structCount {
if CBE_StructHasGeneric(&mod.structs[si]) {
si = si + 1;
continue;
}
StringBuilder_Append(&cbe.sb, "struct ");
StringBuilder_Append(&cbe.sb, mod.structs[si].name);
StringBuilder_Append(&cbe.sb, " {\n");
var fi: int = 0;
while fi < mod.structs[si].fieldCount {
StringBuilder_Append(&cbe.sb, " ");
var ft: String = mod.structs[si].fields[fi].typeName;
if String_Eq(ft, "int") || String_Eq(ft, "") {
StringBuilder_Append(&cbe.sb, "int");
} else if String_Eq(ft, "String") {
StringBuilder_Append(&cbe.sb, "String");
} else if String_Eq(ft, "bool") {
StringBuilder_Append(&cbe.sb, "bool");
} else if String_Eq(ft, "uint32") {
StringBuilder_Append(&cbe.sb, "uint32");
} else {
// Use the type name directly (handles "Foo*", "Bar", etc.)
StringBuilder_Append(&cbe.sb, ft);
}
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, mod.structs[si].fields[fi].name);
StringBuilder_Append(&cbe.sb, ";\n");
fi = fi + 1;
}
StringBuilder_Append(&cbe.sb, "};\n\n");
si = si + 1;
}
// Forward declarations for all functions (skip generics)
var i: int = 0;
while i < mod.funcCount {
if !CBE_FuncHasGeneric(&mod.funcs[i]) {
CBE_EmitFuncDecl(cbe, &mod.funcs[i]);
StringBuilder_Append(&cbe.sb, ";\n");
}
i = i + 1;
}
StringBuilder_Append(&cbe.sb, "\n");
// Extern declarations
i = 0;
while i < mod.externCount {
CBE_EmitFuncDecl(cbe, &mod.externFuncs[i]);
StringBuilder_Append(&cbe.sb, ";\n");
i = i + 1;
}
StringBuilder_Append(&cbe.sb, "\n");
// Function definitions (skip generics)
var hasMain: bool = false;
i = 0;
while i < mod.funcCount {
if String_Eq(mod.funcs[i].name, "Main") { hasMain = true; }
// Skip generic functions
if CBE_FuncHasGeneric(&mod.funcs[i]) {
i = i + 1;
continue;
}
// Skip forward declarations (functions without body)
let body: *HirNode = mod.funcs[i].body;
if body == null as *HirNode {
i = i + 1;
continue;
}
CBE_EmitFuncDecl(cbe, &mod.funcs[i]);
StringBuilder_Append(&cbe.sb, " {\n");
// Body
var hasReturn: bool = false;
cbe.indent = 1;
CBE_EmitExpr(cbe, body);
cbe.indent = 0;
// Check if body has a return statement
var stmt: *HirNode = body.child1;
while stmt != null as *HirNode {
if stmt.kind == hReturn { hasReturn = true; }
stmt = stmt.child3;
}
// Add default return 0 only if function returns int and has no explicit return
if String_Eq(mod.funcs[i].retTypeName, "int") && !hasReturn {
StringBuilder_Append(&cbe.sb, " return 0;\n");
}
StringBuilder_Append(&cbe.sb, "}\n\n");
i = i + 1;
}
// Generate C main wrapper if Main function exists
if hasMain {
StringBuilder_Append(&cbe.sb, "extern int g_argc;\n");
StringBuilder_Append(&cbe.sb, "extern char** g_argv;\n");
StringBuilder_Append(&cbe.sb, "int main(int argc, char** argv) {\n");
StringBuilder_Append(&cbe.sb, " g_argc = argc;\n");
StringBuilder_Append(&cbe.sb, " g_argv = argv;\n");
StringBuilder_Append(&cbe.sb, " return Main();\n");
StringBuilder_Append(&cbe.sb, "}\n");
}
return StringBuilder_Build(&cbe.sb);
}
func CBackend_Free(cbe: *CEmitter) {
StringBuilder_Free(&cbe.sb);
bux_free(cbe as *void);
}
}
-761
View File
@@ -1,761 +0,0 @@
// cli.bux — CLI driver for the Bux self-hosting compiler
// Wires together: Lexer → Parser → Sema → HirLower → CBackend
module Cli {
extern func PrintLine(s: String);
extern func Print(s: String);
extern func bux_read_file(path: String) -> String;
extern func bux_write_file(path: String, content: String) -> bool;
extern func bux_file_exists(path: String) -> int;
extern func bux_dir_exists(path: String) -> int;
extern func bux_path_join(a: String, b: String) -> String;
extern func bux_mkdir_if_needed(path: String) -> int;
extern func bux_run_nim(nim_file: String, out_bin: String) -> int;
extern func bux_list_dir(dir: String, ext: String, out_count: *int) -> *String;
extern func bux_system(cmd: String) -> int;
func ReadFile(path: String) -> String {
return bux_read_file(path);
}
func WriteFile(path: String, content: String) -> bool {
return bux_write_file(path, content);
}
func FileExists(path: String) -> bool {
return bux_file_exists(path) != 0;
}
func DirExists(path: String) -> bool {
return bux_dir_exists(path) != 0;
}
// Import the compiler pipeline
// In self-hosting mode, these are compiled together from compiler/selfhost/
// ---------------------------------------------------------------------------
// Compile a single .bux source file
// ---------------------------------------------------------------------------
func Cli_Compile(source: String, sourceName: String) -> String {
// Phase 1: Lex
PrintLine(" Lexing...");
let lex: *Lexer = Lexer_Tokenize(source);
PrintLine(" Lex done");
if Lexer_DiagCount(lex) > 0 {
PrintLine("Lex errors:");
var i: int = 0;
while i < Lexer_DiagCount(lex) {
Print(" ");
PrintLine(lex.diags[i].message);
i = i + 1;
}
return "";
}
// Phase 2: Parse
PrintLine(" Parsing...");
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
if mod == null as *Module {
PrintLine("Parse failed");
return "";
}
PrintLine(" Parse done");
// Flatten module wrappers: find module decl and hoist its children
var decl: *Decl = mod.firstItem;
while decl != null as *Decl {
if decl.kind == dkModule && decl.childDecl1 != null as *Decl {
mod.firstItem = decl.childDecl1;
break;
}
decl = decl.childDecl2;
}
// Phase 3: Semantic analysis
PrintLine(" Sema...");
let sema: *Sema = Sema_Analyze(mod);
if Sema_HasError(sema) {
PrintLine("Sema errors:");
var i: int = 0;
while i < Sema_DiagCount(sema) {
Print(" ");
PrintLine(sema.diags[i].message);
i = i + 1;
}
return "";
}
PrintLine(" Sema done");
// Phase 4: HIR lowering
PrintLine(" HIR lowering...");
let hirMod: *HirModule = HirLower_LowerModule(mod, sema);
if hirMod == null as *HirModule {
PrintLine("HIR lowering failed");
return "";
}
Print("HIR funcCount=");
PrintInt(hirMod.funcCount);
// Phase 5: C code generation
let cCode: String = CBackend_Generate(hirMod);
// Cleanup
Lexer_Free(lex);
Sema_Free(sema);
return cCode;
}
// ---------------------------------------------------------------------------
// Build command
// ---------------------------------------------------------------------------
func Cli_Build(srcPath: String, outPath: String) -> int {
let source: String = ReadFile(srcPath);
if source == null as String || String_Eq(source, "") || !FileExists(srcPath) {
Print("Error: cannot read source file: ");
PrintLine(srcPath);
return 1;
}
Print("Compiling ");
PrintLine(srcPath);
let cCode: String = Cli_Compile(source, srcPath);
if String_Eq(cCode, "") {
PrintLine("Compilation failed");
return 1;
}
// Write C output
let cFile: String = String_Concat(outPath, ".c");
let ok: bool = WriteFile(cFile, cCode);
if !ok {
PrintLine("Error: cannot write output file");
return 1;
}
Print(" → C written to ");
PrintLine(cFile);
// Find runtime.c and io.c for linking
var rtPath: String = "library/runtime/runtime.c";
var ioPath: String = "library/runtime/io.c";
if !FileExists(rtPath) {
rtPath = "../library/runtime/runtime.c";
}
if !FileExists(ioPath) {
ioPath = "../library/runtime/io.c";
}
if !FileExists(rtPath) {
rtPath = "/home/ziko/z-git/bux/bux/library/runtime/runtime.c";
}
if !FileExists(ioPath) {
ioPath = "/home/ziko/z-git/bux/bux/library/runtime/io.c";
}
// Compile with cc
PrintLine("Compiling C...");
var cmdBuf: StringBuilder = StringBuilder_NewCap(512);
StringBuilder_Append(&cmdBuf, "cc -O2 -pthread -o ");
StringBuilder_Append(&cmdBuf, outPath);
StringBuilder_Append(&cmdBuf, " ");
StringBuilder_Append(&cmdBuf, cFile);
StringBuilder_Append(&cmdBuf, " ");
if FileExists(rtPath) {
StringBuilder_Append(&cmdBuf, rtPath);
StringBuilder_Append(&cmdBuf, " ");
}
if FileExists(ioPath) {
StringBuilder_Append(&cmdBuf, ioPath);
StringBuilder_Append(&cmdBuf, " ");
}
StringBuilder_Append(&cmdBuf, "-lm");
let ccRc: int = bux_system(StringBuilder_Build(&cmdBuf));
if ccRc != 0 {
PrintLine("Error: C compilation failed");
return 1;
}
Print(" → Binary: ");
PrintLine(outPath);
return 0;
}
// ---------------------------------------------------------------------------
// Check command (compile only, no output)
// ---------------------------------------------------------------------------
func Cli_Check(srcPath: String) -> int {
Print("Check: ");
PrintLine(srcPath);
let source: String = ReadFile(srcPath);
if source == null as String || String_Eq(source, "") {
PrintLine("Error: cannot read source file");
return 1;
}
PrintLine(" File read OK");
// Phase 1: Lex
PrintLine(" Lexing...");
let lex: *Lexer = Lexer_Tokenize(source);
PrintLine(" Lex done");
if Lexer_DiagCount(lex) > 0 {
PrintLine("Lex errors found");
return 1;
}
// Phase 2: Parse
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
if mod == null as *Module {
PrintLine("Parse failed");
return 1;
}
PrintLine(" Parse done");
// Flatten module wrappers
var decl2: *Decl = mod.firstItem;
while decl2 != null as *Decl {
if decl2.kind == dkModule && decl2.childDecl1 != null as *Decl {
mod.firstItem = decl2.childDecl1;
break;
}
decl2 = decl2.childDecl2;
}
// Phase 3: Sema
let sema: *Sema = Sema_Analyze(mod);
if Sema_HasError(sema) {
PrintLine("Sema errors found");
var i: int = 0;
while i < Sema_DiagCount(sema) {
Print(" line ");
PrintInt(sema.diags[i].line as int64);
Print(": ");
PrintLine(sema.diags[i].message);
i = i + 1;
}
return 1;
}
// Phase 4: HIR lowering
let hirMod: *HirModule = HirLower_LowerModule(mod, sema);
var dc: *Decl = mod.firstItem;
while dc != null as *Decl {
if dc.kind == dkFunc {
Print("check func ");
PrintLine(dc.strValue);
}
dc = dc.childDecl2;
}
PrintLine("Check passed");
return 0;
}
// ---------------------------------------------------------------------------
// Project build — compile all .bux files in src/ directory
// ---------------------------------------------------------------------------
func Cli_CompileSource(source: String, sourceName: String) -> *HirModule {
// Phase 1: Lex
PrintLine(" Lexing...");
let lex: *Lexer = Lexer_Tokenize(source);
PrintLine(" Lex done");
if Lexer_DiagCount(lex) > 0 {
Print("Lex errors in ");
PrintLine(sourceName);
return null as *HirModule;
}
// Phase 2: Parse
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
if mod == null as *Module {
Print("Parse failed for ");
PrintLine(sourceName);
return null as *HirModule;
}
// Phase 3: Semantic analysis
let sema: *Sema = Sema_Analyze(mod);
if Sema_HasError(sema) {
Print("Sema errors in ");
PrintLine(sourceName);
return null as *HirModule;
}
// Phase 4: HIR lowering
let hirMod: *HirModule = HirLower_LowerModule(mod, sema);
return hirMod;
}
// ---------------------------------------------------------------------------
// Stdlib discovery and merging
// ---------------------------------------------------------------------------
func Cli_FindStdlibDir(projectDir: String) -> String {
var path: String = bux_path_join(projectDir, "stdlib");
if DirExists(path) { return path; }
path = bux_path_join(projectDir, "../stdlib");
if DirExists(path) { return path; }
path = "/home/ziko/z-git/bux/bux/stdlib";
if DirExists(path) { return path; }
return "";
}
func Cli_DeclName(decl: *Decl) -> String {
if decl == null as *Decl { return ""; }
return decl.strValue;
}
func Cli_NameInList(name: String, names: *String, count: int) -> bool {
if String_Eq(name, "") { return false; }
var i: int = 0;
while i < count {
if String_Eq(names[i], name) {
return true;
}
i = i + 1;
}
return false;
}
func Cli_CollectNames(mod: *Module, names: *String, maxCount: int) -> int {
if mod == null as *Module { return 0; }
var count: int = 0;
var decl: *Decl = mod.firstItem;
while decl != null as *Decl && count < maxCount {
let next: *Decl = decl.childDecl2;
if decl.kind == dkModule {
var inner: *Decl = decl.childDecl1;
while inner != null as *Decl && count < maxCount {
let iname: String = Cli_DeclName(inner);
if !String_Eq(iname, "") {
names[count] = iname;
count = count + 1;
}
inner = inner.childDecl2;
}
} else {
let dname: String = Cli_DeclName(decl);
if !String_Eq(dname, "") {
names[count] = dname;
count = count + 1;
}
}
decl = next;
}
return count;
}
// Helper: convert single char int to String
func String_FromChar(c: int) -> String {
var buf: *char8 = bux_alloc(2) as *char8;
buf[0] = c as char8;
buf[1] = 0 as char8;
return buf as String;
}
// Collect stdlib module paths imported by user code.
// Returns number of unique stdlib files to merge (paths written into outPaths).
func Cli_CollectStdlibImports(mod: *Module, outPaths: *String, maxCount: int, stdlibDir: String) -> int {
if mod == null as *Module { return 0; }
var count: int = 0;
var decl: *Decl = mod.firstItem;
while decl != null as *Decl && count < maxCount {
if decl.kind == dkUse {
let path: String = decl.usePath;
// Only handle Std::* imports
if String_StartsWith(path, "Std::") {
// Extract module name after Std:: (e.g., "Std::Io" -> "Io")
var j: int = 5; // skip "Std::"
var modName: String = "";
while j < 100 {
let c: int = path[j] as int;
if c == 0 || c == 58 { break; } // null or ':'
modName = String_Concat(modName, String_FromChar(c));
j = j + 1;
}
if !String_Eq(modName, "") {
let filePath: String = bux_path_join(bux_path_join(stdlibDir, "Std"), String_Concat(modName, ".bux"));
// Check for duplicates
var dup: bool = false;
var di: int = 0;
while di < count {
if String_Eq(outPaths[di], filePath) { dup = true; }
di = di + 1;
}
if !dup {
outPaths[count] = filePath;
count = count + 1;
}
}
}
}
decl = decl.childDecl2;
}
return count;
}
func Cli_MergeFileInto(target: *Module, path: String, skipNames: *String, skipCount: int) -> int {
if !FileExists(path) {
Print("Error: stdlib file not found: ");
PrintLine(path);
return 0;
}
let source: String = ReadFile(path);
if source == null as String || String_Eq(source, "") { return 0; }
let lex: *Lexer = Lexer_Tokenize(source);
if Lexer_DiagCount(lex) > 0 { return 0; }
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
if mod == null as *Module { return 0; }
var added: int = 0;
var decl: *Decl = mod.firstItem;
while decl != null as *Decl {
let next: *Decl = decl.childDecl2;
decl.childDecl2 = null as *Decl;
if decl.kind == dkModule {
var inner: *Decl = decl.childDecl1;
while inner != null as *Decl {
let innerNext: *Decl = inner.childDecl2;
let iname: String = Cli_DeclName(inner);
if !String_Eq(iname, "") && Cli_NameInList(iname, skipNames, skipCount) {
// Skip shadowed stdlib decl
} else {
inner.childDecl2 = target.firstItem;
target.firstItem = inner;
target.itemCount = target.itemCount + 1;
added = added + 1;
}
inner = innerNext;
}
} else {
let dname: String = Cli_DeclName(decl);
if !String_Eq(dname, "") && Cli_NameInList(dname, skipNames, skipCount) {
// Skip shadowed stdlib decl
} else {
decl.childDecl2 = target.firstItem;
target.firstItem = decl;
target.itemCount = target.itemCount + 1;
added = added + 1;
}
}
decl = next;
}
return added;
}
func Cli_CopyModuleDecls(target: *Module, source: *Module) {
if source == null as *Module || source.firstItem == null as *Decl { return; }
var decl: *Decl = source.firstItem;
var limit: int = 0;
while decl != null as *Decl && limit < 10000 {
let next: *Decl = decl.childDecl2;
decl.childDecl2 = target.firstItem;
target.firstItem = decl;
target.itemCount = target.itemCount + 1;
decl = next;
limit = limit + 1;
}
source.firstItem = null as *Decl;
source.itemCount = 0;
}
func Cli_BuildProject(projectDir: String) -> int {
let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml"));
var outName: String = man.name;
if String_Eq(outName, "") {
outName = "bux_out";
}
let srcDir: String = bux_path_join(projectDir, "src");
if !DirExists(srcDir) {
Print("Error: no src/ directory in ");
PrintLine(projectDir);
return 1;
}
Print("Scanning ");
PrintLine(srcDir);
// List all .bux files in src/
var fileCount: int = 0;
let files: *String = bux_list_dir(srcDir, ".bux", &fileCount);
if fileCount == 0 {
PrintLine("Error: no .bux files found in src/");
return 1;
}
Print("Found ");
PrintInt(fileCount);
PrintLine(" source files");
// Create user merged module (collect user decls first)
let userMerged: *Module = bux_alloc(sizeof(Module)) as *Module;
userMerged.name = "main";
userMerged.path = "";
userMerged.itemCount = 0;
userMerged.firstItem = null as *Decl;
// Parse each file and merge declarations into userMerged module
var i: int = 0;
while i < fileCount {
let path: String = files[i];
Print(" Parsing ");
PrintLine(path);
let source: String = ReadFile(path);
if String_Eq(source, "") {
Print(" Error: cannot read ");
PrintLine(path);
return 1;
}
let lex: *Lexer = Lexer_Tokenize(source);
if Lexer_DiagCount(lex) > 0 {
Print(" Lex errors in ");
PrintLine(path);
return 1;
}
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
if mod == null as *Module {
Print(" Parse failed for ");
PrintLine(path);
return 1;
}
// Merge declarations from this module into userMerged
var decl: *Decl = mod.firstItem;
while decl != null as *Decl {
let next: *Decl = decl.childDecl2;
decl.childDecl2 = null as *Decl;
// Flatten module declarations
if decl.kind == dkModule {
var inner: *Decl = decl.childDecl1;
while inner != null as *Decl {
let innerNext: *Decl = inner.childDecl2;
inner.childDecl2 = userMerged.firstItem;
userMerged.firstItem = inner;
userMerged.itemCount = userMerged.itemCount + 1;
inner = innerNext;
}
} else {
decl.childDecl2 = userMerged.firstItem;
userMerged.firstItem = decl;
userMerged.itemCount = userMerged.itemCount + 1;
}
decl = next;
}
i = i + 1;
}
// Collect user declaration names for shadow detection
let maxNames: int = 2048;
let userNames: *String = bux_alloc(maxNames * 8) as *String;
let userNameCount: int = Cli_CollectNames(userMerged, userNames, maxNames);
// Create final merged module
let merged: *Module = bux_alloc(sizeof(Module)) as *Module;
merged.name = "main";
merged.path = "";
merged.itemCount = 0;
merged.firstItem = null as *Decl;
// Find and merge stdlib declarations (only imported modules)
let stdlibDir: String = Cli_FindStdlibDir(projectDir);
if !String_Eq(stdlibDir, "") {
Print("Stdlib found: ");
PrintLine(stdlibDir);
// Collect imported stdlib modules from user code
let maxImports: int = 64;
let importPaths: *String = bux_alloc(maxImports * 8) as *String;
let importCount: int = Cli_CollectStdlibImports(userMerged, importPaths, maxImports, stdlibDir);
Print("Imported stdlib modules: ");
PrintInt(importCount);
PrintLine("");
if importCount > 0 {
var si: int = 0;
var stdAdded: int = 0;
while si < importCount {
Print(" Merging ");
PrintLine(importPaths[si]);
let added: int = Cli_MergeFileInto(merged, importPaths[si], userNames, userNameCount);
stdAdded = stdAdded + added;
si = si + 1;
}
Print("Stdlib declarations added: ");
PrintInt(stdAdded);
}
}
// Copy user declarations into merged (user shadows stdlib)
Cli_CopyModuleDecls(merged, userMerged);
Print("Merged ");
PrintInt(merged.itemCount);
PrintLine(" declarations");
// Semantic analysis
PrintLine("Running sema...");
let sema: *Sema = Sema_Analyze(merged);
if Sema_HasError(sema) {
PrintLine("Sema errors found");
var i: int = 0;
while i < Sema_DiagCount(sema) {
Print(" line ");
PrintInt(sema.diags[i].line as int64);
Print(": ");
PrintLine(sema.diags[i].message);
i = i + 1;
}
return 1;
}
// HIR lowering
PrintLine("Lowering to HIR...");
let hirMod: *HirModule = HirLower_LowerModule(merged, sema);
// C code generation
PrintLine("Generating C code...");
let cCode: String = CBackend_Generate(hirMod);
// Create build directory
let buildDir: String = bux_path_join(projectDir, "build");
discard bux_mkdir_if_needed(buildDir);
// Write C output
let cFile: String = bux_path_join(buildDir, "main.c");
if !WriteFile(cFile, cCode) {
PrintLine("Error: cannot write main.c");
return 1;
}
Print("C code written to ");
PrintLine(cFile);
// Find runtime.c and io.c (use stdlibDir if available)
var rtPath: String = bux_path_join(stdlibDir, "runtime.c");
var ioPath: String = bux_path_join(stdlibDir, "io.c");
if !FileExists(rtPath) {
rtPath = bux_path_join(projectDir, "library/runtime/runtime.c");
}
if !FileExists(ioPath) {
ioPath = bux_path_join(projectDir, "library/runtime/io.c");
}
if !FileExists(rtPath) {
rtPath = bux_path_join(projectDir, "../library/runtime/runtime.c");
}
if !FileExists(ioPath) {
ioPath = bux_path_join(projectDir, "../library/runtime/io.c");
}
// Compile with cc
PrintLine("Compiling C...");
let outBin: String = bux_path_join(buildDir, outName);
var ccBuf: StringBuilder = StringBuilder_NewCap(512);
StringBuilder_Append(&ccBuf, "cc -O2 -pthread -o ");
StringBuilder_Append(&ccBuf, outBin);
StringBuilder_Append(&ccBuf, " ");
StringBuilder_Append(&ccBuf, cFile);
StringBuilder_Append(&ccBuf, " ");
if FileExists(rtPath) {
StringBuilder_Append(&ccBuf, rtPath);
StringBuilder_Append(&ccBuf, " ");
}
if FileExists(ioPath) {
StringBuilder_Append(&ccBuf, ioPath);
StringBuilder_Append(&ccBuf, " ");
}
StringBuilder_Append(&ccBuf, "-lm");
let ccRc: int = bux_system(StringBuilder_Build(&ccBuf));
if ccRc != 0 {
PrintLine("Error: C compilation failed");
return 1;
}
Print("Build successful: ");
PrintLine(outBin);
return 0;
}
// ---------------------------------------------------------------------------
// Run command — build project and execute
// ---------------------------------------------------------------------------
func Cli_RunProject(projectDir: String) -> int {
let rc: int = Cli_BuildProject(projectDir);
if rc != 0 {
return rc;
}
let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml"));
var outName: String = man.name;
if String_Eq(outName, "") {
outName = "bux_out";
}
let outBin: String = bux_path_join(bux_path_join(projectDir, "build"), outName);
Print("Running: ");
PrintLine(outBin);
return bux_system(outBin);
}
// ---------------------------------------------------------------------------
// Main entry — dispatch based on args
// ---------------------------------------------------------------------------
func Cli_Run(args: *String, argCount: int) -> int {
if argCount < 2 {
PrintLine("Bux Self-Hosting Compiler v0.2.0");
PrintLine("Usage: buxc <command> [args]");
PrintLine("Commands: build, check, run, project, help, version");
return 0;
}
let cmd: String = args[1];
if String_Eq(cmd, "version") || String_Eq(cmd, "--version") || String_Eq(cmd, "-v") {
PrintLine("Bux 0.2.0 (self-hosting bootstrap)");
return 0;
}
if String_Eq(cmd, "help") || String_Eq(cmd, "--help") || String_Eq(cmd, "-h") {
PrintLine("Bux Self-Hosting Compiler v0.2.0");
PrintLine("Usage: buxc <command> [args]");
PrintLine("Commands: build, check, run, project, help, version");
PrintLine("Pipeline modules:");
PrintLine(" Lexer ✅ 695 lines");
PrintLine(" Parser ✅ 1004 lines");
PrintLine(" Sema ✅ 393 lines");
PrintLine(" HirLower ✅ 307 lines");
PrintLine(" CBackend ✅ 585 lines");
PrintLine(" Total: 3830 lines of Bux");
return 0;
}
if String_Eq(cmd, "check") {
if argCount < 3 {
PrintLine("Usage: buxc check <file.bux>");
return 1;
}
return Cli_Check(args[2]);
}
if String_Eq(cmd, "build") {
let src: String = "src/Main.bux";
let out: String = "build/main";
if argCount >= 3 { src = args[2]; }
if argCount >= 4 { out = args[3]; }
return Cli_Build(src, out);
}
if String_Eq(cmd, "project") {
let dir: String = ".";
if argCount >= 3 { dir = args[2]; }
return Cli_BuildProject(dir);
}
if String_Eq(cmd, "run") {
let dir: String = ".";
if argCount >= 3 { dir = args[2]; }
return Cli_RunProject(dir);
}
Print("Unknown command: ");
PrintLine(cmd);
return 1;
}
}
-229
View File
@@ -1,229 +0,0 @@
// hir.bux — HIR (High-level Intermediate Representation) node types
module Hir {
// HIR node kinds
const hLit: int = 0;
const hVar: int = 1;
const hSelf: int = 2;
const hUnary: int = 3;
const hBinary: int = 4;
const hAssign: int = 5;
const hIf: int = 6;
const hWhile: int = 7;
const hLoop: int = 8;
const hBreak: int = 9;
const hContinue: int = 10;
const hReturn: int = 11;
const hAlloca: int = 12;
const hLoad: int = 13;
const hStore: int = 14;
const hFieldPtr: int = 15;
const hArrowField: int = 16;
const hIndexPtr: int = 17;
const hCall: int = 18;
const hCallIndirect: int = 19;
const hCast: int = 20;
const hIs: int = 21;
const hSizeOf: int = 22;
const hBlock: int = 23;
const hStructInit: int = 24;
const hSliceInit: int = 25;
const hRange: int = 26;
const hTupleInit: int = 27;
const hMatch: int = 28;
const hSpawn: int = 29;
const hAwait: int = 30;
// ---------------------------------------------------------------------------
// HirArgList — linked list for call arguments beyond 2
// ---------------------------------------------------------------------------
struct HirArgList {
node: *HirNode,
next: *HirArgList,
}
// ---------------------------------------------------------------------------
// HirNode — unified struct with tagged union pattern
// ---------------------------------------------------------------------------
struct HirNode {
kind: int;
line: uint32;
column: uint32;
typeKind: int;
typeName: String;
// Common fields (used by multiple kinds)
strValue: String; // var name; callee name; field name; label
intValue: int; // token kind (for lit; unary op; binary op)
boolValue: bool; // range inclusive; isScope
// Child nodes (up to 3)
child1: *HirNode; // left/operand/condition/base
child2: *HirNode; // right/value/then/body
child3: *HirNode; // else/third
// Extra data pointer (for children arrays, field lists, etc.)
extraData: *void;
extraCount: int;
}
// ---------------------------------------------------------------------------
// HirFunc
// ---------------------------------------------------------------------------
struct HirParam {
name: String;
typeKind: int;
typeName: String;
}
struct HirFunc {
name: String;
paramCount: int;
param0: HirParam;
param1: HirParam;
param2: HirParam;
param3: HirParam;
param4: HirParam;
param5: HirParam;
param6: HirParam;
param7: HirParam;
param8: HirParam;
retTypeKind: int;
retTypeName: String;
body: *HirNode;
isPublic: bool;
}
// ---------------------------------------------------------------------------
// HirEnumVariant
// ---------------------------------------------------------------------------
struct HirEnumVariant {
name: String;
fieldCount: int;
fieldType0: int;
fieldName0: String;
fieldType1: int;
fieldName1: String;
}
// ---------------------------------------------------------------------------
// HirModule
// ---------------------------------------------------------------------------
struct HirStructField {
name: String;
typeName: String;
}
struct HirStruct {
name: String;
fieldCount: int;
fields: *HirStructField;
}
struct HirConst {
name: String;
value: int;
}
struct HirEnum {
name: String;
}
struct HirModule {
funcCount: int;
funcs: *HirFunc;
externCount: int;
externFuncs: *HirFunc;
structCount: int;
structs: *HirStruct;
enumCount: int;
enums: *HirEnum;
constCount: int;
consts: *HirConst;
}
// ---------------------------------------------------------------------------
// Constructor helpers
// ---------------------------------------------------------------------------
func Hir_MakeNode(kind: int, line: uint32, column: uint32) -> HirNode {
return HirNode { kind: kind, line: line, column: column,
typeKind: 0, typeName: "",
strValue: "", intValue: 0, boolValue: false,
child1: null as *HirNode, child2: null as *HirNode, child3: null as *HirNode,
extraData: null as *void, extraCount: 0 };
}
func Hir_MakeLit(tokKind: int, tokText: String, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hLit, line, col);
n.intValue = tokKind;
n.strValue = tokText;
return n;
}
func Hir_MakeVar(name: String, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hVar, line, col);
n.strValue = name;
return n;
}
func Hir_MakeBinary(op: int, left: *HirNode, right: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hBinary, line, col);
n.intValue = op;
n.child1 = left;
n.child2 = right;
return n;
}
func Hir_MakeCall(callee: String, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hCall, line, col);
n.strValue = callee;
return n;
}
func Hir_MakeReturn(value: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hReturn, line, col);
n.child1 = value;
return n;
}
func Hir_MakeBlock(line: uint32, col: uint32) -> HirNode {
return Hir_MakeNode(hBlock, line, col);
}
func Hir_MakeIf(cond: *HirNode, thenBody: *HirNode, elseBody: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hIf, line, col);
n.child1 = cond;
n.child2 = thenBody;
n.child3 = elseBody;
return n;
}
func Hir_MakeWhile(cond: *HirNode, body: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hWhile, line, col);
n.child1 = cond;
n.child2 = body;
return n;
}
func Hir_MakeAlloca(name: String, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hAlloca, line, col);
n.strValue = name;
return n;
}
func Hir_MakeLoad(ptr: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hLoad, line, col);
n.child1 = ptr;
return n;
}
func Hir_MakeStore(ptr: *HirNode, value: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hStore, line, col);
n.child1 = ptr;
n.child2 = value;
return n;
}
}
-740
View File
@@ -1,740 +0,0 @@
// hir_lower.bux — HIR lowering: AST → HIR transformation (ported from hir_lower.nim)
// Transforms the typed AST into a lower-level IR suitable for code generation.
module HirLower {
// ---------------------------------------------------------------------------
// Lowering context
// ---------------------------------------------------------------------------
struct LowerCtx {
module: *Module,
scope: *Scope,
funcs: *HirFunc,
funcCount: int,
externFuncs: *HirFunc,
externCount: int,
varCounter: int,
tryCounter: int,
}
// ---------------------------------------------------------------------------
// TypeExpr.kind → Type.kind resolver
// TypeExpr.kind values (0-5) overlap with Type.kind values — this
// resolves the correct Type.kind for codegen.
// ---------------------------------------------------------------------------
func Lcx_ResolveTypeKind(te: *TypeExpr) -> int {
if te == null as *TypeExpr { return tyUnknown; }
let name: String = te.typeName;
if te.kind == tekPointer { return tyPointer; }
if te.kind == tekSlice { return tySlice; }
if te.kind == tekTuple { return tyTuple; }
// Named types: resolve by name
if String_Eq(name, "void") { return tyVoid; }
if String_Eq(name, "bool") { return tyBool; }
if String_Eq(name, "bool8") { return tyBool8; }
if String_Eq(name, "bool16") { return tyBool16; }
if String_Eq(name, "bool32") { return tyBool32; }
if String_Eq(name, "char8") { return tyChar8; }
if String_Eq(name, "char16") { return tyChar16; }
if String_Eq(name, "char32") { return tyChar32; }
if String_Eq(name, "String") { return tyStr; }
if String_Eq(name, "str") { return tyStr; }
if String_Eq(name, "int8") { return tyInt8; }
if String_Eq(name, "int16") { return tyInt16; }
if String_Eq(name, "int32") { return tyInt32; }
if String_Eq(name, "int64") { return tyInt64; }
if String_Eq(name, "int") { return tyInt; }
if String_Eq(name, "uint8") { return tyUInt8; }
if String_Eq(name, "uint16") { return tyUInt16; }
if String_Eq(name, "uint32") { return tyUInt32; }
if String_Eq(name, "uint64") { return tyUInt64; }
if String_Eq(name, "uint") { return tyUInt; }
if String_Eq(name, "float32") { return tyFloat32; }
if String_Eq(name, "float64") { return tyFloat64; }
if String_Eq(name, "float") { return tyFloat64; }
return tyNamed;
}
// ---------------------------------------------------------------------------
// Fresh name generation
// ---------------------------------------------------------------------------
func Lcx_FreshName(ctx: *LowerCtx) -> String {
ctx.varCounter = ctx.varCounter + 1;
return String_FromInt(ctx.varCounter as int64);
}
// ---------------------------------------------------------------------------
// Type → HIR type
// ---------------------------------------------------------------------------
func Lcx_LowerType(typeKind: int, typeName: String) -> int {
return typeKind;
}
// ---------------------------------------------------------------------------
// Expression lowering
// ---------------------------------------------------------------------------
func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
if expr == null as *Expr { return null as *HirNode; }
let line: uint32 = expr.line;
let col: uint32 = expr.column;
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
n.kind = hBlock;
n.line = line;
n.column = col;
let kind: int = expr.kind;
// Literal
if kind == ekLiteral {
n.kind = hLit;
n.intValue = expr.tokKind;
n.strValue = expr.tokText;
return n;
}
// Identifier → variable reference
if kind == ekIdent {
n.kind = hVar;
n.strValue = expr.strValue;
let sym: Symbol = Scope_Lookup(ctx.scope, expr.strValue);
n.typeKind = sym.typeKind;
if expr.refType != null as *TypeExpr {
n.typeName = expr.refType.typeName;
}
if sym.typeName != null as String && !String_Eq(sym.typeName, "") {
n.typeName = sym.typeName;
}
return n;
}
// self → variable reference named "self"
if kind == ekSelf {
n.kind = hVar;
n.strValue = "self";
let sym: Symbol = Scope_Lookup(ctx.scope, "self");
n.typeKind = sym.typeKind;
if sym.typeName != null as String && !String_Eq(sym.typeName, "") {
n.typeName = sym.typeName;
}
return n;
}
// Binary
if kind == ekBinary {
// Assignment operator → use hAssign
if expr.intValue == tkAssign {
n.kind = hAssign;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
return n;
}
n.kind = hBinary;
n.intValue = expr.intValue; // operator
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
return n;
}
// Unary
if kind == ekUnary {
n.kind = hUnary;
n.intValue = expr.intValue;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
return n;
}
// Call
if kind == ekCall {
n.kind = hCall;
// Method call desugaring: obj.method(args) → Type_method(obj, args)
if expr.child1 != null as *Expr && expr.child1.kind == ekField {
let methodName: String = expr.child1.strValue;
var receiverTypeName: String = "";
if expr.child1.child1 != null as *Expr && expr.child1.child1.kind == ekIdent {
let sym: Symbol = Scope_Lookup(ctx.scope, expr.child1.child1.strValue);
receiverTypeName = sym.typeName;
}
if String_Eq(receiverTypeName, "") && expr.child1.child1 != null as *Expr && expr.child1.child1.refType != null as *TypeExpr {
receiverTypeName = expr.child1.child1.refType.typeName;
}
if !String_Eq(receiverTypeName, "") {
// Strip trailing '*' from pointer type names (e.g. "Box*" → "Box")
var baseName: String = receiverTypeName;
let len: int = bux_strlen(baseName) as int;
if len > 0 {
let lastChar: String = bux_str_slice(baseName, (len - 1) as uint, 1);
if String_Eq(lastChar, "*") {
baseName = bux_str_slice(baseName, 0, (len - 1) as uint);
}
}
n.strValue = String_Concat(baseName, "_");
n.strValue = String_Concat(n.strValue, methodName);
}
// Lower receiver as first argument
let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1.child1);
n.child1 = recv;
// Lower remaining arguments from linked list
var arg: *ExprList = expr.callArgs;
var argIdx: int = 0;
while arg != null as *ExprList {
let lowered: *HirNode = Lcx_LowerExpr(ctx, arg.expr);
if argIdx == 0 {
n.child2 = lowered;
} else if argIdx == 1 {
// Third argument — start linked list
let firstExtra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
firstExtra.node = lowered;
firstExtra.next = null as *HirArgList;
n.extraData = firstExtra as *void;
n.extraCount = 1;
} else {
// Additional args — append to linked list
var cur: *HirArgList = n.extraData as *HirArgList;
while cur.next != null as *HirArgList {
cur = cur.next;
}
let newNode: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
newNode.node = lowered;
newNode.next = null as *HirArgList;
cur.next = newNode;
n.extraCount = n.extraCount + 1;
}
arg = arg.next;
argIdx = argIdx + 1;
}
return n;
}
// Get callee name
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
n.strValue = expr.child1.strValue;
}
// Lower arguments from linked list
var arg: *ExprList = expr.callArgs;
var argIdx: int = 0;
while arg != null as *ExprList {
let lowered: *HirNode = Lcx_LowerExpr(ctx, arg.expr);
if argIdx == 0 {
n.child1 = lowered;
} else if argIdx == 1 {
n.child2 = lowered;
} else if argIdx == 2 {
// Third argument — start linked list
let firstExtra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
firstExtra.node = lowered;
firstExtra.next = null as *HirArgList;
n.extraData = firstExtra as *void;
n.extraCount = 1;
} else {
// Additional args — append to linked list
var cur: *HirArgList = n.extraData as *HirArgList;
while cur.next != null as *HirArgList {
cur = cur.next;
}
let newNode: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
newNode.node = lowered;
newNode.next = null as *HirArgList;
cur.next = newNode;
n.extraCount = n.extraCount + 1;
}
arg = arg.next;
argIdx = argIdx + 1;
}
return n;
}
// Sizeof
if kind == ekSizeOf {
n.kind = hSizeOf;
if expr.refType != null as *TypeExpr {
n.typeName = expr.refType.typeName;
}
return n;
}
// Field access
if kind == ekField {
// Check if this is enum variant access: Color::Green
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
let sym: Symbol = Scope_Lookup(ctx.scope, expr.child1.strValue);
if sym.decl != null as *Decl && sym.decl.kind == dkEnum {
// Emit as variable reference: Color_Green
n.kind = hVar;
n.strValue = String_Concat(String_Concat(expr.child1.strValue, "_"), expr.strValue);
return n;
}
}
n.kind = hFieldPtr;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
n.strValue = expr.strValue;
// Get struct type from base expr refType
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr {
n.typeName = expr.child1.refType.typeName;
}
return n;
}
// spawn Callee(args)
if kind == ekSpawn {
n.kind = hSpawn;
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
n.strValue = expr.child1.strValue;
}
if expr.child2 != null as *Expr {
n.child1 = Lcx_LowerExpr(ctx, expr.child2);
}
return n;
}
// expr.await
if kind == ekAwait {
n.kind = hAwait;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
return n;
}
// Index: arr[idx]
if kind == ekIndex {
n.kind = hIndexPtr;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
return n;
}
// Assign: target = value
if kind == ekAssign {
n.kind = hAssign;
n.child1 = Lcx_LowerExpr(ctx, expr.child1); // target
n.child2 = Lcx_LowerExpr(ctx, expr.child2); // value
return n;
}
// Cast
if kind == ekCast {
n.kind = hCast;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
if expr.refType != null as *TypeExpr {
let resolvedKind: int = Lcx_ResolveTypeKind(expr.refType);
n.typeKind = resolvedKind;
// For pointer types, construct "PointeeType*"
if expr.refType.kind == tekPointer && expr.refType.pointerPointee != null as *TypeExpr {
n.typeName = String_Concat(expr.refType.pointerPointee.typeName, "*");
} else if !String_Eq(expr.refType.typeName, "") {
n.typeName = expr.refType.typeName;
}
}
return n;
}
// Struct init: TypeName { field: value, ... }
if kind == ekStructInit {
n.kind = hStructInit;
n.strValue = expr.structName;
// Lower each field (fields are chained via child3 on synthetic ekField exprs)
var field: *Expr = expr.child1;
var firstField: *HirNode = null as *HirNode;
var lastField: *HirNode = null as *HirNode;
while field != null as *Expr {
let fNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
fNode.kind = hBlock; // placeholder, field is identified by name+value
fNode.line = expr.line;
fNode.column = expr.column;
fNode.strValue = field.strValue; // field name
fNode.child1 = Lcx_LowerExpr(ctx, field.child1); // field value
if firstField == null as *HirNode {
firstField = fNode;
lastField = fNode;
} else {
lastField.child3 = fNode;
lastField = fNode;
}
field = field.child3;
}
n.child1 = firstField;
return n;
}
return n;
}
// ---------------------------------------------------------------------------
// Statement lowering
// ---------------------------------------------------------------------------
func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
if stmt == null as *Stmt { return null as *HirNode; }
let line: uint32 = stmt.line;
let col: uint32 = stmt.column;
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
n.kind = hBlock;
n.line = line;
n.column = col;
let kind: int = stmt.kind;
// Let/var → alloca + store
if kind == skLet {
let init: *HirNode = Lcx_LowerExpr(ctx, stmt.child1);
// alloca for the variable
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
alloca.kind = hAlloca;
alloca.line = line;
alloca.column = col;
alloca.strValue = stmt.strValue;
// Set type from the declared type expression
alloca.typeName = "";
if stmt.refStmtType != null as *TypeExpr {
alloca.intValue = stmt.refStmtType.kind;
alloca.typeKind = Lcx_ResolveTypeKind(stmt.refStmtType);
// For pointer types, construct "PointeeType*"
if stmt.refStmtType.kind == tekPointer && stmt.refStmtType.pointerPointee != null as *TypeExpr {
alloca.typeName = String_Concat(stmt.refStmtType.pointerPointee.typeName, "*");
} else if !String_Eq(stmt.refStmtType.typeName, "") {
alloca.typeName = stmt.refStmtType.typeName;
}
}
// Add to scope for field offset lookups
var sym: Symbol;
sym.kind = skVar;
sym.name = stmt.strValue;
sym.typeKind = alloca.typeKind;
sym.typeName = alloca.typeName;
sym.isMutable = false;
sym.isPublic = false;
sym.decl = null as *Decl;
discard Scope_Define(ctx.scope, sym);
// store the init value
n.kind = hStore;
n.child1 = alloca;
n.child2 = init;
return n;
}
// Return
if kind == skReturn {
n.kind = hReturn;
if stmt.child1 != null as *Expr {
n.child1 = Lcx_LowerExpr(ctx, stmt.child1);
}
return n;
}
// Expression statement
if kind == skExpr && stmt.child1 != null as *Expr {
return Lcx_LowerExpr(ctx, stmt.child1);
}
// If
if kind == skIf {
n.kind = hIf;
n.child1 = Lcx_LowerExpr(ctx, stmt.child1); // condition
if stmt.refStmtBlock != null as *Block {
n.child2 = Lcx_LowerBlock(ctx, stmt.refStmtBlock, -1);
}
if stmt.refStmtElse != null as *Block {
// Store else block in extraData (child3 is reserved for block chaining)
n.extraData = Lcx_LowerBlock(ctx, stmt.refStmtElse, -1) as *void;
}
return n;
}
// While
if kind == skWhile {
n.kind = hWhile;
n.child1 = Lcx_LowerExpr(ctx, stmt.child1);
if stmt.refStmtBlock != null as *Block {
n.child2 = Lcx_LowerBlock(ctx, stmt.refStmtBlock, -1);
}
return n;
}
// Loop
if kind == skLoop {
n.kind = hLoop;
if stmt.refStmtBlock != null as *Block {
n.child1 = Lcx_LowerBlock(ctx, stmt.refStmtBlock, -1);
}
return n;
}
return n;
}
// ---------------------------------------------------------------------------
// Block lowering
// ---------------------------------------------------------------------------
func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode {
if block == null as *Block { return null as *HirNode; }
if block.stmtCount == 0 { return null as *HirNode; }
// Build a linked list of HirNodes via child3:
// node1 (stmt1) → child3 → node2 (stmt2) → child3 → node3 (stmt3) → null
// child3 is safe for chaining because:
// hStore: child1=alloca, child2=value, child3 unused
// hReturn: child1=value, child2/child3 unused
// hCall: child1=arg1, child2=arg2, child3 unused
var firstNode: *HirNode = null as *HirNode;
var prevNode: *HirNode = null as *HirNode;
var stmt: *Stmt = block.firstStmt;
while stmt != null as *Stmt {
let lowered: *HirNode = Lcx_LowerStmt(ctx, stmt);
if lowered != null as *HirNode {
if firstNode == null as *HirNode {
firstNode = lowered;
prevNode = lowered;
} else {
prevNode.child3 = lowered;
prevNode = lowered;
}
}
stmt = stmt.nextStmt;
}
// Wrap in an hBlock node with child1 = first statement in chain
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
n.kind = hBlock;
n.line = block.line;
n.column = block.column;
n.boolValue = true;
n.child1 = firstNode;
return n;
}
// ---------------------------------------------------------------------------
// Param → HirParam conversion
// ---------------------------------------------------------------------------
func Lcx_LowerParam(out: *HirParam, p: *Param) {
out.name = p.name;
if p.refParamType != null as *TypeExpr {
out.typeKind = Lcx_ResolveTypeKind(p.refParamType);
// Use typeName directly if set (parser may have constructed "String*")
if !String_Eq(p.refParamType.typeName, "") {
out.typeName = p.refParamType.typeName;
} else if p.refParamType.kind == tekPointer && p.refParamType.pointerPointee != null as *TypeExpr {
out.typeName = String_Concat(p.refParamType.pointerPointee.typeName, "*");
} else {
out.typeName = "";
}
} else {
out.typeKind = 0;
out.typeName = "";
}
}
// ---------------------------------------------------------------------------
// Function lowering
// ---------------------------------------------------------------------------
func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
let f: *HirFunc = bux_alloc(sizeof(HirFunc)) as *HirFunc;
f.name = decl.strValue;
f.isPublic = decl.isPublic;
f.paramCount = decl.paramCount;
Lcx_LowerParam(&f.param0, &decl.param0);
Lcx_LowerParam(&f.param1, &decl.param1);
Lcx_LowerParam(&f.param2, &decl.param2);
Lcx_LowerParam(&f.param3, &decl.param3);
Lcx_LowerParam(&f.param4, &decl.param4);
Lcx_LowerParam(&f.param5, &decl.param5);
Lcx_LowerParam(&f.param6, &decl.param6);
Lcx_LowerParam(&f.param7, &decl.param7);
Lcx_LowerParam(&f.param8, &decl.param8);
if decl.retType != null as *TypeExpr {
f.retTypeName = decl.retType.typeName;
f.retTypeKind = Lcx_ResolveTypeKind(decl.retType);
} else {
f.retTypeName = "";
f.retTypeKind = 0;
}
// Add parameters to hir_lower scope for field offset lookups
var pi: int = 0;
while pi < decl.paramCount {
var p: *Param = null as *Param;
if pi == 0 { p = &decl.param0; }
else if pi == 1 { p = &decl.param1; }
else if pi == 2 { p = &decl.param2; }
else if pi == 3 { p = &decl.param3; }
else if pi == 4 { p = &decl.param4; }
else if pi == 5 { p = &decl.param5; }
else if pi == 6 { p = &decl.param6; }
else if pi == 7 { p = &decl.param7; }
else if pi == 8 { p = &decl.param8; }
if p != null as *Param && p.refParamType != null as *TypeExpr {
var sym: Symbol;
sym.kind = skVar;
sym.name = p.name;
sym.typeKind = Lcx_ResolveTypeKind(p.refParamType);
sym.typeName = p.refParamType.typeName;
sym.isMutable = false;
sym.isPublic = false;
sym.decl = null as *Decl;
discard Scope_Define(ctx.scope, sym);
}
pi = pi + 1;
}
if decl.refBody != null as *Block {
f.body = Lcx_LowerBlock(ctx, decl.refBody, -1);
} else {
f.body = null as *HirNode;
}
return f;
}
// ---------------------------------------------------------------------------
// Module lowering — main entry point
// ---------------------------------------------------------------------------
func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
let ctx: *LowerCtx = bux_alloc(sizeof(LowerCtx)) as *LowerCtx;
ctx.module = mod;
ctx.scope = sema.scope;
ctx.funcs = bux_alloc(256 as uint * sizeof(HirFunc)) as *HirFunc;
ctx.funcCount = 0;
ctx.externFuncs = bux_alloc(256 as uint * sizeof(HirFunc)) as *HirFunc;
ctx.externCount = 0;
ctx.varCounter = 0;
let hm: *HirModule = bux_alloc(sizeof(HirModule)) as *HirModule;
hm.funcCount = 0;
hm.funcs = ctx.funcs;
hm.structCount = 0;
hm.structs = bux_alloc(64 as uint * sizeof(HirStruct)) as *HirStruct;
hm.enumCount = 0;
hm.enums = bux_alloc(64 as uint * sizeof(HirEnum)) as *HirEnum;
hm.constCount = 0;
hm.consts = bux_alloc(512 as uint * sizeof(HirConst)) as *HirConst;
// First pass: count structs (to allocate field arrays later)
// Second pass: actually collect them
// For simplicity, do single pass with pre-allocated field arrays
// Iterate declarations
var decl: *Decl = mod.firstItem;
while decl != null as *Decl {
if decl.kind == dkFunc {
let f: *HirFunc = Lcx_LowerFunc(ctx, decl);
ctx.funcs[ctx.funcCount] = *f;
ctx.funcCount = ctx.funcCount + 1;
}
if decl.kind == dkImpl {
let implTypeName: String = decl.strValue;
var implDecl: *Decl = decl.childDecl1;
while implDecl != null as *Decl {
if implDecl.kind == dkFunc {
let mangled: String = String_Concat(implTypeName, "_");
implDecl.strValue = String_Concat(mangled, implDecl.strValue);
let f: *HirFunc = Lcx_LowerFunc(ctx, implDecl);
ctx.funcs[ctx.funcCount] = *f;
ctx.funcCount = ctx.funcCount + 1;
}
implDecl = implDecl.childDecl2;
}
}
if decl.kind == dkExternFunc {
let f: *HirFunc = Lcx_LowerFunc(ctx, decl);
ctx.externFuncs[ctx.externCount] = *f;
ctx.externCount = ctx.externCount + 1;
}
if decl.kind == dkStruct {
// Collect struct definition for C codegen
let si: int = hm.structCount;
hm.structs[si].name = decl.strValue;
hm.structs[si].fieldCount = decl.fieldCount;
hm.structs[si].fields = bux_alloc(decl.fieldCount as uint * sizeof(HirStructField)) as *HirStructField;
var fi: int = 0;
while fi < decl.fieldCount {
var fname: String = "";
var ftype: *TypeExpr = null as *TypeExpr;
if fi == 0 { fname = decl.field0.name; ftype = decl.field0.refFieldType; }
else if fi == 1 { fname = decl.field1.name; ftype = decl.field1.refFieldType; }
else if fi == 2 { fname = decl.field2.name; ftype = decl.field2.refFieldType; }
else if fi == 3 { fname = decl.field3.name; ftype = decl.field3.refFieldType; }
else if fi == 4 { fname = decl.field4.name; ftype = decl.field4.refFieldType; }
else if fi == 5 { fname = decl.field5.name; ftype = decl.field5.refFieldType; }
else if fi == 6 { fname = decl.field6.name; ftype = decl.field6.refFieldType; }
else if fi == 7 { fname = decl.field7.name; ftype = decl.field7.refFieldType; }
// Skip empty field names
if String_Eq(fname, "") {
fi = fi + 1;
continue;
}
hm.structs[si].fields[fi].name = fname;
if ftype != null as *TypeExpr {
if ftype.kind == tekPointer && ftype.pointerPointee != null as *TypeExpr {
// Pointer type: emit "TypeName*"
if !String_Eq(ftype.pointerPointee.typeName, "") {
hm.structs[si].fields[fi].typeName = String_Concat(ftype.pointerPointee.typeName, "*");
}
} else if !String_Eq(ftype.typeName, "") {
hm.structs[si].fields[fi].typeName = ftype.typeName;
}
}
fi = fi + 1;
}
hm.structCount = hm.structCount + 1;
}
if decl.kind == dkConst && hm.constCount < 512 {
let ci: int = hm.constCount;
hm.consts[ci].name = decl.strValue;
var val: int = 0;
if decl.constValue != null as *Expr {
if decl.constValue.kind == ekLiteral {
val = decl.constValue.intValue;
}
}
hm.consts[ci].value = val;
hm.constCount = hm.constCount + 1;
}
if decl.kind == dkEnum {
let ei: int = hm.enumCount;
hm.enums[ei].name = decl.strValue;
hm.enumCount = hm.enumCount + 1;
// Emit enum variants as constants: EnumName_VariantName = value
var vi: int = 0;
while vi < decl.variantCount && hm.constCount < 512 {
var vname: String = "";
if vi == 0 { vname = decl.variant0.name; }
if vi == 1 { vname = decl.variant1.name; }
if vi == 2 { vname = decl.variant2.name; }
if vi == 3 { vname = decl.variant3.name; }
if !String_Eq(vname, "") {
let ci: int = hm.constCount;
hm.consts[ci].name = String_Concat(String_Concat(decl.strValue, "_"), vname);
hm.consts[ci].value = vi;
hm.constCount = hm.constCount + 1;
}
vi = vi + 1;
}
}
decl = decl.childDecl2;
}
hm.funcCount = ctx.funcCount;
hm.funcs = ctx.funcs;
hm.externCount = ctx.externCount;
hm.externFuncs = ctx.externFuncs;
return hm;
}
func HirLower_Free(ctx: *LowerCtx) {
bux_free(ctx.funcs as *void);
bux_free(ctx.externFuncs as *void);
bux_free(ctx as *void);
}
}
-816
View File
@@ -1,816 +0,0 @@
// lexer.bux — Lexer state machine (ported from lexer.nim)
// Tokenizes Bux source into a stream of tokens.
module Lexer {
extern func bux_strlen(s: String) -> uint;
// ---------------------------------------------------------------------------
// Character helpers (wrap C ctype)
// ---------------------------------------------------------------------------
func Lex_IsDigit(c: uint32) -> bool {
return c >= 48 && c <= 57; // '0'..'9'
}
func Lex_IsHexDigit(c: uint32) -> bool {
if c >= 48 && c <= 57 { return true; } // 0-9
if c >= 65 && c <= 70 { return true; } // A-F
if c >= 97 && c <= 102 { return true; } // a-f
return false;
}
func Lex_IsBinDigit(c: uint32) -> bool {
return c == 48 || c == 49; // '0' or '1'
}
func Lex_IsOctDigit(c: uint32) -> bool {
return c >= 48 && c <= 55; // '0'..'7'
}
func Lex_IsIdentStart(c: uint32) -> bool {
if c >= 97 && c <= 122 { return true; } // a-z
if c >= 65 && c <= 90 { return true; } // A-Z
return c == 95; // '_'
}
func Lex_IsIdentChar(c: uint32) -> bool {
if Lex_IsIdentStart(c) { return true; }
return Lex_IsDigit(c);
}
// ---------------------------------------------------------------------------
// Lexer state
// ---------------------------------------------------------------------------
const maxTokens: int = 32768;
const maxDiags: int = 128;
struct LexerDiag {
line: uint32;
column: uint32;
message: String;
}
struct Lexer {
source: String;
sourceLen: int;
pos: int;
line: uint32;
column: uint32;
startLine: uint32;
startColumn: uint32;
startPos: int;
tokenCount: int;
tokens: *LexToken;
diagCount: int;
diags: *LexerDiag;
}
struct LexToken {
kind: int;
text: String;
line: uint32;
column: uint32;
}
// ---------------------------------------------------------------------------
// Init
// ---------------------------------------------------------------------------
func Lexer_Init(source: String) -> Lexer {
let len: uint = bux_strlen(source);
let tokBuf: *LexToken = bux_alloc(maxTokens as uint * sizeof(LexToken)) as *LexToken;
let diagBuf: *LexerDiag = bux_alloc(maxDiags as uint * sizeof(LexerDiag)) as *LexerDiag;
return Lexer {
source: source, sourceLen: len as int,
pos: 0, line: 1, column: 1,
startLine: 0, startColumn: 0, startPos: 0,
tokenCount: 0, tokens: tokBuf,
diagCount: 0, diags: diagBuf
};
}
// ---------------------------------------------------------------------------
// Core primitives
// ---------------------------------------------------------------------------
func lexIsAtEnd(lex: *Lexer) -> bool {
return lex.pos >= lex.sourceLen;
}
func lexPeek(lex: *Lexer, ahead: int) -> uint32 {
let i: int = lex.pos + ahead;
if i < lex.sourceLen {
return (lex.source[i] as uint32) & 255;
}
return 0;
}
func lexAdvance(lex: *Lexer) -> uint32 {
let c: uint32 = lexPeek(lex, 0);
if !lexIsAtEnd(lex) {
lex.pos = lex.pos + 1;
if c == 10 { // '\n'
lex.line = lex.line + 1;
lex.column = 1;
} else {
lex.column = lex.column + 1;
}
}
return c;
}
func lexMatch(lex: *Lexer, expected: uint32) -> bool {
if lexIsAtEnd(lex) { return false; }
if lexPeek(lex, 0) != expected { return false; }
discard lexAdvance(lex);
return true;
}
func lexMatchStr(lex: *Lexer, s: String) -> bool {
let len: uint = bux_strlen(s);
var i: int = 0;
while i < (len as int) {
if lexPeek(lex, i) != (s[i] as uint32) {
return false;
}
i = i + 1;
}
i = 0;
while i < (len as int) {
discard lexAdvance(lex);
i = i + 1;
}
return true;
}
// ---------------------------------------------------------------------------
// Token emission
// ---------------------------------------------------------------------------
func lexMarkStart(lex: *Lexer) {
lex.startLine = lex.line;
lex.startColumn = lex.column;
lex.startPos = lex.pos;
}
func lexMakeToken(lex: *Lexer, kind: int) -> LexToken {
var text: String = "";
let endPos: int = lex.pos;
let startPos: int = lex.startPos;
if endPos > startPos {
let len: int = endPos - startPos;
let buf: *char8 = bux_alloc((len + 1) as uint) as *char8;
var i: int = 0;
while i < len {
buf[i] = lex.source[startPos + i] as char8;
i = i + 1;
}
buf[len] = 0 as char8;
text = buf;
} else {
text = "";
}
return LexToken {
kind: kind, text: text,
line: lex.startLine, column: lex.startColumn
};
}
func lexEmitToken(lex: *Lexer, kind: int) {
let tok: LexToken = lexMakeToken(lex, kind);
if lex.tokenCount < maxTokens {
lex.tokens[lex.tokenCount] = tok;
lex.tokenCount = lex.tokenCount + 1;
}
}
func lexSetLastTokenText(lex: *Lexer, text: String) {
if lex.tokenCount > 0 {
lex.tokens[lex.tokenCount - 1].text = text;
}
}
func lexEmitDiag(lex: *Lexer, msg: String) {
if lex.diagCount < maxDiags {
lex.diags[lex.diagCount] = LexerDiag {
line: lex.line, column: lex.column, message: msg
};
lex.diagCount = lex.diagCount + 1;
}
}
// ---------------------------------------------------------------------------
// Whitespace / comments
// ---------------------------------------------------------------------------
func lexSkipLineComment(lex: *Lexer) {
while !lexIsAtEnd(lex) && lexPeek(lex, 0) != 10 { // '\n'
discard lexAdvance(lex);
}
}
func lexSkipBlockComment(lex: *Lexer) {
var depth: int = 1;
while !lexIsAtEnd(lex) && depth > 0 {
if lexPeek(lex, 0) == 47 && lexPeek(lex, 1) == 42 { // /*
discard lexAdvance(lex);
discard lexAdvance(lex);
depth = depth + 1;
} else if lexPeek(lex, 0) == 42 && lexPeek(lex, 1) == 47 { // */
discard lexAdvance(lex);
discard lexAdvance(lex);
depth = depth - 1;
} else {
discard lexAdvance(lex);
}
}
if depth > 0 {
lexEmitDiag(lex, "unterminated block comment");
}
}
func lexSkipWhitespace(lex: *Lexer) {
while !lexIsAtEnd(lex) {
let c: uint32 = lexPeek(lex, 0);
if c == 32 || c == 9 || c == 13 {
discard lexAdvance(lex);
} else {
if c == 47 && lexPeek(lex, 1) == 47 {
lexSkipLineComment(lex);
} else {
if c == 47 && lexPeek(lex, 1) == 42 {
discard lexAdvance(lex);
discard lexAdvance(lex);
lexSkipBlockComment(lex);
} else {
break;
}
}
}
}
}
// ---------------------------------------------------------------------------
// Identifiers
// ---------------------------------------------------------------------------
func lexIsIdentKind(tok: int) -> bool {
return tok == tkFunc || tok == tkLet || tok == tkVar || tok == tkConst
|| tok == tkType || tok == tkStruct || tok == tkEnum || tok == tkUnion
|| tok == tkInterface || tok == tkExtend || tok == tkModule || tok == tkImport
|| tok == tkPub || tok == tkExtern || tok == tkIf || tok == tkElse
|| tok == tkWhile || tok == tkDo || tok == tkLoop || tok == tkFor
|| tok == tkIn || tok == tkBreak || tok == tkContinue || tok == tkReturn
|| tok == tkMatch || tok == tkAs || tok == tkIs || tok == tkNull
|| tok == tkSelf || tok == tkSuper;
}
func lexKeywordKind(text: String) -> int {
if String_Eq(text, "true") { return tkBoolLiteral; }
if String_Eq(text, "false") { return tkBoolLiteral; }
if String_Eq(text, "func") { return tkFunc; }
if String_Eq(text, "let") { return tkLet; }
if String_Eq(text, "var") { return tkVar; }
if String_Eq(text, "const") { return tkConst; }
if String_Eq(text, "type") { return tkType; }
if String_Eq(text, "struct") { return tkStruct; }
if String_Eq(text, "enum") { return tkEnum; }
if String_Eq(text, "union") { return tkUnion; }
if String_Eq(text, "interface") { return tkInterface; }
if String_Eq(text, "extend") { return tkExtend; }
if String_Eq(text, "module") { return tkModule; }
if String_Eq(text, "import") { return tkImport; }
if String_Eq(text, "pub") { return tkPub; }
if String_Eq(text, "extern") { return tkExtern; }
if String_Eq(text, "if") { return tkIf; }
if String_Eq(text, "else") { return tkElse; }
if String_Eq(text, "while") { return tkWhile; }
if String_Eq(text, "do") { return tkDo; }
if String_Eq(text, "loop") { return tkLoop; }
if String_Eq(text, "for") { return tkFor; }
if String_Eq(text, "in") { return tkIn; }
if String_Eq(text, "break") { return tkBreak; }
if String_Eq(text, "continue") { return tkContinue; }
if String_Eq(text, "return") { return tkReturn; }
if String_Eq(text, "match") { return tkMatch; }
if String_Eq(text, "as") { return tkAs; }
if String_Eq(text, "is") { return tkIs; }
if String_Eq(text, "null") { return tkNull; }
if String_Eq(text, "self") { return tkSelf; }
if String_Eq(text, "super") { return tkSuper; }
if String_Eq(text, "sizeof") { return tkSizeOf; }
if String_Eq(text, "discard") { return tkDiscard; }
if String_Eq(text, "async") { return tkAsync; }
if String_Eq(text, "await") { return tkAwait; }
if String_Eq(text, "spawn") { return tkSpawn; }
return tkIdent;
}
func lexScanIdent(lex: *Lexer) {
lexMarkStart(lex);
while !lexIsAtEnd(lex) && Lex_IsIdentChar(lexPeek(lex, 0)) {
discard lexAdvance(lex);
}
let tok: LexToken = lexMakeToken(lex, tkIdent);
let kind: int = lexKeywordKind(tok.text);
tok.kind = kind;
if lex.tokenCount < maxTokens {
lex.tokens[lex.tokenCount] = tok;
lex.tokenCount = lex.tokenCount + 1;
}
}
// ---------------------------------------------------------------------------
// Numbers
// ---------------------------------------------------------------------------
func lexScanDigits(lex: *Lexer) {
while !lexIsAtEnd(lex) && Lex_IsDigit(lexPeek(lex, 0)) {
discard lexAdvance(lex);
}
}
func lexScanHexDigits(lex: *Lexer) {
while !lexIsAtEnd(lex) && Lex_IsHexDigit(lexPeek(lex, 0)) {
discard lexAdvance(lex);
}
}
func lexScanNumber(lex: *Lexer) {
lexMarkStart(lex);
var isFloat: bool = false;
if lexPeek(lex, 0) == 48 { // '0'
let p1: uint32 = lexPeek(lex, 1);
if p1 == 120 || p1 == 88 || p1 == 98 || p1 == 66 || p1 == 111 || p1 == 79 { // x X b B o O
discard lexAdvance(lex); // 0
discard lexAdvance(lex); // prefix
if p1 == 120 || p1 == 88 { lexScanHexDigits(lex); }
else if p1 == 98 || p1 == 66 {
while !lexIsAtEnd(lex) && Lex_IsBinDigit(lexPeek(lex, 0)) {
discard lexAdvance(lex);
}
}
else {
while !lexIsAtEnd(lex) && Lex_IsOctDigit(lexPeek(lex, 0)) {
discard lexAdvance(lex);
}
}
lexEmitToken(lex, tkIntLiteral);
return;
}
}
lexScanDigits(lex);
if lexPeek(lex, 0) == 46 && Lex_IsDigit(lexPeek(lex, 1)) { // . digit
isFloat = true;
discard lexAdvance(lex); // .
lexScanDigits(lex);
}
let e: uint32 = lexPeek(lex, 0);
if e == 101 || e == 69 { // e E
isFloat = true;
discard lexAdvance(lex);
let sign: uint32 = lexPeek(lex, 0);
if sign == 43 || sign == 45 { discard lexAdvance(lex); } // + -
lexScanDigits(lex);
}
if isFloat {
lexEmitToken(lex, tkFloatLiteral);
} else {
lexEmitToken(lex, tkIntLiteral);
}
}
// ---------------------------------------------------------------------------
// Strings and chars
// ---------------------------------------------------------------------------
func lexScanBacktickString(lex: *Lexer) {
lexMarkStart(lex);
if lexPeek(lex, 0) == 96 { discard lexAdvance(lex); } // opening backtick
while !lexIsAtEnd(lex) && lexPeek(lex, 0) != 96 {
discard lexAdvance(lex);
}
if lexIsAtEnd(lex) {
lexEmitDiag(lex, "unterminated backtick string literal");
} else {
discard lexAdvance(lex); // closing backtick
}
lexEmitToken(lex, tkStringLiteral);
}
func lexScanString(lex: *Lexer) {
lexMarkStart(lex);
// Collect the prefix (before opening quote) for the token text
var prefix: String = "";
var prefixLen: int = 0;
if lex.pos > lex.startPos {
let plen: int = lex.pos - lex.startPos;
let pbuf: *char8 = bux_alloc((plen + 1) as uint) as *char8;
var pi: int = 0;
while pi < plen {
pbuf[pi] = lex.source[lex.startPos + pi] as char8;
pi = pi + 1;
}
pbuf[plen] = 0 as char8;
prefix = pbuf;
prefixLen = plen;
}
// Allocate buffer for resolved content (max = remaining source length)
let maxLen: int = 4096;
let resolved: *char8 = bux_alloc(maxLen as uint) as *char8;
var rpos: int = 0;
if lexPeek(lex, 0) == 34 { discard lexAdvance(lex); } // opening "
while !lexIsAtEnd(lex) && lexPeek(lex, 0) != 34 { // closing "
if lexPeek(lex, 0) == 10 { // \n
lexEmitDiag(lex, "unterminated string literal");
break;
}
if lexPeek(lex, 0) == 92 { // backslash
discard lexAdvance(lex);
if !lexIsAtEnd(lex) {
let ec: uint32 = lexAdvance(lex);
var rc: char8 = ec as char8;
if ec == 110 { rc = 10 as char8; } // \n
else if ec == 114 { rc = 13 as char8; } // \r
else if ec == 116 { rc = 9 as char8; } // \t
else if ec == 48 { rc = 0 as char8; } // \0
else if ec == 92 { rc = 92 as char8; } // \\
else if ec == 34 { rc = 34 as char8; } // \"
else if ec == 39 { rc = 39 as char8; } // \'
resolved[rpos] = rc;
rpos = rpos + 1;
}
} else {
let c: uint32 = lexAdvance(lex);
resolved[rpos] = c as char8;
rpos = rpos + 1;
}
}
resolved[rpos] = 0 as char8;
if lexIsAtEnd(lex) {
lexEmitDiag(lex, "unterminated string literal");
} else {
discard lexAdvance(lex); // closing "
}
lexEmitToken(lex, tkStringLiteral);
// Replace token text with resolved version: prefix + " + resolved + "
let totalLen: int = prefixLen + 1 + rpos + 1;
let finalBuf: *char8 = bux_alloc((totalLen + 1) as uint) as *char8;
var fi: int = 0;
var pi2: int = 0;
while pi2 < prefixLen { finalBuf[fi] = prefix[pi2] as char8; fi = fi + 1; pi2 = pi2 + 1; }
finalBuf[fi] = 34 as char8; fi = fi + 1; // opening "
var ri: int = 0;
while ri < rpos { finalBuf[fi] = resolved[ri]; fi = fi + 1; ri = ri + 1; }
finalBuf[fi] = 34 as char8; fi = fi + 1; // closing "
finalBuf[fi] = 0 as char8;
lexSetLastTokenText(lex, finalBuf);
}
func lexScanChar(lex: *Lexer) {
lexMarkStart(lex);
// Collect the prefix for the token text
var prefix: String = "";
var prefixLen: int = 0;
if lex.pos > lex.startPos {
let plen: int = lex.pos - lex.startPos;
let pbuf: *char8 = bux_alloc((plen + 1) as uint) as *char8;
var pi: int = 0;
while pi < plen {
pbuf[pi] = lex.source[lex.startPos + pi] as char8;
pi = pi + 1;
}
pbuf[plen] = 0 as char8;
prefix = pbuf;
prefixLen = plen;
}
var resolved: char8 = 0 as char8;
if lexPeek(lex, 0) == 39 { discard lexAdvance(lex); } // opening '
if lexIsAtEnd(lex) {
lexEmitDiag(lex, "unterminated char literal");
lexEmitToken(lex, tkCharLiteral);
return;
}
if lexPeek(lex, 0) == 10 { // \n
lexEmitDiag(lex, "newline in char literal");
} else if lexPeek(lex, 0) == 92 { // backslash
discard lexAdvance(lex);
if !lexIsAtEnd(lex) {
let ec: uint32 = lexAdvance(lex);
if ec == 110 { resolved = 10 as char8; } // \n
else if ec == 114 { resolved = 13 as char8; } // \r
else if ec == 116 { resolved = 9 as char8; } // \t
else if ec == 48 { resolved = 0 as char8; } // \0
else if ec == 92 { resolved = 92 as char8; } // \\
else if ec == 34 { resolved = 34 as char8; } // \"
else if ec == 39 { resolved = 39 as char8; } // \'
else { resolved = ec as char8; }
}
} else {
resolved = lexAdvance(lex) as char8;
}
if lexIsAtEnd(lex) || lexPeek(lex, 0) != 39 {
lexEmitDiag(lex, "expected closing ' for char literal");
} else {
discard lexAdvance(lex); // closing '
}
lexEmitToken(lex, tkCharLiteral);
// Replace token text with resolved version
let totalLen: int = prefixLen + 1 + 1 + 1;
let finalBuf: *char8 = bux_alloc((totalLen + 1) as uint) as *char8;
var fi: int = 0;
var pi2: int = 0;
while pi2 < prefixLen { finalBuf[fi] = prefix[pi2] as char8; fi = fi + 1; pi2 = pi2 + 1; }
finalBuf[fi] = 39 as char8; fi = fi + 1; // opening '
finalBuf[fi] = resolved; fi = fi + 1; // resolved char
finalBuf[fi] = 39 as char8; fi = fi + 1; // closing '
finalBuf[fi] = 0 as char8;
lexSetLastTokenText(lex, finalBuf);
}
// ---------------------------------------------------------------------------
// Symbols / operators
// ---------------------------------------------------------------------------
func lexScanSymbol(lex: *Lexer) {
lexMarkStart(lex);
let c: uint32 = lexAdvance(lex);
// Single-char tokens
if c == 40 { lexEmitToken(lex, tkLParen); return; }
if c == 41 { lexEmitToken(lex, tkRParen); return; }
if c == 123 { lexEmitToken(lex, tkLBrace); return; }
if c == 125 { lexEmitToken(lex, tkRBrace); return; }
if c == 91 { lexEmitToken(lex, tkLBracket); return; }
if c == 93 { lexEmitToken(lex, tkRBracket); return; }
if c == 44 { lexEmitToken(lex, tkComma); return; }
if c == 59 { lexEmitToken(lex, tkSemicolon); return; }
if c == 64 { lexEmitToken(lex, tkAt); return; }
if c == 63 { lexEmitToken(lex, tkQuestion); return; }
if c == 126 { lexEmitToken(lex, tkTilde); return; }
// : ::
if c == 58 {
if lexMatch(lex, 58) { lexEmitToken(lex, tkColonColon); }
else { lexEmitToken(lex, tkColon); }
return;
}
// . .. ... ..=
if c == 46 {
if lexPeek(lex, 0) == 46 && lexPeek(lex, 1) == 46 {
discard lexAdvance(lex); discard lexAdvance(lex);
lexEmitToken(lex, tkDotDotDot); return;
}
if lexPeek(lex, 0) == 46 && lexPeek(lex, 1) == 61 {
discard lexAdvance(lex); discard lexAdvance(lex);
lexEmitToken(lex, tkDotDotEqual); return;
}
if lexMatch(lex, 46) { lexEmitToken(lex, tkDotDot); return; }
lexEmitToken(lex, tkDot); return;
}
// - -> -- -=
if c == 45 {
if lexMatch(lex, 62) { lexEmitToken(lex, tkArrow); return; }
if lexMatch(lex, 45) { lexEmitToken(lex, tkMinusMinus); return; }
if lexMatch(lex, 61) { lexEmitToken(lex, tkMinusAssign); return; }
lexEmitToken(lex, tkMinus); return;
}
// + ++ +=
if c == 43 {
if lexMatch(lex, 43) { lexEmitToken(lex, tkPlusPlus); return; }
if lexMatch(lex, 61) { lexEmitToken(lex, tkPlusAssign); return; }
lexEmitToken(lex, tkPlus); return;
}
// * ** *=
if c == 42 {
if lexMatch(lex, 42) { lexEmitToken(lex, tkStarStar); return; }
if lexMatch(lex, 61) { lexEmitToken(lex, tkStarAssign); return; }
lexEmitToken(lex, tkStar); return;
}
// / /=
if c == 47 {
if lexMatch(lex, 61) { lexEmitToken(lex, tkSlashAssign); return; }
lexEmitToken(lex, tkSlash); return;
}
// % %=
if c == 37 {
if lexMatch(lex, 61) { lexEmitToken(lex, tkPercentAssign); return; }
lexEmitToken(lex, tkPercent); return;
}
// = == =>
if c == 61 {
if lexMatch(lex, 61) { lexEmitToken(lex, tkEq); return; }
if lexMatch(lex, 62) { lexEmitToken(lex, tkFatArrow); return; }
lexEmitToken(lex, tkAssign); return;
}
// ! !=
if c == 33 {
if lexMatch(lex, 61) { lexEmitToken(lex, tkNe); return; }
lexEmitToken(lex, tkBang); return;
}
// < <= << <<=
if c == 60 {
if lexMatch(lex, 61) { lexEmitToken(lex, tkLe); return; }
if lexMatch(lex, 60) {
if lexMatch(lex, 61) { lexEmitToken(lex, tkShlAssign); return; }
lexEmitToken(lex, tkShl); return;
}
lexEmitToken(lex, tkLt); return;
}
// > >= >> >>=
if c == 62 {
if lexMatch(lex, 61) { lexEmitToken(lex, tkGe); return; }
if lexMatch(lex, 62) {
if lexMatch(lex, 61) { lexEmitToken(lex, tkShrAssign); return; }
lexEmitToken(lex, tkShr); return;
}
lexEmitToken(lex, tkGt); return;
}
// & && &=
if c == 38 {
if lexMatch(lex, 38) { lexEmitToken(lex, tkAmpAmp); return; }
if lexMatch(lex, 61) { lexEmitToken(lex, tkAmpAssign); return; }
lexEmitToken(lex, tkAmp); return;
}
// | || |=
if c == 124 {
if lexMatch(lex, 124) { lexEmitToken(lex, tkPipePipe); return; }
if lexMatch(lex, 61) { lexEmitToken(lex, tkPipeAssign); return; }
lexEmitToken(lex, tkPipe); return;
}
// ^ ^=
if c == 94 {
if lexMatch(lex, 61) { lexEmitToken(lex, tkCaretAssign); return; }
lexEmitToken(lex, tkCaret); return;
}
// # intrinsics
if c == 35 {
if lexMatchStr(lex, "line") { lexEmitToken(lex, tkHashLine); return; }
if lexMatchStr(lex, "column") { lexEmitToken(lex, tkHashColumn); return; }
if lexMatchStr(lex, "file") { lexEmitToken(lex, tkHashFile); return; }
if lexMatchStr(lex, "function") { lexEmitToken(lex, tkHashFunction); return; }
if lexMatchStr(lex, "date") { lexEmitToken(lex, tkHashDate); return; }
if lexMatchStr(lex, "time") { lexEmitToken(lex, tkHashTime); return; }
if lexMatchStr(lex, "module") { lexEmitToken(lex, tkHashModule); return; }
lexEmitToken(lex, tkHash); return;
}
lexEmitDiag(lex, "unexpected character");
lexEmitToken(lex, tkUnknown);
}
// ---------------------------------------------------------------------------
// Next token
// ---------------------------------------------------------------------------
func lexNextToken(lex: *Lexer) {
lexSkipWhitespace(lex);
if lexIsAtEnd(lex) {
lexMarkStart(lex);
lexEmitToken(lex, tkEndOfFile);
return;
}
let c: uint32 = lexPeek(lex, 0);
if c == 10 { // \n
lexMarkStart(lex);
discard lexAdvance(lex);
lexEmitToken(lex, tkNewLine);
return;
}
// String prefixes: c8" c16" c32"
if c == 99 { // 'c'
let d: uint32 = lexPeek(lex, 1);
if d == 56 && lexPeek(lex, 2) == 34 { // c8"
discard lexAdvance(lex); discard lexAdvance(lex); // c 8
lexScanString(lex); return;
}
if d == 49 && lexPeek(lex, 2) == 54 && lexPeek(lex, 3) == 34 { // c16"
discard lexAdvance(lex); discard lexAdvance(lex); discard lexAdvance(lex);
lexScanString(lex); return;
}
if d == 51 && lexPeek(lex, 2) == 50 && lexPeek(lex, 3) == 34 { // c32"
discard lexAdvance(lex); discard lexAdvance(lex); discard lexAdvance(lex);
lexScanString(lex); return;
}
}
if c == 34 { // "
lexScanString(lex); return;
}
// Backtick raw string
if c == 96 { // backtick
lexScanBacktickString(lex); return;
}
// Char prefixes: c8' c16' c32'
if c == 99 { // 'c'
let d: uint32 = lexPeek(lex, 1);
if d == 56 && lexPeek(lex, 2) == 39 { // c8'
discard lexAdvance(lex); discard lexAdvance(lex);
lexScanChar(lex); return;
}
if d == 49 && lexPeek(lex, 2) == 54 && lexPeek(lex, 3) == 39 { // c16'
discard lexAdvance(lex); discard lexAdvance(lex); discard lexAdvance(lex);
lexScanChar(lex); return;
}
if d == 51 && lexPeek(lex, 2) == 50 && lexPeek(lex, 3) == 39 { // c32'
discard lexAdvance(lex); discard lexAdvance(lex); discard lexAdvance(lex);
lexScanChar(lex); return;
}
}
if c == 39 { // '
lexScanChar(lex); return;
}
if Lex_IsIdentStart(c) {
lexScanIdent(lex); return;
}
if Lex_IsDigit(c) {
lexScanNumber(lex); return;
}
lexScanSymbol(lex);
}
// ---------------------------------------------------------------------------
// Tokenize — main entry point
// ---------------------------------------------------------------------------
func Lexer_Tokenize(source: String) -> *Lexer {
let lex: *Lexer = bux_alloc(sizeof(Lexer)) as *Lexer;
lex.source = source;
lex.sourceLen = bux_strlen(source) as int;
lex.pos = 0;
lex.line = 1;
lex.column = 1;
lex.startLine = 0;
lex.startColumn = 0;
lex.startPos = 0;
let tokBuf: *LexToken = bux_alloc(maxTokens as uint * sizeof(LexToken)) as *LexToken;
let diagBuf: *LexerDiag = bux_alloc(maxDiags as uint * sizeof(LexerDiag)) as *LexerDiag;
lex.tokens = tokBuf;
lex.diags = diagBuf;
lex.tokenCount = 0;
lex.diagCount = 0;
while true {
lexNextToken(lex);
if lex.tokenCount >= maxTokens {
lexEmitDiag(lex, "too many tokens");
break;
}
if lex.tokens[lex.tokenCount - 1].kind == tkEndOfFile {
break;
}
}
return lex;
}
func Lexer_TokenCount(lex: *Lexer) -> int {
return lex.tokenCount;
}
func Lexer_GetToken(lex: *Lexer, index: int) -> LexToken {
if index >= 0 && index < lex.tokenCount {
return lex.tokens[index];
}
var empty: LexToken = LexToken { kind: tkEndOfFile, text: "", line: 0, column: 0 };
return empty;
}
func Lexer_DiagCount(lex: *Lexer) -> int {
return lex.diagCount;
}
func Lexer_Free(lex: *Lexer) {
bux_free(lex.tokens as *void);
bux_free(lex.diags as *void);
bux_free(lex as *void);
}
}
-89
View File
@@ -1,89 +0,0 @@
// manifest.bux — Manifest parser for bux.toml files
// Parses package metadata: name, version, type, build output.
module Manifest {
extern func bux_strlen(s: String) -> uint;
// ---------------------------------------------------------------------------
// Manifest struct
// ---------------------------------------------------------------------------
struct Manifest {
name: String;
version: String;
pkgType: String;
output: String;
}
// ---------------------------------------------------------------------------
// Simple TOML parser (handles [Package] and [Build] sections)
// ---------------------------------------------------------------------------
func Manifest_Parse(content: String) -> Manifest {
var m: Manifest;
m.name = "";
m.version = "0.1.0";
m.pkgType = "bin";
m.output = "Bin";
if String_Eq(content, "") { return m; }
var currentSection: String = "";
let count: uint = String_SplitCount(content, "\n");
var i: uint = 0;
while i < count {
let line: String = String_SplitPart(content, "\n", i);
// Skip empty lines and comments
if String_Eq(line, "") { i = i + 1; continue; }
if String_StartsWith(line, "#") { i = i + 1; continue; }
// Section header: [Section]
if String_StartsWith(line, "[") {
if String_StartsWith(line, "[Package]") {
currentSection = "Package";
} else if String_StartsWith(line, "[Build]") {
currentSection = "Build";
} else {
currentSection = "";
}
i = i + 1; continue;
}
// Key = Value
let eqCount: uint = String_SplitCount(line, "=");
if eqCount >= 2 {
let key: String = String_Trim(String_SplitPart(line, "=", 0));
let rawVal: String = String_Trim(String_SplitPart(line, "=", 1));
// Strip quotes from value
var val: String = rawVal;
if String_StartsWith(val, "\"") && String_EndsWith(val, "\"") {
let vlen: uint = bux_strlen(val);
if vlen >= 2 {
val = String_Slice(val, 1, vlen - 2);
}
}
if String_Eq(currentSection, "Package") {
if String_Eq(key, "Name") { m.name = val; }
if String_Eq(key, "Version") { m.version = val; }
if String_Eq(key, "Type") { m.pkgType = val; }
} else if String_Eq(currentSection, "Build") {
if String_Eq(key, "Output") { m.output = val; }
}
}
i = i + 1;
}
return m;
}
// ---------------------------------------------------------------------------
// Load manifest from file
// ---------------------------------------------------------------------------
func Manifest_Load(path: String) -> Manifest {
let content: String = ReadFile(path);
return Manifest_Parse(content);
}
}
File diff suppressed because it is too large Load Diff
-95
View File
@@ -1,95 +0,0 @@
// scope.bux — Symbol table with parent-chain lookup
module Scope {
// Symbol kinds
const skVar: int = 0;
const skFunc: int = 1;
const skType: int = 2;
const skConst: int = 3;
const skModule: int = 4;
// Maximum symbols per scope
const maxSymbols: int = 8192;
struct Symbol {
kind: int;
name: String;
typeKind: int;
typeName: String;
isMutable: bool;
isPublic: bool;
decl: *Decl; // associated declaration (for funcs, structs, enums)
}
struct Scope {
symbols: *Symbol;
count: int;
parent: *Scope;
}
// ---------------------------------------------------------------------------
// Scope operations
// ---------------------------------------------------------------------------
func Scope_New() -> Scope {
let sz: uint = maxSymbols as uint * sizeof(Symbol);
let data: *Symbol = bux_alloc(sz) as *Symbol;
return Scope { symbols: data, count: 0, parent: null as *Scope };
}
func Scope_NewChild(parent: *Scope) -> Scope {
let sz: uint = maxSymbols as uint * sizeof(Symbol);
let data: *Symbol = bux_alloc(sz) as *Symbol;
return Scope { symbols: data, count: 0, parent: parent };
}
func Scope_Define(scope: *Scope, sym: Symbol) -> bool {
// Check local scope for duplicates
var i: int = 0;
while i < scope.count {
if String_Eq(scope.symbols[i].name, sym.name) {
return false;
}
i = i + 1;
}
if scope.count < maxSymbols {
scope.symbols[scope.count] = sym;
scope.count = scope.count + 1;
return true;
}
return false;
}
func Scope_Lookup(scope: *Scope, name: String) -> Symbol {
var cur: *Scope = scope;
while cur != null as *Scope {
var i: int = 0;
while i < cur.count {
if String_Eq(cur.symbols[i].name, name) {
return cur.symbols[i];
}
i = i + 1;
}
cur = cur.parent;
}
var empty: Symbol = Symbol { kind: 0, name: "", typeKind: 0, typeName: "", isMutable: false, isPublic: false, decl: null as *Decl };
return empty;
}
func Scope_LookupLocal(scope: *Scope, name: String) -> Symbol {
var i: int = 0;
while i < scope.count {
if String_Eq(scope.symbols[i].name, name) {
return scope.symbols[i];
}
i = i + 1;
}
var empty: Symbol = Symbol { kind: 0, name: "", typeKind: 0, typeName: "", isMutable: false, isPublic: false, decl: null as *Decl };
return empty;
}
func Scope_Free(scope: *Scope) {
bux_free(scope.symbols as *void);
}
}
-753
View File
@@ -1,753 +0,0 @@
// sema.bux — Semantic analysis (type checker, ported from sema.nim)
// Validates types, resolves identifiers, checks function calls.
module Sema {
// ---------------------------------------------------------------------------
// Sema context
// ---------------------------------------------------------------------------
struct Sema {
module: *Module;
scope: *Scope;
typeTable: *void;
methodTable: *void;
diagCount: int;
diags: *SemaDiag;
hasError: bool;
currentRetType: int; // return type of the function being checked
}
struct SemaDiag {
line: uint32;
column: uint32;
message: String;
}
// ---------------------------------------------------------------------------
// Diagnostics
// ---------------------------------------------------------------------------
func Sema_EmitError(sema: *Sema, line: uint32, col: uint32, msg: String) {
if sema.diagCount < 256 {
sema.diags[sema.diagCount] = SemaDiag { line: line, column: col, message: msg };
sema.diagCount = sema.diagCount + 1;
}
sema.hasError = true;
}
// ---------------------------------------------------------------------------
// Symbol zero-init helper (bootstrap C backend does not zero-init structs)
// ---------------------------------------------------------------------------
func Sema_ZeroInitSymbol(sym: *Symbol) {
sym.kind = 0;
sym.name = "";
sym.typeKind = 0;
sym.typeName = "";
sym.isMutable = false;
sym.isPublic = false;
sym.decl = null as *Decl;
}
// ---------------------------------------------------------------------------
// Type resolution from TypeExpr → Type constants
// ---------------------------------------------------------------------------
func Sema_ResolveType(sema: *Sema, te: *TypeExpr) -> int {
if te == null as *TypeExpr { return tyUnknown; }
let name: String = te.typeName;
if te.kind == tekPointer {
return tyPointer;
}
if String_Eq(name, "void") { return tyVoid; }
if String_Eq(name, "bool") { return tyBool; }
if String_Eq(name, "bool8") { return tyBool8; }
if String_Eq(name, "bool16") { return tyBool16; }
if String_Eq(name, "bool32") { return tyBool32; }
if String_Eq(name, "char8") { return tyChar8; }
if String_Eq(name, "char16") { return tyChar16; }
if String_Eq(name, "char32") { return tyChar32; }
if String_Eq(name, "String") { return tyStr; }
if String_Eq(name, "str") { return tyStr; }
if String_Eq(name, "int8") { return tyInt8; }
if String_Eq(name, "int16") { return tyInt16; }
if String_Eq(name, "int32") { return tyInt32; }
if String_Eq(name, "int64") { return tyInt64; }
if String_Eq(name, "int") { return tyInt; }
if String_Eq(name, "uint8") { return tyUInt8; }
if String_Eq(name, "uint16") { return tyUInt16; }
if String_Eq(name, "uint32") { return tyUInt32; }
if String_Eq(name, "uint64") { return tyUInt64; }
if String_Eq(name, "uint") { return tyUInt; }
if String_Eq(name, "float32") { return tyFloat32; }
if String_Eq(name, "float64") { return tyFloat64; }
if String_Eq(name, "float") { return tyFloat64; }
// Check type table for user-defined types (StringMap not yet supported)
// TODO: re-enable when StringMap generic is available
// if sema.typeTable != null as *void { ... }
return tyNamed; // assume named type
}
// ---------------------------------------------------------------------------
// Type predicates
// ---------------------------------------------------------------------------
func Sema_IsNumeric(kind: int) -> bool {
if kind == tyUnknown || kind == tyNamed || kind == tyTypeParam { return true; }
if kind == tyInt8 || kind == tyInt16 || kind == tyInt32 || kind == tyInt64 || kind == tyInt { return true; }
if kind == tyUInt8 || kind == tyUInt16 || kind == tyUInt32 || kind == tyUInt64 || kind == tyUInt { return true; }
if kind == tyFloat32 || kind == tyFloat64 { return true; }
return false;
}
func Sema_IsBool(kind: int) -> bool {
return kind == tyBool || kind == tyBool8 || kind == tyBool16 || kind == tyBool32;
}
func Sema_TypeName(kind: int) -> String {
if kind == tyUnknown { return "?"; }
if kind == tyVoid { return "void"; }
if kind == tyBool { return "bool"; }
if kind == tyInt{ return "int"; }
if kind == tyInt64 { return "int64"; }
if kind == tyUInt { return "uint"; }
if kind == tyFloat64 { return "float64"; }
if kind == tyStr { return "String"; }
if kind == tyPointer { return "*ptr"; }
if kind == tyNamed { return "user-type"; }
if kind == tyTypeParam { return "type-param"; }
return "?";
}
// ---------------------------------------------------------------------------
// Block checking helper
// ---------------------------------------------------------------------------
func Sema_CheckBlock(sema: *Sema, block: *Block) {
if block == null as *Block { return; }
var blockScope: Scope = Scope_NewChild(sema.scope);
let prevScope: *Scope = sema.scope;
sema.scope = &blockScope;
var stmt: *Stmt = block.firstStmt;
while stmt != null as *Stmt {
Sema_CheckStmt(sema, stmt);
stmt = stmt.nextStmt;
}
sema.scope = prevScope;
}
// ---------------------------------------------------------------------------
// Expression type checking
// ---------------------------------------------------------------------------
func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
if expr == null as *Expr { return tyUnknown; }
let kind: int = expr.kind;
// Debug: print expr kind
// Print("Sema_CheckExpr kind=");
// PrintInt(kind as int64);
// PrintLine("");
// Literal
if kind == ekLiteral {
let tk: int = expr.tokKind;
if tk == tkIntLiteral { return tyInt; }
if tk == tkFloatLiteral { return tyFloat64; }
if tk == tkStringLiteral { return tyStr; }
if tk == tkBoolLiteral { return tyBool; }
if tk == tkCharLiteral { return tyChar32; }
if tk == tkNull { return tyPointer; }
return tyUnknown;
}
// Identifier — look up in scope
if kind == ekIdent {
let sym: Symbol = Scope_Lookup(sema.scope, expr.strValue);
if sym.kind == 0 && !String_Eq(sym.name, expr.strValue) {
Sema_EmitError(sema, expr.line, expr.column, "undeclared identifier");
return tyUnknown;
}
if sym.typeName != null as String && !String_Eq(sym.typeName, "") {
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
te.kind = tekNamed;
te.typeName = sym.typeName;
expr.refType = te;
}
return sym.typeKind;
}
// self
if kind == ekSelf {
let sym: Symbol = Scope_Lookup(sema.scope, "self");
if sym.kind == 0 && !String_Eq(sym.name, "self") {
Sema_EmitError(sema, expr.line, expr.column, "self outside method");
return tyUnknown;
}
return sym.typeKind;
}
// Binary
if kind == ekBinary {
let left: int = Sema_CheckExpr(sema, expr.child1);
let right: int = Sema_CheckExpr(sema, expr.child2);
let op: int = expr.intValue;
// Assignment operators return target type
if op == tkAssign || (op >= tkPlusAssign && op <= tkShrAssign) {
return left;
}
// Comparison operators return bool
if op >= tkEq && op <= tkGe { return tyBool; }
// Logical operators return bool
if op == tkAmpAmp || op == tkPipePipe || op == tkBang { return tyBool; }
// Arithmetic returns wider type
if !Sema_IsNumeric(left) || !Sema_IsNumeric(right) {
Sema_EmitError(sema, expr.line, expr.column, "arithmetic requires numeric operands");
}
if left == tyFloat64 || right == tyFloat64 { return tyFloat64; }
return tyInt;
}
// Unary
if kind == ekUnary {
let operand: int = Sema_CheckExpr(sema, expr.child1);
let op: int = expr.intValue;
if op == tkBang { return tyBool; }
if op == tkStar { return tyUnknown; } // dereference — resolve pointee
if op == tkAmp { return tyPointer; }
return operand;
}
// Assign
if kind == ekAssign {
let target: int = Sema_CheckExpr(sema, expr.child1);
let value: int = Sema_CheckExpr(sema, expr.child2);
// Basic type compatibility (permissive for now)
return target;
}
// Call
if kind == ekCall {
discard Sema_CheckExpr(sema, expr.child1);
var arg: *ExprList = expr.callArgs;
while arg != null as *ExprList {
discard Sema_CheckExpr(sema, arg.expr);
arg = arg.next;
}
// Try to resolve return type from function declaration
if expr.child1.kind == ekIdent {
let sym: Symbol = Scope_Lookup(sema.scope, expr.child1.strValue);
if sym.kind == skFunc && sym.decl != null as *Decl && sym.decl.retType != null as *TypeExpr {
return Sema_ResolveType(sema, sym.decl.retType);
}
}
return tyUnknown;
}
// Ternary
if kind == ekTernary {
return Sema_CheckExpr(sema, expr.child2); // then type
}
// Cast — return target type
if kind == ekCast {
if expr.refType != null as *TypeExpr {
return Sema_ResolveType(sema, expr.refType);
}
return tyUnknown;
}
// Try (?)
if kind == ekTry {
let inner: int = Sema_CheckExpr(sema, expr.child1);
return inner; // simplified
}
// Struct init: TypeName { field: value, ... }
if kind == ekStructInit {
return tyNamed;
}
// Field access
if kind == ekField {
discard Sema_CheckExpr(sema, expr.child1);
return tyUnknown;
}
// Index
if kind == ekIndex {
let obj: int = Sema_CheckExpr(sema, expr.child1);
let idx: int = Sema_CheckExpr(sema, expr.child2);
if !Sema_IsNumeric(idx) && idx != tyUnknown {
Sema_EmitError(sema, expr.line, expr.column, "index must be integer");
}
return tyUnknown;
}
// Block expression
if kind == ekBlock {
if expr.refBlock != null as *Block {
Sema_CheckBlock(sema, expr.refBlock);
}
return tyVoid;
}
return tyUnknown;
}
// ---------------------------------------------------------------------------
// Statement checking
// ---------------------------------------------------------------------------
func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) {
if stmt == null as *Stmt { return; }
let kind: int = stmt.kind;
// Let/var
if kind == skLet {
let initType: int = Sema_CheckExpr(sema, stmt.child1);
// Register variable in scope
var sym: Symbol;
sym.kind = skVar;
sym.name = stmt.strValue;
sym.typeKind = initType;
sym.typeName = "";
if stmt.refStmtType != null as *TypeExpr {
sym.typeName = stmt.refStmtType.typeName;
}
sym.isMutable = stmt.boolValue;
sym.isPublic = false;
sym.decl = null as *Decl;
discard Scope_Define(sema.scope, sym);
return;
}
// Return
if kind == skReturn {
if stmt.child1 != null as *Expr {
let retType: int = Sema_CheckExpr(sema, stmt.child1);
if sema.currentRetType != tyUnknown && retType != tyUnknown {
if retType != sema.currentRetType {
// Be permissive: allow numeric widening, pointer compatibility
if !Sema_IsNumeric(retType) || !Sema_IsNumeric(sema.currentRetType) {
// Allow pointer-type compatibility (String <-> *T, *T <-> *U)
let retIsPtr: bool = retType == tyPointer || retType == tyStr;
let expectedIsPtr: bool = sema.currentRetType == tyPointer || sema.currentRetType == tyStr;
if !retIsPtr || !expectedIsPtr {
Sema_EmitError(sema, stmt.line, stmt.column, "return type mismatch");
}
}
}
}
} else {
if sema.currentRetType != tyVoid && sema.currentRetType != tyUnknown {
Sema_EmitError(sema, stmt.line, stmt.column, "missing return value");
}
}
return;
}
// If
if kind == skIf {
let condType: int = Sema_CheckExpr(sema, stmt.child1);
if !Sema_IsBool(condType) && condType != tyUnknown {
Sema_EmitError(sema, stmt.line, stmt.column, "if condition must be bool");
}
Sema_CheckBlock(sema, stmt.refStmtBlock);
Sema_CheckBlock(sema, stmt.refStmtElse);
return;
}
// While
if kind == skWhile {
let condType: int = Sema_CheckExpr(sema, stmt.child1);
if !Sema_IsBool(condType) && condType != tyUnknown {
Sema_EmitError(sema, stmt.line, stmt.column, "while condition must be bool");
}
Sema_CheckBlock(sema, stmt.refStmtBlock);
return;
}
// Do-while
if kind == skDoWhile {
Sema_CheckBlock(sema, stmt.refStmtBlock);
let condType: int = Sema_CheckExpr(sema, stmt.child1);
if !Sema_IsBool(condType) && condType != tyUnknown {
Sema_EmitError(sema, stmt.line, stmt.column, "do-while condition must be bool");
}
return;
}
// Loop
if kind == skLoop {
Sema_CheckBlock(sema, stmt.refStmtBlock);
return;
}
// For
if kind == skFor {
discard Sema_CheckExpr(sema, stmt.child1);
var forScope: Scope = Scope_NewChild(sema.scope);
var loopSym: Symbol;
loopSym.kind = skVar;
loopSym.name = stmt.strValue;
loopSym.typeKind = tyUnknown;
loopSym.typeName = "";
loopSym.isMutable = true;
loopSym.isPublic = false;
loopSym.decl = null as *Decl;
discard Scope_Define(&forScope, loopSym);
let prevScope: *Scope = sema.scope;
sema.scope = &forScope;
Sema_CheckBlock(sema, stmt.refStmtBlock);
sema.scope = prevScope;
return;
}
// Match
if kind == skMatch {
discard Sema_CheckExpr(sema, stmt.child1);
return;
}
// Break / Continue
if kind == skBreak || kind == skContinue {
return;
}
// Expression statement
if kind == skExpr && stmt.child1 != null as *Expr {
discard Sema_CheckExpr(sema, stmt.child1);
return;
}
// Decl (nested)
if kind == skDecl {
if stmt.refStmtDecl != null as *Decl && stmt.refStmtDecl.kind == dkFunc {
Sema_EmitError(sema, stmt.line, stmt.column, "nested functions not yet supported");
}
return;
}
}
// ---------------------------------------------------------------------------
// Collect globals (register functions, structs, enums in scope)
// ---------------------------------------------------------------------------
func Sema_CollectGlobals(sema: *Sema) {
var decl: *Decl = sema.module.firstItem;
var funcCount: int = 0;
while decl != null as *Decl {
let dk: int = decl.kind;
// Function
if dk == dkFunc {
var sym: Symbol;
Sema_ZeroInitSymbol(&sym);
sym.kind = skFunc;
sym.name = decl.strValue;
sym.typeKind = tyFunc;
sym.isPublic = decl.isPublic;
sym.decl = decl;
discard Scope_Define(sema.scope, sym);
}
// Struct
if dk == dkStruct {
var sym: Symbol;
Sema_ZeroInitSymbol(&sym);
sym.kind = skType;
sym.name = decl.strValue;
sym.typeKind = tyNamed;
sym.isPublic = decl.isPublic;
sym.decl = decl;
discard Scope_Define(sema.scope, sym);
}
// Enum
if dk == dkEnum {
var sym: Symbol;
Sema_ZeroInitSymbol(&sym);
sym.kind = skType;
sym.name = decl.strValue;
sym.typeKind = tyNamed;
sym.isPublic = decl.isPublic;
sym.decl = decl;
discard Scope_Define(sema.scope, sym);
// Register enum variants as constants
var vi: int = 0;
while vi < decl.variantCount && vi < 8 {
var v: EnumVariant;
if vi == 0 { v = decl.variant0; }
else if vi == 1 { v = decl.variant1; }
else if vi == 2 { v = decl.variant2; }
else if vi == 3 { v = decl.variant3; }
else if vi == 4 { v = decl.variant4; }
else if vi == 5 { v = decl.variant5; }
else if vi == 6 { v = decl.variant6; }
else if vi == 7 { v = decl.variant7; }
let variantName: String = String_Concat(decl.strValue, "_");
let variantName2: String = String_Concat(variantName, v.name);
var vSym: Symbol;
vSym.kind = skConst;
vSym.name = variantName2;
vSym.typeKind = tyNamed;
vSym.typeName = String_Concat(decl.strValue, "_Tag");
vSym.isMutable = false;
vSym.isPublic = decl.isPublic;
vSym.decl = decl;
discard Scope_Define(sema.scope, vSym);
vi = vi + 1;
}
}
// Const
if dk == dkConst {
var sym: Symbol;
Sema_ZeroInitSymbol(&sym);
sym.kind = skConst;
sym.name = decl.strValue;
if decl.constType != null as *TypeExpr {
sym.typeKind = Sema_ResolveType(sema, decl.constType);
sym.typeName = decl.constType.typeName;
} else {
sym.typeKind = tyInt;
}
sym.isMutable = false;
sym.isPublic = decl.isPublic;
sym.decl = decl;
discard Scope_Define(sema.scope, sym);
}
// Use (import)
if dk == dkUse {
if decl.useKind == 2 {
// Multi-import: names are comma-separated in useNames
// For now, register the whole useNames string as a single func name
// (permissive fallback — real fix needs string split)
let namesStr: String = decl.useNames;
if !String_Eq(namesStr, "") {
// Simple split by comma using available string functions
var start: uint = 0;
var pos: uint = 0;
let totalLen: uint = String_Len(namesStr);
while pos <= totalLen {
let atEnd: bool = pos == totalLen;
let isComma: bool = false;
if pos < totalLen {
// Check if character at pos is comma
let chStr: String = bux_str_slice(namesStr, pos, 1);
isComma = String_Eq(chStr, ",");
}
if atEnd || isComma {
let nameLen: uint = pos - start;
if nameLen > 0 {
let name: String = bux_str_slice(namesStr, start, nameLen);
var sym: Symbol;
sym.kind = skFunc;
sym.name = name;
sym.typeKind = tyUnknown;
sym.typeName = "";
sym.isMutable = false;
sym.isPublic = true;
sym.decl = null as *Decl;
discard Scope_Define(sema.scope, sym);
}
start = pos + 1;
}
pos = pos + 1;
}
}
} else {
// Single import or glob — add last path segment
let path: String = decl.usePath;
if !String_Eq(path, "") {
// Find last :: segment
var lastSeg: String = path;
let containsColons: int = bux_str_contains(path, "::");
if containsColons != 0 {
// Try to get last segment by finding last ::
// Use a simple heuristic: slice from various positions
let pathLen: uint = String_Len(path);
var tryPos: uint = 0;
while tryPos < pathLen {
let slice: String = bux_str_slice(path, tryPos, pathLen - tryPos);
if String_StartsWith(slice, "::") {
lastSeg = bux_str_slice(slice, 2, String_Len(slice) - 2);
}
tryPos = tryPos + 1;
}
}
var sym: Symbol;
sym.kind = skFunc;
sym.name = lastSeg;
sym.typeKind = tyUnknown;
sym.typeName = "";
sym.isMutable = false;
sym.isPublic = true;
sym.decl = null as *Decl;
discard Scope_Define(sema.scope, sym);
}
}
}
// Const
if dk == dkConst {
var sym: Symbol;
Sema_ZeroInitSymbol(&sym);
sym.kind = skConst;
sym.name = decl.strValue;
sym.typeKind = tyUnknown;
sym.isPublic = decl.isPublic;
sym.decl = decl;
discard Scope_Define(sema.scope, sym);
}
// Extern function
if dk == dkExternFunc {
var sym: Symbol;
Sema_ZeroInitSymbol(&sym);
sym.kind = skFunc;
sym.name = decl.strValue;
sym.typeKind = tyFunc;
sym.isPublic = true;
sym.decl = decl;
discard Scope_Define(sema.scope, sym);
}
decl = decl.childDecl2;
}
}
// ---------------------------------------------------------------------------
// Analyze — main entry point
// ---------------------------------------------------------------------------
func Sema_Analyze(mod: *Module) -> *Sema {
let s: *Sema = bux_alloc(sizeof(Sema)) as *Sema;
s.module = mod;
s.scope = bux_alloc(sizeof(Scope)) as *Scope;
s.scope.symbols = bux_alloc(1024 as uint * sizeof(Symbol)) as *Symbol;
s.scope.count = 0;
s.scope.parent = null as *Scope;
s.hasError = false;
s.diagCount = 0;
s.diags = bux_alloc(256 as uint * sizeof(SemaDiag)) as *SemaDiag;
s.typeTable = null as *void;
s.methodTable = null as *void;
s.currentRetType = tyVoid;
// First pass: collect globals
Sema_CollectGlobals(s);
// Second pass: check function bodies
var decl: *Decl = mod.firstItem;
while decl != null as *Decl {
if decl.kind == dkFunc && decl.refBody != null as *Block {
// Create function scope (child of global)
var funcScope: Scope = Scope_NewChild(s.scope);
// Add type params to scope
if decl.typeParamCount >= 1 {
var tpSym: Symbol;
Sema_ZeroInitSymbol(&tpSym);
tpSym.kind = skType;
tpSym.name = decl.typeParam0;
tpSym.typeKind = tyTypeParam;
tpSym.decl = null as *Decl;
discard Scope_Define(&funcScope, tpSym);
}
if decl.typeParamCount >= 2 {
var tpSym2: Symbol;
Sema_ZeroInitSymbol(&tpSym2);
tpSym2.kind = skType;
tpSym2.name = decl.typeParam1;
tpSym2.typeKind = tyTypeParam;
tpSym2.decl = null as *Decl;
discard Scope_Define(&funcScope, tpSym2);
}
// Add parameters to scope
var i: int = 0;
while i < decl.paramCount {
var p: *Param = null as *Param;
if i == 0 { p = &decl.param0; }
else if i == 1 { p = &decl.param1; }
else if i == 2 { p = &decl.param2; }
else if i == 3 { p = &decl.param3; }
else if i == 4 { p = &decl.param4; }
else if i == 5 { p = &decl.param5; }
else if i == 6 { p = &decl.param6; }
else if i == 7 { p = &decl.param7; }
else if i == 8 { p = &decl.param8; }
var pSym: Symbol;
Sema_ZeroInitSymbol(&pSym);
pSym.kind = skVar;
if p != null as *Param && p.refParamType != null as *TypeExpr {
pSym.typeKind = Sema_ResolveType(s, p.refParamType);
pSym.typeName = p.refParamType.typeName;
} else {
pSym.typeKind = tyInt;
pSym.typeName = "";
}
pSym.isMutable = false;
pSym.isPublic = false;
pSym.decl = null as *Decl;
if i == 0 { pSym.name = decl.param0.name; }
else if i == 1 { pSym.name = decl.param1.name; }
else if i == 2 { pSym.name = decl.param2.name; }
else if i == 3 { pSym.name = decl.param3.name; }
else if i == 4 { pSym.name = decl.param4.name; }
else if i == 5 { pSym.name = decl.param5.name; }
else if i == 6 { pSym.name = decl.param6.name; }
else if i == 7 { pSym.name = decl.param7.name; }
else if i == 8 { pSym.name = decl.param8.name; }
discard Scope_Define(&funcScope, pSym);
i = i + 1;
}
// Switch to function scope and check body statements
let prevScope: *Scope = s.scope;
s.scope = &funcScope;
// Set current function return type
if decl.retType != null as *TypeExpr {
s.currentRetType = Sema_ResolveType(s, decl.retType);
} else {
s.currentRetType = tyVoid;
}
// Check body statements
var stmt: *Stmt = decl.refBody.firstStmt;
while stmt != null as *Stmt {
Sema_CheckStmt(s, stmt);
stmt = stmt.nextStmt;
}
s.scope = prevScope;
}
decl = decl.childDecl2;
}
return s;
}
func Sema_HasError(sema: *Sema) -> bool {
return sema.hasError;
}
func Sema_DiagCount(sema: *Sema) -> int {
return sema.diagCount;
}
func Sema_Free(sema: *Sema) {
bux_free(sema.scope.symbols as *void);
bux_free(sema.scope as *void);
bux_free(sema.diags as *void);
bux_free(sema as *void);
}
}
-13
View File
@@ -1,13 +0,0 @@
// source_location.bux — Source position tracking
module SourceLocation {
struct SourceLocation {
line: uint32;
column: uint32;
offset: uint32;
}
func SourceLocation_New(line: uint32, column: uint32, offset: uint32) -> SourceLocation {
return SourceLocation { line: line, column: column, offset: offset };
}
}
-322
View File
@@ -1,322 +0,0 @@
// token.bux — Token kinds and helpers
module Token {
// ---------------------------------------------------------------------------
// TokenKind enum
// ---------------------------------------------------------------------------
// Literals
const tkIntLiteral: int = 0;
const tkFloatLiteral: int = 1;
const tkStringLiteral: int = 2;
const tkCharLiteral: int = 3;
const tkBoolLiteral: int = 4;
// Identifiers
const tkIdent: int = 5;
const tkUnderscore: int = 6;
// Control flow keywords
const tkIf: int = 7;
const tkElse: int = 8;
const tkWhile: int = 9;
const tkDo: int = 10;
const tkLoop: int = 11;
const tkFor: int = 12;
const tkIn: int = 13;
const tkBreak: int = 14;
const tkContinue: int = 15;
const tkReturn: int = 16;
const tkMatch: int = 17;
// Declaration keywords
const tkFunc: int = 18;
const tkLet: int = 19;
const tkVar: int = 20;
const tkConst: int = 21;
const tkType: int = 22;
const tkStruct: int = 23;
const tkEnum: int = 24;
const tkUnion: int = 25;
const tkInterface: int = 26;
const tkExtend: int = 27;
const tkModule: int = 28;
const tkImport: int = 29;
const tkPub: int = 30;
const tkExtern: int = 31;
// Other keywords
const tkAs: int = 32;
const tkIs: int = 33;
const tkNull: int = 34;
const tkSelf: int = 35;
const tkSuper: int = 36;
const tkSizeOf: int = 37;
// Punctuation
const tkLParen: int = 38;
const tkRParen: int = 39;
const tkLBrace: int = 40;
const tkRBrace: int = 41;
const tkLBracket: int = 42;
const tkRBracket: int = 43;
const tkComma: int = 44;
const tkSemicolon: int = 45;
const tkColon: int = 46;
const tkColonColon: int = 47;
const tkDot: int = 48;
const tkDotDot: int = 49;
const tkDotDotDot: int = 50;
const tkDotDotEqual: int = 51;
const tkArrow: int = 52;
const tkFatArrow: int = 53;
const tkAt: int = 54;
const tkHash: int = 55;
const tkQuestion: int = 56;
// Arithmetic operators
const tkPlus: int = 57;
const tkMinus: int = 58;
const tkStar: int = 59;
const tkSlash: int = 60;
const tkPercent: int = 61;
const tkStarStar: int = 62;
const tkPlusPlus: int = 63;
const tkMinusMinus: int = 64;
// Bitwise operators
const tkAmp: int = 65;
const tkPipe: int = 66;
const tkCaret: int = 67;
const tkTilde: int = 68;
const tkShl: int = 69;
const tkShr: int = 70;
// Logical operators
const tkAmpAmp: int = 71;
const tkPipePipe: int = 72;
const tkBang: int = 73;
// Comparison operators
const tkEq: int = 74;
const tkNe: int = 75;
const tkLt: int = 76;
const tkLe: int = 77;
const tkGt: int = 78;
const tkGe: int = 79;
// Assignment operators
const tkAssign: int = 80;
const tkPlusAssign: int = 81;
const tkMinusAssign: int = 82;
const tkStarAssign: int = 83;
const tkSlashAssign: int = 84;
const tkPercentAssign: int = 85;
const tkAmpAssign: int = 86;
const tkPipeAssign: int = 87;
const tkCaretAssign: int = 88;
const tkShlAssign: int = 89;
const tkShrAssign: int = 90;
// Compile-time intrinsics
const tkHashLine: int = 91;
const tkHashColumn: int = 92;
const tkHashFile: int = 93;
const tkHashFunction: int = 94;
const tkHashDate: int = 95;
const tkHashTime: int = 96;
const tkHashModule: int = 97;
// Special
const tkOwn: int = 98;
const tkNewLine: int = 99;
const tkEndOfFile: int = 100;
const tkUnknown: int = 101;
// Async / concurrency
const tkAsync: int = 102;
const tkAwait: int = 103;
const tkSpawn: int = 104;
const tkDiscard: int = 105;
// ---------------------------------------------------------------------------
// Token struct
// ---------------------------------------------------------------------------
struct Token {
kind: int;
text: String;
line: uint32;
column: uint32;
offset: uint32;
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
func Token_IsKeyword(kind: int) -> bool {
if kind >= tkIf && kind <= tkIn { return true; }
if kind >= tkBreak && kind <= tkMatch { return true; }
if kind >= tkFunc && kind <= tkExtern { return true; }
if kind >= tkAs && kind <= tkSuper { return true; }
if kind == tkSizeOf { return true; }
if kind >= tkAsync && kind <= tkSpawn { return true; }
return false;
}
func Token_IsLiteral(kind: int) -> bool {
if kind >= tkIntLiteral && kind <= tkBoolLiteral { return true; }
return false;
}
func Token_IsOperator(kind: int) -> bool {
if kind >= tkPlus && kind <= tkShrAssign { return true; }
return false;
}
func Token_IsEof(kind: int) -> bool {
return kind == tkEndOfFile;
}
func Token_KeywordKind(text: String) -> int {
if String_Eq(text, "func") { return tkFunc; }
if String_Eq(text, "let") { return tkLet; }
if String_Eq(text, "var") { return tkVar; }
if String_Eq(text, "const") { return tkConst; }
if String_Eq(text, "type") { return tkType; }
if String_Eq(text, "struct") { return tkStruct; }
if String_Eq(text, "enum") { return tkEnum; }
if String_Eq(text, "union") { return tkUnion; }
if String_Eq(text, "interface") { return tkInterface; }
if String_Eq(text, "extend") { return tkExtend; }
if String_Eq(text, "module") { return tkModule; }
if String_Eq(text, "import") { return tkImport; }
if String_Eq(text, "pub") { return tkPub; }
if String_Eq(text, "extern") { return tkExtern; }
if String_Eq(text, "if") { return tkIf; }
if String_Eq(text, "else") { return tkElse; }
if String_Eq(text, "while") { return tkWhile; }
if String_Eq(text, "do") { return tkDo; }
if String_Eq(text, "loop") { return tkLoop; }
if String_Eq(text, "for") { return tkFor; }
if String_Eq(text, "in") { return tkIn; }
if String_Eq(text, "break") { return tkBreak; }
if String_Eq(text, "continue") { return tkContinue; }
if String_Eq(text, "return") { return tkReturn; }
if String_Eq(text, "match") { return tkMatch; }
if String_Eq(text, "as") { return tkAs; }
if String_Eq(text, "is") { return tkIs; }
if String_Eq(text, "null") { return tkNull; }
if String_Eq(text, "self") { return tkSelf; }
if String_Eq(text, "super") { return tkSuper; }
if String_Eq(text, "sizeof") { return tkSizeOf; }
if String_Eq(text, "true") { return tkBoolLiteral; }
if String_Eq(text, "false") { return tkBoolLiteral; }
return tkIdent;
}
func Token_KindName(kind: int) -> String {
if kind == tkIntLiteral { return "integer literal"; }
if kind == tkFloatLiteral { return "float literal"; }
if kind == tkStringLiteral { return "string literal"; }
if kind == tkCharLiteral { return "char literal"; }
if kind == tkBoolLiteral { return "boolean literal"; }
if kind == tkIdent { return "identifier"; }
if kind == tkUnderscore { return "_"; }
if kind == tkSizeOf { return "sizeof"; }
if kind == tkIf { return "if"; }
if kind == tkElse { return "else"; }
if kind == tkWhile { return "while"; }
if kind == tkDo { return "do"; }
if kind == tkLoop { return "loop"; }
if kind == tkFor { return "for"; }
if kind == tkIn { return "in"; }
if kind == tkBreak { return "break"; }
if kind == tkContinue { return "continue"; }
if kind == tkReturn { return "return"; }
if kind == tkMatch { return "match"; }
if kind == tkFunc { return "func"; }
if kind == tkLet { return "let"; }
if kind == tkVar { return "var"; }
if kind == tkConst { return "const"; }
if kind == tkType { return "type"; }
if kind == tkStruct { return "struct"; }
if kind == tkEnum { return "enum"; }
if kind == tkUnion { return "union"; }
if kind == tkInterface { return "interface"; }
if kind == tkExtend { return "extend"; }
if kind == tkModule { return "module"; }
if kind == tkImport { return "import"; }
if kind == tkPub { return "pub"; }
if kind == tkExtern { return "extern"; }
if kind == tkAs { return "as"; }
if kind == tkIs { return "is"; }
if kind == tkNull { return "null"; }
if kind == tkSelf { return "self"; }
if kind == tkSuper { return "super"; }
if kind == tkLParen { return "("; }
if kind == tkRParen { return ")"; }
if kind == tkLBrace { return "{"; }
if kind == tkRBrace { return "}"; }
if kind == tkLBracket { return "["; }
if kind == tkRBracket { return "]"; }
if kind == tkComma { return ","; }
if kind == tkSemicolon { return ";"; }
if kind == tkColon { return ":"; }
if kind == tkColonColon { return "::"; }
if kind == tkDot { return "."; }
if kind == tkDotDot { return ".."; }
if kind == tkDotDotDot { return "..."; }
if kind == tkDotDotEqual { return "..="; }
if kind == tkArrow { return "->"; }
if kind == tkFatArrow { return "=>"; }
if kind == tkAt { return "@"; }
if kind == tkHash { return "#"; }
if kind == tkQuestion { return "?"; }
if kind == tkPlus { return "+"; }
if kind == tkMinus { return "-"; }
if kind == tkStar { return "*"; }
if kind == tkSlash { return "/"; }
if kind == tkPercent { return "%"; }
if kind == tkStarStar { return "**"; }
if kind == tkPlusPlus { return "++"; }
if kind == tkMinusMinus { return "--"; }
if kind == tkAmp { return "&"; }
if kind == tkPipe { return "|"; }
if kind == tkCaret { return "^"; }
if kind == tkTilde { return "~"; }
if kind == tkShl { return "<<"; }
if kind == tkShr { return ">>"; }
if kind == tkAmpAmp { return "&&"; }
if kind == tkPipePipe { return "||"; }
if kind == tkBang { return "!"; }
if kind == tkEq { return "=="; }
if kind == tkNe { return "!="; }
if kind == tkLt { return "<"; }
if kind == tkLe { return "<="; }
if kind == tkGt { return ">"; }
if kind == tkGe { return ">="; }
if kind == tkAssign { return "="; }
if kind == tkPlusAssign { return "+="; }
if kind == tkMinusAssign { return "-="; }
if kind == tkStarAssign { return "*="; }
if kind == tkSlashAssign { return "/="; }
if kind == tkPercentAssign { return "%="; }
if kind == tkAmpAssign { return "&="; }
if kind == tkPipeAssign { return "|="; }
if kind == tkCaretAssign { return "^="; }
if kind == tkShlAssign { return "<<="; }
if kind == tkShrAssign { return ">>="; }
if kind == tkHashLine { return "#line"; }
if kind == tkHashColumn { return "#column"; }
if kind == tkHashFile { return "#file"; }
if kind == tkHashFunction { return "#function"; }
if kind == tkHashDate { return "#date"; }
if kind == tkHashTime { return "#time"; }
if kind == tkHashModule { return "#module"; }
if kind == tkNewLine { return "newline"; }
if kind == tkEndOfFile { return "end of file"; }
return "unknown token";
}
}
-188
View File
@@ -1,188 +0,0 @@
// types.bux — Type system definitions and factories
module Types {
// ---------------------------------------------------------------------------
// TypeKind constants
// ---------------------------------------------------------------------------
const tyUnknown: int = 0;
const tyVoid: int = 1;
const tyBool: int = 2;
const tyBool8: int = 3;
const tyBool16: int = 4;
const tyBool32: int = 5;
const tyChar8: int = 6;
const tyChar16: int = 7;
const tyChar32: int = 8;
const tyStr: int = 9;
const tyInt8: int = 10;
const tyInt16: int = 11;
const tyInt32: int = 12;
const tyInt64: int = 13;
const tyInt: int = 14;
const tyUInt8: int = 15;
const tyUInt16: int = 16;
const tyUInt32: int = 17;
const tyUInt64: int = 18;
const tyUInt: int = 19;
const tyFloat32: int = 20;
const tyFloat64: int = 21;
const tyPointer: int = 22;
const tySlice: int = 23;
const tyRange: int = 24;
const tyTuple: int = 25;
const tyNamed: int = 26;
const tyTypeParam: int = 27;
const tyFunc: int = 28;
// ---------------------------------------------------------------------------
// Type struct
// ---------------------------------------------------------------------------
struct Type {
kind: int;
name: String;
// inner types stored as array of pointers (simplified)
innerKind1: int;
innerName1: String;
innerKind2: int;
innerName2: String;
innerKind3: int;
innerName3: String;
innerCount: int;
}
// ---------------------------------------------------------------------------
// Factories
// ---------------------------------------------------------------------------
func Type_MakeUnknown() -> Type {
return Type { kind: tyUnknown, name: "", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeVoid() -> Type {
return Type { kind: tyVoid, name: "void", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeBool() -> Type {
return Type { kind: tyBool, name: "bool", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeInt() -> Type {
return Type { kind: tyInt, name: "int", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeInt64() -> Type {
return Type { kind: tyInt64, name: "int64", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeUInt() -> Type {
return Type { kind: tyUInt, name: "uint", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeFloat64() -> Type {
return Type { kind: tyFloat64, name: "float64", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeStr() -> Type {
return Type { kind: tyStr, name: "String", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakePointer(pointee: Type) -> Type {
return Type { kind: tyPointer, name: "", innerCount: 1,
innerKind1: pointee.kind, innerName1: pointee.name,
innerKind2: 0, innerName2: "", innerKind3: 0, innerName3: "" };
}
func Type_MakeNamed(name: String) -> Type {
return Type { kind: tyNamed, name: name, innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeTypeParam(name: String) -> Type {
return Type { kind: tyTypeParam, name: name, innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
// ---------------------------------------------------------------------------
// Predicates
// ---------------------------------------------------------------------------
func Type_IsNumeric(t: Type) -> bool {
let k: int = t.kind;
if k == tyInt8 || k == tyInt16 || k == tyInt32 || k == tyInt64 || k == tyInt { return true; }
if k == tyUInt8 || k == tyUInt16 || k == tyUInt32 || k == tyUInt64 || k == tyUInt { return true; }
if k == tyFloat32 || k == tyFloat64 { return true; }
if k == tyUnknown || k == tyNamed || k == tyTypeParam { return true; }
return false;
}
func Type_IsInteger(t: Type) -> bool {
let k: int = t.kind;
if k == tyInt8 || k == tyInt16 || k == tyInt32 || k == tyInt64 || k == tyInt { return true; }
if k == tyUInt8 || k == tyUInt16 || k == tyUInt32 || k == tyUInt64 || k == tyUInt { return true; }
if k == tyUnknown || k == tyNamed || k == tyTypeParam { return true; }
return false;
}
func Type_IsBool(t: Type) -> bool {
let k: int = t.kind;
return k == tyBool || k == tyBool8 || k == tyBool16 || k == tyBool32;
}
func Type_IsPointer(t: Type) -> bool {
return t.kind == tyPointer;
}
func Type_IsSlice(t: Type) -> bool {
return t.kind == tySlice;
}
// ---------------------------------------------------------------------------
// Comparison (structural, limited to kind + name for simplicity)
// ---------------------------------------------------------------------------
func Type_Eq(a: Type, b: Type) -> bool {
if a.kind != b.kind { return false; }
if a.kind == tyNamed || a.kind == tyTypeParam {
return String_Eq(a.name, b.name);
}
return true;
}
// ---------------------------------------------------------------------------
// toString
// ---------------------------------------------------------------------------
func Type_ToString(t: Type) -> String {
if t.kind == tyVoid { return "void"; }
if t.kind == tyBool { return "bool"; }
if t.kind == tyStr { return "String"; }
if t.kind == tyInt { return "int"; }
if t.kind == tyInt64 { return "int64"; }
if t.kind == tyUInt { return "uint"; }
if t.kind == tyFloat64 { return "float64"; }
if t.kind == tyNamed { return t.name; }
if t.kind == tyTypeParam { return t.name; }
if t.kind == tyPointer { return String_Concat("*", t.innerName1); }
return "?";
}
}
+14 -12
View File
@@ -1,5 +1,5 @@
import std/[os, strutils, terminal, strformat, osproc, sets] import std/[os, strutils, terminal, strformat, osproc, sets]
import lexer, parser, ast, sema, manifest, hir_lower, c_backend import lexer, parser, ast, sema, manifest, hir_lower, lir, lir_lower, lir_c_backend
type type
ColorMode* = enum ColorMode* = enum
@@ -259,9 +259,9 @@ proc collectDepDecls(lock: Lockfile, root: string, opts: GlobalOptions): seq[Dec
proc findStdlibDir(root: string): string = proc findStdlibDir(root: string): string =
let searchPaths = @[ let searchPaths = @[
getAppDir() / ".." / "library", getAppDir() / ".." / "lib",
getAppDir() / "library", getAppDir() / "lib",
root / "library", root / "lib",
] ]
for path in searchPaths: for path in searchPaths:
if dirExists(path): if dirExists(path):
@@ -454,14 +454,15 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
return 1 return 1
let hirMod = lowerModule(unifiedModule, semaCtx) let hirMod = lowerModule(unifiedModule, semaCtx)
var cbe = initCBackend() let lirBuilder = lowerModuleToLir(hirMod)
var allCCode = cbe.emitModule(hirMod) var lirCbe = initLirCBackend()
var allCCode = lirCbe.emitModule(lirBuilder, hirMod)
# Write C file # Write C file
let cFile = buildDir / "main.c" let cFile = buildDir / "main.c"
writeFile(cFile, allCCode) writeFile(cFile, allCCode)
# Copy runtime and stdlib files # Copy runtime files (rt/ is sibling of lib/)
let stdlibDir = pctx.stdlibDir let stdlibDir = pctx.stdlibDir
let runtimeDst = buildDir / "runtime.c" let runtimeDst = buildDir / "runtime.c"
let ioDst = buildDir / "io.c" let ioDst = buildDir / "io.c"
@@ -469,24 +470,25 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
printError("stdlib directory not found", useColor) printError("stdlib directory not found", useColor)
return 1 return 1
let runtimeSrc = stdlibDir / "runtime" / "runtime.c" let baseDir = stdlibDir.parentDir()
let runtimeSrc = baseDir / "rt" / "runtime.c"
if fileExists(runtimeSrc): if fileExists(runtimeSrc):
copyFile(runtimeSrc, runtimeDst) copyFile(runtimeSrc, runtimeDst)
else: else:
printError("runtime.c not found in library/runtime/", useColor) printError("runtime.c not found in rt/", useColor)
return 1 return 1
let ioSrc = stdlibDir / "runtime" / "io.c" let ioSrc = baseDir / "rt" / "io.c"
if fileExists(ioSrc): if fileExists(ioSrc):
copyFile(ioSrc, ioDst) copyFile(ioSrc, ioDst)
else: else:
printError("io.c not found in library/runtime/", useColor) printError("io.c not found in rt/", useColor)
return 1 return 1
# Compile with cc # Compile with cc
let outputName = if pctx.man.name != "": pctx.man.name else: "bux_out" let outputName = if pctx.man.name != "": pctx.man.name else: "bux_out"
let outputFile = buildDir / outputName let outputFile = buildDir / outputName
let ccCmd = &"cc -O2 -pthread -o {outputFile} {cFile} {runtimeDst} {ioDst} -lm -lcrypto 2>&1" let ccCmd = &"cc -O0 -g -pthread -o {outputFile} {cFile} {runtimeDst} {ioDst} -lm -lcrypto 2>&1"
if opts.verbose: if opts.verbose:
printInfo(&"running: {ccCmd}", useColor) printInfo(&"running: {ccCmd}", useColor)
let (output, exitCode) = execCmdEx(ccCmd) let (output, exitCode) = execCmdEx(ccCmd)
+4
View File
@@ -203,6 +203,10 @@ proc hirReturn*(value: HirNode, loc: SourceLocation): HirNode =
proc hirBlock*(stmts: seq[HirNode], expr: HirNode, typ: Type, loc: SourceLocation, isScope: bool = false): HirNode = proc hirBlock*(stmts: seq[HirNode], expr: HirNode, typ: Type, loc: SourceLocation, isScope: bool = false): HirNode =
HirNode(kind: hBlock, blockStmts: stmts, blockExpr: expr, typ: typ, loc: loc, isScope: isScope) HirNode(kind: hBlock, blockStmts: stmts, blockExpr: expr, typ: typ, loc: loc, isScope: isScope)
proc hirIf*(cond, thenBranch, elseBranch: HirNode, loc: SourceLocation): HirNode =
HirNode(kind: hIf, ifCond: cond, ifThen: thenBranch, ifElse: elseBranch, typ: makeVoid(), loc: loc)
proc hirAlloca*(name: string, typ: Type, loc: SourceLocation): HirNode = proc hirAlloca*(name: string, typ: Type, loc: SourceLocation): HirNode =
HirNode(kind: hAlloca, allocaType: typ, allocaName: name, typ: makePointer(typ), loc: loc) HirNode(kind: hAlloca, allocaType: typ, allocaName: name, typ: makePointer(typ), loc: loc)
+75 -6
View File
@@ -352,9 +352,39 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
if objType.isPointer and objType.inner.len > 0: if objType.isPointer and objType.inner.len > 0:
objType = objType.inner[0] objType = objType.inner[0]
if objType.kind == tkNamed: if objType.kind == tkNamed:
let sym = ctx.globalScope.lookup(objType.name) var sym = ctx.globalScope.lookup(objType.name)
if sym != nil and sym.decl != nil and sym.decl.kind == dkStruct: var decl = if sym != nil: sym.decl else: nil
for f in sym.decl.declStructFields: # If the type is a monomorphized generic struct instance, look up the base
if decl == nil and ctx.structInstMap.hasKey(objType.name):
let (baseName, typeArgs) = ctx.structInstMap[objType.name]
let baseSym = ctx.globalScope.lookup(baseName)
if baseSym != nil and baseSym.decl != nil and baseSym.decl.kind == dkStruct:
decl = baseSym.decl
var subst = initTable[string, Type]()
for i, tp in decl.declStructTypeParams:
if i < typeArgs.len:
subst[tp.name] = typeArgs[i]
for f in 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:
if subst.hasKey(f.ftype.typeName):
return subst[f.ftype.typeName]
return makeNamed(f.ftype.typeName)
of tekOwn, tekPointer:
return substituteType(ctx, f.ftype, subst)
else: return makeUnknown()
if decl != nil:
case decl.kind
of dkStruct:
for f in decl.declStructFields:
if f.name == expr.exprFieldName: if f.name == expr.exprFieldName:
if f.ftype != nil: if f.ftype != nil:
case f.ftype.kind case f.ftype.kind
@@ -368,6 +398,17 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
of tekOwn, tekPointer: of tekOwn, tekPointer:
return ctx.resolveTypeExpr(f.ftype) return ctx.resolveTypeExpr(f.ftype)
else: return makeUnknown() else: return makeUnknown()
of dkEnum:
# Algebraic enum fields: tag and data
if expr.exprFieldName == "tag":
return makeNamed(objType.name & "_Tag")
elif expr.exprFieldName == "data":
return makeNamed(objType.name & "_Data")
else:
# Enum variant field access: e.g., r.data.Ok_0
# We can't easily resolve this here; return unknown
return makeUnknown()
else: discard
return makeUnknown() return makeUnknown()
of ekStructInit: of ekStructInit:
if expr.exprStructInitTypeArgs.len > 0: if expr.exprStructInitTypeArgs.len > 0:
@@ -395,6 +436,13 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
return makeInt() return makeInt()
of ekUnwrap: of ekUnwrap:
return makeInt() return makeInt()
of ekIndex:
let baseType = ctx.resolveExprType(expr.exprIndexObj)
if baseType.isSlice and baseType.inner.len > 0:
return baseType.inner[0]
if baseType.isPointer and baseType.inner.len > 0:
return baseType.inner[0]
return makeUnknown()
of ekBlock: of ekBlock:
if expr.exprBlock.stmts.len > 0: if expr.exprBlock.stmts.len > 0:
let last = expr.exprBlock.stmts[^1] let last = expr.exprBlock.stmts[^1]
@@ -491,6 +539,28 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
return hirUnary(expr.exprUnaryOp, operand, typ, loc) return hirUnary(expr.exprUnaryOp, operand, typ, loc)
of ekBinary: of ekBinary:
case expr.exprBinaryOp
of tkAmpAmp:
# Short-circuit &&: use if-then-else to avoid evaluating right when left is false
let tmp = hirAlloca("__and_tmp_" & $ctx.varCounter, makeBool(), loc)
inc ctx.varCounter
let left = ctx.lowerExpr(expr.exprBinaryLeft)
let thenBlock = hirBlock(@[hirStore(tmp, ctx.lowerExpr(expr.exprBinaryRight), loc)], nil, makeVoid(), loc)
let falseTok = Token(kind: tkBoolLiteral, text: "false", loc: loc)
let elseBlock = hirBlock(@[hirStore(tmp, hirLit(falseTok, makeBool(), loc), loc)], nil, makeVoid(), loc)
let ifNode = hirIf(left, thenBlock, elseBlock, loc)
return hirBlock(@[tmp, ifNode], hirLoad(tmp, makeBool(), loc), makeBool(), loc)
of tkPipePipe:
# Short-circuit ||: use if-then-else to avoid evaluating right when left is true
let tmp = hirAlloca("__or_tmp_" & $ctx.varCounter, makeBool(), loc)
inc ctx.varCounter
let left = ctx.lowerExpr(expr.exprBinaryLeft)
let trueTok = Token(kind: tkBoolLiteral, text: "true", loc: loc)
let thenBlock = hirBlock(@[hirStore(tmp, hirLit(trueTok, makeBool(), loc), loc)], nil, makeVoid(), loc)
let elseBlock = hirBlock(@[hirStore(tmp, ctx.lowerExpr(expr.exprBinaryRight), loc)], nil, makeVoid(), loc)
let ifNode = hirIf(left, thenBlock, elseBlock, loc)
return hirBlock(@[tmp, ifNode], hirLoad(tmp, makeBool(), loc), makeBool(), loc)
else:
let left = ctx.lowerExpr(expr.exprBinaryLeft) let left = ctx.lowerExpr(expr.exprBinaryLeft)
let right = ctx.lowerExpr(expr.exprBinaryRight) let right = ctx.lowerExpr(expr.exprBinaryRight)
return hirBinary(expr.exprBinaryOp, left, right, typ, loc) return hirBinary(expr.exprBinaryOp, left, right, typ, loc)
@@ -721,7 +791,7 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
let tmpName = ctx.freshTryVar() let tmpName = ctx.freshTryVar()
let tmpAlloca = hirAlloca(tmpName, operandType, loc) let tmpAlloca = hirAlloca(tmpName, operandType, loc)
let tmpVar = hirVar(tmpName, makePointer(operandType), loc) let tmpVar = hirVar(tmpName, operandType, loc)
let tmpStore = hirStore(tmpVar, operand, loc) let tmpStore = hirStore(tmpVar, operand, loc)
let tagPtr = HirNode(kind: hFieldPtr, fieldPtrBase: tmpVar, fieldName: "tag", let tagPtr = HirNode(kind: hFieldPtr, fieldPtrBase: tmpVar, fieldName: "tag",
@@ -1020,8 +1090,7 @@ proc lowerBlock(ctx: var LowerCtx, blk: Block): HirNode =
# but for hVar/hLit/hCall etc. we could treat them as block expr. # but for hVar/hLit/hCall etc. we could treat them as block expr.
# For now, leave as-is to avoid breaking control-flow statements. # For now, leave as-is to avoid breaking control-flow statements.
discard discard
return HirNode(kind: hBlock, blockStmts: stmts, blockExpr: expr, return hirBlock(stmts, expr, if expr != nil: expr.typ else: makeVoid(), blk.loc, isScope = true)
typ: if expr != nil: expr.typ else: makeVoid(), loc: blk.loc)
proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc = proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
# Set up type substitution for generic functions # Set up type substitution for generic functions
+314
View File
@@ -0,0 +1,314 @@
## LIR — Low-level Intermediate Representation
## Linear 3-address code IR, designed for straightforward C emission.
## Each HIR construct lowers to 5-30 LIR instructions.
import types
type
LirKind* = enum
# ── Data movement ──
lirMov ## dst = src
lirLoad ## dst = *(base + offset) [type: base elem type]
lirStore ## *(base + offset) = src
lirLoadGlobal ## dst = global_name
# ── Arithmetic (3-address, signed) ──
lirAdd
lirSub
lirMul
lirDiv
lirMod
# ── Bitwise ──
lirAnd
lirOr
lirXor
lirShl
lirShr
# ── Unary ──
lirNeg ## dst = -src
lirNot ## dst = !src (logical)
lirBNot ## dst = ~src (bitwise)
# ── Comparison (dst = 0 or 1) ──
lirCmpEq
lirCmpNe
lirCmpLt
lirCmpLe
lirCmpGt
lirCmpGe
# ── Control flow ──
lirLabel ## label_name:
lirJmp ## goto target
lirJz ## if (!cond) goto target
lirJnz ## if (cond) goto target
# ── Calls ──
lirCall ## dst = callee(args...)
lirCallVoid ## callee(args...) [no return]
lirCallIndirect ## dst = (*fn_ptr)(args...)
# ── Return ──
lirRet ## return [val]
# ── Stack allocation ──
lirAlloca ## type dst; (declare a C local)
# ── Pointers / addressing ──
lirAddrOf ## dst = &source_var
lirFieldPtr ## dst = &(base.field_name)
lirArrowFieldPtr ## dst = &(base->field_name)
lirIndexPtr ## dst = &base[idx]
lirPtrAdd ## dst = base + offset_bytes (raw pointer arithmetic)
# ── Type conversion ──
lirCast ## dst = (target_type)src
# ── Composite literals ──
lirStructInit ## dst = (StructType){.f1=v1, .f2=v2, ...}
lirSliceInit ## dst = (SliceType){.data=arr, .len=n}
# ── Ternary (convenience; lowered from if-expr) ──
lirSelect ## dst = cond ? a : b
# ── Inline C (for runtime calls that C handles natively) ──
lirRawC ## emit raw C line
# ── Source annotation ──
lirComment ## /* text */
LirValueKind* = enum
lvkTemp ## Virtual register / temp (e.g. "%42")
lvkVar ## Named variable / parameter
lvkInt ## Integer literal
lvkFloat ## Float literal
lvkString ## String literal (already C-escaped)
lvkGlobal ## Global variable / constant name
lvkLabel ## Label reference
lvkField ## Field name (for struct operations)
lvkType ## C type name (for casts, alloca, struct init)
lvkVoid ## No value
LirValue* = object
case kind*: LirValueKind
of lvkVoid: discard
of lvkTemp, lvkVar, lvkGlobal, lvkLabel, lvkField, lvkType, lvkString:
strVal*: string
of lvkInt:
intVal*: int64
of lvkFloat:
floatVal*: float64
LirInstr* = object
kind*: LirKind
dst*: LirValue ## Destination (temp or void)
src*: LirValue ## Source operand
src2*: LirValue ## Second source operand (for binary ops)
extra*: seq[LirValue] ## Extra operands (call args, struct fields, etc.)
locLine*: int ## Source line for debug comments (0 = none)
locFile*: string ## Source file for debug comments ("" = none)
# ── Constructor helpers ──
proc lirTemp*(name: string): LirValue =
LirValue(kind: lvkTemp, strVal: name)
proc lirVar*(name: string): LirValue =
LirValue(kind: lvkVar, strVal: name)
proc lirInt*(v: int64): LirValue =
LirValue(kind: lvkInt, intVal: v)
proc lirFloatLit*(v: float64): LirValue =
LirValue(kind: lvkFloat, floatVal: v)
proc lirStr*(v: string): LirValue =
## Already C-escaped and quoted string
LirValue(kind: lvkString, strVal: v)
proc lirGlobal*(name: string): LirValue =
LirValue(kind: lvkGlobal, strVal: name)
proc lirLabel*(name: string): LirValue =
LirValue(kind: lvkLabel, strVal: name)
proc lirField*(name: string): LirValue =
LirValue(kind: lvkField, strVal: name)
proc lirType*(name: string): LirValue =
LirValue(kind: lvkType, strVal: name)
proc lirVoid*(): LirValue =
LirValue(kind: lvkVoid)
proc `$`*(v: LirValue): string =
case v.kind
of lvkVoid: "void"
of lvkTemp: "%" & v.strVal
of lvkVar: v.strVal
of lvkInt: $v.intVal
of lvkFloat: $v.floatVal
of lvkString: v.strVal
of lvkGlobal: "@" & v.strVal
of lvkLabel: ":" & v.strVal
of lvkField: "." & v.strVal
of lvkType: "<" & v.strVal & ">"
# ── LIR function ──
type
LirFunc* = object
name*: string
params*: seq[tuple[name: string, cType: string]]
retType*: string ## C return type ("int", "void", etc.)
instrs*: seq[LirInstr]
isPublic*: bool
LirModule* = object
funcs*: seq[LirFunc]
globals*: seq[tuple[name: string, cType: string, initVal: string]]
structDefs*: seq[string] ## Raw C struct typedef strings
enumDefs*: seq[string] ## Raw C enum typedef strings
externs*: seq[string] ## Raw C extern declarations
includes*: seq[string] ## #include <...>
preamble*: string ## Raw C code at top of file
# ── Builder context (temp counter, label counter) ──
type
LirBuilder* = object
funcs*: seq[LirFunc]
tempCounter*: int
labelCounter*: int
commentLine*: int
commentFile*: string
## Current function being built
curFunc*: LirFunc
curFuncActive*: bool
proc initLirBuilder*(): LirBuilder =
result = LirBuilder()
result.tempCounter = 0
result.labelCounter = 0
proc freshTemp*(b: var LirBuilder): LirValue =
inc b.tempCounter
result = lirTemp("_t" & $b.tempCounter)
proc freshLabel*(b: var LirBuilder, prefix: string = "L"): LirValue =
inc b.labelCounter
result = lirLabel(prefix & $b.labelCounter)
# ── Instruction emitters ──
proc emit*(b: var LirBuilder, instr: LirInstr) =
var i = instr
if b.commentLine > 0:
i.locLine = b.commentLine
i.locFile = b.commentFile
b.curFunc.instrs.add(i)
proc emitMov*(b: var LirBuilder, dst, src: LirValue) =
b.emit(LirInstr(kind: lirMov, dst: dst, src: src))
proc emitLoad*(b: var LirBuilder, dst, base: LirValue, offset: int = 0) =
b.emit(LirInstr(kind: lirLoad, dst: dst, src: base, src2: lirInt(offset)))
proc emitStore*(b: var LirBuilder, base: LirValue, src: LirValue, offset: int = 0) =
b.emit(LirInstr(kind: lirStore, src: src, src2: base, dst: lirInt(offset)))
proc emitBinOp*(b: var LirBuilder, op: LirKind, dst, a, bl: LirValue) =
b.emit(LirInstr(kind: op, dst: dst, src: a, src2: bl))
proc emitUnary*(b: var LirBuilder, op: LirKind, dst, src: LirValue) =
b.emit(LirInstr(kind: op, dst: dst, src: src))
proc emitCmp*(b: var LirBuilder, op: LirKind, dst, a, bl: LirValue) =
b.emit(LirInstr(kind: op, dst: dst, src: a, src2: bl))
proc emitLabel*(b: var LirBuilder, label: LirValue) =
b.emit(LirInstr(kind: lirLabel, src: label))
proc emitJmp*(b: var LirBuilder, target: LirValue) =
b.emit(LirInstr(kind: lirJmp, src: target))
proc emitJz*(b: var LirBuilder, target, cond: LirValue) =
b.emit(LirInstr(kind: lirJz, src: target, src2: cond))
proc emitJnz*(b: var LirBuilder, target, cond: LirValue) =
b.emit(LirInstr(kind: lirJnz, src: target, src2: cond))
proc emitCall*(b: var LirBuilder, dst: LirValue, callee: string, args: seq[LirValue]) =
b.emit(LirInstr(kind: lirCall, dst: dst, src: lirGlobal(callee), extra: args))
proc emitCallVoid*(b: var LirBuilder, callee: string, args: seq[LirValue]) =
b.emit(LirInstr(kind: lirCallVoid, dst: lirVoid(), src: lirGlobal(callee), extra: args))
proc emitRet*(b: var LirBuilder, val: LirValue = lirVoid()) =
b.emit(LirInstr(kind: lirRet, src: val))
proc emitAlloca*(b: var LirBuilder, name: string, cType: string) =
b.emit(LirInstr(kind: lirAlloca, dst: lirVar(name), src: lirType(cType)))
proc emitAddrOf*(b: var LirBuilder, dst, src: LirValue) =
b.emit(LirInstr(kind: lirAddrOf, dst: dst, src: src))
proc emitFieldPtr*(b: var LirBuilder, dst, base: LirValue, field: string) =
b.emit(LirInstr(kind: lirFieldPtr, dst: dst, src: base, src2: lirField(field)))
proc emitArrowFieldPtr*(b: var LirBuilder, dst, base: LirValue, field: string) =
b.emit(LirInstr(kind: lirArrowFieldPtr, dst: dst, src: base, src2: lirField(field)))
proc emitIndexPtr*(b: var LirBuilder, dst, base, idx: LirValue) =
b.emit(LirInstr(kind: lirIndexPtr, dst: dst, src: base, src2: idx))
proc emitPtrAdd*(b: var LirBuilder, dst, base, offset: LirValue) =
b.emit(LirInstr(kind: lirPtrAdd, dst: dst, src: base, src2: offset))
proc emitCast*(b: var LirBuilder, dst, src: LirValue, targetType: string) =
b.emit(LirInstr(kind: lirCast, dst: dst, src: src, src2: lirType(targetType)))
proc emitStructInit*(b: var LirBuilder, dst: LirValue, structType: string,
fields: seq[tuple[name: string, val: LirValue]]) =
var extras: seq[LirValue] = @[lirType(structType)]
for f in fields:
extras.add(lirField(f.name))
extras.add(f.val)
b.emit(LirInstr(kind: lirStructInit, dst: dst, extra: extras))
proc emitSliceInit*(b: var LirBuilder, dst: LirValue, elemType: string,
dataPtr: LirValue, length: LirValue) =
b.emit(LirInstr(kind: lirSliceInit, dst: dst, src: dataPtr, src2: length,
extra: @[lirType(elemType)]))
proc emitSelect*(b: var LirBuilder, dst, cond, thenVal, elseVal: LirValue) =
b.emit(LirInstr(kind: lirSelect, dst: dst, src: cond, src2: thenVal,
extra: @[elseVal]))
proc emitRawC*(b: var LirBuilder, code: string) =
b.emit(LirInstr(kind: lirRawC, src: lirStr(code)))
proc emitComment*(b: var LirBuilder, text: string) =
b.emit(LirInstr(kind: lirComment, src: lirStr(text)))
# ── Function management ──
proc beginFunc*(b: var LirBuilder, name: string, params: seq[tuple[name: string, cType: string]],
retType: string, isPublic: bool = true) =
if b.curFuncActive:
b.funcs.add(b.curFunc)
b.curFunc = LirFunc(name: name, params: params, retType: retType,
isPublic: isPublic)
b.curFuncActive = true
proc endFunc*(b: var LirBuilder) =
if b.curFuncActive:
b.funcs.add(b.curFunc)
b.curFuncActive = false
b.curFunc = LirFunc()
proc setSourceLoc*(b: var LirBuilder, line: int, file: string) =
b.commentLine = line
b.commentFile = file
+621
View File
@@ -0,0 +1,621 @@
## LIR → C Backend
## Emits clean, well-structured C code from LIR instructions.
## Since LIR is already linear and low-level, C emission is straightforward.
import std/[strutils, strformat, tables, sequtils]
import lir, hir, types, token
type
LirCBackend* = object
output*: string
indent*: int
tempTypes*: Table[string, string] ## Track C types of temp variables
proc initLirCBackend*(): LirCBackend =
result = LirCBackend(
indent: 0,
tempTypes: initTable[string, string](),
)
proc emit(be: var LirCBackend, s: string) =
be.output.add(s)
proc emitIndent(be: var LirCBackend) =
for i in 0 ..< be.indent:
be.output.add(" ")
proc emitLine(be: var LirCBackend, s: string) =
be.emitIndent()
be.output.add(s)
be.output.add("\n")
proc valToC(be: var LirCBackend, v: LirValue): string =
## Convert a LirValue to its C representation.
case v.kind
of lvkVoid: ""
of lvkTemp: v.strVal
of lvkVar: v.strVal
of lvkInt: $v.intVal
of lvkFloat: $v.floatVal
of lvkString: v.strVal
of lvkGlobal: v.strVal
of lvkLabel: v.strVal
of lvkField: v.strVal
of lvkType: v.strVal
proc typeFromValue(be: var LirCBackend, v: LirValue): string =
## Infer a C type for a value. Temps are tracked; named vars use lookup.
case v.kind
of lvkTemp:
if be.tempTypes.hasKey(v.strVal):
return be.tempTypes[v.strVal]
return "int" # Default
of lvkString: return "const char*"
of lvkInt: return "int"
of lvkFloat: return "double"
else: return ""
proc setTempType(be: var LirCBackend, temp: string, cType: string) =
be.tempTypes[temp] = cType
# ── Per-instruction emission ──
proc emitInstr(be: var LirCBackend, instr: LirInstr) =
template v(x: LirValue): string = valToC(be, x)
case instr.kind
# ── Data movement ──
of lirMov:
be.emitLine(&"{v(instr.dst)} = {v(instr.src)};")
of lirLoad:
# dst = *(base + offset) or dst = base->src2 (if src2 is a field name)
if instr.src2.kind == lvkField:
be.emitLine(&"{v(instr.dst)} = {v(instr.src)}.{v(instr.src2)};")
elif instr.src2.kind == lvkInt and instr.src2.intVal == 0:
be.emitLine(&"{v(instr.dst)} = *{v(instr.src)};")
elif instr.src2.kind == lvkTemp or instr.src2.kind == lvkVar:
be.emitLine(&"{v(instr.dst)} = {v(instr.src)}[{v(instr.src2)}];")
else:
be.emitLine(&"{v(instr.dst)} = {v(instr.src)}[{v(instr.src2)}];")
of lirStore:
# *(base + offset) = src
if instr.src2.kind == lvkField:
be.emitLine(&"{v(instr.src2)}.{v(instr.dst)} = {v(instr.src)};")
elif instr.dst.kind == lvkInt and instr.dst.intVal == 0:
be.emitLine(&"*{v(instr.src2)} = {v(instr.src)};")
elif instr.src2.kind == lvkTemp or instr.src2.kind == lvkVar:
be.emitLine(&"{v(instr.src2)}[{v(instr.dst)}] = {v(instr.src)};")
else:
be.emitLine(&"*({v(instr.src2)} + {v(instr.dst)}) = {v(instr.src)};")
of lirLoadGlobal:
be.emitLine(&"{v(instr.dst)} = {v(instr.src)};")
# ── Arithmetic ──
of lirAdd, lirSub, lirMul, lirDiv, lirMod,
lirAnd, lirOr, lirXor, lirShl, lirShr:
let op = case instr.kind
of lirAdd: "+"
of lirSub: "-"
of lirMul: "*"
of lirDiv: "/"
of lirMod: "%"
of lirAnd: "&"
of lirOr: "|"
of lirXor: "^"
of lirShl: "<<"
of lirShr: ">>"
else: "?"
be.emitLine(&"{v(instr.dst)} = {v(instr.src)} {op} {v(instr.src2)};")
of lirNeg:
be.emitLine(&"{v(instr.dst)} = -{v(instr.src)};")
of lirNot:
be.emitLine(&"{v(instr.dst)} = !{v(instr.src)};")
of lirBNot:
be.emitLine(&"{v(instr.dst)} = ~{v(instr.src)};")
# ── Comparison ──
of lirCmpEq, lirCmpNe, lirCmpLt, lirCmpLe, lirCmpGt, lirCmpGe:
let op = case instr.kind
of lirCmpEq: "=="
of lirCmpNe: "!="
of lirCmpLt: "<"
of lirCmpLe: "<="
of lirCmpGt: ">"
of lirCmpGe: ">="
else: "=="
be.emitLine(&"{v(instr.dst)} = ({v(instr.src)} {op} {v(instr.src2)});")
# ── Control flow ──
of lirLabel:
be.emitLine(&"{v(instr.src)}:;") # C requires statement after label
# Add a null statement to avoid "label at end of compound statement" warnings
# Handled by the next instruction naturally
of lirJmp:
be.emitLine(&"goto {v(instr.src)};")
of lirJz:
be.emitLine(&"if (!{v(instr.src2)}) goto {v(instr.src)};")
of lirJnz:
be.emitLine(&"if ({v(instr.src2)}) goto {v(instr.src)};")
# ── Calls ──
of lirCall:
var argsStr = ""
for i, arg in instr.extra:
if i > 0: argsStr.add(", ")
argsStr.add(v(arg))
be.emitLine(&"{v(instr.dst)} = {v(instr.src)}({argsStr});")
of lirCallVoid:
var argsStr = ""
for i, arg in instr.extra:
if i > 0: argsStr.add(", ")
argsStr.add(v(arg))
be.emitLine(&"{v(instr.src)}({argsStr});")
of lirCallIndirect:
var argsStr = ""
for i, arg in instr.extra:
if i > 0: argsStr.add(", ")
argsStr.add(v(arg))
if instr.dst.kind != lvkVoid:
be.emitLine(&"{v(instr.dst)} = ({v(instr.src)})({argsStr});")
else:
be.emitLine(&"({v(instr.src)})({argsStr});")
# ── Return ──
of lirRet:
if instr.src.kind != lvkVoid:
be.emitLine(&"return {v(instr.src)};")
else:
be.emitLine("return;")
# ── Alloca ──
of lirAlloca:
var ct = v(instr.src)
if instr.dst.strVal.len > 0 and be.tempTypes.hasKey(instr.dst.strVal):
let inferred = be.tempTypes[instr.dst.strVal]
if inferred != "" and inferred != ct:
ct = inferred
be.emitLine(&"{ct} {v(instr.dst)};")
# ── Pointers ──
of lirAddrOf:
be.emitLine(&"{v(instr.dst)} = &{v(instr.src)};")
of lirFieldPtr:
be.emitLine(&"{v(instr.dst)} = &({v(instr.src)}.{v(instr.src2)});")
of lirArrowFieldPtr:
be.emitLine(&"{v(instr.dst)} = &({v(instr.src)}->{v(instr.src2)});")
of lirIndexPtr:
be.emitLine(&"{v(instr.dst)} = &({v(instr.src)}[{v(instr.src2)}]);")
of lirPtrAdd:
be.emitLine(&"{v(instr.dst)} = ({v(instr.src)} + {v(instr.src2)});")
# ── Cast ──
of lirCast:
be.emitLine(&"{v(instr.dst)} = ({v(instr.src2)}){v(instr.src)};")
# ── StructInit ──
of lirStructInit:
let structType = v(instr.extra[0])
var fieldPairs = ""
var i = 1
while i < instr.extra.len:
let fieldName = v(instr.extra[i]) # e.g. "width"
let fieldVal = v(instr.extra[i + 1]) # e.g. "10"
if i > 1: fieldPairs.add(", ")
fieldPairs.add(&".{fieldName} = {fieldVal}")
i += 2
be.emitLine(&"{v(instr.dst)} = ({structType}){{{fieldPairs}}};")
# ── SliceInit ──
of lirSliceInit:
let elemType = v(instr.extra[0])
be.emitLine(&"{v(instr.dst)} = (Slice_{elemType}){{.data = ({elemType}*){v(instr.src)}, .len = {v(instr.src2)}}};")
# ── Select (ternary) ──
of lirSelect:
let elseVal = if instr.extra.len > 0: v(instr.extra[0]) else: "0"
be.emitLine(&"{v(instr.dst)} = ({v(instr.src)}) ? {v(instr.src2)} : {elseVal};")
# ── Raw C ──
of lirRawC:
let code = v(instr.src)
if code.len > 0:
be.emitLine(code)
# ── Comment ──
of lirComment:
let text = v(instr.src)
be.emitLine(&"/* {text} */")
# ── Function emission ──
proc emitFunc(be: var LirCBackend, f: LirFunc, funcRetTypes: Table[string, string]) =
var paramsStr = ""
for i, p in f.params:
if i > 0: paramsStr.add(", ")
paramsStr.add(&"{p.cType} {p.name}")
if f.params.len == 0:
paramsStr = "void"
be.emitLine(&"{f.retType} {f.name}({paramsStr}) {{")
be.indent += 1
# ── Pass 1: collect types from allocas, params, and instructions ──
var varTypes = initTable[string, string]()
var tempsSet: seq[string] = @[]
for p in f.params:
varTypes[p.name] = p.cType
be.tempTypes[p.name] = p.cType
for instr in f.instrs:
if instr.kind == lirAlloca and instr.dst.kind == lvkVar and instr.src.kind == lvkType:
varTypes[instr.dst.strVal] = instr.src.strVal
be.tempTypes[instr.dst.strVal] = instr.src.strVal
if instr.dst.strVal notin tempsSet:
tempsSet.add(instr.dst.strVal)
# ── Pass 2: iterative type inference for temps ──
var changed = true
while changed:
changed = false
for instr in f.instrs:
if instr.dst.kind != lvkTemp or instr.dst.strVal.len == 0:
continue
let name = instr.dst.strVal
let oldType = if be.tempTypes.hasKey(name): be.tempTypes[name] else: ""
var newType = oldType
case instr.kind
of lirStructInit:
if instr.extra.len > 0 and instr.extra[0].kind == lvkType:
newType = instr.extra[0].strVal
of lirSliceInit:
if instr.extra.len > 0 and instr.extra[0].kind == lvkType:
newType = "Slice_" & instr.extra[0].strVal
of lirCast:
if instr.src2.kind == lvkType:
newType = instr.src2.strVal
of lirCall:
if instr.src.kind == lvkGlobal and funcRetTypes.hasKey(instr.src.strVal):
newType = funcRetTypes[instr.src.strVal]
of lirCallIndirect:
# Conservative; try to infer from dst usage in later passes
discard
of lirMov:
if instr.src.kind == lvkTemp and be.tempTypes.hasKey(instr.src.strVal):
newType = be.tempTypes[instr.src.strVal]
elif instr.src.kind == lvkVar and varTypes.hasKey(instr.src.strVal):
newType = varTypes[instr.src.strVal]
of lirLoad, lirLoadGlobal:
# Try to deduce pointee type from pointer vars/temps
if instr.src.kind == lvkVar and varTypes.hasKey(instr.src.strVal):
let srcType = varTypes[instr.src.strVal]
if srcType.endsWith("*"):
newType = srcType[0 ..< srcType.len - 1]
elif srcType.startsWith("Slice_"):
newType = srcType[6 ..< srcType.len]
elif instr.src.kind == lvkTemp and be.tempTypes.hasKey(instr.src.strVal):
let srcType = be.tempTypes[instr.src.strVal]
if srcType.endsWith("*"):
newType = srcType[0 ..< srcType.len - 1]
elif srcType.startsWith("Slice_"):
newType = srcType[6 ..< srcType.len]
of lirSelect:
if instr.src2.kind == lvkTemp and be.tempTypes.hasKey(instr.src2.strVal):
newType = be.tempTypes[instr.src2.strVal]
elif instr.extra.len > 0 and instr.extra[0].kind == lvkTemp and be.tempTypes.hasKey(instr.extra[0].strVal):
newType = be.tempTypes[instr.extra[0].strVal]
elif instr.src2.kind == lvkVar and varTypes.hasKey(instr.src2.strVal):
newType = varTypes[instr.src2.strVal]
of lirAddrOf, lirFieldPtr, lirArrowFieldPtr, lirIndexPtr, lirPtrAdd:
newType = "void*"
of lirAdd, lirSub, lirMul, lirDiv, lirMod, lirNeg,
lirCmpEq, lirCmpNe, lirCmpLt, lirCmpLe, lirCmpGt, lirCmpGe,
lirAnd, lirOr, lirXor, lirShl, lirShr, lirNot, lirBNot:
newType = "int"
else:
discard
if newType != "" and newType != oldType:
be.tempTypes[name] = newType
changed = true
# ── Pass 3: declare temps that were inferred ──
var declared: seq[string] = @[]
for instr in f.instrs:
if instr.kind == lirAlloca and instr.dst.strVal.len > 0 and instr.dst.strVal notin declared:
declared.add(instr.dst.strVal)
continue
if instr.dst.kind == lvkTemp and instr.dst.strVal.len > 0 and instr.dst.strVal notin declared:
if be.tempTypes.hasKey(instr.dst.strVal):
let ct = be.tempTypes[instr.dst.strVal]
if ct != "":
declared.add(instr.dst.strVal)
be.emitLine(&"{ct} {instr.dst.strVal};")
# ── Pass 4: emit instructions ──
for instr in f.instrs:
be.emitInstr(instr)
be.indent -= 1
be.emitLine("}")
be.emitLine("")
# ── Struct/Enum emission (from HIR module) ──
proc typeToCStr(typ: Type): string =
## Duplicate from lir_lower for self-containedness
if typ == nil: return "int"
case typ.kind
of tkVoid: return "void"
of tkBool, tkBool8, tkBool16, 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, tkRef, tkMutRef:
if typ.inner.len > 0:
return typeToCStr(typ.inner[0]) & "*"
return "void*"
of tkDynRef:
return typ.name & "_FatPtr"
of tkSlice:
let elem = if typ.inner.len > 0: typeToCStr(typ.inner[0]) else: "void"
return "Slice_" & elem.replace(" ", "_").replace("*", "Ptr")
of tkNamed:
case typ.name
of "String", "str": return "const char*"
of "int": return "int"
of "int8": return "int8_t"
of "int16": return "int16_t"
of "int32": return "int32_t"
of "int64": return "int64_t"
of "uint": return "unsigned int"
of "uint8": return "uint8_t"
of "uint16": return "uint16_t"
of "uint32": return "uint32_t"
of "uint64": return "uint64_t"
of "float32": return "float"
of "float64": return "double"
of "bool": return "bool"
else: return typ.name
else: return "int"
proc emitStructDef(be: var LirCBackend, name: string, fields: seq[tuple[name: string, typ: Type]]) =
be.emitLine(&"typedef struct {name} {{")
be.indent += 1
for f in fields:
be.emitLine(&"{typeToCStr(f.typ)} {f.name};")
be.indent -= 1
be.emitLine(&"}} {name};")
be.emitLine("")
proc emitEnumDef(be: var LirCBackend, name: string, variants: seq[HirEnumVariant]) =
var hasData = false
for v in variants:
if v.fields.len > 0 or v.namedFields.len > 0:
hasData = true
break
if not hasData:
# Simple enum
be.emitLine(&"typedef enum {{")
be.indent += 1
for i, v in variants:
if i < variants.len - 1:
be.emitLine(&"{name}_{v.name},")
else:
be.emitLine(&"{name}_{v.name}")
be.indent -= 1
be.emitLine(&"}} {name};")
be.emitLine("")
else:
# Tagged union
be.emitLine(&"typedef enum {{")
be.indent += 1
for i, v in variants:
if i < variants.len - 1:
be.emitLine(&"{name}_{v.name},")
else:
be.emitLine(&"{name}_{v.name}")
be.indent -= 1
be.emitLine(&"}} {name}_Tag;")
be.emitLine("")
be.emitLine(&"typedef union {{")
be.indent += 1
for v in variants:
if v.fields.len > 0:
for i, f in v.fields:
be.emitLine(&"{typeToCStr(f)} {v.name}_{i};")
elif v.namedFields.len > 0:
be.emitLine(&"struct {{")
be.indent += 1
for nf in v.namedFields:
be.emitLine(&"{typeToCStr(nf.typ)} {nf.name};")
be.indent -= 1
be.emitLine(&"}} {v.name};")
be.indent -= 1
be.emitLine(&"}} {name}_Data;")
be.emitLine("")
be.emitLine(&"typedef struct {{")
be.indent += 1
be.emitLine(&"{name}_Tag tag;")
be.emitLine(&"{name}_Data data;")
be.indent -= 1
be.emitLine(&"}} {name};")
be.emitLine("")
# ── Module emission ──
proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): string =
## Emit full C source from LIR builder + HIR module metadata.
be.output = ""
# Build function return type lookup table
var funcRetTypes = initTable[string, string]()
for f in module.funcs:
funcRetTypes[f.name] = typeToCStr(f.retType)
for f in module.externFuncs:
funcRetTypes[f.name] = typeToCStr(f.retType)
# Header
be.emitLine("/* Generated by Bux Compiler (LIR backend) */")
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 struct declarations
for s in module.structs:
be.emitLine(&"typedef struct {s.name} {s.name};")
if module.structs.len > 0:
be.emitLine("")
# Forward trait object declarations
for iface in module.interfaces:
if not iface.hasAssocTypes:
be.emitLine(&"typedef struct {iface.name}_FatPtr {iface.name}_FatPtr;")
if module.interfaces.len > 0:
be.emitLine("")
# Extern declarations
if module.externFuncs.len > 0:
be.emitLine("/* Extern function declarations */")
for ef in module.externFuncs:
let rt = typeToCStr(ef.retType)
var params: seq[string] = @[]
for p in ef.params:
params.add(&"{typeToCStr(p.typ)} {p.name}")
if params.len == 0: params.add("void")
be.emitLine(&"extern {rt} {ef.name}({params.join(\", \")});")
be.emitLine("")
# Constants as #define
if module.consts.len > 0:
be.emitLine("/* Constants */")
for c in module.consts:
if c.value != nil and c.value.kind == hLit:
case c.value.litToken.kind
of tkIntLiteral: be.emitLine(&"#define {c.name} {c.value.litToken.text}")
of tkStringLiteral: be.emitLine(&"#define {c.name} \"{c.value.litToken.text}\"")
of tkBoolLiteral: be.emitLine(&"#define {c.name} {c.value.litToken.text}")
else: discard
be.emitLine("")
# Enum definitions
for e in module.enums:
be.emitEnumDef(e.name, e.variants)
if module.enums.len > 0:
be.emitLine("")
# Struct definitions
for s in module.structs:
be.emitStructDef(s.name, s.fields)
# Slice types (collect from functions/structs)
# Simple: scan function params/returns for slice types
var sliceTypes: seq[tuple[name: string, elem: string]] = @[]
for f in module.funcs:
for p in f.params:
let ct = typeToCStr(p.typ)
if ct.startsWith("Slice_"):
let elem = ct[6 .. ^1]
if not sliceTypes.anyIt(it.name == ct):
sliceTypes.add((ct, elem))
if sliceTypes.len > 0:
for st in sliceTypes:
be.emitLine(&"typedef struct {{ {st.elem}* data; size_t len; }} {st.name};")
be.emitLine("")
# Forward function declarations
for f in module.funcs:
let rt = typeToCStr(f.retType)
var params: seq[string] = @[]
for p in f.params:
params.add(&"{typeToCStr(p.typ)} {p.name}")
if params.len == 0: params.add("void")
be.emitLine(&"{rt} {f.name}({params.join(\", \")});")
be.emitLine("")
# VTable and fat pointer structs
for iface in module.interfaces:
if iface.hasAssocTypes: continue
let iname = iface.name
be.emitLine(&"typedef struct {iname}_VTable {{")
be.indent += 1
for m in iface.methods:
var paramCTypes: seq[string] = @["void* self"]
for i in 1 ..< m.params.len:
paramCTypes.add(typeToCStr(m.params[i]) & " param")
let rt = typeToCStr(m.ret)
be.emitLine(&"{rt} (*{m.name})({paramCTypes.join(\", \")});")
be.indent -= 1
be.emitLine(&"}} {iname}_VTable;")
be.emitLine(&"typedef struct {iname}_FatPtr {{")
be.indent += 1
be.emitLine("void* data;")
be.emitLine(&"{iname}_VTable* vtable;")
be.indent -= 1
be.emitLine(&"}} {iname}_FatPtr;")
be.emitLine("")
# VTable instances
for vt in module.vtables:
if vt.hasAssocTypes: continue
let varName = vt.concreteType & "_" & vt.interfaceName & "_VTable"
be.emitLine(&"{vt.interfaceName}_VTable {varName} = {{")
be.indent += 1
for m in vt.methodNames:
be.emitLine(&".{m} = (void*){vt.concreteType}_{m},")
be.indent -= 1
be.emitLine("};")
be.emitLine("")
# Emit all LIR functions
for f in builder.funcs:
be.emitFunc(f, funcRetTypes)
# C main wrapper
var hasMain = false
for f in module.funcs:
if f.name == "Main":
hasMain = true
break
if hasMain:
be.emitLine("/* C entry point wrapper */")
be.emitLine("extern int g_argc;")
be.emitLine("extern char** g_argv;")
be.emitLine("int main(int argc, char** argv) {")
be.emitLine(" g_argc = argc;")
be.emitLine(" g_argv = argv;")
be.emitLine(" return Main();")
be.emitLine("}")
return be.output
+780
View File
@@ -0,0 +1,780 @@
## LIR Lowering — HIR → LIR
## Converts the high-level HIR tree into flat, linear LIR instructions.
## Each HIR node kind lowers to 1-20 LIR instructions.
import std/[strutils, strformat, tables, sequtils]
import ast, types, token, hir, lir
## Convert LirValue to C expression string (no % prefix)
proc lirValToC(v: LirValue): string =
case v.kind
of lvkTemp: v.strVal
of lvkVar: v.strVal
of lvkInt: $v.intVal
of lvkFloat: $v.floatVal
of lvkString: v.strVal
of lvkLabel: v.strVal
of lvkGlobal: v.strVal
of lvkField: v.strVal
of lvkVoid: ""
of lvkType: v.strVal
type
LowerToLirCtx* = object
builder*: LirBuilder
## Map HIR var names -> C type names (for alloca/load/store type info)
varTypes*: Table[string, string]
## Map HIR var names -> LirValue kind (lvkVar or lvkTemp)
varLirValues*: Table[string, LirValue]
## C types for function params / returns
funcRetType*: string
## Current source location for debug
currentFile*: string
## Loop end labels for break/continue
loopEndLabels*: seq[string]
loopStartLabels*: seq[string]
proc initLowerToLirCtx*(): LowerToLirCtx =
result = LowerToLirCtx(
builder: initLirBuilder(),
varTypes: initTable[string, string](),
varLirValues: initTable[string, LirValue](),
loopEndLabels: @[],
loopStartLabels: @[],
)
# ── Helpers ──
proc cEscape(s: string): string =
result = ""
for c in s:
case c
of '\\': result.add("\\\\")
of '"': result.add("\\\"")
of '\n': result.add("\\n")
of '\r': result.add("\\r")
of '\t': result.add("\\t")
of '\0': result.add("\\0")
else: result.add(c)
proc typeToCStr(typ: Type): string =
## Convert a Bux Type to a C type string.
if typ == nil: return "int"
case typ.kind
of tkVoid: return "void"
of tkBool, tkBool8, tkBool16, 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, tkRef, tkMutRef:
if typ.inner.len > 0:
return typeToCStr(typ.inner[0]) & "*"
return "void*"
of tkDynRef:
return typ.name & "_FatPtr"
of tkSlice:
let elem = if typ.inner.len > 0: typeToCStr(typ.inner[0]) else: "void"
return "Slice_" & elem.replace(" ", "_").replace("*", "Ptr")
of tkNamed:
case typ.name
of "String", "str": return "const char*"
of "int": return "int"
of "int8": return "int8_t"
of "int16": return "int16_t"
of "int32": return "int32_t"
of "int64": return "int64_t"
of "uint": return "unsigned int"
of "uint8": return "uint8_t"
of "uint16": return "uint16_t"
of "uint32": return "uint32_t"
of "uint64": return "uint64_t"
of "float32": return "float"
of "float64": return "double"
of "bool": return "bool"
else: return typ.name
else: return "int"
proc hirTypeToC(ctx: var LowerToLirCtx, node: HirNode): string =
if node == nil: return "int"
result = typeToCStr(node.typ)
proc binOpToLir(op: TokenKind): LirKind =
case op
of tkPlus: lirAdd
of tkMinus: lirSub
of tkStar: lirMul
of tkSlash: lirDiv
of tkPercent: lirMod
of tkAmp: lirAnd
of tkPipe: lirOr
of tkCaret: lirXor
of tkShl: lirShl
of tkShr: lirShr
else: lirAdd
proc cmpOpToLir(op: TokenKind): LirKind =
case op
of tkEq: lirCmpEq
of tkNe: lirCmpNe
of tkLt: lirCmpLt
of tkLe: lirCmpLe
of tkGt: lirCmpGt
of tkGe: lirCmpGe
else: lirCmpEq
# ── Forward declarations ──
proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue
proc lowerStmt(ctx: var LowerToLirCtx, node: HirNode)
# ── Lowering: Expressions → LirValue ──
proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
if node == nil: return lirInt(0)
template b: var LirBuilder = ctx.builder
case node.kind
# ── Literals ──
of hLit:
case node.litToken.kind
of tkBoolLiteral:
if node.litToken.text == "true": return lirInt(1)
else: return lirInt(0)
of tkStringLiteral:
var text = node.litToken.text
# Handle backtick strings
if text.len >= 2 and text[0] == '`' and text[text.len-1] == '`':
text = "\"" & cEscape(text[1 ..< text.len-1]) & "\""
elif text.len >= 2 and text[0] == '"' and text[text.len-1] == '"':
# Strip c8" c16" c32" prefixes
if text.startsWith("c32\""):
text = "\"" & cEscape(text[4 ..< text.len-1]) & "\""
elif text.startsWith("c16\""):
text = "\"" & cEscape(text[4 ..< text.len-1]) & "\""
elif text.startsWith("c8\""):
text = "\"" & cEscape(text[3 ..< text.len-1]) & "\""
else:
text = "\"" & cEscape(text[1 ..< text.len-1]) & "\""
elif text.len >= 2 and text[0] == '"':
text = "\"" & cEscape(text[1 ..< text.len]) & "\""
else:
text = "\"" & cEscape(text) & "\""
return lirStr(text)
of tkNull:
return lirInt(0)
else:
# Integer/float literal
return lirVar(node.litToken.text)
# ── Variable reference ──
of hVar:
let name = node.varName
if ctx.varLirValues.hasKey(name):
return ctx.varLirValues[name]
return lirVar(name)
# ── Alloca (address of local) ──
of hAlloca:
let cType = typeToCStr(node.allocaType)
let name = node.allocaName
if not ctx.varLirValues.hasKey(name):
ctx.varTypes[name] = cType
ctx.varLirValues[name] = lirVar(name)
b.emitAlloca(name, cType)
return lirVar("&" & name)
# ── Self ──
of hSelf:
return lirVar("self")
# ── Unary ──
of hUnary:
let operand = lowerExpr(ctx, node.unaryOperand)
case node.unaryOp
of tkMinus:
let t = b.freshTemp()
b.emitUnary(lirNeg, t, operand)
return t
of tkBang:
let t = b.freshTemp()
b.emitUnary(lirNot, t, operand)
return t
of tkTilde:
let t = b.freshTemp()
b.emitUnary(lirBNot, t, operand)
return t
of tkStar:
# Dereference: *ptr → load
let t = b.freshTemp()
b.emitLoad(t, operand)
return t
of tkAmp:
# Address of: &expr
# Optimize: &struct.field → fieldPtr (no temp copy)
# &array[i] → indexPtr (no temp copy)
if node.unaryOperand.kind == hLoad and node.unaryOperand.loadPtr != nil:
let ptrNode = node.unaryOperand.loadPtr
case ptrNode.kind
of hFieldPtr:
let base = lowerExpr(ctx, ptrNode.fieldPtrBase)
let baseTyp = ptrNode.fieldPtrBase.typ
let isPtr = baseTyp != nil and baseTyp.kind in {tkPointer, tkRef, tkMutRef}
let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*")
if isPtr:
b.emitRawC(&"{t.strVal} = &({lirValToC(base)}->{ptrNode.fieldName});")
else:
b.emitRawC(&"{t.strVal} = &({lirValToC(base)}.{ptrNode.fieldName});")
return t
of hArrowField:
let base = lowerExpr(ctx, ptrNode.arrowFieldBase)
let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*")
b.emitRawC(&"{t.strVal} = &({lirValToC(base)}->{ptrNode.arrowFieldName});")
return t
of hIndexPtr:
let base = lowerExpr(ctx, ptrNode.indexPtrBase)
let idx = lowerExpr(ctx, ptrNode.indexPtrIndex)
let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*")
b.emitRawC(&"{t.strVal} = &({lirValToC(base)}[{lirValToC(idx)}]);")
return t
else: discard
let t = b.freshTemp()
b.emitAddrOf(t, operand)
return t
else:
return operand
# ── Binary ──
of hBinary:
let left = lowerExpr(ctx, node.binaryLeft)
let right = lowerExpr(ctx, node.binaryRight)
case node.binaryOp
of tkEq, tkNe, tkLt, tkLe, tkGt, tkGe:
let t = b.freshTemp()
b.emitCmp(cmpOpToLir(node.binaryOp), t, left, right)
return t
of tkAmpAmp, tkPipePipe:
# Logical and/or: lowered to branches for short-circuit evaluation
let t = b.freshTemp()
b.emitAlloca(t.strVal, "int")
let falseLbl = b.freshLabel("and_false")
let trueLbl = b.freshLabel("and_true")
let endLbl = b.freshLabel("and_end")
if node.binaryOp == tkAmpAmp:
# left && right: if !left goto false; if !right goto false; t=1; goto end; false: t=0; end:
b.emitJz(falseLbl, left)
b.emitJz(falseLbl, right)
b.emitMov(t, lirInt(1))
b.emitJmp(endLbl)
b.emitLabel(falseLbl)
b.emitMov(t, lirInt(0))
b.emitLabel(endLbl)
else:
# left || right: if left goto true; if right goto true; t=0; goto end; true: t=1; end:
b.emitJnz(trueLbl, left)
b.emitJnz(trueLbl, right)
b.emitMov(t, lirInt(0))
b.emitJmp(endLbl)
b.emitLabel(trueLbl)
b.emitMov(t, lirInt(1))
b.emitLabel(endLbl)
return t
else:
let t = b.freshTemp()
b.emitBinOp(binOpToLir(node.binaryOp), t, left, right)
return t
# ── Call ──
of hCall:
var args: seq[LirValue] = @[]
for arg in node.callArgs:
args.add(lowerExpr(ctx, arg))
let callee = node.callCallee
let t = b.freshTemp()
let cType = hirTypeToC(ctx, node)
if cType != "void" and cType != "":
b.emitAlloca(t.strVal, cType)
b.emitCall(t, callee, args)
return t
# ── CallIndirect ──
of hCallIndirect:
let callee = lowerExpr(ctx, node.callIndirectCallee)
var args: seq[LirValue] = @[callee]
for arg in node.callIndirectArgs:
args.add(lowerExpr(ctx, arg))
let t = b.freshTemp()
let cType = hirTypeToC(ctx, node)
if cType != "void" and cType != "":
b.emitAlloca(t.strVal, cType)
# Use lirCallIndirect: dst = (*fn_ptr)(args...)
b.emit(LirInstr(kind: lirCallIndirect, dst: t, src: callee, extra: args[1..^1]))
return t
# ── Field pointer expressions (return address) ──
# These return a typed pointer (void* for now, cast before deref)
of hFieldPtr:
let base = lowerExpr(ctx, node.fieldPtrBase)
let baseTyp = node.fieldPtrBase.typ
let isPtr = baseTyp != nil and baseTyp.kind in {tkPointer, tkRef, tkMutRef}
let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*")
if isPtr:
b.emitRawC(&"{t.strVal} = (void*)&({lirValToC(base)}->{node.fieldName});")
else:
b.emitRawC(&"{t.strVal} = (void*)&({lirValToC(base)}.{node.fieldName});")
return t
of hArrowField:
let base = lowerExpr(ctx, node.arrowFieldBase)
let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*")
b.emitRawC(&"{t.strVal} = (void*)&({lirValToC(base)}->{node.arrowFieldName});")
return t
of hIndexPtr:
let base = lowerExpr(ctx, node.indexPtrBase)
let idx = lowerExpr(ctx, node.indexPtrIndex)
let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*")
b.emitRawC(&"{t.strVal} = (void*)&({lirValToC(base)}[{lirValToC(idx)}]);")
return t
# ── Load ──
of hLoad:
# Load through a pointer or field access
# Optimize common patterns: load(field_ptr) → direct field access
if node.loadPtr != nil and node.loadPtr.kind == hArrowField:
let base = lowerExpr(ctx, node.loadPtr.arrowFieldBase)
let cType = hirTypeToC(ctx, node)
let t = b.freshTemp()
b.emitAlloca(t.strVal, cType)
b.emitRawC(&"{t.strVal} = {lirValToC(base)}->{node.loadPtr.arrowFieldName};")
return t
if node.loadPtr != nil and node.loadPtr.kind == hFieldPtr:
let base = lowerExpr(ctx, node.loadPtr.fieldPtrBase)
let baseTyp = node.loadPtr.fieldPtrBase.typ
let isPtr = baseTyp != nil and baseTyp.kind in {tkPointer, tkRef, tkMutRef}
let cType = hirTypeToC(ctx, node)
let t = b.freshTemp()
b.emitAlloca(t.strVal, cType)
if isPtr:
b.emitRawC(&"{t.strVal} = {lirValToC(base)}->{node.loadPtr.fieldName};")
else:
b.emitRawC(&"{t.strVal} = {lirValToC(base)}.{node.loadPtr.fieldName};")
return t
if node.loadPtr != nil and node.loadPtr.kind == hIndexPtr:
let base = lowerExpr(ctx, node.loadPtr.indexPtrBase)
let idx = lowerExpr(ctx, node.loadPtr.indexPtrIndex)
let cType = hirTypeToC(ctx, node)
let t = b.freshTemp()
b.emitAlloca(t.strVal, cType)
b.emitRawC(&"{t.strVal} = {lirValToC(base)}[{lirValToC(idx)}];")
return t
# Generic: dereference pointer
let ptrVal = lowerExpr(ctx, node.loadPtr)
let cType = hirTypeToC(ctx, node)
let t = b.freshTemp()
b.emitAlloca(t.strVal, cType)
b.emitRawC(&"{t.strVal} = *({cType}*){lirValToC(ptrVal)};")
return t
# ── Slice Index ──
of hSliceIndex:
let base = lowerExpr(ctx, node.sliceIndexBase)
let idx = lowerExpr(ctx, node.sliceIndexIndex)
let t = b.freshTemp()
# Emit: base.data[idx] (with optional bounds check)
if node.sliceIndexBoundsCheck:
b.emitRawC(&"bux_bounds_check((size_t)({lirValToC(idx)}), ({lirValToC(base)}).len)")
b.emit(LirInstr(kind: lirLoad, dst: t, src: base, src2: idx))
return t
# ── Cast ──
of hCast:
let operand = lowerExpr(ctx, node.castOperand)
let targetCType = typeToCStr(node.castType)
let t = b.freshTemp()
b.emitCast(t, operand, targetCType)
return t
# ── SizeOf ──
of hSizeOf:
let ctype = typeToCStr(node.sizeOfType)
let t = b.freshTemp()
b.emit(LirInstr(kind: lirRawC, src: lirStr(&"/* sizeof({ctype}) */")))
return lirVar(&"sizeof({ctype})")
# ── Spawn ──
of hSpawn:
if node.spawnAsync:
let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*")
b.emitCall(t, "bux_async_spawn", @[lirGlobal(node.spawnCallee)])
return t
else:
var args: seq[LirValue] = @[]
if node.spawnArgs.len > 0:
args.add(lowerExpr(ctx, node.spawnArgs[0]))
else:
args.add(lirInt(0))
let t = b.freshTemp()
b.emitAlloca(t.strVal, "void*")
b.emitCall(t, "bux_task_spawn", @[lirGlobal(node.spawnCallee)] & args)
return t
# ── DynRef (trait object) ──
of hDynRef:
let data = lowerExpr(ctx, node.dynRefData)
let t = b.freshTemp()
let fatPtrType = node.dynRefInterface & "_FatPtr"
b.emitRawC(&"{fatPtrType} {t.strVal};")
b.emitMov(lirVar(t.strVal & ".data"), data)
b.emitMov(lirVar(t.strVal & ".vtable"), lirGlobal(node.dynRefConcreteType & "_" & node.dynRefInterface & "_VTable"))
return t
# ── DynCall ──
of hDynCall:
let receiver = lowerExpr(ctx, node.dynCallReceiver)
var args: seq[LirValue] = @[receiver]
for i in 1 ..< node.dynCallArgs.len:
args.add(lowerExpr(ctx, node.dynCallArgs[i]))
let t = b.freshTemp()
b.emitRawC(&"{t.strVal} = {lirValToC(receiver)}.vtable->{node.dynCallMethod}({args.mapIt($it).join(\", \")});")
return t
# ── StructInit ──
of hStructInit:
var fields: seq[tuple[name: string, val: LirValue]] = @[]
for f in node.structInitFields:
fields.add((f.name, lowerExpr(ctx, f.value)))
let t = b.freshTemp()
b.emitStructInit(t, node.structInitName, fields)
return t
# ── SliceInit ──
of hSliceInit:
let t = b.freshTemp()
let elemType = if node.typ.inner.len > 0: typeToCStr(node.typ.inner[0]) else: "void"
var elems: seq[LirValue] = @[]
for e in node.sliceInitElements:
elems.add(lowerExpr(ctx, e))
# Create a temporary array, then wrap in slice
let arrTmp = b.freshTemp()
b.emitRawC(&"{elemType} {arrTmp.strVal}[] = {{{elems.mapIt($it).join(\", \")}}};")
b.emitSliceInit(t, elemType, arrTmp, lirInt(node.sliceInitLen))
return t
# ── TupleInit ──
of hTupleInit:
var elems: seq[LirValue] = @[]
for e in node.tupleInitElements:
elems.add(lowerExpr(ctx, e))
let t = b.freshTemp()
b.emitRawC(&"/* tuple */ {t.strVal} = {{{elems.mapIt($it).join(\", \")}}};")
return t
# ── If expression (ternary) ──
of hIf:
if node.ifThen.kind != hBlock and node.ifElse != nil:
# Simple ternary
let cond = lowerExpr(ctx, node.ifCond)
let thenVal = lowerExpr(ctx, node.ifThen)
let elseVal = lowerExpr(ctx, node.ifElse)
let t = b.freshTemp()
b.emitSelect(t, cond, thenVal, elseVal)
return t
else:
# Complex if — fallback to block lowering
# This shouldn't happen if lowering is done right, but handle gracefully
return lirInt(0)
# ── Block expression (returns last expr) ──
of hBlock:
for stmt in node.blockStmts:
lowerStmt(ctx, stmt)
if node.blockExpr != nil:
return lowerExpr(ctx, node.blockExpr)
return lirVoid()
# ── Match (lowered by hir_lower already, but handle if present) ──
of hMatch:
# Should have been lowered by hir_lower.nim already
return lirInt(0)
else:
# Fallback for unhandled expression kinds
b.emitComment(&"unhandled expr kind: {node.kind}")
return lirInt(0)
# ── Build C lvalue string for direct field/index assignment ──
proc buildLval(ctx: var LowerToLirCtx, n: HirNode): string =
case n.kind
of hLoad:
if n.loadPtr != nil:
return buildLval(ctx, n.loadPtr)
else:
let v = lowerExpr(ctx, n)
return lirValToC(v)
of hVar:
return n.varName
of hSelf:
return "self"
of hFieldPtr:
let baseStr = buildLval(ctx, n.fieldPtrBase)
let baseTyp = n.fieldPtrBase.typ
let isPtr = baseTyp != nil and baseTyp.kind in {tkPointer, tkRef, tkMutRef}
let sep = if isPtr: "->" else: "."
return baseStr & sep & n.fieldName
of hArrowField:
let baseStr = buildLval(ctx, n.arrowFieldBase)
return baseStr & "->" & n.arrowFieldName
of hIndexPtr:
let baseStr = buildLval(ctx, n.indexPtrBase)
let idx = lowerExpr(ctx, n.indexPtrIndex)
return baseStr & "[" & lirValToC(idx) & "]"
else:
let v = lowerExpr(ctx, n)
return lirValToC(v)
# ── Lowering: Statements → void ──
proc lowerStmt(ctx: var LowerToLirCtx, node: HirNode) =
if node == nil: return
template b: var LirBuilder = ctx.builder
case node.kind
# ── Return ──
of hReturn:
if node.returnValue != nil:
let val = lowerExpr(ctx, node.returnValue)
b.emitRet(val)
else:
b.emitRet()
# ── If statement ──
of hIf:
# Lower to: cond = lower(ifCond); jz else_label, cond
# lower(ifThen); jmp end_label
# else_label: lower(ifElse); end_label:
let cond = lowerExpr(ctx, node.ifCond)
let elseLbl = b.freshLabel("else")
let endLbl = b.freshLabel("endif")
if node.ifElse != nil:
b.emitJz(elseLbl, cond)
lowerStmt(ctx, node.ifThen)
b.emitJmp(endLbl)
b.emitLabel(elseLbl)
lowerStmt(ctx, node.ifElse)
b.emitLabel(endLbl)
else:
b.emitJz(endLbl, cond)
lowerStmt(ctx, node.ifThen)
b.emitLabel(endLbl)
# ── While statement ──
of hWhile:
let startLbl = b.freshLabel("while")
let bodyLbl = b.freshLabel("wbody")
let endLbl = b.freshLabel("wend")
ctx.loopStartLabels.add(startLbl.strVal)
ctx.loopEndLabels.add(endLbl.strVal)
b.emitLabel(startLbl)
let cond = lowerExpr(ctx, node.whileCond)
b.emitJz(endLbl, cond)
lowerStmt(ctx, node.whileBody)
b.emitJmp(startLbl)
b.emitLabel(endLbl)
discard ctx.loopStartLabels.pop()
discard ctx.loopEndLabels.pop()
# ── Loop (infinite) ──
of hLoop:
let startLbl = b.freshLabel("loop")
let endLbl = b.freshLabel("lend")
ctx.loopStartLabels.add(startLbl.strVal)
ctx.loopEndLabels.add(endLbl.strVal)
b.emitLabel(startLbl)
lowerStmt(ctx, node.loopBody)
b.emitJmp(startLbl)
b.emitLabel(endLbl)
discard ctx.loopStartLabels.pop()
discard ctx.loopEndLabels.pop()
# ── Break ──
of hBreak:
if ctx.loopEndLabels.len > 0:
b.emitJmp(lirLabel(ctx.loopEndLabels[^1]))
else:
b.emitRawC("break;")
# ── Continue ──
of hContinue:
if ctx.loopStartLabels.len > 0:
b.emitJmp(lirLabel(ctx.loopStartLabels[^1]))
else:
b.emitRawC("continue;")
# ── Alloca ──
of hAlloca:
let cType = typeToCStr(node.allocaType)
let name = node.allocaName
ctx.varTypes[name] = cType
ctx.varLirValues[name] = lirVar(name)
b.emitAlloca(name, cType)
# ── Store ──
of hStore:
# If storing to a simple variable, use mov (direct assignment)
if node.storePtr.kind == hVar:
let val = lowerExpr(ctx, node.storeValue)
b.emitMov(lirVar(node.storePtr.varName), val)
else:
let ptrVal = lowerExpr(ctx, node.storePtr)
let val = lowerExpr(ctx, node.storeValue)
# ptrVal is a void* address; cast and store
let valCType = hirTypeToC(ctx, node.storeValue)
b.emitRawC(&"*({valCType}*){lirValToC(ptrVal)} = {lirValToC(val)};")
# ── Assign ──
of hAssign:
let value = lowerExpr(ctx, node.assignValue)
case node.assignOp
of tkAssign:
case node.assignTarget.kind
of hFieldPtr:
let lval = buildLval(ctx, node.assignTarget)
b.emitRawC(&"{lval} = {lirValToC(value)};")
of hArrowField:
let lval = buildLval(ctx, node.assignTarget)
b.emitRawC(&"{lval} = {lirValToC(value)};")
of hIndexPtr:
let base = lowerExpr(ctx, node.assignTarget.indexPtrBase)
let idx = lowerExpr(ctx, node.assignTarget.indexPtrIndex)
b.emit(LirInstr(kind: lirStore, src: value, src2: base, dst: idx))
of hLoad:
if node.assignTarget.loadPtr != nil:
let ptrNode = node.assignTarget.loadPtr
case ptrNode.kind
of hIndexPtr:
let base = lowerExpr(ctx, ptrNode.indexPtrBase)
let idx = lowerExpr(ctx, ptrNode.indexPtrIndex)
b.emit(LirInstr(kind: lirStore, src: value, src2: base, dst: idx))
of hFieldPtr:
let lval = buildLval(ctx, ptrNode)
b.emitRawC(&"{lval} = {lirValToC(value)};")
of hArrowField:
let lval = buildLval(ctx, ptrNode)
b.emitRawC(&"{lval} = {lirValToC(value)};")
else:
let ptrVal = lowerExpr(ctx, ptrNode)
let valCType = hirTypeToC(ctx, node.assignValue)
b.emitRawC(&"*({valCType}*){lirValToC(ptrVal)} = {lirValToC(value)};")
else:
let target = lowerExpr(ctx, node.assignTarget)
b.emitMov(target, value)
else:
let target = lowerExpr(ctx, node.assignTarget)
b.emitMov(target, value)
of tkPlusAssign:
let target = lowerExpr(ctx, node.assignTarget)
let t = b.freshTemp()
b.emitBinOp(lirAdd, t, target, value)
b.emit(LirInstr(kind: lirStore, src: t, src2: target))
of tkMinusAssign:
let target = lowerExpr(ctx, node.assignTarget)
let t = b.freshTemp()
b.emitBinOp(lirSub, t, target, value)
b.emit(LirInstr(kind: lirStore, src: t, src2: target))
else:
let target = lowerExpr(ctx, node.assignTarget)
b.emitMov(target, value)
# ── Call statement (void return) ──
of hCall:
var args: seq[LirValue] = @[]
for arg in node.callArgs:
args.add(lowerExpr(ctx, arg))
b.emitCallVoid(node.callCallee, args)
# ── CallIndirect statement ──
of hCallIndirect:
let callee = lowerExpr(ctx, node.callIndirectCallee)
var args: seq[LirValue] = @[]
for arg in node.callIndirectArgs:
args.add(lowerExpr(ctx, arg))
b.emit(LirInstr(kind: lirCallIndirect, src: callee, extra: args))
# ── Block ──
of hBlock:
if node.isScope:
b.emitRawC("{")
for stmt in node.blockStmts:
lowerStmt(ctx, stmt)
if node.blockExpr != nil:
let exprVal = lowerExpr(ctx, node.blockExpr)
# If block is an expression, store result
discard
if node.isScope:
b.emitRawC("}")
# ── Emit (inline C) ──
of hEmit:
b.emitRawC(node.emitCode)
# ── Expression statement ──
else:
let exprVal = lowerExpr(ctx, node)
# Expression evaluated for side effects; temp is unused
discard
# ── Module-level lowering ──
proc lowerModuleToLir*(hirMod: HirModule): LirBuilder =
## Convert a full HIR module into LIR functions.
var ctx = initLowerToLirCtx()
for f in hirMod.funcs:
var params: seq[tuple[name: string, cType: string]] = @[]
for p in f.params:
let ct = typeToCStr(p.typ)
params.add((p.name, ct))
ctx.varTypes[p.name] = ct
ctx.varLirValues[p.name] = lirVar(p.name)
let retCT = if f.retType != nil: typeToCStr(f.retType) else: "void"
ctx.funcRetType = retCT
ctx.builder.beginFunc(f.name, params, retCT, f.isPublic)
if f.body != nil:
if f.body.kind == hBlock:
for stmt in f.body.blockStmts:
lowerStmt(ctx, stmt)
if f.body.blockExpr != nil and f.retType != nil and f.retType.kind != tkVoid:
let val = lowerExpr(ctx, f.body.blockExpr)
ctx.builder.emitRet(val)
else:
lowerStmt(ctx, f.body)
ctx.builder.endFunc()
return ctx.builder
+1 -1
View File
@@ -1,6 +1,6 @@
[Package] [Package]
Name = "buxc" Name = "buxc"
Version = "0.1.0" Version = "0.3.0"
Type = "bin" Type = "bin"
[Build] [Build]
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+35 -27
View File
@@ -36,7 +36,7 @@ make dev
The output is a single binary: `buxc` (bootstrap compiler in Nim). The output is a single binary: `buxc` (bootstrap compiler in Nim).
The self-hosted compiler `buxc2` is built from `compiler/selfhost/*.bux` sources via: The self-hosted compiler `buxc2` is built from `src/*.bux` sources via:
```bash ```bash
make selfhost make selfhost
``` ```
@@ -135,31 +135,39 @@ cd examples_pkg/hello && ../../buxc run
``` ```
bux/ bux/
├── src/ # Bootstrap compiler source (Nim) ├── src/ # Self-hosted compiler source (Bux)
│ ├── main.nim # Entry point │ ├── Main.bux # Entry point
│ ├── cli.nim # CLI commands │ ├── Cli.bux # CLI commands
│ ├── lexer.nim # Tokenizer │ ├── Lexer.bux # Tokenizer
│ ├── parser.nim # Parser │ ├── Parser.bux # Parser
│ ├── sema.nim # Semantic analysis │ ├── Ast.bux # AST definitions
│ ├── hir.nim # High-level IR │ ├── Sema.bux # Semantic analysis
│ ├── hir_lower.nim # AST → HIR lowering │ ├── Types.bux # Type system
│ ├── c_backend.nim # HIR → C code generation │ ├── Scope.bux # Symbol table
│ ├── Hir.bux # High-level IR
│ ├── HirLower.bux # AST → HIR lowering
│ ├── CBackend.bux # HIR → C code generation
│ ├── Manifest.bux # bux.toml parser
│ └── Token.bux # Token definitions
├── bootstrap/ # Bootstrap compiler (Nim) — compiles src/ → buxc
│ ├── main.nim
│ ├── cli.nim
│ └── ... │ └── ...
├── library/ # Standard library ├── lib/ # Standard library (Bux)
│ ├── Std/ │ ├── Io.bux
│ ├── Io.bux │ ├── Array.bux
│ ├── Array.bux │ ├── String.bux
│ ├── String.bux │ ├── Map.bux
│ ├── Map.bux │ ├── Fs.bux
│ ├── Fs.bux │ ├── Mem.bux
│ ├── Mem.bux │ ├── Set.bux
│ ├── Set.bux │ ├── Path.bux
│ ├── Path.bux │ ├── Math.bux
│ ├── Math.bux │ ├── Task.bux
│ ├── Task.bux └── Channel.bux
│ │ └── Channel.bux ├── rt/ # C runtime
│ ├── runtime.c # C runtime shim │ ├── runtime.c
│ └── io.c # C I/O functions │ └── io.c
├── examples/ # Example programs ├── examples/ # Example programs
├── tests/ # Unit tests (Nim) ├── tests/ # Unit tests (Nim)
├── docs/ # Documentation ├── docs/ # Documentation
@@ -183,7 +191,7 @@ cat build/main.c
### Inspecting Generated C (self-hosted) ### Inspecting Generated C (self-hosted)
```bash ```bash
cd _selfhost && ../buxc build cd src && ../buxc build
cat build/main.c cat build/main.c
``` ```
@@ -191,7 +199,7 @@ cat build/main.c
| Error | Cause | Fix | | Error | Cause | Fix |
|-------|-------|-----| |-------|-------|-----|
| `stdlib directory not found` | `buxc` can't find `library/` | Run from project root or set correct path | | `stdlib directory not found` | `buxc` can't find `lib/` | Run from project root or set correct path |
| `duplicate symbol 'bux_alloc'` | Multiple stdlib modules declare same extern | Only declare in one module | | `duplicate symbol 'bux_alloc'` | Multiple stdlib modules declare same extern | Only declare in one module |
| `C compilation failed` | Generated C has errors | Check `build/main.c` for issues | | `C compilation failed` | Generated C has errors | Check `build/main.c` for issues |
+18 -19
View File
@@ -89,16 +89,16 @@ func Json_ArrayGet(v: JsonValue, index: uint) -> JsonValue {
func Json_ArrayPush(self: *JsonValue, val: JsonValue) { func Json_ArrayPush(self: *JsonValue, val: JsonValue) {
if self.tag != JsonTagArray { return; } if self.tag != JsonTagArray { return; }
if self.arrLen >= self.arrCap { if self.arrLen >= self.arrCap {
var newCap: uint = self.arrCap; // Compute new capacity (avoid var scoping bug in selfhost sema)
if newCap == 0 { newCap = 4; } let arrNewCap: uint = self.arrCap;
else { newCap = newCap * 2; } if arrNewCap == 0 {
let newSize: uint = newCap * sizeof(JsonValue); self.arrCap = 4;
if self.arrCap == 0 { self.arrData = Alloc(4 * sizeof(JsonValue)) as *JsonValue;
self.arrData = Alloc(newSize) as *JsonValue;
} else { } else {
self.arrData = Realloc(self.arrData as *void, newSize) as *JsonValue; let doubleCap: uint = arrNewCap * 2;
self.arrCap = doubleCap;
self.arrData = Realloc(self.arrData as *void, doubleCap * sizeof(JsonValue)) as *JsonValue;
} }
self.arrCap = newCap;
} }
self.arrData[self.arrLen] = val; self.arrData[self.arrLen] = val;
self.arrLen = self.arrLen + 1; self.arrLen = self.arrLen + 1;
@@ -145,19 +145,18 @@ func Json_ObjectSet(self: *JsonValue, key: String, val: JsonValue) {
i = i + 1; i = i + 1;
} }
if self.objLen >= self.objCap { if self.objLen >= self.objCap {
var newCap: uint = self.objCap; // Compute new capacity (avoid var scoping bug in selfhost sema)
if newCap == 0 { newCap = 4; } let objNewCap: uint = self.objCap;
else { newCap = newCap * 2; } if objNewCap == 0 {
let keySize: uint = newCap * sizeof(String); self.objCap = 4;
let valSize: uint = newCap * sizeof(JsonValue); self.objKeys = Alloc(4 * sizeof(String)) as *String;
if self.objCap == 0 { self.objValues = Alloc(4 * sizeof(JsonValue)) as *JsonValue;
self.objKeys = Alloc(keySize) as *String;
self.objValues = Alloc(valSize) as *JsonValue;
} else { } else {
self.objKeys = Realloc(self.objKeys as *void, keySize) as *String; let doubleCap: uint = objNewCap * 2;
self.objValues = Realloc(self.objValues as *void, valSize) as *JsonValue; self.objCap = doubleCap;
self.objKeys = Realloc(self.objKeys as *void, doubleCap * sizeof(String)) as *String;
self.objValues = Realloc(self.objValues as *void, doubleCap * sizeof(JsonValue)) as *JsonValue;
} }
self.objCap = newCap;
} }
self.objKeys[self.objLen] = key; self.objKeys[self.objLen] = key;
self.objValues[self.objLen] = val; self.objValues[self.objLen] = val;
+1 -25
View File
@@ -507,31 +507,7 @@ int bux_write_file(const char* path, const char* content) {
FILE* f = fopen(path, "wb"); FILE* f = fopen(path, "wb");
if (!f) return 0; if (!f) return 0;
size_t len = strlen(content); size_t len = strlen(content);
size_t written = 0; size_t written = fwrite(content, 1, len, f);
for (size_t i = 0; i < len; i++) {
if (content[i] == '\\' && i + 1 < len && content[i + 1] == 'n') {
if (fputc('\n', f) == EOF) break;
i++;
} else if (content[i] == '\\' && i + 1 < len && content[i + 1] == 't') {
if (fputc('\t', f) == EOF) break;
i++;
} else if (content[i] == '\\' && i + 1 < len && content[i + 1] == 'r') {
if (fputc('\r', f) == EOF) break;
i++;
} else if (content[i] == '\\' && i + 1 < len && content[i + 1] == '\\') {
if (fputc('\\', f) == EOF) break;
i++;
} else if (content[i] == '\\' && i + 1 < len && content[i + 1] == '"') {
if (fputc('"', f) == EOF) break;
i++;
} else if (content[i] == '\\' && i + 1 < len && content[i + 1] == '\'') {
if (fputc('\'', f) == EOF) break;
i++;
} else {
if (fputc(content[i], f) == EOF) break;
}
written++;
}
fclose(f); fclose(f);
return written > 0 ? 1 : 0; return written > 0 ? 1 : 0;
} }
+226 -9
View File
@@ -2,6 +2,7 @@
// Generates C code from the HIR. // Generates C code from the HIR.
module CBackend { module CBackend {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Type → C type name // Type → C type name
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -102,13 +103,49 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
if node.intValue == tkNull { if node.intValue == tkNull {
StringBuilder_Append(&cbe.sb, "0"); StringBuilder_Append(&cbe.sb, "0");
} else { } else {
StringBuilder_Append(&cbe.sb, node.strValue); let s: String = node.strValue;
let slen: int = String_Len(s) as int;
var start: int = 0;
var end: int = slen;
// If the literal is wrapped in quotes (from lexer), preserve outer quotes
// and only escape the inner content.
if slen >= 2 && s[0] == 34 as char8 && s[slen - 1] == 34 as char8 {
StringBuilder_Append(&cbe.sb, "\"");
start = 1;
end = slen - 1;
}
var i: int = start;
while i < end {
let c: int = s[i] as int;
if c == 34 {
StringBuilder_Append(&cbe.sb, "\\\"");
} else if c == 92 {
StringBuilder_Append(&cbe.sb, "\\\\");
} else if c == 10 {
StringBuilder_Append(&cbe.sb, "\\n");
} else if c == 9 {
StringBuilder_Append(&cbe.sb, "\\t");
} else if c == 13 {
StringBuilder_Append(&cbe.sb, "\\r");
} else {
StringBuilder_AppendChar(&cbe.sb, c as char8);
}
i = i + 1;
}
if slen >= 2 && s[0] == 34 as char8 && s[slen - 1] == 34 as char8 {
StringBuilder_Append(&cbe.sb, "\"");
}
} }
return; return;
} }
// Variable // Variable
if kind == hVar { if kind == hVar {
if node.intValue != 0 {
StringBuilder_Append(&cbe.sb, "/* hVar tk=");
StringBuilder_AppendInt(&cbe.sb, node.intValue);
StringBuilder_Append(&cbe.sb, " */");
}
StringBuilder_Append(&cbe.sb, node.strValue); StringBuilder_Append(&cbe.sb, node.strValue);
return; return;
} }
@@ -228,6 +265,7 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
// If // If
if kind == hIf { if kind == hIf {
StringBuilder_Append(&cbe.sb, "if ("); StringBuilder_Append(&cbe.sb, "if (");
CBE_EmitExpr(cbe, node.child1); CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ") {\n"); StringBuilder_Append(&cbe.sb, ") {\n");
@@ -296,10 +334,32 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
return; return;
} }
// Field access: obj.field — emit as obj->field (since most are pointers) // Break / Continue
if kind == hBreak {
StringBuilder_Append(&cbe.sb, "break");
return;
}
if kind == hContinue {
StringBuilder_Append(&cbe.sb, "continue");
return;
}
// Field access: obj.field — use -> if base is pointer, else .
if kind == hFieldPtr { if kind == hFieldPtr {
CBE_EmitExpr(cbe, node.child1); CBE_EmitExpr(cbe, node.child1);
var isPtr: bool = false;
if node.child1 != null as *HirNode {
if node.child1.typeKind == tyPointer {
isPtr = true;
} else if String_EndsWith(node.child1.typeName, "*") {
isPtr = true;
}
}
if isPtr {
StringBuilder_Append(&cbe.sb, "->"); StringBuilder_Append(&cbe.sb, "->");
} else {
StringBuilder_Append(&cbe.sb, ".");
}
StringBuilder_Append(&cbe.sb, node.strValue); StringBuilder_Append(&cbe.sb, node.strValue);
return; return;
} }
@@ -326,7 +386,53 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
} }
// Load: *ptr — emit as *ptr (dereference) // Load: *ptr — emit as *ptr (dereference)
// Optimize common patterns to avoid & / * temporaries
if kind == hLoad { if kind == hLoad {
if node.child1 != null as *HirNode {
let ptrNode: *HirNode = node.child1;
let ptrKind: int = ptrNode.kind;
// field access: load(field_ptr(base, field)) → base.field
if ptrKind == hFieldPtr {
CBE_EmitExpr(cbe, ptrNode.child1);
var isPtr: bool = false;
if ptrNode.child1 != null as *HirNode {
if ptrNode.child1.typeKind == tyPointer {
isPtr = true;
} else if String_EndsWith(ptrNode.child1.typeName, "*") {
isPtr = true;
}
}
if isPtr {
StringBuilder_Append(&cbe.sb, "->");
} else {
StringBuilder_Append(&cbe.sb, ".");
}
StringBuilder_Append(&cbe.sb, ptrNode.strValue);
return;
}
// arrow field: load(arrow_field(base, field)) → base->field
if ptrKind == hArrowField {
CBE_EmitExpr(cbe, ptrNode.child1);
StringBuilder_Append(&cbe.sb, "->");
StringBuilder_Append(&cbe.sb, ptrNode.strValue);
return;
}
// arrow field: load(arrow_field(base, field)) → base->field
if ptrKind == hArrowField {
CBE_EmitExpr(cbe, ptrNode.child1);
StringBuilder_Append(&cbe.sb, "->");
StringBuilder_Append(&cbe.sb, ptrNode.strValue);
return;
}
// index: load(index_ptr(base, idx)) → base[idx]
if ptrKind == hIndexPtr {
CBE_EmitExpr(cbe, ptrNode.child1);
StringBuilder_Append(&cbe.sb, "[");
CBE_EmitExpr(cbe, ptrNode.child2);
StringBuilder_Append(&cbe.sb, "]");
return;
}
}
StringBuilder_Append(&cbe.sb, "(*"); StringBuilder_Append(&cbe.sb, "(*");
CBE_EmitExpr(cbe, node.child1); CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ")"); StringBuilder_Append(&cbe.sb, ")");
@@ -467,11 +573,18 @@ func CBE_EmitFuncDecl(cbe: *CEmitter, f: *HirFunc) {
func CBE_IsGenericTypeName(name: String) -> bool { func CBE_IsGenericTypeName(name: String) -> bool {
if String_Eq(name, "T") || String_Eq(name, "K") || String_Eq(name, "V") { return true; } if String_Eq(name, "T") || String_Eq(name, "K") || String_Eq(name, "V") { return true; }
if String_Eq(name, "T*") || String_Eq(name, "K*") || String_Eq(name, "V*") { return true; } if String_Eq(name, "T*") || String_Eq(name, "K*") || String_Eq(name, "V*") { return true; }
// Any type name containing '<' is a generic instantiation or parameter
if String_Contains(name, "<") { return true; }
// Generic container structs and their pointer variants // Generic container structs and their pointer variants
if String_Eq(name, "Array") || String_Eq(name, "Array*") { return true; } if String_Eq(name, "Array") || String_Eq(name, "Array*") { return true; }
if String_Eq(name, "Channel") || String_Eq(name, "Channel*") { return true; }
if String_Eq(name, "Iter") || String_Eq(name, "Iter*") { return true; }
if String_Eq(name, "Set") || String_Eq(name, "Set*") { return true; }
if String_Eq(name, "SetEntry") || String_Eq(name, "SetEntry*") { return true; } if String_Eq(name, "SetEntry") || String_Eq(name, "SetEntry*") { return true; }
if String_Eq(name, "StringMapEntry") || String_Eq(name, "StringMapEntry*") { return true; } if String_Eq(name, "Map") || String_Eq(name, "Map*") { return true; }
if String_Eq(name, "MapEntry") || String_Eq(name, "MapEntry*") { return true; } if String_Eq(name, "MapEntry") || String_Eq(name, "MapEntry*") { return true; }
if String_Eq(name, "StringMap") || String_Eq(name, "StringMap*") { return true; }
if String_Eq(name, "StringMapEntry") || String_Eq(name, "StringMapEntry*") { return true; }
return false; return false;
} }
@@ -550,16 +663,104 @@ func CBackend_Generate(mod: *HirModule) -> String {
} }
StringBuilder_Append(&cbe.sb, "\n"); StringBuilder_Append(&cbe.sb, "\n");
// Enum definitions (typedef to int) // Enum definitions
var ei: int = 0; var ei: int = 0;
while ei < mod.enumCount { while ei < mod.enumCount {
StringBuilder_Append(&cbe.sb, "typedef int "); let en: *HirEnum = &mod.enums[ei];
StringBuilder_Append(&cbe.sb, mod.enums[ei].name); var hasData: bool = false;
StringBuilder_Append(&cbe.sb, ";\n"); var vi: int = 0;
ei = ei + 1; while vi < en.variantCount {
if en.variants[vi].fieldCount > 0 {
hasData = true;
}
vi = vi + 1;
}
if !hasData {
// Simple enum: typedef enum { A, B } Name;
StringBuilder_Append(&cbe.sb, "typedef enum {\n");
vi = 0;
while vi < en.variantCount {
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, "_");
StringBuilder_Append(&cbe.sb, en.variants[vi].name);
if vi < en.variantCount - 1 {
StringBuilder_Append(&cbe.sb, ",");
} }
if mod.enumCount > 0 {
StringBuilder_Append(&cbe.sb, "\n"); StringBuilder_Append(&cbe.sb, "\n");
vi = vi + 1;
}
StringBuilder_Append(&cbe.sb, "} ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, ";\n\n");
} else {
// Algebraic enum: tag enum + data union + struct
// 1. Tag enum
StringBuilder_Append(&cbe.sb, "typedef enum {\n");
vi = 0;
while vi < en.variantCount {
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, "_");
StringBuilder_Append(&cbe.sb, en.variants[vi].name);
if vi < en.variantCount - 1 {
StringBuilder_Append(&cbe.sb, ",");
}
StringBuilder_Append(&cbe.sb, "\n");
vi = vi + 1;
}
StringBuilder_Append(&cbe.sb, "} ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, "_Tag;\n\n");
// 2. Data union
StringBuilder_Append(&cbe.sb, "typedef union {\n");
vi = 0;
while vi < en.variantCount {
let ev: *HirEnumVariant = &en.variants[vi];
if ev.fieldCount > 0 {
if ev.fieldCount == 1 {
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, CBackend_TypeToC(ev.fieldType0));
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, ev.name);
StringBuilder_Append(&cbe.sb, "_0;\n");
} else {
StringBuilder_Append(&cbe.sb, " struct {\n");
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, CBackend_TypeToC(ev.fieldType0));
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, ev.fieldName0);
StringBuilder_Append(&cbe.sb, ";\n");
if ev.fieldCount > 1 {
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, CBackend_TypeToC(ev.fieldType1));
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, ev.fieldName1);
StringBuilder_Append(&cbe.sb, ";\n");
}
StringBuilder_Append(&cbe.sb, " } ");
StringBuilder_Append(&cbe.sb, ev.name);
StringBuilder_Append(&cbe.sb, ";\n");
}
}
vi = vi + 1;
}
StringBuilder_Append(&cbe.sb, "} ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, "_Data;\n\n");
// 3. Main struct
StringBuilder_Append(&cbe.sb, "typedef struct {\n");
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, "_Tag tag;\n");
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, "_Data data;\n");
StringBuilder_Append(&cbe.sb, "} ");
StringBuilder_Append(&cbe.sb, en.name);
StringBuilder_Append(&cbe.sb, ";\n\n");
}
ei = ei + 1;
} }
// Constant definitions // Constant definitions
@@ -611,6 +812,22 @@ func CBackend_Generate(mod: *HirModule) -> String {
si = si + 1; si = si + 1;
} }
// Opaque forward declarations for generic structs (used only by pointer)
si = 0;
while si < mod.structCount {
if CBE_StructHasGeneric(&mod.structs[si]) {
StringBuilder_Append(&cbe.sb, "typedef struct ");
StringBuilder_Append(&cbe.sb, mod.structs[si].name);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, mod.structs[si].name);
StringBuilder_Append(&cbe.sb, ";\n");
}
si = si + 1;
}
if mod.structCount > 0 {
StringBuilder_Append(&cbe.sb, "\n");
}
// Forward declarations for all functions (skip generics) // Forward declarations for all functions (skip generics)
var i: int = 0; var i: int = 0;
while i < mod.funcCount { while i < mod.funcCount {
+30 -33
View File
@@ -31,7 +31,7 @@ func DirExists(path: String) -> bool {
} }
// Import the compiler pipeline // Import the compiler pipeline
// In self-hosting mode, these are compiled together from compiler/selfhost/ // In self-hosting mode, these are compiled together from src/
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Compile a single .bux source file // Compile a single .bux source file
@@ -139,20 +139,20 @@ func Cli_Build(srcPath: String, outPath: String) -> int {
Print(" → C written to "); Print(" → C written to ");
PrintLine(cFile); PrintLine(cFile);
// Find runtime.c and io.c for linking // Find runtime.c and io.c for linking (rt/ directory)
var rtPath: String = "library/runtime/runtime.c"; var rtPath: String = "rt/runtime.c";
var ioPath: String = "library/runtime/io.c"; var ioPath: String = "rt/io.c";
if !FileExists(rtPath) { if !FileExists(rtPath) {
rtPath = "../library/runtime/runtime.c"; rtPath = "../rt/runtime.c";
} }
if !FileExists(ioPath) { if !FileExists(ioPath) {
ioPath = "../library/runtime/io.c"; ioPath = "../rt/io.c";
} }
if !FileExists(rtPath) { if !FileExists(rtPath) {
rtPath = "/home/ziko/z-git/bux/bux/library/runtime/runtime.c"; rtPath = "../../rt/runtime.c";
} }
if !FileExists(ioPath) { if !FileExists(ioPath) {
ioPath = "/home/ziko/z-git/bux/bux/library/runtime/io.c"; ioPath = "../../rt/io.c";
} }
// Compile with cc // Compile with cc
@@ -294,11 +294,11 @@ func Cli_CompileSource(source: String, sourceName: String) -> *HirModule {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
func Cli_FindStdlibDir(projectDir: String) -> String { func Cli_FindStdlibDir(projectDir: String) -> String {
var path: String = bux_path_join(projectDir, "stdlib"); var path: String = bux_path_join(projectDir, "lib");
if DirExists(path) { return path; } if DirExists(path) { return path; }
path = bux_path_join(projectDir, "../stdlib"); path = bux_path_join(projectDir, "../lib");
if DirExists(path) { return path; } if DirExists(path) { return path; }
path = "/home/ziko/z-git/bux/bux/stdlib"; path = bux_path_join(projectDir, "../../lib");
if DirExists(path) { return path; } if DirExists(path) { return path; }
return ""; return "";
} }
@@ -377,7 +377,7 @@ func Cli_CollectStdlibImports(mod: *Module, outPaths: *String, maxCount: int, st
j = j + 1; j = j + 1;
} }
if !String_Eq(modName, "") { if !String_Eq(modName, "") {
let filePath: String = bux_path_join(bux_path_join(stdlibDir, "Std"), String_Concat(modName, ".bux")); let filePath: String = bux_path_join(stdlibDir, String_Concat(modName, ".bux"));
// Check for duplicates // Check for duplicates
var dup: bool = false; var dup: bool = false;
var di: int = 0; var di: int = 0;
@@ -558,31 +558,28 @@ func Cli_BuildProject(projectDir: String) -> int {
merged.itemCount = 0; merged.itemCount = 0;
merged.firstItem = null as *Decl; merged.firstItem = null as *Decl;
// Find and merge stdlib declarations (only imported modules) // Find and merge ALL stdlib declarations
let stdlibDir: String = Cli_FindStdlibDir(projectDir); let stdlibDir: String = Cli_FindStdlibDir(projectDir);
if !String_Eq(stdlibDir, "") { if !String_Eq(stdlibDir, "") {
Print("Stdlib found: "); Print("Stdlib found: ");
PrintLine(stdlibDir); PrintLine(stdlibDir);
// Collect imported stdlib modules from user code // List all .bux files in lib/
let maxImports: int = 64; var libCount: int = 0;
let importPaths: *String = bux_alloc(maxImports * 8) as *String; let libFiles: *String = bux_list_dir(stdlibDir, ".bux", &libCount);
let importCount: int = Cli_CollectStdlibImports(userMerged, importPaths, maxImports, stdlibDir);
Print("Imported stdlib modules: ");
PrintInt(importCount);
PrintLine("");
if importCount > 0 {
var si: int = 0;
var stdAdded: int = 0; var stdAdded: int = 0;
while si < importCount { if libCount > 0 {
var si: int = 0;
while si < libCount {
Print(" Merging "); Print(" Merging ");
PrintLine(importPaths[si]); PrintLine(libFiles[si]);
let added: int = Cli_MergeFileInto(merged, importPaths[si], userNames, userNameCount); let added: int = Cli_MergeFileInto(merged, libFiles[si], userNames, userNameCount);
stdAdded = stdAdded + added; stdAdded = stdAdded + added;
si = si + 1; si = si + 1;
} }
}
Print("Stdlib declarations added: "); Print("Stdlib declarations added: ");
PrintInt(stdAdded); PrintInt(stdAdded);
} PrintLine("");
} }
// Copy user declarations into merged (user shadows stdlib) // Copy user declarations into merged (user shadows stdlib)
@@ -629,20 +626,20 @@ func Cli_BuildProject(projectDir: String) -> int {
Print("C code written to "); Print("C code written to ");
PrintLine(cFile); PrintLine(cFile);
// Find runtime.c and io.c (use stdlibDir if available) // Find runtime.c and io.c (look in rt/ directory)
var rtPath: String = bux_path_join(stdlibDir, "runtime.c"); var rtPath: String = bux_path_join(projectDir, "rt/runtime.c");
var ioPath: String = bux_path_join(stdlibDir, "io.c"); var ioPath: String = bux_path_join(projectDir, "rt/io.c");
if !FileExists(rtPath) { if !FileExists(rtPath) {
rtPath = bux_path_join(projectDir, "library/runtime/runtime.c"); rtPath = bux_path_join(projectDir, "../rt/runtime.c");
} }
if !FileExists(ioPath) { if !FileExists(ioPath) {
ioPath = bux_path_join(projectDir, "library/runtime/io.c"); ioPath = bux_path_join(projectDir, "../rt/io.c");
} }
if !FileExists(rtPath) { if !FileExists(rtPath) {
rtPath = bux_path_join(projectDir, "../library/runtime/runtime.c"); rtPath = bux_path_join(projectDir, "../../rt/runtime.c");
} }
if !FileExists(ioPath) { if !FileExists(ioPath) {
ioPath = bux_path_join(projectDir, "../library/runtime/io.c"); ioPath = bux_path_join(projectDir, "../../rt/io.c");
} }
// Compile with cc // Compile with cc
+2
View File
@@ -129,6 +129,8 @@ struct HirConst {
struct HirEnum { struct HirEnum {
name: String; name: String;
variantCount: int;
variants: *HirEnumVariant;
} }
struct HirModule { struct HirModule {
+128 -31
View File
@@ -2,6 +2,7 @@
// Transforms the typed AST into a lower-level IR suitable for code generation. // Transforms the typed AST into a lower-level IR suitable for code generation.
module HirLower { module HirLower {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Lowering context // Lowering context
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -22,15 +23,7 @@ struct LowerCtx {
// resolves the correct Type.kind for codegen. // resolves the correct Type.kind for codegen.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
func Lcx_ResolveTypeKind(te: *TypeExpr) -> int { func Lcx_ResolveTypeKindFromName(name: String) -> int {
if te == null as *TypeExpr { return tyUnknown; }
let name: String = te.typeName;
if te.kind == tekPointer { return tyPointer; }
if te.kind == tekSlice { return tySlice; }
if te.kind == tekTuple { return tyTuple; }
// Named types: resolve by name
if String_Eq(name, "void") { return tyVoid; } if String_Eq(name, "void") { return tyVoid; }
if String_Eq(name, "bool") { return tyBool; } if String_Eq(name, "bool") { return tyBool; }
if String_Eq(name, "bool8") { return tyBool8; } if String_Eq(name, "bool8") { return tyBool8; }
@@ -54,10 +47,19 @@ func Lcx_ResolveTypeKind(te: *TypeExpr) -> int {
if String_Eq(name, "float32") { return tyFloat32; } if String_Eq(name, "float32") { return tyFloat32; }
if String_Eq(name, "float64") { return tyFloat64; } if String_Eq(name, "float64") { return tyFloat64; }
if String_Eq(name, "float") { return tyFloat64; } if String_Eq(name, "float") { return tyFloat64; }
return tyNamed; return tyNamed;
} }
func Lcx_ResolveTypeKind(te: *TypeExpr) -> int {
if te == null as *TypeExpr { return tyUnknown; }
if te.kind == tekPointer { return tyPointer; }
if te.kind == tekSlice { return tySlice; }
if te.kind == tekTuple { return tyTuple; }
return Lcx_ResolveTypeKindFromName(te.typeName);
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Fresh name generation // Fresh name generation
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -400,7 +402,7 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
alloca.typeName = stmt.refStmtType.typeName; alloca.typeName = stmt.refStmtType.typeName;
} }
} }
// Add to scope for field offset lookups // Add to scope for field offset lookups (skip if already defined)
var sym: Symbol; var sym: Symbol;
sym.kind = skVar; sym.kind = skVar;
sym.name = stmt.strValue; sym.name = stmt.strValue;
@@ -439,7 +441,6 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
n.child2 = Lcx_LowerBlock(ctx, stmt.refStmtBlock, -1); n.child2 = Lcx_LowerBlock(ctx, stmt.refStmtBlock, -1);
} }
if stmt.refStmtElse != null as *Block { if stmt.refStmtElse != null as *Block {
// Store else block in extraData (child3 is reserved for block chaining)
n.extraData = Lcx_LowerBlock(ctx, stmt.refStmtElse, -1) as *void; n.extraData = Lcx_LowerBlock(ctx, stmt.refStmtElse, -1) as *void;
} }
return n; return n;
@@ -464,6 +465,18 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
return n; return n;
} }
// Break
if kind == skBreak {
n.kind = hBreak;
return n;
}
// Continue
if kind == skContinue {
n.kind = hContinue;
return n;
}
return n; return n;
} }
@@ -557,7 +570,10 @@ func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
f.retTypeKind = 0; f.retTypeKind = 0;
} }
// Add parameters to hir_lower scope for field offset lookups // Create function scope as child of current scope
var funcScope: Scope = Scope_NewChild(ctx.scope);
// Add parameters to function scope for field offset lookups
var pi: int = 0; var pi: int = 0;
while pi < decl.paramCount { while pi < decl.paramCount {
var p: *Param = null as *Param; var p: *Param = null as *Param;
@@ -575,20 +591,31 @@ func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
sym.kind = skVar; sym.kind = skVar;
sym.name = p.name; sym.name = p.name;
sym.typeKind = Lcx_ResolveTypeKind(p.refParamType); sym.typeKind = Lcx_ResolveTypeKind(p.refParamType);
// Build typeName same as Lcx_LowerParam
if !String_Eq(p.refParamType.typeName, "") {
sym.typeName = p.refParamType.typeName; sym.typeName = p.refParamType.typeName;
} else if p.refParamType.kind == tekPointer && p.refParamType.pointerPointee != null as *TypeExpr {
sym.typeName = String_Concat(p.refParamType.pointerPointee.typeName, "*");
} else {
sym.typeName = "";
}
sym.isMutable = false; sym.isMutable = false;
sym.isPublic = false; sym.isPublic = false;
sym.decl = null as *Decl; sym.decl = null as *Decl;
discard Scope_Define(ctx.scope, sym); discard Scope_Define(&funcScope, sym);
} }
pi = pi + 1; pi = pi + 1;
} }
// Lower body with function scope active
let prevScope: *Scope = ctx.scope;
ctx.scope = &funcScope;
if decl.refBody != null as *Block { if decl.refBody != null as *Block {
f.body = Lcx_LowerBlock(ctx, decl.refBody, -1); f.body = Lcx_LowerBlock(ctx, decl.refBody, -1);
} else { } else {
f.body = null as *HirNode; f.body = null as *HirNode;
} }
ctx.scope = prevScope;
return f; return f;
} }
@@ -601,9 +628,9 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
let ctx: *LowerCtx = bux_alloc(sizeof(LowerCtx)) as *LowerCtx; let ctx: *LowerCtx = bux_alloc(sizeof(LowerCtx)) as *LowerCtx;
ctx.module = mod; ctx.module = mod;
ctx.scope = sema.scope; ctx.scope = sema.scope;
ctx.funcs = bux_alloc(256 as uint * sizeof(HirFunc)) as *HirFunc; ctx.funcs = bux_alloc(512 as uint * sizeof(HirFunc)) as *HirFunc;
ctx.funcCount = 0; ctx.funcCount = 0;
ctx.externFuncs = bux_alloc(256 as uint * sizeof(HirFunc)) as *HirFunc; ctx.externFuncs = bux_alloc(512 as uint * sizeof(HirFunc)) as *HirFunc;
ctx.externCount = 0; ctx.externCount = 0;
ctx.varCounter = 0; ctx.varCounter = 0;
@@ -624,7 +651,7 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
// Iterate declarations // Iterate declarations
var decl: *Decl = mod.firstItem; var decl: *Decl = mod.firstItem;
while decl != null as *Decl { while decl != null as *Decl {
if decl.kind == dkFunc { if decl.kind == dkFunc && decl.refBody != null as *Block {
let f: *HirFunc = Lcx_LowerFunc(ctx, decl); let f: *HirFunc = Lcx_LowerFunc(ctx, decl);
ctx.funcs[ctx.funcCount] = *f; ctx.funcs[ctx.funcCount] = *f;
ctx.funcCount = ctx.funcCount + 1; ctx.funcCount = ctx.funcCount + 1;
@@ -633,7 +660,7 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
let implTypeName: String = decl.strValue; let implTypeName: String = decl.strValue;
var implDecl: *Decl = decl.childDecl1; var implDecl: *Decl = decl.childDecl1;
while implDecl != null as *Decl { while implDecl != null as *Decl {
if implDecl.kind == dkFunc { if implDecl.kind == dkFunc && implDecl.refBody != null as *Block {
let mangled: String = String_Concat(implTypeName, "_"); let mangled: String = String_Concat(implTypeName, "_");
implDecl.strValue = String_Concat(mangled, implDecl.strValue); implDecl.strValue = String_Concat(mangled, implDecl.strValue);
let f: *HirFunc = Lcx_LowerFunc(ctx, implDecl); let f: *HirFunc = Lcx_LowerFunc(ctx, implDecl);
@@ -666,6 +693,62 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
else if fi == 5 { fname = decl.field5.name; ftype = decl.field5.refFieldType; } else if fi == 5 { fname = decl.field5.name; ftype = decl.field5.refFieldType; }
else if fi == 6 { fname = decl.field6.name; ftype = decl.field6.refFieldType; } else if fi == 6 { fname = decl.field6.name; ftype = decl.field6.refFieldType; }
else if fi == 7 { fname = decl.field7.name; ftype = decl.field7.refFieldType; } else if fi == 7 { fname = decl.field7.name; ftype = decl.field7.refFieldType; }
else if fi == 8 { fname = decl.field8.name; ftype = decl.field8.refFieldType; }
else if fi == 9 { fname = decl.field9.name; ftype = decl.field9.refFieldType; }
else if fi == 10 { fname = decl.field10.name; ftype = decl.field10.refFieldType; }
else if fi == 11 { fname = decl.field11.name; ftype = decl.field11.refFieldType; }
else if fi == 12 { fname = decl.field12.name; ftype = decl.field12.refFieldType; }
else if fi == 13 { fname = decl.field13.name; ftype = decl.field13.refFieldType; }
else if fi == 14 { fname = decl.field14.name; ftype = decl.field14.refFieldType; }
else if fi == 15 { fname = decl.field15.name; ftype = decl.field15.refFieldType; }
else if fi == 16 { fname = decl.field16.name; ftype = decl.field16.refFieldType; }
else if fi == 17 { fname = decl.field17.name; ftype = decl.field17.refFieldType; }
else if fi == 18 { fname = decl.field18.name; ftype = decl.field18.refFieldType; }
else if fi == 19 { fname = decl.field19.name; ftype = decl.field19.refFieldType; }
else if fi == 20 { fname = decl.field20.name; ftype = decl.field20.refFieldType; }
else if fi == 21 { fname = decl.field21.name; ftype = decl.field21.refFieldType; }
else if fi == 22 { fname = decl.field22.name; ftype = decl.field22.refFieldType; }
else if fi == 23 { fname = decl.field23.name; ftype = decl.field23.refFieldType; }
else if fi == 24 { fname = decl.field24.name; ftype = decl.field24.refFieldType; }
else if fi == 25 { fname = decl.field25.name; ftype = decl.field25.refFieldType; }
else if fi == 26 { fname = decl.field26.name; ftype = decl.field26.refFieldType; }
else if fi == 27 { fname = decl.field27.name; ftype = decl.field27.refFieldType; }
else if fi == 28 { fname = decl.field28.name; ftype = decl.field28.refFieldType; }
else if fi == 29 { fname = decl.field29.name; ftype = decl.field29.refFieldType; }
else if fi == 30 { fname = decl.field30.name; ftype = decl.field30.refFieldType; }
else if fi == 31 { fname = decl.field31.name; ftype = decl.field31.refFieldType; }
else if fi == 32 { fname = decl.field32.name; ftype = decl.field32.refFieldType; }
else if fi == 33 { fname = decl.field33.name; ftype = decl.field33.refFieldType; }
else if fi == 34 { fname = decl.field34.name; ftype = decl.field34.refFieldType; }
else if fi == 35 { fname = decl.field35.name; ftype = decl.field35.refFieldType; }
else if fi == 36 { fname = decl.field36.name; ftype = decl.field36.refFieldType; }
else if fi == 37 { fname = decl.field37.name; ftype = decl.field37.refFieldType; }
else if fi == 38 { fname = decl.field38.name; ftype = decl.field38.refFieldType; }
else if fi == 39 { fname = decl.field39.name; ftype = decl.field39.refFieldType; }
else if fi == 40 { fname = decl.field40.name; ftype = decl.field40.refFieldType; }
else if fi == 41 { fname = decl.field41.name; ftype = decl.field41.refFieldType; }
else if fi == 42 { fname = decl.field42.name; ftype = decl.field42.refFieldType; }
else if fi == 43 { fname = decl.field43.name; ftype = decl.field43.refFieldType; }
else if fi == 44 { fname = decl.field44.name; ftype = decl.field44.refFieldType; }
else if fi == 45 { fname = decl.field45.name; ftype = decl.field45.refFieldType; }
else if fi == 46 { fname = decl.field46.name; ftype = decl.field46.refFieldType; }
else if fi == 47 { fname = decl.field47.name; ftype = decl.field47.refFieldType; }
else if fi == 48 { fname = decl.field48.name; ftype = decl.field48.refFieldType; }
else if fi == 49 { fname = decl.field49.name; ftype = decl.field49.refFieldType; }
else if fi == 50 { fname = decl.field50.name; ftype = decl.field50.refFieldType; }
else if fi == 51 { fname = decl.field51.name; ftype = decl.field51.refFieldType; }
else if fi == 52 { fname = decl.field52.name; ftype = decl.field52.refFieldType; }
else if fi == 53 { fname = decl.field53.name; ftype = decl.field53.refFieldType; }
else if fi == 54 { fname = decl.field54.name; ftype = decl.field54.refFieldType; }
else if fi == 55 { fname = decl.field55.name; ftype = decl.field55.refFieldType; }
else if fi == 56 { fname = decl.field56.name; ftype = decl.field56.refFieldType; }
else if fi == 57 { fname = decl.field57.name; ftype = decl.field57.refFieldType; }
else if fi == 58 { fname = decl.field58.name; ftype = decl.field58.refFieldType; }
else if fi == 59 { fname = decl.field59.name; ftype = decl.field59.refFieldType; }
else if fi == 60 { fname = decl.field60.name; ftype = decl.field60.refFieldType; }
else if fi == 61 { fname = decl.field61.name; ftype = decl.field61.refFieldType; }
else if fi == 62 { fname = decl.field62.name; ftype = decl.field62.refFieldType; }
else if fi == 63 { fname = decl.field63.name; ftype = decl.field63.refFieldType; }
// Skip empty field names // Skip empty field names
if String_Eq(fname, "") { if String_Eq(fname, "") {
fi = fi + 1; fi = fi + 1;
@@ -701,23 +784,37 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
if decl.kind == dkEnum { if decl.kind == dkEnum {
let ei: int = hm.enumCount; let ei: int = hm.enumCount;
hm.enums[ei].name = decl.strValue; hm.enums[ei].name = decl.strValue;
hm.enumCount = hm.enumCount + 1; // Populate variants
// Emit enum variants as constants: EnumName_VariantName = value hm.enums[ei].variantCount = decl.variantCount;
if decl.variantCount > 0 {
hm.enums[ei].variants = bux_alloc(decl.variantCount as uint * sizeof(HirEnumVariant)) as *HirEnumVariant;
}
var vi: int = 0; var vi: int = 0;
while vi < decl.variantCount && hm.constCount < 512 { while vi < decl.variantCount {
var vname: String = ""; var v: *EnumVariant = null as *EnumVariant;
if vi == 0 { vname = decl.variant0.name; } if vi == 0 { v = &decl.variant0; }
if vi == 1 { vname = decl.variant1.name; } if vi == 1 { v = &decl.variant1; }
if vi == 2 { vname = decl.variant2.name; } if vi == 2 { v = &decl.variant2; }
if vi == 3 { vname = decl.variant3.name; } if vi == 3 { v = &decl.variant3; }
if !String_Eq(vname, "") { if vi == 4 { v = &decl.variant4; }
let ci: int = hm.constCount; if vi == 5 { v = &decl.variant5; }
hm.consts[ci].name = String_Concat(String_Concat(decl.strValue, "_"), vname); if vi == 6 { v = &decl.variant6; }
hm.consts[ci].value = vi; if vi == 7 { v = &decl.variant7; }
hm.constCount = hm.constCount + 1; if v != null as *EnumVariant {
hm.enums[ei].variants[vi].name = v.name;
hm.enums[ei].variants[vi].fieldCount = v.fieldCount;
if v.fieldCount > 0 {
hm.enums[ei].variants[vi].fieldName0 = "value";
hm.enums[ei].variants[vi].fieldType0 = Lcx_ResolveTypeKindFromName(v.fieldTypeName0);
}
if v.fieldCount > 1 {
hm.enums[ei].variants[vi].fieldName1 = "value2";
hm.enums[ei].variants[vi].fieldType1 = Lcx_ResolveTypeKindFromName(v.fieldTypeName1);
}
} }
vi = vi + 1; vi = vi + 1;
} }
hm.enumCount = hm.enumCount + 1;
} }
decl = decl.childDecl2; decl = decl.childDecl2;
} }
-23
View File
@@ -1,23 +0,0 @@
// main.bux — Entry point for the Bux self-hosting compiler
module Main {
// C runtime for command-line args
extern func bux_argc() -> int;
extern func bux_argv(index: int) -> String;
extern func bux_alloc(size: uint) -> *void;
// Forward declaration from Cli module
func Cli_Run(args: *String, argCount: int) -> int;
func Main() -> int {
let count: int = bux_argc();
// Allocate array of String pointers
let args: *String = bux_alloc(count as uint * 8) as *String;
var i: int = 0;
while i < count {
args[i] = bux_argv(i);
i = i + 1;
}
return Cli_Run(args, count);
}
}
+20 -2
View File
@@ -4,6 +4,14 @@ module Parser {
extern func bux_strlen(s: String) -> uint; extern func bux_strlen(s: String) -> uint;
// Forward declarations for mutual recursion
func parserParseExpr(p: *Parser) -> *Expr;
func parserParseStmt(p: *Parser) -> *Stmt;
func parserParseBlock(p: *Parser) -> *Block;
func parserParsePrimary(p: *Parser) -> *Expr;
func parserParsePostfixExpr(p: *Parser) -> *Expr;
func parserParseBinaryPrec(p: *Parser, minPrec: int) -> *Expr;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Parser state // Parser state
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -326,7 +334,7 @@ func parserParsePrimary(p: *Parser) -> *Expr {
// Postfix: call, index, field access, as, is, ? // Postfix: call, index, field access, as, is, ?
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
func parserParsePostfix(p: *Parser) -> *Expr { func parserParsePostfixExpr(p: *Parser) -> *Expr {
var left: *Expr = parserParsePrimary(p); var left: *Expr = parserParsePrimary(p);
while true { while true {
@@ -566,7 +574,7 @@ func parserPrecedence(op: int) -> int {
} }
func parserParseBinaryPrec(p: *Parser, minPrec: int) -> *Expr { func parserParseBinaryPrec(p: *Parser, minPrec: int) -> *Expr {
var left: *Expr = parserParsePostfix(p); var left: *Expr = parserParsePostfixExpr(p);
while true { while true {
let op: int = parserPeek(p, 0); let op: int = parserPeek(p, 0);
let prec: int = parserPrecedence(op); let prec: int = parserPrecedence(op);
@@ -658,6 +666,9 @@ func parserParseStmt(p: *Parser) -> *Stmt {
p.structInitAllowed = true; p.structInitAllowed = true;
let thenBlock: *Block = parserParseBlock(p); let thenBlock: *Block = parserParseBlock(p);
var elseBlock: *Block = null as *Block; var elseBlock: *Block = null as *Block;
while parserCheck(p, tkNewLine) {
discard parserAdvance(p);
}
if parserMatch(p, tkElse) { if parserMatch(p, tkElse) {
if parserCheck(p, tkIf) { if parserCheck(p, tkIf) {
// else if → parse the if statement, wrap in a synthetic block // else if → parse the if statement, wrap in a synthetic block
@@ -1218,6 +1229,9 @@ func parserParseEnumDecl(p: *Parser, isPublic: bool) -> *Decl {
var v: EnumVariant; var v: EnumVariant;
v.name = vName.text; v.name = vName.text;
v.fieldCount = 0;
v.fieldTypeName0 = "";
v.fieldTypeName1 = "";
// Optional (Type, Type) data // Optional (Type, Type) data
if parserMatch(p, tkLParen) { if parserMatch(p, tkLParen) {
@@ -1236,6 +1250,10 @@ func parserParseEnumDecl(p: *Parser, isPublic: bool) -> *Decl {
else if d.variantCount == 1 { d.variant1 = v; } else if d.variantCount == 1 { d.variant1 = v; }
else if d.variantCount == 2 { d.variant2 = v; } else if d.variantCount == 2 { d.variant2 = v; }
else if d.variantCount == 3 { d.variant3 = v; } else if d.variantCount == 3 { d.variant3 = v; }
else if d.variantCount == 4 { d.variant4 = v; }
else if d.variantCount == 5 { d.variant5 = v; }
else if d.variantCount == 6 { d.variant6 = v; }
else if d.variantCount == 7 { d.variant7 = v; }
d.variantCount = d.variantCount + 1; d.variantCount = d.variantCount + 1;
parserMatch(p, tkComma); parserMatch(p, tkComma);
+9 -2
View File
@@ -146,7 +146,6 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
if expr == null as *Expr { return tyUnknown; } if expr == null as *Expr { return tyUnknown; }
let kind: int = expr.kind; let kind: int = expr.kind;
// Debug: print expr kind // Debug: print expr kind
// Print("Sema_CheckExpr kind=");
// PrintInt(kind as int64); // PrintInt(kind as int64);
// PrintLine(""); // PrintLine("");
@@ -239,10 +238,14 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
// Try to resolve return type from function declaration // Try to resolve return type from function declaration
if expr.child1.kind == ekIdent { if expr.child1.kind == ekIdent {
let sym: Symbol = Scope_Lookup(sema.scope, expr.child1.strValue); let sym: Symbol = Scope_Lookup(sema.scope, expr.child1.strValue);
if sym.kind == skFunc && sym.decl != null as *Decl && sym.decl.retType != null as *TypeExpr { if sym.kind == skFunc {
if sym.decl != null as *Decl {
if sym.decl.retType != null as *TypeExpr {
return Sema_ResolveType(sema, sym.decl.retType); return Sema_ResolveType(sema, sym.decl.retType);
} }
} }
}
}
return tyUnknown; return tyUnknown;
} }
@@ -489,6 +492,10 @@ func Sema_CollectGlobals(sema: *Sema) {
else if vi == 5 { v = decl.variant5; } else if vi == 5 { v = decl.variant5; }
else if vi == 6 { v = decl.variant6; } else if vi == 6 { v = decl.variant6; }
else if vi == 7 { v = decl.variant7; } else if vi == 7 { v = decl.variant7; }
if v.name == null as String || String_Eq(v.name, "") {
vi = vi + 1;
continue;
}
let variantName: String = String_Concat(decl.strValue, "_"); let variantName: String = String_Concat(decl.strValue, "_");
let variantName2: String = String_Concat(variantName, v.name); let variantName2: String = String_Concat(variantName, v.name);
var vSym: Symbol; var vSym: Symbol;
+1 -1
View File
@@ -1,6 +1,6 @@
import "../bootstrap/lexer", "../bootstrap/parser", "../bootstrap/ast" import "../bootstrap/lexer", "../bootstrap/parser", "../bootstrap/ast"
let source = readFile("../_selfhost/src/ast.bux") let source = readFile("../src/ast.bux")
let lexRes = tokenize(source, "ast.bux") let lexRes = tokenize(source, "ast.bux")
let res = parse(lexRes.tokens, "ast.bux") let res = parse(lexRes.tokens, "ast.bux")
if res.module != nil: if res.module != nil:
+1 -1
View File
@@ -1,7 +1,7 @@
import "../bootstrap/lexer", "../bootstrap/parser", "../bootstrap/ast", "../bootstrap/token" import "../bootstrap/lexer", "../bootstrap/parser", "../bootstrap/ast", "../bootstrap/token"
import std/os import std/os
let source = readFile("../_selfhost/src/ast.bux") let source = readFile("../src/ast.bux")
let lexRes = tokenize(source, "ast.bux") let lexRes = tokenize(source, "ast.bux")
echo "Tokens: ", lexRes.tokens.len echo "Tokens: ", lexRes.tokens.len
echo "Errors: ", lexRes.hasErrors echo "Errors: ", lexRes.hasErrors