An open API service indexing awesome lists of open source software.

https://github.com/audulus/lyte

A programming language for Audulus nodes
https://github.com/audulus/lyte

Last synced: 2 months ago
JSON representation

A programming language for Audulus nodes

Awesome Lists containing this project

README

          

# lyte

![build status](https://github.com/audulus/lyte/actions/workflows/rust.yml/badge.svg)
[![dependency status](https://deps.rs/repo/github/audulus/lyte/status.svg)](https://deps.rs/repo/github/audulus/lyte)

Lyte is a simple, statically-typed programming language designed for writing [Audulus](https://audulus.com) nodes. It compiles to native code via Cranelift or LLVM, with a VM backend for environments where JIT isn't available (iOS).

## Goals

- Familiar syntax (a mix of Rust and Swift)
- Function and operator overloading
- Generics constrained by interfaces
- Type inference
- No GC — no recursive data structures, no heap allocation
- Memory safety with static bounds checking
- Cranelift and LLVM JIT backends
- VM backend for sandboxed environments (iOS)
- Safe cancellation of long-running programs

## Quick Start

```bash
# Build
cargo build

# Run a file with JIT
cargo run --bin lyte hello.lyte -c

# Run a file with VM
cargo run --bin lyte hello.lyte -r

# Run all tests
cargo test --workspace
```

### LLVM JIT Backend (Optional)

Lyte includes an optional LLVM JIT backend alongside the default Cranelift backend. To use it, you need LLVM 18 installed:

```bash
# macOS (Homebrew)
brew install llvm@18

# Build with LLVM support (requires the "llvm" feature)
LLVM_SYS_180_PREFIX=/opt/homebrew/opt/llvm@18 LIBRARY_PATH="/opt/homebrew/lib:$LIBRARY_PATH" \
cargo build --features llvm

# Run a file with the LLVM backend
LLVM_SYS_180_PREFIX=/opt/homebrew/opt/llvm@18 LIBRARY_PATH="/opt/homebrew/lib:$LIBRARY_PATH" \
cargo run -p lyte-cli --features llvm -- hello.lyte -l
```

On Linux, install LLVM 18 via your package manager (e.g. `apt install llvm-18-dev libzstd-dev`) and set `LLVM_SYS_180_PREFIX` to the install prefix (e.g. `/usr/lib/llvm-18`).

## Language Tour

### Hello World

```rust
main {
println("hello world")
}
```

### Variables and Control Flow

```rust
main {
var x = 42
let y = x + 1 // immutable binding

if x > 0 {
println("positive")
} else {
println("non-positive")
}

var sum = 0
for i in 0 .. 10 {
sum = sum + i
}
}
```

### Functions

Functions support type annotations, overloading, and implicit returns (the last expression is the return value).

```rust
add(a: i32, b: i32) -> i32 {
a + b
}

add(a: f32, b: f32) -> f32 {
a + b
}

fact(x: i32) -> i32 {
if x == 1 { return 1 }
x * fact(x - 1)
}

main {
assert(add(2, 3) == 5)
assert(add(1.0, 2.0) == 3.0)
assert(fact(5) == 120)
}
```

### Structs

```rust
struct Point {
x: f32,
y: f32
}

length(p: Point) -> f32 {
sqrt(p.x * p.x + p.y * p.y)
}

main {
var p: Point
p.x = 3.0
p.y = 4.0
assert(length(p) == 5.0)
}
```

### Operator Overloading

Define `__add`, `__sub`, `__mul`, etc. for custom types:

```rust
__add(lhs: Point, rhs: Point) -> Point {
var p: Point
p.x = lhs.x + rhs.x
p.y = lhs.y + rhs.y
p
}

__mul(lhs: Point, rhs: f32) -> Point {
var p: Point
p.x = lhs.x * rhs
p.y = lhs.y * rhs
p
}
```

### Enums

```rust
enum Direction { Up, Down, Left, Right }

is_vertical(d: Direction) -> bool {
(d == .Up) || (d == .Down)
}

main {
var d = Direction.Up
assert(is_vertical(d))
assert(!is_vertical(.Left))
}
```

### Arrays

Fixed-size arrays with static bounds checking:

```rust
main {
var a = [1, 2, 3, 4, 5]
assert(a[0] == 1)
assert(a.len == 5)

var grid: [f32; 64]
for i in 0 .. 64 {
grid[i] = 0.0
}
}
```

### Tuples

```rust
main {
var pair = (1, 2)
assert(pair.0 == 1)
assert(pair.1 == 2)
}
```

### Slices

Slices are dynamically-sized views into arrays, passed by reference:

```rust
sum(a: [i32]) -> i32 {
var s = 0
for i in 0 .. a.len {
s = s + a[i]
}
s
}

main {
assert(sum([1, 2, 3]) == 6)

var data = [10, 20, 30, 40, 50]
assert(sum(data) == 150)
}
```

### Lambdas and Closures

```rust
apply(x: i32, f: i32 -> i32) -> i32 {
f(x)
}

main {
var f = |x| x * 2
assert(f(3) == 6)

assert(apply(5, |x| x + 1) == 6)

// closures capture variables by reference
var count = 0
var inc = || count = count + 1
inc()
inc()
assert(count == 2)
}
```

### Generics

Generic functions infer type parameters from arguments:

```rust
id(x: T) -> T { x }

map(a: [T0; N], f: T0 -> T1) -> [T1; N] {
var i = 0
var b: [T1; N]
while i < a.len {
b[i] = f(a[i])
i = i + 1
}
b
}

main {
assert(id(42) == 42)
assert(id("hello") == "hello")
assert(map([1, 2, 3], |x| x + 1) == [2, 3, 4])
}
```

### Generic Structs

```rust
struct Pair {
first: A,
second: B
}

main {
var p: Pair
p.first = 1
p.second = 2.0
}
```

### Interfaces

Interfaces constrain generics and enable static dispatch:

```rust
interface Compare
{
cmp(lhs: A, rhs: A) -> i32
}

sort(array: [T; N]) -> [T; N] where Compare {
var a = array
var i = 0
while i < a.len {
var j = 0
while j < a.len - 1 - i {
if cmp(a[j], a[j + 1]) > 0 {
var tmp = a[j]
a[j] = a[j + 1]
a[j + 1] = tmp
}
j = j + 1
}
i = i + 1
}
a
}

// Implement Compare for i32
cmp(lhs: i32, rhs: i32) -> i32 { lhs - rhs }

main {
assert(sort([3, 1, 2]) == [1, 2, 3])
}
```

### A Real-World Example: Biquad Filter

```rust
struct Biquad {
b0: f32, b1: f32, b2: f32,
a1: f32, a2: f32,
x1: f32, x2: f32,
y1: f32, y2: f32
}

lpf(fc: f32, fs: f32, q: f32) -> Biquad {
var w0 = 2.0 * 3.14159265 * fc / fs
var alpha = sin(w0) / (2.0 * q)
var cs = cos(w0)
var a0 = 1.0 + alpha
var inv = 1.0 / a0

var bq: Biquad
bq.b1 = (1.0 - cs) * inv
bq.b0 = bq.b1 / 2.0
bq.b2 = bq.b0
bq.a1 = (0.0 - 2.0 * cs) * inv
bq.a2 = (1.0 - alpha) * inv
return bq
}

process(bq: Biquad, x: f32) -> (Biquad, f32) {
var y = bq.b0*x + bq.b1*bq.x1 + bq.b2*bq.x2
- bq.a1*bq.y1 - bq.a2*bq.y2
bq.x2 = bq.x1
bq.x1 = x
bq.y2 = bq.y1
bq.y1 = y
(bq, y)
}
```

## Language Server

Lyte includes an LSP server (`lyte-lsp`) that provides IDE features for any editor that supports the Language Server Protocol.

### Features

- **Diagnostics** — parse errors, type errors, and safety warnings as you type
- **Hover** — show inferred types for expressions and variables
- **Go to Definition** — jump to function and struct field definitions

### Building

```bash
cargo build -p lyte-lsp
```

The binary is at `target/debug/lyte-lsp` (or `target/release/lyte-lsp` with `--release`).

### VS Code

A VS Code extension is included in `vscode-lyte/`. To install it locally:

```bash
cd vscode-lyte
npm install
code --install-extension .
```

The extension activates on `.lyte` files and starts the language server automatically. Set `lyte.lspPath` in VS Code settings if the `lyte-lsp` binary isn't on your `PATH`.

### Other Editors

Point your editor's LSP client at the `lyte-lsp` binary. It communicates over stdin/stdout using the standard LSP protocol. For example, in Neovim with `nvim-lspconfig`:

```lua
vim.lsp.start({
name = 'lyte',
cmd = { 'lyte-lsp' },
filetypes = { 'lyte' },
})
```

## Embedding via Swift Package

Lyte is available as a Swift package for embedding in macOS and iOS apps. Add it to your `Package.swift`:

```swift
dependencies: [
.package(url: "https://github.com/audulus/lyte.git", from: "0.12.0")
]
```

The package auto-selects the best backend: LLVM JIT on ARM macOS, VM on iOS and other platforms.

```swift
let compiler = LyteCompiler(entryPoints: ["init", "process"])
try compiler.addSource(source, filename: "node.lyte")
let program = try compiler.compile()

let globals = program.allocGlobals()
let initEP = program.entryPoint(named: "init")!
let processEP = program.entryPoint(named: "process")!

initEP.call(globals: globals) // initialize state
processEP.call(globals: globals) // run on audio thread
```

Entry point handles are resolved once at compile time and called with zero overhead — no string lookups on the hot path.

## Releasing

Creating a release builds and publishes the xcframework automatically:

```bash
gh release create v0.12
```

This triggers the `release.yml` CI workflow which:

1. Builds `CLyte.xcframework` for macOS (ARM64 + x86_64) and iOS
2. Uploads `CLyte.xcframework.zip` as a release asset
3. Computes the checksum and updates `Package.swift` on main

The xcframework is self-contained — LLVM dependencies (zstd, ffi) are statically linked into the ARM64 macOS library. Swift consumers only need system libraries (libc++, libz, libcurses).

## Architecture

The compiler pipeline:

1. **Lexer** — tokenizes source text
2. **Parser** — builds an AST
3. **Type Checker** — Hindley-Milner inference with constraint solving
4. **Safety Checker** — static bounds checking and division-by-zero detection via abstract interpretation
5. **Code Generation** — Cranelift JIT, LLVM JIT, or VM bytecode

See [`src/README.md`](src/README.md) for detailed design notes.

## Benchmarks

Three benchmarks compare Lyte (VM, ARM64 VM, JIT, LLVM) against C, Lua 5.5, and LuaJIT:

| Benchmark | Description |
|-----------|-------------|
| **Biquad** | 10M samples through a 1kHz lowpass filter (DSP) |
| **Sort** | Quicksort 10K elements × 50 iterations |
| **FFT** | 1024-point radix-2 FFT × 2000 iterations |

Results from the latest CI run are in the [Actions workflow summary](../../actions/workflows/rust.yml) (click the latest run, scroll to "Benchmark Results").

```bash
./benchmark/run.sh # run locally (requires lua and luajit)
```