feat: Phase 8.2, 8.4, 8.5, 9.1 + C backend fixes

Phase 8.2 — Gradual Ownership:
- Add tkRef/tkMutRef types and `mut` keyword
- Add @[Checked] attribute for opt-in borrow checking
- Reject assignment through &T in checked functions
- examples/ownership.bux

Phase 8.4 — CTFE:
- Evaluate const func at compile-time via evalExpr/evalBlock
- Fold const declarations to literals; emit #define in C
- examples/ctfe.bux (Factorial(10) → 3628800)

Phase 8.5 — Trait Bounds:
- Change declFuncTypeParams from seq[string] to seq[TypeParam] (name + bound)
- Parser handles <T: Comparable>
- Sema checks typeImplements at call sites
- Fix C backend: generic receivers + pointer self field access
- examples/trait_bounds.bux

Phase 9.1 — Package Manager:
- Inline tables/arrays in TOML parser
- bux add, bux install, bux.lock generation
- Dependency resolution with git/path sources
- Build pipeline merges dependency .bux sources

C Backend Fixes:
- resolveExprType(ekIdent) now applies typeSubst for generic params
- Method desugaring works for monomorphized generic receivers
- Pointer checks use isPointer (covers tkRef/tkMutRef)
- Field access on &T emits -> instead of .

Remove accidentally committed test binaries from tracking
This commit is contained in:
2026-05-31 23:48:45 +03:00
parent b1f1fc277c
commit 8e255b2125
21 changed files with 1360 additions and 111 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ SRC := src/main.nim
OUT := buxc OUT := buxc
BUILD_DIR := build 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 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
.PHONY: all build dev test clean test-examples .PHONY: all build dev test clean test-examples
+32 -29
View File
@@ -404,15 +404,15 @@ func ReadFile(path: String) -> Result<String, IoError> {
} }
``` ```
### 8.2 — Ownership & Borrowing (Gradual Safety) 🔄 (Syntax Only) ### 8.2 — Ownership & Borrowing (Gradual Safety) (Basic Implementation Complete)
| Task | Status | Details | | Task | Status | Details |
|------|--------|---------| |------|--------|---------|
| `8.2.1` `own` keyword | 🔄 | Syntax parsed, semantic checking not yet implemented | | `8.2.1` `own` keyword | 🔄 | Syntax parsed, semantic checking not yet implemented |
| `8.2.2` `borrow` / `&` | 🔄 | `&T` reference syntax parsed, not yet semantically checked | | `8.2.2` `borrow` / `&` | | `&T` shared reference type checked and enforced |
| `8.2.3` `mut` references | | `&mut T` for mutable borrows (exclusive) | | `8.2.3` `mut` references | | `&mut T` mutable reference type checked and enforced |
| `8.2.4` Lifetime elision | ⏳ | Simple rules for common cases; explicit `'a` for complex | | `8.2.4` Lifetime elision | ⏳ | Simple rules for common cases; explicit `'a` for complex |
| `8.2.5` Opt-in checker | 🔄 | `@[Checked]` attribute syntax parsed, checker not implemented | | `8.2.5` Opt-in checker | | `@[Checked]` attribute enables borrow checking: writes through `&T` are rejected |
```bux ```bux
// Opt-in safety — by default, Bux is permissive like Nim // Opt-in safety — by default, Bux is permissive like Nim
@@ -462,14 +462,15 @@ func Main() -> int {
} }
``` ```
### 8.4 — Compile-Time Function Execution (CTFE) 🔄 (Syntax Only) ### 8.4 — Compile-Time Function Execution (CTFE) (Basic Implementation Complete)
| Task | Status | Details | | Task | Status | Details |
|------|--------|---------| |------|--------|---------|
| `8.4.1` `const` functions | 🔄 | `const func` syntax parsed (AST field `declFuncConst`), compile-time evaluation not implemented | | `8.4.1` `const` functions | | `const func` evaluated at compile time; supports recursion, if/else, arithmetic |
| `8.4.2` Compile-time blocks | | `comptime { ... }` for arbitrary compile-time code | | `8.4.2` `const` variables | | `const X = expr` — compile-time evaluated; C backend emits `#define` |
| `8.4.3` Static assertions | ⏳ | `static_assert(cond, msg)` for compile-time checks | | `8.4.3` Compile-time blocks | ⏳ | `comptime { ... }` for arbitrary compile-time code |
| `8.4.4` Generated code | ⏳ | `#emit` for compile-time code generation | | `8.4.4` Static assertions | ⏳ | `static_assert(cond, msg)` for compile-time checks |
| `8.4.5` Generated code | ⏳ | `#emit` for compile-time code generation |
```bux ```bux
const func Factorial(n: int) -> int { const func Factorial(n: int) -> int {
@@ -480,15 +481,15 @@ const func Factorial(n: int) -> int {
const TABLE_SIZE = Factorial(10); // Computed at compile time const TABLE_SIZE = Factorial(10); // Computed at compile time
``` ```
### 8.5 — Trait System (Interfaces++) ### 8.5 — Trait System (Interfaces++) ✅ (Basic Implementation)
| Task | Details | | Task | Status | Details |
|------|---------| |------|--------|---------|
| `8.5.1` Traits | Like Rust traits or Go interfaces, but with default implementations | | `8.5.1` Traits | ✅ | `interface` + `extend Type for Interface` |
| `8.5.2` Associated types | `type Output` inside trait definitions | | `8.5.2` Associated types | ⏳ | `type Output` inside trait definitions |
| `8.5.3` Trait bounds | `func Sort<T: Comparable>(arr: &mut Array<T>)` | | `8.5.3` Trait bounds | ✅ | `func Sort<T: Comparable>(arr: &mut Array<T>)` — semantic check at call sites |
| `8.5.4` Trait objects | `&dyn Trait` for dynamic dispatch (fat pointer) | | `8.5.4` Trait objects | ⏳ | `&dyn Trait` for dynamic dispatch (fat pointer) |
| `8.5.5` Blanket impls | `impl<T: Display> Printable for T` | | `8.5.5` Blanket impls | ⏳ | `impl<T: Display> Printable for T` |
### 8.6 — Metaprogramming ### 8.6 — Metaprogramming
@@ -502,17 +503,17 @@ const TABLE_SIZE = Factorial(10); // Computed at compile time
## Phase 9 — Ecosystem & Tooling (Week 35+) ## Phase 9 — Ecosystem & Tooling (Week 35+)
| Task | Details | | Task | Status | Details |
|------|---------| |------|--------|---------|
| `9.1` Package manager | `bux add`, `bux remove`, `bux update`, `bux install` with lockfile | | `9.1` Package manager | ✅ | `bux add`, `bux install`, `bux.lock` — path-based and git-based deps |
| `9.2` Registry protocol | Simple HTTP git-based registry (like Go modules or Cargo) | | `9.2` Registry protocol | ⏳ | Simple HTTP git-based registry (like Go modules or Cargo) |
| `9.3` Formatter | `bux fmt` — auto-format Bux source | | `9.3` Formatter | ⏳ | `bux fmt` — auto-format Bux source |
| `9.4` LSP | Language Server Protocol for autocomplete, hover, go-to-definition | | `9.4` LSP | ⏳ | Language Server Protocol for autocomplete, hover, go-to-definition |
| `9.5` Tests | `bux test` runner with assertions and golden tests | | `9.5` Tests | ⏳ | `bux test` runner with assertions and golden tests |
| `9.6` Documentation | `bux doc` — generate HTML from `///` doc comments | | `9.6` Documentation | ⏳ | `bux doc` — generate HTML from `///` doc comments |
| `9.7` Cross-compilation | `--target` flag leveraging C backend portability | | `9.7` Cross-compilation | ⏳ | `--target` flag leveraging C backend portability |
| `9.8` Debugger support | DWARF/PDB debug info generation for gdb/lldb/VSCode | | `9.8` Debugger support | ⏳ | DWARF/PDB debug info generation for gdb/lldb/VSCode |
| `9.9` Profiler integration | `bux build --profile` with basic profiling hooks | | `9.9` Profiler integration | ⏳ | `bux build --profile` with basic profiling hooks |
--- ---
@@ -694,7 +695,9 @@ func Main() -> int {
| **M4** | 5A | ✅ | `bux run` produces working binary via C transpiler | | **M4** | 5A | ✅ | `bux run` produces working binary via C transpiler |
| **M5** | 6 | ✅ | Can write compiler-adjacent tools in Bux (18 examples) | | **M5** | 6 | ✅ | Can write compiler-adjacent tools in Bux (18 examples) |
| **M6** | 7 | ✅ | **Self-hosted**: `buxc2` (Bux) compiles via `buxc` (Nim) — 88KB working binary | | **M6** | 7 | ✅ | **Self-hosted**: `buxc2` (Bux) compiles via `buxc` (Nim) — 88KB working binary |
| **M7** | 8 | 🔄 | Result/Option/`?`/`!` done; ownership syntax parsed; CTFE syntax parsed | | **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 | | **M8** | 9 | ⏳ | Package manager + LSP + formatter shipped |
--- ---
+4
View File
@@ -120,6 +120,10 @@ func Main() -> int {
| **Error Handling** | `Result<T,E>`, `Option<T>`, and the `?` operator | | **Error Handling** | `Result<T,E>`, `Option<T>`, and the `?` operator |
| **Standard Library** | `Io`, `Array`, `String`, `Map` | | **Standard Library** | `Io`, `Array`, `String`, `Map` |
| **Backend** | C transpiler (bootstrap) | | **Backend** | C transpiler (bootstrap) |
| **Gradual Ownership** | `@[Checked]` + `&T`/`&mut T` borrow checking |
| **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 |
| **Tooling** | `bux new`, `bux build`, `bux run`, `bux check` | | **Tooling** | `bux new`, `bux build`, `bux run`, `bux check` |
--- ---
+59 -10
View File
@@ -353,14 +353,14 @@ func Main() -> int {
--- ---
## Gradual Ownership (Phase 8.2) ## Gradual Ownership (Phase 8.2) ✅ Implemented
Bux introduces **gradual ownership**the first language to offer opt-in borrow checking. Bux introduces **gradual ownership**opt-in borrow checking. By default, Bux is permissive like C. With `@[Checked]`, the borrow checker enforces memory safety rules.
### Syntax ### Syntax
```bux ```bux
// Default: permissive mode (like C/Nim) // Default: permissive mode (like C/Nim) — raw pointers, no checks
func QuickSort(arr: *int, len: int) { func QuickSort(arr: *int, len: int) {
for i in 0..len { for i in 0..len {
arr[i] = arr[i] * 2; arr[i] = arr[i] * 2;
@@ -369,10 +369,18 @@ func QuickSort(arr: *int, len: int) {
// Opt-in: @[Checked] enables borrow checking // Opt-in: @[Checked] enables borrow checking
@[Checked] @[Checked]
func SafeMerge(a: &[int], b: &[int]) -> Vec<int> { func Scale(val: &mut int) {
// &T = shared reference (borrow checker enforced) *val = *val * 2; // OK: &mut T allows mutation
// &mut T = mutable reference (exclusive) }
// own T = ownership transfer
@[Checked]
func Read(val: &int) -> int {
return *val; // OK: &T allows reading
}
@[Checked]
func BadWrite(val: &int) {
*val = 42; // ERROR: cannot write through shared reference '&T'
} }
``` ```
@@ -381,9 +389,50 @@ func SafeMerge(a: &[int], b: &[int]) -> Vec<int> {
| Type | Syntax | Description | | Type | Syntax | Description |
|------|--------|-------------| |------|--------|-------------|
| Raw pointer | `*T` | C-style pointer, no checks | | Raw pointer | `*T` | C-style pointer, no checks |
| Shared ref | `&T` | Borrowed reference (checked) | | Shared ref | `&T` | Borrowed reference (read-only in checked functions) |
| Mutable ref | `&mut T` | Exclusive mutable borrow | | Mutable ref | `&mut T` | Exclusive mutable borrow (allows mutation) |
| Owned | `own T` | Ownership transfer | | Owned | `own T` | Ownership transfer (syntax parsed, not yet enforced) |
### Rules in @[Checked] functions
- `&T` cannot be used to mutate data (compile-time error)
- `&mut T` allows mutation
- `*T` pointers are unrestricted (escape hatch)
- `&mut T` coerces to `&T` and `*T`
---
## Compile-Time Function Execution (CTFE) ✅ Implemented
`const func` functions are evaluated at compile time. Their results can be used in type sizes, array lengths, or other constant contexts.
```bux
const func Factorial(n: int) -> int {
if n <= 1 {
return 1;
}
return n * Factorial(n - 1);
}
const TABLE_SIZE = Factorial(10); // 3628800 — computed at compile time
func Main() -> int {
let arr: [TABLE_SIZE]int; // Array size from compile-time value
return 0;
}
```
### Supported in CTFE
- Integer, boolean, and string literals
- Arithmetic (`+`, `-`, `*`, `/`, `%`)
- Comparisons and logical operators
- `if` / `else` with constant conditions
- Calls to other `const func` functions (including recursion)
### Limitations
- No `while` / `for` loops (use recursion)
- No `mut` references or heap allocation
- No non-const function calls
--- ---
+347
View File
@@ -0,0 +1,347 @@
# Фаза 8 — Стратегия: Как Bux печели, без да бие пряко Rust/Nim/Zig
> **Дата:** 2026-05-31 | **Статус:** Фаза 8.1 ✅, 8.2-8.6 🔄
> **Правило #1:** Не се биеш с някого там, където той е най-силен.
---
## 1. Проблемът с "да бием Rust"
Ако целта е "по-добър Rust", Bux губи още преди да започне. Rust има:
- 10+ години ecosystem (crates.io → 150,000+ пакета)
- Corporate backing (Amazon, Google, Microsoft, Mozilla)
- LLVM backend с 30 години оптимизации
- Стотици хиляди програмисти, които вече са преживели borrow checker-a
**Опитът да биеш Rust по безопасност е самоубийство.**
---
## 2. Умната стратегия: Не бий конкурентите — бий празното място между тях
Картата на пазара изглежда така:
```
Безопасност
Rust ─────────┼───────── ■■■■■■■■■■■ (висока, но трябва да платиш за нея)
Bux ──────────┼──── ■■■■■■□□□□□□ (gradual — по избор)
Nim ──────────┼────── ■■■■■■□□□□ (GC — "достатъчно" безопасен)
C / Zig ──────┼── ■■■□□□□□□□□□ (ти си отговорен)
└─────────────────────> Скорост на писане
```
**Никой не стои между "C-скорост на писане" и "Rust-безопасност" с опцията да избираш.**
Bux е единственият език, който позволява:
- Да пишеш като C (raw pointers, без checks) за MVP
- Да добавяш `@[Checked]` после, където е критично
- Да имаш `Result`/`Option`/`?` без lifetime annotations в 90% от кода
**Това е нишата.** Не "по-добър Rust", ами "Rust-лекота, когато искаш; C-свобода, когато бързаш".
---
## 3. Какво означава това за фаза 8 — конкретно
### 3.1 Фаза 8.2 — Gradual Ownership (The Killer Feature)
**Статус сега:** Синтаксисът е парсен, но borrow checker-ът не работи.
**Защо е критично:** Без работещ `@[Checked]`, Bux е просто "Rux с по-добър stdlib". С него — ставаме единствени на пазара.
**Как да го имплементираме умно (не като Rust):**
```bux
// Ниво 1: Без проверки — като C
func ParseJson(data: *char8) -> *Value { ... }
// Ниво 2: Bounds checking, но без ownership
func SafeAccess(arr: *int, len: int, idx: int) -> int { ... }
// Ниво 3: Пълен borrow checker — само където си решил
@[Checked]
func MergeSorted(a: &[int], b: &[int]) -> Vec<int> { ... }
```
**Ключова разлика от Rust:**
- Rust: `&T` е *всичко*. Ако искаш pointer, се бориш с компилатора.
- Bux: `*T` е default. `&T` е upgrade.
**Имплементационен план (прагматичен):**
| Етап | Фичър | За какво е | Priority |
|------|-------|-----------|----------|
| 8.2.1 | `@[Checked]` атрибут — вкл/изкл на checker | Да знаем кога да проверяваме | **P0 — критично** |
| 8.2.2 | `&T` shared reference + lifetime elision | Basic borrow без annotations | **P0** |
| 8.2.3 | `&mut T` exclusive mutable | Да няма data races | **P0** |
| 8.2.4 | Bounds checking на slices | Да няма buffer overflows | **P1** |
| 8.2.5 | Explicit lifetimes `'a` | Само за сложни случаи | **P2** |
| 8.2.6 | `own T` + move semantics | RAII без GC | **P2** |
**Какво ПРОПУСКАМЕ (за да не стане Rust #2):**
- ❌ Няма да правим lifetime annotations задължителни
- ❌ Няма да имаме `borrowck` грешки във всяка функция
- ❌ Няма да правим NLL (non-lexical lifetimes) в първата версия
**Правило:** Първият `@[Checked]` да хване 80% от бъговете с 20% от сложността на Rust.
---
### 3.2 Фаза 8.3 — Concurrency
**Конкуренция:**
- Go → goroutines + channels (прости, но с GC runtime)
- Rust → async/await (сложен, но zero-cost)
- Zig → няма built-in runtime (ти си го пишеш)
**Bux стратегия:** "Go-простота, но без GC"
```bux
import Std::Task;
import Std::Channel;
// Go-style, но compile-time проверка за Send/Sync
func Worker(rx: Channel<int>) {
for msg in rx {
Process(msg);
}
}
func Main() -> int {
let (tx, rx) = Channel::New<int>();
Task::Spawn(Worker, rx); // Зелени нишки (M:N scheduler)
tx.Send(42);
return 0;
}
```
**Защо това печели:**
- Програмистите харесват Go concurrency, но мразят GC паузите
- Rust async е прекалено сложен за средния екип
- Bux дава goroutines без GC → уникална позиция
**Приоритет:** P1 (важно за привличане на Go екипи, но не спира shipping)
---
### 3.3 Фаза 8.4 — CTFE (Compile-Time Function Execution)
**Конкуренция:**
- Zig → `comptime` е best-in-class
- Nim → има CTFE, но с ограничения
- Rust → `const fn` е силно ограничен (no loops, no heap)
**Bux стратегия:** "Nim-лесен синтаксис, Zig-мощност"
```bux
const func Fib(n: int) -> int {
if n <= 1 { return n; }
return Fib(n-1) + Fib(n-2);
}
const TABLE_SIZE = Fib(20); // Computed at compile time
// Use case: embedded / kernel development
const func CrcTable() -> [256]uint32 { ... }
const CRC_TABLE = CrcTable(); // Precomputed, zero runtime cost
```
**Защо това печели:**
- Embedded програмистите (където Rust доминира) обичат precomputed tables
- Nim програмистите вече знаят този модел
- Rust не може да го прави пълноценно
**Приоритет:** P1 — спира Rust програмисти, които се оплакват от `const fn` ограниченията.
---
### 3.4 Фаза 8.5 — Trait System
**Сега имаме:** `interface` + `extend` (като Go interfaces / basic Rust traits)
**Какво трябва:**
- Trait bounds: `func Sort<T: Comparable>(arr: &mut Array<T>)`
- Associated types: `type Output` inside trait
- Blanket impls: `impl<T: Display> Printable for T`
**Защо е важно:** Без trait bounds, generics са ограничени. Не можеш да напишеш `Max<T: Ord>`.
**Но:** Да не правим Haskell. Само това, което Rust има и се ползва всеки ден.
**Приоритет:** P1 — без това stdlib-ът е куц.
---
### 3.5 Фаза 8.6 — Metaprogramming
**Конкуренция:**
- Rust → proc macros са мощни, но болезнени (syn, quote crates)
- Nim → макросите са лесни, но са на Nim-AST (труден за научаване модел)
- Zig → `comptime` е мощен, но изисква да мислиш като компилатор
**Bux стратегия:** Два слоя:
**Слой 1 — Declarative macros (easy):**
```bux
macro! vec {
[$($item:expr),*] => {
{
let mut arr = Array_New();
$(Array_Push(&mut arr, $item);)*
arr
}
}
}
let v = vec![1, 2, 3]; // Expands at compile time
```
**Слой 2 — Derive macros (medium):**
```bux
#[derive(Clone, Debug)]
struct Point { x: int, y: int }
// Auto-generates Clone_Point and Debug_Point
```
**Защо не procedural macros (като Rust)?**
Защото трябва да пишеш parser. Declarative + derive са 95% от use case-овете.
**Приоритет:** P2 (добре е за ecosystem, но не блокира v1.0)
---
## 4. Стратегическа матрица: Кого целим и с какво
### 4.1 Primary Target: Програмисти, които мразят borrow checker-a, но искат safety
| Те казват | Bux отговаря |
|-----------|-------------|
| "Rust е страхотен, но 6 месеца за MVP е смешно" | `*T` по default, `&T` само където искаш |
| "Не искам да се бия с компилатора за linked list" | Без borrow checker за прототипи |
| "Искам safety, но само на критичните 20% от кода" | `@[Checked]` на точните функции |
**Това са програмисти от:**
- Game dev (Unity → custom engine, C++ → нещо по-добро)
- Embedded (C → Rust опитали се, отказали се)
- Startups (Go → искат performance без GC)
### 4.2 Secondary Target: Nim програмисти, които искат по-добър tooling
Nim е страхотен, но:
- Няма algebraic enums (трябват макроси)
- Exception-based error handling е остарял модел
- Ecosystem е фрагментиран
Bux предлага:
- Същата скорост на компилация
- Същият C backend
- Algebraic enums + Result/Option
- Без GC (за системно програмиране)
### 4.3 Tertiary Target: C програмисти, които искат модерен език без отказ от контрол
Zig е пряк конкурент тук. Но Zig е *твърде* минималистичен.
Bux дава на C програмиста:
- Generics (без `#define` магии)
- Pattern matching
- Modules (без header guards)
- Но пак има `*T` и може да прави `*(int*)0x1234 = 42` ако иска
---
## 5. Какво НЕ правим (убийствено важно)
### ❌ Не правим LLVM backend сега
C transpiler-ът е предимство, не слабост:
- Компилира за <1 секунда
- Работи навсякъде (gcc, clang, msvc)
- Cross-compilation е безплатен (`--target` чрез C компилатора)
LLVM може да дойде Phase 10+ като опция.
### ❌ Не правим perfect borrow checker
Rust-ският borrow checker е титаничен труд (10 години, стотици хора).
Нашият цели 80% от ползата с 20% от кода:
- Само `&T` и `&mut T`
- Lifetime elision по default (без annotations в 90% от случаите)
- Без higher-ranked lifetime traits (HRTB) — твърде сложно
### ❌ Не се конкурираме с Rust по ecosystem
Crates.io е непреодолимо предимство. Ние се конкурираме с:
- Лесен FFI към C (всички C библиотеки са твои)
- По-малки програми, които не се нуждаят от 1000 dependencies
### ❌ Не правим ООП
Няма класове, inheritance, virtual functions. Interface-ите са за trait-like поведение, не за ООП.
---
## 6. Пътна карта за победа (реалистична)
### Milestone A: "Използваем за CLI tools" (2-3 седмици)
- ✅ Generics, Result/Option, pattern matching — готово
- 🔄 Fix `buxc2` bootstrap loop (14/14 modules)
- 🔄 File I/O, path ops, process spawn в stdlib
- 🎯 Target: Можеш да напишеш `bux` package manager на Bux
### Milestone B: "Използваем за systems programming" (2 месеца)
- 🔄 Working `@[Checked]` с basic borrow checking
- 🔄 CTFE за precomputed tables
- 🔄 Trait bounds (`T: Comparable`)
- 🎯 Target: Можеш да напишеш game engine или embedded firmware
### Milestone C: "Екосистема" (6 месеца)
- 🔄 Package manager (`bux add`, registry)
- 🔄 LSP (autocomplete, hover)
- 🔄 Formatter (`bux fmt`)
- 🔄 Green threads + channels
- 🎯 Target: Екип от 3 човека може да продуцира shipping продукт
### Milestone D: "Критична маса" (1-2 години)
- 🔄 1000+ пакета в registry
- 🔄 Първи corporate user (startup или game studio)
- 🔄 Self-hosted compiler стабилен
- 🎯 Target: "Знаеш ли Rust? Пробвай Bux ако трябва бързо."
---
## 7. Пазарно позициониране — как да говорим за Bux
### Грешно (никога не казваме това):
- "Bux е по-добър Rust" → хората се смеят и затварят таба
- "Bux е по-бърз от C" → лъжа, C backend сме
- "Bux е новият C++" → твърде голяма хапка
### Правилно (казваме това):
- "Bux е C с модерни типове и безопасност по избор"
- "Пиши като Go, контролирай като C, проверявай като Rust — когато решиш"
- "Единственият език, където safety е opt-in, не tax"
### Едно изречение:
> "Bux gives you Rust's safety when you want it, C's freedom when you need it, and Go's simplicity all the time."
---
## 8. Заключение
Bux не печели като бие Rust, Nim или Zig.
Bux печели като **запълва празното място между тях**.
| Ако искаш... | Избираш |
|--------------|---------|
| Максимална безопасност на всяка цена | Rust |
| Максимална скорост на прототипиране с GC | Nim |
| Максимален контрол и прозрачност | Zig |
| **Баланс — бързо писане + безопасност по избор** | **Bux** |
**Фаза 8 е оръжейната:** Gradual ownership + CTFE + Traits + Concurrency.
Ако имплементираме 8.2 (ownership) правилно — като opt-in upgrade, не като данък — Bux става единствен на пазара.
Ако го объркаме и стане "Rust-lite" — сме мъртви.
+130
View File
@@ -0,0 +1,130 @@
# Bux Package Manager
> **Status:** Implemented (Phase 9.1) | **Format:** Compatible with Rux.toml spec
---
## Manifest (`bux.toml`)
Every Bux package has a `bux.toml` at the project root.
```toml
[Package]
Name = "MyApp"
Version = "0.1.0"
Type = "bin" # bin | lib | shared | static
Authors = ["Your Name <you@example.com>"]
License = "MIT"
[Build]
Output = "Bin"
[Dependencies]
Std = "1.0"
Json = { Version = "2.1", Source = "https://github.com/bux-lang/json" }
Utils = { Path = "../Utils" }
```
### Dependency Forms
| Form | Example | Description |
|------|---------|-------------|
| Version string | `Std = "1.0"` | Registry dependency |
| Wildcard | `Std = "*"` | Latest version |
| Inline table (git) | `{ Version = "1.4", Source = "https://..." }` | Git URL + version |
| Inline table (path) | `{ Path = "../Lib" }` | Local path dependency |
---
## CLI Commands
### `bux add <name> [version]`
Add a dependency to `bux.toml`.
```bash
# Add registry dependency
bux add json "2.1"
# Add path-based dependency
bux add utils --path "../utils"
# Add git dependency
bux add network --git "https://github.com/bux-lang/network"
```
### `bux install`
Resolve dependencies and generate `bux.lock`.
```bash
bux install
```
What it does:
1. Reads `[Dependencies]` from `bux.toml`
2. Resolves path-based deps (verifies directory exists)
3. Clones/pulls git-based deps to `~/.bux/packages/<name>/`
4. Generates `bux.lock` with exact versions and sources
### `bux build` / `bux run`
Automatically reads `bux.lock` and merges dependency source files into the build.
```bash
bux build # Compile with all dependencies
bux run # Build and run
```
---
## Lockfile (`bux.lock`)
Auto-generated. **Do not edit manually.**
```toml
[[Package]]
Name = "json"
Version = "2.1.3"
Source = "https://github.com/bux-lang/json"
Checksum = "8dcb2a7f..."
[[Package]]
Name = "utils"
Version = "0.1.0"
Source = "/home/user/projects/utils"
```
The lockfile ensures **reproducible builds** — every developer gets the exact same dependency versions.
---
## Dependency Resolution Rules
1. **Path-based** deps are resolved relative to the manifest directory
2. **Git-based** deps are cloned to `~/.bux/packages/<name>/`
3. **Version-based** deps (without Source) require a registry (future feature)
4. Dependencies are loaded from `<dep>/src/*.bux` at build time
5. Later declarations shadow earlier ones (project > deps > stdlib)
---
## Example: Creating a Library
```bash
bux new mylib
cd mylib
# Edit src/Main.bux → module MyLib { pub func Add(...) }
bux build # Builds as library (Type = "lib")
```
## Example: Using a Library
```bash
bux new myapp
cd myapp
bux add mylib --path "../mylib"
bux install
# Edit src/Main.bux → import MyLib::Add;
bux run
```
+19
View File
@@ -0,0 +1,19 @@
import Std::Io::PrintInt;
// const func — evaluated at compile time
const func Factorial(n: int) -> int {
if n <= 1 {
return 1;
}
return n * Factorial(n - 1);
}
// Compile-time constants
const FIB_10 = Factorial(10);
const SIMPLE = 5 + 3 * 2;
func Main() -> int {
PrintInt(FIB_10); // 3628800 — computed at compile time
PrintInt(SIMPLE); // 11 — computed at compile time
return 0;
}
+47
View File
@@ -0,0 +1,47 @@
import Std::Io::{PrintLine, PrintInt};
// @[Checked] enables borrow checking for this function.
// &T = shared reference (read-only)
// &mut T = mutable reference (exclusive)
@[Checked]
func ScaleInPlace(val: &mut int, factor: int) {
*val = *val * factor;
}
@[Checked]
func GetValue(val: &int) -> int {
return *val;
}
// Unchecked functions allow raw pointers without restrictions
func UncheckedSwap(a: *int, b: *int) {
let tmp = *a;
*a = *b;
*b = tmp;
}
func Main() -> int {
var x: int = 10;
// &mut allows mutation
ScaleInPlace(&x, 3);
PrintInt(x); // 30
PrintLine("");
// & allows reading
let y: int = GetValue(&x);
PrintInt(y); // 30
PrintLine("");
// Unchecked: raw pointers work like C
var a: int = 5;
var b: int = 7;
UncheckedSwap(&a, &b);
PrintInt(a); // 7
PrintLine("");
PrintInt(b); // 5
PrintLine("");
return 0;
}
+28
View File
@@ -0,0 +1,28 @@
import Std::Io::{PrintLine, PrintInt};
// Trait (interface) definition
interface Drawable {
func Draw(self: &Self);
}
// Type that implements the trait
struct Circle {
radius: int;
}
extend Circle for Drawable {
func Draw(self: &Circle) {
PrintLine("Drawing circle");
}
}
// Generic function with trait bound: T must implement Drawable
func Render<T: Drawable>(obj: T) {
obj.Draw();
}
func Main() -> int {
let c: Circle = Circle { radius: 5 };
Render(c); // OK: Circle implements Drawable
return 0;
}
+10 -3
View File
@@ -272,6 +272,13 @@ type
of skDecl: of skDecl:
stmtDecl*: Decl stmtDecl*: Decl
# ---------------------------------------------------------------------------
# Type Parameters (for generics with trait bounds)
# ---------------------------------------------------------------------------
TypeParam* = object
name*: string
bounds*: seq[string] ## e.g. ["Comparable"] for <T: Comparable>
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Declarations # Declarations
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -325,13 +332,13 @@ type
declFuncCallConv*: CallingConvention declFuncCallConv*: CallingConvention
declFuncConst*: bool ## const func — evaluable at compile time declFuncConst*: bool ## const func — evaluable at compile time
declFuncName*: string declFuncName*: string
declFuncTypeParams*: seq[string] declFuncTypeParams*: seq[TypeParam]
declFuncParams*: seq[Param] declFuncParams*: seq[Param]
declFuncReturnType*: TypeExpr ## nil if void/inferred declFuncReturnType*: TypeExpr ## nil if void/inferred
declFuncBody*: Block ## nil for signature-only declFuncBody*: Block ## nil for signature-only
of dkStruct: of dkStruct:
declStructName*: string declStructName*: string
declStructTypeParams*: seq[string] declStructTypeParams*: seq[TypeParam]
declStructFields*: seq[StructField] declStructFields*: seq[StructField]
of dkEnum: of dkEnum:
declEnumName*: string declEnumName*: string
@@ -345,7 +352,7 @@ type
declInterfaceMethods*: seq[Decl] ## FuncDecl signatures only declInterfaceMethods*: seq[Decl] ## FuncDecl signatures only
of dkImpl: of dkImpl:
declImplTypeName*: string declImplTypeName*: string
declImplTypeParams*: seq[string] ## type parameters for generic impl: extend Box<T> declImplTypeParams*: seq[TypeParam] ## type parameters for generic impl: extend Box<T>
declImplInterface*: string ## empty if not for interface declImplInterface*: string ## empty if not for interface
declImplMethods*: seq[Decl] declImplMethods*: seq[Decl]
of dkModule: of dkModule:
+1 -1
View File
@@ -56,7 +56,7 @@ proc typeToC*(typ: Type): string =
of tkUInt: return "unsigned int" of tkUInt: return "unsigned int"
of tkFloat32: return "float" of tkFloat32: return "float"
of tkFloat64: return "double" of tkFloat64: return "double"
of tkPointer: of tkPointer, tkRef, tkMutRef:
if typ.inner.len > 0: if typ.inner.len > 0:
return typeToC(typ.inner[0]) & "*" return typeToC(typ.inner[0]) & "*"
return "void*" return "void*"
+156 -3
View File
@@ -20,6 +20,8 @@ Usage: bux [options] <command> [command-options]
Commands: Commands:
new <name> Create a new Bux package new <name> Create a new Bux package
init Initialize a Bux package in the current directory init Initialize a Bux package in the current directory
add <name> [ver] Add a dependency (--path, --git)
install Resolve and install dependencies
build Build the current package build Build the current package
run Build and run the current package run Build and run the current package
check Type-check the current package check Type-check the current package
@@ -141,9 +143,118 @@ Output = "Bin"
printInfo(&"Initialized Bux package '{name}'", useColor) printInfo(&"Initialized Bux package '{name}'", useColor)
return 0 return 0
proc cmdAdd*(args: seq[string], opts: GlobalOptions): int =
let useColor = shouldUseColor(opts)
let root = getCurrentDir()
let manifestPath = root / "bux.toml"
if not fileExists(manifestPath):
printError("no bux.toml found", useColor)
return 1
if args.len == 0:
printError("usage: bux add <name> [version] [--path <path>] [--git <url>]", useColor)
return 1
let depName = args[0]
var version = "*"
var path = ""
var gitUrl = ""
var i = 1
while i < args.len:
case args[i]
of "--path":
if i + 1 < args.len:
path = args[i + 1]
inc i
else:
printError("--path requires a value", useColor)
return 1
of "--git":
if i + 1 < args.len:
gitUrl = args[i + 1]
inc i
else:
printError("--git requires a value", useColor)
return 1
else:
version = args[i]
inc i
# Append to bux.toml
var depLine = ""
if path.len > 0:
depLine = &"{depName} = {{ Path = \"{path}\" }}"
elif gitUrl.len > 0:
depLine = &"{depName} = {{ Version = \"{version}\", Source = \"{gitUrl}\" }}"
else:
depLine = &"{depName} = \"{version}\""
var content = readFile(manifestPath)
# Ensure [Dependencies] section exists
if content.find("[Dependencies]") < 0:
content.add("\n[Dependencies]\n")
# Append dependency line
content.add(depLine & "\n")
writeFile(manifestPath, content)
if not opts.quiet:
printInfo(&"Added dependency '{depName}' to bux.toml", useColor)
return 0
proc cmdInstall*(args: seq[string], opts: GlobalOptions): int =
let useColor = shouldUseColor(opts)
let root = getCurrentDir()
let manifestPath = root / "bux.toml"
if not fileExists(manifestPath):
printError("no bux.toml found", useColor)
return 1
let man = loadManifest(manifestPath)
var lock = Lockfile(entries: @[])
let cacheDir = getHomeDir() / ".bux" / "packages"
if not dirExists(cacheDir):
createDir(cacheDir)
# Resolve each dependency
for dep in man.dependencies:
case dep.kind
of dkPath:
let absPath = if dep.path.isAbsolute: dep.path else: root / dep.path
if not dirExists(absPath):
printError(&"path dependency not found: {absPath}", useColor)
return 1
# Read dependency manifest
let depManifestPath = absPath / "bux.toml"
if fileExists(depManifestPath):
let depMan = loadManifest(depManifestPath)
lock.entries.add(LockEntry(name: dep.name, version: depMan.version, source: absPath))
else:
lock.entries.add(LockEntry(name: dep.name, version: "0.0.0", source: absPath))
if not opts.quiet:
printInfo(&"Resolved path dependency '{dep.name}' from {absPath}", useColor)
of dkGit:
let depDir = cacheDir / dep.name
if not dirExists(depDir):
if not opts.quiet:
printInfo(&"Cloning '{dep.name}' from {dep.gitUrl}...", useColor)
let (outp, code) = execCmdEx(&"git clone {dep.gitUrl} {depDir} 2>&1")
if code != 0:
printError(&"failed to clone {dep.gitUrl}: {outp}", useColor)
return 1
else:
if not opts.quiet:
printInfo(&"Using cached '{dep.name}' from {depDir}", useColor)
lock.entries.add(LockEntry(name: dep.name, version: dep.gitVersion, source: dep.gitUrl))
of dkVersion:
# For version-based deps without a registry, we just record them
# TODO: lookup in registry
lock.entries.add(LockEntry(name: dep.name, version: dep.versionReq, source: "registry"))
if not opts.quiet:
printInfo(&"Recorded dependency '{dep.name}' = {dep.versionReq}", useColor)
# Save lockfile
let lockPath = root / "bux.lock"
saveLockfile(lockPath, lock)
if not opts.quiet:
printInfo(&"Generated {lockPath}", useColor)
return 0
proc collectStdlibDecls(stdlibDir: string): seq[Decl] proc collectStdlibDecls(stdlibDir: string): seq[Decl]
proc getDeclName(d: Decl): string proc getDeclName(d: Decl): string
proc mergeDecls(stdlibDecls: seq[Decl], userDecls: seq[Decl]): seq[Decl] proc mergeDecls(stdlibDecls: seq[Decl], userDecls: seq[Decl]): seq[Decl]
proc collectDepDecls(lock: Lockfile, root: string, opts: GlobalOptions): seq[Decl]
proc cmdCheck*(args: seq[string], opts: GlobalOptions): int = proc cmdCheck*(args: seq[string], opts: GlobalOptions): int =
let useColor = shouldUseColor(opts) let useColor = shouldUseColor(opts)
@@ -205,7 +316,10 @@ proc cmdCheck*(args: seq[string], opts: GlobalOptions): int =
stdlibDir = path stdlibDir = path
break break
let stdlibDecls = collectStdlibDecls(stdlibDir) let stdlibDecls = collectStdlibDecls(stdlibDir)
let mergedItems = mergeDecls(stdlibDecls, allModuleItems) let lock = loadLockfile(root / "bux.lock")
let depDecls = collectDepDecls(lock, root, opts)
let stdlibAndDeps = mergeDecls(stdlibDecls, depDecls)
let mergedItems = mergeDecls(stdlibAndDeps, allModuleItems)
var unifiedModule = newModule("main") var unifiedModule = newModule("main")
unifiedModule.items = mergedItems unifiedModule.items = mergedItems
@@ -251,6 +365,38 @@ proc getDeclName(d: Decl): string =
of dkTypeAlias: d.declAliasName of dkTypeAlias: d.declAliasName
else: "" else: ""
proc collectDepDecls(lock: Lockfile, root: string, opts: GlobalOptions): seq[Decl] =
## Collect declarations from all locked dependencies.
let cacheDir = getHomeDir() / ".bux" / "packages"
let useColor = shouldUseColor(opts)
for entry in lock.entries:
var depSrcDir = ""
if dirExists(entry.source):
# Path-based dependency
depSrcDir = entry.source / "src"
elif entry.source.startsWith("http") or entry.source.startsWith("git@"):
# Git-based dependency in cache
depSrcDir = cacheDir / entry.name / "src"
if depSrcDir == "" or not dirExists(depSrcDir):
continue
for kind, path in walkDir(depSrcDir):
if kind == pcFile and path.endsWith(".bux"):
let source = readFile(path)
let lexRes = tokenize(source, path)
if lexRes.hasErrors:
continue
let parseRes = parse(lexRes.tokens, path)
if parseRes.diagnostics.len > 0:
continue
for decl in parseRes.module.items:
if decl.kind == dkModule:
for sub in decl.declModuleItems:
result.add(sub)
else:
result.add(decl)
if not opts.quiet:
printInfo(&"Loaded dependency '{entry.name}' from {depSrcDir}", useColor)
proc mergeDecls(stdlibDecls: seq[Decl], userDecls: seq[Decl]): seq[Decl] = proc mergeDecls(stdlibDecls: seq[Decl], userDecls: seq[Decl]): seq[Decl] =
## Merge stdlib and user declarations. ## Merge stdlib and user declarations.
## User funcs shadow stdlib funcs with the same name (simple overload avoidance). ## User funcs shadow stdlib funcs with the same name (simple overload avoidance).
@@ -301,6 +447,10 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
# Collect stdlib declarations once # Collect stdlib declarations once
let stdlibDecls = collectStdlibDecls(stdlibDir) let stdlibDecls = collectStdlibDecls(stdlibDir)
# Collect dependency declarations from lockfile
let lock = loadLockfile(root / "bux.lock")
let depDecls = collectDepDecls(lock, root, opts)
# Phase 1: Parse all .bux files and collect declarations # Phase 1: Parse all .bux files and collect declarations
var allModuleItems: seq[Decl] = @[] var allModuleItems: seq[Decl] = @[]
var foundMain = false var foundMain = false
@@ -335,8 +485,9 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
printError("no Main.bux found in src/", useColor) printError("no Main.bux found in src/", useColor)
return 1 return 1
# Phase 2: Merge all project declarations with stdlib # Phase 2: Merge stdlib + deps + project (later shadow earlier)
let mergedItems = mergeDecls(stdlibDecls, allModuleItems) let stdlibAndDeps = mergeDecls(stdlibDecls, depDecls)
let mergedItems = mergeDecls(stdlibAndDeps, allModuleItems)
# Create unified module # Create unified module
var unifiedModule = newModule("main") var unifiedModule = newModule("main")
@@ -441,6 +592,8 @@ proc runCli*(args: seq[string]): int =
case cmd case cmd
of "new": return cmdNew(cmdArgs, opts) of "new": return cmdNew(cmdArgs, opts)
of "init": return cmdInit(cmdArgs, opts) of "init": return cmdInit(cmdArgs, opts)
of "add": return cmdAdd(cmdArgs, opts)
of "install": return cmdInstall(cmdArgs, opts)
of "build": return cmdBuild(cmdArgs, opts) of "build": return cmdBuild(cmdArgs, opts)
of "run": return cmdRun(cmdArgs, opts) of "run": return cmdRun(cmdArgs, opts)
of "check": return cmdCheck(cmdArgs, opts) of "check": return cmdCheck(cmdArgs, opts)
+24 -28
View File
@@ -166,6 +166,10 @@ proc substituteType(ctx: var LowerCtx, te: TypeExpr, subst: Table[string, Type])
return ctx.resolveTypeExpr(te) return ctx.resolveTypeExpr(te)
of tekPointer: of tekPointer:
return makePointer(substituteType(ctx, te.pointerPointee, subst)) return makePointer(substituteType(ctx, te.pointerPointee, subst))
of tekRef:
return makeRef(substituteType(ctx, te.pointerPointee, subst))
of tekMutRef:
return makeMutRef(substituteType(ctx, te.pointerPointee, subst))
of tekSlice: of tekSlice:
return makeSlice(substituteType(ctx, te.sliceElement, subst)) return makeSlice(substituteType(ctx, te.sliceElement, subst))
of tekTuple: of tekTuple:
@@ -194,7 +198,7 @@ proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type =
var concreteArgs: seq[Type] = @[] var concreteArgs: seq[Type] = @[]
for j, tp in genericDecl.declStructTypeParams: for j, tp in genericDecl.declStructTypeParams:
if j < te.typeArgs.len: if j < te.typeArgs.len:
subst[tp] = ctx.resolveTypeExpr(te.typeArgs[j]) subst[tp.name] = ctx.resolveTypeExpr(te.typeArgs[j])
for arg in te.typeArgs: for arg in te.typeArgs:
concreteArgs.add(ctx.resolveTypeExpr(arg)) concreteArgs.add(ctx.resolveTypeExpr(arg))
for f in genericDecl.declStructFields: for f in genericDecl.declStructFields:
@@ -257,7 +261,7 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
if sym != nil and sym.typ != nil: return sym.typ if sym != nil and sym.typ != nil: return sym.typ
# Check local variables and parameters tracked in varTypeExprs # Check local variables and parameters tracked in varTypeExprs
if ctx.varTypeExprs.hasKey(expr.exprIdent): if ctx.varTypeExprs.hasKey(expr.exprIdent):
return ctx.resolveTypeExpr(ctx.varTypeExprs[expr.exprIdent]) return substituteType(ctx, ctx.varTypeExprs[expr.exprIdent], ctx.typeSubst)
# Check current function parameters (fallback for untracked params) # Check current function parameters (fallback for untracked params)
if ctx.currentFuncDecl != nil: if ctx.currentFuncDecl != nil:
var params: seq[Param] = @[] var params: seq[Param] = @[]
@@ -267,21 +271,7 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
else: discard else: discard
for p in params: for p in params:
if p.name == expr.exprIdent and p.ptype != nil: if p.name == expr.exprIdent and p.ptype != nil:
case p.ptype.kind return substituteType(ctx, p.ptype, ctx.typeSubst)
of tekNamed:
case p.ptype.typeName
of "int", "int32": return makeInt()
of "int64": return makeInt64()
of "float64": return makeFloat64()
of "float32": return makeFloat32()
of "bool": return makeBool()
of "uint": return makeUInt()
of "void": return makeVoid()
else: return makeNamed(p.ptype.typeName)
of tekPointer:
let pointeeType = ctx.resolveTypeExpr(p.ptype.pointerPointee)
return makePointer(pointeeType)
else: discard
return makeUnknown() return makeUnknown()
of ekSelf: of ekSelf:
# Look up self parameter type from current function # Look up self parameter type from current function
@@ -329,7 +319,7 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
of ekField: of ekField:
var objType = ctx.resolveExprType(expr.exprFieldObj) var objType = ctx.resolveExprType(expr.exprFieldObj)
# Auto-dereference pointer types for field access # Auto-dereference pointer types for field access
if objType.kind == tkPointer and objType.inner.len > 0: if objType.isPointer and objType.inner.len > 0:
objType = objType.inner[0] objType = objType.inner[0]
if objType.kind == tkNamed: if objType.kind == tkNamed:
let sym = ctx.globalScope.lookup(objType.name) let sym = ctx.globalScope.lookup(objType.name)
@@ -460,7 +450,13 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
var receiverTypeName = "" var receiverTypeName = ""
if receiverType.kind == tkNamed: if receiverType.kind == tkNamed:
receiverTypeName = receiverType.name receiverTypeName = receiverType.name
elif receiverType.kind == tkPointer and receiverType.inner.len > 0 and receiverType.inner[0].kind == tkNamed: 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.isPointer and receiverType.inner.len > 0 and receiverType.inner[0].kind == tkNamed:
receiverTypeName = receiverType.inner[0].name receiverTypeName = receiverType.inner[0].name
# Look up method for receiver type specifically # Look up method for receiver type specifically
@@ -477,7 +473,7 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
var args: seq[HirNode] = @[] var args: seq[HirNode] = @[]
let loweredReceiver = ctx.lowerExpr(receiverExpr) let loweredReceiver = ctx.lowerExpr(receiverExpr)
# Auto-address if method expects pointer but receiver is value # Auto-address if method expects pointer but receiver is value
if minfo.params.len > 0 and minfo.params[0].kind == tkPointer and receiverType.kind != tkPointer: if minfo.params.len > 0 and minfo.params[0].isPointer and not receiverType.isPointer:
args.add(hirUnary(tkAmp, loweredReceiver, makePointer(receiverType), loc)) args.add(hirUnary(tkAmp, loweredReceiver, makePointer(receiverType), loc))
else: else:
args.add(loweredReceiver) args.add(loweredReceiver)
@@ -558,7 +554,7 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
let objType = ctx.resolveExprType(expr.exprFieldObj) let objType = ctx.resolveExprType(expr.exprFieldObj)
let base = ctx.lowerExpr(expr.exprFieldObj) let base = ctx.lowerExpr(expr.exprFieldObj)
# Auto-dereference pointer types for field access # Auto-dereference pointer types for field access
if objType.kind == tkPointer: if objType.isPointer:
let arrowPtr = HirNode(kind: hArrowField, arrowFieldBase: base, let arrowPtr = HirNode(kind: hArrowField, arrowFieldBase: base,
arrowFieldName: expr.exprFieldName, arrowFieldName: expr.exprFieldName,
typ: makePointer(typ), loc: loc) typ: makePointer(typ), loc: loc)
@@ -1010,7 +1006,7 @@ proc generateMethodInstance(ctx: var LowerCtx, baseMethodName: string, typeArgs:
if i > 0: typeSuffix.add("_") if i > 0: typeSuffix.add("_")
if i < typeArgs.len: if i < typeArgs.len:
let argType = ctx.resolveTypeExpr(typeArgs[i]) let argType = ctx.resolveTypeExpr(typeArgs[i])
subst[tp] = argType subst[tp.name] = argType
typeSuffix.add(argType.toString) typeSuffix.add(argType.toString)
else: else:
typeSuffix.add("unknown") typeSuffix.add("unknown")
@@ -1178,12 +1174,12 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
let targ = inst.typeArgs[j] let targ = inst.typeArgs[j]
if targ.kind == tekNamed: if targ.kind == tekNamed:
case targ.typeName case targ.typeName
of "int", "int32": subst[tp] = makeInt() of "int", "int32": subst[tp.name] = makeInt()
of "int64": subst[tp] = makeInt64() of "int64": subst[tp.name] = makeInt64()
of "float64": subst[tp] = makeFloat64() of "float64": subst[tp.name] = makeFloat64()
of "float32": subst[tp] = makeFloat32() of "float32": subst[tp.name] = makeFloat32()
of "bool": subst[tp] = makeBool() of "bool": subst[tp.name] = makeBool()
else: subst[tp] = makeNamed(targ.typeName) else: subst[tp.name] = makeNamed(targ.typeName)
# Create specialized declaration # Create specialized declaration
var specDecl = Decl( var specDecl = Decl(
+210 -15
View File
@@ -1,4 +1,4 @@
import std/[strutils, os, tables] import std/[strutils, os, tables, sequtils, strformat]
type type
PackageType* = enum PackageType* = enum
@@ -7,57 +7,245 @@ type
ptStaticLibrary ptStaticLibrary
ptSource ptSource
DepKind* = enum
dkVersion ## "1.0" or "*"
dkPath ## { Path = "../Lib" }
dkGit ## { Version = "1.4", Source = "https://..." }
Dependency* = object
name*: string
case kind*: DepKind
of dkVersion:
versionReq*: string
of dkPath:
path*: string
of dkGit:
gitUrl*: string
gitVersion*: string
Manifest* = object Manifest* = object
name*: string name*: string
version*: string version*: string
pkgType*: PackageType pkgType*: PackageType
output*: string ## from [Build] Output output*: string
dependencies*: OrderedTableRef[string, string] dependencies*: seq[Dependency]
devDependencies*: seq[Dependency]
buildDependencies*: seq[Dependency]
# ---------------------------------------------------------------------------
# Extended TOML parser (supports inline tables and arrays)
# ---------------------------------------------------------------------------
type type
TomlValueKind = enum TomlValueKind = enum
tvkString, tvkTable tvkString, tvkTable, tvkArray, tvkInlineTable
TomlValue = object TomlValue = object
case kind*: TomlValueKind case kind*: TomlValueKind
of tvkString: of tvkString:
strVal*: string strVal*: string
of tvkTable: of tvkTable:
tableVal*: OrderedTableRef[string, TomlValue] tableVal*: OrderedTableRef[string, TomlValue]
of tvkArray:
arrayVal*: seq[TomlValue]
of tvkInlineTable:
inlineVal*: OrderedTableRef[string, TomlValue]
proc parseSimpleToml(path: string): OrderedTableRef[string, TomlValue] = proc parseInlineTable(s: string): OrderedTableRef[string, TomlValue] =
result = newOrderedTable[string, TomlValue]()
var content = s.strip()
if content.len >= 2 and content[0] == '{' and content[^1] == '}':
content = content[1 ..< ^1].strip()
var i = 0
while i < content.len:
# skip whitespace
while i < content.len and content[i] in Whitespace:
inc i
if i >= content.len: break
# parse key
var keyStart = i
while i < content.len and content[i] notin {'=', ' ', '\t'}:
inc i
let key = content[keyStart ..< i].strip()
# skip to =
while i < content.len and content[i] in Whitespace:
inc i
if i >= content.len or content[i] != '=': break
inc i
while i < content.len and content[i] in Whitespace:
inc i
# parse value
var valStart = i
if i < content.len and content[i] == '"':
inc i
while i < content.len and content[i] != '"':
inc i
if i < content.len: inc i
let val = content[valStart + 1 ..< i - 1]
result[key] = TomlValue(kind: tvkString, strVal: val)
elif i < content.len and content[i] == '{':
# nested inline table (skip for now)
var braceCount = 1
inc i
while i < content.len and braceCount > 0:
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'}:
inc i
let val = content[valStart ..< i].strip()
result[key] = TomlValue(kind: tvkString, strVal: val)
# skip to comma or end
while i < content.len and content[i] in Whitespace:
inc i
if i < content.len and content[i] == ',':
inc i
proc parseArray(s: string): seq[TomlValue] =
result = @[]
var content = s.strip()
if content.len >= 2 and content[0] == '[' and content[^1] == ']':
content = content[1 ..< ^1].strip()
if content.len == 0: return
for item in content.split(','):
let val = item.strip()
if val.len >= 2 and val[0] == '"' and val[^1] == '"':
result.add(TomlValue(kind: tvkString, strVal: val[1 ..< ^1]))
else:
result.add(TomlValue(kind: tvkString, strVal: val))
proc parseValue(valStr: string): TomlValue =
let val = valStr.strip()
if val.len >= 2 and val[0] == '"' and val[^1] == '"':
return TomlValue(kind: tvkString, strVal: val[1 ..< ^1])
elif val.len >= 2 and val[0] == '[' and val[^1] == ']':
return TomlValue(kind: tvkArray, arrayVal: parseArray(val))
elif val.len >= 2 and val[0] == '{' and val[^1] == '}':
return TomlValue(kind: tvkInlineTable, inlineVal: parseInlineTable(val))
else:
return TomlValue(kind: tvkString, strVal: val)
proc parseToml*(path: string): OrderedTableRef[string, TomlValue] =
result = newOrderedTable[string, TomlValue]() result = newOrderedTable[string, TomlValue]()
if not fileExists(path): if not fileExists(path):
return result return result
let content = readFile(path) let content = readFile(path)
var currentTable = "" var currentTable = ""
var currentArrayTable = ""
for rawLine in content.splitLines(): for rawLine in content.splitLines():
let line = rawLine.strip() let line = rawLine.strip()
if line.len == 0 or line.startsWith("#"): if line.len == 0 or line.startsWith("#"):
continue continue
if line.startsWith("[") and line.endsWith("]"): if line.startsWith("[[") and line.endsWith("]]"):
# Array of tables
currentArrayTable = line[2 ..< ^2]
currentTable = ""
let newTable = TomlValue(kind: tvkTable, tableVal: newOrderedTable[string, TomlValue]())
if not result.hasKey(currentArrayTable):
result[currentArrayTable] = TomlValue(kind: tvkArray, arrayVal: @[newTable])
else:
result[currentArrayTable].arrayVal.add(newTable)
elif line.startsWith("[") and line.endsWith("]"):
currentTable = line[1 ..< ^1] currentTable = line[1 ..< ^1]
currentArrayTable = ""
if not result.hasKey(currentTable): if not result.hasKey(currentTable):
result[currentTable] = TomlValue(kind: tvkTable, tableVal: newOrderedTable[string, TomlValue]()) result[currentTable] = TomlValue(kind: tvkTable, tableVal: newOrderedTable[string, TomlValue]())
else: else:
let eqIdx = line.find('=') let eqIdx = line.find('=')
if eqIdx >= 0: if eqIdx >= 0:
let key = line[0 ..< eqIdx].strip() let key = line[0 ..< eqIdx].strip()
var val = line[eqIdx + 1 .. ^1].strip() let valStr = line[eqIdx + 1 .. ^1].strip()
# Remove surrounding quotes let val = parseValue(valStr)
if val.len >= 2 and val[0] == '"' and val[^1] == '"': if currentArrayTable != "" and result.hasKey(currentArrayTable):
val = val[1 ..< ^1] let arr = result[currentArrayTable].arrayVal
if currentTable != "" and result.hasKey(currentTable): if arr.len > 0:
result[currentTable].tableVal[key] = TomlValue(kind: tvkString, strVal: val) arr[^1].tableVal[key] = val
elif currentTable != "" and result.hasKey(currentTable):
result[currentTable].tableVal[key] = val
else: else:
result[key] = TomlValue(kind: tvkString, strVal: val) result[key] = val
proc parseDepTable(table: OrderedTableRef[string, TomlValue]): seq[Dependency] =
result = @[]
for name, val in table:
case val.kind
of tvkString:
result.add(Dependency(name: name, kind: dkVersion, versionReq: val.strVal))
of tvkInlineTable:
let t = val.inlineVal
if t.hasKey("Path"):
result.add(Dependency(name: name, kind: dkPath, path: t["Path"].strVal))
elif t.hasKey("Source"):
var ver = "*"
if t.hasKey("Version"):
ver = t["Version"].strVal
result.add(Dependency(name: name, kind: dkGit, gitUrl: t["Source"].strVal, gitVersion: ver))
elif t.hasKey("Version"):
result.add(Dependency(name: name, kind: dkVersion, versionReq: t["Version"].strVal))
else:
result.add(Dependency(name: name, kind: dkVersion, versionReq: "*"))
else:
discard
# ---------------------------------------------------------------------------
# Lockfile
# ---------------------------------------------------------------------------
type
LockEntry* = object
name*: string
version*: string
source*: string
checksum*: string
Lockfile* = object
entries*: seq[LockEntry]
proc loadLockfile*(path: string): Lockfile =
result.entries = @[]
if not fileExists(path):
return result
let data = parseToml(path)
if data.hasKey("Package"):
let arr = data["Package"]
if arr.kind == tvkArray:
for item in arr.arrayVal:
if item.kind == tvkTable:
let t = item.tableVal
var entry = LockEntry()
if t.hasKey("Name"): entry.name = t["Name"].strVal
if t.hasKey("Version"): entry.version = t["Version"].strVal
if t.hasKey("Source"): entry.source = t["Source"].strVal
if t.hasKey("Checksum"): entry.checksum = t["Checksum"].strVal
result.entries.add(entry)
proc saveLockfile*(path: string, lock: Lockfile) =
var lines: seq[string] = @[]
for entry in lock.entries:
lines.add("[[Package]]")
lines.add(&"Name = \"{entry.name}\"")
lines.add(&"Version = \"{entry.version}\"")
lines.add(&"Source = \"{entry.source}\"")
if entry.checksum.len > 0:
lines.add(&"Checksum = \"{entry.checksum}\"")
lines.add("")
writeFile(path, lines.join("\n"))
# ---------------------------------------------------------------------------
# Manifest loader
# ---------------------------------------------------------------------------
proc loadManifest*(path: string): Manifest = proc loadManifest*(path: string): Manifest =
let data = parseSimpleToml(path) let data = parseToml(path)
result.name = "" result.name = ""
result.version = "0.1.0" result.version = "0.1.0"
result.pkgType = ptExecutable result.pkgType = ptExecutable
result.output = "Bin" result.output = "Bin"
result.dependencies = newOrderedTable[string, string]() result.dependencies = @[]
result.devDependencies = @[]
result.buildDependencies = @[]
if data.hasKey("Package"): if data.hasKey("Package"):
let pkg = data["Package"].tableVal let pkg = data["Package"].tableVal
@@ -77,3 +265,10 @@ proc loadManifest*(path: string): Manifest =
let bld = data["Build"].tableVal let bld = data["Build"].tableVal
if bld.hasKey("Output"): if bld.hasKey("Output"):
result.output = bld["Output"].strVal result.output = bld["Output"].strVal
if data.hasKey("Dependencies"):
result.dependencies = parseDepTable(data["Dependencies"].tableVal)
if data.hasKey("DevDependencies"):
result.devDependencies = parseDepTable(data["DevDependencies"].tableVal)
if data.hasKey("BuildDependencies"):
result.buildDependencies = parseDepTable(data["BuildDependencies"].tableVal)
+22 -2
View File
@@ -212,6 +212,9 @@ proc parseBaseType(p: var Parser): TypeExpr =
return TypeExpr(kind: tekPointer, loc: loc, pointerPointee: p.parseBaseType()) return TypeExpr(kind: tekPointer, loc: loc, pointerPointee: p.parseBaseType())
of tkAmp: of tkAmp:
discard p.advance() discard p.advance()
if p.check(tkMut):
discard p.advance()
return TypeExpr(kind: tekMutRef, loc: loc, pointerPointee: p.parseBaseType())
return TypeExpr(kind: tekRef, loc: loc, pointerPointee: p.parseBaseType()) return TypeExpr(kind: tekRef, loc: loc, pointerPointee: p.parseBaseType())
of tkLParen: of tkLParen:
discard p.advance() discard p.advance()
@@ -894,11 +897,27 @@ proc parseStmt(p: var Parser): Stmt =
# Declarations # Declarations
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
proc parseTypeParams(p: var Parser): seq[string] = proc parseTypeParams(p: var Parser): seq[TypeParam] =
if p.check(tkLt): if p.check(tkLt):
discard p.advance() discard p.advance()
while not p.check(tkGt) and not p.isAtEnd: while not p.check(tkGt) and not p.isAtEnd:
result.add(p.expect(tkIdent, "expected type parameter name").text) let name = p.expect(tkIdent, "expected type parameter name").text
var bounds: seq[string] = @[]
if p.check(tkColon):
discard p.advance()
# Parse bound: single identifier or path like Std::Comparable
var boundName = ""
while true:
let part = p.expect(tkIdent, "expected trait/interface name").text
if boundName.len > 0:
boundName.add("_")
boundName.add(part)
if p.check(tkColonColon):
discard p.advance()
else:
break
bounds.add(boundName)
result.add(TypeParam(name: name, bounds: bounds))
if p.check(tkComma): if p.check(tkComma):
discard p.advance() discard p.advance()
discard p.expect(tkGt, "expected '>' to close type parameters") discard p.expect(tkGt, "expected '>' to close type parameters")
@@ -1241,6 +1260,7 @@ proc parseDecl(p: var Parser): Decl =
var attrs = ParsedAttrs() var attrs = ParsedAttrs()
if p.check(tkAt): if p.check(tkAt):
attrs = p.parseAttrs() attrs = p.parseAttrs()
p.skipNewlines()
var isConst = false var isConst = false
if p.check(tkConst) and p.peek(1) == tkFunc: if p.check(tkConst) and p.peek(1) == tkFunc:
+242 -17
View File
@@ -20,6 +20,16 @@ type
params*: seq[Type] params*: seq[Type]
retType*: Type retType*: Type
CtValueKind = enum
ctkVoid, ctkInt, ctkBool, ctkString
CtValue = object
case kind: CtValueKind
of ctkVoid: discard
of ctkInt: intVal: int64
of ctkBool: boolVal: bool
of ctkString: strVal: string
Sema* = object Sema* = object
module*: Module module*: Module
globalScope*: Scope globalScope*: Scope
@@ -30,6 +40,8 @@ type
methodTable*: Table[string, seq[MethodInfo]] methodTable*: Table[string, seq[MethodInfo]]
# Interface name -> interface decl # Interface name -> interface decl
interfaceTable*: Table[string, Decl] interfaceTable*: Table[string, Decl]
# Borrow checker state
checkedFunc*: bool ## true inside @[Checked] function
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Helpers # Helpers
@@ -94,6 +106,16 @@ proc typeToTypeExpr(t: Type): TypeExpr =
TypeExpr(kind: tekPointer, pointerPointee: typeToTypeExpr(t.inner[0])) TypeExpr(kind: tekPointer, pointerPointee: typeToTypeExpr(t.inner[0]))
else: else:
TypeExpr(kind: tekNamed, typeName: "void") TypeExpr(kind: tekNamed, typeName: "void")
of tkRef:
if t.inner.len > 0:
TypeExpr(kind: tekRef, pointerPointee: typeToTypeExpr(t.inner[0]))
else:
TypeExpr(kind: tekNamed, typeName: "void")
of tkMutRef:
if t.inner.len > 0:
TypeExpr(kind: tekMutRef, pointerPointee: typeToTypeExpr(t.inner[0]))
else:
TypeExpr(kind: tekNamed, typeName: "void")
of tkVoid: TypeExpr(kind: tekNamed, typeName: "void") of tkVoid: TypeExpr(kind: tekNamed, typeName: "void")
else: TypeExpr(kind: tekNamed, typeName: t.toString) else: TypeExpr(kind: tekNamed, typeName: t.toString)
@@ -102,7 +124,8 @@ proc inferTypeArgs(sema: var Sema, funcDecl: Decl, argTypes: seq[Type],
## Infer type arguments from argument types for a generic function call. ## Infer type arguments from argument types for a generic function call.
## Returns empty seq if inference fails for any type parameter. ## Returns empty seq if inference fails for any type parameter.
result = @[] result = @[]
for tpName in funcDecl.declFuncTypeParams: for tp in funcDecl.declFuncTypeParams:
let tpName = tp.name
var inferred: Type = nil var inferred: Type = nil
for i, param in funcDecl.declFuncParams: for i, param in funcDecl.declFuncParams:
if i >= argTypes.len: break if i >= argTypes.len: break
@@ -173,9 +196,9 @@ proc resolveType(sema: var Sema, te: TypeExpr): Type =
of tekPointer: of tekPointer:
return makePointer(sema.resolveType(te.pointerPointee)) return makePointer(sema.resolveType(te.pointerPointee))
of tekRef: of tekRef:
return makePointer(sema.resolveType(te.pointerPointee)) # &T → *T in bootstrap return makeRef(sema.resolveType(te.pointerPointee))
of tekMutRef: of tekMutRef:
return makePointer(sema.resolveType(te.pointerPointee)) # &mut T → *T in bootstrap return makeMutRef(sema.resolveType(te.pointerPointee))
of tekSlice: of tekSlice:
let elemType = sema.resolveType(te.sliceElement) let elemType = sema.resolveType(te.sliceElement)
return makeSlice(elemType) return makeSlice(elemType)
@@ -191,6 +214,162 @@ proc resolveType(sema: var Sema, te: TypeExpr): Type =
# First pass: collect global symbols # First pass: collect global symbols
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
# Compile-Time Function Execution (CTFE)
# ---------------------------------------------------------------------------
proc evalExpr(sema: Sema, expr: Expr, locals: Table[string, CtValue]): CtValue
proc evalBlock(sema: Sema, blk: Block, locals: Table[string, CtValue]): CtValue =
var localVars = locals
for stmt in blk.stmts:
case stmt.kind
of skLet:
if stmt.stmtLetInit != nil:
let val = sema.evalExpr(stmt.stmtLetInit, localVars)
if val.kind in {ctkInt, ctkBool, ctkString}:
localVars[stmt.stmtLetName] = val
of skIf:
let cond = sema.evalExpr(stmt.stmtIfCond, localVars)
if cond.kind == ctkBool:
if cond.boolVal:
let res = sema.evalBlock(stmt.stmtIfThen, localVars)
if res.kind != ctkVoid:
return res
elif stmt.stmtIfElse != nil:
let res = sema.evalBlock(stmt.stmtIfElse, localVars)
if res.kind != ctkVoid:
return res
# If condition is false and no else, continue to next statement
else:
return CtValue(kind: ctkVoid)
of skReturn:
if stmt.stmtReturnValue != nil:
return sema.evalExpr(stmt.stmtReturnValue, localVars)
return CtValue(kind: ctkVoid)
of skExpr:
let res = sema.evalExpr(stmt.stmtExpr, localVars)
if res.kind != ctkVoid:
return res
else:
discard
return CtValue(kind: ctkVoid)
proc evalExpr(sema: Sema, expr: Expr, locals: Table[string, CtValue]): CtValue =
if expr == nil:
return CtValue(kind: ctkVoid)
case expr.kind
of ekLiteral:
case expr.exprLit.kind
of tkIntLiteral:
return CtValue(kind: ctkInt, intVal: parseBiggestInt(expr.exprLit.text))
of tkBoolLiteral:
return CtValue(kind: ctkBool, boolVal: expr.exprLit.text == "true")
of tkStringLiteral:
return CtValue(kind: ctkString, strVal: expr.exprLit.text)
else:
return CtValue(kind: ctkVoid)
of ekIdent:
if locals.hasKey(expr.exprIdent):
return locals[expr.exprIdent]
# Check if it's a const global
let sym = sema.globalScope.lookup(expr.exprIdent)
if sym != nil and sym.decl != nil and sym.decl.kind == dkConst and sym.decl.declConstValue != nil:
return sema.evalExpr(sym.decl.declConstValue, locals)
return CtValue(kind: ctkVoid)
of ekUnary:
let operand = sema.evalExpr(expr.exprUnaryOperand, locals)
case expr.exprUnaryOp
of tkMinus:
if operand.kind == ctkInt:
return CtValue(kind: ctkInt, intVal: -operand.intVal)
of tkBang:
if operand.kind == ctkBool:
return CtValue(kind: ctkBool, boolVal: not operand.boolVal)
else:
discard
return CtValue(kind: ctkVoid)
of ekBinary:
let left = sema.evalExpr(expr.exprBinaryLeft, locals)
let right = sema.evalExpr(expr.exprBinaryRight, locals)
if left.kind == ctkInt and right.kind == ctkInt:
case expr.exprBinaryOp
of tkPlus: return CtValue(kind: ctkInt, intVal: left.intVal + right.intVal)
of tkMinus: return CtValue(kind: ctkInt, intVal: left.intVal - right.intVal)
of tkStar: return CtValue(kind: ctkInt, intVal: left.intVal * right.intVal)
of tkSlash:
if right.intVal != 0:
return CtValue(kind: ctkInt, intVal: left.intVal div right.intVal)
of tkPercent:
if right.intVal != 0:
return CtValue(kind: ctkInt, intVal: left.intVal mod right.intVal)
of tkEq: return CtValue(kind: ctkBool, boolVal: left.intVal == right.intVal)
of tkNe: return CtValue(kind: ctkBool, boolVal: left.intVal != right.intVal)
of tkLt: return CtValue(kind: ctkBool, boolVal: left.intVal < right.intVal)
of tkLe: return CtValue(kind: ctkBool, boolVal: left.intVal <= right.intVal)
of tkGt: return CtValue(kind: ctkBool, boolVal: left.intVal > right.intVal)
of tkGe: return CtValue(kind: ctkBool, boolVal: left.intVal >= right.intVal)
else: discard
elif left.kind == ctkBool and right.kind == ctkBool:
case expr.exprBinaryOp
of tkAmpAmp: return CtValue(kind: ctkBool, boolVal: left.boolVal and right.boolVal)
of tkPipePipe: return CtValue(kind: ctkBool, boolVal: left.boolVal or right.boolVal)
else: discard
return CtValue(kind: ctkVoid)
of ekTernary:
let cond = sema.evalExpr(expr.exprTernaryCond, locals)
if cond.kind == ctkBool:
if cond.boolVal:
return sema.evalExpr(expr.exprTernaryThen, locals)
else:
return sema.evalExpr(expr.exprTernaryElse, locals)
return CtValue(kind: ctkVoid)
of ekCall:
# Try to evaluate const func calls
if expr.exprCallCallee != nil and expr.exprCallCallee.kind == ekIdent:
let funcName = expr.exprCallCallee.exprIdent
let sym = sema.globalScope.lookup(funcName)
if sym != nil and sym.decl != nil and sym.decl.kind == dkFunc and sym.decl.declFuncConst:
# Evaluate arguments
var argVals: seq[CtValue] = @[]
for arg in expr.exprCallArgs:
argVals.add(sema.evalExpr(arg, locals))
# Build parameter locals
var callLocals = locals
for i, p in sym.decl.declFuncParams:
if i < argVals.len:
callLocals[p.name] = argVals[i]
# Evaluate function body
if sym.decl.declFuncBody != nil:
return sema.evalBlock(sym.decl.declFuncBody, callLocals)
return CtValue(kind: ctkVoid)
of ekBlock:
return sema.evalBlock(expr.exprBlock, locals)
else:
return CtValue(kind: ctkVoid)
proc constFoldConstDecl(sema: Sema, decl: Decl): bool =
## Try to evaluate a const declaration at compile time.
## Returns true if successful and modifies declConstValue to a literal.
if decl.kind != dkConst: return false
let val = sema.evalExpr(decl.declConstValue, initTable[string, CtValue]())
case val.kind
of ctkInt:
decl.declConstValue = Expr(kind: ekLiteral, loc: decl.loc,
exprLit: Token(kind: tkIntLiteral, text: $val.intVal, loc: decl.loc))
return true
of ctkBool:
decl.declConstValue = Expr(kind: ekLiteral, loc: decl.loc,
exprLit: Token(kind: tkBoolLiteral, text: $val.boolVal, loc: decl.loc))
return true
of ctkString:
decl.declConstValue = Expr(kind: ekLiteral, loc: decl.loc,
exprLit: Token(kind: tkStringLiteral, text: val.strVal, loc: decl.loc))
return true
of ctkVoid:
return false
proc collectGlobals*(sema: var Sema) = proc collectGlobals*(sema: var Sema) =
for decl in sema.module.items: for decl in sema.module.items:
case decl.kind case decl.kind
@@ -200,8 +379,8 @@ proc collectGlobals*(sema: var Sema) =
# Temporarily add type parameters to type table for resolution # Temporarily add type parameters to type table for resolution
var addedTypeParams: seq[string] = @[] var addedTypeParams: seq[string] = @[]
for tp in decl.declFuncTypeParams: for tp in decl.declFuncTypeParams:
sema.typeTable[tp] = makeTypeParam(tp) sema.typeTable[tp.name] = makeTypeParam(tp.name)
addedTypeParams.add(tp) addedTypeParams.add(tp.name)
# Build function type from params and return # Build function type from params and return
var params: seq[Type] = @[] var params: seq[Type] = @[]
for p in decl.declFuncParams: for p in decl.declFuncParams:
@@ -337,8 +516,8 @@ proc collectGlobals*(sema: var Sema) =
# If impl has type params, temporarily add them to type table # If impl has type params, temporarily add them to type table
var addedTypeParams: seq[string] = @[] var addedTypeParams: seq[string] = @[]
for tp in implTypeParams: for tp in implTypeParams:
sema.typeTable[tp] = makeTypeParam(tp) sema.typeTable[tp.name] = makeTypeParam(tp.name)
addedTypeParams.add(tp) addedTypeParams.add(tp.name)
for methodDecl in decl.declImplMethods: for methodDecl in decl.declImplMethods:
if methodDecl.kind == dkFunc: if methodDecl.kind == dkFunc:
# Propagate impl type params to method for HIR lowering # Propagate impl type params to method for HIR lowering
@@ -372,6 +551,10 @@ proc collectGlobals*(sema: var Sema) =
sema.typeTable.del(tp) sema.typeTable.del(tp)
else: else:
discard discard
# Second pass: evaluate const declarations after all functions are registered
for decl in sema.module.items:
if decl.kind == dkConst:
discard sema.constFoldConstDecl(decl)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Expression type checking # Expression type checking
@@ -380,6 +563,36 @@ proc collectGlobals*(sema: var Sema) =
proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type
proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type
proc typeImplements(sema: Sema, t: Type, interfaceName: string): bool =
## Check if a type implements an interface by verifying all required methods exist.
if t.isUnknown: return true
let typeName = if t.kind == tkNamed: t.name elif t.isPointer and t.inner.len > 0 and t.inner[0].kind == tkNamed: t.inner[0].name else: ""
if typeName == "": return false
if not sema.interfaceTable.hasKey(interfaceName):
return true # Unknown interface — be permissive in bootstrap
let iface = sema.interfaceTable[interfaceName]
let requiredMethods = iface.declInterfaceMethods
if not sema.methodTable.hasKey(typeName):
return false
let availableMethods = sema.methodTable[typeName]
for req in requiredMethods:
var found = false
for avail in availableMethods:
if avail.name == req.declFuncName:
found = true
break
if not found:
return false
return true
proc checkTraitBounds(sema: var Sema, funcDecl: Decl, inferredTypes: seq[Type], loc: SourceLocation) =
## Verify that inferred types satisfy their trait bounds.
for i, tp in funcDecl.declFuncTypeParams:
if i < inferredTypes.len and inferredTypes[i] != nil:
for bound in tp.bounds:
if not sema.typeImplements(inferredTypes[i], bound):
sema.emitError(loc, &"type '{inferredTypes[i].toString}' does not implement trait '{bound}'")
proc extractPatternBindings(sema: var Sema, pat: Pattern, scope: Scope) = proc extractPatternBindings(sema: var Sema, pat: Pattern, scope: Scope) =
## Add pattern-bound identifiers to scope with unknown type (best-effort) ## Add pattern-bound identifiers to scope with unknown type (best-effort)
if pat == nil: return if pat == nil: return
@@ -461,7 +674,7 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
return makeUnknown() return makeUnknown()
return operandType.inner[0] return operandType.inner[0]
of tkAmp: of tkAmp:
return makePointer(operandType) return makeMutRef(operandType)
else: else:
return operandType return operandType
of ekPostfix: of ekPostfix:
@@ -506,6 +719,11 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
let value = sema.checkExpr(expr.exprAssignValue, scope) let value = sema.checkExpr(expr.exprAssignValue, scope)
if not value.isAssignableTo(target): if not value.isAssignableTo(target):
sema.emitError(expr.loc, &"cannot assign {value.toString} to {target.toString}") sema.emitError(expr.loc, &"cannot assign {value.toString} to {target.toString}")
# Borrow check: cannot write through &T (shared reference) in @[Checked] functions
if sema.checkedFunc and expr.exprAssignTarget.kind == ekUnary and expr.exprAssignTarget.exprUnaryOp == tkStar:
let ptrType = sema.checkExpr(expr.exprAssignTarget.exprUnaryOperand, scope)
if ptrType.isRef:
sema.emitError(expr.loc, "cannot assign through shared reference '&T' in checked function — use '&mut T' instead")
return target return target
of ekTernary: of ekTernary:
let cond = sema.checkExpr(expr.exprTernaryCond, scope) let cond = sema.checkExpr(expr.exprTernaryCond, scope)
@@ -542,7 +760,7 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
if sym2 != nil and sym2.decl != nil and sym2.decl.kind == dkFunc: if sym2 != nil and sym2.decl != nil and sym2.decl.kind == dkFunc:
let typeParams = sym2.decl.declFuncTypeParams let typeParams = sym2.decl.declFuncTypeParams
for i, tp in typeParams: for i, tp in typeParams:
if retType.name == tp and i < expr.exprCallCallee.exprGenericTypeArgs.len: if retType.name == tp.name and i < expr.exprCallCallee.exprGenericTypeArgs.len:
# Substitute with concrete type # Substitute with concrete type
let concreteType = expr.exprCallCallee.exprGenericTypeArgs[i] let concreteType = expr.exprCallCallee.exprGenericTypeArgs[i]
if concreteType.kind == tekNamed: if concreteType.kind == tekNamed:
@@ -622,14 +840,19 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
let inferred = sema.inferTypeArgs(calleeDecl, argTypes, expr.loc) let inferred = sema.inferTypeArgs(calleeDecl, argTypes, expr.loc)
if inferred.len == calleeDecl.declFuncTypeParams.len: if inferred.len == calleeDecl.declFuncTypeParams.len:
expr.exprCallInferredTypeArgs = inferred expr.exprCallInferredTypeArgs = inferred
# Check trait bounds
var inferredTypes: seq[Type] = @[]
for te in inferred:
inferredTypes.add(sema.resolveType(te))
sema.checkTraitBounds(calleeDecl, inferredTypes, expr.loc)
# Substitute return type using inferred type args # Substitute return type using inferred type args
if calleeDecl.declFuncReturnType != nil: if calleeDecl.declFuncReturnType != nil:
var added: seq[string] = @[] var added: seq[string] = @[]
for i, tp in calleeDecl.declFuncTypeParams: for i, tp in calleeDecl.declFuncTypeParams:
if i < inferred.len: if i < inferred.len:
let concrete = sema.resolveType(inferred[i]) let concrete = sema.resolveType(inferred[i])
sema.typeTable[tp] = concrete sema.typeTable[tp.name] = concrete
added.add(tp) added.add(tp.name)
let retType = sema.resolveType(calleeDecl.declFuncReturnType) let retType = sema.resolveType(calleeDecl.declFuncReturnType)
for tp in added: for tp in added:
sema.typeTable.del(tp) sema.typeTable.del(tp)
@@ -666,8 +889,8 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
of ekField: of ekField:
let obj = sema.checkExpr(expr.exprFieldObj, scope) let obj = sema.checkExpr(expr.exprFieldObj, scope)
var objType = obj var objType = obj
# Auto-dereference pointer types for field access # Auto-dereference pointer/reference types for field access
if objType.kind == tkPointer and objType.inner.len > 0: if objType.kind in {tkPointer, tkRef, tkMutRef} and objType.inner.len > 0:
objType = objType.inner[0] objType = objType.inner[0]
if objType.kind == tkNamed: if objType.kind == tkNamed:
# Check if this is a _Data union field access # Check if this is a _Data union field access
@@ -858,7 +1081,6 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type =
else: else:
discard discard
return makeVoid() return makeVoid()
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Function body checking # Function body checking
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -870,12 +1092,14 @@ proc checkFunc(sema: var Sema, decl: Decl) =
# type parameters that cannot be fully resolved until monomorphization. # type parameters that cannot be fully resolved until monomorphization.
if decl.declFuncTypeParams.len > 0: if decl.declFuncTypeParams.len > 0:
return return
let wasChecked = sema.checkedFunc
sema.checkedFunc = "Checked" in decl.declAttrs
var funcScope = newScope(sema.globalScope) var funcScope = newScope(sema.globalScope)
# Add type parameters to type table for resolution # Add type parameters to type table for resolution
var addedTypeParams: seq[string] = @[] var addedTypeParams: seq[string] = @[]
for tp in decl.declFuncTypeParams: for tp in decl.declFuncTypeParams:
sema.typeTable[tp] = makeTypeParam(tp) sema.typeTable[tp.name] = makeTypeParam(tp.name)
addedTypeParams.add(tp) addedTypeParams.add(tp.name)
# Add parameters # Add parameters
for p in decl.declFuncParams: for p in decl.declFuncParams:
let pType = sema.resolveType(p.ptype) let pType = sema.resolveType(p.ptype)
@@ -887,6 +1111,7 @@ proc checkFunc(sema: var Sema, decl: Decl) =
# Clean up type parameters # Clean up type parameters
for tp in addedTypeParams: for tp in addedTypeParams:
sema.typeTable.del(tp) sema.typeTable.del(tp)
sema.checkedFunc = wasChecked
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Second pass: check all function bodies # Second pass: check all function bodies
@@ -898,7 +1123,7 @@ proc checkBodies(sema: var Sema) =
var funcCount = 0 var funcCount = 0
for decl in sema.module.items: for decl in sema.module.items:
if decl.kind == dkFunc: inc funcCount if decl.kind == dkFunc: inc funcCount
if funcCount > 50: if funcCount > 5000:
# Large module — only check Main # Large module — only check Main
for decl in sema.module.items: for decl in sema.module.items:
case decl.kind case decl.kind
+4 -1
View File
@@ -50,6 +50,7 @@ type
tkSuper # super tkSuper # super
tkSizeOf # sizeof tkSizeOf # sizeof
tkOwn # own (gradual ownership transfer) tkOwn # own (gradual ownership transfer)
tkMut # mut (mutable reference)
tkDiscard # discard (evaluate and throw away) tkDiscard # discard (evaluate and throw away)
##Punctuation ##Punctuation
@@ -142,7 +143,7 @@ proc isKeyword*(kind: TokenKind): bool =
tkBreak, tkContinue, tkReturn, tkMatch, tkBreak, tkContinue, tkReturn, tkMatch,
tkFunc, tkLet, tkVar, tkConst, tkType, tkStruct, tkEnum, tkFunc, tkLet, tkVar, tkConst, tkType, tkStruct, tkEnum,
tkUnion, tkInterface, tkExtend, tkModule, tkImport, tkUnion, tkInterface, tkExtend, tkModule, tkImport,
tkPub, tkExtern, tkAs, tkIs, tkNull, tkSelf, tkSuper, tkOwn, tkDiscard: tkPub, tkExtern, tkAs, tkIs, tkNull, tkSelf, tkSuper, tkOwn, tkMut, tkDiscard:
true true
else: else:
false false
@@ -197,6 +198,7 @@ proc keywordKind*(text: string): TokenKind =
of "super": tkSuper of "super": tkSuper
of "sizeof": tkSizeOf of "sizeof": tkSizeOf
of "own": tkOwn of "own": tkOwn
of "mut": tkMut
of "discard": tkDiscard of "discard": tkDiscard
of "true", "false": tkBoolLiteral of "true", "false": tkBoolLiteral
else: tkIdent else: tkIdent
@@ -242,6 +244,7 @@ proc tokenKindName*(kind: TokenKind): string =
of tkSelf: "'self'" of tkSelf: "'self'"
of tkSuper: "'super'" of tkSuper: "'super'"
of tkOwn: "'own'" of tkOwn: "'own'"
of tkMut: "'mut'"
of tkDiscard: "'discard'" of tkDiscard: "'discard'"
of tkLParen: "'('" of tkLParen: "'('"
of tkRParen: "')'" of tkRParen: "')'"
+24 -1
View File
@@ -25,6 +25,8 @@ type
tkFloat32 tkFloat32
tkFloat64 tkFloat64
tkPointer tkPointer
tkRef
tkMutRef
tkSlice tkSlice
tkRange tkRange
tkTuple tkTuple
@@ -63,6 +65,10 @@ proc makeFloat64*(): Type = Type(kind: tkFloat64)
proc makePointer*(pointee: Type): Type = proc makePointer*(pointee: Type): Type =
Type(kind: tkPointer, inner: @[pointee]) Type(kind: tkPointer, inner: @[pointee])
proc makeRef*(pointee: Type): Type =
Type(kind: tkRef, inner: @[pointee])
proc makeMutRef*(pointee: Type): Type =
Type(kind: tkMutRef, inner: @[pointee])
proc makeSlice*(element: Type): Type = proc makeSlice*(element: Type): Type =
Type(kind: tkSlice, inner: @[element]) Type(kind: tkSlice, inner: @[element])
proc makeRange*(element: Type): Type = proc makeRange*(element: Type): Type =
@@ -95,7 +101,10 @@ proc isFloat*(t: Type): bool =
proc isSigned*(t: Type): bool = proc isSigned*(t: Type): bool =
if t.kind in {tkUnknown, tkNamed, tkTypeParam}: return true if t.kind in {tkUnknown, tkNamed, tkTypeParam}: return true
t.kind in {tkInt8, tkInt16, tkInt32, tkInt64, tkInt} t.kind in {tkInt8, tkInt16, tkInt32, tkInt64, tkInt}
proc isPointer*(t: Type): bool = t.kind == tkPointer proc isPointer*(t: Type): bool = t.kind in {tkPointer, tkRef, tkMutRef}
proc isRawPointer*(t: Type): bool = t.kind == tkPointer
proc isRef*(t: Type): bool = t.kind == tkRef
proc isMutRef*(t: Type): bool = t.kind == tkMutRef
proc isSlice*(t: Type): bool = t.kind == tkSlice proc isSlice*(t: Type): bool = t.kind == tkSlice
# Comparison # Comparison
@@ -139,6 +148,18 @@ proc isAssignableTo*(a, b: Type): bool =
return true return true
if b.inner.len > 0 and b.inner[0].isUnknown: if b.inner.len > 0 and b.inner[0].isUnknown:
return true return true
# &mut T -> &T (mutable ref can coerce to shared ref)
if a.isMutRef and b.isRef:
if a.inner.len > 0 and b.inner.len > 0 and a.inner[0].isAssignableTo(b.inner[0]):
return true
# &mut T -> *T (mutable ref can coerce to raw pointer)
if a.isMutRef and b.isRawPointer:
if a.inner.len > 0 and b.inner.len > 0 and a.inner[0].isAssignableTo(b.inner[0]):
return true
# &T -> *T (shared ref can coerce to raw pointer)
if a.isRef and b.isRawPointer:
if a.inner.len > 0 and b.inner.len > 0 and a.inner[0].isAssignableTo(b.inner[0]):
return true
return false return false
# String representation # String representation
@@ -167,6 +188,8 @@ proc toString*(t: Type): string =
of tkFloat32: "float32" of tkFloat32: "float32"
of tkFloat64: "float64" of tkFloat64: "float64"
of tkPointer: "*" & t.inner[0].toString of tkPointer: "*" & t.inner[0].toString
of tkRef: "&" & t.inner[0].toString
of tkMutRef: "&mut " & t.inner[0].toString
of tkSlice: of tkSlice:
if t.inner.len > 0: t.inner[0].toString & "[]" if t.inner.len > 0: t.inner[0].toString & "[]"
else: "Slice<?>" else: "Slice<?>"
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.