Implement basic interpreter

Added code for interpreting parsed AbleScript expressions and
statements, and hooked it up to the REPL.
This commit is contained in:
Alexander Bethel 2021-05-20 18:18:01 -05:00
parent 0ad680cadd
commit eccc00ff81
6 changed files with 202 additions and 6 deletions

View file

@ -11,4 +11,7 @@ pub enum ErrorKind {
SyntaxError(String), SyntaxError(String),
EndOfTokenStream, EndOfTokenStream,
InvalidIdentifier, InvalidIdentifier,
UnknownVariable(String),
MeloVariable(String),
TypeError(String),
} }

127
src/interpret.rs Normal file
View file

@ -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<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.
}
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<Value, Error> {
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<Value, Error> {
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<Value, Error> {
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!()
}
}
}
}

View file

@ -3,6 +3,7 @@
mod base_55; mod base_55;
mod brian; mod brian;
mod error; mod error;
mod interpret;
mod lexer; mod lexer;
mod parser; mod parser;
mod repl; mod repl;
@ -50,7 +51,7 @@ fn main() {
} }
None => { None => {
println!( println!(
"Hi [AbleScript {}] - AST Printer", "Hi [AbleScript {}] - AST Printer & Interpreter",
env!("CARGO_PKG_VERSION") env!("CARGO_PKG_VERSION")
); );
repl::repl(); repl::repl();

View file

@ -1,4 +1,4 @@
mod item; pub mod item;
mod ops; mod ops;
mod utils; mod utils;

View file

@ -1,10 +1,11 @@
use logos::Source; use logos::Source;
use rustyline::Editor; use rustyline::Editor;
use crate::parser::Parser; use crate::{interpret::Scope, parser::Parser};
pub fn repl() { pub fn repl() {
let mut rl = Editor::<()>::new(); let mut rl = Editor::<()>::new();
let mut ctx = Scope::new();
loop { loop {
let readline = rl.readline(":: "); let readline = rl.readline(":: ");
match readline { match readline {
@ -16,7 +17,10 @@ pub fn repl() {
let mut parser = Parser::new(&line); let mut parser = Parser::new(&line);
let ast = parser.init(); let ast = parser.init();
match ast { match ast {
Ok(ast) => println!("{:?}", ast), Ok(ast) => {
println!("{:?}", ast);
println!("{:?}", ctx.eval_items(&ast));
},
Err(e) => { Err(e) => {
println!( println!(
"Error `{:?}` occured at span: {:?} = `{:?}`", "Error `{:?}` occured at span: {:?} = `{:?}`",

View file

@ -1,5 +1,9 @@
use std::{convert::TryFrom, fmt::Display};
use rand::Rng; use rand::Rng;
use crate::error::{Error, ErrorKind};
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Abool { pub enum Abool {
Never = -1, Never = -1,
@ -7,6 +11,16 @@ pub enum Abool {
Always = 1, 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<Abool> for bool { impl From<Abool> for bool {
fn from(val: Abool) -> Self { fn from(val: Abool) -> Self {
match val { match val {
@ -26,8 +40,55 @@ pub enum Value {
Nul, 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<Value> for i32 {
type Error = Error;
fn try_from(value: Value) -> Result<Self, Self::Error> {
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<Value> for bool {
type Error = Error;
fn try_from(value: Value) -> Result<Self, Self::Error> {
if let Value::Bool(b) = value {
Ok(b)
} else {
Err(Error {
kind: ErrorKind::TypeError(format!("Expected bool, got {}", value)),
position: 0..0,
})
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct Variable { pub struct Variable {
melo: bool, pub melo: bool,
value: Value, pub value: Value,
} }