Merge pull request #24 from AlexBethel/master
This commit is contained in:
commit
18973333b0
|
@ -14,4 +14,5 @@ pub enum ErrorKind {
|
|||
UnknownVariable(String),
|
||||
MeloVariable(String),
|
||||
TypeError(String),
|
||||
TopLevelBreak,
|
||||
}
|
||||
|
|
245
src/interpret.rs
245
src/interpret.rs
|
@ -1,10 +1,10 @@
|
|||
//! Expression evaluator and statement interpreter.
|
||||
//!
|
||||
//! To interpret a piece of AbleScript code, you first need to
|
||||
//! construct a [Scope], which is responsible for storing the list of
|
||||
//! variable and function definitions accessible from an AbleScript
|
||||
//! snippet. You can then call [Scope::eval_items] to evaluate or
|
||||
//! execute any number of expressions or statements.
|
||||
//! construct an [ExecEnv], which is responsible for storing the stack
|
||||
//! of local variable and function definitions accessible from an
|
||||
//! AbleScript snippet. You can then call [ExecEnv::eval_items] to
|
||||
//! evaluate or execute any number of expressions or statements.
|
||||
|
||||
#[deny(missing_docs)]
|
||||
use std::collections::HashMap;
|
||||
|
@ -16,41 +16,91 @@ use crate::{
|
|||
variables::{Value, Variable},
|
||||
};
|
||||
|
||||
/// A set of visible variable and function definitions, which serves
|
||||
/// as a context in which expressions can be evaluated.
|
||||
pub struct Scope {
|
||||
/// The mapping from variable names to values.
|
||||
variables: HashMap<String, Variable>,
|
||||
|
||||
// In the future, this will store functio definitions, a link to a
|
||||
// parent scope (so we can have nested scopes), and possibly other
|
||||
// information.
|
||||
/// An environment for executing AbleScript code.
|
||||
pub struct ExecEnv {
|
||||
/// The stack, ordered such that `stack[stack.len() - 1]` is the
|
||||
/// top-most (newest) stack frame, and `stack[0]` is the
|
||||
/// bottom-most (oldest) stack frame.
|
||||
stack: Vec<Scope>,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
/// A set of visible variable and function definitions in a single
|
||||
/// stack frame.
|
||||
#[derive(Default)]
|
||||
struct Scope {
|
||||
/// The mapping from variable names to values.
|
||||
variables: HashMap<String, Variable>,
|
||||
// In the future, this will store functio definitions and possibly
|
||||
// other information.
|
||||
}
|
||||
|
||||
/// The reason a successful series of statements halted.
|
||||
enum HaltStatus {
|
||||
/// The last statement in the list evaluated to this value.
|
||||
Value(Value),
|
||||
|
||||
/// A `break` statement occurred and was not caught by a `loop`
|
||||
/// statement.
|
||||
Break,
|
||||
|
||||
/// A `hopback` statement occurred and was not caught by a `loop`
|
||||
/// statement.
|
||||
Hopback,
|
||||
}
|
||||
|
||||
impl ExecEnv {
|
||||
/// Create a new Scope with no predefined variable definitions or
|
||||
/// other information.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
variables: HashMap::new(),
|
||||
stack: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate a set of Items. Returns the value of the last Item
|
||||
/// evaluated, or an error if one or more of the Items failed to
|
||||
/// evaluate.
|
||||
/// Evaluate a set of Items in their own stack frame. Return the
|
||||
/// value of the last Item evaluated, or an error if one or more
|
||||
/// of the Items failed to evaluate or if a `break` or `hopback`
|
||||
/// statement occurred at the top level.
|
||||
pub fn eval_items(&mut self, items: &[Item]) -> Result<Value, Error> {
|
||||
items
|
||||
.iter()
|
||||
.map(|item| self.eval_item(item))
|
||||
.try_fold(Value::Nul, |_, result| result)
|
||||
match self.eval_items_hs(items)? {
|
||||
HaltStatus::Value(v) => Ok(v),
|
||||
HaltStatus::Break | HaltStatus::Hopback => Err(Error {
|
||||
// It's an error to issue a `break` outside of a
|
||||
// `loop` statement.
|
||||
kind: ErrorKind::TopLevelBreak,
|
||||
position: 0..0,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// The same as `eval_items`, but report "break" and "hopback"
|
||||
/// exit codes as normal conditions in a HaltStatus enum.
|
||||
///
|
||||
/// `interpret`-internal code should typically prefer this
|
||||
/// function over `eval_items`.
|
||||
fn eval_items_hs(&mut self, items: &[Item]) -> Result<HaltStatus, Error> {
|
||||
let init_depth = self.stack.len();
|
||||
|
||||
self.stack.push(Default::default());
|
||||
let mut final_result = Ok(HaltStatus::Value(Value::Nul));
|
||||
for item in items {
|
||||
final_result = self.eval_item(item);
|
||||
if !matches!(final_result, Ok(HaltStatus::Value(_))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.stack.pop();
|
||||
|
||||
// Invariant: stack size must have net 0 change.
|
||||
debug_assert_eq!(self.stack.len(), init_depth);
|
||||
final_result
|
||||
}
|
||||
|
||||
/// Evaluate a single Item, returning its value or an error.
|
||||
fn eval_item(&mut self, item: &Item) -> Result<Value, Error> {
|
||||
fn eval_item(&mut self, item: &Item) -> Result<HaltStatus, Error> {
|
||||
match item {
|
||||
Item::Expr(expr) => self.eval_expr(expr),
|
||||
Item::Stmt(stmt) => self.eval_stmt(stmt).map(|_| Value::Nul),
|
||||
Item::Expr(expr) => self.eval_expr(expr).map(|v| HaltStatus::Value(v)),
|
||||
Item::Stmt(stmt) => self.eval_stmt(stmt),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,46 +132,131 @@ impl Scope {
|
|||
}
|
||||
Eq { left, right } => Bool(self.eval_expr(left)? == self.eval_expr(right)?),
|
||||
Neq { left, right } => Bool(self.eval_expr(left)? != self.eval_expr(right)?),
|
||||
And { left, right } => Bool(
|
||||
bool::try_from(self.eval_expr(left)?)? && bool::try_from(self.eval_expr(right)?)?,
|
||||
),
|
||||
Or { left, right } => Bool(
|
||||
bool::try_from(self.eval_expr(left)?)? || bool::try_from(self.eval_expr(right)?)?,
|
||||
),
|
||||
Not(expr) => Bool(!bool::try_from(self.eval_expr(expr)?)?),
|
||||
And { left, right } => {
|
||||
Bool(bool::from(self.eval_expr(left)?) && bool::from(self.eval_expr(right)?))
|
||||
}
|
||||
Or { left, right } => {
|
||||
Bool(bool::from(self.eval_expr(left)?) || bool::from(self.eval_expr(right)?))
|
||||
}
|
||||
Not(expr) => Bool(!bool::from(self.eval_expr(expr)?)),
|
||||
Literal(value) => value.clone(),
|
||||
Identifier(Iden(name)) => self
|
||||
.variables
|
||||
.get(name)
|
||||
.ok_or_else(|| Error {
|
||||
kind: ErrorKind::UnknownVariable(name.to_owned()),
|
||||
// TODO: figure out some way to avoid this 0..0
|
||||
// dumbness
|
||||
position: 0..0,
|
||||
})
|
||||
.and_then(|var| {
|
||||
if !var.melo {
|
||||
Ok(var.value.clone())
|
||||
} else {
|
||||
Err(Error {
|
||||
kind: ErrorKind::MeloVariable(name.to_owned()),
|
||||
position: 0..0,
|
||||
})
|
||||
}
|
||||
})?,
|
||||
Identifier(Iden(name)) => self.get_var(name)?.value.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Perform the action indicated by a statement.
|
||||
fn eval_stmt(&mut self, stmt: &Stmt) -> Result<(), Error> {
|
||||
fn eval_stmt(&mut self, stmt: &Stmt) -> Result<HaltStatus, Error> {
|
||||
match stmt {
|
||||
Stmt::Print(expr) => {
|
||||
println!("{}", self.eval_expr(expr)?);
|
||||
Ok(())
|
||||
}
|
||||
_ => {
|
||||
todo!()
|
||||
Stmt::VariableDeclaration { iden, init } => {
|
||||
let init = match init {
|
||||
Some(e) => self.eval_expr(e)?,
|
||||
None => Value::Nul,
|
||||
};
|
||||
|
||||
// There's always at least one stack frame on the
|
||||
// stack if we're evaluating something, so we can
|
||||
// `unwrap` here.
|
||||
self.stack.iter_mut().last().unwrap().variables.insert(
|
||||
iden.0.clone(),
|
||||
Variable {
|
||||
melo: false,
|
||||
value: init,
|
||||
},
|
||||
);
|
||||
}
|
||||
Stmt::FunctionDeclaration {
|
||||
iden: _,
|
||||
args: _,
|
||||
body: _,
|
||||
} => todo!(),
|
||||
Stmt::BfFDeclaration { iden: _, body: _ } => todo!(),
|
||||
Stmt::If { cond, body } => {
|
||||
if self.eval_expr(cond)?.into() {
|
||||
return self.eval_items_hs(body);
|
||||
}
|
||||
}
|
||||
Stmt::FunctionCall { iden: _, args: _ } => todo!(),
|
||||
Stmt::Loop { body } => loop {
|
||||
let res = self.eval_items_hs(body)?;
|
||||
match res {
|
||||
HaltStatus::Value(_) => {}
|
||||
HaltStatus::Break => break,
|
||||
HaltStatus::Hopback => continue,
|
||||
}
|
||||
},
|
||||
Stmt::VarAssignment { iden, value } => {
|
||||
self.get_var_mut(&iden.0)?.value = self.eval_expr(value)?;
|
||||
}
|
||||
Stmt::Break => {
|
||||
return Ok(HaltStatus::Break);
|
||||
}
|
||||
Stmt::HopBack => {
|
||||
return Ok(HaltStatus::Hopback);
|
||||
}
|
||||
Stmt::Melo(iden) => {
|
||||
self.get_var_mut(&iden.0)?.melo = true;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(HaltStatus::Value(Value::Nul))
|
||||
}
|
||||
|
||||
/// Get a shared reference to the value of a variable. Throw an
|
||||
/// error if the variable is inaccessible or banned.
|
||||
fn get_var(&self, name: &str) -> Result<&Variable, Error> {
|
||||
match self
|
||||
.stack
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(|scope| scope.variables.get(name))
|
||||
{
|
||||
Some(var) => {
|
||||
if !var.melo {
|
||||
Ok(var)
|
||||
} else {
|
||||
Err(Error {
|
||||
kind: ErrorKind::MeloVariable(name.to_owned()),
|
||||
// TODO: figure out some way to avoid this
|
||||
// 0..0 dumbness
|
||||
position: 0..0,
|
||||
})
|
||||
}
|
||||
}
|
||||
None => Err(Error {
|
||||
kind: ErrorKind::UnknownVariable(name.to_owned()),
|
||||
position: 0..0,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a variable. Throw an error if the
|
||||
/// variable is inaccessible or banned.
|
||||
fn get_var_mut(&mut self, name: &str) -> Result<&mut Variable, Error> {
|
||||
// This function is almost exactly 22 lines of duplicated code
|
||||
// from get_var, which I feel like is a bad sign...
|
||||
match self
|
||||
.stack
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find_map(|scope| scope.variables.get_mut(name))
|
||||
{
|
||||
Some(var) => {
|
||||
if !var.melo {
|
||||
Ok(var)
|
||||
} else {
|
||||
Err(Error {
|
||||
kind: ErrorKind::MeloVariable(name.to_owned()),
|
||||
position: 0..0,
|
||||
})
|
||||
}
|
||||
}
|
||||
None => Err(Error {
|
||||
kind: ErrorKind::UnknownVariable(name.to_owned()),
|
||||
position: 0..0,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ mod repl;
|
|||
mod variables;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use interpret::Scope;
|
||||
use interpret::ExecEnv;
|
||||
use logos::Source;
|
||||
use parser::Parser;
|
||||
|
||||
|
@ -41,8 +41,8 @@ fn main() {
|
|||
match ast {
|
||||
Ok(ast) => {
|
||||
println!("{:#?}", ast);
|
||||
let mut ctx = Scope::new();
|
||||
println!("{:?}", ctx.eval_items(&ast));
|
||||
let mut env = ExecEnv::new();
|
||||
println!("{:?}", env.eval_items(&ast));
|
||||
}
|
||||
Err(e) => {
|
||||
println!(
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use logos::Source;
|
||||
use rustyline::Editor;
|
||||
|
||||
use crate::{interpret::Scope, parser::Parser};
|
||||
use crate::{interpret::ExecEnv, parser::Parser};
|
||||
|
||||
pub fn repl() {
|
||||
let mut rl = Editor::<()>::new();
|
||||
let mut ctx = Scope::new();
|
||||
let mut env = ExecEnv::new();
|
||||
loop {
|
||||
let readline = rl.readline(":: ");
|
||||
match readline {
|
||||
|
@ -19,7 +19,7 @@ pub fn repl() {
|
|||
match ast {
|
||||
Ok(ast) => {
|
||||
println!("{:?}", ast);
|
||||
println!("{:?}", ctx.eval_items(&ast));
|
||||
println!("{:?}", env.eval_items(&ast));
|
||||
},
|
||||
Err(e) => {
|
||||
println!(
|
||||
|
|
|
@ -71,17 +71,23 @@ impl TryFrom<Value> for i32 {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Value> for bool {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: Value) -> Result<Self, Self::Error> {
|
||||
// Coercions from a value to a boolean always succeed, so every value
|
||||
// can be used in an `if` statement. C does things that way, so how
|
||||
// could it possibly be a bad idea?
|
||||
impl From<Value> for bool {
|
||||
fn from(value: Value) -> Self {
|
||||
match value {
|
||||
Value::Bool(b) => Ok(b),
|
||||
Value::Abool(b) => Ok(b.into()),
|
||||
_ => Err(Error {
|
||||
kind: ErrorKind::TypeError(format!("Expected bool, got {}", value)),
|
||||
position: 0..0,
|
||||
}),
|
||||
// Booleans and abooleans have a trivial conversion.
|
||||
Value::Bool(b) => b,
|
||||
Value::Abool(b) => b.into(),
|
||||
// The empty string is falsey, other strings are truthy.
|
||||
Value::Str(s) => s.len() != 0,
|
||||
// 0 is falsey, nonzero is truthy.
|
||||
Value::Int(x) => x != 0,
|
||||
// And nul is truthy as a symbol of the fact that the
|
||||
// deep, fundamental truth of this world is nothing but
|
||||
// the eternal void.
|
||||
Value::Nul => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue