From eccc00ff81821ab6398cf5bf126f9796b590ad78 Mon Sep 17 00:00:00 2001 From: Alexander Bethel Date: Thu, 20 May 2021 18:18:01 -0500 Subject: [PATCH 1/3] Implement basic interpreter Added code for interpreting parsed AbleScript expressions and statements, and hooked it up to the REPL. --- src/error.rs | 3 ++ src/interpret.rs | 127 ++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 3 +- src/parser/mod.rs | 2 +- src/repl.rs | 8 ++- src/variables.rs | 65 +++++++++++++++++++++++- 6 files changed, 202 insertions(+), 6 deletions(-) create mode 100644 src/interpret.rs diff --git a/src/error.rs b/src/error.rs index 1b3ac84..3d3b94b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -11,4 +11,7 @@ pub enum ErrorKind { SyntaxError(String), EndOfTokenStream, InvalidIdentifier, + UnknownVariable(String), + MeloVariable(String), + TypeError(String), } diff --git a/src/interpret.rs b/src/interpret.rs new file mode 100644 index 0000000..051b092 --- /dev/null +++ b/src/interpret.rs @@ -0,0 +1,127 @@ +//! 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. + +#[deny(missing_docs)] +use std::collections::HashMap; +use std::convert::TryFrom; + +use crate::{ + error::{Error, ErrorKind}, + parser::item::{Expr, Iden, Item, Stmt}, + 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, + + // 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 { + /// Create a new Scope with no predefined variable definitions or + /// other information. + pub fn new() -> Self { + Self { + variables: HashMap::new(), + } + } + + /// 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. + pub fn eval_items(&mut self, items: &[Item]) -> Result { + items + .iter() + .map(|item| self.eval_item(item)) + .try_fold(Value::Nul, |_, result| result) + } + + /// Evaluate a single Item, returning its value or an error. + fn eval_item(&mut self, item: &Item) -> Result { + match item { + Item::Expr(expr) => self.eval_expr(expr), + Item::Stmt(stmt) => self.eval_stmt(stmt).map(|_| Value::Nul), + } + } + + /// Evaluate an Expr, returning its value or an error. + fn eval_expr(&self, expr: &Expr) -> Result { + use Expr::*; + use Value::*; + + // NOTE(Alex): This is quite nasty, and should probably be + // re-done using macros or something. + Ok(match expr { + Add { left, right } => { + Int(i32::try_from(self.eval_expr(left)?)? + i32::try_from(self.eval_expr(right)?)?) + } + Subtract { left, right } => { + Int(i32::try_from(self.eval_expr(left)?)? - i32::try_from(self.eval_expr(right)?)?) + } + Multiply { left, right } => { + Int(i32::try_from(self.eval_expr(left)?)? * i32::try_from(self.eval_expr(right)?)?) + } + Divide { left, right } => { + Int(i32::try_from(self.eval_expr(left)?)? / i32::try_from(self.eval_expr(right)?)?) + } + Lt { left, right } => { + Bool(i32::try_from(self.eval_expr(left)?)? < i32::try_from(self.eval_expr(right)?)?) + } + Gt { left, right } => { + Bool(i32::try_from(self.eval_expr(left)?)? > i32::try_from(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)?), + 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)?)?), + 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, + }) + } + })?, + }) + } + + /// Perform the action indicated by a statement. + fn eval_stmt(&mut self, stmt: &Stmt) -> Result<(), Error> { + match stmt { + Stmt::Print(expr) => { + println!("{}", self.eval_expr(expr)?); + Ok(()) + } + _ => { + todo!() + } + } + } +} diff --git a/src/main.rs b/src/main.rs index e72027f..0578230 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ mod base_55; mod brian; mod error; +mod interpret; mod lexer; mod parser; mod repl; @@ -50,7 +51,7 @@ fn main() { } None => { println!( - "Hi [AbleScript {}] - AST Printer", + "Hi [AbleScript {}] - AST Printer & Interpreter", env!("CARGO_PKG_VERSION") ); repl::repl(); diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f6acf6d..aea7710 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,4 +1,4 @@ -mod item; +pub mod item; mod ops; mod utils; diff --git a/src/repl.rs b/src/repl.rs index 4aa3038..58ac398 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -1,10 +1,11 @@ use logos::Source; use rustyline::Editor; -use crate::parser::Parser; +use crate::{interpret::Scope, parser::Parser}; pub fn repl() { let mut rl = Editor::<()>::new(); + let mut ctx = Scope::new(); loop { let readline = rl.readline(":: "); match readline { @@ -16,7 +17,10 @@ pub fn repl() { let mut parser = Parser::new(&line); let ast = parser.init(); match ast { - Ok(ast) => println!("{:?}", ast), + Ok(ast) => { + println!("{:?}", ast); + println!("{:?}", ctx.eval_items(&ast)); + }, Err(e) => { println!( "Error `{:?}` occured at span: {:?} = `{:?}`", diff --git a/src/variables.rs b/src/variables.rs index 7caeb0f..2ea96e4 100644 --- a/src/variables.rs +++ b/src/variables.rs @@ -1,5 +1,9 @@ +use std::{convert::TryFrom, fmt::Display}; + use rand::Rng; +use crate::error::{Error, ErrorKind}; + #[derive(Debug, Clone, PartialEq)] pub enum Abool { Never = -1, @@ -7,6 +11,16 @@ pub enum Abool { Always = 1, } +impl Display for Abool { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Abool::Never => write!(f, "never"), + Abool::Sometimes => write!(f, "sometimes"), + Abool::Always => write!(f, "always"), + } + } +} + impl From for bool { fn from(val: Abool) -> Self { match val { @@ -26,8 +40,55 @@ pub enum Value { Nul, } +impl Display for Value { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Value::Str(v) => write!(f, "{}", v), + Value::Int(v) => write!(f, "{}", v), + Value::Bool(v) => write!(f, "{}", v), + Value::Abool(v) => write!(f, "{}", v), + Value::Nul => write!(f, "nul"), + } + } +} + +impl TryFrom for i32 { + type Error = Error; + + fn try_from(value: Value) -> Result { + if let Value::Int(i) = value { + Ok(i) + } else { + Err(Error { + kind: ErrorKind::TypeError(format!("Expected int, got {}", value)), + // TODO: either add some kind of metadata to `Value` + // so we can tell where the value came from and assign + // this `position` correctly, or re-write the + // `error::Error` struct so we can omit the `position` + // when using some error kinds. + position: 0..0, + }) + } + } +} + +impl TryFrom for bool { + type Error = Error; + + fn try_from(value: Value) -> Result { + if let Value::Bool(b) = value { + Ok(b) + } else { + Err(Error { + kind: ErrorKind::TypeError(format!("Expected bool, got {}", value)), + position: 0..0, + }) + } + } +} + #[derive(Debug)] pub struct Variable { - melo: bool, - value: Value, + pub melo: bool, + pub value: Value, } From ea211fc3b093b12d44a18974c88fdf7d4e8676af Mon Sep 17 00:00:00 2001 From: Alexander Bethel Date: Thu, 20 May 2021 18:24:18 -0500 Subject: [PATCH 2/3] Make `ablescript -f` run interpreter `ablescript -f foo.able` will now both parse and interpret `foo.able`, rather than just parsing it. --- src/main.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 0578230..94626c8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ mod repl; mod variables; use clap::{App, Arg}; +use interpret::Scope; use logos::Source; use parser::Parser; @@ -38,7 +39,11 @@ fn main() { let mut parser = Parser::new(&source); let ast = parser.init(); match ast { - Ok(ast) => println!("{:#?}", ast), + Ok(ast) => { + println!("{:#?}", ast); + let mut ctx = Scope::new(); + println!("{:?}", ctx.eval_items(&ast)); + } Err(e) => { println!( "Error `{:?}` occured at span: {:?} = `{:?}`", From b625a71711cc2226e3443b6ec2a914c2d6d9f82a Mon Sep 17 00:00:00 2001 From: Alexander Bethel Date: Fri, 21 May 2021 12:25:37 -0500 Subject: [PATCH 3/3] Allow abool -> bool coercion The expression `sometimes & true` now evaluates to `true` 50% of the time and false 50% of the time, rather than throwing a type error because `sometimes` is not a bool. --- src/variables.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/variables.rs b/src/variables.rs index 2ea96e4..3150b44 100644 --- a/src/variables.rs +++ b/src/variables.rs @@ -56,10 +56,9 @@ impl TryFrom for i32 { type Error = Error; fn try_from(value: Value) -> Result { - if let Value::Int(i) = value { - Ok(i) - } else { - Err(Error { + match value { + Value::Int(i) => Ok(i), + _ => Err(Error { kind: ErrorKind::TypeError(format!("Expected int, got {}", value)), // TODO: either add some kind of metadata to `Value` // so we can tell where the value came from and assign @@ -67,7 +66,7 @@ impl TryFrom for i32 { // `error::Error` struct so we can omit the `position` // when using some error kinds. position: 0..0, - }) + }), } } } @@ -76,13 +75,13 @@ impl TryFrom for bool { type Error = Error; fn try_from(value: Value) -> Result { - if let Value::Bool(b) = value { - Ok(b) - } else { - Err(Error { + 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, - }) + }), } } }