1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
//! # Passerine
//! 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:
//! ```bash
//! $ 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
//! [passerine.io](https://www.passerine.io/#install).
//!
//! ## Embedding Passerine in Rust
//! > TODO: Clean up crate visibility, create `run` function.
//!
//! Add passerine to your `Cargo.toml`:
//! ```toml
//! # make sure this is the latest version
//! passerine = 0.9
//! ```
//! Then simply:
//! ```ignore
//! // 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 `Span`s,
//! 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
//! 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.
//!
//! ### Execution
//! 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 `Lambda`s are passed to the `VM` to be run.
//! before being run by the `VM`, `Lambdas` are wrapped in `Closure`s,
//! which hold some extra context.
//! To run some bytecode:
//!
//! ```
//! # use passerine::common::{closure::Closure, source::Source};
//! # use passerine::compiler::{lex, parse, desugar, hoist, gen};
//! # use passerine::vm::VM;
//! #
//! # fn main() {
//! # let source = Source::source("pi = 3.14");
//! # let bytecode = Closure::wrap(
//! # lex(source)
//! # .and_then(parse)
//! # .and_then(desugar)
//! # .and_then(hoist)
//! # .and_then(gen)
//! # .unwrap());
//! // Initialize a VM with some bytecode:
//! let mut vm = VM::init(bytecode);
//! // Run the initialized VM:
//! vm.run();
//! # }
//! ```
//!
//! The `VM` is just a simple light stack-based VM.
pub mod common;
pub mod core;
pub mod compiler;
pub mod vm;
// exported functions:
// TODO: clean up exports
use std::rc::Rc;
use common::{closure::Closure, source::Source};
use compiler::{lex, parse, desugar, hoist, gen::{gen, gen_with_ffi}, syntax::Syntax};
use crate::core::ffi::FFI;
use vm::{VM, trace::Trace};
/// Compiles a [`Source`] to some bytecode.
pub fn compile(source: Rc<Source>) -> Result<Closure, Syntax> {
let tokens = lex(source)?;
let ast = parse(tokens)?;
let cst = desugar(ast)?;
let sst = hoist(cst)?;
let bytecode = gen(sst)?;
Ok(Closure::wrap(bytecode))
}
/// Compiles a [`Source`] to some bytecode,
/// With a specific [`FFI`].
pub fn compile_with_ffi(source: Rc<Source>, ffi: FFI) -> Result<Closure, Syntax> {
let tokens = lex(source)?;
let ast = parse(tokens)?;
let cst = desugar(ast)?;
let sst = hoist(cst)?;
let bytecode = gen_with_ffi(sst, ffi)?;
Ok(Closure::wrap(bytecode))
}
/// Run a compiled [`Closure`].
pub fn run(closure: Closure) -> Result<(), Trace> {
let mut vm = VM::init(closure);
vm.run()?;
Ok(())
}