A Rust-inspired programming language combining affine types, dependent types, row polymorphism, and extensible effects—compiling to WebAssembly with no garbage collector.
AffineScript brings the safety of linear types, the expressiveness of dependent types, and the modularity of algebraic effects into a cohesive, practical language.
📖 New to this repository? See Navigation Guide for directory structure and quick links.
AffineScript is designed for systems programming where correctness matters. It combines ideas from:
-
Rust — ownership, borrowing, no GC
-
Idris/Agda — dependent types, totality checking
-
PureScript/Koka — row polymorphism, algebraic effects
-
Linear Haskell — quantitative type theory
| Feature | Description |
|---|---|
Affine Types |
Track resource usage with quantities: |
Dependent Types |
Types that depend on values—length-indexed vectors, refinement types |
Row Polymorphism |
Extensible records with |
Extensible Effects |
User-defined effects with |
Ownership |
|
Totality Checking |
Mark functions as |
WebAssembly Target |
Compiles to WASM for portable, high-performance execution |
Browser Playground: The AffineScript interpreter runs directly in your browser via js_of_ocaml!
-
Try it: affinescript-playground
-
No installation needed - open
test.htmlin any browser -
Full interpreter - all features work client-side
-
Interactive - edit code and see results instantly
Example code you can try:
// Exception handling
try {
10 / 0
} catch {
DivisionByZero => 42
}
// Recursion
fn factorial(n: Int) -> Int {
if n <= 1 { 1 } else { n * factorial(n - 1) }
}
factorial(5) // Returns: 120effect IO {
fn print(s: String);
fn println(s: String);
}
fn main() -> () / IO {
println("Hello, AffineScript!");
}type File = own { fd: Int }
fn open(path: ref String) -> Result[own File, IOError] / IO + Exn[IOError] {
Ok(File { fd: 42 })
}
fn read(file: ref File) -> Result[String, IOError] / IO {
// Borrows file — doesn't consume it
Ok("file contents")
}
fn close(file: own File) -> Result[(), IOError] / IO {
// Consumes file — can't use it after this
Ok(())
}
// Safe resource handling with RAII pattern
fn withFile[T](
path: ref String,
action: (ref File) -> Result[T, IOError] / IO
) -> Result[T, IOError] / IO {
let file = open(path)?;
let result = action(ref file);
close(file)?;
result
}// Works on any record that has a 'name' field
fn greet[..r](person: {name: String, ..r}) -> String / Pure {
"Hello, " ++ person.name
}
// Extend records while preserving other fields
fn fullName[..r](
person: {first: String, last: String, ..r}
) -> {first: String, last: String, fullName: String, ..r} / Pure {
{fullName: person.first ++ " " ++ person.last, ..person}
}
fn main() -> () / Pure {
let alice = {name: "Alice", age: 30, role: "Engineer"};
let bob = {name: "Bob", department: "Sales"};
// Both work despite different record shapes
let greeting1 = greet(alice); // ✓
let greeting2 = greet(bob); // ✓
}type Vec[n: Nat, T: Type] =
| Nil : Vec[0, T]
| Cons(head: T, tail: Vec[n, T]) : Vec[n + 1, T]
// Can only be called on non-empty vectors — enforced by types!
total fn head[n: Nat, T](v: Vec[n + 1, T]) -> T / Pure {
match v {
Cons(h, _) => h
}
}
// Concatenate: result length is sum of input lengths
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))
}
}| Component | Status | Details |
|---|---|---|
Lexer |
✅ Complete |
sedlex-based, Unicode support, full test coverage |
Parser Grammar |
✅ Complete |
615-line Menhir grammar covering entire syntax |
Abstract Syntax Tree |
✅ Complete |
395 lines representing all language constructs |
Error Handling |
✅ Complete |
Rich diagnostics with 50+ error codes, colored output |
CLI Interface |
✅ Complete |
|
Name Resolution |
✅ Complete |
Module loader with stdlib imports, scope analysis |
Type Checker |
✅ 98% Complete |
Bidirectional inference with effect system, subsumption |
Effect System |
✅ Complete |
Pure/impure separation, effect polymorphism, safety enforcement |
Borrow Checker |
✅ 95% Complete |
Use-after-move detection, ownership tracking |
Trait System |
🚧 70% Complete |
Trait registry, impl validation, method resolution |
Interpreter |
✅ 85% Complete |
Pattern matching, control flow, basic effect handlers |
Code Generation |
🚧 75% Complete |
WASM IR, binary encoder, WASI I/O runtime |
Standard Library |
🚧 65% Complete |
Core, Result, Option, Math, Traits, Effects modules |
Formatter |
✅ Complete |
AST-based pretty printer with configurable style |
Linter |
✅ Complete |
4 lint rules: unused vars, missing effects, dead code, naming |
IDE Tooling |
✅ Complete |
VSCode extension, LSP server, Tree-sitter grammar |
The AffineScript compiler is 85% complete with a working end-to-end toolchain:
✅ Complete Features:
-
Full Language Frontend — Lex, parse, name resolution with module system
-
Type Checking — Bidirectional inference with effect tracking and subsumption
-
Effect System — Pure/impure separation enforced at compile time
-
Borrow Checking — Use-after-move detection with ownership tracking
-
WebAssembly Compilation — Compiles
.asfiles to.wasmwith WASI I/O -
Tree-Walking Interpreter — Run AffineScript code directly with
eval -
Interactive REPL — Read-eval-print loop for experimentation
-
Code Formatting — AST-based pretty printer (
fmtcommand) -
Static Analysis — Linter with 4 rules (
lintcommand) -
IDE Support — VSCode extension, LSP server, Tree-sitter grammar
🚧 In Progress:
-
Trait System (70%) — Method resolution and generic trait bounds
-
Standard Library (65%) — Expanding Core, Collections, and I/O modules
-
Effect Handlers — Delimited continuations for full
resumesupport
# Tokenize a file
dune exec affinescript -- lex examples/hello.as
# Parse and display AST
dune exec affinescript -- parse examples/ownership.as
# Type check a file
dune exec affinescript -- check examples/hello.as
# Run with interpreter
dune exec affinescript -- eval examples/factorial.as
# Compile to WebAssembly
dune exec affinescript -- compile examples/hello.as -o hello.wasm
# Format code
dune exec affinescript -- fmt examples/hello.as
# Lint for code quality
dune exec affinescript -- lint examples/hello.as
# Start REPL
dune exec affinescript -- replAffineScript has complete IDE tooling for a professional development experience:
Located in editors/vscode/:
-
Syntax Highlighting — TextMate grammar with full language support
-
Commands — Check, eval, compile, format with keyboard shortcuts
-
LSP Integration — Real-time diagnostics powered by affinescript-lsp
-
Language Configuration — Auto-closing brackets, comment toggling
Installation:
cd editors/vscode
npm install
code --install-extension .Located in tools/affinescript-lsp/:
-
Real-time Diagnostics — Type errors, borrow errors shown as you type
-
Powered by Rust — Uses tower-lsp for LSP protocol
-
Compiler Integration — Calls
affinescript checkfor analysis
Build:
cd tools/affinescript-lsp
cargo build --releaseLocated in editors/tree-sitter-affinescript/:
-
Incremental Parsing — Fast syntax analysis for modern editors
-
Syntax Highlighting — Queries for Neovim, Emacs, Helix
-
Full Coverage — All AffineScript constructs supported
Installation (Neovim):
require('nvim-treesitter.configs').setup {
parser_install_dir = "~/.local/share/nvim/site/parser",
ensure_installed = { "affinescript" }
}affinescript/
├── lib/ # Core compiler library
│ ├── ast.ml # Abstract syntax tree (395 lines)
│ ├── token.ml # Token definitions (222 lines)
│ ├── lexer.ml # Lexer — sedlex-based (323 lines)
│ ├── parser.mly # Parser — Menhir grammar (615 lines)
│ ├── parse.ml # Parser driver
│ ├── span.ml # Source location tracking
│ └── error.ml # Diagnostics and error handling
├── bin/ # CLI executable
│ └── main.ml # Command-line interface
├── test/ # Test suite
│ ├── test_lexer.ml # Lexer tests (~145 cases)
│ └── test_parser.ml # Parser tests (~80 cases)
├── examples/ # Example programs
│ ├── hello.as # Basic IO effect
│ ├── ownership.as # Resource management
│ ├── rows.as # Row polymorphism
│ └── vectors.as # Dependent types
├── docs/ # Documentation
│ └── wiki/ # Compiler & language documentation
└── affinescript-spec.md # Complete language specification (53KB)-
affinescript-spec.md— Complete language specification -
docs/wiki/compiler/— Compiler architecture and phase documentation -
docs/wiki/language-reference/— Language feature guides -
docs/wiki/tutorials/— Getting started guides
-
Safety by default — Ownership and effects make unsafe code explicit
-
Types as documentation — Dependent types encode invariants in the type system
-
Composable abstractions — Row polymorphism and effects compose cleanly
-
No runtime cost for safety — Linear types enable compile-time resource management
-
Partial by default — Functions may diverge unless marked
total