Compare commits
6 Commits
bc60f1c8f1
...
d26fd975d1
| Author | SHA1 | Date | |
|---|---|---|---|
| d26fd975d1 | |||
| 1fa599f856 | |||
| c2404a5ec1 | |||
| 19068ead96 | |||
| 44ea1eebb0 | |||
| 8c90d5a8dc |
111
CLAUDE.md
Normal file
111
CLAUDE.md
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
# Lux Project Notes
|
||||||
|
|
||||||
|
## Development Environment
|
||||||
|
|
||||||
|
This is a **Nix environment**. Tools like `cargo`, `rustc`, `clippy`, etc. are not available in the base shell.
|
||||||
|
|
||||||
|
To run Rust/Cargo commands, use one of:
|
||||||
|
```bash
|
||||||
|
nix develop --command cargo test
|
||||||
|
nix develop --command cargo build
|
||||||
|
nix develop --command cargo clippy
|
||||||
|
nix develop --command cargo fmt
|
||||||
|
```
|
||||||
|
|
||||||
|
Or enter the development shell first:
|
||||||
|
```bash
|
||||||
|
nix develop
|
||||||
|
# then run commands normally
|
||||||
|
cargo test
|
||||||
|
```
|
||||||
|
|
||||||
|
The `lux` binary can be run directly if already built:
|
||||||
|
```bash
|
||||||
|
./target/debug/lux test
|
||||||
|
./target/release/lux <file.lux>
|
||||||
|
```
|
||||||
|
|
||||||
|
For additional tools not in the dev shell:
|
||||||
|
```bash
|
||||||
|
nix-shell -p <program>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
When making changes:
|
||||||
|
1. **Always run tests**: `cargo check && cargo test` - fix all errors and warnings
|
||||||
|
2. **Lint the Lux code**: `./target/release/lux lint` - fix warnings
|
||||||
|
3. **Check Lux code**: `./target/release/lux check` - type check + lint in one pass
|
||||||
|
4. **Format Lux code**: `./target/release/lux fmt` - auto-format all .lux files
|
||||||
|
5. **Write tests**: Add tests to cover new code
|
||||||
|
6. **Document features**: Provide documentation and tutorials for new features/frameworks
|
||||||
|
7. **Fix language limitations**: If you encounter parser/type system limitations, fix them (without regressions on guarantees or speed)
|
||||||
|
8. **Git commits**: Always use `--no-gpg-sign` flag
|
||||||
|
|
||||||
|
### Post-work checklist (run after each major piece of work)
|
||||||
|
```bash
|
||||||
|
nix develop --command cargo check # No Rust errors
|
||||||
|
nix develop --command cargo test # All tests pass (currently 381)
|
||||||
|
./target/release/lux check # Type check + lint all .lux files
|
||||||
|
./target/release/lux fmt # Format all .lux files
|
||||||
|
./target/release/lux lint # Standalone lint pass
|
||||||
|
```
|
||||||
|
|
||||||
|
**IMPORTANT: Always verify Lux code you write:**
|
||||||
|
- Run with interpreter: `./target/release/lux file.lux`
|
||||||
|
- Compile to binary: `./target/release/lux compile file.lux`
|
||||||
|
- Both must work before claiming code is functional
|
||||||
|
- The C backend has limited effect support (Console, File only - no HttpServer, Http, etc.)
|
||||||
|
|
||||||
|
## CLI Commands & Aliases
|
||||||
|
|
||||||
|
| Command | Alias | Description |
|
||||||
|
|---------|-------|-------------|
|
||||||
|
| `lux fmt` | `lux f` | Format .lux files |
|
||||||
|
| `lux test` | `lux t` | Run test suite |
|
||||||
|
| `lux check` | `lux k` | Type check + lint |
|
||||||
|
| `lux lint` | `lux l` | Lint only (with `--explain` for detailed help) |
|
||||||
|
| `lux serve` | `lux s` | Static file server |
|
||||||
|
| `lux compile` | `lux c` | Compile to binary |
|
||||||
|
|
||||||
|
## Code Quality
|
||||||
|
|
||||||
|
- Fix all compiler warnings before committing
|
||||||
|
- Ensure all tests pass (currently 381 tests)
|
||||||
|
- Add new tests when adding features
|
||||||
|
- Keep examples and documentation in sync
|
||||||
|
|
||||||
|
## Lux Language Notes
|
||||||
|
|
||||||
|
### Top-level expressions
|
||||||
|
Bare `run` expressions are not allowed at top-level. You must wrap them in a `let` binding:
|
||||||
|
```lux
|
||||||
|
// WRONG: parse error
|
||||||
|
run main() with {}
|
||||||
|
|
||||||
|
// CORRECT
|
||||||
|
let output = run main() with {}
|
||||||
|
```
|
||||||
|
|
||||||
|
### String methods
|
||||||
|
Lux uses module-qualified function calls, not method syntax on primitives:
|
||||||
|
```lux
|
||||||
|
// WRONG: not valid syntax
|
||||||
|
path.endsWith(".html")
|
||||||
|
|
||||||
|
// CORRECT
|
||||||
|
String.endsWith(path, ".html")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Available String functions
|
||||||
|
Key string functions (all in `String.` namespace):
|
||||||
|
- `String.length(s)` - get length
|
||||||
|
- `String.startsWith(s, prefix)` - check prefix
|
||||||
|
- `String.endsWith(s, suffix)` - check suffix
|
||||||
|
- `String.split(s, delimiter)` - split into list
|
||||||
|
- `String.join(list, delimiter)` - join list
|
||||||
|
- `String.substring(s, start, end)` - extract substring
|
||||||
|
- `String.indexOf(s, needle)` - find position (returns Option)
|
||||||
|
- `String.replace(s, old, new)` - replace occurrences
|
||||||
|
- `String.trim(s)` - trim whitespace
|
||||||
|
- `String.toLower(s)` / `String.toUpper(s)` - case conversion
|
||||||
@@ -1,36 +1,19 @@
|
|||||||
// Demonstrating behavioral properties in Lux
|
fn add(a: Int, b: Int): Int is pure = a + b
|
||||||
// Behavioral properties are compile-time guarantees about function behavior
|
|
||||||
//
|
|
||||||
// Expected output:
|
|
||||||
// add(5, 3) = 8
|
|
||||||
// factorial(5) = 120
|
|
||||||
// multiply(7, 6) = 42
|
|
||||||
// abs(-5) = 5
|
|
||||||
|
|
||||||
// A pure function - no side effects, same input always gives same output
|
fn factorial(n: Int): Int is deterministic = if n <= 1 then 1 else n * factorial(n - 1)
|
||||||
fn add(a: Int, b: Int): Int is pure =
|
|
||||||
a + b
|
|
||||||
|
|
||||||
// A deterministic function - same input always gives same output
|
fn multiply(a: Int, b: Int): Int is commutative = a * b
|
||||||
fn factorial(n: Int): Int is deterministic =
|
|
||||||
if n <= 1 then 1
|
|
||||||
else n * factorial(n - 1)
|
|
||||||
|
|
||||||
// A commutative function - order of arguments doesn't matter
|
fn abs(x: Int): Int is idempotent = if x < 0 then 0 - x else x
|
||||||
fn multiply(a: Int, b: Int): Int is commutative =
|
|
||||||
a * b
|
|
||||||
|
|
||||||
// An idempotent function - absolute value
|
|
||||||
fn abs(x: Int): Int is idempotent =
|
|
||||||
if x < 0 then 0 - x else x
|
|
||||||
|
|
||||||
// Test the functions
|
|
||||||
let sumResult = add(5, 3)
|
let sumResult = add(5, 3)
|
||||||
|
|
||||||
let factResult = factorial(5)
|
let factResult = factorial(5)
|
||||||
|
|
||||||
let productResult = multiply(7, 6)
|
let productResult = multiply(7, 6)
|
||||||
|
|
||||||
let absResult = abs(0 - 5)
|
let absResult = abs(0 - 5)
|
||||||
|
|
||||||
// Print results
|
|
||||||
fn printResults(): Unit with {Console} = {
|
fn printResults(): Unit with {Console} = {
|
||||||
Console.print("add(5, 3) = " + toString(sumResult))
|
Console.print("add(5, 3) = " + toString(sumResult))
|
||||||
Console.print("factorial(5) = " + toString(factResult))
|
Console.print("factorial(5) = " + toString(factResult))
|
||||||
|
|||||||
@@ -1,82 +1,42 @@
|
|||||||
// Behavioral Types Demo
|
|
||||||
// Demonstrates compile-time verification of function properties
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// PART 1: Pure Functions
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
// Pure functions have no side effects
|
|
||||||
fn add(a: Int, b: Int): Int is pure = a + b
|
fn add(a: Int, b: Int): Int is pure = a + b
|
||||||
|
|
||||||
fn subtract(a: Int, b: Int): Int is pure = a - b
|
fn subtract(a: Int, b: Int): Int is pure = a - b
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// PART 2: Commutative Functions
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
// Commutative functions: f(a, b) = f(b, a)
|
|
||||||
fn multiply(a: Int, b: Int): Int is commutative = a * b
|
fn multiply(a: Int, b: Int): Int is commutative = a * b
|
||||||
|
|
||||||
fn sum(a: Int, b: Int): Int is commutative = a + b
|
fn sum(a: Int, b: Int): Int is commutative = a + b
|
||||||
|
|
||||||
// ============================================================
|
fn abs(x: Int): Int is idempotent = if x < 0 then 0 - x else x
|
||||||
// PART 3: Idempotent Functions
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
// Idempotent functions: f(f(x)) = f(x)
|
|
||||||
fn abs(x: Int): Int is idempotent =
|
|
||||||
if x < 0 then 0 - x else x
|
|
||||||
|
|
||||||
fn identity(x: Int): Int is idempotent = x
|
fn identity(x: Int): Int is idempotent = x
|
||||||
|
|
||||||
// ============================================================
|
fn factorial(n: Int): Int is deterministic = if n <= 1 then 1 else n * factorial(n - 1)
|
||||||
// PART 4: Deterministic Functions
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
// Deterministic functions always produce the same output for the same input
|
fn fib(n: Int): Int is deterministic = if n <= 1 then n else fib(n - 1) + fib(n - 2)
|
||||||
fn factorial(n: Int): Int is deterministic =
|
|
||||||
if n <= 1 then 1 else n * factorial(n - 1)
|
|
||||||
|
|
||||||
fn fib(n: Int): Int is deterministic =
|
fn sumTo(n: Int): Int is total = if n <= 0 then 0 else n + sumTo(n - 1)
|
||||||
if n <= 1 then n else fib(n - 1) + fib(n - 2)
|
|
||||||
|
|
||||||
// ============================================================
|
fn power(base: Int, exp: Int): Int is total = if exp <= 0 then 1 else base * power(base, exp - 1)
|
||||||
// PART 5: Total Functions
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
// Total functions are defined for all inputs (no infinite loops, no exceptions)
|
|
||||||
fn sumTo(n: Int): Int is total =
|
|
||||||
if n <= 0 then 0 else n + sumTo(n - 1)
|
|
||||||
|
|
||||||
fn power(base: Int, exp: Int): Int is total =
|
|
||||||
if exp <= 0 then 1 else base * power(base, exp - 1)
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// RESULTS
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
fn main(): Unit with {Console} = {
|
fn main(): Unit with {Console} = {
|
||||||
Console.print("=== Behavioral Types Demo ===")
|
Console.print("=== Behavioral Types Demo ===")
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
Console.print("Part 1: Pure functions")
|
Console.print("Part 1: Pure functions")
|
||||||
Console.print(" add(5, 3) = " + toString(add(5, 3)))
|
Console.print(" add(5, 3) = " + toString(add(5, 3)))
|
||||||
Console.print(" subtract(10, 4) = " + toString(subtract(10, 4)))
|
Console.print(" subtract(10, 4) = " + toString(subtract(10, 4)))
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
Console.print("Part 2: Commutative functions")
|
Console.print("Part 2: Commutative functions")
|
||||||
Console.print(" multiply(7, 6) = " + toString(multiply(7, 6)))
|
Console.print(" multiply(7, 6) = " + toString(multiply(7, 6)))
|
||||||
Console.print(" sum(10, 20) = " + toString(sum(10, 20)))
|
Console.print(" sum(10, 20) = " + toString(sum(10, 20)))
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
Console.print("Part 3: Idempotent functions")
|
Console.print("Part 3: Idempotent functions")
|
||||||
Console.print(" abs(-42) = " + toString(abs(0 - 42)))
|
Console.print(" abs(-42) = " + toString(abs(0 - 42)))
|
||||||
Console.print(" identity(100) = " + toString(identity(100)))
|
Console.print(" identity(100) = " + toString(identity(100)))
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
Console.print("Part 4: Deterministic functions")
|
Console.print("Part 4: Deterministic functions")
|
||||||
Console.print(" factorial(5) = " + toString(factorial(5)))
|
Console.print(" factorial(5) = " + toString(factorial(5)))
|
||||||
Console.print(" fib(10) = " + toString(fib(10)))
|
Console.print(" fib(10) = " + toString(fib(10)))
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
Console.print("Part 5: Total functions")
|
Console.print("Part 5: Total functions")
|
||||||
Console.print(" sumTo(10) = " + toString(sumTo(10)))
|
Console.print(" sumTo(10) = " + toString(sumTo(10)))
|
||||||
Console.print(" power(2, 8) = " + toString(power(2, 8)))
|
Console.print(" power(2, 8) = " + toString(power(2, 8)))
|
||||||
|
|||||||
@@ -1,31 +1,7 @@
|
|||||||
// Demonstrating built-in effects in Lux
|
fn safeDivide(a: Int, b: Int): Int with {Fail} = if b == 0 then Fail.fail("Division by zero") else a / b
|
||||||
//
|
|
||||||
// Lux provides several built-in effects:
|
|
||||||
// - Console: print and read from terminal
|
|
||||||
// - Fail: early termination with error
|
|
||||||
// - State: get/put mutable state (requires runtime initialization)
|
|
||||||
// - Reader: read-only environment access (requires runtime initialization)
|
|
||||||
//
|
|
||||||
// This example demonstrates Console and Fail effects.
|
|
||||||
//
|
|
||||||
// Expected output:
|
|
||||||
// Starting computation...
|
|
||||||
// Step 1: validating input
|
|
||||||
// Step 2: processing
|
|
||||||
// Result: 42
|
|
||||||
// Done!
|
|
||||||
|
|
||||||
// A function that can fail
|
fn validatePositive(n: Int): Int with {Fail} = if n < 0 then Fail.fail("Negative number not allowed") else n
|
||||||
fn safeDivide(a: Int, b: Int): Int with {Fail} =
|
|
||||||
if b == 0 then Fail.fail("Division by zero")
|
|
||||||
else a / b
|
|
||||||
|
|
||||||
// A function that validates input
|
|
||||||
fn validatePositive(n: Int): Int with {Fail} =
|
|
||||||
if n < 0 then Fail.fail("Negative number not allowed")
|
|
||||||
else n
|
|
||||||
|
|
||||||
// A computation that uses multiple effects
|
|
||||||
fn compute(input: Int): Int with {Console, Fail} = {
|
fn compute(input: Int): Int with {Console, Fail} = {
|
||||||
Console.print("Starting computation...")
|
Console.print("Starting computation...")
|
||||||
Console.print("Step 1: validating input")
|
Console.print("Step 1: validating input")
|
||||||
@@ -36,7 +12,6 @@ fn compute(input: Int): Int with {Console, Fail} = {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main function
|
|
||||||
fn main(): Unit with {Console} = {
|
fn main(): Unit with {Console} = {
|
||||||
let result = run compute(21) with {}
|
let result = run compute(21) with {}
|
||||||
Console.print("Done!")
|
Console.print("Done!")
|
||||||
|
|||||||
@@ -1,14 +1,3 @@
|
|||||||
// Counter Example - A simple interactive counter using TEA pattern
|
|
||||||
//
|
|
||||||
// This example demonstrates:
|
|
||||||
// - Model-View-Update architecture (TEA)
|
|
||||||
// - Html DSL for describing UI (inline version)
|
|
||||||
// - Message-based state updates
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Html Types (subset of stdlib/html)
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
type Html<M> =
|
type Html<M> =
|
||||||
| Element(String, List<Attr<M>>, List<Html<M>>)
|
| Element(String, List<Attr<M>>, List<Html<M>>)
|
||||||
| Text(String)
|
| Text(String)
|
||||||
@@ -19,130 +8,96 @@ type Attr<M> =
|
|||||||
| Id(String)
|
| Id(String)
|
||||||
| OnClick(M)
|
| OnClick(M)
|
||||||
|
|
||||||
// Html builder helpers
|
fn div<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> = Element("div", attrs, children)
|
||||||
fn div<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
|
|
||||||
Element("div", attrs, children)
|
|
||||||
|
|
||||||
fn span<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
|
fn span<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> = Element("span", attrs, children)
|
||||||
Element("span", attrs, children)
|
|
||||||
|
|
||||||
fn h1<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
|
fn h1<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> = Element("h1", attrs, children)
|
||||||
Element("h1", attrs, children)
|
|
||||||
|
|
||||||
fn button<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
|
fn button<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> = Element("button", attrs, children)
|
||||||
Element("button", attrs, children)
|
|
||||||
|
|
||||||
fn text<M>(content: String): Html<M> =
|
fn text<M>(content: String): Html<M> = Text(content)
|
||||||
Text(content)
|
|
||||||
|
|
||||||
fn class<M>(name: String): Attr<M> =
|
fn class<M>(name: String): Attr<M> = Class(name)
|
||||||
Class(name)
|
|
||||||
|
|
||||||
fn onClick<M>(msg: M): Attr<M> =
|
fn onClick<M>(msg: M): Attr<M> = OnClick(msg)
|
||||||
OnClick(msg)
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Model - The application state (using ADT wrapper)
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
type Model =
|
type Model =
|
||||||
| Counter(Int)
|
| Counter(Int)
|
||||||
|
|
||||||
fn getCount(model: Model): Int =
|
fn getCount(model: Model): Int =
|
||||||
match model {
|
match model {
|
||||||
Counter(n) => n
|
Counter(n) => n,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(): Model = Counter(0)
|
fn init(): Model = Counter(0)
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Messages - Events that can occur
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
type Msg =
|
type Msg =
|
||||||
| Increment
|
| Increment
|
||||||
| Decrement
|
| Decrement
|
||||||
| Reset
|
| Reset
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Update - State transitions
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
fn update(model: Model, msg: Msg): Model =
|
fn update(model: Model, msg: Msg): Model =
|
||||||
match msg {
|
match msg {
|
||||||
Increment => Counter(getCount(model) + 1),
|
Increment => Counter(getCount(model) + 1),
|
||||||
Decrement => Counter(getCount(model) - 1),
|
Decrement => Counter(getCount(model) - 1),
|
||||||
Reset => Counter(0)
|
Reset => Counter(0),
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// View - Render the UI
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
fn viewCounter(count: Int): Html<Msg> = {
|
fn viewCounter(count: Int): Html<Msg> = {
|
||||||
let countText = text(toString(count))
|
let countText = text(toString(count))
|
||||||
let countSpan = span([class("count")], [countText])
|
let countSpan = span([class("count")], [countText])
|
||||||
let displayDiv = div([class("counter-display")], [countSpan])
|
let displayDiv = div([class("counter-display")], [countSpan])
|
||||||
|
|
||||||
let minusBtn = button([onClick(Decrement), class("btn")], [text("-")])
|
let minusBtn = button([onClick(Decrement), class("btn")], [text("-")])
|
||||||
let resetBtn = button([onClick(Reset), class("btn btn-reset")], [text("Reset")])
|
let resetBtn = button([onClick(Reset), class("btn btn-reset")], [text("Reset")])
|
||||||
let plusBtn = button([onClick(Increment), class("btn")], [text("+")])
|
let plusBtn = button([onClick(Increment), class("btn")], [text("+")])
|
||||||
let buttonsDiv = div([class("counter-buttons")], [minusBtn, resetBtn, plusBtn])
|
let buttonsDiv = div([class("counter-buttons")], [minusBtn, resetBtn, plusBtn])
|
||||||
|
|
||||||
let title = h1([], [text("Counter")])
|
let title = h1([], [text("Counter")])
|
||||||
div([class("counter-app")], [title, displayDiv, buttonsDiv])
|
div([class("counter-app")], [title, displayDiv, buttonsDiv])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(model: Model): Html<Msg> = viewCounter(getCount(model))
|
fn view(model: Model): Html<Msg> = viewCounter(getCount(model))
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Debug: Print Html structure
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
fn showAttr(attr: Attr<Msg>): String =
|
fn showAttr(attr: Attr<Msg>): String =
|
||||||
match attr {
|
match attr {
|
||||||
Class(s) => "class=\"" + s + "\"",
|
Class(s) => "class=\"" + s + "\"",
|
||||||
Id(s) => "id=\"" + s + "\"",
|
Id(s) => "id=\"" + s + "\"",
|
||||||
OnClick(msg) => match msg {
|
OnClick(msg) => match msg {
|
||||||
Increment => "onclick=\"Increment\"",
|
Increment => "onclick=\"Increment\"",
|
||||||
Decrement => "onclick=\"Decrement\"",
|
Decrement => "onclick=\"Decrement\"",
|
||||||
Reset => "onclick=\"Reset\""
|
Reset => "onclick=\"Reset\"",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fn showAttrs(attrs: List<Attr<Msg>>): String =
|
fn showAttrs(attrs: List<Attr<Msg>>): String =
|
||||||
match List.head(attrs) {
|
match List.head(attrs) {
|
||||||
None => "",
|
None => "",
|
||||||
Some(a) => match List.tail(attrs) {
|
Some(a) => match List.tail(attrs) {
|
||||||
None => showAttr(a),
|
None => showAttr(a),
|
||||||
Some(rest) => showAttr(a) + " " + showAttrs(rest)
|
Some(rest) => showAttr(a) + " " + showAttrs(rest),
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fn showChildren(children: List<Html<Msg>>, indent: Int): String =
|
fn showChildren(children: List<Html<Msg>>, indent: Int): String =
|
||||||
match List.head(children) {
|
match List.head(children) {
|
||||||
None => "",
|
None => "",
|
||||||
Some(c) => match List.tail(children) {
|
Some(c) => match List.tail(children) {
|
||||||
None => showHtml(c, indent),
|
None => showHtml(c, indent),
|
||||||
Some(rest) => showHtml(c, indent) + showChildren(rest, indent)
|
Some(rest) => showHtml(c, indent) + showChildren(rest, indent),
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fn showHtml(html: Html<Msg>, indent: Int): String =
|
fn showHtml(html: Html<Msg>, indent: Int): String =
|
||||||
match html {
|
match html {
|
||||||
Empty => "",
|
Empty => "",
|
||||||
Text(s) => s,
|
Text(s) => s,
|
||||||
Element(tag, attrs, children) => {
|
Element(tag, attrs, children) => {
|
||||||
let attrStr = showAttrs(attrs)
|
let attrStr = showAttrs(attrs)
|
||||||
let attrPart = if String.length(attrStr) > 0 then " " + attrStr else ""
|
let attrPart = if String.length(attrStr) > 0 then " " + attrStr else ""
|
||||||
let childStr = showChildren(children, indent + 2)
|
let childStr = showChildren(children, indent + 2)
|
||||||
"<" + tag + attrPart + ">" + childStr + "</" + tag + ">"
|
"<" + tag + attrPart + ">" + childStr + "</" + tag + ">"
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Entry point
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
fn main(): Unit with {Console} = {
|
fn main(): Unit with {Console} = {
|
||||||
let model = init()
|
let model = init()
|
||||||
@@ -150,24 +105,19 @@ fn main(): Unit with {Console} = {
|
|||||||
Console.print("")
|
Console.print("")
|
||||||
Console.print("Initial count: " + toString(getCount(model)))
|
Console.print("Initial count: " + toString(getCount(model)))
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
let m1 = update(model, Increment)
|
let m1 = update(model, Increment)
|
||||||
Console.print("After Increment: " + toString(getCount(m1)))
|
Console.print("After Increment: " + toString(getCount(m1)))
|
||||||
|
|
||||||
let m2 = update(m1, Increment)
|
let m2 = update(m1, Increment)
|
||||||
Console.print("After Increment: " + toString(getCount(m2)))
|
Console.print("After Increment: " + toString(getCount(m2)))
|
||||||
|
|
||||||
let m3 = update(m2, Increment)
|
let m3 = update(m2, Increment)
|
||||||
Console.print("After Increment: " + toString(getCount(m3)))
|
Console.print("After Increment: " + toString(getCount(m3)))
|
||||||
|
|
||||||
let m4 = update(m3, Decrement)
|
let m4 = update(m3, Decrement)
|
||||||
Console.print("After Decrement: " + toString(getCount(m4)))
|
Console.print("After Decrement: " + toString(getCount(m4)))
|
||||||
|
|
||||||
let m5 = update(m4, Reset)
|
let m5 = update(m4, Reset)
|
||||||
Console.print("After Reset: " + toString(getCount(m5)))
|
Console.print("After Reset: " + toString(getCount(m5)))
|
||||||
|
|
||||||
Console.print("")
|
Console.print("")
|
||||||
Console.print("=== View (HTML Structure) ===")
|
Console.print("=== View (HTML Structure) ===")
|
||||||
Console.print(showHtml(view(m2), 0))
|
Console.print(showHtml(view(m2), 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = run main() with {}
|
let output = run main() with {}
|
||||||
|
|||||||
@@ -1,57 +1,37 @@
|
|||||||
// Demonstrating algebraic data types and pattern matching
|
|
||||||
//
|
|
||||||
// Expected output:
|
|
||||||
// Tree sum: 8
|
|
||||||
// Tree depth: 3
|
|
||||||
// Safe divide 10/2: Result: 5
|
|
||||||
// Safe divide 10/0: Division by zero!
|
|
||||||
|
|
||||||
// Define a binary tree
|
|
||||||
type Tree =
|
type Tree =
|
||||||
| Leaf(Int)
|
| Leaf(Int)
|
||||||
| Node(Tree, Tree)
|
| Node(Tree, Tree)
|
||||||
|
|
||||||
// Sum all values in a tree
|
|
||||||
fn sumTree(tree: Tree): Int =
|
fn sumTree(tree: Tree): Int =
|
||||||
match tree {
|
match tree {
|
||||||
Leaf(n) => n,
|
Leaf(n) => n,
|
||||||
Node(left, right) => sumTree(left) + sumTree(right)
|
Node(left, right) => sumTree(left) + sumTree(right),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the depth of a tree
|
|
||||||
fn depth(tree: Tree): Int =
|
fn depth(tree: Tree): Int =
|
||||||
match tree {
|
match tree {
|
||||||
Leaf(_) => 1,
|
Leaf(_) => 1,
|
||||||
Node(left, right) => {
|
Node(left, right) => {
|
||||||
let leftDepth = depth(left)
|
let leftDepth = depth(left)
|
||||||
let rightDepth = depth(right)
|
let rightDepth = depth(right)
|
||||||
1 + (if leftDepth > rightDepth then leftDepth else rightDepth)
|
1 + if leftDepth > rightDepth then leftDepth else rightDepth
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Example tree:
|
|
||||||
// Node
|
|
||||||
// / \
|
|
||||||
// Node Leaf(5)
|
|
||||||
// / \
|
|
||||||
// Leaf(1) Leaf(2)
|
|
||||||
|
|
||||||
let myTree = Node(Node(Leaf(1), Leaf(2)), Leaf(5))
|
let myTree = Node(Node(Leaf(1), Leaf(2)), Leaf(5))
|
||||||
|
|
||||||
let treeSum = sumTree(myTree)
|
let treeSum = sumTree(myTree)
|
||||||
|
|
||||||
let treeDepth = depth(myTree)
|
let treeDepth = depth(myTree)
|
||||||
|
|
||||||
// Option type example
|
fn safeDivide(a: Int, b: Int): Option<Int> = if b == 0 then None else Some(a / b)
|
||||||
fn safeDivide(a: Int, b: Int): Option<Int> =
|
|
||||||
if b == 0 then None
|
|
||||||
else Some(a / b)
|
|
||||||
|
|
||||||
fn showResult(result: Option<Int>): String =
|
fn showResult(result: Option<Int>): String =
|
||||||
match result {
|
match result {
|
||||||
None => "Division by zero!",
|
None => "Division by zero!",
|
||||||
Some(n) => "Result: " + toString(n)
|
Some(n) => "Result: " + toString(n),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print results
|
|
||||||
fn printResults(): Unit with {Console} = {
|
fn printResults(): Unit with {Console} = {
|
||||||
Console.print("Tree sum: " + toString(treeSum))
|
Console.print("Tree sum: " + toString(treeSum))
|
||||||
Console.print("Tree depth: " + toString(treeDepth))
|
Console.print("Tree depth: " + toString(treeDepth))
|
||||||
|
|||||||
@@ -1,17 +1,8 @@
|
|||||||
// Demonstrating algebraic effects in Lux
|
|
||||||
//
|
|
||||||
// Expected output:
|
|
||||||
// [info] Processing data...
|
|
||||||
// [debug] Result computed
|
|
||||||
// Final result: 42
|
|
||||||
|
|
||||||
// Define a custom logging effect
|
|
||||||
effect Logger {
|
effect Logger {
|
||||||
fn log(level: String, msg: String): Unit
|
fn log(level: String, msg: String): Unit
|
||||||
fn getLevel(): String
|
fn getLevel(): String
|
||||||
}
|
}
|
||||||
|
|
||||||
// A function that uses the Logger effect
|
|
||||||
fn processData(data: Int): Int with {Logger} = {
|
fn processData(data: Int): Int with {Logger} = {
|
||||||
Logger.log("info", "Processing data...")
|
Logger.log("info", "Processing data...")
|
||||||
let result = data * 2
|
let result = data * 2
|
||||||
@@ -19,17 +10,15 @@ fn processData(data: Int): Int with {Logger} = {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
// A handler that prints logs to console
|
|
||||||
handler consoleLogger: Logger {
|
handler consoleLogger: Logger {
|
||||||
fn log(level, msg) = Console.print("[" + level + "] " + msg)
|
fn log(level, msg) = Console.print("[" + level + "] " + msg)
|
||||||
fn getLevel() = "debug"
|
fn getLevel() = "debug"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run and print
|
|
||||||
fn main(): Unit with {Console} = {
|
fn main(): Unit with {Console} = {
|
||||||
let result = run processData(21) with {
|
let result = run processData(21) with {
|
||||||
Logger = consoleLogger
|
Logger = consoleLogger,
|
||||||
}
|
}
|
||||||
Console.print("Final result: " + toString(result))
|
Console.print("Final result: " + toString(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,7 @@
|
|||||||
// Factorial function demonstrating recursion
|
fn factorial(n: Int): Int = if n <= 1 then 1 else n * factorial(n - 1)
|
||||||
//
|
|
||||||
// Expected output: 10! = 3628800
|
|
||||||
|
|
||||||
fn factorial(n: Int): Int =
|
|
||||||
if n <= 1 then 1
|
|
||||||
else n * factorial(n - 1)
|
|
||||||
|
|
||||||
// Calculate factorial of 10
|
|
||||||
let result = factorial(10)
|
let result = factorial(10)
|
||||||
|
|
||||||
// Print result using Console effect
|
fn showResult(): Unit with {Console} = Console.print("10! = " + toString(result))
|
||||||
fn showResult(): Unit with {Console} =
|
|
||||||
Console.print("10! = " + toString(result))
|
|
||||||
|
|
||||||
let output = run showResult() with {}
|
let output = run showResult() with {}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
// File I/O example - demonstrates the File effect
|
|
||||||
//
|
|
||||||
// This script reads a file, counts lines/words, and writes a report
|
|
||||||
|
|
||||||
fn countLines(content: String): Int = {
|
fn countLines(content: String): Int = {
|
||||||
let lines = String.split(content, "\n")
|
let lines = String.split(content, "
|
||||||
|
")
|
||||||
List.length(lines)
|
List.length(lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,35 +11,28 @@ fn countWords(content: String): Int = {
|
|||||||
|
|
||||||
fn analyzeFile(path: String): Unit with {File, Console} = {
|
fn analyzeFile(path: String): Unit with {File, Console} = {
|
||||||
Console.print("Analyzing file: " + path)
|
Console.print("Analyzing file: " + path)
|
||||||
|
|
||||||
if File.exists(path) then {
|
if File.exists(path) then {
|
||||||
let content = File.read(path)
|
let content = File.read(path)
|
||||||
let lines = countLines(content)
|
let lines = countLines(content)
|
||||||
let words = countWords(content)
|
let words = countWords(content)
|
||||||
let chars = String.length(content)
|
let chars = String.length(content)
|
||||||
|
Console.print(" Lines: " + toString(lines))
|
||||||
Console.print(" Lines: " + toString(lines))
|
Console.print(" Words: " + toString(words))
|
||||||
Console.print(" Words: " + toString(words))
|
Console.print(" Chars: " + toString(chars))
|
||||||
Console.print(" Chars: " + toString(chars))
|
} else {
|
||||||
} else {
|
Console.print(" Error: File not found!")
|
||||||
Console.print(" Error: File not found!")
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main(): Unit with {File, Console} = {
|
fn main(): Unit with {File, Console} = {
|
||||||
Console.print("=== Lux File Analyzer ===")
|
Console.print("=== Lux File Analyzer ===")
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Analyze this file itself
|
|
||||||
analyzeFile("examples/file_io.lux")
|
analyzeFile("examples/file_io.lux")
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Analyze hello.lux
|
|
||||||
analyzeFile("examples/hello.lux")
|
analyzeFile("examples/hello.lux")
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
let report = "File analysis complete.
|
||||||
// Write a report
|
Analyzed 2 files."
|
||||||
let report = "File analysis complete.\nAnalyzed 2 files."
|
|
||||||
File.write("/tmp/lux_report.txt", report)
|
File.write("/tmp/lux_report.txt", report)
|
||||||
Console.print("Report written to /tmp/lux_report.txt")
|
Console.print("Report written to /tmp/lux_report.txt")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,55 +1,39 @@
|
|||||||
// Demonstrating functional programming features
|
|
||||||
//
|
|
||||||
// Expected output:
|
|
||||||
// apply(double, 21) = 42
|
|
||||||
// compose(addOne, double)(5) = 11
|
|
||||||
// pipe: 5 |> double |> addOne |> square = 121
|
|
||||||
// curried add5(10) = 15
|
|
||||||
// partial times3(7) = 21
|
|
||||||
// record transform = 30
|
|
||||||
|
|
||||||
// Higher-order functions
|
|
||||||
fn apply(f: fn(Int): Int, x: Int): Int = f(x)
|
fn apply(f: fn(Int): Int, x: Int): Int = f(x)
|
||||||
|
|
||||||
fn compose(f: fn(Int): Int, g: fn(Int): Int): fn(Int): Int =
|
fn compose(f: fn(Int): Int, g: fn(Int): Int): fn(Int): Int = fn(x: Int): Int => f(g(x))
|
||||||
fn(x: Int): Int => f(g(x))
|
|
||||||
|
|
||||||
// Basic functions
|
|
||||||
fn double(x: Int): Int = x * 2
|
fn double(x: Int): Int = x * 2
|
||||||
|
|
||||||
fn addOne(x: Int): Int = x + 1
|
fn addOne(x: Int): Int = x + 1
|
||||||
|
|
||||||
fn square(x: Int): Int = x * x
|
fn square(x: Int): Int = x * x
|
||||||
|
|
||||||
// Using apply
|
|
||||||
let result1 = apply(double, 21)
|
let result1 = apply(double, 21)
|
||||||
|
|
||||||
// Using compose
|
|
||||||
let doubleAndAddOne = compose(addOne, double)
|
let doubleAndAddOne = compose(addOne, double)
|
||||||
|
|
||||||
let result2 = doubleAndAddOne(5)
|
let result2 = doubleAndAddOne(5)
|
||||||
|
|
||||||
// Using the pipe operator
|
let result3 = square(addOne(double(5)))
|
||||||
let result3 = 5 |> double |> addOne |> square
|
|
||||||
|
|
||||||
// Currying example
|
fn add(a: Int): fn(Int): Int = fn(b: Int): Int => a + b
|
||||||
fn add(a: Int): fn(Int): Int =
|
|
||||||
fn(b: Int): Int => a + b
|
|
||||||
|
|
||||||
let add5 = add(5)
|
let add5 = add(5)
|
||||||
|
|
||||||
let result4 = add5(10)
|
let result4 = add5(10)
|
||||||
|
|
||||||
// Partial application simulation
|
|
||||||
fn multiply(a: Int, b: Int): Int = a * b
|
fn multiply(a: Int, b: Int): Int = a * b
|
||||||
|
|
||||||
let times3 = fn(x: Int): Int => multiply(3, x)
|
let times3 = fn(x: Int): Int => multiply(3, x)
|
||||||
|
|
||||||
let result5 = times3(7)
|
let result5 = times3(7)
|
||||||
|
|
||||||
// Working with records
|
let transform = fn(record: { x: Int, y: Int }): Int => record.x + record.y
|
||||||
let transform = fn(record: { x: Int, y: Int }): Int =>
|
|
||||||
record.x + record.y
|
|
||||||
|
|
||||||
let point = { x: 10, y: 20 }
|
let point = { x: 10, y: 20 }
|
||||||
|
|
||||||
let recordSum = transform(point)
|
let recordSum = transform(point)
|
||||||
|
|
||||||
// Print all results
|
|
||||||
fn printResults(): Unit with {Console} = {
|
fn printResults(): Unit with {Console} = {
|
||||||
Console.print("apply(double, 21) = " + toString(result1))
|
Console.print("apply(double, 21) = " + toString(result1))
|
||||||
Console.print("compose(addOne, double)(5) = " + toString(result2))
|
Console.print("compose(addOne, double)(5) = " + toString(result2))
|
||||||
|
|||||||
@@ -1,54 +1,43 @@
|
|||||||
// Demonstrating generic type parameters in Lux
|
|
||||||
//
|
|
||||||
// Expected output:
|
|
||||||
// identity(42) = 42
|
|
||||||
// identity("hello") = hello
|
|
||||||
// first(MkPair(1, "one")) = 1
|
|
||||||
// second(MkPair(1, "one")) = one
|
|
||||||
// map(Some(21), double) = Some(42)
|
|
||||||
|
|
||||||
// Generic identity function
|
|
||||||
fn identity<T>(x: T): T = x
|
fn identity<T>(x: T): T = x
|
||||||
|
|
||||||
// Generic pair type
|
|
||||||
type Pair<A, B> =
|
type Pair<A, B> =
|
||||||
| MkPair(A, B)
|
| MkPair(A, B)
|
||||||
|
|
||||||
fn first<A, B>(p: Pair<A, B>): A =
|
fn first<A, B>(p: Pair<A, B>): A =
|
||||||
match p {
|
match p {
|
||||||
MkPair(a, _) => a
|
MkPair(a, _) => a,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn second<A, B>(p: Pair<A, B>): B =
|
fn second<A, B>(p: Pair<A, B>): B =
|
||||||
match p {
|
match p {
|
||||||
MkPair(_, b) => b
|
MkPair(_, b) => b,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generic map function for Option
|
|
||||||
fn mapOption<T, U>(opt: Option<T>, f: fn(T): U): Option<U> =
|
fn mapOption<T, U>(opt: Option<T>, f: fn(T): U): Option<U> =
|
||||||
match opt {
|
match opt {
|
||||||
None => None,
|
None => None,
|
||||||
Some(x) => Some(f(x))
|
Some(x) => Some(f(x)),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function for testing
|
|
||||||
fn double(x: Int): Int = x * 2
|
fn double(x: Int): Int = x * 2
|
||||||
|
|
||||||
// Test usage
|
|
||||||
let id_int = identity(42)
|
let id_int = identity(42)
|
||||||
|
|
||||||
let id_str = identity("hello")
|
let id_str = identity("hello")
|
||||||
|
|
||||||
let pair = MkPair(1, "one")
|
let pair = MkPair(1, "one")
|
||||||
|
|
||||||
let fst = first(pair)
|
let fst = first(pair)
|
||||||
|
|
||||||
let snd = second(pair)
|
let snd = second(pair)
|
||||||
|
|
||||||
let doubled = mapOption(Some(21), double)
|
let doubled = mapOption(Some(21), double)
|
||||||
|
|
||||||
fn showOption(opt: Option<Int>): String =
|
fn showOption(opt: Option<Int>): String =
|
||||||
match opt {
|
match opt {
|
||||||
None => "None",
|
None => "None",
|
||||||
Some(x) => "Some(" + toString(x) + ")"
|
Some(x) => "Some(" + toString(x) + ")",
|
||||||
}
|
}
|
||||||
|
|
||||||
fn printResults(): Unit with {Console} = {
|
fn printResults(): Unit with {Console} = {
|
||||||
Console.print("identity(42) = " + toString(id_int))
|
Console.print("identity(42) = " + toString(id_int))
|
||||||
|
|||||||
@@ -1,21 +1,8 @@
|
|||||||
// Demonstrating resumable effect handlers in Lux
|
|
||||||
//
|
|
||||||
// Handlers can use `resume(value)` to return a value to the effect call site
|
|
||||||
// and continue the computation. This enables powerful control flow patterns.
|
|
||||||
//
|
|
||||||
// Expected output:
|
|
||||||
// [INFO] Starting computation
|
|
||||||
// [DEBUG] Intermediate result: 10
|
|
||||||
// [INFO] Computation complete
|
|
||||||
// Final result: 20
|
|
||||||
|
|
||||||
// Define a custom logging effect
|
|
||||||
effect Logger {
|
effect Logger {
|
||||||
fn log(level: String, msg: String): Unit
|
fn log(level: String, msg: String): Unit
|
||||||
fn getLogLevel(): String
|
fn getLogLevel(): String
|
||||||
}
|
}
|
||||||
|
|
||||||
// A function that uses the Logger effect
|
|
||||||
fn compute(): Int with {Logger} = {
|
fn compute(): Int with {Logger} = {
|
||||||
Logger.log("INFO", "Starting computation")
|
Logger.log("INFO", "Starting computation")
|
||||||
let x = 10
|
let x = 10
|
||||||
@@ -25,20 +12,19 @@ fn compute(): Int with {Logger} = {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
// A handler that prints logs with brackets and resumes with Unit
|
|
||||||
handler prettyLogger: Logger {
|
handler prettyLogger: Logger {
|
||||||
fn log(level, msg) = {
|
fn log(level, msg) =
|
||||||
Console.print("[" + level + "] " + msg)
|
{
|
||||||
resume(())
|
Console.print("[" + level + "] " + msg)
|
||||||
}
|
resume(())
|
||||||
|
}
|
||||||
fn getLogLevel() = resume("DEBUG")
|
fn getLogLevel() = resume("DEBUG")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main function
|
|
||||||
fn main(): Unit with {Console} = {
|
fn main(): Unit with {Console} = {
|
||||||
let result = run compute() with {
|
let result = run compute() with {
|
||||||
Logger = prettyLogger
|
Logger = prettyLogger,
|
||||||
}
|
}
|
||||||
Console.print("Final result: " + toString(result))
|
Console.print("Final result: " + toString(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,3 @@
|
|||||||
// Hello World in Lux
|
fn greet(): Unit with {Console} = Console.print("Hello, World!")
|
||||||
// Demonstrates basic effect usage
|
|
||||||
//
|
|
||||||
// Expected output: Hello, World!
|
|
||||||
|
|
||||||
fn greet(): Unit with {Console} =
|
|
||||||
Console.print("Hello, World!")
|
|
||||||
|
|
||||||
// Run the greeting with the Console effect
|
|
||||||
let output = run greet() with {}
|
let output = run greet() with {}
|
||||||
|
|||||||
@@ -1,91 +1,72 @@
|
|||||||
// HTTP example - demonstrates the Http effect
|
|
||||||
//
|
|
||||||
// This script makes HTTP requests and parses JSON responses
|
|
||||||
|
|
||||||
fn main(): Unit with {Console, Http} = {
|
fn main(): Unit with {Console, Http} = {
|
||||||
Console.print("=== Lux HTTP Example ===")
|
Console.print("=== Lux HTTP Example ===")
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Make a GET request to a public API
|
|
||||||
Console.print("Fetching data from httpbin.org...")
|
Console.print("Fetching data from httpbin.org...")
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
match Http.get("https://httpbin.org/get") {
|
match Http.get("https://httpbin.org/get") {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
Console.print("GET request successful!")
|
Console.print("GET request successful!")
|
||||||
Console.print(" Status: " + toString(response.status))
|
Console.print(" Status: " + toString(response.status))
|
||||||
Console.print(" Body length: " + toString(String.length(response.body)) + " bytes")
|
Console.print(" Body length: " + toString(String.length(response.body)) + " bytes")
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
match Json.parse(response.body) {
|
||||||
// Parse the JSON response
|
Ok(json) => {
|
||||||
match Json.parse(response.body) {
|
Console.print("Parsed JSON response:")
|
||||||
Ok(json) => {
|
match Json.get(json, "origin") {
|
||||||
Console.print("Parsed JSON response:")
|
Some(origin) => match Json.asString(origin) {
|
||||||
match Json.get(json, "origin") {
|
Some(ip) => Console.print(" Your IP: " + ip),
|
||||||
Some(origin) => match Json.asString(origin) {
|
None => Console.print(" origin: (not a string)"),
|
||||||
Some(ip) => Console.print(" Your IP: " + ip),
|
},
|
||||||
None => Console.print(" origin: (not a string)")
|
None => Console.print(" origin: (not found)"),
|
||||||
},
|
}
|
||||||
None => Console.print(" origin: (not found)")
|
match Json.get(json, "url") {
|
||||||
}
|
Some(url) => match Json.asString(url) {
|
||||||
match Json.get(json, "url") {
|
Some(u) => Console.print(" URL: " + u),
|
||||||
Some(url) => match Json.asString(url) {
|
None => Console.print(" url: (not a string)"),
|
||||||
Some(u) => Console.print(" URL: " + u),
|
},
|
||||||
None => Console.print(" url: (not a string)")
|
None => Console.print(" url: (not found)"),
|
||||||
},
|
}
|
||||||
None => Console.print(" url: (not found)")
|
},
|
||||||
}
|
Err(e) => Console.print("JSON parse error: " + e),
|
||||||
},
|
}
|
||||||
Err(e) => Console.print("JSON parse error: " + e)
|
},
|
||||||
}
|
Err(e) => Console.print("GET request failed: " + e),
|
||||||
},
|
}
|
||||||
Err(e) => Console.print("GET request failed: " + e)
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.print("")
|
Console.print("")
|
||||||
Console.print("--- POST Request ---")
|
Console.print("--- POST Request ---")
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Make a POST request with JSON body
|
|
||||||
let requestBody = Json.object([("message", Json.string("Hello from Lux!")), ("version", Json.int(1))])
|
let requestBody = Json.object([("message", Json.string("Hello from Lux!")), ("version", Json.int(1))])
|
||||||
Console.print("Sending POST with JSON body...")
|
Console.print("Sending POST with JSON body...")
|
||||||
Console.print(" Body: " + Json.stringify(requestBody))
|
Console.print(" Body: " + Json.stringify(requestBody))
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
match Http.postJson("https://httpbin.org/post", requestBody) {
|
match Http.postJson("https://httpbin.org/post", requestBody) {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
Console.print("POST request successful!")
|
Console.print("POST request successful!")
|
||||||
Console.print(" Status: " + toString(response.status))
|
Console.print(" Status: " + toString(response.status))
|
||||||
|
match Json.parse(response.body) {
|
||||||
// Parse and extract what we sent
|
Ok(json) => match Json.get(json, "json") {
|
||||||
match Json.parse(response.body) {
|
Some(sentJson) => {
|
||||||
Ok(json) => match Json.get(json, "json") {
|
Console.print(" Server received:")
|
||||||
Some(sentJson) => {
|
Console.print(" " + Json.stringify(sentJson))
|
||||||
Console.print(" Server received:")
|
},
|
||||||
Console.print(" " + Json.stringify(sentJson))
|
None => Console.print(" (no json field in response)"),
|
||||||
},
|
},
|
||||||
None => Console.print(" (no json field in response)")
|
Err(e) => Console.print("JSON parse error: " + e),
|
||||||
},
|
}
|
||||||
Err(e) => Console.print("JSON parse error: " + e)
|
},
|
||||||
}
|
Err(e) => Console.print("POST request failed: " + e),
|
||||||
},
|
}
|
||||||
Err(e) => Console.print("POST request failed: " + e)
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.print("")
|
Console.print("")
|
||||||
Console.print("--- Headers ---")
|
Console.print("--- Headers ---")
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Show response headers
|
|
||||||
match Http.get("https://httpbin.org/headers") {
|
match Http.get("https://httpbin.org/headers") {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
Console.print("Response headers (first 5):")
|
Console.print("Response headers (first 5):")
|
||||||
let count = 0
|
let count = 0
|
||||||
// Note: Can't easily iterate with effects in callbacks, so just show count
|
Console.print(" Total headers: " + toString(List.length(response.headers)))
|
||||||
Console.print(" Total headers: " + toString(List.length(response.headers)))
|
},
|
||||||
},
|
Err(e) => Console.print("Request failed: " + e),
|
||||||
Err(e) => Console.print("Request failed: " + e)
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = run main() with {}
|
let result = run main() with {}
|
||||||
|
|||||||
@@ -1,85 +1,48 @@
|
|||||||
// HTTP API Example
|
fn httpOk(body: String): { status: Int, body: String } = { status: 200, body: body }
|
||||||
//
|
|
||||||
// A complete REST API demonstrating:
|
|
||||||
// - Route matching with path parameters
|
|
||||||
// - Response builders
|
|
||||||
// - JSON construction
|
|
||||||
//
|
|
||||||
// Run with: lux examples/http_api.lux
|
|
||||||
// Test with:
|
|
||||||
// curl http://localhost:8080/
|
|
||||||
// curl http://localhost:8080/users
|
|
||||||
// curl http://localhost:8080/users/42
|
|
||||||
|
|
||||||
// ============================================================
|
fn httpCreated(body: String): { status: Int, body: String } = { status: 201, body: body }
|
||||||
// Response Helpers
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
fn httpOk(body: String): { status: Int, body: String } =
|
fn httpNotFound(body: String): { status: Int, body: String } = { status: 404, body: body }
|
||||||
{ status: 200, body: body }
|
|
||||||
|
|
||||||
fn httpCreated(body: String): { status: Int, body: String } =
|
fn httpBadRequest(body: String): { status: Int, body: String } = { status: 400, body: body }
|
||||||
{ status: 201, body: body }
|
|
||||||
|
|
||||||
fn httpNotFound(body: String): { status: Int, body: String } =
|
fn jsonEscape(s: String): String = String.replace(String.replace(s, "\\", "\\\\"), "\"", "\\\"")
|
||||||
{ status: 404, body: body }
|
|
||||||
|
|
||||||
fn httpBadRequest(body: String): { status: Int, body: String } =
|
fn jsonStr(key: String, value: String): String = "\"" + jsonEscape(key) + "\":\"" + jsonEscape(value) + "\""
|
||||||
{ status: 400, body: body }
|
|
||||||
|
|
||||||
// ============================================================
|
fn jsonNum(key: String, value: Int): String = "\"" + jsonEscape(key) + "\":" + toString(value)
|
||||||
// JSON Helpers
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
fn jsonEscape(s: String): String =
|
fn jsonObj(content: String): String = toString(" + content + ")
|
||||||
String.replace(String.replace(s, "\\", "\\\\"), "\"", "\\\"")
|
|
||||||
|
|
||||||
fn jsonStr(key: String, value: String): String =
|
fn jsonArr(content: String): String = "[" + content + "]"
|
||||||
"\"" + jsonEscape(key) + "\":\"" + jsonEscape(value) + "\""
|
|
||||||
|
|
||||||
fn jsonNum(key: String, value: Int): String =
|
fn jsonError(message: String): String = jsonObj(jsonStr("error", message))
|
||||||
"\"" + jsonEscape(key) + "\":" + toString(value)
|
|
||||||
|
|
||||||
fn jsonObj(content: String): String =
|
|
||||||
"{" + content + "}"
|
|
||||||
|
|
||||||
fn jsonArr(content: String): String =
|
|
||||||
"[" + content + "]"
|
|
||||||
|
|
||||||
fn jsonError(message: String): String =
|
|
||||||
jsonObj(jsonStr("error", message))
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Path Matching
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
fn pathMatches(path: String, pattern: String): Bool = {
|
fn pathMatches(path: String, pattern: String): Bool = {
|
||||||
let pathParts = String.split(path, "/")
|
let pathParts = String.split(path, "/")
|
||||||
let patternParts = String.split(pattern, "/")
|
let patternParts = String.split(pattern, "/")
|
||||||
if List.length(pathParts) != List.length(patternParts) then false
|
if List.length(pathParts) != List.length(patternParts) then false else matchParts(pathParts, patternParts)
|
||||||
else matchParts(pathParts, patternParts)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matchParts(pathParts: List<String>, patternParts: List<String>): Bool = {
|
fn matchParts(pathParts: List<String>, patternParts: List<String>): Bool = {
|
||||||
if List.length(pathParts) == 0 then true
|
if List.length(pathParts) == 0 then true else {
|
||||||
else {
|
match List.head(pathParts) {
|
||||||
match List.head(pathParts) {
|
None => true,
|
||||||
None => true,
|
Some(pathPart) => {
|
||||||
Some(pathPart) => {
|
match List.head(patternParts) {
|
||||||
match List.head(patternParts) {
|
None => true,
|
||||||
None => true,
|
Some(patternPart) => {
|
||||||
Some(patternPart) => {
|
let isMatch = if String.startsWith(patternPart, ":") then true else pathPart == patternPart
|
||||||
let isMatch = if String.startsWith(patternPart, ":") then true else pathPart == patternPart
|
if isMatch then {
|
||||||
if isMatch then {
|
let restPath = Option.getOrElse(List.tail(pathParts), [])
|
||||||
let restPath = Option.getOrElse(List.tail(pathParts), [])
|
let restPattern = Option.getOrElse(List.tail(patternParts), [])
|
||||||
let restPattern = Option.getOrElse(List.tail(patternParts), [])
|
matchParts(restPath, restPattern)
|
||||||
matchParts(restPath, restPattern)
|
} else false
|
||||||
} else false
|
},
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getPathSegment(path: String, index: Int): Option<String> = {
|
fn getPathSegment(path: String, index: Int): Option<String> = {
|
||||||
@@ -87,15 +50,9 @@ fn getPathSegment(path: String, index: Int): Option<String> = {
|
|||||||
List.get(parts, index + 1)
|
List.get(parts, index + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
fn indexHandler(): { status: Int, body: String } = httpOk(jsonObj(jsonStr("message", "Welcome to Lux HTTP API")))
|
||||||
// Handlers
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
fn indexHandler(): { status: Int, body: String } =
|
fn healthHandler(): { status: Int, body: String } = httpOk(jsonObj(jsonStr("status", "healthy")))
|
||||||
httpOk(jsonObj(jsonStr("message", "Welcome to Lux HTTP API")))
|
|
||||||
|
|
||||||
fn healthHandler(): { status: Int, body: String } =
|
|
||||||
httpOk(jsonObj(jsonStr("status", "healthy")))
|
|
||||||
|
|
||||||
fn listUsersHandler(): { status: Int, body: String } = {
|
fn listUsersHandler(): { status: Int, body: String } = {
|
||||||
let user1 = jsonObj(jsonNum("id", 1) + "," + jsonStr("name", "Alice"))
|
let user1 = jsonObj(jsonNum("id", 1) + "," + jsonStr("name", "Alice"))
|
||||||
@@ -105,12 +62,12 @@ fn listUsersHandler(): { status: Int, body: String } = {
|
|||||||
|
|
||||||
fn getUserHandler(path: String): { status: Int, body: String } = {
|
fn getUserHandler(path: String): { status: Int, body: String } = {
|
||||||
match getPathSegment(path, 1) {
|
match getPathSegment(path, 1) {
|
||||||
Some(id) => {
|
Some(id) => {
|
||||||
let body = jsonObj(jsonStr("id", id) + "," + jsonStr("name", "User " + id))
|
let body = jsonObj(jsonStr("id", id) + "," + jsonStr("name", "User " + id))
|
||||||
httpOk(body)
|
httpOk(body)
|
||||||
},
|
},
|
||||||
None => httpNotFound(jsonError("User not found"))
|
None => httpNotFound(jsonError("User not found")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn createUserHandler(body: String): { status: Int, body: String } = {
|
fn createUserHandler(body: String): { status: Int, body: String } = {
|
||||||
@@ -118,34 +75,21 @@ fn createUserHandler(body: String): { status: Int, body: String } = {
|
|||||||
httpCreated(newUser)
|
httpCreated(newUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Router
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
fn router(method: String, path: String, body: String): { status: Int, body: String } = {
|
fn router(method: String, path: String, body: String): { status: Int, body: String } = {
|
||||||
if method == "GET" && path == "/" then indexHandler()
|
if method == "GET" && path == "/" then indexHandler() else if method == "GET" && path == "/health" then healthHandler() else if method == "GET" && path == "/users" then listUsersHandler() else if method == "GET" && pathMatches(path, "/users/:id") then getUserHandler(path) else if method == "POST" && path == "/users" then createUserHandler(body) else httpNotFound(jsonError("Not found: " + path))
|
||||||
else if method == "GET" && path == "/health" then healthHandler()
|
|
||||||
else if method == "GET" && path == "/users" then listUsersHandler()
|
|
||||||
else if method == "GET" && pathMatches(path, "/users/:id") then getUserHandler(path)
|
|
||||||
else if method == "POST" && path == "/users" then createUserHandler(body)
|
|
||||||
else httpNotFound(jsonError("Not found: " + path))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Server
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
fn serveLoop(remaining: Int): Unit with {Console, HttpServer} = {
|
fn serveLoop(remaining: Int): Unit with {Console, HttpServer} = {
|
||||||
if remaining <= 0 then {
|
if remaining <= 0 then {
|
||||||
Console.print("Max requests reached, stopping server.")
|
Console.print("Max requests reached, stopping server.")
|
||||||
HttpServer.stop()
|
HttpServer.stop()
|
||||||
} else {
|
} else {
|
||||||
let req = HttpServer.accept()
|
let req = HttpServer.accept()
|
||||||
Console.print(req.method + " " + req.path)
|
Console.print(req.method + " " + req.path)
|
||||||
let resp = router(req.method, req.path, req.body)
|
let resp = router(req.method, req.path, req.body)
|
||||||
HttpServer.respond(resp.status, resp.body)
|
HttpServer.respond(resp.status, resp.body)
|
||||||
serveLoop(remaining - 1)
|
serveLoop(remaining - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main(): Unit with {Console, HttpServer} = {
|
fn main(): Unit with {Console, HttpServer} = {
|
||||||
|
|||||||
@@ -1,24 +1,4 @@
|
|||||||
// HTTP Router Example
|
fn indexHandler(): { status: Int, body: String } = httpOk("Welcome to Lux HTTP Framework!")
|
||||||
//
|
|
||||||
// Demonstrates the HTTP helper library with:
|
|
||||||
// - Path pattern matching
|
|
||||||
// - Response builders
|
|
||||||
// - JSON helpers
|
|
||||||
//
|
|
||||||
// Run with: lux examples/http_router.lux
|
|
||||||
// Test with:
|
|
||||||
// curl http://localhost:8080/
|
|
||||||
// curl http://localhost:8080/users
|
|
||||||
// curl http://localhost:8080/users/42
|
|
||||||
|
|
||||||
import stdlib/http
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Route Handlers
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
fn indexHandler(): { status: Int, body: String } =
|
|
||||||
httpOk("Welcome to Lux HTTP Framework!")
|
|
||||||
|
|
||||||
fn listUsersHandler(): { status: Int, body: String } = {
|
fn listUsersHandler(): { status: Int, body: String } = {
|
||||||
let user1 = jsonObject(jsonJoin([jsonNumber("id", 1), jsonString("name", "Alice")]))
|
let user1 = jsonObject(jsonJoin([jsonNumber("id", 1), jsonString("name", "Alice")]))
|
||||||
@@ -29,44 +9,31 @@ fn listUsersHandler(): { status: Int, body: String } = {
|
|||||||
|
|
||||||
fn getUserHandler(path: String): { status: Int, body: String } = {
|
fn getUserHandler(path: String): { status: Int, body: String } = {
|
||||||
match getPathSegment(path, 1) {
|
match getPathSegment(path, 1) {
|
||||||
Some(id) => {
|
Some(id) => {
|
||||||
let body = jsonObject(jsonJoin([jsonString("id", id), jsonString("name", "User " + id)]))
|
let body = jsonObject(jsonJoin([jsonString("id", id), jsonString("name", "User " + id)]))
|
||||||
httpOk(body)
|
httpOk(body)
|
||||||
},
|
},
|
||||||
None => httpNotFound(jsonErrorMsg("User ID required"))
|
None => httpNotFound(jsonErrorMsg("User ID required")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn healthHandler(): { status: Int, body: String } =
|
fn healthHandler(): { status: Int, body: String } = httpOk(jsonObject(jsonString("status", "healthy")))
|
||||||
httpOk(jsonObject(jsonString("status", "healthy")))
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Router
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
fn router(method: String, path: String, body: String): { status: Int, body: String } = {
|
fn router(method: String, path: String, body: String): { status: Int, body: String } = {
|
||||||
if method == "GET" && path == "/" then indexHandler()
|
if method == "GET" && path == "/" then indexHandler() else if method == "GET" && path == "/health" then healthHandler() else if method == "GET" && path == "/users" then listUsersHandler() else if method == "GET" && pathMatches(path, "/users/:id") then getUserHandler(path) else httpNotFound(jsonErrorMsg("Not found: " + path))
|
||||||
else if method == "GET" && path == "/health" then healthHandler()
|
|
||||||
else if method == "GET" && path == "/users" then listUsersHandler()
|
|
||||||
else if method == "GET" && pathMatches(path, "/users/:id") then getUserHandler(path)
|
|
||||||
else httpNotFound(jsonErrorMsg("Not found: " + path))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Server
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
fn serveLoop(remaining: Int): Unit with {Console, HttpServer} = {
|
fn serveLoop(remaining: Int): Unit with {Console, HttpServer} = {
|
||||||
if remaining <= 0 then {
|
if remaining <= 0 then {
|
||||||
Console.print("Max requests reached, stopping server.")
|
Console.print("Max requests reached, stopping server.")
|
||||||
HttpServer.stop()
|
HttpServer.stop()
|
||||||
} else {
|
} else {
|
||||||
let req = HttpServer.accept()
|
let req = HttpServer.accept()
|
||||||
Console.print(req.method + " " + req.path)
|
Console.print(req.method + " " + req.path)
|
||||||
let resp = router(req.method, req.path, req.body)
|
let resp = router(req.method, req.path, req.body)
|
||||||
HttpServer.respond(resp.status, resp.body)
|
HttpServer.respond(resp.status, resp.body)
|
||||||
serveLoop(remaining - 1)
|
serveLoop(remaining - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main(): Unit with {Console, HttpServer} = {
|
fn main(): Unit with {Console, HttpServer} = {
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
// Test file for JIT compilation
|
fn fib(n: Int): Int = if n <= 1 then n else fib(n - 1) + fib(n - 2)
|
||||||
// This uses only features the JIT supports: integers, arithmetic, conditionals, functions
|
|
||||||
|
|
||||||
fn fib(n: Int): Int =
|
fn factorial(n: Int): Int = if n <= 1 then 1 else n * factorial(n - 1)
|
||||||
if n <= 1 then n
|
|
||||||
else fib(n - 1) + fib(n - 2)
|
|
||||||
|
|
||||||
fn factorial(n: Int): Int =
|
|
||||||
if n <= 1 then 1
|
|
||||||
else n * factorial(n - 1)
|
|
||||||
|
|
||||||
fn main(): Unit with {Console} = {
|
fn main(): Unit with {Console} = {
|
||||||
let fibResult = fib(30)
|
let fibResult = fib(30)
|
||||||
|
|||||||
@@ -1,107 +1,79 @@
|
|||||||
// JSON example - demonstrates JSON parsing and manipulation
|
|
||||||
//
|
|
||||||
// This script parses JSON, extracts values, and builds new JSON structures
|
|
||||||
|
|
||||||
fn main(): Unit with {Console, File} = {
|
fn main(): Unit with {Console, File} = {
|
||||||
Console.print("=== Lux JSON Example ===")
|
Console.print("=== Lux JSON Example ===")
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// First, build some JSON programmatically
|
|
||||||
Console.print("=== Building JSON ===")
|
Console.print("=== Building JSON ===")
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
let name = Json.string("Alice")
|
let name = Json.string("Alice")
|
||||||
let age = Json.int(30)
|
let age = Json.int(30)
|
||||||
let active = Json.bool(true)
|
let active = Json.bool(true)
|
||||||
let scores = Json.array([Json.int(95), Json.int(87), Json.int(92)])
|
let scores = Json.array([Json.int(95), Json.int(87), Json.int(92)])
|
||||||
|
|
||||||
let person = Json.object([("name", name), ("age", age), ("active", active), ("scores", scores)])
|
let person = Json.object([("name", name), ("age", age), ("active", active), ("scores", scores)])
|
||||||
|
|
||||||
Console.print("Built JSON:")
|
Console.print("Built JSON:")
|
||||||
let pretty = Json.prettyPrint(person)
|
let pretty = Json.prettyPrint(person)
|
||||||
Console.print(pretty)
|
Console.print(pretty)
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Stringify to a compact string
|
|
||||||
let jsonStr = Json.stringify(person)
|
let jsonStr = Json.stringify(person)
|
||||||
Console.print("Compact: " + jsonStr)
|
Console.print("Compact: " + jsonStr)
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Write to file and read back to test parsing
|
|
||||||
File.write("/tmp/test.json", jsonStr)
|
File.write("/tmp/test.json", jsonStr)
|
||||||
Console.print("Written to /tmp/test.json")
|
Console.print("Written to /tmp/test.json")
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Read and parse from file
|
|
||||||
Console.print("=== Parsing JSON ===")
|
Console.print("=== Parsing JSON ===")
|
||||||
Console.print("")
|
Console.print("")
|
||||||
let content = File.read("/tmp/test.json")
|
let content = File.read("/tmp/test.json")
|
||||||
Console.print("Read from file: " + content)
|
Console.print("Read from file: " + content)
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
match Json.parse(content) {
|
match Json.parse(content) {
|
||||||
Ok(json) => {
|
Ok(json) => {
|
||||||
Console.print("Parse succeeded!")
|
Console.print("Parse succeeded!")
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
Console.print("Extracting fields:")
|
||||||
// Get string field
|
match Json.get(json, "name") {
|
||||||
Console.print("Extracting fields:")
|
Some(nameJson) => match Json.asString(nameJson) {
|
||||||
match Json.get(json, "name") {
|
Some(n) => Console.print(" name: " + n),
|
||||||
Some(nameJson) => match Json.asString(nameJson) {
|
None => Console.print(" name: (not a string)"),
|
||||||
Some(n) => Console.print(" name: " + n),
|
},
|
||||||
None => Console.print(" name: (not a string)")
|
None => Console.print(" name: (not found)"),
|
||||||
},
|
}
|
||||||
None => Console.print(" name: (not found)")
|
match Json.get(json, "age") {
|
||||||
}
|
Some(ageJson) => match Json.asInt(ageJson) {
|
||||||
|
Some(a) => Console.print(" age: " + toString(a)),
|
||||||
// Get int field
|
None => Console.print(" age: (not an int)"),
|
||||||
match Json.get(json, "age") {
|
},
|
||||||
Some(ageJson) => match Json.asInt(ageJson) {
|
None => Console.print(" age: (not found)"),
|
||||||
Some(a) => Console.print(" age: " + toString(a)),
|
}
|
||||||
None => Console.print(" age: (not an int)")
|
match Json.get(json, "active") {
|
||||||
},
|
Some(activeJson) => match Json.asBool(activeJson) {
|
||||||
None => Console.print(" age: (not found)")
|
Some(a) => Console.print(" active: " + toString(a)),
|
||||||
}
|
None => Console.print(" active: (not a bool)"),
|
||||||
|
},
|
||||||
// Get bool field
|
None => Console.print(" active: (not found)"),
|
||||||
match Json.get(json, "active") {
|
}
|
||||||
Some(activeJson) => match Json.asBool(activeJson) {
|
match Json.get(json, "scores") {
|
||||||
Some(a) => Console.print(" active: " + toString(a)),
|
Some(scoresJson) => match Json.asArray(scoresJson) {
|
||||||
None => Console.print(" active: (not a bool)")
|
Some(arr) => {
|
||||||
},
|
Console.print(" scores: " + toString(List.length(arr)) + " items")
|
||||||
None => Console.print(" active: (not found)")
|
match Json.getIndex(scoresJson, 0) {
|
||||||
}
|
Some(firstJson) => match Json.asInt(firstJson) {
|
||||||
|
Some(first) => Console.print(" first score: " + toString(first)),
|
||||||
// Get array field
|
None => Console.print(" first score: (not an int)"),
|
||||||
match Json.get(json, "scores") {
|
},
|
||||||
Some(scoresJson) => match Json.asArray(scoresJson) {
|
None => Console.print(" (no first element)"),
|
||||||
Some(arr) => {
|
}
|
||||||
Console.print(" scores: " + toString(List.length(arr)) + " items")
|
},
|
||||||
// Get first score
|
None => Console.print(" scores: (not an array)"),
|
||||||
match Json.getIndex(scoresJson, 0) {
|
},
|
||||||
Some(firstJson) => match Json.asInt(firstJson) {
|
None => Console.print(" scores: (not found)"),
|
||||||
Some(first) => Console.print(" first score: " + toString(first)),
|
}
|
||||||
None => Console.print(" first score: (not an int)")
|
Console.print("")
|
||||||
},
|
Console.print("Object keys:")
|
||||||
None => Console.print(" (no first element)")
|
match Json.keys(json) {
|
||||||
}
|
Some(ks) => Console.print(" " + String.join(ks, ", ")),
|
||||||
},
|
None => Console.print(" (not an object)"),
|
||||||
None => Console.print(" scores: (not an array)")
|
}
|
||||||
},
|
},
|
||||||
None => Console.print(" scores: (not found)")
|
Err(e) => Console.print("Parse error: " + e),
|
||||||
}
|
}
|
||||||
Console.print("")
|
|
||||||
|
|
||||||
// Get the keys
|
|
||||||
Console.print("Object keys:")
|
|
||||||
match Json.keys(json) {
|
|
||||||
Some(ks) => Console.print(" " + String.join(ks, ", ")),
|
|
||||||
None => Console.print(" (not an object)")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => Console.print("Parse error: " + e)
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.print("")
|
Console.print("")
|
||||||
Console.print("=== JSON Null Check ===")
|
Console.print("=== JSON Null Check ===")
|
||||||
let nullVal = Json.null()
|
let nullVal = Json.null()
|
||||||
|
|||||||
@@ -1,17 +1,9 @@
|
|||||||
// Main program that imports modules
|
|
||||||
import examples/modules/math_utils
|
|
||||||
import examples/modules/string_utils
|
|
||||||
|
|
||||||
fn main(): Unit with {Console} = {
|
fn main(): Unit with {Console} = {
|
||||||
Console.print("=== Testing Module Imports ===")
|
Console.print("=== Testing Module Imports ===")
|
||||||
|
|
||||||
// Use math_utils
|
|
||||||
Console.print("square(5) = " + toString(math_utils.square(5)))
|
Console.print("square(5) = " + toString(math_utils.square(5)))
|
||||||
Console.print("cube(3) = " + toString(math_utils.cube(3)))
|
Console.print("cube(3) = " + toString(math_utils.cube(3)))
|
||||||
Console.print("factorial(6) = " + toString(math_utils.factorial(6)))
|
Console.print("factorial(6) = " + toString(math_utils.factorial(6)))
|
||||||
Console.print("sumRange(1, 10) = " + toString(math_utils.sumRange(1, 10)))
|
Console.print("sumRange(1, 10) = " + toString(math_utils.sumRange(1, 10)))
|
||||||
|
|
||||||
// Use string_utils
|
|
||||||
Console.print(string_utils.greet("World"))
|
Console.print(string_utils.greet("World"))
|
||||||
Console.print(string_utils.exclaim("Modules work"))
|
Console.print(string_utils.exclaim("Modules work"))
|
||||||
Console.print("repeat(\"ab\", 3) = " + string_utils.repeat("ab", 3))
|
Console.print("repeat(\"ab\", 3) = " + string_utils.repeat("ab", 3))
|
||||||
|
|||||||
@@ -1,15 +1,7 @@
|
|||||||
// Test selective imports
|
|
||||||
import examples/modules/math_utils.{square, factorial}
|
|
||||||
import examples/modules/string_utils as str
|
|
||||||
|
|
||||||
fn main(): Unit with {Console} = {
|
fn main(): Unit with {Console} = {
|
||||||
Console.print("=== Selective & Aliased Imports ===")
|
Console.print("=== Selective & Aliased Imports ===")
|
||||||
|
|
||||||
// Direct imports (no module prefix)
|
|
||||||
Console.print("square(7) = " + toString(square(7)))
|
Console.print("square(7) = " + toString(square(7)))
|
||||||
Console.print("factorial(5) = " + toString(factorial(5)))
|
Console.print("factorial(5) = " + toString(factorial(5)))
|
||||||
|
|
||||||
// Aliased import
|
|
||||||
Console.print(str.greet("Lux"))
|
Console.print(str.greet("Lux"))
|
||||||
Console.print(str.exclaim("Aliased imports work"))
|
Console.print(str.exclaim("Aliased imports work"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
// Test wildcard imports
|
|
||||||
import examples/modules/math_utils.*
|
|
||||||
|
|
||||||
fn main(): Unit with {Console} = {
|
fn main(): Unit with {Console} = {
|
||||||
Console.print("=== Wildcard Imports ===")
|
Console.print("=== Wildcard Imports ===")
|
||||||
|
|
||||||
// All functions available directly
|
|
||||||
Console.print("square(4) = " + toString(square(4)))
|
Console.print("square(4) = " + toString(square(4)))
|
||||||
Console.print("cube(4) = " + toString(cube(4)))
|
Console.print("cube(4) = " + toString(cube(4)))
|
||||||
Console.print("factorial(4) = " + toString(factorial(4)))
|
Console.print("factorial(4) = " + toString(factorial(4)))
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
// Math utilities module
|
fn square(n: Int): Int = n * n
|
||||||
// Exports: square, cube, factorial
|
|
||||||
|
|
||||||
pub fn square(n: Int): Int = n * n
|
fn cube(n: Int): Int = n * n * n
|
||||||
|
|
||||||
pub fn cube(n: Int): Int = n * n * n
|
fn factorial(n: Int): Int = if n <= 1 then 1 else n * factorial(n - 1)
|
||||||
|
|
||||||
pub fn factorial(n: Int): Int =
|
fn sumRange(start: Int, end: Int): Int = if start > end then 0 else start + sumRange(start + 1, end)
|
||||||
if n <= 1 then 1
|
|
||||||
else n * factorial(n - 1)
|
|
||||||
|
|
||||||
pub fn sumRange(start: Int, end: Int): Int =
|
|
||||||
if start > end then 0
|
|
||||||
else start + sumRange(start + 1, end)
|
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
// String utilities module
|
fn repeat(s: String, n: Int): String = if n <= 0 then "" else s + repeat(s, n - 1)
|
||||||
// Exports: repeat, exclaim
|
|
||||||
|
|
||||||
pub fn repeat(s: String, n: Int): String =
|
fn exclaim(s: String): String = s + "!"
|
||||||
if n <= 0 then ""
|
|
||||||
else s + repeat(s, n - 1)
|
|
||||||
|
|
||||||
pub fn exclaim(s: String): String = s + "!"
|
fn greet(name: String): String = "Hello, " + name + "!"
|
||||||
|
|
||||||
pub fn greet(name: String): String =
|
|
||||||
"Hello, " + name + "!"
|
|
||||||
|
|||||||
@@ -1,17 +1,9 @@
|
|||||||
// Example using the standard library
|
|
||||||
import std/prelude.*
|
|
||||||
import std/option as opt
|
|
||||||
|
|
||||||
fn main(): Unit with {Console} = {
|
fn main(): Unit with {Console} = {
|
||||||
Console.print("=== Using Standard Library ===")
|
Console.print("=== Using Standard Library ===")
|
||||||
|
|
||||||
// Prelude functions
|
|
||||||
Console.print("identity(42) = " + toString(identity(42)))
|
Console.print("identity(42) = " + toString(identity(42)))
|
||||||
Console.print("not(true) = " + toString(not(true)))
|
Console.print("not(true) = " + toString(not(true)))
|
||||||
Console.print("and(true, false) = " + toString(and(true, false)))
|
Console.print("and(true, false) = " + toString(and(true, false)))
|
||||||
Console.print("or(true, false) = " + toString(or(true, false)))
|
Console.print("or(true, false) = " + toString(or(true, false)))
|
||||||
|
|
||||||
// Option utilities
|
|
||||||
let x = opt.some(10)
|
let x = opt.some(10)
|
||||||
let y = opt.none()
|
let y = opt.none()
|
||||||
Console.print("isSome(Some(10)) = " + toString(opt.isSome(x)))
|
Console.print("isSome(Some(10)) = " + toString(opt.isSome(x)))
|
||||||
|
|||||||
@@ -1,47 +1,31 @@
|
|||||||
// Demonstrating the pipe operator and functional data processing
|
|
||||||
//
|
|
||||||
// Expected output:
|
|
||||||
// 5 |> double |> addTen |> square = 400
|
|
||||||
// Pipeline result2 = 42
|
|
||||||
// process(1) = 144
|
|
||||||
// process(2) = 196
|
|
||||||
// process(3) = 256
|
|
||||||
// clamped = 0
|
|
||||||
// composed = 121
|
|
||||||
|
|
||||||
// Basic transformations
|
|
||||||
fn double(x: Int): Int = x * 2
|
fn double(x: Int): Int = x * 2
|
||||||
|
|
||||||
fn addTen(x: Int): Int = x + 10
|
fn addTen(x: Int): Int = x + 10
|
||||||
|
|
||||||
fn square(x: Int): Int = x * x
|
fn square(x: Int): Int = x * x
|
||||||
|
|
||||||
fn negate(x: Int): Int = -x
|
fn negate(x: Int): Int = -x
|
||||||
|
|
||||||
// Using the pipe operator for data transformation
|
let result1 = square(addTen(double(5)))
|
||||||
let result1 = 5 |> double |> addTen |> square
|
|
||||||
|
|
||||||
// Chaining multiple operations
|
let result2 = addTen(double(addTen(double(3))))
|
||||||
let result2 = 3 |> double |> addTen |> double |> addTen
|
|
||||||
|
|
||||||
// More complex pipelines
|
fn process(n: Int): Int = square(addTen(double(n)))
|
||||||
fn process(n: Int): Int =
|
|
||||||
n |> double |> addTen |> square
|
|
||||||
|
|
||||||
// Multiple values through same pipeline
|
|
||||||
let a = process(1)
|
let a = process(1)
|
||||||
|
|
||||||
let b = process(2)
|
let b = process(2)
|
||||||
|
|
||||||
let c = process(3)
|
let c = process(3)
|
||||||
|
|
||||||
// Conditional in pipeline
|
fn clampPositive(x: Int): Int = if x < 0 then 0 else x
|
||||||
fn clampPositive(x: Int): Int =
|
|
||||||
if x < 0 then 0 else x
|
|
||||||
|
|
||||||
let clamped = -5 |> double |> clampPositive
|
let clamped = clampPositive(double(-5))
|
||||||
|
|
||||||
// Function composition using pipe
|
|
||||||
fn increment(x: Int): Int = x + 1
|
fn increment(x: Int): Int = x + 1
|
||||||
|
|
||||||
let composed = 5 |> double |> increment |> square
|
let composed = square(increment(double(5)))
|
||||||
|
|
||||||
// Print results
|
|
||||||
fn printResults(): Unit with {Console} = {
|
fn printResults(): Unit with {Console} = {
|
||||||
Console.print("5 |> double |> addTen |> square = " + toString(result1))
|
Console.print("5 |> double |> addTen |> square = " + toString(result1))
|
||||||
Console.print("Pipeline result2 = " + toString(result2))
|
Console.print("Pipeline result2 = " + toString(result2))
|
||||||
|
|||||||
@@ -1,72 +1,42 @@
|
|||||||
// PostgreSQL Database Example
|
fn jsonStr(key: String, value: String): String = "\"" + key + "\":\"" + value + "\""
|
||||||
//
|
|
||||||
// Demonstrates the Postgres effect for database operations.
|
|
||||||
//
|
|
||||||
// Prerequisites:
|
|
||||||
// - PostgreSQL server running locally
|
|
||||||
// - Database 'testdb' created
|
|
||||||
// - User 'testuser' with password 'testpass'
|
|
||||||
//
|
|
||||||
// To set up:
|
|
||||||
// createdb testdb
|
|
||||||
// psql testdb -c "CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT, email TEXT);"
|
|
||||||
//
|
|
||||||
// Run with: lux examples/postgres_demo.lux
|
|
||||||
|
|
||||||
// ============================================================
|
fn jsonNum(key: String, value: Int): String = "\"" + key + "\":" + toString(value)
|
||||||
// Helper Functions
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
fn jsonStr(key: String, value: String): String =
|
fn jsonObj(content: String): String = toString(" + content + ")
|
||||||
"\"" + key + "\":\"" + value + "\""
|
|
||||||
|
|
||||||
fn jsonNum(key: String, value: Int): String =
|
|
||||||
"\"" + key + "\":" + toString(value)
|
|
||||||
|
|
||||||
fn jsonObj(content: String): String =
|
|
||||||
"{" + content + "}"
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Database Operations
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
// Insert a user
|
|
||||||
fn insertUser(connId: Int, name: String, email: String): Int with {Console, Postgres} = {
|
fn insertUser(connId: Int, name: String, email: String): Int with {Console, Postgres} = {
|
||||||
let sql = "INSERT INTO users (name, email) VALUES ('" + name + "', '" + email + "') RETURNING id"
|
let sql = "INSERT INTO users (name, email) VALUES ('" + name + "', '" + email + "') RETURNING id"
|
||||||
Console.print("Inserting user: " + name)
|
Console.print("Inserting user: " + name)
|
||||||
match Postgres.queryOne(connId, sql) {
|
match Postgres.queryOne(connId, sql) {
|
||||||
Some(row) => {
|
Some(row) => {
|
||||||
Console.print(" Inserted with ID: " + toString(row.id))
|
Console.print(" Inserted with ID: " + toString(row.id))
|
||||||
row.id
|
row.id
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
Console.print(" Insert failed")
|
Console.print(" Insert failed")
|
||||||
-1
|
-1
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all users
|
|
||||||
fn getUsers(connId: Int): Unit with {Console, Postgres} = {
|
fn getUsers(connId: Int): Unit with {Console, Postgres} = {
|
||||||
Console.print("Fetching all users...")
|
Console.print("Fetching all users...")
|
||||||
let rows = Postgres.query(connId, "SELECT id, name, email FROM users ORDER BY id")
|
let rows = Postgres.query(connId, "SELECT id, name, email FROM users ORDER BY id")
|
||||||
Console.print(" Found " + toString(List.length(rows)) + " users:")
|
Console.print(" Found " + toString(List.length(rows)) + " users:")
|
||||||
List.forEach(rows, fn(row: { id: Int, name: String, email: String }): Unit with {Console} => {
|
List.forEach(rows, fn(row: { id: Int, name: String, email: String }): Unit => {
|
||||||
Console.print(" - " + toString(row.id) + ": " + row.name + " <" + row.email + ">")
|
Console.print(" - " + toString(row.id) + ": " + row.name + " <" + row.email + ">")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user by ID
|
|
||||||
fn getUserById(connId: Int, id: Int): Unit with {Console, Postgres} = {
|
fn getUserById(connId: Int, id: Int): Unit with {Console, Postgres} = {
|
||||||
let sql = "SELECT id, name, email FROM users WHERE id = " + toString(id)
|
let sql = "SELECT id, name, email FROM users WHERE id = " + toString(id)
|
||||||
Console.print("Looking up user " + toString(id) + "...")
|
Console.print("Looking up user " + toString(id) + "...")
|
||||||
match Postgres.queryOne(connId, sql) {
|
match Postgres.queryOne(connId, sql) {
|
||||||
Some(row) => Console.print(" Found: " + row.name + " <" + row.email + ">"),
|
Some(row) => Console.print(" Found: " + row.name + " <" + row.email + ">"),
|
||||||
None => Console.print(" User not found")
|
None => Console.print(" User not found"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update user email
|
|
||||||
fn updateUserEmail(connId: Int, id: Int, newEmail: String): Unit with {Console, Postgres} = {
|
fn updateUserEmail(connId: Int, id: Int, newEmail: String): Unit with {Console, Postgres} = {
|
||||||
let sql = "UPDATE users SET email = '" + newEmail + "' WHERE id = " + toString(id)
|
let sql = "UPDATE users SET email = '" + newEmail + "' WHERE id = " + toString(id)
|
||||||
Console.print("Updating user " + toString(id) + " email to " + newEmail)
|
Console.print("Updating user " + toString(id) + " email to " + newEmail)
|
||||||
@@ -74,7 +44,6 @@ fn updateUserEmail(connId: Int, id: Int, newEmail: String): Unit with {Console,
|
|||||||
Console.print(" Rows affected: " + toString(affected))
|
Console.print(" Rows affected: " + toString(affected))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete user
|
|
||||||
fn deleteUser(connId: Int, id: Int): Unit with {Console, Postgres} = {
|
fn deleteUser(connId: Int, id: Int): Unit with {Console, Postgres} = {
|
||||||
let sql = "DELETE FROM users WHERE id = " + toString(id)
|
let sql = "DELETE FROM users WHERE id = " + toString(id)
|
||||||
Console.print("Deleting user " + toString(id))
|
Console.print("Deleting user " + toString(id))
|
||||||
@@ -82,104 +51,63 @@ fn deleteUser(connId: Int, id: Int): Unit with {Console, Postgres} = {
|
|||||||
Console.print(" Rows affected: " + toString(affected))
|
Console.print(" Rows affected: " + toString(affected))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Transaction Example
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
fn transactionDemo(connId: Int): Unit with {Console, Postgres} = {
|
fn transactionDemo(connId: Int): Unit with {Console, Postgres} = {
|
||||||
Console.print("")
|
Console.print("")
|
||||||
Console.print("=== Transaction Demo ===")
|
Console.print("=== Transaction Demo ===")
|
||||||
|
|
||||||
// Start transaction
|
|
||||||
Console.print("Beginning transaction...")
|
Console.print("Beginning transaction...")
|
||||||
Postgres.beginTx(connId)
|
Postgres.beginTx(connId)
|
||||||
|
|
||||||
// Make some changes
|
|
||||||
insertUser(connId, "TxUser1", "tx1@example.com")
|
insertUser(connId, "TxUser1", "tx1@example.com")
|
||||||
insertUser(connId, "TxUser2", "tx2@example.com")
|
insertUser(connId, "TxUser2", "tx2@example.com")
|
||||||
|
|
||||||
// Show users before commit
|
|
||||||
Console.print("Users before commit:")
|
Console.print("Users before commit:")
|
||||||
getUsers(connId)
|
getUsers(connId)
|
||||||
|
|
||||||
// Commit the transaction
|
|
||||||
Console.print("Committing transaction...")
|
Console.print("Committing transaction...")
|
||||||
Postgres.commit(connId)
|
Postgres.commit(connId)
|
||||||
|
|
||||||
Console.print("Transaction committed!")
|
Console.print("Transaction committed!")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Main
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
fn main(): Unit with {Console, Postgres} = {
|
fn main(): Unit with {Console, Postgres} = {
|
||||||
Console.print("========================================")
|
Console.print("========================================")
|
||||||
Console.print(" PostgreSQL Demo")
|
Console.print(" PostgreSQL Demo")
|
||||||
Console.print("========================================")
|
Console.print("========================================")
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Connect to database
|
|
||||||
Console.print("Connecting to PostgreSQL...")
|
Console.print("Connecting to PostgreSQL...")
|
||||||
let connStr = "host=localhost user=testuser password=testpass dbname=testdb"
|
let connStr = "host=localhost user=testuser password=testpass dbname=testdb"
|
||||||
let connId = Postgres.connect(connStr)
|
let connId = Postgres.connect(connStr)
|
||||||
Console.print("Connected! Connection ID: " + toString(connId))
|
Console.print("Connected! Connection ID: " + toString(connId))
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Create table if not exists
|
|
||||||
Console.print("Creating users table...")
|
Console.print("Creating users table...")
|
||||||
Postgres.execute(connId, "CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, email TEXT NOT NULL)")
|
Postgres.execute(connId, "CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, email TEXT NOT NULL)")
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Clear table for demo
|
|
||||||
Console.print("Clearing existing data...")
|
Console.print("Clearing existing data...")
|
||||||
Postgres.execute(connId, "DELETE FROM users")
|
Postgres.execute(connId, "DELETE FROM users")
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Insert some users
|
|
||||||
Console.print("=== Inserting Users ===")
|
Console.print("=== Inserting Users ===")
|
||||||
let id1 = insertUser(connId, "Alice", "alice@example.com")
|
let id1 = insertUser(connId, "Alice", "alice@example.com")
|
||||||
let id2 = insertUser(connId, "Bob", "bob@example.com")
|
let id2 = insertUser(connId, "Bob", "bob@example.com")
|
||||||
let id3 = insertUser(connId, "Charlie", "charlie@example.com")
|
let id3 = insertUser(connId, "Charlie", "charlie@example.com")
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Query all users
|
|
||||||
Console.print("=== All Users ===")
|
Console.print("=== All Users ===")
|
||||||
getUsers(connId)
|
getUsers(connId)
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Query single user
|
|
||||||
Console.print("=== Single User Lookup ===")
|
Console.print("=== Single User Lookup ===")
|
||||||
getUserById(connId, id2)
|
getUserById(connId, id2)
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Update user
|
|
||||||
Console.print("=== Update User ===")
|
Console.print("=== Update User ===")
|
||||||
updateUserEmail(connId, id2, "bob.new@example.com")
|
updateUserEmail(connId, id2, "bob.new@example.com")
|
||||||
getUserById(connId, id2)
|
getUserById(connId, id2)
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Delete user
|
|
||||||
Console.print("=== Delete User ===")
|
Console.print("=== Delete User ===")
|
||||||
deleteUser(connId, id3)
|
deleteUser(connId, id3)
|
||||||
getUsers(connId)
|
getUsers(connId)
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Transaction demo
|
|
||||||
transactionDemo(connId)
|
transactionDemo(connId)
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Final state
|
|
||||||
Console.print("=== Final State ===")
|
Console.print("=== Final State ===")
|
||||||
getUsers(connId)
|
getUsers(connId)
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Close connection
|
|
||||||
Console.print("Closing connection...")
|
Console.print("Closing connection...")
|
||||||
Postgres.close(connId)
|
Postgres.close(connId)
|
||||||
Console.print("Done!")
|
Console.print("Done!")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: This will fail if PostgreSQL is not running
|
|
||||||
// To test the syntax only, you can comment out the last line
|
|
||||||
let output = run main() with {}
|
let output = run main() with {}
|
||||||
|
|||||||
@@ -1,18 +1,6 @@
|
|||||||
// Property-Based Testing Example
|
|
||||||
//
|
|
||||||
// This example demonstrates property-based testing in Lux,
|
|
||||||
// where we verify properties hold for randomly generated inputs.
|
|
||||||
//
|
|
||||||
// Run with: lux examples/property_testing.lux
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Generator Functions (using Random effect)
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
let CHARS = "abcdefghijklmnopqrstuvwxyz"
|
let CHARS = "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
|
||||||
fn genInt(min: Int, max: Int): Int with {Random} =
|
fn genInt(min: Int, max: Int): Int with {Random} = Random.int(min, max)
|
||||||
Random.int(min, max)
|
|
||||||
|
|
||||||
fn genIntList(min: Int, max: Int, maxLen: Int): List<Int> with {Random} = {
|
fn genIntList(min: Int, max: Int, maxLen: Int): List<Int> with {Random} = {
|
||||||
let len = Random.int(0, maxLen)
|
let len = Random.int(0, maxLen)
|
||||||
@@ -20,10 +8,7 @@ fn genIntList(min: Int, max: Int, maxLen: Int): List<Int> with {Random} = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn genIntListHelper(min: Int, max: Int, len: Int): List<Int> with {Random} = {
|
fn genIntListHelper(min: Int, max: Int, len: Int): List<Int> with {Random} = {
|
||||||
if len <= 0 then
|
if len <= 0 then [] else List.concat([Random.int(min, max)], genIntListHelper(min, max, len - 1))
|
||||||
[]
|
|
||||||
else
|
|
||||||
List.concat([Random.int(min, max)], genIntListHelper(min, max, len - 1))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn genChar(): String with {Random} = {
|
fn genChar(): String with {Random} = {
|
||||||
@@ -37,195 +22,147 @@ fn genString(maxLen: Int): String with {Random} = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn genStringHelper(len: Int): String with {Random} = {
|
fn genStringHelper(len: Int): String with {Random} = {
|
||||||
if len <= 0 then
|
if len <= 0 then "" else genChar() + genStringHelper(len - 1)
|
||||||
""
|
|
||||||
else
|
|
||||||
genChar() + genStringHelper(len - 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Test Runner State
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
fn printResult(name: String, passed: Bool, count: Int): Unit with {Console} = {
|
fn printResult(name: String, passed: Bool, count: Int): Unit with {Console} = {
|
||||||
if passed then
|
if passed then Console.print(" PASS " + name + " (" + toString(count) + " tests)") else Console.print(" FAIL " + name)
|
||||||
Console.print(" PASS " + name + " (" + toString(count) + " tests)")
|
|
||||||
else
|
|
||||||
Console.print(" FAIL " + name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Property Tests
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
// Test: List reverse is involutive
|
|
||||||
fn testReverseInvolutive(n: Int, count: Int): Bool with {Console, Random} = {
|
fn testReverseInvolutive(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
if n <= 0 then {
|
if n <= 0 then {
|
||||||
printResult("reverse(reverse(xs)) == xs", true, count)
|
printResult("reverse(reverse(xs)) == xs", true, count)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
let xs = genIntList(0, 100, 20)
|
let xs = genIntList(0, 100, 20)
|
||||||
if List.reverse(List.reverse(xs)) == xs then
|
if List.reverse(List.reverse(xs)) == xs then testReverseInvolutive(n - 1, count) else {
|
||||||
testReverseInvolutive(n - 1, count)
|
printResult("reverse(reverse(xs)) == xs", false, count - n + 1)
|
||||||
else {
|
false
|
||||||
printResult("reverse(reverse(xs)) == xs", false, count - n + 1)
|
}
|
||||||
false
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test: List reverse preserves length
|
|
||||||
fn testReverseLength(n: Int, count: Int): Bool with {Console, Random} = {
|
fn testReverseLength(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
if n <= 0 then {
|
if n <= 0 then {
|
||||||
printResult("length(reverse(xs)) == length(xs)", true, count)
|
printResult("length(reverse(xs)) == length(xs)", true, count)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
let xs = genIntList(0, 100, 20)
|
let xs = genIntList(0, 100, 20)
|
||||||
if List.length(List.reverse(xs)) == List.length(xs) then
|
if List.length(List.reverse(xs)) == List.length(xs) then testReverseLength(n - 1, count) else {
|
||||||
testReverseLength(n - 1, count)
|
printResult("length(reverse(xs)) == length(xs)", false, count - n + 1)
|
||||||
else {
|
false
|
||||||
printResult("length(reverse(xs)) == length(xs)", false, count - n + 1)
|
}
|
||||||
false
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test: List map preserves length
|
|
||||||
fn testMapLength(n: Int, count: Int): Bool with {Console, Random} = {
|
fn testMapLength(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
if n <= 0 then {
|
if n <= 0 then {
|
||||||
printResult("length(map(xs, f)) == length(xs)", true, count)
|
printResult("length(map(xs, f)) == length(xs)", true, count)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
let xs = genIntList(0, 100, 20)
|
let xs = genIntList(0, 100, 20)
|
||||||
if List.length(List.map(xs, fn(x) => x * 2)) == List.length(xs) then
|
if List.length(List.map(xs, fn(x: _) => x * 2)) == List.length(xs) then testMapLength(n - 1, count) else {
|
||||||
testMapLength(n - 1, count)
|
printResult("length(map(xs, f)) == length(xs)", false, count - n + 1)
|
||||||
else {
|
false
|
||||||
printResult("length(map(xs, f)) == length(xs)", false, count - n + 1)
|
}
|
||||||
false
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test: List concat length is sum
|
|
||||||
fn testConcatLength(n: Int, count: Int): Bool with {Console, Random} = {
|
fn testConcatLength(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
if n <= 0 then {
|
if n <= 0 then {
|
||||||
printResult("length(xs ++ ys) == length(xs) + length(ys)", true, count)
|
printResult("length(xs ++ ys) == length(xs) + length(ys)", true, count)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
let xs = genIntList(0, 50, 10)
|
let xs = genIntList(0, 50, 10)
|
||||||
let ys = genIntList(0, 50, 10)
|
let ys = genIntList(0, 50, 10)
|
||||||
if List.length(List.concat(xs, ys)) == List.length(xs) + List.length(ys) then
|
if List.length(List.concat(xs, ys)) == List.length(xs) + List.length(ys) then testConcatLength(n - 1, count) else {
|
||||||
testConcatLength(n - 1, count)
|
printResult("length(xs ++ ys) == length(xs) + length(ys)", false, count - n + 1)
|
||||||
else {
|
false
|
||||||
printResult("length(xs ++ ys) == length(xs) + length(ys)", false, count - n + 1)
|
}
|
||||||
false
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test: Addition is commutative
|
|
||||||
fn testAddCommutative(n: Int, count: Int): Bool with {Console, Random} = {
|
fn testAddCommutative(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
if n <= 0 then {
|
if n <= 0 then {
|
||||||
printResult("a + b == b + a", true, count)
|
printResult("a + b == b + a", true, count)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
let a = genInt(-1000, 1000)
|
let a = genInt(-1000, 1000)
|
||||||
let b = genInt(-1000, 1000)
|
let b = genInt(-1000, 1000)
|
||||||
if a + b == b + a then
|
if a + b == b + a then testAddCommutative(n - 1, count) else {
|
||||||
testAddCommutative(n - 1, count)
|
printResult("a + b == b + a", false, count - n + 1)
|
||||||
else {
|
false
|
||||||
printResult("a + b == b + a", false, count - n + 1)
|
}
|
||||||
false
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test: Multiplication is associative
|
|
||||||
fn testMulAssociative(n: Int, count: Int): Bool with {Console, Random} = {
|
fn testMulAssociative(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
if n <= 0 then {
|
if n <= 0 then {
|
||||||
printResult("(a * b) * c == a * (b * c)", true, count)
|
printResult("(a * b) * c == a * (b * c)", true, count)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
let a = genInt(-100, 100)
|
let a = genInt(-100, 100)
|
||||||
let b = genInt(-100, 100)
|
let b = genInt(-100, 100)
|
||||||
let c = genInt(-100, 100)
|
let c = genInt(-100, 100)
|
||||||
if (a * b) * c == a * (b * c) then
|
if a * b * c == a * b * c then testMulAssociative(n - 1, count) else {
|
||||||
testMulAssociative(n - 1, count)
|
printResult("(a * b) * c == a * (b * c)", false, count - n + 1)
|
||||||
else {
|
false
|
||||||
printResult("(a * b) * c == a * (b * c)", false, count - n + 1)
|
}
|
||||||
false
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test: String concat length is sum
|
|
||||||
fn testStringConcatLength(n: Int, count: Int): Bool with {Console, Random} = {
|
fn testStringConcatLength(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
if n <= 0 then {
|
if n <= 0 then {
|
||||||
printResult("length(s1 + s2) == length(s1) + length(s2)", true, count)
|
printResult("length(s1 + s2) == length(s1) + length(s2)", true, count)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
let s1 = genString(10)
|
let s1 = genString(10)
|
||||||
let s2 = genString(10)
|
let s2 = genString(10)
|
||||||
if String.length(s1 + s2) == String.length(s1) + String.length(s2) then
|
if String.length(s1 + s2) == String.length(s1) + String.length(s2) then testStringConcatLength(n - 1, count) else {
|
||||||
testStringConcatLength(n - 1, count)
|
printResult("length(s1 + s2) == length(s1) + length(s2)", false, count - n + 1)
|
||||||
else {
|
false
|
||||||
printResult("length(s1 + s2) == length(s1) + length(s2)", false, count - n + 1)
|
}
|
||||||
false
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test: Zero is identity for addition
|
|
||||||
fn testAddIdentity(n: Int, count: Int): Bool with {Console, Random} = {
|
fn testAddIdentity(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
if n <= 0 then {
|
if n <= 0 then {
|
||||||
printResult("x + 0 == x && 0 + x == x", true, count)
|
printResult("x + 0 == x && 0 + x == x", true, count)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
let x = genInt(-10000, 10000)
|
let x = genInt(-10000, 10000)
|
||||||
if x + 0 == x && 0 + x == x then
|
if x + 0 == x && 0 + x == x then testAddIdentity(n - 1, count) else {
|
||||||
testAddIdentity(n - 1, count)
|
printResult("x + 0 == x && 0 + x == x", false, count - n + 1)
|
||||||
else {
|
false
|
||||||
printResult("x + 0 == x && 0 + x == x", false, count - n + 1)
|
}
|
||||||
false
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test: Filter reduces or maintains length
|
|
||||||
fn testFilterLength(n: Int, count: Int): Bool with {Console, Random} = {
|
fn testFilterLength(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
if n <= 0 then {
|
if n <= 0 then {
|
||||||
printResult("length(filter(xs, p)) <= length(xs)", true, count)
|
printResult("length(filter(xs, p)) <= length(xs)", true, count)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
let xs = genIntList(0, 100, 20)
|
let xs = genIntList(0, 100, 20)
|
||||||
if List.length(List.filter(xs, fn(x) => x > 50)) <= List.length(xs) then
|
if List.length(List.filter(xs, fn(x: _) => x > 50)) <= List.length(xs) then testFilterLength(n - 1, count) else {
|
||||||
testFilterLength(n - 1, count)
|
printResult("length(filter(xs, p)) <= length(xs)", false, count - n + 1)
|
||||||
else {
|
false
|
||||||
printResult("length(filter(xs, p)) <= length(xs)", false, count - n + 1)
|
}
|
||||||
false
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test: Empty list is identity for concat
|
|
||||||
fn testConcatIdentity(n: Int, count: Int): Bool with {Console, Random} = {
|
fn testConcatIdentity(n: Int, count: Int): Bool with {Console, Random} = {
|
||||||
if n <= 0 then {
|
if n <= 0 then {
|
||||||
printResult("concat(xs, []) == xs && concat([], xs) == xs", true, count)
|
printResult("concat(xs, []) == xs && concat([], xs) == xs", true, count)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
let xs = genIntList(0, 100, 10)
|
let xs = genIntList(0, 100, 10)
|
||||||
if List.concat(xs, []) == xs && List.concat([], xs) == xs then
|
if List.concat(xs, []) == xs && List.concat([], xs) == xs then testConcatIdentity(n - 1, count) else {
|
||||||
testConcatIdentity(n - 1, count)
|
printResult("concat(xs, []) == xs && concat([], xs) == xs", false, count - n + 1)
|
||||||
else {
|
false
|
||||||
printResult("concat(xs, []) == xs && concat([], xs) == xs", false, count - n + 1)
|
}
|
||||||
false
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Main
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
fn main(): Unit with {Console, Random} = {
|
fn main(): Unit with {Console, Random} = {
|
||||||
Console.print("========================================")
|
Console.print("========================================")
|
||||||
@@ -234,7 +171,6 @@ fn main(): Unit with {Console, Random} = {
|
|||||||
Console.print("")
|
Console.print("")
|
||||||
Console.print("Running 100 iterations per property...")
|
Console.print("Running 100 iterations per property...")
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
testReverseInvolutive(100, 100)
|
testReverseInvolutive(100, 100)
|
||||||
testReverseLength(100, 100)
|
testReverseLength(100, 100)
|
||||||
testMapLength(100, 100)
|
testMapLength(100, 100)
|
||||||
@@ -245,7 +181,6 @@ fn main(): Unit with {Console, Random} = {
|
|||||||
testAddIdentity(100, 100)
|
testAddIdentity(100, 100)
|
||||||
testFilterLength(100, 100)
|
testFilterLength(100, 100)
|
||||||
testConcatIdentity(100, 100)
|
testConcatIdentity(100, 100)
|
||||||
|
|
||||||
Console.print("")
|
Console.print("")
|
||||||
Console.print("========================================")
|
Console.print("========================================")
|
||||||
Console.print(" All property tests completed!")
|
Console.print(" All property tests completed!")
|
||||||
|
|||||||
@@ -1,39 +1,22 @@
|
|||||||
// Demonstrating Random and Time effects in Lux
|
|
||||||
//
|
|
||||||
// Expected output (values will vary):
|
|
||||||
// Rolling dice...
|
|
||||||
// Die 1: <random 1-6>
|
|
||||||
// Die 2: <random 1-6>
|
|
||||||
// Die 3: <random 1-6>
|
|
||||||
// Coin flip: <true/false>
|
|
||||||
// Random float: <0.0-1.0>
|
|
||||||
// Current time: <timestamp>
|
|
||||||
|
|
||||||
// Roll a single die (1-6)
|
|
||||||
fn rollDie(): Int with {Random} = Random.int(1, 6)
|
fn rollDie(): Int with {Random} = Random.int(1, 6)
|
||||||
|
|
||||||
// Roll multiple dice and print results
|
|
||||||
fn rollDice(count: Int): Unit with {Random, Console} = {
|
fn rollDice(count: Int): Unit with {Random, Console} = {
|
||||||
if count > 0 then {
|
if count > 0 then {
|
||||||
let value = rollDie()
|
let value = rollDie()
|
||||||
Console.print("Die " + toString(4 - count) + ": " + toString(value))
|
Console.print("Die " + toString(4 - count) + ": " + toString(value))
|
||||||
rollDice(count - 1)
|
rollDice(count - 1)
|
||||||
} else {
|
} else {
|
||||||
()
|
()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main function demonstrating random effects
|
|
||||||
fn main(): Unit with {Random, Console, Time} = {
|
fn main(): Unit with {Random, Console, Time} = {
|
||||||
Console.print("Rolling dice...")
|
Console.print("Rolling dice...")
|
||||||
rollDice(3)
|
rollDice(3)
|
||||||
|
|
||||||
let coin = Random.bool()
|
let coin = Random.bool()
|
||||||
Console.print("Coin flip: " + toString(coin))
|
Console.print("Coin flip: " + toString(coin))
|
||||||
|
|
||||||
let f = Random.float()
|
let f = Random.float()
|
||||||
Console.print("Random float: " + toString(f))
|
Console.print("Random float: " + toString(f))
|
||||||
|
|
||||||
let now = Time.now()
|
let now = Time.now()
|
||||||
Console.print("Current time: " + toString(now))
|
Console.print("Current time: " + toString(now))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +1,41 @@
|
|||||||
// Schema Evolution Demo
|
type User = {
|
||||||
// Demonstrates version tracking and automatic migrations
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// PART 1: Type-Declared Migrations
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
// Define a versioned type with a migration from v1 to v2
|
|
||||||
type User @v2 {
|
|
||||||
name: String,
|
name: String,
|
||||||
email: String,
|
email: String,
|
||||||
|
|
||||||
// Migration from v1: add default email
|
|
||||||
from @v1 = { name: old.name, email: "unknown@example.com" }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a v1 user
|
|
||||||
let v1_user = Schema.versioned("User", 1, { name: "Alice" })
|
let v1_user = Schema.versioned("User", 1, { name: "Alice" })
|
||||||
let v1_version = Schema.getVersion(v1_user) // 1
|
|
||||||
|
|
||||||
// Migrate to v2 - uses the declared migration automatically
|
let v1_version = Schema.getVersion(v1_user)
|
||||||
|
|
||||||
let v2_user = Schema.migrate(v1_user, 2)
|
let v2_user = Schema.migrate(v1_user, 2)
|
||||||
let v2_version = Schema.getVersion(v2_user) // 2
|
|
||||||
|
|
||||||
// ============================================================
|
let v2_version = Schema.getVersion(v2_user)
|
||||||
// PART 2: Runtime Schema Operations (separate type)
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
// Create versioned values for a different type (no migration)
|
|
||||||
let config1 = Schema.versioned("Config", 1, "debug")
|
let config1 = Schema.versioned("Config", 1, "debug")
|
||||||
|
|
||||||
let config2 = Schema.versioned("Config", 2, "release")
|
let config2 = Schema.versioned("Config", 2, "release")
|
||||||
|
|
||||||
// Check versions
|
let c1 = Schema.getVersion(config1)
|
||||||
let c1 = Schema.getVersion(config1) // 1
|
|
||||||
let c2 = Schema.getVersion(config2) // 2
|
let c2 = Schema.getVersion(config2)
|
||||||
|
|
||||||
// Migrate config (auto-migration since no explicit migration defined)
|
|
||||||
let upgradedConfig = Schema.migrate(config1, 2)
|
let upgradedConfig = Schema.migrate(config1, 2)
|
||||||
let upgradedConfigVersion = Schema.getVersion(upgradedConfig) // 2
|
|
||||||
|
|
||||||
// ============================================================
|
let upgradedConfigVersion = Schema.getVersion(upgradedConfig)
|
||||||
// PART 2: Practical Example - API Versioning
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
// Simulate different API response versions
|
fn createResponseV1(data: String): { version: Int, payload: String } = { version: 1, payload: data }
|
||||||
fn createResponseV1(data: String): { version: Int, payload: String } =
|
|
||||||
{ version: 1, payload: data }
|
|
||||||
|
|
||||||
fn createResponseV2(data: String, timestamp: Int): { version: Int, payload: String, meta: { ts: Int } } =
|
fn createResponseV2(data: String, timestamp: Int): { version: Int, payload: String, meta: { ts: Int } } = { version: 2, payload: data, meta: { ts: timestamp } }
|
||||||
{ version: 2, payload: data, meta: { ts: timestamp } }
|
|
||||||
|
|
||||||
// Version-aware processing
|
fn getPayload(response: { version: Int, payload: String }): String = response.payload
|
||||||
fn getPayload(response: { version: Int, payload: String }): String =
|
|
||||||
response.payload
|
|
||||||
|
|
||||||
let resp1 = createResponseV1("Hello")
|
let resp1 = createResponseV1("Hello")
|
||||||
|
|
||||||
let resp2 = createResponseV2("World", 1234567890)
|
let resp2 = createResponseV2("World", 1234567890)
|
||||||
|
|
||||||
let payload1 = getPayload(resp1)
|
let payload1 = getPayload(resp1)
|
||||||
let payload2 = resp2.payload
|
|
||||||
|
|
||||||
// ============================================================
|
let payload2 = resp2.payload
|
||||||
// RESULTS
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
fn main(): Unit with {Console} = {
|
fn main(): Unit with {Console} = {
|
||||||
Console.print("=== Schema Evolution Demo ===")
|
Console.print("=== Schema Evolution Demo ===")
|
||||||
|
|||||||
@@ -1,58 +1,43 @@
|
|||||||
// Shell/Process example - demonstrates the Process effect
|
|
||||||
//
|
|
||||||
// This script runs shell commands and uses environment variables
|
|
||||||
|
|
||||||
fn main(): Unit with {Process, Console} = {
|
fn main(): Unit with {Process, Console} = {
|
||||||
Console.print("=== Lux Shell Example ===")
|
Console.print("=== Lux Shell Example ===")
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Get current working directory
|
|
||||||
let cwd = Process.cwd()
|
let cwd = Process.cwd()
|
||||||
Console.print("Current directory: " + cwd)
|
Console.print("Current directory: " + cwd)
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Get environment variables
|
|
||||||
Console.print("Environment variables:")
|
Console.print("Environment variables:")
|
||||||
match Process.env("USER") {
|
match Process.env("USER") {
|
||||||
Some(user) => Console.print(" USER: " + user),
|
Some(user) => Console.print(" USER: " + user),
|
||||||
None => Console.print(" USER: (not set)")
|
None => Console.print(" USER: (not set)"),
|
||||||
}
|
}
|
||||||
match Process.env("HOME") {
|
match Process.env("HOME") {
|
||||||
Some(home) => Console.print(" HOME: " + home),
|
Some(home) => Console.print(" HOME: " + home),
|
||||||
None => Console.print(" HOME: (not set)")
|
None => Console.print(" HOME: (not set)"),
|
||||||
}
|
}
|
||||||
match Process.env("SHELL") {
|
match Process.env("SHELL") {
|
||||||
Some(shell) => Console.print(" SHELL: " + shell),
|
Some(shell) => Console.print(" SHELL: " + shell),
|
||||||
None => Console.print(" SHELL: (not set)")
|
None => Console.print(" SHELL: (not set)"),
|
||||||
}
|
}
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Run shell commands
|
|
||||||
Console.print("Running shell commands:")
|
Console.print("Running shell commands:")
|
||||||
|
|
||||||
let date = Process.exec("date")
|
let date = Process.exec("date")
|
||||||
Console.print(" date: " + String.trim(date))
|
Console.print(" date: " + String.trim(date))
|
||||||
|
|
||||||
let kernel = Process.exec("uname -r")
|
let kernel = Process.exec("uname -r")
|
||||||
Console.print(" kernel: " + String.trim(kernel))
|
Console.print(" kernel: " + String.trim(kernel))
|
||||||
|
|
||||||
let files = Process.exec("ls examples/*.lux | wc -l")
|
let files = Process.exec("ls examples/*.lux | wc -l")
|
||||||
Console.print(" .lux files in examples/: " + String.trim(files))
|
Console.print(" .lux files in examples/: " + String.trim(files))
|
||||||
Console.print("")
|
Console.print("")
|
||||||
|
|
||||||
// Command line arguments
|
|
||||||
Console.print("Command line arguments:")
|
Console.print("Command line arguments:")
|
||||||
let args = Process.args()
|
let args = Process.args()
|
||||||
let argCount = List.length(args)
|
let argCount = List.length(args)
|
||||||
if argCount == 0 then {
|
if argCount == 0 then {
|
||||||
Console.print(" (no arguments)")
|
Console.print(" (no arguments)")
|
||||||
} else {
|
} else {
|
||||||
Console.print(" Count: " + toString(argCount))
|
Console.print(" Count: " + toString(argCount))
|
||||||
match List.head(args) {
|
match List.head(args) {
|
||||||
Some(first) => Console.print(" First: " + first),
|
Some(first) => Console.print(" First: " + first),
|
||||||
None => Console.print(" First: (empty)")
|
None => Console.print(" First: (empty)"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = run main() with {}
|
let result = run main() with {}
|
||||||
|
|||||||
@@ -1,15 +1,3 @@
|
|||||||
// The "Ask" Pattern - Resumable Effects
|
|
||||||
//
|
|
||||||
// Unlike exceptions which unwind the stack, effect handlers can
|
|
||||||
// RESUME with a value. This enables "ask the environment" patterns.
|
|
||||||
//
|
|
||||||
// Expected output:
|
|
||||||
// Need config: api_url
|
|
||||||
// Got: https://api.example.com
|
|
||||||
// Need config: timeout
|
|
||||||
// Got: 30
|
|
||||||
// Configured with url=https://api.example.com, timeout=30
|
|
||||||
|
|
||||||
effect Config {
|
effect Config {
|
||||||
fn get(key: String): String
|
fn get(key: String): String
|
||||||
}
|
}
|
||||||
@@ -25,14 +13,13 @@ fn configure(): String with {Config, Console} = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handler envConfig: Config {
|
handler envConfig: Config {
|
||||||
fn get(key) =
|
fn get(key) = if key == "api_url" then resume("https://api.example.com") else if key == "timeout" then resume("30") else resume("unknown")
|
||||||
if key == "api_url" then resume("https://api.example.com")
|
|
||||||
else if key == "timeout" then resume("30")
|
|
||||||
else resume("unknown")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main(): Unit with {Console} = {
|
fn main(): Unit with {Console} = {
|
||||||
let result = run configure() with { Config = envConfig }
|
let result = run configure() with {
|
||||||
|
Config = envConfig,
|
||||||
|
}
|
||||||
Console.print(result)
|
Console.print(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,3 @@
|
|||||||
// Custom Logging with Effects
|
|
||||||
//
|
|
||||||
// This demonstrates how effects let you abstract side effects.
|
|
||||||
// The same code can be run with different logging implementations.
|
|
||||||
//
|
|
||||||
// Expected output:
|
|
||||||
// [INFO] Starting computation
|
|
||||||
// [DEBUG] x = 10
|
|
||||||
// [INFO] Processing
|
|
||||||
// [DEBUG] result = 20
|
|
||||||
// Final: 20
|
|
||||||
|
|
||||||
effect Log {
|
effect Log {
|
||||||
fn info(msg: String): Unit
|
fn info(msg: String): Unit
|
||||||
fn debug(msg: String): Unit
|
fn debug(msg: String): Unit
|
||||||
@@ -26,18 +14,22 @@ fn computation(): Int with {Log} = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handler consoleLogger: Log {
|
handler consoleLogger: Log {
|
||||||
fn info(msg) = {
|
fn info(msg) =
|
||||||
Console.print("[INFO] " + msg)
|
{
|
||||||
resume(())
|
Console.print("[INFO] " + msg)
|
||||||
}
|
resume(())
|
||||||
fn debug(msg) = {
|
}
|
||||||
Console.print("[DEBUG] " + msg)
|
fn debug(msg) =
|
||||||
resume(())
|
{
|
||||||
}
|
Console.print("[DEBUG] " + msg)
|
||||||
|
resume(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main(): Unit with {Console} = {
|
fn main(): Unit with {Console} = {
|
||||||
let result = run computation() with { Log = consoleLogger }
|
let result = run computation() with {
|
||||||
|
Log = consoleLogger,
|
||||||
|
}
|
||||||
Console.print("Final: " + toString(result))
|
Console.print("Final: " + toString(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +1,18 @@
|
|||||||
// Early Return with Fail Effect
|
|
||||||
//
|
|
||||||
// The Fail effect provides clean early termination.
|
|
||||||
// Functions declare their failure modes in the type signature.
|
|
||||||
//
|
|
||||||
// Expected output:
|
|
||||||
// Parsing "42"...
|
|
||||||
// Result: 42
|
|
||||||
// Parsing "100"...
|
|
||||||
// Result: 100
|
|
||||||
// Dividing 100 by 4...
|
|
||||||
// Result: 25
|
|
||||||
|
|
||||||
fn parsePositive(s: String): Int with {Fail, Console} = {
|
fn parsePositive(s: String): Int with {Fail, Console} = {
|
||||||
Console.print("Parsing \"" + s + "\"...")
|
Console.print("Parsing \"" + s + "\"...")
|
||||||
if s == "42" then 42
|
if s == "42" then 42 else if s == "100" then 100 else Fail.fail("Invalid number: " + s)
|
||||||
else if s == "100" then 100
|
|
||||||
else Fail.fail("Invalid number: " + s)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn safeDivide(a: Int, b: Int): Int with {Fail, Console} = {
|
fn safeDivide(a: Int, b: Int): Int with {Fail, Console} = {
|
||||||
Console.print("Dividing " + toString(a) + " by " + toString(b) + "...")
|
Console.print("Dividing " + toString(a) + " by " + toString(b) + "...")
|
||||||
if b == 0 then Fail.fail("Division by zero")
|
if b == 0 then Fail.fail("Division by zero") else a / b
|
||||||
else a / b
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main(): Unit with {Console} = {
|
fn main(): Unit with {Console} = {
|
||||||
// These succeed
|
|
||||||
let n1 = run parsePositive("42") with {}
|
let n1 = run parsePositive("42") with {}
|
||||||
Console.print("Result: " + toString(n1))
|
Console.print("Result: " + toString(n1))
|
||||||
|
|
||||||
let n2 = run parsePositive("100") with {}
|
let n2 = run parsePositive("100") with {}
|
||||||
Console.print("Result: " + toString(n2))
|
Console.print("Result: " + toString(n2))
|
||||||
|
|
||||||
let n3 = run safeDivide(100, 4) with {}
|
let n3 = run safeDivide(100, 4) with {}
|
||||||
Console.print("Result: " + toString(n3))
|
Console.print("Result: " + toString(n3))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,3 @@
|
|||||||
// Effect Composition - Combine multiple effects cleanly
|
|
||||||
//
|
|
||||||
// Unlike monad transformers (which have ordering issues),
|
|
||||||
// effects can be freely combined without boilerplate.
|
|
||||||
// Each handler handles its own effect, ignoring others.
|
|
||||||
//
|
|
||||||
// Expected output:
|
|
||||||
// [LOG] Starting computation
|
|
||||||
// Generated: 7
|
|
||||||
// [LOG] Processing value
|
|
||||||
// [LOG] Done
|
|
||||||
// Result: 14
|
|
||||||
|
|
||||||
effect Log {
|
effect Log {
|
||||||
fn log(msg: String): Unit
|
fn log(msg: String): Unit
|
||||||
}
|
}
|
||||||
@@ -30,8 +17,8 @@ handler consoleLog: Log {
|
|||||||
|
|
||||||
fn main(): Unit with {Console} = {
|
fn main(): Unit with {Console} = {
|
||||||
let result = run computation() with {
|
let result = run computation() with {
|
||||||
Log = consoleLog
|
Log = consoleLog,
|
||||||
}
|
}
|
||||||
Console.print("Generated: " + toString(result / 2))
|
Console.print("Generated: " + toString(result / 2))
|
||||||
Console.print("Result: " + toString(result))
|
Console.print("Result: " + toString(result))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,19 @@
|
|||||||
// Higher-Order Functions and Closures
|
|
||||||
//
|
|
||||||
// Functions are first-class values in Lux.
|
|
||||||
// Closures capture their environment.
|
|
||||||
//
|
|
||||||
// Expected output:
|
|
||||||
// Square of 5: 25
|
|
||||||
// Cube of 3: 27
|
|
||||||
// Add 10 to 5: 15
|
|
||||||
// Add 10 to 20: 30
|
|
||||||
// Composed: 15625 (cube(square(5)) = cube(25) = 15625)
|
|
||||||
|
|
||||||
fn apply(f: fn(Int): Int, x: Int): Int = f(x)
|
fn apply(f: fn(Int): Int, x: Int): Int = f(x)
|
||||||
|
|
||||||
fn compose(f: fn(Int): Int, g: fn(Int): Int): fn(Int): Int =
|
fn compose(f: fn(Int): Int, g: fn(Int): Int): fn(Int): Int = fn(x: Int): Int => f(g(x))
|
||||||
fn(x: Int): Int => f(g(x))
|
|
||||||
|
|
||||||
fn square(n: Int): Int = n * n
|
fn square(n: Int): Int = n * n
|
||||||
|
|
||||||
fn cube(n: Int): Int = n * n * n
|
fn cube(n: Int): Int = n * n * n
|
||||||
|
|
||||||
fn makeAdder(n: Int): fn(Int): Int =
|
fn makeAdder(n: Int): fn(Int): Int = fn(x: Int): Int => x + n
|
||||||
fn(x: Int): Int => x + n
|
|
||||||
|
|
||||||
fn main(): Unit with {Console} = {
|
fn main(): Unit with {Console} = {
|
||||||
// Apply functions
|
|
||||||
Console.print("Square of 5: " + toString(apply(square, 5)))
|
Console.print("Square of 5: " + toString(apply(square, 5)))
|
||||||
Console.print("Cube of 3: " + toString(apply(cube, 3)))
|
Console.print("Cube of 3: " + toString(apply(cube, 3)))
|
||||||
|
|
||||||
// Closures
|
|
||||||
let add10 = makeAdder(10)
|
let add10 = makeAdder(10)
|
||||||
Console.print("Add 10 to 5: " + toString(add10(5)))
|
Console.print("Add 10 to 5: " + toString(add10(5)))
|
||||||
Console.print("Add 10 to 20: " + toString(add10(20)))
|
Console.print("Add 10 to 20: " + toString(add10(20)))
|
||||||
|
|
||||||
// Function composition
|
|
||||||
let squareThenCube = compose(cube, square)
|
let squareThenCube = compose(cube, square)
|
||||||
Console.print("Composed: " + toString(squareThenCube(5)))
|
Console.print("Composed: " + toString(squareThenCube(5)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,3 @@
|
|||||||
// Algebraic Data Types and Pattern Matching
|
|
||||||
//
|
|
||||||
// Lux has powerful ADTs with exhaustive pattern matching.
|
|
||||||
// The type system ensures all cases are handled.
|
|
||||||
//
|
|
||||||
// Expected output:
|
|
||||||
// Evaluating: (2 + 3)
|
|
||||||
// Result: 5
|
|
||||||
// Evaluating: ((1 + 2) * (3 + 4))
|
|
||||||
// Result: 21
|
|
||||||
// Evaluating: (10 - (2 * 3))
|
|
||||||
// Result: 4
|
|
||||||
|
|
||||||
type Expr =
|
type Expr =
|
||||||
| Num(Int)
|
| Num(Int)
|
||||||
| Add(Expr, Expr)
|
| Add(Expr, Expr)
|
||||||
@@ -19,19 +6,19 @@ type Expr =
|
|||||||
|
|
||||||
fn eval(e: Expr): Int =
|
fn eval(e: Expr): Int =
|
||||||
match e {
|
match e {
|
||||||
Num(n) => n,
|
Num(n) => n,
|
||||||
Add(a, b) => eval(a) + eval(b),
|
Add(a, b) => eval(a) + eval(b),
|
||||||
Sub(a, b) => eval(a) - eval(b),
|
Sub(a, b) => eval(a) - eval(b),
|
||||||
Mul(a, b) => eval(a) * eval(b)
|
Mul(a, b) => eval(a) * eval(b),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn showExpr(e: Expr): String =
|
fn showExpr(e: Expr): String =
|
||||||
match e {
|
match e {
|
||||||
Num(n) => toString(n),
|
Num(n) => toString(n),
|
||||||
Add(a, b) => "(" + showExpr(a) + " + " + showExpr(b) + ")",
|
Add(a, b) => "(" + showExpr(a) + " + " + showExpr(b) + ")",
|
||||||
Sub(a, b) => "(" + showExpr(a) + " - " + showExpr(b) + ")",
|
Sub(a, b) => "(" + showExpr(a) + " - " + showExpr(b) + ")",
|
||||||
Mul(a, b) => "(" + showExpr(a) + " * " + showExpr(b) + ")"
|
Mul(a, b) => "(" + showExpr(a) + " * " + showExpr(b) + ")",
|
||||||
}
|
}
|
||||||
|
|
||||||
fn evalAndPrint(e: Expr): Unit with {Console} = {
|
fn evalAndPrint(e: Expr): Unit with {Console} = {
|
||||||
Console.print("Evaluating: " + showExpr(e))
|
Console.print("Evaluating: " + showExpr(e))
|
||||||
@@ -39,15 +26,10 @@ fn evalAndPrint(e: Expr): Unit with {Console} = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main(): Unit with {Console} = {
|
fn main(): Unit with {Console} = {
|
||||||
// (2 + 3)
|
|
||||||
let e1 = Add(Num(2), Num(3))
|
let e1 = Add(Num(2), Num(3))
|
||||||
evalAndPrint(e1)
|
evalAndPrint(e1)
|
||||||
|
|
||||||
// ((1 + 2) * (3 + 4))
|
|
||||||
let e2 = Mul(Add(Num(1), Num(2)), Add(Num(3), Num(4)))
|
let e2 = Mul(Add(Num(1), Num(2)), Add(Num(3), Num(4)))
|
||||||
evalAndPrint(e2)
|
evalAndPrint(e2)
|
||||||
|
|
||||||
// (10 - (2 * 3))
|
|
||||||
let e3 = Sub(Num(10), Mul(Num(2), Num(3)))
|
let e3 = Sub(Num(10), Mul(Num(2), Num(3)))
|
||||||
evalAndPrint(e3)
|
evalAndPrint(e3)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
// Factorial - compute n!
|
fn factorial(n: Int): Int = if n <= 1 then 1 else n * factorial(n - 1)
|
||||||
|
|
||||||
// Recursive version
|
fn factorialTail(n: Int, acc: Int): Int = if n <= 1 then acc else factorialTail(n - 1, n * acc)
|
||||||
fn factorial(n: Int): Int =
|
|
||||||
if n <= 1 then 1
|
|
||||||
else n * factorial(n - 1)
|
|
||||||
|
|
||||||
// Tail-recursive version (optimized)
|
|
||||||
fn factorialTail(n: Int, acc: Int): Int =
|
|
||||||
if n <= 1 then acc
|
|
||||||
else factorialTail(n - 1, n * acc)
|
|
||||||
|
|
||||||
fn factorial2(n: Int): Int = factorialTail(n, 1)
|
fn factorial2(n: Int): Int = factorialTail(n, 1)
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,11 @@
|
|||||||
// FizzBuzz - print numbers 1-100, but:
|
fn fizzbuzz(n: Int): String = if n % 15 == 0 then "FizzBuzz" else if n % 3 == 0 then "Fizz" else if n % 5 == 0 then "Buzz" else toString(n)
|
||||||
// - multiples of 3: print "Fizz"
|
|
||||||
// - multiples of 5: print "Buzz"
|
|
||||||
// - multiples of both: print "FizzBuzz"
|
|
||||||
|
|
||||||
fn fizzbuzz(n: Int): String =
|
|
||||||
if n % 15 == 0 then "FizzBuzz"
|
|
||||||
else if n % 3 == 0 then "Fizz"
|
|
||||||
else if n % 5 == 0 then "Buzz"
|
|
||||||
else toString(n)
|
|
||||||
|
|
||||||
fn printFizzbuzz(i: Int, max: Int): Unit with {Console} =
|
fn printFizzbuzz(i: Int, max: Int): Unit with {Console} =
|
||||||
if i > max then ()
|
if i > max then () else {
|
||||||
else {
|
Console.print(fizzbuzz(i))
|
||||||
Console.print(fizzbuzz(i))
|
printFizzbuzz(i + 1, max)
|
||||||
printFizzbuzz(i + 1, max)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn main(): Unit with {Console} =
|
fn main(): Unit with {Console} = printFizzbuzz(1, 100)
|
||||||
printFizzbuzz(1, 100)
|
|
||||||
|
|
||||||
let output = run main() with {}
|
let output = run main() with {}
|
||||||
|
|||||||
@@ -1,42 +1,17 @@
|
|||||||
// Number guessing game - demonstrates Random and Console effects
|
fn checkGuess(guess: Int, secret: Int): String = if guess == secret then "Correct" else if guess < secret then "Too low" else "Too high"
|
||||||
//
|
|
||||||
// Expected output:
|
|
||||||
// Welcome to the Guessing Game!
|
|
||||||
// Target number: 42
|
|
||||||
// Simulating guesses...
|
|
||||||
// Guess 50: Too high!
|
|
||||||
// Guess 25: Too low!
|
|
||||||
// Guess 37: Too low!
|
|
||||||
// Guess 43: Too high!
|
|
||||||
// Guess 40: Too low!
|
|
||||||
// Guess 41: Too low!
|
|
||||||
// Guess 42: Correct!
|
|
||||||
// Found in 7 attempts!
|
|
||||||
|
|
||||||
// Game logic - check a guess against the secret
|
|
||||||
fn checkGuess(guess: Int, secret: Int): String =
|
|
||||||
if guess == secret then "Correct"
|
|
||||||
else if guess < secret then "Too low"
|
|
||||||
else "Too high"
|
|
||||||
|
|
||||||
// Binary search simulation to find the number
|
|
||||||
fn binarySearch(low: Int, high: Int, secret: Int, attempts: Int): Int with {Console} = {
|
fn binarySearch(low: Int, high: Int, secret: Int, attempts: Int): Int with {Console} = {
|
||||||
let mid = (low + high) / 2
|
let mid = low + high / 2
|
||||||
let result = checkGuess(mid, secret)
|
let result = checkGuess(mid, secret)
|
||||||
Console.print("Guess " + toString(mid) + ": " + result + "!")
|
Console.print("Guess " + toString(mid) + ": " + result + "!")
|
||||||
|
if result == "Correct" then attempts else if result == "Too low" then binarySearch(mid + 1, high, secret, attempts + 1) else binarySearch(low, mid - 1, secret, attempts + 1)
|
||||||
if result == "Correct" then attempts
|
|
||||||
else if result == "Too low" then binarySearch(mid + 1, high, secret, attempts + 1)
|
|
||||||
else binarySearch(low, mid - 1, secret, attempts + 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main(): Unit with {Console} = {
|
fn main(): Unit with {Console} = {
|
||||||
Console.print("Welcome to the Guessing Game!")
|
Console.print("Welcome to the Guessing Game!")
|
||||||
// Use a fixed "secret" for reproducible output
|
|
||||||
let secret = 42
|
let secret = 42
|
||||||
Console.print("Target number: " + toString(secret))
|
Console.print("Target number: " + toString(secret))
|
||||||
Console.print("Simulating guesses...")
|
Console.print("Simulating guesses...")
|
||||||
|
|
||||||
let attempts = binarySearch(1, 100, secret, 1)
|
let attempts = binarySearch(1, 100, secret, 1)
|
||||||
Console.print("Found in " + toString(attempts) + " attempts!")
|
Console.print("Found in " + toString(attempts) + " attempts!")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
// The classic first program
|
fn main(): Unit with {Console} = Console.print("Hello, World!")
|
||||||
// Expected output: Hello, World!
|
|
||||||
|
|
||||||
fn main(): Unit with {Console} =
|
|
||||||
Console.print("Hello, World!")
|
|
||||||
|
|
||||||
let output = run main() with {}
|
let output = run main() with {}
|
||||||
|
|||||||
@@ -1,25 +1,14 @@
|
|||||||
// Prime number utilities
|
fn isPrime(n: Int): Bool = if n < 2 then false else isPrimeHelper(n, 2)
|
||||||
|
|
||||||
fn isPrime(n: Int): Bool =
|
fn isPrimeHelper(n: Int, i: Int): Bool = if i * i > n then true else if n % i == 0 then false else isPrimeHelper(n, i + 1)
|
||||||
if n < 2 then false
|
|
||||||
else isPrimeHelper(n, 2)
|
|
||||||
|
|
||||||
fn isPrimeHelper(n: Int, i: Int): Bool =
|
fn findPrimes(count: Int): Unit with {Console} = findPrimesHelper(2, count)
|
||||||
if i * i > n then true
|
|
||||||
else if n % i == 0 then false
|
|
||||||
else isPrimeHelper(n, i + 1)
|
|
||||||
|
|
||||||
// Find first n primes
|
|
||||||
fn findPrimes(count: Int): Unit with {Console} =
|
|
||||||
findPrimesHelper(2, count)
|
|
||||||
|
|
||||||
fn findPrimesHelper(current: Int, remaining: Int): Unit with {Console} =
|
fn findPrimesHelper(current: Int, remaining: Int): Unit with {Console} =
|
||||||
if remaining <= 0 then ()
|
if remaining <= 0 then () else if isPrime(current) then {
|
||||||
else if isPrime(current) then {
|
Console.print(toString(current))
|
||||||
Console.print(toString(current))
|
findPrimesHelper(current + 1, remaining - 1)
|
||||||
findPrimesHelper(current + 1, remaining - 1)
|
} else findPrimesHelper(current + 1, remaining)
|
||||||
}
|
|
||||||
else findPrimesHelper(current + 1, remaining)
|
|
||||||
|
|
||||||
fn main(): Unit with {Console} = {
|
fn main(): Unit with {Console} = {
|
||||||
Console.print("First 20 prime numbers:")
|
Console.print("First 20 prime numbers:")
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
// Standard Library Demo
|
|
||||||
// Demonstrates the built-in modules: List, String, Option, Math
|
|
||||||
|
|
||||||
fn main(): Unit with {Console} = {
|
fn main(): Unit with {Console} = {
|
||||||
Console.print("=== List Operations ===")
|
Console.print("=== List Operations ===")
|
||||||
let nums = [1, 2, 3, 4, 5]
|
let nums = [1, 2, 3, 4, 5]
|
||||||
@@ -11,7 +8,6 @@ fn main(): Unit with {Console} = {
|
|||||||
Console.print("Length: " + toString(List.length(nums)))
|
Console.print("Length: " + toString(List.length(nums)))
|
||||||
Console.print("Reversed: " + toString(List.reverse(nums)))
|
Console.print("Reversed: " + toString(List.reverse(nums)))
|
||||||
Console.print("Range 1-5: " + toString(List.range(1, 6)))
|
Console.print("Range 1-5: " + toString(List.range(1, 6)))
|
||||||
|
|
||||||
Console.print("")
|
Console.print("")
|
||||||
Console.print("=== String Operations ===")
|
Console.print("=== String Operations ===")
|
||||||
let text = " Hello, World! "
|
let text = " Hello, World! "
|
||||||
@@ -22,7 +18,6 @@ fn main(): Unit with {Console} = {
|
|||||||
Console.print("Contains 'World': " + toString(String.contains(text, "World")))
|
Console.print("Contains 'World': " + toString(String.contains(text, "World")))
|
||||||
Console.print("Split by comma: " + toString(String.split("a,b,c", ",")))
|
Console.print("Split by comma: " + toString(String.split("a,b,c", ",")))
|
||||||
Console.print("Join with dash: " + String.join(["x", "y", "z"], "-"))
|
Console.print("Join with dash: " + String.join(["x", "y", "z"], "-"))
|
||||||
|
|
||||||
Console.print("")
|
Console.print("")
|
||||||
Console.print("=== Option Operations ===")
|
Console.print("=== Option Operations ===")
|
||||||
let some_val = Some(42)
|
let some_val = Some(42)
|
||||||
@@ -31,7 +26,6 @@ fn main(): Unit with {Console} = {
|
|||||||
Console.print("None mapped: " + toString(Option.map(none_val, fn(x: Int): Int => x * 2)))
|
Console.print("None mapped: " + toString(Option.map(none_val, fn(x: Int): Int => x * 2)))
|
||||||
Console.print("Some(42) getOrElse(0): " + toString(Option.getOrElse(some_val, 0)))
|
Console.print("Some(42) getOrElse(0): " + toString(Option.getOrElse(some_val, 0)))
|
||||||
Console.print("None getOrElse(0): " + toString(Option.getOrElse(none_val, 0)))
|
Console.print("None getOrElse(0): " + toString(Option.getOrElse(none_val, 0)))
|
||||||
|
|
||||||
Console.print("")
|
Console.print("")
|
||||||
Console.print("=== Math Operations ===")
|
Console.print("=== Math Operations ===")
|
||||||
Console.print("abs(-5): " + toString(Math.abs(-5)))
|
Console.print("abs(-5): " + toString(Math.abs(-5)))
|
||||||
|
|||||||
@@ -1,13 +1,3 @@
|
|||||||
// State machine example using algebraic data types
|
|
||||||
// Demonstrates pattern matching for state transitions
|
|
||||||
//
|
|
||||||
// Expected output:
|
|
||||||
// Initial light: red
|
|
||||||
// After transition: green
|
|
||||||
// After two transitions: yellow
|
|
||||||
// Door: Closed -> Open -> Closed -> Locked
|
|
||||||
|
|
||||||
// Traffic light state machine
|
|
||||||
type TrafficLight =
|
type TrafficLight =
|
||||||
| Red
|
| Red
|
||||||
| Yellow
|
| Yellow
|
||||||
@@ -15,26 +5,25 @@ type TrafficLight =
|
|||||||
|
|
||||||
fn nextLight(light: TrafficLight): TrafficLight =
|
fn nextLight(light: TrafficLight): TrafficLight =
|
||||||
match light {
|
match light {
|
||||||
Red => Green,
|
Red => Green,
|
||||||
Green => Yellow,
|
Green => Yellow,
|
||||||
Yellow => Red
|
Yellow => Red,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn canGo(light: TrafficLight): Bool =
|
fn canGo(light: TrafficLight): Bool =
|
||||||
match light {
|
match light {
|
||||||
Green => true,
|
Green => true,
|
||||||
Yellow => false,
|
Yellow => false,
|
||||||
Red => false
|
Red => false,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lightColor(light: TrafficLight): String =
|
fn lightColor(light: TrafficLight): String =
|
||||||
match light {
|
match light {
|
||||||
Red => "red",
|
Red => "red",
|
||||||
Yellow => "yellow",
|
Yellow => "yellow",
|
||||||
Green => "green"
|
Green => "green",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Door state machine
|
|
||||||
type DoorState =
|
type DoorState =
|
||||||
| Open
|
| Open
|
||||||
| Closed
|
| Closed
|
||||||
@@ -48,31 +37,34 @@ type DoorAction =
|
|||||||
|
|
||||||
fn applyAction(state: DoorState, action: DoorAction): DoorState =
|
fn applyAction(state: DoorState, action: DoorAction): DoorState =
|
||||||
match (state, action) {
|
match (state, action) {
|
||||||
(Closed, OpenDoor) => Open,
|
(Closed, OpenDoor) => Open,
|
||||||
(Open, CloseDoor) => Closed,
|
(Open, CloseDoor) => Closed,
|
||||||
(Closed, LockDoor) => Locked,
|
(Closed, LockDoor) => Locked,
|
||||||
(Locked, UnlockDoor) => Closed,
|
(Locked, UnlockDoor) => Closed,
|
||||||
_ => state
|
_ => state,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn doorStateName(state: DoorState): String =
|
fn doorStateName(state: DoorState): String =
|
||||||
match state {
|
match state {
|
||||||
Open => "Open",
|
Open => "Open",
|
||||||
Closed => "Closed",
|
Closed => "Closed",
|
||||||
Locked => "Locked"
|
Locked => "Locked",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test the state machines
|
|
||||||
let light1 = Red
|
let light1 = Red
|
||||||
|
|
||||||
let light2 = nextLight(light1)
|
let light2 = nextLight(light1)
|
||||||
|
|
||||||
let light3 = nextLight(light2)
|
let light3 = nextLight(light2)
|
||||||
|
|
||||||
let door1 = Closed
|
let door1 = Closed
|
||||||
|
|
||||||
let door2 = applyAction(door1, OpenDoor)
|
let door2 = applyAction(door1, OpenDoor)
|
||||||
|
|
||||||
let door3 = applyAction(door2, CloseDoor)
|
let door3 = applyAction(door2, CloseDoor)
|
||||||
|
|
||||||
let door4 = applyAction(door3, LockDoor)
|
let door4 = applyAction(door3, LockDoor)
|
||||||
|
|
||||||
// Print results
|
|
||||||
fn printResults(): Unit with {Console} = {
|
fn printResults(): Unit with {Console} = {
|
||||||
Console.print("Initial light: " + lightColor(light1))
|
Console.print("Initial light: " + lightColor(light1))
|
||||||
Console.print("After transition: " + lightColor(light2))
|
Console.print("After transition: " + lightColor(light2))
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
// Stress test for RC system with large lists
|
|
||||||
// Tests FBIP optimization with single-owner chains
|
|
||||||
|
|
||||||
fn processChain(n: Int): Int = {
|
fn processChain(n: Int): Int = {
|
||||||
// Single owner chain - FBIP should reuse lists
|
|
||||||
let nums = List.range(1, n)
|
let nums = List.range(1, n)
|
||||||
let doubled = List.map(nums, fn(x: Int): Int => x * 2)
|
let doubled = List.map(nums, fn(x: Int): Int => x * 2)
|
||||||
let filtered = List.filter(doubled, fn(x: Int): Bool => x > n)
|
let filtered = List.filter(doubled, fn(x: Int): Bool => x > n)
|
||||||
@@ -12,13 +8,10 @@ fn processChain(n: Int): Int = {
|
|||||||
|
|
||||||
fn main(): Unit = {
|
fn main(): Unit = {
|
||||||
Console.print("=== RC Stress Test ===")
|
Console.print("=== RC Stress Test ===")
|
||||||
|
|
||||||
// Run multiple iterations of list operations
|
|
||||||
let result1 = processChain(100)
|
let result1 = processChain(100)
|
||||||
let result2 = processChain(200)
|
let result2 = processChain(200)
|
||||||
let result3 = processChain(500)
|
let result3 = processChain(500)
|
||||||
let result4 = processChain(1000)
|
let result4 = processChain(1000)
|
||||||
|
|
||||||
Console.print("Completed 4 chains")
|
Console.print("Completed 4 chains")
|
||||||
Console.print("Sizes: 100, 200, 500, 1000")
|
Console.print("Sizes: 100, 200, 500, 1000")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
// Stress test for RC system WITH shared references
|
|
||||||
// Forces rc>1 path by keeping aliases
|
|
||||||
|
|
||||||
fn processWithAlias(n: Int): Int = {
|
fn processWithAlias(n: Int): Int = {
|
||||||
let nums = List.range(1, n)
|
let nums = List.range(1, n)
|
||||||
let alias = nums // This increments rc, forcing copy path
|
let alias = nums
|
||||||
let _len = List.length(alias) // Use the alias
|
let _len = List.length(alias)
|
||||||
|
|
||||||
// Now nums has rc>1, so map must allocate new
|
|
||||||
let doubled = List.map(nums, fn(x: Int): Int => x * 2)
|
let doubled = List.map(nums, fn(x: Int): Int => x * 2)
|
||||||
let filtered = List.filter(doubled, fn(x: Int): Bool => x > n)
|
let filtered = List.filter(doubled, fn(x: Int): Bool => x > n)
|
||||||
let reversed = List.reverse(filtered)
|
let reversed = List.reverse(filtered)
|
||||||
@@ -15,12 +10,9 @@ fn processWithAlias(n: Int): Int = {
|
|||||||
|
|
||||||
fn main(): Unit = {
|
fn main(): Unit = {
|
||||||
Console.print("=== RC Stress Test (Shared Refs) ===")
|
Console.print("=== RC Stress Test (Shared Refs) ===")
|
||||||
|
|
||||||
// Run multiple iterations with shared references
|
|
||||||
let result1 = processWithAlias(100)
|
let result1 = processWithAlias(100)
|
||||||
let result2 = processWithAlias(200)
|
let result2 = processWithAlias(200)
|
||||||
let result3 = processWithAlias(500)
|
let result3 = processWithAlias(500)
|
||||||
let result4 = processWithAlias(1000)
|
let result4 = processWithAlias(1000)
|
||||||
|
|
||||||
Console.print("Completed 4 chains with shared refs")
|
Console.print("Completed 4 chains with shared refs")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +1,25 @@
|
|||||||
// Demonstrating tail call optimization (TCO) in Lux
|
fn factorialTCO(n: Int, acc: Int): Int = if n <= 1 then acc else factorialTCO(n - 1, n * acc)
|
||||||
// TCO allows recursive functions to run in constant stack space
|
|
||||||
//
|
|
||||||
// Expected output:
|
|
||||||
// factorial(20) = 2432902008176640000
|
|
||||||
// fib(30) = 832040
|
|
||||||
// sumTo(1000) = 500500
|
|
||||||
// countdown(10000) completed
|
|
||||||
|
|
||||||
// Factorial with accumulator - tail recursive
|
|
||||||
fn factorialTCO(n: Int, acc: Int): Int =
|
|
||||||
if n <= 1 then acc
|
|
||||||
else factorialTCO(n - 1, n * acc)
|
|
||||||
|
|
||||||
fn factorial(n: Int): Int = factorialTCO(n, 1)
|
fn factorial(n: Int): Int = factorialTCO(n, 1)
|
||||||
|
|
||||||
// Fibonacci with accumulator - tail recursive
|
fn fibTCO(n: Int, a: Int, b: Int): Int = if n <= 0 then a else fibTCO(n - 1, b, a + b)
|
||||||
fn fibTCO(n: Int, a: Int, b: Int): Int =
|
|
||||||
if n <= 0 then a
|
|
||||||
else fibTCO(n - 1, b, a + b)
|
|
||||||
|
|
||||||
fn fib(n: Int): Int = fibTCO(n, 0, 1)
|
fn fib(n: Int): Int = fibTCO(n, 0, 1)
|
||||||
|
|
||||||
// Count down - simple tail recursion
|
fn countdown(n: Int): Int = if n <= 0 then 0 else countdown(n - 1)
|
||||||
fn countdown(n: Int): Int =
|
|
||||||
if n <= 0 then 0
|
|
||||||
else countdown(n - 1)
|
|
||||||
|
|
||||||
// Sum with accumulator - tail recursive
|
fn sumToTCO(n: Int, acc: Int): Int = if n <= 0 then acc else sumToTCO(n - 1, acc + n)
|
||||||
fn sumToTCO(n: Int, acc: Int): Int =
|
|
||||||
if n <= 0 then acc
|
|
||||||
else sumToTCO(n - 1, acc + n)
|
|
||||||
|
|
||||||
fn sumTo(n: Int): Int = sumToTCO(n, 0)
|
fn sumTo(n: Int): Int = sumToTCO(n, 0)
|
||||||
|
|
||||||
// Test the functions
|
|
||||||
let fact20 = factorial(20)
|
let fact20 = factorial(20)
|
||||||
|
|
||||||
let fib30 = fib(30)
|
let fib30 = fib(30)
|
||||||
|
|
||||||
let sum1000 = sumTo(1000)
|
let sum1000 = sumTo(1000)
|
||||||
|
|
||||||
let countResult = countdown(10000)
|
let countResult = countdown(10000)
|
||||||
|
|
||||||
// Print results
|
|
||||||
fn printResults(): Unit with {Console} = {
|
fn printResults(): Unit with {Console} = {
|
||||||
Console.print("factorial(20) = " + toString(fact20))
|
Console.print("factorial(20) = " + toString(fact20))
|
||||||
Console.print("fib(30) = " + toString(fib30))
|
Console.print("fib(30) = " + toString(fib30))
|
||||||
|
|||||||
@@ -1,17 +1,8 @@
|
|||||||
// This test shows FBIP optimization by comparing allocation counts
|
|
||||||
// With FBIP (rc=1): lists are reused in-place
|
|
||||||
// Without FBIP (rc>1): new lists are allocated
|
|
||||||
|
|
||||||
fn main(): Unit = {
|
fn main(): Unit = {
|
||||||
Console.print("=== FBIP Allocation Test ===")
|
Console.print("=== FBIP Allocation Test ===")
|
||||||
|
|
||||||
// Case 1: Single owner (FBIP active) - should reuse list
|
|
||||||
let a = List.range(1, 100)
|
let a = List.range(1, 100)
|
||||||
let b = List.map(a, fn(x: Int): Int => x * 2)
|
let b = List.map(a, fn(x: Int): Int => x * 2)
|
||||||
let c = List.filter(b, fn(x: Int): Bool => x > 50)
|
let c = List.filter(b, fn(x: Int): Bool => x > 50)
|
||||||
let d = List.reverse(c)
|
let d = List.reverse(c)
|
||||||
Console.print("Single owner chain done")
|
Console.print("Single owner chain done")
|
||||||
|
|
||||||
// The allocation count will show FBIP is working
|
|
||||||
// if allocations are low relative to operations performed
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
fn main(): Unit = {
|
fn main(): Unit = {
|
||||||
// Test FBIP without string operations
|
|
||||||
let nums = [1, 2, 3, 4, 5]
|
let nums = [1, 2, 3, 4, 5]
|
||||||
let doubled = List.map(nums, fn(x: Int): Int => x * 2)
|
let doubled = List.map(nums, fn(x: Int): Int => x * 2)
|
||||||
let filtered = List.filter(doubled, fn(x: Int): Bool => x > 4)
|
let filtered = List.filter(doubled, fn(x: Int): Bool => x > 4)
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
// List Operations Test Suite
|
|
||||||
// Run with: lux test examples/test_lists.lux
|
|
||||||
|
|
||||||
fn test_list_length(): Unit with {Test} = {
|
fn test_list_length(): Unit with {Test} = {
|
||||||
Test.assertEqual(0, List.length([]))
|
Test.assertEqual(0, List.length([]))
|
||||||
Test.assertEqual(1, List.length([1]))
|
Test.assertEqual(1, List.length([1]))
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
// Math Test Suite
|
|
||||||
// Run with: lux test examples/test_math.lux
|
|
||||||
|
|
||||||
fn test_addition(): Unit with {Test} = {
|
fn test_addition(): Unit with {Test} = {
|
||||||
Test.assertEqual(4, 2 + 2)
|
Test.assertEqual(4, 2 + 2)
|
||||||
Test.assertEqual(0, 0 + 0)
|
Test.assertEqual(0, 0 + 0)
|
||||||
|
|||||||
@@ -1,21 +1,10 @@
|
|||||||
// Test demonstrating ownership transfer with aliases
|
|
||||||
// The ownership transfer optimization ensures FBIP still works
|
|
||||||
// even when variables are aliased, because ownership is transferred
|
|
||||||
// rather than reference count being incremented.
|
|
||||||
|
|
||||||
fn main(): Unit = {
|
fn main(): Unit = {
|
||||||
Console.print("=== Ownership Transfer Test ===")
|
Console.print("=== Ownership Transfer Test ===")
|
||||||
|
|
||||||
let a = List.range(1, 100)
|
let a = List.range(1, 100)
|
||||||
// Ownership transfers from 'a' to 'alias', keeping rc=1
|
|
||||||
let alias = a
|
let alias = a
|
||||||
let len1 = List.length(alias)
|
let len1 = List.length(alias)
|
||||||
|
|
||||||
// Since ownership transferred, 'a' still has rc=1
|
|
||||||
// FBIP can still optimize map/filter/reverse
|
|
||||||
let b = List.map(a, fn(x: Int): Int => x * 2)
|
let b = List.map(a, fn(x: Int): Int => x * 2)
|
||||||
let c = List.filter(b, fn(x: Int): Bool => x > 50)
|
let c = List.filter(b, fn(x: Int): Bool => x > 50)
|
||||||
let d = List.reverse(c)
|
let d = List.reverse(c)
|
||||||
|
|
||||||
Console.print("Ownership transfer chain done")
|
Console.print("Ownership transfer chain done")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
fn main(): Unit = {
|
fn main(): Unit = {
|
||||||
Console.print("=== Allocation Comparison ===")
|
Console.print("=== Allocation Comparison ===")
|
||||||
|
|
||||||
// FBIP path (rc=1): list is reused
|
|
||||||
Console.print("Test 1: FBIP path")
|
Console.print("Test 1: FBIP path")
|
||||||
let a1 = List.range(1, 50)
|
let a1 = List.range(1, 50)
|
||||||
let b1 = List.map(a1, fn(x: Int): Int => x * 2)
|
let b1 = List.map(a1, fn(x: Int): Int => x * 2)
|
||||||
let c1 = List.reverse(b1)
|
let c1 = List.reverse(b1)
|
||||||
Console.print("FBIP done")
|
Console.print("FBIP done")
|
||||||
|
|
||||||
// To show non-FBIP, we need concat which doesn't have FBIP
|
|
||||||
Console.print("Test 2: Non-FBIP path (concat)")
|
Console.print("Test 2: Non-FBIP path (concat)")
|
||||||
let x = List.range(1, 25)
|
let x = List.range(1, 25)
|
||||||
let y = List.range(26, 50)
|
let y = List.range(26, 50)
|
||||||
let z = List.concat(x, y) // concat always allocates new
|
let z = List.concat(x, y)
|
||||||
Console.print("Concat done")
|
Console.print("Concat done")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,11 @@
|
|||||||
// Demonstrating type classes (traits) in Lux
|
|
||||||
//
|
|
||||||
// Expected output:
|
|
||||||
// RGB color: rgb(255,128,0)
|
|
||||||
// Red color: red
|
|
||||||
// Green color: green
|
|
||||||
|
|
||||||
// Define a simple Printable trait
|
|
||||||
trait Printable {
|
trait Printable {
|
||||||
fn format(value: Int): String
|
fn format(value: Int): String
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement Printable
|
|
||||||
impl Printable for Int {
|
impl Printable for Int {
|
||||||
fn format(value: Int): String = "Number: " + toString(value)
|
fn format(value: Int): String = "Number: " + toString(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Color type with pattern matching
|
|
||||||
type Color =
|
type Color =
|
||||||
| Red
|
| Red
|
||||||
| Green
|
| Green
|
||||||
@@ -24,18 +14,18 @@ type Color =
|
|||||||
|
|
||||||
fn colorName(c: Color): String =
|
fn colorName(c: Color): String =
|
||||||
match c {
|
match c {
|
||||||
Red => "red",
|
Red => "red",
|
||||||
Green => "green",
|
Green => "green",
|
||||||
Blue => "blue",
|
Blue => "blue",
|
||||||
RGB(r, g, b) => "rgb(" + toString(r) + "," + toString(g) + "," + toString(b) + ")"
|
RGB(r, g, b) => "rgb(" + toString(r) + "," + toString(g) + "," + toString(b) + ")",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test
|
|
||||||
let myColor = RGB(255, 128, 0)
|
let myColor = RGB(255, 128, 0)
|
||||||
|
|
||||||
let redColor = Red
|
let redColor = Red
|
||||||
|
|
||||||
let greenColor = Green
|
let greenColor = Green
|
||||||
|
|
||||||
// Print results
|
|
||||||
fn printResults(): Unit with {Console} = {
|
fn printResults(): Unit with {Console} = {
|
||||||
Console.print("RGB color: " + colorName(myColor))
|
Console.print("RGB color: " + colorName(myColor))
|
||||||
Console.print("Red color: " + colorName(redColor))
|
Console.print("Red color: " + colorName(redColor))
|
||||||
|
|||||||
@@ -1,15 +1,3 @@
|
|||||||
// Demonstrating Schema Evolution in Lux
|
|
||||||
//
|
|
||||||
// Lux provides versioned types to help manage data evolution over time.
|
|
||||||
// The Schema module provides functions for creating and migrating versioned values.
|
|
||||||
//
|
|
||||||
// Expected output:
|
|
||||||
// Created user v1: Alice (age unknown)
|
|
||||||
// User version: 1
|
|
||||||
// Migrated to v2: Alice (age unknown)
|
|
||||||
// User version after migration: 2
|
|
||||||
|
|
||||||
// Create a versioned User value at v1
|
|
||||||
fn createUserV1(name: String): Unit with {Console} = {
|
fn createUserV1(name: String): Unit with {Console} = {
|
||||||
let user = Schema.versioned("User", 1, { name: name })
|
let user = Schema.versioned("User", 1, { name: name })
|
||||||
let version = Schema.getVersion(user)
|
let version = Schema.getVersion(user)
|
||||||
@@ -17,7 +5,6 @@ fn createUserV1(name: String): Unit with {Console} = {
|
|||||||
Console.print("User version: " + toString(version))
|
Console.print("User version: " + toString(version))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate a user to v2
|
|
||||||
fn migrateUserToV2(name: String): Unit with {Console} = {
|
fn migrateUserToV2(name: String): Unit with {Console} = {
|
||||||
let userV1 = Schema.versioned("User", 1, { name: name })
|
let userV1 = Schema.versioned("User", 1, { name: name })
|
||||||
let userV2 = Schema.migrate(userV1, 2)
|
let userV2 = Schema.migrate(userV1, 2)
|
||||||
@@ -26,7 +13,6 @@ fn migrateUserToV2(name: String): Unit with {Console} = {
|
|||||||
Console.print("User version after migration: " + toString(newVersion))
|
Console.print("User version after migration: " + toString(newVersion))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main
|
|
||||||
fn main(): Unit with {Console} = {
|
fn main(): Unit with {Console} = {
|
||||||
createUserV1("Alice")
|
createUserV1("Alice")
|
||||||
migrateUserToV2("Alice")
|
migrateUserToV2("Alice")
|
||||||
|
|||||||
@@ -1,62 +1,38 @@
|
|||||||
// Simple Counter for Browser
|
type Model =
|
||||||
// Compile with: lux compile examples/web/counter.lux --target js -o examples/web/counter.js
|
| Counter(Int)
|
||||||
|
|
||||||
// ============================================================================
|
fn getCount(m: Model): Int =
|
||||||
// Model
|
match m {
|
||||||
// ============================================================================
|
Counter(n) => n,
|
||||||
|
}
|
||||||
type Model = | Counter(Int)
|
|
||||||
|
|
||||||
fn getCount(m: Model): Int = match m { Counter(n) => n }
|
|
||||||
|
|
||||||
fn init(): Model = Counter(0)
|
fn init(): Model = Counter(0)
|
||||||
|
|
||||||
// ============================================================================
|
type Msg =
|
||||||
// Messages
|
| Increment
|
||||||
// ============================================================================
|
| Decrement
|
||||||
|
| Reset
|
||||||
type Msg = | Increment | Decrement | Reset
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Update
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
fn update(model: Model, msg: Msg): Model =
|
fn update(model: Model, msg: Msg): Model =
|
||||||
match msg {
|
match msg {
|
||||||
Increment => Counter(getCount(model) + 1),
|
Increment => Counter(getCount(model) + 1),
|
||||||
Decrement => Counter(getCount(model) - 1),
|
Decrement => Counter(getCount(model) - 1),
|
||||||
Reset => Counter(0)
|
Reset => Counter(0),
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// View - Returns HTML string for simplicity
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
fn view(model: Model): String = {
|
fn view(model: Model): String = {
|
||||||
let count = getCount(model)
|
let count = getCount(model)
|
||||||
"<div class=\"counter\">" +
|
"<div class=\"counter\">" + "<h1>Lux Counter</h1>" + "<div class=\"display\">" + toString(count) + "</div>" + "<div class=\"buttons\">" + "<button onclick=\"dispatch('Decrement')\">-</button>" + "<button onclick=\"dispatch('Reset')\">Reset</button>" + "<button onclick=\"dispatch('Increment')\">+</button>" + "</div>" + "</div>"
|
||||||
"<h1>Lux Counter</h1>" +
|
|
||||||
"<div class=\"display\">" + toString(count) + "</div>" +
|
|
||||||
"<div class=\"buttons\">" +
|
|
||||||
"<button onclick=\"dispatch('Decrement')\">-</button>" +
|
|
||||||
"<button onclick=\"dispatch('Reset')\">Reset</button>" +
|
|
||||||
"<button onclick=\"dispatch('Increment')\">+</button>" +
|
|
||||||
"</div>" +
|
|
||||||
"</div>"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Export for browser runtime
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
fn luxInit(): Model = init()
|
fn luxInit(): Model = init()
|
||||||
|
|
||||||
fn luxUpdate(model: Model, msgName: String): Model =
|
fn luxUpdate(model: Model, msgName: String): Model =
|
||||||
match msgName {
|
match msgName {
|
||||||
"Increment" => update(model, Increment),
|
"Increment" => update(model, Increment),
|
||||||
"Decrement" => update(model, Decrement),
|
"Decrement" => update(model, Decrement),
|
||||||
"Reset" => update(model, Reset),
|
"Reset" => update(model, Reset),
|
||||||
_ => model
|
_ => model,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn luxView(model: Model): String = view(model)
|
fn luxView(model: Model): String = view(model)
|
||||||
|
|||||||
@@ -224,10 +224,31 @@ pub mod colors {
|
|||||||
pub const BOLD: &str = "\x1b[1m";
|
pub const BOLD: &str = "\x1b[1m";
|
||||||
pub const DIM: &str = "\x1b[2m";
|
pub const DIM: &str = "\x1b[2m";
|
||||||
pub const RED: &str = "\x1b[31m";
|
pub const RED: &str = "\x1b[31m";
|
||||||
|
pub const GREEN: &str = "\x1b[32m";
|
||||||
pub const YELLOW: &str = "\x1b[33m";
|
pub const YELLOW: &str = "\x1b[33m";
|
||||||
pub const BLUE: &str = "\x1b[34m";
|
pub const BLUE: &str = "\x1b[34m";
|
||||||
|
pub const MAGENTA: &str = "\x1b[35m";
|
||||||
pub const CYAN: &str = "\x1b[36m";
|
pub const CYAN: &str = "\x1b[36m";
|
||||||
pub const WHITE: &str = "\x1b[37m";
|
pub const WHITE: &str = "\x1b[37m";
|
||||||
|
pub const GRAY: &str = "\x1b[90m";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply color to text, respecting NO_COLOR / TERM=dumb
|
||||||
|
pub fn c(color: &str, text: &str) -> String {
|
||||||
|
if supports_color() {
|
||||||
|
format!("{}{}{}", color, text, colors::RESET)
|
||||||
|
} else {
|
||||||
|
text.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply bold + color to text
|
||||||
|
pub fn bc(color: &str, text: &str) -> String {
|
||||||
|
if supports_color() {
|
||||||
|
format!("{}{}{}{}", colors::BOLD, color, text, colors::RESET)
|
||||||
|
} else {
|
||||||
|
text.to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Severity level for diagnostics
|
/// Severity level for diagnostics
|
||||||
|
|||||||
1143
src/linter.rs
Normal file
1143
src/linter.rs
Normal file
File diff suppressed because it is too large
Load Diff
377
src/lsp.rs
377
src/lsp.rs
@@ -19,7 +19,7 @@ use crate::formatter::{format as format_source, FormatConfig};
|
|||||||
use lsp_server::{Connection, ExtractError, Message, Request, RequestId, Response};
|
use lsp_server::{Connection, ExtractError, Message, Request, RequestId, Response};
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
notification::{DidChangeTextDocument, DidOpenTextDocument, Notification},
|
notification::{DidChangeTextDocument, DidOpenTextDocument, Notification},
|
||||||
request::{Completion, GotoDefinition, HoverRequest, References, DocumentSymbolRequest, Rename, SignatureHelpRequest, Formatting},
|
request::{Completion, GotoDefinition, HoverRequest, References, DocumentSymbolRequest, Rename, SignatureHelpRequest, Formatting, InlayHintRequest},
|
||||||
CompletionItem, CompletionItemKind, CompletionOptions, CompletionParams, CompletionResponse,
|
CompletionItem, CompletionItemKind, CompletionOptions, CompletionParams, CompletionResponse,
|
||||||
Diagnostic, DiagnosticSeverity, DidChangeTextDocumentParams, DidOpenTextDocumentParams,
|
Diagnostic, DiagnosticSeverity, DidChangeTextDocumentParams, DidOpenTextDocumentParams,
|
||||||
GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams,
|
GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams,
|
||||||
@@ -28,7 +28,8 @@ use lsp_types::{
|
|||||||
TextDocumentSyncKind, Url, ReferenceParams, Location, DocumentSymbolParams,
|
TextDocumentSyncKind, Url, ReferenceParams, Location, DocumentSymbolParams,
|
||||||
DocumentSymbolResponse, SymbolInformation, RenameParams, WorkspaceEdit, TextEdit,
|
DocumentSymbolResponse, SymbolInformation, RenameParams, WorkspaceEdit, TextEdit,
|
||||||
SignatureHelpParams, SignatureHelp, SignatureInformation, ParameterInformation,
|
SignatureHelpParams, SignatureHelp, SignatureInformation, ParameterInformation,
|
||||||
SignatureHelpOptions, DocumentFormattingParams, TextDocumentIdentifier,
|
SignatureHelpOptions, DocumentFormattingParams,
|
||||||
|
InlayHint, InlayHintKind, InlayHintLabel, InlayHintParams,
|
||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
@@ -88,6 +89,7 @@ impl LspServer {
|
|||||||
work_done_progress_options: Default::default(),
|
work_done_progress_options: Default::default(),
|
||||||
}),
|
}),
|
||||||
document_formatting_provider: Some(lsp_types::OneOf::Left(true)),
|
document_formatting_provider: Some(lsp_types::OneOf::Left(true)),
|
||||||
|
inlay_hint_provider: Some(lsp_types::OneOf::Left(true)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -191,7 +193,7 @@ impl LspServer {
|
|||||||
Err(req) => req,
|
Err(req) => req,
|
||||||
};
|
};
|
||||||
|
|
||||||
let _req = match cast_request::<Formatting>(req) {
|
let req = match cast_request::<Formatting>(req) {
|
||||||
Ok((id, params)) => {
|
Ok((id, params)) => {
|
||||||
let result = self.handle_formatting(params);
|
let result = self.handle_formatting(params);
|
||||||
let resp = Response::new_ok(id, result);
|
let resp = Response::new_ok(id, result);
|
||||||
@@ -201,6 +203,16 @@ impl LspServer {
|
|||||||
Err(req) => req,
|
Err(req) => req,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let _req = match cast_request::<InlayHintRequest>(req) {
|
||||||
|
Ok((id, params)) => {
|
||||||
|
let result = self.handle_inlay_hints(params);
|
||||||
|
let resp = Response::new_ok(id, result);
|
||||||
|
self.connection.sender.send(Message::Response(resp))?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(req) => req,
|
||||||
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,10 +340,16 @@ impl LspServer {
|
|||||||
.map(|d| format!("\n\n{}", d))
|
.map(|d| format!("\n\n{}", d))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// Format signature: wrap long signatures onto multiple lines
|
||||||
|
let formatted_sig = format_signature_for_hover(signature);
|
||||||
|
|
||||||
|
// Add behavioral property documentation if present
|
||||||
|
let property_docs = extract_property_docs(signature);
|
||||||
|
|
||||||
return Some(Hover {
|
return Some(Hover {
|
||||||
contents: HoverContents::Markup(MarkupContent {
|
contents: HoverContents::Markup(MarkupContent {
|
||||||
kind: MarkupKind::Markdown,
|
kind: MarkupKind::Markdown,
|
||||||
value: format!("```lux\n{}\n```\n\n*{}*{}", signature, kind_str, doc_str),
|
value: format!("```lux\n{}\n```\n\n*{}*{}{}", formatted_sig, kind_str, property_docs, doc_str),
|
||||||
}),
|
}),
|
||||||
range: None,
|
range: None,
|
||||||
});
|
});
|
||||||
@@ -343,19 +361,20 @@ impl LspServer {
|
|||||||
// Extract the word at the cursor position
|
// Extract the word at the cursor position
|
||||||
let word = self.get_word_at_position(source, position)?;
|
let word = self.get_word_at_position(source, position)?;
|
||||||
|
|
||||||
// Look up documentation for known symbols
|
// Look up rich documentation for known symbols
|
||||||
let info = self.get_symbol_info(&word);
|
let info = self.get_rich_symbol_info(&word)
|
||||||
|
.or_else(|| self.get_symbol_info(&word).map(|(s, d)| (s.to_string(), d.to_string())));
|
||||||
|
|
||||||
if let Some((signature, doc)) = info {
|
if let Some((signature, doc)) = info {
|
||||||
|
let formatted_sig = format_signature_for_hover(&signature);
|
||||||
Some(Hover {
|
Some(Hover {
|
||||||
contents: HoverContents::Markup(MarkupContent {
|
contents: HoverContents::Markup(MarkupContent {
|
||||||
kind: MarkupKind::Markdown,
|
kind: MarkupKind::Markdown,
|
||||||
value: format!("```lux\n{}\n```\n\n{}", signature, doc),
|
value: format!("```lux\n{}\n```\n\n{}", formatted_sig, doc),
|
||||||
}),
|
}),
|
||||||
range: None,
|
range: None,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Return generic info for unknown symbols
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -439,6 +458,84 @@ impl LspServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Rich documentation for behavioral properties and keywords
|
||||||
|
fn get_rich_symbol_info(&self, word: &str) -> Option<(String, String)> {
|
||||||
|
match word {
|
||||||
|
"pure" => Some((
|
||||||
|
"is pure".to_string(),
|
||||||
|
"**Behavioral Property: Pure**\n\n\
|
||||||
|
A pure function has no side effects and always produces the same output for the same inputs. \
|
||||||
|
The compiler can safely memoize calls, reorder them, or eliminate duplicates.\n\n\
|
||||||
|
```lux\nfn add(a: Int, b: Int): Int is pure = a + b\n```\n\n\
|
||||||
|
**Guarantees:**\n\
|
||||||
|
- No effect operations (Console, File, Http, etc.)\n\
|
||||||
|
- Referential transparency: `f(x)` can be replaced with its result\n\
|
||||||
|
- Enables memoization and common subexpression elimination".to_string(),
|
||||||
|
)),
|
||||||
|
"total" => Some((
|
||||||
|
"is total".to_string(),
|
||||||
|
"**Behavioral Property: Total**\n\n\
|
||||||
|
A total function always terminates and never throws exceptions. \
|
||||||
|
The compiler verifies termination through structural recursion analysis.\n\n\
|
||||||
|
```lux\nfn factorial(n: Int): Int is total =\n if n <= 0 then 1\n else n * factorial(n - 1)\n```\n\n\
|
||||||
|
**Guarantees:**\n\
|
||||||
|
- Always produces a result (no infinite loops)\n\
|
||||||
|
- Cannot use the `Fail` effect\n\
|
||||||
|
- Recursive calls must be structurally decreasing".to_string(),
|
||||||
|
)),
|
||||||
|
"idempotent" => Some((
|
||||||
|
"is idempotent".to_string(),
|
||||||
|
"**Behavioral Property: Idempotent**\n\n\
|
||||||
|
An idempotent function satisfies `f(f(x)) == f(x)` for all inputs. \
|
||||||
|
Applying it multiple times has the same effect as applying it once.\n\n\
|
||||||
|
```lux\nfn abs(x: Int): Int is idempotent =\n if x < 0 then 0 - x else x\n\n\
|
||||||
|
fn clamp(x: Int): Int is idempotent =\n if x < 0 then 0\n else if x > 100 then 100\n else x\n```\n\n\
|
||||||
|
**Guarantees:**\n\
|
||||||
|
- `f(f(x)) == f(x)` for all valid inputs\n\
|
||||||
|
- Safe to retry without changing outcome\n\
|
||||||
|
- Compiler can deduplicate consecutive calls".to_string(),
|
||||||
|
)),
|
||||||
|
"deterministic" => Some((
|
||||||
|
"is deterministic".to_string(),
|
||||||
|
"**Behavioral Property: Deterministic**\n\n\
|
||||||
|
A deterministic function always produces the same output for the same inputs, \
|
||||||
|
with no dependence on randomness, time, or external state.\n\n\
|
||||||
|
```lux\nfn multiply(a: Int, b: Int): Int is deterministic = a * b\n```\n\n\
|
||||||
|
**Guarantees:**\n\
|
||||||
|
- Cannot use `Random` or `Time` effects\n\
|
||||||
|
- Same inputs always produce same outputs\n\
|
||||||
|
- Results can be cached across runs".to_string(),
|
||||||
|
)),
|
||||||
|
"commutative" => Some((
|
||||||
|
"is commutative".to_string(),
|
||||||
|
"**Behavioral Property: Commutative**\n\n\
|
||||||
|
A commutative function satisfies `f(a, b) == f(b, a)`. \
|
||||||
|
The order of arguments doesn't affect the result.\n\n\
|
||||||
|
```lux\nfn add(a: Int, b: Int): Int is commutative = a + b\nfn max(a: Int, b: Int): Int is commutative =\n if a > b then a else b\n```\n\n\
|
||||||
|
**Guarantees:**\n\
|
||||||
|
- Must have exactly 2 parameters\n\
|
||||||
|
- `f(a, b) == f(b, a)` for all inputs\n\
|
||||||
|
- Compiler can normalize argument order for optimization".to_string(),
|
||||||
|
)),
|
||||||
|
"run" => Some((
|
||||||
|
"run expr with { handlers }".to_string(),
|
||||||
|
"**Effect Handler**\n\n\
|
||||||
|
Execute an effectful expression with explicit effect handlers. \
|
||||||
|
Must be bound to a variable at top level.\n\n\
|
||||||
|
```lux\nlet result = run myFunction() with {\n Console = { /* handler */ }\n}\n```\n\n\
|
||||||
|
Handlers intercept effect operations and provide implementations.".to_string(),
|
||||||
|
)),
|
||||||
|
"with" => Some((
|
||||||
|
"with {Effect1, Effect2}".to_string(),
|
||||||
|
"**Effect Declaration / Handler Block**\n\n\
|
||||||
|
Declares which effects a function may perform, or provides handlers in a `run` expression.\n\n\
|
||||||
|
```lux\n// In function signature:\nfn greet(name: String): Unit with {Console} =\n Console.print(\"Hello, \" + name)\n\n\
|
||||||
|
// In run expression:\nlet _ = run greet(\"world\") with {}\n```".to_string(),
|
||||||
|
)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_completion(&self, params: CompletionParams) -> Option<CompletionResponse> {
|
fn handle_completion(&self, params: CompletionParams) -> Option<CompletionResponse> {
|
||||||
let uri = params.text_document_position.text_document.uri;
|
let uri = params.text_document_position.text_document.uri;
|
||||||
let position = params.text_document_position.position;
|
let position = params.text_document_position.position;
|
||||||
@@ -1022,6 +1119,90 @@ impl LspServer {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_inlay_hints(&self, params: InlayHintParams) -> Option<Vec<InlayHint>> {
|
||||||
|
let uri = params.text_document.uri;
|
||||||
|
let doc = self.documents.get(&uri)?;
|
||||||
|
let source = &doc.text;
|
||||||
|
|
||||||
|
// Parse the document to get AST
|
||||||
|
let program = Parser::parse_source(source).ok()?;
|
||||||
|
|
||||||
|
// Type-check to get inferred types
|
||||||
|
let mut checker = TypeChecker::new();
|
||||||
|
let _ = checker.check_program(&program);
|
||||||
|
|
||||||
|
let mut hints = Vec::new();
|
||||||
|
|
||||||
|
// Collect parameter names for known functions (from symbol table)
|
||||||
|
let param_names = self.collect_function_params(&program);
|
||||||
|
|
||||||
|
for decl in &program.declarations {
|
||||||
|
match decl {
|
||||||
|
crate::ast::Declaration::Let(l) => {
|
||||||
|
// Show inferred type for let bindings without explicit type annotations
|
||||||
|
if l.typ.is_none() {
|
||||||
|
if let Some(inferred_type) = checker.get_inferred_type(&l.name.name) {
|
||||||
|
let type_str = format!(": {}", inferred_type);
|
||||||
|
let pos = offset_to_position(source, l.name.span.end);
|
||||||
|
hints.push(InlayHint {
|
||||||
|
position: pos,
|
||||||
|
label: InlayHintLabel::String(type_str),
|
||||||
|
kind: Some(InlayHintKind::TYPE),
|
||||||
|
text_edits: None,
|
||||||
|
tooltip: None,
|
||||||
|
padding_left: Some(false),
|
||||||
|
padding_right: Some(true),
|
||||||
|
data: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Walk into the value expression for call-site parameter hints
|
||||||
|
collect_call_site_hints(source, &l.value, ¶m_names, &mut hints);
|
||||||
|
}
|
||||||
|
crate::ast::Declaration::Function(f) => {
|
||||||
|
// Walk into the function body for call-site parameter hints
|
||||||
|
collect_call_site_hints(source, &f.body, ¶m_names, &mut hints);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hints.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(hints)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collect parameter names for all functions defined in the program
|
||||||
|
fn collect_function_params(&self, program: &crate::ast::Program) -> HashMap<String, Vec<String>> {
|
||||||
|
let mut params = HashMap::new();
|
||||||
|
for decl in &program.declarations {
|
||||||
|
if let crate::ast::Declaration::Function(f) = decl {
|
||||||
|
let names: Vec<String> = f.params.iter()
|
||||||
|
.map(|p| p.name.name.clone())
|
||||||
|
.collect();
|
||||||
|
params.insert(f.name.name.clone(), names);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add builtin function parameter names
|
||||||
|
params.insert("map".into(), vec!["list".into(), "f".into()]);
|
||||||
|
params.insert("filter".into(), vec!["list".into(), "predicate".into()]);
|
||||||
|
params.insert("fold".into(), vec!["list".into(), "init".into(), "f".into()]);
|
||||||
|
params.insert("concat".into(), vec!["a".into(), "b".into()]);
|
||||||
|
params.insert("range".into(), vec!["start".into(), "end".into()]);
|
||||||
|
params.insert("get".into(), vec!["list".into(), "index".into()]);
|
||||||
|
params.insert("take".into(), vec!["list".into(), "n".into()]);
|
||||||
|
params.insert("drop".into(), vec!["list".into(), "n".into()]);
|
||||||
|
params.insert("split".into(), vec!["s".into(), "delimiter".into()]);
|
||||||
|
params.insert("join".into(), vec!["list".into(), "delimiter".into()]);
|
||||||
|
params.insert("replace".into(), vec!["s".into(), "old".into(), "new".into()]);
|
||||||
|
params.insert("substring".into(), vec!["s".into(), "start".into(), "end".into()]);
|
||||||
|
params.insert("contains".into(), vec!["s".into(), "substr".into()]);
|
||||||
|
params.insert("getOrElse".into(), vec!["opt".into(), "default".into()]);
|
||||||
|
params
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_formatting(&self, params: DocumentFormattingParams) -> Option<Vec<TextEdit>> {
|
fn handle_formatting(&self, params: DocumentFormattingParams) -> Option<Vec<TextEdit>> {
|
||||||
let uri = params.text_document.uri;
|
let uri = params.text_document.uri;
|
||||||
let doc = self.documents.get(&uri)?;
|
let doc = self.documents.get(&uri)?;
|
||||||
@@ -1061,6 +1242,186 @@ impl LspServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convert byte offsets to LSP Position
|
/// Convert byte offsets to LSP Position
|
||||||
|
/// Format a function signature for hover display, wrapping long lines
|
||||||
|
fn format_signature_for_hover(sig: &str) -> String {
|
||||||
|
// If it fits in ~60 chars, keep it on one line
|
||||||
|
if sig.len() <= 60 {
|
||||||
|
return sig.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to break at parameter list for function signatures
|
||||||
|
if let Some(paren_start) = sig.find('(') {
|
||||||
|
if let Some(paren_end) = sig.rfind(')') {
|
||||||
|
let prefix = &sig[..paren_start + 1];
|
||||||
|
let params = &sig[paren_start + 1..paren_end];
|
||||||
|
let suffix = &sig[paren_end..];
|
||||||
|
|
||||||
|
// Split parameters and format each on its own line
|
||||||
|
let param_parts: Vec<&str> = params.split(", ").collect();
|
||||||
|
if param_parts.len() > 1 {
|
||||||
|
let indent = " ";
|
||||||
|
let formatted_params = param_parts.join(&format!(",\n{}", indent));
|
||||||
|
return format!("{}\n{}{}\n{}", prefix, indent, formatted_params, suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sig.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract behavioral property documentation from a signature string
|
||||||
|
fn extract_property_docs(sig: &str) -> String {
|
||||||
|
let properties = [
|
||||||
|
("is pure", "**pure** — no side effects, same output for same inputs"),
|
||||||
|
("is total", "**total** — always terminates, no exceptions"),
|
||||||
|
("is idempotent", "**idempotent** — `f(f(x)) == f(x)`"),
|
||||||
|
("is deterministic", "**deterministic** — no randomness or time dependence"),
|
||||||
|
("is commutative", "**commutative** — `f(a, b) == f(b, a)`"),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut found = Vec::new();
|
||||||
|
for (keyword, description) in &properties {
|
||||||
|
if sig.contains(keyword) {
|
||||||
|
found.push(*description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.is_empty() {
|
||||||
|
String::new()
|
||||||
|
} else {
|
||||||
|
format!("\n\n{}", found.join(" \n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursively collect parameter name hints at call sites
|
||||||
|
fn collect_call_site_hints(
|
||||||
|
source: &str,
|
||||||
|
expr: &crate::ast::Expr,
|
||||||
|
param_names: &HashMap<String, Vec<String>>,
|
||||||
|
hints: &mut Vec<InlayHint>,
|
||||||
|
) {
|
||||||
|
use crate::ast::Expr;
|
||||||
|
match expr {
|
||||||
|
Expr::Call { func, args, .. } => {
|
||||||
|
// Get the function name for parameter lookup
|
||||||
|
let func_name = match func.as_ref() {
|
||||||
|
Expr::Var(ident) => Some(ident.name.clone()),
|
||||||
|
// Module.method calls like List.map
|
||||||
|
Expr::Field { object, field, .. } => {
|
||||||
|
if let Expr::Var(_) = object.as_ref() {
|
||||||
|
Some(field.name.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(name) = func_name {
|
||||||
|
if let Some(names) = param_names.get(&name) {
|
||||||
|
for (i, arg) in args.iter().enumerate() {
|
||||||
|
if let Some(param_name) = names.get(i) {
|
||||||
|
// Skip hint if the argument is already a variable with the same name
|
||||||
|
if let Expr::Var(ident) = arg {
|
||||||
|
if &ident.name == param_name {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Skip hints for single-arg functions (obvious)
|
||||||
|
if args.len() <= 1 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let pos = offset_to_position(source, arg.span().start);
|
||||||
|
hints.push(InlayHint {
|
||||||
|
position: pos,
|
||||||
|
label: InlayHintLabel::String(format!("{}:", param_name)),
|
||||||
|
kind: Some(InlayHintKind::PARAMETER),
|
||||||
|
text_edits: None,
|
||||||
|
tooltip: None,
|
||||||
|
padding_left: Some(false),
|
||||||
|
padding_right: Some(true),
|
||||||
|
data: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recurse into function expression and arguments
|
||||||
|
collect_call_site_hints(source, func, param_names, hints);
|
||||||
|
for arg in args {
|
||||||
|
collect_call_site_hints(source, arg, param_names, hints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::BinaryOp { left, right, .. } => {
|
||||||
|
collect_call_site_hints(source, left, param_names, hints);
|
||||||
|
collect_call_site_hints(source, right, param_names, hints);
|
||||||
|
}
|
||||||
|
Expr::UnaryOp { operand, .. } => {
|
||||||
|
collect_call_site_hints(source, operand, param_names, hints);
|
||||||
|
}
|
||||||
|
Expr::If { condition, then_branch, else_branch, .. } => {
|
||||||
|
collect_call_site_hints(source, condition, param_names, hints);
|
||||||
|
collect_call_site_hints(source, then_branch, param_names, hints);
|
||||||
|
collect_call_site_hints(source, else_branch, param_names, hints);
|
||||||
|
}
|
||||||
|
Expr::Let { value, body, .. } => {
|
||||||
|
collect_call_site_hints(source, value, param_names, hints);
|
||||||
|
collect_call_site_hints(source, body, param_names, hints);
|
||||||
|
}
|
||||||
|
Expr::Block { statements, result, .. } => {
|
||||||
|
for stmt in statements {
|
||||||
|
match stmt {
|
||||||
|
crate::ast::Statement::Expr(e) => {
|
||||||
|
collect_call_site_hints(source, e, param_names, hints);
|
||||||
|
}
|
||||||
|
crate::ast::Statement::Let { value, .. } => {
|
||||||
|
collect_call_site_hints(source, value, param_names, hints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
collect_call_site_hints(source, result, param_names, hints);
|
||||||
|
}
|
||||||
|
Expr::Match { scrutinee, arms, .. } => {
|
||||||
|
collect_call_site_hints(source, scrutinee, param_names, hints);
|
||||||
|
for arm in arms {
|
||||||
|
collect_call_site_hints(source, &arm.body, param_names, hints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::Lambda { body, .. } => {
|
||||||
|
collect_call_site_hints(source, body, param_names, hints);
|
||||||
|
}
|
||||||
|
Expr::Tuple { elements, .. } | Expr::List { elements, .. } => {
|
||||||
|
for e in elements {
|
||||||
|
collect_call_site_hints(source, e, param_names, hints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::Record { fields, .. } => {
|
||||||
|
for (_, e) in fields {
|
||||||
|
collect_call_site_hints(source, e, param_names, hints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::Field { object, .. } => {
|
||||||
|
collect_call_site_hints(source, object, param_names, hints);
|
||||||
|
}
|
||||||
|
Expr::Run { expr, handlers, .. } => {
|
||||||
|
collect_call_site_hints(source, expr, param_names, hints);
|
||||||
|
for (_, handler_expr) in handlers {
|
||||||
|
collect_call_site_hints(source, handler_expr, param_names, hints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::Resume { value, .. } => {
|
||||||
|
collect_call_site_hints(source, value, param_names, hints);
|
||||||
|
}
|
||||||
|
Expr::EffectOp { args, .. } => {
|
||||||
|
for arg in args {
|
||||||
|
collect_call_site_hints(source, arg, param_names, hints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::Literal { .. } | Expr::Var(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn span_to_range(source: &str, start: usize, end: usize) -> Range {
|
fn span_to_range(source: &str, start: usize, end: usize) -> Range {
|
||||||
let start_pos = offset_to_position(source, start);
|
let start_pos = offset_to_position(source, start);
|
||||||
let end_pos = offset_to_position(source, end);
|
let end_pos = offset_to_position(source, end);
|
||||||
|
|||||||
667
src/main.rs
667
src/main.rs
File diff suppressed because it is too large
Load Diff
@@ -879,7 +879,8 @@ impl Parser {
|
|||||||
Ok(effects)
|
Ok(effects)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse behavioral properties: is pure, is total, is idempotent, etc.
|
/// Parse behavioral properties: is pure, total, idempotent, etc.
|
||||||
|
/// Supports: `is pure`, `is pure is total`, `is pure, total`, `is pure, is total`
|
||||||
fn parse_behavioral_properties(&mut self) -> Result<Vec<BehavioralProperty>, ParseError> {
|
fn parse_behavioral_properties(&mut self) -> Result<Vec<BehavioralProperty>, ParseError> {
|
||||||
let mut properties = Vec::new();
|
let mut properties = Vec::new();
|
||||||
|
|
||||||
@@ -901,9 +902,15 @@ impl Parser {
|
|||||||
let property = self.parse_single_property()?;
|
let property = self.parse_single_property()?;
|
||||||
properties.push(property);
|
properties.push(property);
|
||||||
|
|
||||||
// Optional comma for multiple properties: is pure, is total
|
// After first property, allow comma-separated list without repeating 'is'
|
||||||
if self.check(TokenKind::Comma) {
|
while self.check(TokenKind::Comma) {
|
||||||
self.advance();
|
self.advance(); // consume comma
|
||||||
|
// Allow optional 'is' after comma: `is pure, is total` or `is pure, total`
|
||||||
|
if self.check(TokenKind::Is) {
|
||||||
|
self.advance();
|
||||||
|
}
|
||||||
|
let property = self.parse_single_property()?;
|
||||||
|
properties.push(property);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -263,7 +263,21 @@ impl SymbolTable {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(", "))
|
.join(", "))
|
||||||
};
|
};
|
||||||
let type_sig = format!("fn {}({}): {}{}", f.name.name, param_types.join(", "), return_type, effects);
|
let properties = if f.properties.is_empty() {
|
||||||
|
String::new()
|
||||||
|
} else {
|
||||||
|
format!(" is {}", f.properties.iter()
|
||||||
|
.map(|p| match p {
|
||||||
|
crate::ast::BehavioralProperty::Pure => "pure",
|
||||||
|
crate::ast::BehavioralProperty::Total => "total",
|
||||||
|
crate::ast::BehavioralProperty::Idempotent => "idempotent",
|
||||||
|
crate::ast::BehavioralProperty::Deterministic => "deterministic",
|
||||||
|
crate::ast::BehavioralProperty::Commutative => "commutative",
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", "))
|
||||||
|
};
|
||||||
|
let type_sig = format!("fn {}({}): {}{}{}", f.name.name, param_types.join(", "), return_type, properties, effects);
|
||||||
|
|
||||||
let symbol = self.new_symbol(
|
let symbol = self.new_symbol(
|
||||||
f.name.name.clone(),
|
f.name.name.clone(),
|
||||||
|
|||||||
@@ -759,6 +759,17 @@ impl TypeChecker {
|
|||||||
self.env.bindings.get(name)
|
self.env.bindings.get(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the inferred type of a binding as a display string (for LSP inlay hints)
|
||||||
|
pub fn get_inferred_type(&self, name: &str) -> Option<String> {
|
||||||
|
let scheme = self.env.bindings.get(name)?;
|
||||||
|
let type_str = scheme.typ.to_string();
|
||||||
|
// Skip unhelpful types
|
||||||
|
if type_str == "<error>" || type_str.contains('?') {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(type_str)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get auto-generated migrations from type checking
|
/// Get auto-generated migrations from type checking
|
||||||
/// Returns: type_name -> from_version -> migration_body
|
/// Returns: type_name -> from_version -> migration_body
|
||||||
pub fn get_auto_migrations(&self) -> &HashMap<String, HashMap<u32, Expr>> {
|
pub fn get_auto_migrations(&self) -> &HashMap<String, HashMap<u32, Expr>> {
|
||||||
|
|||||||
Reference in New Issue
Block a user