diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..43c4a51 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,256 @@ +# AffineScript v0.1.0 - Reference Parser Release + +This is the first public release of AffineScript, featuring a complete specification and reference parser. + +## What's Included + +### Specification +- `SPEC.md` - Condensed language specification (essential grammar and semantics) +- `affinescript-spec.md` - Complete language specification v2.0 + +### Reference Implementation +- **Lexer** (sedlex) - Complete tokenization +- **Parser** (menhir) - Complete parsing to AST +- **AST** - Full abstract syntax tree definitions +- **Error Handling** - Structured diagnostics with source locations + +### Examples +- `examples/hello.as` - Hello World with effects +- `examples/vectors.as` - Dependent types with length-indexed vectors +- `examples/ownership.as` - Ownership and borrowing patterns +- `examples/rows.as` - Row polymorphism +- `examples/effects.as` - Effect handling and state +- `examples/traits.as` - Traits and type classes +- `examples/refinements.as` - Refinement types + +## Building from Source + +### Prerequisites + +- OCaml 5.1+ +- opam 2.1+ +- dune 3.14+ + +### Quick Start + +```bash +# Clone the repository +git clone https://github.com/hyperpolymath/affinescript.git +cd affinescript + +# Install dependencies +opam install . --deps-only + +# Build +dune build + +# Run tests +dune runtest + +# Install locally +dune install +``` + +### Using Guix (Preferred) + +```bash +guix shell -f guix.scm +dune build +``` + +### Using Nix + +```bash +nix develop +dune build +``` + +## Usage + +### Lex a File + +```bash +dune exec affinescript -- lex examples/hello.as +``` + +Output: +``` +EFFECT @ 1:1-1:7 +UPPER_IDENT(IO) @ 1:8-1:10 +LBRACE @ 1:11-1:12 +FN @ 2:3-2:5 +... +``` + +### Parse a File + +```bash +dune exec affinescript -- parse examples/hello.as +``` + +Output: +``` +{ prog_module = None; prog_imports = []; + prog_decls = + [TopEffect { ed_vis = Private; ed_name = { name = "IO"; ... }; ...}; + TopFn { fd_vis = Private; fd_total = false; fd_name = { name = "main"; ...}; ...}] +} +``` + +### Check a File (WIP) + +```bash +dune exec affinescript -- check examples/hello.as +``` + +Note: Type checking is not yet implemented in this release. + +## Implementation Status + +| Component | Status | Notes | +|-----------|--------|-------| +| Lexer | Complete | All tokens, comments, string escapes | +| Parser | Complete | Full grammar, 50+ test cases | +| AST | Complete | All language constructs | +| Diagnostics | Complete | Structured errors with locations | +| Name Resolution | Not Started | Planned for v0.2 | +| Type Checker | Not Started | Planned for v0.2 | +| Borrow Checker | Not Started | Planned for v0.3 | +| Effect Checker | Not Started | Planned for v0.3 | +| WASM Codegen | Not Started | Planned for v0.4 | + +## Language Features + +### Affine Types (Ownership) + +```affinescript +type File = own { fd: Int } + +fn processFile(file: own File) -> () / IO { + // file is consumed here - cannot use after + close(file) +} + +fn readFile(file: ref File) -> String / IO { + // Borrows file - doesn't consume it + read(file) +} +``` + +### Dependent Types + +```affinescript +type Vec[n: Nat, T: Type] = + | Nil : Vec[0, T] + | Cons(T, Vec[n, T]) : Vec[n + 1, T] + +// Type system prevents calling on empty vectors +total fn head[n: Nat, T](v: Vec[n + 1, T]) -> T / Pure { + match v { Cons(h, _) => h } +} +``` + +### Row Polymorphism + +```affinescript +// Works on any record with 'name' field +fn greet[..r](person: {name: String, ..r}) -> String / Pure { + "Hello, " ++ person.name +} + +// Both work: +greet({name: "Alice", age: 30}) +greet({name: "Bob", role: "Engineer"}) +``` + +### Extensible Effects + +```affinescript +effect State[S] { + fn get() -> S; + fn put(s: S); +} + +fn counter() -> Int / State[Int] { + let n = State.get(); + State.put(n + 1); + n +} + +// Handle the effect +handle counter() with { + return x => x, + get() => resume(0), + put(s) => resume(()) +} +``` + +## Running Tests + +```bash +# All tests +dune runtest + +# With verbose output +dune runtest --force --verbose + +# Specific test suite +dune exec test/test_main.exe -- test "Lexer" +dune exec test/test_main.exe -- test "Parser" +``` + +## Documentation + +```bash +# Generate documentation +dune build @doc + +# View in browser +open _build/default/_doc/_html/index.html +``` + +## File Structure + +``` +affinescript/ +├── lib/ # Core compiler library +│ ├── ast.ml # Abstract syntax tree +│ ├── token.ml # Token definitions +│ ├── lexer.ml # Sedlex-based lexer +│ ├── parser.mly # Menhir grammar +│ ├── parse.ml # Parser wrapper +│ ├── span.ml # Source locations +│ └── error.ml # Diagnostics +├── bin/ # CLI executable +│ └── main.ml # Command-line interface +├── test/ # Test suite +│ ├── test_lexer.ml # Lexer tests +│ └── test_parser.ml # Parser tests +├── examples/ # Example programs +├── wiki/ # Documentation +├── SPEC.md # Condensed specification +├── affinescript-spec.md # Full specification +└── RELEASE.md # This file +``` + +## Contributing + +Contributions welcome! Areas of interest: + +1. **Type Checker** - Bidirectional type checking with dependent types +2. **Borrow Checker** - Ownership verification +3. **Effect Checker** - Effect tracking and handling +4. **Standard Library** - Core types and functions +5. **WASM Backend** - Code generation + +See `affinescript-spec.md` Part 10 for implementation guidance. + +## License + +MIT License - see LICENSE file. + +## Links + +- Repository: https://github.com/hyperpolymath/affinescript +- Specification: See `SPEC.md` or `affinescript-spec.md` +- Issues: https://github.com/hyperpolymath/affinescript/issues diff --git a/SPEC.md b/SPEC.md new file mode 100644 index 0000000..9c6f878 --- /dev/null +++ b/SPEC.md @@ -0,0 +1,538 @@ +# AffineScript Language Specification v0.1 + +A programming language combining affine types, dependent types, row polymorphism, and extensible effects. + +## Overview + +AffineScript is designed for safe, efficient systems programming with: + +- **Affine Types**: Rust-style ownership ensuring memory safety without GC +- **Dependent Types**: Types that depend on values (e.g., `Vec[n, T]`) +- **Row Polymorphism**: Extensible records with compile-time field tracking +- **Extensible Effects**: User-defined, tracked side effects +- **WASM Target**: Compiles to WebAssembly for portable execution + +## 1. Lexical Grammar + +### 1.1 Identifiers + +``` +lower_ident = [a-z][a-zA-Z0-9_]* +upper_ident = [A-Z][a-zA-Z0-9_]* +row_var = ".." lower_ident +``` + +### 1.2 Keywords + +``` +fn let mut own ref type struct enum trait impl effect handle +resume handler match if else while for return break continue in +true false where total module use pub as unsafe assume transmute +forget Nat Int Bool Float String Type Row +``` + +### 1.3 Literals + +``` +int_lit = [0-9]+ | 0x[0-9a-fA-F]+ | 0b[01]+ | 0o[0-7]+ +float_lit = [0-9]+ "." [0-9]+ ([eE][+-]?[0-9]+)? +char_lit = "'" (escape | [^'\\]) "'" +string_lit = '"' (escape | [^"\\])* '"' +bool_lit = "true" | "false" +unit_lit = "()" +``` + +### 1.4 Operators + +``` +Arithmetic: + - * / % +Comparison: == != < > <= >= +Logical: && || ! +Bitwise: & | ^ ~ << >> +Type-level: -> => : / +Special: \ (row restriction) +``` + +### 1.5 Quantity Annotations + +``` +0 Erased (compile-time only) +1 Linear (use exactly once) +ω Unrestricted (use any number of times) +``` + +## 2. Syntactic Grammar + +### 2.1 Program Structure + +```ebnf +program = [module_decl] {import_decl} {top_level} +top_level = fn_decl | type_decl | trait_decl | impl_block | effect_decl +``` + +### 2.2 Type Declarations + +```ebnf +type_decl = [visibility] "type" UPPER_IDENT [type_params] "=" type_body + +type_params = "[" type_param {"," type_param} "]" +type_param = [quantity] IDENT [":" kind] + +kind = "Type" | "Nat" | "Row" | "Effect" | kind "->" kind + +type_body = type_expr (* alias *) + | struct_body (* record *) + | enum_body (* variant *) + +struct_body = "{" field {"," field} "}" +enum_body = ["|"] variant {"|" variant} +variant = UPPER_IDENT ["(" type_expr {"," type_expr} ")"] [":" type_expr] +``` + +### 2.3 Type Expressions + +```ebnf +type_expr = type_atom + | type_expr "->" type_expr ["/" effects] (* function *) + | "(" [quantity] IDENT ":" type_expr ")" "->" type_expr ["/" effects] + | type_expr "where" "(" predicate ")" (* refinement *) + +type_atom = PRIM_TYPE | UPPER_IDENT | TYPE_VAR + | UPPER_IDENT "[" type_arg {"," type_arg} "]" + | "own" type_atom | "ref" type_atom | "mut" type_atom + | "{" row_fields "}" (* record *) + | "(" type_expr {"," type_expr} ")" (* tuple *) + +row_fields = field_type {"," field_type} ["," row_var] + | row_var + +effects = effect_term {"+" effect_term} +effect_term = UPPER_IDENT ["[" type_arg {"," type_arg} "]"] +``` + +### 2.4 Function Declarations + +```ebnf +fn_decl = [visibility] ["total"] "fn" LOWER_IDENT + [type_params] "(" [param_list] ")" + ["->" type_expr] ["/" effects] + [where_clause] fn_body + +param_list = param {"," param} +param = [quantity] [ownership] IDENT ":" type_expr +ownership = "own" | "ref" | "mut" + +where_clause = "where" constraint {"," constraint} +constraint = predicate | TYPE_VAR ":" trait_bounds + +fn_body = block | "=" expr +``` + +### 2.5 Expressions + +```ebnf +expr = let_expr | if_expr | match_expr | fn_expr + | handle_expr | return_expr | binary_expr + +let_expr = "let" ["mut"] pattern [":" type_expr] "=" expr ["in" expr] +if_expr = "if" expr block ["else" (if_expr | block)] +match_expr = "match" expr "{" {match_arm} "}" +match_arm = pattern ["if" expr] "=>" expr [","] + +fn_expr = "|" [param_list] "|" expr + | "fn" "(" [param_list] ")" fn_body + +handle_expr = "handle" expr "with" "{" {handler_arm} "}" +handler_arm = "return" pattern "=>" expr [","] + | LOWER_IDENT "(" [pattern {"," pattern}] ")" "=>" expr [","] + +block = "{" {statement} [expr] "}" + +binary_expr = unary_expr {BINARY_OP unary_expr} +unary_expr = [UNARY_OP] postfix_expr +postfix_expr = primary_expr {postfix} +postfix = "." IDENT | "." INT | "[" expr "]" | "(" [args] ")" + | "::" UPPER_IDENT | "\\" IDENT + +primary_expr = literal | IDENT + | "(" expr ")" | "(" expr {"," expr} ")" + | "[" [expr {"," expr}] "]" + | "{" [field_init {"," field_init}] [".." expr] "}" + | "resume" "(" [expr] ")" +``` + +### 2.6 Patterns + +```ebnf +pattern = "_" (* wildcard *) + | IDENT (* binding *) + | literal (* literal match *) + | UPPER_IDENT ["(" pattern {"," pattern} ")"] + | "(" pattern {"," pattern} ")" (* tuple *) + | "{" field_pat {"," field_pat} [".." ] "}" + | pattern "|" pattern (* or-pattern *) + | IDENT "@" pattern (* binding with pattern *) +``` + +### 2.7 Effect Declarations + +```ebnf +effect_decl = [visibility] "effect" UPPER_IDENT [type_params] + "{" {effect_op} "}" +effect_op = "fn" LOWER_IDENT "(" [param_list] ")" ["->" type_expr] ";" +``` + +### 2.8 Trait Declarations + +```ebnf +trait_decl = [visibility] "trait" UPPER_IDENT [type_params] + [":" trait_bounds] "{" {trait_item} "}" +trait_bounds = UPPER_IDENT {"+" UPPER_IDENT} +trait_item = fn_sig ";" | fn_decl | assoc_type + +impl_block = "impl" [type_params] [trait_ref "for"] type_expr + [where_clause] "{" {impl_item} "}" +``` + +## 3. Type System + +### 3.1 Judgement Forms + +``` +Γ ⊢ e : τ / ε Expression e has type τ with effects ε +Γ ⊢ τ : κ Type τ has kind κ +Γ ⊢ P true Predicate P is satisfied +``` + +### 3.2 Quantities (QTT) + +| Quantity | Meaning | Usage | +|----------|---------|-------| +| `0` | Erased | Compile-time only, no runtime cost | +| `1` | Linear | Must use exactly once | +| `ω` | Unrestricted | Use any number of times | + +**Algebra:** +``` +0 + q = q 0 * q = 0 +1 + 1 = ω 1 * q = q +ω + ω = ω ω * ω = ω +``` + +### 3.3 Ownership + +| Modifier | Meaning | +|----------|---------| +| `own T` | Owned value - caller transfers ownership | +| `ref T` | Immutable borrow - cannot modify | +| `mut T` | Mutable borrow - exclusive access | + +**Rules:** +- Owned values are consumed on use +- Multiple `ref` borrows allowed simultaneously +- Only one `mut` borrow at a time +- Borrows cannot outlive owner + +### 3.4 Effects + +Functions declare effects after `/`: + +```affinescript +fn pure_fn(x: Int) -> Int / Pure { x + 1 } +fn io_fn() -> () / IO { println("hello") } +fn fallible() -> Int / Exn[Error] { throw(Error::new()) } +fn combined() -> () / IO + Exn[Error] { ... } +``` + +**Partial by Default:** +- Functions are partial by default (may not terminate) +- `total` functions must provably terminate + +### 3.5 Dependent Types + +Types can depend on values: + +```affinescript +type Vec[n: Nat, T: Type] = + | Nil : Vec[0, T] + | Cons(T, Vec[n, T]) : Vec[n + 1, T] + +// Can only call on non-empty vectors +fn head[n: Nat, T](v: Vec[n + 1, T]) -> T +``` + +### 3.6 Refinement Types + +Constrain types with predicates: + +```affinescript +type PosInt = Int where (self > 0) +fn safeDiv(a: Int, b: Int where (b != 0)) -> Int +``` + +### 3.7 Row Polymorphism + +Extensible records with row variables: + +```affinescript +// Works on any record with 'name' field +fn greet[..r](person: {name: String, ..r}) -> String { + "Hello, " ++ person.name +} + +// Add fields +fn addAge[..r](rec: {..r}) -> {age: Int, ..r} { + {age: 0, ..rec} +} + +// Remove fields +fn removeName[..r](rec: {name: String, ..r}) -> {..r} { + rec \ name +} +``` + +## 4. Core Typing Rules + +### Variables +``` +Γ, x :q τ ⊢ x : τ / ∅ +``` + +### Functions +``` +Γ, x :q τ₁ ⊢ e : τ₂ / ε +──────────────────────────────── +Γ ⊢ fn(x: τ₁) => e : τ₁ -> τ₂ / ε +``` + +### Application +``` +Γ ⊢ f : τ₁ -> τ₂ / ε₁ Γ ⊢ e : τ₁ / ε₂ +────────────────────────────────────────── +Γ ⊢ f(e) : τ₂ / ε₁ + ε₂ +``` + +### Records +``` +Γ ⊢ e : {ℓ: τ, ..r} / ε +──────────────────────── +Γ ⊢ e.ℓ : τ / ε + +Γ ⊢ e : {..r} / ε +─────────────────────────────── +Γ ⊢ {ℓ: v, ..e} : {ℓ: τ, ..r} / ε +``` + +### Effect Handling +``` +handle (return v) with { return x => eᵣ, ... } + → eᵣ[x ↦ v] + +handle E[op(v)] with { op(x) => eₒₚ, ... } + → eₒₚ[x ↦ v, resume ↦ fn(y) => handle E[y] with {...}] +``` + +## 5. Standard Library (Core) + +### 5.1 Primitive Types + +```affinescript +type Nat // Natural numbers (0, 1, 2, ...) +type Int // 64-bit signed integers +type Float // 64-bit floats +type Bool // true | false +type String // UTF-8 string +type Char // Unicode scalar value +type Never // Uninhabited type +``` + +### 5.2 Standard Effects + +```affinescript +effect IO { + fn print(s: String); + fn println(s: String); + fn readLine() -> String; +} + +effect Exn[E] { + fn throw(err: E) -> Never; +} + +effect State[S] { + fn get() -> S; + fn put(s: S); +} +``` + +### 5.3 Option and Result + +```affinescript +type Option[T] = None | Some(T) + +type Result[T, E] = Ok(T) | Err(E) +``` + +### 5.4 Core Traits + +```affinescript +trait Eq { + fn eq(ref self, other: ref Self) -> Bool; +} + +trait Ord: Eq { + fn cmp(ref self, other: ref Self) -> Ordering; +} + +trait Show { + fn show(ref self) -> String; +} + +trait Clone { + fn clone(ref self) -> Self; +} + +trait Drop { + fn drop(own self); +} +``` + +## 6. Example Programs + +### 6.1 Hello World + +```affinescript +effect IO { + fn println(s: String); +} + +fn main() -> () / IO { + println("Hello, AffineScript!") +} +``` + +### 6.2 Length-Indexed Vector + +```affinescript +type Vec[n: Nat, T: Type] = + | Nil : Vec[0, T] + | Cons(head: T, tail: Vec[n, T]) : Vec[n + 1, T] + +total fn head[n: Nat, T](v: Vec[n + 1, T]) -> T / Pure { + match v { Cons(h, _) => h } +} + +total fn append[n: Nat, m: Nat, T]( + a: Vec[n, T], b: Vec[m, T] +) -> Vec[n + m, T] / Pure { + match a { + Nil => b, + Cons(h, t) => Cons(h, append(t, b)) + } +} +``` + +### 6.3 Ownership and Resources + +```affinescript +type File = own { fd: Int } + +fn open(path: ref String) -> Result[own File, IOError] / IO +fn read(file: ref File) -> Result[String, IOError] / IO +fn close(file: own File) -> Result[(), IOError] / IO + +fn withFile[T]( + path: ref String, + action: (ref File) -> Result[T, IOError] +) -> Result[T, IOError] / IO + Exn[IOError] { + let file = open(path)?; + let result = action(ref file); + close(file)?; + result +} +``` + +### 6.4 Row Polymorphism + +```affinescript +fn greet[..r](person: {name: String, ..r}) -> String / Pure { + "Hello, " ++ person.name +} + +fn addField[..r](rec: {..r}, age: Int) -> {age: Int, ..r} / Pure { + {age: age, ..rec} +} + +fn main() -> () / Pure { + let alice = {name: "Alice", role: "Engineer"}; + let bob = {name: "Bob", dept: "Sales"}; + + // Both work despite different shapes + greet(alice); // "Hello, Alice" + greet(bob); // "Hello, Bob" +} +``` + +### 6.5 Effect Handlers + +```affinescript +effect State[S] { + fn get() -> S; + fn put(s: S); +} + +fn counter() -> Int / State[Int] { + let n = State.get(); + State.put(n + 1); + n +} + +fn runState[S, T](init: S, comp: () -> T / State[S]) -> (T, S) / Pure { + handle comp() with { + return x => (x, init), + get() => resume(init), + put(s) => resume(()) + } +} +``` + +## 7. WASM Compilation + +### 7.1 Type Mapping + +| AffineScript | WASM | +|--------------|------| +| `Int`, `Nat` | `i64` | +| `Float` | `f64` | +| `Bool` | `i32` | +| `String` | `(ref (array i8))` | +| `{fields}` | `(ref (struct ...))` | +| `own T` | `(ref T)` (ownership is erased) | +| `T -> U` | `(ref (struct $func $env))` | + +### 7.2 Ownership Erasure + +Ownership and quantity annotations exist only at compile time: + +```affinescript +fn useFile(own file: File) -> () / IO { close(file) } +``` + +Compiles to (ownership removed): +```wat +(func $useFile (param $file (ref $File)) + (call $close (local.get $file))) +``` + +## Appendix: Grammar Reference + +See the full specification at `affinescript-spec.md` for: +- Complete EBNF grammar +- Detailed typing rules +- Operational semantics +- Error message catalog +- Implementation guide + +--- + +*AffineScript Specification v0.1 - Reference Implementation* diff --git a/examples/effects.as b/examples/effects.as new file mode 100644 index 0000000..0a090bf --- /dev/null +++ b/examples/effects.as @@ -0,0 +1,96 @@ +// Effect handling in AffineScript + +// Define a custom exception effect +effect Exn[E] { + fn throw(err: E) -> Never; +} + +// Define a state effect +effect State[S] { + fn get() -> S; + fn put(s: S); + fn modify(f: S -> S); +} + +// A computation that uses state +fn increment() -> Int / State[Int] { + let n = State.get(); + State.put(n + 1); + n +} + +fn triple() -> (Int, Int, Int) / State[Int] { + let a = increment(); + let b = increment(); + let c = increment(); + (a, b, c) +} + +// Run state effect with an initial value +fn runState[S, T](init: S, comp: () -> T / State[S]) -> (T, S) / Pure { + let mut state = init; + handle comp() with { + return x => (x, state), + get() => resume(state), + put(s) => { + state = s; + resume(()) + }, + modify(f) => { + state = f(state); + resume(()) + } + } +} + +// Error handling with effects +type ParseError = { line: Int, message: String } + +fn parseInt(s: String) -> Int / Exn[ParseError] { + // Simplified - would actually parse + if s == "42" { + 42 + } else { + Exn.throw(ParseError { line: 1, message: "not a number" }) + } +} + +fn parseTwo(a: String, b: String) -> (Int, Int) / Exn[ParseError] { + (parseInt(a), parseInt(b)) +} + +// Convert exceptions to Result +fn catchExn[E, T](comp: () -> T / Exn[E]) -> Result[T, E] / Pure { + handle comp() with { + return x => Ok(x), + throw(e) => Err(e) + } +} + +// Combine multiple effects +effect Log { + fn log(msg: String); +} + +fn processWithLogging(x: Int) -> Int / State[Int] + Log { + Log.log("Processing input"); + let current = State.get(); + let result = current + x; + State.put(result); + Log.log("Updated state"); + result +} + +fn main() -> () / IO { + // Run the state computation + let (result, finalState) = runState(0, || triple()); + println("Result: " ++ result.show()); + println("Final state: " ++ finalState.show()); + + // Handle exceptions + let parsed = catchExn(|| parseInt("42")); + match parsed { + Ok(n) => println("Parsed: " ++ n.show()), + Err(e) => println("Error: " ++ e.message) + } +} diff --git a/examples/refinements.as b/examples/refinements.as new file mode 100644 index 0000000..edd664d --- /dev/null +++ b/examples/refinements.as @@ -0,0 +1,115 @@ +// Refinement types in AffineScript + +// Positive integers - enforced at compile time +type PosInt = Int where (self > 0) + +// Non-zero integers for division +type NonZero = Int where (self != 0) + +// Percentage values (0-100) +type Percentage = Int where (self >= 0 && self <= 100) + +// Safe division - cannot divide by zero +fn safeDiv(a: Int, b: NonZero) -> Int / Pure { + a / b +} + +// Square root only on non-negative numbers +fn sqrt(n: Int where (n >= 0)) -> Float / Pure { + // Implementation + 0.0 +} + +// Array indexing with bounds checking at compile time +total fn safeGet[n: Nat, T]( + arr: ref Vec[n, T], + i: Nat where (i < n) +) -> ref T / Pure { + match (arr, i) { + (Cons(h, _), 0) => ref h, + (Cons(_, t), _) => safeGet(ref t, i - 1) + } +} + +// Bounded integers +type Age = Int where (self >= 0 && self <= 150) + +type Port = Int where (self >= 0 && self <= 65535) + +// Create validated values +fn mkPosInt(n: Int) -> Option[PosInt] / Pure { + if n > 0 { + Some(n) // Compiler verifies the refinement + } else { + None + } +} + +fn mkPercentage(n: Int) -> Option[Percentage] / Pure { + if n >= 0 && n <= 100 { + Some(n) + } else { + None + } +} + +// Using unsafe assume when you know better than the compiler +fn unsafePos(n: Int) -> PosInt / Pure { + unsafe { + assume(n > 0) + } + n +} + +// Length-indexed strings +type BoundedString[max: Nat] = String where (len(self) <= max) + +type Username = BoundedString[32] +type Email = BoundedString[254] + +// Matrices with compile-time dimension checking +type Matrix[rows: Nat, cols: Nat, T: Type] = Vec[rows, Vec[cols, T]] + +// Matrix multiplication with dimension constraints +total fn matmul[m: Nat, n: Nat, p: Nat]( + a: ref Matrix[m, n, Float], + b: ref Matrix[n, p, Float] +) -> Matrix[m, p, Float] / Pure { + // The types guarantee dimensions are compatible + // n in matrix 'a' must equal n in matrix 'b' + // ... + Nil +} + +// Refined function parameters +fn processAge(age: Age) -> String / Pure { + if age < 18 { + "minor" + } else if age < 65 { + "adult" + } else { + "senior" + } +} + +// Contracts on return values +fn double(n: PosInt) -> Int where (self > n) / Pure { + n * 2 // Compiler proves 2n > n for positive n +} + +fn main() -> () / IO { + // These compile - refinements satisfied + let age: Age = 25; + let port: Port = 8080; + let pct: Percentage = 75; + + // This would fail at compile time: + // let bad: Age = 200; // Error: cannot prove 200 <= 150 + + // Runtime validation for unknown values + let input = readLine(); + match mkPercentage(parseInt(input)) { + Some(p) => println("Valid percentage: " ++ p.show()), + None => println("Invalid percentage") + } +} diff --git a/examples/traits.as b/examples/traits.as new file mode 100644 index 0000000..c8b9a5a --- /dev/null +++ b/examples/traits.as @@ -0,0 +1,161 @@ +// Traits and type classes in AffineScript + +// Equality trait +trait Eq { + fn eq(ref self, other: ref Self) -> Bool / Pure; + + fn neq(ref self, other: ref Self) -> Bool / Pure { + !self.eq(other) + } +} + +// Ordering trait (requires Eq) +trait Ord: Eq { + fn cmp(ref self, other: ref Self) -> Ordering / Pure; + + fn lt(ref self, other: ref Self) -> Bool / Pure { + self.cmp(other) == Less + } + + fn le(ref self, other: ref Self) -> Bool / Pure { + self.cmp(other) != Greater + } + + fn gt(ref self, other: ref Self) -> Bool / Pure { + self.cmp(other) == Greater + } + + fn ge(ref self, other: ref Self) -> Bool / Pure { + self.cmp(other) != Less + } +} + +type Ordering = Less | Equal | Greater + +// Show trait for string representation +trait Show { + fn show(ref self) -> String / Pure; +} + +// Implement traits for Int +impl Eq for Int { + fn eq(ref self, other: ref Int) -> Bool / Pure { + *self == *other + } +} + +impl Ord for Int { + fn cmp(ref self, other: ref Int) -> Ordering / Pure { + if *self < *other { Less } + else if *self > *other { Greater } + else { Equal } + } +} + +impl Show for Int { + fn show(ref self) -> String / Pure { + intToString(*self) + } +} + +// Implement Show for Option +impl[T: Show] Show for Option[T] { + fn show(ref self) -> String / Pure { + match self { + None => "None", + Some(x) => "Some(" ++ x.show() ++ ")" + } + } +} + +// Implement Show for Result +impl[T: Show, E: Show] Show for Result[T, E] { + fn show(ref self) -> String / Pure { + match self { + Ok(x) => "Ok(" ++ x.show() ++ ")", + Err(e) => "Err(" ++ e.show() ++ ")" + } + } +} + +// Functor trait +trait Functor[F: Type -> Type] { + fn map[A, B](self: F[A], f: A -> B / Pure) -> F[B] / Pure; +} + +// Monad trait +trait Monad[M: Type -> Type]: Functor[M] { + fn pure[A](value: A) -> M[A] / Pure; + fn flatMap[A, B](self: M[A], f: A -> M[B] / Pure) -> M[B] / Pure; +} + +// Implement Functor for Option +impl Functor[Option] for Option { + fn map[A, B](self: Option[A], f: A -> B / Pure) -> Option[B] / Pure { + match self { + None => None, + Some(x) => Some(f(x)) + } + } +} + +// Implement Monad for Option +impl Monad[Option] for Option { + fn pure[A](value: A) -> Option[A] / Pure { + Some(value) + } + + fn flatMap[A, B](self: Option[A], f: A -> Option[B] / Pure) -> Option[B] / Pure { + match self { + None => None, + Some(x) => f(x) + } + } +} + +// Generic functions using trait bounds +fn max[T: Ord](a: T, b: T) -> T / Pure { + if a.gt(ref b) { a } else { b } +} + +fn min[T: Ord](a: T, b: T) -> T / Pure { + if a.lt(ref b) { a } else { b } +} + +fn printAny[T: Show](value: ref T) -> () / IO { + println(value.show()) +} + +fn sortBy[T, K: Ord](items: mut [T], key: (ref T) -> K / Pure) -> () / Pure { + // Sorting implementation using Ord trait + // ... +} + +// Custom type with trait implementations +type Point = { x: Int, y: Int } + +impl Eq for Point { + fn eq(ref self, other: ref Point) -> Bool / Pure { + self.x == other.x && self.y == other.y + } +} + +impl Show for Point { + fn show(ref self) -> String / Pure { + "(" ++ self.x.show() ++ ", " ++ self.y.show() ++ ")" + } +} + +fn main() -> () / IO { + let p1 = Point { x: 1, y: 2 }; + let p2 = Point { x: 3, y: 4 }; + + printAny(ref p1); + printAny(ref p2); + + let m = max(10, 20); + println("Max: " ++ m.show()); + + let opt = Some(42); + printAny(ref opt); +}