Merge pull request #24 from AlexBethel/master

This commit is contained in:
Able 2021-05-25 23:06:08 -05:00 committed by GitHub
commit 7379f9650c
5 changed files with 213 additions and 71 deletions

View file

@ -14,4 +14,5 @@ pub enum ErrorKind {
UnknownVariable(String), UnknownVariable(String),
MeloVariable(String), MeloVariable(String),
TypeError(String), TypeError(String),
TopLevelBreak,
} }

View file

@ -1,10 +1,10 @@
//! Expression evaluator and statement interpreter. //! Expression evaluator and statement interpreter.
//! //!
//! To interpret a piece of AbleScript code, you first need to //! To interpret a piece of AbleScript code, you first need to
//! construct a [Scope], which is responsible for storing the list of //! construct an [ExecEnv], which is responsible for storing the stack
//! variable and function definitions accessible from an AbleScript //! of local variable and function definitions accessible from an
//! snippet. You can then call [Scope::eval_items] to evaluate or //! AbleScript snippet. You can then call [ExecEnv::eval_items] to
//! execute any number of expressions or statements. //! evaluate or execute any number of expressions or statements.
#[deny(missing_docs)] #[deny(missing_docs)]
use std::collections::HashMap; use std::collections::HashMap;
@ -16,41 +16,91 @@ use crate::{
variables::{Value, Variable}, variables::{Value, Variable},
}; };
/// A set of visible variable and function definitions, which serves /// An environment for executing AbleScript code.
/// as a context in which expressions can be evaluated. pub struct ExecEnv {
pub struct Scope { /// The stack, ordered such that `stack[stack.len() - 1]` is the
/// The mapping from variable names to values. /// top-most (newest) stack frame, and `stack[0]` is the
variables: HashMap<String, Variable>, /// bottom-most (oldest) stack frame.
stack: Vec<Scope>,
// In the future, this will store functio definitions, a link to a
// parent scope (so we can have nested scopes), and possibly other
// information.
} }
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 /// Create a new Scope with no predefined variable definitions or
/// other information. /// other information.
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
variables: HashMap::new(), stack: Default::default(),
} }
} }
/// Evaluate a set of Items. Returns the value of the last Item /// Evaluate a set of Items in their own stack frame. Return the
/// evaluated, or an error if one or more of the Items failed to /// value of the last Item evaluated, or an error if one or more
/// evaluate. /// 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> { pub fn eval_items(&mut self, items: &[Item]) -> Result<Value, Error> {
items match self.eval_items_hs(items)? {
.iter() HaltStatus::Value(v) => Ok(v),
.map(|item| self.eval_item(item)) HaltStatus::Break | HaltStatus::Hopback => Err(Error {
.try_fold(Value::Nul, |_, result| result) // 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. /// 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 { match item {
Item::Expr(expr) => self.eval_expr(expr), Item::Expr(expr) => self.eval_expr(expr).map(|v| HaltStatus::Value(v)),
Item::Stmt(stmt) => self.eval_stmt(stmt).map(|_| Value::Nul), 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)?), Eq { left, right } => Bool(self.eval_expr(left)? == self.eval_expr(right)?),
Neq { 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( And { left, right } => {
bool::try_from(self.eval_expr(left)?)? && bool::try_from(self.eval_expr(right)?)?, Bool(bool::from(self.eval_expr(left)?) && bool::from(self.eval_expr(right)?))
), }
Or { left, right } => Bool( Or { left, right } => {
bool::try_from(self.eval_expr(left)?)? || bool::try_from(self.eval_expr(right)?)?, Bool(bool::from(self.eval_expr(left)?) || bool::from(self.eval_expr(right)?))
), }
Not(expr) => Bool(!bool::try_from(self.eval_expr(expr)?)?), Not(expr) => Bool(!bool::from(self.eval_expr(expr)?)),
Literal(value) => value.clone(), Literal(value) => value.clone(),
Identifier(Iden(name)) => self Identifier(Iden(name)) => self.get_var(name)?.value.clone(),
.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,
})
}
})?,
}) })
} }
/// Perform the action indicated by a statement. /// 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 { match stmt {
Stmt::Print(expr) => { Stmt::Print(expr) => {
println!("{}", self.eval_expr(expr)?); println!("{}", self.eval_expr(expr)?);
Ok(())
} }
_ => { Stmt::VariableDeclaration { iden, init } => {
todo!() 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,
}),
} }
} }
} }

View file

@ -10,7 +10,7 @@ mod repl;
mod variables; mod variables;
use clap::{App, Arg}; use clap::{App, Arg};
use interpret::Scope; use interpret::ExecEnv;
use logos::Source; use logos::Source;
use parser::Parser; use parser::Parser;
@ -41,8 +41,8 @@ fn main() {
match ast { match ast {
Ok(ast) => { Ok(ast) => {
println!("{:#?}", ast); println!("{:#?}", ast);
let mut ctx = Scope::new(); let mut env = ExecEnv::new();
println!("{:?}", ctx.eval_items(&ast)); println!("{:?}", env.eval_items(&ast));
} }
Err(e) => { Err(e) => {
println!( println!(

View file

@ -1,11 +1,11 @@
use logos::Source; use logos::Source;
use rustyline::Editor; use rustyline::Editor;
use crate::{interpret::Scope, parser::Parser}; use crate::{interpret::ExecEnv, parser::Parser};
pub fn repl() { pub fn repl() {
let mut rl = Editor::<()>::new(); let mut rl = Editor::<()>::new();
let mut ctx = Scope::new(); let mut env = ExecEnv::new();
loop { loop {
let readline = rl.readline(":: "); let readline = rl.readline(":: ");
match readline { match readline {
@ -19,7 +19,7 @@ pub fn repl() {
match ast { match ast {
Ok(ast) => { Ok(ast) => {
println!("{:?}", ast); println!("{:?}", ast);
println!("{:?}", ctx.eval_items(&ast)); println!("{:?}", env.eval_items(&ast));
}, },
Err(e) => { Err(e) => {
println!( println!(

View file

@ -71,17 +71,23 @@ impl TryFrom<Value> for i32 {
} }
} }
impl TryFrom<Value> for bool { // Coercions from a value to a boolean always succeed, so every value
type Error = Error; // can be used in an `if` statement. C does things that way, so how
// could it possibly be a bad idea?
fn try_from(value: Value) -> Result<Self, Self::Error> { impl From<Value> for bool {
fn from(value: Value) -> Self {
match value { match value {
Value::Bool(b) => Ok(b), // Booleans and abooleans have a trivial conversion.
Value::Abool(b) => Ok(b.into()), Value::Bool(b) => b,
_ => Err(Error { Value::Abool(b) => b.into(),
kind: ErrorKind::TypeError(format!("Expected bool, got {}", value)), // The empty string is falsey, other strings are truthy.
position: 0..0, 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,
} }
} }
} }