Skip to content

About

Sushi Lang is a statically-typed compiled language, made out of sheer boredom and curiosity.

Resources

Stars

6 stars

Watchers

0 watching

Forks

Repository files navigation

Sushi Lang

Tests Sushi tests Python tests

A compiled programming language with a Lark parser frontend and LLVM backend that produces native binaries.

PARENTAL ADVISORY

This project is a result of a thought process when I asked myself "how hard is it to create a programming language?" Then I went and wrote:

fn main():
    return(0)

I spent a couple of hours reading about grammar parsers, AST, LLVM, and similar stuff, and then after some time I had a compiled executable file that did nothing.

Then I started working on print() and then on variables, soon after this I realized that this is going way too slow and I will need to ask peoplerobots to help me. Then things really took off.

Long story short, now I am vibe coding a programming language, losing my mind trying to convince LLMs to output the code that I want, and I generally know much more about compilers that I used to know. It's been fun.

If you are triggered by mediocre and badly written code, you might want to stay away. Or you might find this funny.

Apart from this section, most of the things in the documentation were just briefly checked for sanity, and revised by a human. Same goes for code. A rough estimate is that 80% of the code was iteratively generated by LLMs. Then LLMs were used to refactor this code. Obvious mistakes spotted by a human were then corrected mostly by LLMs. This code base is in some parts quite ridiculous and can bring even seasoned developers and code reviewers to tears. You have been warned.

Sushi is actively tested on macOS (Apple Silicon) and Linux (via CI). No testing has been done on Windows yet, so the current status is unknown.

What is the goal with Sushi? Mostly me teaching myself about compilers and related subjects, and playing around with LLMs. At one point I will decide that I added enough features, then I will try and turn it into a self-hosted compiler.

Overview

Sushi Lang is a statically-typed compiled language designed with safety, simplicity, and performance in mind. The compiler follows a clean multi-pass architecture with comprehensive semantic analysis and LLVM-powered code generation.

Key Features:

  • Static type system with explicit casting
  • Generic types with compile-time monomorphization (Result@(T, E), Maybe@(T), List@(T), HashMap@(K, V), user-defined structs, enums, and functions)
  • Generic functions with automatic type inference and perk constraints
  • Perks (traits/interfaces) for polymorphic behavior with static dispatch
  • Error types declared with error, the error propagation operator (??), and declared conversions between error types (extend FileError as AppError:)
  • Parameter modes: a parameter borrows by default, nom hands the value over, and peek/poke borrow by pointer, all with compile-time borrow checking
  • Own@(T) heap allocation for recursive types (linked lists, trees)
  • Extension methods for zero-cost method chaining, with an optional error channel (| E) and static methods (extend Vec static at(...), called as Vec.at(...))
  • Visibility: every declaration is private to its unit unless it says public
  • Units as namespaces (use "geometry" as geo) and re-exports (public use)
  • Unit-level storage (var) beside compile-time constants (const)
  • Reference bindings (let peek T x = ..., let poke T x = ...)
  • The predefined perks Drop (a type that owns a resource) and Hashable
  • Documentation blocks (##: ... :##) that the compiler checks and a library carries
  • I/O contracts (Reader, Writer, Seek) and buffered I/O (BufReader, BufWriter)
  • Closures / lambdas (|x| expr) with RAII-managed, move-semantics captures
  • First-class functions (fn(i32) -> i32 values, passable and callable)
  • Foreign Function Interface (unsafe external "C") for calling C libraries
  • Variadic functions (memory-safe native ...T array sugar; C ... for FFI bindings)
  • Variadic generics / parameter packs (...Ts: Perk + expand(x in args), compile-time unrolled)
  • Rust-style enums with exhaustive pattern matching
  • Automatic memory management (RAII) for structs, arrays, and collections
  • Full UTF-8 Unicode support
  • Native code generation via LLVM
  • Incremental compilation with per-unit object file caching

Quick Start

# Compile a program
./sushic hello.sushi

# Run the compiled binary
./hello

# Optimization levels
./sushic --opt O2 program.sushi       # Recommended: balanced performance
./sushic --opt O3 program.sushi       # Maximum performance

# Create and use libraries
./sushic --lib --lib-version 1.0.0 mylib.sushi -o mylib.slib  # Compile to library
export SUSHI_LIB_PATH=.                   # Set library path
./sushic main.sushi                       # use <lib/mylib> in source

Mostly Harmless

fn main() i32:
    println("Mostly Harmless")
    return 0

Documentation

📚 Complete Documentation

Getting Started

Language Reference

Compiler

Language Highlights

Explicit Error Handling

A function that can fail writes its error channel | E, and its call returns a Result@(T, E). A function with no | E is bare: it returns the value itself. A bare function is the exception: use it only for a function that is total over its inputs and will stay so. A public function keeps a channel when there is any doubt, because a channel added later breaks every caller. See the error channel design.

fn divide(i32 a, i32 b) i32 | StdError:
    if (b == 0):
        return Result.Err(StdError.Error)
    return Result.Ok(a / b)

fn main() i32:
    let i32 result = divide(10, 2).realise(0)
    println("Result: {result}")
    return 0

Error Propagation

The ?? operator unwraps a Result or propagates its error. When the two error types differ, it calls a conversion that the program declares:

use <io/fs>

fn read_config() string | IoError:
    let File f = open("config.txt", FileMode.Read())??
    let string content = f.read_all()??
    f.close()??
    return Result.Ok(content)

error AppError:
    Io(IoError)

extend IoError as AppError:
    return AppError.Io(self)

fn load() string | AppError:
    return Result.Ok(read_config()??)     # IoError as AppError

Pattern Matching

Exhaustive pattern matching with enums:

enum Status:
    Idle()
    Working(i32)
    Done()

fn check(Status s) ~:
    match s:
        Status.Idle() -> println("Idle")
        Status.Working(progress) -> println("Progress: {progress}%")
        Status.Done() -> println("Completed")

Generic Types

Type-safe generics with zero runtime overhead:

struct Pair@(T, U):
    T first
    U second

fn main() i32:
    let Pair@(i32, string) p = Pair(first: 42, second: "answer")
    println("{p.second}: {p.first}")
    return 0

Memory Safety

Compile-time borrow checking and RAII. A parameter borrows unless it says nom, and a marked mode is written at both ends:

fn increment(poke i32 counter) ~:
    counter := counter + 1

fn eat(nom i32[] items) i32:
    return items.len()              # items is freed here

fn main() i32:
    let i32 count = 0
    increment(poke count)
    println("Count: {count}")       # 1

    let i32[] data = from([1, 2, 3])
    println(eat(nom data))
    # println(data.len())           # CE2405: data was handed over
    return 0

Perks (Traits/Interfaces)

Static polymorphism through perks with zero runtime overhead:

perk Shape:
    fn area() i32

struct Rect:
    i32 w
    i32 h

extend Rect with Shape:
    fn area() i32:
        return self.w * self.h

# Generic function with perk constraint
fn total_area@(T: Shape)(T value) i32:
    return value.area()

fn main() i32:
    let Rect r = Rect(4, 5)
    let i32 a = total_area(r)             # T is inferred as Rect
    println("Area: {a}")
    return 0

Variadic Generics (Parameter Packs)

Heterogeneous, perk-constrained parameter packs, fully monomorphized at compile time:

perk Display:
    fn display() string

extend i32 with Display:
    fn display() string:
        return "int:42"

extend string with Display:
    fn display() string:
        return self.clone()

fn print_all@(...Ts: Display)(...Ts args) ~:
    expand(a in args):          # compile-time unrolled, not a runtime loop
        println(a.display())

fn main() i32:
    print_all(42, "hi")         # monomorphizes per (arity, type-tuple)
    print_all()                 # arity-0: expand body runs 0 times
    return 0

See the variadics design doc for the full design and its limits.

Optimization Levels

Level Description Use Case
none No optimization Debugging
mem2reg Basic SROA (default) Quick builds
O1 Basic optimizations Fast compilation
O2 Moderate optimizations Recommended for production
O3 Aggressive optimizations Maximum performance
# Development
./sushic program.sushi

# Production
./sushic --opt O2 program.sushi -o app

Testing

# Run test suite
python tests/run_tests.py

# Only the fixtures that do not run a binary (the fast diagnostics gate)
python tests/run_tests.py --compile-only

# Run only the leak-annotated subset (the same check, a faster gate)
python tests/run_tests.py --leaks-only

# Filter specific tests
python tests/run_tests.py --filter hashmap

Every run executes each compiled binary and enforces the # EXPECT_* directives it declares, including EXPECT_NO_LEAKS: the binary is run again under a malloc interposer (tests/leakcheck) and an allocation that is not freed fails the test. A flag only selects WHICH fixtures run, never how hard they are checked. --leaks-only selects the tests that carry EXPECT_NO_LEAKS.

CI (GitHub Actions) additionally runs ruff and mypy (blocking over a growing set of type-checked packages, informational over the full tree) as a lint gate, and the cross-platform leak gate (tests/run_tests.py --leaks-only) on both Linux and macOS before the full suites run.

Examples

Check out the examples directory for hands-on learning:

See all 29 examples →

Project Structure

sushi/
├── sushi_lang/                  # Main package (installed to site-packages)
│   ├── compiler/                # CLI entry point, pipeline & caching
│   ├── grammar.lark             # Lark grammar specification
│   ├── internals/               # Diagnostics, parser, error registry
│   ├── semantics/               # Semantic analysis passes
│   │   ├── passes/              # Multi-pass type checking
│   │   └── generics/            # Generic type system
│   ├── backend/                 # LLVM code generation
│   │   ├── expressions/         # Expression emission
│   │   ├── statements/          # Statement emission
│   │   └── types/               # Type-specific codegen
│   ├── sushi_stdlib/            # Standard library
│   │   ├── src/                 # Python IR generators
│   │   ├── src_sushi/           # Stdlib modules written in Sushi
│   │   └── dist/                # Precompiled .bc files
│   └── packager/                # Nori package manager CLI
├── toolchain/                   # Sushi programs that compile Sushi (repository only)
├── sushic                       # Development wrapper script
├── tests/                       # Test suite
└── docs/                        # Documentation

Philosophy

Sushi combines:

  • Rust's safety - Ownership, borrowing, explicit error handling
  • Python's simplicity - Clean syntax, readable code
  • C's performance - Zero-cost abstractions, native binaries

Development Setup

Prerequisites

  • Python 3.13+ (managed by uv)
  • LLVM 20 (llvmlite 0.45 requirement)
  • cmake (required for building llvmlite)

Installation

# Install uv (if not already installed)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Install cmake (macOS)
brew install cmake

# Install LLVM 20
brew install llvm@20

# Install Python dependencies
uv sync --extra dev

# Build standard library
uv run python sushi_lang/sushi_stdlib/build.py

# Test installation
./sushic --help

Important: LLVM 20 is keg-only on macOS. The build process will automatically use the correct version through the LLVM_CONFIG environment variable if needed.

Development Commands

# Compile with debug output
./sushic --traceback --dump-ll program.sushi

# View AST
./sushic --dump-ast program.sushi

# Save LLVM IR
./sushic --write-ll program.sushi
cat program.ll

# Run test suite
uv run python tests/run_tests.py

# Run the Python unit layer
uv run python -m pytest -q

Building a Distribution Package

Sushi can be packaged as a Python wheel for easy installation without requiring users to set up LLVM or clone the repository.

Building the Wheel

# Build the wheel (requires hatchling)
uv build --wheel

# The wheel will be created in dist/
ls dist/
# sushi_lang-0.15.0-py3-none-any.whl

Installing from Wheel

# Install in a virtual environment
pip install sushi_lang-0.15.0-py3-none-any.whl

# The sushic command is now available
sushic program.sushi

What's Included

The wheel contains:

  • The sushi_lang package with all compiler modules
  • Grammar file (sushi_lang/grammar.lark)
  • Standard library sources, both the Python IR generators (sushi_lang/sushi_stdlib/src/) and the Sushi-source modules (sushi_lang/sushi_stdlib/src_sushi/)
  • Precompiled stdlib bitcode for macOS and Linux (sushi_lang/sushi_stdlib/dist/)

Users don't need to install LLVM - the llvmlite dependency bundles LLVM binaries in its wheels.

Release Workflow

GitHub releases automatically build wheels with fresh stdlib for both platforms via .github/workflows/release.yml. The workflow:

  1. Builds stdlib .bc files on Linux (Debian Trixie) and macOS
  2. Merges artifacts and builds the wheel
  3. Attaches the wheel to the GitHub release

To create a release:

  1. Tag a commit: git tag v0.15.0
  2. Push the tag: git push upstream v0.15.0
  3. Create a release on GitHub from the tag
  4. The workflow will automatically build and attach the wheel

Contributing

Contributions welcome! See documentation for compiler internals and architecture.

Glossary

Sushi (すし) — The programming language and its compiler.

In Japanese, sushi refers to a dish of vinegared rice accompanied by various ingredients such as raw fish, seafood, and vegetables. It is one of the most recognized symbols of Japanese cuisine worldwide. Just as sushi combines simple ingredients into something refined, the Sushi language aims to blend safety, simplicity, and performance into a cohesive whole.

Nori (海苔) — The package manager for Sushi Lang.

Nori is edible seaweed, most commonly used as the dark green wrapper that holds sushi rolls together. In the Sushi ecosystem, Nori is the tool that wraps everything up — it packages compiled libraries into distributable .nori archives, manages project manifests (nori.toml), and handles installing and publishing packages.

Bento (弁当) — The package storage directory structure.

A bento is a Japanese meal served in a compartmentalized box, with each section neatly holding a different dish. Similarly, the bento directories (~/.sushi/bento/ for global packages, .sushi_bento/ for project-local dependencies) serve as organized containers where installed packages are stored, each in its own compartment.

Omakase (お任せ) — The central package repository.

Omakase literally means "I'll leave it up to you" and refers to a style of dining where the chef selects and serves the best available dishes. The Omakase repository at omakase.lubica.net is the curated central hub where Nori packages are published, discovered, and shared — the chef's choice of the Sushi ecosystem.


📚 Read the full documentation | 🚀 Get started | 💡 See examples

About

Sushi Lang is a statically-typed compiled language, made out of sheer boredom and curiosity.

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages