forked from AbleScript/ablescript
Implement basic interpreter
Added code for interpreting parsed AbleScript expressions and statements, and hooked it up to the REPL.
This commit is contained in:
parent
2febc944e4
commit
e31b8fb00d
|
@ -11,4 +11,7 @@ pub enum ErrorKind {
|
|||
SyntaxError(String),
|
||||
EndOfTokenStream,
|
||||
InvalidIdentifier,
|
||||
UnknownVariable(String),
|
||||
MeloVariable(String),
|
||||
TypeError(String),
|
||||
}
|
||||
|
|
127
src/interpret.rs
Normal file
127
src/interpret.rs
Normal 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!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
mod item;
|
||||
pub mod item;
|
||||
mod ops;
|
||||
mod utils;
|
||||
|
||||
|
|
|
@ -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: {:?} = `{:?}`",
|
||||
|
|
|
@ -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<Abool> 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<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)]
|
||||
pub struct Variable {
|
||||
melo: bool,
|
||||
value: Value,
|
||||
pub melo: bool,
|
||||
pub value: Value,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue