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:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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" — сме мъртви.
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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<?>"
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user