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(())
}