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
0ad680cadd
commit
eccc00ff81
|
@ -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
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 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();
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
mod item;
|
pub mod item;
|
||||||
mod ops;
|
mod ops;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
|
|
|
@ -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: {:?} = `{:?}`",
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue