Skip to content

Programming language combining affine types, dependent types, and extensible effects - compiling to WebAssembly

License

Notifications You must be signed in to change notification settings

hyperpolymath/affinescript

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AffineScript

MPL-2.0 Palimpsest

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.

Overview

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

Key Features

Feature Description

Affine Types

Track resource usage with quantities: 0 (erased), 1 (linear), ω (unrestricted)

Dependent Types

Types that depend on values—length-indexed vectors, refinement types

Row Polymorphism

Extensible records with {x: Int, ..r} syntax for flexible data structures

Extensible Effects

User-defined effects with effect declarations and handle/resume

Ownership

own, ref, mut modifiers for memory safety without GC

Totality Checking

Mark functions as total to prove termination

WebAssembly Target

Compiles to WASM for portable, high-performance execution

Try It Now! 🚀

Browser Playground: The AffineScript interpreter runs directly in your browser via js_of_ocaml!

  • Try it: affinescript-playground

  • No installation needed - open test.html in 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: 120

Language Examples

Hello World with Effects

effect IO {
  fn print(s: String);
  fn println(s: String);
}

fn main() -> () / IO {
  println("Hello, AffineScript!");
}

Ownership and Resource Safety

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
}

Row Polymorphism

// 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);    // ✓
}

Dependent Types: Length-Indexed Vectors

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))
  }
}

Project Status

Implementation Progress

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

lex, parse, check, eval, compile, fmt, lint, repl

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

What Works Today

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 .as files to .wasm with 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 (fmt command)

  • Static Analysis — Linter with 4 rules (lint command)

  • 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 resume support

# 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 -- repl

IDE Support

AffineScript has complete IDE tooling for a professional development experience:

VSCode Extension

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 .

Language Server (LSP)

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 check for analysis

Build:

cd tools/affinescript-lsp
cargo build --release

Tree-sitter Grammar

Located 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" }
}

Code Quality Tools

  • Formatter (affinescript fmt) — AST-based pretty printer

  • Linter (affinescript lint) — 4 static analysis rules:

    • L001: Unused variables

    • L002: Missing effect annotations

    • L003: Dead code after return

    • L004: Naming convention violations

Building

Prerequisites

  • OCaml 5.1+

  • Dune 3.0+

  • opam packages: sedlex, menhir, ppx_deriving, cmdliner, alcotest

Commands

# Build
dune build

# Run tests
dune runtest

# Format code
dune fmt

# Generate documentation
dune build @doc

# Run compiler
dune exec affinescript -- <command> <file>

Repository Structure

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)

Documentation

  • 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

Design Philosophy

  1. Safety by default — Ownership and effects make unsafe code explicit

  2. Types as documentation — Dependent types encode invariants in the type system

  3. Composable abstractions — Row polymorphism and effects compose cleanly

  4. No runtime cost for safety — Linear types enable compile-time resource management

  5. Partial by default — Functions may diverge unless marked total

License

SPDX-License-Identifier: PMPL-1.0-or-later