Skip to content

alii/al

Repository files navigation

AL

A small, statically-typed, expression-oriented programming language.

Install

curl -fsSL al.alistair.sh/install.sh | bash

Usage

al run <file.al>      Run a program
al check <file.al>    Type-check without running
al build <file.al>    Print the AST

Overview

AL compiles to bytecode and runs on a stack-based virtual machine. The compiler is written in V, producing a single native binary with no dependencies.

Statically typed with inference. Every expression has a type known at compile time. The type checker catches errors before your code runs, while inference keeps the syntax clean.

Expression-oriented. No statements—if/else, match, and blocks all return values.

Unified error handling. Both optional values (?T) and errors (T!E) use the same or syntax.

Error messages

The parser and type checker recover from errors to report multiple issues at once:

error: Unexpected ')'
  --> example.al:3:8
   |
3  |     x = )
   |         ^

error: Unexpected ']'
  --> example.al:7:12
   |
7  |     value = ]
   |             ^

Found 2 errors

Type errors are caught at compile time:

error: Type mismatch: expected Int, got String
  --> example.al:5:12
   |
5  |     return 'oops'
   |            ^^^^^^

error: Unknown variable: 'undefined_var'
  --> example.al:8:5
   |
8  |     undefined_var + 1
   |     ^^^^^^^^^^^^^

Found 2 errors

Language

Everything is an expression

No statements. If/else, match, and blocks all return values.

result = if x > 0 { 'positive' } else { 'negative' }

grade = match score {
    90..100 -> 'A',
    80..90 -> 'B',
    else -> 'C',
}

Optional values

Functions that might not return a value use ? in their return type. Handle with or.

fn find_user(id Int) ?User {
    if id == 0 { none } else { User{ id: id, name: 'found' } }
}

user = find_user(0) or User{ id: 0, name: 'guest' }

Error handling

Functions that can fail use ! with an error type. Handle with or.

fn divide(a Int, b Int) Int!DivisionError {
    if b == 0 {
        error DivisionError{ message: 'divide by zero' }
    } else {
        a / b
    }
}

safe = divide(10, 0) or 0
result = divide(10, 2) or err -> {
    println('Error: ${err.message}')
    0
}

Pattern matching

Match on values, enums, and literal payloads.

enum Result {
    Ok(String),
    Err(String),
}

fn handle(r Result) String {
    match r {
        Ok('special') -> 'matched literal',
        Ok(value) -> 'got: $value',
        Err(e) -> 'error: $e',
    }
}

Structs and enums

struct Person {
    name String,
    age Int,
}

enum Status {
    Active,
    Inactive,
    Banned(String),
}

person = Person{ name: 'alice', age: 30 }
status = Status.Banned('spam')

First-class functions

fn apply(x Int, f fn(Int) Int) Int {
    f(x)
}

double = fn(n Int) Int { n * 2 }
result = apply(5, double)

String interpolation

name = 'world'
greeting = 'Hello, $name!'
math = 'Result: ${1 + 2 * 3}'

Static typing with inference

Types are inferred from context. Annotate when needed, skip when obvious.

// Types inferred
count = 42
name = 'alice'
numbers = [1, 2, 3]

// Explicit annotations
fn add(a Int, b Int) Int {
    a + b
}

Generics

Use lowercase type variables for polymorphic functions.

fn identity(x a) a {
    x
}

fn first(arr []a) a {
    arr[0]
}

fn map(arr []a, f fn(a) b) []b {
    result = []
    for item in arr {
        result = result + [f(item)]
    }
    result
}

// Works with any type
x = identity(42)
y = identity('hello')
head = first([1, 2, 3])

License

MIT

About

A small, statically-typed, expression-oriented programming language.

Resources

Stars

Watchers

Forks