Compare commits
135 Commits
ac969b37c1
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 94e6806dda | |||
| 9c632ce389 | |||
| aaeb01e518 | |||
| fc0a560e60 | |||
| e11dce56d2 | |||
| e5e490605a | |||
| 2c8223f94e | |||
| 44bac83471 | |||
| 06db4926a7 | |||
| 84df4bba9d | |||
| a668127721 | |||
| 9bbeb4fd1d | |||
| 0e93ea911c | |||
| 290cbc8f98 | |||
| 224542df7f | |||
| a619247f06 | |||
| a4f25bc737 | |||
| aa2d6f9632 | |||
| b549e09a3b | |||
| 2259567ac2 | |||
| f4f09602c6 | |||
| ef4aa42b0b | |||
| 8f68a9da1a | |||
| e4e4e0d8ef | |||
| 18aa232b1c | |||
| c6bae8b9ee | |||
| aef0cd7936 | |||
| 62d92b0aa6 | |||
| 9a63dcca11 | |||
| 10a2b64ac4 | |||
| 92962fa1b0 | |||
| b971775baf | |||
| 85d15cd16d | |||
| de3ac76581 | |||
| 2824d25369 | |||
| 9cd0580832 | |||
| 9e547f123a | |||
| 211a811f32 | |||
| 4456bb00af | |||
| 6a0a0425dd | |||
| 9e515c157d | |||
| 8f8708c5df | |||
| 7392013301 | |||
| 0b887b92d3 | |||
| 7d6cf7f511 | |||
| 0a41ce85f4 | |||
| d6f0a30948 | |||
| a2236de69a | |||
| a4e410fb30 | |||
| 51bd3c172b | |||
| 3a7b4bd5c1 | |||
| a099999e02 | |||
| 5eec4328f3 | |||
| c7d4c87fcf | |||
| d70ccba5ca | |||
| 01677001c3 | |||
| bb84693acb | |||
| f63cbd1bf0 | |||
| 499b389ba8 | |||
| 632fa1535c | |||
| 40bcbeb710 | |||
| b36df0f5b7 | |||
| b3141dbcd5 | |||
| 74db8a5790 | |||
| 6414ef236b | |||
| 9b473c667c | |||
| ba54aa240e | |||
| c83f6d5994 | |||
| 34504d1647 | |||
| 079b76ae71 | |||
| 3ce7ba51d2 | |||
| 41c0fb1d6f | |||
| f809827bea | |||
| 81281cbb11 | |||
| c14578d9dd | |||
| 0ac254facc | |||
| 392dd64f97 | |||
| 90e67079f4 | |||
| 265bda850d | |||
| 3ed5891de2 | |||
| 64bd41067a | |||
| 4a860095fd | |||
| 0c41c7bb25 | |||
| 2e536488e6 | |||
| 3f0a3901ca | |||
| 17c896c4dc | |||
| cfce2a93e5 | |||
| d9aceeac6e | |||
| a67271b08c | |||
| cf92b63ba6 | |||
| 9c3d6dc723 | |||
| 7d1d722cba | |||
| 3c7938163c | |||
| c4e3f80405 | |||
| d253a01aa9 | |||
| 2cb971a483 | |||
| b1deff7fd5 | |||
| 38bc469be7 | |||
| bebc362ae6 | |||
| a7a1b45588 | |||
| a13cee8a0f | |||
| 3b7fc455b0 | |||
| e7e900973f | |||
| a2617e9954 | |||
| 3ffa9f7d54 | |||
| b3af1208e8 | |||
| 0d7a30835f | |||
| adca52b269 | |||
| 04be9eb400 | |||
| bfe87a8acb | |||
| 46814b2abc | |||
| 9758b17b1a | |||
| 8347599d23 | |||
| 4794335673 | |||
| 134e8e877d | |||
| 88af8b09d4 | |||
| e41a8d31df | |||
| a11071ab95 | |||
| 093e08a2b9 | |||
| 1500575982 | |||
| fe048eb4aa | |||
| 9dd964dc48 | |||
| e1038059c2 | |||
| 1f0baefa5a | |||
| 3b4456f01f | |||
| 0bdef58182 | |||
| 3ae8c60ebb | |||
| 9416c486b2 | |||
| ba0a55f467 | |||
| 797ca7c80c | |||
| 4f7653a410 | |||
| 62a9c8b84a | |||
| aff03ffb5a | |||
| a8b909726b | |||
| 2698ca92b7 |
+11
@@ -1,5 +1,6 @@
|
||||
# Compiled binary
|
||||
buxc
|
||||
buxc_lir
|
||||
src/main
|
||||
|
||||
# Nim cache
|
||||
@@ -10,6 +11,7 @@ tests/lexer_test
|
||||
tests/parser_test
|
||||
tests/sema_test
|
||||
tests/hir_test
|
||||
tests/borrow_test
|
||||
tests/test_generics_parse
|
||||
tests/*.exe
|
||||
|
||||
@@ -21,3 +23,12 @@ build/
|
||||
_test_tmp_pkg/
|
||||
test_pkg/
|
||||
examples_pkg/
|
||||
|
||||
# Temporary test directories
|
||||
_test_*/
|
||||
|
||||
# Backup files
|
||||
*.bak
|
||||
|
||||
# Log files
|
||||
*.log
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Dimitar Gigov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,17 +1,17 @@
|
||||
NIM := nim
|
||||
SRC := compiler/bootstrap/main.nim
|
||||
SRC := bootstrap/main.nim
|
||||
OUT := buxc
|
||||
BUILD_DIR := build
|
||||
|
||||
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ctfe async concurrency os_time process json iter
|
||||
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ctfe async concurrency os_time process json iter trait_bounds channel sync jwt
|
||||
|
||||
.PHONY: all build dev debug test clean clean-all test-examples selfhost
|
||||
.PHONY: all build dev debug test clean clean-all test-examples selfhost test-golden selfhost-loop lsp
|
||||
|
||||
all: build
|
||||
|
||||
build:
|
||||
$(NIM) c -o:$(OUT) -d:release --opt:size $(SRC)
|
||||
strip $(OUT)
|
||||
# strip $(OUT)
|
||||
|
||||
dev:
|
||||
$(NIM) c -o:buxc_debug -d:debug --stackTrace:on --lineTrace:on $(SRC)
|
||||
@@ -21,15 +21,15 @@ debug: dev
|
||||
|
||||
test: build test-examples
|
||||
@echo "Running lexer tests..."
|
||||
$(NIM) c -r compiler/tests/lexer_test.nim
|
||||
$(NIM) c -r tests/lexer_test.nim
|
||||
@echo "Running parser tests..."
|
||||
$(NIM) c -r compiler/tests/parser_test.nim
|
||||
$(NIM) c -r tests/parser_test.nim
|
||||
@echo "Running sema tests..."
|
||||
$(NIM) c -r compiler/tests/sema_test.nim
|
||||
$(NIM) c -r tests/sema_test.nim
|
||||
@echo "Running HIR tests..."
|
||||
$(NIM) c -r compiler/tests/hir_test.nim
|
||||
$(NIM) c -r tests/hir_test.nim
|
||||
@echo "Running borrow checker tests..."
|
||||
$(NIM) c -r compiler/tests/borrow_test.nim
|
||||
$(NIM) c -r tests/borrow_test.nim
|
||||
@echo "Running integration tests..."
|
||||
rm -rf _test_tmp_pkg
|
||||
./$(OUT) new _test_tmp_pkg
|
||||
@@ -62,14 +62,81 @@ clean:
|
||||
rm -rf _test_cast _test_cast2 _test_cast3 _test_channel
|
||||
|
||||
clean-all: clean
|
||||
rm -rf _selfhost/src _selfhost/build
|
||||
rm -rf build/selfhost build/selfhost-loop-a build/selfhost-loop-b
|
||||
rm -rf tests/golden/*/build
|
||||
|
||||
selfhost: build
|
||||
@echo "=== Building self-hosted compiler ==="
|
||||
@rm -rf _selfhost/src _selfhost/build
|
||||
@mkdir -p _selfhost/src
|
||||
@cp compiler/selfhost/*.bux _selfhost/src/
|
||||
@mv _selfhost/src/main.bux _selfhost/src/Main.bux 2>/dev/null || true
|
||||
@cd _selfhost && ../$(OUT) build
|
||||
@strip _selfhost/build/buxc2
|
||||
@rm -rf build/selfhost
|
||||
@mkdir -p build/selfhost/src
|
||||
@cp src/*.bux build/selfhost/src/
|
||||
@cp src/bux.toml build/selfhost/
|
||||
@mv build/selfhost/src/main.bux build/selfhost/src/Main.bux 2>/dev/null || true
|
||||
@cd build/selfhost && ../../$(OUT) build
|
||||
# strip removed for debug
|
||||
@echo "=== Self-hosted compiler built successfully ==="
|
||||
|
||||
.PHONY: test-golden
|
||||
|
||||
GOLDEN_TESTS := hello fibonacci structs generics algebraic_enums enums methods strings modern_features
|
||||
|
||||
test-golden: build
|
||||
@echo "=== Golden tests ==="
|
||||
@passed=0; failed=0; \
|
||||
for test in $(GOLDEN_TESTS); do \
|
||||
gd="tests/golden/$$test"; \
|
||||
if [ ! -d "$$gd" ]; then echo " SKIP $$test (no dir)"; continue; fi; \
|
||||
if [ ! -f "$$gd/expected.c" ]; then echo " SKIP $$test (no expected.c)"; continue; fi; \
|
||||
rm -rf "$$gd/build"; \
|
||||
(cd "$$gd" && ../../../$(OUT) build > /dev/null 2>&1); \
|
||||
if diff "$$gd/build/main.c" "$$gd/expected.c" > /dev/null 2>&1; then \
|
||||
echo " PASS $$test"; \
|
||||
passed=$$((passed + 1)); \
|
||||
else \
|
||||
echo " FAIL $$test — C output differs from expected"; \
|
||||
failed=$$((failed + 1)); \
|
||||
fi; \
|
||||
done; \
|
||||
echo "Golden tests: $$passed passed, $$failed failed"; \
|
||||
if [ $$failed -gt 0 ]; then exit 1; fi
|
||||
|
||||
selfhost-loop: build
|
||||
@echo "=== Selfhost loop: bootstrap determinism check ==="
|
||||
@echo "Build A..."
|
||||
@rm -rf build/selfhost-loop-a
|
||||
@mkdir -p build/selfhost-loop-a/src
|
||||
@cp src/*.bux build/selfhost-loop-a/src/
|
||||
@cp src/bux.toml build/selfhost-loop-a/
|
||||
@mv build/selfhost-loop-a/src/main.bux build/selfhost-loop-a/src/Main.bux 2>/dev/null || true
|
||||
@cd build/selfhost-loop-a && ../../$(OUT) build
|
||||
@echo "Build B..."
|
||||
@rm -rf build/selfhost-loop-b
|
||||
@mkdir -p build/selfhost-loop-b/src
|
||||
@cp src/*.bux build/selfhost-loop-b/src/
|
||||
@cp src/bux.toml build/selfhost-loop-b/
|
||||
@mv build/selfhost-loop-b/src/main.bux build/selfhost-loop-b/src/Main.bux 2>/dev/null || true
|
||||
@cd build/selfhost-loop-b && ../../$(OUT) build
|
||||
@echo ""
|
||||
@echo "Comparing C output..."
|
||||
@if diff build/selfhost-loop-a/build/main.c build/selfhost-loop-b/build/main.c > /dev/null 2>&1; then \
|
||||
echo " C output: IDENTICAL ✓"; \
|
||||
else \
|
||||
echo " C output: DIFFERENT ✗"; \
|
||||
fi
|
||||
@echo "Comparing ELF binaries (stripped)..."
|
||||
@cp build/selfhost-loop-a/build/buxc2 /tmp/buxc2_a && strip -d /tmp/buxc2_a
|
||||
@cp build/selfhost-loop-b/build/buxc2 /tmp/buxc2_b && strip -d /tmp/buxc2_b
|
||||
@if diff /tmp/buxc2_a /tmp/buxc2_b > /dev/null 2>&1; then \
|
||||
echo " ELF binary: IDENTICAL ✓"; \
|
||||
echo "=== Selfhost loop PASSED ==="; \
|
||||
else \
|
||||
echo " ELF binary: DIFFERENT ✗"; \
|
||||
echo "=== Selfhost loop FAILED ==="; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
lsp: tools/bux-lsp
|
||||
@echo "LSP server ready at tools/bux-lsp"
|
||||
|
||||
tools/bux-lsp: tools/lsp_server.nim
|
||||
cd tools && $(NIM) c -o:bux-lsp lsp_server.nim
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
# Bux Programming Language — Roadmap to Self-Hosting
|
||||
# Bux Programming Language — Roadmap to v1.0.0
|
||||
|
||||
> **Bootstrap Implementation:** Nim
|
||||
> **Target:** Bux compiler written in Bux (self-hosting)
|
||||
> **Version:** 0.3.1 (2026-06-06)
|
||||
> **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
|
||||
|
||||
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.
|
||||
|
||||
**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)
|
||||
@@ -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**.
|
||||
|
||||
**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 |
|
||||
|------|--------|---------|-----|
|
||||
@@ -342,7 +346,7 @@ Phase 7.5 — Driver (depends on all):
|
||||
|
||||
**Self-hosted compiler stats:**
|
||||
```
|
||||
$ _selfhost/build/buxc2 version
|
||||
$ src/build/buxc2 version
|
||||
Bux 0.2.0 (self-hosting bootstrap)
|
||||
Pipeline modules:
|
||||
Lexer ✅ 695 lines
|
||||
@@ -355,8 +359,8 @@ Pipeline modules:
|
||||
|
||||
**Bootstrap loop goal:**
|
||||
```
|
||||
buxc (Nim) → compile compiler/selfhost/*.bux → buxc2 (Bux binary)
|
||||
buxc2 (Bux) → compile compiler/selfhost/*.bux → buxc3 (Bux binary)
|
||||
buxc (Nim) → compile src/*.bux → buxc2 (Bux binary)
|
||||
buxc2 (Bux) → compile src/*.bux → buxc3 (Bux binary)
|
||||
compare buxc2 == buxc3 → SELF-HOSTED ✅
|
||||
```
|
||||
|
||||
@@ -516,6 +520,34 @@ const TABLE_SIZE = Factorial(10); // Computed at compile time
|
||||
| `8.6.2` Procedural macros | `#[derive(Clone)]`, `#[derive(Debug)]` |
|
||||
| `8.6.3` Reflection | Compile-time type introspection for serialization |
|
||||
|
||||
### 8.7 — Optimization & Release Mode
|
||||
|
||||
**Goal:** C-speed for hot paths. `@[Release]` disables all runtime checks and inlines auto-drop.
|
||||
|
||||
| Task | Status | Details |
|
||||
|------|--------|---------|
|
||||
| `8.7.1` `@[Release]` attribute | ⏳ | Disables bounds checking, borrow checking, null checks inside the function |
|
||||
| `8.7.2` Auto-drop inline | ⏳ | Emit `free`/`close` directly instead of `Type_Drop(&var)` call |
|
||||
| `8.7.3` Dead-store elimination | ⏳ | C backend removes redundant temp variables (`_t1`, `_t2`) |
|
||||
| `8.7.4` `-O3` by default | ⏳ | `bux build --release` passes `-O3 -flto` to C compiler |
|
||||
| `8.7.5` Profile-guided optimization | ⏳ | `bux profile` + `bux build --pgo` for guided inlining |
|
||||
|
||||
```bux
|
||||
// Default: safe but slower — bounds checks + borrow checks
|
||||
@[Checked]
|
||||
func SafeSum(arr: Array<int>) -> int { ... }
|
||||
|
||||
// Hot path: zero-cost — straight C
|
||||
@[Release]
|
||||
func HotLoop(data: *float64, n: int) {
|
||||
for i in 0..n {
|
||||
data[i] = data[i] * 2.0; // no bounds check, inlined
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Why this matters:** Nim compiles to C but has ARC/ORC overhead. Crystal has Boehm GC. Bux with `@[Release]` has **literally zero overhead** — the generated C is hand-written quality.
|
||||
|
||||
---
|
||||
|
||||
## Phase 9 — Ecosystem & Tooling (Week 35+)
|
||||
@@ -534,60 +566,188 @@ const TABLE_SIZE = Factorial(10); // Computed at compile time
|
||||
|
||||
---
|
||||
|
||||
## File Structure (Target)
|
||||
## File Structure (v0.3.0 — Current)
|
||||
|
||||
```
|
||||
bux/
|
||||
├── bux.toml # Compiler package manifest
|
||||
├── bux.toml # Bootstrap compiler manifest (v0.3.0)
|
||||
├── README.md
|
||||
├── PLAN.md
|
||||
├── PLAN.md # This file
|
||||
├── Makefile # build, test, selfhost
|
||||
├── src/
|
||||
│ ├── Main.bux # CLI entry point
|
||||
│ ├── Lexer.bux
|
||||
│ ├── Parser.bux
|
||||
│ ├── Ast.bux
|
||||
│ ├── Sema.bux
|
||||
│ ├── Type.bux
|
||||
│ ├── Hir.bux
|
||||
│ ├── Lir.bux
|
||||
│ ├── CBackend.bux # C transpiler (primary backend)
|
||||
│ ├── X64Backend.bux # Native x86-64 backend (optional)
|
||||
│ ├── Linker.bux # Custom linker / build driver
|
||||
│ ├── Manifest.bux # bux.toml parser
|
||||
│ └── Package.bux # Package resolution
|
||||
├── library/
|
||||
│ ├── Std/
|
||||
│ │ ├── Io.bux
|
||||
│ │ ├── Memory.bux
|
||||
│ │ ├── String.bux
|
||||
│ │ ├── Array.bux
|
||||
│ │ ├── Map.bux
|
||||
│ │ ├── Math.bux
|
||||
│ │ ├── Os.bux
|
||||
│ │ ├── Path.bux
|
||||
│ │ ├── Process.bux
|
||||
│ │ ├── Result.bux # Result<T,E> and Option<T>
|
||||
│ │ ├── Iter.bux # Iterator trait and combinators
|
||||
│ │ ├── Fmt.bux # String formatting
|
||||
│ │ ├── Task.bux # Lightweight concurrency
|
||||
│ │ ├── Channel.bux # Message passing
|
||||
│ │ └── Sync.bux # Mutex, RwLock, atomic
|
||||
│ └── Runtime.c # C runtime shim
|
||||
├── tests/
|
||||
│ ├── Lexer/
|
||||
│ ├── Parser/
|
||||
│ ├── Sema/
|
||||
│ ├── Codegen/
|
||||
│ └── Integration/
|
||||
└── docs/
|
||||
├── LanguageRef.md
|
||||
├── Ownership.md
|
||||
└── Concurrency.md
|
||||
├── src/ # 🎯 CANONICAL: Bux compiler source
|
||||
│ ├── bux.toml # Self-host compiler manifest
|
||||
│ ├── main.bux # Entry point (renamed → Main.bux at build)
|
||||
│ ├── lexer.bux # Tokenizer (UTF-8 state machine, 697 LOC)
|
||||
│ ├── parser.bux # Pratt parser (1004 LOC)
|
||||
│ ├── ast.bux # AST node types (algebraic enums)
|
||||
│ ├── sema.bux # Type checker / semantic analysis (393 LOC)
|
||||
│ ├── types.bux # Type factories
|
||||
│ ├── scope.bux # Symbol table
|
||||
│ ├── hir.bux # High-level IR definitions
|
||||
│ ├── hir_lower.bux # AST → HIR lowering (307 LOC)
|
||||
│ ├── c_backend.bux # HIR → C code generator (264 LOC)
|
||||
│ ├── cli.bux # CLI command dispatch
|
||||
│ ├── manifest.bux # bux.toml / TOML parser
|
||||
│ ├── token.bux # Token kind definitions
|
||||
│ └── source_location.bux # Source location tracking
|
||||
├── bootstrap/ # 🔧 Nim bootstrap (build scaffold only)
|
||||
│ ├── main.nim # Entry point
|
||||
│ ├── cli.nim # CLI commands + build driver
|
||||
│ └── ... # (mirrors src/ structure)
|
||||
├── lib/ # 📦 Standard library (23 modules)
|
||||
│ ├── Io.bux # Print, ReadFile, WriteFile
|
||||
│ ├── String.bux # Full string API (len, concat, split, format...)
|
||||
│ ├── Array.bux # Generic Array<T>
|
||||
│ ├── Map.bux # Generic Map<K,V> + StringMap
|
||||
│ ├── Set.bux # Generic Set<T>
|
||||
│ ├── Math.bux # Sqrt, Pow, Min, Max, Abs
|
||||
│ ├── Mem.bux # Alloc, Realloc, Free
|
||||
│ ├── Path.bux # Path_Join, Path_Parent, Path_Ext
|
||||
│ ├── Fs.bux # DirExists, Mkdir, ListDir
|
||||
│ ├── Task.bux # Lightweight tasks (spawn/await)
|
||||
│ ├── Channel.bux # Producer/consumer channels
|
||||
│ ├── Sync.bux # Mutex, RwLock
|
||||
│ ├── Result.bux # Result<T,E> + Option<T> + ? operator
|
||||
│ ├── Iter.bux # Iterator trait
|
||||
│ ├── Fmt.bux # String formatting
|
||||
│ ├── Os.bux # Args, Env, Exit, Cwd
|
||||
│ ├── Time.bux # Time measurement
|
||||
│ ├── Process.bux # Subprocess spawning
|
||||
│ ├── Net.bux # TCP client/server
|
||||
│ ├── Crypto.bux # SHA256, HMAC, Base64
|
||||
│ ├── 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.5.0 target)
|
||||
|
||||
**Goal:** `buxc2` can compile itself producing a binary-identical `buxc3`.
|
||||
|
||||
```
|
||||
buxc (Nim) → src/*.bux → buxc2 ✅
|
||||
buxc2 → src/*.bux → buxc3 ✅
|
||||
buxc2 == buxc3 ✅ (binary-identical)
|
||||
```
|
||||
|
||||
| Task | Status | Details |
|
||||
|------|--------|---------|
|
||||
| `10.1.1` Verify buxc2 can build src/ | ✅ | `buxc2 build` works on selfhost project |
|
||||
| `10.1.2` Deterministic C codegen | ✅ | C output identical on every iteration |
|
||||
| `10.1.3` Binary-identical loop | ✅ | `make selfhost-loop` passes (C + ELF parity) |
|
||||
| `10.1.4` Remove hardcoded paths | ✅ | Paths resolved via `bux_getcwd()` / `bux_path_join()` |
|
||||
| `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. `@[Release]` disables checks for zero-cost hot paths.
|
||||
|
||||
| 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 | ✅ | `Slice_Get` / `Array_Get` with `bux_bounds_check` |
|
||||
| `10.2.5` `@[Release]` zero-cost mode | ✅ | Disables borrow + bounds checks, passes `-O3 -flto` |
|
||||
| `10.2.6` Lifetime elision (simple rules) | ⏳ | 80% of cases without annotations |
|
||||
| `10.2.7` 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)
|
||||
|
||||
### What Bux learns from Rust
|
||||
@@ -689,12 +849,18 @@ func Main() -> int {
|
||||
| **M2** | 2 | ✅ | Type-checker rejects invalid programs |
|
||||
| **M3** | 3 | ✅ | HIR lowering works for all constructs |
|
||||
| **M4** | 5A | ✅ | `bux run` produces working binary via C transpiler |
|
||||
| **M5** | 6 | ✅ | Can write compiler-adjacent tools in Bux (18 examples) |
|
||||
| **M6** | 7 | ✅ | **Self-hosted**: `buxc2` (Bux) compiles via `buxc` (Nim) — 88KB working binary |
|
||||
| **M5** | 6 | ✅ | Can write compiler-adjacent tools in Bux (26 examples) |
|
||||
| **M6** | 7 | ✅ | **Self-hosted**: `buxc2` (Bux) compiles via `buxc` (Nim) — working binary |
|
||||
| **M7** | 8 | ✅ | Result/Option/`?`/`!` done; **borrow checker working**; **CTFE working** |
|
||||
| **M8** | 8-9 | ✅ | **Borrow checker**, **CTFE**, **Package manager** working |
|
||||
| **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 | ✅ | `@[Release]` zero-cost mode; Rust-style error snippets |
|
||||
| **M14** | 8.3 | ✅ | **Green threads** — M:N scheduler with channels |
|
||||
| **M15** | 11.5 | ⏳ | Native x86-64 backend (no C transpiler) |
|
||||
| **M16** | 11.4 | ⏳ | `bux test`, `bux fmt`, `bux doc` shipped |
|
||||
|
||||
---
|
||||
|
||||
@@ -752,11 +918,58 @@ func Main() -> int {
|
||||
|
||||
### Next Actions (Priority Order)
|
||||
|
||||
1. **Fix parameter/return types** — `String*` instead of `int` in function signatures
|
||||
2. **Debug ast/lexer/parser** — Get all 14 modules passing check
|
||||
3. **Full 14-module project build** — Complete bootstrap loop: buxc3 produced by buxc2
|
||||
4. **Compare buxc2 vs buxc3 output** — True self-hosting verification
|
||||
5. **Phase 8** — Advanced features: ownership checker, CTFE evaluation, string interpolation
|
||||
1. ✅ **Fix LIR backend type inference** — struct temps, undeclared vars, break/continue, duplicate declarations
|
||||
2. ✅ **All 30 examples passing** — bootstrap and self-host compilers build and run every example
|
||||
3. ✅ **Selfhost build working** — `make selfhost` produces working `buxc2`
|
||||
4. ✅ **Selfhost loop verification** — `buxc2` compiles `src/` → `buxc3`; C output and stripped binary identical
|
||||
5. ✅ **Golden tests for C codegen** — `make test-golden` passes for 8 critical examples
|
||||
6. ✅ **`bux test` runner** — discovers `tests/*.bux`, builds each as a temp package, and reports PASS/FAIL
|
||||
|
||||
---
|
||||
|
||||
## 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 5–6 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.0–v0.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 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
|
||||

|
||||
|
||||
> **Status:** Bootstrap compiler (`buxc`, Nim) compiles `.bux` → C → native binary.
|
||||
> Self-hosted compiler (`buxc2`) exists as a proof-of-concept but is **deprioritized** in favor of language features and stdlib maturity.
|
||||
> **Status:** v0.5.0 — Bootstrap compiler (`buxc`, Nim) and self-hosted compiler (`buxc2`, Bux) both compile `.bux` → C → native binary.
|
||||
> **Selfhost loop:** `buxc2` compiles itself → binary-identical `buxc3` ✅ Deterministic C codegen + ELF verified.
|
||||
> **Gradual Ownership:** `@[Checked]` borrow checker, `@[Release]` zero-cost mode, `borrow &mut` expressions.
|
||||
> **Green Threads:** M:N scheduler with channels (Go-style goroutines without GC).
|
||||
> **All 26 examples pass.** Compiler successfully parses all 3 real-world apps (`apps/boko-framework`, `apps/jwt-pitbul`, `apps/nexus`).
|
||||
|
||||
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 (opt-in borrow checking), async/await, generics, algebraic enums, and a package manager.
|
||||
|
||||
---
|
||||
|
||||
@@ -21,6 +24,12 @@ cd hello
|
||||
|
||||
# Build and run
|
||||
bux run
|
||||
|
||||
# Build optimized release binary
|
||||
bux build --release
|
||||
|
||||
# Cross-compile for ARM Linux (requires clang)
|
||||
bux build --target aarch64-linux-gnu
|
||||
```
|
||||
|
||||
---
|
||||
@@ -128,7 +137,7 @@ func Main() -> int {
|
||||
### Channels (Producer/Consumer)
|
||||
```bux
|
||||
import Std::Io::{PrintLine, PrintInt};
|
||||
import Std::Task::{Task_Join, TaskHandle};
|
||||
import Std::Task::{Task_Wait, TaskHandle};
|
||||
import Std::Channel::{Channel, Channel_New, Channel_SendInt, Channel_RecvInt, Channel_Close};
|
||||
|
||||
func Producer(chPtr: *Channel<int>) {
|
||||
@@ -158,8 +167,8 @@ func Main() -> int {
|
||||
let ch: Channel<int> = Channel_New<int>(3);
|
||||
let p: *void = spawn Producer(&ch);
|
||||
let c: *void = spawn Consumer(&ch);
|
||||
Task_Join(TaskHandle { handle: p });
|
||||
Task_Join(TaskHandle { handle: c });
|
||||
Task_Wait(TaskHandle { handle: p });
|
||||
Task_Wait(TaskHandle { handle: c });
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
@@ -190,14 +199,17 @@ func Main() -> int {
|
||||
| **Interfaces** | `interface` + `extend` for trait-like behavior |
|
||||
| **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` |
|
||||
| **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 |
|
||||
| **Gradual Ownership** | `@[Checked]` + `&T`/`&mut T` borrow checking |
|
||||
| **Gradual Ownership** | `@[Checked]` + `@[Release]` + `@[Shared]` + `borrow &mut` / `borrow &` |
|
||||
| **Drop Trait** | Auto-drop for `@[Drop]` types (Array, Map, user-defined structs) |
|
||||
| **Green Threads** | M:N scheduler (ucontext + SIGVTALRM), work-stealing queues |
|
||||
| **Async/Await** | `async func`, `spawn`, `.await` with stackful coroutines |
|
||||
| **Concurrency** | `Task`/`Channel`/`Sync` (pthread-based), `bux_async_yield`/`spawn` |
|
||||
| **CTFE** | `const func` — compile-time function execution |
|
||||
| **Trait Bounds** | `func Max<T: Comparable>(a: T, b: T) -> T` |
|
||||
| **Package Manager** | `bux add`, `bux install`, `bux.lock`, path + git deps |
|
||||
| **Cross-Compilation** | `--target <triple>` via clang (e.g. `aarch64-linux-gnu`) |
|
||||
| **Tooling** | `bux new`, `bux build`, `bux run`, `bux test`, `bux check` |
|
||||
|
||||
---
|
||||
@@ -206,24 +218,38 @@ func Main() -> int {
|
||||
|
||||
```
|
||||
bux/
|
||||
├── compiler/ # Compiler source code
|
||||
│ ├── bootstrap/ # Bootstrap compiler (Nim)
|
||||
│ ├── selfhost/ # Self-hosting compiler source (Bux)
|
||||
│ └── tests/ # Compiler unit tests (Nim)
|
||||
├── library/ # Standard library
|
||||
│ ├── std/ # Standard library modules (.bux)
|
||||
│ │ ├── Io.bux, String.bux, Array.bux, Map.bux
|
||||
│ │ ├── Fs.bux, Mem.bux, Set.bux
|
||||
│ │ ├── Path.bux, Math.bux
|
||||
│ │ └── Task.bux, Channel.bux
|
||||
│ └── runtime/ # C runtime files (runtime.c, io.c)
|
||||
├── tests/ # Language integration tests
|
||||
├── src/ # 🎯 Self-hosting compiler source (Bux)
|
||||
│ ├── main.bux # Entry point
|
||||
│ ├── lexer.bux # Tokenizer
|
||||
│ ├── parser.bux # Pratt parser
|
||||
│ ├── ast.bux # AST node types
|
||||
│ ├── sema.bux # Type checker
|
||||
│ ├── hir_lower.bux # AST → HIR lowering
|
||||
│ ├── c_backend.bux # HIR → C code generator
|
||||
│ └── cli.bux # CLI driver
|
||||
├── bootstrap/ # 🔧 Bootstrap compiler (Nim)
|
||||
│ ├── main.nim # Entry point
|
||||
│ ├── cli.nim # CLI commands + build driver
|
||||
│ └── ... # (mirrors src/ structure)
|
||||
├── lib/ # 📦 Standard library (23 modules)
|
||||
│ ├── Io.bux # Print, ReadFile, WriteFile
|
||||
│ ├── String.bux # Full string API
|
||||
│ ├── Array.bux # Generic Array<T>
|
||||
│ ├── Map.bux # Generic Map<K,V> + StringMap
|
||||
│ ├── Set.bux # Generic Set<T>
|
||||
│ ├── Task.bux # Green threads (spawn/await)
|
||||
│ ├── Channel.bux # Producer/consumer channels
|
||||
│ ├── Drop.bux # Drop trait interface
|
||||
│ └── ... # Math, Fs, Path, Sync, Result, ...
|
||||
├── rt/ # ⚙️ C runtime
|
||||
│ ├── runtime.c # Memory, scheduler, channels
|
||||
│ └── io.c # File I/O wrappers
|
||||
├── tests/ # 🧪 Unit tests (Nim)
|
||||
├── examples/ # Example programs
|
||||
├── tools/ # Additional tooling
|
||||
├── apps/ # Real-world applications
|
||||
├── docs/ # Documentation
|
||||
├── buxs/ # Windows-compatible project root
|
||||
├── README.md
|
||||
├── PLAN.md # Roadmap to self-hosting
|
||||
├── PLAN.md # Roadmap to v1.0.0
|
||||
└── Makefile
|
||||
```
|
||||
|
||||
@@ -244,6 +270,9 @@ make test
|
||||
# Run example programs
|
||||
make test-examples
|
||||
|
||||
# Verify selfhost binary parity (buxc2 → buxc3, identical)
|
||||
make selfhost-loop
|
||||
|
||||
# Clean build artifacts
|
||||
make clean
|
||||
```
|
||||
@@ -252,6 +281,20 @@ make clean
|
||||
|
||||
---
|
||||
|
||||
## Applications
|
||||
|
||||
The `apps/` directory contains real-world Bux applications that serve as integration tests for the compiler:
|
||||
|
||||
| App | Description | Lines |
|
||||
|-----|-------------|-------|
|
||||
| **boko-framework** | Async web framework (like FastAPI), multi-threaded HTTP server | ~660 |
|
||||
| **jwt-pitbul** | JWT CLI tool — sign, verify, decode (HS256/384/512, RS256/384/512, ES256/384, EdDSA) | ~326 |
|
||||
| **nexus** | High-performance HTTP/1.1, HTTP/2 & WebSocket server | ~550 |
|
||||
|
||||
The bootstrap compiler successfully parses and type-checks all three applications without hanging or crashing.
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
- [`docs/LanguageRef.md`](docs/LanguageRef.md) — Language reference
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
[Package]
|
||||
Name = "buxc2"
|
||||
Version = "0.2.0"
|
||||
Type = "bin"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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 "?";
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
name = "test_array"
|
||||
version = "0.1.0"
|
||||
@@ -1,33 +0,0 @@
|
||||
module Main {
|
||||
import Std::Array::Array;
|
||||
import Std::Iter::Iter;
|
||||
import Std::Test::Test;
|
||||
import Std::Io::Io;
|
||||
|
||||
func Main() -> int {
|
||||
var arr: Array<int> = Array_New<int>(4);
|
||||
Array_Push<int>(&arr, 10);
|
||||
Array_Push<int>(&arr, 20);
|
||||
Array_Push<int>(&arr, 30);
|
||||
|
||||
Test_AssertEqInt(Array_Len<int>(&arr) as int, 3);
|
||||
Test_AssertEqInt(Array_Get<int>(&arr, 0), 10);
|
||||
Test_AssertEqInt(Array_Get<int>(&arr, 1), 20);
|
||||
Test_AssertEqInt(Array_Get<int>(&arr, 2), 30);
|
||||
|
||||
var it: Iter<int> = Array_Iter<int>(&arr);
|
||||
Test_AssertTrue(Iter_HasNext<int>(&it));
|
||||
Test_AssertEqInt(Iter_Next<int>(&it), 10);
|
||||
Test_AssertEqInt(Iter_Next<int>(&it), 20);
|
||||
Test_AssertEqInt(Iter_Next<int>(&it), 30);
|
||||
Test_AssertFalse(Iter_HasNext<int>(&it));
|
||||
|
||||
var it2: Iter<int> = Array_Iter<int>(&arr);
|
||||
let count: uint = Iter_Count<int>(&it2);
|
||||
Test_AssertEqInt(count as int, 3);
|
||||
|
||||
Print("All array+iter tests passed!");
|
||||
Array_Free<int>(&arr);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,54 @@
|
||||
/* Bux Standard Library - I/O functions */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
/* PrintLine - print string with newline */
|
||||
void PrintLine(const char* s) {
|
||||
if (s != NULL) {
|
||||
puts(s);
|
||||
} else {
|
||||
puts("");
|
||||
}
|
||||
}
|
||||
|
||||
/* Print - print string without newline */
|
||||
void Print(const char* s) {
|
||||
if (s != NULL) {
|
||||
printf("%s", s);
|
||||
}
|
||||
}
|
||||
|
||||
/* PrintInt - print integer */
|
||||
void PrintInt(int n) {
|
||||
printf("%d", n);
|
||||
}
|
||||
|
||||
/* PrintInt64 - print 64-bit integer */
|
||||
void PrintInt64(int64_t n) {
|
||||
printf("%lld", (long long)n);
|
||||
}
|
||||
|
||||
/* PrintFloat - print float */
|
||||
void PrintFloat(double f) {
|
||||
printf("%g", f);
|
||||
}
|
||||
|
||||
/* PrintBool - print boolean */
|
||||
void PrintBool(int b) {
|
||||
printf("%s", b ? "true" : "false");
|
||||
}
|
||||
|
||||
/* ReadLine - read line from stdin (simplified) */
|
||||
const char* ReadLine(void) {
|
||||
static char buffer[1024];
|
||||
if (fgets(buffer, sizeof(buffer), stdin) != NULL) {
|
||||
size_t len = strlen(buffer);
|
||||
if (len > 0 && buffer[len-1] == '\n') {
|
||||
buffer[len-1] = '\0';
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
[Package]
|
||||
Name = "closure_test"
|
||||
Version = "0.1.0"
|
||||
Type = "bin"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
@@ -0,0 +1,34 @@
|
||||
module Main {
|
||||
|
||||
extern func PrintInt(x: int);
|
||||
extern func PrintLine(msg: String);
|
||||
|
||||
func Apply(x: int, op: func(int) -> int) -> int {
|
||||
return op(x);
|
||||
}
|
||||
|
||||
func Double(x: int) -> int {
|
||||
return x * 2;
|
||||
}
|
||||
|
||||
func main() -> int {
|
||||
// Closure as variable
|
||||
let add: func(int, int) -> int = |a: int, b: int| -> int { return a + b; };
|
||||
let sum: int = add(3, 4);
|
||||
PrintInt(sum);
|
||||
PrintLine("");
|
||||
|
||||
// Closure passed to function
|
||||
let result: int = Apply(5, |x: int| -> int { return x * 3; });
|
||||
PrintInt(result);
|
||||
PrintLine("");
|
||||
|
||||
// Address of named function as function pointer
|
||||
let d: func(int) -> int = &Double;
|
||||
PrintInt(d(7));
|
||||
PrintLine("");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
@[Drop]
|
||||
struct Buffer {
|
||||
ptr: *int
|
||||
}
|
||||
|
||||
func Buffer_Drop(self: *Buffer) {
|
||||
bux_free(self.ptr as *void);
|
||||
}
|
||||
|
||||
func main() -> int {
|
||||
let buf: Buffer = Buffer { ptr: bux_alloc(10 as uint * sizeof(int)) as *int };
|
||||
|
||||
// Buffer should be auto-dropped here via Buffer_Drop(&buf)
|
||||
return 0;
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
[package]
|
||||
name = "error_snippet_test"
|
||||
version = "0.1.0"
|
||||
pkgType = "bin"
|
||||
@@ -0,0 +1,4 @@
|
||||
func Main() -> int {
|
||||
let x: int = undefined_variable;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
name = "test_funcptr"
|
||||
version = "0.1.0"
|
||||
@@ -0,0 +1,17 @@
|
||||
module Main {
|
||||
|
||||
func Apply(x: int, op: func(int) -> int) -> int {
|
||||
return op(x);
|
||||
}
|
||||
|
||||
func Double(n: int) -> int {
|
||||
return n * 2;
|
||||
}
|
||||
|
||||
func main() -> int {
|
||||
let result: int = Apply(5, &Double);
|
||||
if result != 10 { return 1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
[package]
|
||||
name = "green_threads_test"
|
||||
version = "0.1.0"
|
||||
pkgType = "bin"
|
||||
@@ -0,0 +1,29 @@
|
||||
import Std::Task;
|
||||
import Std::String;
|
||||
|
||||
func Worker(id: int) {
|
||||
PrintLine(String_Concat(String_Concat("Worker ", String_FromInt(id)), " starting"));
|
||||
Task_Sleep(50);
|
||||
PrintLine(String_Concat(String_Concat("Worker ", String_FromInt(id)), " done"));
|
||||
}
|
||||
|
||||
func Main() -> int {
|
||||
PrintLine("=== Green Thread Test ===");
|
||||
|
||||
Task_Init(4);
|
||||
|
||||
let h1: TaskHandle = Task_Spawn(Worker as *void, 1 as *void);
|
||||
let h2: TaskHandle = Task_Spawn(Worker as *void, 2 as *void);
|
||||
let h3: TaskHandle = Task_Spawn(Worker as *void, 3 as *void);
|
||||
|
||||
PrintLine("Waiting for tasks...");
|
||||
|
||||
Task_Wait(h1);
|
||||
Task_Wait(h2);
|
||||
Task_Wait(h3);
|
||||
|
||||
Task_Shutdown();
|
||||
|
||||
PrintLine("=== All tasks complete ===");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import Std::Io;
|
||||
import Std::Array;
|
||||
import Std::Slice;
|
||||
|
||||
func Main() -> int {
|
||||
// Test Array operator_index_get/set
|
||||
let arr: Array<int> = Array_New<int>(10);
|
||||
Array_Push<int>(&arr, 10);
|
||||
Array_Push<int>(&arr, 20);
|
||||
Array_Push<int>(&arr, 30);
|
||||
|
||||
// Use [] syntax (operator_index_get)
|
||||
if arr[0] != 10 { return 1; }
|
||||
if arr[1] != 20 { return 2; }
|
||||
if arr[2] != 30 { return 3; }
|
||||
|
||||
// Use [] syntax (operator_index_set)
|
||||
arr[1] = 99;
|
||||
if arr[1] != 99 { return 4; }
|
||||
|
||||
// Test Slice operator_index_get/set
|
||||
let s: Slice<int> = Slice_FromArray<int>(&arr);
|
||||
|
||||
if s[0] != 10 { return 5; }
|
||||
if s[1] != 99 { return 6; }
|
||||
if s[2] != 30 { return 7; }
|
||||
|
||||
s[2] = 42;
|
||||
if s[2] != 42 { return 8; }
|
||||
|
||||
PrintInt(s[0]);
|
||||
PrintLine("");
|
||||
PrintInt(Slice_Len<int>(&s));
|
||||
PrintLine("");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
# Boko Framework
|
||||
|
||||
**Async web framework for Bux — inspired by FastAPI.**
|
||||
|
||||
Boko is a lightweight, multi-threaded web framework that brings FastAPI-style routing to the Bux programming language. It handles HTTP parsing, path pattern matching, query parameter extraction, and response building so you can focus on your application logic.
|
||||
|
||||
This version (0.2.0) is rewritten with modern Bux: `struct` methods, generic `StringMap<String>` for headers/query/path params, `for` loops, algebraic enums, `StringBuilder`, and string interpolation.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
cd apps/boko-framework
|
||||
../../buxc build
|
||||
./build/boko-framework
|
||||
```
|
||||
|
||||
Open `http://localhost:8080` — you'll see the demo landing page.
|
||||
|
||||
## How It Works
|
||||
|
||||
Boko follows a **single-dispatch-function** pattern. You define one function — `Boko_Router` — and the framework calls it for every incoming request:
|
||||
|
||||
```bux
|
||||
import Boko::{Request, Response, Response_Json, Response_Html, Response_NotFound,
|
||||
Path_Match,
|
||||
App, App_New, App_Run};
|
||||
|
||||
func Boko_Router(req: Request) -> Response {
|
||||
// Route: GET /
|
||||
if String_Eq(req.path, "/") {
|
||||
return Response_Html("<h1>Hello Boko!</h1>");
|
||||
}
|
||||
|
||||
// Route: GET /api/health
|
||||
if String_Eq(req.path, "/api/health") {
|
||||
return Response_Json("{\"status\":\"ok\"}");
|
||||
}
|
||||
|
||||
// Route: GET /users/{id} — path parameter
|
||||
if Path_Match("/users/{id}", req.path, &req) {
|
||||
let id: String = req.GetPathParam("id");
|
||||
// Build JSON response with id
|
||||
return Response_Json(...);
|
||||
}
|
||||
|
||||
// Route: GET /search?q=... — query parameter
|
||||
if String_Eq(req.path, "/search") {
|
||||
let q: String = req.GetQuery("q");
|
||||
return Response_Json(...);
|
||||
}
|
||||
|
||||
return Response_NotFound();
|
||||
}
|
||||
|
||||
func Main() -> int {
|
||||
let app: App = App_New(8080, 4); // port, threads
|
||||
App_Run(&app);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Request
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `method` | `HttpVerb` | GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS |
|
||||
| `path` | `String` | Request path (without query string) |
|
||||
| `body` | `String` | Request body (for POST/PUT) |
|
||||
| `headers` | `StringMap<String>` | Parsed request headers |
|
||||
| `query` | `StringMap<String>` | Parsed query parameters |
|
||||
| `pathParams` | `StringMap<String>` | Extracted path parameters |
|
||||
|
||||
| Method | Returns | Description |
|
||||
|--------|---------|-------------|
|
||||
| `req.GetHeader(name)` | `String` | Get header value by name |
|
||||
| `req.GetQuery(name)` | `String` | Get query parameter by name |
|
||||
| `req.HasQuery(name)` | `bool` | Check if query parameter exists |
|
||||
| `req.GetPathParam(name)` | `String` | Get extracted path parameter |
|
||||
|
||||
### Response
|
||||
|
||||
| Constructor | Content-Type | Status |
|
||||
|-------------|-------------|--------|
|
||||
| `Response_Html(html)` | `text/html` | 200 |
|
||||
| `Response_Json(json)` | `application/json` | 200 |
|
||||
| `Response_Text(text)` | `text/plain` | 200 |
|
||||
| `Response_Redirect(url)` | — | 302 |
|
||||
| `Response_NotFound()` | `application/json` | 404 |
|
||||
| `Response_Error(code, msg)` | `application/json` | custom |
|
||||
| `Response_NoContent()` | — | 204 |
|
||||
|
||||
### Path Matching
|
||||
|
||||
`Path_Match(pattern, actualPath, req)` matches a pattern with `{param}` placeholders:
|
||||
|
||||
```bux
|
||||
// Pattern: /users/{id}/posts/{postId}
|
||||
// Actual: /users/42/posts/7
|
||||
// Extracts: id=42, postId=7
|
||||
|
||||
if Path_Match("/users/{id}/posts/{postId}", req.path, &req) {
|
||||
let userId: String = req.GetPathParam("id"); // "42"
|
||||
let postId: String = req.GetPathParam("postId"); // "7"
|
||||
}
|
||||
```
|
||||
|
||||
The function returns `true` if the pattern matches and populates `req.pathParams`.
|
||||
|
||||
### App
|
||||
|
||||
```bux
|
||||
let app: App = App_New(8080, 4); // port 8080, 4 worker threads
|
||||
App_Run(&app); // blocks, handles requests
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Incoming connection
|
||||
│
|
||||
▼
|
||||
Net_Accept() ─── worker thread (1 of N)
|
||||
│
|
||||
▼
|
||||
Net_Recv() → raw HTTP bytes
|
||||
│
|
||||
▼
|
||||
Request_Parse() → method, path, query, headers, body
|
||||
│
|
||||
▼
|
||||
Boko_Router(req) ←── YOUR CODE
|
||||
│
|
||||
▼
|
||||
Response_Build() → HTTP/1.1 response string
|
||||
│
|
||||
▼
|
||||
Net_Send() → bytes to client
|
||||
│
|
||||
▼
|
||||
Net_Close()
|
||||
```
|
||||
|
||||
## Endpoints in Demo App
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| GET | `/` | Landing page (HTML) |
|
||||
| GET | `/api/health` | Health check (JSON) |
|
||||
| GET | `/api/info` | Framework info (JSON) |
|
||||
| GET | `/hello?name=X` | Query param demo |
|
||||
| GET | `/users/{id}` | Path param demo |
|
||||
| GET | `/posts/{id}/comments/{cid}` | Multi path param demo |
|
||||
| GET | `/redirect` | 302 redirect to `/` |
|
||||
| POST | `/api/echo` | Echo request body |
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
apps/boko-framework/
|
||||
├── bux.toml # Package manifest
|
||||
├── README.md # This file
|
||||
└── src/
|
||||
├── Boko.bux # Framework core (~500 lines)
|
||||
└── Main.bux # Example app (~150 lines)
|
||||
```
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
Boko is intentionally simple. Instead of complex DSLs or code generation, it gives you:
|
||||
|
||||
- **One function to write** — `Boko_Router(req) -> Response`
|
||||
- **Direct control** — you write plain if/else or match for routing
|
||||
- **No magic** — path params, query params, headers are explicit function calls
|
||||
- **Fast** — multi-threaded accept loop, zero allocations where possible
|
||||
|
||||
This is the same philosophy as Go's `net/http` — simple, explicit, composable.
|
||||
|
||||
## License
|
||||
|
||||
Part of the Bux project. See root [LICENSE](../../LICENSE).
|
||||
@@ -0,0 +1 @@
|
||||
EXIT:137
|
||||
@@ -0,0 +1,8 @@
|
||||
[Package]
|
||||
Name = "boko-framework"
|
||||
Version = "0.2.0"
|
||||
Type = "bin"
|
||||
Description = "Boko — async web framework for Bux, inspired by FastAPI"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
@@ -0,0 +1 @@
|
||||
EXIT:137
|
||||
@@ -0,0 +1 @@
|
||||
EXIT:143
|
||||
@@ -0,0 +1 @@
|
||||
EXIT:137
|
||||
@@ -0,0 +1,2 @@
|
||||
bash: ред 1: ../../buxc_lir: Няма такъв файл или директория
|
||||
EXIT:127
|
||||
@@ -0,0 +1 @@
|
||||
EXIT:143
|
||||
@@ -0,0 +1 @@
|
||||
EXIT:143
|
||||
@@ -0,0 +1,2 @@
|
||||
bux 0.1.0 (bootstrap)
|
||||
EXIT:0
|
||||
@@ -0,0 +1,40 @@
|
||||
nim c -o:buxc -d:release --opt:size bootstrap/main.nim
|
||||
Hint: used config file '/etc/nim/nim.cfg' [Conf]
|
||||
Hint: used config file '/etc/nim/config.nims' [Conf]
|
||||
...........................................................................................................................................
|
||||
/home/ziko/z-git/bux/bux/bootstrap/lexer.nim(340, 12) Hint: 'check3' is declared but not used [XDeclaredButNotUsed]
|
||||
/home/ziko/z-git/bux/bux/bootstrap/lexer.nim(350, 12) Hint: 'checkEq' is declared but not used [XDeclaredButNotUsed]
|
||||
/home/ziko/z-git/bux/bux/bootstrap/lexer.nim(64, 6) Hint: 'match' is declared but not used [XDeclaredButNotUsed]
|
||||
/home/ziko/z-git/bux/bux/bootstrap/lexer.nim(84, 6) Hint: 'emitWarning' is declared but not used [XDeclaredButNotUsed]
|
||||
..
|
||||
/home/ziko/z-git/bux/bux/bootstrap/parser.nim(663, 7) Hint: 'loc' is declared but not used [XDeclaredButNotUsed]
|
||||
/home/ziko/z-git/bux/bux/bootstrap/parser.nim(1001, 9) Hint: 'isVar' is declared but not used [XDeclaredButNotUsed]
|
||||
/home/ziko/z-git/bux/bux/bootstrap/parser.nim(59, 6) Hint: 'match' is declared but not used [XDeclaredButNotUsed]
|
||||
.......
|
||||
/home/ziko/z-git/bux/bux/bootstrap/sema.nim(1150, 9) Hint: 'operandType' is declared but not used [XDeclaredButNotUsed]
|
||||
/home/ziko/z-git/bux/bux/bootstrap/sema.nim(1155, 9) Hint: 'operandType' is declared but not used [XDeclaredButNotUsed]
|
||||
/home/ziko/z-git/bux/bux/bootstrap/sema.nim(1165, 9) Hint: 'subjectType' is declared but not used [XDeclaredButNotUsed]
|
||||
/home/ziko/z-git/bux/bux/bootstrap/sema.nim(1200, 9) Hint: 'operand' is declared but not used [XDeclaredButNotUsed]
|
||||
/home/ziko/z-git/bux/bux/bootstrap/sema.nim(94, 6) Hint: 'emitWarning' is declared but not used [XDeclaredButNotUsed]
|
||||
.
|
||||
/home/ziko/z-git/bux/bux/bootstrap/manifest.nim(93, 11) Hint: 'val' is declared but not used [XDeclaredButNotUsed]
|
||||
..
|
||||
/home/ziko/z-git/bux/bux/bootstrap/hir_lower.nim(1057, 9) Hint: 'loweredIter' is declared but not used [XDeclaredButNotUsed]
|
||||
.
|
||||
/home/ziko/z-git/bux/bux/bootstrap/lir.nim(5, 8) Warning: imported and not used: 'types' [UnusedImport]
|
||||
.
|
||||
/home/ziko/z-git/bux/bux/bootstrap/lir_lower.nim(419, 9) Hint: 't' is declared but not used [XDeclaredButNotUsed]
|
||||
/home/ziko/z-git/bux/bux/bootstrap/lir_lower.nim(595, 9) Hint: 'bodyLbl' is declared but not used [XDeclaredButNotUsed]
|
||||
/home/ziko/z-git/bux/bux/bootstrap/lir_lower.nim(734, 11) Hint: 'exprVal' is declared but not used [XDeclaredButNotUsed]
|
||||
/home/ziko/z-git/bux/bux/bootstrap/lir_lower.nim(746, 9) Hint: 'exprVal' is declared but not used [XDeclaredButNotUsed]
|
||||
/home/ziko/z-git/bux/bux/bootstrap/lir_lower.nim(6, 8) Warning: imported and not used: 'ast' [UnusedImport]
|
||||
.
|
||||
/home/ziko/z-git/bux/bux/bootstrap/lir_c_backend.nim(20, 6) Hint: 'emit' is declared but not used [XDeclaredButNotUsed]
|
||||
/home/ziko/z-git/bux/bux/bootstrap/lir_c_backend.nim(46, 6) Hint: 'typeFromValue' is declared but not used [XDeclaredButNotUsed]
|
||||
/home/ziko/z-git/bux/bux/bootstrap/lir_c_backend.nim(58, 6) Hint: 'setTempType' is declared but not used [XDeclaredButNotUsed]
|
||||
/home/ziko/z-git/bux/bux/bootstrap/cli.nim(2, 55) Warning: imported and not used: 'lir' [UnusedImport]
|
||||
Hint: [Link]
|
||||
Hint: mm: orc; threads: on; opt: size; options: -d:release
|
||||
76982 lines; 1.552s; 174.824MiB peakmem; proj: /home/ziko/z-git/bux/bux/bootstrap/main.nim; out: /home/ziko/z-git/bux/bux/buxc [SuccessX]
|
||||
# strip buxc
|
||||
EXIT:0
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
cd /home/ziko/z-git/bux/bux/apps/boko-framework
|
||||
|
||||
echo "=== CHECK ==="
|
||||
../../buxc check 2>&1
|
||||
echo "CHECK_EXIT=$?"
|
||||
|
||||
echo ""
|
||||
echo "=== BUILD ==="
|
||||
../../buxc build 2>&1
|
||||
echo "BUILD_EXIT=$?"
|
||||
|
||||
echo ""
|
||||
echo "=== FILES ==="
|
||||
find . -type f -newer bux.toml 2>/dev/null | head -30
|
||||
echo ""
|
||||
echo "=== BINARY SEARCH ==="
|
||||
file boko-framework 2>/dev/null || echo "no boko-framework"
|
||||
ls -la build/ 2>/dev/null || echo "no build dir"
|
||||
@@ -0,0 +1,467 @@
|
||||
// =============================================================================
|
||||
// Boko — Async Web Framework for Bux (inspired by FastAPI)
|
||||
// Rewritten with modern Bux: methods on structs, generic StringMap, for-in
|
||||
// loops, algebraic enums, StringBuilder, and string interpolation.
|
||||
// =============================================================================
|
||||
module Boko {
|
||||
|
||||
import Std::Io::{PrintLine, Print, PrintInt};
|
||||
import Std::Net::{Net_Create, Net_SetReuse, Net_Bind, Net_Listen, Net_Accept, Net_Send, Net_Recv, Net_Close, Net_LastError};
|
||||
import Std::String::{
|
||||
String_Len, String_Eq, String_IsNull,
|
||||
String_StartsWith, String_Contains,
|
||||
String_Find, String_Offset, String_Slice,
|
||||
String_SplitCount, String_SplitPart,
|
||||
StringBuilder, StringBuilder_New, StringBuilder_Append,
|
||||
StringBuilder_AppendInt, StringBuilder_Build, StringBuilder_Free
|
||||
};
|
||||
import Std::Map::{StringMap, StringMap_New, StringMap_Set, StringMap_Get, StringMap_Has};
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// HTTP Methods
|
||||
// =============================================================================
|
||||
enum HttpVerb {
|
||||
GET,
|
||||
POST,
|
||||
PUT,
|
||||
DELETE,
|
||||
PATCH,
|
||||
HEAD,
|
||||
OPTIONS,
|
||||
}
|
||||
|
||||
func HttpVerb_MethodName(verb: HttpVerb) -> String {
|
||||
if verb.tag == HttpVerb_GET { return "GET"; }
|
||||
if verb.tag == HttpVerb_POST { return "POST"; }
|
||||
if verb.tag == HttpVerb_PUT { return "PUT"; }
|
||||
if verb.tag == HttpVerb_DELETE { return "DELETE"; }
|
||||
if verb.tag == HttpVerb_PATCH { return "PATCH"; }
|
||||
if verb.tag == HttpVerb_HEAD { return "HEAD"; }
|
||||
if verb.tag == HttpVerb_OPTIONS { return "OPTIONS"; }
|
||||
return "?";
|
||||
}
|
||||
|
||||
func HttpVerb_Parse(methodStr: String) -> HttpVerb {
|
||||
if String_Eq(methodStr, "GET") { return HttpVerb { tag: HttpVerb_GET }; }
|
||||
if String_Eq(methodStr, "POST") { return HttpVerb { tag: HttpVerb_POST }; }
|
||||
if String_Eq(methodStr, "PUT") { return HttpVerb { tag: HttpVerb_PUT }; }
|
||||
if String_Eq(methodStr, "DELETE") { return HttpVerb { tag: HttpVerb_DELETE }; }
|
||||
if String_Eq(methodStr, "PATCH") { return HttpVerb { tag: HttpVerb_PATCH }; }
|
||||
if String_Eq(methodStr, "HEAD") { return HttpVerb { tag: HttpVerb_HEAD }; }
|
||||
if String_Eq(methodStr, "OPTIONS") { return HttpVerb { tag: HttpVerb_OPTIONS }; }
|
||||
return HttpVerb { tag: HttpVerb_GET };
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Request — parsed incoming HTTP request
|
||||
// =============================================================================
|
||||
struct Request {
|
||||
method: HttpVerb,
|
||||
path: String,
|
||||
body: String,
|
||||
headers: StringMap<String>,
|
||||
query: StringMap<String>,
|
||||
pathParams: StringMap<String>,
|
||||
}
|
||||
|
||||
extend Request {
|
||||
func GetHeader(self: Request, name: String) -> String {
|
||||
if StringMap_Has<String>(&self.headers, name) {
|
||||
return StringMap_Get<String>(&self.headers, name);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
func GetQuery(self: Request, name: String) -> String {
|
||||
if StringMap_Has<String>(&self.query, name) {
|
||||
return StringMap_Get<String>(&self.query, name);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
func HasQuery(self: Request, name: String) -> bool {
|
||||
return StringMap_Has<String>(&self.query, name);
|
||||
}
|
||||
|
||||
func GetPathParam(self: Request, name: String) -> String {
|
||||
if StringMap_Has<String>(&self.pathParams, name) {
|
||||
return StringMap_Get<String>(&self.pathParams, name);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Response — outgoing HTTP response
|
||||
// =============================================================================
|
||||
struct Response {
|
||||
statusCode: int,
|
||||
contentType: String,
|
||||
body: String,
|
||||
extraHeaders: String,
|
||||
}
|
||||
|
||||
// --- Constructors ---
|
||||
func Response_New(status: int, contentType: String, body: String) -> Response {
|
||||
return Response { statusCode: status, contentType: contentType, body: body, extraHeaders: "" };
|
||||
}
|
||||
|
||||
func Response_Ok(body: String) -> Response {
|
||||
return Response_New(200, "text/html; charset=utf-8", body);
|
||||
}
|
||||
|
||||
func Response_Html(html: String) -> Response {
|
||||
return Response_New(200, "text/html; charset=utf-8", html);
|
||||
}
|
||||
|
||||
func Response_Json(json: String) -> Response {
|
||||
return Response_New(200, "application/json; charset=utf-8", json);
|
||||
}
|
||||
|
||||
func Response_Text(text: String) -> Response {
|
||||
return Response_New(200, "text/plain; charset=utf-8", text);
|
||||
}
|
||||
|
||||
func Response_Redirect(url: String) -> Response {
|
||||
let sb: StringBuilder = StringBuilder_New();
|
||||
StringBuilder_Append(&sb, "Location: ");
|
||||
StringBuilder_Append(&sb, url);
|
||||
StringBuilder_Append(&sb, "\r\n");
|
||||
let headers: String = StringBuilder_Build(&sb);
|
||||
StringBuilder_Free(&sb);
|
||||
return Response { statusCode: 302, contentType: "", body: "", extraHeaders: headers };
|
||||
}
|
||||
|
||||
func Response_NotFound() -> Response {
|
||||
return Response_New(404, "application/json; charset=utf-8", "{\"error\":\"not_found\"}");
|
||||
}
|
||||
|
||||
func Response_Error(status: int, message: String) -> Response {
|
||||
let sb: StringBuilder = StringBuilder_New();
|
||||
StringBuilder_Append(&sb, "{\"error\":\"");
|
||||
StringBuilder_Append(&sb, message);
|
||||
StringBuilder_Append(&sb, "\"}");
|
||||
let body: String = StringBuilder_Build(&sb);
|
||||
StringBuilder_Free(&sb);
|
||||
return Response_New(status, "application/json; charset=utf-8", body);
|
||||
}
|
||||
|
||||
func Response_NoContent() -> Response {
|
||||
return Response_New(204, "", "");
|
||||
}
|
||||
|
||||
// --- Instance helpers ---
|
||||
extend Response {
|
||||
func WithHeader(self: Response, key: String, value: String) -> Response {
|
||||
let sb: StringBuilder = StringBuilder_New();
|
||||
StringBuilder_Append(&sb, self.extraHeaders);
|
||||
StringBuilder_Append(&sb, key);
|
||||
StringBuilder_Append(&sb, ": ");
|
||||
StringBuilder_Append(&sb, value);
|
||||
StringBuilder_Append(&sb, "\r\n");
|
||||
self.extraHeaders = StringBuilder_Build(&sb);
|
||||
StringBuilder_Free(&sb);
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Status phrase lookup
|
||||
// =============================================================================
|
||||
func StatusPhrase(code: int) -> String {
|
||||
if code == 200 { return "OK"; }
|
||||
if code == 201 { return "Created"; }
|
||||
if code == 204 { return "No Content"; }
|
||||
if code == 301 { return "Moved Permanently"; }
|
||||
if code == 302 { return "Found"; }
|
||||
if code == 400 { return "Bad Request"; }
|
||||
if code == 401 { return "Unauthorized"; }
|
||||
if code == 403 { return "Forbidden"; }
|
||||
if code == 404 { return "Not Found"; }
|
||||
if code == 500 { return "Internal Server Error"; }
|
||||
if code == 502 { return "Bad Gateway"; }
|
||||
if code == 503 { return "Service Unavailable"; }
|
||||
return "OK";
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Build HTTP response string from Response struct
|
||||
// =============================================================================
|
||||
func Response_Build(resp: Response) -> String {
|
||||
let sb: StringBuilder = StringBuilder_New();
|
||||
|
||||
// Status line
|
||||
StringBuilder_Append(&sb, "HTTP/1.1 ");
|
||||
StringBuilder_AppendInt(&sb, resp.statusCode as int64);
|
||||
StringBuilder_Append(&sb, " ");
|
||||
StringBuilder_Append(&sb, StatusPhrase(resp.statusCode));
|
||||
StringBuilder_Append(&sb, "\r\n");
|
||||
|
||||
// Server
|
||||
StringBuilder_Append(&sb, "Server: Boko/0.2.0 (Bux)\r\n");
|
||||
|
||||
// Extra headers
|
||||
if String_Len(resp.extraHeaders) > 0 {
|
||||
StringBuilder_Append(&sb, resp.extraHeaders);
|
||||
}
|
||||
|
||||
// Content-Type
|
||||
if String_Len(resp.contentType) > 0 {
|
||||
StringBuilder_Append(&sb, "Content-Type: ");
|
||||
StringBuilder_Append(&sb, resp.contentType);
|
||||
StringBuilder_Append(&sb, "\r\n");
|
||||
}
|
||||
|
||||
// Content-Length
|
||||
let bodyLen: uint = String_Len(resp.body);
|
||||
StringBuilder_Append(&sb, "Content-Length: ");
|
||||
StringBuilder_AppendInt(&sb, bodyLen as int64);
|
||||
StringBuilder_Append(&sb, "\r\n");
|
||||
|
||||
// Connection close
|
||||
StringBuilder_Append(&sb, "Connection: close\r\n");
|
||||
StringBuilder_Append(&sb, "\r\n");
|
||||
|
||||
if bodyLen > 0 {
|
||||
StringBuilder_Append(&sb, resp.body);
|
||||
}
|
||||
|
||||
let result: String = StringBuilder_Build(&sb);
|
||||
StringBuilder_Free(&sb);
|
||||
return result;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Parse query string: ?key=val&key2=val2 → generic StringMap
|
||||
// =============================================================================
|
||||
func Query_Parse(queryString: String) -> StringMap<String> {
|
||||
let map: StringMap<String> = StringMap_New<String>(8);
|
||||
if String_Len(queryString) == 0 {
|
||||
return map;
|
||||
}
|
||||
|
||||
let pairCount: uint = String_SplitCount(queryString, "&");
|
||||
for i in 0..pairCount {
|
||||
let pair: String = String_SplitPart(queryString, "&", i);
|
||||
let eqPos: String = String_Find(pair, "=");
|
||||
var key: String = pair;
|
||||
var value: String = "";
|
||||
if !String_IsNull(eqPos) {
|
||||
let keyLen: uint = String_Offset(eqPos, pair);
|
||||
key = String_Slice(pair, 0, keyLen);
|
||||
let valStart: uint = keyLen + 1;
|
||||
let pairLen: uint = String_Len(pair);
|
||||
if valStart < pairLen {
|
||||
value = String_Slice(pair, valStart, pairLen - valStart);
|
||||
}
|
||||
}
|
||||
StringMap_Set<String>(&map, key, value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Parse incoming HTTP request from raw bytes
|
||||
// =============================================================================
|
||||
func Request_Parse(raw: String) -> Request {
|
||||
var req: Request;
|
||||
req.method = HttpVerb { tag: HttpVerb_GET };
|
||||
req.path = "/";
|
||||
req.body = "";
|
||||
req.headers = StringMap_New<String>(8);
|
||||
req.query = StringMap_New<String>(4);
|
||||
req.pathParams = StringMap_New<String>(4);
|
||||
|
||||
if String_Len(raw) == 0 { return req; }
|
||||
|
||||
// Find header/body boundary
|
||||
let boundary: String = String_Find(raw, "\r\n\r\n");
|
||||
let rawLen: uint = String_Len(raw);
|
||||
var headerLen: uint = rawLen;
|
||||
if !String_IsNull(boundary) {
|
||||
headerLen = String_Offset(boundary, raw);
|
||||
let bodyStart: uint = headerLen + 4;
|
||||
if bodyStart < rawLen {
|
||||
req.body = String_Slice(raw, bodyStart, rawLen - bodyStart);
|
||||
}
|
||||
}
|
||||
|
||||
if headerLen == 0 { return req; }
|
||||
let headerBlock: String = String_Slice(raw, 0, headerLen);
|
||||
let lineCount: uint = String_SplitCount(headerBlock, "\r\n");
|
||||
if lineCount == 0 { return req; }
|
||||
|
||||
// Parse request line
|
||||
let requestLine: String = String_SplitPart(headerBlock, "\r\n", 0);
|
||||
if String_Len(requestLine) > 0 {
|
||||
let methodStr: String = String_SplitPart(requestLine, " ", 0);
|
||||
req.method = HttpVerb_Parse(methodStr);
|
||||
|
||||
let fullPath: String = String_SplitPart(requestLine, " ", 1);
|
||||
let qmark: String = String_Find(fullPath, "?");
|
||||
if !String_IsNull(qmark) {
|
||||
let pathLen: uint = String_Offset(qmark, fullPath);
|
||||
req.path = String_Slice(fullPath, 0, pathLen);
|
||||
let qsStart: uint = pathLen + 1;
|
||||
let fullLen: uint = String_Len(fullPath);
|
||||
if qsStart < fullLen {
|
||||
let qs: String = String_Slice(fullPath, qsStart, fullLen - qsStart);
|
||||
req.query = Query_Parse(qs);
|
||||
}
|
||||
} else {
|
||||
req.path = fullPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse headers
|
||||
for i in 1..lineCount {
|
||||
let line: String = String_SplitPart(headerBlock, "\r\n", i);
|
||||
let colonPos: String = String_Find(line, ": ");
|
||||
if !String_IsNull(colonPos) {
|
||||
let keyLen: uint = String_Offset(colonPos, line);
|
||||
let key: String = String_Slice(line, 0, keyLen);
|
||||
let valStart: uint = keyLen + 2;
|
||||
let lineLen: uint = String_Len(line);
|
||||
var value: String = "";
|
||||
if valStart < lineLen {
|
||||
value = String_Slice(line, valStart, lineLen - valStart);
|
||||
}
|
||||
StringMap_Set<String>(&req.headers, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Path pattern matching: /users/{id} against /users/42 → extracts id=42
|
||||
// =============================================================================
|
||||
func Path_Match(pattern: String, path: String, req: *Request) -> bool {
|
||||
let patParts: uint = String_SplitCount(pattern, "/");
|
||||
let pathParts: uint = String_SplitCount(path, "/");
|
||||
|
||||
if patParts != pathParts { return false; }
|
||||
|
||||
req.pathParams = StringMap_New<String>(8);
|
||||
|
||||
for i in 0..patParts {
|
||||
let patPart: String = String_SplitPart(pattern, "/", i);
|
||||
let pathPart: String = String_SplitPart(path, "/", i);
|
||||
|
||||
if String_StartsWith(patPart, "{") && String_Contains(patPart, "}") {
|
||||
let nameLen: uint = String_Len(patPart) - 2;
|
||||
let name: String = String_Slice(patPart, 1, nameLen);
|
||||
StringMap_Set<String>(&req.pathParams, name, pathPart);
|
||||
} else {
|
||||
if !String_Eq(patPart, pathPart) { return false; }
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// App — server instance
|
||||
// =============================================================================
|
||||
struct App {
|
||||
port: int,
|
||||
threadCount: int,
|
||||
serverName: String,
|
||||
}
|
||||
|
||||
func App_New(port: int, threadCount: int) -> App {
|
||||
return App { port: port, threadCount: threadCount, serverName: "Boko/0.2.0 (Bux)" };
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Forward declaration — user must implement this in their module
|
||||
// =============================================================================
|
||||
func Boko_Router(req: Request) -> Response;
|
||||
|
||||
// =============================================================================
|
||||
// Handle a single connection: parse → dispatch → respond
|
||||
// =============================================================================
|
||||
func App_HandleConnection(clientFd: int) {
|
||||
let raw: String = Net_Recv(clientFd, 8192);
|
||||
if String_Len(raw) == 0 { return; }
|
||||
|
||||
let req: Request = Request_Parse(raw);
|
||||
let resp: Response = Boko_Router(req);
|
||||
|
||||
// Log
|
||||
Print(HttpVerb_MethodName(req.method));
|
||||
Print(" ");
|
||||
Print(req.path);
|
||||
Print(" → ");
|
||||
PrintInt(resp.statusCode);
|
||||
PrintLine("");
|
||||
|
||||
// Send
|
||||
let respStr: String = Response_Build(resp);
|
||||
Net_Send(clientFd, respStr);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Worker thread: accept loop
|
||||
// =============================================================================
|
||||
func App_Worker(serverFd: int) {
|
||||
while true {
|
||||
let clientFd: int = Net_Accept(serverFd);
|
||||
if clientFd < 0 { continue; }
|
||||
App_HandleConnection(clientFd);
|
||||
Net_Close(clientFd);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// App_Run: start the server (blocking)
|
||||
// =============================================================================
|
||||
func App_Run(app: *App) {
|
||||
PrintLine("╔══════════════════════════════════════╗");
|
||||
PrintLine("║ Boko Framework v0.2.0 ║");
|
||||
PrintLine("║ Async web framework for Bux ║");
|
||||
PrintLine("╚══════════════════════════════════════════════════════╝");
|
||||
PrintLine("");
|
||||
|
||||
let fd: int = Net_Create();
|
||||
if fd < 0 {
|
||||
PrintLine("FATAL: socket() failed");
|
||||
return;
|
||||
}
|
||||
|
||||
Net_SetReuse(fd);
|
||||
|
||||
if !Net_Bind(fd, "0.0.0.0", app.port) {
|
||||
Print("FATAL: bind(:");
|
||||
PrintInt(app.port);
|
||||
Print(") failed: ");
|
||||
PrintLine(Net_LastError());
|
||||
Net_Close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
if !Net_Listen(fd, 128) {
|
||||
PrintLine("FATAL: listen() failed");
|
||||
Net_Close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
Print("✓ Boko running on http://0.0.0.0:");
|
||||
PrintInt(app.port);
|
||||
PrintLine("");
|
||||
Print("✓ Workers: ");
|
||||
PrintInt(app.threadCount);
|
||||
PrintLine("");
|
||||
PrintLine("");
|
||||
|
||||
// Spawn workers
|
||||
for i in 0..(app.threadCount - 1) {
|
||||
spawn App_Worker(fd);
|
||||
}
|
||||
|
||||
// Main thread is the last worker
|
||||
App_Worker(fd);
|
||||
}
|
||||
|
||||
} // module Boko
|
||||
@@ -0,0 +1,152 @@
|
||||
// =============================================================================
|
||||
// Boko Example App — demonstrates the modern framework API
|
||||
// =============================================================================
|
||||
module Main {
|
||||
|
||||
import Std::Io::{PrintLine, Print};
|
||||
import Std::String::{
|
||||
String_Eq, String_Len,
|
||||
StringBuilder, StringBuilder_New, StringBuilder_Append,
|
||||
StringBuilder_Build, StringBuilder_Free
|
||||
};
|
||||
import Boko::{
|
||||
App, App_New, App_Run,
|
||||
Request, Response,
|
||||
Response_Html, Response_Json, Response_NotFound, Response_Redirect,
|
||||
Path_Match,
|
||||
HttpVerb
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Boko_Router — user-defined dispatch (called by the framework)
|
||||
// =============================================================================
|
||||
func Boko_Router(req: Request) -> Response {
|
||||
// --- GET / ---
|
||||
if String_Eq(req.path, "/") && req.method.tag == HttpVerb_GET {
|
||||
return Response_Html(PageHome());
|
||||
}
|
||||
|
||||
// --- GET /api/health ---
|
||||
if String_Eq(req.path, "/api/health") {
|
||||
return Response_Json("{\"status\":\"ok\",\"framework\":\"Boko\",\"version\":\"0.2.0\"}");
|
||||
}
|
||||
|
||||
// --- GET /api/info ---
|
||||
if String_Eq(req.path, "/api/info") {
|
||||
return Response_Json("{\"name\":\"Boko\",\"language\":\"Bux\",\"inspiration\":\"FastAPI\",\"features\":[\"routing\",\"path-params\",\"query-params\",\"json\"]}");
|
||||
}
|
||||
|
||||
// --- GET /hello?name=World ---
|
||||
if String_Eq(req.path, "/hello") {
|
||||
var name: String = req.GetQuery("name");
|
||||
if String_Len(name) == 0 { name = "World"; }
|
||||
let html: String = f"<h1>Hello, {name}!</h1>";
|
||||
return Response_Html(html);
|
||||
}
|
||||
|
||||
// --- GET /users/{id} ---
|
||||
if Path_Match("/users/{id}", req.path, &req) {
|
||||
let id: String = req.GetPathParam("id");
|
||||
let sb: StringBuilder = StringBuilder_New();
|
||||
StringBuilder_Append(&sb, "{\"id\":");
|
||||
StringBuilder_Append(&sb, id);
|
||||
StringBuilder_Append(&sb, ",\"name\":\"User ");
|
||||
StringBuilder_Append(&sb, id);
|
||||
StringBuilder_Append(&sb, "\"}");
|
||||
let json: String = StringBuilder_Build(&sb);
|
||||
StringBuilder_Free(&sb);
|
||||
return Response_Json(json);
|
||||
}
|
||||
|
||||
// --- GET /posts/{postId}/comments/{commentId} ---
|
||||
if Path_Match("/posts/{postId}/comments/{commentId}", req.path, &req) {
|
||||
let pid: String = req.GetPathParam("postId");
|
||||
let cid: String = req.GetPathParam("commentId");
|
||||
let sb: StringBuilder = StringBuilder_New();
|
||||
StringBuilder_Append(&sb, "{\"postId\":");
|
||||
StringBuilder_Append(&sb, pid);
|
||||
StringBuilder_Append(&sb, ",\"commentId\":");
|
||||
StringBuilder_Append(&sb, cid);
|
||||
StringBuilder_Append(&sb, ",\"text\":\"Great post!\"}");
|
||||
let json: String = StringBuilder_Build(&sb);
|
||||
StringBuilder_Free(&sb);
|
||||
return Response_Json(json);
|
||||
}
|
||||
|
||||
// --- GET /redirect → 302 to / ---
|
||||
if String_Eq(req.path, "/redirect") {
|
||||
return Response_Redirect("/");
|
||||
}
|
||||
|
||||
// --- POST /api/echo ---
|
||||
if String_Eq(req.path, "/api/echo") && req.method.tag == HttpVerb_POST {
|
||||
let sb: StringBuilder = StringBuilder_New();
|
||||
StringBuilder_Append(&sb, "{\"echo\":");
|
||||
StringBuilder_Append(&sb, req.body);
|
||||
StringBuilder_Append(&sb, "}");
|
||||
let json: String = StringBuilder_Build(&sb);
|
||||
StringBuilder_Free(&sb);
|
||||
return Response_Json(json);
|
||||
}
|
||||
|
||||
// --- 404 ---
|
||||
return Response_NotFound();
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// HTML landing page (raw multi-line string via backticks)
|
||||
// =============================================================================
|
||||
func PageHome() -> String {
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Boko Framework</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{font-family:system-ui,sans-serif;background:#0d1117;color:#c9d1d9;min-height:100vh;display:flex;align-items:center;justify-content:center}
|
||||
.card{background:#161b22;border:1px solid #30363d;border-radius:12px;padding:3rem;max-width:560px;width:100%}
|
||||
h1{color:#58a6ff;font-size:2rem;margin-bottom:.5rem}
|
||||
.tag{color:#8b949e;font-size:.9rem;margin-bottom:2rem}
|
||||
h3{color:#d2a8ff;margin:1.5rem 0 .75rem}
|
||||
.ep{display:flex;gap:1rem;padding:.35rem 0;font-family:monospace;font-size:.9rem}
|
||||
.ep .m{color:#3fb950;font-weight:bold;min-width:52px}
|
||||
.ep .p{color:#c9d1d9}
|
||||
.ep .d{color:#484f58;margin-left:auto}
|
||||
a{color:#58a6ff}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>⚡ Boko</h1>
|
||||
<p class="tag">Async web framework for Bux — inspired by FastAPI</p>
|
||||
<h3>Try it</h3>
|
||||
<div class="ep"><span class="m">GET</span><span class="p"><a href="/hello?name=Bux">/hello?name=Bux</a></span><span class="d">query param</span></div>
|
||||
<div class="ep"><span class="m">GET</span><span class="p"><a href="/users/42">/users/42</a></span><span class="d">path param</span></div>
|
||||
<div class="ep"><span class="m">GET</span><span class="p"><a href="/posts/7/comments/3">/posts/7/comments/3</a></span><span class="d">multi params</span></div>
|
||||
<div class="ep"><span class="m">GET</span><span class="p"><a href="/redirect">/redirect</a></span><span class="d">302 → /</span></div>
|
||||
<div class="ep"><span class="m">GET</span><span class="p"><a href="/api/health">/api/health</a></span><span class="d">JSON</span></div>
|
||||
<div class="ep"><span class="m">GET</span><span class="p"><a href="/api/info">/api/info</a></span><span class="d">JSON</span></div>
|
||||
<h3>Features</h3>
|
||||
<div class="ep"><span class="m">✓</span><span class="p">Path routing with {params}</span></div>
|
||||
<div class="ep"><span class="m">✓</span><span class="p">Query parameter extraction</span></div>
|
||||
<div class="ep"><span class="m">✓</span><span class="p">JSON / HTML / Text responses</span></div>
|
||||
<div class="ep"><span class="m">✓</span><span class="p">Multi-threaded (configurable)</span></div>
|
||||
<div class="ep"><span class="m">✓</span><span class="p">Redirects (302)</span></div>
|
||||
<div class="ep"><span class="m">✓</span><span class="p">POST body access</span></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Main — start the server
|
||||
// =============================================================================
|
||||
func Main() -> int {
|
||||
let app: App = App_New(8080, 4);
|
||||
App_Run(&app);
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // module Main
|
||||
@@ -0,0 +1,177 @@
|
||||
# jwt-pitbul
|
||||
|
||||
**JWT CLI tool for the Bux ecosystem — sign, verify, decode, and key generation.**
|
||||
|
||||
A standalone command-line utility for working with JSON Web Tokens (RFC 7519). Built on `Std::Crypto`, backed by OpenSSL.
|
||||
|
||||
This version (0.2.0) is rewritten with modern Bux: algebraic enums, generic `Array<String>`, methods on `JwtAlg`, string interpolation, `for` loops, and structured `Result`/`Option`-style error handling.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
cd apps/jwt-pitbul
|
||||
../../buxc build
|
||||
./build/jwt-pitbul
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
### `sign` — Create a JWT
|
||||
|
||||
```
|
||||
jwt-pitbul sign <alg> <key> <claims_json>
|
||||
```
|
||||
|
||||
| Argument | Description |
|
||||
|----------|-------------|
|
||||
| `<alg>` | Algorithm: `HS256`, `HS384`, `HS512`, `RS256`, `RS384`, `RS512`, `ES256`, `ES384`, `EdDSA` |
|
||||
| `<key>` | HMAC secret string, or path to PEM file (RSA/ECDSA), or base64 raw key (Ed25519) |
|
||||
| `<claims_json>` | JSON payload string (e.g. `{"sub":"123","exp":1735689600}`) |
|
||||
|
||||
```bash
|
||||
# HMAC
|
||||
jwt-pitbul sign HS256 'my-secret-key' '{"sub":"123","role":"admin"}'
|
||||
|
||||
# RSA (private key from file)
|
||||
jwt-pitbul sign RS256 private.pem '{"sub":"456"}'
|
||||
|
||||
# ECDSA
|
||||
jwt-pitbul sign ES256 ec_private.pem '{"sub":"789"}'
|
||||
|
||||
# Ed25519 (base64-encoded 32-byte private key)
|
||||
jwt-pitbul sign EdDSA 'MC4CAQAwBQYDK2VwBCIEIJ...' '{"sub":"000"}'
|
||||
```
|
||||
|
||||
### `verify` — Verify and decode
|
||||
|
||||
```
|
||||
jwt-pitbul verify <token> <alg> <key>
|
||||
```
|
||||
|
||||
```bash
|
||||
jwt-pitbul verify eyJhbGciOiJ... HS256 'my-secret-key'
|
||||
# ✓ Signature valid
|
||||
#
|
||||
# Header:
|
||||
# {"alg":"HS256","typ":"JWT"}
|
||||
#
|
||||
# Payload:
|
||||
# {"sub":"123","role":"admin"}
|
||||
```
|
||||
|
||||
### `decode` — Decode without verification
|
||||
|
||||
```
|
||||
jwt-pitbul decode <token>
|
||||
```
|
||||
|
||||
```bash
|
||||
jwt-pitbul decode eyJhbGciOiJ...
|
||||
# Decoded (no verification):
|
||||
#
|
||||
# Header:
|
||||
# {"alg":"HS256","typ":"JWT"}
|
||||
#
|
||||
# Payload:
|
||||
# {"sub":"123"}
|
||||
#
|
||||
# Signature (base64url): xxxxxxxxx...
|
||||
```
|
||||
|
||||
### `keygen` — Generate keys
|
||||
|
||||
```
|
||||
jwt-pitbul keygen <type>
|
||||
```
|
||||
|
||||
| Type | Output |
|
||||
|------|--------|
|
||||
| `ed25519` | Generates an Ed25519 keypair (prints base64 public/private keys) |
|
||||
| `rsa` | Prints OpenSSL CLI commands to generate RSA 2048-bit keys |
|
||||
| `ecdsa` | Prints OpenSSL CLI commands to generate ECDSA P-256 keys |
|
||||
|
||||
```bash
|
||||
jwt-pitbul keygen ed25519
|
||||
# Ed25519 keypair (base64):
|
||||
# Public: MCowBQYDK2VwAyEA...
|
||||
# Private: MC4CAQAwBQYDK2VwBCIEIJ...
|
||||
|
||||
jwt-pitbul keygen rsa
|
||||
# RSA key generation requires OpenSSL CLI:
|
||||
# openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048
|
||||
# openssl rsa -in private.pem -pubout -out public.pem
|
||||
```
|
||||
|
||||
## Supported Algorithms
|
||||
|
||||
| Algorithm | Type | Key Format |
|
||||
|-----------|------|------------|
|
||||
| HS256 | HMAC-SHA256 | Raw secret string |
|
||||
| HS384 | HMAC-SHA384 | Raw secret string |
|
||||
| HS512 | HMAC-SHA512 | Raw secret string |
|
||||
| RS256 | RSA PKCS#1 v1.5 SHA-256 | PEM file path |
|
||||
| RS384 | RSA PKCS#1 v1.5 SHA-384 | PEM file path |
|
||||
| RS512 | RSA PKCS#1 v1.5 SHA-512 | PEM file path |
|
||||
| ES256 | ECDSA P-256 | PEM file path |
|
||||
| ES384 | ECDSA P-384 | PEM file path |
|
||||
| EdDSA | Ed25519 | Base64 32-byte raw key |
|
||||
|
||||
## Generating Keys with OpenSSL
|
||||
|
||||
### RSA 2048-bit
|
||||
|
||||
```bash
|
||||
openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048
|
||||
openssl rsa -in private.pem -pubout -out public.pem
|
||||
```
|
||||
|
||||
### ECDSA P-256
|
||||
|
||||
```bash
|
||||
openssl ecparam -genkey -name prime256v1 -noout -out ec_private.pem
|
||||
openssl ec -in ec_private.pem -pubout -out ec_public.pem
|
||||
```
|
||||
|
||||
### ECDSA P-384
|
||||
|
||||
```bash
|
||||
openssl ecparam -genkey -name secp384r1 -noout -out ec_private.pem
|
||||
openssl ec -in ec_private.pem -pubout -out ec_public.pem
|
||||
```
|
||||
|
||||
### Ed25519
|
||||
|
||||
```bash
|
||||
# In-app generation:
|
||||
jwt-pitbul keygen ed25519
|
||||
|
||||
# Or via OpenSSL:
|
||||
openssl genpkey -algorithm Ed25519 -out ed25519_private.pem
|
||||
openssl pkey -in ed25519_private.pem -pubout -out ed25519_public.pem
|
||||
```
|
||||
|
||||
## Error Codes
|
||||
|
||||
| Exit | Meaning |
|
||||
|------|---------|
|
||||
| 0 | Success |
|
||||
| 1 | Error (invalid arguments, bad signature, missing file, etc.) |
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
cd apps/jwt-pitbul
|
||||
../../buxc build # Bootstrap compiler (Nim)
|
||||
# or
|
||||
../../buxc_lir build # Self-hosted compiler
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Bux standard library (`Std::Crypto`, `Std::Io`, `Std::String`)
|
||||
- OpenSSL 1.1.1+ (linked via runtime)
|
||||
- Bux compiler (`buxc` or `buxc_lir`)
|
||||
|
||||
## License
|
||||
|
||||
Part of the Bux project. See root [LICENSE](../../LICENSE).
|
||||
@@ -0,0 +1,8 @@
|
||||
[Package]
|
||||
Name = "jwt-pitbul"
|
||||
Version = "0.2.0"
|
||||
Type = "bin"
|
||||
Description = "JWT CLI tool — encode, decode, verify, and key generation"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
@@ -0,0 +1,468 @@
|
||||
// =============================================================================
|
||||
// jwt-pitbul — JWT CLI Tool (encode, decode, verify, keygen)
|
||||
// Rewritten with modern Bux: algebraic enums, methods, string interpolation,
|
||||
// for-in loops, generic arrays, and structured error handling.
|
||||
// =============================================================================
|
||||
module Main {
|
||||
|
||||
import Std::Io::{PrintLine, Print, PrintInt};
|
||||
import Std::String::{
|
||||
String_Len, String_Eq,
|
||||
String_SplitCount, String_SplitPart,
|
||||
StringBuilder, StringBuilder_New, StringBuilder_Append,
|
||||
StringBuilder_AppendInt, StringBuilder_Build, StringBuilder_Free
|
||||
};
|
||||
import Std::Crypto::Jwt::{
|
||||
JwtAlg,
|
||||
Jwt_MakeHeader,
|
||||
Jwt_Encode,
|
||||
Jwt_Decode,
|
||||
Jwt_EncodeHS256, Jwt_EncodeHS384, Jwt_EncodeHS512,
|
||||
Jwt_EncodeRS256, Jwt_EncodeES256, Jwt_EncodeEdDSA
|
||||
};
|
||||
import Std::Crypto::Base64::{Base64URL_Decode, Base64_Encode};
|
||||
import Std::Crypto::Ed25519::{Ed25519_Keypair};
|
||||
|
||||
extern func bux_argc() -> int;
|
||||
extern func bux_argv(index: int) -> String;
|
||||
extern func bux_alloc(size: uint) -> *void;
|
||||
extern func bux_read_file(path: String) -> String;
|
||||
extern func bux_file_exists(path: String) -> int;
|
||||
extern func bux_base64_encode(data: String, len: int) -> String;
|
||||
|
||||
// =============================================================================
|
||||
// Constants
|
||||
// =============================================================================
|
||||
const AppName: String = "jwt-pitbul";
|
||||
const Version: String = "0.2.0";
|
||||
|
||||
// =============================================================================
|
||||
// Algebraic enums for optionals and results
|
||||
// =============================================================================
|
||||
enum AlgOption {
|
||||
Some(JwtAlg),
|
||||
None,
|
||||
}
|
||||
|
||||
func AlgOption_MakeSome(value: JwtAlg) -> AlgOption {
|
||||
let o: AlgOption = AlgOption { tag: AlgOption_Some };
|
||||
o.data.Some_0 = value;
|
||||
return o;
|
||||
}
|
||||
|
||||
func AlgOption_MakeNone() -> AlgOption {
|
||||
return AlgOption { tag: AlgOption_None };
|
||||
}
|
||||
|
||||
enum CmdOption {
|
||||
Some(Cmd),
|
||||
None,
|
||||
}
|
||||
|
||||
func CmdOption_MakeSome(value: Cmd) -> CmdOption {
|
||||
let o: CmdOption = CmdOption { tag: CmdOption_Some };
|
||||
o.data.Some_0 = value;
|
||||
return o;
|
||||
}
|
||||
|
||||
func CmdOption_MakeNone() -> CmdOption {
|
||||
return CmdOption { tag: CmdOption_None };
|
||||
}
|
||||
|
||||
enum KeyTypeOption {
|
||||
Some(KeyType),
|
||||
None,
|
||||
}
|
||||
|
||||
func KeyTypeOption_MakeSome(value: KeyType) -> KeyTypeOption {
|
||||
let o: KeyTypeOption = KeyTypeOption { tag: KeyTypeOption_Some };
|
||||
o.data.Some_0 = value;
|
||||
return o;
|
||||
}
|
||||
|
||||
func KeyTypeOption_MakeNone() -> KeyTypeOption {
|
||||
return KeyTypeOption { tag: KeyTypeOption_None };
|
||||
}
|
||||
|
||||
enum KeyResult {
|
||||
Ok(String),
|
||||
Err(String),
|
||||
}
|
||||
|
||||
func KeyResult_MakeOk(value: String) -> KeyResult {
|
||||
let r: KeyResult = KeyResult { tag: KeyResult_Ok };
|
||||
r.data.Ok_0 = value;
|
||||
return r;
|
||||
}
|
||||
|
||||
func KeyResult_MakeErr(msg: String) -> KeyResult {
|
||||
let r: KeyResult = KeyResult { tag: KeyResult_Err };
|
||||
r.data.Err_0 = msg;
|
||||
return r;
|
||||
}
|
||||
|
||||
enum ExitResult {
|
||||
Ok(int),
|
||||
Err(String),
|
||||
}
|
||||
|
||||
func ExitResult_MakeOk(value: int) -> ExitResult {
|
||||
let r: ExitResult = ExitResult { tag: ExitResult_Ok };
|
||||
r.data.Ok_0 = value;
|
||||
return r;
|
||||
}
|
||||
|
||||
func ExitResult_MakeErr(msg: String) -> ExitResult {
|
||||
let r: ExitResult = ExitResult { tag: ExitResult_Err };
|
||||
r.data.Err_0 = msg;
|
||||
return r;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Commands
|
||||
// =============================================================================
|
||||
enum Cmd {
|
||||
Sign,
|
||||
Verify,
|
||||
Decode,
|
||||
Keygen,
|
||||
Help,
|
||||
}
|
||||
|
||||
func ParseCmd(name: String) -> CmdOption {
|
||||
if String_Eq(name, "sign") { return CmdOption_MakeSome(Cmd { tag: Cmd_Sign }); }
|
||||
if String_Eq(name, "verify") { return CmdOption_MakeSome(Cmd { tag: Cmd_Verify }); }
|
||||
if String_Eq(name, "decode") { return CmdOption_MakeSome(Cmd { tag: Cmd_Decode }); }
|
||||
if String_Eq(name, "keygen") { return CmdOption_MakeSome(Cmd { tag: Cmd_Keygen }); }
|
||||
if String_Eq(name, "help") || String_Eq(name, "--help") || String_Eq(name, "-h") {
|
||||
return CmdOption_MakeSome(Cmd { tag: Cmd_Help });
|
||||
}
|
||||
return CmdOption_MakeNone();
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Key type generation
|
||||
// =============================================================================
|
||||
enum KeyType {
|
||||
Rsa,
|
||||
Ecdsa,
|
||||
Ed25519,
|
||||
}
|
||||
|
||||
func ParseKeyType(name: String) -> KeyTypeOption {
|
||||
if String_Eq(name, "rsa") { return KeyTypeOption_MakeSome(KeyType { tag: KeyType_Rsa }); }
|
||||
if String_Eq(name, "ecdsa") { return KeyTypeOption_MakeSome(KeyType { tag: KeyType_Ecdsa }); }
|
||||
if String_Eq(name, "ed25519") { return KeyTypeOption_MakeSome(KeyType { tag: KeyType_Ed25519 }); }
|
||||
return KeyTypeOption_MakeNone();
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Extend JwtAlg with parsing / introspection methods
|
||||
// =============================================================================
|
||||
extend JwtAlg {
|
||||
func Name(self: JwtAlg) -> String {
|
||||
match self {
|
||||
JwtAlg::HS256 => "HS256",
|
||||
JwtAlg::HS384 => "HS384",
|
||||
JwtAlg::HS512 => "HS512",
|
||||
JwtAlg::RS256 => "RS256",
|
||||
JwtAlg::RS384 => "RS384",
|
||||
JwtAlg::RS512 => "RS512",
|
||||
JwtAlg::ES256 => "ES256",
|
||||
JwtAlg::ES384 => "ES384",
|
||||
JwtAlg::EdDSA => "EdDSA",
|
||||
_ => "HS256",
|
||||
}
|
||||
}
|
||||
|
||||
func IsHmac(self: JwtAlg) -> bool {
|
||||
return self.tag == JwtAlg_HS256 || self.tag == JwtAlg_HS384 || self.tag == JwtAlg_HS512;
|
||||
}
|
||||
|
||||
func NeedsPemFile(self: JwtAlg) -> bool {
|
||||
return self.tag == JwtAlg_RS256 || self.tag == JwtAlg_RS384 || self.tag == JwtAlg_RS512 ||
|
||||
self.tag == JwtAlg_ES256 || self.tag == JwtAlg_ES384;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func ParseAlg(name: String) -> AlgOption {
|
||||
if String_Eq(name, "HS256") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_HS256 }); }
|
||||
if String_Eq(name, "HS384") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_HS384 }); }
|
||||
if String_Eq(name, "HS512") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_HS512 }); }
|
||||
if String_Eq(name, "RS256") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_RS256 }); }
|
||||
if String_Eq(name, "RS384") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_RS384 }); }
|
||||
if String_Eq(name, "RS512") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_RS512 }); }
|
||||
if String_Eq(name, "ES256") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_ES256 }); }
|
||||
if String_Eq(name, "ES384") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_ES384 }); }
|
||||
if String_Eq(name, "EdDSA") { return AlgOption_MakeSome(JwtAlg { tag: JwtAlg_EdDSA }); }
|
||||
return AlgOption_MakeNone();
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Help / Usage
|
||||
// =============================================================================
|
||||
func PrintUsage() {
|
||||
PrintLine("╔══════════════════════════════════════════════════════╗");
|
||||
PrintLine(f"║ {AppName} — JWT CLI Tool v{Version} ║");
|
||||
PrintLine("║ Sign, verify, decode JSON Web Tokens ║");
|
||||
PrintLine("╚══════════════════════════════════════════════════════╝");
|
||||
PrintLine("");
|
||||
PrintLine("Usage:");
|
||||
PrintLine(f" {AppName} sign <alg> <key> <claims>");
|
||||
PrintLine(f" {AppName} verify <token> <alg> <key>");
|
||||
PrintLine(f" {AppName} decode <token>");
|
||||
PrintLine(f" {AppName} keygen <type>");
|
||||
PrintLine("");
|
||||
PrintLine("Commands:");
|
||||
PrintLine(" sign Create a signed JWT from claims JSON");
|
||||
PrintLine(" verify Verify a JWT signature and print payload");
|
||||
PrintLine(" decode Decode a JWT without verification");
|
||||
PrintLine(" keygen Generate cryptographic keys");
|
||||
PrintLine("");
|
||||
PrintLine("Algorithms:");
|
||||
PrintLine(" HS256, HS384, HS512 — HMAC (symmetric)");
|
||||
PrintLine(" RS256, RS384, RS512 — RSA PKCS#1 v1.5");
|
||||
PrintLine(" ES256, ES384 — ECDSA P-256 / P-384");
|
||||
PrintLine(" EdDSA — Ed25519");
|
||||
PrintLine("");
|
||||
PrintLine("Key formats:");
|
||||
PrintLine(" HMAC: raw secret string");
|
||||
PrintLine(" RSA: path to PEM private/public key file");
|
||||
PrintLine(" ECDSA: path to PEM private/public key file");
|
||||
PrintLine(" EdDSA: base64-encoded 32-byte raw key");
|
||||
PrintLine("");
|
||||
PrintLine("Key generation:");
|
||||
PrintLine(f" {AppName} keygen rsa # RSA 2048-bit (PEM)");
|
||||
PrintLine(f" {AppName} keygen ecdsa # ECDSA P-256 (PEM)");
|
||||
PrintLine(f" {AppName} keygen ed25519 # Ed25519 (raw base64)");
|
||||
PrintLine("");
|
||||
PrintLine("Examples:");
|
||||
Print(f" {AppName} sign HS256 'my-secret' ");
|
||||
PrintLine("'{\"sub\":\"123\"}'");
|
||||
PrintLine(f" {AppName} verify eyJh... HS256 'my-secret'");
|
||||
PrintLine(f" {AppName} decode eyJh...");
|
||||
PrintLine(f" {AppName} keygen ed25519");
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Key resolution — for RSA/ECDSA, read PEM file; for HMAC/EdDSA, pass through
|
||||
// =============================================================================
|
||||
func ResolveKey(alg: JwtAlg, keyArg: String) -> KeyResult {
|
||||
if !alg.NeedsPemFile() {
|
||||
return KeyResult_MakeOk(keyArg);
|
||||
}
|
||||
|
||||
if bux_file_exists(keyArg) == 0 {
|
||||
return KeyResult_MakeErr(f"ERROR: PEM file not found: {keyArg}");
|
||||
}
|
||||
|
||||
let pem: String = bux_read_file(keyArg);
|
||||
if String_Len(pem) == 0 {
|
||||
return KeyResult_MakeErr(f"ERROR: could not read PEM file: {keyArg}");
|
||||
}
|
||||
return KeyResult_MakeOk(pem);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Command: sign
|
||||
// =============================================================================
|
||||
func CmdSign(algName: String, keyArg: String, claimsJson: String) -> ExitResult {
|
||||
let algOpt: AlgOption = ParseAlg(algName);
|
||||
if algOpt.tag != AlgOption_Some {
|
||||
return ExitResult_MakeErr(f"ERROR: unknown algorithm '{algName}'");
|
||||
}
|
||||
let alg: JwtAlg = algOpt.data.Some_0;
|
||||
|
||||
let keyRes: KeyResult = ResolveKey(alg, keyArg);
|
||||
if keyRes.tag != KeyResult_Ok {
|
||||
return ExitResult_MakeErr(keyRes.data.Err_0);
|
||||
}
|
||||
let key: String = keyRes.data.Ok_0;
|
||||
|
||||
let header: String = Jwt_MakeHeader(alg);
|
||||
let token: String = Jwt_Encode(header, claimsJson, alg, key);
|
||||
|
||||
if String_Len(token) == 0 {
|
||||
return ExitResult_MakeErr("ERROR: signing failed");
|
||||
}
|
||||
|
||||
PrintLine(token);
|
||||
return ExitResult_MakeOk(0);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Command: verify
|
||||
// =============================================================================
|
||||
func CmdVerify(token: String, algName: String, keyArg: String) -> ExitResult {
|
||||
let algOpt: AlgOption = ParseAlg(algName);
|
||||
if algOpt.tag != AlgOption_Some {
|
||||
return ExitResult_MakeErr(f"ERROR: unknown algorithm '{algName}'");
|
||||
}
|
||||
let alg: JwtAlg = algOpt.data.Some_0;
|
||||
|
||||
let keyRes: KeyResult = ResolveKey(alg, keyArg);
|
||||
if keyRes.tag != KeyResult_Ok {
|
||||
return ExitResult_MakeErr(keyRes.data.Err_0);
|
||||
}
|
||||
let key: String = keyRes.data.Ok_0;
|
||||
|
||||
var header: String = "";
|
||||
var payload: String = "";
|
||||
|
||||
if !Jwt_Decode(token, alg, key, &header, &payload) {
|
||||
return ExitResult_MakeErr("✗ Signature INVALID (or malformed token)");
|
||||
}
|
||||
|
||||
PrintLine("✓ Signature valid");
|
||||
PrintLine("");
|
||||
PrintLine("Header:");
|
||||
PrintLine(header);
|
||||
PrintLine("");
|
||||
PrintLine("Payload:");
|
||||
PrintLine(payload);
|
||||
return ExitResult_MakeOk(0);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Command: decode (no verification)
|
||||
// =============================================================================
|
||||
func CmdDecode(token: String) -> ExitResult {
|
||||
let partCount: uint = String_SplitCount(token, ".");
|
||||
if partCount != 3 {
|
||||
return ExitResult_MakeErr("ERROR: not a valid JWT (expected 3 parts)");
|
||||
}
|
||||
|
||||
let headerB64: String = String_SplitPart(token, ".", 0);
|
||||
let payloadB64: String = String_SplitPart(token, ".", 1);
|
||||
let sigB64: String = String_SplitPart(token, ".", 2);
|
||||
|
||||
let headerJson: String = Base64URL_Decode(headerB64);
|
||||
let payloadJson: String = Base64URL_Decode(payloadB64);
|
||||
|
||||
PrintLine("Decoded (no verification):");
|
||||
PrintLine("");
|
||||
PrintLine("Header:");
|
||||
PrintLine(headerJson);
|
||||
PrintLine("");
|
||||
PrintLine("Payload:");
|
||||
PrintLine(payloadJson);
|
||||
PrintLine("");
|
||||
Print("Signature (base64url): ");
|
||||
PrintLine(sigB64);
|
||||
|
||||
return ExitResult_MakeOk(0);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Command: keygen
|
||||
// =============================================================================
|
||||
func CmdKeygen(keyType: String) -> ExitResult {
|
||||
let ktOpt: KeyTypeOption = ParseKeyType(keyType);
|
||||
if ktOpt.tag != KeyTypeOption_Some {
|
||||
return ExitResult_MakeErr(f"ERROR: unknown key type '{keyType}'. Use: rsa, ecdsa, ed25519");
|
||||
}
|
||||
let kt: KeyType = ktOpt.data.Some_0;
|
||||
|
||||
if kt.tag == KeyType_Ed25519 {
|
||||
let pubBuf: *void = bux_alloc(32);
|
||||
let priv: *void = bux_alloc(32);
|
||||
if !Ed25519_Keypair(pubBuf, priv) {
|
||||
return ExitResult_MakeErr("ERROR: Ed25519 key generation failed (OpenSSL 1.1.1+ required)");
|
||||
}
|
||||
let pubB64: String = bux_base64_encode(pubBuf as String, 32);
|
||||
let privB64: String = bux_base64_encode(priv as String, 32);
|
||||
PrintLine("Ed25519 keypair (base64):");
|
||||
PrintLine(f" Public: {pubB64}");
|
||||
PrintLine(f" Private: {privB64}");
|
||||
return ExitResult_MakeOk(0);
|
||||
}
|
||||
if kt.tag == KeyType_Rsa {
|
||||
PrintLine("RSA key generation requires OpenSSL CLI:");
|
||||
PrintLine(" openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048");
|
||||
PrintLine(" openssl rsa -in private.pem -pubout -out public.pem");
|
||||
PrintLine("");
|
||||
PrintLine("(Key generation from within Bux requires PEM write support — coming soon)");
|
||||
return ExitResult_MakeOk(0);
|
||||
}
|
||||
if kt.tag == KeyType_Ecdsa {
|
||||
PrintLine("ECDSA key generation requires OpenSSL CLI:");
|
||||
PrintLine(" openssl ecparam -genkey -name prime256v1 -noout -out ec_private.pem");
|
||||
PrintLine(" openssl ec -in ec_private.pem -pubout -out ec_public.pem");
|
||||
PrintLine("");
|
||||
PrintLine("(Key generation from within Bux requires PEM write support — coming soon)");
|
||||
return ExitResult_MakeOk(0);
|
||||
}
|
||||
return ExitResult_MakeErr(f"ERROR: unknown key type '{keyType}'");
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Dispatch a parsed command to its handler
|
||||
// =============================================================================
|
||||
func RunCommand(cmd: Cmd, args: Array<String>) -> ExitResult {
|
||||
if cmd.tag == Cmd_Help {
|
||||
PrintUsage();
|
||||
return ExitResult_MakeOk(0);
|
||||
}
|
||||
if cmd.tag == Cmd_Sign {
|
||||
if args.len < 5 {
|
||||
return ExitResult_MakeErr("ERROR: 'sign' requires: <alg> <key> <claims_json>");
|
||||
}
|
||||
return CmdSign(args[2], args[3], args[4]);
|
||||
}
|
||||
if cmd.tag == Cmd_Verify {
|
||||
if args.len < 5 {
|
||||
return ExitResult_MakeErr("ERROR: 'verify' requires: <token> <alg> <key>");
|
||||
}
|
||||
return CmdVerify(args[2], args[3], args[4]);
|
||||
}
|
||||
if cmd.tag == Cmd_Decode {
|
||||
if args.len < 3 {
|
||||
return ExitResult_MakeErr("ERROR: 'decode' requires: <token>");
|
||||
}
|
||||
return CmdDecode(args[2]);
|
||||
}
|
||||
if cmd.tag == Cmd_Keygen {
|
||||
if args.len < 3 {
|
||||
return ExitResult_MakeErr("ERROR: 'keygen' requires: <type>");
|
||||
}
|
||||
return CmdKeygen(args[2]);
|
||||
}
|
||||
return ExitResult_MakeErr("ERROR: unhandled command");
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Main Entry Point
|
||||
// =============================================================================
|
||||
func Main() -> int {
|
||||
let argc: int = bux_argc();
|
||||
|
||||
// Collect CLI arguments into a generic Array<String>
|
||||
let args: Array<String> = Array_New<String>(argc as uint);
|
||||
for i in 0..argc {
|
||||
Array_Push<String>(&args, bux_argv(i));
|
||||
}
|
||||
|
||||
if args.len < 2 {
|
||||
PrintUsage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
let command: String = args[1];
|
||||
let cmdOpt: CmdOption = ParseCmd(command);
|
||||
if cmdOpt.tag != CmdOption_Some {
|
||||
PrintLine(f"ERROR: unknown command '{command}'");
|
||||
PrintLine(f"Run '{AppName} help' for usage.");
|
||||
return 1;
|
||||
}
|
||||
let cmd: Cmd = cmdOpt.data.Some_0;
|
||||
|
||||
let result: ExitResult = RunCommand(cmd, args);
|
||||
if result.tag == ExitResult_Err {
|
||||
PrintLine(result.data.Err_0);
|
||||
return 1;
|
||||
}
|
||||
return result.data.Ok_0;
|
||||
}
|
||||
|
||||
} // module Main
|
||||
@@ -0,0 +1,189 @@
|
||||
# Nexus
|
||||
|
||||
**High-performance, multi-threaded HTTP/1.1, HTTP/2 & WebSocket server — built with the [Bux](https://github.com/bux-lang/bux) programming language.**
|
||||
|
||||
Nexus is a from-scratch web server that demonstrates Bux's systems-programming capabilities: raw TCP sockets, pthread-based concurrency, manual memory management, and zero-dependency C ABI interop — all from a clean, modern syntax.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
| Area | What's Implemented |
|
||||
|------|-------------------|
|
||||
| **HTTP/1.1** | Full request parsing (method, path, headers, body), response building with status codes, content negotiation |
|
||||
| **Multi-threaded** | Configurable worker pool using the multi-accept pattern — each worker calls `accept()` directly on the shared listen socket |
|
||||
| **HTTP/2** | Connection preface detection (`PRI * HTTP/2.0`), upgrade-aware routing |
|
||||
| **WebSocket** | RFC 6455 upgrade handshake detection, `Sec-WebSocket-Key` extraction |
|
||||
| **Static files** | Serves from `public/` with MIME-type detection for 20+ file types, directory-traversal protection |
|
||||
| **JSON API** | Built-in `/api/health` and `/api/info` endpoints |
|
||||
| **Logging** | Per-request structured logging (method, path, status code) |
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# From the project root
|
||||
cd apps/nexus
|
||||
|
||||
# Build with the bootstrap compiler (Nim)
|
||||
../../buxc build
|
||||
|
||||
# Or with the self-hosted compiler
|
||||
../../buxc_lir build
|
||||
|
||||
# Run
|
||||
./nexus
|
||||
```
|
||||
|
||||
Server starts on `http://0.0.0.0:8080`:
|
||||
|
||||
```
|
||||
╔══════════════════════════════════════════════╗
|
||||
║ Nexus HTTP Server v0.1.0 ║
|
||||
║ High-performance multi-threaded HTTP/1.1 ║
|
||||
║ HTTP/2 & WebSocket detection included ║
|
||||
║ Built with Bux ║
|
||||
╚══════════════════════════════════════════════╝
|
||||
|
||||
✓ Server listening on http://0.0.0.0:8080
|
||||
✓ Worker threads: 4
|
||||
✓ Static files: ./public/
|
||||
|
||||
Endpoints:
|
||||
GET / — Static files (public/)
|
||||
GET /api/health — Health check (JSON)
|
||||
GET /api/info — Server info (JSON)
|
||||
GET /ws — WebSocket upgrade
|
||||
ANY /* — Static file serving
|
||||
```
|
||||
|
||||
## Testing It
|
||||
|
||||
```bash
|
||||
# Home page (HTML)
|
||||
curl http://localhost:8080/
|
||||
|
||||
# Health check
|
||||
curl http://localhost:8080/api/health
|
||||
# → {"status":"ok","server":"Nexus","version":"0.1.0"}
|
||||
|
||||
# Server info
|
||||
curl http://localhost:8080/api/info
|
||||
# → {"name":"Nexus","language":"Bux","features":[...]}
|
||||
|
||||
# Static file (404 page)
|
||||
curl http://localhost:8080/404.html
|
||||
|
||||
# Nonexistent file
|
||||
curl http://localhost:8080/nope
|
||||
# → 404 Not Found
|
||||
|
||||
# WebSocket upgrade attempt
|
||||
curl -H "Upgrade: websocket" \
|
||||
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
|
||||
http://localhost:8080/ws
|
||||
# → 101 Switching Protocols
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Thread Model — Multi-Accept
|
||||
|
||||
Nexus uses the **multi-accept** pattern rather than a traditional thread pool with a work queue:
|
||||
|
||||
```
|
||||
Main Thread Worker 1 Worker 2 Worker 3
|
||||
│ │ │ │
|
||||
├─ socket()/bind()/listen() │ │ │
|
||||
├─ spawn Worker() ──────────► │ │
|
||||
├─ spawn Worker() ─────────────────────────► │
|
||||
├─ spawn Worker() ────────────────────────────────────────►
|
||||
│ │ │ │
|
||||
▼ Worker() ▼ accept() ▼ accept() ▼ accept()
|
||||
accept() loop │ │ │
|
||||
```
|
||||
|
||||
Each worker thread calls `accept()` on the **same** listening socket. The Linux kernel distributes incoming connections across the blocked accept calls — the same mechanism used by nginx and Apache prefork. No mutex, no queue, no context switching between a dispatcher and workers.
|
||||
|
||||
### Request Lifecycle
|
||||
|
||||
```
|
||||
Client connects
|
||||
│
|
||||
▼
|
||||
Net_Accept() → client fd
|
||||
│
|
||||
▼
|
||||
Net_Recv(fd, 8192) → raw bytes
|
||||
│
|
||||
▼
|
||||
ParseRequest()
|
||||
├─ Split headers on \r\n\r\n
|
||||
├─ Parse request line → method, path, version
|
||||
└─ Parse header lines → key-value array
|
||||
│
|
||||
▼
|
||||
Router_Dispatch()
|
||||
├─ /api/* → JSON handlers
|
||||
├─ /ws → WebSocket upgrade
|
||||
├─ Upgrade header → HTTP/2 or WS detection
|
||||
└─ /* → Static file serving
|
||||
│
|
||||
▼
|
||||
BuildResponse() → HTTP/1.1 status line + headers + body
|
||||
│
|
||||
▼
|
||||
Net_Send(fd, response) → bytes to client
|
||||
│
|
||||
▼
|
||||
Net_Close(fd)
|
||||
```
|
||||
|
||||
### Design Decisions
|
||||
|
||||
- **No keep-alive by default.** Each connection is closed after one response. This avoids blocking worker threads on idle clients (Bux doesn't yet expose `SO_RCVTIMEO`).
|
||||
- **Linear header array instead of hash map.** HTTP requests typically carry 5–15 headers. A linear scan over a key-value array is faster than hashing for this N, and avoids the complexity of iterating over Bux's generic `StringMap`.
|
||||
- **Raw extern calls for the string builder.** The stdlib `StringBuilder` wrapper adds a struct indirection. Calling `bux_sb_new` / `bux_sb_append` / `bux_sb_build` directly is simpler and equally safe.
|
||||
- **WebSocket accept key is a placeholder.** A full implementation needs SHA-1 hashing and Base64 encoding. These aren't in Bux's stdlib yet; they can be added as extern C functions when needed.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
apps/nexus/
|
||||
├── bux.toml # Package manifest
|
||||
├── README.md # This file
|
||||
├── src/
|
||||
│ └── Main.bux # The entire server (~640 lines)
|
||||
└── public/
|
||||
├── index.html # Landing page
|
||||
└── 404.html # Error page
|
||||
```
|
||||
|
||||
Everything lives in one file (`src/Main.bux`) by design — it keeps the module graph flat and the build fast. As the server grows, the HTTP parser, router, and handlers can be split into separate modules.
|
||||
|
||||
## Configuration
|
||||
|
||||
Edit the constants at the top of `src/Main.bux`:
|
||||
|
||||
```bux
|
||||
const SERVER_PORT: int = 8080; // Listen port
|
||||
const THREAD_COUNT: int = 4; // Number of worker threads
|
||||
const RECV_BUF_SIZE: int = 8192; // Receive buffer per request
|
||||
const SERVER_NAME: String = "Nexus/0.1.0 (Bux)";
|
||||
const PUBLIC_DIR: String = "public"; // Static files directory
|
||||
```
|
||||
|
||||
## Roadmap
|
||||
|
||||
- [ ] Keep-alive with configurable socket timeout
|
||||
- [ ] Full WebSocket frame read/write (requires SHA-1 + Base64)
|
||||
- [ ] HTTP/2 binary framing layer (HPACK, stream multiplexing)
|
||||
- [ ] SSL/TLS via OpenSSL extern bindings
|
||||
- [ ] Middleware / filter chain
|
||||
- [ ] Request body parsing (JSON, form-encoded, multipart)
|
||||
- [ ] Virtual hosts
|
||||
- [ ] Access logging to file
|
||||
- [ ] Rate limiting
|
||||
|
||||
## License
|
||||
|
||||
Nexus is part of the Bux project. See the root [LICENSE](../../LICENSE) for terms.
|
||||
@@ -0,0 +1 @@
|
||||
EXIT:137
|
||||
@@ -0,0 +1 @@
|
||||
EXIT:124
|
||||
@@ -0,0 +1 @@
|
||||
EXIT:124
|
||||
@@ -0,0 +1 @@
|
||||
EXIT:124
|
||||
@@ -0,0 +1 @@
|
||||
EXIT:124
|
||||
@@ -0,0 +1,2 @@
|
||||
timeout: командата „„../../buxc““ не може да бъде изпълнена: Няма такъв файл или директория
|
||||
EXIT:127
|
||||
@@ -0,0 +1 @@
|
||||
EXIT:137
|
||||
@@ -0,0 +1,8 @@
|
||||
[Package]
|
||||
Name = "nexus"
|
||||
Version = "0.1.0"
|
||||
Type = "bin"
|
||||
Description = "High-performance multi-threaded HTTP/1.1, HTTP/2 and WebSocket server"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>404 — Not Found | Nexus</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #0d1117;
|
||||
color: #c9d1d9;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
h1 { font-size: 4rem; color: #f85149; margin-bottom: 0.5rem; }
|
||||
p { color: #8b949e; font-size: 1.2rem; }
|
||||
.back { margin-top: 2rem; }
|
||||
.back a { color: #58a6ff; text-decoration: none; }
|
||||
.back a:hover { text-decoration: underline; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>404</h1>
|
||||
<p>The requested resource was not found on this server.</p>
|
||||
<p class="back"><a href="/">← Back to Home</a></p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
<html><body>Hello Nexus</body></html>
|
||||
@@ -0,0 +1,21 @@
|
||||
module Config {
|
||||
|
||||
pub struct ServerConfig {
|
||||
bindAddr: String;
|
||||
port: int;
|
||||
workerCount: int;
|
||||
publicDir: String;
|
||||
backlog: int;
|
||||
}
|
||||
|
||||
pub const func DefaultConfig() -> ServerConfig {
|
||||
return ServerConfig {
|
||||
bindAddr: "0.0.0.0",
|
||||
port: 8080,
|
||||
workerCount: 4,
|
||||
publicDir: "public",
|
||||
backlog: 128,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
module Errors {
|
||||
|
||||
import Http::{HttpRequest};
|
||||
|
||||
pub enum HttpError {
|
||||
BadRequest,
|
||||
NotFound,
|
||||
MethodNotAllowed,
|
||||
InternalError(String),
|
||||
}
|
||||
|
||||
pub enum ParseResult {
|
||||
Ok(HttpRequest),
|
||||
Err(HttpError),
|
||||
}
|
||||
|
||||
pub enum FileResult {
|
||||
Ok(String),
|
||||
Err(HttpError),
|
||||
}
|
||||
|
||||
pub func ParseResult_NewOk(req: HttpRequest) -> ParseResult {
|
||||
let r: ParseResult = ParseResult { tag: ParseResult_Ok };
|
||||
r.data.Ok_0 = req;
|
||||
return r;
|
||||
}
|
||||
|
||||
pub func ParseResult_NewErr(err: HttpError) -> ParseResult {
|
||||
let r: ParseResult = ParseResult { tag: ParseResult_Err };
|
||||
r.data.Err_0 = err;
|
||||
return r;
|
||||
}
|
||||
|
||||
pub func FileResult_NewOk(content: String) -> FileResult {
|
||||
let r: FileResult = FileResult { tag: FileResult_Ok };
|
||||
r.data.Ok_0 = content;
|
||||
return r;
|
||||
}
|
||||
|
||||
pub func FileResult_NewErr(err: HttpError) -> FileResult {
|
||||
let r: FileResult = FileResult { tag: FileResult_Err };
|
||||
r.data.Err_0 = err;
|
||||
return r;
|
||||
}
|
||||
|
||||
pub func FileResult_IsOk(r: FileResult) -> bool {
|
||||
return r.tag == FileResult_Ok;
|
||||
}
|
||||
|
||||
pub func FileResult_Unwrap(r: FileResult) -> String {
|
||||
return r.data.Ok_0;
|
||||
}
|
||||
|
||||
pub func FileResult_UnwrapErr(r: FileResult) -> HttpError {
|
||||
return r.data.Err_0;
|
||||
}
|
||||
|
||||
pub func HttpError_ToString(err: HttpError) -> String {
|
||||
if err.tag == HttpError_BadRequest { return "Bad Request"; }
|
||||
if err.tag == HttpError_NotFound { return "Not Found"; }
|
||||
if err.tag == HttpError_MethodNotAllowed { return "Method Not Allowed"; }
|
||||
return err.data.InternalError_0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
module Handlers {
|
||||
|
||||
import Std::String::{String_Eq, String_Contains};
|
||||
import Http::{HttpMethod, HttpRequest, HttpResponse, Http_NewResponse, Http_MimeType, RequestHeader_Get};
|
||||
import Errors::{HttpError, FileResult, FileResult_NewOk, FileResult_NewErr, FileResult_IsOk, FileResult_Unwrap, FileResult_UnwrapErr, HttpError_ToString};
|
||||
|
||||
extern func bux_strlen(s: String) -> uint;
|
||||
extern func bux_file_exists(path: String) -> int;
|
||||
extern func bux_read_file(path: String) -> String;
|
||||
extern func bux_path_join(a: String, b: String) -> String;
|
||||
extern func bux_sb_new(initial_cap: uint) -> *void;
|
||||
extern func bux_sb_append(sb: *void, s: String);
|
||||
extern func bux_sb_append_int(sb: *void, n: int64);
|
||||
extern func bux_sb_build(sb: *void) -> String;
|
||||
extern func bux_sb_free(sb: *void);
|
||||
|
||||
pub func NotFoundResponse() -> HttpResponse {
|
||||
return Http_NewResponse(404, "application/json; charset=utf-8", "{\"error\":\"not_found\"}");
|
||||
}
|
||||
|
||||
pub func MethodNotAllowedResponse() -> HttpResponse {
|
||||
return Http_NewResponse(405, "text/plain; charset=utf-8", "Method Not Allowed");
|
||||
}
|
||||
|
||||
pub func ReadStaticFile(requestPath: String) -> FileResult {
|
||||
if String_Contains(requestPath, "..") {
|
||||
return FileResult_NewErr(HttpError { tag: HttpError_NotFound });
|
||||
}
|
||||
|
||||
var filePath: String = requestPath;
|
||||
if String_Eq(filePath, "/") {
|
||||
filePath = "/index.html";
|
||||
}
|
||||
|
||||
let fullPath: String = bux_path_join("public", filePath);
|
||||
if bux_file_exists(fullPath) == 0 {
|
||||
return FileResult_NewErr(HttpError { tag: HttpError_NotFound });
|
||||
}
|
||||
|
||||
let content: String = bux_read_file(fullPath);
|
||||
return FileResult_NewOk(content);
|
||||
}
|
||||
|
||||
func FileErrorResponse(err: HttpError) -> HttpResponse {
|
||||
match err {
|
||||
HttpError::NotFound => NotFoundResponse(),
|
||||
_ => Http_NewResponse(500, "text/plain; charset=utf-8", HttpError_ToString(err)),
|
||||
}
|
||||
}
|
||||
|
||||
pub func ServeStaticFile(req: HttpRequest) -> HttpResponse {
|
||||
if req.method != HttpMethod_GET && req.method != HttpMethod_HEAD {
|
||||
return MethodNotAllowedResponse();
|
||||
}
|
||||
|
||||
let result: FileResult = ReadStaticFile(req.path);
|
||||
if FileResult_IsOk(result) {
|
||||
let content: String = FileResult_Unwrap(result);
|
||||
return Http_NewResponse(200, Http_MimeType(req.path), content);
|
||||
}
|
||||
return FileErrorResponse(FileResult_UnwrapErr(result));
|
||||
}
|
||||
|
||||
pub func HandleApiHealth() -> HttpResponse {
|
||||
return Http_NewResponse(200, "application/json; charset=utf-8",
|
||||
"{\"status\":\"ok\",\"server\":\"Nexus\",\"version\":\"0.2.0\"}");
|
||||
}
|
||||
|
||||
pub func HandleApiInfo() -> HttpResponse {
|
||||
return Http_NewResponse(200, "application/json; charset=utf-8",
|
||||
"{\"name\":\"Nexus\",\"language\":\"Bux\",\"features\":[\"HTTP/1.1\",\"thread-pool\",\"algebraic-enums\"]}");
|
||||
}
|
||||
|
||||
pub func HandleWebSocketUpgrade(req: HttpRequest) -> HttpResponse {
|
||||
let wsKey: String = RequestHeader_Get(req, "Sec-WebSocket-Key");
|
||||
|
||||
var resp: HttpResponse;
|
||||
resp.statusCode = 101;
|
||||
resp.contentType = "";
|
||||
resp.body = "";
|
||||
|
||||
let sb: *void = bux_sb_new(256);
|
||||
bux_sb_append(sb, "Upgrade: websocket\r\n");
|
||||
bux_sb_append(sb, "Connection: Upgrade\r\n");
|
||||
bux_sb_append(sb, "Sec-WebSocket-Accept: ");
|
||||
bux_sb_append(sb, wsKey);
|
||||
bux_sb_append(sb, "\r\n");
|
||||
resp.extraHeaders = bux_sb_build(sb);
|
||||
bux_sb_free(sb);
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
module Http {
|
||||
|
||||
import Std::Array::{Array};
|
||||
import Std::String::{String_Eq, String_EndsWith, String_Contains};
|
||||
|
||||
pub enum HttpMethod {
|
||||
GET,
|
||||
POST,
|
||||
PUT,
|
||||
DELETE,
|
||||
PATCH,
|
||||
HEAD,
|
||||
OPTIONS,
|
||||
UNKNOWN,
|
||||
}
|
||||
|
||||
pub struct HeaderEntry {
|
||||
key: String;
|
||||
value: String;
|
||||
}
|
||||
|
||||
pub struct HttpRequest {
|
||||
method: HttpMethod;
|
||||
path: String;
|
||||
version: String;
|
||||
body: String;
|
||||
headers: Array<HeaderEntry>;
|
||||
}
|
||||
|
||||
pub struct HttpResponse {
|
||||
statusCode: int;
|
||||
contentType: String;
|
||||
body: String;
|
||||
extraHeaders: String;
|
||||
}
|
||||
|
||||
pub func Http_StatusText(code: int) -> String {
|
||||
if code == 200 { return "OK"; }
|
||||
if code == 201 { return "Created"; }
|
||||
if code == 204 { return "No Content"; }
|
||||
if code == 301 { return "Moved Permanently"; }
|
||||
if code == 302 { return "Found"; }
|
||||
if code == 304 { return "Not Modified"; }
|
||||
if code == 400 { return "Bad Request"; }
|
||||
if code == 401 { return "Unauthorized"; }
|
||||
if code == 403 { return "Forbidden"; }
|
||||
if code == 404 { return "Not Found"; }
|
||||
if code == 405 { return "Method Not Allowed"; }
|
||||
if code == 413 { return "Payload Too Large"; }
|
||||
if code == 414 { return "URI Too Long"; }
|
||||
if code == 500 { return "Internal Server Error"; }
|
||||
if code == 501 { return "Not Implemented"; }
|
||||
if code == 503 { return "Service Unavailable"; }
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
pub func Http_MimeType(path: String) -> String {
|
||||
if String_EndsWith(path, ".html") || String_EndsWith(path, ".htm") { return "text/html; charset=utf-8"; }
|
||||
if String_EndsWith(path, ".css") { return "text/css; charset=utf-8"; }
|
||||
if String_EndsWith(path, ".js") { return "application/javascript; charset=utf-8"; }
|
||||
if String_EndsWith(path, ".json") { return "application/json; charset=utf-8"; }
|
||||
if String_EndsWith(path, ".xml") { return "application/xml; charset=utf-8"; }
|
||||
if String_EndsWith(path, ".txt") { return "text/plain; charset=utf-8"; }
|
||||
if String_EndsWith(path, ".png") { return "image/png"; }
|
||||
if String_EndsWith(path, ".jpg") || String_EndsWith(path, ".jpeg") { return "image/jpeg"; }
|
||||
if String_EndsWith(path, ".gif") { return "image/gif"; }
|
||||
if String_EndsWith(path, ".svg") { return "image/svg+xml"; }
|
||||
if String_EndsWith(path, ".ico") { return "image/x-icon"; }
|
||||
if String_EndsWith(path, ".webp") { return "image/webp"; }
|
||||
if String_EndsWith(path, ".woff2") { return "font/woff2"; }
|
||||
if String_EndsWith(path, ".woff") { return "font/woff"; }
|
||||
if String_EndsWith(path, ".wasm") { return "application/wasm"; }
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
pub func Http_MethodName(m: HttpMethod) -> String {
|
||||
match m {
|
||||
HttpMethod::GET => "GET",
|
||||
HttpMethod::POST => "POST",
|
||||
HttpMethod::PUT => "PUT",
|
||||
HttpMethod::DELETE => "DELETE",
|
||||
HttpMethod::PATCH => "PATCH",
|
||||
HttpMethod::HEAD => "HEAD",
|
||||
HttpMethod::OPTIONS => "OPTIONS",
|
||||
HttpMethod::UNKNOWN => "UNKNOWN",
|
||||
}
|
||||
}
|
||||
|
||||
pub func Http_NewResponse(code: int, contentType: String, body: String) -> HttpResponse {
|
||||
var resp: HttpResponse;
|
||||
resp.statusCode = code;
|
||||
resp.contentType = contentType;
|
||||
resp.body = body;
|
||||
resp.extraHeaders = "";
|
||||
return resp;
|
||||
}
|
||||
|
||||
pub func RequestHeader_Get(req: HttpRequest, key: String) -> String {
|
||||
for entry in req.headers {
|
||||
if String_Eq(entry.key, key) {
|
||||
return entry.value;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
module Main {
|
||||
|
||||
import Config::{ServerConfig, DefaultConfig};
|
||||
import Http::{HttpMethod};
|
||||
import Router::{Handler, Route, Router};
|
||||
import Server::{RunServer};
|
||||
import Std::Array::{Array, Array_New, Array_Push};
|
||||
|
||||
func BuildRouter() -> Router {
|
||||
var routes: Array<Route> = Array_New<Route>(8);
|
||||
|
||||
Array_Push<Route>(&routes, Route {
|
||||
method: HttpMethod { tag: HttpMethod_GET },
|
||||
path: "/api/health",
|
||||
handler: Handler { tag: Handler_ApiHealth },
|
||||
});
|
||||
|
||||
Array_Push<Route>(&routes, Route {
|
||||
method: HttpMethod { tag: HttpMethod_GET },
|
||||
path: "/api/info",
|
||||
handler: Handler { tag: Handler_ApiInfo },
|
||||
});
|
||||
|
||||
Array_Push<Route>(&routes, Route {
|
||||
method: HttpMethod { tag: HttpMethod_GET },
|
||||
path: "/ws",
|
||||
handler: Handler { tag: Handler_WsUpgrade },
|
||||
});
|
||||
|
||||
Array_Push<Route>(&routes, Route {
|
||||
method: HttpMethod { tag: HttpMethod_GET },
|
||||
path: "/",
|
||||
handler: Handler { tag: Handler_StaticFile },
|
||||
});
|
||||
|
||||
return Router {
|
||||
routes: routes,
|
||||
notFound: Handler { tag: Handler_NotFound },
|
||||
};
|
||||
}
|
||||
|
||||
func Main() -> int {
|
||||
let config: ServerConfig = DefaultConfig();
|
||||
let router: Router = BuildRouter();
|
||||
|
||||
return RunServer(config, router);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
module Parser {
|
||||
|
||||
import Std::String::{String_Len, String_Eq, String_Trim};
|
||||
import Std::Array::{Array, Array_New, Array_Push};
|
||||
import Http::{HttpMethod, HttpRequest, HeaderEntry};
|
||||
import Errors::{HttpError, ParseResult, ParseResult_NewOk, ParseResult_NewErr};
|
||||
|
||||
extern func bux_strlen(s: String) -> uint;
|
||||
extern func bux_strstr(haystack: String, needle: String) -> String;
|
||||
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
|
||||
extern func bux_str_offset(pos: String, base: String) -> uint;
|
||||
|
||||
pub func ParseMethod(s: String) -> HttpMethod {
|
||||
if String_Eq(s, "GET") { return HttpMethod { tag: HttpMethod_GET }; }
|
||||
if String_Eq(s, "POST") { return HttpMethod { tag: HttpMethod_POST }; }
|
||||
if String_Eq(s, "PUT") { return HttpMethod { tag: HttpMethod_PUT }; }
|
||||
if String_Eq(s, "DELETE") { return HttpMethod { tag: HttpMethod_DELETE }; }
|
||||
if String_Eq(s, "PATCH") { return HttpMethod { tag: HttpMethod_PATCH }; }
|
||||
if String_Eq(s, "HEAD") { return HttpMethod { tag: HttpMethod_HEAD }; }
|
||||
if String_Eq(s, "OPTIONS") { return HttpMethod { tag: HttpMethod_OPTIONS }; }
|
||||
return HttpMethod { tag: HttpMethod_UNKNOWN };
|
||||
}
|
||||
|
||||
func Slice(raw: String, start: int, len: int) -> String {
|
||||
if start < 0 || len <= 0 { return ""; }
|
||||
return bux_str_slice(raw, start as uint, len as uint);
|
||||
}
|
||||
|
||||
func FindCrlf(raw: String, start: int) -> int {
|
||||
let rawLen: uint = bux_strlen(raw);
|
||||
if start as uint >= rawLen { return -1; }
|
||||
let tail: String = bux_str_slice(raw, start as uint, rawLen - start as uint);
|
||||
let hit: String = bux_strstr(tail, "\r\n");
|
||||
if String_Len(hit) == 0 { return -1; }
|
||||
let offset: uint = bux_str_offset(hit, tail);
|
||||
return start + offset as int;
|
||||
}
|
||||
|
||||
func ParseHeaders(raw: String, start: int, end: int) -> Array<HeaderEntry> {
|
||||
var headers: Array<HeaderEntry> = Array_New<HeaderEntry>(16);
|
||||
var pos: int = start;
|
||||
while pos < end {
|
||||
let lineEnd: int = FindCrlf(raw, pos);
|
||||
if lineEnd < 0 || lineEnd >= end { break; }
|
||||
let lineLen: int = lineEnd - pos;
|
||||
if lineLen > 0 {
|
||||
let line: String = Slice(raw, pos, lineLen);
|
||||
let colon: String = bux_strstr(line, ":");
|
||||
if String_Len(colon) > 0 {
|
||||
let keyLen: uint = bux_str_offset(colon, line);
|
||||
let key: String = String_Trim(Slice(line, 0, keyLen as int));
|
||||
let valStart: int = keyLen as int + 1;
|
||||
let val: String = String_Trim(Slice(line, valStart, lineLen - valStart));
|
||||
let entry: HeaderEntry = HeaderEntry { key: key, value: val };
|
||||
Array_Push<HeaderEntry>(&headers, entry);
|
||||
}
|
||||
}
|
||||
pos = lineEnd + 2;
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
pub func ParseRequest(raw: String) -> ParseResult {
|
||||
let rawLen: uint = bux_strlen(raw);
|
||||
if rawLen == 0 {
|
||||
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
|
||||
}
|
||||
|
||||
// Find end of request line
|
||||
let lineEnd: int = FindCrlf(raw, 0);
|
||||
if lineEnd < 0 {
|
||||
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
|
||||
}
|
||||
|
||||
// Split request line: METHOD PATH VERSION
|
||||
var methodEnd: int = -1;
|
||||
var pathStart: int = -1;
|
||||
var pathEnd: int = -1;
|
||||
var i: int = 0;
|
||||
while i < lineEnd {
|
||||
if raw[i] as int == 32 { // space
|
||||
if methodEnd < 0 {
|
||||
methodEnd = i;
|
||||
pathStart = i + 1;
|
||||
} else if pathEnd < 0 {
|
||||
pathEnd = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
if methodEnd < 0 || pathStart < 0 || pathEnd < 0 {
|
||||
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
|
||||
}
|
||||
|
||||
let methodStr: String = Slice(raw, 0, methodEnd);
|
||||
let path: String = Slice(raw, pathStart, pathEnd - pathStart);
|
||||
let version: String = Slice(raw, pathEnd + 1, lineEnd - pathEnd - 1);
|
||||
|
||||
// Find header/body boundary
|
||||
let boundary: String = bux_strstr(raw, "\r\n\r\n");
|
||||
var headers: Array<HeaderEntry> = Array_New<HeaderEntry>(16);
|
||||
var body: String = "";
|
||||
if String_Len(boundary) > 0 {
|
||||
let headerEnd: uint = bux_str_offset(boundary, raw);
|
||||
headers = ParseHeaders(raw, lineEnd + 2, headerEnd as int);
|
||||
let bodyStart: uint = headerEnd + 4;
|
||||
if bodyStart < rawLen {
|
||||
body = bux_str_slice(raw, bodyStart, rawLen - bodyStart);
|
||||
}
|
||||
} else {
|
||||
headers = ParseHeaders(raw, lineEnd + 2, rawLen as int);
|
||||
}
|
||||
|
||||
if String_Eq(path, "") {
|
||||
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
|
||||
}
|
||||
|
||||
let req: HttpRequest = HttpRequest {
|
||||
method: ParseMethod(methodStr),
|
||||
path: path,
|
||||
version: version,
|
||||
body: body,
|
||||
headers: headers,
|
||||
};
|
||||
return ParseResult_NewOk(req);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
module Router {
|
||||
|
||||
import Std::Array::{Array};
|
||||
import Std::String::{String_Eq};
|
||||
import Http::{HttpMethod, HttpRequest, HttpResponse, Http_NewResponse};
|
||||
import Handlers::{ServeStaticFile, HandleApiHealth, HandleApiInfo, HandleWebSocketUpgrade, NotFoundResponse};
|
||||
|
||||
pub enum Handler {
|
||||
StaticFile,
|
||||
ApiHealth,
|
||||
ApiInfo,
|
||||
WsUpgrade,
|
||||
NotFound,
|
||||
}
|
||||
|
||||
pub struct Route {
|
||||
method: HttpMethod;
|
||||
path: String;
|
||||
handler: Handler;
|
||||
}
|
||||
|
||||
pub struct Router {
|
||||
routes: Array<Route>;
|
||||
notFound: Handler;
|
||||
}
|
||||
|
||||
pub func Handler_Handle(h: Handler, req: HttpRequest) -> HttpResponse {
|
||||
match h {
|
||||
Handler::StaticFile => ServeStaticFile(req),
|
||||
Handler::ApiHealth => HandleApiHealth(),
|
||||
Handler::ApiInfo => HandleApiInfo(),
|
||||
Handler::WsUpgrade => HandleWebSocketUpgrade(req),
|
||||
Handler::NotFound => NotFoundResponse(),
|
||||
}
|
||||
}
|
||||
|
||||
pub func Router_Dispatch(r: Router, req: HttpRequest) -> HttpResponse {
|
||||
for route in r.routes {
|
||||
if route.method == req.method && String_Eq(route.path, req.path) {
|
||||
return Handler_Handle(route.handler, req);
|
||||
}
|
||||
}
|
||||
return Handler_Handle(r.notFound, req);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
module Server {
|
||||
|
||||
import Std::Io::{Print, PrintLine, PrintInt};
|
||||
import Std::Net::{Net_Create, Net_SetReuse, Net_Bind, Net_Listen, Net_Accept, Net_Send, Net_Recv, Net_Close, Net_LastError};
|
||||
import Std::String::{String_Len, String_StartsWith};
|
||||
import Std::Channel::{Channel, Channel_New, Channel_Send, Channel_Recv};
|
||||
import Config::{ServerConfig};
|
||||
import Http::{HttpRequest, HttpResponse, Http_StatusText, Http_NewResponse};
|
||||
import Errors::{ParseResult};
|
||||
import Parser::{ParseRequest};
|
||||
import Router::{Router, Router_Dispatch};
|
||||
|
||||
extern func bux_strlen(s: String) -> uint;
|
||||
extern func bux_sb_new(initial_cap: uint) -> *void;
|
||||
extern func bux_sb_append(sb: *void, s: String);
|
||||
extern func bux_sb_append_int(sb: *void, n: int64);
|
||||
extern func bux_sb_build(sb: *void) -> String;
|
||||
extern func bux_sb_free(sb: *void);
|
||||
|
||||
pub struct ConnectionTask {
|
||||
fd: int;
|
||||
}
|
||||
|
||||
pub func BuildResponse(resp: HttpResponse) -> String {
|
||||
let sb: *void = bux_sb_new(4096);
|
||||
|
||||
bux_sb_append(sb, "HTTP/1.1 ");
|
||||
bux_sb_append_int(sb, resp.statusCode as int64);
|
||||
bux_sb_append(sb, " ");
|
||||
bux_sb_append(sb, Http_StatusText(resp.statusCode));
|
||||
bux_sb_append(sb, "\r\n");
|
||||
|
||||
bux_sb_append(sb, "Server: Nexus/0.2.0 (Bux)\r\n");
|
||||
|
||||
if bux_strlen(resp.extraHeaders) > 0 {
|
||||
bux_sb_append(sb, resp.extraHeaders);
|
||||
}
|
||||
|
||||
if bux_strlen(resp.contentType) > 0 {
|
||||
bux_sb_append(sb, "Content-Type: ");
|
||||
bux_sb_append(sb, resp.contentType);
|
||||
bux_sb_append(sb, "\r\n");
|
||||
}
|
||||
|
||||
let bodyLen: uint = bux_strlen(resp.body);
|
||||
bux_sb_append(sb, "Content-Length: ");
|
||||
bux_sb_append_int(sb, bodyLen as int64);
|
||||
bux_sb_append(sb, "\r\n");
|
||||
bux_sb_append(sb, "Connection: close\r\n");
|
||||
bux_sb_append(sb, "\r\n");
|
||||
|
||||
if bodyLen > 0 {
|
||||
bux_sb_append(sb, resp.body);
|
||||
}
|
||||
|
||||
let result: String = bux_sb_build(sb);
|
||||
bux_sb_free(sb);
|
||||
return result;
|
||||
}
|
||||
|
||||
pub func HandleConnection(fd: int, router: Router) {
|
||||
let raw: String = Net_Recv(fd, 8192);
|
||||
if String_Len(raw) == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// HTTP/2 preface detection
|
||||
if String_StartsWith(raw, "PRI * HTTP/2.0") {
|
||||
let resp: HttpResponse = Http_NewResponse(200, "text/plain; charset=utf-8",
|
||||
"HTTP/2 detected — full support planned for future release.\r\n");
|
||||
Net_Send(fd, BuildResponse(resp));
|
||||
return;
|
||||
}
|
||||
|
||||
let parsed: ParseResult = ParseRequest(raw);
|
||||
if parsed.tag == ParseResult_Ok {
|
||||
let req: HttpRequest = parsed.data.Ok_0;
|
||||
let resp: HttpResponse = Router_Dispatch(router, req);
|
||||
Net_Send(fd, BuildResponse(resp));
|
||||
} else {
|
||||
let resp: HttpResponse = Http_NewResponse(400, "text/plain; charset=utf-8", "Bad Request");
|
||||
Net_Send(fd, BuildResponse(resp));
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WorkerCtx {
|
||||
taskQueue: *Channel<ConnectionTask>;
|
||||
router: Router;
|
||||
}
|
||||
|
||||
pub func Worker(ctx: *WorkerCtx) {
|
||||
while true {
|
||||
let task: ConnectionTask = Channel_Recv<ConnectionTask>(ctx.taskQueue);
|
||||
HandleConnection(task.fd, ctx.router);
|
||||
Net_Close(task.fd);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AcceptorCtx {
|
||||
serverFd: int;
|
||||
taskQueue: *Channel<ConnectionTask>;
|
||||
}
|
||||
|
||||
pub func Acceptor(ctx: *AcceptorCtx) {
|
||||
while true {
|
||||
let fd: int = Net_Accept(ctx.serverFd);
|
||||
if fd >= 0 {
|
||||
let task: ConnectionTask = ConnectionTask { fd: fd };
|
||||
Channel_Send<ConnectionTask>(ctx.taskQueue, task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub func RunServer(config: ServerConfig, router: Router) -> int {
|
||||
PrintLine("================================================");
|
||||
PrintLine(" Nexus HTTP Server v0.2.0");
|
||||
PrintLine(" Production-ready HTTP/1.1 with thread-pool");
|
||||
PrintLine(" Built with Bux");
|
||||
PrintLine("================================================");
|
||||
PrintLine("");
|
||||
|
||||
let serverFd: int = Net_Create();
|
||||
if serverFd < 0 {
|
||||
PrintLine("FATAL: socket() failed");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if !Net_SetReuse(serverFd) {
|
||||
PrintLine("WARN: SO_REUSEADDR failed");
|
||||
}
|
||||
|
||||
if !Net_Bind(serverFd, config.bindAddr, config.port) {
|
||||
Print("FATAL: bind failed: ");
|
||||
PrintLine(Net_LastError());
|
||||
Net_Close(serverFd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if !Net_Listen(serverFd, config.backlog) {
|
||||
PrintLine("FATAL: listen() failed");
|
||||
Net_Close(serverFd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Print("Listening on http://");
|
||||
Print(config.bindAddr);
|
||||
Print(":");
|
||||
PrintInt(config.port);
|
||||
PrintLine("");
|
||||
PrintInt(config.workerCount);
|
||||
PrintLine(" worker threads | static: ./public/");
|
||||
PrintLine("Endpoints: / /api/health /api/info /ws");
|
||||
PrintLine("Press Ctrl+C to stop.");
|
||||
PrintLine("");
|
||||
|
||||
let taskQueue: Channel<ConnectionTask> = Channel_New<ConnectionTask>(config.backlog as int64);
|
||||
let workerCtx: WorkerCtx = WorkerCtx { taskQueue: &taskQueue, router: router };
|
||||
let acceptorCtx: AcceptorCtx = AcceptorCtx { serverFd: serverFd, taskQueue: &taskQueue };
|
||||
|
||||
// Spawn workers (main thread will also become one)
|
||||
var i: int = 0;
|
||||
while i < config.workerCount - 1 {
|
||||
spawn Worker(&workerCtx);
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
// Spawn acceptor
|
||||
spawn Acceptor(&acceptorCtx);
|
||||
|
||||
// Main thread works too
|
||||
Worker(&workerCtx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
bux 0.1.0 (bootstrap)
|
||||
EXIT:0
|
||||
@@ -0,0 +1,77 @@
|
||||
# SimpleDB
|
||||
|
||||
File-backed key-value database for Bux.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
simpledb <dbfile> set <key> <value>
|
||||
simpledb <dbfile> get <key>
|
||||
simpledb <dbfile> del <key>
|
||||
simpledb <dbfile> has <key>
|
||||
simpledb <dbfile> keys
|
||||
simpledb <dbfile> count
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
```sh
|
||||
$ simpledb data.db set name BuxLang
|
||||
OK name = BuxLang
|
||||
|
||||
$ simpledb data.db set version 0.1.0
|
||||
OK version = 0.1.0
|
||||
|
||||
$ simpledb data.db get name
|
||||
BuxLang
|
||||
|
||||
$ simpledb data.db has name
|
||||
true name
|
||||
|
||||
$ simpledb data.db keys
|
||||
keys: 2
|
||||
name
|
||||
version
|
||||
|
||||
$ simpledb data.db count
|
||||
count: 2
|
||||
|
||||
$ simpledb data.db del version
|
||||
DEL version
|
||||
|
||||
$ cat data.db
|
||||
name=BuxLang
|
||||
```
|
||||
|
||||
## Storage
|
||||
|
||||
Data is stored as plain text, one `key=value` per line. The file is created automatically on first write.
|
||||
|
||||
## Build
|
||||
|
||||
```sh
|
||||
# workaround: disable broken JWT module
|
||||
mv ../../lib/crypto/jwt.bux ../../lib/crypto/jwt.bux.bak
|
||||
../../buxc build
|
||||
mv ../../lib/crypto/jwt.bux.bak ../../lib/crypto/jwt.bux
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
The `Database` struct and its functions are defined in `src/Main.bux` and can be imported into other Bux projects:
|
||||
|
||||
```bux
|
||||
import Simpledb::{Database, DB_New, DB_Load, DB_Save, DB_Get, DB_Set, DB_Del, DB_Has, DB_Count, DB_Keys};
|
||||
```
|
||||
|
||||
| Function | Signature |
|
||||
|----------|-----------|
|
||||
| `DB_New` | `(path: String) -> Database` |
|
||||
| `DB_Load` | `(db: *Database) -> bool` |
|
||||
| `DB_Save` | `(db: *Database) -> bool` |
|
||||
| `DB_Get` | `(db: *Database, key: String) -> String` |
|
||||
| `DB_Set` | `(db: *Database, key: String, value: String)` |
|
||||
| `DB_Del` | `(db: *Database, key: String) -> bool` |
|
||||
| `DB_Has` | `(db: *Database, key: String) -> bool` |
|
||||
| `DB_Count` | `(db: *Database) -> uint` |
|
||||
| `DB_Keys` | `(db: *Database) -> *String` |
|
||||
@@ -0,0 +1,8 @@
|
||||
[Package]
|
||||
Name = "simpledb"
|
||||
Version = "0.1.0"
|
||||
Type = "bin"
|
||||
Description = "SimpleDB — file-backed JSON key-value database for Bux"
|
||||
|
||||
[Build]
|
||||
Output = "Bin"
|
||||
@@ -0,0 +1,339 @@
|
||||
module Main {
|
||||
|
||||
import Std::Io::{PrintLine, Print, PrintInt, ReadFile, WriteFile, FileExists};
|
||||
import Std::Os::{Os_ArgsCount, Os_Args};
|
||||
import Std::String::{String_Len, String_Eq, String_StartsWith, String_Find, String_Slice, String_Offset};
|
||||
|
||||
extern func bux_strlen(s: String) -> uint;
|
||||
extern func bux_strstr(haystack: String, needle: String) -> String;
|
||||
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
|
||||
extern func bux_str_split_count(s: String, delim: String) -> uint;
|
||||
extern func bux_str_split_part(s: String, delim: String, index: uint) -> String;
|
||||
extern func bux_sb_new(initial_cap: uint) -> *void;
|
||||
extern func bux_sb_append(sb: *void, s: String);
|
||||
extern func bux_sb_append_char(sb: *void, c: char8);
|
||||
|
||||
func SB_AppendChar(sb: *void, c: int) {
|
||||
bux_sb_append_char(sb, c as char8);
|
||||
}
|
||||
extern func bux_sb_build(sb: *void) -> String;
|
||||
extern func bux_sb_free(sb: *void);
|
||||
extern func bux_alloc(size: uint) -> *void;
|
||||
extern func bux_free(ptr: *void);
|
||||
|
||||
struct Database {
|
||||
path: String,
|
||||
sb: *void,
|
||||
loaded: bool,
|
||||
count: uint,
|
||||
}
|
||||
|
||||
func DB_New(filePath: String) -> Database {
|
||||
var db: Database;
|
||||
db.path = filePath;
|
||||
db.sb = bux_sb_new(4096);
|
||||
db.loaded = false;
|
||||
db.count = 0;
|
||||
return db;
|
||||
}
|
||||
|
||||
func DB_Load(self: *Database) -> bool {
|
||||
if self.loaded { return true; }
|
||||
|
||||
if !FileExists(self.path) {
|
||||
self.loaded = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
let raw: String = ReadFile(self.path);
|
||||
if String_Len(raw) == 0 || bux_strlen(raw) == 0 {
|
||||
self.loaded = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bux_sb_append(self.sb, raw);
|
||||
|
||||
let lineCount: uint = bux_str_split_count(raw, "\n");
|
||||
var i: uint = 0;
|
||||
var n: uint = 0;
|
||||
while i < lineCount {
|
||||
let line: String = bux_str_split_part(raw, "\n", i);
|
||||
if bux_strlen(line) > 0 {
|
||||
n = n + 1;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
self.count = n;
|
||||
self.loaded = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
func DB_Get(self: *Database, key: String) -> String {
|
||||
let raw: String = bux_sb_build(self.sb);
|
||||
let lineCount: uint = bux_str_split_count(raw, "\n");
|
||||
var i: uint = 0;
|
||||
while i < lineCount {
|
||||
let line: String = bux_str_split_part(raw, "\n", i);
|
||||
if String_StartsWith(line, key) {
|
||||
let eqPos: String = String_Find(line, "=");
|
||||
if String_Len(eqPos) > 0 {
|
||||
let keyLen: uint = bux_strlen(key);
|
||||
let valStart: uint = keyLen + 1;
|
||||
let lineLen: uint = bux_strlen(line);
|
||||
if valStart < lineLen {
|
||||
return bux_str_slice(line, valStart, lineLen - valStart);
|
||||
}
|
||||
}
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
func DB_Set(self: *Database, key: String, value: String) {
|
||||
let raw: String = bux_sb_build(self.sb);
|
||||
let lineCount: uint = bux_str_split_count(raw, "\n");
|
||||
|
||||
var newSb: *void = bux_sb_new(4096);
|
||||
var found: bool = false;
|
||||
var i: uint = 0;
|
||||
while i < lineCount {
|
||||
let line: String = bux_str_split_part(raw, "\n", i);
|
||||
if bux_strlen(line) > 0 {
|
||||
if String_StartsWith(line, key) {
|
||||
let eqPos: String = String_Find(line, "=");
|
||||
if String_Len(eqPos) > 0 {
|
||||
bux_sb_append(newSb, key);
|
||||
SB_AppendChar(newSb, 61);
|
||||
bux_sb_append(newSb, value);
|
||||
SB_AppendChar(newSb, 10);
|
||||
found = true;
|
||||
} else {
|
||||
bux_sb_append(newSb, line);
|
||||
SB_AppendChar(newSb, 10);
|
||||
}
|
||||
} else {
|
||||
bux_sb_append(newSb, line);
|
||||
SB_AppendChar(newSb, 10);
|
||||
}
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
if !found {
|
||||
bux_sb_append(newSb, key);
|
||||
SB_AppendChar(newSb, 61);
|
||||
bux_sb_append(newSb, value);
|
||||
SB_AppendChar(newSb, 10);
|
||||
self.count = self.count + 1;
|
||||
}
|
||||
|
||||
bux_sb_free(self.sb);
|
||||
self.sb = newSb;
|
||||
}
|
||||
|
||||
func DB_Del(self: *Database, key: String) {
|
||||
let raw: String = bux_sb_build(self.sb);
|
||||
let lineCount: uint = bux_str_split_count(raw, "\n");
|
||||
|
||||
var newSb: *void = bux_sb_new(4096);
|
||||
var i: uint = 0;
|
||||
var delCount: uint = 0;
|
||||
while i < lineCount {
|
||||
let line: String = bux_str_split_part(raw, "\n", i);
|
||||
if bux_strlen(line) > 0 {
|
||||
if String_StartsWith(line, key) {
|
||||
let eqPos: String = String_Find(line, "=");
|
||||
if String_Len(eqPos) > 0 {
|
||||
delCount = delCount + 1;
|
||||
} else {
|
||||
bux_sb_append(newSb, line);
|
||||
SB_AppendChar(newSb, 10);
|
||||
}
|
||||
} else {
|
||||
bux_sb_append(newSb, line);
|
||||
SB_AppendChar(newSb, 10);
|
||||
}
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
if delCount > 0 {
|
||||
self.count = self.count - delCount;
|
||||
}
|
||||
|
||||
bux_sb_free(self.sb);
|
||||
self.sb = newSb;
|
||||
}
|
||||
|
||||
func DB_Has(self: *Database, key: String) -> bool {
|
||||
let val: String = DB_Get(self, key);
|
||||
return bux_strlen(val) > 0;
|
||||
}
|
||||
|
||||
func DB_Count(self: *Database) -> uint {
|
||||
return self.count;
|
||||
}
|
||||
|
||||
func DB_Keys(self: *Database) -> *String {
|
||||
if self.count == 0 { return null as *String; }
|
||||
|
||||
let raw: String = bux_sb_build(self.sb);
|
||||
let lineCount: uint = bux_str_split_count(raw, "\n");
|
||||
let keys: *String = bux_alloc(self.count * sizeof(String)) as *String;
|
||||
|
||||
var found: uint = 0;
|
||||
var i: uint = 0;
|
||||
while i < lineCount && found < self.count {
|
||||
let line: String = bux_str_split_part(raw, "\n", i);
|
||||
if bux_strlen(line) > 0 {
|
||||
let eqPos: String = String_Find(line, "=");
|
||||
if String_Len(eqPos) > 0 {
|
||||
let keyLen: uint = String_Offset(eqPos, line);
|
||||
keys[found] = bux_str_slice(line, 0, keyLen);
|
||||
found = found + 1;
|
||||
}
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
func DB_Save(self: *Database) -> bool {
|
||||
let content: String = bux_sb_build(self.sb);
|
||||
let ok: bool = WriteFile(self.path, content);
|
||||
return ok;
|
||||
}
|
||||
|
||||
func PrintUsage() {
|
||||
PrintLine("SimpleDB — file-backed key-value database");
|
||||
PrintLine("Usage:");
|
||||
PrintLine(" simpledb <dbfile> set <key> <value>");
|
||||
PrintLine(" simpledb <dbfile> get <key>");
|
||||
PrintLine(" simpledb <dbfile> del <key>");
|
||||
PrintLine(" simpledb <dbfile> has <key>");
|
||||
PrintLine(" simpledb <dbfile> keys");
|
||||
PrintLine(" simpledb <dbfile> count");
|
||||
}
|
||||
|
||||
func Main() -> int {
|
||||
let argc: int = Os_ArgsCount();
|
||||
|
||||
if argc < 3 {
|
||||
PrintUsage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
let dbFile: String = Os_Args(1);
|
||||
let cmd: String = Os_Args(2);
|
||||
|
||||
var db: Database = DB_New(dbFile);
|
||||
|
||||
if String_Eq(cmd, "get") {
|
||||
DB_Load(&db);
|
||||
if argc < 4 {
|
||||
PrintLine("get: missing key");
|
||||
return 1;
|
||||
}
|
||||
let key: String = Os_Args(3);
|
||||
let val: String = DB_Get(&db, key);
|
||||
if bux_strlen(val) > 0 {
|
||||
PrintLine(val);
|
||||
} else {
|
||||
Print("(not found) ");
|
||||
PrintLine(key);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if String_Eq(cmd, "set") {
|
||||
DB_Load(&db);
|
||||
if argc < 5 {
|
||||
PrintLine("set: missing key or value");
|
||||
return 1;
|
||||
}
|
||||
let key: String = Os_Args(3);
|
||||
let val: String = Os_Args(4);
|
||||
DB_Set(&db, key, val);
|
||||
let saved: bool = DB_Save(&db);
|
||||
if saved {
|
||||
Print("OK ");
|
||||
Print(key);
|
||||
Print(" = ");
|
||||
PrintLine(val);
|
||||
} else {
|
||||
PrintLine("set: save failed");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if String_Eq(cmd, "del") {
|
||||
DB_Load(&db);
|
||||
if argc < 4 {
|
||||
PrintLine("del: missing key");
|
||||
return 1;
|
||||
}
|
||||
let key: String = Os_Args(3);
|
||||
DB_Del(&db, key);
|
||||
DB_Save(&db);
|
||||
Print("DEL ");
|
||||
PrintLine(key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if String_Eq(cmd, "has") {
|
||||
DB_Load(&db);
|
||||
if argc < 4 {
|
||||
PrintLine("has: missing key");
|
||||
return 1;
|
||||
}
|
||||
let key: String = Os_Args(3);
|
||||
let found: bool = DB_Has(&db, key);
|
||||
if found {
|
||||
Print("true ");
|
||||
PrintLine(key);
|
||||
} else {
|
||||
Print("false ");
|
||||
PrintLine(key);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if String_Eq(cmd, "keys") {
|
||||
DB_Load(&db);
|
||||
let n: uint = DB_Count(&db);
|
||||
Print("keys: ");
|
||||
PrintInt(n as int);
|
||||
PrintLine("");
|
||||
|
||||
if n == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let list: *String = DB_Keys(&db);
|
||||
var i: uint = 0;
|
||||
while i < n {
|
||||
Print(" ");
|
||||
PrintLine(list[i]);
|
||||
i = i + 1;
|
||||
}
|
||||
bux_free(list as *void);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if String_Eq(cmd, "count") {
|
||||
DB_Load(&db);
|
||||
let n: uint = DB_Count(&db);
|
||||
Print("count: ");
|
||||
PrintInt(n as int);
|
||||
PrintLine("");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Print("unknown command: ");
|
||||
PrintLine(cmd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,6 +33,7 @@ type
|
||||
tekDynRef ## &dyn Trait — trait object (fat pointer)
|
||||
tekTuple
|
||||
tekSelf
|
||||
tekFunc
|
||||
|
||||
TypeExpr* = ref object
|
||||
loc*: SourceLocation
|
||||
@@ -54,6 +55,9 @@ type
|
||||
tupleElements*: seq[TypeExpr]
|
||||
of tekSelf:
|
||||
discard
|
||||
of tekFunc:
|
||||
funcParams*: seq[TypeExpr]
|
||||
funcRet*: TypeExpr
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Patterns
|
||||
@@ -124,8 +128,11 @@ type
|
||||
ekUnwrap ## expr! — unwrap or panic
|
||||
ekSpawn ## spawn expr — create a new task
|
||||
ekAwait ## expr.await — suspend until future resolves
|
||||
ekBorrow ## borrow &mut expr — explicit borrow expression
|
||||
ekBlock
|
||||
ekMatch
|
||||
ekStringInterp
|
||||
ekClosure
|
||||
|
||||
MatchArm* = object
|
||||
loc*: SourceLocation
|
||||
@@ -172,6 +179,7 @@ type
|
||||
of ekCall:
|
||||
exprCallCallee*: Expr
|
||||
exprCallArgs*: seq[Expr]
|
||||
exprCallArgNames*: seq[string] ## empty = positional, non-empty = named arg
|
||||
exprCallInferredTypeArgs*: seq[TypeExpr] ## filled by sema for inferred generic calls
|
||||
of ekGenericCall:
|
||||
exprGenericCallee*: string
|
||||
@@ -210,11 +218,24 @@ type
|
||||
exprSpawnAsync*: bool
|
||||
of ekAwait:
|
||||
exprAwaitOperand*: Expr
|
||||
of ekBorrow:
|
||||
exprBorrowOperand*: Expr
|
||||
exprBorrowMutable*: bool
|
||||
of ekBlock:
|
||||
exprBlock*: Block
|
||||
of ekMatch:
|
||||
exprMatchSubject*: Expr
|
||||
exprMatchArms*: seq[MatchArm]
|
||||
of ekStringInterp:
|
||||
exprInterpTexts*: seq[string]
|
||||
exprInterpExprs*: seq[Expr]
|
||||
of ekClosure:
|
||||
exprClosureParams*: seq[Param]
|
||||
exprClosureBody*: Block
|
||||
exprClosureReturnType*: TypeExpr
|
||||
captureCount*: int
|
||||
captureNames*: seq[string]
|
||||
captureTypeKinds*: seq[int]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Statements
|
||||
@@ -234,6 +255,8 @@ type
|
||||
skStaticAssert
|
||||
skComptime
|
||||
skEmit
|
||||
skDefer
|
||||
skSwitch
|
||||
skDecl
|
||||
|
||||
ElseIf* = object
|
||||
@@ -241,6 +264,11 @@ type
|
||||
cond*: Expr
|
||||
blk*: Block
|
||||
|
||||
SwitchCase* = object
|
||||
loc*: SourceLocation
|
||||
caseValue*: Expr
|
||||
caseBody*: Block
|
||||
|
||||
Block* = ref object
|
||||
loc*: SourceLocation
|
||||
stmts*: seq[Stmt]
|
||||
@@ -294,6 +322,12 @@ type
|
||||
of skEmit:
|
||||
stmtEmitExpr*: Expr
|
||||
stmtEmitEvaluated*: string ## filled by sema CTFE
|
||||
of skDefer:
|
||||
stmtDeferBody*: Expr
|
||||
of skSwitch:
|
||||
stmtSwitchExpr*: Expr
|
||||
stmtSwitchCases*: seq[SwitchCase]
|
||||
stmtSwitchDefault*: Block
|
||||
of skDecl:
|
||||
stmtDecl*: Decl
|
||||
|
||||
@@ -441,3 +475,6 @@ proc newBinaryExpr*(op: TokenKind, left, right: Expr, loc: SourceLocation): Expr
|
||||
|
||||
proc newUnaryExpr*(op: TokenKind, operand: Expr, loc: SourceLocation): Expr =
|
||||
result = Expr(kind: ekUnary, loc: loc, exprUnaryOp: op, exprUnaryOperand: operand)
|
||||
|
||||
proc newStringInterpExpr*(texts: seq[string], exprs: seq[Expr], loc: SourceLocation): Expr =
|
||||
result = Expr(kind: ekStringInterp, loc: loc, exprInterpTexts: texts, exprInterpExprs: exprs)
|
||||
|
||||
@@ -8,6 +8,7 @@ type
|
||||
varCounter*: int
|
||||
declaredVars*: seq[string]
|
||||
sliceTypeDefs*: seq[tuple[name: string, elem: string]] ## Generated Slice_<T> typedefs
|
||||
deferStack*: seq[seq[string]] ## Function-level deferred statements (LIFO)
|
||||
|
||||
proc cEscape(s: string): string =
|
||||
## Escape a string for use as a C string literal.
|
||||
@@ -45,6 +46,28 @@ proc freshVar(be: var CBackend): string =
|
||||
inc be.varCounter
|
||||
result = &"__tmp_{be.varCounter}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Defer helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
proc pushDeferScope(be: var CBackend) =
|
||||
be.deferStack.add(@[])
|
||||
|
||||
proc popDeferScope(be: var CBackend) =
|
||||
if be.deferStack.len > 0:
|
||||
be.deferStack.setLen(be.deferStack.len - 1)
|
||||
|
||||
proc emitCurrentDefers(be: var CBackend) =
|
||||
## Emit all deferred statements in current scope, LIFO order
|
||||
if be.deferStack.len == 0: return
|
||||
let idx = be.deferStack.len - 1
|
||||
for i in countdown(be.deferStack[idx].len - 1, 0):
|
||||
be.emitLine(be.deferStack[idx][i])
|
||||
|
||||
proc clearCurrentDefers(be: var CBackend) =
|
||||
if be.deferStack.len > 0:
|
||||
let idx = be.deferStack.len - 1
|
||||
be.deferStack[idx].setLen(0)
|
||||
|
||||
# Type conversion: Bux Type → C type string
|
||||
proc typeToC*(be: var CBackend, typ: Type): string =
|
||||
if typ == nil: return "void"
|
||||
@@ -249,6 +272,10 @@ proc emitExpr(be: var CBackend, node: HirNode): string =
|
||||
let base = be.emitExpr(node.fieldPtrBase)
|
||||
return &"(&({base}.{node.fieldName}))"
|
||||
|
||||
of hFieldAccess:
|
||||
let base = be.emitExpr(node.fieldAccessBase)
|
||||
return &"({base}.{node.fieldAccessName})"
|
||||
|
||||
of hArrowField:
|
||||
let base = be.emitExpr(node.arrowFieldBase)
|
||||
return &"(&({base}->{node.arrowFieldName}))"
|
||||
@@ -358,7 +385,14 @@ proc emitExpr(be: var CBackend, node: HirNode): string =
|
||||
proc emitStmt(be: var CBackend, node: HirNode) =
|
||||
if node == nil: return
|
||||
case node.kind
|
||||
of hDefer:
|
||||
let expr = be.emitExpr(node.deferBody)
|
||||
if be.deferStack.len > 0:
|
||||
let idx = be.deferStack.len - 1
|
||||
be.deferStack[idx].add(expr & ";")
|
||||
|
||||
of hReturn:
|
||||
be.emitCurrentDefers()
|
||||
if node.returnValue != nil:
|
||||
let val = be.emitExpr(node.returnValue)
|
||||
be.emitLine(&"return {val};")
|
||||
@@ -394,9 +428,11 @@ proc emitStmt(be: var CBackend, node: HirNode) =
|
||||
be.emitLine("}")
|
||||
|
||||
of hBreak:
|
||||
be.emitCurrentDefers()
|
||||
be.emitLine("break;")
|
||||
|
||||
of hContinue:
|
||||
be.emitCurrentDefers()
|
||||
be.emitLine("continue;")
|
||||
|
||||
of hEmit:
|
||||
@@ -444,6 +480,18 @@ proc emitStmt(be: var CBackend, node: HirNode) =
|
||||
be.emitLine(&"{expr};")
|
||||
|
||||
proc emitFunc*(be: var CBackend, hfunc: HirFunc) =
|
||||
# Emit env struct and global instance for closures with captures
|
||||
if hfunc.captureNames.len > 0 and hfunc.envStructName != "":
|
||||
be.emitLine(&"struct {hfunc.envStructName} {{")
|
||||
inc be.indent
|
||||
for i in 0 ..< hfunc.captureNames.len:
|
||||
let capName = hfunc.captureNames[i]
|
||||
let capType = if i < hfunc.captureTypes.len: typeToC(be, hfunc.captureTypes[i]) else: "int"
|
||||
be.emitLine(&"{capType} {capName};")
|
||||
dec be.indent
|
||||
be.emitLine("};")
|
||||
be.emitLine(&"static struct {hfunc.envStructName} {hfunc.envInstanceName};")
|
||||
be.emitLine("")
|
||||
let retType = typeToC(be, hfunc.retType)
|
||||
var params: seq[string] = @[]
|
||||
for p in hfunc.params:
|
||||
@@ -453,15 +501,23 @@ proc emitFunc*(be: var CBackend, hfunc: HirFunc) =
|
||||
let paramsStr = params.join(", ")
|
||||
be.emitLine(&"{retType} {hfunc.name}({paramsStr}) {{")
|
||||
inc be.indent
|
||||
be.pushDeferScope()
|
||||
if hfunc.body != nil:
|
||||
if hfunc.body.kind == hBlock:
|
||||
for stmt in hfunc.body.blockStmts:
|
||||
be.emitStmt(stmt)
|
||||
if hfunc.body.blockExpr != nil and hfunc.retType.kind != tkVoid:
|
||||
be.emitCurrentDefers()
|
||||
let val = be.emitExpr(hfunc.body.blockExpr)
|
||||
be.emitLine(&"return {val};")
|
||||
else:
|
||||
be.emitCurrentDefers()
|
||||
else:
|
||||
be.emitStmt(hfunc.body)
|
||||
be.emitCurrentDefers()
|
||||
else:
|
||||
be.emitCurrentDefers()
|
||||
be.popDeferScope()
|
||||
dec be.indent
|
||||
be.emitLine("}")
|
||||
be.emitLine("")
|
||||
|
||||
+14
-12
@@ -1,5 +1,5 @@
|
||||
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_lower, lir_c_backend
|
||||
|
||||
type
|
||||
ColorMode* = enum
|
||||
@@ -259,9 +259,9 @@ proc collectDepDecls(lock: Lockfile, root: string, opts: GlobalOptions): seq[Dec
|
||||
|
||||
proc findStdlibDir(root: string): string =
|
||||
let searchPaths = @[
|
||||
getAppDir() / ".." / "library",
|
||||
getAppDir() / "library",
|
||||
root / "library",
|
||||
getAppDir() / ".." / "lib",
|
||||
getAppDir() / "lib",
|
||||
root / "lib",
|
||||
]
|
||||
for path in searchPaths:
|
||||
if dirExists(path):
|
||||
@@ -454,14 +454,15 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
|
||||
return 1
|
||||
|
||||
let hirMod = lowerModule(unifiedModule, semaCtx)
|
||||
var cbe = initCBackend()
|
||||
var allCCode = cbe.emitModule(hirMod)
|
||||
let lirBuilder = lowerModuleToLir(hirMod)
|
||||
var lirCbe = initLirCBackend()
|
||||
var allCCode = lirCbe.emitModule(lirBuilder, hirMod)
|
||||
|
||||
# Write C file
|
||||
let cFile = buildDir / "main.c"
|
||||
writeFile(cFile, allCCode)
|
||||
|
||||
# Copy runtime and stdlib files
|
||||
# Copy runtime files (rt/ is sibling of lib/)
|
||||
let stdlibDir = pctx.stdlibDir
|
||||
let runtimeDst = buildDir / "runtime.c"
|
||||
let ioDst = buildDir / "io.c"
|
||||
@@ -469,24 +470,25 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
|
||||
printError("stdlib directory not found", useColor)
|
||||
return 1
|
||||
|
||||
let runtimeSrc = stdlibDir / "runtime" / "runtime.c"
|
||||
let baseDir = stdlibDir.parentDir()
|
||||
let runtimeSrc = baseDir / "rt" / "runtime.c"
|
||||
if fileExists(runtimeSrc):
|
||||
copyFile(runtimeSrc, runtimeDst)
|
||||
else:
|
||||
printError("runtime.c not found in library/runtime/", useColor)
|
||||
printError("runtime.c not found in rt/", useColor)
|
||||
return 1
|
||||
|
||||
let ioSrc = stdlibDir / "runtime" / "io.c"
|
||||
let ioSrc = baseDir / "rt" / "io.c"
|
||||
if fileExists(ioSrc):
|
||||
copyFile(ioSrc, ioDst)
|
||||
else:
|
||||
printError("io.c not found in library/runtime/", useColor)
|
||||
printError("io.c not found in rt/", useColor)
|
||||
return 1
|
||||
|
||||
# Compile with cc
|
||||
let outputName = if pctx.man.name != "": pctx.man.name else: "bux_out"
|
||||
let outputFile = buildDir / outputName
|
||||
let ccCmd = &"cc -O2 -pthread -o {outputFile} {cFile} {runtimeDst} {ioDst} -lm -lcrypto 2>&1"
|
||||
let ccCmd = &"cc -O0 -g -pthread -Wl,--build-id=none -o {outputFile} {cFile} {runtimeDst} {ioDst} -lm -lcrypto 2>&1"
|
||||
if opts.verbose:
|
||||
printInfo(&"running: {ccCmd}", useColor)
|
||||
let (output, exitCode) = execCmdEx(ccCmd)
|
||||
|
||||
@@ -17,11 +17,13 @@ type
|
||||
hBreak
|
||||
hContinue
|
||||
hReturn
|
||||
hDefer
|
||||
# Memory
|
||||
hAlloca
|
||||
hLoad
|
||||
hStore
|
||||
hFieldPtr
|
||||
hFieldAccess
|
||||
hArrowField
|
||||
hIndexPtr
|
||||
hSliceIndex
|
||||
@@ -84,6 +86,8 @@ type
|
||||
continueLabel*: string
|
||||
of hReturn:
|
||||
returnValue*: HirNode
|
||||
of hDefer:
|
||||
deferBody*: HirNode
|
||||
of hAlloca:
|
||||
allocaType*: Type
|
||||
allocaName*: string
|
||||
@@ -95,6 +99,9 @@ type
|
||||
of hFieldPtr:
|
||||
fieldPtrBase*: HirNode
|
||||
fieldName*: string
|
||||
of hFieldAccess:
|
||||
fieldAccessBase*: HirNode
|
||||
fieldAccessName*: string
|
||||
of hArrowField:
|
||||
arrowFieldBase*: HirNode
|
||||
arrowFieldName*: string
|
||||
@@ -163,6 +170,11 @@ type
|
||||
retType*: Type
|
||||
body*: HirNode
|
||||
isPublic*: bool
|
||||
# Closure capture metadata
|
||||
captureNames*: seq[string]
|
||||
captureTypes*: seq[Type]
|
||||
envStructName*: string
|
||||
envInstanceName*: string
|
||||
|
||||
HirEnumVariant* = object
|
||||
name*: string
|
||||
@@ -200,15 +212,25 @@ proc hirCall*(callee: string, args: seq[HirNode], typ: Type, loc: SourceLocation
|
||||
proc hirReturn*(value: HirNode, loc: SourceLocation): HirNode =
|
||||
HirNode(kind: hReturn, returnValue: value, typ: makeVoid(), loc: loc)
|
||||
|
||||
proc hirDefer*(body: HirNode, loc: SourceLocation): HirNode =
|
||||
HirNode(kind: hDefer, deferBody: body, typ: makeVoid(), loc: loc)
|
||||
|
||||
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)
|
||||
|
||||
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 =
|
||||
HirNode(kind: hAlloca, allocaType: typ, allocaName: name, typ: makePointer(typ), loc: loc)
|
||||
|
||||
proc hirStore*(ptrNode, value: HirNode, loc: SourceLocation): HirNode =
|
||||
HirNode(kind: hStore, storePtr: ptrNode, storeValue: value, typ: makeVoid(), loc: loc)
|
||||
|
||||
proc hirAssign*(target, value: HirNode, loc: SourceLocation): HirNode =
|
||||
HirNode(kind: hAssign, assignOp: tkAssign, assignTarget: target, assignValue: value, typ: makeVoid(), loc: loc)
|
||||
|
||||
proc hirLoad*(ptrNode: HirNode, typ: Type, loc: SourceLocation): HirNode =
|
||||
HirNode(kind: hLoad, loadPtr: ptrNode, typ: typ, loc: loc)
|
||||
|
||||
|
||||
+682
-43
@@ -11,6 +11,7 @@ type
|
||||
varCounter*: int
|
||||
tryCounter*: int
|
||||
pendingStmts*: seq[HirNode]
|
||||
deferStmts*: seq[HirNode]
|
||||
typeSubst*: Table[string, Type] # Type parameter substitution for generics
|
||||
importTable*: Table[string, string] # Local name → fully qualified name for imports
|
||||
genericStructs*: Table[string, Decl] # Generic struct declarations
|
||||
@@ -21,6 +22,9 @@ type
|
||||
generatedFuncInsts*: Table[string, bool] # Track generated function instantiations
|
||||
extraFuncs*: seq[HirFunc] # Monomorphized generic methods
|
||||
varTypeExprs*: Table[string, TypeExpr] # Track variable names -> type expr for generic method inference
|
||||
closureDepth*: int
|
||||
currentClosureExpr*: Expr
|
||||
envInstanceName*: string
|
||||
|
||||
proc freshName(ctx: var LowerCtx): string =
|
||||
inc ctx.varCounter
|
||||
@@ -38,6 +42,14 @@ proc flushPending(ctx: var LowerCtx, node: HirNode): HirNode =
|
||||
return hirBlock(stmts, nil, makeVoid(), node.loc)
|
||||
return node
|
||||
|
||||
proc enumHasDataVariants(ctx: var LowerCtx, enumName: string): bool =
|
||||
let sym = ctx.globalScope.lookup(enumName)
|
||||
if sym != nil and sym.decl != nil and sym.decl.kind == dkEnum:
|
||||
for v in sym.decl.declEnumVariants:
|
||||
if v.fields.len > 0 or v.namedFields.len > 0:
|
||||
return true
|
||||
return false
|
||||
|
||||
proc lowerMatch(ctx: var LowerCtx, subject: HirNode, arms: seq[HirMatchArm], typ: Type, loc: SourceLocation): HirNode =
|
||||
# Lower match expression to a block with if-else chain.
|
||||
# For now, supports enum tag matching and wildcard/ident fallbacks.
|
||||
@@ -47,6 +59,13 @@ proc lowerMatch(ctx: var LowerCtx, subject: HirNode, arms: seq[HirMatchArm], typ
|
||||
# Allocate result variable
|
||||
stmts.add(hirAlloca(resultName, typ, loc))
|
||||
|
||||
# Determine whether the matched enum has data variants (needs .tag access).
|
||||
var subjectEnumName = ""
|
||||
var subjectHasData = false
|
||||
if subject.typ != nil and subject.typ.kind == tkNamed:
|
||||
subjectEnumName = subject.typ.name
|
||||
subjectHasData = ctx.enumHasDataVariants(subjectEnumName)
|
||||
|
||||
# Build if-else chain from arms (last arm is the outermost else)
|
||||
var ifChain: HirNode = nil
|
||||
|
||||
@@ -62,12 +81,18 @@ proc lowerMatch(ctx: var LowerCtx, subject: HirNode, arms: seq[HirMatchArm], typ
|
||||
let variantName = path[^1]
|
||||
let tagName = enumName & "_" & variantName
|
||||
|
||||
# condition: subject.tag == EnumName_VariantName
|
||||
let tagField = HirNode(kind: hFieldPtr, fieldPtrBase: subject, fieldName: "tag",
|
||||
typ: makePointer(makeNamed(enumName & "_Tag")), loc: loc)
|
||||
let tagLoad = HirNode(kind: hLoad, loadPtr: tagField, typ: makeNamed(enumName & "_Tag"), loc: loc)
|
||||
let tagConst = hirLit(Token(kind: tkIdent, text: tagName, loc: loc), makeNamed(enumName & "_Tag"), loc)
|
||||
let cond = hirBinary(tkEq, tagLoad, tagConst, makeBool(), loc)
|
||||
var cond: HirNode
|
||||
if subjectHasData and enumName == subjectEnumName:
|
||||
# Algebraic enum: compare subject.tag
|
||||
let tagField = HirNode(kind: hFieldPtr, fieldPtrBase: subject, fieldName: "tag",
|
||||
typ: makePointer(makeNamed(enumName & "_Tag")), loc: loc)
|
||||
let tagLoad = HirNode(kind: hLoad, loadPtr: tagField, typ: makeNamed(enumName & "_Tag"), loc: loc)
|
||||
let tagConst = hirLit(Token(kind: tkIdent, text: tagName, loc: loc), makeNamed(enumName & "_Tag"), loc)
|
||||
cond = hirBinary(tkEq, tagLoad, tagConst, makeBool(), loc)
|
||||
else:
|
||||
# Simple enum or cross-enum match: compare subject directly
|
||||
let tagConst = hirLit(Token(kind: tkIdent, text: tagName, loc: loc), makeNamed(enumName), loc)
|
||||
cond = hirBinary(tkEq, subject, tagConst, makeBool(), loc)
|
||||
|
||||
# body: result = arm_body
|
||||
var armStmts: seq[HirNode] = @[]
|
||||
@@ -162,10 +187,14 @@ proc substituteType(ctx: var LowerCtx, te: TypeExpr, subst: Table[string, Type])
|
||||
break
|
||||
if hasUnresolved: break
|
||||
if not hasUnresolved:
|
||||
var localSubst = subst
|
||||
for j, tp in genericDecl.declStructTypeParams:
|
||||
if j < te.typeArgs.len:
|
||||
localSubst[tp.name] = substituteType(ctx, te.typeArgs[j], subst)
|
||||
var fields: seq[tuple[name: string, typ: Type]] = @[]
|
||||
var concreteArgs: seq[Type] = @[]
|
||||
for f in genericDecl.declStructFields:
|
||||
let resolvedType = substituteType(ctx, f.ftype, subst)
|
||||
let resolvedType = substituteType(ctx, f.ftype, localSubst)
|
||||
fields.add((f.name, resolvedType))
|
||||
for arg in te.typeArgs:
|
||||
concreteArgs.add(substituteType(ctx, arg, subst))
|
||||
@@ -263,12 +292,19 @@ proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type =
|
||||
of tekDynRef: return makeDynRef(te.dynInterface)
|
||||
of tekPointer: return makePointer(ctx.resolveTypeExpr(te.pointerPointee))
|
||||
of tekSlice: return makeSlice(ctx.resolveTypeExpr(te.sliceElement))
|
||||
of tekFunc:
|
||||
var params: seq[Type] = @[]
|
||||
for p in te.funcParams:
|
||||
params.add(ctx.resolveTypeExpr(p))
|
||||
let ret = if te.funcRet != nil: ctx.resolveTypeExpr(te.funcRet) else: makeVoid()
|
||||
return makeFunc(params, ret)
|
||||
else: return makeUnknown()
|
||||
|
||||
# Forward declarations
|
||||
proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode
|
||||
proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode
|
||||
proc lowerBlock(ctx: var LowerCtx, blk: Block): HirNode
|
||||
proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc
|
||||
|
||||
proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
|
||||
if expr == nil: return makeUnknown()
|
||||
@@ -319,7 +355,7 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
|
||||
of ekUnary:
|
||||
case expr.exprUnaryOp
|
||||
of tkBang: return makeBool()
|
||||
of tkAmp: return makePointer(ctx.resolveExprType(expr.exprUnaryOperand))
|
||||
of tkAmp: return makeMutRef(ctx.resolveExprType(expr.exprUnaryOperand))
|
||||
of tkStar:
|
||||
let inner = ctx.resolveExprType(expr.exprUnaryOperand)
|
||||
if inner.isPointer: return inner.inner[0]
|
||||
@@ -352,22 +388,87 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
|
||||
if objType.isPointer and objType.inner.len > 0:
|
||||
objType = objType.inner[0]
|
||||
if objType.kind == tkNamed:
|
||||
let sym = ctx.globalScope.lookup(objType.name)
|
||||
if sym != nil and sym.decl != nil and sym.decl.kind == dkStruct:
|
||||
for f in sym.decl.declStructFields:
|
||||
if f.name == expr.exprFieldName:
|
||||
if f.ftype != nil:
|
||||
case f.ftype.kind
|
||||
of tekNamed:
|
||||
case f.ftype.typeName
|
||||
of "int", "int32", "int64": return makeInt()
|
||||
of "float64": return makeFloat64()
|
||||
of "float32": return makeFloat32()
|
||||
of "bool": return makeBool()
|
||||
else: return makeNamed(f.ftype.typeName)
|
||||
of tekOwn, tekPointer:
|
||||
return ctx.resolveTypeExpr(f.ftype)
|
||||
else: return makeUnknown()
|
||||
# Check if this is a _Data union field access
|
||||
if objType.name.endsWith("_Data"):
|
||||
let enumName = objType.name[0..^6]
|
||||
let enumSym = ctx.globalScope.lookup(enumName)
|
||||
if enumSym != nil and enumSym.decl != nil and enumSym.decl.kind == dkEnum:
|
||||
for variant in enumSym.decl.declEnumVariants:
|
||||
for i, f in variant.fields:
|
||||
let fieldName = variant.name & "_" & $i
|
||||
if fieldName == expr.exprFieldName:
|
||||
return ctx.resolveTypeExpr(f)
|
||||
for nf in variant.namedFields:
|
||||
if nf.name == expr.exprFieldName:
|
||||
return ctx.resolveTypeExpr(nf.ftype)
|
||||
var sym = ctx.globalScope.lookup(objType.name)
|
||||
var decl = if sym != nil: sym.decl else: nil
|
||||
# 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:
|
||||
if f.ftype.typeArgs.len > 0:
|
||||
return substituteType(ctx, f.ftype, subst)
|
||||
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.ftype != nil:
|
||||
case f.ftype.kind
|
||||
of tekNamed:
|
||||
if f.ftype.typeArgs.len > 0:
|
||||
return ctx.resolveTypeExpr(f.ftype)
|
||||
case f.ftype.typeName
|
||||
of "int", "int32", "int64": return makeInt()
|
||||
of "float64": return makeFloat64()
|
||||
of "float32": return makeFloat32()
|
||||
of "bool": return makeBool()
|
||||
else: return makeNamed(f.ftype.typeName)
|
||||
of tekOwn, tekPointer:
|
||||
return ctx.resolveTypeExpr(f.ftype)
|
||||
else: return makeUnknown()
|
||||
of dkEnum:
|
||||
# Algebraic enum fields: tag and data
|
||||
var hasData = false
|
||||
for v in decl.declEnumVariants:
|
||||
if v.fields.len > 0 or v.namedFields.len > 0:
|
||||
hasData = true
|
||||
break
|
||||
if not hasData and expr.exprFieldName == "tag":
|
||||
return makeNamed(objType.name)
|
||||
elif 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()
|
||||
of ekStructInit:
|
||||
if expr.exprStructInitTypeArgs.len > 0:
|
||||
@@ -380,7 +481,15 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
|
||||
return makeSlice(makeUnknown())
|
||||
of ekRange:
|
||||
let loType = ctx.resolveExprType(expr.exprRangeLo)
|
||||
return makeRange(loType)
|
||||
let hiType = ctx.resolveExprType(expr.exprRangeHi)
|
||||
if loType == hiType:
|
||||
return makeRange(loType)
|
||||
elif loType.isAssignableTo(hiType):
|
||||
return makeRange(hiType)
|
||||
elif hiType.isAssignableTo(loType):
|
||||
return makeRange(loType)
|
||||
else:
|
||||
return makeRange(loType)
|
||||
of ekTuple:
|
||||
var elems: seq[Type] = @[]
|
||||
for e in expr.exprTupleElements:
|
||||
@@ -395,12 +504,25 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
|
||||
return makeInt()
|
||||
of ekUnwrap:
|
||||
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 ekMatch:
|
||||
if expr.exprMatchArms.len > 0:
|
||||
return ctx.resolveExprType(expr.exprMatchArms[0].body)
|
||||
return makeUnknown()
|
||||
of ekBlock:
|
||||
if expr.exprBlock.stmts.len > 0:
|
||||
let last = expr.exprBlock.stmts[^1]
|
||||
if last.kind == skExpr:
|
||||
return ctx.resolveExprType(last.stmtExpr)
|
||||
return makeVoid()
|
||||
of ekBorrow:
|
||||
return ctx.resolveExprType(expr.exprBorrowOperand)
|
||||
else: return makeUnknown()
|
||||
|
||||
proc extractGenericStructInfo(ctx: LowerCtx, te: TypeExpr): tuple[baseName: string, typeArgs: seq[TypeExpr]] =
|
||||
@@ -427,6 +549,49 @@ proc getReceiverTypeExpr(ctx: LowerCtx, expr: Expr): TypeExpr =
|
||||
else: discard
|
||||
return nil
|
||||
|
||||
proc getCollectionElementTypeExpr(ctx: var LowerCtx, expr: Expr): TypeExpr =
|
||||
## Return the element TypeExpr of a collection expression (Array<T>, Iter<T>, Channel<T>).
|
||||
## For identifiers we can use the declared TypeExpr directly; for other expressions we
|
||||
## fall back to the resolved concrete Type.
|
||||
case expr.kind
|
||||
of ekIdent:
|
||||
if ctx.varTypeExprs.hasKey(expr.exprIdent):
|
||||
let te = ctx.varTypeExprs[expr.exprIdent]
|
||||
if te.kind == tekNamed and te.typeArgs.len > 0:
|
||||
return te.typeArgs[0]
|
||||
if te.kind in {tekPointer, tekRef, tekMutRef} and te.pointerPointee.kind == tekNamed and te.pointerPointee.typeArgs.len > 0:
|
||||
return te.pointerPointee.typeArgs[0]
|
||||
of ekField:
|
||||
# Try to resolve the field's declared TypeExpr directly.
|
||||
let objType = ctx.resolveExprType(expr.exprFieldObj)
|
||||
if objType.kind == tkNamed:
|
||||
var decl = ctx.globalScope.lookup(objType.name).decl
|
||||
if decl == nil and ctx.structInstMap.hasKey(objType.name):
|
||||
let (baseName, _) = 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
|
||||
if decl != nil and decl.kind == dkStruct:
|
||||
for f in decl.declStructFields:
|
||||
if f.name == expr.exprFieldName and f.ftype != nil:
|
||||
let fte = f.ftype
|
||||
if fte.kind == tekNamed and fte.typeArgs.len > 0 and (fte.typeName == "Array" or fte.typeName == "Iter" or fte.typeName == "Channel"):
|
||||
return fte.typeArgs[0]
|
||||
return fte
|
||||
else:
|
||||
discard
|
||||
let t = ctx.resolveExprType(expr)
|
||||
if t.kind == tkNamed and t.inner.len > 0:
|
||||
return typeToTypeExpr(t.inner[0])
|
||||
if t.isPointer and t.inner.len > 0 and t.inner[0].kind == tkNamed and t.inner[0].inner.len > 0:
|
||||
return typeToTypeExpr(t.inner[0].inner[0])
|
||||
# Generic struct instances (e.g. Array_HeaderEntry) store their type args in structInstMap.
|
||||
if t.kind == tkNamed and ctx.structInstMap.hasKey(t.name):
|
||||
let (baseName, concreteArgs) = ctx.structInstMap[t.name]
|
||||
if concreteArgs.len > 0 and (baseName == "Array" or baseName == "Iter" or baseName == "Channel"):
|
||||
return typeToTypeExpr(concreteArgs[0])
|
||||
return TypeExpr(kind: tekNamed, typeName: "unknown")
|
||||
|
||||
proc generateMethodInstance(ctx: var LowerCtx, baseMethodName: string, typeArgs: seq[TypeExpr]): string
|
||||
|
||||
proc lowerExprWithDynRefCoerce(ctx: var LowerCtx, arg: Expr, expectedType: Type): HirNode =
|
||||
@@ -462,6 +627,103 @@ proc findMethodEntry(ctx: LowerCtx, typeName: string): (string, seq[MethodInfo])
|
||||
return (prefix, ctx.methodTable[prefix])
|
||||
return ("", @[])
|
||||
|
||||
proc operatorMethodName(op: TokenKind): string =
|
||||
case op
|
||||
of tkPlus: "operator_add"
|
||||
of tkMinus: "operator_sub"
|
||||
of tkStar: "operator_mul"
|
||||
of tkSlash: "operator_div"
|
||||
of tkPercent: "operator_mod"
|
||||
of tkEq: "operator_eq"
|
||||
of tkNe: "operator_ne"
|
||||
of tkLt: "operator_lt"
|
||||
of tkLe: "operator_le"
|
||||
of tkGt: "operator_gt"
|
||||
of tkGe: "operator_ge"
|
||||
of tkAmp: "operator_bitand"
|
||||
of tkPipe: "operator_bitor"
|
||||
of tkCaret: "operator_xor"
|
||||
of tkShl: "operator_shl"
|
||||
of tkShr: "operator_shr"
|
||||
else: ""
|
||||
|
||||
proc tryLowerOperatorCall(ctx: var LowerCtx, op: TokenKind, leftExpr, rightExpr: Expr, typ: Type, loc: SourceLocation): HirNode =
|
||||
## Try to lower a binary operator to a method call. Returns nil if no overload found.
|
||||
let methodName = operatorMethodName(op)
|
||||
if methodName == "": return nil
|
||||
let receiverType = ctx.resolveExprType(leftExpr)
|
||||
var receiverTypeName = ""
|
||||
if receiverType.kind == tkNamed:
|
||||
receiverTypeName = receiverType.name
|
||||
if ctx.typeSubst.hasKey(receiverTypeName):
|
||||
let substituted = ctx.typeSubst[receiverTypeName]
|
||||
if substituted.kind == tkNamed:
|
||||
receiverTypeName = substituted.name
|
||||
elif substituted.isPointer and substituted.inner.len > 0 and substituted.inner[0].kind == tkNamed:
|
||||
receiverTypeName = substituted.inner[0].name
|
||||
elif receiverType.kind in {tkInt, tkInt8, tkInt16, tkInt32, tkInt64,
|
||||
tkUInt, tkUInt8, tkUInt16, tkUInt32, tkUInt64,
|
||||
tkFloat32, tkFloat64, tkBool, tkStr, tkChar8}:
|
||||
receiverTypeName = receiverType.toString
|
||||
elif receiverType.isPointer and receiverType.inner.len > 0 and receiverType.inner[0].kind == tkNamed:
|
||||
receiverTypeName = receiverType.inner[0].name
|
||||
let (typeName, methods) = ctx.findMethodEntry(receiverTypeName)
|
||||
if typeName == "": return nil
|
||||
for minfo in methods:
|
||||
if minfo.name == methodName:
|
||||
var calleeName = typeName & "_" & methodName
|
||||
# Check generic method instantiation
|
||||
let recvTypeExpr = ctx.getReceiverTypeExpr(leftExpr)
|
||||
let (baseName, typeArgs) = ctx.extractGenericStructInfo(recvTypeExpr)
|
||||
if baseName != "" and baseName == typeName and minfo.decl.declFuncTypeParams.len > 0:
|
||||
calleeName = ctx.generateMethodInstance(calleeName, typeArgs)
|
||||
var args: seq[HirNode] = @[]
|
||||
let loweredReceiver = ctx.lowerExpr(leftExpr)
|
||||
if minfo.params.len > 0 and minfo.params[0].isPointer and not receiverType.isPointer:
|
||||
args.add(hirUnary(tkAmp, loweredReceiver, makePointer(receiverType), loc))
|
||||
else:
|
||||
args.add(loweredReceiver)
|
||||
args.add(ctx.lowerExpr(rightExpr))
|
||||
return hirCall(calleeName, args, typ, loc)
|
||||
return nil
|
||||
|
||||
proc tryLowerIndexCall(ctx: var LowerCtx, objExpr, idxExpr: Expr, typ: Type, loc: SourceLocation): HirNode =
|
||||
## Try to lower arr[i] to operator_index_get(arr, i). Returns nil if no overload found.
|
||||
let receiverType = ctx.resolveExprType(objExpr)
|
||||
var receiverTypeName = ""
|
||||
if receiverType.kind == tkNamed:
|
||||
receiverTypeName = receiverType.name
|
||||
if ctx.typeSubst.hasKey(receiverTypeName):
|
||||
let substituted = ctx.typeSubst[receiverTypeName]
|
||||
if substituted.kind == tkNamed:
|
||||
receiverTypeName = substituted.name
|
||||
elif substituted.isPointer and substituted.inner.len > 0 and substituted.inner[0].kind == tkNamed:
|
||||
receiverTypeName = substituted.inner[0].name
|
||||
elif receiverType.kind in {tkInt, tkInt8, tkInt16, tkInt32, tkInt64,
|
||||
tkUInt, tkUInt8, tkUInt16, tkUInt32, tkUInt64,
|
||||
tkFloat32, tkFloat64, tkBool, tkStr, tkChar8}:
|
||||
receiverTypeName = receiverType.toString
|
||||
elif receiverType.isPointer and receiverType.inner.len > 0 and receiverType.inner[0].kind == tkNamed:
|
||||
receiverTypeName = receiverType.inner[0].name
|
||||
let (typeName, methods) = ctx.findMethodEntry(receiverTypeName)
|
||||
if typeName == "": return nil
|
||||
for minfo in methods:
|
||||
if minfo.name == "operator_index_get":
|
||||
var calleeName = typeName & "_operator_index_get"
|
||||
let recvTypeExpr = ctx.getReceiverTypeExpr(objExpr)
|
||||
let (baseName, typeArgs) = ctx.extractGenericStructInfo(recvTypeExpr)
|
||||
if baseName != "" and baseName == typeName and minfo.decl.declFuncTypeParams.len > 0:
|
||||
calleeName = ctx.generateMethodInstance(calleeName, typeArgs)
|
||||
var args: seq[HirNode] = @[]
|
||||
let loweredReceiver = ctx.lowerExpr(objExpr)
|
||||
if minfo.params.len > 0 and minfo.params[0].isPointer and not receiverType.isPointer:
|
||||
args.add(hirUnary(tkAmp, loweredReceiver, makePointer(receiverType), loc))
|
||||
else:
|
||||
args.add(loweredReceiver)
|
||||
args.add(ctx.lowerExpr(idxExpr))
|
||||
return hirCall(calleeName, args, typ, loc)
|
||||
return nil
|
||||
|
||||
proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
if expr == nil: return nil
|
||||
let loc = expr.loc
|
||||
@@ -473,6 +735,13 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
|
||||
of ekIdent:
|
||||
let name = expr.exprIdent
|
||||
# Capture rewriting: if inside closure and ident is captured
|
||||
if ctx.closureDepth > 0 and ctx.currentClosureExpr != nil and ctx.envInstanceName != "":
|
||||
let idx = ctx.currentClosureExpr.captureNames.find(name)
|
||||
if idx >= 0:
|
||||
let capType = if idx < ctx.currentClosureExpr.captureTypeKinds.len: Type(kind: TypeKind(ctx.currentClosureExpr.captureTypeKinds[idx])) else: makeInt()
|
||||
let base = hirVar(ctx.envInstanceName, makeNamed(""), loc)
|
||||
return HirNode(kind: hFieldAccess, fieldAccessName: name, fieldAccessBase: base, typ: capType, loc: loc)
|
||||
if ctx.importTable.hasKey(name):
|
||||
return hirVar(ctx.importTable[name], typ, loc)
|
||||
return hirVar(name, typ, loc)
|
||||
@@ -491,9 +760,34 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
return hirUnary(expr.exprUnaryOp, operand, typ, loc)
|
||||
|
||||
of ekBinary:
|
||||
let left = ctx.lowerExpr(expr.exprBinaryLeft)
|
||||
let right = ctx.lowerExpr(expr.exprBinaryRight)
|
||||
return hirBinary(expr.exprBinaryOp, left, right, typ, loc)
|
||||
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 lowered = ctx.tryLowerOperatorCall(expr.exprBinaryOp, expr.exprBinaryLeft, expr.exprBinaryRight, typ, loc)
|
||||
if lowered != nil:
|
||||
return lowered
|
||||
let left = ctx.lowerExpr(expr.exprBinaryLeft)
|
||||
let right = ctx.lowerExpr(expr.exprBinaryRight)
|
||||
return hirBinary(expr.exprBinaryOp, left, right, typ, loc)
|
||||
|
||||
of ekCall:
|
||||
# Method call desugaring: obj.method(args) → Type_method(obj, args)
|
||||
@@ -598,6 +892,17 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
of ekField:
|
||||
let objType = ctx.resolveExprType(expr.exprFieldObj)
|
||||
let base = ctx.lowerExpr(expr.exprFieldObj)
|
||||
# Simple enum .tag is the enum value itself
|
||||
if objType.kind == tkNamed and expr.exprFieldName == "tag":
|
||||
let sym = ctx.globalScope.lookup(objType.name)
|
||||
if sym != nil and sym.decl != nil and sym.decl.kind == dkEnum:
|
||||
var hasData = false
|
||||
for v in sym.decl.declEnumVariants:
|
||||
if v.fields.len > 0 or v.namedFields.len > 0:
|
||||
hasData = true
|
||||
break
|
||||
if not hasData:
|
||||
return base
|
||||
# Auto-dereference pointer types for field access
|
||||
if objType.isPointer:
|
||||
let arrowPtr = HirNode(kind: hArrowField, arrowFieldBase: base,
|
||||
@@ -610,9 +915,13 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
return HirNode(kind: hLoad, loadPtr: basePtr, typ: typ, loc: loc)
|
||||
|
||||
of ekIndex:
|
||||
let baseType = ctx.resolveExprType(expr.exprIndexObj)
|
||||
if not baseType.isSlice:
|
||||
let lowered = ctx.tryLowerIndexCall(expr.exprIndexObj, expr.exprIndexIdx, typ, loc)
|
||||
if lowered != nil:
|
||||
return lowered
|
||||
let base = ctx.lowerExpr(expr.exprIndexObj)
|
||||
let idx = ctx.lowerExpr(expr.exprIndexIdx)
|
||||
let baseType = ctx.resolveExprType(expr.exprIndexObj)
|
||||
if baseType.isSlice:
|
||||
let sliceIdx = HirNode(kind: hSliceIndex, sliceIndexBase: base,
|
||||
sliceIndexIndex: idx,
|
||||
@@ -624,6 +933,44 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
return HirNode(kind: hLoad, loadPtr: basePtr, typ: typ, loc: loc)
|
||||
|
||||
of ekAssign:
|
||||
# Check for operator_index_set overload
|
||||
if expr.exprAssignTarget.kind == ekIndex:
|
||||
let objExpr = expr.exprAssignTarget.exprIndexObj
|
||||
let idxExpr = expr.exprAssignTarget.exprIndexIdx
|
||||
let receiverType = ctx.resolveExprType(objExpr)
|
||||
var receiverTypeName = ""
|
||||
if receiverType.kind == tkNamed:
|
||||
receiverTypeName = receiverType.name
|
||||
if ctx.typeSubst.hasKey(receiverTypeName):
|
||||
let substituted = ctx.typeSubst[receiverTypeName]
|
||||
if substituted.kind == tkNamed:
|
||||
receiverTypeName = substituted.name
|
||||
elif substituted.isPointer and substituted.inner.len > 0 and substituted.inner[0].kind == tkNamed:
|
||||
receiverTypeName = substituted.inner[0].name
|
||||
elif receiverType.kind in {tkInt, tkInt8, tkInt16, tkInt32, tkInt64,
|
||||
tkUInt, tkUInt8, tkUInt16, tkUInt32, tkUInt64,
|
||||
tkFloat32, tkFloat64, tkBool, tkStr, tkChar8}:
|
||||
receiverTypeName = receiverType.toString
|
||||
elif receiverType.isPointer and receiverType.inner.len > 0 and receiverType.inner[0].kind == tkNamed:
|
||||
receiverTypeName = receiverType.inner[0].name
|
||||
let (typeName, methods) = ctx.findMethodEntry(receiverTypeName)
|
||||
if typeName != "":
|
||||
for minfo in methods:
|
||||
if minfo.name == "operator_index_set":
|
||||
var calleeName = typeName & "_operator_index_set"
|
||||
let recvTypeExpr = ctx.getReceiverTypeExpr(objExpr)
|
||||
let (baseName, typeArgs) = ctx.extractGenericStructInfo(recvTypeExpr)
|
||||
if baseName != "" and baseName == typeName and minfo.decl.declFuncTypeParams.len > 0:
|
||||
calleeName = ctx.generateMethodInstance(calleeName, typeArgs)
|
||||
var args: seq[HirNode] = @[]
|
||||
let loweredReceiver = ctx.lowerExpr(objExpr)
|
||||
if minfo.params.len > 0 and minfo.params[0].isPointer and not receiverType.isPointer:
|
||||
args.add(hirUnary(tkAmp, loweredReceiver, makePointer(receiverType), loc))
|
||||
else:
|
||||
args.add(loweredReceiver)
|
||||
args.add(ctx.lowerExpr(idxExpr))
|
||||
args.add(ctx.lowerExpr(expr.exprAssignValue))
|
||||
return hirCall(calleeName, args, makeVoid(), loc)
|
||||
let target = ctx.lowerExpr(expr.exprAssignTarget)
|
||||
let value = ctx.lowerExpr(expr.exprAssignValue)
|
||||
return HirNode(kind: hAssign, assignOp: expr.exprAssignOp,
|
||||
@@ -631,9 +978,6 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
typ: makeVoid(), loc: loc)
|
||||
|
||||
of ekStructInit:
|
||||
var fields: seq[tuple[name: string, value: HirNode]] = @[]
|
||||
for f in expr.exprStructInitFields:
|
||||
fields.add((f.name, ctx.lowerExpr(f.value)))
|
||||
var structName = expr.exprStructInitName
|
||||
if expr.exprStructInitTypeArgs.len > 0:
|
||||
var suffix = ""
|
||||
@@ -642,6 +986,24 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
let argType = ctx.resolveTypeExpr(targ)
|
||||
suffix.add(argType.toString)
|
||||
structName = structName & "_" & suffix
|
||||
# Simple enum init: EnumName { tag: EnumName_Variant } -> EnumName_Variant
|
||||
var enumDecl: Decl = nil
|
||||
let enumSym = ctx.globalScope.lookup(structName)
|
||||
if enumSym != nil and enumSym.decl != nil and enumSym.decl.kind == dkEnum:
|
||||
enumDecl = enumSym.decl
|
||||
var isSimple = false
|
||||
if enumDecl != nil:
|
||||
for v in enumDecl.declEnumVariants:
|
||||
if v.fields.len > 0 or v.namedFields.len > 0:
|
||||
isSimple = true
|
||||
break
|
||||
isSimple = not isSimple
|
||||
if isSimple and expr.exprStructInitFields.len == 1 and expr.exprStructInitFields[0].name == "tag":
|
||||
let variantExpr = ctx.lowerExpr(expr.exprStructInitFields[0].value)
|
||||
return variantExpr
|
||||
var fields: seq[tuple[name: string, value: HirNode]] = @[]
|
||||
for f in expr.exprStructInitFields:
|
||||
fields.add((f.name, ctx.lowerExpr(f.value)))
|
||||
return HirNode(kind: hStructInit, structInitName: structName,
|
||||
structInitFields: fields, typ: typ, loc: loc)
|
||||
|
||||
@@ -721,7 +1083,7 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
|
||||
let tmpName = ctx.freshTryVar()
|
||||
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 tagPtr = HirNode(kind: hFieldPtr, fieldPtrBase: tmpVar, fieldName: "tag",
|
||||
@@ -826,6 +1188,56 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
let lowered = ctx.lowerExpr(expr.exprAwaitOperand)
|
||||
return hirCall("bux_async_await", @[lowered], makePointer(makeVoid()), loc)
|
||||
|
||||
of ekBorrow:
|
||||
# borrow &mut expr — lowered to the operand directly (borrow is a no-op in HIR)
|
||||
# The borrow checker validates before lowering
|
||||
return ctx.lowerExpr(expr.exprBorrowOperand)
|
||||
|
||||
of ekStringInterp:
|
||||
# Desugar string interpolation to chained String_Concat calls with conversions
|
||||
var resultNode: HirNode = nil
|
||||
for i in 0 ..< expr.exprInterpExprs.len:
|
||||
let textPart = expr.exprInterpTexts[i]
|
||||
let exprPart = expr.exprInterpExprs[i]
|
||||
# Text literal
|
||||
var textNode = HirNode(kind: hLit,
|
||||
litToken: Token(kind: tkStringLiteral, text: "\"" & textPart & "\"", loc: loc),
|
||||
typ: makeStr(), loc: loc)
|
||||
if resultNode == nil:
|
||||
resultNode = textNode
|
||||
else:
|
||||
resultNode = hirCall("String_Concat", @[resultNode, textNode], makeStr(), loc)
|
||||
# Expression part with conversion if needed
|
||||
let loweredExpr = ctx.lowerExpr(exprPart)
|
||||
let exprType = ctx.resolveExprType(exprPart)
|
||||
var convertedExpr = loweredExpr
|
||||
if exprType.kind == tkInt or exprType.kind == tkInt8 or exprType.kind == tkInt16 or
|
||||
exprType.kind == tkInt32 or exprType.kind == tkInt64 or
|
||||
exprType.kind == tkUInt or exprType.kind == tkUInt8 or exprType.kind == tkUInt16 or
|
||||
exprType.kind == tkUInt32 or exprType.kind == tkUInt64:
|
||||
convertedExpr = hirCall("String_FromInt", @[loweredExpr], makeStr(), loc)
|
||||
elif exprType.kind == tkFloat32 or exprType.kind == tkFloat64:
|
||||
convertedExpr = hirCall("String_FromFloat", @[loweredExpr], makeStr(), loc)
|
||||
elif exprType.kind == tkBool:
|
||||
convertedExpr = hirCall("String_FromBool", @[loweredExpr], makeStr(), loc)
|
||||
elif exprType.kind == tkStr:
|
||||
discard # already a string
|
||||
resultNode = hirCall("String_Concat", @[resultNode, convertedExpr], makeStr(), loc)
|
||||
# Add final text part
|
||||
let lastText = expr.exprInterpTexts[^1]
|
||||
var lastTextNode = HirNode(kind: hLit,
|
||||
litToken: Token(kind: tkStringLiteral, text: "\"" & lastText & "\"", loc: loc),
|
||||
typ: makeStr(), loc: loc)
|
||||
if resultNode == nil:
|
||||
resultNode = lastTextNode
|
||||
else:
|
||||
resultNode = hirCall("String_Concat", @[resultNode, lastTextNode], makeStr(), loc)
|
||||
return resultNode
|
||||
|
||||
of ekClosure:
|
||||
let f = ctx.lowerClosureFunc(expr)
|
||||
return hirUnary(tkAmp, hirVar(f.name, makeFunc(@[], makeVoid()), loc), typ, loc)
|
||||
|
||||
else:
|
||||
return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc),
|
||||
typ: makeVoid(), loc: loc)
|
||||
@@ -854,6 +1266,12 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
||||
of tekSlice:
|
||||
let elemType = ctx.resolveTypeExpr(stmt.stmtLetType.sliceElement)
|
||||
makeSlice(elemType)
|
||||
of tekFunc:
|
||||
var params: seq[Type] = @[]
|
||||
for p in stmt.stmtLetType.funcParams:
|
||||
params.add(ctx.resolveTypeExpr(p))
|
||||
let ret = if stmt.stmtLetType.funcRet != nil: ctx.resolveTypeExpr(stmt.stmtLetType.funcRet) else: makeVoid()
|
||||
makeFunc(params, ret)
|
||||
else: makeUnknown()
|
||||
elif stmt.stmtLetInit != nil:
|
||||
ctx.resolveExprType(stmt.stmtLetInit)
|
||||
@@ -878,11 +1296,33 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
||||
if initHir != nil:
|
||||
let store = hirStore(varNode, initHir, loc)
|
||||
stmts.add(store)
|
||||
# If init is a closure with captures, emit capture assignments
|
||||
if stmt.stmtLetInit != nil and stmt.stmtLetInit.kind == ekClosure and stmt.stmtLetInit.captureCount > 0:
|
||||
let closureIdx = ctx.varCounter - 1
|
||||
let envInst = "__closure_env_instance_" & $closureIdx
|
||||
var capStmts: seq[HirNode] = @[]
|
||||
for i in 0 ..< stmt.stmtLetInit.captureCount:
|
||||
let capName = stmt.stmtLetInit.captureNames[i]
|
||||
let capType = if i < stmt.stmtLetInit.captureTypeKinds.len: Type(kind: TypeKind(stmt.stmtLetInit.captureTypeKinds[i])) else: makeInt()
|
||||
let base = hirVar(envInst, makeNamed(""), loc)
|
||||
let field = HirNode(kind: hFieldAccess, fieldAccessName: capName, fieldAccessBase: base, typ: capType, loc: loc)
|
||||
let val = hirVar(capName, capType, loc)
|
||||
capStmts.add(hirAssign(field, val, loc))
|
||||
# Prepend capture assignments before the let
|
||||
var allStmts = capStmts
|
||||
allStmts.add(stmts)
|
||||
return hirBlock(allStmts, nil, makeVoid(), loc)
|
||||
return hirBlock(stmts, nil, makeVoid(), loc)
|
||||
|
||||
of skReturn:
|
||||
let value = if stmt.stmtReturnValue != nil: ctx.lowerExpr(stmt.stmtReturnValue) else: nil
|
||||
return ctx.flushPending(hirReturn(value, loc))
|
||||
var stmts = ctx.pendingStmts
|
||||
ctx.pendingStmts = @[]
|
||||
# Add defers in reverse order (LIFO)
|
||||
for i in countdown(ctx.deferStmts.len - 1, 0):
|
||||
stmts.add(ctx.deferStmts[i])
|
||||
stmts.add(hirReturn(value, loc))
|
||||
return hirBlock(stmts, nil, makeVoid(), loc)
|
||||
|
||||
of skIf:
|
||||
let cond = ctx.lowerExpr(stmt.stmtIfCond)
|
||||
@@ -945,7 +1385,8 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
||||
let inclusive = iterExpr.exprRangeInclusive
|
||||
|
||||
# Determine loop variable type from range bounds
|
||||
let varType = ctx.resolveExprType(iterExpr.exprRangeLo)
|
||||
let rangeType = ctx.resolveExprType(iterExpr)
|
||||
let varType = if rangeType.inner.len > 0: rangeType.inner[0] else: ctx.resolveExprType(iterExpr.exprRangeLo)
|
||||
|
||||
# Create: var i = lo; while i < hi { body; i = i + 1; }
|
||||
let initStmt = hirAlloca(varName, varType, loc)
|
||||
@@ -976,10 +1417,130 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
||||
let forBlock = hirBlock(@[initStmt, initStore, whileNode], nil, makeVoid(), loc, isScope = true)
|
||||
return ctx.flushPending(forBlock)
|
||||
|
||||
# Generic iterator for loop (simplified - just infinite loop for now)
|
||||
let loweredIter = ctx.lowerExpr(iterExpr)
|
||||
# Collection-based for: for x in collection { body }
|
||||
let collType = ctx.resolveExprType(iterExpr)
|
||||
let elemTypeExpr = ctx.getCollectionElementTypeExpr(iterExpr)
|
||||
let elemType = ctx.resolveTypeExpr(elemTypeExpr)
|
||||
# Resolve the collection type to its mangled struct instance (e.g. Array<int> -> Array_int).
|
||||
let collTypeMangled = substituteType(ctx, typeToTypeExpr(collType), ctx.typeSubst)
|
||||
|
||||
let isChannel = collType.kind == tkNamed and collType.name.startsWith("Channel")
|
||||
|
||||
if isChannel:
|
||||
# Channel lowering:
|
||||
# alloca x
|
||||
# while (true) {
|
||||
# if (!Channel_Recv_Ok_T(&ch, &x)) break;
|
||||
# body
|
||||
# }
|
||||
let recvOkName = ctx.generateMethodInstance("Channel_Recv_Ok", @[elemTypeExpr])
|
||||
|
||||
let xAlloca = hirAlloca(varName, elemType, loc)
|
||||
let xVar = hirVar(varName, elemType, loc)
|
||||
|
||||
ctx.varTypeExprs[varName] = elemTypeExpr
|
||||
|
||||
let chAddr = HirNode(kind: hUnary, unaryOp: tkAmp, unaryOperand: ctx.lowerExpr(iterExpr),
|
||||
typ: makePointer(collType), loc: loc)
|
||||
let xAddr = HirNode(kind: hUnary, unaryOp: tkAmp, unaryOperand: xVar,
|
||||
typ: makePointer(elemType), loc: loc)
|
||||
let recvOkCall = hirCall(recvOkName, @[chAddr, xAddr], makeBool(), loc)
|
||||
let notRecvOk = HirNode(kind: hUnary, unaryOp: tkBang, unaryOperand: recvOkCall,
|
||||
typ: makeBool(), loc: loc)
|
||||
let breakNode = HirNode(kind: hBreak, loc: loc)
|
||||
let ifNode = HirNode(kind: hIf, ifCond: notRecvOk, ifThen: breakNode, ifElse: nil,
|
||||
typ: makeVoid(), loc: loc)
|
||||
|
||||
let loweredBody = ctx.lowerBlock(body)
|
||||
var whileBodyStmts: seq[HirNode] = @[]
|
||||
whileBodyStmts.add(xAlloca)
|
||||
whileBodyStmts.add(ifNode)
|
||||
if loweredBody != nil:
|
||||
whileBodyStmts.add(loweredBody)
|
||||
let whileBody = hirBlock(whileBodyStmts, nil, makeVoid(), loc)
|
||||
|
||||
let trueLit = hirLit(Token(kind: tkBoolLiteral, text: "true", loc: loc), makeBool(), loc)
|
||||
let whileNode = HirNode(kind: hWhile, whileCond: trueLit, whileBody: whileBody,
|
||||
typ: makeVoid(), loc: loc)
|
||||
|
||||
let forBlock = hirBlock(@[whileNode], nil, makeVoid(), loc, isScope = true)
|
||||
return ctx.flushPending(forBlock)
|
||||
|
||||
# Array / Iter lowering:
|
||||
# alloca __iter
|
||||
# __iter = Array_Iter_T(&collection);
|
||||
# while (Iter_HasNext_T(&__iter)) {
|
||||
# alloca x
|
||||
# x = Iter_Next_T(&__iter);
|
||||
# body
|
||||
# }
|
||||
let iterFuncName = ctx.generateMethodInstance("Array_Iter", @[elemTypeExpr])
|
||||
let hasNextFuncName = ctx.generateMethodInstance("Iter_HasNext", @[elemTypeExpr])
|
||||
let nextFuncName = ctx.generateMethodInstance("Iter_Next", @[elemTypeExpr])
|
||||
|
||||
# Ensure Iter<T> struct instance exists and resolve its mangled name.
|
||||
let iterType = substituteType(ctx, TypeExpr(kind: tekNamed, typeName: "Iter", typeArgs: @[elemTypeExpr]), ctx.typeSubst)
|
||||
|
||||
let iterVarName = "__iter_" & varName & "_" & $ctx.varCounter
|
||||
inc ctx.varCounter
|
||||
|
||||
# Build collection pointer. If the collection is not a simple identifier, spill to a temp.
|
||||
var preStmts: seq[HirNode] = @[]
|
||||
var collPtr: HirNode = nil
|
||||
if iterExpr.kind == ekIdent:
|
||||
let collVar = hirVar(iterExpr.exprIdent, collType, loc)
|
||||
collPtr = HirNode(kind: hUnary, unaryOp: tkAmp, unaryOperand: collVar,
|
||||
typ: makePointer(collType), loc: loc)
|
||||
else:
|
||||
let collAllocaName = ctx.freshName()
|
||||
let collAlloca = hirAlloca(collAllocaName, collTypeMangled, loc)
|
||||
let collVarPtr = hirVar(collAllocaName, makePointer(collTypeMangled), loc)
|
||||
let collValue = ctx.lowerExpr(iterExpr)
|
||||
let collStore = hirStore(collVarPtr, collValue, loc)
|
||||
preStmts.add(collAlloca)
|
||||
preStmts.add(collStore)
|
||||
collPtr = HirNode(kind: hUnary, unaryOp: tkAmp,
|
||||
unaryOperand: hirVar(collAllocaName, collTypeMangled, loc),
|
||||
typ: makePointer(collTypeMangled), loc: loc)
|
||||
|
||||
let iterAlloca = hirAlloca(iterVarName, iterType, loc)
|
||||
let iterVarPtr = hirVar(iterVarName, makePointer(iterType), loc)
|
||||
let iterInitCall = hirCall(iterFuncName, @[collPtr], iterType, loc)
|
||||
let iterStore = hirStore(iterVarPtr, iterInitCall, loc)
|
||||
|
||||
preStmts.add(iterAlloca)
|
||||
preStmts.add(iterStore)
|
||||
|
||||
# while condition: Iter_HasNext_T(&__iter)
|
||||
let iterAddr = HirNode(kind: hUnary, unaryOp: tkAmp, unaryOperand: hirVar(iterVarName, iterType, loc),
|
||||
typ: makePointer(iterType), loc: loc)
|
||||
let condCall = hirCall(hasNextFuncName, @[iterAddr], makeBool(), loc)
|
||||
|
||||
# loop body: alloca x; x = Iter_Next_T(&__iter); body
|
||||
let xAlloca = hirAlloca(varName, elemType, loc)
|
||||
let xVarPtr = hirVar(varName, makePointer(elemType), loc)
|
||||
let iterAddr2 = HirNode(kind: hUnary, unaryOp: tkAmp, unaryOperand: hirVar(iterVarName, iterType, loc),
|
||||
typ: makePointer(iterType), loc: loc)
|
||||
let nextCall = hirCall(nextFuncName, @[iterAddr2], elemType, loc)
|
||||
let xStore = hirStore(xVarPtr, nextCall, loc)
|
||||
|
||||
ctx.varTypeExprs[varName] = elemTypeExpr
|
||||
let loweredBody = ctx.lowerBlock(body)
|
||||
return ctx.flushPending(HirNode(kind: hLoop, loopBody: loweredBody, typ: makeVoid(), loc: loc))
|
||||
|
||||
var bodyStmts: seq[HirNode] = @[]
|
||||
bodyStmts.add(xAlloca)
|
||||
bodyStmts.add(xStore)
|
||||
if loweredBody != nil:
|
||||
bodyStmts.add(loweredBody)
|
||||
let whileBody = hirBlock(bodyStmts, nil, makeVoid(), loc)
|
||||
|
||||
let whileNode = HirNode(kind: hWhile, whileCond: condCall, whileBody: whileBody,
|
||||
typ: makeVoid(), loc: loc)
|
||||
|
||||
var blockStmts = preStmts
|
||||
blockStmts.add(whileNode)
|
||||
let forBlock = hirBlock(blockStmts, nil, makeVoid(), loc, isScope = true)
|
||||
return ctx.flushPending(forBlock)
|
||||
|
||||
of skDoWhile:
|
||||
let body = ctx.lowerBlock(stmt.stmtDoWhileBody)
|
||||
@@ -997,6 +1558,29 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
||||
return ctx.flushPending(HirNode(kind: hMatch, matchSubject: subject, matchArms: arms,
|
||||
typ: makeVoid(), loc: loc))
|
||||
|
||||
of skSwitch:
|
||||
let subject = ctx.lowerExpr(stmt.stmtSwitchExpr)
|
||||
var current: HirNode = nil
|
||||
# Build if-else chain from bottom up (default first)
|
||||
if stmt.stmtSwitchDefault != nil:
|
||||
current = ctx.lowerBlock(stmt.stmtSwitchDefault)
|
||||
# Cases in reverse order
|
||||
for i in countdown(stmt.stmtSwitchCases.len - 1, 0):
|
||||
let caseBranch = stmt.stmtSwitchCases[i]
|
||||
let caseVal = ctx.lowerExpr(caseBranch.caseValue)
|
||||
let caseBody = ctx.lowerBlock(caseBranch.caseBody)
|
||||
let cond = HirNode(kind: hBinary, binaryOp: tkEq,
|
||||
binaryLeft: subject, binaryRight: caseVal,
|
||||
typ: makeBool(), loc: caseBranch.loc)
|
||||
current = HirNode(kind: hIf, ifCond: cond, ifThen: caseBody, ifElse: current,
|
||||
typ: makeVoid(), loc: caseBranch.loc)
|
||||
return ctx.flushPending(current)
|
||||
|
||||
of skDefer:
|
||||
let body = ctx.lowerExpr(stmt.stmtDeferBody)
|
||||
ctx.deferStmts.add(body)
|
||||
return nil
|
||||
|
||||
of skDecl:
|
||||
return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc),
|
||||
typ: makeVoid(), loc: loc)
|
||||
@@ -1020,8 +1604,7 @@ proc lowerBlock(ctx: var LowerCtx, blk: Block): HirNode =
|
||||
# but for hVar/hLit/hCall etc. we could treat them as block expr.
|
||||
# For now, leave as-is to avoid breaking control-flow statements.
|
||||
discard
|
||||
return HirNode(kind: hBlock, blockStmts: stmts, blockExpr: expr,
|
||||
typ: if expr != nil: expr.typ else: makeVoid(), loc: blk.loc)
|
||||
return hirBlock(stmts, expr, if expr != nil: expr.typ else: makeVoid(), blk.loc, isScope = true)
|
||||
|
||||
proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
|
||||
# Set up type substitution for generic functions
|
||||
@@ -1053,8 +1636,6 @@ proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
|
||||
if p.ptype != nil:
|
||||
pType = substituteType(ctx, p.ptype, ctx.typeSubst)
|
||||
params.add((p.name, pType))
|
||||
if p.ptype != nil:
|
||||
ctx.varTypeExprs[p.name] = p.ptype
|
||||
|
||||
var retType = makeVoid()
|
||||
if funcReturnType != nil:
|
||||
@@ -1066,7 +1647,28 @@ proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
|
||||
ctx.currentFuncRetType = retType
|
||||
ctx.currentFuncDecl = decl
|
||||
ctx.varTypeExprs = initTable[string, TypeExpr]() # Clear local vars for new function
|
||||
let body = if funcBody != nil: ctx.lowerBlock(funcBody) else: nil
|
||||
# Add parameters to varTypeExprs after clearing so they are visible in the body.
|
||||
for p in funcParams:
|
||||
if p.ptype != nil:
|
||||
ctx.varTypeExprs[p.name] = p.ptype
|
||||
var body = if funcBody != nil: ctx.lowerBlock(funcBody) else: nil
|
||||
|
||||
# Inject remaining defers at end of function (for implicit return)
|
||||
if ctx.deferStmts.len > 0 and body != nil and body.kind == hBlock:
|
||||
# Only add if last statement is not already a return (defers already injected there)
|
||||
var hasReturn = false
|
||||
if body.blockStmts.len > 0 and body.blockStmts[^1].kind == hReturn:
|
||||
hasReturn = true
|
||||
elif body.blockStmts.len > 0 and body.blockStmts[^1].kind == hBlock:
|
||||
# Check nested block's last statement
|
||||
let last = body.blockStmts[^1]
|
||||
if last.blockStmts.len > 0 and last.blockStmts[^1].kind == hReturn:
|
||||
hasReturn = true
|
||||
if not hasReturn:
|
||||
for i in countdown(ctx.deferStmts.len - 1, 0):
|
||||
body.blockStmts.add(ctx.deferStmts[i])
|
||||
ctx.deferStmts = @[]
|
||||
|
||||
ctx.currentFuncDecl = oldFuncDecl
|
||||
ctx.currentFuncRetType = oldFuncRetType
|
||||
ctx.varTypeExprs = oldVarTypeExprs
|
||||
@@ -1117,6 +1719,40 @@ proc generateMethodInstance(ctx: var LowerCtx, baseMethodName: string, typeArgs:
|
||||
ctx.generatedFuncInsts[mangledName] = true
|
||||
return mangledName
|
||||
|
||||
proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc =
|
||||
let name = "__closure_" & $ctx.varCounter
|
||||
inc ctx.varCounter
|
||||
var f = HirFunc(name: name, isPublic: false)
|
||||
# Copy capture metadata
|
||||
if expr.captureCount > 0:
|
||||
f.captureNames = expr.captureNames
|
||||
for tk in expr.captureTypeKinds:
|
||||
f.captureTypes.add(Type(kind: TypeKind(tk)))
|
||||
f.envStructName = "__closure_env_" & $(ctx.varCounter - 1)
|
||||
f.envInstanceName = "__closure_env_instance_" & $(ctx.varCounter - 1)
|
||||
# Params
|
||||
for p in expr.exprClosureParams:
|
||||
f.params.add((name: p.name, typ: if p.ptype != nil: ctx.resolveTypeExpr(p.ptype) else: makeUnknown()))
|
||||
# Return type
|
||||
if expr.exprClosureReturnType != nil:
|
||||
f.retType = ctx.resolveTypeExpr(expr.exprClosureReturnType)
|
||||
else:
|
||||
f.retType = makeVoid()
|
||||
# Body with closure rewriting
|
||||
let savedDepth = ctx.closureDepth
|
||||
let savedExpr = ctx.currentClosureExpr
|
||||
let savedEnv = ctx.envInstanceName
|
||||
ctx.closureDepth = ctx.closureDepth + 1
|
||||
ctx.currentClosureExpr = expr
|
||||
ctx.envInstanceName = f.envInstanceName
|
||||
if expr.exprClosureBody != nil:
|
||||
f.body = ctx.lowerBlock(expr.exprClosureBody)
|
||||
ctx.closureDepth = savedDepth
|
||||
ctx.currentClosureExpr = savedExpr
|
||||
ctx.envInstanceName = savedEnv
|
||||
ctx.extraFuncs.add(f)
|
||||
return f
|
||||
|
||||
proc lowerModule*(module: Module, sema: Sema): HirModule =
|
||||
var ctx = initLowerCtx(module, sema)
|
||||
var funcs: seq[HirFunc] = @[]
|
||||
@@ -1287,6 +1923,9 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
|
||||
var found = false
|
||||
for avail in methods:
|
||||
if avail.name == req.declFuncName:
|
||||
# Skip generic methods — they have no concrete C function to put in vtable
|
||||
if avail.decl.declFuncTypeParams.len > 0:
|
||||
break
|
||||
found = true
|
||||
methodNames.add(req.declFuncName)
|
||||
break
|
||||
|
||||
+4
-23
@@ -61,12 +61,6 @@ proc advance(lex: var Lexer): char =
|
||||
else:
|
||||
inc lex.col
|
||||
|
||||
proc match(lex: var Lexer, expected: char): bool =
|
||||
if lex.isAtEnd(): return false
|
||||
if lex.peek() != expected: return false
|
||||
discard lex.advance()
|
||||
return true
|
||||
|
||||
proc matchStr(lex: var Lexer, s: string): bool =
|
||||
for i, c in s:
|
||||
if lex.peek(i) != c:
|
||||
@@ -81,9 +75,6 @@ proc currentLocation(lex: Lexer): SourceLocation =
|
||||
proc emitError(lex: var Lexer, loc: SourceLocation, message: string) =
|
||||
lex.diagnostics.add(LexerDiagnostic(severity: ldsError, loc: loc, message: message))
|
||||
|
||||
proc emitWarning(lex: var Lexer, loc: SourceLocation, message: string) =
|
||||
lex.diagnostics.add(LexerDiagnostic(severity: ldsWarning, loc: loc, message: message))
|
||||
|
||||
proc makeToken(lex: Lexer, kind: TokenKind, startLoc: SourceLocation, startPos: int): Token =
|
||||
let text = lex.source[startPos ..< lex.pos]
|
||||
result = Token(kind: kind, text: text, loc: startLoc)
|
||||
@@ -337,19 +328,6 @@ proc scanSymbol(lex: var Lexer, startLoc: SourceLocation): Token =
|
||||
else:
|
||||
return lex.makeToken(kind1, startLoc, startPos)
|
||||
|
||||
template check3(c2: char, kind2: TokenKind, c3: char, kind3: TokenKind, kind1: TokenKind) =
|
||||
if lex.peek() == c2:
|
||||
discard lex.advance()
|
||||
if lex.peek() == c3:
|
||||
discard lex.advance()
|
||||
return lex.makeToken(kind3, startLoc, startPos)
|
||||
return lex.makeToken(kind2, startLoc, startPos)
|
||||
else:
|
||||
return lex.makeToken(kind1, startLoc, startPos)
|
||||
|
||||
template checkEq(c2: char, kind2: TokenKind, kind1: TokenKind) =
|
||||
check2(c2, kind2, kind1)
|
||||
|
||||
case c1
|
||||
of '(': return lex.makeToken(tkLParen, startLoc, startPos)
|
||||
of ')': return lex.makeToken(tkRParen, startLoc, startPos)
|
||||
@@ -527,7 +505,10 @@ proc nextToken(lex: var Lexer): Token =
|
||||
discard lex.advance()
|
||||
return Token(kind: tkNewLine, text: "\n", loc: startLoc)
|
||||
|
||||
# String prefixes: c8" c16" c32" — must come before ident check
|
||||
# String prefixes: f" c8" c16" c32" — must come before ident check
|
||||
if c == 'f' and lex.peek(1) == '"':
|
||||
discard lex.advance() # f
|
||||
return lex.scanString(startLoc, 1)
|
||||
if c == 'c' and lex.peek(1) in {'8', '1', '3'}:
|
||||
let d = lex.peek(1)
|
||||
if d == '8' and lex.peek(2) == '"':
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
## LIR — Low-level Intermediate Representation
|
||||
## Linear 3-address code IR, designed for straightforward C emission.
|
||||
## Each HIR construct lowers to 5-30 LIR instructions.
|
||||
|
||||
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
|
||||
@@ -0,0 +1,744 @@
|
||||
## 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, sets]
|
||||
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 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 cParamDecl(cType, name: string): string =
|
||||
## Emit a C parameter declaration, handling function-pointer syntax.
|
||||
if cType.contains("(*)"):
|
||||
return cType.replace("(*)", "(*" & name & ")")
|
||||
else:
|
||||
return cType & " " & name
|
||||
|
||||
# ── 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(cParamDecl(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], funcPtrTypes: Table[string, string]) =
|
||||
var paramsStr = ""
|
||||
for i, p in f.params:
|
||||
if i > 0: paramsStr.add(", ")
|
||||
paramsStr.add(cParamDecl(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:
|
||||
if funcPtrTypes.hasKey(instr.src.strVal):
|
||||
newType = funcPtrTypes[instr.src.strVal]
|
||||
else:
|
||||
newType = "void*";
|
||||
of 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(cParamDecl(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
|
||||
of tkFunc:
|
||||
if typ.inner.len == 0: return "void (*)(void)"
|
||||
let params = typ.inner[0..^2].mapIt(typeToCStr(it)).join(", ")
|
||||
let ret = typeToCStr(typ.inner[^1])
|
||||
return ret & " (*)(" & params & ")"
|
||||
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("")
|
||||
|
||||
# ── Type dependency ordering ──
|
||||
|
||||
proc collectValueDeps(typ: Type): seq[string] =
|
||||
## Return type names that must be fully defined before a value of `typ`
|
||||
## can be declared. Pointers/refs only need a forward declaration, so
|
||||
## they do not introduce a dependency.
|
||||
if typ == nil: return @[]
|
||||
case typ.kind
|
||||
of tkNamed:
|
||||
return @[typ.name]
|
||||
of tkSlice:
|
||||
return @[typeToCStr(typ)]
|
||||
of tkPointer, tkRef, tkMutRef, tkTuple, tkFunc:
|
||||
return @[]
|
||||
else:
|
||||
return @[]
|
||||
|
||||
proc emitSliceTypeDef(be: var LirCBackend, name: string, elem: string) =
|
||||
be.emitLine(&"typedef struct {{ {elem}* data; size_t len; }} {name};")
|
||||
|
||||
# ── 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)
|
||||
|
||||
# Build function-pointer type lookup table (for address-of)
|
||||
var funcPtrTypes = initTable[string, string]()
|
||||
for f in module.funcs:
|
||||
let params = f.params.mapIt(typeToCStr(it.typ)).join(", ")
|
||||
let ret = typeToCStr(f.retType)
|
||||
funcPtrTypes[f.name] = ret & " (*)(" & params & ")"
|
||||
for f in module.externFuncs:
|
||||
let params = f.params.mapIt(typeToCStr(it.typ)).join(", ")
|
||||
let ret = typeToCStr(f.retType)
|
||||
funcPtrTypes[f.name] = ret & " (*)(" & params & ")"
|
||||
|
||||
# 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(cParamDecl(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("")
|
||||
|
||||
# Collect local type names (structs and enums defined in this module).
|
||||
var localTypeNames: HashSet[string]
|
||||
for s in module.structs:
|
||||
localTypeNames.incl(s.name)
|
||||
for e in module.enums:
|
||||
localTypeNames.incl(e.name)
|
||||
|
||||
# Collect slice types used in struct fields and enum payloads.
|
||||
var sliceTypes: seq[tuple[name: string, elem: string]] = @[]
|
||||
var sliceNames: HashSet[string]
|
||||
proc registerSlice(t: Type) =
|
||||
if t == nil or t.kind != tkSlice: return
|
||||
let name = typeToCStr(t)
|
||||
if sliceNames.contains(name): return
|
||||
sliceNames.incl(name)
|
||||
let elem = if t.inner.len > 0: typeToCStr(t.inner[0]) else: "void"
|
||||
sliceTypes.add((name, elem))
|
||||
|
||||
for s in module.structs:
|
||||
for f in s.fields:
|
||||
registerSlice(f.typ)
|
||||
for e in module.enums:
|
||||
for v in e.variants:
|
||||
for ft in v.fields:
|
||||
registerSlice(ft)
|
||||
for nf in v.namedFields:
|
||||
registerSlice(nf.typ)
|
||||
|
||||
# Build dependency graph among structs, enums, and slice types.
|
||||
# Edge A -> B means "A depends on B, so B must be emitted before A".
|
||||
var deps: Table[string, seq[string]]
|
||||
for s in module.structs:
|
||||
deps[s.name] = @[]
|
||||
for e in module.enums:
|
||||
deps[e.name] = @[]
|
||||
for st in sliceTypes:
|
||||
deps[st.name] = @[]
|
||||
|
||||
proc addDeps(node: string, t: Type) =
|
||||
for dep in collectValueDeps(t):
|
||||
if dep == node: continue
|
||||
if localTypeNames.contains(dep) or sliceNames.contains(dep):
|
||||
if dep notin deps[node]:
|
||||
deps[node].add(dep)
|
||||
|
||||
for s in module.structs:
|
||||
for f in s.fields:
|
||||
addDeps(s.name, f.typ)
|
||||
for e in module.enums:
|
||||
for v in e.variants:
|
||||
for ft in v.fields:
|
||||
addDeps(e.name, ft)
|
||||
for nf in v.namedFields:
|
||||
addDeps(e.name, nf.typ)
|
||||
|
||||
# Topological sort (Kahn's algorithm).
|
||||
var inDegree: Table[string, int]
|
||||
var dependents: Table[string, seq[string]]
|
||||
for node in deps.keys:
|
||||
inDegree[node] = 0
|
||||
for node, nodeDeps in deps:
|
||||
for d in nodeDeps:
|
||||
if not inDegree.hasKey(d): inDegree[d] = 0
|
||||
inDegree[node] += 1
|
||||
dependents.mgetOrPut(d, @[]).add(node)
|
||||
|
||||
var queue: seq[string] = @[]
|
||||
for node, deg in inDegree:
|
||||
if deg == 0:
|
||||
queue.add(node)
|
||||
|
||||
var sorted: seq[string] = @[]
|
||||
while queue.len > 0:
|
||||
let node = queue.pop()
|
||||
sorted.add(node)
|
||||
for depNode in dependents.getOrDefault(node):
|
||||
inDegree[depNode] -= 1
|
||||
if inDegree[depNode] == 0:
|
||||
queue.add(depNode)
|
||||
|
||||
if sorted.len < deps.len:
|
||||
# Cycle detected; fall back to a safe deterministic order.
|
||||
sorted = @[]
|
||||
for s in module.structs: sorted.add(s.name)
|
||||
for e in module.enums: sorted.add(e.name)
|
||||
for st in sliceTypes: sorted.add(st.name)
|
||||
|
||||
# Map type names back to their definitions.
|
||||
var structMap: Table[string, seq[tuple[name: string, typ: Type]]]
|
||||
for s in module.structs: structMap[s.name] = s.fields
|
||||
var enumMap: Table[string, seq[HirEnumVariant]]
|
||||
for e in module.enums: enumMap[e.name] = e.variants
|
||||
var sliceMap: Table[string, string]
|
||||
for st in sliceTypes: sliceMap[st.name] = st.elem
|
||||
|
||||
# Emit type definitions in dependency order.
|
||||
for name in sorted:
|
||||
if structMap.hasKey(name):
|
||||
be.emitStructDef(name, structMap[name])
|
||||
elif enumMap.hasKey(name):
|
||||
be.emitEnumDef(name, enumMap[name])
|
||||
elif sliceMap.hasKey(name):
|
||||
be.emitSliceTypeDef(name, sliceMap[name])
|
||||
|
||||
# Forward function declarations
|
||||
for f in module.funcs:
|
||||
let rt = typeToCStr(f.retType)
|
||||
var params: seq[string] = @[]
|
||||
for p in f.params:
|
||||
params.add(cParamDecl(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(cParamDecl(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 env structs for closures with captures
|
||||
for f in module.funcs:
|
||||
if f.captureNames.len > 0 and f.envStructName != "":
|
||||
be.emitLine(&"struct {f.envStructName} {{")
|
||||
be.indent += 1
|
||||
for i in 0 ..< f.captureNames.len:
|
||||
let capName = f.captureNames[i]
|
||||
let capType = if i < f.captureTypes.len: typeToCStr(f.captureTypes[i]) else: "int"
|
||||
be.emitLine(&"{capType} {capName};")
|
||||
be.indent -= 1
|
||||
be.emitLine("};")
|
||||
be.emitLine(&"static struct {f.envStructName} {f.envInstanceName};")
|
||||
be.emitLine("")
|
||||
|
||||
# Emit all LIR functions
|
||||
for f in builder.funcs:
|
||||
be.emitFunc(f, funcRetTypes, funcPtrTypes)
|
||||
|
||||
# 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
|
||||
@@ -0,0 +1,795 @@
|
||||
## 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 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
|
||||
of tkFunc:
|
||||
if typ.inner.len == 0: return "void (*)(void)"
|
||||
let params = typ.inner[0..^2].mapIt(typeToCStr(it)).join(", ")
|
||||
let ret = typeToCStr(typ.inner[^1])
|
||||
return ret & " (*)(" & params & ")"
|
||||
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 hFieldAccess:
|
||||
let base = lowerExpr(ctx, node.fieldAccessBase)
|
||||
let cType = hirTypeToC(ctx, node)
|
||||
let t = b.freshTemp()
|
||||
b.emitAlloca(t.strVal, cType)
|
||||
b.emitRawC(&"{t.strVal} = {lirValToC(base)}.{node.fieldAccessName};")
|
||||
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)
|
||||
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 hFieldAccess:
|
||||
let baseStr = buildLval(ctx, n.fieldAccessBase)
|
||||
return baseStr & "." & n.fieldAccessName
|
||||
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 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 hFieldAccess:
|
||||
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:
|
||||
# If block is an expression, result is unused at statement level
|
||||
discard lowerExpr(ctx, node.blockExpr)
|
||||
if node.isScope:
|
||||
b.emitRawC("}")
|
||||
|
||||
# ── Emit (inline C) ──
|
||||
of hEmit:
|
||||
b.emitRawC(node.emitCode)
|
||||
|
||||
# ── Expression statement ──
|
||||
else:
|
||||
# Expression evaluated for side effects; temp is unused
|
||||
discard lowerExpr(ctx, node)
|
||||
|
||||
# ── 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
|
||||
@@ -90,7 +90,6 @@ proc parseInlineTable(s: string): OrderedTableRef[string, TomlValue] =
|
||||
if content[i] == '{': inc braceCount
|
||||
elif content[i] == '}': dec braceCount
|
||||
inc i
|
||||
let val = content[valStart ..< i]
|
||||
result[key] = TomlValue(kind: tvkInlineTable, inlineVal: newOrderedTable[string, TomlValue]())
|
||||
else:
|
||||
while i < content.len and content[i] notin {',', ' ', '\t'}:
|
||||
|
||||
+191
-11
@@ -1,5 +1,5 @@
|
||||
import std/strutils
|
||||
import token, source_location, ast
|
||||
import token, source_location, ast, lexer
|
||||
|
||||
type
|
||||
ParserDiagnosticSeverity* = enum
|
||||
@@ -48,6 +48,12 @@ proc advance(p: var Parser): Token =
|
||||
if p.pos < p.tokens.len:
|
||||
inc p.pos
|
||||
|
||||
proc hasErrors*(p: Parser): bool =
|
||||
for d in p.diagnostics:
|
||||
if d.severity == pdsError:
|
||||
return true
|
||||
return false
|
||||
|
||||
proc check(p: Parser, kind: TokenKind): bool =
|
||||
p.peek() == kind
|
||||
|
||||
@@ -56,12 +62,6 @@ proc checkAny(p: Parser, kinds: openArray[TokenKind]): bool =
|
||||
if p.peek() == k: return true
|
||||
return false
|
||||
|
||||
proc match(p: var Parser, kind: TokenKind): bool =
|
||||
if p.check(kind):
|
||||
discard p.advance()
|
||||
return true
|
||||
return false
|
||||
|
||||
proc isTypeArgListAhead(p: Parser): bool =
|
||||
## Lookahead to determine if '<' starts a type argument list.
|
||||
## Returns true if we can find a matching '>' before EOF, '{', or ';'.
|
||||
@@ -169,6 +169,7 @@ type
|
||||
callConv*: CallingConvention
|
||||
targetOs*: string
|
||||
checked*: bool ## @[Checked] — enable borrow checking
|
||||
shared*: bool ## @[Shared] — mark function as thread-safe
|
||||
|
||||
proc parseAttrs(p: var Parser): ParsedAttrs =
|
||||
while p.check(tkAt):
|
||||
@@ -177,6 +178,8 @@ proc parseAttrs(p: var Parser): ParsedAttrs =
|
||||
let name = p.expect(tkIdent, "expected attribute name").text
|
||||
if name == "Checked":
|
||||
result.checked = true
|
||||
elif name == "Shared":
|
||||
result.shared = true
|
||||
elif name == "Import":
|
||||
discard p.expect(tkLParen, "expected '('")
|
||||
let key = p.expect(tkIdent, "expected attribute key").text
|
||||
@@ -201,6 +204,10 @@ proc parseBaseType(p: var Parser): TypeExpr =
|
||||
var typeArgs: seq[TypeExpr] = @[]
|
||||
discard p.advance()
|
||||
while not p.check(tkGt) and not p.isAtEnd:
|
||||
while p.check(tkNewLine):
|
||||
discard p.advance()
|
||||
if p.check(tkGt) or p.isAtEnd:
|
||||
break
|
||||
typeArgs.add(p.parseType())
|
||||
if p.check(tkComma):
|
||||
discard p.advance()
|
||||
@@ -235,6 +242,20 @@ proc parseBaseType(p: var Parser): TypeExpr =
|
||||
discard p.advance()
|
||||
discard p.expect(tkRParen, "expected ')' to close tuple type")
|
||||
return TypeExpr(kind: tekTuple, loc: loc, tupleElements: elems)
|
||||
of tkFunc:
|
||||
discard p.advance() # func
|
||||
discard p.expect(tkLParen, "expected '(' after 'func'")
|
||||
var params: seq[TypeExpr] = @[]
|
||||
while not p.check(tkRParen) and not p.isAtEnd:
|
||||
params.add(p.parseType())
|
||||
if p.check(tkComma):
|
||||
discard p.advance()
|
||||
discard p.expect(tkRParen, "expected ')' to close func param types")
|
||||
var ret: TypeExpr = nil
|
||||
if p.check(tkArrow):
|
||||
discard p.advance()
|
||||
ret = p.parseType()
|
||||
return TypeExpr(kind: tekFunc, loc: loc, funcParams: params, funcRet: ret)
|
||||
else:
|
||||
p.emitError(loc, "expected type expression")
|
||||
return TypeExpr(kind: tekNamed, loc: loc, typeName: "")
|
||||
@@ -307,6 +328,10 @@ proc parsePrimaryPattern(p: var Parser): Pattern =
|
||||
discard p.advance()
|
||||
var fields: seq[tuple[name: string, pattern: Pattern]] = @[]
|
||||
while not p.check(tkRBrace) and not p.isAtEnd:
|
||||
while p.check(tkNewLine):
|
||||
discard p.advance()
|
||||
if p.check(tkRBrace) or p.isAtEnd:
|
||||
break
|
||||
let fieldName = p.expect(tkIdent, "expected field name in struct pattern").text
|
||||
discard p.expect(tkColon, "expected ':' after field name in pattern")
|
||||
fields.add((fieldName, p.parsePattern()))
|
||||
@@ -380,11 +405,77 @@ proc parseAssign(p: var Parser): Expr
|
||||
proc parseExpr(p: var Parser): Expr =
|
||||
p.parseAssign()
|
||||
|
||||
proc parseStringInterpolation(p: var Parser, tok: Token): Expr =
|
||||
## Parse a string literal that contains {expr} interpolations.
|
||||
let text = tok.text
|
||||
# Only f"..." strings support interpolation; plain "...", backtick, and c8/c16/c32 are raw.
|
||||
if text.len < 3 or text[0] != 'f' or text[1] != '"' or text[^1] != '"':
|
||||
return newLiteralExpr(tok)
|
||||
let inner = text[2 ..< ^1] # strip f" ... "
|
||||
var texts: seq[string] = @[]
|
||||
var exprs: seq[Expr] = @[]
|
||||
var i = 0
|
||||
var currentText = ""
|
||||
while i < inner.len:
|
||||
if inner[i] == '\\' and i + 1 < inner.len:
|
||||
if inner[i + 1] == '{':
|
||||
currentText.add('{')
|
||||
i += 2
|
||||
continue
|
||||
elif inner[i + 1] == '}':
|
||||
currentText.add('}')
|
||||
i += 2
|
||||
continue
|
||||
elif inner[i] == '{':
|
||||
texts.add(currentText)
|
||||
currentText = ""
|
||||
var j = i + 1
|
||||
var depth = 1
|
||||
while j < inner.len and depth > 0:
|
||||
if inner[j] == '{': depth += 1
|
||||
elif inner[j] == '}': depth -= 1
|
||||
j += 1
|
||||
if depth != 0:
|
||||
p.diagnostics.add(ParserDiagnostic(severity: pdsError, loc: tok.loc,
|
||||
message: "unmatched '{' in string interpolation"))
|
||||
return newLiteralExpr(tok)
|
||||
let exprStr = inner[i + 1 ..< j - 1]
|
||||
if exprStr.len == 0:
|
||||
p.diagnostics.add(ParserDiagnostic(severity: pdsError, loc: tok.loc,
|
||||
message: "empty interpolation {}"))
|
||||
return newLiteralExpr(tok)
|
||||
let lexRes = tokenize(exprStr, p.sourceName & "<interp>")
|
||||
if lexRes.hasErrors:
|
||||
p.diagnostics.add(ParserDiagnostic(severity: pdsError, loc: tok.loc,
|
||||
message: "invalid expression in string interpolation: " & exprStr))
|
||||
return newLiteralExpr(tok)
|
||||
var subParser = initParser(lexRes.tokens, p.sourceName & "<interp>")
|
||||
let expr = subParser.parseExpr()
|
||||
if subParser.hasErrors:
|
||||
for d in subParser.diagnostics:
|
||||
p.diagnostics.add(ParserDiagnostic(severity: pdsError, loc: tok.loc,
|
||||
message: "interpolation parse error: " & d.message))
|
||||
return newLiteralExpr(tok)
|
||||
exprs.add(expr)
|
||||
i = j
|
||||
continue
|
||||
currentText.add(inner[i])
|
||||
i += 1
|
||||
texts.add(currentText)
|
||||
if exprs.len == 0:
|
||||
return newLiteralExpr(tok)
|
||||
return newStringInterpExpr(texts, exprs, tok.loc)
|
||||
|
||||
proc parsePrimary(p: var Parser): Expr =
|
||||
while p.check(tkNewLine):
|
||||
discard p.advance()
|
||||
let loc = p.currentLoc
|
||||
case p.peek()
|
||||
of tkIntLiteral, tkFloatLiteral, tkStringLiteral, tkCharLiteral, tkBoolLiteral:
|
||||
return newLiteralExpr(p.advance())
|
||||
let tok = p.advance()
|
||||
if tok.kind == tkStringLiteral:
|
||||
return parseStringInterpolation(p, tok)
|
||||
return newLiteralExpr(tok)
|
||||
of tkSelf:
|
||||
discard p.advance()
|
||||
return Expr(kind: ekSelf, loc: loc)
|
||||
@@ -490,6 +581,26 @@ proc parsePrimary(p: var Parser): Expr =
|
||||
of tkNull:
|
||||
discard p.advance()
|
||||
return newLiteralExpr(Token(kind: tkNull, text: "null", loc: loc))
|
||||
of tkPipe:
|
||||
# Closure: |params| -> Ret { body }
|
||||
discard p.advance() # |
|
||||
var params: seq[Param] = @[]
|
||||
while not p.check(tkPipe) and not p.isAtEnd:
|
||||
let nameTok = p.expect(tkIdent, "expected parameter name in closure")
|
||||
discard p.expect(tkColon, "expected ':' in closure parameter")
|
||||
let ptype = p.parseType()
|
||||
params.add(Param(name: nameTok.text, ptype: ptype))
|
||||
if p.check(tkComma):
|
||||
discard p.advance()
|
||||
else:
|
||||
break
|
||||
discard p.expect(tkPipe, "expected '|' to close closure params")
|
||||
var retType: TypeExpr = nil
|
||||
if p.check(tkArrow):
|
||||
discard p.advance() # ->
|
||||
retType = p.parseType()
|
||||
let body = p.parseBlock()
|
||||
return Expr(kind: ekClosure, loc: loc, exprClosureParams: params, exprClosureBody: body, exprClosureReturnType: retType)
|
||||
else:
|
||||
p.emitError(loc, "expected expression")
|
||||
discard p.advance()
|
||||
@@ -498,23 +609,35 @@ proc parsePrimary(p: var Parser): Expr =
|
||||
proc parsePostfix(p: var Parser): Expr =
|
||||
var left = p.parsePrimary()
|
||||
while true:
|
||||
while p.check(tkNewLine):
|
||||
discard p.advance()
|
||||
let loc = p.currentLoc
|
||||
case p.peek()
|
||||
of tkLParen:
|
||||
# Call expression
|
||||
discard p.advance()
|
||||
var args: seq[Expr] = @[]
|
||||
var argNames: seq[string] = @[]
|
||||
while not p.check(tkRParen) and not p.isAtEnd:
|
||||
if p.check(tkDotDotDot):
|
||||
discard p.advance()
|
||||
let operand = p.parseExpr()
|
||||
args.add(Expr(kind: ekSpread, loc: operand.loc, exprSpreadOperand: operand))
|
||||
argNames.add("")
|
||||
elif p.peek() == tkIdent and p.peek(1) == tkColon:
|
||||
# Named argument: name: value
|
||||
let nameTok = p.advance()
|
||||
discard p.advance() # :
|
||||
let value = p.parseExpr()
|
||||
args.add(value)
|
||||
argNames.add(nameTok.text)
|
||||
else:
|
||||
args.add(p.parseExpr())
|
||||
argNames.add("")
|
||||
if p.check(tkComma):
|
||||
discard p.advance()
|
||||
discard p.expect(tkRParen, "expected ')' to close call")
|
||||
left = Expr(kind: ekCall, loc: loc, exprCallCallee: left, exprCallArgs: args)
|
||||
left = Expr(kind: ekCall, loc: loc, exprCallCallee: left, exprCallArgs: args, exprCallArgNames: argNames)
|
||||
of tkLt:
|
||||
# Generic type arguments: Max<int>(10, 20)
|
||||
# Only treat '<' as generic args if lookahead confirms a matching '>'
|
||||
@@ -594,8 +717,18 @@ proc parsePostfix(p: var Parser): Expr =
|
||||
return left
|
||||
|
||||
proc parseUnary(p: var Parser): Expr =
|
||||
while p.check(tkNewLine):
|
||||
discard p.advance()
|
||||
let loc = p.currentLoc
|
||||
case p.peek()
|
||||
of tkBorrow:
|
||||
discard p.advance() # borrow
|
||||
discard p.expect(tkAmp, "expected '&' after 'borrow'")
|
||||
var isMut = p.check(tkMut)
|
||||
if isMut:
|
||||
discard p.advance() # mut
|
||||
let operand = p.parseUnary() # parse the moved value
|
||||
return Expr(kind: ekBorrow, loc: loc, exprBorrowOperand: operand, exprBorrowMutable: isMut)
|
||||
of tkBang, tkMinus, tkTilde, tkStar, tkAmp:
|
||||
let op = p.advance().kind
|
||||
if op == tkAmp and p.check(tkMut):
|
||||
@@ -649,7 +782,6 @@ proc parseShift(p: var Parser): Expr =
|
||||
return left
|
||||
|
||||
proc parseCast(p: var Parser): Expr =
|
||||
let loc = p.currentLoc
|
||||
var left = p.parseShift()
|
||||
# 'as' and 'is' are handled in postfix for chaining
|
||||
return left
|
||||
@@ -876,6 +1008,37 @@ proc parseStmt(p: var Parser): Stmt =
|
||||
discard p.advance()
|
||||
discard p.expect(tkRBrace, "expected '}' to close match")
|
||||
return Stmt(kind: skExpr, loc: loc, stmtExpr: Expr(kind: ekMatch, loc: loc, exprMatchSubject: subject, exprMatchArms: arms))
|
||||
of tkSwitch:
|
||||
discard p.advance()
|
||||
p.structInitAllowed = false
|
||||
let subject = p.parseExpr()
|
||||
p.structInitAllowed = true
|
||||
while p.check(tkNewLine):
|
||||
discard p.advance()
|
||||
discard p.expect(tkLBrace, "expected '{' to start switch body")
|
||||
var cases: seq[SwitchCase] = @[]
|
||||
var defaultBlock: Block = nil
|
||||
while not p.check(tkRBrace) and not p.isAtEnd:
|
||||
while p.check(tkNewLine):
|
||||
discard p.advance()
|
||||
if p.check(tkRBrace) or p.isAtEnd:
|
||||
break
|
||||
if p.check(tkDefault):
|
||||
discard p.advance()
|
||||
discard p.expect(tkColon, "expected ':' after default")
|
||||
defaultBlock = Block(loc: p.currentLoc, stmts: @[p.parseStmt()])
|
||||
continue
|
||||
if p.check(tkCase):
|
||||
discard p.advance()
|
||||
let caseVal = p.parseExpr()
|
||||
discard p.expect(tkColon, "expected ':' after case value")
|
||||
let caseBody = Block(loc: p.currentLoc, stmts: @[p.parseStmt()])
|
||||
cases.add(SwitchCase(loc: caseVal.loc, caseValue: caseVal, caseBody: caseBody))
|
||||
continue
|
||||
# Unknown token in switch body — skip
|
||||
discard p.advance()
|
||||
discard p.expect(tkRBrace, "expected '}' to close switch")
|
||||
return Stmt(kind: skSwitch, loc: loc, stmtSwitchExpr: subject, stmtSwitchCases: cases, stmtSwitchDefault: defaultBlock)
|
||||
of tkReturn:
|
||||
discard p.advance()
|
||||
var val: Expr = nil
|
||||
@@ -934,6 +1097,12 @@ proc parseStmt(p: var Parser): Stmt =
|
||||
# No-op: emit literal 0 as expression statement
|
||||
let zeroTok = Token(kind: tkIntLiteral, text: "0", loc: loc)
|
||||
return Stmt(kind: skExpr, loc: loc, stmtExpr: Expr(kind: ekLiteral, loc: loc, exprLit: zeroTok))
|
||||
of tkDefer:
|
||||
discard p.advance()
|
||||
let expr = p.parseExpr()
|
||||
if p.check(tkSemicolon):
|
||||
discard p.advance()
|
||||
return Stmt(kind: skDefer, loc: loc, stmtDeferBody: expr)
|
||||
of tkFunc, tkStruct, tkEnum, tkUnion, tkInterface, tkExtend, tkModule,
|
||||
tkImport, tkConst, tkType, tkExtern, tkPub:
|
||||
# Local declaration
|
||||
@@ -954,6 +1123,10 @@ proc parseTypeParams(p: var Parser): seq[TypeParam] =
|
||||
if p.check(tkLt):
|
||||
discard p.advance()
|
||||
while not p.check(tkGt) and not p.isAtEnd:
|
||||
while p.check(tkNewLine):
|
||||
discard p.advance()
|
||||
if p.check(tkGt) or p.isAtEnd:
|
||||
break
|
||||
var name = ""
|
||||
var isLifetime = false
|
||||
if p.check(tkIdent):
|
||||
@@ -986,8 +1159,11 @@ proc parseTypeParams(p: var Parser): seq[TypeParam] =
|
||||
proc parseParamList(p: var Parser, allowVariadic: bool = false): seq[Param] =
|
||||
discard p.expect(tkLParen, "expected '('")
|
||||
while not p.check(tkRParen) and not p.isAtEnd:
|
||||
while p.check(tkNewLine):
|
||||
discard p.advance()
|
||||
if p.check(tkRParen) or p.isAtEnd:
|
||||
break
|
||||
let loc = p.currentLoc
|
||||
var isVar = false
|
||||
if allowVariadic and p.check(tkDotDotDot):
|
||||
discard p.advance()
|
||||
let nameTok = p.at
|
||||
@@ -1250,6 +1426,10 @@ proc parseUseDecl(p: var Parser, attrs: ParsedAttrs): Decl =
|
||||
if p.check(tkLBrace):
|
||||
discard p.advance()
|
||||
while not p.check(tkRBrace) and not p.isAtEnd:
|
||||
while p.check(tkNewLine):
|
||||
discard p.advance()
|
||||
if p.check(tkRBrace) or p.isAtEnd:
|
||||
break
|
||||
names.add(p.expect(tkIdent, "expected name in multi-import").text)
|
||||
if p.check(tkComma):
|
||||
discard p.advance()
|
||||
|
||||
@@ -44,3 +44,13 @@ proc lookupLocal*(scope: Scope, name: string): Symbol =
|
||||
if scope.table.hasKey(name):
|
||||
return scope.table[name]
|
||||
return nil
|
||||
|
||||
proc lookupUpTo*(scope: Scope, name: string, limit: Scope): Symbol =
|
||||
var cur = scope
|
||||
while cur != nil:
|
||||
if cur.table.hasKey(name):
|
||||
return cur.table[name]
|
||||
if cur == limit:
|
||||
break
|
||||
cur = cur.parent
|
||||
return nil
|
||||
+319
-51
@@ -44,6 +44,10 @@ type
|
||||
checkedFunc*: bool ## true inside @[Checked] function
|
||||
currentFuncIsAsync*: bool ## true inside async func
|
||||
movedVars*: seq[string] ## variables moved in current checked function
|
||||
currentRetType*: Type ## return type of the function being checked
|
||||
closureDepth*: int ## nesting depth inside closures
|
||||
currentClosureExpr*: Expr ## current closure being analyzed
|
||||
closureScope*: Scope ## scope at which the current closure was entered
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
@@ -91,9 +95,6 @@ proc unescapeStringLiteral*(s: string): string =
|
||||
proc emitError(sema: var Sema, loc: SourceLocation, message: string) =
|
||||
sema.diagnostics.add(SemaDiagnostic(severity: sdsError, loc: loc, message: message))
|
||||
|
||||
proc emitWarning(sema: var Sema, loc: SourceLocation, message: string) =
|
||||
sema.diagnostics.add(SemaDiagnostic(severity: sdsWarning, loc: loc, message: message))
|
||||
|
||||
proc hasErrors*(res: SemaResult): bool =
|
||||
for d in res.diagnostics:
|
||||
if d.severity == sdsError:
|
||||
@@ -126,10 +127,14 @@ proc typeExprReferencesTypeParam(te: TypeExpr, name: string): bool =
|
||||
of tekTuple:
|
||||
for elem in te.tupleElements:
|
||||
if typeExprReferencesTypeParam(elem, name): return true
|
||||
of tekFunc:
|
||||
for p in te.funcParams:
|
||||
if typeExprReferencesTypeParam(p, name): return true
|
||||
return typeExprReferencesTypeParam(te.funcRet, name)
|
||||
of tekSelf:
|
||||
return false
|
||||
|
||||
proc typeToTypeExpr(t: Type): TypeExpr =
|
||||
proc typeToTypeExpr*(t: Type): TypeExpr =
|
||||
## Convert a resolved Type back to a TypeExpr for storage in inferred type args.
|
||||
case t.kind
|
||||
of tkInt: TypeExpr(kind: tekNamed, typeName: "int")
|
||||
@@ -146,7 +151,11 @@ proc typeToTypeExpr(t: Type): TypeExpr =
|
||||
of tkFloat64: TypeExpr(kind: tekNamed, typeName: "float64")
|
||||
of tkBool: TypeExpr(kind: tekNamed, typeName: "bool")
|
||||
of tkStr: TypeExpr(kind: tekNamed, typeName: "String")
|
||||
of tkNamed: TypeExpr(kind: tekNamed, typeName: t.name)
|
||||
of tkNamed:
|
||||
var args: seq[TypeExpr] = @[]
|
||||
for a in t.inner:
|
||||
args.add(typeToTypeExpr(a))
|
||||
return TypeExpr(kind: tekNamed, typeName: t.name, typeArgs: args)
|
||||
of tkPointer:
|
||||
if t.inner.len > 0:
|
||||
TypeExpr(kind: tekPointer, refLifetime: "", pointerPointee: typeToTypeExpr(t.inner[0]))
|
||||
@@ -165,6 +174,45 @@ proc typeToTypeExpr(t: Type): TypeExpr =
|
||||
of tkVoid: TypeExpr(kind: tekNamed, typeName: "void")
|
||||
else: TypeExpr(kind: tekNamed, typeName: t.toString)
|
||||
|
||||
proc substituteTypeInType(sema: var Sema, t: Type, subst: Table[string, Type]): Type =
|
||||
## Recursively substitute type parameters in a resolved Type.
|
||||
if t == nil:
|
||||
return makeUnknown()
|
||||
case t.kind
|
||||
of tkTypeParam:
|
||||
if subst.hasKey(t.name):
|
||||
return subst[t.name]
|
||||
return t
|
||||
of tkPointer, tkRef, tkMutRef:
|
||||
if t.inner.len > 0:
|
||||
return Type(kind: t.kind, inner: @[sema.substituteTypeInType(t.inner[0], subst)])
|
||||
return t
|
||||
of tkSlice, tkRange:
|
||||
if t.inner.len > 0:
|
||||
return Type(kind: t.kind, inner: @[sema.substituteTypeInType(t.inner[0], subst)])
|
||||
return t
|
||||
of tkTuple:
|
||||
var elems: seq[Type] = @[]
|
||||
for e in t.inner:
|
||||
elems.add(sema.substituteTypeInType(e, subst))
|
||||
return makeTuple(elems)
|
||||
of tkFunc:
|
||||
var inner: seq[Type] = @[]
|
||||
for it in t.inner:
|
||||
inner.add(sema.substituteTypeInType(it, subst))
|
||||
return Type(kind: tkFunc, inner: inner)
|
||||
of tkNamed:
|
||||
if subst.hasKey(t.name):
|
||||
return subst[t.name]
|
||||
if t.inner.len > 0:
|
||||
var args: seq[Type] = @[]
|
||||
for a in t.inner:
|
||||
args.add(sema.substituteTypeInType(a, subst))
|
||||
return Type(kind: tkNamed, name: t.name, inner: args)
|
||||
return t
|
||||
else:
|
||||
return t
|
||||
|
||||
proc inferTypeArgs(sema: var Sema, funcDecl: Decl, argTypes: seq[Type],
|
||||
loc: SourceLocation): seq[TypeExpr] =
|
||||
## Infer type arguments from argument types for a generic function call.
|
||||
@@ -252,6 +300,11 @@ proc resolveType(sema: var Sema, te: TypeExpr): Type =
|
||||
of "float64": return makeFloat64()
|
||||
of "float": return makeFloat64()
|
||||
else:
|
||||
if te.typeArgs.len > 0:
|
||||
var args: seq[Type] = @[]
|
||||
for arg in te.typeArgs:
|
||||
args.add(sema.resolveType(arg))
|
||||
return Type(kind: tkNamed, name: name, inner: args)
|
||||
if sema.typeTable.hasKey(name):
|
||||
return sema.typeTable[name]
|
||||
return makeNamed(name)
|
||||
@@ -278,6 +331,12 @@ proc resolveType(sema: var Sema, te: TypeExpr): Type =
|
||||
return makeTuple(elems)
|
||||
of tekSelf:
|
||||
return makeNamed("self")
|
||||
of tekFunc:
|
||||
var params: seq[Type] = @[]
|
||||
for p in te.funcParams:
|
||||
params.add(sema.resolveType(p))
|
||||
let ret = if te.funcRet != nil: sema.resolveType(te.funcRet) else: makeVoid()
|
||||
return makeFunc(params, ret)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# First pass: collect global symbols
|
||||
@@ -535,10 +594,17 @@ proc collectGlobals*(sema: var Sema) =
|
||||
if not sema.globalScope.define(sym):
|
||||
sema.emitError(decl.loc, &"duplicate symbol '{decl.declEnumName}'")
|
||||
sema.typeTable[decl.declEnumName] = t
|
||||
# Check if algebraic or simple enum
|
||||
var hasData = false
|
||||
for variant in decl.declEnumVariants:
|
||||
if variant.fields.len > 0 or variant.namedFields.len > 0:
|
||||
hasData = true
|
||||
break
|
||||
# For algebraic enums, add variant constants with _Tag type
|
||||
# For simple enums, variant constants have the enum type itself
|
||||
for variant in decl.declEnumVariants:
|
||||
let variantName = decl.declEnumName & "_" & variant.name
|
||||
let variantType = makeNamed(decl.declEnumName & "_Tag")
|
||||
let variantType = if hasData: makeNamed(decl.declEnumName & "_Tag") else: makeNamed(decl.declEnumName)
|
||||
let variantSym = Symbol(kind: skConst, name: variantName, typ: variantType,
|
||||
decl: decl, isPublic: decl.isPublic)
|
||||
discard sema.globalScope.define(variantSym)
|
||||
@@ -563,24 +629,8 @@ proc collectGlobals*(sema: var Sema) =
|
||||
sema.emitError(decl.loc, &"duplicate symbol '{decl.declAliasName}'")
|
||||
sema.typeTable[decl.declAliasName] = t
|
||||
of dkUse:
|
||||
# Imports: register imported names into scope
|
||||
if decl.declUsePath.len > 0:
|
||||
case decl.declUseKind
|
||||
of ukMulti:
|
||||
for name in decl.declUseNames:
|
||||
if sema.globalScope.lookup(name) == nil:
|
||||
let sym = Symbol(kind: skFunc, name: name, typ: makeUnknown(), isPublic: true)
|
||||
discard sema.globalScope.define(sym)
|
||||
of ukGlob:
|
||||
let name = decl.declUsePath[^1]
|
||||
if sema.globalScope.lookup(name) == nil:
|
||||
let sym = Symbol(kind: skModule, name: name, typ: makeUnknown(), isPublic: true)
|
||||
discard sema.globalScope.define(sym)
|
||||
of ukSingle:
|
||||
let name = decl.declUsePath[^1]
|
||||
if sema.globalScope.lookup(name) == nil:
|
||||
let sym = Symbol(kind: skFunc, name: name, typ: makeUnknown(), isPublic: true)
|
||||
discard sema.globalScope.define(sym)
|
||||
# Imports handled in second pass after all declarations are registered
|
||||
discard
|
||||
of dkInterface:
|
||||
# Register interface for conformance checking
|
||||
sema.interfaceTable[decl.declInterfaceName] = decl
|
||||
@@ -641,6 +691,26 @@ proc collectGlobals*(sema: var Sema) =
|
||||
for decl in sema.module.items:
|
||||
if decl.kind == dkConst:
|
||||
discard sema.constFoldConstDecl(decl)
|
||||
# Third pass: register imports after all real declarations are known
|
||||
for decl in sema.module.items:
|
||||
if decl.kind == dkUse:
|
||||
if decl.declUsePath.len > 0:
|
||||
case decl.declUseKind
|
||||
of ukMulti:
|
||||
for name in decl.declUseNames:
|
||||
if sema.globalScope.lookup(name) == nil:
|
||||
let sym = Symbol(kind: skFunc, name: name, typ: makeUnknown(), isPublic: true)
|
||||
discard sema.globalScope.define(sym)
|
||||
of ukGlob:
|
||||
let name = decl.declUsePath[^1]
|
||||
if sema.globalScope.lookup(name) == nil:
|
||||
let sym = Symbol(kind: skModule, name: name, typ: makeUnknown(), isPublic: true)
|
||||
discard sema.globalScope.define(sym)
|
||||
of ukSingle:
|
||||
let name = decl.declUsePath[^1]
|
||||
if sema.globalScope.lookup(name) == nil:
|
||||
let sym = Symbol(kind: skFunc, name: name, typ: makeUnknown(), isPublic: true)
|
||||
discard sema.globalScope.define(sym)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Expression type checking
|
||||
@@ -714,6 +784,63 @@ proc checkExprList(sema: var Sema, exprs: seq[Expr], scope: Scope): seq[Type] =
|
||||
for e in exprs:
|
||||
result.add(sema.checkExpr(e, scope))
|
||||
|
||||
proc resolveCallArgs(sema: var Sema, expr: Expr, calleeDecl: Decl, scope: Scope) =
|
||||
## Reorder named args and inject defaults for missing positional args.
|
||||
if expr.kind != ekCall or calleeDecl == nil or calleeDecl.kind != dkFunc:
|
||||
return
|
||||
let params = calleeDecl.declFuncParams
|
||||
let providedArgs = expr.exprCallArgs
|
||||
let providedNames = expr.exprCallArgNames
|
||||
if providedNames.len == 0:
|
||||
# All positional — just inject defaults for trailing missing args
|
||||
if providedArgs.len < params.len:
|
||||
var newArgs = providedArgs
|
||||
var newNames = providedNames
|
||||
for i in providedArgs.len ..< params.len:
|
||||
if params[i].defaultValue != nil:
|
||||
newArgs.add(params[i].defaultValue)
|
||||
newNames.add("")
|
||||
else:
|
||||
sema.emitError(expr.loc, &"missing argument for parameter '{params[i].name}'")
|
||||
break
|
||||
expr.exprCallArgs = newArgs
|
||||
expr.exprCallArgNames = newNames
|
||||
return
|
||||
# Named args present
|
||||
var newArgs: seq[Expr] = @[]
|
||||
var newNames: seq[string] = @[]
|
||||
var usedNamed = false
|
||||
var namedArgMap: Table[string, Expr]
|
||||
# Collect named args and validate ordering
|
||||
for i in 0 ..< providedArgs.len:
|
||||
if providedNames[i] != "":
|
||||
usedNamed = true
|
||||
if providedNames[i] in namedArgMap:
|
||||
sema.emitError(expr.loc, &"duplicate named argument '{providedNames[i]}'")
|
||||
return
|
||||
namedArgMap[providedNames[i]] = providedArgs[i]
|
||||
else:
|
||||
if usedNamed:
|
||||
sema.emitError(expr.loc, "positional argument after named argument")
|
||||
return
|
||||
# Build final arg list in param order
|
||||
for i in 0 ..< params.len:
|
||||
if i < providedArgs.len and providedNames[i] == "":
|
||||
# Positional arg at expected position
|
||||
newArgs.add(providedArgs[i])
|
||||
newNames.add("")
|
||||
elif params[i].name in namedArgMap:
|
||||
newArgs.add(namedArgMap[params[i].name])
|
||||
newNames.add("")
|
||||
elif params[i].defaultValue != nil:
|
||||
newArgs.add(params[i].defaultValue)
|
||||
newNames.add("")
|
||||
else:
|
||||
sema.emitError(expr.loc, &"missing argument for parameter '{params[i].name}'")
|
||||
break
|
||||
expr.exprCallArgs = newArgs
|
||||
expr.exprCallArgNames = newNames
|
||||
|
||||
proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
if expr == nil:
|
||||
return makeUnknown()
|
||||
@@ -737,6 +864,14 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
return makeUnknown()
|
||||
if sym.typ == nil:
|
||||
return makeUnknown()
|
||||
# Capture tracking
|
||||
if sema.closureDepth > 0 and sema.currentClosureExpr != nil and sema.closureScope != nil:
|
||||
let localSym = scope.lookupUpTo(expr.exprIdent, sema.closureScope)
|
||||
if localSym == nil and sym.kind == skVar:
|
||||
if expr.exprIdent notin sema.currentClosureExpr.captureNames:
|
||||
sema.currentClosureExpr.captureNames.add(expr.exprIdent)
|
||||
sema.currentClosureExpr.captureTypeKinds.add(sym.typ.kind.int)
|
||||
sema.currentClosureExpr.captureCount = sema.currentClosureExpr.captureNames.len
|
||||
return sym.typ
|
||||
of ekSelf:
|
||||
let sym = scope.lookup("self")
|
||||
@@ -786,6 +921,33 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
of ekBinary:
|
||||
let left = sema.checkExpr(expr.exprBinaryLeft, scope)
|
||||
let right = sema.checkExpr(expr.exprBinaryRight, scope)
|
||||
# Operator overloading: check method table before builtin rules
|
||||
let opMethodName = case expr.exprBinaryOp
|
||||
of tkPlus: "operator_add"
|
||||
of tkMinus: "operator_sub"
|
||||
of tkStar: "operator_mul"
|
||||
of tkSlash: "operator_div"
|
||||
of tkPercent: "operator_mod"
|
||||
of tkEq: "operator_eq"
|
||||
of tkNe: "operator_ne"
|
||||
of tkLt: "operator_lt"
|
||||
of tkLe: "operator_le"
|
||||
of tkGt: "operator_gt"
|
||||
of tkGe: "operator_ge"
|
||||
of tkAmp: "operator_bitand"
|
||||
of tkPipe: "operator_bitor"
|
||||
of tkCaret: "operator_xor"
|
||||
of tkShl: "operator_shl"
|
||||
of tkShr: "operator_shr"
|
||||
else: ""
|
||||
if opMethodName != "" and left.kind == tkNamed and sema.methodTable.hasKey(left.name):
|
||||
for minfo in sema.methodTable[left.name]:
|
||||
if minfo.name == opMethodName:
|
||||
# Validate argument count (self + other)
|
||||
if minfo.params.len == 2:
|
||||
let otherType = minfo.params[1]
|
||||
if right.isAssignableTo(otherType) or otherType.isAssignableTo(right) or right.kind == tkUnknown:
|
||||
return minfo.retType
|
||||
case expr.exprBinaryOp
|
||||
of tkPlus, tkMinus, tkStar, tkSlash, tkPercent, tkStarStar:
|
||||
if not left.isNumeric or not right.isNumeric:
|
||||
@@ -845,9 +1007,16 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
of ekRange:
|
||||
let lo = sema.checkExpr(expr.exprRangeLo, scope)
|
||||
let hi = sema.checkExpr(expr.exprRangeHi, scope)
|
||||
if lo != hi:
|
||||
var rangeType: Type = lo
|
||||
if lo == hi:
|
||||
rangeType = lo
|
||||
elif lo.isAssignableTo(hi):
|
||||
rangeType = hi
|
||||
elif hi.isAssignableTo(lo):
|
||||
rangeType = lo
|
||||
else:
|
||||
sema.emitError(expr.loc, "range bounds must have same type")
|
||||
return makeRange(lo)
|
||||
return makeRange(rangeType)
|
||||
of ekCall:
|
||||
if expr.exprCallCallee == nil:
|
||||
sema.emitError(expr.loc, "internal error: nil callee in call expression")
|
||||
@@ -860,19 +1029,22 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
sema.emitError(expr.loc, &"undeclared identifier '{expr.exprCallCallee.exprGenericCallee}'")
|
||||
return makeUnknown()
|
||||
if sym.typ != nil and sym.typ.kind == tkFunc:
|
||||
# Get the return type and substitute type parameters
|
||||
let retType = sym.typ.inner[^1]
|
||||
if retType.kind == tkNamed:
|
||||
# Check if this is a type parameter
|
||||
let sym2 = sema.globalScope.lookup(expr.exprCallCallee.exprGenericCallee)
|
||||
if sym2 != nil and sym2.decl != nil and sym2.decl.kind == dkFunc:
|
||||
let typeParams = sym2.decl.declFuncTypeParams
|
||||
for i, tp in typeParams:
|
||||
if retType.name == tp.name and i < expr.exprCallCallee.exprGenericTypeArgs.len:
|
||||
# Substitute with concrete type
|
||||
let concreteType = expr.exprCallCallee.exprGenericTypeArgs[i]
|
||||
if concreteType.kind == tekNamed:
|
||||
return sema.resolveType(concreteType)
|
||||
let sym2 = sema.globalScope.lookup(expr.exprCallCallee.exprGenericCallee)
|
||||
if sym2 != nil and sym2.decl != nil and sym2.decl.kind == dkFunc and
|
||||
sym2.decl.declFuncTypeParams.len > 0 and
|
||||
sym2.decl.declFuncReturnType != nil:
|
||||
let typeParams = sym2.decl.declFuncTypeParams
|
||||
var added: seq[string] = @[]
|
||||
for i, tp in typeParams:
|
||||
if i < expr.exprCallCallee.exprGenericTypeArgs.len:
|
||||
let concrete = sema.resolveType(expr.exprCallCallee.exprGenericTypeArgs[i])
|
||||
sema.typeTable[tp.name] = concrete
|
||||
added.add(tp.name)
|
||||
let resolvedRet = sema.resolveType(sym2.decl.declFuncReturnType)
|
||||
for tp in added:
|
||||
sema.typeTable.del(tp)
|
||||
return resolvedRet
|
||||
return retType
|
||||
return makeUnknown()
|
||||
|
||||
@@ -945,8 +1117,7 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
|
||||
# Regular function call
|
||||
let calleeType = sema.checkExpr(expr.exprCallCallee, scope)
|
||||
var argTypes = sema.checkExprList(expr.exprCallArgs, scope)
|
||||
# Look up callee declaration early (needed for borrow checking)
|
||||
# Look up callee declaration early (needed for borrow checking and defaults)
|
||||
var calleeDecl: Decl = nil
|
||||
case expr.exprCallCallee.kind
|
||||
of ekIdent:
|
||||
@@ -957,13 +1128,16 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
let sym = scope.lookup(fullName)
|
||||
if sym != nil: calleeDecl = sym.decl
|
||||
else: discard
|
||||
if calleeDecl != nil and calleeDecl.kind == dkFunc and calleeDecl.declFuncTypeParams.len > 0:
|
||||
discard # will be handled later
|
||||
# Resolve named args and inject defaults before type-checking args
|
||||
sema.resolveCallArgs(expr, calleeDecl, scope)
|
||||
var argTypes = sema.checkExprList(expr.exprCallArgs, scope)
|
||||
let isGenericFunc = calleeDecl != nil and calleeDecl.kind == dkFunc and calleeDecl.declFuncTypeParams.len > 0
|
||||
if calleeType.kind == tkFunc:
|
||||
let expectedParams = calleeType.inner[0..^2]
|
||||
if argTypes.len != expectedParams.len:
|
||||
sema.emitError(expr.loc, &"expected {expectedParams.len} arguments, got {argTypes.len}")
|
||||
else:
|
||||
elif not isGenericFunc:
|
||||
# Generic function arg checks are deferred until after type inference/monomorphization.
|
||||
for i in 0 ..< argTypes.len:
|
||||
if not argTypes[i].isAssignableTo(expectedParams[i]) and not (argTypes[i].kind in {TypeKind.tkUnknown, TypeKind.tkNamed, TypeKind.tkTypeParam}):
|
||||
sema.emitError(expr.loc, &"argument {i+1}: expected {expectedParams[i].toString}, got {argTypes[i].toString}")
|
||||
@@ -1038,6 +1212,26 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
let idx = sema.checkExpr(expr.exprIndexIdx, scope)
|
||||
if not idx.isInteger:
|
||||
sema.emitError(expr.loc, "index must be integer")
|
||||
|
||||
# Try method-table operator_index_get on a named receiver (possibly behind a pointer).
|
||||
var receiverNamed: Type = nil
|
||||
if obj.kind == tkNamed:
|
||||
receiverNamed = obj
|
||||
elif obj.isPointer and obj.inner.len > 0 and obj.inner[0].kind == tkNamed:
|
||||
receiverNamed = obj.inner[0]
|
||||
|
||||
if receiverNamed != nil and sema.methodTable.hasKey(receiverNamed.name):
|
||||
for minfo in sema.methodTable[receiverNamed.name]:
|
||||
if minfo.name == "operator_index_get" and minfo.params.len == 2:
|
||||
var subst = initTable[string, Type]()
|
||||
if minfo.decl.declFuncTypeParams.len > 0 and receiverNamed.inner.len > 0:
|
||||
for i, tp in minfo.decl.declFuncTypeParams:
|
||||
if i < receiverNamed.inner.len:
|
||||
subst[tp.name] = receiverNamed.inner[i]
|
||||
let idxType = sema.substituteTypeInType(minfo.params[1], subst)
|
||||
if idx.isAssignableTo(idxType) or idxType.isAssignableTo(idx) or idx.kind == tkUnknown:
|
||||
return sema.substituteTypeInType(minfo.retType, subst)
|
||||
|
||||
if obj.isSlice:
|
||||
if sema.checkedFunc:
|
||||
expr.exprIndexBoundsCheck = true
|
||||
@@ -1079,13 +1273,24 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
let sym = sema.globalScope.lookup(objType.name)
|
||||
if sym != nil and sym.decl != nil:
|
||||
if sym.decl.kind == dkStruct:
|
||||
var subst = initTable[string, Type]()
|
||||
for i, tp in sym.decl.declStructTypeParams:
|
||||
if i < objType.inner.len:
|
||||
subst[tp.name] = objType.inner[i]
|
||||
for f in sym.decl.declStructFields:
|
||||
if f.name == expr.exprFieldName:
|
||||
return sema.resolveType(f.ftype)
|
||||
return sema.substituteTypeInType(sema.resolveType(f.ftype), subst)
|
||||
sema.emitError(expr.loc, &"struct '{objType.name}' has no field '{expr.exprFieldName}'")
|
||||
elif sym.decl.kind == dkEnum:
|
||||
# Algebraic enum fields
|
||||
if expr.exprFieldName == "tag":
|
||||
var hasData = false
|
||||
for v in sym.decl.declEnumVariants:
|
||||
if v.fields.len > 0 or v.namedFields.len > 0:
|
||||
hasData = true
|
||||
break
|
||||
if not hasData and expr.exprFieldName == "tag":
|
||||
return makeNamed(objType.name)
|
||||
elif expr.exprFieldName == "tag":
|
||||
return makeNamed(objType.name & "_Tag")
|
||||
elif expr.exprFieldName == "data":
|
||||
return makeNamed(objType.name & "_Data")
|
||||
@@ -1147,12 +1352,12 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
discard sema.checkExpr(expr.exprIsOperand, scope)
|
||||
return makeBool()
|
||||
of ekTry:
|
||||
let operandType = sema.checkExpr(expr.exprTryOperand, scope)
|
||||
discard sema.checkExpr(expr.exprTryOperand, scope)
|
||||
# For now, assume Result<int, String> -> int
|
||||
# TODO: check operand is Result/Option and current function returns same type
|
||||
return makeInt()
|
||||
of ekUnwrap:
|
||||
let operandType = sema.checkExpr(expr.exprUnwrapOperand, scope)
|
||||
discard sema.checkExpr(expr.exprUnwrapOperand, scope)
|
||||
# Unwrap: extract Ok value or panic on Err
|
||||
return makeInt()
|
||||
of ekBlock:
|
||||
@@ -1162,7 +1367,7 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
lastType = sema.checkStmt(stmt, blockScope)
|
||||
return lastType
|
||||
of ekMatch:
|
||||
let subjectType = sema.checkExpr(expr.exprMatchSubject, scope)
|
||||
discard sema.checkExpr(expr.exprMatchSubject, scope)
|
||||
var resultType = makeUnknown()
|
||||
for arm in expr.exprMatchArms:
|
||||
var armScope = newScope(scope)
|
||||
@@ -1197,11 +1402,55 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
expr.exprSpawnAsync = true
|
||||
return makePointer(makeVoid())
|
||||
of ekAwait:
|
||||
let operand = sema.checkExpr(expr.exprAwaitOperand, scope)
|
||||
discard sema.checkExpr(expr.exprAwaitOperand, scope)
|
||||
# await on a task handle returns *void (result pointer)
|
||||
return makePointer(makeVoid())
|
||||
of ekBorrow:
|
||||
let operand = sema.checkExpr(expr.exprBorrowOperand, scope)
|
||||
# borrow &mut expr returns the same type as the original (reference)
|
||||
# The borrow is tracked in the borrow checker
|
||||
if sema.checkedFunc and expr.exprBorrowMutable:
|
||||
# Track: variable "operand" is mutably borrowed here
|
||||
# For now, just validate the type
|
||||
discard
|
||||
return operand
|
||||
of ekSpread:
|
||||
return sema.checkExpr(expr.exprSpreadOperand, scope)
|
||||
of ekStringInterp:
|
||||
for e in expr.exprInterpExprs:
|
||||
discard sema.checkExpr(e, scope)
|
||||
return makeStr()
|
||||
of ekClosure:
|
||||
let savedRetType = sema.currentRetType
|
||||
let savedClosureDepth = sema.closureDepth
|
||||
let savedClosureExpr = sema.currentClosureExpr
|
||||
let savedClosureScope = sema.closureScope
|
||||
let childScope = Scope(parent: scope)
|
||||
sema.closureDepth = sema.closureDepth + 1
|
||||
sema.currentClosureExpr = expr
|
||||
sema.closureScope = childScope
|
||||
expr.captureCount = 0
|
||||
expr.captureNames = @[]
|
||||
expr.captureTypeKinds = @[]
|
||||
sema.currentRetType = if expr.exprClosureReturnType != nil: sema.resolveType(expr.exprClosureReturnType) else: makeUnknown()
|
||||
# Register params
|
||||
for p in expr.exprClosureParams:
|
||||
let ptype = if p.ptype != nil: sema.resolveType(p.ptype) else: makeUnknown()
|
||||
discard childScope.define(Symbol(kind: skVar, name: p.name, typ: ptype))
|
||||
# Check body
|
||||
if expr.exprClosureBody != nil:
|
||||
for stmt in expr.exprClosureBody.stmts:
|
||||
discard sema.checkStmt(stmt, childScope)
|
||||
sema.currentRetType = savedRetType
|
||||
sema.closureDepth = savedClosureDepth
|
||||
sema.currentClosureExpr = savedClosureExpr
|
||||
sema.closureScope = savedClosureScope
|
||||
# Build function type
|
||||
var params: seq[Type] = @[]
|
||||
for p in expr.exprClosureParams:
|
||||
params.add(if p.ptype != nil: sema.resolveType(p.ptype) else: makeUnknown())
|
||||
let retType = if expr.exprClosureReturnType != nil: sema.resolveType(expr.exprClosureReturnType) else: makeVoid()
|
||||
return makeFunc(params, retType)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Statement type checking
|
||||
@@ -1262,9 +1511,17 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type =
|
||||
discard sema.checkStmt(Stmt(kind: skExpr, loc: stmt.stmtLoopBody.loc, stmtExpr: Expr(kind: ekBlock, loc: stmt.stmtLoopBody.loc, exprBlock: stmt.stmtLoopBody)), scope)
|
||||
return makeVoid()
|
||||
of skFor:
|
||||
discard sema.checkExpr(stmt.stmtForIter, scope)
|
||||
let iterExpr = stmt.stmtForIter
|
||||
let collType = sema.checkExpr(iterExpr, scope)
|
||||
var forScope = newScope(scope)
|
||||
let iterSym = Symbol(kind: skVar, name: stmt.stmtForVar, typ: makeUnknown(), isMutable: true)
|
||||
var iterTyp = makeUnknown()
|
||||
if iterExpr.kind == ekRange:
|
||||
iterTyp = sema.checkExpr(iterExpr.exprRangeLo, scope)
|
||||
elif collType.kind == tkNamed and collType.inner.len > 0:
|
||||
iterTyp = collType.inner[0]
|
||||
elif collType.isPointer and collType.inner.len > 0 and collType.inner[0].kind == tkNamed and collType.inner[0].inner.len > 0:
|
||||
iterTyp = collType.inner[0].inner[0]
|
||||
let iterSym = Symbol(kind: skVar, name: stmt.stmtForVar, typ: iterTyp, isMutable: true)
|
||||
discard forScope.define(iterSym)
|
||||
discard sema.checkStmt(Stmt(kind: skExpr, loc: stmt.stmtForBody.loc, stmtExpr: Expr(kind: ekBlock, loc: stmt.stmtForBody.loc, exprBlock: stmt.stmtForBody)), forScope)
|
||||
return makeVoid()
|
||||
@@ -1308,6 +1565,17 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type =
|
||||
elif not exprType.isUnknown and exprType.kind != tkStr:
|
||||
sema.emitError(stmt.loc, "#emit requires a string expression")
|
||||
return makeVoid()
|
||||
of skDefer:
|
||||
discard sema.checkExpr(stmt.stmtDeferBody, scope)
|
||||
return makeVoid()
|
||||
of skSwitch:
|
||||
discard sema.checkExpr(stmt.stmtSwitchExpr, scope)
|
||||
for caseBranch in stmt.stmtSwitchCases:
|
||||
discard sema.checkExpr(caseBranch.caseValue, scope)
|
||||
discard sema.checkStmt(Stmt(kind: skExpr, loc: caseBranch.caseBody.loc, stmtExpr: Expr(kind: ekBlock, loc: caseBranch.caseBody.loc, exprBlock: caseBranch.caseBody)), scope)
|
||||
if stmt.stmtSwitchDefault != nil:
|
||||
discard sema.checkStmt(Stmt(kind: skExpr, loc: stmt.stmtSwitchDefault.loc, stmtExpr: Expr(kind: ekBlock, loc: stmt.stmtSwitchDefault.loc, exprBlock: stmt.stmtSwitchDefault)), scope)
|
||||
return makeVoid()
|
||||
of skDecl:
|
||||
# Local declaration inside block
|
||||
case stmt.stmtDecl.kind
|
||||
|
||||
+17
-2
@@ -25,6 +25,9 @@ type
|
||||
tkContinue # continue
|
||||
tkReturn # return
|
||||
tkMatch # match
|
||||
tkSwitch # switch
|
||||
tkCase # case
|
||||
tkDefault # default
|
||||
|
||||
##Declaration keywords
|
||||
tkFunc # func
|
||||
@@ -51,6 +54,7 @@ type
|
||||
tkSizeOf # sizeof
|
||||
tkOwn # own (gradual ownership transfer)
|
||||
tkMut # mut (mutable reference)
|
||||
tkBorrow # borrow (explicit borrow expression)
|
||||
tkDiscard # discard (evaluate and throw away)
|
||||
tkAsync # async
|
||||
tkAwait # await
|
||||
@@ -58,6 +62,7 @@ type
|
||||
tkStaticAssert # static_assert
|
||||
tkComptime # comptime
|
||||
tkDyn # dyn
|
||||
tkDefer # defer
|
||||
tkLifetime # 'a (lifetime parameter)
|
||||
|
||||
##Punctuation
|
||||
@@ -148,10 +153,10 @@ type
|
||||
proc isKeyword*(kind: TokenKind): bool =
|
||||
case kind
|
||||
of tkIf, tkElse, tkWhile, tkDo, tkLoop, tkFor, tkIn,
|
||||
tkBreak, tkContinue, tkReturn, tkMatch,
|
||||
tkBreak, tkContinue, tkReturn, tkMatch, tkSwitch, tkCase, tkDefault,
|
||||
tkFunc, tkLet, tkVar, tkConst, tkType, tkStruct, tkEnum,
|
||||
tkUnion, tkInterface, tkExtend, tkModule, tkImport,
|
||||
tkPub, tkExtern, tkAs, tkIs, tkNull, tkSelf, tkSuper, tkOwn, tkMut, tkDiscard, tkAsync, tkAwait, tkSpawn, tkStaticAssert, tkComptime, tkDyn:
|
||||
tkPub, tkExtern, tkAs, tkIs, tkNull, tkSelf, tkSuper, tkOwn, tkMut, tkBorrow, tkDiscard, tkAsync, tkAwait, tkSpawn, tkStaticAssert, tkComptime, tkDyn, tkDefer:
|
||||
true
|
||||
else:
|
||||
false
|
||||
@@ -199,6 +204,9 @@ proc keywordKind*(text: string): TokenKind =
|
||||
of "continue": tkContinue
|
||||
of "return": tkReturn
|
||||
of "match": tkMatch
|
||||
of "switch": tkSwitch
|
||||
of "case": tkCase
|
||||
of "default": tkDefault
|
||||
of "as": tkAs
|
||||
of "is": tkIs
|
||||
of "null": tkNull
|
||||
@@ -207,6 +215,7 @@ proc keywordKind*(text: string): TokenKind =
|
||||
of "sizeof": tkSizeOf
|
||||
of "own": tkOwn
|
||||
of "mut": tkMut
|
||||
of "borrow": tkBorrow
|
||||
of "discard": tkDiscard
|
||||
of "async": tkAsync
|
||||
of "await": tkAwait
|
||||
@@ -214,6 +223,7 @@ proc keywordKind*(text: string): TokenKind =
|
||||
of "static_assert": tkStaticAssert
|
||||
of "comptime": tkComptime
|
||||
of "dyn": tkDyn
|
||||
of "defer": tkDefer
|
||||
of "true", "false": tkBoolLiteral
|
||||
else: tkIdent
|
||||
|
||||
@@ -238,6 +248,9 @@ proc tokenKindName*(kind: TokenKind): string =
|
||||
of tkContinue: "'continue'"
|
||||
of tkReturn: "'return'"
|
||||
of tkMatch: "'match'"
|
||||
of tkSwitch: "'switch'"
|
||||
of tkCase: "'case'"
|
||||
of tkDefault: "'default'"
|
||||
of tkFunc: "'func'"
|
||||
of tkLet: "'let'"
|
||||
of tkVar: "'var'"
|
||||
@@ -259,6 +272,7 @@ proc tokenKindName*(kind: TokenKind): string =
|
||||
of tkSuper: "'super'"
|
||||
of tkOwn: "'own'"
|
||||
of tkMut: "'mut'"
|
||||
of tkBorrow: "'borrow'"
|
||||
of tkDiscard: "'discard'"
|
||||
of tkAsync: "'async'"
|
||||
of tkAwait: "'await'"
|
||||
@@ -266,6 +280,7 @@ proc tokenKindName*(kind: TokenKind): string =
|
||||
of tkStaticAssert: "'static_assert'"
|
||||
of tkComptime: "'comptime'"
|
||||
of tkDyn: "'dyn'"
|
||||
of tkDefer: "'defer'"
|
||||
of tkLifetime: "lifetime"
|
||||
of tkLParen: "'('"
|
||||
of tkRParen: "')'"
|
||||
|
||||
@@ -183,6 +183,10 @@ proc isAssignableTo*(a, b: Type): bool =
|
||||
return true
|
||||
if b.isDynRef and a.isMutRef:
|
||||
return true
|
||||
# &mut func(...) -> func(...) (function pointer decay)
|
||||
if a.isMutRef and b.kind == tkFunc:
|
||||
if a.inner.len > 0 and a.inner[0].kind == tkFunc:
|
||||
return true
|
||||
return false
|
||||
|
||||
# String representation
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[Package]
|
||||
Name = "buxc"
|
||||
Version = "0.1.0"
|
||||
Version = "0.3.0"
|
||||
Type = "bin"
|
||||
|
||||
[Build]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user