diff --git a/.claude/TODO.md b/.claude/TODO.md index fbf96fd..80c8b2a 100644 --- a/.claude/TODO.md +++ b/.claude/TODO.md @@ -1,249 +1,160 @@ -# naml Language - Development Todo List - -## Current Status -- [x] Project setup with Cargo workspace -- [x] Source module (file, span, diagnostics) -- [x] Lexer module (cursor, keywords, literals, tokenize) - ---- - -## Phase 1: Foundation (In Progress) - -### AST Definitions (`namlc/src/ast/`) -- [ ] Create `ast/mod.rs` - Module root with re-exports -- [ ] Create `ast/types.rs` - NamlType enum with all type variants - - Primitives: int, uint, float, decimal, bool, string, bytes, unit - - Composites: array, fixed array, option, map, channel, promise - - User-defined: struct, enum, interface references -- [ ] Create `ast/literals.rs` - Literal value variants - - Int, UInt, Float, Decimal, Bool, String, Bytes - - Array, Map literals -- [ ] Create `ast/operators.rs` - Binary and unary operators - - Binary: arithmetic, comparison, logical, bitwise - - Unary: negation, not, reference, dereference -- [ ] Create `ast/expressions.rs` - Expression enum - - Literal, Identifier, Binary, Unary - - Call, MethodCall, Index, Field access - - If expression, Match expression - - Lambda, Spawn, Channel operations -- [ ] Create `ast/statements.rs` - Statement enum - - Variable declaration (var, const) - - Assignment, Return, Throw - - If, While, For, Switch - - Expression statement, Block -- [ ] Create `ast/items.rs` - Top-level item definitions - - Function, Struct, Interface, Enum, Exception - - Import, Use, Extern declarations - - Platform attributes -- [ ] Create `ast/visitor.rs` - AST visitor trait for traversal - -### Parser (`namlc/src/parser/`) -- [ ] Create `parser/mod.rs` - Parser struct with token stream management -- [ ] Create `parser/types.rs` - Type annotation parsing -- [ ] Create `parser/literals.rs` - Literal expression parsing -- [ ] Create `parser/expressions.rs` - Expression parsing with Pratt algorithm -- [ ] Create `parser/precedence.rs` - Operator precedence table -- [ ] Create `parser/statements.rs` - Statement parsing -- [ ] Create `parser/items.rs` - Top-level item parsing -- [ ] Create `parser/attributes.rs` - Platform attribute parsing (#[platforms(...)]) -- [ ] Implement error recovery for better diagnostics - ---- - -## Phase 2: Type System - -### Symbol Table (`namlc/src/typechecker/symbols.rs`) -- [ ] Implement scoped variable lookup with shadowing -- [ ] Track function signatures with generics -- [ ] Store type definitions (struct, enum, interface) -- [ ] Module imports tracking - -### Type Checker (`namlc/src/typechecker/`) -- [ ] Create `typechecker/mod.rs` - TypeChecker struct -- [ ] Create `typechecker/errors.rs` - TypeError enum -- [ ] Create `typechecker/symbols.rs` - Symbol table -- [ ] Create `typechecker/inference.rs` - Type inference (Hindley-Milner) -- [ ] Create `typechecker/unify.rs` - Type unification -- [ ] Create `typechecker/checker.rs` - Statement validation -- [ ] Create `typechecker/expressions.rs` - Expression type checking -- [ ] Create `typechecker/generics.rs` - Generic type resolution -- [ ] Create `typechecker/platform.rs` - Platform constraint validation - -### Error Reporting Enhancements -- [ ] Add source location to all diagnostics -- [ ] Implement multiple error accumulation -- [ ] Add suggestions for common mistakes - ---- - -## Phase 3: Cranelift JIT Engine - -### JIT Compiler (`namlc/src/jit/`) -- [ ] Create `jit/mod.rs` - JIT engine entry point -- [ ] Create `jit/compiler.rs` - AST to Cranelift IR generation -- [ ] Create `jit/types.rs` - Type lowering to Cranelift types -- [ ] Create `jit/builtins.rs` - Built-in function implementations -- [ ] Create `jit/runtime.rs` - JIT runtime support - -### Code Cache (`namlc/src/cache/`) -- [ ] Create `cache/mod.rs` - Cache manager -- [ ] Create `cache/format.rs` - Cache file format (machine code + metadata) -- [ ] Create `cache/hash.rs` - Content hashing with blake3 -- [ ] Implement cache invalidation strategy -- [ ] Add precompilation support (`naml precompile`) - -### Runtime (`namlc/src/runtime/`) -- [ ] Create `runtime/mod.rs` - RuntimeRegistry -- [ ] Create `runtime/memory/` - Arena allocator, string interning -- [ ] Create `runtime/native/` - Native Rust implementations -- [ ] Create `runtime/browser/` - Browser JS implementations - ---- - -## Phase 4: Rust Code Generation - -### Rust Backend (`namlc/src/codegen/rust/`) -- [ ] Create `codegen/mod.rs` - Target enum, Backend trait -- [ ] Create `codegen/rust/mod.rs` - Rust backend entry -- [ ] Create `codegen/rust/types.rs` - naml -> Rust type conversion -- [ ] Create `codegen/rust/expressions.rs` - Expression generation -- [ ] Create `codegen/rust/statements.rs` - Statement generation -- [ ] Create `codegen/rust/items.rs` - Top-level item generation -- [ ] Create `codegen/rust/platform.rs` - Platform-specific code paths (cfg) - -### Multi-Target Support -- [ ] Target::Native - Rust std library, threads -- [ ] Target::Server - WASM with WASI -- [ ] Target::Browser - WASM with wasm-bindgen, async-only - -### Build Pipeline -- [ ] Generate Cargo.toml for compiled projects -- [ ] Invoke rustc/cargo for final compilation -- [ ] Add WASM optimization (wasm-opt) - ---- - -## Phase 5: Standard Library - -### Core Modules (`naml_stdlib/std/`) -- [ ] Create `io.naml` - I/O operations -- [ ] Create `fmt.naml` - Formatting utilities -- [ ] Create `fs.naml` - File system (native/server) -- [ ] Create `path.naml` - Path manipulation -- [ ] Create `os.naml` - OS interaction -- [ ] Create `http.naml` - HTTP client/server -- [ ] Create `net.naml` - Networking primitives -- [ ] Create `ws.naml` - WebSocket support -- [ ] Create `json.naml` - JSON parsing/serialization -- [ ] Create `collections.naml` - Data structures -- [ ] Create `crypto.naml` - Cryptography -- [ ] Create `rand.naml` - Random number generation -- [ ] Create `time.naml` - Time operations - -### Platform-Specific Modules -- [ ] Create `opfs.naml` - Browser Origin Private File System -- [ ] Create `channel.naml` - Go-style channels (native/server only) -- [ ] Create `sync.naml` - Synchronization primitives -- [ ] Create `async.naml` - Async utilities - -### FFI System -- [ ] Implement `extern "rust"` for Rust crates -- [ ] Implement `extern "js"` for browser JavaScript - ---- - -## Phase 6: Concurrency - -### Spawn Blocks -- [ ] Native: std::thread::spawn implementation -- [ ] Server: tokio task spawning -- [ ] Browser: spawn_local for async tasks - -### Channels (`channel.naml`) -- [ ] Bounded channels -- [ ] Unbounded channels -- [ ] Send/receive operations -- [ ] Select statement for multiple channels - -### Sync Primitives (`sync.naml`) -- [ ] Mutex implementation -- [ ] RwLock implementation -- [ ] WaitGroup -- [ ] Async sleep/delay - ---- - -## Phase 7: Package Manager & Polish - -### Manifest System (`namlc/src/manifest/`) -- [ ] Create `manifest/mod.rs` - Manifest parser entry -- [ ] Create `manifest/config.rs` - Package configuration -- [ ] Create `manifest/dependencies.rs` - Dependency resolution -- [ ] Parse naml.toml files - -### CLI Commands -- [ ] `naml init` - Create new project -- [ ] `naml add ` - Add dependency -- [ ] `naml build` - Compile to binary/WASM -- [ ] `naml run` - Execute directly (JIT) -- [ ] `naml run --cached` - Use cached compilation -- [ ] `naml check` - Type check without building -- [ ] `naml watch` - Hot reload dev server -- [ ] `naml test` - Run tests -- [ ] `naml precompile` - Pre-JIT all files - -### Rust Crate Bindings (`namlc/src/bindings/`) -- [ ] Create `bindings/mod.rs` - Binding generator entry -- [ ] Create `bindings/parser.rs` - Parse Rust crate API (using syn) -- [ ] Create `bindings/mapper.rs` - Rust -> naml type mapping -- [ ] Create `bindings/codegen.rs` - Generate binding code - -### Driver (`namlc/src/driver/`) -- [ ] Create `driver/mod.rs` - Compilation orchestration -- [ ] Create `driver/session.rs` - Compilation session management - ---- - -## Testing & Examples - -### Unit Tests -- [ ] Lexer tests -- [ ] Parser tests (snapshot with insta) -- [ ] Type checker tests -- [ ] JIT compiler tests -- [ ] Codegen tests - -### Integration Tests (`tests/`) -- [ ] End-to-end compilation tests -- [ ] Platform-specific tests (native, server, browser) - -### Examples (`examples/`) -- [ ] Create `hello.naml` - Hello world -- [ ] Create `http_server.naml` - HTTP server example -- [ ] Create `concurrency.naml` - Spawn and concurrent operations -- [ ] Create `channels.naml` - Channel communication example - -### Benchmarks -- [ ] Startup time comparison vs Bun -- [ ] Fibonacci computation benchmark -- [ ] HTTP server throughput -- [ ] JSON parsing performance - ---- - -## Immediate Next Steps - -1. **AST Module** - Define all AST node types -2. **Parser** - Build recursive descent parser with Pratt expressions -3. **Basic CLI** - Get `naml run hello.naml` working with print -4. **Type Checker** - Implement core type inference -5. **JIT** - Add Cranelift compilation for basic programs - ---- - -## Notes - -- All files must stay under 1000 lines -- Use block comments only (no inline comments) -- Zero-allocation, zero-copy patterns where possible -- Every feature must handle all three platforms (native, server, browser) \ No newline at end of file +Plan: Fix Remaining Type Checker Errors (109 → 0) C + +Goal + +Run cargo run -- run examples/simple.naml with zero type errors. + +--- +Error Categories (109 total) +┌────────────────────────────────────┬───────┬────────────────────────────────────────────────┐ +│ Category │ Count │ Root Cause │ +├────────────────────────────────────┼───────┼────────────────────────────────────────────────┤ +│ Array methods/fields missing │ ~15 │ No built-in .push(), .length for [T] │ +├────────────────────────────────────┼───────┼────────────────────────────────────────────────┤ +│ Generic struct literal typing │ ~10 │ Type params not substituted in struct literals │ +├────────────────────────────────────┼───────┼────────────────────────────────────────────────┤ +│ Option field access without unwrap │ ~25 │ Code bugs in simple.naml │ +├────────────────────────────────────┼───────┼────────────────────────────────────────────────┤ +│ Exception struct literals │ ~2 │ Exceptions not handled in infer_struct_literal │ +├────────────────────────────────────┼───────┼────────────────────────────────────────────────┤ +│ Async/promise return types │ ~15 │ Async functions not wrapped in promise │ +├────────────────────────────────────┼───────┼────────────────────────────────────────────────┤ +│ Spawn block return type │ ~5 │ Always returns promise │ +├────────────────────────────────────┼───────┼────────────────────────────────────────────────┤ +│ Generic type parameter issues │ ~35 │ Various T substitution failures │ +└────────────────────────────────────┴───────┴────────────────────────────────────────────────┘ + --- +Implementation Phases + +Phase 1: Array Built-in Methods and Fields + +File: namlc/src/typechecker/infer.rs + +Changes: + +1. In infer_method_call(), add handling for Type::Array(elem) before the type_name extraction: +- push(item: T) → Type::Unit (validates item unifies with elem) +- pop() → Type::Option(elem) +- clear() → Type::Unit +- len() → Type::Int +2. In infer_field(), add handling for Type::Array: +- length → Type::Int + + --- +Phase 2: Exception Type Struct Literals + +File: namlc/src/typechecker/infer.rs + +Change: In infer_struct_literal(), handle TypeDef::Exception same as TypeDef::Struct: +- Extract exception fields +- Type-check each field value +- Return appropriate type + +File: namlc/src/typechecker/types.rs + +Optional: Add Type::Exception(ExceptionType) variant for semantic clarity. + +--- +Phase 3: Async Function Promise Wrapping + +File: namlc/src/typechecker/mod.rs + +Change: In collect_function(), if func.is_async, wrap return type in Type::Promise: +let return_ty = if func.is_async { +Type::Promise(Box::new(return_ty)) +} else { +return_ty +}; + +--- +Phase 4: Spawn Block Return Type Inference + +File: namlc/src/typechecker/infer.rs + +Change: In infer_spawn(), infer the block's actual return type: +fn infer_spawn(&mut self, spawn: &ast::SpawnExpr) -> Type { +let body_ty = self.infer_block(&spawn.body); +Type::Promise(Box::new(body_ty)) +} + +--- +Phase 5: Generic Type Parameter Context + +Files: +- namlc/src/typechecker/infer.rs +- namlc/src/typechecker/mod.rs +- namlc/src/typechecker/env.rs + +Changes: + +1. Add type parameter tracking to TypeEnv: + type_params: HashMap // Maps T -> concrete type or type var +2. In check_function(), register function type parameters in env before checking body +3. In infer_struct_literal(), when creating struct types: +- Look up type args from annotation or infer from field values +- Apply substitution to field types using Type::substitute() + + --- +Phase 6: Fix Code Issues in simple.naml + +File: examples/simple.naml + +Some errors are actual code bugs, not type checker issues: +- Option field access without .unwrap() (lines 280, 289, 292, etc.) +- Assigning option to T without unwrapping + +Fix: Add .unwrap() calls where needed, or use proper pattern matching. + +--- +Implementation Order +┌──────────┬───────────────────────────┬────────────┬────────────┐ +│ Priority │ Phase │ Complexity │ Impact │ +├──────────┼───────────────────────────┼────────────┼────────────┤ +│ 1 │ Phase 1 (Array methods) │ Low │ ~15 errors │ +├──────────┼───────────────────────────┼────────────┼────────────┤ +│ 2 │ Phase 2 (Exceptions) │ Low │ ~2 errors │ +├──────────┼───────────────────────────┼────────────┼────────────┤ +│ 3 │ Phase 3 (Async wrap) │ Low │ ~10 errors │ +├──────────┼───────────────────────────┼────────────┼────────────┤ +│ 4 │ Phase 4 (Spawn return) │ Low │ ~5 errors │ +├──────────┼───────────────────────────┼────────────┼────────────┤ +│ 5 │ Phase 6 (Fix simple.naml) │ Medium │ ~25 errors │ +├──────────┼───────────────────────────┼────────────┼────────────┤ +│ 6 │ Phase 5 (Generic context) │ High │ ~50 errors │ +└──────────┴───────────────────────────┴────────────┴────────────┘ + --- +Files to Modify +┌────────────────────────────────┬────────────┐ +│ File │ Phases │ +├────────────────────────────────┼────────────┤ +│ namlc/src/typechecker/infer.rs │ 1, 2, 4, 5 │ +├────────────────────────────────┼────────────┤ +│ namlc/src/typechecker/mod.rs │ 3, 5 │ +├────────────────────────────────┼────────────┤ +│ namlc/src/typechecker/env.rs │ 5 │ +├────────────────────────────────┼────────────┤ +│ examples/simple.naml │ 6 │ +└────────────────────────────────┴────────────┘ + --- +Verification + +After each phase: +cargo run -- run examples/simple.naml 2>&1 | grep -c "^ ×" + +Final success: +cargo run -- run examples/simple.naml +# Should complete with no type errors + +--- +Risk Assessment + +- Phase 1-4: Low risk, isolated changes +- Phase 5: High risk, may require significant refactoring of type inference +- Phase 6: May need to significantly simplify generic code in simple.naml + +Alternative Approach + +If Phase 5 proves too complex, we can: +1. Simplify examples/simple.naml to avoid complex generics +2. Mark generic type inference as a known limitation +3. Focus on getting basic programs to work first \ No newline at end of file diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 4e59bd3..0a741fc 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -12,7 +12,19 @@ "Bash(grep:*)", "Bash(cat:*)", "mcp__ide__getDiagnostics", - "Bash(cargo test:*)" + "Bash(cargo test:*)", + "Bash(cargo build:*)", + "Bash(cargo run:*)", + "Bash(xargs ls:*)", + "Bash(cargo check:*)", + "Bash(find:*)", + "Bash(gh issue create --title \"Fix Remaining 35 Rust Codegen Compilation Errors\" --body \"$\\(cat <<''EOF''\n## Problem\n\nThe Rust code generated by `naml run` produces 35 compilation errors, preventing programs from executing.\n\n## Error Breakdown\n\n| Error | Count | Root Cause |\n|-------|-------|------------|\n| E0308 | 17 | Type mismatches \\(closures ≠ fn pointers, compare method\\) |\n| E0382 | 8 | Use of moved value |\n| E0594 | 2 | Parameter needs `mut` keyword |\n| E0599 | 3 | Missing methods \\(compare, iter on String\\) |\n| E0277 | 3 | Missing trait bounds |\n| E0505/E0507 | 3 | Borrow/move conflicts |\n\n## Implementation Plan\n\n### 1. Fix Closure Return Types \\(E0308\\)\n- [ ] Detect when return type is `fn\\(A\\) -> B` and closure is returned\n- [ ] Change codegen to use `impl Fn\\(A\\) -> B` or `Box B>`\n- [ ] Handle closure capture semantics\n\n### 2. Add Parameter Mutability \\(E0594\\)\n- [ ] Analyze function body for assignments to parameters\n- [ ] Add `mut` keyword to parameters that are mutated\n- [ ] Create AST visitor to detect mutations\n\n### 3. Fix Use-After-Move \\(E0382\\)\n- [ ] Track variable usage in struct literals\n- [ ] Clone fields when used multiple times\n- [ ] Handle loop variable reuse\n\n### 4. Remove compare\\(\\) Method \\(E0599\\)\n- [ ] Replace `a.compare\\(b\\)` with `a.partial_cmp\\(&b\\)`\n- [ ] Or use comparison operators directly\n- [ ] Update generic function codegen\n\n### 5. Fix Remaining Borrow Conflicts \\(E0505/E0507\\)\n- [ ] Clone self fields before unwrap in `&self` methods\n- [ ] Use `.as_ref\\(\\)` where appropriate\n\n## Files to Modify\n\n- `namlc/src/codegen/rust/expressions.rs`\n- `namlc/src/codegen/rust/statements.rs`\n- `namlc/src/codegen/rust/mod.rs`\n\n## Acceptance Criteria\n\n- [ ] `naml run examples/simple.naml` compiles with 0 Rust errors\n- [ ] All existing tests pass\n- [ ] Generated Rust code is idiomatic\n\n## Related\n\n- Continues work from #3\nEOF\n\\)\" --label \"enhancement\" --label \"priority:critical\")", + "Bash(gh issue create --title \"Fix Remaining 35 Rust Codegen Compilation Errors\" --body \"$\\(cat <<''EOF''\n## Priority: 🔴 Critical\n\nThe Rust code generated by `naml run` produces 35 compilation errors, preventing programs from executing.\n\n## Error Breakdown\n\n| Error | Count | Root Cause |\n|-------|-------|------------|\n| E0308 | 17 | Type mismatches \\(closures ≠ fn pointers, compare method\\) |\n| E0382 | 8 | Use of moved value |\n| E0594 | 2 | Parameter needs `mut` keyword |\n| E0599 | 3 | Missing methods \\(compare, iter on String\\) |\n| E0277 | 3 | Missing trait bounds |\n| E0505/E0507 | 3 | Borrow/move conflicts |\n\n## Implementation Plan\n\n### 1. Fix Closure Return Types \\(E0308\\)\n- [ ] Detect when return type is `fn\\(A\\) -> B` and closure is returned\n- [ ] Change codegen to use `impl Fn\\(A\\) -> B` or `Box B>`\n- [ ] Handle closure capture semantics\n\n### 2. Add Parameter Mutability \\(E0594\\)\n- [ ] Analyze function body for assignments to parameters\n- [ ] Add `mut` keyword to parameters that are mutated\n- [ ] Create AST visitor to detect mutations\n\n### 3. Fix Use-After-Move \\(E0382\\)\n- [ ] Track variable usage in struct literals\n- [ ] Clone fields when used multiple times\n- [ ] Handle loop variable reuse\n\n### 4. Remove compare\\(\\) Method \\(E0599\\)\n- [ ] Replace `a.compare\\(b\\)` with `a.partial_cmp\\(&b\\)`\n- [ ] Or use comparison operators directly\n- [ ] Update generic function codegen\n\n### 5. Fix Remaining Borrow Conflicts \\(E0505/E0507\\)\n- [ ] Clone self fields before unwrap in `&self` methods\n- [ ] Use `.as_ref\\(\\)` where appropriate\n\n## Files to Modify\n\n- `namlc/src/codegen/rust/expressions.rs`\n- `namlc/src/codegen/rust/statements.rs`\n- `namlc/src/codegen/rust/mod.rs`\n\n## Acceptance Criteria\n\n- [ ] `naml run examples/simple.naml` compiles with 0 Rust errors\n- [ ] All existing tests pass\n- [ ] Generated Rust code is idiomatic\n\n## Related\n\n- Continues work from #3\nEOF\n\\)\" --label \"enhancement\")", + "Bash(gh issue view:*)", + "Skill(superpowers:using-git-worktrees)", + "Skill(superpowers:writing-plans)", + "Bash(gh issue list:*)", + "Bash(gh issue create:*)" ] }, "hooks": { diff --git a/.gitignore b/.gitignore index 532ba41..ceb749e 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,6 @@ rust-project.json .naml_build/ -settings.local.json \ No newline at end of file +settings.local.json + +.worktrees/ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index b5e7fc6..06f6c47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,6 +106,11 @@ memchr = "2.7" ## bumpalo = { version = "3.16", features = ["collections"] } +## +## C library bindings for runtime +## +libc = "0.2" + [profile.release] lto = true codegen-units = 1 diff --git a/examples/arrays.naml b/examples/arrays.naml new file mode 100644 index 0000000..4afa6ba --- /dev/null +++ b/examples/arrays.naml @@ -0,0 +1,30 @@ +fn main() { + println("Testing arrays with for loop:"); + + var arr: [int] = [10, 20, 30, 40, 50]; + + print("Array length: "); + println(arr.len()); + + println("Iterating with for loop:"); + for (val in arr) { + print(" Value: "); + println(val); + } + + println("Iterating with index:"); + for (i, val in arr) { + print(" arr["); + print(i); + print("] = "); + println(val); + } + + println("Sum of elements:"); + var sum: int = 0; + for (val in arr) { + sum = sum + val; + } + print(" Total: "); + println(sum); +} diff --git a/examples/channels.naml b/examples/channels.naml new file mode 100644 index 0000000..82d7c1b --- /dev/null +++ b/examples/channels.naml @@ -0,0 +1,46 @@ +fn main() { + println("Testing channels:"); + + // Create a channel with capacity 5 + var ch: channel = make_channel(5); + + // Send values + println("Sending values..."); + ch.send(10); + ch.send(20); + ch.send(30); + + // Receive values + println("Receiving values..."); + var v1: int = ch.receive(); + print("Received: "); + println(v1); + + var v2: int = ch.receive(); + print("Received: "); + println(v2); + + var v3: int = ch.receive(); + print("Received: "); + println(v3); + + // Test with a loop + println("Testing channel with loop:"); + var i: int = 0; + while (i < 5) { + ch.send(i * 10); + i = i + 1; + } + + var sum: int = 0; + i = 0; + while (i < 5) { + sum = sum + ch.receive(); + i = i + 1; + } + print("Sum of received values: "); + println(sum); + + ch.close(); + println("Channel closed."); +} diff --git a/examples/ffi.naml b/examples/ffi.naml new file mode 100644 index 0000000..dc2d9bb --- /dev/null +++ b/examples/ffi.naml @@ -0,0 +1,18 @@ +// Declare external C functions from libc +extern fn abs(x: int) -> int; +extern fn labs(x: int) -> int; + +fn main() { + println("Testing FFI with libc functions:"); + + var x: int = -42; + print("abs(-42) = "); + println(abs(x)); + + var y: int = -100; + print("labs(-100) = "); + println(labs(y)); + + print("abs(10) = "); + println(abs(10)); +} diff --git a/examples/ffi_math.naml b/examples/ffi_math.naml new file mode 100644 index 0000000..8c16a31 --- /dev/null +++ b/examples/ffi_math.naml @@ -0,0 +1,27 @@ +// Declare external C math functions +extern fn sqrt(x: float) -> float; +extern fn sin(x: float) -> float; +extern fn cos(x: float) -> float; +extern fn pow(base: float, exp: float) -> float; +extern fn floor(x: float) -> float; +extern fn ceil(x: float) -> float; + +fn main() { + println("Testing FFI with math functions:"); + + print("sqrt(16.0) = "); + var s: float = sqrt(16.0); + println(s); + + print("pow(2.0, 10.0) = "); + var p: float = pow(2.0, 10.0); + println(p); + + print("floor(3.7) = "); + var f: float = floor(3.7); + println(f); + + print("ceil(3.2) = "); + var c: float = ceil(3.2); + println(c); +} diff --git a/examples/generics_test.naml b/examples/generics_test.naml new file mode 100644 index 0000000..fa4fcab --- /dev/null +++ b/examples/generics_test.naml @@ -0,0 +1,180 @@ +/// +/// Generics Test - Basic generic features +/// + +enum UserStatus { + Active, + Inactive, + Suspended(string), + PendingVerification(string) +} + +enum Result { + Ok(T), + Err(E) +} + +struct Pair { + first: A, + second: B +} + +struct Box { + value: T +} + +fn test_user_status() { + println("=== UserStatus Enum Tests ==="); + + var active: UserStatus = UserStatus::Active; + var inactive: UserStatus = UserStatus::Inactive; + var suspended: UserStatus = UserStatus::Suspended("policy violation"); + var pending: UserStatus = UserStatus::PendingVerification("email sent"); + + print("Testing Active: "); + switch (active) { + case Active: { + println("PASS"); + } + default: { + println("FAIL"); + } + } + + print("Testing Inactive: "); + switch (inactive) { + case Inactive: { + println("PASS"); + } + default: { + println("FAIL"); + } + } + + print("Testing Suspended with data: "); + switch (suspended) { + case Suspended(reason): { + print("PASS - reason: "); + println(reason); + } + default: { + println("FAIL"); + } + } + + print("Testing PendingVerification: "); + switch (pending) { + case PendingVerification(msg): { + print("PASS - msg: "); + println(msg); + } + default: { + println("FAIL"); + } + } + + print("Testing switch default for Inactive: "); + switch (inactive) { + case Active: { + println("FAIL"); + } + case Suspended(r): { + println("FAIL"); + } + default: { + println("PASS - matched default"); + } + } +} + +fn test_generic_struct() { + println("=== Generic Struct Tests ==="); + + var int_pair: Pair = Pair { first: 10, second: 20 }; + + print("Testing Pair.first: "); + if (int_pair.first == 10) { + println("PASS"); + } else { + println("FAIL"); + } + + print("Testing Pair.second: "); + if (int_pair.second == 20) { + println("PASS"); + } else { + println("FAIL"); + } + + var mixed_pair: Pair = Pair { first: "hello", second: 42 }; + + print("Testing Pair.first: "); + if (mixed_pair.first == "hello") { + println("PASS"); + } else { + println("FAIL"); + } + + print("Testing Pair.second: "); + if (mixed_pair.second == 42) { + println("PASS"); + } else { + println("FAIL"); + } + + var nested: Pair, string> = Pair { first: Box { value: 100 }, second: "nested" }; + print("Testing nested generic: "); + if (nested.second == "nested") { + println("PASS"); + } else { + println("FAIL"); + } +} + +fn test_generic_box() { + println("=== Generic Box Tests ==="); + + var int_box: Box = Box { value: 42 }; + print("Testing Box.value: "); + if (int_box.value == 42) { + println("PASS"); + } else { + println("FAIL"); + } + + var str_box: Box = Box { value: "boxed string" }; + print("Testing Box.value: "); + if (str_box.value == "boxed string") { + println("PASS"); + } else { + println("FAIL"); + } + + var point_box: Box> = Box { value: Pair { first: 1, second: 2 } }; + print("Testing Box.value.first: "); + if (point_box.value.first == 1) { + println("PASS"); + } else { + println("FAIL"); + } +} + +fn main() { + println("========================================"); + println(" NAML Generics Test "); + println("========================================"); + println(""); + + test_user_status(); + println(""); + + test_generic_struct(); + println(""); + + test_generic_box(); + println(""); + + println("========================================"); + println(" Tests Completed! "); + println("========================================"); +} diff --git a/examples/memory_test.naml b/examples/memory_test.naml new file mode 100644 index 0000000..cb765c2 --- /dev/null +++ b/examples/memory_test.naml @@ -0,0 +1,99 @@ +/// +/// Memory Management Test +/// +/// Tests reference counting and cleanup for heap-allocated types. +/// Run this multiple times and monitor memory usage to verify no leaks. +/// + +fn test_string_cleanup() { + println("=== String Cleanup Test ==="); + var s: string = "hello world"; + println(s); + var s2: string = "goodbye world"; + s = s2; + println(s); +} + +fn test_string_reassignment() { + println("=== String Reassignment Test ==="); + var name: string = "Alice"; + println(name); + name = "Bob"; + println(name); + name = "Charlie"; + println(name); +} + +fn test_array_cleanup() { + println("=== Array Cleanup Test ==="); + var arr: [int] = [1, 2, 3, 4, 5]; + print("Array length: "); + println(arr.len()); +} + +fn test_map_cleanup() { + println("=== Map Cleanup Test ==="); + var scores: map = {}; + scores["alice"] = 100; + scores["bob"] = 95; + print("Alice score: "); + println(scores["alice"]); + print("Bob score: "); + println(scores["bob"]); +} + +fn test_loop_allocations() { + println("=== Loop Allocation Test ==="); + var count: int = 0; + while (count < 5) { + var temp: string = "iteration"; + print(temp); + print(": "); + println(count); + count = count + 1; + } + println("Loop completed"); +} + +struct TestStruct { + value: int, + name: string +} + +fn test_struct_cleanup() { + println("=== Struct Cleanup Test ==="); + var ts: TestStruct = TestStruct { value: 42, name: "test struct" }; + print("Struct value: "); + println(ts.value); + print("Struct name: "); + println(ts.name); +} + +fn main() { + println("========================================"); + println(" Memory Management Tests "); + println("========================================"); + println(""); + + test_string_cleanup(); + println(""); + + test_string_reassignment(); + println(""); + + test_array_cleanup(); + println(""); + + test_map_cleanup(); + println(""); + + test_loop_allocations(); + println(""); + + test_struct_cleanup(); + println(""); + + println("========================================"); + println(" All Tests Completed! "); + println("========================================"); +} diff --git a/examples/simple.naml b/examples/simple.naml index 7679d59..f986806 100644 --- a/examples/simple.naml +++ b/examples/simple.naml @@ -659,7 +659,7 @@ fn (self: UserRepository) map_row_to_user(row: map) -> User { last_name: none, address: none, phone: none, - status: UserStatus.Active, + status: UserStatus::Active, roles: [UserRole::User], metadata: {}, created_at: 0, diff --git a/examples/spawn.naml b/examples/spawn.naml new file mode 100644 index 0000000..6fbb964 --- /dev/null +++ b/examples/spawn.naml @@ -0,0 +1,41 @@ +fn main() { + println("Testing spawn:"); + + // Create a channel to communicate results + var ch: channel = make_channel(10); + + // Test basic spawn by spawning work that sends to channel + println("Spawning 3 tasks..."); + + spawn { + sleep(10); + ch.send(100); + }; + + spawn { + sleep(5); + ch.send(200); + }; + + spawn { + ch.send(300); + }; + + // Wait for all spawned tasks + wait_all(); + + // Read results + println("Tasks complete. Reading results:"); + var sum: int = 0; + var i: int = 0; + while (i < 3) { + sum = sum + ch.receive(); + i = i + 1; + } + + print("Sum: "); + println(sum); + + ch.close(); + println("Done!"); +} diff --git a/examples/struct_cleanup_test.naml b/examples/struct_cleanup_test.naml new file mode 100644 index 0000000..41d6b15 --- /dev/null +++ b/examples/struct_cleanup_test.naml @@ -0,0 +1,102 @@ +/// +/// Struct Cleanup Test +/// +/// Tests that structs with heap-allocated fields are properly cleaned up. +/// Each struct should have its heap fields decremented when the struct is freed. +/// + +struct Person { + name: string, + age: int +} + +struct PersonWithScores { + name: string, + scores: [int], + metadata: map +} + +struct SimpleStruct { + value: int, + count: int +} + +fn test_simple_struct() { + println("=== Simple Struct Test (no heap fields) ==="); + var s: SimpleStruct = SimpleStruct { value: 42, count: 10 }; + print("Value: "); + println(s.value); + print("Count: "); + println(s.count); +} + +fn test_struct_with_string() { + println("=== Struct With String Test ==="); + var p: Person = Person { name: "Alice", age: 30 }; + print("Name: "); + println(p.name); + print("Age: "); + println(p.age); +} + +fn test_struct_with_array() { + println("=== Struct With Array Test ==="); + var ps: PersonWithScores = PersonWithScores { + name: "Bob", + scores: [95, 88, 92], + metadata: {} + }; + ps.metadata["grade"] = 100; + print("Name: "); + println(ps.name); + print("Scores length: "); + println(ps.scores.len()); +} + +fn test_struct_reassignment() { + println("=== Struct Reassignment Test ==="); + var p: Person = Person { name: "First", age: 20 }; + println(p.name); + p = Person { name: "Second", age: 25 }; + println(p.name); + p = Person { name: "Third", age: 30 }; + println(p.name); +} + +fn test_loop_struct_creation() { + println("=== Loop Struct Creation Test ==="); + var count: int = 0; + while (count < 3) { + var p: Person = Person { name: "Temp", age: count }; + print("Loop iteration: "); + println(count); + count = count + 1; + } + println("Loop completed"); +} + +fn main() { + println("========================================"); + println(" Struct Cleanup Tests "); + println("========================================"); + println(""); + + test_simple_struct(); + println(""); + + test_struct_with_string(); + println(""); + + test_struct_with_array(); + println(""); + + test_struct_reassignment(); + println(""); + + test_loop_struct_creation(); + println(""); + + println("========================================"); + println(" All Tests Completed! "); + println("========================================"); +} diff --git a/examples/structs.naml b/examples/structs.naml new file mode 100644 index 0000000..38c3ade --- /dev/null +++ b/examples/structs.naml @@ -0,0 +1,28 @@ +struct Point { + x: int, + y: int +} + +struct Rectangle { + width: int, + height: int +} + +fn main() { + println("Testing structs:"); + + var p: Point = Point { x: 10, y: 20 }; + print("Point x: "); + println(p.x); + print("Point y: "); + println(p.y); + + var rect: Rectangle = Rectangle { width: 100, height: 50 }; + print("Rectangle width: "); + println(rect.width); + print("Rectangle height: "); + println(rect.height); + + print("Area: "); + println(rect.width * rect.height); +} diff --git a/examples/switch.naml b/examples/switch.naml new file mode 100644 index 0000000..8db4041 --- /dev/null +++ b/examples/switch.naml @@ -0,0 +1,49 @@ +fn main() { + println("Testing switch statements:"); + + var day: int = 3; + + switch (day) { + case 1: { + println("Monday"); + } + case 2: { + println("Tuesday"); + } + case 3: { + println("Wednesday"); + } + case 4: { + println("Thursday"); + } + case 5: { + println("Friday"); + } + default: { + println("Weekend"); + } + } + + println("Testing multiple values:"); + var x: int = 0; + while (x < 8) { + print("x = "); + print(x); + print(" -> "); + switch (x) { + case 0: { + println("zero"); + } + case 1: { + println("one"); + } + case 2: { + println("two"); + } + default: { + println("many"); + } + } + x = x + 1; + } +} diff --git a/examples/test_enum.naml b/examples/test_enum.naml new file mode 100644 index 0000000..9fd5341 --- /dev/null +++ b/examples/test_enum.naml @@ -0,0 +1,51 @@ +enum Color { + Red, + Green, + Blue +} + +fn main() { + println("Testing enum pattern matching:"); + + var c: Color = Color::Red; + + switch (c) { + case Red: { + println("Color is red"); + } + case Green: { + println("Color is green"); + } + case Blue: { + println("Color is blue"); + } + } + + c = Color::Green; + switch (c) { + case Red: { + println("Color is red"); + } + case Green: { + println("Color is green"); + } + case Blue: { + println("Color is blue"); + } + } + + c = Color::Blue; + switch (c) { + case Red: { + println("Color is red"); + } + case Green: { + println("Color is green"); + } + case Blue: { + println("Color is blue"); + } + } + + println("Done"); +} diff --git a/examples/test_map.naml b/examples/test_map.naml new file mode 100644 index 0000000..c40f518 --- /dev/null +++ b/examples/test_map.naml @@ -0,0 +1,10 @@ +fn main() { + var m: map = {}; + m["one"] = 1; + m["two"] = 2; + m["three"] = 3; + + println(m["one"]); + println(m["two"]); + println(m["three"]); +} diff --git a/examples/test_option.naml b/examples/test_option.naml new file mode 100644 index 0000000..ecc11e1 --- /dev/null +++ b/examples/test_option.naml @@ -0,0 +1,18 @@ +fn main() { + var x: option = some(42); + var y: option = none; + + if (x.is_some()) { + println("x has a value"); + } + + if (y.is_none()) { + println("y is none"); + } + + var val: int = x.or_default(0); + println(val); + + var default_val: int = y.or_default(100); + println(default_val); +} diff --git a/examples/tier1_test.naml b/examples/tier1_test.naml new file mode 100644 index 0000000..71acd8b --- /dev/null +++ b/examples/tier1_test.naml @@ -0,0 +1,427 @@ +/// +/// Tier 1 Integration Test +/// +/// Tests the core features implemented in Tier 1: +/// - Enums with unit variants +/// - Enum pattern matching with switch +/// - Option type (some, none, is_some, is_none, or_default) +/// - Maps (creation, indexing, assignment) +/// - Structs (definition, instantiation, field access) +/// - Arrays (creation, len, iteration with for) +/// - Control flow (if, while, for) +/// + +enum Color { + Red, + Green, + Blue +} + +enum Priority { + Low, + Medium, + High, + Critical +} + +struct Point { + x: int, + y: int +} + +struct Rectangle { + width: int, + height: int +} + +fn test_enums() { + println("=== Enum Tests ==="); + + var c1: Color = Color::Red; + var c2: Color = Color::Green; + var c3: Color = Color::Blue; + + print("Testing Color::Red: "); + switch (c1) { + case Red: { + println("PASS - Red"); + } + case Green: { + println("FAIL"); + } + case Blue: { + println("FAIL"); + } + } + + print("Testing Color::Green: "); + switch (c2) { + case Red: { + println("FAIL"); + } + case Green: { + println("PASS - Green"); + } + case Blue: { + println("FAIL"); + } + } + + print("Testing Color::Blue: "); + switch (c3) { + case Red: { + println("FAIL"); + } + case Green: { + println("FAIL"); + } + case Blue: { + println("PASS - Blue"); + } + } + + var p: Priority = Priority::High; + print("Testing Priority with default: "); + switch (p) { + case Low: { + println("FAIL"); + } + case High: { + println("PASS - High"); + } + default: { + println("FAIL - default"); + } + } + + var p2: Priority = Priority::Medium; + print("Testing default branch: "); + switch (p2) { + case Low: { + println("FAIL"); + } + case High: { + println("FAIL"); + } + default: { + println("PASS - default matched Medium"); + } + } +} + +fn test_options() { + println("=== Option Tests ==="); + + var opt_some: option = some(42); + var opt_none: option = none; + + print("Testing is_some on some(42): "); + if (opt_some.is_some()) { + println("PASS"); + } else { + println("FAIL"); + } + + print("Testing is_none on none: "); + if (opt_none.is_none()) { + println("PASS"); + } else { + println("FAIL"); + } + + print("Testing or_default on some(42): "); + var val1: int = opt_some.or_default(0); + if (val1 == 42) { + println("PASS"); + } else { + println("FAIL"); + } + + print("Testing or_default on none with default 100: "); + var val2: int = opt_none.or_default(100); + if (val2 == 100) { + println("PASS"); + } else { + println("FAIL"); + } + + print("Testing is_none on some: "); + if (opt_some.is_none()) { + println("FAIL"); + } else { + println("PASS"); + } + + print("Testing is_some on none: "); + if (opt_none.is_some()) { + println("FAIL"); + } else { + println("PASS"); + } + + var opt_point: option = some(Point { x: 10, y: 20 }); + print("Testing option: "); + if (opt_point.is_some()) { + println("PASS"); + } else { + println("FAIL"); + } +} + +fn test_maps() { + println("=== Map Tests ==="); + + var scores: map = {}; + scores["alice"] = 100; + scores["bob"] = 85; + scores["charlie"] = 92; + + print("Testing map set and get alice: "); + println(scores["alice"]); + + print("Testing map set and get bob: "); + println(scores["bob"]); + + print("Testing map set and get charlie: "); + println(scores["charlie"]); + + scores["alice"] = 95; + print("Testing map update alice: "); + println(scores["alice"]); + + var counts: map = {}; + counts["a"] = 1; + counts["b"] = 2; + counts["c"] = 3; + print("Testing counts a: "); + println(counts["a"]); + print("Testing counts b: "); + println(counts["b"]); + print("Testing counts c: "); + println(counts["c"]); +} + +fn test_arrays() { + println("=== Array Tests ==="); + + var numbers: [int] = [10, 20, 30, 40, 50]; + + print("Testing array length: "); + if (numbers.len() == 5) { + println("PASS"); + } else { + println("FAIL"); + } + + print("Testing for loop iteration: "); + var sum: int = 0; + for (n in numbers) { + sum = sum + n; + } + if (sum == 150) { + println("PASS"); + } else { + println("FAIL"); + } + + print("Testing indexed for loop: "); + var idx_sum: int = 0; + for (i, n in numbers) { + idx_sum = idx_sum + i; + } + if (idx_sum == 10) { + println("PASS"); + } else { + println("FAIL"); + } + + var empty: [int] = []; + print("Testing empty array length: "); + if (empty.len() == 0) { + println("PASS"); + } else { + println("FAIL"); + } +} + +fn test_structs() { + println("=== Struct Tests ==="); + + var p: Point = Point { x: 5, y: 10 }; + + print("Testing struct field x: "); + if (p.x == 5) { + println("PASS"); + } else { + println("FAIL"); + } + + print("Testing struct field y: "); + if (p.y == 10) { + println("PASS"); + } else { + println("FAIL"); + } + + var rect: Rectangle = Rectangle { width: 100, height: 50 }; + + print("Testing rectangle area: "); + var area: int = rect.width * rect.height; + if (area == 5000) { + println("PASS"); + } else { + println("FAIL"); + } + + var nested: Point = Point { x: 15, y: 25 }; + + print("Testing nested struct access: "); + if (nested.x == 15) { + if (nested.y == 25) { + println("PASS"); + } else { + println("FAIL"); + } + } else { + println("FAIL"); + } +} + +fn test_control_flow() { + println("=== Control Flow Tests ==="); + + print("Testing if-else: "); + var x: int = 42; + if (x == 42) { + println("PASS"); + } else { + println("FAIL"); + } + + print("Testing while loop: "); + var count: int = 0; + var i: int = 0; + while (i < 5) { + count = count + 1; + i = i + 1; + } + if (count == 5) { + println("PASS"); + } else { + println("FAIL"); + } + + print("Testing nested if: "); + var a: int = 10; + var b: int = 20; + if (a < b) { + if (b > 15) { + println("PASS"); + } else { + println("FAIL"); + } + } else { + println("FAIL"); + } + + print("Testing switch with int: "); + var day: int = 3; + switch (day) { + case 1: { + println("FAIL"); + } + case 2: { + println("FAIL"); + } + case 3: { + println("PASS"); + } + default: { + println("FAIL"); + } + } +} + +fn test_combined() { + println("=== Combined Feature Tests ==="); + + print("Testing enum in loop: "); + var colors: [Color] = [Color::Red, Color::Green, Color::Blue]; + var count: int = 0; + for (c in colors) { + count = count + 1; + } + if (count == 3) { + println("PASS"); + } else { + println("FAIL"); + } + + print("Testing option with arithmetic: "); + var opt1: option = some(10); + var opt2: option = some(20); + var v1: int = opt1.or_default(0); + var v2: int = opt2.or_default(0); + if (v1 + v2 == 30) { + println("PASS"); + } else { + println("FAIL"); + } + + print("Testing map with computed values x: "); + var data: map = {}; + var base: int = 10; + data["x"] = base * 2; + data["y"] = base * 3; + println(data["x"]); + print("Testing map with computed values y: "); + println(data["y"]); + + print("Testing struct in array: "); + var points: [Point] = [ + Point { x: 1, y: 2 }, + Point { x: 3, y: 4 }, + Point { x: 5, y: 6 } + ]; + var x_sum: int = 0; + for (pt in points) { + x_sum = x_sum + pt.x; + } + if (x_sum == 9) { + println("PASS"); + } else { + println("FAIL"); + } +} + +fn main() { + println("========================================"); + println(" NAML Tier 1 Integration Test "); + println("========================================"); + println(""); + + test_enums(); + println(""); + + test_options(); + println(""); + + test_maps(); + println(""); + + test_arrays(); + println(""); + + test_structs(); + println(""); + + test_control_flow(); + println(""); + + test_combined(); + println(""); + + println("========================================"); + println(" All Tests Completed! "); + println("========================================"); +} diff --git a/namlc/Cargo.toml b/namlc/Cargo.toml index 1c74de5..97ccfc3 100644 --- a/namlc/Cargo.toml +++ b/namlc/Cargo.toml @@ -90,6 +90,11 @@ memchr.workspace = true ## bumpalo.workspace = true +## +## C library bindings for runtime +## +libc.workspace = true + [dev-dependencies] insta.workspace = true diff --git a/namlc/examples/test_parse.rs b/namlc/examples/test_parse.rs index 540c7e1..5d49ae8 100644 --- a/namlc/examples/test_parse.rs +++ b/namlc/examples/test_parse.rs @@ -1,10 +1,10 @@ -/// -/// Comprehensive Parser Test -/// -/// This file tests the naml parser with a complex, real-world-like codebase -/// that exercises all language features: interfaces, structs, enums, generics, -/// async/await, error handling, lambdas, and more. -/// +//! +//! Comprehensive Parser Test +//! +//! This file tests the naml parser with a complex, real-world-like codebase +//! that exercises all language features: interfaces, structs, enums, generics, +//! async/await, error handling, lambdas, and more. +//! use std::time::Instant; diff --git a/namlc/src/ast/arena.rs b/namlc/src/ast/arena.rs index b3067ac..7dbbbd1 100644 --- a/namlc/src/ast/arena.rs +++ b/namlc/src/ast/arena.rs @@ -1,24 +1,24 @@ -/// -/// AST Arena Allocator -/// -/// This module provides bump allocation for AST nodes, significantly reducing -/// allocation overhead during parsing. Instead of individual Box allocations -/// for each expression node, all AST nodes are allocated from a single arena. -/// -/// Key benefits: -/// - Reduced allocator pressure (single large allocation vs many small ones) -/// - Better cache locality (nodes allocated sequentially in memory) -/// - Faster deallocation (drop entire arena at once) -/// - No individual Box overhead per node -/// -/// Usage: -/// ```ignore -/// let arena = AstArena::new(); -/// let left = arena.alloc(expr1); -/// let right = arena.alloc(expr2); -/// // left and right are &'arena Expression references -/// ``` -/// +//! +//! AST Arena Allocator +//! +//! This module provides bump allocation for AST nodes, significantly reducing +//! allocation overhead during parsing. Instead of individual Box allocations +//! for each expression node, all AST nodes are allocated from a single arena. +//! +//! Key benefits: +//! - Reduced allocator pressure (single large allocation vs many small ones) +//! - Better cache locality (nodes allocated sequentially in memory) +//! - Faster deallocation (drop entire arena at once) +//! - No individual Box overhead per node +//! +//! Usage: +//! ```ignore +//! let arena = AstArena::new(); +//! let left = arena.alloc(expr1); +//! let right = arena.alloc(expr2); +//! // left and right are &'arena Expression references +//! ``` +//! use bumpalo::Bump; diff --git a/namlc/src/ast/expressions.rs b/namlc/src/ast/expressions.rs index eda262f..c6c7675 100644 --- a/namlc/src/ast/expressions.rs +++ b/namlc/src/ast/expressions.rs @@ -1,22 +1,22 @@ -/// -/// Expression AST Nodes -/// -/// This module defines all expression types in the naml language. Expressions -/// are constructs that evaluate to a value. -/// -/// Key design decisions: -/// - Wrapper enum with separate structs for each expression type -/// - Each struct carries its own Span for precise error reporting -/// - Arena-allocated references for recursive structures (zero Box overhead) -/// - All types implement Spanned trait for uniform span access -/// -/// Expression categories: -/// - Atoms: literals, identifiers, grouped expressions -/// - Operators: binary, unary operations -/// - Access: field access, indexing, method calls -/// - Control: if expressions, blocks, spawn, await -/// - Constructors: array literals, map literals, lambdas -/// +//! +//! Expression AST Nodes +//! +//! This module defines all expression types in the naml language. Expressions +//! are constructs that evaluate to a value. +//! +//! Key design decisions: +//! - Wrapper enum with separate structs for each expression type +//! - Each struct carries its own Span for precise error reporting +//! - Arena-allocated references for recursive structures (zero Box overhead) +//! - All types implement Spanned trait for uniform span access +//! +//! Expression categories: +//! - Atoms: literals, identifiers, grouped expressions +//! - Operators: binary, unary operations +//! - Access: field access, indexing, method calls +//! - Control: if expressions, blocks, spawn, await +//! - Constructors: array literals, map literals, lambdas +//! use crate::source::{Span, Spanned}; use super::literals::Literal; diff --git a/namlc/src/ast/items.rs b/namlc/src/ast/items.rs index 91d2e7c..3c1037f 100644 --- a/namlc/src/ast/items.rs +++ b/namlc/src/ast/items.rs @@ -1,23 +1,23 @@ -/// -/// Top-Level Item AST Nodes -/// -/// This module defines all top-level items that can appear in a naml source -/// file. Items are declarations that define named entities in the program. -/// -/// Key item types: -/// - FunctionItem: Function and method definitions -/// - StructItem: Struct type definitions with fields -/// - InterfaceItem: Interface/trait definitions -/// - EnumItem: Enum type definitions with variants -/// - ExceptionItem: Exception type definitions -/// - ImportItem: Module imports -/// - UseItem: Type/function imports from modules -/// - ExternItem: External function declarations -/// -/// Platform annotations: -/// - Functions can be marked with #[platforms(native, server, browser)] -/// - Platform-specific implementations are handled at codegen time -/// +//! +//! Top-Level Item AST Nodes +//! +//! This module defines all top-level items that can appear in a naml source +//! file. Items are declarations that define named entities in the program. +//! +//! Key item types: +//! - FunctionItem: Function and method definitions +//! - StructItem: Struct type definitions with fields +//! - InterfaceItem: Interface/trait definitions +//! - EnumItem: Enum type definitions with variants +//! - ExceptionItem: Exception type definitions +//! - ImportItem: Module imports +//! - UseItem: Type/function imports from modules +//! - ExternItem: External function declarations +//! +//! Platform annotations: +//! - Functions can be marked with #[platforms(native, server, browser)] +//! - Platform-specific implementations are handled at codegen time +//! use crate::source::{Span, Spanned}; use super::statements::{BlockStmt, Statement}; diff --git a/namlc/src/ast/literals.rs b/namlc/src/ast/literals.rs index 1482b79..20f96b3 100644 --- a/namlc/src/ast/literals.rs +++ b/namlc/src/ast/literals.rs @@ -1,15 +1,15 @@ -/// -/// Literal Value Definitions -/// -/// This module defines literal values that can appear in naml source code. -/// Literals are the atomic values like numbers, strings, and booleans. -/// -/// Design decisions: -/// - No Nil literal - use option with None instead -/// - String content is interned via Spur for zero-allocation -/// - Bytes stored as Vec for raw byte data -/// - Separate Int (signed) and UInt (unsigned) for type safety -/// +//! +//! Literal Value Definitions +//! +//! This module defines literal values that can appear in naml source code. +//! Literals are the atomic values like numbers, strings, and booleans. +//! +//! Design decisions: +//! - No Nil literal - use option with None instead +//! - String content is interned via Spur for zero-allocation +//! - Bytes stored as Vec for raw byte data +//! - Separate Int (signed) and UInt (unsigned) for type safety +//! use lasso::Spur; diff --git a/namlc/src/ast/mod.rs b/namlc/src/ast/mod.rs index 9f3f50e..c3993d4 100644 --- a/namlc/src/ast/mod.rs +++ b/namlc/src/ast/mod.rs @@ -1,27 +1,28 @@ -/// -/// Abstract Syntax Tree Module -/// -/// This module defines the complete AST for the naml programming language. -/// The AST is the intermediate representation produced by the parser and -/// consumed by the type checker and code generators. -/// -/// Module structure: -/// - types: Core type system (Ident, NamlType) -/// - literals: Literal values (int, float, string, etc.) -/// - operators: Binary, unary, and assignment operators -/// - expressions: All expression node types -/// - statements: All statement node types -/// - items: Top-level declarations (functions, structs, etc.) -/// - visitor: Visitor pattern for AST traversal -/// -/// The root AST node is SourceFile, representing a complete naml source file. -/// +//! +//! Abstract Syntax Tree Module +//! +//! This module defines the complete AST for the naml programming language. +//! The AST is the intermediate representation produced by the parser and +//! consumed by the type checker and code generators. +//! +//! Module structure: +//! - types: Core type system (Ident, NamlType) +//! - literals: Literal values (int, float, string, etc.) +//! - operators: Binary, unary, and assignment operators +//! - expressions: All expression node types +//! - statements: All statement node types +//! - items: Top-level declarations (functions, structs, etc.) +//! - visitor: Visitor pattern for AST traversal +//! +//! The root AST node is SourceFile, representing a complete naml source file. +//! pub mod arena; pub mod expressions; pub mod items; pub mod literals; pub mod operators; +pub mod patterns; pub mod statements; pub mod types; pub mod visitor; @@ -31,6 +32,7 @@ pub use expressions::*; pub use items::*; pub use literals::*; pub use operators::*; +pub use patterns::*; pub use statements::*; pub use types::*; pub use visitor::*; diff --git a/namlc/src/ast/operators.rs b/namlc/src/ast/operators.rs index a84da86..1586a3d 100644 --- a/namlc/src/ast/operators.rs +++ b/namlc/src/ast/operators.rs @@ -1,17 +1,17 @@ -/// -/// Operator Definitions -/// -/// This module defines all operators in the naml language including binary, -/// unary, and assignment operators. -/// -/// Key types: -/// - BinaryOp: Two-operand operators (arithmetic, comparison, logical, bitwise) -/// - UnaryOp: Single-operand operators (negation, logical not, bitwise not) -/// - AssignOp: Assignment and compound assignment operators -/// -/// The precedence() method on BinaryOp is used by the Pratt parser for -/// correct operator precedence handling. -/// +//! +//! Operator Definitions +//! +//! This module defines all operators in the naml language including binary, +//! unary, and assignment operators. +//! +//! Key types: +//! - BinaryOp: Two-operand operators (arithmetic, comparison, logical, bitwise) +//! - UnaryOp: Single-operand operators (negation, logical not, bitwise not) +//! - AssignOp: Assignment and compound assignment operators +//! +//! The precedence() method on BinaryOp is used by the Pratt parser for +//! correct operator precedence handling. +//! #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum BinaryOp { diff --git a/namlc/src/ast/patterns.rs b/namlc/src/ast/patterns.rs new file mode 100644 index 0000000..f96e61b --- /dev/null +++ b/namlc/src/ast/patterns.rs @@ -0,0 +1,112 @@ +//! +//! Pattern AST Nodes +//! +//! Patterns are used in switch cases and destructuring bindings. +//! They can match literals, enum variants, and bind variables. +//! +//! Key pattern types: +//! - LiteralPattern: Match a literal value (int, string, etc.) +//! - IdentPattern: Match an identifier (binds or compares) +//! - VariantPattern: Match an enum variant with optional bindings +//! - WildcardPattern: Match anything (the `_` pattern) +//! +//! Design decisions: +//! - Each pattern carries its own Span for error reporting +//! - VariantPattern supports both simple (Active) and destructuring (Suspended(reason)) forms +//! - The path in VariantPattern allows qualified names like EnumType.Variant +//! - VariantPattern uses Vec for path and bindings, which allocates on the heap +//! + +use crate::source::{Span, Spanned}; +use super::types::Ident; +use super::literals::Literal; + +#[derive(Debug, Clone, PartialEq)] +pub enum Pattern<'ast> { + Literal(LiteralPattern), + Identifier(IdentPattern), + Variant(VariantPattern), + Wildcard(WildcardPattern), + #[doc(hidden)] + _Phantom(std::marker::PhantomData<&'ast ()>), +} + +impl<'ast> Spanned for Pattern<'ast> { + fn span(&self) -> Span { + match self { + Pattern::Literal(p) => p.span, + Pattern::Identifier(p) => p.span, + Pattern::Variant(p) => p.span, + Pattern::Wildcard(p) => p.span, + Pattern::_Phantom(_) => unreachable!(), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct LiteralPattern { + pub value: Literal, + pub span: Span, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct IdentPattern { + pub ident: Ident, + pub span: Span, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct VariantPattern { + pub path: Vec, + pub bindings: Vec, + pub span: Span, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct WildcardPattern { + pub span: Span, +} + +#[cfg(test)] +mod tests { + use super::*; + use lasso::Spur; + + #[test] + fn test_literal_pattern_span() { + let pattern = Pattern::Literal(LiteralPattern { + value: Literal::Int(42), + span: Span::new(0, 2, 0), + }); + assert_eq!(pattern.span(), Span::new(0, 2, 0)); + } + + #[test] + fn test_wildcard_pattern_span() { + let pattern = Pattern::Wildcard(WildcardPattern { + span: Span::new(10, 11, 0), + }); + assert_eq!(pattern.span(), Span::new(10, 11, 0)); + } + + #[test] + fn test_ident_pattern_span() { + let pattern = Pattern::Identifier(IdentPattern { + ident: Ident::new(Spur::default(), Span::new(5, 10, 0)), + span: Span::new(5, 10, 0), + }); + assert_eq!(pattern.span(), Span::new(5, 10, 0)); + } + + #[test] + fn test_variant_pattern_span() { + let pattern = Pattern::Variant(VariantPattern { + path: vec![ + Ident::new(Spur::default(), Span::new(0, 6, 0)), + ], + bindings: vec![], + span: Span::new(0, 6, 0), + }); + assert_eq!(pattern.span(), Span::new(0, 6, 0)); + } +} diff --git a/namlc/src/ast/statements.rs b/namlc/src/ast/statements.rs index 1a928ef..5ae8f33 100644 --- a/namlc/src/ast/statements.rs +++ b/namlc/src/ast/statements.rs @@ -1,24 +1,25 @@ -/// -/// Statement AST Nodes -/// -/// This module defines all statement types in the naml language. Statements -/// are constructs that perform actions but don't necessarily produce values. -/// -/// Key statement categories: -/// - Declarations: var, const -/// - Control flow: if, while, for, loop, switch, break, continue, return -/// - Expression statements: expressions used for side effects -/// - Error handling: throw -/// -/// Design notes: -/// - VarStmt supports both `var x` and `var mut x` for mutability -/// - ForStmt supports optional index binding `for (i, val in collection)` -/// - IfStmt vs IfExpr: statements don't require else, expressions do -/// +//! +//! Statement AST Nodes +//! +//! This module defines all statement types in the naml language. Statements +//! are constructs that perform actions but don't necessarily produce values. +//! +//! Key statement categories: +//! - Declarations: var, const +//! - Control flow: if, while, for, loop, switch, break, continue, return +//! - Expression statements: expressions used for side effects +//! - Error handling: throw +//! +//! Design notes: +//! - VarStmt supports both `var x` and `var mut x` for mutability +//! - ForStmt supports optional index binding `for (i, val in collection)` +//! - IfStmt vs IfExpr: statements don't require else, expressions do +//! use crate::source::{Span, Spanned}; use super::expressions::{BlockExpr, Expression}; use super::operators::AssignOp; +use super::patterns::Pattern; use super::types::{Ident, NamlType}; #[derive(Debug, Clone, PartialEq)] @@ -151,7 +152,7 @@ pub struct SwitchStmt<'ast> { #[derive(Debug, Clone, PartialEq)] pub struct SwitchCase<'ast> { - pub pattern: Expression<'ast>, + pub pattern: Pattern<'ast>, pub body: BlockStmt<'ast>, pub span: Span, } diff --git a/namlc/src/ast/types.rs b/namlc/src/ast/types.rs index b86d2cf..49e163d 100644 --- a/namlc/src/ast/types.rs +++ b/namlc/src/ast/types.rs @@ -1,20 +1,20 @@ -/// -/// AST Type Definitions -/// -/// This module defines the core type system for naml's AST. All types that -/// can appear in type annotations are represented here. -/// -/// Key types: -/// - Ident: An identifier with its source location (uses string interning) -/// - NamlType: The complete type system including primitives, composites, -/// generics, and function types -/// -/// Design decisions: -/// - No Any type - naml is strongly typed with no dynamic escape hatch -/// - Ident carries its Span for better error messages -/// - Box-based nesting for simplicity (can optimize to arena later) -/// - Inferred placeholder for type inference pass -/// +//! +//! AST Type Definitions +//! +//! This module defines the core type system for naml's AST. All types that +//! can appear in type annotations are represented here. +//! +//! Key types: +//! - Ident: An identifier with its source location (uses string interning) +//! - NamlType: The complete type system including primitives, composites, +//! generics, and function types +//! +//! Design decisions: +//! - No Any type - naml is strongly typed with no dynamic escape hatch +//! - Ident carries its Span for better error messages +//! - Box-based nesting for simplicity (can optimize to arena later) +//! - Inferred placeholder for type inference pass +//! use lasso::Spur; use crate::source::Span; diff --git a/namlc/src/ast/visitor.rs b/namlc/src/ast/visitor.rs index 5cf7015..a92155c 100644 --- a/namlc/src/ast/visitor.rs +++ b/namlc/src/ast/visitor.rs @@ -1,24 +1,25 @@ -/// -/// AST Visitor Pattern -/// -/// This module provides a visitor trait for traversing the AST. The visitor -/// pattern allows implementing different operations over the AST without -/// modifying the AST node types. -/// -/// Usage: -/// - Implement the Visitor trait -/// - Override only the methods you need -/// - Default implementations call walk_* functions for recursive traversal -/// -/// Common use cases: -/// - Type checking: visit expressions and statements to check types -/// - Code generation: visit items to emit code -/// - Linting: visit nodes to check for patterns -/// - Pretty printing: visit nodes to format code -/// +//! +//! AST Visitor Pattern +//! +//! This module provides a visitor trait for traversing the AST. The visitor +//! pattern allows implementing different operations over the AST without +//! modifying the AST node types. +//! +//! Usage: +//! - Implement the Visitor trait +//! - Override only the methods you need +//! - Default implementations call walk_* functions for recursive traversal +//! +//! Common use cases: +//! - Type checking: visit expressions and statements to check types +//! - Code generation: visit items to emit code +//! - Linting: visit nodes to check for patterns +//! - Pretty printing: visit nodes to format code +//! use super::expressions::*; use super::items::*; +use super::patterns::*; use super::statements::*; use super::types::*; @@ -39,6 +40,10 @@ pub trait Visitor<'ast>: Sized { walk_type(self, ty) } + fn visit_pattern(&mut self, pattern: &Pattern<'ast>) { + walk_pattern(self, pattern) + } + fn visit_ident(&mut self, _ident: &Ident) {} } @@ -240,7 +245,7 @@ pub fn walk_stmt<'ast, V: Visitor<'ast>>(v: &mut V, stmt: &Statement<'ast>) { Statement::Switch(s) => { v.visit_expr(&s.scrutinee); for case in &s.cases { - v.visit_expr(&case.pattern); + v.visit_pattern(&case.pattern); for stmt in &case.body.statements { v.visit_stmt(stmt); } @@ -440,6 +445,29 @@ pub fn walk_type<'ast, V: Visitor<'ast>>(v: &mut V, ty: &NamlType) { } } +pub fn walk_pattern<'ast, V: Visitor<'ast>>(v: &mut V, pattern: &Pattern<'ast>) { + match pattern { + Pattern::Literal(_) => { + // Literals don't contain nested visitable elements + } + Pattern::Identifier(p) => { + v.visit_ident(&p.ident); + } + Pattern::Variant(p) => { + for seg in &p.path { + v.visit_ident(seg); + } + for binding in &p.bindings { + v.visit_ident(binding); + } + } + Pattern::Wildcard(_) => { + // Wildcard has no nested elements + } + Pattern::_Phantom(_) => {} + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/namlc/src/codegen/cranelift/mod.rs b/namlc/src/codegen/cranelift/mod.rs new file mode 100644 index 0000000..9a72975 --- /dev/null +++ b/namlc/src/codegen/cranelift/mod.rs @@ -0,0 +1,3244 @@ +//! +//! Cranelift JIT Compiler +//! +//! Compiles naml AST directly to machine code using Cranelift. +//! This eliminates the Rust transpilation step and gives full control +//! over memory management and runtime semantics. +//! + +mod types; + +use std::collections::HashMap; + +use cranelift::prelude::*; +use cranelift_codegen::ir::AtomicRmwOp; +use cranelift_jit::{JITBuilder, JITModule}; +use cranelift_module::{DataDescription, FuncId, Linkage, Module}; +use lasso::Rodeo; + +use crate::ast::{ + BinaryOp, Expression, FunctionItem, Item, Literal, SourceFile, Statement, UnaryOp, + LiteralExpr, +}; +use crate::codegen::CodegenError; +use crate::source::Spanned; +use crate::typechecker::{SymbolTable, Type, TypeAnnotations}; + +#[derive(Clone)] +pub struct StructDef { + pub type_id: u32, + pub fields: Vec, + pub(crate) field_heap_types: Vec>, +} + +#[derive(Clone)] +pub struct EnumDef { + pub name: String, + pub variants: Vec, + pub size: usize, +} + +#[derive(Clone)] +pub struct EnumVariantDef { + pub name: String, + pub tag: u32, + pub field_types: Vec, + pub data_offset: usize, +} + +#[derive(Clone)] +pub struct ExternFn { + pub link_name: String, + pub param_types: Vec, + pub return_type: Option, +} + +#[derive(Clone)] +pub struct SpawnBlockInfo { + pub id: u32, + pub func_name: String, + pub captured_vars: Vec, + pub body_ptr: *const crate::ast::BlockExpr<'static>, +} + +unsafe impl Send for SpawnBlockInfo {} + +pub struct JitCompiler<'a> { + interner: &'a Rodeo, + #[allow(dead_code)] + annotations: &'a TypeAnnotations, + #[allow(dead_code)] + symbols: &'a SymbolTable, + module: JITModule, + ctx: codegen::Context, + functions: HashMap, + struct_defs: HashMap, + enum_defs: HashMap, + extern_fns: HashMap, + next_type_id: u32, + spawn_counter: u32, + spawn_blocks: HashMap, +} + +impl<'a> JitCompiler<'a> { + pub fn new( + interner: &'a Rodeo, + annotations: &'a TypeAnnotations, + symbols: &'a SymbolTable, + ) -> Result { + let mut flag_builder = settings::builder(); + flag_builder.set("use_colocated_libcalls", "false").unwrap(); + flag_builder.set("is_pic", "false").unwrap(); + + let isa_builder = cranelift_native::builder() + .map_err(|e| CodegenError::JitCompile(format!("Failed to create ISA builder: {}", e)))?; + + let isa = isa_builder + .finish(settings::Flags::new(flag_builder)) + .map_err(|e| CodegenError::JitCompile(format!("Failed to create ISA: {}", e)))?; + + let mut builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names()); + + // Print builtins + builder.symbol("naml_print_int", naml_print_int as *const u8); + builder.symbol("naml_print_float", naml_print_float as *const u8); + builder.symbol("naml_print_str", naml_print_str as *const u8); + builder.symbol("naml_print_newline", naml_print_newline as *const u8); + + // Array runtime functions + builder.symbol("naml_array_new", crate::runtime::naml_array_new as *const u8); + builder.symbol("naml_array_from", crate::runtime::naml_array_from as *const u8); + builder.symbol("naml_array_push", crate::runtime::naml_array_push as *const u8); + builder.symbol("naml_array_get", crate::runtime::naml_array_get as *const u8); + builder.symbol("naml_array_set", crate::runtime::naml_array_set as *const u8); + builder.symbol("naml_array_len", crate::runtime::naml_array_len as *const u8); + builder.symbol("naml_array_pop", crate::runtime::naml_array_pop as *const u8); + builder.symbol("naml_array_print", crate::runtime::naml_array_print as *const u8); + builder.symbol("naml_array_incref", crate::runtime::naml_array_incref as *const u8); + builder.symbol("naml_array_decref", crate::runtime::naml_array_decref as *const u8); + builder.symbol("naml_array_decref_strings", crate::runtime::naml_array_decref_strings as *const u8); + builder.symbol("naml_array_decref_arrays", crate::runtime::naml_array_decref_arrays as *const u8); + builder.symbol("naml_array_decref_maps", crate::runtime::naml_array_decref_maps as *const u8); + builder.symbol("naml_array_decref_structs", crate::runtime::naml_array_decref_structs as *const u8); + + // Struct operations + builder.symbol("naml_struct_new", crate::runtime::naml_struct_new as *const u8); + builder.symbol("naml_struct_incref", crate::runtime::naml_struct_incref as *const u8); + builder.symbol("naml_struct_decref", crate::runtime::naml_struct_decref as *const u8); + builder.symbol("naml_struct_free", crate::runtime::naml_struct_free as *const u8); + builder.symbol("naml_struct_get_field", crate::runtime::naml_struct_get_field as *const u8); + builder.symbol("naml_struct_set_field", crate::runtime::naml_struct_set_field as *const u8); + + // Scheduler operations + builder.symbol("naml_spawn", crate::runtime::naml_spawn as *const u8); + builder.symbol("naml_spawn_closure", crate::runtime::naml_spawn_closure as *const u8); + builder.symbol("naml_alloc_closure_data", crate::runtime::naml_alloc_closure_data as *const u8); + builder.symbol("naml_wait_all", crate::runtime::naml_wait_all as *const u8); + builder.symbol("naml_sleep", crate::runtime::naml_sleep as *const u8); + + // Channel operations + builder.symbol("naml_channel_new", crate::runtime::naml_channel_new as *const u8); + builder.symbol("naml_channel_send", crate::runtime::naml_channel_send as *const u8); + builder.symbol("naml_channel_receive", crate::runtime::naml_channel_receive as *const u8); + builder.symbol("naml_channel_close", crate::runtime::naml_channel_close as *const u8); + builder.symbol("naml_channel_len", crate::runtime::naml_channel_len as *const u8); + builder.symbol("naml_channel_incref", crate::runtime::naml_channel_incref as *const u8); + builder.symbol("naml_channel_decref", crate::runtime::naml_channel_decref as *const u8); + + // Map operations + builder.symbol("naml_map_new", crate::runtime::naml_map_new as *const u8); + builder.symbol("naml_map_set", crate::runtime::naml_map_set as *const u8); + builder.symbol("naml_map_set_string", crate::runtime::naml_map_set_string as *const u8); + builder.symbol("naml_map_set_array", crate::runtime::naml_map_set_array as *const u8); + builder.symbol("naml_map_set_map", crate::runtime::naml_map_set_map as *const u8); + builder.symbol("naml_map_set_struct", crate::runtime::naml_map_set_struct as *const u8); + builder.symbol("naml_map_get", crate::runtime::naml_map_get as *const u8); + builder.symbol("naml_map_contains", crate::runtime::naml_map_contains as *const u8); + builder.symbol("naml_map_len", crate::runtime::naml_map_len as *const u8); + builder.symbol("naml_map_incref", crate::runtime::naml_map_incref as *const u8); + builder.symbol("naml_map_decref", crate::runtime::naml_map_decref as *const u8); + builder.symbol("naml_map_decref_strings", crate::runtime::naml_map_decref_strings as *const u8); + builder.symbol("naml_map_decref_arrays", crate::runtime::naml_map_decref_arrays as *const u8); + builder.symbol("naml_map_decref_maps", crate::runtime::naml_map_decref_maps as *const u8); + builder.symbol("naml_map_decref_structs", crate::runtime::naml_map_decref_structs as *const u8); + + // String operations + builder.symbol("naml_string_from_cstr", crate::runtime::naml_string_from_cstr as *const u8); + builder.symbol("naml_string_print", crate::runtime::naml_string_print as *const u8); + builder.symbol("naml_string_eq", crate::runtime::naml_string_eq as *const u8); + builder.symbol("naml_string_incref", crate::runtime::naml_string_incref as *const u8); + builder.symbol("naml_string_decref", crate::runtime::naml_string_decref as *const u8); + + let module = JITModule::new(builder); + let ctx = module.make_context(); + + // Built-in option type (polymorphic, treat as Option for now) + let mut enum_defs = HashMap::new(); + enum_defs.insert("option".to_string(), EnumDef { + name: "option".to_string(), + variants: vec![ + EnumVariantDef { + name: "none".to_string(), + tag: 0, + field_types: vec![], + data_offset: 8, + }, + EnumVariantDef { + name: "some".to_string(), + tag: 1, + field_types: vec![crate::ast::NamlType::Int], + data_offset: 8, + }, + ], + size: 16, // 8 (tag+pad) + 8 (data) + }); + + Ok(Self { + interner, + annotations, + symbols, + module, + ctx, + functions: HashMap::new(), + struct_defs: HashMap::new(), + enum_defs, + extern_fns: HashMap::new(), + next_type_id: 0, + spawn_counter: 0, + spawn_blocks: HashMap::new(), + }) + } + + pub fn compile(&mut self, ast: &SourceFile<'_>) -> Result<(), CodegenError> { + // First pass: collect struct definitions with field heap types + for item in &ast.items { + if let crate::ast::Item::Struct(struct_item) = item { + let name = self.interner.resolve(&struct_item.name.symbol).to_string(); + let mut fields = Vec::new(); + let mut field_heap_types = Vec::new(); + + for f in &struct_item.fields { + fields.push(self.interner.resolve(&f.name.symbol).to_string()); + field_heap_types.push(get_heap_type(&f.ty)); + } + + let type_id = self.next_type_id; + self.next_type_id += 1; + + self.struct_defs.insert(name, StructDef { type_id, fields, field_heap_types }); + } + } + + // Collect enum definitions + for item in &ast.items { + if let crate::ast::Item::Enum(enum_item) = item { + let name = self.interner.resolve(&enum_item.name.symbol).to_string(); + let mut variants = Vec::new(); + let mut max_data_size: usize = 0; + + for (tag, variant) in enum_item.variants.iter().enumerate() { + let variant_name = self.interner.resolve(&variant.name.symbol).to_string(); + let field_types = variant.fields.clone().unwrap_or_default(); + let data_size = field_types.len() * 8; // Each field is 8 bytes + max_data_size = max_data_size.max(data_size); + + variants.push(EnumVariantDef { + name: variant_name, + tag: tag as u32, + field_types, + data_offset: 8, // After tag + padding + }); + } + + // Align to 8 bytes + let size = 8 + ((max_data_size + 7) / 8) * 8; + + self.enum_defs.insert(name.clone(), EnumDef { + name, + variants, + size, + }); + } + } + + // Collect extern function declarations + for item in &ast.items { + if let crate::ast::Item::Extern(extern_item) = item { + let name = self.interner.resolve(&extern_item.name.symbol).to_string(); + let link_name = if let Some(ref ln) = extern_item.link_name { + self.interner.resolve(&ln.symbol).to_string() + } else { + name.clone() + }; + + let param_types: Vec<_> = extern_item.params.iter() + .map(|p| p.ty.clone()) + .collect(); + + self.extern_fns.insert(name, ExternFn { + link_name, + param_types, + return_type: extern_item.return_ty.clone(), + }); + } + } + + // Generate per-struct decref functions for structs with heap fields + self.generate_struct_decref_functions()?; + + // Scan for spawn blocks and collect captured variable info + for item in &ast.items { + if let Item::Function(f) = item { + if let Some(ref body) = f.body { + self.scan_for_spawn_blocks(body)?; + } + } + } + + // Declare spawn trampolines + for (id, info) in &self.spawn_blocks.clone() { + self.declare_spawn_trampoline(*id, info)?; + } + + // Compile spawn trampolines (must be done before regular functions) + for info in self.spawn_blocks.clone().values() { + self.compile_spawn_trampoline(info)?; + } + + for item in &ast.items { + if let Item::Function(f) = item { + if f.receiver.is_none() { + self.declare_function(f)?; + } + } + } + + for item in &ast.items { + if let Item::Function(f) = item { + if f.receiver.is_none() && f.body.is_some() { + self.compile_function(f)?; + } + } + } + + Ok(()) + } + + fn generate_struct_decref_functions(&mut self) -> Result<(), CodegenError> { + // Collect structs that need specialized decref functions + let structs_with_heap_fields: Vec<(String, StructDef)> = self.struct_defs.iter() + .filter(|(_, def)| def.field_heap_types.iter().any(|ht| ht.is_some())) + .map(|(name, def)| (name.clone(), def.clone())) + .collect(); + + for (struct_name, struct_def) in structs_with_heap_fields { + self.generate_struct_decref(&struct_name, &struct_def)?; + } + + Ok(()) + } + + fn generate_struct_decref(&mut self, struct_name: &str, struct_def: &StructDef) -> Result<(), CodegenError> { + let ptr_type = self.module.target_config().pointer_type(); + let func_name = format!("naml_struct_decref_{}", struct_name); + + // Function signature: fn(struct_ptr: *mut NamlStruct) + let mut sig = self.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + + let func_id = self.module + .declare_function(&func_name, Linkage::Local, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare {}: {}", func_name, e)))?; + + // Store for later reference + self.functions.insert(func_name.clone(), func_id); + + self.ctx.func.signature = sig; + + let mut builder_ctx = FunctionBuilderContext::new(); + let mut builder = FunctionBuilder::new(&mut self.ctx.func, &mut builder_ctx); + + let entry_block = builder.create_block(); + builder.append_block_params_for_function_params(entry_block); + builder.switch_to_block(entry_block); + builder.seal_block(entry_block); + + let struct_ptr = builder.block_params(entry_block)[0]; + + // Check if ptr is null + let zero = builder.ins().iconst(ptr_type, 0); + let is_null = builder.ins().icmp(IntCC::Equal, struct_ptr, zero); + + let null_block = builder.create_block(); + let decref_block = builder.create_block(); + + builder.ins().brif(is_null, null_block, &[], decref_block, &[]); + + // Null case: just return + builder.switch_to_block(null_block); + builder.seal_block(null_block); + builder.ins().return_(&[]); + + // Non-null case: decref the struct + builder.switch_to_block(decref_block); + builder.seal_block(decref_block); + + // Call atomic decref on refcount (at offset 0 in HeapHeader) + // HeapHeader layout: refcount (8 bytes), tag (1 byte), pad (7 bytes) + // Use atomic_rmw to safely decrement refcount in multi-threaded scenarios + let one = builder.ins().iconst(cranelift::prelude::types::I64, 1); + let old_refcount = builder.ins().atomic_rmw( + cranelift::prelude::types::I64, + MemFlags::new(), + AtomicRmwOp::Sub, + struct_ptr, + one, + ); + + // Check if old refcount was 1 (meaning it's now 0 and we should free) + let should_free = builder.ins().icmp(IntCC::Equal, old_refcount, one); + + let free_block = builder.create_block(); + let done_block = builder.create_block(); + + builder.ins().brif(should_free, free_block, &[], done_block, &[]); + builder.switch_to_block(free_block); + builder.seal_block(free_block); + builder.ins().fence(); + + // Struct memory layout after header: + // - type_id: u32 (offset 16) + // - field_count: u32 (offset 20) + // - fields[]: i64 (offset 24+) + let base_field_offset: i32 = 24; // sizeof(HeapHeader) + type_id + field_count + + let mut decref_sig = self.module.make_signature(); + decref_sig.params.push(AbiParam::new(ptr_type)); + + for (field_idx, heap_type) in struct_def.field_heap_types.iter().enumerate() { + if let Some(ht) = heap_type { + let field_offset = base_field_offset + (field_idx as i32 * 8); + let field_val = builder.ins().load(cranelift::prelude::types::I64, MemFlags::new(), struct_ptr, field_offset); + + let field_is_null = builder.ins().icmp(IntCC::Equal, field_val, zero); + let decref_field_block = builder.create_block(); + let next_field_block = builder.create_block(); + + builder.ins().brif(field_is_null, next_field_block, &[], decref_field_block, &[]); + builder.switch_to_block(decref_field_block); + builder.seal_block(decref_field_block); + + let decref_func_name = match ht { + HeapType::String => "naml_string_decref", + HeapType::Array(None) => "naml_array_decref", + HeapType::Array(Some(elem_type)) => { + match elem_type.as_ref() { + HeapType::String => "naml_array_decref_strings", + HeapType::Array(_) => "naml_array_decref_arrays", + HeapType::Map(_) => "naml_array_decref_maps", + HeapType::Struct(_) => "naml_array_decref_structs", + } + } + HeapType::Map(None) => "naml_map_decref", + HeapType::Map(Some(val_type)) => { + match val_type.as_ref() { + HeapType::String => "naml_map_decref_strings", + HeapType::Array(_) => "naml_map_decref_arrays", + HeapType::Map(_) => "naml_map_decref_maps", + HeapType::Struct(_) => "naml_map_decref_structs", + } + } + HeapType::Struct(None) => "naml_struct_decref", + HeapType::Struct(Some(nested_struct_name)) => { + if self.struct_defs.get(nested_struct_name) + .map(|def| def.field_heap_types.iter().any(|h| h.is_some())) + .unwrap_or(false) + { + "naml_struct_decref" // Fallback for now + } else { + "naml_struct_decref" + } + } + }; + + let decref_func_id = self.module + .declare_function(decref_func_name, Linkage::Import, &decref_sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare {}: {}", decref_func_name, e)))?; + + let decref_func_ref = self.module.declare_func_in_func(decref_func_id, builder.func); + builder.ins().call(decref_func_ref, &[field_val]); + builder.ins().jump(next_field_block, &[]); + + builder.switch_to_block(next_field_block); + builder.seal_block(next_field_block); + } + } + + // Call naml_struct_free to deallocate the struct memory + let free_func_id = self.module + .declare_function("naml_struct_free", Linkage::Import, &decref_sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_struct_free: {}", e)))?; + let free_func_ref = self.module.declare_func_in_func(free_func_id, builder.func); + builder.ins().call(free_func_ref, &[struct_ptr]); + builder.ins().jump(done_block, &[]); + + // Done block: return + builder.switch_to_block(done_block); + builder.seal_block(done_block); + builder.ins().return_(&[]); + + builder.finalize(); + + self.module + .define_function(func_id, &mut self.ctx) + .map_err(|e| CodegenError::JitCompile(format!("Failed to define {}: {}", func_name, e)))?; + + self.ctx.clear(); + + Ok(()) + } + + fn scan_for_spawn_blocks(&mut self, block: &crate::ast::BlockStmt<'_>) -> Result<(), CodegenError> { + for stmt in &block.statements { + self.scan_statement_for_spawns(stmt)?; + } + Ok(()) + } + + fn scan_statement_for_spawns(&mut self, stmt: &Statement<'_>) -> Result<(), CodegenError> { + match stmt { + Statement::Expression(expr_stmt) => { + self.scan_expression_for_spawns(&expr_stmt.expr)?; + } + Statement::If(if_stmt) => { + self.scan_expression_for_spawns(&if_stmt.condition)?; + self.scan_for_spawn_blocks(&if_stmt.then_branch)?; + if let Some(ref else_branch) = if_stmt.else_branch { + match else_branch { + crate::ast::ElseBranch::ElseIf(elif) => { + self.scan_statement_for_spawns(&Statement::If(*elif.clone()))?; + } + crate::ast::ElseBranch::Else(block) => { + self.scan_for_spawn_blocks(block)?; + } + } + } + } + Statement::While(while_stmt) => { + self.scan_expression_for_spawns(&while_stmt.condition)?; + self.scan_for_spawn_blocks(&while_stmt.body)?; + } + Statement::For(for_stmt) => { + self.scan_expression_for_spawns(&for_stmt.iterable)?; + self.scan_for_spawn_blocks(&for_stmt.body)?; + } + Statement::Loop(loop_stmt) => { + self.scan_for_spawn_blocks(&loop_stmt.body)?; + } + Statement::Switch(switch_stmt) => { + self.scan_expression_for_spawns(&switch_stmt.scrutinee)?; + for case in &switch_stmt.cases { + self.scan_for_spawn_blocks(&case.body)?; + } + if let Some(ref default) = switch_stmt.default { + self.scan_for_spawn_blocks(default)?; + } + } + Statement::Block(block) => { + self.scan_for_spawn_blocks(block)?; + } + Statement::Var(var_stmt) => { + if let Some(ref init) = var_stmt.init { + self.scan_expression_for_spawns(init)?; + } + } + Statement::Assign(assign_stmt) => { + self.scan_expression_for_spawns(&assign_stmt.value)?; + } + Statement::Return(ret_stmt) => { + if let Some(ref value) = ret_stmt.value { + self.scan_expression_for_spawns(value)?; + } + } + _ => {} + } + Ok(()) + } + + fn scan_expression_for_spawns(&mut self, expr: &Expression<'_>) -> Result<(), CodegenError> { + match expr { + Expression::Spawn(spawn_expr) => { + // Found a spawn block - collect captured variables + let captured = self.collect_captured_vars_expr(&spawn_expr.body); + let id = self.spawn_counter; + self.spawn_counter += 1; + let func_name = format!("__spawn_{}", id); + + // Store raw pointer to body for deferred trampoline compilation + // Safety: Only used within the same compile() call + // Note: spawn_expr.body is already a &BlockExpr, so we cast it directly + let body_ptr = spawn_expr.body as *const crate::ast::BlockExpr<'_> as *const crate::ast::BlockExpr<'static>; + + self.spawn_blocks.insert(id, SpawnBlockInfo { + id, + func_name, + captured_vars: captured, + body_ptr, + }); + + // Also scan inside spawn block for nested spawns + self.scan_for_spawn_blocks_expr(&spawn_expr.body)?; + } + Expression::Binary(bin) => { + self.scan_expression_for_spawns(&bin.left)?; + self.scan_expression_for_spawns(&bin.right)?; + } + Expression::Unary(un) => { + self.scan_expression_for_spawns(&un.operand)?; + } + Expression::Call(call) => { + self.scan_expression_for_spawns(&call.callee)?; + for arg in &call.args { + self.scan_expression_for_spawns(arg)?; + } + } + Expression::MethodCall(method) => { + self.scan_expression_for_spawns(&method.receiver)?; + for arg in &method.args { + self.scan_expression_for_spawns(arg)?; + } + } + Expression::Index(idx) => { + self.scan_expression_for_spawns(&idx.base)?; + self.scan_expression_for_spawns(&idx.index)?; + } + Expression::Array(arr) => { + for elem in &arr.elements { + self.scan_expression_for_spawns(elem)?; + } + } + Expression::If(if_expr) => { + self.scan_expression_for_spawns(&if_expr.condition)?; + self.scan_for_spawn_blocks_expr(&if_expr.then_branch)?; + self.scan_else_branch_for_spawns(&if_expr.else_branch)?; + } + Expression::Block(block) => { + self.scan_for_spawn_blocks_expr(block)?; + } + Expression::Grouped(grouped) => { + self.scan_expression_for_spawns(&grouped.inner)?; + } + _ => {} + } + Ok(()) + } + + fn scan_for_spawn_blocks_expr(&mut self, block: &crate::ast::BlockExpr<'_>) -> Result<(), CodegenError> { + for stmt in &block.statements { + self.scan_statement_for_spawns(stmt)?; + } + if let Some(tail) = block.tail { + self.scan_expression_for_spawns(tail)?; + } + Ok(()) + } + + fn scan_else_branch_for_spawns(&mut self, else_branch: &Option>) -> Result<(), CodegenError> { + if let Some(branch) = else_branch { + match branch { + crate::ast::ElseExpr::ElseIf(elif) => { + self.scan_expression_for_spawns(&elif.condition)?; + self.scan_for_spawn_blocks_expr(&elif.then_branch)?; + self.scan_else_branch_for_spawns(&elif.else_branch)?; + } + crate::ast::ElseExpr::Else(block) => { + self.scan_for_spawn_blocks_expr(block)?; + } + } + } + Ok(()) + } + + fn collect_captured_vars_expr(&self, block: &crate::ast::BlockExpr<'_>) -> Vec { + let mut captured = Vec::new(); + let mut defined = std::collections::HashSet::new(); + self.collect_vars_in_block_expr(block, &mut captured, &mut defined); + captured + } + + fn collect_vars_in_block(&self, + block: &crate::ast::BlockStmt<'_>, + captured: &mut Vec, + defined: &mut std::collections::HashSet, + ) { + for stmt in &block.statements { + self.collect_vars_in_statement(stmt, captured, defined); + } + } + + fn collect_vars_in_block_expr( + &self, + block: &crate::ast::BlockExpr<'_>, + captured: &mut Vec, + defined: &mut std::collections::HashSet, + ) { + for stmt in &block.statements { + self.collect_vars_in_statement(stmt, captured, defined); + } + if let Some(tail) = block.tail { + self.collect_vars_in_expression(tail, captured, defined); + } + } + + fn collect_vars_in_statement( + &self, + stmt: &Statement<'_>, + captured: &mut Vec, + defined: &mut std::collections::HashSet, + ) { + match stmt { + Statement::Var(var_stmt) => { + if let Some(ref init) = var_stmt.init { + self.collect_vars_in_expression(init, captured, defined); + } + let name = self.interner.resolve(&var_stmt.name.symbol).to_string(); + defined.insert(name); + } + Statement::Expression(expr_stmt) => { + self.collect_vars_in_expression(&expr_stmt.expr, captured, defined); + } + Statement::Assign(assign) => { + self.collect_vars_in_expression(&assign.target, captured, defined); + self.collect_vars_in_expression(&assign.value, captured, defined); + } + Statement::If(if_stmt) => { + self.collect_vars_in_expression(&if_stmt.condition, captured, defined); + self.collect_vars_in_block(&if_stmt.then_branch, captured, defined); + } + Statement::While(while_stmt) => { + self.collect_vars_in_expression(&while_stmt.condition, captured, defined); + self.collect_vars_in_block(&while_stmt.body, captured, defined); + } + Statement::For(for_stmt) => { + self.collect_vars_in_expression(&for_stmt.iterable, captured, defined); + let val_name = self.interner.resolve(&for_stmt.value.symbol).to_string(); + defined.insert(val_name); + if let Some(ref idx) = for_stmt.index { + let idx_name = self.interner.resolve(&idx.symbol).to_string(); + defined.insert(idx_name); + } + self.collect_vars_in_block(&for_stmt.body, captured, defined); + } + Statement::Return(ret) => { + if let Some(ref value) = ret.value { + self.collect_vars_in_expression(value, captured, defined); + } + } + _ => {} + } + } + + fn collect_vars_in_expression( + &self, + expr: &Expression<'_>, + captured: &mut Vec, + defined: &std::collections::HashSet, + ) { + match expr { + Expression::Identifier(ident) => { + let name = self.interner.resolve(&ident.ident.symbol).to_string(); + if !defined.contains(&name) && !captured.contains(&name) { + captured.push(name); + } + } + Expression::Binary(bin) => { + self.collect_vars_in_expression(&bin.left, captured, defined); + self.collect_vars_in_expression(&bin.right, captured, defined); + } + Expression::Unary(un) => { + self.collect_vars_in_expression(&un.operand, captured, defined); + } + Expression::Call(call) => { + self.collect_vars_in_expression(&call.callee, captured, defined); + for arg in &call.args { + self.collect_vars_in_expression(arg, captured, defined); + } + } + Expression::MethodCall(method) => { + self.collect_vars_in_expression(&method.receiver, captured, defined); + for arg in &method.args { + self.collect_vars_in_expression(arg, captured, defined); + } + } + Expression::Index(idx) => { + self.collect_vars_in_expression(&idx.base, captured, defined); + self.collect_vars_in_expression(&idx.index, captured, defined); + } + Expression::Array(arr) => { + for elem in &arr.elements { + self.collect_vars_in_expression(elem, captured, defined); + } + } + Expression::Grouped(grouped) => { + self.collect_vars_in_expression(&grouped.inner, captured, defined); + } + _ => {} + } + } + + fn declare_spawn_trampoline(&mut self, _id: u32, info: &SpawnBlockInfo) -> Result { + // Spawn trampolines take a single pointer parameter (closure data) + let mut sig = self.module.make_signature(); + sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); // *mut u8 as i64 + + let func_id = self.module + .declare_function(&info.func_name, Linkage::Local, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare spawn trampoline '{}': {}", info.func_name, e)))?; + + self.functions.insert(info.func_name.clone(), func_id); + + Ok(func_id) + } + + fn compile_spawn_trampoline(&mut self, info: &SpawnBlockInfo) -> Result<(), CodegenError> { + let func_id = *self.functions.get(&info.func_name) + .ok_or_else(|| CodegenError::JitCompile(format!("Trampoline '{}' not declared", info.func_name)))?; + + self.ctx.func.signature = self.module.declarations().get_function_decl(func_id).signature.clone(); + self.ctx.func.name = cranelift_codegen::ir::UserFuncName::user(0, func_id.as_u32()); + + let mut builder_ctx = FunctionBuilderContext::new(); + let mut builder = FunctionBuilder::new(&mut self.ctx.func, &mut builder_ctx); + + let entry_block = builder.create_block(); + builder.append_block_params_for_function_params(entry_block); + builder.switch_to_block(entry_block); + builder.seal_block(entry_block); + + // Get the closure data pointer (first and only parameter) + let data_ptr = builder.block_params(entry_block)[0]; + + let mut ctx = CompileContext { + interner: self.interner, + module: &mut self.module, + functions: &self.functions, + struct_defs: &self.struct_defs, + enum_defs: &self.enum_defs, + extern_fns: &self.extern_fns, + variables: HashMap::new(), + var_heap_types: HashMap::new(), + var_counter: 0, + block_terminated: false, + loop_exit_block: None, + loop_header_block: None, + spawn_blocks: &self.spawn_blocks, + current_spawn_id: 0, // Not used in trampolines + annotations: self.annotations, + }; + + // Load captured variables from closure data + for (i, var_name) in info.captured_vars.iter().enumerate() { + let var = Variable::new(ctx.var_counter); + ctx.var_counter += 1; + builder.declare_var(var, cranelift::prelude::types::I64); + + // Load value from closure data: data_ptr + (i * 8) + let offset = builder.ins().iconst(cranelift::prelude::types::I64, (i * 8) as i64); + let addr = builder.ins().iadd(data_ptr, offset); + let val = builder.ins().load( + cranelift::prelude::types::I64, + MemFlags::new(), + addr, + 0, + ); + builder.def_var(var, val); + ctx.variables.insert(var_name.clone(), var); + } + + // Compile the spawn block body + // Safety: body_ptr is valid within the same compile() call + let body = unsafe { &*info.body_ptr }; + for stmt in &body.statements { + compile_statement(&mut ctx, &mut builder, stmt)?; + if ctx.block_terminated { + break; + } + } + + // Return (trampolines return void) + if !ctx.block_terminated { + builder.ins().return_(&[]); + } + + builder.finalize(); + + self.module + .define_function(func_id, &mut self.ctx) + .map_err(|e| CodegenError::JitCompile(format!("Failed to define trampoline '{}': {}", info.func_name, e)))?; + + self.module.clear_context(&mut self.ctx); + + Ok(()) + } + + fn declare_function(&mut self, func: &FunctionItem<'_>) -> Result { + let name = self.interner.resolve(&func.name.symbol); + + let mut sig = self.module.make_signature(); + + for param in &func.params { + let ty = types::naml_to_cranelift(¶m.ty); + sig.params.push(AbiParam::new(ty)); + } + + if let Some(ref return_ty) = func.return_ty { + let ty = types::naml_to_cranelift(return_ty); + sig.returns.push(AbiParam::new(ty)); + } + + let func_id = self.module + .declare_function(name, Linkage::Export, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare function '{}': {}", name, e)))?; + + self.functions.insert(name.to_string(), func_id); + + Ok(func_id) + } + + fn compile_function(&mut self, func: &FunctionItem<'_>) -> Result<(), CodegenError> { + let name = self.interner.resolve(&func.name.symbol); + let func_id = *self.functions.get(name) + .ok_or_else(|| CodegenError::JitCompile(format!("Function '{}' not declared", name)))?; + + self.ctx.func.signature = self.module.declarations().get_function_decl(func_id).signature.clone(); + self.ctx.func.name = cranelift_codegen::ir::UserFuncName::user(0, func_id.as_u32()); + + let mut builder_ctx = FunctionBuilderContext::new(); + let mut builder = FunctionBuilder::new(&mut self.ctx.func, &mut builder_ctx); + + let entry_block = builder.create_block(); + builder.append_block_params_for_function_params(entry_block); + builder.switch_to_block(entry_block); + builder.seal_block(entry_block); + + let mut ctx = CompileContext { + interner: self.interner, + module: &mut self.module, + functions: &self.functions, + struct_defs: &self.struct_defs, + enum_defs: &self.enum_defs, + extern_fns: &self.extern_fns, + variables: HashMap::new(), + var_heap_types: HashMap::new(), + var_counter: 0, + block_terminated: false, + loop_exit_block: None, + loop_header_block: None, + spawn_blocks: &self.spawn_blocks, + current_spawn_id: 0, + annotations: self.annotations, + }; + + for (i, param) in func.params.iter().enumerate() { + let param_name = self.interner.resolve(¶m.name.symbol).to_string(); + let val = builder.block_params(entry_block)[i]; + let var = Variable::new(ctx.var_counter); + ctx.var_counter += 1; + let ty = types::naml_to_cranelift(¶m.ty); + builder.declare_var(var, ty); + builder.def_var(var, val); + ctx.variables.insert(param_name, var); + } + + if let Some(ref body) = func.body { + for stmt in &body.statements { + compile_statement(&mut ctx, &mut builder, stmt)?; + if ctx.block_terminated { + break; + } + } + } + + if !ctx.block_terminated && func.return_ty.is_none() { + // Cleanup all heap variables before implicit void return + emit_cleanup_all_vars(&mut ctx, &mut builder, None)?; + builder.ins().return_(&[]); + } + + builder.finalize(); + + self.module + .define_function(func_id, &mut self.ctx) + .map_err(|e| CodegenError::JitCompile(format!("Failed to define function '{}': {}", name, e)))?; + + self.module.clear_context(&mut self.ctx); + + Ok(()) + } + + pub fn run_main(&mut self) -> Result<(), CodegenError> { + self.module.finalize_definitions() + .map_err(|e| CodegenError::JitCompile(format!("Failed to finalize: {}", e)))?; + + let main_id = self.functions.get("main") + .ok_or_else(|| CodegenError::Execution("No main function found".to_string()))?; + + let main_ptr = self.module.get_finalized_function(*main_id); + + let main_fn: fn() = unsafe { std::mem::transmute(main_ptr) }; + main_fn(); + + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum HeapType { + String, + Array(Option>), + Map(Option>), + Struct(Option), +} + +fn get_heap_type(naml_ty: &crate::ast::NamlType) -> Option { + use crate::ast::NamlType; + match naml_ty { + NamlType::String => Some(HeapType::String), + NamlType::Array(elem_ty) => { + let elem_heap_type = get_heap_type(elem_ty).map(Box::new); + Some(HeapType::Array(elem_heap_type)) + } + NamlType::FixedArray(elem_ty, _) => { + let elem_heap_type = get_heap_type(elem_ty).map(Box::new); + Some(HeapType::Array(elem_heap_type)) + } + NamlType::Map(_, val_ty) => { + let val_heap_type = get_heap_type(val_ty).map(Box::new); + Some(HeapType::Map(val_heap_type)) + } + NamlType::Named(_) => Some(HeapType::Struct(None)), + NamlType::Generic(_, _) => Some(HeapType::Struct(None)), + _ => None, + } +} + +#[allow(dead_code)] +fn get_heap_type_from_type(ty: &Type) -> Option { + match ty { + Type::String => Some(HeapType::String), + Type::Array(elem_ty) => { + let elem_heap_type = get_heap_type_from_type(elem_ty).map(Box::new); + Some(HeapType::Array(elem_heap_type)) + } + Type::FixedArray(elem_ty, _) => { + let elem_heap_type = get_heap_type_from_type(elem_ty).map(Box::new); + Some(HeapType::Array(elem_heap_type)) + } + Type::Map(_, val_ty) => { + let val_heap_type = get_heap_type_from_type(val_ty).map(Box::new); + Some(HeapType::Map(val_heap_type)) + } + Type::Struct(_) => Some(HeapType::Struct(None)), + Type::Generic(_, _) => Some(HeapType::Struct(None)), + _ => None, + } +} + +struct CompileContext<'a> { + interner: &'a Rodeo, + module: &'a mut JITModule, + functions: &'a HashMap, + struct_defs: &'a HashMap, + enum_defs: &'a HashMap, + extern_fns: &'a HashMap, + variables: HashMap, + var_heap_types: HashMap, + var_counter: usize, + block_terminated: bool, + loop_exit_block: Option, + loop_header_block: Option, + spawn_blocks: &'a HashMap, + current_spawn_id: u32, + annotations: &'a TypeAnnotations, +} + +fn compile_statement( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + stmt: &Statement<'_>, +) -> Result<(), CodegenError> { + match stmt { + Statement::Var(var_stmt) => { + let var_name = ctx.interner.resolve(&var_stmt.name.symbol).to_string(); + let ty = if let Some(ref naml_ty) = var_stmt.ty { + types::naml_to_cranelift(naml_ty) + } else { + cranelift::prelude::types::I64 + }; + + // Check if this is a string variable + let is_string_var = matches!(var_stmt.ty.as_ref(), Some(crate::ast::NamlType::String)); + + // Track heap type for cleanup + if let Some(ref naml_ty) = var_stmt.ty { + if let Some(heap_type) = get_heap_type(naml_ty) { + ctx.var_heap_types.insert(var_name.clone(), heap_type); + } + } + + let var = Variable::new(ctx.var_counter); + ctx.var_counter += 1; + builder.declare_var(var, ty); + + if let Some(ref init) = var_stmt.init { + let mut val = compile_expression(ctx, builder, init)?; + + // Box string literals as NamlString* for consistent memory management + if is_string_var { + if matches!(init, Expression::Literal(LiteralExpr { value: Literal::String(_), .. })) { + val = call_string_from_cstr(ctx, builder, val)?; + } + } + + builder.def_var(var, val); + // Incref the value since we're storing a reference + let heap_type_clone = ctx.var_heap_types.get(&var_name).cloned(); + if let Some(ref heap_type) = heap_type_clone { + emit_incref(ctx, builder, val, heap_type)?; + } + } else { + let zero = builder.ins().iconst(ty, 0); + builder.def_var(var, zero); + } + + ctx.variables.insert(var_name, var); + } + + Statement::Assign(assign) => { + match &assign.target { + Expression::Identifier(ident) => { + let var_name = ctx.interner.resolve(&ident.ident.symbol).to_string(); + + if let Some(&var) = ctx.variables.get(&var_name) { + // Clone heap type before mutable operations + let heap_type_clone = ctx.var_heap_types.get(&var_name).cloned(); + + // For heap variables: decref old value before assigning new one + if let Some(ref heap_type) = heap_type_clone { + let old_val = builder.use_var(var); + emit_decref(ctx, builder, old_val, heap_type)?; + } + + let mut val = compile_expression(ctx, builder, &assign.value)?; + + // Box string literals as NamlString* when assigning to string variables + if matches!(&heap_type_clone, Some(HeapType::String)) { + if matches!(&assign.value, Expression::Literal(LiteralExpr { value: Literal::String(_), .. })) { + val = call_string_from_cstr(ctx, builder, val)?; + } + } + + builder.def_var(var, val); + + // Incref the new value since we're storing a new reference + if let Some(ref heap_type) = heap_type_clone { + emit_incref(ctx, builder, val, heap_type)?; + } + } else { + return Err(CodegenError::JitCompile(format!("Undefined variable: {}", var_name))); + } + } + Expression::Index(index_expr) => { + let base = compile_expression(ctx, builder, &index_expr.base)?; + let value = compile_expression(ctx, builder, &assign.value)?; + + // Check if index is a string literal - if so, use map_set with NamlString conversion + if let Expression::Literal(LiteralExpr { value: Literal::String(_), .. }) = &*index_expr.index { + let cstr_ptr = compile_expression(ctx, builder, &index_expr.index)?; + let naml_str = call_string_from_cstr(ctx, builder, cstr_ptr)?; + call_map_set(ctx, builder, base, naml_str, value)?; + } else { + // Default to array set for integer indices + let index = compile_expression(ctx, builder, &index_expr.index)?; + call_array_set(ctx, builder, base, index, value)?; + } + } + _ => { + return Err(CodegenError::Unsupported( + format!("Assignment target not supported: {:?}", std::mem::discriminant(&assign.target)) + )); + } + } + } + + Statement::Return(ret) => { + if let Some(ref expr) = ret.value { + let val = compile_expression(ctx, builder, expr)?; + + // Determine if we're returning a local heap variable (ownership transfer) + let returned_var = get_returned_var_name(expr, ctx.interner); + let exclude_var = returned_var.as_ref().and_then(|name| { + if ctx.var_heap_types.contains_key(name) { + Some(name.as_str()) + } else { + None + } + }); + + // Cleanup all local heap variables except the returned one + emit_cleanup_all_vars(ctx, builder, exclude_var)?; + builder.ins().return_(&[val]); + } else { + // Void return - cleanup all heap variables + emit_cleanup_all_vars(ctx, builder, None)?; + builder.ins().return_(&[]); + } + ctx.block_terminated = true; + } + + Statement::Expression(expr_stmt) => { + compile_expression(ctx, builder, &expr_stmt.expr)?; + } + + Statement::If(if_stmt) => { + let condition = compile_expression(ctx, builder, &if_stmt.condition)?; + + let then_block = builder.create_block(); + let else_block = builder.create_block(); + let merge_block = builder.create_block(); + + builder.ins().brif(condition, then_block, &[], else_block, &[]); + + builder.switch_to_block(then_block); + builder.seal_block(then_block); + ctx.block_terminated = false; + for stmt in &if_stmt.then_branch.statements { + compile_statement(ctx, builder, stmt)?; + if ctx.block_terminated { + break; + } + } + if !ctx.block_terminated { + builder.ins().jump(merge_block, &[]); + } + + builder.switch_to_block(else_block); + builder.seal_block(else_block); + ctx.block_terminated = false; + if let Some(ref else_branch) = if_stmt.else_branch { + match else_branch { + crate::ast::ElseBranch::Else(else_block_stmt) => { + for stmt in &else_block_stmt.statements { + compile_statement(ctx, builder, stmt)?; + if ctx.block_terminated { + break; + } + } + } + crate::ast::ElseBranch::ElseIf(else_if) => { + let nested_if = Statement::If(crate::ast::IfStmt { + condition: else_if.condition.clone(), + then_branch: else_if.then_branch.clone(), + else_branch: else_if.else_branch.clone(), + span: else_if.span, + }); + compile_statement(ctx, builder, &nested_if)?; + } + } + } + if !ctx.block_terminated { + builder.ins().jump(merge_block, &[]); + } + + builder.switch_to_block(merge_block); + builder.seal_block(merge_block); + ctx.block_terminated = false; + } + + Statement::While(while_stmt) => { + let header_block = builder.create_block(); + let body_block = builder.create_block(); + let exit_block = builder.create_block(); + + builder.ins().jump(header_block, &[]); + + builder.switch_to_block(header_block); + let condition = compile_expression(ctx, builder, &while_stmt.condition)?; + builder.ins().brif(condition, body_block, &[], exit_block, &[]); + + builder.switch_to_block(body_block); + builder.seal_block(body_block); + ctx.block_terminated = false; + for stmt in &while_stmt.body.statements { + compile_statement(ctx, builder, stmt)?; + if ctx.block_terminated { + break; + } + } + if !ctx.block_terminated { + builder.ins().jump(header_block, &[]); + } + + builder.seal_block(header_block); + builder.switch_to_block(exit_block); + builder.seal_block(exit_block); + ctx.block_terminated = false; + } + + Statement::For(for_stmt) => { + // Compile the iterable (should be an array) + let arr_ptr = compile_expression(ctx, builder, &for_stmt.iterable)?; + + // Get array length + let len = call_array_len(ctx, builder, arr_ptr)?; + + // Create index variable + let idx_var = Variable::new(ctx.var_counter); + ctx.var_counter += 1; + builder.declare_var(idx_var, cranelift::prelude::types::I64); + let zero = builder.ins().iconst(cranelift::prelude::types::I64, 0); + builder.def_var(idx_var, zero); + + // Create value variable + let val_var = Variable::new(ctx.var_counter); + ctx.var_counter += 1; + builder.declare_var(val_var, cranelift::prelude::types::I64); + let val_name = ctx.interner.resolve(&for_stmt.value.symbol).to_string(); + ctx.variables.insert(val_name, val_var); + + // Optionally create index binding + if let Some(ref idx_ident) = for_stmt.index { + let idx_name = ctx.interner.resolve(&idx_ident.symbol).to_string(); + ctx.variables.insert(idx_name, idx_var); + } + + // Create blocks + let header_block = builder.create_block(); + let body_block = builder.create_block(); + let exit_block = builder.create_block(); + + // Store exit block for break statements + let prev_loop_exit = ctx.loop_exit_block.take(); + let prev_loop_header = ctx.loop_header_block.take(); + ctx.loop_exit_block = Some(exit_block); + ctx.loop_header_block = Some(header_block); + + builder.ins().jump(header_block, &[]); + + // Header: check if idx < len + builder.switch_to_block(header_block); + let idx_val = builder.use_var(idx_var); + let cond = builder.ins().icmp(IntCC::SignedLessThan, idx_val, len); + builder.ins().brif(cond, body_block, &[], exit_block, &[]); + + // Body + builder.switch_to_block(body_block); + builder.seal_block(body_block); + ctx.block_terminated = false; + + // Get current element + let idx_val = builder.use_var(idx_var); + let elem = call_array_get(ctx, builder, arr_ptr, idx_val)?; + builder.def_var(val_var, elem); + + // Compile body + for stmt in &for_stmt.body.statements { + compile_statement(ctx, builder, stmt)?; + if ctx.block_terminated { + break; + } + } + + // Increment index + if !ctx.block_terminated { + let idx_val = builder.use_var(idx_var); + let one = builder.ins().iconst(cranelift::prelude::types::I64, 1); + let next_idx = builder.ins().iadd(idx_val, one); + builder.def_var(idx_var, next_idx); + builder.ins().jump(header_block, &[]); + } + + builder.seal_block(header_block); + builder.switch_to_block(exit_block); + builder.seal_block(exit_block); + ctx.block_terminated = false; + + // Restore previous loop context + ctx.loop_exit_block = prev_loop_exit; + ctx.loop_header_block = prev_loop_header; + } + + Statement::Loop(loop_stmt) => { + let body_block = builder.create_block(); + let exit_block = builder.create_block(); + + let prev_loop_exit = ctx.loop_exit_block.take(); + let prev_loop_header = ctx.loop_header_block.take(); + ctx.loop_exit_block = Some(exit_block); + ctx.loop_header_block = Some(body_block); + + builder.ins().jump(body_block, &[]); + + builder.switch_to_block(body_block); + builder.seal_block(body_block); + ctx.block_terminated = false; + + for stmt in &loop_stmt.body.statements { + compile_statement(ctx, builder, stmt)?; + if ctx.block_terminated { + break; + } + } + + if !ctx.block_terminated { + builder.ins().jump(body_block, &[]); + } + + builder.switch_to_block(exit_block); + builder.seal_block(exit_block); + ctx.block_terminated = false; + + ctx.loop_exit_block = prev_loop_exit; + ctx.loop_header_block = prev_loop_header; + } + + Statement::Break(_) => { + if let Some(exit_block) = ctx.loop_exit_block { + builder.ins().jump(exit_block, &[]); + ctx.block_terminated = true; + } else { + return Err(CodegenError::JitCompile("break outside of loop".to_string())); + } + } + + Statement::Continue(_) => { + if let Some(header_block) = ctx.loop_header_block { + builder.ins().jump(header_block, &[]); + ctx.block_terminated = true; + } else { + return Err(CodegenError::JitCompile("continue outside of loop".to_string())); + } + } + + Statement::Switch(switch_stmt) => { + let scrutinee = compile_expression(ctx, builder, &switch_stmt.scrutinee)?; + let merge_block = builder.create_block(); + let default_block = builder.create_block(); + + // Create case blocks and check blocks + let mut case_blocks = Vec::new(); + let mut check_blocks = Vec::new(); + + for _ in &switch_stmt.cases { + case_blocks.push(builder.create_block()); + check_blocks.push(builder.create_block()); + } + + // Jump to first check (or default if no cases) + if !check_blocks.is_empty() { + builder.ins().jump(check_blocks[0], &[]); + } else { + builder.ins().jump(default_block, &[]); + } + + // Build the chain of checks using pattern matching + for (i, case) in switch_stmt.cases.iter().enumerate() { + builder.switch_to_block(check_blocks[i]); + builder.seal_block(check_blocks[i]); + + // Use compile_pattern_match instead of compile_expression + let cond = compile_pattern_match(ctx, builder, &case.pattern, scrutinee)?; + + let next_check = if i + 1 < switch_stmt.cases.len() { + check_blocks[i + 1] + } else { + default_block + }; + + builder.ins().brif(cond, case_blocks[i], &[], next_check, &[]); + } + + // Compile each case body with pattern variable bindings + for (i, case) in switch_stmt.cases.iter().enumerate() { + builder.switch_to_block(case_blocks[i]); + builder.seal_block(case_blocks[i]); + ctx.block_terminated = false; + + // Bind pattern variables before executing the case body + bind_pattern_vars(ctx, builder, &case.pattern, scrutinee)?; + + for stmt in &case.body.statements { + compile_statement(ctx, builder, stmt)?; + if ctx.block_terminated { + break; + } + } + + if !ctx.block_terminated { + builder.ins().jump(merge_block, &[]); + } + } + + // Compile default + builder.switch_to_block(default_block); + builder.seal_block(default_block); + ctx.block_terminated = false; + + if let Some(ref default_body) = switch_stmt.default { + for stmt in &default_body.statements { + compile_statement(ctx, builder, stmt)?; + if ctx.block_terminated { + break; + } + } + } + + if !ctx.block_terminated { + builder.ins().jump(merge_block, &[]); + } + + builder.switch_to_block(merge_block); + builder.seal_block(merge_block); + ctx.block_terminated = false; + } + + _ => { + return Err(CodegenError::Unsupported( + format!("Statement type not yet implemented: {:?}", std::mem::discriminant(stmt)) + )); + } + } + + Ok(()) +} + +fn compile_pattern_match( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + pattern: &crate::ast::Pattern<'_>, + scrutinee: Value, +) -> Result { + use crate::ast::Pattern; + + match pattern { + Pattern::Literal(lit) => { + let lit_val = compile_literal(ctx, builder, &lit.value)?; + Ok(builder.ins().icmp(IntCC::Equal, scrutinee, lit_val)) + } + + Pattern::Identifier(ident) => { + let name = ctx.interner.resolve(&ident.ident.symbol).to_string(); + for enum_def in ctx.enum_defs.values() { + if let Some(variant) = enum_def.variants.iter().find(|v| v.name == name) { + let tag = builder.ins().load(cranelift::prelude::types::I32, MemFlags::new(), scrutinee, 0); + let expected_tag = builder.ins().iconst(cranelift::prelude::types::I32, variant.tag as i64); + return Ok(builder.ins().icmp(IntCC::Equal, tag, expected_tag)); + } + } + Ok(builder.ins().iconst(cranelift::prelude::types::I8, 1)) + } + + Pattern::Variant(variant) => { + if variant.path.is_empty() { + return Err(CodegenError::JitCompile("Empty variant path".to_string())); + } + let (enum_name, variant_name) = if variant.path.len() == 1 { + let var_name = ctx.interner.resolve(&variant.path[0].symbol).to_string(); + let mut found = None; + for (e_name, enum_def) in ctx.enum_defs.iter() { + if enum_def.variants.iter().any(|v| v.name == var_name) { + found = Some((e_name.clone(), var_name.clone())); + break; + } + } + + match found { + Some(pair) => pair, + None => return Err(CodegenError::JitCompile(format!( + "Unknown variant: {}", + var_name + ))), + } + } else { + // Qualified path + let enum_name = ctx.interner.resolve(&variant.path[0].symbol).to_string(); + let variant_name = ctx.interner.resolve(&variant.path.last().unwrap().symbol).to_string(); + (enum_name, variant_name) + }; + + if let Some(enum_def) = ctx.enum_defs.get(&enum_name) { + if let Some(var_def) = enum_def.variants.iter().find(|v| v.name == variant_name) { + let tag = builder.ins().load(cranelift::prelude::types::I32, MemFlags::new(), scrutinee, 0); + let expected_tag = builder.ins().iconst(cranelift::prelude::types::I32, var_def.tag as i64); + return Ok(builder.ins().icmp(IntCC::Equal, tag, expected_tag)); + } + } + + Err(CodegenError::JitCompile(format!( + "Unknown enum variant: {}::{}", + enum_name, variant_name + ))) + } + + Pattern::Wildcard(_) => { + Ok(builder.ins().iconst(cranelift::prelude::types::I8, 1)) + } + + Pattern::_Phantom(_) => { + Ok(builder.ins().iconst(cranelift::prelude::types::I8, 0)) + } + } +} + +fn bind_pattern_vars( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + pattern: &crate::ast::Pattern<'_>, + scrutinee: Value, +) -> Result<(), CodegenError> { + use crate::ast::Pattern; + + match pattern { + Pattern::Variant(variant) if !variant.bindings.is_empty() => { + // Get the enum and variant info + let (enum_name, variant_name) = if variant.path.len() == 1 { + let var_name = ctx.interner.resolve(&variant.path[0].symbol).to_string(); + + // Search all enum definitions for this variant + let mut found = None; + for (e_name, enum_def) in ctx.enum_defs.iter() { + if enum_def.variants.iter().any(|v| v.name == var_name) { + found = Some((e_name.clone(), var_name.clone())); + break; + } + } + + match found { + Some(pair) => pair, + None => return Ok(()), // Variant not found, nothing to bind + } + } else { + let enum_name = ctx.interner.resolve(&variant.path[0].symbol).to_string(); + let variant_name = ctx.interner.resolve(&variant.path.last().unwrap().symbol).to_string(); + (enum_name, variant_name) + }; + + if let Some(enum_def) = ctx.enum_defs.get(&enum_name) { + if let Some(var_def) = enum_def.variants.iter().find(|v| v.name == variant_name) { + for (i, binding) in variant.bindings.iter().enumerate() { + let binding_name = ctx.interner.resolve(&binding.symbol).to_string(); + let offset = (var_def.data_offset + i * 8) as i32; + + let field_val = builder.ins().load( + cranelift::prelude::types::I64, + MemFlags::new(), + scrutinee, + offset, + ); + + let var = Variable::new(ctx.var_counter); + ctx.var_counter += 1; + builder.declare_var(var, cranelift::prelude::types::I64); + builder.def_var(var, field_val); + ctx.variables.insert(binding_name, var); + } + } + } + } + + Pattern::Identifier(ident) => { + // Check if it's not a variant name (binding patterns) + let name = ctx.interner.resolve(&ident.ident.symbol).to_string(); + + // Check if it's a variant name - don't bind in that case + let is_variant = ctx.enum_defs.values() + .any(|def| def.variants.iter().any(|v| v.name == name)); + + if !is_variant { + let var = Variable::new(ctx.var_counter); + ctx.var_counter += 1; + builder.declare_var(var, cranelift::prelude::types::I64); + builder.def_var(var, scrutinee); + ctx.variables.insert(name, var); + } + } + + _ => {} + } + + Ok(()) +} + +fn compile_expression( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + expr: &Expression<'_>, +) -> Result { + match expr { + Expression::Literal(lit_expr) => compile_literal(ctx, builder, &lit_expr.value), + + Expression::Identifier(ident) => { + let name = ctx.interner.resolve(&ident.ident.symbol).to_string(); + if let Some(&var) = ctx.variables.get(&name) { + Ok(builder.use_var(var)) + } else { + Err(CodegenError::JitCompile(format!("Undefined variable: {}", name))) + } + } + + Expression::Path(path_expr) => { + // Handle enum variant access: EnumType::Variant + if path_expr.segments.len() == 2 { + let enum_name = ctx.interner.resolve(&path_expr.segments[0].symbol).to_string(); + let variant_name = ctx.interner.resolve(&path_expr.segments[1].symbol).to_string(); + + if let Some(enum_def) = ctx.enum_defs.get(&enum_name) { + if let Some(variant) = enum_def.variants.iter().find(|v| v.name == variant_name) { + // Unit variant - allocate stack slot and set tag + let slot = builder.create_sized_stack_slot(StackSlotData::new( + StackSlotKind::ExplicitSlot, + enum_def.size as u32, + 0, + )); + let slot_addr = builder.ins().stack_addr(cranelift::prelude::types::I64, slot, 0); + + // Store tag at offset 0 + let tag_val = builder.ins().iconst(cranelift::prelude::types::I32, variant.tag as i64); + builder.ins().store(MemFlags::new(), tag_val, slot_addr, 0); + + // Return pointer to stack slot + return Ok(slot_addr); + } + } + } + + Err(CodegenError::Unsupported(format!( + "Path expression not supported: {:?}", + path_expr.segments.iter() + .map(|s| ctx.interner.resolve(&s.symbol)) + .collect::>() + ))) + } + + Expression::Binary(bin) => { + // Check if this is a string comparison (Eq/NotEq) + let lhs_type = ctx.annotations.get_type(bin.left.span()); + if matches!(lhs_type, Some(Type::String)) && matches!(bin.op, BinaryOp::Eq | BinaryOp::NotEq) { + let lhs = compile_expression(ctx, builder, &bin.left)?; + let rhs = compile_expression(ctx, builder, &bin.right)?; + // Convert lhs to NamlString if it's a string literal + let lhs_str = if matches!(&*bin.left, Expression::Literal(LiteralExpr { value: Literal::String(_), .. })) { + call_string_from_cstr(ctx, builder, lhs)? + } else { + lhs + }; + // Convert rhs to NamlString if it's a string literal + let rhs_str = if matches!(&*bin.right, Expression::Literal(LiteralExpr { value: Literal::String(_), .. })) { + call_string_from_cstr(ctx, builder, rhs)? + } else { + rhs + }; + let result = call_string_equals(ctx, builder, lhs_str, rhs_str)?; + if bin.op == BinaryOp::NotEq { + // Negate the result + let one = builder.ins().iconst(cranelift::prelude::types::I64, 1); + return Ok(builder.ins().bxor(result, one)); + } + return Ok(result); + } + let lhs = compile_expression(ctx, builder, &bin.left)?; + let rhs = compile_expression(ctx, builder, &bin.right)?; + compile_binary_op(builder, &bin.op, lhs, rhs) + } + + Expression::Unary(unary) => { + let operand = compile_expression(ctx, builder, &unary.operand)?; + compile_unary_op(builder, &unary.op, operand) + } + + Expression::Call(call) => { + if let Expression::Identifier(ident) = call.callee { + let func_name = ctx.interner.resolve(&ident.ident.symbol); + + match func_name { + "printf" | "print" | "println" => { + return compile_print_call(ctx, builder, &call.args, func_name == "println"); + } + "sleep" => { + if call.args.is_empty() { + return Err(CodegenError::JitCompile("sleep requires milliseconds argument".to_string())); + } + let ms = compile_expression(ctx, builder, &call.args[0])?; + return call_sleep(ctx, builder, ms); + } + "wait_all" => { + return call_wait_all(ctx, builder); + } + "make_channel" => { + let capacity = if call.args.is_empty() { + builder.ins().iconst(cranelift::prelude::types::I64, 1) + } else { + compile_expression(ctx, builder, &call.args[0])? + }; + return call_channel_new(ctx, builder, capacity); + } + _ => {} + } + + // Check for normal (naml) function + if let Some(&func_id) = ctx.functions.get(func_name) { + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + + let mut args = Vec::new(); + for arg in &call.args { + args.push(compile_expression(ctx, builder, arg)?); + } + + let call_inst = builder.ins().call(func_ref, &args); + let results = builder.inst_results(call_inst); + + if results.is_empty() { + Ok(builder.ins().iconst(cranelift::prelude::types::I64, 0)) + } else { + Ok(results[0]) + } + } + // Check for extern function + else if let Some(extern_fn) = ctx.extern_fns.get(func_name).cloned() { + compile_extern_call(ctx, builder, &extern_fn, &call.args) + } + else { + Err(CodegenError::JitCompile(format!("Unknown function: {}", func_name))) + } + } + // Check for enum variant constructor: EnumType::Variant(data) + else if let Expression::Path(path_expr) = call.callee { + if path_expr.segments.len() == 2 { + let enum_name = ctx.interner.resolve(&path_expr.segments[0].symbol).to_string(); + let variant_name = ctx.interner.resolve(&path_expr.segments[1].symbol).to_string(); + + if let Some(enum_def) = ctx.enum_defs.get(&enum_name) { + if let Some(variant) = enum_def.variants.iter().find(|v| v.name == variant_name) { + // Allocate stack slot for enum + let slot = builder.create_sized_stack_slot(StackSlotData::new( + StackSlotKind::ExplicitSlot, + enum_def.size as u32, + 0, + )); + let slot_addr = builder.ins().stack_addr(cranelift::prelude::types::I64, slot, 0); + + // Store tag + let tag_val = builder.ins().iconst(cranelift::prelude::types::I32, variant.tag as i64); + builder.ins().store(MemFlags::new(), tag_val, slot_addr, 0); + + // Store data fields + for (i, arg) in call.args.iter().enumerate() { + let mut arg_val = compile_expression(ctx, builder, arg)?; + // Check if argument is a string type - if so, convert C string to NamlString + if let Some(Type::String) = ctx.annotations.get_type(arg.span()) { + // For string literals, convert to NamlString + if matches!(arg, Expression::Literal(LiteralExpr { value: Literal::String(_), .. })) { + arg_val = call_string_from_cstr(ctx, builder, arg_val)?; + } + } + let offset = (variant.data_offset + i * 8) as i32; + builder.ins().store(MemFlags::new(), arg_val, slot_addr, offset); + } + + return Ok(slot_addr); + } + } + } + + Err(CodegenError::Unsupported(format!( + "Unknown enum variant call: {:?}", + path_expr.segments.iter() + .map(|s| ctx.interner.resolve(&s.symbol)) + .collect::>() + ))) + } + else { + Err(CodegenError::Unsupported("Indirect function calls not yet supported".to_string())) + } + } + + Expression::Grouped(grouped) => { + compile_expression(ctx, builder, &grouped.inner) + } + + Expression::Array(arr_expr) => { + compile_array_literal(ctx, builder, &arr_expr.elements) + } + + Expression::Map(map_expr) => { + compile_map_literal(ctx, builder, &map_expr.entries) + } + + Expression::Index(index_expr) => { + let base = compile_expression(ctx, builder, &index_expr.base)?; + + // Check if index is a string literal - if so, use map_get with NamlString conversion + if let Expression::Literal(LiteralExpr { value: Literal::String(_), .. }) = &*index_expr.index { + let cstr_ptr = compile_expression(ctx, builder, &index_expr.index)?; + let naml_str = call_string_from_cstr(ctx, builder, cstr_ptr)?; + call_map_get(ctx, builder, base, naml_str) + } else { + // Default to array access for integer indices + let index = compile_expression(ctx, builder, &index_expr.index)?; + call_array_get(ctx, builder, base, index) + } + } + + Expression::MethodCall(method_call) => { + let method_name = ctx.interner.resolve(&method_call.method.symbol); + compile_method_call(ctx, builder, &method_call.receiver, method_name, &method_call.args) + } + + Expression::StructLiteral(struct_lit) => { + let struct_name = ctx.interner.resolve(&struct_lit.name.symbol).to_string(); + + let struct_def = ctx.struct_defs.get(&struct_name) + .ok_or_else(|| CodegenError::JitCompile(format!("Unknown struct: {}", struct_name)))? + .clone(); + + let type_id = builder.ins().iconst(cranelift::prelude::types::I32, struct_def.type_id as i64); + let field_count = builder.ins().iconst(cranelift::prelude::types::I32, struct_def.fields.len() as i64); + + // Call naml_struct_new(type_id, field_count) + let struct_ptr = call_struct_new(ctx, builder, type_id, field_count)?; + + // Set each field value + for field in struct_lit.fields.iter() { + let field_name = ctx.interner.resolve(&field.name.symbol).to_string(); + // Find field index in struct definition + let field_idx = struct_def.fields.iter() + .position(|f| *f == field_name) + .ok_or_else(|| CodegenError::JitCompile(format!("Unknown field: {}", field_name)))?; + + let mut value = compile_expression(ctx, builder, &field.value)?; + // Convert string literals to NamlString + if let Some(Type::String) = ctx.annotations.get_type(field.value.span()) { + if matches!(&field.value, Expression::Literal(LiteralExpr { value: Literal::String(_), .. })) { + value = call_string_from_cstr(ctx, builder, value)?; + } + } + let idx_val = builder.ins().iconst(cranelift::prelude::types::I32, field_idx as i64); + call_struct_set_field(ctx, builder, struct_ptr, idx_val, value)?; + } + + Ok(struct_ptr) + } + + Expression::Field(field_expr) => { + let struct_ptr = compile_expression(ctx, builder, &field_expr.base)?; + let field_name = ctx.interner.resolve(&field_expr.field.symbol).to_string(); + + // Try to determine the struct type from the base expression + // For now, we'll need to look up the field in all structs + // In a real implementation, we'd use type information from the typechecker + for (_, struct_def) in ctx.struct_defs.iter() { + if let Some(field_idx) = struct_def.fields.iter().position(|f| *f == field_name) { + let idx_val = builder.ins().iconst(cranelift::prelude::types::I32, field_idx as i64); + return call_struct_get_field(ctx, builder, struct_ptr, idx_val); + } + } + + Err(CodegenError::JitCompile(format!("Unknown field: {}", field_name))) + } + + Expression::Spawn(_spawn_expr) => { + // True M:N spawn: schedule the spawn block on the thread pool + let spawn_id = ctx.current_spawn_id; + ctx.current_spawn_id += 1; + + let info = ctx.spawn_blocks.get(&spawn_id) + .ok_or_else(|| CodegenError::JitCompile(format!("Spawn block {} not found", spawn_id)))? + .clone(); + + let ptr_type = ctx.module.target_config().pointer_type(); + + // Calculate closure data size (8 bytes per captured variable) + let data_size = info.captured_vars.len() * 8; + let data_size_val = builder.ins().iconst(cranelift::prelude::types::I64, data_size as i64); + + // Allocate closure data + let data_ptr = if data_size > 0 { + call_alloc_closure_data(ctx, builder, data_size_val)? + } else { + builder.ins().iconst(ptr_type, 0) + }; + + // Store captured variables in closure data + for (i, var_name) in info.captured_vars.iter().enumerate() { + if let Some(&var) = ctx.variables.get(var_name) { + let val = builder.use_var(var); + let offset = builder.ins().iconst(ptr_type, (i * 8) as i64); + let addr = builder.ins().iadd(data_ptr, offset); + builder.ins().store(MemFlags::new(), val, addr, 0); + } + } + + // Get trampoline function address + let trampoline_id = *ctx.functions.get(&info.func_name) + .ok_or_else(|| CodegenError::JitCompile(format!("Trampoline '{}' not found", info.func_name)))?; + let trampoline_ref = ctx.module.declare_func_in_func(trampoline_id, builder.func); + let trampoline_addr = builder.ins().func_addr(ptr_type, trampoline_ref); + + // Call spawn_closure to schedule the task + call_spawn_closure(ctx, builder, trampoline_addr, data_ptr, data_size_val)?; + + // Return unit (0) as spawn expressions don't have a meaningful return value + Ok(builder.ins().iconst(cranelift::prelude::types::I64, 0)) + } + + Expression::Some(some_expr) => { + let inner_val = compile_expression(ctx, builder, &some_expr.value)?; + + // Allocate option on stack + let slot = builder.create_sized_stack_slot(StackSlotData::new( + StackSlotKind::ExplicitSlot, + 16, // option size + 0, + )); + let slot_addr = builder.ins().stack_addr(cranelift::prelude::types::I64, slot, 0); + + // Tag = 1 (some) + let tag = builder.ins().iconst(cranelift::prelude::types::I32, 1); + builder.ins().store(MemFlags::new(), tag, slot_addr, 0); + + // Store inner value at offset 8 + builder.ins().store(MemFlags::new(), inner_val, slot_addr, 8); + + Ok(slot_addr) + } + + _ => { + Err(CodegenError::Unsupported( + format!("Expression type not yet implemented: {:?}", std::mem::discriminant(expr)) + )) + } + } +} + +fn compile_literal( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + lit: &Literal, +) -> Result { + match lit { + Literal::Int(n) => { + Ok(builder.ins().iconst(cranelift::prelude::types::I64, *n)) + } + Literal::UInt(n) => { + Ok(builder.ins().iconst(cranelift::prelude::types::I64, *n as i64)) + } + Literal::Float(f) => { + Ok(builder.ins().f64const(*f)) + } + Literal::Bool(b) => { + let val = if *b { 1i64 } else { 0i64 }; + Ok(builder.ins().iconst(cranelift::prelude::types::I64, val)) + } + Literal::String(spur) => { + let s = ctx.interner.resolve(spur); + compile_string_literal(ctx, builder, s) + } + Literal::None => { + let slot = builder.create_sized_stack_slot(StackSlotData::new( + StackSlotKind::ExplicitSlot, + 16, + 0, + )); + let slot_addr = builder.ins().stack_addr(cranelift::prelude::types::I64, slot, 0); + + let tag = builder.ins().iconst(cranelift::prelude::types::I32, 0); + builder.ins().store(MemFlags::new(), tag, slot_addr, 0); + + Ok(slot_addr) + } + _ => { + Err(CodegenError::Unsupported( + format!("Literal type not yet implemented: {:?}", std::mem::discriminant(lit)) + )) + } + } +} + +fn compile_string_literal( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + s: &str, +) -> Result { + let mut bytes = s.as_bytes().to_vec(); + bytes.push(0); + + let data_id = ctx.module + .declare_anonymous_data(false, false) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare string data: {}", e)))?; + + let mut data_description = DataDescription::new(); + data_description.define(bytes.into_boxed_slice()); + + ctx.module + .define_data(data_id, &data_description) + .map_err(|e| CodegenError::JitCompile(format!("Failed to define string data: {}", e)))?; + + let global_value = ctx.module.declare_data_in_func(data_id, builder.func); + let ptr = builder.ins().global_value(ctx.module.target_config().pointer_type(), global_value); + + Ok(ptr) +} + +fn compile_binary_op( + builder: &mut FunctionBuilder<'_>, + op: &BinaryOp, + lhs: Value, + rhs: Value, +) -> Result { + let result = match op { + BinaryOp::Add => builder.ins().iadd(lhs, rhs), + BinaryOp::Sub => builder.ins().isub(lhs, rhs), + BinaryOp::Mul => builder.ins().imul(lhs, rhs), + BinaryOp::Div => builder.ins().sdiv(lhs, rhs), + BinaryOp::Mod => builder.ins().srem(lhs, rhs), + + BinaryOp::Eq => { + let cmp = builder.ins().icmp(IntCC::Equal, lhs, rhs); + builder.ins().uextend(cranelift::prelude::types::I64, cmp) + } + BinaryOp::NotEq => { + let cmp = builder.ins().icmp(IntCC::NotEqual, lhs, rhs); + builder.ins().uextend(cranelift::prelude::types::I64, cmp) + } + BinaryOp::Lt => { + let cmp = builder.ins().icmp(IntCC::SignedLessThan, lhs, rhs); + builder.ins().uextend(cranelift::prelude::types::I64, cmp) + } + BinaryOp::LtEq => { + let cmp = builder.ins().icmp(IntCC::SignedLessThanOrEqual, lhs, rhs); + builder.ins().uextend(cranelift::prelude::types::I64, cmp) + } + BinaryOp::Gt => { + let cmp = builder.ins().icmp(IntCC::SignedGreaterThan, lhs, rhs); + builder.ins().uextend(cranelift::prelude::types::I64, cmp) + } + BinaryOp::GtEq => { + let cmp = builder.ins().icmp(IntCC::SignedGreaterThanOrEqual, lhs, rhs); + builder.ins().uextend(cranelift::prelude::types::I64, cmp) + } + + BinaryOp::And => builder.ins().band(lhs, rhs), + BinaryOp::Or => builder.ins().bor(lhs, rhs), + + BinaryOp::BitAnd => builder.ins().band(lhs, rhs), + BinaryOp::BitOr => builder.ins().bor(lhs, rhs), + BinaryOp::BitXor => builder.ins().bxor(lhs, rhs), + BinaryOp::Shl => builder.ins().ishl(lhs, rhs), + BinaryOp::Shr => builder.ins().sshr(lhs, rhs), + + _ => { + return Err(CodegenError::Unsupported( + format!("Binary operator not yet implemented: {:?}", op) + )); + } + }; + + Ok(result) +} + +fn compile_unary_op( + builder: &mut FunctionBuilder<'_>, + op: &UnaryOp, + operand: Value, +) -> Result { + let result = match op { + UnaryOp::Neg => builder.ins().ineg(operand), + UnaryOp::Not => { + let one = builder.ins().iconst(cranelift::prelude::types::I64, 1); + builder.ins().bxor(operand, one) + } + UnaryOp::BitNot => builder.ins().bnot(operand), + }; + + Ok(result) +} + +fn compile_print_call( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + args: &[Expression<'_>], + newline: bool, +) -> Result { + if args.is_empty() { + if newline { + call_print_newline(ctx, builder)?; + } + return Ok(builder.ins().iconst(cranelift::prelude::types::I64, 0)); + } + + for (i, arg) in args.iter().enumerate() { + match arg { + Expression::Literal(LiteralExpr { value: Literal::String(spur), .. }) => { + let s = ctx.interner.resolve(spur); + let ptr = compile_string_literal(ctx, builder, s)?; + call_print_str(ctx, builder, ptr)?; + } + Expression::Literal(LiteralExpr { value: Literal::Int(n), .. }) => { + let val = builder.ins().iconst(cranelift::prelude::types::I64, *n); + call_print_int(ctx, builder, val)?; + } + Expression::Literal(LiteralExpr { value: Literal::Float(f), .. }) => { + let val = builder.ins().f64const(*f); + call_print_float(ctx, builder, val)?; + } + _ => { + let val = compile_expression(ctx, builder, arg)?; + // Check type from annotations to call appropriate print function + let expr_type = ctx.annotations.get_type(arg.span()); + match expr_type { + Some(Type::String) => { + // String variables now hold NamlString* (boxed strings) + call_print_naml_string(ctx, builder, val)?; + } + Some(Type::Float) => { + call_print_float(ctx, builder, val)?; + } + _ => { + // Default: check Cranelift value type for F64, otherwise int + let val_type = builder.func.dfg.value_type(val); + if val_type == cranelift::prelude::types::F64 { + call_print_float(ctx, builder, val)?; + } else { + call_print_int(ctx, builder, val)?; + } + } + } + } + } + + if i < args.len() - 1 { + let space = compile_string_literal(ctx, builder, " ")?; + call_print_str(ctx, builder, space)?; + } + } + + if newline { + call_print_newline(ctx, builder)?; + } + + Ok(builder.ins().iconst(cranelift::prelude::types::I64, 0)) +} + +fn emit_incref( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + val: Value, + heap_type: &HeapType, +) -> Result<(), CodegenError> { + let ptr_type = ctx.module.target_config().pointer_type(); + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + + let func_name = match heap_type { + HeapType::String => "naml_string_incref", + HeapType::Array(_) => "naml_array_incref", + HeapType::Map(_) => "naml_map_incref", + HeapType::Struct(_) => "naml_struct_incref", + }; + + let func_id = ctx.module + .declare_function(func_name, Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare {}: {}", func_name, e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let zero = builder.ins().iconst(ptr_type, 0); + let is_null = builder.ins().icmp(IntCC::Equal, val, zero); + + let call_block = builder.create_block(); + let merge_block = builder.create_block(); + + builder.ins().brif(is_null, merge_block, &[], call_block, &[]); + + builder.switch_to_block(call_block); + builder.seal_block(call_block); + builder.ins().call(func_ref, &[val]); + builder.ins().jump(merge_block, &[]); + + builder.switch_to_block(merge_block); + builder.seal_block(merge_block); + + Ok(()) +} + +fn struct_has_heap_fields(struct_defs: &HashMap, struct_name: &str) -> bool { + if let Some(def) = struct_defs.get(struct_name) { + def.field_heap_types.iter().any(|ht| ht.is_some()) + } else { + false + } +} + +fn emit_decref( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + val: Value, + heap_type: &HeapType, +) -> Result<(), CodegenError> { + let ptr_type = ctx.module.target_config().pointer_type(); + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + + // Select the appropriate decref function based on element type for nested cleanup + let func_name: String = match heap_type { + HeapType::String => "naml_string_decref".to_string(), + HeapType::Array(None) => "naml_array_decref".to_string(), + HeapType::Array(Some(elem_type)) => { + // Use specialized decref that also decrefs elements + match elem_type.as_ref() { + HeapType::String => "naml_array_decref_strings".to_string(), + HeapType::Array(_) => "naml_array_decref_arrays".to_string(), + HeapType::Map(_) => "naml_array_decref_maps".to_string(), + HeapType::Struct(_) => "naml_array_decref_structs".to_string(), + } + } + HeapType::Map(None) => "naml_map_decref".to_string(), + HeapType::Map(Some(val_type)) => { + // Use specialized decref that also decrefs values + match val_type.as_ref() { + HeapType::String => "naml_map_decref_strings".to_string(), + HeapType::Array(_) => "naml_map_decref_arrays".to_string(), + HeapType::Map(_) => "naml_map_decref_maps".to_string(), + HeapType::Struct(_) => "naml_map_decref_structs".to_string(), + } + } + HeapType::Struct(None) => "naml_struct_decref".to_string(), + HeapType::Struct(Some(struct_name)) => { + // Check if this struct has heap fields that need cleanup + if struct_has_heap_fields(ctx.struct_defs, struct_name) { + format!("naml_struct_decref_{}", struct_name) + } else { + "naml_struct_decref".to_string() + } + } + }; + + let func_id = ctx.module + .declare_function(&func_name, Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare {}: {}", func_name, e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let zero = builder.ins().iconst(ptr_type, 0); + let is_null = builder.ins().icmp(IntCC::Equal, val, zero); + + let call_block = builder.create_block(); + let merge_block = builder.create_block(); + + builder.ins().brif(is_null, merge_block, &[], call_block, &[]); + + builder.switch_to_block(call_block); + builder.seal_block(call_block); + builder.ins().call(func_ref, &[val]); + builder.ins().jump(merge_block, &[]); + + builder.switch_to_block(merge_block); + builder.seal_block(merge_block); + + Ok(()) +} + +fn emit_cleanup_all_vars( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + exclude_var: Option<&str>, +) -> Result<(), CodegenError> { + let vars_to_cleanup: Vec<(String, Variable, HeapType)> = ctx.var_heap_types + .iter() + .filter_map(|(name, heap_type)| { + if let Some(excl) = exclude_var { + if name == excl { + return None; + } + } + ctx.variables.get(name).map(|var| (name.clone(), *var, heap_type.clone())) + }) + .collect(); + + for (_, var, ref heap_type) in vars_to_cleanup { + let val = builder.use_var(var); + emit_decref(ctx, builder, val, heap_type)?; + } + + Ok(()) +} + +fn get_returned_var_name(expr: &Expression, interner: &Rodeo) -> Option { + match expr { + Expression::Identifier(ident) => { + Some(interner.resolve(&ident.ident.symbol).to_string()) + } + _ => None, + } +} + +fn call_print_int( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + val: Value, +) -> Result<(), CodegenError> { + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); + + let func_id = ctx.module + .declare_function("naml_print_int", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_print_int: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + builder.ins().call(func_ref, &[val]); + Ok(()) +} + +fn call_print_float( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + val: Value, +) -> Result<(), CodegenError> { + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(cranelift::prelude::types::F64)); + + let func_id = ctx.module + .declare_function("naml_print_float", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_print_float: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + builder.ins().call(func_ref, &[val]); + Ok(()) +} + +fn call_print_str( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + ptr: Value, +) -> Result<(), CodegenError> { + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ctx.module.target_config().pointer_type())); + + let func_id = ctx.module + .declare_function("naml_print_str", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_print_str: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + builder.ins().call(func_ref, &[ptr]); + Ok(()) +} + +fn call_print_naml_string( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + ptr: Value, +) -> Result<(), CodegenError> { + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ctx.module.target_config().pointer_type())); + + let func_id = ctx.module + .declare_function("naml_string_print", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_string_print: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + builder.ins().call(func_ref, &[ptr]); + Ok(()) +} + +fn call_string_equals( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + a: Value, + b: Value, +) -> Result { + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ctx.module.target_config().pointer_type())); + sig.params.push(AbiParam::new(ctx.module.target_config().pointer_type())); + sig.returns.push(AbiParam::new(cranelift::prelude::types::I64)); + + let func_id = ctx.module + .declare_function("naml_string_eq", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_string_eq: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[a, b]); + Ok(builder.inst_results(call)[0]) +} + +fn call_print_newline( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, +) -> Result<(), CodegenError> { + let sig = ctx.module.make_signature(); + + let func_id = ctx.module + .declare_function("naml_print_newline", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_print_newline: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + builder.ins().call(func_ref, &[]); + Ok(()) +} + +fn compile_array_literal( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + elements: &[Expression<'_>], +) -> Result { + // First, compile all elements and store on stack + let mut element_values = Vec::new(); + for elem in elements { + element_values.push(compile_expression(ctx, builder, elem)?); + } + + // Create array with capacity + let capacity = builder.ins().iconst(cranelift::prelude::types::I64, elements.len() as i64); + let arr_ptr = call_array_new(ctx, builder, capacity)?; + + // Push each element + for val in element_values { + call_array_push(ctx, builder, arr_ptr, val)?; + } + + Ok(arr_ptr) +} + +fn call_array_new( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + capacity: Value, +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); + sig.returns.push(AbiParam::new(ptr_type)); + + let func_id = ctx.module + .declare_function("naml_array_new", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_array_new: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[capacity]); + Ok(builder.inst_results(call)[0]) +} + +fn call_array_push( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + arr: Value, + value: Value, +) -> Result<(), CodegenError> { + let ptr_type = ctx.module.target_config().pointer_type(); + + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); + + let func_id = ctx.module + .declare_function("naml_array_push", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_array_push: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + builder.ins().call(func_ref, &[arr, value]); + Ok(()) +} + +fn call_array_get( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + arr: Value, + index: Value, +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); + sig.returns.push(AbiParam::new(cranelift::prelude::types::I64)); + + let func_id = ctx.module + .declare_function("naml_array_get", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_array_get: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[arr, index]); + Ok(builder.inst_results(call)[0]) +} + +fn call_array_len( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + arr: Value, +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + sig.returns.push(AbiParam::new(cranelift::prelude::types::I64)); + + let func_id = ctx.module + .declare_function("naml_array_len", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_array_len: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[arr]); + Ok(builder.inst_results(call)[0]) +} + +#[allow(dead_code)] +fn call_array_print( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + arr: Value, +) -> Result<(), CodegenError> { + let ptr_type = ctx.module.target_config().pointer_type(); + + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + + let func_id = ctx.module + .declare_function("naml_array_print", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_array_print: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + builder.ins().call(func_ref, &[arr]); + Ok(()) +} + +fn call_array_set( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + arr: Value, + index: Value, + value: Value, +) -> Result<(), CodegenError> { + let ptr_type = ctx.module.target_config().pointer_type(); + + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); + sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); + + let func_id = ctx.module + .declare_function("naml_array_set", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_array_set: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + builder.ins().call(func_ref, &[arr, index, value]); + Ok(()) +} + +fn compile_map_literal( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + entries: &[crate::ast::MapEntry<'_>], +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + + // Create naml_map_new signature + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); // capacity + sig.returns.push(AbiParam::new(ptr_type)); // map ptr + + let func_id = ctx.module + .declare_function("naml_map_new", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(e.to_string()))?; + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + + // Create map with capacity 16 + let capacity = builder.ins().iconst(cranelift::prelude::types::I64, 16); + let call = builder.ins().call(func_ref, &[capacity]); + let map_ptr = builder.inst_results(call)[0]; + + // For each entry, call naml_map_set + if !entries.is_empty() { + let mut set_sig = ctx.module.make_signature(); + set_sig.params.push(AbiParam::new(ptr_type)); // map + set_sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); // key + set_sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); // value + + let set_func_id = ctx.module + .declare_function("naml_map_set", Linkage::Import, &set_sig) + .map_err(|e| CodegenError::JitCompile(e.to_string()))?; + let set_func_ref = ctx.module.declare_func_in_func(set_func_id, builder.func); + + for entry in entries { + // Convert string literals to NamlString pointers for map keys + let key = if let Expression::Literal(LiteralExpr { value: Literal::String(_), .. }) = &entry.key { + let cstr_ptr = compile_expression(ctx, builder, &entry.key)?; + call_string_from_cstr(ctx, builder, cstr_ptr)? + } else { + compile_expression(ctx, builder, &entry.key)? + }; + let value = compile_expression(ctx, builder, &entry.value)?; + builder.ins().call(set_func_ref, &[map_ptr, key, value]); + } + } + + Ok(map_ptr) +} + +fn call_string_from_cstr( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + cstr_ptr: Value, +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); // cstr: *const i8 + sig.returns.push(AbiParam::new(ptr_type)); // *mut NamlString + + let func_id = ctx.module + .declare_function("naml_string_from_cstr", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_string_from_cstr: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[cstr_ptr]); + Ok(builder.inst_results(call)[0]) +} + +#[allow(dead_code)] +fn call_map_new( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + capacity: Value, +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); + sig.returns.push(AbiParam::new(ptr_type)); + + let func_id = ctx.module + .declare_function("naml_map_new", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_map_new: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[capacity]); + Ok(builder.inst_results(call)[0]) +} + +fn call_map_set( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + map: Value, + key: Value, + value: Value, +) -> Result<(), CodegenError> { + call_map_set_typed(ctx, builder, map, key, value, None) +} + +fn call_map_set_typed( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + map: Value, + key: Value, + value: Value, + value_type: Option<&HeapType>, +) -> Result<(), CodegenError> { + let ptr_type = ctx.module.target_config().pointer_type(); + + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); + sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); + + // Select the appropriate set function based on value type + // This ensures proper decref of old values when updating map entries + let func_name = match value_type { + Some(HeapType::String) => "naml_map_set_string", + Some(HeapType::Array(_)) => "naml_map_set_array", + Some(HeapType::Map(_)) => "naml_map_set_map", + Some(HeapType::Struct(_)) => "naml_map_set_struct", + None => "naml_map_set", + }; + + let func_id = ctx.module + .declare_function(func_name, Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare {}: {}", func_name, e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + builder.ins().call(func_ref, &[map, key, value]); + Ok(()) +} + +fn call_map_get( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + map: Value, + key: Value, +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); + sig.returns.push(AbiParam::new(cranelift::prelude::types::I64)); + + let func_id = ctx.module + .declare_function("naml_map_get", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_map_get: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[map, key]); + Ok(builder.inst_results(call)[0]) +} + +fn call_map_contains( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + map: Value, + key: Value, +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); + sig.returns.push(AbiParam::new(cranelift::prelude::types::I64)); + + let func_id = ctx.module + .declare_function("naml_map_contains", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_map_contains: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[map, key]); + Ok(builder.inst_results(call)[0]) +} + +#[allow(dead_code)] +fn call_map_len( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + map: Value, +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + sig.returns.push(AbiParam::new(cranelift::prelude::types::I64)); + + let func_id = ctx.module + .declare_function("naml_map_len", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_map_len: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[map]); + Ok(builder.inst_results(call)[0]) +} + +fn call_struct_new( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + type_id: Value, + field_count: Value, +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(cranelift::prelude::types::I32)); + sig.params.push(AbiParam::new(cranelift::prelude::types::I32)); + sig.returns.push(AbiParam::new(ptr_type)); + + let func_id = ctx.module + .declare_function("naml_struct_new", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_struct_new: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[type_id, field_count]); + Ok(builder.inst_results(call)[0]) +} + +fn call_struct_get_field( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + struct_ptr: Value, + field_index: Value, +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + sig.params.push(AbiParam::new(cranelift::prelude::types::I32)); + sig.returns.push(AbiParam::new(cranelift::prelude::types::I64)); + + let func_id = ctx.module + .declare_function("naml_struct_get_field", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_struct_get_field: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[struct_ptr, field_index]); + Ok(builder.inst_results(call)[0]) +} + +fn call_struct_set_field( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + struct_ptr: Value, + field_index: Value, + value: Value, +) -> Result<(), CodegenError> { + let ptr_type = ctx.module.target_config().pointer_type(); + + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + sig.params.push(AbiParam::new(cranelift::prelude::types::I32)); + sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); + + let func_id = ctx.module + .declare_function("naml_struct_set_field", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_struct_set_field: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + builder.ins().call(func_ref, &[struct_ptr, field_index, value]); + Ok(()) +} + +fn compile_extern_call( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + extern_fn: &ExternFn, + args: &[Expression<'_>], +) -> Result { + // Build the signature + let mut sig = ctx.module.make_signature(); + + for param_ty in &extern_fn.param_types { + let cl_type = types::naml_to_cranelift(param_ty); + sig.params.push(AbiParam::new(cl_type)); + } + + if let Some(ref ret_ty) = extern_fn.return_type { + let cl_type = types::naml_to_cranelift(ret_ty); + sig.returns.push(AbiParam::new(cl_type)); + } + + // Declare the external function + let func_id = ctx.module + .declare_function(&extern_fn.link_name, Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare extern fn {}: {}", extern_fn.link_name, e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + + // Compile arguments + let mut compiled_args = Vec::new(); + for arg in args { + compiled_args.push(compile_expression(ctx, builder, arg)?); + } + + // Make the call + let call_inst = builder.ins().call(func_ref, &compiled_args); + let results = builder.inst_results(call_inst); + + if results.is_empty() { + Ok(builder.ins().iconst(cranelift::prelude::types::I64, 0)) + } else { + Ok(results[0]) + } +} + +fn compile_method_call( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + receiver: &Expression<'_>, + method_name: &str, + args: &[Expression<'_>], +) -> Result { + // Handle option methods first (before compiling receiver for or_default) + match method_name { + "is_some" => { + let opt_ptr = compile_expression(ctx, builder, receiver)?; + let tag = builder.ins().load(cranelift::prelude::types::I32, MemFlags::new(), opt_ptr, 0); + let one = builder.ins().iconst(cranelift::prelude::types::I32, 1); + let result = builder.ins().icmp(IntCC::Equal, tag, one); + return Ok(builder.ins().uextend(cranelift::prelude::types::I64, result)); + } + "is_none" => { + let opt_ptr = compile_expression(ctx, builder, receiver)?; + let tag = builder.ins().load(cranelift::prelude::types::I32, MemFlags::new(), opt_ptr, 0); + let zero = builder.ins().iconst(cranelift::prelude::types::I32, 0); + let result = builder.ins().icmp(IntCC::Equal, tag, zero); + return Ok(builder.ins().uextend(cranelift::prelude::types::I64, result)); + } + "or_default" => { + let opt_ptr = compile_expression(ctx, builder, receiver)?; + if args.is_empty() { + return Err(CodegenError::JitCompile("or_default requires a default value argument".to_string())); + } + let default_val = compile_expression(ctx, builder, &args[0])?; + + let tag = builder.ins().load(cranelift::prelude::types::I32, MemFlags::new(), opt_ptr, 0); + let is_some = builder.ins().icmp_imm(IntCC::Equal, tag, 1); + + let some_val = builder.ins().load(cranelift::prelude::types::I64, MemFlags::new(), opt_ptr, 8); + + return Ok(builder.ins().select(is_some, some_val, default_val)); + } + _ => {} + } + + let recv = compile_expression(ctx, builder, receiver)?; + + match method_name { + "len" => { + call_array_len(ctx, builder, recv) + } + "push" => { + if args.is_empty() { + return Err(CodegenError::JitCompile("push requires an argument".to_string())); + } + let val = compile_expression(ctx, builder, &args[0])?; + call_array_push(ctx, builder, recv, val)?; + Ok(builder.ins().iconst(cranelift::prelude::types::I64, 0)) + } + "pop" => { + let ptr_type = ctx.module.target_config().pointer_type(); + + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + sig.returns.push(AbiParam::new(cranelift::prelude::types::I64)); + + let func_id = ctx.module + .declare_function("naml_array_pop", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_array_pop: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[recv]); + Ok(builder.inst_results(call)[0]) + } + "get" => { + if args.is_empty() { + return Err(CodegenError::JitCompile("get requires an index argument".to_string())); + } + let idx = compile_expression(ctx, builder, &args[0])?; + call_array_get(ctx, builder, recv, idx) + } + // Channel methods + "send" => { + if args.is_empty() { + return Err(CodegenError::JitCompile("send requires a value argument".to_string())); + } + let val = compile_expression(ctx, builder, &args[0])?; + call_channel_send(ctx, builder, recv, val) + } + "receive" => { + call_channel_receive(ctx, builder, recv) + } + "close" => { + call_channel_close(ctx, builder, recv)?; + Ok(builder.ins().iconst(cranelift::prelude::types::I64, 0)) + } + // Map methods + "contains" => { + if args.is_empty() { + return Err(CodegenError::JitCompile("contains requires a key argument".to_string())); + } + let key = compile_expression(ctx, builder, &args[0])?; + call_map_contains(ctx, builder, recv, key) + } + "set" => { + if args.len() < 2 { + return Err(CodegenError::JitCompile("set requires key and value arguments".to_string())); + } + let key = compile_expression(ctx, builder, &args[0])?; + let value = compile_expression(ctx, builder, &args[1])?; + call_map_set(ctx, builder, recv, key, value)?; + Ok(builder.ins().iconst(cranelift::prelude::types::I64, 0)) + } + _ => { + Err(CodegenError::Unsupported(format!("Unknown method: {}", method_name))) + } + } +} + +// Scheduler helper functions +fn call_sleep( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + ms: Value, +) -> Result { + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); + + let func_id = ctx.module + .declare_function("naml_sleep", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_sleep: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + builder.ins().call(func_ref, &[ms]); + Ok(builder.ins().iconst(cranelift::prelude::types::I64, 0)) +} + +fn call_wait_all( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, +) -> Result { + let sig = ctx.module.make_signature(); + + let func_id = ctx.module + .declare_function("naml_wait_all", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_wait_all: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + builder.ins().call(func_ref, &[]); + Ok(builder.ins().iconst(cranelift::prelude::types::I64, 0)) +} + +// Channel helper functions +fn call_channel_new( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + capacity: Value, +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); + sig.returns.push(AbiParam::new(ptr_type)); + + let func_id = ctx.module + .declare_function("naml_channel_new", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_channel_new: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[capacity]); + Ok(builder.inst_results(call)[0]) +} + +fn call_channel_send( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + ch: Value, + value: Value, +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); + sig.returns.push(AbiParam::new(cranelift::prelude::types::I64)); + + let func_id = ctx.module + .declare_function("naml_channel_send", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_channel_send: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[ch, value]); + Ok(builder.inst_results(call)[0]) +} + +fn call_channel_receive( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + ch: Value, +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + sig.returns.push(AbiParam::new(cranelift::prelude::types::I64)); + + let func_id = ctx.module + .declare_function("naml_channel_receive", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_channel_receive: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[ch]); + Ok(builder.inst_results(call)[0]) +} + +fn call_channel_close( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + ch: Value, +) -> Result<(), CodegenError> { + let ptr_type = ctx.module.target_config().pointer_type(); + + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); + + let func_id = ctx.module + .declare_function("naml_channel_close", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_channel_close: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + builder.ins().call(func_ref, &[ch]); + Ok(()) +} + +fn call_alloc_closure_data( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + size: Value, +) -> Result { + let ptr_type = ctx.module.target_config().pointer_type(); + + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); // size: usize + sig.returns.push(AbiParam::new(ptr_type)); // *mut u8 + + let func_id = ctx.module + .declare_function("naml_alloc_closure_data", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_alloc_closure_data: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + let call = builder.ins().call(func_ref, &[size]); + Ok(builder.inst_results(call)[0]) +} + +fn call_spawn_closure( + ctx: &mut CompileContext<'_>, + builder: &mut FunctionBuilder<'_>, + func_addr: Value, + data: Value, + data_size: Value, +) -> Result<(), CodegenError> { + let ptr_type = ctx.module.target_config().pointer_type(); + + let mut sig = ctx.module.make_signature(); + sig.params.push(AbiParam::new(ptr_type)); // func: extern "C" fn(*mut u8) + sig.params.push(AbiParam::new(ptr_type)); // data: *mut u8 + sig.params.push(AbiParam::new(cranelift::prelude::types::I64)); // data_size: usize + + let func_id = ctx.module + .declare_function("naml_spawn_closure", Linkage::Import, &sig) + .map_err(|e| CodegenError::JitCompile(format!("Failed to declare naml_spawn_closure: {}", e)))?; + + let func_ref = ctx.module.declare_func_in_func(func_id, builder.func); + builder.ins().call(func_ref, &[func_addr, data, data_size]); + Ok(()) +} + +extern "C" fn naml_print_int(val: i64) { + print!("{}", val); +} + +extern "C" fn naml_print_float(val: f64) { + print!("{}", val); +} + +extern "C" fn naml_print_str(ptr: *const i8) { + if !ptr.is_null() { + let c_str = unsafe { std::ffi::CStr::from_ptr(ptr) }; + if let Ok(s) = c_str.to_str() { + print!("{}", s); + } + } +} + +extern "C" fn naml_print_newline() { + println!(); +} + +#[cfg(test)] +mod tests { + #[test] + fn test_module_exists() { + assert!(true); + } +} diff --git a/namlc/src/codegen/cranelift/types.rs b/namlc/src/codegen/cranelift/types.rs new file mode 100644 index 0000000..e5da968 --- /dev/null +++ b/namlc/src/codegen/cranelift/types.rs @@ -0,0 +1,52 @@ +//! +//! Type Mappings (naml -> Cranelift) +//! +//! Maps naml types to Cranelift IR types: +//! - int -> I64 +//! - uint -> I64 +//! - float -> F64 +//! - bool -> I64 (0 or 1) +//! - string -> I64 (pointer) +//! + +use cranelift::prelude::types; +use cranelift::prelude::Type; + +use crate::ast::NamlType; + +pub fn naml_to_cranelift(ty: &NamlType) -> Type { + match ty { + NamlType::Int => types::I64, + NamlType::Uint => types::I64, + NamlType::Float => types::F64, + NamlType::Bool => types::I64, + NamlType::String => types::I64, + NamlType::Bytes => types::I64, + NamlType::Unit => types::I64, + + NamlType::Array(_) => types::I64, + NamlType::FixedArray(_, _) => types::I64, + NamlType::Option(_) => types::I64, + NamlType::Map(_, _) => types::I64, + NamlType::Channel(_) => types::I64, + NamlType::Promise(_) => types::I64, + + NamlType::Named(_) => types::I64, + NamlType::Generic(_, _) => types::I64, + NamlType::Function { .. } => types::I64, + NamlType::Decimal { .. } => types::I64, + NamlType::Inferred => types::I64, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_primitive_types() { + assert_eq!(naml_to_cranelift(&NamlType::Int), types::I64); + assert_eq!(naml_to_cranelift(&NamlType::Float), types::F64); + assert_eq!(naml_to_cranelift(&NamlType::Bool), types::I64); + } +} diff --git a/namlc/src/codegen/mod.rs b/namlc/src/codegen/mod.rs index af8d567..9cfda53 100644 --- a/namlc/src/codegen/mod.rs +++ b/namlc/src/codegen/mod.rs @@ -1,22 +1,16 @@ -/// -/// Code Generation Module -/// -/// This module handles transpilation of naml AST to Rust source code. -/// The generated Rust code is then compiled with cargo and executed. -/// -/// Pipeline: -/// 1. Generate Rust source from AST -/// 2. Write to .naml_build/src/main.rs -/// 3. Generate Cargo.toml -/// 4. Run cargo build --release -/// 5. Execute the resulting binary -/// - -pub mod rust; - -use std::fs; -use std::path::PathBuf; -use std::process::Command; +//! +//! Code Generation Module +//! +//! This module handles JIT compilation of naml AST using Cranelift. +//! The generated machine code is executed directly without transpilation. +//! +//! Pipeline: +//! 1. Convert AST to Cranelift IR +//! 2. JIT compile to native machine code +//! 3. Execute directly +//! + +pub mod cranelift; use lasso::Rodeo; use thiserror::Error; @@ -26,40 +20,17 @@ use crate::typechecker::{SymbolTable, TypeAnnotations}; #[derive(Debug, Error)] pub enum CodegenError { - #[error("IO error: {0}")] - Io(#[from] std::io::Error), - - #[error("Cargo build failed: {0}")] - CargoBuild(String), + #[error("JIT compilation failed: {0}")] + JitCompile(String), #[error("Execution failed: {0}")] Execution(String), #[error("Unsupported feature: {0}")] Unsupported(String), -} - -pub struct BuildConfig { - pub output_dir: PathBuf, - pub release: bool, - pub target: Target, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Target { - Native, - Server, - Browser, -} -impl Default for BuildConfig { - fn default() -> Self { - Self { - output_dir: PathBuf::from(".naml_build"), - release: true, - target: Target::Native, - } - } + #[error("Type error: {0}")] + TypeError(String), } pub fn compile_and_run( @@ -68,138 +39,15 @@ pub fn compile_and_run( annotations: &TypeAnnotations, symbols: &SymbolTable, ) -> Result<(), CodegenError> { - let config = BuildConfig::default(); - - let rust_code = rust::generate(ast, interner, annotations, symbols)?; - - setup_build_directory(&config)?; - - let main_rs_path = config.output_dir.join("src").join("main.rs"); - fs::write(&main_rs_path, &rust_code)?; - - let cargo_toml = generate_cargo_toml("naml_program"); - let cargo_toml_path = config.output_dir.join("Cargo.toml"); - fs::write(&cargo_toml_path, cargo_toml)?; - - build_project(&config)?; - - run_binary(&config)?; - - Ok(()) -} - -pub fn compile_only( - ast: &SourceFile<'_>, - interner: &Rodeo, - annotations: &TypeAnnotations, - symbols: &SymbolTable, - config: &BuildConfig, -) -> Result { - let rust_code = rust::generate(ast, interner, annotations, symbols)?; - - setup_build_directory(config)?; - - let main_rs_path = config.output_dir.join("src").join("main.rs"); - fs::write(&main_rs_path, &rust_code)?; - - let cargo_toml = generate_cargo_toml("naml_program"); - let cargo_toml_path = config.output_dir.join("Cargo.toml"); - fs::write(&cargo_toml_path, cargo_toml)?; - - build_project(config)?; - - let binary_name = if cfg!(windows) { - "naml_program.exe" - } else { - "naml_program" - }; - - let binary_path = if config.release { - config.output_dir.join("target").join("release").join(binary_name) - } else { - config.output_dir.join("target").join("debug").join(binary_name) - }; - - Ok(binary_path) -} - -fn setup_build_directory(config: &BuildConfig) -> Result<(), CodegenError> { - let src_dir = config.output_dir.join("src"); - fs::create_dir_all(&src_dir)?; - Ok(()) -} - -fn generate_cargo_toml(name: &str) -> String { - format!( - r#"[package] -name = "{}" -version = "0.1.0" -edition = "2021" - -[workspace] - -[dependencies] -tokio = {{ version = "1", features = ["full"] }} -"#, - name - ) -} - -fn build_project(config: &BuildConfig) -> Result<(), CodegenError> { - let mut cmd = Command::new("cargo"); - cmd.arg("build"); - - if config.release { - cmd.arg("--release"); - } - - cmd.current_dir(&config.output_dir); - - let output = cmd.output()?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - return Err(CodegenError::CargoBuild(stderr.to_string())); - } - - Ok(()) -} - -fn run_binary(config: &BuildConfig) -> Result<(), CodegenError> { - let binary_name = if cfg!(windows) { - "naml_program.exe" - } else { - "naml_program" - }; - - let binary_path = if config.release { - config.output_dir.join("target").join("release").join(binary_name) - } else { - config.output_dir.join("target").join("debug").join(binary_name) - }; - - let output = Command::new(&binary_path).output()?; - - print!("{}", String::from_utf8_lossy(&output.stdout)); - eprint!("{}", String::from_utf8_lossy(&output.stderr)); - - if !output.status.success() { - if let Some(code) = output.status.code() { - std::process::exit(code); - } - } - - Ok(()) + let mut jit = cranelift::JitCompiler::new(interner, annotations, symbols)?; + jit.compile(ast)?; + jit.run_main() } #[cfg(test)] mod tests { - use super::*; - #[test] - fn test_generate_cargo_toml() { - let toml = generate_cargo_toml("test_project"); - assert!(toml.contains("name = \"test_project\"")); - assert!(toml.contains("edition = \"2021\"")); + fn test_module_exists() { + assert!(true); } } diff --git a/namlc/src/codegen/rust/expressions.rs b/namlc/src/codegen/rust/expressions.rs deleted file mode 100644 index c8a270e..0000000 --- a/namlc/src/codegen/rust/expressions.rs +++ /dev/null @@ -1,804 +0,0 @@ -/// -/// Expression Code Generation -/// -/// Converts naml expressions to Rust expressions: -/// - Literals (int, float, string, bool) -/// - Binary operations (+, -, *, /, ==, etc.) -/// - Unary operations (-, !) -/// - Function calls -/// - Identifiers -/// - Field access -/// - Index access -/// - -use crate::ast::{BinaryOp, Expression, Literal, LiteralExpr, UnaryOp}; -use crate::codegen::CodegenError; -use crate::source::Spanned; -use crate::typechecker::Type; - -use super::RustGenerator; - -pub fn emit_expression(g: &mut RustGenerator, expr: &Expression<'_>) -> Result<(), CodegenError> { - match expr { - Expression::Literal(lit) => emit_literal(g, lit), - - Expression::Identifier(ident) => { - let name = g.interner().resolve(&ident.ident.symbol).to_string(); - g.write(&name); - Ok(()) - } - - Expression::Binary(bin) => { - if bin.op == BinaryOp::Add { - let left_ty = g.type_of(bin.left.span()); - let right_ty = g.type_of(bin.right.span()); - - let is_string_concat = matches!(left_ty, Some(Type::String)) - || matches!(right_ty, Some(Type::String)); - - if is_string_concat { - g.write("format!(\"{}{}\", "); - emit_expression(g, bin.left)?; - g.write(", "); - emit_expression(g, bin.right)?; - g.write(")"); - return Ok(()); - } - } - - g.write("("); - emit_expression(g, bin.left)?; - g.write(" "); - g.write(binary_op_to_rust(&bin.op)); - g.write(" "); - emit_expression(g, bin.right)?; - g.write(")"); - Ok(()) - } - - Expression::Unary(un) => { - g.write(unary_op_to_rust(&un.op)); - emit_expression(g, un.operand)?; - Ok(()) - } - - Expression::Call(call) => { - if let Expression::Identifier(ident) = call.callee { - let name = g.interner().resolve(&ident.ident.symbol).to_string(); - - match name.as_str() { - "print" => { - g.write("print!(\"{}\""); - for arg in &call.args { - g.write(", "); - emit_expression(g, arg)?; - } - g.write(")"); - } - "println" => { - if call.args.is_empty() { - g.write("println!()"); - } else { - g.write("println!(\"{}\""); - for arg in &call.args { - g.write(", "); - emit_expression(g, arg)?; - } - g.write(")"); - } - } - "printf" => { - if let Some(Expression::Literal(LiteralExpr { - value: Literal::String(fmt_spur), - .. - })) = call.args.first() - { - let fmt = g.interner().resolve(fmt_spur); - let rust_fmt = fmt.replace("{}", "{}"); - g.write(&format!("println!(\"{}\"", rust_fmt)); - for arg in call.args.iter().skip(1) { - g.write(", "); - emit_expression(g, arg)?; - } - g.write(")"); - } else { - g.write("println!(\"{}\""); - for arg in &call.args { - g.write(", "); - emit_expression(g, arg)?; - } - g.write(")"); - } - } - _ => { - g.write(&name); - g.write("("); - for (i, arg) in call.args.iter().enumerate() { - if i > 0 { - g.write(", "); - } - emit_expression(g, arg)?; - } - g.write(")"); - - // Add ? for throws functions when in throws context - // Skip if inside await (await handles the ?) - if g.function_throws(&name) - && g.is_in_throws_function() - && !g.is_in_await_expr() - { - g.write("?"); - } - } - } - } else { - return Err(CodegenError::Unsupported( - "Complex call targets not yet supported".to_string(), - )); - } - Ok(()) - } - - Expression::Grouped(grouped) => { - g.write("("); - emit_expression(g, grouped.inner)?; - g.write(")"); - Ok(()) - } - - Expression::Field(field) => { - let field_name = g.interner().resolve(&field.field.symbol).to_string(); - - // Check if this is an enum variant access (EnumName.Variant) - if let Expression::Identifier(ident) = field.base { - let base_name = g.interner().resolve(&ident.ident.symbol).to_string(); - if g.is_enum(&base_name) { - g.write(&format!("{}::{}", base_name, field_name)); - return Ok(()); - } - } - - let base_ty = g.type_of(field.base.span()).cloned(); - - match (&base_ty, field_name.as_str()) { - (Some(Type::Array(_)), "length") - | (Some(Type::FixedArray(_, _)), "length") - | (Some(Type::String), "length") => { - emit_expression(g, field.base)?; - g.write(".len() as i64"); - return Ok(()); - } - (Some(Type::String), "chars") => { - emit_expression(g, field.base)?; - g.write(".chars()"); - return Ok(()); - } - (Some(Type::String), "bytes") => { - emit_expression(g, field.base)?; - g.write(".as_bytes()"); - return Ok(()); - } - _ => {} - } - - emit_expression(g, field.base)?; - g.write(&format!(".{}", field_name)); - - // Add .clone() for non-Copy types accessed from &self in methods - if g.is_in_ref_method() && g.needs_clone(field.span) { - g.write(".clone()"); - } - - Ok(()) - } - - Expression::Index(index) => { - let base_ty = g.type_of(index.base.span()).cloned(); - let idx_ty = g.type_of(index.index.span()).cloned(); - - emit_expression(g, index.base)?; - - // For Map types, use .get() with reference instead of [] to avoid borrow issues - // .cloned() converts Option<&V> to Option - if matches!(base_ty, Some(Type::Map(_, _))) { - g.write(".get(&"); - emit_expression(g, index.index)?; - g.write(").cloned()"); - } else { - g.write("["); - emit_expression(g, index.index)?; - - let needs_usize_cast = matches!(idx_ty, Some(Type::Int) | Some(Type::Uint)); - if needs_usize_cast { - g.write(" as usize"); - } - - g.write("]"); - - // Clone array elements of non-Copy types to avoid move errors - let element_is_non_copy = match &base_ty { - Some(Type::Array(inner)) | Some(Type::FixedArray(inner, _)) => { - !is_copy_type_ref(inner) - } - _ => false, - }; - if element_is_non_copy { - g.write(".clone()"); - } - } - Ok(()) - } - - Expression::MethodCall(method) => { - let receiver_ty = g.type_of(method.receiver.span()).cloned(); - let method_name = g.interner().resolve(&method.method.symbol).to_string(); - - match (&receiver_ty, method_name.as_str()) { - (Some(Type::Array(_)), "push") | (Some(Type::Array(_)), "append") => { - emit_expression(g, method.receiver)?; - g.write(".push("); - for (i, arg) in method.args.iter().enumerate() { - if i > 0 { - g.write(", "); - } - emit_expression(g, arg)?; - } - g.write(")"); - return Ok(()); - } - (Some(Type::Array(_)), "pop") => { - emit_expression(g, method.receiver)?; - g.write(".pop()"); - return Ok(()); - } - (Some(Type::Array(_)), "len") | (Some(Type::String), "len") => { - emit_expression(g, method.receiver)?; - g.write(".len() as i64"); - return Ok(()); - } - (Some(Type::Option(_)), "or_default") => { - emit_expression(g, method.receiver)?; - // Clone before unwrap_or if accessing self field in &self method - if g.is_in_ref_method() && is_self_field_access(g, method.receiver) { - g.write(".clone()"); - } - g.write(".unwrap_or("); - if let Some(arg) = method.args.first() { - emit_expression(g, arg)?; - } - g.write(")"); - return Ok(()); - } - (Some(Type::Option(_)), "is_some") => { - emit_expression(g, method.receiver)?; - g.write(".is_some()"); - return Ok(()); - } - (Some(Type::Option(_)), "is_none") => { - emit_expression(g, method.receiver)?; - g.write(".is_none()"); - return Ok(()); - } - (Some(Type::Map(_, _)), "get") => { - emit_expression(g, method.receiver)?; - g.write(".get(&"); - if let Some(arg) = method.args.first() { - emit_expression(g, arg)?; - } - g.write(").cloned()"); - return Ok(()); - } - (Some(Type::Map(_, _)), "insert") | (Some(Type::Map(_, _)), "set") => { - emit_expression(g, method.receiver)?; - g.write(".insert("); - for (i, arg) in method.args.iter().enumerate() { - if i > 0 { - g.write(", "); - } - emit_expression(g, arg)?; - } - g.write(")"); - return Ok(()); - } - (Some(Type::Map(_, _)), "contains") | (Some(Type::Map(_, _)), "contains_key") => { - emit_expression(g, method.receiver)?; - g.write(".contains_key(&"); - if let Some(arg) = method.args.first() { - emit_expression(g, arg)?; - } - g.write(")"); - return Ok(()); - } - (Some(Type::Map(_, _)), "remove") => { - emit_expression(g, method.receiver)?; - g.write(".remove(&"); - if let Some(arg) = method.args.first() { - emit_expression(g, arg)?; - } - g.write(")"); - return Ok(()); - } - (Some(Type::String), "contains") => { - emit_expression(g, method.receiver)?; - g.write(".contains(&"); - if let Some(arg) = method.args.first() { - emit_expression(g, arg)?; - } - g.write(")"); - return Ok(()); - } - (Some(Type::String), "starts_with") => { - emit_expression(g, method.receiver)?; - g.write(".starts_with(&"); - if let Some(arg) = method.args.first() { - emit_expression(g, arg)?; - } - g.write(")"); - return Ok(()); - } - (Some(Type::String), "ends_with") => { - emit_expression(g, method.receiver)?; - g.write(".ends_with(&"); - if let Some(arg) = method.args.first() { - emit_expression(g, arg)?; - } - g.write(")"); - return Ok(()); - } - (Some(Type::String), "split") => { - emit_expression(g, method.receiver)?; - g.write(".split(&"); - if let Some(arg) = method.args.first() { - emit_expression(g, arg)?; - } - g.write(").map(|s| s.to_string()).collect::>()"); - return Ok(()); - } - (Some(Type::String), "trim") => { - emit_expression(g, method.receiver)?; - g.write(".trim().to_string()"); - return Ok(()); - } - (Some(Type::String), "to_uppercase") | (Some(Type::String), "upper") => { - emit_expression(g, method.receiver)?; - g.write(".to_uppercase()"); - return Ok(()); - } - (Some(Type::String), "to_lowercase") | (Some(Type::String), "lower") => { - emit_expression(g, method.receiver)?; - g.write(".to_lowercase()"); - return Ok(()); - } - (Some(Type::String), "replace") => { - emit_expression(g, method.receiver)?; - g.write(".replace(&"); - if let Some(arg) = method.args.first() { - emit_expression(g, arg)?; - } - g.write(", &"); - if let Some(arg) = method.args.get(1) { - emit_expression(g, arg)?; - } - g.write(")"); - return Ok(()); - } - (Some(Type::String), "substring") | (Some(Type::String), "substr") => { - emit_expression(g, method.receiver)?; - g.write(".chars().skip("); - if let Some(arg) = method.args.first() { - emit_expression(g, arg)?; - } - g.write(" as usize).take("); - if let Some(arg) = method.args.get(1) { - emit_expression(g, arg)?; - } - g.write(" as usize).collect::()"); - return Ok(()); - } - _ => {} - } - - emit_expression(g, method.receiver)?; - g.write(&format!(".{}(", method_name)); - for (i, arg) in method.args.iter().enumerate() { - if i > 0 { - g.write(", "); - } - emit_expression(g, arg)?; - } - g.write(")"); - - // Add ? for throws methods when in throws context - // Skip if inside await (await handles the ?) - if let Some(Type::Struct(st)) = receiver_ty { - let type_name = g.interner().resolve(&st.name).to_string(); - if g.method_throws(&type_name, &method_name) - && g.is_in_throws_function() - && !g.is_in_await_expr() - { - g.write("?"); - } - } - - Ok(()) - } - - Expression::If(if_expr) => { - g.write("if "); - emit_expression(g, if_expr.condition)?; - g.write(" { "); - for stmt in &if_expr.then_branch.statements { - super::statements::emit_statement(g, stmt)?; - } - if let Some(tail) = if_expr.then_branch.tail { - emit_expression(g, tail)?; - } - g.write(" }"); - - if let Some(ref else_branch) = if_expr.else_branch { - g.write(" else "); - match else_branch { - crate::ast::ElseExpr::Else(block) => { - g.write("{ "); - for stmt in &block.statements { - super::statements::emit_statement(g, stmt)?; - } - if let Some(tail) = block.tail { - emit_expression(g, tail)?; - } - g.write(" }"); - } - crate::ast::ElseExpr::ElseIf(elif) => { - emit_expression(g, &Expression::If((*elif).clone()))?; - } - } - } - Ok(()) - } - - Expression::Array(arr) => { - g.write("vec!["); - for (i, elem) in arr.elements.iter().enumerate() { - if i > 0 { - g.write(", "); - } - emit_expression(g, elem)?; - } - g.write("]"); - Ok(()) - } - - Expression::StructLiteral(lit) => { - let struct_name = g.interner().resolve(&lit.name.symbol); - g.write(&format!("{} {{ ", struct_name)); - for (i, field) in lit.fields.iter().enumerate() { - if i > 0 { - g.write(", "); - } - let name = g.interner().resolve(&field.name.symbol); - g.write(&format!("{}: ", name)); - emit_expression(g, &field.value)?; - } - g.write(" }"); - Ok(()) - } - - Expression::Range(range) => { - if let Some(start) = range.start { - emit_expression(g, start)?; - } - if range.inclusive { - g.write("..="); - } else { - g.write(".."); - } - if let Some(end) = range.end { - emit_expression(g, end)?; - } - Ok(()) - } - - Expression::Some(some_expr) => { - g.write("Some("); - emit_expression(g, some_expr.value)?; - g.write(")"); - Ok(()) - } - - Expression::Cast(cast) => { - let from_ty = g.type_of(cast.expr.span()); - let target = &cast.target_ty; - - match (from_ty, target) { - (Some(Type::String), crate::ast::NamlType::Bytes) => { - emit_expression(g, cast.expr)?; - g.write(".into_bytes()"); - } - (Some(Type::Int), crate::ast::NamlType::String) - | (Some(Type::Uint), crate::ast::NamlType::String) - | (Some(Type::Float), crate::ast::NamlType::String) => { - emit_expression(g, cast.expr)?; - g.write(".to_string()"); - } - (Some(Type::Bool), crate::ast::NamlType::String) => { - emit_expression(g, cast.expr)?; - g.write(".to_string()"); - } - (Some(Type::Bytes), crate::ast::NamlType::String) => { - g.write("String::from_utf8("); - emit_expression(g, cast.expr)?; - g.write(").unwrap_or_default()"); - } - (Some(Type::String), crate::ast::NamlType::Int) => { - emit_expression(g, cast.expr)?; - g.write(".parse::().unwrap_or(0)"); - } - (Some(Type::String), crate::ast::NamlType::Float) => { - emit_expression(g, cast.expr)?; - g.write(".parse::().unwrap_or(0.0)"); - } - _ => { - emit_expression(g, cast.expr)?; - let target_ty = super::types::naml_to_rust(target, g.interner()); - g.write(&format!(" as {}", target_ty)); - } - } - Ok(()) - } - - Expression::Lambda(lambda) => { - g.write("|"); - for (i, param) in lambda.params.iter().enumerate() { - if i > 0 { - g.write(", "); - } - let param_name = g.interner().resolve(¶m.name.symbol).to_string(); - g.write(¶m_name); - if let Some(ref ty) = param.ty { - g.write(": "); - let param_ty = super::types::naml_to_rust(ty, g.interner()); - g.write(¶m_ty); - } - } - g.write("| "); - emit_expression(g, lambda.body)?; - Ok(()) - } - - Expression::Map(map) => { - g.write("std::collections::HashMap::from(["); - for (i, entry) in map.entries.iter().enumerate() { - if i > 0 { - g.write(", "); - } - g.write("("); - emit_expression(g, &entry.key)?; - g.write(", "); - emit_expression(g, &entry.value)?; - g.write(")"); - } - g.write("])"); - Ok(()) - } - - Expression::Block(block) => { - g.write("{ "); - for stmt in &block.statements { - super::statements::emit_statement(g, stmt)?; - } - if let Some(tail) = block.tail { - emit_expression(g, tail)?; - } - g.write(" }"); - Ok(()) - } - - Expression::Await(await_expr) => { - // Set flag to prevent inner call from adding ? (we'll add it after .await) - let was_in_await = g.is_in_await_expr(); - g.set_in_await_expr(true); - emit_expression(g, await_expr.expr)?; - g.set_in_await_expr(was_in_await); - - g.write(".await"); - - // Add ? after .await if inner expression throws - if g.is_in_throws_function() { - let inner_throws = match await_expr.expr { - Expression::Call(call) => { - if let Expression::Identifier(ident) = call.callee { - let name = g.interner().resolve(&ident.ident.symbol).to_string(); - g.function_throws(&name) - } else { - false - } - } - Expression::MethodCall(method) => { - let receiver_ty = g.type_of(method.receiver.span()); - let method_name = g.interner().resolve(&method.method.symbol).to_string(); - if let Some(Type::Struct(st)) = receiver_ty { - let type_name = g.interner().resolve(&st.name); - g.method_throws(type_name, &method_name) - } else { - false - } - } - _ => false, - }; - if inner_throws { - g.write("?"); - } - } - Ok(()) - } - - Expression::Spawn(_) => Err(CodegenError::Unsupported( - "Spawn expressions not yet supported in Rust codegen".to_string(), - )), - - Expression::Try(_) => Err(CodegenError::Unsupported( - "Try expressions not yet supported in Rust codegen".to_string(), - )), - - Expression::Catch(catch) => { - emit_expression(g, catch.expr)?; - g.write(".unwrap_or_else(|"); - let error_name = g.interner().resolve(&catch.error_binding.symbol).to_string(); - g.write(&error_name); - g.write("| {\n"); - g.indent += 1; - for stmt in &catch.handler.statements { - super::statements::emit_statement(g, stmt)?; - } - g.indent -= 1; - g.write_indent(); - g.write("})"); - Ok(()) - } - - Expression::Path(path) => { - for (i, segment) in path.segments.iter().enumerate() { - if i > 0 { - g.write("::"); - } - let name = g.interner().resolve(&segment.symbol).to_string(); - g.write(&name); - } - Ok(()) - } - } -} - -fn emit_literal(g: &mut RustGenerator, lit: &LiteralExpr) -> Result<(), CodegenError> { - match &lit.value { - Literal::Int(n) => { - g.write(&n.to_string()); - g.write("_i64"); - } - Literal::UInt(n) => { - g.write(&n.to_string()); - g.write("_u64"); - } - Literal::Float(f) => { - g.write(&f.to_string()); - if !f.to_string().contains('.') { - g.write(".0"); - } - g.write("_f64"); - } - Literal::Bool(b) => { - g.write(if *b { "true" } else { "false" }); - } - Literal::String(spur) => { - let s = g.interner().resolve(spur); - g.write(&format!("\"{}\".to_string()", escape_string(s))); - } - Literal::Bytes(bytes) => { - g.write("vec!["); - for (i, b) in bytes.iter().enumerate() { - if i > 0 { - g.write(", "); - } - g.write(&format!("{}u8", b)); - } - g.write("]"); - } - Literal::None => { - g.write("None"); - } - } - Ok(()) -} - -fn binary_op_to_rust(op: &BinaryOp) -> &'static str { - match op { - BinaryOp::Add => "+", - BinaryOp::Sub => "-", - BinaryOp::Mul => "*", - BinaryOp::Div => "/", - BinaryOp::Mod => "%", - BinaryOp::Eq => "==", - BinaryOp::NotEq => "!=", - BinaryOp::Lt => "<", - BinaryOp::LtEq => "<=", - BinaryOp::Gt => ">", - BinaryOp::GtEq => ">=", - BinaryOp::And => "&&", - BinaryOp::Or => "||", - BinaryOp::BitAnd => "&", - BinaryOp::BitOr => "|", - BinaryOp::BitXor => "^", - BinaryOp::Shl => "<<", - BinaryOp::Shr => ">>", - BinaryOp::Range => "..", - BinaryOp::RangeIncl => "..=", - BinaryOp::Is => "/* is */", - } -} - -fn unary_op_to_rust(op: &UnaryOp) -> &'static str { - match op { - UnaryOp::Neg => "-", - UnaryOp::Not => "!", - UnaryOp::BitNot => "!", - } -} - -fn escape_string(s: &str) -> String { - s.replace('\\', "\\\\") - .replace('"', "\\\"") - .replace('\n', "\\n") - .replace('\r', "\\r") - .replace('\t', "\\t") -} - -fn is_self_field_access(g: &RustGenerator, expr: &Expression) -> bool { - match expr { - Expression::Identifier(ident) => { - let name = g.interner().resolve(&ident.ident.symbol); - name == "self" - } - Expression::Field(field) => is_self_field_access(g, field.base), - _ => false, - } -} - -fn is_copy_type(ty: &Option<&Type>) -> bool { - matches!( - ty, - Some(Type::Int) | Some(Type::Uint) | Some(Type::Float) | Some(Type::Bool) | Some(Type::Unit) - ) -} - -fn is_copy_type_val(ty: &Option) -> bool { - matches!( - ty, - Some(Type::Int) | Some(Type::Uint) | Some(Type::Float) | Some(Type::Bool) | Some(Type::Unit) - ) -} - -fn is_copy_type_ref(ty: &Type) -> bool { - matches!( - ty, - Type::Int | Type::Uint | Type::Float | Type::Bool | Type::Unit - ) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_binary_ops() { - assert_eq!(binary_op_to_rust(&BinaryOp::Add), "+"); - assert_eq!(binary_op_to_rust(&BinaryOp::Eq), "=="); - assert_eq!(binary_op_to_rust(&BinaryOp::And), "&&"); - } - - #[test] - fn test_escape_string() { - assert_eq!(escape_string("hello"), "hello"); - assert_eq!(escape_string("hello\nworld"), "hello\\nworld"); - assert_eq!(escape_string("say \"hi\""), "say \\\"hi\\\""); - } -} diff --git a/namlc/src/codegen/rust/mod.rs b/namlc/src/codegen/rust/mod.rs deleted file mode 100644 index fa8e708..0000000 --- a/namlc/src/codegen/rust/mod.rs +++ /dev/null @@ -1,598 +0,0 @@ -/// -/// Rust Code Generator -/// -/// This module generates Rust source code from naml AST. It handles: -/// - Type mappings (naml types → Rust types) -/// - Expression codegen (literals, binary ops, calls) -/// - Statement codegen (var, if, while, for, return) -/// - Function and struct definitions -/// - -mod expressions; -mod statements; -mod types; - -use lasso::Rodeo; - -use crate::ast::{EnumItem, ExceptionItem, FunctionItem, GenericParam, Item, NamlType, SourceFile, StructItem}; -use crate::codegen::CodegenError; -use crate::source::Span; -use crate::typechecker::{SymbolTable, Type, TypeAnnotations}; - -use std::collections::{HashMap, HashSet}; - -pub struct RustGenerator<'a> { - interner: &'a Rodeo, - annotations: &'a TypeAnnotations, - symbols: &'a SymbolTable, - output: String, - pub(crate) indent: usize, - enum_names: HashSet, - enum_variants: HashMap, - in_ref_method: bool, - in_throws_function: bool, - in_await_expr: bool, -} - -impl<'a> RustGenerator<'a> { - pub fn new( - interner: &'a Rodeo, - annotations: &'a TypeAnnotations, - symbols: &'a SymbolTable, - ) -> Self { - Self { - interner, - annotations, - symbols, - output: String::new(), - indent: 0, - enum_names: HashSet::new(), - enum_variants: HashMap::new(), - in_ref_method: false, - in_throws_function: false, - in_await_expr: false, - } - } - - pub fn type_of(&self, span: Span) -> Option<&Type> { - self.annotations.get_type(span) - } - - pub fn needs_clone(&self, span: Span) -> bool { - self.annotations.needs_clone(span) - } - - pub fn generate(mut self, ast: &SourceFile<'_>) -> Result { - // Collect enum names and variants first - for item in &ast.items { - if let Item::Enum(e) = item { - let enum_name = self.interner.resolve(&e.name.symbol).to_string(); - self.enum_names.insert(enum_name.clone()); - for variant in &e.variants { - let variant_name = self.interner.resolve(&variant.name.symbol).to_string(); - self.enum_variants.insert(variant_name, enum_name.clone()); - } - } - } - - self.emit_prelude(); - - for item in &ast.items { - match item { - Item::Struct(s) => self.emit_struct(s)?, - Item::Function(f) => self.emit_function(f)?, - Item::TopLevelStmt(_stmt) => { - return Err(CodegenError::Unsupported( - "Top-level statements outside main not yet supported".to_string(), - )); - } - Item::Enum(e) => self.emit_enum(e)?, - Item::Interface(_) => { - // Interfaces are type-level only, no runtime code needed - } - Item::Exception(e) => self.emit_exception(e)?, - Item::Import(_) | Item::Use(_) => {} - Item::Extern(_) => { - // Extern declarations are handled by the runtime - } - } - } - - self.emit_impl_blocks(&ast.items)?; - - Ok(self.output) - } - - fn emit_prelude(&mut self) { - self.writeln("// Generated by naml compiler"); - self.writeln("#![allow(unused_variables)]"); - self.writeln("#![allow(unused_mut)]"); - self.writeln("#![allow(dead_code)]"); - self.writeln("#![allow(unused_parens)]"); - self.writeln(""); - self.writeln("use tokio::task::yield_now;"); - self.writeln("use tokio::time::{sleep as tokio_sleep, Duration};"); - self.writeln(""); - self.writeln("async fn sleep(ms: i64) {"); - self.writeln(" tokio_sleep(Duration::from_millis(ms as u64)).await;"); - self.writeln("}"); - self.writeln(""); - } - - fn emit_struct(&mut self, s: &StructItem) -> Result<(), CodegenError> { - let name = self.interner.resolve(&s.name.symbol); - - self.writeln("#[derive(Clone, Debug, Default, PartialEq, Eq)]"); - if s.is_public { - self.write("pub "); - } - - self.write(&format!("struct {}", name)); - self.emit_generic_params(&s.generics)?; - self.writeln(" {"); - self.indent += 1; - - for field in &s.fields { - let field_name = self.interner.resolve(&field.name.symbol); - let field_type = types::naml_to_rust_in_struct(&field.ty, self.interner, name); - - self.write_indent(); - if field.is_public { - self.write("pub "); - } - self.writeln(&format!("{}: {},", field_name, field_type)); - } - - self.indent -= 1; - self.writeln("}"); - self.writeln(""); - - Ok(()) - } - - fn emit_enum(&mut self, e: &EnumItem) -> Result<(), CodegenError> { - let name = self.interner.resolve(&e.name.symbol); - - // Skip Option and Result - use Rust's std types - if name == "Option" || name == "Result" { - return Ok(()); - } - - self.writeln("#[derive(Clone, Debug, Default, PartialEq, Eq)]"); - if e.is_public { - self.write("pub "); - } - - self.write(&format!("enum {}", name)); - self.emit_generic_params(&e.generics)?; - self.writeln(" {"); - self.indent += 1; - - for (i, variant) in e.variants.iter().enumerate() { - let variant_name = self.interner.resolve(&variant.name.symbol); - self.write_indent(); - // Mark first variant as default - if i == 0 { - self.writeln("#[default]"); - self.write_indent(); - } - self.write(variant_name); - - if let Some(ref fields) = variant.fields { - self.write("("); - for (j, field_ty) in fields.iter().enumerate() { - if j > 0 { - self.write(", "); - } - let rust_ty = types::naml_to_rust(field_ty, self.interner); - self.write(&rust_ty); - } - self.write(")"); - } - self.writeln(","); - } - - self.indent -= 1; - self.writeln("}"); - self.writeln(""); - - Ok(()) - } - - fn emit_exception(&mut self, e: &ExceptionItem) -> Result<(), CodegenError> { - let name = self.interner.resolve(&e.name.symbol); - - self.writeln("#[derive(Debug, Clone)]"); - if e.is_public { - self.write("pub "); - } - self.writeln(&format!("struct {} {{", name)); - self.indent += 1; - - for field in &e.fields { - let field_name = self.interner.resolve(&field.name.symbol); - let field_type = types::naml_to_rust(&field.ty, self.interner); - self.write_indent(); - self.writeln(&format!("pub {}: {},", field_name, field_type)); - } - - self.indent -= 1; - self.writeln("}"); - self.writeln(""); - - // Implement Display trait - self.writeln(&format!("impl std::fmt::Display for {} {{", name)); - self.indent += 1; - self.writeln("fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {"); - self.indent += 1; - self.writeln("write!(f, \"{:?}\", self)"); - self.indent -= 1; - self.writeln("}"); - self.indent -= 1; - self.writeln("}"); - self.writeln(""); - - // Implement Error trait - self.writeln(&format!("impl std::error::Error for {} {{}}", name)); - self.writeln(""); - - Ok(()) - } - - fn emit_function(&mut self, f: &FunctionItem<'_>) -> Result<(), CodegenError> { - let name = self.interner.resolve(&f.name.symbol); - - if f.receiver.is_some() { - return Ok(()); - } - - // Add #[tokio::main] for async main function - if name == "main" && f.is_async { - self.writeln("#[tokio::main]"); - } - - if f.is_public { - self.write("pub "); - } - - if f.is_async { - self.write("async "); - } - - self.write(&format!("fn {}", name)); - self.emit_generic_params(&f.generics)?; - self.write("("); - - for (i, param) in f.params.iter().enumerate() { - if i > 0 { - self.write(", "); - } - let param_name = self.interner.resolve(¶m.name.symbol); - let param_type = types::naml_to_rust(¶m.ty, self.interner); - self.write(&format!("{}: {}", param_name, param_type)); - } - - self.write(")"); - - match (&f.return_ty, &f.throws) { - (Some(return_ty), Some(throws_ty)) => { - let rust_ret = types::naml_to_rust(return_ty, self.interner); - let rust_err = types::naml_to_rust(throws_ty, self.interner); - self.write(&format!(" -> Result<{}, {}>", rust_ret, rust_err)); - } - (None, Some(throws_ty)) => { - let rust_err = types::naml_to_rust(throws_ty, self.interner); - self.write(&format!(" -> Result<(), {}>", rust_err)); - } - (Some(return_ty), None) => { - let rust_ty = types::naml_to_rust(return_ty, self.interner); - self.write(&format!(" -> {}", rust_ty)); - } - (None, None) => {} - } - - if let Some(ref body) = f.body { - self.writeln(" {"); - self.indent += 1; - - let was_in_throws = self.in_throws_function; - self.in_throws_function = f.throws.is_some(); - - statements::emit_block(self, body)?; - - self.in_throws_function = was_in_throws; - self.indent -= 1; - self.writeln("}"); - } else { - self.writeln(";"); - } - - self.writeln(""); - - Ok(()) - } - - pub(crate) fn write(&mut self, s: &str) { - self.output.push_str(s); - } - - pub(crate) fn writeln(&mut self, s: &str) { - self.write_indent(); - self.output.push_str(s); - self.output.push('\n'); - } - - pub(crate) fn write_indent(&mut self) { - for _ in 0..self.indent { - self.output.push_str(" "); - } - } - - pub(crate) fn interner(&self) -> &Rodeo { - self.interner - } - - pub(crate) fn is_enum(&self, name: &str) -> bool { - self.enum_names.contains(name) - } - - pub(crate) fn get_enum_for_variant(&self, variant: &str) -> Option<&String> { - self.enum_variants.get(variant) - } - - pub(crate) fn is_in_ref_method(&self) -> bool { - self.in_ref_method - } - - pub(crate) fn is_in_throws_function(&self) -> bool { - self.in_throws_function - } - - pub(crate) fn is_in_await_expr(&self) -> bool { - self.in_await_expr - } - - pub(crate) fn set_in_await_expr(&mut self, value: bool) { - self.in_await_expr = value; - } - - pub(crate) fn indent_inc(&mut self) { - self.indent += 1; - } - - pub(crate) fn indent_dec(&mut self) { - self.indent -= 1; - } - - pub(crate) fn function_throws(&self, name: &str) -> bool { - if let Some(spur) = self.interner.get(name) { - if let Some(sig) = self.symbols.get_function(spur) { - return sig.throws.is_some(); - } - } - false - } - - pub(crate) fn method_throws(&self, type_name: &str, method_name: &str) -> bool { - let type_spur = match self.interner.get(type_name) { - Some(s) => s, - None => return false, - }; - let method_spur = match self.interner.get(method_name) { - Some(s) => s, - None => return false, - }; - if let Some(sig) = self.symbols.get_method(type_spur, method_spur) { - return sig.throws.is_some(); - } - false - } - - pub(crate) fn symbols(&self) -> &SymbolTable { - self.symbols - } - - fn emit_generic_params(&mut self, params: &[GenericParam]) -> Result<(), CodegenError> { - if params.is_empty() { - return Ok(()); - } - - self.write("<"); - for (i, param) in params.iter().enumerate() { - if i > 0 { - self.write(", "); - } - let name = self.interner.resolve(¶m.name.symbol); - self.write(name); - // Add default trait bounds for generic types to support common operations - self.write(": Clone + Default + PartialEq + PartialOrd"); - } - self.write(">"); - Ok(()) - } - - fn collect_methods<'b>( - &self, - items: &'b [Item<'b>], - ) -> HashMap>> { - let mut methods: HashMap> = HashMap::new(); - - for item in items { - if let Item::Function(f) = item { - if let Some(ref receiver) = f.receiver { - let type_name = self.get_receiver_type_name(&receiver.ty); - methods.entry(type_name).or_default().push(f); - } - } - } - methods - } - - fn get_receiver_type_name(&self, ty: &NamlType) -> String { - match ty { - NamlType::Named(ident) => self.interner.resolve(&ident.symbol).to_string(), - NamlType::Generic(name, _) => self.interner.resolve(&name.symbol).to_string(), - _ => "Unknown".to_string(), - } - } - - fn find_struct_generics<'b>( - &self, - type_name: &str, - items: &'b [Item<'b>], - ) -> Vec<&'b GenericParam> { - for item in items { - if let Item::Struct(s) = item { - let name = self.interner.resolve(&s.name.symbol); - if name == type_name { - return s.generics.iter().collect(); - } - } - } - Vec::new() - } - - fn emit_impl_blocks<'b>( - &mut self, - items: &'b [Item<'b>], - ) -> Result<(), CodegenError> { - let methods = self.collect_methods(items); - - for (type_name, type_methods) in &methods { - let struct_generics = self.find_struct_generics(type_name, items); - - self.write("impl"); - if !struct_generics.is_empty() { - self.write("<"); - for (i, param) in struct_generics.iter().enumerate() { - if i > 0 { - self.write(", "); - } - let name = self.interner.resolve(¶m.name.symbol); - self.write(name); - // Add same trait bounds as struct definition - self.write(": Clone + Default + PartialEq + PartialOrd"); - } - self.write(">"); - } - - self.write(&format!(" {}", type_name)); - if !struct_generics.is_empty() { - self.write("<"); - for (i, param) in struct_generics.iter().enumerate() { - if i > 0 { - self.write(", "); - } - let name = self.interner.resolve(¶m.name.symbol); - self.write(name); - } - self.write(">"); - } - self.writeln(" {"); - self.indent += 1; - - for method in type_methods { - self.emit_method(method)?; - } - - self.indent -= 1; - self.writeln("}"); - self.writeln(""); - } - - Ok(()) - } - - fn emit_method(&mut self, f: &FunctionItem<'_>) -> Result<(), CodegenError> { - let name = self.interner.resolve(&f.name.symbol); - let receiver = f.receiver.as_ref().unwrap(); - - self.write_indent(); - if f.is_public { - self.write("pub "); - } - - if f.is_async { - self.write("async "); - } - - self.write(&format!("fn {}", name)); - self.emit_generic_params(&f.generics)?; - self.write("("); - - if receiver.mutable { - self.write("&mut self"); - } else { - self.write("&self"); - } - - for param in &f.params { - self.write(", "); - let param_name = self.interner.resolve(¶m.name.symbol); - let param_type = types::naml_to_rust(¶m.ty, self.interner); - self.write(&format!("{}: {}", param_name, param_type)); - } - - self.write(")"); - - match (&f.return_ty, &f.throws) { - (Some(return_ty), Some(throws_ty)) => { - let rust_ret = types::naml_to_rust(return_ty, self.interner); - let rust_err = types::naml_to_rust(throws_ty, self.interner); - self.write(&format!(" -> Result<{}, {}>", rust_ret, rust_err)); - } - (None, Some(throws_ty)) => { - let rust_err = types::naml_to_rust(throws_ty, self.interner); - self.write(&format!(" -> Result<(), {}>", rust_err)); - } - (Some(return_ty), None) => { - let rust_ty = types::naml_to_rust(return_ty, self.interner); - self.write(&format!(" -> {}", rust_ty)); - } - (None, None) => {} - } - - if let Some(ref body) = f.body { - self.output.push_str(" {\n"); - self.indent += 1; - - let was_in_ref_method = self.in_ref_method; - let was_in_throws = self.in_throws_function; - if !receiver.mutable { - self.in_ref_method = true; - } - self.in_throws_function = f.throws.is_some(); - - statements::emit_block(self, body)?; - - self.in_ref_method = was_in_ref_method; - self.in_throws_function = was_in_throws; - self.indent -= 1; - self.writeln("}"); - } else { - self.output.push_str(";\n"); - } - - self.writeln(""); - - Ok(()) - } -} - -pub fn generate( - ast: &SourceFile<'_>, - interner: &Rodeo, - annotations: &TypeAnnotations, - symbols: &SymbolTable, -) -> Result { - let generator = RustGenerator::new(interner, annotations, symbols); - generator.generate(ast) -} - -#[cfg(test)] -mod tests { - #[test] - fn test_module_exists() { - assert!(true); - } -} diff --git a/namlc/src/codegen/rust/statements.rs b/namlc/src/codegen/rust/statements.rs deleted file mode 100644 index 41824b1..0000000 --- a/namlc/src/codegen/rust/statements.rs +++ /dev/null @@ -1,341 +0,0 @@ -/// -/// Statement Code Generation -/// -/// Converts naml statements to Rust statements: -/// - Variable declarations (var, const) -/// - Assignments -/// - If/else -/// - While loops -/// - For loops -/// - Return statements -/// - Expression statements -/// - -use crate::ast::{AssignOp, BlockStmt, Expression, Statement}; -use crate::codegen::CodegenError; -use crate::source::Spanned; - -use super::expressions::emit_expression; -use super::types::naml_to_rust; -use super::RustGenerator; - -pub fn emit_block(g: &mut RustGenerator, block: &BlockStmt<'_>) -> Result<(), CodegenError> { - for stmt in &block.statements { - emit_statement(g, stmt)?; - } - Ok(()) -} - -fn emit_switch_pattern(g: &mut RustGenerator, pattern: &Expression<'_>) -> Result<(), CodegenError> { - if let Expression::Identifier(ident) = pattern { - let name = g.interner().resolve(&ident.ident.symbol).to_string(); - if let Some(enum_name) = g.get_enum_for_variant(&name) { - g.write(&format!("{}::{}", enum_name, name)); - return Ok(()); - } - } - emit_expression(g, pattern) -} - -pub fn emit_statement(g: &mut RustGenerator, stmt: &Statement<'_>) -> Result<(), CodegenError> { - match stmt { - Statement::Var(var_stmt) => { - let name = g.interner().resolve(&var_stmt.name.symbol).to_string(); - - // Handle var x = opt else { ... } pattern - if let Some(ref else_block) = var_stmt.else_block { - g.write_indent(); - g.write("let "); - if var_stmt.mutable { - g.write("mut "); - } - g.write(&name); - - if let Some(ref ty) = var_stmt.ty { - let rust_ty = naml_to_rust(ty, g.interner()); - g.write(&format!(": {}", rust_ty)); - } - - g.write(" = match "); - if let Some(ref init) = var_stmt.init { - emit_expression(g, init)?; - } - g.write(" {\n"); - - g.indent_inc(); - g.write_indent(); - g.write("Some(__naml_val) => __naml_val,\n"); - g.write_indent(); - g.write("None => {\n"); - g.indent_inc(); - emit_block(g, else_block)?; - g.indent_dec(); - g.write_indent(); - g.write("}\n"); - g.indent_dec(); - - g.write_indent(); - g.write("};\n"); - return Ok(()); - } - - // Normal var statement - g.write_indent(); - g.write("let "); - if var_stmt.mutable { - g.write("mut "); - } - g.write(&name); - - if let Some(ref ty) = var_stmt.ty { - let rust_ty = naml_to_rust(ty, g.interner()); - g.write(&format!(": {}", rust_ty)); - } - - if let Some(ref init) = var_stmt.init { - g.write(" = "); - emit_expression(g, init)?; - } - - g.write(";\n"); - Ok(()) - } - - Statement::Const(const_stmt) => { - let name = g.interner().resolve(&const_stmt.name.symbol).to_string(); - - g.write_indent(); - g.write("let "); - g.write(&name); - - if let Some(ref ty) = const_stmt.ty { - let rust_ty = naml_to_rust(ty, g.interner()); - g.write(&format!(": {}", rust_ty)); - } - - g.write(" = "); - emit_expression(g, &const_stmt.init)?; - g.write(";\n"); - Ok(()) - } - - Statement::Assign(assign_stmt) => { - g.write_indent(); - - // Check if target is a map index expression - use insert instead - if let Expression::Index(idx) = &assign_stmt.target { - if let AssignOp::Assign = assign_stmt.op { - let base_ty = g.type_of(idx.base.span()); - if matches!(base_ty, Some(crate::typechecker::Type::Map(_, _))) { - emit_expression(g, idx.base)?; - g.write(".insert("); - emit_expression(g, idx.index)?; - g.write(", "); - emit_expression(g, &assign_stmt.value)?; - g.write(");\n"); - return Ok(()); - } - } - } - - emit_expression(g, &assign_stmt.target)?; - - match assign_stmt.op { - AssignOp::Assign => g.write(" = "), - AssignOp::AddAssign => g.write(" += "), - AssignOp::SubAssign => g.write(" -= "), - AssignOp::MulAssign => g.write(" *= "), - AssignOp::DivAssign => g.write(" /= "), - AssignOp::ModAssign => g.write(" %= "), - AssignOp::BitAndAssign => g.write(" &= "), - AssignOp::BitOrAssign => g.write(" |= "), - AssignOp::BitXorAssign => g.write(" ^= "), - } - - emit_expression(g, &assign_stmt.value)?; - g.write(";\n"); - Ok(()) - } - - Statement::Expression(expr_stmt) => { - g.write_indent(); - emit_expression(g, &expr_stmt.expr)?; - g.write(";\n"); - Ok(()) - } - - Statement::Return(return_stmt) => { - g.write_indent(); - if g.is_in_throws_function() { - if let Some(ref value) = return_stmt.value { - g.write("return Ok("); - emit_expression(g, value)?; - g.write(");\n"); - } else { - g.write("return Ok(());\n"); - } - } else { - g.write("return"); - if let Some(ref value) = return_stmt.value { - g.write(" "); - emit_expression(g, value)?; - } - g.write(";\n"); - } - Ok(()) - } - - Statement::If(if_stmt) => { - g.write_indent(); - g.write("if "); - emit_expression(g, &if_stmt.condition)?; - g.write(" {\n"); - - g.indent_inc(); - emit_block(g, &if_stmt.then_branch)?; - g.indent_dec(); - - g.write_indent(); - g.write("}"); - - if let Some(ref else_branch) = if_stmt.else_branch { - g.write(" else "); - match else_branch { - crate::ast::ElseBranch::Else(block) => { - g.write("{\n"); - g.indent_inc(); - emit_block(g, block)?; - g.indent_dec(); - g.write_indent(); - g.write("}"); - } - crate::ast::ElseBranch::ElseIf(elif) => { - emit_statement(g, &Statement::If(*elif.clone()))?; - return Ok(()); - } - } - } - - g.write("\n"); - Ok(()) - } - - Statement::While(while_stmt) => { - g.write_indent(); - g.write("while "); - emit_expression(g, &while_stmt.condition)?; - g.write(" {\n"); - - g.indent_inc(); - emit_block(g, &while_stmt.body)?; - g.indent_dec(); - - g.writeln("}"); - Ok(()) - } - - Statement::For(for_stmt) => { - g.write_indent(); - - let var_name = g.interner().resolve(&for_stmt.value.symbol); - - if let Some(ref index_var) = for_stmt.index { - let index_name = g.interner().resolve(&index_var.symbol); - g.write(&format!("for ({}, {}) in ", index_name, var_name)); - emit_expression(g, &for_stmt.iterable)?; - g.write(".iter().enumerate()"); - } else { - g.write(&format!("for {} in ", var_name)); - emit_expression(g, &for_stmt.iterable)?; - } - - g.write(" {\n"); - - g.indent_inc(); - emit_block(g, &for_stmt.body)?; - g.indent_dec(); - - g.writeln("}"); - Ok(()) - } - - Statement::Loop(loop_stmt) => { - g.write_indent(); - g.write("loop {\n"); - - g.indent_inc(); - emit_block(g, &loop_stmt.body)?; - g.indent_dec(); - - g.writeln("}"); - Ok(()) - } - - Statement::Break(_) => { - g.writeln("break;"); - Ok(()) - } - - Statement::Continue(_) => { - g.writeln("continue;"); - Ok(()) - } - - Statement::Block(block) => { - g.write_indent(); - g.write("{\n"); - g.indent_inc(); - emit_block(g, block)?; - g.indent_dec(); - g.writeln("}"); - Ok(()) - } - - Statement::Switch(switch_stmt) => { - g.write_indent(); - g.write("match &"); - emit_expression(g, &switch_stmt.scrutinee)?; - g.write(" {\n"); - - g.indent_inc(); - - for case in &switch_stmt.cases { - g.write_indent(); - emit_switch_pattern(g, &case.pattern)?; - g.write(" => {\n"); - g.indent_inc(); - emit_block(g, &case.body)?; - g.indent_dec(); - g.writeln("},"); - } - - if let Some(ref default) = switch_stmt.default { - g.writeln("_ => {"); - g.indent_inc(); - emit_block(g, default)?; - g.indent_dec(); - g.writeln("},"); - } - - g.indent_dec(); - g.writeln("}"); - Ok(()) - } - - Statement::Throw(throw_stmt) => { - g.write_indent(); - g.write("return Err("); - emit_expression(g, &throw_stmt.value)?; - g.write(");\n"); - Ok(()) - } - } -} - -#[cfg(test)] -mod tests { - #[test] - fn test_module_exists() { - assert!(true); - } -} diff --git a/namlc/src/codegen/rust/types.rs b/namlc/src/codegen/rust/types.rs deleted file mode 100644 index 96bb3c0..0000000 --- a/namlc/src/codegen/rust/types.rs +++ /dev/null @@ -1,161 +0,0 @@ -/// -/// Type Mappings (naml → Rust) -/// -/// Converts naml types to their Rust equivalents: -/// - int → i64 -/// - uint → u64 -/// - float → f64 -/// - bool → bool -/// - string → String -/// - [T] → Vec -/// - option → Option -/// - map → std::collections::HashMap -/// - -use lasso::Rodeo; - -use crate::ast::NamlType; - -pub fn naml_to_rust(ty: &NamlType, interner: &Rodeo) -> String { - naml_to_rust_in_context(ty, interner, None) -} - -pub fn naml_to_rust_in_struct(ty: &NamlType, interner: &Rodeo, struct_name: &str) -> String { - naml_to_rust_in_context(ty, interner, Some(struct_name)) -} - -fn type_references_struct(ty: &NamlType, interner: &Rodeo, struct_name: &str) -> bool { - match ty { - NamlType::Named(ident) => interner.resolve(&ident.symbol) == struct_name, - NamlType::Generic(name, _) => interner.resolve(&name.symbol) == struct_name, - _ => false, - } -} - -fn naml_to_rust_in_context(ty: &NamlType, interner: &Rodeo, current_struct: Option<&str>) -> String { - match ty { - NamlType::Int => "i64".to_string(), - NamlType::Uint => "u64".to_string(), - NamlType::Float => "f64".to_string(), - NamlType::Bool => "bool".to_string(), - NamlType::String => "String".to_string(), - NamlType::Bytes => "Vec".to_string(), - NamlType::Unit => "()".to_string(), - - NamlType::Array(elem_ty) => { - let elem = naml_to_rust_in_context(elem_ty, interner, current_struct); - if let Some(struct_name) = current_struct { - if type_references_struct(elem_ty, interner, struct_name) { - return format!("Vec>", elem); - } - } - format!("Vec<{}>", elem) - } - - NamlType::FixedArray(elem_ty, size) => { - let elem = naml_to_rust_in_context(elem_ty, interner, current_struct); - format!("[{}; {}]", elem, size) - } - - NamlType::Option(inner_ty) => { - let inner = naml_to_rust_in_context(inner_ty, interner, current_struct); - if let Some(struct_name) = current_struct { - if type_references_struct(inner_ty, interner, struct_name) { - return format!("Option>", inner); - } - } - format!("Option<{}>", inner) - } - - NamlType::Map(key_ty, val_ty) => { - let key = naml_to_rust_in_context(key_ty, interner, current_struct); - let val = naml_to_rust_in_context(val_ty, interner, current_struct); - format!("std::collections::HashMap<{}, {}>", key, val) - } - - NamlType::Channel(inner_ty) => { - let inner = naml_to_rust_in_context(inner_ty, interner, current_struct); - format!("tokio::sync::mpsc::Sender<{}>", inner) - } - - NamlType::Promise(inner_ty) => { - let inner = naml_to_rust_in_context(inner_ty, interner, current_struct); - format!("std::pin::Pin + Send>>", inner) - } - - NamlType::Named(ident) => { - interner.resolve(&ident.symbol).to_string() - } - - NamlType::Generic(name, type_args) => { - let base_name = interner.resolve(&name.symbol); - let args: Vec = type_args - .iter() - .map(|t| naml_to_rust_in_context(t, interner, current_struct)) - .collect(); - format!("{}<{}>", base_name, args.join(", ")) - } - - NamlType::Function { params, returns } => { - let param_types: Vec = params - .iter() - .map(|t| naml_to_rust_in_context(t, interner, current_struct)) - .collect(); - let return_type = naml_to_rust_in_context(returns, interner, current_struct); - format!("fn({}) -> {}", param_types.join(", "), return_type) - } - - NamlType::Decimal { precision, scale } => { - format!("Decimal<{}, {}>", precision, scale) - } - - NamlType::Inferred => "/* inferred */".to_string(), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use lasso::Rodeo; - - #[test] - fn test_primitive_types() { - let interner = Rodeo::new(); - - assert_eq!(naml_to_rust(&NamlType::Int, &interner), "i64"); - assert_eq!(naml_to_rust(&NamlType::Uint, &interner), "u64"); - assert_eq!(naml_to_rust(&NamlType::Float, &interner), "f64"); - assert_eq!(naml_to_rust(&NamlType::Bool, &interner), "bool"); - assert_eq!(naml_to_rust(&NamlType::String, &interner), "String"); - } - - #[test] - fn test_array_type() { - let interner = Rodeo::new(); - - let arr_ty = NamlType::Array(Box::new(NamlType::Int)); - assert_eq!(naml_to_rust(&arr_ty, &interner), "Vec"); - } - - #[test] - fn test_option_type() { - let interner = Rodeo::new(); - - let opt_ty = NamlType::Option(Box::new(NamlType::String)); - assert_eq!(naml_to_rust(&opt_ty, &interner), "Option"); - } - - #[test] - fn test_map_type() { - let interner = Rodeo::new(); - - let map_ty = NamlType::Map( - Box::new(NamlType::String), - Box::new(NamlType::Int), - ); - assert_eq!( - naml_to_rust(&map_ty, &interner), - "std::collections::HashMap" - ); - } -} diff --git a/namlc/src/diagnostic.rs b/namlc/src/diagnostic.rs index fb8f320..d5c65c7 100644 --- a/namlc/src/diagnostic.rs +++ b/namlc/src/diagnostic.rs @@ -1,14 +1,14 @@ -/// -/// Diagnostic Module - Rich Error Reporting -/// -/// This module provides error reporting with source context using miette. -/// Errors display line numbers, column positions, and source code snippets. -/// -/// Usage: -/// let reporter = DiagnosticReporter::new(&source_file); -/// reporter.report_parse_errors(&errors); -/// reporter.report_type_errors(&errors); -/// +//! +//! Diagnostic Module - Rich Error Reporting +//! +//! This module provides error reporting with source context using miette. +//! Errors display line numbers, column positions, and source code snippets. +//! +//! Usage: +//! let reporter = DiagnosticReporter::new(&source_file); +//! reporter.report_parse_errors(&errors); +//! reporter.report_type_errors(&errors); +//! use miette::{Diagnostic, LabeledSpan, NamedSource, Report, SourceSpan}; use thiserror::Error; diff --git a/namlc/src/lexer/mod.rs b/namlc/src/lexer/mod.rs index 6c3cbdd..54f2563 100644 --- a/namlc/src/lexer/mod.rs +++ b/namlc/src/lexer/mod.rs @@ -1,22 +1,22 @@ -/// -/// Lexer Module - Zero-Copy Tokenization -/// -/// This module handles tokenization of naml source code. It produces a -/// stream of tokens that the parser consumes to build the AST. -/// -/// Key design decisions: -/// - Zero-copy: Tokens reference the source string, no allocations per token -/// - String interning: Identifiers and strings stored via lasso::Spur -/// - Whitespace/comments filtered out for fast parsing (no trivia in output) -/// -/// Token categories: -/// - Keywords: fn, var, const, if, while, for, etc. -/// - Identifiers: User-defined names -/// - Literals: Numbers, strings, booleans -/// - Operators: +, -, *, /, ==, etc. -/// - Delimiters: (, ), {, }, [, ], etc. -/// - Trivia: Whitespace, comments (preserved but skippable) -/// +//! +//! Lexer Module - Zero-Copy Tokenization +//! +//! This module handles tokenization of naml source code. It produces a +//! stream of tokens that the parser consumes to build the AST. +//! +//! Key design decisions: +//! - Zero-copy: Tokens reference the source string, no allocations per token +//! - String interning: Identifiers and strings stored via lasso::Spur +//! - Whitespace/comments filtered out for fast parsing (no trivia in output) +//! +//! Token categories: +//! - Keywords: fn, var, const, if, while, for, etc. +//! - Identifiers: User-defined names +//! - Literals: Numbers, strings, booleans +//! - Operators: +, -, *, /, ==, etc. +//! - Delimiters: (, ), {, }, [, ], etc. +//! - Trivia: Whitespace, comments (preserved but skippable) +//! use crate::source::Span; use lasso::{Rodeo, Spur}; diff --git a/namlc/src/lib.rs b/namlc/src/lib.rs index 5a52176..09d280f 100644 --- a/namlc/src/lib.rs +++ b/namlc/src/lib.rs @@ -1,28 +1,30 @@ -/// -/// namlc - The naml Compiler Library -/// -/// This crate provides the core compiler infrastructure for the naml -/// programming language. It includes: -/// -/// - source: Source file handling, spans, and diagnostics -/// - lexer: Tokenization of naml source code -/// - ast: Abstract syntax tree definitions -/// - parser: Parsing tokens into AST -/// - typechecker: Type system and inference -/// - codegen: Rust code generation (transpilation) -/// -/// Entry points: -/// - `tokenize`: Convert source text into tokens -/// - `parse`: Parse tokens into AST -/// - `check`: Type check an AST -/// - `codegen`: Generate Rust code from AST -/// +//! +//! namlc - The naml Compiler Library +//! +//! This crate provides the core compiler infrastructure for the naml +//! programming language. It includes: +//! +//! - source: Source file handling, spans, and diagnostics +//! - lexer: Tokenization of naml source code +//! - ast: Abstract syntax tree definitions +//! - parser: Parsing tokens into AST +//! - typechecker: Type system and inference +//! - codegen: Cranelift JIT code generation +//! - runtime: Runtime support (arrays, strings, memory management) +//! +//! Entry points: +//! - `tokenize`: Convert source text into tokens +//! - `parse`: Parse tokens into AST +//! - `check`: Type check an AST +//! - `compile_and_run`: JIT compile and execute +//! pub mod ast; pub mod codegen; pub mod diagnostic; pub mod lexer; pub mod parser; +pub mod runtime; pub mod source; pub mod typechecker; diff --git a/namlc/src/main.rs b/namlc/src/main.rs index a268707..e6821aa 100644 --- a/namlc/src/main.rs +++ b/namlc/src/main.rs @@ -1,12 +1,12 @@ -/// -/// naml CLI - The naml programming language command-line interface -/// -/// Provides commands for running, building, and checking naml code: -/// - naml run : Transpile to Rust and execute -/// - naml build: Compile to native binary or WASM -/// - naml check: Type check without building -/// - naml init: Create a new project -/// +//! +//! naml CLI - The naml programming language command-line interface +//! +//! Provides commands for running, building, and checking naml code: +//! - naml run : Transpile to Rust and execute +//! - naml build: Compile to native binary or WASM +//! - naml check: Type check without building +//! - naml init: Create a new project +//! use clap::{Parser, Subcommand}; use std::path::PathBuf; @@ -23,42 +23,24 @@ struct Cli { #[derive(Subcommand)] enum Commands { - /// Execute a naml file (transpile to Rust and run) Run { - /// The file to run file: PathBuf, - - /// Use cached compilation for faster startup #[arg(long)] cached: bool, }, - - /// Build a naml project Build { - /// Target platform (native, server, browser) #[arg(long, default_value = "native")] target: String, - - /// Build in release mode #[arg(long)] release: bool, }, - - /// Type check without building Check { - /// File or directory to check path: Option, }, - - /// Initialize a new naml project Init { - /// Project name name: Option, }, - - /// Run tests Test { - /// Filter tests by name filter: Option, }, } diff --git a/namlc/src/parser/combinators.rs b/namlc/src/parser/combinators.rs index ebd1386..dae4007 100644 --- a/namlc/src/parser/combinators.rs +++ b/namlc/src/parser/combinators.rs @@ -1,8 +1,8 @@ -/// -/// Base Combinators for Token Parsing -/// -/// Reusable nom combinators for matching tokens, keywords, and identifiers. -/// +//! +//! Base Combinators for Token Parsing +//! +//! Reusable nom combinators for matching tokens, keywords, and identifiers. +//! use nom::error::{ErrorKind, ParseError}; use nom::{IResult, InputTake}; diff --git a/namlc/src/parser/error.rs b/namlc/src/parser/error.rs index e1f2047..f559037 100644 --- a/namlc/src/parser/error.rs +++ b/namlc/src/parser/error.rs @@ -1,16 +1,16 @@ -/// -/// Parser Error Types -/// -/// This module defines error types for the parser. Errors carry source -/// location information (Span) for precise error reporting. -/// -/// Error categories: -/// - Expected: A specific token or construct was expected but not found -/// - Unexpected: An unexpected token was encountered -/// - InvalidSyntax: The syntax is malformed -/// -/// Errors integrate with miette for rich error display. -/// +//! +//! Parser Error Types +//! +//! This module defines error types for the parser. Errors carry source +//! location information (Span) for precise error reporting. +//! +//! Error categories: +//! - Expected: A specific token or construct was expected but not found +//! - Unexpected: An unexpected token was encountered +//! - InvalidSyntax: The syntax is malformed +//! +//! Errors integrate with miette for rich error display. +//! use crate::lexer::{Keyword, TokenKind}; use crate::source::Span; diff --git a/namlc/src/parser/expressions.rs b/namlc/src/parser/expressions.rs index 2c8c494..2b96634 100644 --- a/namlc/src/parser/expressions.rs +++ b/namlc/src/parser/expressions.rs @@ -1,9 +1,9 @@ -/// -/// Expression Parser -/// -/// Parses expressions using nom combinators with Pratt-style precedence. -/// Uses arena allocation for all nested expression nodes. -/// +//! +//! Expression Parser +//! +//! Parses expressions using nom combinators with Pratt-style precedence. +//! Uses arena allocation for all nested expression nodes. +//! use nom::branch::alt; use nom::combinator::map; diff --git a/namlc/src/parser/input.rs b/namlc/src/parser/input.rs index 6915e59..cfbc22a 100644 --- a/namlc/src/parser/input.rs +++ b/namlc/src/parser/input.rs @@ -1,9 +1,9 @@ -/// -/// TokenStream Input Type for nom -/// -/// This module provides a custom input type that wraps a slice of tokens. -/// nom requires specific traits to be implemented for custom input types. -/// +//! +//! TokenStream Input Type for nom +//! +//! This module provides a custom input type that wraps a slice of tokens. +//! nom requires specific traits to be implemented for custom input types. +//! use std::iter::Enumerate; use std::slice::Iter; diff --git a/namlc/src/parser/items.rs b/namlc/src/parser/items.rs index 7ad67ff..b50cacb 100644 --- a/namlc/src/parser/items.rs +++ b/namlc/src/parser/items.rs @@ -1,9 +1,9 @@ -/// -/// Item Parser -/// -/// Parses top-level items using nom combinators. -/// Handles functions, structs, enums, interfaces, exceptions, imports, and extern. -/// +//! +//! Item Parser +//! +//! Parses top-level items using nom combinators. +//! Handles functions, structs, enums, interfaces, exceptions, imports, and extern. +//! use nom::multi::separated_list0; diff --git a/namlc/src/parser/mod.rs b/namlc/src/parser/mod.rs index 3ce5b00..b6c505b 100644 --- a/namlc/src/parser/mod.rs +++ b/namlc/src/parser/mod.rs @@ -1,27 +1,30 @@ -/// -/// Parser Module - nom-based Token Parsing -/// -/// This module provides the parser for the naml programming language. -/// It uses nom parser combinators to parse a stream of tokens into an AST. -/// -/// The parser is structured as follows: -/// - input: TokenStream type for nom integration -/// - combinators: Reusable token-matching combinators -/// - types: Type annotation parsing -/// - expressions: Expression parsing with Pratt precedence -/// - statements: Statement parsing -/// - items: Top-level item parsing -/// -/// Entry point: parse() function takes tokens and returns a SourceFile AST. -/// +//! +//! Parser Module - nom-based Token Parsing +//! +//! This module provides the parser for the naml programming language. +//! It uses nom parser combinators to parse a stream of tokens into an AST. +//! +//! The parser is structured as follows: +//! - input: TokenStream type for nom integration +//! - combinators: Reusable token-matching combinators +//! - types: Type annotation parsing +//! - expressions: Expression parsing with Pratt precedence +//! - statements: Statement parsing +//! - items: Top-level item parsing +//! +//! Entry point: parse() function takes tokens and returns a SourceFile AST. +//! mod combinators; mod expressions; mod input; mod items; +mod patterns; mod statements; mod types; +pub use patterns::parse_pattern; + pub use combinators::{PError, PErrorKind}; pub use input::TokenStream; diff --git a/namlc/src/parser/patterns.rs b/namlc/src/parser/patterns.rs new file mode 100644 index 0000000..9530f0c --- /dev/null +++ b/namlc/src/parser/patterns.rs @@ -0,0 +1,280 @@ +//! +//! Pattern Parser +//! +//! Parses patterns for switch cases and destructuring. +//! Supports: literals, identifiers, enum variants with bindings, wildcards. +//! +//! Pattern types: +//! - Literal: Match against a literal value (int, float, string, bool, none) +//! - Identifier: Bind a value to a name or match against a constant +//! - Variant: Match an enum variant, optionally with bindings (e.g., Some(x)) +//! - Wildcard: Match anything and discard (_) +//! +//! The parser determines pattern type by examining the token: +//! - An identifier "_" is the wildcard pattern +//! - An identifier followed by :: is a path (enum variant) +//! - An identifier followed by ( is a variant pattern with bindings +//! - Other identifiers are identifier patterns (bindings or constants) +//! - Literals (int, float, string, true/false, none) become literal patterns +//! + +use nom::InputTake; + +use crate::ast::{ + IdentPattern, Literal, LiteralPattern, Pattern, VariantPattern, WildcardPattern, +}; +use crate::lexer::{Keyword, TokenKind}; + +use super::combinators::*; +use super::input::TokenStream; + +pub fn parse_pattern<'a, 'ast>(input: TokenStream<'a>) -> PResult<'a, Pattern<'ast>> { + match input.first().map(|t| t.kind) { + Some(TokenKind::Ident) => parse_ident_or_variant_pattern(input), + Some(TokenKind::IntLit) => parse_int_pattern(input), + Some(TokenKind::FloatLit) => parse_float_pattern(input), + Some(TokenKind::StringLit) => parse_string_pattern(input), + Some(TokenKind::Keyword(Keyword::True)) => parse_bool_pattern(input, true), + Some(TokenKind::Keyword(Keyword::False)) => parse_bool_pattern(input, false), + Some(TokenKind::Keyword(Keyword::None)) => parse_none_pattern(input), + _ => Err(nom::Err::Error(PError { + input, + kind: PErrorKind::ExpectedExpr, + })), + } +} + +fn parse_ident_or_variant_pattern<'a, 'ast>( + input: TokenStream<'a>, +) -> PResult<'a, Pattern<'ast>> { + let first_tok = input.first().unwrap(); + let first_text = input.span_text(first_tok.span); + + if first_text == "_" { + let (input, _) = input.take_split(1); + return Ok(( + input, + Pattern::Wildcard(WildcardPattern { + span: first_tok.span, + }), + )); + } + + let (mut input, first) = ident(input)?; + let start_span = first.span; + let mut path = vec![first]; + + while check(TokenKind::ColonColon)(input) { + let (new_input, _) = token(TokenKind::ColonColon)(input)?; + let (new_input, segment) = ident(new_input)?; + path.push(segment); + input = new_input; + } + + if check(TokenKind::LParen)(input) { + let (new_input, _) = token(TokenKind::LParen)(input)?; + input = new_input; + + let mut bindings = Vec::new(); + if !check(TokenKind::RParen)(input) { + let (new_input, binding) = ident(input)?; + bindings.push(binding); + input = new_input; + + while check(TokenKind::Comma)(input) { + let (new_input, _) = token(TokenKind::Comma)(input)?; + let (new_input, binding) = ident(new_input)?; + bindings.push(binding); + input = new_input; + } + } + + let (new_input, end) = token(TokenKind::RParen)(input)?; + let span = start_span.merge(end.span); + return Ok(( + new_input, + Pattern::Variant(VariantPattern { + path, + bindings, + span, + }), + )); + } + + if path.len() > 1 { + let end_span = path.last().unwrap().span; + return Ok(( + input, + Pattern::Variant(VariantPattern { + path, + bindings: Vec::new(), + span: start_span.merge(end_span), + }), + )); + } + + Ok(( + input, + Pattern::Identifier(IdentPattern { + ident: path.remove(0), + span: start_span, + }), + )) +} + +fn parse_int_pattern<'a, 'ast>(input: TokenStream<'a>) -> PResult<'a, Pattern<'ast>> { + let (input, (value, span)) = int_lit(input)?; + Ok(( + input, + Pattern::Literal(LiteralPattern { + value: Literal::Int(value), + span, + }), + )) +} + +fn parse_float_pattern<'a, 'ast>(input: TokenStream<'a>) -> PResult<'a, Pattern<'ast>> { + let (input, (value, span)) = float_lit(input)?; + Ok(( + input, + Pattern::Literal(LiteralPattern { + value: Literal::Float(value), + span, + }), + )) +} + +fn parse_string_pattern<'a, 'ast>(input: TokenStream<'a>) -> PResult<'a, Pattern<'ast>> { + let (input, (symbol, span)) = string_lit(input)?; + Ok(( + input, + Pattern::Literal(LiteralPattern { + value: Literal::String(symbol), + span, + }), + )) +} + +fn parse_bool_pattern<'a, 'ast>( + input: TokenStream<'a>, + value: bool, +) -> PResult<'a, Pattern<'ast>> { + let kw = if value { Keyword::True } else { Keyword::False }; + let (input, tok) = keyword(kw)(input)?; + Ok(( + input, + Pattern::Literal(LiteralPattern { + value: Literal::Bool(value), + span: tok.span, + }), + )) +} + +fn parse_none_pattern<'a, 'ast>(input: TokenStream<'a>) -> PResult<'a, Pattern<'ast>> { + let (input, tok) = keyword(Keyword::None)(input)?; + Ok(( + input, + Pattern::Literal(LiteralPattern { + value: Literal::None, + span: tok.span, + }), + )) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::lexer::tokenize; + + fn parse_pattern_from_source(source: &str) -> Pattern<'static> { + let (tokens, _interner) = tokenize(source); + let input = TokenStream::new(&tokens, source); + let (_, pattern) = parse_pattern(input).expect("Failed to parse pattern"); + pattern + } + + #[test] + fn test_wildcard_pattern() { + let pattern = parse_pattern_from_source("_"); + assert!(matches!(pattern, Pattern::Wildcard(_))); + } + + #[test] + fn test_identifier_pattern() { + let pattern = parse_pattern_from_source("x"); + assert!(matches!(pattern, Pattern::Identifier(_))); + } + + #[test] + fn test_int_literal_pattern() { + let pattern = parse_pattern_from_source("42"); + if let Pattern::Literal(lit) = pattern { + assert!(matches!(lit.value, Literal::Int(42))); + } else { + panic!("Expected literal pattern"); + } + } + + #[test] + fn test_string_literal_pattern() { + let pattern = parse_pattern_from_source("\"hello\""); + if let Pattern::Literal(lit) = pattern { + assert!(matches!(lit.value, Literal::String(_))); + } else { + panic!("Expected literal pattern"); + } + } + + #[test] + fn test_bool_literal_pattern() { + let pattern = parse_pattern_from_source("true"); + if let Pattern::Literal(lit) = pattern { + assert!(matches!(lit.value, Literal::Bool(true))); + } else { + panic!("Expected literal pattern"); + } + } + + #[test] + fn test_none_literal_pattern() { + let pattern = parse_pattern_from_source("none"); + if let Pattern::Literal(lit) = pattern { + assert!(matches!(lit.value, Literal::None)); + } else { + panic!("Expected literal pattern"); + } + } + + #[test] + fn test_variant_pattern_simple() { + let pattern = parse_pattern_from_source("Status::Active"); + if let Pattern::Variant(variant) = pattern { + assert_eq!(variant.path.len(), 2); + assert!(variant.bindings.is_empty()); + } else { + panic!("Expected variant pattern"); + } + } + + #[test] + fn test_variant_pattern_with_binding() { + let pattern = parse_pattern_from_source("Some(value)"); + if let Pattern::Variant(variant) = pattern { + assert_eq!(variant.path.len(), 1); + assert_eq!(variant.bindings.len(), 1); + } else { + panic!("Expected variant pattern"); + } + } + + #[test] + fn test_variant_pattern_with_multiple_bindings() { + let pattern = parse_pattern_from_source("Result::Ok(a, b)"); + if let Pattern::Variant(variant) = pattern { + assert_eq!(variant.path.len(), 2); + assert_eq!(variant.bindings.len(), 2); + } else { + panic!("Expected variant pattern"); + } + } +} diff --git a/namlc/src/parser/statements.rs b/namlc/src/parser/statements.rs index 84763a0..e7d1071 100644 --- a/namlc/src/parser/statements.rs +++ b/namlc/src/parser/statements.rs @@ -1,8 +1,8 @@ -/// -/// Statement Parser -/// -/// Parses statements using nom combinators. -/// +//! +//! Statement Parser +//! +//! Parses statements using nom combinators. +//! use nom::InputTake; @@ -334,7 +334,7 @@ fn parse_switch_stmt<'a, 'ast>( if check_keyword(Keyword::Case)(input) { let (new_input, _) = keyword(Keyword::Case)(input)?; - let (new_input, pattern) = parse_expression(arena, new_input)?; + let (new_input, pattern) = super::parse_pattern(new_input)?; let pattern_span = pattern.span(); let (new_input, _) = token(TokenKind::Colon)(new_input)?; let (new_input, body) = parse_block(arena, new_input)?; diff --git a/namlc/src/parser/types.rs b/namlc/src/parser/types.rs index 892d21b..2b81d62 100644 --- a/namlc/src/parser/types.rs +++ b/namlc/src/parser/types.rs @@ -1,9 +1,9 @@ -/// -/// Type Annotation Parser -/// -/// Parses type annotations using nom combinators. -/// Supports primitives, arrays, generics, tuples, and function types. -/// +//! +//! Type Annotation Parser +//! +//! Parses type annotations using nom combinators. +//! Supports primitives, arrays, generics, tuples, and function types. +//! use nom::branch::alt; use nom::combinator::{map, opt}; diff --git a/namlc/src/runtime/array.rs b/namlc/src/runtime/array.rs new file mode 100644 index 0000000..12ab81d --- /dev/null +++ b/namlc/src/runtime/array.rs @@ -0,0 +1,360 @@ +//! +//! Runtime Array Operations +//! +//! Provides heap-allocated, reference-counted arrays for naml. +//! Arrays are generic over element type at the naml level, but at runtime +//! we store elements as 64-bit values (either primitives or pointers). +//! + +use std::alloc::{alloc, dealloc, realloc, Layout}; + +use super::value::{HeapHeader, HeapTag}; + +/// A heap-allocated array of i64 values +/// (All naml values are represented as i64 at runtime) +#[repr(C)] +pub struct NamlArray { + pub header: HeapHeader, + pub len: usize, + pub capacity: usize, + pub data: *mut i64, +} + +/// Create a new empty array with given initial capacity +#[unsafe(no_mangle)] +pub extern "C" fn naml_array_new(capacity: usize) -> *mut NamlArray { + unsafe { + let layout = Layout::new::(); + let ptr = alloc(layout) as *mut NamlArray; + if ptr.is_null() { + panic!("Failed to allocate array"); + } + + let cap = if capacity == 0 { 4 } else { capacity }; + let data_layout = Layout::array::(cap).unwrap(); + let data = alloc(data_layout) as *mut i64; + if data.is_null() { + dealloc(ptr as *mut u8, layout); + panic!("Failed to allocate array data"); + } + + (*ptr).header = HeapHeader::new(HeapTag::Array); + (*ptr).len = 0; + (*ptr).capacity = cap; + (*ptr).data = data; + + ptr + } +} + +/// Create an array from existing values +#[unsafe(no_mangle)] +pub extern "C" fn naml_array_from(values: *const i64, len: usize) -> *mut NamlArray { + unsafe { + let arr = naml_array_new(len); + if len > 0 && !values.is_null() { + std::ptr::copy_nonoverlapping(values, (*arr).data, len); + (*arr).len = len; + } + arr + } +} + +/// Increment reference count +#[unsafe(no_mangle)] +pub extern "C" fn naml_array_incref(arr: *mut NamlArray) { + if !arr.is_null() { + unsafe { (*arr).header.incref(); } + } +} + +/// Decrement reference count and free if zero (for arrays of primitives) +#[unsafe(no_mangle)] +pub extern "C" fn naml_array_decref(arr: *mut NamlArray) { + if !arr.is_null() { + unsafe { + if (*arr).header.decref() { + let data_layout = Layout::array::((*arr).capacity).unwrap(); + dealloc((*arr).data as *mut u8, data_layout); + + let layout = Layout::new::(); + dealloc(arr as *mut u8, layout); + } + } + } +} + +/// Decrement reference count and free if zero, also decref string elements +#[unsafe(no_mangle)] +pub extern "C" fn naml_array_decref_strings(arr: *mut NamlArray) { + if !arr.is_null() { + unsafe { + if (*arr).header.decref() { + // Decref each string element + for i in 0..(*arr).len { + let elem = *(*arr).data.add(i); + if elem != 0 { + super::value::naml_string_decref(elem as *mut super::value::NamlString); + } + } + + let data_layout = Layout::array::((*arr).capacity).unwrap(); + dealloc((*arr).data as *mut u8, data_layout); + + let layout = Layout::new::(); + dealloc(arr as *mut u8, layout); + } + } + } +} + +/// Decrement reference count and free if zero, also decref nested array elements +#[unsafe(no_mangle)] +pub extern "C" fn naml_array_decref_arrays(arr: *mut NamlArray) { + if !arr.is_null() { + unsafe { + if (*arr).header.decref() { + // Decref each nested array element + for i in 0..(*arr).len { + let elem = *(*arr).data.add(i); + if elem != 0 { + naml_array_decref(elem as *mut NamlArray); + } + } + + let data_layout = Layout::array::((*arr).capacity).unwrap(); + dealloc((*arr).data as *mut u8, data_layout); + + let layout = Layout::new::(); + dealloc(arr as *mut u8, layout); + } + } + } +} + +/// Decrement reference count and free if zero, also decref map elements +#[unsafe(no_mangle)] +pub extern "C" fn naml_array_decref_maps(arr: *mut NamlArray) { + if !arr.is_null() { + unsafe { + if (*arr).header.decref() { + // Decref each map element + for i in 0..(*arr).len { + let elem = *(*arr).data.add(i); + if elem != 0 { + super::map::naml_map_decref(elem as *mut super::map::NamlMap); + } + } + + let data_layout = Layout::array::((*arr).capacity).unwrap(); + dealloc((*arr).data as *mut u8, data_layout); + + let layout = Layout::new::(); + dealloc(arr as *mut u8, layout); + } + } + } +} + +/// Decrement reference count and free if zero, also decref struct elements +#[unsafe(no_mangle)] +pub extern "C" fn naml_array_decref_structs(arr: *mut NamlArray) { + if !arr.is_null() { + unsafe { + if (*arr).header.decref() { + // Decref each struct element + for i in 0..(*arr).len { + let elem = *(*arr).data.add(i); + if elem != 0 { + super::value::naml_struct_decref(elem as *mut super::value::NamlStruct); + } + } + + let data_layout = Layout::array::((*arr).capacity).unwrap(); + dealloc((*arr).data as *mut u8, data_layout); + + let layout = Layout::new::(); + dealloc(arr as *mut u8, layout); + } + } + } +} + +/// Get array length +#[unsafe(no_mangle)] +pub extern "C" fn naml_array_len(arr: *const NamlArray) -> i64 { + if arr.is_null() { + 0 + } else { + unsafe { (*arr).len as i64 } + } +} + +/// Get element at index (returns 0 if out of bounds) +#[unsafe(no_mangle)] +pub extern "C" fn naml_array_get(arr: *const NamlArray, index: i64) -> i64 { + if arr.is_null() { + return 0; + } + + unsafe { + let idx = index as usize; + if idx >= (*arr).len { + return 0; + } + *(*arr).data.add(idx) + } +} + +/// Set element at index (no-op if out of bounds) +#[unsafe(no_mangle)] +pub extern "C" fn naml_array_set(arr: *mut NamlArray, index: i64, value: i64) { + if arr.is_null() { + return; + } + + unsafe { + let idx = index as usize; + if idx < (*arr).len { + *(*arr).data.add(idx) = value; + } + } +} + +/// Push element to end of array +#[unsafe(no_mangle)] +pub extern "C" fn naml_array_push(arr: *mut NamlArray, value: i64) { + if arr.is_null() { + return; + } + + unsafe { + if (*arr).len >= (*arr).capacity { + // Grow the array + let new_capacity = (*arr).capacity * 2; + let old_layout = Layout::array::((*arr).capacity).unwrap(); + let new_layout = Layout::array::(new_capacity).unwrap(); + + let new_data = realloc((*arr).data as *mut u8, old_layout, new_layout.size()) as *mut i64; + if new_data.is_null() { + panic!("Failed to grow array"); + } + + (*arr).data = new_data; + (*arr).capacity = new_capacity; + } + + *(*arr).data.add((*arr).len) = value; + (*arr).len += 1; + } +} + +/// Pop element from end of array (returns 0 if empty) +#[unsafe(no_mangle)] +pub extern "C" fn naml_array_pop(arr: *mut NamlArray) -> i64 { + if arr.is_null() { + return 0; + } + + unsafe { + if (*arr).len == 0 { + return 0; + } + + (*arr).len -= 1; + *(*arr).data.add((*arr).len) + } +} + +/// Check if array contains a value +#[unsafe(no_mangle)] +pub extern "C" fn naml_array_contains(arr: *const NamlArray, value: i64) -> i64 { + if arr.is_null() { + return 0; + } + + unsafe { + for i in 0..(*arr).len { + if *(*arr).data.add(i) == value { + return 1; + } + } + 0 + } +} + +/// Create a copy of the array +#[unsafe(no_mangle)] +pub extern "C" fn naml_array_clone(arr: *const NamlArray) -> *mut NamlArray { + if arr.is_null() { + return naml_array_new(0); + } + + unsafe { + naml_array_from((*arr).data, (*arr).len) + } +} + +/// Print array contents (for debugging) +#[unsafe(no_mangle)] +pub extern "C" fn naml_array_print(arr: *const NamlArray) { + if arr.is_null() { + print!("[]"); + return; + } + + unsafe { + print!("["); + for i in 0..(*arr).len { + if i > 0 { + print!(", "); + } + print!("{}", *(*arr).data.add(i)); + } + print!("]"); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_array_creation() { + let arr = naml_array_new(10); + assert!(!arr.is_null()); + unsafe { + assert_eq!((*arr).len, 0); + assert_eq!((*arr).capacity, 10); + naml_array_decref(arr); + } + } + + #[test] + fn test_array_push_get() { + let arr = naml_array_new(2); + naml_array_push(arr, 10); + naml_array_push(arr, 20); + naml_array_push(arr, 30); // Triggers growth + + assert_eq!(naml_array_len(arr), 3); + assert_eq!(naml_array_get(arr, 0), 10); + assert_eq!(naml_array_get(arr, 1), 20); + assert_eq!(naml_array_get(arr, 2), 30); + + naml_array_decref(arr); + } + + #[test] + fn test_array_from() { + let values = [1i64, 2, 3, 4, 5]; + let arr = naml_array_from(values.as_ptr(), values.len()); + + assert_eq!(naml_array_len(arr), 5); + for i in 0..5 { + assert_eq!(naml_array_get(arr, i as i64), (i + 1) as i64); + } + + naml_array_decref(arr); + } +} diff --git a/namlc/src/runtime/channel.rs b/namlc/src/runtime/channel.rs new file mode 100644 index 0000000..f232a84 --- /dev/null +++ b/namlc/src/runtime/channel.rs @@ -0,0 +1,268 @@ +//! +//! Channels for naml +//! +//! Provides bounded channels for communication between tasks. +//! Channels are typed at the naml level but at runtime store i64 values +//! (like all naml values). +//! + +use std::alloc::{alloc, dealloc, Layout}; +use std::collections::VecDeque; +use std::sync::{Mutex, Condvar}; + +use super::value::{HeapHeader, HeapTag}; + +/// A bounded channel for inter-task communication +#[repr(C)] +pub struct NamlChannel { + pub header: HeapHeader, + pub capacity: usize, + inner: Mutex, + not_empty: Condvar, + not_full: Condvar, +} + +struct ChannelInner { + buffer: VecDeque, + closed: bool, +} + +/// Create a new channel with the given capacity +#[unsafe(no_mangle)] +pub extern "C" fn naml_channel_new(capacity: usize) -> *mut NamlChannel { + let cap = if capacity == 0 { 1 } else { capacity }; + + unsafe { + let layout = Layout::new::(); + let ptr = alloc(layout) as *mut NamlChannel; + if ptr.is_null() { + panic!("Failed to allocate channel"); + } + + std::ptr::write(ptr, NamlChannel { + header: HeapHeader::new(HeapTag::Map), // Reusing Map tag for channels + capacity: cap, + inner: Mutex::new(ChannelInner { + buffer: VecDeque::with_capacity(cap), + closed: false, + }), + not_empty: Condvar::new(), + not_full: Condvar::new(), + }); + + ptr + } +} + +/// Increment reference count +#[unsafe(no_mangle)] +pub extern "C" fn naml_channel_incref(ch: *mut NamlChannel) { + if !ch.is_null() { + unsafe { (*ch).header.incref(); } + } +} + +/// Decrement reference count and free if zero +#[unsafe(no_mangle)] +pub extern "C" fn naml_channel_decref(ch: *mut NamlChannel) { + if !ch.is_null() { + unsafe { + if (*ch).header.decref() { + std::ptr::drop_in_place(ch); + let layout = Layout::new::(); + dealloc(ch as *mut u8, layout); + } + } + } +} + +/// Send a value to the channel (blocks if full) +/// Returns 1 on success, 0 if channel is closed +#[unsafe(no_mangle)] +pub extern "C" fn naml_channel_send(ch: *mut NamlChannel, value: i64) -> i64 { + if ch.is_null() { + return 0; + } + + unsafe { + let channel = &*ch; + let mut inner = channel.inner.lock().unwrap(); + + // Wait while buffer is full and channel is open + while inner.buffer.len() >= channel.capacity && !inner.closed { + inner = channel.not_full.wait(inner).unwrap(); + } + + if inner.closed { + return 0; + } + + inner.buffer.push_back(value); + channel.not_empty.notify_one(); + 1 + } +} + +/// Receive a value from the channel (blocks if empty) +/// Returns the value, or 0 if channel is closed and empty +#[unsafe(no_mangle)] +pub extern "C" fn naml_channel_receive(ch: *mut NamlChannel) -> i64 { + if ch.is_null() { + return 0; + } + + unsafe { + let channel = &*ch; + let mut inner = channel.inner.lock().unwrap(); + + // Wait while buffer is empty and channel is open + while inner.buffer.is_empty() && !inner.closed { + inner = channel.not_empty.wait(inner).unwrap(); + } + + if let Some(value) = inner.buffer.pop_front() { + channel.not_full.notify_one(); + value + } else { + 0 // Channel closed and empty + } + } +} + +/// Try to send without blocking +/// Returns 1 on success, 0 if would block or closed +#[unsafe(no_mangle)] +pub extern "C" fn naml_channel_try_send(ch: *mut NamlChannel, value: i64) -> i64 { + if ch.is_null() { + return 0; + } + + unsafe { + let channel = &*ch; + let mut inner = channel.inner.lock().unwrap(); + + if inner.closed || inner.buffer.len() >= channel.capacity { + return 0; + } + + inner.buffer.push_back(value); + channel.not_empty.notify_one(); + 1 + } +} + +/// Try to receive without blocking +/// Returns the value in the high bits and success (1) or failure (0) in low bit +/// Use naml_channel_try_receive_value() and naml_channel_try_receive_ok() to extract +#[unsafe(no_mangle)] +pub extern "C" fn naml_channel_try_receive(ch: *mut NamlChannel) -> i64 { + if ch.is_null() { + return 0; + } + + unsafe { + let channel = &*ch; + let mut inner = channel.inner.lock().unwrap(); + + if let Some(value) = inner.buffer.pop_front() { + channel.not_full.notify_one(); + // Pack value and success flag + // For simplicity, just return the value (caller should use try_send for status) + value + } else { + 0 + } + } +} + +/// Close the channel +#[unsafe(no_mangle)] +pub extern "C" fn naml_channel_close(ch: *mut NamlChannel) { + if ch.is_null() { + return; + } + + unsafe { + let channel = &*ch; + let mut inner = channel.inner.lock().unwrap(); + inner.closed = true; + channel.not_empty.notify_all(); + channel.not_full.notify_all(); + } +} + +/// Check if channel is closed +#[unsafe(no_mangle)] +pub extern "C" fn naml_channel_is_closed(ch: *mut NamlChannel) -> i64 { + if ch.is_null() { + return 1; + } + + unsafe { + let channel = &*ch; + let inner = channel.inner.lock().unwrap(); + if inner.closed { 1 } else { 0 } + } +} + +/// Get number of items in channel buffer +#[unsafe(no_mangle)] +pub extern "C" fn naml_channel_len(ch: *mut NamlChannel) -> i64 { + if ch.is_null() { + return 0; + } + + unsafe { + let channel = &*ch; + let inner = channel.inner.lock().unwrap(); + inner.buffer.len() as i64 + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::thread; + + #[test] + fn test_channel_basic() { + let ch = naml_channel_new(2); + assert!(!ch.is_null()); + + assert_eq!(naml_channel_send(ch, 42), 1); + assert_eq!(naml_channel_send(ch, 43), 1); + assert_eq!(naml_channel_receive(ch), 42); + assert_eq!(naml_channel_receive(ch), 43); + + naml_channel_decref(ch); + } + + #[test] + fn test_channel_concurrent() { + let ch = naml_channel_new(10); + + let ch_send = ch as usize; + let sender = thread::spawn(move || { + let ch = ch_send as *mut NamlChannel; + for i in 0..5 { + naml_channel_send(ch, i); + } + }); + + let ch_recv = ch as usize; + let receiver = thread::spawn(move || { + let ch = ch_recv as *mut NamlChannel; + let mut sum = 0i64; + for _ in 0..5 { + sum += naml_channel_receive(ch); + } + sum + }); + + sender.join().unwrap(); + let sum = receiver.join().unwrap(); + assert_eq!(sum, 0 + 1 + 2 + 3 + 4); + + naml_channel_decref(ch); + } +} diff --git a/namlc/src/runtime/map.rs b/namlc/src/runtime/map.rs new file mode 100644 index 0000000..6e5dad4 --- /dev/null +++ b/namlc/src/runtime/map.rs @@ -0,0 +1,460 @@ +//! +//! Map Runtime +//! +//! Hash map implementation for naml map type. +//! Uses string keys with FNV-1a hashing and linear probing. +//! + +use std::alloc::{alloc, alloc_zeroed, dealloc, Layout}; +use super::value::{HeapHeader, HeapTag, NamlString, naml_string_decref}; + +const INITIAL_CAPACITY: usize = 16; +const LOAD_FACTOR: f64 = 0.75; + +#[repr(C)] +pub struct NamlMap { + pub header: HeapHeader, + pub capacity: usize, + pub length: usize, + pub entries: *mut MapEntry, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct MapEntry { + pub key: i64, // Pointer to NamlString or 0 if empty + pub value: i64, // The stored value + pub occupied: bool, +} + +impl Default for MapEntry { + fn default() -> Self { + Self { key: 0, value: 0, occupied: false } + } +} + +fn hash_string(s: *const NamlString) -> u64 { + if s.is_null() { return 0; } + unsafe { + let len = (*s).len; + let data = (*s).data.as_ptr(); + // FNV-1a hash + let mut hash: u64 = 0xcbf29ce484222325; + for i in 0..len { + hash ^= *data.add(i) as u64; + hash = hash.wrapping_mul(0x100000001b3); + } + hash + } +} + +fn string_eq(a: *const NamlString, b: *const NamlString) -> bool { + if a.is_null() && b.is_null() { return true; } + if a.is_null() || b.is_null() { return false; } + unsafe { + if (*a).len != (*b).len { return false; } + let a_slice = std::slice::from_raw_parts((*a).data.as_ptr(), (*a).len); + let b_slice = std::slice::from_raw_parts((*b).data.as_ptr(), (*b).len); + a_slice == b_slice + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn naml_map_new(capacity: usize) -> *mut NamlMap { + let cap = if capacity < INITIAL_CAPACITY { INITIAL_CAPACITY } else { capacity }; + unsafe { + let map_layout = Layout::new::(); + let map_ptr = alloc(map_layout) as *mut NamlMap; + if map_ptr.is_null() { panic!("Failed to allocate map"); } + + let entries_layout = Layout::array::(cap).unwrap(); + let entries_ptr = alloc_zeroed(entries_layout) as *mut MapEntry; + if entries_ptr.is_null() { panic!("Failed to allocate map entries"); } + + (*map_ptr).header = HeapHeader::new(HeapTag::Map); + (*map_ptr).capacity = cap; + (*map_ptr).length = 0; + (*map_ptr).entries = entries_ptr; + map_ptr + } +} + +/// Set a primitive value in the map (no refcount management for values) +#[unsafe(no_mangle)] +pub extern "C" fn naml_map_set(map: *mut NamlMap, key: i64, value: i64) { + if map.is_null() { return; } + unsafe { + if ((*map).length + 1) as f64 / (*map).capacity as f64 > LOAD_FACTOR { + resize_map(map); + } + let hash = hash_string(key as *const NamlString); + let mut idx = (hash as usize) % (*map).capacity; + loop { + let entry = (*map).entries.add(idx); + if !(*entry).occupied { + (*entry).key = key; + (*entry).value = value; + (*entry).occupied = true; + (*map).length += 1; + if key != 0 { (*(key as *mut NamlString)).header.incref(); } + return; + } + if string_eq((*entry).key as *const NamlString, key as *const NamlString) { + (*entry).value = value; + return; + } + idx = (idx + 1) % (*map).capacity; + } + } +} + +/// Set a string value in the map (decrefs old string value when updating) +#[unsafe(no_mangle)] +pub extern "C" fn naml_map_set_string(map: *mut NamlMap, key: i64, value: i64) { + if map.is_null() { return; } + unsafe { + if ((*map).length + 1) as f64 / (*map).capacity as f64 > LOAD_FACTOR { + resize_map(map); + } + let hash = hash_string(key as *const NamlString); + let mut idx = (hash as usize) % (*map).capacity; + loop { + let entry = (*map).entries.add(idx); + if !(*entry).occupied { + (*entry).key = key; + (*entry).value = value; + (*entry).occupied = true; + (*map).length += 1; + if key != 0 { (*(key as *mut NamlString)).header.incref(); } + return; + } + if string_eq((*entry).key as *const NamlString, key as *const NamlString) { + // Decref old string value before replacing + if (*entry).value != 0 { + naml_string_decref((*entry).value as *mut NamlString); + } + (*entry).value = value; + return; + } + idx = (idx + 1) % (*map).capacity; + } + } +} + +/// Set an array value in the map (decrefs old array value when updating) +#[unsafe(no_mangle)] +pub extern "C" fn naml_map_set_array(map: *mut NamlMap, key: i64, value: i64) { + if map.is_null() { return; } + unsafe { + if ((*map).length + 1) as f64 / (*map).capacity as f64 > LOAD_FACTOR { + resize_map(map); + } + let hash = hash_string(key as *const NamlString); + let mut idx = (hash as usize) % (*map).capacity; + loop { + let entry = (*map).entries.add(idx); + if !(*entry).occupied { + (*entry).key = key; + (*entry).value = value; + (*entry).occupied = true; + (*map).length += 1; + if key != 0 { (*(key as *mut NamlString)).header.incref(); } + return; + } + if string_eq((*entry).key as *const NamlString, key as *const NamlString) { + // Decref old array value before replacing + if (*entry).value != 0 { + super::array::naml_array_decref((*entry).value as *mut super::array::NamlArray); + } + (*entry).value = value; + return; + } + idx = (idx + 1) % (*map).capacity; + } + } +} + +/// Set a map value in the map (decrefs old map value when updating) +#[unsafe(no_mangle)] +pub extern "C" fn naml_map_set_map(map: *mut NamlMap, key: i64, value: i64) { + if map.is_null() { return; } + unsafe { + if ((*map).length + 1) as f64 / (*map).capacity as f64 > LOAD_FACTOR { + resize_map(map); + } + let hash = hash_string(key as *const NamlString); + let mut idx = (hash as usize) % (*map).capacity; + loop { + let entry = (*map).entries.add(idx); + if !(*entry).occupied { + (*entry).key = key; + (*entry).value = value; + (*entry).occupied = true; + (*map).length += 1; + if key != 0 { (*(key as *mut NamlString)).header.incref(); } + return; + } + if string_eq((*entry).key as *const NamlString, key as *const NamlString) { + // Decref old map value before replacing + if (*entry).value != 0 { + naml_map_decref((*entry).value as *mut NamlMap); + } + (*entry).value = value; + return; + } + idx = (idx + 1) % (*map).capacity; + } + } +} + +/// Set a struct value in the map (decrefs old struct value when updating) +#[unsafe(no_mangle)] +pub extern "C" fn naml_map_set_struct(map: *mut NamlMap, key: i64, value: i64) { + if map.is_null() { return; } + unsafe { + if ((*map).length + 1) as f64 / (*map).capacity as f64 > LOAD_FACTOR { + resize_map(map); + } + let hash = hash_string(key as *const NamlString); + let mut idx = (hash as usize) % (*map).capacity; + loop { + let entry = (*map).entries.add(idx); + if !(*entry).occupied { + (*entry).key = key; + (*entry).value = value; + (*entry).occupied = true; + (*map).length += 1; + if key != 0 { (*(key as *mut NamlString)).header.incref(); } + return; + } + if string_eq((*entry).key as *const NamlString, key as *const NamlString) { + // Decref old struct value before replacing + if (*entry).value != 0 { + super::value::naml_struct_decref((*entry).value as *mut super::value::NamlStruct); + } + (*entry).value = value; + return; + } + idx = (idx + 1) % (*map).capacity; + } + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn naml_map_get(map: *const NamlMap, key: i64) -> i64 { + if map.is_null() { return 0; } + unsafe { + let hash = hash_string(key as *const NamlString); + let mut idx = (hash as usize) % (*map).capacity; + let start_idx = idx; + loop { + let entry = (*map).entries.add(idx); + if !(*entry).occupied { return 0; } + if string_eq((*entry).key as *const NamlString, key as *const NamlString) { + return (*entry).value; + } + idx = (idx + 1) % (*map).capacity; + if idx == start_idx { break; } + } + 0 + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn naml_map_contains(map: *const NamlMap, key: i64) -> i64 { + if map.is_null() { return 0; } + unsafe { + let hash = hash_string(key as *const NamlString); + let mut idx = (hash as usize) % (*map).capacity; + let start_idx = idx; + loop { + let entry = (*map).entries.add(idx); + if !(*entry).occupied { return 0; } + if string_eq((*entry).key as *const NamlString, key as *const NamlString) { + return 1; + } + idx = (idx + 1) % (*map).capacity; + if idx == start_idx { break; } + } + 0 + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn naml_map_len(map: *const NamlMap) -> i64 { + if map.is_null() { 0 } else { unsafe { (*map).length as i64 } } +} + +#[unsafe(no_mangle)] +pub extern "C" fn naml_map_incref(map: *mut NamlMap) { + if !map.is_null() { unsafe { (*map).header.incref(); } } +} + +#[unsafe(no_mangle)] +pub extern "C" fn naml_map_decref(map: *mut NamlMap) { + if map.is_null() { return; } + unsafe { + if (*map).header.decref() { + for i in 0..(*map).capacity { + let entry = (*map).entries.add(i); + if (*entry).occupied && (*entry).key != 0 { + naml_string_decref((*entry).key as *mut NamlString); + } + } + let entries_layout = Layout::array::((*map).capacity).unwrap(); + dealloc((*map).entries as *mut u8, entries_layout); + let map_layout = Layout::new::(); + dealloc(map as *mut u8, map_layout); + } + } +} + +/// Decrement map reference count and also decref string values +#[unsafe(no_mangle)] +pub extern "C" fn naml_map_decref_strings(map: *mut NamlMap) { + if map.is_null() { return; } + unsafe { + if (*map).header.decref() { + for i in 0..(*map).capacity { + let entry = (*map).entries.add(i); + if (*entry).occupied { + if (*entry).key != 0 { + naml_string_decref((*entry).key as *mut NamlString); + } + if (*entry).value != 0 { + naml_string_decref((*entry).value as *mut NamlString); + } + } + } + let entries_layout = Layout::array::((*map).capacity).unwrap(); + dealloc((*map).entries as *mut u8, entries_layout); + let map_layout = Layout::new::(); + dealloc(map as *mut u8, map_layout); + } + } +} + +/// Decrement map reference count and also decref array values +#[unsafe(no_mangle)] +pub extern "C" fn naml_map_decref_arrays(map: *mut NamlMap) { + if map.is_null() { return; } + unsafe { + if (*map).header.decref() { + for i in 0..(*map).capacity { + let entry = (*map).entries.add(i); + if (*entry).occupied { + if (*entry).key != 0 { + naml_string_decref((*entry).key as *mut NamlString); + } + if (*entry).value != 0 { + super::array::naml_array_decref((*entry).value as *mut super::array::NamlArray); + } + } + } + let entries_layout = Layout::array::((*map).capacity).unwrap(); + dealloc((*map).entries as *mut u8, entries_layout); + let map_layout = Layout::new::(); + dealloc(map as *mut u8, map_layout); + } + } +} + +/// Decrement map reference count and also decref nested map values +#[unsafe(no_mangle)] +pub extern "C" fn naml_map_decref_maps(map: *mut NamlMap) { + if map.is_null() { return; } + unsafe { + if (*map).header.decref() { + for i in 0..(*map).capacity { + let entry = (*map).entries.add(i); + if (*entry).occupied { + if (*entry).key != 0 { + naml_string_decref((*entry).key as *mut NamlString); + } + if (*entry).value != 0 { + naml_map_decref((*entry).value as *mut NamlMap); + } + } + } + let entries_layout = Layout::array::((*map).capacity).unwrap(); + dealloc((*map).entries as *mut u8, entries_layout); + let map_layout = Layout::new::(); + dealloc(map as *mut u8, map_layout); + } + } +} + +/// Decrement map reference count and also decref struct values +#[unsafe(no_mangle)] +pub extern "C" fn naml_map_decref_structs(map: *mut NamlMap) { + if map.is_null() { return; } + unsafe { + if (*map).header.decref() { + for i in 0..(*map).capacity { + let entry = (*map).entries.add(i); + if (*entry).occupied { + if (*entry).key != 0 { + naml_string_decref((*entry).key as *mut NamlString); + } + if (*entry).value != 0 { + super::value::naml_struct_decref((*entry).value as *mut super::value::NamlStruct); + } + } + } + let entries_layout = Layout::array::((*map).capacity).unwrap(); + dealloc((*map).entries as *mut u8, entries_layout); + let map_layout = Layout::new::(); + dealloc(map as *mut u8, map_layout); + } + } +} + +unsafe fn resize_map(map: *mut NamlMap) { + unsafe { + let old_capacity = (*map).capacity; + let old_entries = (*map).entries; + let new_capacity = old_capacity * 2; + + let new_layout = Layout::array::(new_capacity).unwrap(); + let new_entries = alloc_zeroed(new_layout) as *mut MapEntry; + if new_entries.is_null() { panic!("Failed to resize map"); } + + (*map).entries = new_entries; + (*map).capacity = new_capacity; + (*map).length = 0; + + for i in 0..old_capacity { + let entry = old_entries.add(i); + if (*entry).occupied { + // Use internal rehash function that doesn't modify reference counts. + // We're moving entries to new locations - the map still owns the same + // references, so refcounts should remain unchanged. + rehash_entry(map, (*entry).key, (*entry).value); + } + } + + let old_layout = Layout::array::(old_capacity).unwrap(); + dealloc(old_entries as *mut u8, old_layout); + } +} + +/// Internal function to insert an entry during rehashing without modifying reference counts. +/// Used only during resize when moving existing entries to new locations. +unsafe fn rehash_entry(map: *mut NamlMap, key: i64, value: i64) { + unsafe { + let hash = hash_string(key as *const NamlString); + let mut idx = (hash as usize) % (*map).capacity; + loop { + let entry = (*map).entries.add(idx); + if !(*entry).occupied { + (*entry).key = key; + (*entry).value = value; + (*entry).occupied = true; + (*map).length += 1; + // No incref here - we're just moving the entry, not creating a new reference + return; + } + idx = (idx + 1) % (*map).capacity; + } + } +} diff --git a/namlc/src/runtime/mod.rs b/namlc/src/runtime/mod.rs new file mode 100644 index 0000000..23a6dde --- /dev/null +++ b/namlc/src/runtime/mod.rs @@ -0,0 +1,35 @@ +//! +//! naml Runtime +//! +//! This module provides the runtime support for naml programs compiled with +//! Cranelift JIT. It includes: +//! +//! - Value representation (tagged union for dynamic typing at runtime boundaries) +//! - Reference-counted memory management +//! - Array operations +//! - String operations +//! - Struct field access +//! +//! Design: All heap objects use atomic reference counting for thread safety. +//! Values are passed as 64-bit tagged pointers or inline primitives. +//! + +pub mod value; +pub mod array; +pub mod scheduler; +pub mod channel; +pub mod map; + +pub use value::*; +pub use array::*; +pub use scheduler::*; +pub use channel::*; +pub use map::*; + +use std::io::Write; + +/// Initialize the runtime (call once at program start) +pub fn init() { + // Ensure stdout is line-buffered for print statements + let _ = std::io::stdout().flush(); +} diff --git a/namlc/src/runtime/scheduler.rs b/namlc/src/runtime/scheduler.rs new file mode 100644 index 0000000..5de9d44 --- /dev/null +++ b/namlc/src/runtime/scheduler.rs @@ -0,0 +1,329 @@ +//! +//! M:N Task Scheduler for naml +//! +//! Implements an M:N threading model where M user-space tasks (goroutines) +//! are multiplexed onto N OS threads. Features: +//! +//! - Thread pool with configurable worker count (defaults to CPU cores) +//! - Work-stealing queue for load balancing +//! - Closure support for captured variables +//! - Efficient task scheduling +//! + +use std::alloc::{alloc, dealloc, Layout}; +use std::collections::VecDeque; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::{Arc, Condvar, Mutex, OnceLock}; +use std::thread::{self, JoinHandle}; + +/// Task function signature: takes a pointer to captured data +type TaskFn = extern "C" fn(*mut u8); + +/// A task consists of a function pointer and captured data +struct Task { + func: TaskFn, + data: *mut u8, + data_size: usize, +} + +unsafe impl Send for Task {} + +/// The global task queue +struct TaskQueue { + tasks: Mutex>, + condvar: Condvar, + shutdown: AtomicBool, +} + +impl TaskQueue { + fn new() -> Self { + Self { + tasks: Mutex::new(VecDeque::new()), + condvar: Condvar::new(), + shutdown: AtomicBool::new(false), + } + } + + fn push(&self, task: Task) { + let mut tasks = self.tasks.lock().unwrap(); + tasks.push_back(task); + self.condvar.notify_one(); + } + + fn pop(&self) -> Option { + let mut tasks = self.tasks.lock().unwrap(); + while tasks.is_empty() && !self.shutdown.load(Ordering::SeqCst) { + tasks = self.condvar.wait(tasks).unwrap(); + } + tasks.pop_front() + } + + fn shutdown(&self) { + self.shutdown.store(true, Ordering::SeqCst); + self.condvar.notify_all(); + } + + /// Check if the queue has been shut down (utility method for future use) + #[allow(dead_code)] + fn is_shutdown(&self) -> bool { + self.shutdown.load(Ordering::SeqCst) + } +} + +/// The M:N scheduler manages a pool of worker threads +struct Scheduler { + queue: Arc, + workers: Vec>, + active_tasks: Arc, +} + +impl Scheduler { + fn new(num_workers: usize) -> Self { + let queue = Arc::new(TaskQueue::new()); + let active_tasks = Arc::new(AtomicUsize::new(0)); + let mut workers = Vec::with_capacity(num_workers); + + for _ in 0..num_workers { + let queue_clone = Arc::clone(&queue); + let tasks_clone = Arc::clone(&active_tasks); + let handle = thread::spawn(move || { + worker_loop(queue_clone, tasks_clone); + }); + workers.push(handle); + } + + Self { + queue, + workers, + active_tasks, + } + } + + fn spawn(&self, func: TaskFn, data: *mut u8, data_size: usize) { + self.active_tasks.fetch_add(1, Ordering::SeqCst); + self.queue.push(Task { func, data, data_size }); + } + + fn active_count(&self) -> usize { + self.active_tasks.load(Ordering::SeqCst) + } + + fn wait_all(&self) { + while self.active_count() > 0 { + thread::yield_now(); + } + } +} + +impl Drop for Scheduler { + fn drop(&mut self) { + self.queue.shutdown(); + for handle in self.workers.drain(..) { + let _ = handle.join(); + } + } +} + +fn worker_loop(queue: Arc, active_tasks: Arc) { + while let Some(task) = queue.pop() { + // Execute the task + (task.func)(task.data); + + // Free the captured data if any + if !task.data.is_null() && task.data_size > 0 { + unsafe { + let layout = Layout::from_size_align_unchecked(task.data_size, 8); + dealloc(task.data, layout); + } + } + + // Mark task as completed + active_tasks.fetch_sub(1, Ordering::SeqCst); + } +} + +/// Global scheduler instance using OnceLock for safe initialization +static SCHEDULER: OnceLock = OnceLock::new(); + +fn get_scheduler() -> &'static Scheduler { + SCHEDULER.get_or_init(|| { + let num_workers = thread::available_parallelism() + .map(|n| n.get()) + .unwrap_or(4); + Scheduler::new(num_workers) + }) +} + +/// Spawn a task with captured data +/// +/// # Arguments +/// * `func` - Function to execute, takes pointer to captured data +/// * `data` - Pointer to heap-allocated captured data (will be freed after execution) +/// * `data_size` - Size of captured data for deallocation +#[unsafe(no_mangle)] +pub extern "C" fn naml_spawn_closure( + func: extern "C" fn(*mut u8), + data: *mut u8, + data_size: usize, +) { + get_scheduler().spawn(func, data, data_size); +} + +/// Spawn a task without captured data (legacy interface) +#[unsafe(no_mangle)] +pub extern "C" fn naml_spawn(func: extern "C" fn()) { + // Wrap the no-arg function as a closure function that ignores data + extern "C" fn wrapper(data: *mut u8) { + let func: extern "C" fn() = unsafe { std::mem::transmute(data) }; + func(); + } + + // Pass function pointer as the data pointer + get_scheduler().spawn(wrapper, func as *mut u8, 0); +} + +/// Wait for all spawned tasks to complete +#[unsafe(no_mangle)] +pub extern "C" fn naml_wait_all() { + get_scheduler().wait_all(); +} + +/// Get the number of active tasks +#[unsafe(no_mangle)] +pub extern "C" fn naml_active_tasks() -> i64 { + get_scheduler().active_count() as i64 +} + +/// Sleep for the given number of milliseconds +#[unsafe(no_mangle)] +pub extern "C" fn naml_sleep(ms: i64) { + thread::sleep(std::time::Duration::from_millis(ms as u64)); +} + +/// Allocate memory for captured closure data +/// Returns a pointer to allocated memory that will be freed after task execution +#[unsafe(no_mangle)] +pub extern "C" fn naml_alloc_closure_data(size: usize) -> *mut u8 { + if size == 0 { + return std::ptr::null_mut(); + } + unsafe { + let layout = Layout::from_size_align_unchecked(size, 8); + alloc(layout) + } +} + +/// Get the number of worker threads in the pool +#[unsafe(no_mangle)] +pub extern "C" fn naml_worker_count() -> i64 { + thread::available_parallelism() + .map(|n| n.get() as i64) + .unwrap_or(4) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::atomic::AtomicI64; + + // Each test uses its own counter to avoid interference in parallel test execution + static BASIC_COUNTER: AtomicI64 = AtomicI64::new(0); + static CLOSURE_COUNTER: AtomicI64 = AtomicI64::new(0); + + extern "C" fn increment_basic_counter() { + BASIC_COUNTER.fetch_add(1, Ordering::SeqCst); + } + + #[test] + fn test_spawn_basic() { + BASIC_COUNTER.store(0, Ordering::SeqCst); + + naml_spawn(increment_basic_counter); + naml_spawn(increment_basic_counter); + naml_spawn(increment_basic_counter); + + naml_wait_all(); + + assert_eq!(BASIC_COUNTER.load(Ordering::SeqCst), 3); + } + + extern "C" fn add_value_to_closure_counter(data: *mut u8) { + let value = unsafe { *(data as *const i64) }; + CLOSURE_COUNTER.fetch_add(value, Ordering::SeqCst); + } + + #[test] + fn test_spawn_with_closure() { + CLOSURE_COUNTER.store(0, Ordering::SeqCst); + + // Spawn tasks with captured values + for i in 1..=5 { + let data = naml_alloc_closure_data(8); + unsafe { + *(data as *mut i64) = i; + } + naml_spawn_closure(add_value_to_closure_counter, data, 8); + } + + naml_wait_all(); + + // Sum of 1+2+3+4+5 = 15 + assert_eq!(CLOSURE_COUNTER.load(Ordering::SeqCst), 15); + } + + #[test] + fn test_thread_pool_parallelism() { + use std::sync::atomic::AtomicI32; + use std::time::Instant; + + static CONCURRENT_MAX: AtomicI32 = AtomicI32::new(0); + static CONCURRENT_CURRENT: AtomicI32 = AtomicI32::new(0); + + extern "C" fn track_concurrency(_: *mut u8) { + let current = CONCURRENT_CURRENT.fetch_add(1, Ordering::SeqCst) + 1; + + // Update max if current is higher + let mut max = CONCURRENT_MAX.load(Ordering::SeqCst); + while current > max { + match CONCURRENT_MAX.compare_exchange_weak( + max, current, Ordering::SeqCst, Ordering::SeqCst + ) { + Ok(_) => break, + Err(m) => max = m, + } + } + + // Simulate some work + thread::sleep(std::time::Duration::from_millis(50)); + + CONCURRENT_CURRENT.fetch_sub(1, Ordering::SeqCst); + } + + CONCURRENT_MAX.store(0, Ordering::SeqCst); + CONCURRENT_CURRENT.store(0, Ordering::SeqCst); + + let start = Instant::now(); + + // Spawn 8 tasks that each take 50ms + for _ in 0..8 { + naml_spawn_closure(track_concurrency, std::ptr::null_mut(), 0); + } + + naml_wait_all(); + + let elapsed = start.elapsed(); + + // If running in parallel, should complete in ~100-200ms (depending on cores) + // If sequential, would take 400ms + // Max concurrent should be > 1 if parallelism is working + let max_concurrent = CONCURRENT_MAX.load(Ordering::SeqCst); + + // On multi-core systems, we should see some parallelism + // Allow for single-core CI environments + assert!(max_concurrent >= 1); + + // Time check: should be faster than sequential (8 * 50ms = 400ms) + // But be lenient for CI variability + assert!(elapsed.as_millis() < 1000); + } +} diff --git a/namlc/src/runtime/value.rs b/namlc/src/runtime/value.rs new file mode 100644 index 0000000..01db46f --- /dev/null +++ b/namlc/src/runtime/value.rs @@ -0,0 +1,359 @@ +//! +//! Runtime Value Representation +//! +//! naml values at runtime are represented as 64-bit values that can be either: +//! - Inline primitives (int, float, bool) stored directly +//! - Heap pointers to reference-counted objects (strings, arrays, structs) +//! +//! We use NaN-boxing for efficient representation: +//! - If the high bits indicate NaN, the low bits contain a pointer or tag +//! - Otherwise, the value is a valid f64 +//! +//! For simplicity in Phase 2, we use a simpler tagged pointer scheme: +//! - Bit 0: 0 = pointer, 1 = immediate +//! - For immediates, bits 1-3 encode the type +//! + +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::alloc::{alloc, dealloc, Layout}; + +/// Type tags for heap objects +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum HeapTag { + String = 0, + Array = 1, + Struct = 2, + Map = 3, + Closure = 4, +} + +/// Header for all heap-allocated objects +#[repr(C)] +pub struct HeapHeader { + pub refcount: AtomicUsize, + pub tag: HeapTag, + pub _pad: [u8; 7], +} + +impl HeapHeader { + pub fn new(tag: HeapTag) -> Self { + Self { + refcount: AtomicUsize::new(1), + tag, + _pad: [0; 7], + } + } + + pub fn incref(&self) { + self.refcount.fetch_add(1, Ordering::Relaxed); + } + + pub fn decref(&self) -> bool { + if self.refcount.fetch_sub(1, Ordering::Release) == 1 { + std::sync::atomic::fence(Ordering::Acquire); + true + } else { + false + } + } + + pub fn refcount(&self) -> usize { + self.refcount.load(Ordering::Relaxed) + } +} + +/// A heap-allocated string +#[repr(C)] +pub struct NamlString { + pub header: HeapHeader, + pub len: usize, + pub data: [u8; 0], // Flexible array member +} + +impl NamlString { + pub fn as_str(&self) -> &str { + unsafe { + let slice = std::slice::from_raw_parts(self.data.as_ptr(), self.len); + std::str::from_utf8_unchecked(slice) + } + } +} + +/// A heap-allocated struct instance +#[repr(C)] +pub struct NamlStruct { + pub header: HeapHeader, + pub type_id: u32, + pub field_count: u32, + pub fields: [i64; 0], // Flexible array of field values +} + +/// Allocate a new string on the heap +#[unsafe(no_mangle)] +pub extern "C" fn naml_string_new(data: *const u8, len: usize) -> *mut NamlString { + unsafe { + let layout = Layout::from_size_align( + std::mem::size_of::() + len, + std::mem::align_of::(), + ).unwrap(); + + let ptr = alloc(layout) as *mut NamlString; + if ptr.is_null() { + panic!("Failed to allocate string"); + } + + (*ptr).header = HeapHeader::new(HeapTag::String); + (*ptr).len = len; + + if !data.is_null() && len > 0 { + std::ptr::copy_nonoverlapping(data, (*ptr).data.as_mut_ptr(), len); + } + + ptr + } +} + +/// Increment reference count of a string +#[unsafe(no_mangle)] +pub extern "C" fn naml_string_incref(s: *mut NamlString) { + if !s.is_null() { + unsafe { (*s).header.incref(); } + } +} + +/// Decrement reference count and free if zero +#[unsafe(no_mangle)] +pub extern "C" fn naml_string_decref(s: *mut NamlString) { + if !s.is_null() { + unsafe { + if (*s).header.decref() { + let len = (*s).len; + let layout = Layout::from_size_align( + std::mem::size_of::() + len, + std::mem::align_of::(), + ).unwrap(); + dealloc(s as *mut u8, layout); + } + } + } +} + +/// Get string length +#[unsafe(no_mangle)] +pub extern "C" fn naml_string_len(s: *const NamlString) -> i64 { + if s.is_null() { + 0 + } else { + unsafe { (*s).len as i64 } + } +} + +/// Get pointer to string data (for printing) +#[unsafe(no_mangle)] +pub extern "C" fn naml_string_data(s: *const NamlString) -> *const u8 { + if s.is_null() { + std::ptr::null() + } else { + unsafe { (*s).data.as_ptr() } + } +} + +/// Concatenate two strings +#[unsafe(no_mangle)] +pub extern "C" fn naml_string_concat(a: *const NamlString, b: *const NamlString) -> *mut NamlString { + unsafe { + let a_len = if a.is_null() { 0 } else { (*a).len }; + let b_len = if b.is_null() { 0 } else { (*b).len }; + let total_len = a_len + b_len; + + let result = naml_string_new(std::ptr::null(), total_len); + + if a_len > 0 { + std::ptr::copy_nonoverlapping((*a).data.as_ptr(), (*result).data.as_mut_ptr(), a_len); + } + if b_len > 0 { + std::ptr::copy_nonoverlapping((*b).data.as_ptr(), (*result).data.as_mut_ptr().add(a_len), b_len); + } + + result + } +} + +/// Compare two strings for equality +#[unsafe(no_mangle)] +pub extern "C" fn naml_string_eq(a: *const NamlString, b: *const NamlString) -> i64 { + unsafe { + if a.is_null() && b.is_null() { + return 1; + } + if a.is_null() || b.is_null() { + return 0; + } + if (*a).len != (*b).len { + return 0; + } + + let a_slice = std::slice::from_raw_parts((*a).data.as_ptr(), (*a).len); + let b_slice = std::slice::from_raw_parts((*b).data.as_ptr(), (*b).len); + + if a_slice == b_slice { 1 } else { 0 } + } +} + +/// Print a NamlString (for debugging) +#[unsafe(no_mangle)] +pub extern "C" fn naml_string_print(s: *const NamlString) { + if !s.is_null() { + unsafe { + let slice = std::slice::from_raw_parts((*s).data.as_ptr(), (*s).len); + if let Ok(str_val) = std::str::from_utf8(slice) { + print!("{}", str_val); + } + } + } +} + +/// Create a NamlString from a null-terminated C string pointer +#[unsafe(no_mangle)] +pub extern "C" fn naml_string_from_cstr(cstr: *const i8) -> *mut NamlString { + if cstr.is_null() { + return naml_string_new(std::ptr::null(), 0); + } + unsafe { + let c_str = std::ffi::CStr::from_ptr(cstr); + let bytes = c_str.to_bytes(); + naml_string_new(bytes.as_ptr(), bytes.len()) + } +} + +/// Allocate a new struct on the heap +#[unsafe(no_mangle)] +pub extern "C" fn naml_struct_new(type_id: u32, field_count: u32) -> *mut NamlStruct { + unsafe { + let layout = Layout::from_size_align( + std::mem::size_of::() + (field_count as usize) * std::mem::size_of::(), + std::mem::align_of::(), + ).unwrap(); + + let ptr = alloc(layout) as *mut NamlStruct; + if ptr.is_null() { + panic!("Failed to allocate struct"); + } + + (*ptr).header = HeapHeader::new(HeapTag::Struct); + (*ptr).type_id = type_id; + (*ptr).field_count = field_count; + + // Initialize fields to zero + let fields_ptr = (*ptr).fields.as_mut_ptr(); + for i in 0..field_count as usize { + *fields_ptr.add(i) = 0; + } + + ptr + } +} + +/// Increment reference count of a struct +#[unsafe(no_mangle)] +pub extern "C" fn naml_struct_incref(s: *mut NamlStruct) { + if !s.is_null() { + unsafe { (*s).header.incref(); } + } +} + +/// Decrement reference count and free if zero (for structs with no heap fields) +#[unsafe(no_mangle)] +pub extern "C" fn naml_struct_decref(s: *mut NamlStruct) { + if !s.is_null() { + unsafe { + if (*s).header.decref() { + let field_count = (*s).field_count; + let layout = Layout::from_size_align( + std::mem::size_of::() + (field_count as usize) * std::mem::size_of::(), + std::mem::align_of::(), + ).unwrap(); + dealloc(s as *mut u8, layout); + } + } + } +} + +/// Free struct memory without refcount check (called by generated decref functions) +/// Generated decref functions handle field cleanup before calling this. +#[unsafe(no_mangle)] +pub extern "C" fn naml_struct_free(s: *mut NamlStruct) { + if !s.is_null() { + unsafe { + let field_count = (*s).field_count; + let layout = Layout::from_size_align( + std::mem::size_of::() + (field_count as usize) * std::mem::size_of::(), + std::mem::align_of::(), + ).unwrap(); + dealloc(s as *mut u8, layout); + } + } +} + +/// Get field value by index +#[unsafe(no_mangle)] +pub extern "C" fn naml_struct_get_field(s: *const NamlStruct, field_index: u32) -> i64 { + if s.is_null() { + return 0; + } + + unsafe { + if field_index >= (*s).field_count { + return 0; + } + *(*s).fields.as_ptr().add(field_index as usize) + } +} + +/// Set field value by index +#[unsafe(no_mangle)] +pub extern "C" fn naml_struct_set_field(s: *mut NamlStruct, field_index: u32, value: i64) { + if s.is_null() { + return; + } + + unsafe { + if field_index < (*s).field_count { + *(*s).fields.as_mut_ptr().add(field_index as usize) = value; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_string_creation() { + let data = b"hello"; + let s = naml_string_new(data.as_ptr(), data.len()); + assert!(!s.is_null()); + unsafe { + assert_eq!((*s).len, 5); + assert_eq!((*s).header.refcount(), 1); + naml_string_decref(s); + } + } + + #[test] + fn test_string_concat() { + let a = naml_string_new(b"hello ".as_ptr(), 6); + let b = naml_string_new(b"world".as_ptr(), 5); + let c = naml_string_concat(a, b); + + unsafe { + assert_eq!((*c).len, 11); + assert_eq!((*c).as_str(), "hello world"); + + naml_string_decref(a); + naml_string_decref(b); + naml_string_decref(c); + } + } +} diff --git a/namlc/src/source/mod.rs b/namlc/src/source/mod.rs index cf4ba0f..6c15e70 100644 --- a/namlc/src/source/mod.rs +++ b/namlc/src/source/mod.rs @@ -1,21 +1,21 @@ -/// -/// Source Location and Span Module -/// -/// This module provides types for tracking source code locations throughout -/// the compilation pipeline. Every AST node carries a Span indicating where -/// it came from in the original source text. -/// -/// Key types: -/// - Span: A range in source code (start offset, end offset, file id) -/// - Spanned: Trait for types that have an associated span -/// - SourceFile: Holds source text with line/column lookup -/// -/// Design decisions: -/// - Offsets are byte-based, not character-based (faster, works with UTF-8) -/// - File ID allows spans to reference different source files -/// - Spans are Copy for ergonomic use throughout the compiler -/// - SourceFile caches line starts for O(log n) offset-to-line conversion -/// +//! +//! Source Location and Span Module +//! +//! This module provides types for tracking source code locations throughout +//! the compilation pipeline. Every AST node carries a Span indicating where +//! it came from in the original source text. +//! +//! Key types: +//! - Span: A range in source code (start offset, end offset, file id) +//! - Spanned: Trait for types that have an associated span +//! - SourceFile: Holds source text with line/column lookup +//! +//! Design decisions: +//! - Offsets are byte-based, not character-based (faster, works with UTF-8) +//! - File ID allows spans to reference different source files +//! - Spans are Copy for ergonomic use throughout the compiler +//! - SourceFile caches line starts for O(log n) offset-to-line conversion +//! use std::fmt; use std::sync::Arc; diff --git a/namlc/src/typechecker/env.rs b/namlc/src/typechecker/env.rs index 011dc03..d967bce 100644 --- a/namlc/src/typechecker/env.rs +++ b/namlc/src/typechecker/env.rs @@ -1,17 +1,17 @@ -/// -/// Type Environment - Scope Management -/// -/// This module manages the type environment during type checking. It tracks: -/// -/// - Variable bindings and their types in nested scopes -/// - Whether variables are mutable -/// - The current function's return type (for return statement checking) -/// - Loop nesting (for break/continue validation) -/// - Async context (for await validation) -/// -/// Scopes are managed as a stack, pushed when entering blocks and popped -/// when leaving them. -/// +//! +//! Type Environment - Scope Management +//! +//! This module manages the type environment during type checking. It tracks: +//! +//! - Variable bindings and their types in nested scopes +//! - Whether variables are mutable +//! - The current function's return type (for return statement checking) +//! - Loop nesting (for break/continue validation) +//! - Async context (for await validation) +//! +//! Scopes are managed as a stack, pushed when entering blocks and popped +//! when leaving them. +//! use std::collections::HashMap; diff --git a/namlc/src/typechecker/error.rs b/namlc/src/typechecker/error.rs index a8e50b3..d951290 100644 --- a/namlc/src/typechecker/error.rs +++ b/namlc/src/typechecker/error.rs @@ -1,20 +1,20 @@ -/// -/// Type Checker Error Types -/// -/// This module defines error types for the type checking phase. Errors -/// carry source location information for precise error reporting. -/// -/// Error categories: -/// - TypeMismatch: Expected one type, found another -/// - UndefinedVariable: Variable not found in scope -/// - UndefinedType: Type name not found -/// - UndefinedFunction: Function not found -/// - UndefinedField: Struct field not found -/// - UndefinedMethod: Method not found on type -/// - DuplicateDefinition: Name already defined in scope -/// - InvalidOperation: Operation not valid for types -/// - InferenceFailed: Could not infer type -/// +//! +//! Type Checker Error Types +//! +//! This module defines error types for the type checking phase. Errors +//! carry source location information for precise error reporting. +//! +//! Error categories: +//! - TypeMismatch: Expected one type, found another +//! - UndefinedVariable: Variable not found in scope +//! - UndefinedType: Type name not found +//! - UndefinedFunction: Function not found +//! - UndefinedField: Struct field not found +//! - UndefinedMethod: Method not found on type +//! - DuplicateDefinition: Name already defined in scope +//! - InvalidOperation: Operation not valid for types +//! - InferenceFailed: Could not infer type +//! use crate::source::Span; use thiserror::Error; diff --git a/namlc/src/typechecker/generics.rs b/namlc/src/typechecker/generics.rs index 2fe40aa..88da6ae 100644 --- a/namlc/src/typechecker/generics.rs +++ b/namlc/src/typechecker/generics.rs @@ -1,16 +1,16 @@ -/// -/// Generics Module - Type Substitution and Bound Checking -/// -/// This module handles generic type operations: -/// -/// - Building substitution maps from type params to concrete types -/// - Finding methods on type parameters by checking their bounds -/// - Instantiating generic functions with fresh type variables -/// - Checking that concrete types satisfy their bounds -/// -/// These operations enable proper generic type inference and trait method -/// resolution for code like `T: Comparable` where `T.compare()` is called. -/// +//! +//! Generics Module - Type Substitution and Bound Checking +//! +//! This module handles generic type operations: +//! +//! - Building substitution maps from type params to concrete types +//! - Finding methods on type parameters by checking their bounds +//! - Instantiating generic functions with fresh type variables +//! - Checking that concrete types satisfy their bounds +//! +//! These operations enable proper generic type inference and trait method +//! resolution for code like `T: Comparable` where `T.compare()` is called. +//! use std::collections::HashMap; diff --git a/namlc/src/typechecker/infer.rs b/namlc/src/typechecker/infer.rs index d7250ed..69b9e29 100644 --- a/namlc/src/typechecker/infer.rs +++ b/namlc/src/typechecker/infer.rs @@ -1,23 +1,23 @@ -/// -/// Type Inference for Expressions -/// -/// This module infers types for expressions. Each expression form has -/// specific typing rules: -/// -/// - Literals: Type is determined by literal form -/// - Identifiers: Look up in environment -/// - Binary/Unary: Check operand types, return result type -/// - Calls: Unify arguments with parameters, return result type -/// - Field access: Look up field type on struct -/// - Index: Check indexable type, return element type -/// -/// Type variables are created for unknown types and unified during -/// inference to discover concrete types. -/// +//! +//! Type Inference for Expressions +//! +//! This module infers types for expressions. Each expression form has +//! specific typing rules: +//! +//! - Literals: Type is determined by literal form +//! - Identifiers: Look up in environment +//! - Binary/Unary: Check operand types, return result type +//! - Calls: Unify arguments with parameters, return result type +//! - Field access: Look up field type on struct +//! - Index: Check indexable type, return element type +//! +//! Type variables are created for unknown types and unified during +//! inference to discover concrete types. +//! use lasso::Rodeo; -use crate::ast::{self, Expression, Literal}; +use crate::ast::{self, Expression, Literal, Pattern}; use crate::source::Spanned; use super::env::TypeEnv; @@ -34,7 +34,6 @@ pub struct TypeInferrer<'a> { pub next_var_id: &'a mut u32, pub errors: &'a mut Vec, pub annotations: &'a mut TypeAnnotations, - /// Current switch scrutinee type for resolving bare enum variants in case patterns pub switch_scrutinee: Option, } @@ -181,11 +180,22 @@ impl<'a> TypeInferrer<'a> { use super::symbols::TypeDef; match def { TypeDef::Enum(e) => { + let enum_ty = self.symbols.to_enum_type(e); if path.segments.len() == 2 { let variant_name = path.segments[1].symbol; - for (name, _) in &e.variants { + for (name, fields) in &e.variants { if *name == variant_name { - return Type::Enum(self.symbols.to_enum_type(e)); + // If variant has associated data, return function type + if let Some(field_types) = fields { + return Type::Function(FunctionType { + params: field_types.clone(), + returns: Box::new(Type::Enum(enum_ty)), + throws: None, + is_async: false, + is_variadic: false, + }); + } + return Type::Enum(enum_ty); } } let variant = self.interner.resolve(&variant_name).to_string(); @@ -195,7 +205,7 @@ impl<'a> TypeInferrer<'a> { span: path.span, }); } - Type::Enum(self.symbols.to_enum_type(e)) + Type::Enum(enum_ty) } _ => { Type::Generic(first.symbol, Vec::new()) @@ -594,6 +604,56 @@ impl<'a> TypeInferrer<'a> { }; } + // Handle built-in channel methods + if let Type::Channel(inner) = &resolved { + let method_name = self.interner.resolve(&call.method.symbol); + return match method_name { + "send" => { + if call.args.len() != 1 { + self.errors.push(TypeError::WrongArgCount { + expected: 1, + found: call.args.len(), + span: call.span, + }); + return Type::Int; + } + let arg_ty = self.infer_expr(&call.args[0]); + if let Err(e) = unify(&arg_ty, inner, call.args[0].span()) { + self.errors.push(e); + } + Type::Int + } + "receive" => { + if !call.args.is_empty() { + self.errors.push(TypeError::WrongArgCount { + expected: 0, + found: call.args.len(), + span: call.span, + }); + } + (**inner).clone() + } + "close" => { + if !call.args.is_empty() { + self.errors.push(TypeError::WrongArgCount { + expected: 0, + found: call.args.len(), + span: call.span, + }); + } + Type::Unit + } + _ => { + self.errors.push(TypeError::UndefinedMethod { + ty: resolved.to_string(), + method: method_name.to_string(), + span: call.span, + }); + Type::Error + } + }; + } + // Check if receiver is a bare type parameter (T with no type args) // If so, look up methods from its bounds if let Type::Generic(param_name, type_args) = &resolved { @@ -752,14 +812,23 @@ impl<'a> TypeInferrer<'a> { }); Type::Error } - Type::Generic(name, ref _type_args) => { + Type::Generic(name, ref type_args) => { // Look up the struct definition by name use super::symbols::TypeDef; + use std::collections::HashMap; if let Some(TypeDef::Struct(def)) = self.symbols.get_type(name) { let struct_ty = self.symbols.to_struct_type(def); + // Build substitution map from type params to actual type args + let substitution: HashMap = struct_ty + .type_params + .iter() + .zip(type_args.iter()) + .map(|(tp, arg)| (tp.name, arg.clone())) + .collect(); for f in &struct_ty.fields { if f.name == field.field.symbol { - return f.ty.clone(); + // Apply substitution to get concrete field type + return f.ty.substitute(&substitution); } } let field_name = self.interner.resolve(&field.field.symbol).to_string(); @@ -839,15 +908,25 @@ impl<'a> TypeInferrer<'a> { fn infer_struct_literal(&mut self, lit: &ast::StructLiteralExpr) -> Type { if let Some(def) = self.symbols.get_type(lit.name.symbol) { use super::symbols::TypeDef; + use std::collections::HashMap; match def { TypeDef::Struct(s) => { let struct_ty = self.symbols.to_struct_type(s); + // Build substitution map from type params to fresh type vars + let substitution: HashMap = struct_ty + .type_params + .iter() + .map(|tp| (tp.name, fresh_type_var(self.next_var_id))) + .collect(); + for field_lit in &lit.fields { let field_def = struct_ty.fields.iter().find(|f| f.name == field_lit.name.symbol); if let Some(field_def) = field_def { let value_ty = self.infer_expr(&field_lit.value); - if let Err(e) = unify(&value_ty, &field_def.ty, field_lit.span) { + // Apply substitution to field type to replace type params with type vars + let substituted_field_ty = field_def.ty.substitute(&substitution); + if let Err(e) = unify(&value_ty, &substituted_field_ty, field_lit.span) { self.errors.push(e); } } else { @@ -862,11 +941,11 @@ impl<'a> TypeInferrer<'a> { // If struct has type parameters, return Generic type instead of Struct if !struct_ty.type_params.is_empty() { - // Create fresh type variables for each type param + // Collect the type vars in param order for the return type let type_args: Vec = struct_ty .type_params .iter() - .map(|_| fresh_type_var(self.next_var_id)) + .map(|tp| substitution.get(&tp.name).cloned().unwrap_or_else(|| fresh_type_var(self.next_var_id))) .collect(); Type::Generic(lit.name.symbol, type_args) } else { @@ -1072,6 +1151,110 @@ impl<'a> TypeInferrer<'a> { Type::Array(Box::new(elem_ty.resolve())) } + pub fn infer_pattern(&mut self, pattern: &Pattern, scrutinee_ty: &Type) -> Type { + match pattern { + Pattern::Literal(lit) => { + // Literal patterns have a fixed type + match &lit.value { + Literal::Int(_) => Type::Int, + Literal::UInt(_) => Type::Uint, + Literal::Float(_) => Type::Float, + Literal::Bool(_) => Type::Bool, + Literal::String(_) => Type::String, + Literal::Bytes(_) => Type::Bytes, + Literal::None => { + let inner = fresh_type_var(self.next_var_id); + Type::Option(Box::new(inner)) + } + } + } + + Pattern::Identifier(ident) => { + // Identifier pattern: first check if it's an enum variant name + if let Type::Enum(enum_ty) = scrutinee_ty { + for variant in &enum_ty.variants { + if variant.name == ident.ident.symbol { + // It's a variant name, return the enum type + return scrutinee_ty.clone(); + } + } + } + // Otherwise it's a binding that captures the scrutinee value + self.env.define(ident.ident.symbol, scrutinee_ty.clone(), false); + scrutinee_ty.clone() + } + + Pattern::Variant(variant) => { + // Get the enum type from the path + if variant.path.is_empty() { + return Type::Error; + } + + // First segment is the enum type or variant name + let first = &variant.path[0]; + + // Check if it's a qualified path (EnumType::Variant) or bare variant + if variant.path.len() == 1 { + // Bare variant name - use scrutinee type to resolve + if let Type::Enum(enum_ty) = scrutinee_ty { + for var in &enum_ty.variants { + if var.name == first.symbol { + // Bind variant fields if present + if let Some(ref fields) = var.fields { + for (i, binding) in variant.bindings.iter().enumerate() { + if i < fields.len() { + self.env.define(binding.symbol, fields[i].clone(), false); + } + } + } + return scrutinee_ty.clone(); + } + } + // Unknown variant + let variant_name = self.interner.resolve(&first.symbol).to_string(); + self.errors.push(TypeError::Custom { + message: format!("unknown variant '{}'", variant_name), + span: variant.span, + }); + } + return Type::Error; + } + + // Qualified path - look up the enum type + if let Some(def) = self.symbols.get_type(first.symbol) { + use super::symbols::TypeDef; + if let TypeDef::Enum(e) = def { + let enum_ty = self.symbols.to_enum_type(e); + let variant_name = variant.path.last().unwrap().symbol; + + for var in &enum_ty.variants { + if var.name == variant_name { + // Bind variant fields if present + if let Some(ref fields) = var.fields { + for (i, binding) in variant.bindings.iter().enumerate() { + if i < fields.len() { + self.env.define(binding.symbol, fields[i].clone(), false); + } + } + } + return Type::Enum(enum_ty); + } + } + } + } + + Type::Error + } + + Pattern::Wildcard(_) => { + // Wildcard matches anything + scrutinee_ty.clone() + } + + Pattern::_Phantom(_) => Type::Error, + } + } + pub fn check_stmt(&mut self, stmt: &ast::Statement) { use ast::Statement::*; match stmt { @@ -1298,12 +1481,12 @@ impl<'a> TypeInferrer<'a> { self.switch_scrutinee = Some(scrutinee_ty.resolve()); for case in &switch.cases { - let pattern_ty = self.infer_expr(&case.pattern); + self.env.push_scope(); + let resolved_scrutinee = scrutinee_ty.resolve(); + let pattern_ty = self.infer_pattern(&case.pattern, &resolved_scrutinee); if let Err(e) = unify(&pattern_ty, &scrutinee_ty, case.pattern.span()) { self.errors.push(e); } - - self.env.push_scope(); for s in &case.body.statements { self.check_stmt(s); } @@ -1367,7 +1550,21 @@ impl<'a> TypeInferrer<'a> { ast::NamlType::Promise(inner) => { Type::Promise(Box::new(self.convert_ast_type(inner))) } - ast::NamlType::Named(ident) => Type::Generic(ident.symbol, Vec::new()), + ast::NamlType::Named(ident) => { + // Look up the name to see if it's a known type (struct, enum, etc.) + if let Some(def) = self.symbols.get_type(ident.symbol) { + use super::symbols::TypeDef; + match def { + TypeDef::Struct(s) => Type::Struct(self.symbols.to_struct_type(s)), + TypeDef::Enum(e) => Type::Enum(self.symbols.to_enum_type(e)), + TypeDef::Interface(i) => Type::Interface(self.symbols.to_interface_type(i)), + TypeDef::Exception(_) => Type::Generic(ident.symbol, Vec::new()), + } + } else { + // Fall back to generic type (for type parameters) + Type::Generic(ident.symbol, Vec::new()) + } + } ast::NamlType::Generic(ident, args) => { let converted_args = args.iter().map(|a| self.convert_ast_type(a)).collect(); Type::Generic(ident.symbol, converted_args) @@ -1386,21 +1583,16 @@ impl<'a> TypeInferrer<'a> { } } - /// Check if this is an int literal being assigned to a uint type (allowed coercion) fn is_int_to_uint_coercion(&self, init_ty: &Type, target_ty: &Type, init: &Expression) -> bool { let init_resolved = init_ty.resolve(); let target_resolved = target_ty.resolve(); - // Check if target is uint and init type is int if target_resolved != Type::Uint || init_resolved != Type::Int { return false; } - - // Check if the expression is an integer literal matches!(init, Expression::Literal(lit) if matches!(lit.value, Literal::Int(_))) } - /// Coerce int/uint operations: if one operand is uint and the other is an int literal, result is uint fn coerce_int_uint( &self, left_ty: &Type, diff --git a/namlc/src/typechecker/mod.rs b/namlc/src/typechecker/mod.rs index 87495ce..11f01aa 100644 --- a/namlc/src/typechecker/mod.rs +++ b/namlc/src/typechecker/mod.rs @@ -1,19 +1,19 @@ -/// -/// Type Checker Module -/// -/// This module provides type checking for naml programs. The type checker: -/// -/// 1. Collects all type and function definitions (first pass) -/// 2. Validates type definitions and builds the symbol table -/// 3. Type checks all function bodies and expressions -/// 4. Reports type errors with source locations -/// -/// The type checker uses Hindley-Milner style type inference with -/// unification. Type variables are created for unknown types and bound -/// during inference. -/// -/// Entry point: `check()` function takes an AST and returns errors -/// +//! +//! Type Checker Module +//! +//! This module provides type checking for naml programs. The type checker: +//! +//! 1. Collects all type and function definitions (first pass) +//! 2. Validates type definitions and builds the symbol table +//! 3. Type checks all function bodies and expressions +//! 4. Reports type errors with source locations +//! +//! The type checker uses Hindley-Milner style type inference with +//! unification. Type variables are created for unknown types and bound +//! during inference. +//! +//! Entry point: `check()` function takes an AST and returns errors +//! pub mod env; pub mod error; @@ -78,6 +78,8 @@ impl<'a> TypeChecker<'a> { ("println", true, Type::Unit), ("printf", true, Type::Unit), ("read_line", false, Type::String), + // Concurrency builtins (no params) + ("wait_all", false, Type::Unit), ]; for (name, is_variadic, return_ty) in builtins { @@ -95,6 +97,43 @@ impl<'a> TypeChecker<'a> { }); } } + + // Register sleep(ms: int) -> Unit + if let Some(spur) = self.interner.get("sleep") { + self.symbols.define_function(FunctionSig { + name: spur, + type_params: vec![], + params: vec![(spur, Type::Int)], // ms parameter + return_ty: Type::Unit, + throws: None, + is_async: false, + is_public: true, + is_variadic: false, + span: Span::dummy(), + }); + } + + // Register make_channel as a generic function: make_channel(capacity: int) -> channel + if let Some(spur) = self.interner.get("make_channel") { + // Create a type parameter T + let t_spur = self.interner.get("T").unwrap_or(spur); // Use T if interned, fallback to name + let type_param = TypeParam { + name: t_spur, + bounds: vec![], + }; + + self.symbols.define_function(FunctionSig { + name: spur, + type_params: vec![type_param.clone()], + params: vec![(spur, Type::Int)], // capacity parameter + return_ty: Type::Channel(Box::new(Type::Generic(t_spur, vec![]))), + throws: None, + is_async: false, + is_public: true, + is_variadic: false, + span: Span::dummy(), + }); + } } pub fn check(&mut self, file: &SourceFile) -> Vec { diff --git a/namlc/src/typechecker/symbols.rs b/namlc/src/typechecker/symbols.rs index 4ff347c..e5399f9 100644 --- a/namlc/src/typechecker/symbols.rs +++ b/namlc/src/typechecker/symbols.rs @@ -1,15 +1,15 @@ -/// -/// Symbol Table - Global Definitions -/// -/// This module manages the symbol table for type checking. It stores: -/// -/// - Type definitions (structs, enums, interfaces, exceptions) -/// - Function signatures (including methods) -/// - Built-in types and functions -/// -/// The symbol table is built in a first pass over the AST to collect all -/// definitions, then used during type checking to resolve references. -/// +//! +//! Symbol Table - Global Definitions +//! +//! This module manages the symbol table for type checking. It stores: +//! +//! - Type definitions (structs, enums, interfaces, exceptions) +//! - Function signatures (including methods) +//! - Built-in types and functions +//! +//! The symbol table is built in a first pass over the AST to collect all +//! definitions, then used during type checking to resolve references. +//! use std::collections::HashMap; diff --git a/namlc/src/typechecker/typed_ast.rs b/namlc/src/typechecker/typed_ast.rs index 0ef0f3d..68fe444 100644 --- a/namlc/src/typechecker/typed_ast.rs +++ b/namlc/src/typechecker/typed_ast.rs @@ -1,21 +1,21 @@ -/// -/// Typed AST Annotations -/// -/// This module provides a structure to store resolved type information for -/// expressions during type checking. The TypeAnnotations map allows the code -/// generator to look up the type of any expression by its source span. -/// -/// Design decisions: -/// - Uses Span as key (Copy, Hash) for zero-copy lookup without AST modification -/// - Stores resolved types (TypeVar bindings followed) for codegen use -/// - Tracks additional metadata like lvalue status and clone requirements -/// - Separate from AST to maintain clean separation between parse and check phases -/// -/// Usage flow: -/// 1. TypeInferrer records annotations during inference via annotate() -/// 2. TypeChecker returns TypeAnnotations alongside errors -/// 3. Codegen uses get_type() and needs_clone() for type-aware generation -/// +//! +//! Typed AST Annotations +//! +//! This module provides a structure to store resolved type information for +//! expressions during type checking. The TypeAnnotations map allows the code +//! generator to look up the type of any expression by its source span. +//! +//! Design decisions: +//! - Uses Span as key (Copy, Hash) for zero-copy lookup without AST modification +//! - Stores resolved types (TypeVar bindings followed) for codegen use +//! - Tracks additional metadata like lvalue status and clone requirements +//! - Separate from AST to maintain clean separation between parse and check phases +//! +//! Usage flow: +//! 1. TypeInferrer records annotations during inference via annotate() +//! 2. TypeChecker returns TypeAnnotations alongside errors +//! 3. Codegen uses get_type() and needs_clone() for type-aware generation +//! use std::collections::HashMap; diff --git a/namlc/src/typechecker/types.rs b/namlc/src/typechecker/types.rs index c8cf00e..9a72e33 100644 --- a/namlc/src/typechecker/types.rs +++ b/namlc/src/typechecker/types.rs @@ -1,16 +1,16 @@ -/// -/// Internal Type Representation -/// -/// This module defines the internal type representation used during type -/// checking. Unlike the AST NamlType, this representation supports: -/// -/// - Type variables for inference (TypeVar) -/// - Resolved named types with full definitions -/// - Substitution during unification -/// -/// The type checker converts AST types to these internal types, performs -/// inference and checking, then can convert back for error messages. -/// +//! +//! Internal Type Representation +//! +//! This module defines the internal type representation used during type +//! checking. Unlike the AST NamlType, this representation supports: +//! +//! - Type variables for inference (TypeVar) +//! - Resolved named types with full definitions +//! - Substitution during unification +//! +//! The type checker converts AST types to these internal types, performs +//! inference and checking, then can convert back for error messages. +//! use std::cell::RefCell; use std::collections::HashMap; diff --git a/namlc/src/typechecker/unify.rs b/namlc/src/typechecker/unify.rs index 50e3ac7..1fef2dc 100644 --- a/namlc/src/typechecker/unify.rs +++ b/namlc/src/typechecker/unify.rs @@ -1,19 +1,19 @@ -/// -/// Type Unification -/// -/// This module implements the unification algorithm for type inference. -/// Unification determines if two types can be made equal by binding type -/// variables to concrete types. -/// -/// The algorithm: -/// 1. Resolve any type variables to their bound types -/// 2. If both types are identical, succeed -/// 3. If one is a type variable, bind it to the other (occurs check) -/// 4. If both are composite types, recursively unify components -/// 5. Otherwise, fail with a type mismatch error -/// -/// The occurs check prevents infinite types like `?0 = [?0]`. -/// +//! +//! Type Unification +//! +//! This module implements the unification algorithm for type inference. +//! Unification determines if two types can be made equal by binding type +//! variables to concrete types. +//! +//! The algorithm: +//! 1. Resolve any type variables to their bound types +//! 2. If both types are identical, succeed +//! 3. If one is a type variable, bind it to the other (occurs check) +//! 4. If both are composite types, recursively unify components +//! 5. Otherwise, fail with a type mismatch error +//! +//! The occurs check prevents infinite types like `?0 = [?0]`. +//! use crate::source::Span;