Crate passerine[][src]


This repository contains the core of the Passerine Programming Language, including the compiler, VM, and various utilities.

Running Passerine

Passerine is primarily run through Aspen, Passerine's package manager. Simply install Aspen, then:

$ aspen new first-package
$ cd first-package
$ aspen run

Aspen is the idiomatic way to create and run Passerine packages. The documentation that follows is about the core compiler itself. This is only useful if you're trying to embed Passerine in your Rust project or developing the core Passerine compiler and VM. If that's the case, read on!

You can install Passerine by following the instructions at

Embedding Passerine in Rust

TODO: Clean up crate visibility, create run function.

Add passerine to your Cargo.toml:

# make sure this is the latest version
passerine = 0.7

Then simply:

// DISCLAIMER: The `run` function used here has not been implemented yet,
//             although the underlying interface is mostly stable.

use passerine;

fn main() {
    passerine::run("print \"Hello from Passerine!\"");

Overview of the compilation process

NOTE: For a more detail, read through the documentation for any of the components mentioned.

Within the compiler pipeline, source code is represented as a Source object. A source is a reference to some code, with an associated path telling which file it came from.

Regions of source code can be marked with Spans, Which are like &strs but with a reference-counted reference to the original Source, methods for combining them, and so on. Spans are used throughout the compiler when reporting errors. Compiler Datastructures can be Spanned to indicate where they originated.


Compilation steps can raise Err(Syntax), indicating that an error occured. Syntax is just a Span and a message, which can be pretty-printed.

The first phase of compilation is lexing. The Lexer reads through a source, and produces a stream of Spanned<Token>s. The Lexer is super simple - it greedily looks for the longest next token, Then consumes it and advances by the token's length. To lex a file, use the compiler::lex::lex function.

The next phase of compilation is parsing. The parser takes a spanned token stream, and builts a spanned Abstract Syntax Tree (AST). The parser used is a modified Pratt parser. (It's modified to handle the special function-call syntax used.) To parse a token stream, use the compiler::parse::parse function.

The AST is then traversed and simplified; this is where macro expansion and so on take place. The result is a simplified Concrete Syntax Tree (CST).

After constructing the CST, bytecode is generated. Bytecode is just a vector of u8s, interlaced with split numbers. All the opcodes are defined in common::opcode, And implemented in compiler::vm::vm. A bytecode object is a called a Lambda. The bytecode generator works by walking the CST, Recursively nesting itself when a new scope is encountered. To generate bytecode for an CST, use the compiler::gen::gen function.


The VM can raise Err(Trace) if it encounters errors during execution. A Trace is similar to Syntax, but it keeps track of multiple spans representing function calls and so on.

After this, raw Lambdas are passed to the VM to be run. before being run by the VM, Lambdas are wrapped in Closures, which hold some extra context. To run some bytecode:

// Initialize a VM with some bytecode:
let mut vm = VM::init(bytecode);
// Run the initialized VM:;

The VM is just a simple light stack-based VM.



Contains datastructures and utility functions common to both the compiler and vm.


This module contains the compiler implementation. Note that these modules are public for documentation visiblility, But should never be used outside of the module by common or vm.


This module provides the standard/core language library And compiler-magic FFI bindings.


This module contains the core VM implementation. Note that these modules are public for documentation visiblility, But should never be used outside of the module by common or compiler.