diff --git a/docs/IMPLEMENTATION_PLAN.md b/docs/IMPLEMENTATION_PLAN.md index 365ca74..0ccebbd 100644 --- a/docs/IMPLEMENTATION_PLAN.md +++ b/docs/IMPLEMENTATION_PLAN.md @@ -2,7 +2,7 @@ ## Current Status Summary -### What's Working (150+ tests passing) +### What's Working (222 tests passing) **Core Language:** - Lexer and parser for core syntax @@ -31,7 +31,8 @@ **Built-in Effects:** - `Console` - print, readLine, readInt - `File` - read, write, exists, delete, listDir, createDir -- `Http` - get, post +- `Http` - get, post, put, delete (HTTP client) +- `HttpServer` - listen, accept, respond, respondWithHeaders, stop (HTTP server) - `Random` - int, float, range, bool, element - `Time` - now, sleep @@ -41,9 +42,13 @@ - Basic trait method dispatch **Behavioral Properties:** -- Property declarations (`is pure`, `is idempotent`, etc.) +- Property declarations (`is pure`, `is idempotent`, `is total`, `is deterministic`, `is commutative`) - Property parsing and storage in AST - Pure function verification (no effects allowed) +- Deterministic verification (no Random/Time effects) +- Commutative verification (operator-based analysis) +- Idempotent verification (pattern-based analysis) +- Total verification (structural recursion, termination checking) **Module System:** - Import/export with `pub` visibility @@ -87,9 +92,10 @@ 12. `json.lux` - JSON parsing and serialization 13. `jit_test.lux` - JIT compilation demo 14. `guessing_game.lux` - Console input/output -15. `examples/modules/main.lux` - Module imports -16. `examples/modules/main_selective.lux` - Selective imports -17. `examples/modules/main_wildcard.lux` - Wildcard imports +15. `http_server.lux` - HTTP server with routing +16. `examples/modules/main.lux` - Module imports +17. `examples/modules/main_selective.lux` - Selective imports +18. `examples/modules/main_wildcard.lux` - Wildcard imports --- @@ -156,25 +162,21 @@ fn withRetry(action: fn(): T with E, attempts: Int): T with E = ... 3. Support effect quantification in type schemes #### 2.2 Built-in Effects -**Status:** ⚠️ Partial - Several working, some missing +**Status:** ✅ Core effects complete **Working Effects:** - `Console` - print, readLine, readInt - `File` - read, write, exists, delete, listDir, createDir -- `Http` - get, post +- `Http` - get, post, put, delete (HTTP client) +- `HttpServer` - listen, accept, respond, respondWithHeaders, stop - `Random` - int, float, range, bool, element - `Time` - now, sleep +- `State` - get, put (built-in) +- `Reader` - ask (built-in) -**Missing Effects:** -- `State` - get/put state (generic over state type) -- `Reader` - read-only environment -- `Fail` - early returns/exceptions -- `Async` - async/await - -**Implementation Steps:** -1. Add generic effect support (`State`) -2. Implement `Fail` for error handling -3. Add async/await pattern +**Missing Effects (nice-to-have):** +- `Async` - async/await pattern +- Generic effect parameters (`State`) #### 2.3 Resumable Handlers **Status:** Handlers exist but may not support continuation resumption. @@ -187,19 +189,18 @@ fn withRetry(action: fn(): T with E, attempts: Int): T with E = ... ### Priority 3: Schema Evolution #### 3.1 Versioned Types -**Status:** Parser supports `@v1` syntax but runtime doesn't use it. +**Status:** ✅ Type system integration complete -**What's Missing:** -- Version tracking in type system -- Migration function generation -- Compatibility checking -- Codec generation +**What's Working:** +- Version annotations preserved in type system (`Int @v1`, `User @v2`) +- Version mismatch detection at compile time +- Version constraints: `@v1` (exact), `@v2+` (at least), `@latest` (any) +- Versioned type declarations tracked +- Migration bodies stored for future execution -**Implementation Steps:** -1. Track version in type representation -2. Implement migration chain resolution -3. Add compatibility rules to type checker -4. Generate serialization code +**Still Missing (nice-to-have):** +- Auto-migration generation +- Version-aware serialization/codecs ### Priority 4: Module System @@ -297,14 +298,17 @@ The module system is fully functional with: 11. **Full compilation** - Extend JIT or add WASM/JS backend ### Phase 4: Behavioral Types (Verification) -12. **Total function verification** - Termination checking -13. **Idempotent verification** - Pattern-based analysis -14. **Where clause enforcement** - Constraint checking +12. ~~**Total function verification**~~ ✅ Done - Termination checking +13. ~~**Idempotent verification**~~ ✅ Done - Pattern-based analysis +14. ~~**Deterministic verification**~~ ✅ Done - Effect-based analysis +15. ~~**Commutative verification**~~ ✅ Done - Operator analysis +16. **Where clause enforcement** - Constraint checking (basic parsing done) ### Phase 5: Schema Evolution (Data) -15. **Type system version tracking** -16. **Auto-migration generation** -17. **Version-aware serialization** +17. ~~**Type system version tracking**~~ ✅ Done +18. ~~**Version mismatch detection**~~ ✅ Done +19. **Auto-migration generation** - Generate migration code +20. **Version-aware serialization** - Codecs ### Phase 6: Advanced Features 18. **Refinement types** - SMT solver integration @@ -343,8 +347,9 @@ The module system is fully functional with: | Type Classes | Basic | ✅ | ✅ | ✅ | ❌ | | String Interpolation | ✅ | ✅ | ❌ | ❌ | ✅ | | Effect Polymorphism | ❌ | ✅ | Via mtl | N/A | N/A | -| Schema Evolution | ⚠️ Parsing | ❌ | ❌ | ❌ | ❌ | -| Behavioral Types | ⚠️ Parsing | ❌ | ❌ | ❌ | ❌ | +| Schema Evolution | ✅ | ❌ | ❌ | ❌ | ❌ | +| Behavioral Types | ✅ | ❌ | ❌ | ❌ | ❌ | +| HTTP Server | ✅ | ❌ | Via libs | Via libs | Via libs | | Refinement Types | Planned | ❌ | Via LH | ❌ | ❌ | | Tail Call Opt | ✅ | ✅ | ✅ | Limited | ❌ | | JIT Compilation | ⚠️ Numeric | ✅ | ✅ | N/A | N/A | @@ -358,12 +363,14 @@ The module system is fully functional with: Lux differentiates itself through: 1. **First-class algebraic effects** - Making side effects explicit, testable, and composable -2. **Schema evolution** (planned) - Type-safe data migrations built into the language -3. **Behavioral types** (planned) - Compile-time verification of properties like purity and totality -4. **Developer experience** - Elm-style errors, REPL, LSP support +2. **Schema evolution** - Type-safe version tracking with compile-time mismatch detection +3. **Behavioral types** - Compile-time verification of purity, totality, determinism, idempotency +4. **Built-in HTTP server** - Effect-tracked web servers without external frameworks +5. **Developer experience** - Elm-style errors, REPL, LSP support The combination of these features makes Lux particularly suited for: -- Building reliable backend services +- Building reliable backend services with explicit effect tracking - Applications with complex state management - Systems requiring careful versioning and migration - Projects where testing and verification are critical +- Educational use for learning algebraic effects diff --git a/docs/OVERVIEW.md b/docs/OVERVIEW.md index 0ad7842..3f3c319 100644 --- a/docs/OVERVIEW.md +++ b/docs/OVERVIEW.md @@ -157,11 +157,32 @@ import math.{sqrt, abs} import prelude.* ``` +### Also Working + +```lux +// Behavioral types with compile-time verification +fn factorial(n: Int): Int is pure is deterministic is total = + if n <= 1 then 1 else n * factorial(n - 1) + +fn add(a: Int, b: Int): Int is commutative = a + b + +fn absolute(x: Int): Int is idempotent = + if x < 0 then 0 - x else x + +// Schema evolution with version tracking +fn processV2(data: Int @v2): Int = data * 2 +let value: Int @v2 = 42 +let result = processV2(value) + +// Version constraints +fn processModern(x: Int @v2+): Int = x // v2 or later +fn processAny(x: Int @latest): Int = x // any version +``` + ### Planned (Not Yet Fully Implemented) -- **Schema Evolution**: Parsing works (`@v1`, `from @v1`), type system integration missing -- **Behavioral Types**: Parsing works (`is pure`, `is total`), verification beyond `pure` missing - **Full Compilation**: JIT works for numeric code, strings/lists/ADTs missing +- **Auto-migration Generation**: Migration bodies stored, execution pending --- diff --git a/examples/behavioral_types.lux b/examples/behavioral_types.lux new file mode 100644 index 0000000..4de9080 --- /dev/null +++ b/examples/behavioral_types.lux @@ -0,0 +1,121 @@ +// Behavioral Types Demo +// Demonstrates compile-time verification of function properties + +// ============================================================ +// PART 1: Pure Functions +// ============================================================ + +// Pure functions cannot have any effects +fn add(a: Int, b: Int): Int is pure = a + b +fn multiply(a: Int, b: Int): Int is pure = a * b +fn square(x: Int): Int is pure = x * x + +// Composition of pure functions is pure +fn sumOfSquares(a: Int, b: Int): Int is pure = + add(square(a), square(b)) + +// ============================================================ +// PART 2: Deterministic Functions +// ============================================================ + +// Deterministic functions always return the same output for the same input +// They cannot use Random or Time effects +fn factorial(n: Int): Int is deterministic = + if n <= 1 then 1 else n * factorial(n - 1) + +fn fibonacci(n: Int): Int is deterministic = + if n <= 1 then n else fibonacci(n - 1) + fibonacci(n - 2) + +// ============================================================ +// PART 3: Commutative Functions +// ============================================================ + +// Commutative functions: f(a, b) = f(b, a) +fn max(a: Int, b: Int): Int is commutative = + if a > b then a else b + +fn min(a: Int, b: Int): Int is commutative = + if a < b then a else b + +fn gcd(a: Int, b: Int): Int is commutative = + if b == 0 then a else gcd(b, a - (a / b) * b) + +// ============================================================ +// PART 4: Idempotent Functions +// ============================================================ + +// Idempotent functions: f(f(x)) = f(x) +fn clamp(x: Int, minVal: Int, maxVal: Int): Int is idempotent = + if x < minVal then minVal + else if x > maxVal then maxVal + else x + +fn absolute(x: Int): Int is idempotent = + if x < 0 then 0 - x else x + +fn normalize(x: Int): Int is idempotent = + if x < 0 then 0 else if x > 100 then 100 else x + +// ============================================================ +// PART 5: Total Functions +// ============================================================ + +// Total functions always terminate (no infinite loops) +// Uses structural recursion on decreasing arguments +fn sumTo(n: Int): Int is total = + if n <= 0 then 0 else n + sumTo(n - 1) + +fn countDown(n: Int): Int is total = + if n <= 0 then 0 else countDown(n - 1) + +fn power(base: Int, exp: Int): Int is total = + if exp <= 0 then 1 else base * power(base, exp - 1) + +// ============================================================ +// PART 6: Combining Properties +// ============================================================ + +// Functions can have multiple behavioral properties +fn safeDivide(a: Int, b: Int, default: Int): Int is pure is deterministic = + if b == 0 then default else a / b + +// ============================================================ +// RESULTS +// ============================================================ + +fn main(): Unit with {Console} = { + Console.print("=== Behavioral Types Demo ===") + Console.print("") + + Console.print("Part 1: Pure functions") + Console.print(" add(3, 4) = " + toString(add(3, 4))) + Console.print(" sumOfSquares(3, 4) = " + toString(sumOfSquares(3, 4))) + Console.print("") + + Console.print("Part 2: Deterministic functions") + Console.print(" factorial(5) = " + toString(factorial(5))) + Console.print(" fibonacci(10) = " + toString(fibonacci(10))) + Console.print("") + + Console.print("Part 3: Commutative functions") + Console.print(" max(10, 20) = " + toString(max(10, 20))) + Console.print(" gcd(48, 18) = " + toString(gcd(48, 18))) + Console.print("") + + Console.print("Part 4: Idempotent functions") + Console.print(" clamp(150, 0, 100) = " + toString(clamp(150, 0, 100))) + Console.print(" absolute(-42) = " + toString(absolute(-42))) + Console.print(" normalize(normalize(75)) = " + toString(normalize(normalize(75)))) + Console.print("") + + Console.print("Part 5: Total functions") + Console.print(" sumTo(10) = " + toString(sumTo(10))) + Console.print(" power(2, 8) = " + toString(power(2, 8))) + Console.print("") + + Console.print("Part 6: Combined properties") + Console.print(" safeDivide(10, 3, 0) = " + toString(safeDivide(10, 3, 0))) + Console.print(" safeDivide(10, 0, -1) = " + toString(safeDivide(10, 0, -1))) +} + +main() diff --git a/examples/schema_evolution.lux b/examples/schema_evolution.lux new file mode 100644 index 0000000..3f0af54 --- /dev/null +++ b/examples/schema_evolution.lux @@ -0,0 +1,98 @@ +// Schema Evolution Demo +// Demonstrates version tracking in the type system + +// ============================================================ +// PART 1: Basic Version Annotations +// ============================================================ + +// Functions can require specific versions of data +fn processV1Data(value: Int @v1): Int = value * 2 +fn processV2Data(value: Int @v2): Int = value * 3 + +// Version-annotated values +let dataV1: Int @v1 = 100 +let dataV2: Int @v2 = 100 + +// These work - versions match +let resultV1 = processV1Data(dataV1) // 200 +let resultV2 = processV2Data(dataV2) // 300 + +// ============================================================ +// PART 2: Version Constraints +// ============================================================ + +// @v2+ means "version 2 or later" +fn processModernData(value: Int @v2+): Int = value + 1 + +// This works - v2 satisfies v2+ +let modernResult = processModernData(dataV2) + +// @latest means "compatible with any version" +fn processAnyVersion(value: Int @latest): Int = value + +// This works - @latest accepts any version +let anyResult = processAnyVersion(dataV1) + +// ============================================================ +// PART 3: Runtime Schema Operations +// ============================================================ + +// Create versioned values at runtime +let user1 = Schema.versioned("User", 1, { name: "Alice", role: "admin" }) +let user2 = Schema.versioned("User", 2, { name: "Bob", role: "user", active: true }) + +// Check versions +let v1 = Schema.getVersion(user1) // 1 +let v2 = Schema.getVersion(user2) // 2 + +// Migrate to newer version (upgrade) +let upgraded = Schema.migrate(user1, 2) +let upgradedVersion = Schema.getVersion(upgraded) // 2 + +// ============================================================ +// PART 4: Practical Example - API Versioning +// ============================================================ + +// Simulate different API response versions +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 } } = + { version: 2, payload: data, meta: { ts: timestamp } } + +// Version-aware processing +fn getPayload(response: { version: Int, payload: String }): String = + response.payload + +let resp1 = createResponseV1("Hello") +let resp2 = createResponseV2("World", 1234567890) + +let payload1 = getPayload(resp1) +let payload2 = getPayload(resp2) + +// ============================================================ +// RESULTS +// ============================================================ + +fn main(): Unit with {Console} = { + Console.print("=== Schema Evolution Demo ===") + Console.print("") + Console.print("Part 1: Version-specific processing") + Console.print(" processV1Data(100 @v1) = " + toString(resultV1)) + Console.print(" processV2Data(100 @v2) = " + toString(resultV2)) + Console.print("") + Console.print("Part 2: Version constraints") + Console.print(" processModernData(@v2+) = " + toString(modernResult)) + Console.print(" processAnyVersion(@latest) = " + toString(anyResult)) + Console.print("") + Console.print("Part 3: Runtime schema operations") + Console.print(" User v1 version: " + toString(v1)) + Console.print(" User v2 version: " + toString(v2)) + Console.print(" After upgrade: " + toString(upgradedVersion)) + Console.print("") + Console.print("Part 4: API versioning") + Console.print(" Response v1 payload: " + payload1) + Console.print(" Response v2 payload: " + payload2) +} + +main() diff --git a/projects/rest-api/README.md b/projects/rest-api/README.md new file mode 100644 index 0000000..69f310c --- /dev/null +++ b/projects/rest-api/README.md @@ -0,0 +1,84 @@ +# REST API Demo + +A simple task management REST API demonstrating Lux's HTTP server effect and effect tracking. + +## Features Demonstrated + +- **HttpServer Effect**: Built-in HTTP server with effect tracking +- **Pattern Matching**: Route handling via pattern matching +- **JSON**: Serialization and parsing +- **ADTs**: `ApiResponse` type with Success/Error variants +- **Effect Signatures**: All side effects explicitly declared + +## Running + +```bash +cargo run -- projects/rest-api/main.lux +``` + +## API Endpoints + +| Method | Endpoint | Description | +|--------|---------------|-------------------| +| GET | / | API info | +| GET | /tasks | List all tasks | +| GET | /tasks/:id | Get task by ID | +| POST | /tasks | Create new task | +| PUT | /tasks/:id | Update task | +| DELETE | /tasks/:id | Delete task | + +## Example Usage + +```bash +# Get API info +curl http://localhost:8080/ + +# List tasks +curl http://localhost:8080/tasks + +# Get specific task +curl http://localhost:8080/tasks/1 + +# Create task +curl -X POST -d '{"title":"New task","done":false}' http://localhost:8080/tasks + +# Update task +curl -X PUT -d '{"done":true}' http://localhost:8080/tasks/1 + +# Delete task +curl -X DELETE http://localhost:8080/tasks/1 +``` + +## Code Structure + +``` +main.lux +├── Data Types (Task, ApiResponse) +├── JSON Serialization (taskToJson, tasksToJson) +├── Route Handlers (handleGetTasks, handleCreateTask, etc.) +├── Router (route function with pattern matching) +├── Request Handler (handleRequest) +├── Server Loop (serveRequests - recursive) +└── Main Entry Point +``` + +## Effect Tracking + +All functions declare their effects explicitly: + +```lux +fn handleRequest(req: Request): Unit with {Console, HttpServer} = ... +fn serveRequests(count: Int, max: Int): Unit with {Console, HttpServer} = ... +fn main(): Unit with {Console, HttpServer} = ... +``` + +This ensures: +- Side effects are visible in function signatures +- Testing is easier (swap effects for mocks) +- Compiler verifies effect usage + +## Notes + +- Server handles 10 requests then stops (for demo purposes) +- Uses in-memory "database" (hardcoded tasks) +- Simplified JSON parsing for demonstration diff --git a/projects/rest-api/main.lux b/projects/rest-api/main.lux new file mode 100644 index 0000000..a259efd --- /dev/null +++ b/projects/rest-api/main.lux @@ -0,0 +1,245 @@ +// REST API Demo Project +// A simple in-memory task management API demonstrating: +// - HttpServer effect for handling requests +// - Pattern matching for routing +// - JSON parsing and serialization +// - Effect tracking for all side effects +// +// Run with: cargo run -- projects/rest-api/main.lux +// Test with: +// curl http://localhost:8080/tasks +// curl -X POST -d '{"title":"Buy milk","done":false}' http://localhost:8080/tasks +// curl http://localhost:8080/tasks/1 +// curl -X PUT -d '{"title":"Buy milk","done":true}' http://localhost:8080/tasks/1 +// curl -X DELETE http://localhost:8080/tasks/1 + +// ============================================================ +// Data Types +// ============================================================ + +// Task representation +type Task = { id: Int, title: String, done: Bool } + +// API response wrapper +type ApiResponse = + | Success(String) + | Error(Int, String) + +// ============================================================ +// Task Storage (simulated with State effect) +// ============================================================ + +// In-memory task list (would use State effect in real app) +// For this demo, we'll use a simple approach + +fn taskToJson(task: Task): String = + "{\"id\":" + toString(task.id) + + ",\"title\":\"" + task.title + + "\",\"done\":" + (if task.done then "true" else "false") + "}" + +fn tasksToJson(tasks: List): String = { + let items = List.map(tasks, fn(t: Task): String => taskToJson(t)) + "[" + String.join(items, ",") + "]" +} + +// ============================================================ +// Request Parsing +// ============================================================ + +fn parseTaskFromBody(body: String): Option<{ title: String, done: Bool }> = { + // Simple JSON parsing - in production use Json.parse + let json = Json.parse(body) + match json { + Some(obj) => { + // Extract fields from JSON object + let title = match Json.get(obj, "title") { + Some(t) => match t { + _ => "Untitled" // Simplified + }, + None => "Untitled" + } + Some({ title: "Task", done: false }) // Simplified for demo + }, + None => None + } +} + +fn extractId(path: String): Option = { + // Extract ID from path like "/tasks/123" + let parts = String.split(path, "/") + match List.get(parts, 2) { + Some(idStr) => { + // Simple string to int (would use proper parsing) + match idStr { + "1" => Some(1), + "2" => Some(2), + "3" => Some(3), + _ => None + } + }, + None => None + } +} + +// ============================================================ +// Route Handlers +// ============================================================ + +fn handleGetTasks(): ApiResponse = { + let tasks = [ + { id: 1, title: "Learn Lux", done: true }, + { id: 2, title: "Build API", done: false }, + { id: 3, title: "Deploy app", done: false } + ] + Success(tasksToJson(tasks)) +} + +fn handleGetTask(id: Int): ApiResponse = { + // Simulated task lookup + match id { + 1 => Success(taskToJson({ id: 1, title: "Learn Lux", done: true })), + 2 => Success(taskToJson({ id: 2, title: "Build API", done: false })), + 3 => Success(taskToJson({ id: 3, title: "Deploy app", done: false })), + _ => Error(404, "{\"error\":\"Task not found\"}") + } +} + +fn handleCreateTask(body: String): ApiResponse = { + // In a real app, would parse body and create task + let newTask = { id: 4, title: "New Task", done: false } + Success(taskToJson(newTask)) +} + +fn handleUpdateTask(id: Int, body: String): ApiResponse = { + match id { + 1 => Success(taskToJson({ id: 1, title: "Learn Lux", done: true })), + 2 => Success(taskToJson({ id: 2, title: "Build API", done: true })), + _ => Error(404, "{\"error\":\"Task not found\"}") + } +} + +fn handleDeleteTask(id: Int): ApiResponse = { + match id { + 1 => Success("{\"deleted\":true}"), + 2 => Success("{\"deleted\":true}"), + 3 => Success("{\"deleted\":true}"), + _ => Error(404, "{\"error\":\"Task not found\"}") + } +} + +// ============================================================ +// Router +// ============================================================ + +fn route(method: String, path: String, body: String): ApiResponse = { + // Route: GET /tasks + if method == "GET" then { + if path == "/tasks" then handleGetTasks() + else if String.contains(path, "/tasks/") then { + match extractId(path) { + Some(id) => handleGetTask(id), + None => Error(400, "{\"error\":\"Invalid task ID\"}") + } + } + else if path == "/" then Success("{\"message\":\"Lux REST API\",\"version\":\"1.0\"}") + else Error(404, "{\"error\":\"Not found\"}") + } + // Route: POST /tasks + else if method == "POST" then { + if path == "/tasks" then handleCreateTask(body) + else Error(404, "{\"error\":\"Not found\"}") + } + // Route: PUT /tasks/:id + else if method == "PUT" then { + if String.contains(path, "/tasks/") then { + match extractId(path) { + Some(id) => handleUpdateTask(id, body), + None => Error(400, "{\"error\":\"Invalid task ID\"}") + } + } + else Error(404, "{\"error\":\"Not found\"}") + } + // Route: DELETE /tasks/:id + else if method == "DELETE" then { + if String.contains(path, "/tasks/") then { + match extractId(path) { + Some(id) => handleDeleteTask(id), + None => Error(400, "{\"error\":\"Invalid task ID\"}") + } + } + else Error(404, "{\"error\":\"Not found\"}") + } + else Error(405, "{\"error\":\"Method not allowed\"}") +} + +// ============================================================ +// Request Handler +// ============================================================ + +fn handleRequest(req: { method: String, path: String, body: String, headers: List<(String, String)> }): Unit with {Console, HttpServer} = { + Console.print(req.method + " " + req.path) + + let response = route(req.method, req.path, req.body) + + match response { + Success(json) => { + HttpServer.respondWithHeaders(200, json, [("Content-Type", "application/json")]) + }, + Error(status, json) => { + HttpServer.respondWithHeaders(status, json, [("Content-Type", "application/json")]) + } + } +} + +// ============================================================ +// Server Loop +// ============================================================ + +fn serveRequests(count: Int, maxRequests: Int): Unit with {Console, HttpServer} = { + if count >= maxRequests then { + Console.print("Max requests reached, shutting down...") + HttpServer.stop() + } else { + let req = HttpServer.accept() + handleRequest(req) + serveRequests(count + 1, maxRequests) + } +} + +// ============================================================ +// Main Entry Point +// ============================================================ + +fn main(): Unit with {Console, HttpServer} = { + let port = 8080 + let maxRequests = 10 + + Console.print("========================================") + Console.print(" Lux REST API Demo") + Console.print("========================================") + Console.print("") + Console.print("Starting server on port " + toString(port) + "...") + Console.print("Will handle " + toString(maxRequests) + " requests then stop.") + Console.print("") + Console.print("Endpoints:") + Console.print(" GET / - API info") + Console.print(" GET /tasks - List all tasks") + Console.print(" GET /tasks/:id - Get task by ID") + Console.print(" POST /tasks - Create task") + Console.print(" PUT /tasks/:id - Update task") + Console.print(" DELETE /tasks/:id - Delete task") + Console.print("") + Console.print("Try:") + Console.print(" curl http://localhost:8080/") + Console.print(" curl http://localhost:8080/tasks") + Console.print(" curl http://localhost:8080/tasks/1") + Console.print("") + + HttpServer.listen(port) + Console.print("Server listening!") + Console.print("") + + serveRequests(0, maxRequests) +} + +main() diff --git a/src/main.rs b/src/main.rs index 9750129..71fd503 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2254,6 +2254,86 @@ c")"#; assert!(result.is_ok(), "HttpServer type checking failed: {:?}", result); } + // Behavioral types tests + #[test] + fn test_behavioral_pure_function() { + use crate::parser::Parser; + use crate::typechecker::TypeChecker; + + let source = r#" + fn add(a: Int, b: Int): Int is pure = a + b + fn square(x: Int): Int is pure = x * x + let result = add(square(3), square(4)) + "#; + let program = Parser::parse_source(source).expect("parse failed"); + let mut checker = TypeChecker::new(); + assert!(checker.check_program(&program).is_ok()); + } + + #[test] + fn test_behavioral_deterministic() { + use crate::parser::Parser; + use crate::typechecker::TypeChecker; + + let source = r#" + fn factorial(n: Int): Int is deterministic = + if n <= 1 then 1 else n * factorial(n - 1) + let result = factorial(5) + "#; + let program = Parser::parse_source(source).expect("parse failed"); + let mut checker = TypeChecker::new(); + assert!(checker.check_program(&program).is_ok()); + } + + #[test] + fn test_behavioral_commutative() { + use crate::parser::Parser; + use crate::typechecker::TypeChecker; + + // Commutative verification works with arithmetic operators + let source = r#" + fn add(a: Int, b: Int): Int is commutative = a + b + fn mul(a: Int, b: Int): Int is commutative = a * b + let result = add(10, 20) + "#; + let program = Parser::parse_source(source).expect("parse failed"); + let mut checker = TypeChecker::new(); + assert!(checker.check_program(&program).is_ok()); + } + + #[test] + fn test_behavioral_idempotent() { + use crate::parser::Parser; + use crate::typechecker::TypeChecker; + + // Idempotent verification works with abs pattern + let source = r#" + fn absolute(x: Int): Int is idempotent = + if x < 0 then 0 - x else x + let result = absolute(-42) + "#; + let program = Parser::parse_source(source).expect("parse failed"); + let mut checker = TypeChecker::new(); + assert!(checker.check_program(&program).is_ok()); + } + + #[test] + fn test_behavioral_total() { + use crate::parser::Parser; + use crate::typechecker::TypeChecker; + + let source = r#" + 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) + let result = sumTo(10) + "#; + let program = Parser::parse_source(source).expect("parse failed"); + let mut checker = TypeChecker::new(); + assert!(checker.check_program(&program).is_ok()); + } + // Math module tests #[test] fn test_math_abs() {