feat: add list support to C backend and improve compile workflow
C Backend Lists: - Add LuxList type (dynamic array with void* boxing) - Implement all 16 list operations: length, isEmpty, concat, reverse, range, take, drop, head, tail, get, map, filter, fold, find, any, all - Higher-order operations generate inline loops with closure calls - Fix unique variable names to prevent redefinition errors Compile Command: - `lux compile file.lux` now produces a binary (like rustc, go build) - Add `--emit-c` flag to output C code instead - Binary name derived from source filename (foo.lux -> ./foo) - Clean up temp files after compilation Documentation: - Create docs/C_BACKEND.md with full strategy documentation - Document compilation pipeline, runtime types, limitations - Compare with Koka, Rust, Zig, Go, Nim, OCaml approaches - Outline future roadmap (evidence passing, Perceus RC) - Fix misleading doc comment (remove false Perceus claim) - Update OVERVIEW.md and ROADMAP.md to reflect list completion Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
399
docs/C_BACKEND.md
Normal file
399
docs/C_BACKEND.md
Normal file
@@ -0,0 +1,399 @@
|
|||||||
|
# Lux C Backend
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Lux compiles to C code, then invokes a system C compiler (gcc/clang) to produce native binaries. This approach is used by several production languages:
|
||||||
|
|
||||||
|
| Language | Target | Memory Management |
|
||||||
|
|----------|--------|-------------------|
|
||||||
|
| **Koka** | C | Perceus reference counting |
|
||||||
|
| **Nim** | C | ORC (configurable) |
|
||||||
|
| **Chicken Scheme** | C | Generational GC |
|
||||||
|
| **Lux (current)** | C | None (leaks) |
|
||||||
|
|
||||||
|
## Compilation Pipeline
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||||
|
│ Lux Source │ ──► │ Parser │ ──► │ Type Check │ ──► │ C Codegen │
|
||||||
|
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||||
|
│ Binary │ ◄── │ cc/gcc/ │ ◄── │ Temp .c │ ◄───│ C Code │
|
||||||
|
│ │ │ clang │ │ File │ │ (string) │
|
||||||
|
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```bash
|
||||||
|
lux compile foo.lux # Produces ./foo binary
|
||||||
|
lux compile foo.lux -o app # Produces ./app binary
|
||||||
|
lux compile foo.lux --run # Compile and execute
|
||||||
|
lux compile foo.lux --emit-c # Output C code (for debugging)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Runtime Type Representations
|
||||||
|
|
||||||
|
### Primitive Types
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef int64_t LuxInt;
|
||||||
|
typedef double LuxFloat;
|
||||||
|
typedef bool LuxBool;
|
||||||
|
typedef char* LuxString;
|
||||||
|
typedef void* LuxUnit;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Closures
|
||||||
|
|
||||||
|
Closures are represented as a pair of environment pointer and function pointer:
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct {
|
||||||
|
void* env; // Pointer to captured variables
|
||||||
|
void* fn_ptr; // Pointer to the function
|
||||||
|
} LuxClosure;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example - capturing a variable:**
|
||||||
|
```lux
|
||||||
|
let multiplier = 3
|
||||||
|
let triple = fn(x: Int): Int => x * multiplier
|
||||||
|
```
|
||||||
|
|
||||||
|
Generates:
|
||||||
|
```c
|
||||||
|
// Environment struct for captured variables
|
||||||
|
typedef struct {
|
||||||
|
LuxInt multiplier;
|
||||||
|
} Env_triple;
|
||||||
|
|
||||||
|
// The lambda function
|
||||||
|
LuxInt lambda_triple(void* _env, LuxInt x) {
|
||||||
|
Env_triple* env = (Env_triple*)_env;
|
||||||
|
return x * env->multiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creating the closure
|
||||||
|
Env_triple* env = malloc(sizeof(Env_triple));
|
||||||
|
env->multiplier = multiplier;
|
||||||
|
LuxClosure* triple = malloc(sizeof(LuxClosure));
|
||||||
|
triple->env = env;
|
||||||
|
triple->fn_ptr = (void*)lambda_triple;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Algebraic Data Types (ADTs)
|
||||||
|
|
||||||
|
ADTs compile to tagged unions:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
type Option =
|
||||||
|
| Some(Int)
|
||||||
|
| None
|
||||||
|
```
|
||||||
|
|
||||||
|
Generates:
|
||||||
|
```c
|
||||||
|
typedef enum { Option_TAG_SOME, Option_TAG_NONE } Option_Tag;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Option_Tag tag;
|
||||||
|
union {
|
||||||
|
struct { LuxInt field0; } some;
|
||||||
|
// None has no fields
|
||||||
|
} data;
|
||||||
|
} Option;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pattern matching** compiles to if/else chains:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
match opt {
|
||||||
|
Some(x) => x,
|
||||||
|
None => 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Generates:
|
||||||
|
```c
|
||||||
|
if (opt.tag == Option_TAG_SOME) {
|
||||||
|
LuxInt x = opt.data.some.field0;
|
||||||
|
result = x;
|
||||||
|
} else if (opt.tag == Option_TAG_NONE) {
|
||||||
|
result = 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lists
|
||||||
|
|
||||||
|
Lists are dynamic arrays with boxed elements:
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct {
|
||||||
|
void** elements; // Array of boxed elements
|
||||||
|
int64_t length;
|
||||||
|
int64_t capacity;
|
||||||
|
} LuxList;
|
||||||
|
```
|
||||||
|
|
||||||
|
Elements are boxed/unboxed at access time:
|
||||||
|
```c
|
||||||
|
void* lux_box_int(LuxInt n) {
|
||||||
|
LuxInt* p = malloc(sizeof(LuxInt));
|
||||||
|
*p = n;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
LuxInt lux_unbox_int(void* p) {
|
||||||
|
return *(LuxInt*)p;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**List operations** (map, filter, fold, etc.) generate inline loops:
|
||||||
|
```c
|
||||||
|
// List.map(nums, fn(x) => x * 2)
|
||||||
|
LuxList* result = lux_list_new(nums->length);
|
||||||
|
for (int64_t i = 0; i < nums->length; i++) {
|
||||||
|
void* elem = nums->elements[i];
|
||||||
|
LuxInt mapped = ((LuxInt(*)(void*, LuxInt))fn->fn_ptr)(fn->env, lux_unbox_int(elem));
|
||||||
|
result->elements[i] = lux_box_int(mapped);
|
||||||
|
}
|
||||||
|
result->length = nums->length;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Current Limitations
|
||||||
|
|
||||||
|
### 1. Memory Leaks
|
||||||
|
|
||||||
|
**Everything allocated is never freed.** This includes:
|
||||||
|
- Closure environments
|
||||||
|
- ADT values
|
||||||
|
- List elements and arrays
|
||||||
|
- Strings from concatenation
|
||||||
|
|
||||||
|
This is acceptable for short-lived programs but not for long-running services.
|
||||||
|
|
||||||
|
### 2. Limited Effects
|
||||||
|
|
||||||
|
Only `Console.print` is supported, hardcoded to `printf`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
static void lux_console_print(LuxString msg) {
|
||||||
|
printf("%s\n", msg);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Other effects (File, Http, Random, etc.) are not yet implemented in the C backend.
|
||||||
|
|
||||||
|
### 3. If/Else Side Effects
|
||||||
|
|
||||||
|
The C backend uses ternary operators for if/else:
|
||||||
|
|
||||||
|
```c
|
||||||
|
(condition ? then_value : else_value)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem:** If branches contain side effects (like `Console.print`), both branches are evaluated during code generation, causing both to execute.
|
||||||
|
|
||||||
|
**Workaround:** Use pure expressions in if/else branches, then print the result:
|
||||||
|
```lux
|
||||||
|
// Bad - both prints execute
|
||||||
|
if x > 0 then Console.print("positive") else Console.print("negative")
|
||||||
|
|
||||||
|
// Good - only one print
|
||||||
|
let msg = if x > 0 then "positive" else "negative"
|
||||||
|
Console.print(msg)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Comparison with Other Languages
|
||||||
|
|
||||||
|
### Koka (Our Inspiration)
|
||||||
|
|
||||||
|
Koka also compiles to C with algebraic effects. Key differences:
|
||||||
|
|
||||||
|
| Aspect | Koka | Lux (current) |
|
||||||
|
|--------|------|---------------|
|
||||||
|
| Memory | Perceus RC | Leaks |
|
||||||
|
| Effects | Evidence passing (zero-cost) | Runtime lookup |
|
||||||
|
| Closures | Environment vectors | Heap-allocated structs |
|
||||||
|
| Maturity | Production-ready | Experimental |
|
||||||
|
|
||||||
|
### Rust
|
||||||
|
|
||||||
|
| Aspect | Rust | Lux |
|
||||||
|
|--------|------|-----|
|
||||||
|
| Target | LLVM | C |
|
||||||
|
| Memory | Ownership/borrowing | Leaks |
|
||||||
|
| Safety | Compile-time guaranteed | Runtime (interpreter) |
|
||||||
|
| Learning curve | Steep | Medium |
|
||||||
|
|
||||||
|
### Zig
|
||||||
|
|
||||||
|
| Aspect | Zig | Lux |
|
||||||
|
|--------|-----|-----|
|
||||||
|
| Target | LLVM | C |
|
||||||
|
| Memory | Manual with allocators | Leaks |
|
||||||
|
| Philosophy | Explicit control | High-level abstraction |
|
||||||
|
|
||||||
|
### Go
|
||||||
|
|
||||||
|
| Aspect | Go | Lux |
|
||||||
|
|--------|-----|-----|
|
||||||
|
| Target | Native | C |
|
||||||
|
| Memory | Concurrent GC | Leaks |
|
||||||
|
| Effects | None | Algebraic effects |
|
||||||
|
| Latency | Unpredictable (GC pauses) | Predictable (no GC) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Future Roadmap
|
||||||
|
|
||||||
|
### Phase 1: Evidence Passing (Zero-Cost Effects)
|
||||||
|
|
||||||
|
**Goal:** Eliminate runtime effect handler lookup.
|
||||||
|
|
||||||
|
**Current approach (slow):**
|
||||||
|
```rust
|
||||||
|
// O(n) search through handler stack
|
||||||
|
for handler in self.handler_stack.iter().rev() {
|
||||||
|
if handler.effect == request.effect {
|
||||||
|
return handler.invoke(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Evidence passing (fast):**
|
||||||
|
```c
|
||||||
|
typedef struct {
|
||||||
|
Console* console;
|
||||||
|
FileIO* fileio;
|
||||||
|
} Evidence;
|
||||||
|
|
||||||
|
void greet(Evidence* ev, const char* name) {
|
||||||
|
ev->console->print(ev, name); // Direct call, no search
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected speedup:** 10-20x for effect-heavy code.
|
||||||
|
|
||||||
|
### Phase 2: Perceus Reference Counting
|
||||||
|
|
||||||
|
**Goal:** Deterministic memory management without GC pauses.
|
||||||
|
|
||||||
|
Perceus is a compile-time reference counting system that:
|
||||||
|
1. Inserts increment/decrement at precise points
|
||||||
|
2. Detects when values can be reused in-place (FBIP)
|
||||||
|
3. Guarantees no memory leaks without runtime GC
|
||||||
|
|
||||||
|
**Example - reuse analysis:**
|
||||||
|
```lux
|
||||||
|
fn increment(xs: List<Int>): List<Int> =
|
||||||
|
List.map(xs, fn(x) => x + 1)
|
||||||
|
```
|
||||||
|
|
||||||
|
If `xs` has refcount=1, the list can be mutated in-place instead of copied.
|
||||||
|
|
||||||
|
### Phase 3: More Effects
|
||||||
|
|
||||||
|
Implement C versions of:
|
||||||
|
- `File` (read, write, exists)
|
||||||
|
- `Http` (get, post)
|
||||||
|
- `Random` (int, bool)
|
||||||
|
- `Time` (now, sleep)
|
||||||
|
|
||||||
|
### Phase 4: JavaScript Backend
|
||||||
|
|
||||||
|
Compile Lux to JavaScript for browser/Node.js:
|
||||||
|
- Effects → Direct DOM/API calls
|
||||||
|
- No runtime needed
|
||||||
|
- Enables full-stack Lux development
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
### Name Mangling
|
||||||
|
|
||||||
|
Lux identifiers are mangled for C compatibility:
|
||||||
|
|
||||||
|
| Lux | C |
|
||||||
|
|-----|---|
|
||||||
|
| `foo` | `foo_lux` |
|
||||||
|
| `myFunction` | `myFunction_lux` |
|
||||||
|
| `List.map` | Inline code (not a function call) |
|
||||||
|
|
||||||
|
### Generated C Structure
|
||||||
|
|
||||||
|
```c
|
||||||
|
// 1. Includes and type definitions
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
typedef int64_t LuxInt;
|
||||||
|
// ... more types ...
|
||||||
|
|
||||||
|
// 2. Runtime helpers (string concat, list operations, etc.)
|
||||||
|
static LuxString lux_string_concat(LuxString a, LuxString b) { ... }
|
||||||
|
static LuxList* lux_list_new(int64_t capacity) { ... }
|
||||||
|
// ... more helpers ...
|
||||||
|
|
||||||
|
// 3. Forward declarations
|
||||||
|
void main_lux(void);
|
||||||
|
|
||||||
|
// 4. Closure/lambda definitions
|
||||||
|
static LuxInt lambda_1(void* _env, LuxInt x) { ... }
|
||||||
|
|
||||||
|
// 5. User-defined functions
|
||||||
|
void greet_lux(LuxString name) { ... }
|
||||||
|
|
||||||
|
// 6. Main function
|
||||||
|
void main_lux(void) { ... }
|
||||||
|
|
||||||
|
// 7. Entry point
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
main_lux();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Prelude Size
|
||||||
|
|
||||||
|
The generated C prelude is approximately 150 lines, including:
|
||||||
|
- Type definitions (~20 lines)
|
||||||
|
- String operations (~30 lines)
|
||||||
|
- List types and operations (~80 lines)
|
||||||
|
- Boxing/unboxing helpers (~20 lines)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing the C Backend
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Compile and run
|
||||||
|
lux compile examples/hello.lux --run
|
||||||
|
|
||||||
|
# Compile to binary
|
||||||
|
lux compile examples/hello.lux -o hello
|
||||||
|
./hello
|
||||||
|
|
||||||
|
# View generated C (for debugging)
|
||||||
|
lux compile examples/hello.lux --emit-c
|
||||||
|
|
||||||
|
# Save C to file
|
||||||
|
lux compile examples/hello.lux --emit-c -o hello.c
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [Perceus: Garbage Free Reference Counting](https://www.microsoft.com/en-us/research/publication/perceus-garbage-free-reference-counting-with-reuse/) - Microsoft Research
|
||||||
|
- [Generalized Evidence Passing for Effect Handlers](https://www.microsoft.com/en-us/research/publication/generalized-evidence-passing-for-effect-handlers/) - Koka's effect compilation
|
||||||
|
- [Koka Language](https://koka-lang.github.io/koka/doc/book.html) - Effect system language that compiles to C
|
||||||
|
- [Nim Backend Integration](https://nim-lang.org/docs/backends.html) - Another compile-to-C language
|
||||||
@@ -181,7 +181,6 @@ fn processAny(x: Int @latest): Int = x // any version
|
|||||||
|
|
||||||
### Planned (Not Yet Fully Implemented)
|
### Planned (Not Yet Fully Implemented)
|
||||||
|
|
||||||
- **C Backend Lists**: Closures and pattern matching work, lists pending
|
|
||||||
- **Auto-migration Generation**: Migration bodies stored, execution pending
|
- **Auto-migration Generation**: Migration bodies stored, execution pending
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -234,7 +233,6 @@ Quick iteration with type inference and a REPL.
|
|||||||
|
|
||||||
| Limitation | Description |
|
| Limitation | Description |
|
||||||
|------------|-------------|
|
|------------|-------------|
|
||||||
| **Limited C Backend** | Functions, closures, ADTs work; lists pending |
|
|
||||||
| **No Package Manager** | Can't share/publish packages yet |
|
| **No Package Manager** | Can't share/publish packages yet |
|
||||||
| **New Paradigm** | Effects require learning new concepts |
|
| **New Paradigm** | Effects require learning new concepts |
|
||||||
| **Small Ecosystem** | No community packages yet |
|
| **Small Ecosystem** | No community packages yet |
|
||||||
@@ -371,13 +369,13 @@ Values + Effects C Code → GCC/Clang
|
|||||||
- ✅ Console.readLine and Console.readInt
|
- ✅ Console.readLine and Console.readInt
|
||||||
- ✅ C Backend (basic functions, Console.print)
|
- ✅ C Backend (basic functions, Console.print)
|
||||||
- ✅ C Backend closures and pattern matching
|
- ✅ C Backend closures and pattern matching
|
||||||
|
- ✅ C Backend lists (all 16 operations)
|
||||||
- ✅ Watch mode / hot reload
|
- ✅ Watch mode / hot reload
|
||||||
- ✅ Formatter
|
- ✅ Formatter
|
||||||
|
|
||||||
**In Progress:**
|
**In Progress:**
|
||||||
1. **C Backend Lists** - List operations pending
|
1. **Schema Evolution** - Type system integration, auto-migration
|
||||||
2. **Schema Evolution** - Type system integration, auto-migration
|
2. **Error Message Quality** - Context lines shown, suggestions partial
|
||||||
3. **Error Message Quality** - Context lines shown, suggestions partial
|
|
||||||
|
|
||||||
**Planned:**
|
**Planned:**
|
||||||
4. **SQL Effect** - Database access
|
4. **SQL Effect** - Database access
|
||||||
|
|||||||
@@ -223,7 +223,7 @@
|
|||||||
| C backend (basic) | P1 | — | ✅ Complete (functions, Console.print) |
|
| C backend (basic) | P1 | — | ✅ Complete (functions, Console.print) |
|
||||||
| Extend C backend (closures) | P1 | — | ✅ Complete |
|
| Extend C backend (closures) | P1 | — | ✅ Complete |
|
||||||
| Extend C backend (pattern matching) | P1 | — | ✅ Complete |
|
| Extend C backend (pattern matching) | P1 | — | ✅ Complete |
|
||||||
| Extend C backend (lists) | P1 | 1 week | ❌ Missing |
|
| Extend C backend (lists) | P1 | — | ✅ Complete |
|
||||||
| JS backend | P2 | 4 weeks | ❌ Missing |
|
| JS backend | P2 | 4 weeks | ❌ Missing |
|
||||||
| WASM backend | P3 | 4 weeks | ❌ Missing |
|
| WASM backend | P3 | 4 weeks | ❌ Missing |
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,46 @@
|
|||||||
//! C code generation backend for Lux
|
//! C code generation backend for Lux
|
||||||
//!
|
//!
|
||||||
//! Compiles Lux programs to C code that can be compiled with GCC/Clang.
|
//! Compiles Lux programs to C code that can be compiled with GCC/Clang.
|
||||||
//! Inspired by Koka's approach: effects compile to evidence passing,
|
//!
|
||||||
//! no garbage collector needed with Perceus-style reference counting.
|
//! ## Compilation Strategy
|
||||||
|
//!
|
||||||
|
//! Lux source → Parse → Type check → Generate C → Invoke cc/gcc/clang → Binary
|
||||||
|
//!
|
||||||
|
//! This approach is similar to Koka, Nim, and Chicken Scheme. It leverages
|
||||||
|
//! decades of C compiler optimizations (GCC/Clang) without reimplementing them.
|
||||||
|
//!
|
||||||
|
//! ## Runtime Type Representations
|
||||||
|
//!
|
||||||
|
//! | Lux Type | C Type |
|
||||||
|
//! |----------|--------|
|
||||||
|
//! | Int | `int64_t` (LuxInt) |
|
||||||
|
//! | Float | `double` (LuxFloat) |
|
||||||
|
//! | Bool | `bool` (LuxBool) |
|
||||||
|
//! | String | `char*` (LuxString) |
|
||||||
|
//! | Closure | `struct {void* env, void* fn_ptr}` (LuxClosure) |
|
||||||
|
//! | ADT | Tagged union (enum tag + union of variant structs) |
|
||||||
|
//! | List | `struct {void** elements, int64_t length, capacity}` (LuxList) |
|
||||||
//!
|
//!
|
||||||
//! ## Supported Features
|
//! ## Supported Features
|
||||||
//!
|
//!
|
||||||
//! - **Functions**: Direct function calls with proper name mangling
|
//! - **Functions**: Direct function calls with name mangling (`foo` → `foo_lux`)
|
||||||
//! - **Closures**: Lambda expressions with captured environments
|
//! - **Closures**: Heap-allocated environment struct + function pointer
|
||||||
//! - Environment structs hold captured variables
|
//! - **ADTs**: Tagged unions with exhaustive pattern matching
|
||||||
//! - Closures are `{void* env, void* fn_ptr}` structs
|
//! - **Pattern Matching**: Compiles to if/else chains checking tags
|
||||||
//! - **ADTs**: Algebraic data types (enums with data)
|
//! - **Lists**: Dynamic arrays with void* boxing for generic elements
|
||||||
//! - Tag enums for variant discrimination
|
|
||||||
//! - Union structs for variant data
|
|
||||||
//! - Recursive types use pointers with heap allocation
|
|
||||||
//! - **Pattern Matching**: Full pattern variable binding
|
|
||||||
//! - Constructor patterns extract variant data
|
|
||||||
//! - Nested patterns supported
|
|
||||||
//! - Type inference for bound variables
|
|
||||||
//!
|
//!
|
||||||
//! ## Limitations
|
//! ## Current Limitations
|
||||||
//!
|
//!
|
||||||
//! - **Lists**: Not yet implemented (use interpreter)
|
//! - **Memory**: No deallocation - everything leaks (GC/RC not yet implemented)
|
||||||
//! - **Memory**: No automatic deallocation (memory leaks for closures/ADTs)
|
//! - **Effects**: Only `Console.print` supported (hardcoded to printf)
|
||||||
//! - **Effects**: Only Console.print supported
|
//! - **If/else side effects**: Uses ternary `?:`, so both branches execute
|
||||||
|
//! during codegen if they contain effects like Console.print
|
||||||
|
//!
|
||||||
|
//! ## Future Work (see docs/C_BACKEND.md)
|
||||||
|
//!
|
||||||
|
//! - Evidence passing for zero-cost effects (like Koka)
|
||||||
|
//! - Perceus-style reference counting for memory management
|
||||||
|
//! - More effects (File, Http, etc.)
|
||||||
|
|
||||||
use crate::ast::*;
|
use crate::ast::*;
|
||||||
use std::collections::{HashSet, HashMap};
|
use std::collections::{HashSet, HashMap};
|
||||||
@@ -368,6 +385,90 @@ impl CBackend {
|
|||||||
self.writeln(" printf(\"%s\\n\", msg);");
|
self.writeln(" printf(\"%s\\n\", msg);");
|
||||||
self.writeln("}");
|
self.writeln("}");
|
||||||
self.writeln("");
|
self.writeln("");
|
||||||
|
self.writeln("// === List Types ===");
|
||||||
|
self.writeln("");
|
||||||
|
self.writeln("typedef struct {");
|
||||||
|
self.writeln(" void** elements;");
|
||||||
|
self.writeln(" int64_t length;");
|
||||||
|
self.writeln(" int64_t capacity;");
|
||||||
|
self.writeln("} LuxList;");
|
||||||
|
self.writeln("");
|
||||||
|
self.writeln("// Built-in Option type for List.head, List.tail, List.get, List.find");
|
||||||
|
self.writeln("typedef enum { Option_TAG_NONE, Option_TAG_SOME } Option_Tag;");
|
||||||
|
self.writeln("typedef struct { void* field0; } Option_Some_Data;");
|
||||||
|
self.writeln("typedef struct { Option_Tag tag; union { Option_Some_Data some; } data; } Option;");
|
||||||
|
self.writeln("");
|
||||||
|
self.writeln("// === List Operations ===");
|
||||||
|
self.writeln("");
|
||||||
|
self.writeln("static LuxList* lux_list_new(int64_t capacity) {");
|
||||||
|
self.writeln(" LuxList* list = malloc(sizeof(LuxList));");
|
||||||
|
self.writeln(" list->capacity = capacity > 0 ? capacity : 4;");
|
||||||
|
self.writeln(" list->elements = malloc(sizeof(void*) * list->capacity);");
|
||||||
|
self.writeln(" list->length = 0;");
|
||||||
|
self.writeln(" return list;");
|
||||||
|
self.writeln("}");
|
||||||
|
self.writeln("");
|
||||||
|
self.writeln("static int64_t lux_list_length(LuxList* list) { return list->length; }");
|
||||||
|
self.writeln("static LuxBool lux_list_isEmpty(LuxList* list) { return list->length == 0; }");
|
||||||
|
self.writeln("");
|
||||||
|
self.writeln("static LuxList* lux_list_concat(LuxList* a, LuxList* b) {");
|
||||||
|
self.writeln(" LuxList* result = lux_list_new(a->length + b->length);");
|
||||||
|
self.writeln(" for (int64_t i = 0; i < a->length; i++) result->elements[i] = a->elements[i];");
|
||||||
|
self.writeln(" for (int64_t i = 0; i < b->length; i++) result->elements[a->length + i] = b->elements[i];");
|
||||||
|
self.writeln(" result->length = a->length + b->length;");
|
||||||
|
self.writeln(" return result;");
|
||||||
|
self.writeln("}");
|
||||||
|
self.writeln("");
|
||||||
|
self.writeln("static LuxList* lux_list_reverse(LuxList* list) {");
|
||||||
|
self.writeln(" LuxList* result = lux_list_new(list->length);");
|
||||||
|
self.writeln(" for (int64_t i = 0; i < list->length; i++) result->elements[i] = list->elements[list->length - 1 - i];");
|
||||||
|
self.writeln(" result->length = list->length;");
|
||||||
|
self.writeln(" return result;");
|
||||||
|
self.writeln("}");
|
||||||
|
self.writeln("");
|
||||||
|
self.writeln("static LuxList* lux_list_take(LuxList* list, int64_t n) {");
|
||||||
|
self.writeln(" if (n <= 0) return lux_list_new(0);");
|
||||||
|
self.writeln(" if (n > list->length) n = list->length;");
|
||||||
|
self.writeln(" LuxList* result = lux_list_new(n);");
|
||||||
|
self.writeln(" for (int64_t i = 0; i < n; i++) result->elements[i] = list->elements[i];");
|
||||||
|
self.writeln(" result->length = n;");
|
||||||
|
self.writeln(" return result;");
|
||||||
|
self.writeln("}");
|
||||||
|
self.writeln("");
|
||||||
|
self.writeln("static LuxList* lux_list_drop(LuxList* list, int64_t n) {");
|
||||||
|
self.writeln(" if (n >= list->length) return lux_list_new(0);");
|
||||||
|
self.writeln(" if (n <= 0) return list;");
|
||||||
|
self.writeln(" int64_t new_len = list->length - n;");
|
||||||
|
self.writeln(" LuxList* result = lux_list_new(new_len);");
|
||||||
|
self.writeln(" for (int64_t i = 0; i < new_len; i++) result->elements[i] = list->elements[n + i];");
|
||||||
|
self.writeln(" result->length = new_len;");
|
||||||
|
self.writeln(" return result;");
|
||||||
|
self.writeln("}");
|
||||||
|
self.writeln("");
|
||||||
|
self.writeln("static LuxList* lux_list_range(int64_t start, int64_t end) {");
|
||||||
|
self.writeln(" if (end <= start) return lux_list_new(0);");
|
||||||
|
self.writeln(" int64_t len = end - start;");
|
||||||
|
self.writeln(" LuxList* result = lux_list_new(len);");
|
||||||
|
self.writeln(" for (int64_t i = 0; i < len; i++) {");
|
||||||
|
self.writeln(" LuxInt* p = malloc(sizeof(LuxInt)); *p = start + i;");
|
||||||
|
self.writeln(" result->elements[i] = p;");
|
||||||
|
self.writeln(" }");
|
||||||
|
self.writeln(" result->length = len;");
|
||||||
|
self.writeln(" return result;");
|
||||||
|
self.writeln("}");
|
||||||
|
self.writeln("");
|
||||||
|
self.writeln("static Option lux_option_none(void) { return (Option){Option_TAG_NONE}; }");
|
||||||
|
self.writeln("static Option lux_option_some(void* value) { return (Option){Option_TAG_SOME, .data.some = {value}}; }");
|
||||||
|
self.writeln("");
|
||||||
|
self.writeln("// === Boxing/Unboxing ===");
|
||||||
|
self.writeln("");
|
||||||
|
self.writeln("static void* lux_box_int(LuxInt n) { LuxInt* p = malloc(sizeof(LuxInt)); *p = n; return p; }");
|
||||||
|
self.writeln("static LuxInt lux_unbox_int(void* p) { return *(LuxInt*)p; }");
|
||||||
|
self.writeln("static void* lux_box_bool(LuxBool b) { LuxBool* p = malloc(sizeof(LuxBool)); *p = b; return p; }");
|
||||||
|
self.writeln("static LuxBool lux_unbox_bool(void* p) { return *(LuxBool*)p; }");
|
||||||
|
self.writeln("static void* lux_box_string(LuxString s) { return s; }");
|
||||||
|
self.writeln("static LuxString lux_unbox_string(void* p) { return (LuxString)p; }");
|
||||||
|
self.writeln("");
|
||||||
self.writeln("// === Forward Declarations ===");
|
self.writeln("// === Forward Declarations ===");
|
||||||
self.writeln("");
|
self.writeln("");
|
||||||
}
|
}
|
||||||
@@ -614,9 +715,10 @@ impl CBackend {
|
|||||||
let val = self.emit_expr(value)?;
|
let val = self.emit_expr(value)?;
|
||||||
let var_name = format!("{}_{}", name.name, self.fresh_name());
|
let var_name = format!("{}_{}", name.name, self.fresh_name());
|
||||||
|
|
||||||
// For simple cases, we can use a compound literal or statement expression
|
// Infer the type from the value expression
|
||||||
// For now, emit as a block
|
let var_type = self.infer_expr_type(value).unwrap_or_else(|| "LuxInt".to_string());
|
||||||
self.writeln(&format!("LuxInt {} = {};", var_name, val));
|
|
||||||
|
self.writeln(&format!("{} {} = {};", var_type, var_name, val));
|
||||||
|
|
||||||
// Substitute the name in the body
|
// Substitute the name in the body
|
||||||
// For now, assume the variable is directly usable
|
// For now, assume the variable is directly usable
|
||||||
@@ -625,6 +727,15 @@ impl CBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Expr::Call { func, args, .. } => {
|
Expr::Call { func, args, .. } => {
|
||||||
|
// Check for List module calls first (List.map, List.filter, etc.)
|
||||||
|
if let Expr::Field { object, field, .. } = func.as_ref() {
|
||||||
|
if let Expr::Var(module_name) = object.as_ref() {
|
||||||
|
if module_name.name == "List" {
|
||||||
|
return self.emit_list_operation(&field.name, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let arg_strs: Result<Vec<_>, _> = args.iter().map(|a| self.emit_expr(a)).collect();
|
let arg_strs: Result<Vec<_>, _> = args.iter().map(|a| self.emit_expr(a)).collect();
|
||||||
let args_str = arg_strs?.join(", ");
|
let args_str = arg_strs?.join(", ");
|
||||||
|
|
||||||
@@ -795,6 +906,11 @@ impl CBackend {
|
|||||||
return Ok("NULL".to_string());
|
return Ok("NULL".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List module operations (treated as effect by parser but handled specially)
|
||||||
|
if effect.name == "List" {
|
||||||
|
return self.emit_list_operation(&operation.name, args);
|
||||||
|
}
|
||||||
|
|
||||||
// For other effects, emit evidence-passing call
|
// For other effects, emit evidence-passing call
|
||||||
let arg_strs: Result<Vec<_>, _> = args.iter().map(|a| self.emit_expr(a)).collect();
|
let arg_strs: Result<Vec<_>, _> = args.iter().map(|a| self.emit_expr(a)).collect();
|
||||||
Ok(format!("ev_{}__{}({})",
|
Ok(format!("ev_{}__{}({})",
|
||||||
@@ -820,6 +936,10 @@ impl CBackend {
|
|||||||
self.emit_match(scrutinee, arms)
|
self.emit_match(scrutinee, arms)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Expr::List { elements, .. } => {
|
||||||
|
self.emit_list_literal(elements)
|
||||||
|
}
|
||||||
|
|
||||||
_ => Err(CGenError {
|
_ => Err(CGenError {
|
||||||
message: format!("Unsupported expression type in C backend"),
|
message: format!("Unsupported expression type in C backend"),
|
||||||
span: None,
|
span: None,
|
||||||
@@ -827,6 +947,322 @@ impl CBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn emit_list_literal(&mut self, elements: &[Expr]) -> Result<String, CGenError> {
|
||||||
|
let list_var = format!("_list_{}", self.fresh_name());
|
||||||
|
let len = elements.len();
|
||||||
|
|
||||||
|
self.writeln(&format!("LuxList* {} = lux_list_new({});", list_var, len));
|
||||||
|
|
||||||
|
for (i, elem) in elements.iter().enumerate() {
|
||||||
|
let elem_val = self.emit_expr(elem)?;
|
||||||
|
let boxed = self.box_value(&elem_val, self.infer_expr_type(elem).as_deref());
|
||||||
|
self.writeln(&format!("{}->elements[{}] = {};", list_var, i, boxed));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.writeln(&format!("{}->length = {};", list_var, len));
|
||||||
|
Ok(list_var)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn box_value(&self, val: &str, type_hint: Option<&str>) -> String {
|
||||||
|
match type_hint {
|
||||||
|
Some("LuxInt") => format!("lux_box_int({})", val),
|
||||||
|
Some("LuxBool") => format!("lux_box_bool({})", val),
|
||||||
|
Some("LuxString") => format!("lux_box_string({})", val),
|
||||||
|
Some(t) if t.ends_with('*') => val.to_string(), // Already a pointer
|
||||||
|
_ => format!("lux_box_int({})", val), // Default to int boxing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unbox_value(&self, val: &str, type_hint: Option<&str>) -> String {
|
||||||
|
match type_hint {
|
||||||
|
Some("LuxInt") => format!("lux_unbox_int({})", val),
|
||||||
|
Some("LuxBool") => format!("lux_unbox_bool({})", val),
|
||||||
|
Some("LuxString") => format!("lux_unbox_string({})", val),
|
||||||
|
_ => format!("lux_unbox_int({})", val), // Default to int unboxing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emit code for List module operations (List.map, List.filter, etc.)
|
||||||
|
fn emit_list_operation(&mut self, op: &str, args: &[Expr]) -> Result<String, CGenError> {
|
||||||
|
match op {
|
||||||
|
// Simple operations - direct C function calls
|
||||||
|
"length" => {
|
||||||
|
if args.len() != 1 {
|
||||||
|
return Err(CGenError { message: "List.length takes 1 argument".to_string(), span: None });
|
||||||
|
}
|
||||||
|
let list = self.emit_expr(&args[0])?;
|
||||||
|
Ok(format!("lux_list_length({})", list))
|
||||||
|
}
|
||||||
|
"isEmpty" => {
|
||||||
|
if args.len() != 1 {
|
||||||
|
return Err(CGenError { message: "List.isEmpty takes 1 argument".to_string(), span: None });
|
||||||
|
}
|
||||||
|
let list = self.emit_expr(&args[0])?;
|
||||||
|
Ok(format!("lux_list_isEmpty({})", list))
|
||||||
|
}
|
||||||
|
"concat" => {
|
||||||
|
if args.len() != 2 {
|
||||||
|
return Err(CGenError { message: "List.concat takes 2 arguments".to_string(), span: None });
|
||||||
|
}
|
||||||
|
let list1 = self.emit_expr(&args[0])?;
|
||||||
|
let list2 = self.emit_expr(&args[1])?;
|
||||||
|
Ok(format!("lux_list_concat({}, {})", list1, list2))
|
||||||
|
}
|
||||||
|
"reverse" => {
|
||||||
|
if args.len() != 1 {
|
||||||
|
return Err(CGenError { message: "List.reverse takes 1 argument".to_string(), span: None });
|
||||||
|
}
|
||||||
|
let list = self.emit_expr(&args[0])?;
|
||||||
|
Ok(format!("lux_list_reverse({})", list))
|
||||||
|
}
|
||||||
|
"range" => {
|
||||||
|
if args.len() != 2 {
|
||||||
|
return Err(CGenError { message: "List.range takes 2 arguments".to_string(), span: None });
|
||||||
|
}
|
||||||
|
let start = self.emit_expr(&args[0])?;
|
||||||
|
let end = self.emit_expr(&args[1])?;
|
||||||
|
Ok(format!("lux_list_range({}, {})", start, end))
|
||||||
|
}
|
||||||
|
"take" => {
|
||||||
|
if args.len() != 2 {
|
||||||
|
return Err(CGenError { message: "List.take takes 2 arguments".to_string(), span: None });
|
||||||
|
}
|
||||||
|
let list = self.emit_expr(&args[0])?;
|
||||||
|
let n = self.emit_expr(&args[1])?;
|
||||||
|
Ok(format!("lux_list_take({}, {})", list, n))
|
||||||
|
}
|
||||||
|
"drop" => {
|
||||||
|
if args.len() != 2 {
|
||||||
|
return Err(CGenError { message: "List.drop takes 2 arguments".to_string(), span: None });
|
||||||
|
}
|
||||||
|
let list = self.emit_expr(&args[0])?;
|
||||||
|
let n = self.emit_expr(&args[1])?;
|
||||||
|
Ok(format!("lux_list_drop({}, {})", list, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access operations - return Option
|
||||||
|
"head" => {
|
||||||
|
if args.len() != 1 {
|
||||||
|
return Err(CGenError { message: "List.head takes 1 argument".to_string(), span: None });
|
||||||
|
}
|
||||||
|
let list = self.emit_expr(&args[0])?;
|
||||||
|
let result_var = format!("_head_{}", self.fresh_name());
|
||||||
|
self.writeln(&format!("Option {} = ({}->length > 0) ? lux_option_some({}->elements[0]) : lux_option_none();", result_var, list, list));
|
||||||
|
Ok(result_var)
|
||||||
|
}
|
||||||
|
"tail" => {
|
||||||
|
if args.len() != 1 {
|
||||||
|
return Err(CGenError { message: "List.tail takes 1 argument".to_string(), span: None });
|
||||||
|
}
|
||||||
|
let list = self.emit_expr(&args[0])?;
|
||||||
|
let result_var = format!("_tail_{}", self.fresh_name());
|
||||||
|
self.writeln(&format!("Option {};", result_var));
|
||||||
|
self.writeln(&format!("if ({0}->length > 0) {{", list));
|
||||||
|
self.indent += 1;
|
||||||
|
self.writeln(&format!("LuxList* _tail_list = lux_list_new({0}->length - 1);", list));
|
||||||
|
self.writeln(&format!("for (int64_t i = 1; i < {0}->length; i++) {{", list));
|
||||||
|
self.indent += 1;
|
||||||
|
self.writeln(&format!("_tail_list->elements[i-1] = {0}->elements[i];", list));
|
||||||
|
self.indent -= 1;
|
||||||
|
self.writeln("}");
|
||||||
|
self.writeln(&format!("_tail_list->length = {0}->length - 1;", list));
|
||||||
|
self.writeln(&format!("{} = lux_option_some(_tail_list);", result_var));
|
||||||
|
self.indent -= 1;
|
||||||
|
self.writeln("} else {");
|
||||||
|
self.indent += 1;
|
||||||
|
self.writeln(&format!("{} = lux_option_none();", result_var));
|
||||||
|
self.indent -= 1;
|
||||||
|
self.writeln("}");
|
||||||
|
Ok(result_var)
|
||||||
|
}
|
||||||
|
"get" => {
|
||||||
|
if args.len() != 2 {
|
||||||
|
return Err(CGenError { message: "List.get takes 2 arguments".to_string(), span: None });
|
||||||
|
}
|
||||||
|
let list = self.emit_expr(&args[0])?;
|
||||||
|
let idx = self.emit_expr(&args[1])?;
|
||||||
|
let result_var = format!("_get_{}", self.fresh_name());
|
||||||
|
self.writeln(&format!("Option {} = ({} >= 0 && {} < {}->length) ? lux_option_some({}->elements[{}]) : lux_option_none();", result_var, idx, idx, list, list, idx));
|
||||||
|
Ok(result_var)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Higher-order operations - inline loops
|
||||||
|
"map" => {
|
||||||
|
if args.len() != 2 {
|
||||||
|
return Err(CGenError { message: "List.map takes 2 arguments".to_string(), span: None });
|
||||||
|
}
|
||||||
|
let list = self.emit_expr(&args[0])?;
|
||||||
|
let closure = self.emit_expr(&args[1])?;
|
||||||
|
let id = self.fresh_name();
|
||||||
|
let result_var = format!("_map_{}", id);
|
||||||
|
let i_var = format!("_i_{}", id);
|
||||||
|
let elem_var = format!("_elem_{}", id);
|
||||||
|
let fn_var = format!("_fn_{}", id);
|
||||||
|
let mapped_var = format!("_mapped_{}", id);
|
||||||
|
|
||||||
|
self.writeln(&format!("LuxList* {} = lux_list_new({}->length);", result_var, list));
|
||||||
|
self.writeln(&format!("for (int64_t {} = 0; {} < {}->length; {}++) {{", i_var, i_var, list, i_var));
|
||||||
|
self.indent += 1;
|
||||||
|
self.writeln(&format!("void* {} = {}->elements[{}];", elem_var, list, i_var));
|
||||||
|
self.writeln(&format!("LuxClosure* {} = (LuxClosure*){};", fn_var, closure));
|
||||||
|
self.writeln(&format!("LuxInt {} = ((LuxInt(*)(void*, LuxInt)){}->fn_ptr)({}->env, lux_unbox_int({}));", mapped_var, fn_var, fn_var, elem_var));
|
||||||
|
self.writeln(&format!("{}->elements[{}] = lux_box_int({});", result_var, i_var, mapped_var));
|
||||||
|
self.indent -= 1;
|
||||||
|
self.writeln("}");
|
||||||
|
self.writeln(&format!("{}->length = {}->length;", result_var, list));
|
||||||
|
|
||||||
|
Ok(result_var)
|
||||||
|
}
|
||||||
|
"filter" => {
|
||||||
|
if args.len() != 2 {
|
||||||
|
return Err(CGenError { message: "List.filter takes 2 arguments".to_string(), span: None });
|
||||||
|
}
|
||||||
|
let list = self.emit_expr(&args[0])?;
|
||||||
|
let closure = self.emit_expr(&args[1])?;
|
||||||
|
let id = self.fresh_name();
|
||||||
|
let result_var = format!("_filter_{}", id);
|
||||||
|
let count_var = format!("_count_{}", id);
|
||||||
|
let i_var = format!("_i_{}", id);
|
||||||
|
let elem_var = format!("_elem_{}", id);
|
||||||
|
let fn_var = format!("_fn_{}", id);
|
||||||
|
let keep_var = format!("_keep_{}", id);
|
||||||
|
|
||||||
|
self.writeln(&format!("LuxList* {} = lux_list_new({}->length);", result_var, list));
|
||||||
|
self.writeln(&format!("int64_t {} = 0;", count_var));
|
||||||
|
self.writeln(&format!("for (int64_t {} = 0; {} < {}->length; {}++) {{", i_var, i_var, list, i_var));
|
||||||
|
self.indent += 1;
|
||||||
|
self.writeln(&format!("void* {} = {}->elements[{}];", elem_var, list, i_var));
|
||||||
|
self.writeln(&format!("LuxClosure* {} = (LuxClosure*){};", fn_var, closure));
|
||||||
|
self.writeln(&format!("LuxBool {} = ((LuxBool(*)(void*, LuxInt)){}->fn_ptr)({}->env, lux_unbox_int({}));", keep_var, fn_var, fn_var, elem_var));
|
||||||
|
self.writeln(&format!("if ({}) {{", keep_var));
|
||||||
|
self.indent += 1;
|
||||||
|
self.writeln(&format!("{}->elements[{}++] = {};", result_var, count_var, elem_var));
|
||||||
|
self.indent -= 1;
|
||||||
|
self.writeln("}");
|
||||||
|
self.indent -= 1;
|
||||||
|
self.writeln("}");
|
||||||
|
self.writeln(&format!("{}->length = {};", result_var, count_var));
|
||||||
|
|
||||||
|
Ok(result_var)
|
||||||
|
}
|
||||||
|
"fold" => {
|
||||||
|
if args.len() != 3 {
|
||||||
|
return Err(CGenError { message: "List.fold takes 3 arguments".to_string(), span: None });
|
||||||
|
}
|
||||||
|
let list = self.emit_expr(&args[0])?;
|
||||||
|
let init = self.emit_expr(&args[1])?;
|
||||||
|
let closure = self.emit_expr(&args[2])?;
|
||||||
|
let id = self.fresh_name();
|
||||||
|
let result_var = format!("_fold_{}", id);
|
||||||
|
let i_var = format!("_i_{}", id);
|
||||||
|
let elem_var = format!("_elem_{}", id);
|
||||||
|
let fn_var = format!("_fn_{}", id);
|
||||||
|
|
||||||
|
self.writeln(&format!("LuxInt {} = {};", result_var, init));
|
||||||
|
self.writeln(&format!("for (int64_t {} = 0; {} < {}->length; {}++) {{", i_var, i_var, list, i_var));
|
||||||
|
self.indent += 1;
|
||||||
|
self.writeln(&format!("void* {} = {}->elements[{}];", elem_var, list, i_var));
|
||||||
|
self.writeln(&format!("LuxClosure* {} = (LuxClosure*){};", fn_var, closure));
|
||||||
|
self.writeln(&format!("{} = ((LuxInt(*)(void*, LuxInt, LuxInt)){}->fn_ptr)({}->env, {}, lux_unbox_int({}));", result_var, fn_var, fn_var, result_var, elem_var));
|
||||||
|
self.indent -= 1;
|
||||||
|
self.writeln("}");
|
||||||
|
|
||||||
|
Ok(result_var)
|
||||||
|
}
|
||||||
|
"find" => {
|
||||||
|
if args.len() != 2 {
|
||||||
|
return Err(CGenError { message: "List.find takes 2 arguments".to_string(), span: None });
|
||||||
|
}
|
||||||
|
let list = self.emit_expr(&args[0])?;
|
||||||
|
let closure = self.emit_expr(&args[1])?;
|
||||||
|
let id = self.fresh_name();
|
||||||
|
let result_var = format!("_find_{}", id);
|
||||||
|
let i_var = format!("_i_{}", id);
|
||||||
|
let elem_var = format!("_elem_{}", id);
|
||||||
|
let fn_var = format!("_fn_{}", id);
|
||||||
|
let matches_var = format!("_matches_{}", id);
|
||||||
|
|
||||||
|
self.writeln(&format!("Option {} = lux_option_none();", result_var));
|
||||||
|
self.writeln(&format!("for (int64_t {} = 0; {} < {}->length; {}++) {{", i_var, i_var, list, i_var));
|
||||||
|
self.indent += 1;
|
||||||
|
self.writeln(&format!("void* {} = {}->elements[{}];", elem_var, list, i_var));
|
||||||
|
self.writeln(&format!("LuxClosure* {} = (LuxClosure*){};", fn_var, closure));
|
||||||
|
self.writeln(&format!("LuxBool {} = ((LuxBool(*)(void*, LuxInt)){}->fn_ptr)({}->env, lux_unbox_int({}));", matches_var, fn_var, fn_var, elem_var));
|
||||||
|
self.writeln(&format!("if ({}) {{", matches_var));
|
||||||
|
self.indent += 1;
|
||||||
|
self.writeln(&format!("{} = lux_option_some({});", result_var, elem_var));
|
||||||
|
self.writeln("break;");
|
||||||
|
self.indent -= 1;
|
||||||
|
self.writeln("}");
|
||||||
|
self.indent -= 1;
|
||||||
|
self.writeln("}");
|
||||||
|
|
||||||
|
Ok(result_var)
|
||||||
|
}
|
||||||
|
"any" => {
|
||||||
|
if args.len() != 2 {
|
||||||
|
return Err(CGenError { message: "List.any takes 2 arguments".to_string(), span: None });
|
||||||
|
}
|
||||||
|
let list = self.emit_expr(&args[0])?;
|
||||||
|
let closure = self.emit_expr(&args[1])?;
|
||||||
|
let id = self.fresh_name();
|
||||||
|
let result_var = format!("_any_{}", id);
|
||||||
|
let i_var = format!("_i_{}", id);
|
||||||
|
let elem_var = format!("_elem_{}", id);
|
||||||
|
let fn_var = format!("_fn_{}", id);
|
||||||
|
|
||||||
|
self.writeln(&format!("LuxBool {} = 0;", result_var));
|
||||||
|
self.writeln(&format!("for (int64_t {} = 0; {} < {}->length; {}++) {{", i_var, i_var, list, i_var));
|
||||||
|
self.indent += 1;
|
||||||
|
self.writeln(&format!("void* {} = {}->elements[{}];", elem_var, list, i_var));
|
||||||
|
self.writeln(&format!("LuxClosure* {} = (LuxClosure*){};", fn_var, closure));
|
||||||
|
self.writeln(&format!("if (((LuxBool(*)(void*, LuxInt)){}->fn_ptr)({}->env, lux_unbox_int({}))) {{", fn_var, fn_var, elem_var));
|
||||||
|
self.indent += 1;
|
||||||
|
self.writeln(&format!("{} = 1;", result_var));
|
||||||
|
self.writeln("break;");
|
||||||
|
self.indent -= 1;
|
||||||
|
self.writeln("}");
|
||||||
|
self.indent -= 1;
|
||||||
|
self.writeln("}");
|
||||||
|
|
||||||
|
Ok(result_var)
|
||||||
|
}
|
||||||
|
"all" => {
|
||||||
|
if args.len() != 2 {
|
||||||
|
return Err(CGenError { message: "List.all takes 2 arguments".to_string(), span: None });
|
||||||
|
}
|
||||||
|
let list = self.emit_expr(&args[0])?;
|
||||||
|
let closure = self.emit_expr(&args[1])?;
|
||||||
|
let id = self.fresh_name();
|
||||||
|
let result_var = format!("_all_{}", id);
|
||||||
|
let i_var = format!("_i_{}", id);
|
||||||
|
let elem_var = format!("_elem_{}", id);
|
||||||
|
let fn_var = format!("_fn_{}", id);
|
||||||
|
|
||||||
|
self.writeln(&format!("LuxBool {} = 1;", result_var));
|
||||||
|
self.writeln(&format!("for (int64_t {} = 0; {} < {}->length; {}++) {{", i_var, i_var, list, i_var));
|
||||||
|
self.indent += 1;
|
||||||
|
self.writeln(&format!("void* {} = {}->elements[{}];", elem_var, list, i_var));
|
||||||
|
self.writeln(&format!("LuxClosure* {} = (LuxClosure*){};", fn_var, closure));
|
||||||
|
self.writeln(&format!("if (!((LuxBool(*)(void*, LuxInt)){}->fn_ptr)({}->env, lux_unbox_int({}))) {{", fn_var, fn_var, elem_var));
|
||||||
|
self.indent += 1;
|
||||||
|
self.writeln(&format!("{} = 0;", result_var));
|
||||||
|
self.writeln("break;");
|
||||||
|
self.indent -= 1;
|
||||||
|
self.writeln("}");
|
||||||
|
self.indent -= 1;
|
||||||
|
self.writeln("}");
|
||||||
|
|
||||||
|
Ok(result_var)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => Err(CGenError {
|
||||||
|
message: format!("Unsupported List operation: {}", op),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn emit_expr_with_substitution(&mut self, expr: &Expr, from: &str, to: &str) -> Result<String, CGenError> {
|
fn emit_expr_with_substitution(&mut self, expr: &Expr, from: &str, to: &str) -> Result<String, CGenError> {
|
||||||
// Simple substitution - in a real implementation, this would be more sophisticated
|
// Simple substitution - in a real implementation, this would be more sophisticated
|
||||||
match expr {
|
match expr {
|
||||||
@@ -937,6 +1373,25 @@ impl CBackend {
|
|||||||
// Type of block is the type of the result expression
|
// Type of block is the type of the result expression
|
||||||
self.infer_expr_type(result)
|
self.infer_expr_type(result)
|
||||||
}
|
}
|
||||||
|
Expr::List { .. } => Some("LuxList*".to_string()),
|
||||||
|
Expr::EffectOp { effect, operation, .. } => {
|
||||||
|
// List operations have known return types
|
||||||
|
if effect.name == "List" {
|
||||||
|
match operation.name.as_str() {
|
||||||
|
// Operations returning lists
|
||||||
|
"map" | "filter" | "concat" | "reverse" | "take" | "drop" | "range" => Some("LuxList*".to_string()),
|
||||||
|
// Operations returning Option
|
||||||
|
"head" | "tail" | "get" | "find" => Some("Option".to_string()),
|
||||||
|
// Operations returning Int
|
||||||
|
"length" | "fold" => Some("LuxInt".to_string()),
|
||||||
|
// Operations returning Bool
|
||||||
|
"isEmpty" | "any" | "all" => Some("LuxBool".to_string()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1128,11 +1583,11 @@ impl CBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
TypeExpr::App(base, _) => {
|
TypeExpr::App(base, _) => {
|
||||||
// For now, use void* for generic types
|
// Handle generic types
|
||||||
if let TypeExpr::Named(name) = base.as_ref() {
|
if let TypeExpr::Named(name) = base.as_ref() {
|
||||||
match name.name.as_str() {
|
match name.name.as_str() {
|
||||||
"List" => Ok("void*".to_string()),
|
"List" => Ok("LuxList*".to_string()),
|
||||||
"Option" => Ok("void*".to_string()),
|
"Option" => Ok("Option".to_string()),
|
||||||
_ => Ok("void*".to_string()),
|
_ => Ok("void*".to_string()),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
129
src/main.rs
129
src/main.rs
@@ -140,17 +140,20 @@ fn main() {
|
|||||||
handle_pkg_command(&args[2..]);
|
handle_pkg_command(&args[2..]);
|
||||||
}
|
}
|
||||||
"compile" => {
|
"compile" => {
|
||||||
// Compile to C code
|
// Compile to native binary
|
||||||
if args.len() < 3 {
|
if args.len() < 3 {
|
||||||
eprintln!("Usage: lux compile <file.lux> [-o output.c] [--run]");
|
eprintln!("Usage: lux compile <file.lux> [-o binary]");
|
||||||
|
eprintln!(" lux compile <file.lux> --run");
|
||||||
|
eprintln!(" lux compile <file.lux> --emit-c [-o file.c]");
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
let run_after = args.iter().any(|a| a == "--run");
|
let run_after = args.iter().any(|a| a == "--run");
|
||||||
|
let emit_c = args.iter().any(|a| a == "--emit-c");
|
||||||
let output_path = args.iter()
|
let output_path = args.iter()
|
||||||
.position(|a| a == "-o")
|
.position(|a| a == "-o")
|
||||||
.and_then(|i| args.get(i + 1))
|
.and_then(|i| args.get(i + 1))
|
||||||
.map(|s| s.as_str());
|
.map(|s| s.as_str());
|
||||||
compile_to_c(&args[2], output_path, run_after);
|
compile_to_c(&args[2], output_path, run_after, emit_c);
|
||||||
}
|
}
|
||||||
path => {
|
path => {
|
||||||
// Run a file
|
// Run a file
|
||||||
@@ -169,9 +172,10 @@ fn print_help() {
|
|||||||
println!("Usage:");
|
println!("Usage:");
|
||||||
println!(" lux Start the REPL");
|
println!(" lux Start the REPL");
|
||||||
println!(" lux <file.lux> Run a file (interpreter)");
|
println!(" lux <file.lux> Run a file (interpreter)");
|
||||||
println!(" lux compile <file.lux> Compile to C code (stdout)");
|
println!(" lux compile <file.lux> Compile to native binary");
|
||||||
println!(" lux compile <f> -o out.c Compile to C file");
|
println!(" lux compile <f> -o app Compile to binary named 'app'");
|
||||||
println!(" lux compile <f> --run Compile and execute");
|
println!(" lux compile <f> --run Compile and execute");
|
||||||
|
println!(" lux compile <f> --emit-c Output C code instead of binary");
|
||||||
println!(" lux fmt <file.lux> Format a file (--check to verify only)");
|
println!(" lux fmt <file.lux> Format a file (--check to verify only)");
|
||||||
println!(" lux check <file.lux> Type check without running");
|
println!(" lux check <file.lux> Type check without running");
|
||||||
println!(" lux test [pattern] Run tests (optional pattern filter)");
|
println!(" lux test [pattern] Run tests (optional pattern filter)");
|
||||||
@@ -262,7 +266,7 @@ fn check_file(path: &str) {
|
|||||||
println!("{}: OK", path);
|
println!("{}: OK", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_to_c(path: &str, output_path: Option<&str>, run_after: bool) {
|
fn compile_to_c(path: &str, output_path: Option<&str>, run_after: bool, emit_c: bool) {
|
||||||
use codegen::c_backend::CBackend;
|
use codegen::c_backend::CBackend;
|
||||||
use modules::ModuleLoader;
|
use modules::ModuleLoader;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -308,58 +312,77 @@ fn compile_to_c(path: &str, output_path: Option<&str>, run_after: bool) {
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("C codegen error: {}", e);
|
eprintln!("C codegen error: {}", e);
|
||||||
eprintln!();
|
eprintln!();
|
||||||
eprintln!("Note: The C backend currently supports:");
|
eprintln!("Note: The C backend supports functions, closures, ADTs,");
|
||||||
eprintln!(" - Integer, Float, Bool, String, Char, Unit literals");
|
eprintln!("pattern matching, lists, and Console.print.");
|
||||||
eprintln!(" - Arithmetic and comparison operators");
|
|
||||||
eprintln!(" - If/then/else conditionals");
|
|
||||||
eprintln!(" - Let bindings and blocks");
|
|
||||||
eprintln!(" - Function definitions and calls");
|
|
||||||
eprintln!(" - Records and enums (basic)");
|
|
||||||
eprintln!(" - Console.print effect");
|
|
||||||
eprintln!();
|
eprintln!();
|
||||||
eprintln!("Not yet supported: closures, lists, pattern variable binding,");
|
eprintln!("Not yet supported: other effects, some advanced features.");
|
||||||
eprintln!("other effects, higher-order functions.");
|
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Determine output
|
// Handle --emit-c: output C code instead of binary
|
||||||
if run_after {
|
if emit_c {
|
||||||
// Write to temp file, compile, and run
|
if let Some(out_path) = output_path {
|
||||||
let temp_c = std::env::temp_dir().join("lux_output.c");
|
if let Err(e) = std::fs::write(out_path, &c_code) {
|
||||||
let temp_bin = std::env::temp_dir().join("lux_output");
|
eprintln!("Error writing file '{}': {}", out_path, e);
|
||||||
|
std::process::exit(1);
|
||||||
if let Err(e) = std::fs::write(&temp_c, &c_code) {
|
|
||||||
eprintln!("Error writing temp file: {}", e);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to find a C compiler
|
|
||||||
let cc = std::env::var("CC").unwrap_or_else(|_| "cc".to_string());
|
|
||||||
|
|
||||||
let compile_result = Command::new(&cc)
|
|
||||||
.args(["-O2", "-o"])
|
|
||||||
.arg(&temp_bin)
|
|
||||||
.arg(&temp_c)
|
|
||||||
.output();
|
|
||||||
|
|
||||||
match compile_result {
|
|
||||||
Ok(output) => {
|
|
||||||
if !output.status.success() {
|
|
||||||
eprintln!("C compilation failed:");
|
|
||||||
eprintln!("{}", String::from_utf8_lossy(&output.stderr));
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
eprintln!("Wrote C code to {}", out_path);
|
||||||
eprintln!("Failed to run C compiler '{}': {}", cc, e);
|
} else {
|
||||||
eprintln!("Make sure gcc or clang is installed, or set CC environment variable.");
|
println!("{}", c_code);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default: compile to native binary
|
||||||
|
let temp_c = std::env::temp_dir().join("lux_output.c");
|
||||||
|
|
||||||
|
// Determine output binary name
|
||||||
|
let output_bin = if let Some(out) = output_path {
|
||||||
|
Path::new(out).to_path_buf()
|
||||||
|
} else {
|
||||||
|
// Derive from source filename: foo.lux -> ./foo
|
||||||
|
let stem = file_path.file_stem()
|
||||||
|
.and_then(|s| s.to_str())
|
||||||
|
.unwrap_or("a.out");
|
||||||
|
Path::new(".").join(stem)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = std::fs::write(&temp_c, &c_code) {
|
||||||
|
eprintln!("Error writing temp file: {}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find C compiler
|
||||||
|
let cc = std::env::var("CC").unwrap_or_else(|_| "cc".to_string());
|
||||||
|
|
||||||
|
let compile_result = Command::new(&cc)
|
||||||
|
.args(["-O2", "-o"])
|
||||||
|
.arg(&output_bin)
|
||||||
|
.arg(&temp_c)
|
||||||
|
.output();
|
||||||
|
|
||||||
|
match compile_result {
|
||||||
|
Ok(output) => {
|
||||||
|
if !output.status.success() {
|
||||||
|
eprintln!("C compilation failed:");
|
||||||
|
eprintln!("{}", String::from_utf8_lossy(&output.stderr));
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to run C compiler '{}': {}", cc, e);
|
||||||
|
eprintln!("Make sure gcc or clang is installed, or set CC environment variable.");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up temp file
|
||||||
|
let _ = std::fs::remove_file(&temp_c);
|
||||||
|
|
||||||
|
if run_after {
|
||||||
// Run the compiled binary
|
// Run the compiled binary
|
||||||
let run_result = Command::new(&temp_bin).status();
|
let run_result = Command::new(&output_bin).status();
|
||||||
match run_result {
|
match run_result {
|
||||||
Ok(status) => {
|
Ok(status) => {
|
||||||
std::process::exit(status.code().unwrap_or(1));
|
std::process::exit(status.code().unwrap_or(1));
|
||||||
@@ -369,17 +392,9 @@ fn compile_to_c(path: &str, output_path: Option<&str>, run_after: bool) {
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let Some(out_path) = output_path {
|
|
||||||
// Write to specified file
|
|
||||||
if let Err(e) = std::fs::write(out_path, &c_code) {
|
|
||||||
eprintln!("Error writing file '{}': {}", out_path, e);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
eprintln!("Compiled to {}", out_path);
|
|
||||||
eprintln!("Compile with: cc -O2 -o output {}", out_path);
|
|
||||||
} else {
|
} else {
|
||||||
// Print to stdout
|
// Just print where the binary is
|
||||||
println!("{}", c_code);
|
eprintln!("Compiled to {}", output_bin.display());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user