202 lines
6.7 KiB
Rust
202 lines
6.7 KiB
Rust
//! Tree-walk interpreter for syntax.
|
|
|
|
use std::fmt::Display;
|
|
|
|
use thiserror::Error;
|
|
|
|
use crate::builtins::Builtin;
|
|
use crate::dictionary::Dictionary;
|
|
use crate::syntax::{Block, Expression, InteractiveEntry, Item, Name, Program};
|
|
|
|
/// Runtime values.
|
|
#[derive(Debug, Clone)]
|
|
pub enum Value {
|
|
Builtin(Builtin),
|
|
Closure {
|
|
environment: Dictionary<Value>,
|
|
parameters: Vec<Name>,
|
|
result: Expression,
|
|
},
|
|
Boolean(bool),
|
|
Integer(i64),
|
|
Unit,
|
|
}
|
|
|
|
impl Display for Value {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Value::Builtin(b) => write!(f, "<builtin:{b:?}>"),
|
|
Value::Closure { parameters, .. } => write!(f, "<closure({})>", parameters.join(", ")),
|
|
Value::Boolean(b) => write!(f, "{b}"),
|
|
Value::Integer(i) => write!(f, "{i}"),
|
|
Value::Unit => write!(f, "()"),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Errors that may occur during evaluation.
|
|
#[derive(Debug, Error)]
|
|
pub enum Error {
|
|
#[error("The name '{0} was not bound to any value in this context")]
|
|
UnboundName(Name),
|
|
#[error("Tried to call a term which was not a function")]
|
|
NotAFunction,
|
|
#[error("Mismatched number of function parameters and arguments: expected {0}, got {1}")]
|
|
ArgumentsMismatch(usize, usize),
|
|
#[error("No function called main to run")]
|
|
MissingMainFunction,
|
|
}
|
|
|
|
type Result<T> = std::result::Result<T, Error>;
|
|
|
|
/// AST walking interpreter.
|
|
#[derive(Debug, Clone)]
|
|
pub struct Interpreter {
|
|
environment: Dictionary<Value>,
|
|
}
|
|
|
|
macro_rules! bind {
|
|
($interpreter:expr, $name:expr, $term:expr, to: $nested:expr) => {{
|
|
let value = $interpreter.evaluate($term)?;
|
|
$nested.environment.insert($name, value.clone());
|
|
Ok(value)
|
|
}};
|
|
($interpreter:expr, $name:expr, $term:expr) => {
|
|
bind!($interpreter, $name, $term, to: $interpreter)
|
|
};
|
|
}
|
|
|
|
impl Interpreter {
|
|
/// Create a fresh interpreter populated with builtins.
|
|
pub fn new() -> Self {
|
|
Interpreter {
|
|
environment: Builtin::dictionary(),
|
|
}
|
|
}
|
|
|
|
/// Create a nested interpreter given an existing environment.
|
|
fn nested(environment: Dictionary<Value>) -> Self {
|
|
Interpreter { environment }
|
|
}
|
|
|
|
/// Run a program. Expects a main function to exist.
|
|
pub fn run(&mut self, program: &Program) -> Result<Value> {
|
|
for item in program {
|
|
self.interpret_item(item)?;
|
|
}
|
|
if let Some(main) = self.environment.get("main").cloned() {
|
|
self.apply_call(&main, &vec![])
|
|
} else {
|
|
Err(Error::MissingMainFunction)
|
|
}
|
|
}
|
|
|
|
fn interpret_item(&mut self, item: &Item) -> Result<Value> {
|
|
match item {
|
|
Item::Fn {
|
|
name,
|
|
parameters,
|
|
body,
|
|
..
|
|
} => {
|
|
let closure = Value::Closure {
|
|
environment: self.environment.clone(),
|
|
parameters: parameters.iter().map(|p| p.name.clone()).collect(),
|
|
result: Expression::Block(Box::new(body.clone())),
|
|
};
|
|
self.environment.insert(name.clone(), closure.clone());
|
|
Ok(closure)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Evaluate a code fragment from an interactive context.
|
|
pub fn evaluate_interactive_entry(&mut self, entry: &InteractiveEntry) -> Result<Value> {
|
|
match entry {
|
|
InteractiveEntry::Item(item) => self.interpret_item(item),
|
|
InteractiveEntry::Binding(binding) => {
|
|
bind!(self, binding.name.clone(), &binding.expression)
|
|
}
|
|
InteractiveEntry::Expression(term) => self.evaluate(term),
|
|
}
|
|
}
|
|
|
|
fn evaluate(&mut self, term: &Expression) -> Result<Value> {
|
|
match term {
|
|
Expression::Variable(name) => self
|
|
.environment
|
|
.get(name)
|
|
.ok_or(Error::UnboundName(name.clone()))
|
|
.cloned(),
|
|
Expression::Block(b) => self.evaluate_block(b.as_ref()),
|
|
Expression::Lambda {
|
|
parameters, result, ..
|
|
} => Ok(Value::Closure {
|
|
environment: self.environment.clone(),
|
|
parameters: parameters.iter().map(|p| p.name.clone()).collect(),
|
|
result: *result.clone(),
|
|
}),
|
|
Expression::Call { callee, arguments } => {
|
|
let callee = self.evaluate(callee.as_ref())?;
|
|
self.apply_call(&callee, arguments)
|
|
}
|
|
Expression::Annotation { expression, .. } => self.evaluate(expression.as_ref()),
|
|
Expression::Boolean(b) => Ok(Value::Boolean(*b)),
|
|
Expression::Integer(i) => Ok(Value::Integer(*i)),
|
|
Expression::Unit => Ok(Value::Unit),
|
|
}
|
|
}
|
|
|
|
fn apply_call(&mut self, callee: &Value, arguments: &Vec<Expression>) -> Result<Value> {
|
|
match callee {
|
|
Value::Closure {
|
|
environment,
|
|
parameters,
|
|
result,
|
|
} => {
|
|
if parameters.len() != arguments.len() {
|
|
return Err(Error::ArgumentsMismatch(parameters.len(), arguments.len()));
|
|
}
|
|
let mut nested = Interpreter::nested(environment.clone());
|
|
for (name, argument) in parameters.iter().zip(arguments) {
|
|
// we don't want arguments to refer to each other, so use the parent interpreter
|
|
bind!(self, name.clone(), argument, to: nested)?;
|
|
}
|
|
nested.evaluate(result)
|
|
}
|
|
Value::Builtin(b) => {
|
|
if arguments.len() < b.min_parameters() {
|
|
return Err(Error::ArgumentsMismatch(
|
|
b.min_parameters(),
|
|
arguments.len(),
|
|
));
|
|
} else if let Some(max) = b.max_parameters() {
|
|
if arguments.len() > max {
|
|
return Err(Error::ArgumentsMismatch(max, arguments.len()));
|
|
}
|
|
}
|
|
let arguments = arguments
|
|
.iter()
|
|
.map(|term| self.evaluate(term))
|
|
.collect::<Result<Vec<_>>>()?;
|
|
Ok(b.call(arguments))
|
|
}
|
|
_ => Err(Error::NotAFunction),
|
|
}
|
|
}
|
|
|
|
fn evaluate_block(&mut self, block: &Block) -> Result<Value> {
|
|
let mut nested = self.clone();
|
|
for binding in &block.bindings {
|
|
bind!(nested, binding.name.clone(), &binding.expression)?;
|
|
}
|
|
nested.evaluate(&block.result)
|
|
}
|
|
}
|
|
|
|
impl Default for Interpreter {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|