From 4501905705cc8acb359f477778230b2e10108046 Mon Sep 17 00:00:00 2001 From: Ryan Chandler Date: Thu, 24 Nov 2022 01:43:52 +0000 Subject: [PATCH] interpreter: do enough to get recursive fib working --- Cargo.toml | 3 +- trunk_interpreter/Cargo.toml | 11 + trunk_interpreter/src/cmd/mod.rs | 3 + trunk_interpreter/src/cmd/run.rs | 19 ++ trunk_interpreter/src/engine/environment.rs | 36 +++ trunk_interpreter/src/engine/mod.rs | 242 ++++++++++++++++++++ trunk_interpreter/src/engine/value.rs | 52 +++++ trunk_interpreter/src/main.rs | 23 ++ trunk_lexer/src/byte_string.rs | 7 + trunk_parser/src/lib.rs | 4 +- 10 files changed, 396 insertions(+), 4 deletions(-) create mode 100644 trunk_interpreter/Cargo.toml create mode 100644 trunk_interpreter/src/cmd/mod.rs create mode 100644 trunk_interpreter/src/cmd/run.rs create mode 100644 trunk_interpreter/src/engine/environment.rs create mode 100644 trunk_interpreter/src/engine/mod.rs create mode 100644 trunk_interpreter/src/engine/value.rs create mode 100644 trunk_interpreter/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 0582b7d..fa481e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ "trunk_lexer", - "trunk_parser" + "trunk_parser", + "trunk_interpreter" ] \ No newline at end of file diff --git a/trunk_interpreter/Cargo.toml b/trunk_interpreter/Cargo.toml new file mode 100644 index 0000000..0ac7063 --- /dev/null +++ b/trunk_interpreter/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "trunk_interpreter" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cmder = "0.6.1" +trunk_lexer = { path = "../trunk_lexer" } +trunk_parser = { path = "../trunk_parser" } diff --git a/trunk_interpreter/src/cmd/mod.rs b/trunk_interpreter/src/cmd/mod.rs new file mode 100644 index 0000000..66c2af1 --- /dev/null +++ b/trunk_interpreter/src/cmd/mod.rs @@ -0,0 +1,3 @@ +mod run; + +pub use run::run; \ No newline at end of file diff --git a/trunk_interpreter/src/cmd/run.rs b/trunk_interpreter/src/cmd/run.rs new file mode 100644 index 0000000..3b7d789 --- /dev/null +++ b/trunk_interpreter/src/cmd/run.rs @@ -0,0 +1,19 @@ +use cmder::ParserMatches; +use trunk_lexer::Lexer; +use trunk_parser::Parser; + +use crate::engine::eval; + +pub fn run(matches: ParserMatches) { + // FIXME: Better error handling needed. + let file = matches.get_arg("file").unwrap(); + let contents = std::fs::read_to_string(file).unwrap(); + + let mut lexer = Lexer::new(None); + let tokens = lexer.tokenize(&contents).unwrap(); + + let mut parser = Parser::new(None); + let program = parser.parse(tokens).unwrap(); + + eval(program); +} \ No newline at end of file diff --git a/trunk_interpreter/src/engine/environment.rs b/trunk_interpreter/src/engine/environment.rs new file mode 100644 index 0000000..ce5703f --- /dev/null +++ b/trunk_interpreter/src/engine/environment.rs @@ -0,0 +1,36 @@ +use std::{cell::RefCell, collections::HashMap}; + +use super::value::Value; + +#[derive(Clone, Debug)] +pub struct Environment { + entries: RefCell>, +} + +impl Environment { + pub fn new() -> Self { + Self { + entries: RefCell::new(HashMap::new()), + } + } + + pub fn get(&self, name: &str) -> Option { + let entries = self.entries.borrow(); + + if let Some(value) = entries.get(name) { + return Some(value.clone()); + } + + return None; + } + + pub fn set(&mut self, name: &str, value: Value) { + let mut entries = self.entries.borrow_mut(); + + if let Some(current) = entries.get_mut(name) { + *current = value; + } else { + entries.insert(name.to_owned(), value); + } + } +} \ No newline at end of file diff --git a/trunk_interpreter/src/engine/mod.rs b/trunk_interpreter/src/engine/mod.rs new file mode 100644 index 0000000..5b416d0 --- /dev/null +++ b/trunk_interpreter/src/engine/mod.rs @@ -0,0 +1,242 @@ +use std::collections::HashMap; + +use trunk_parser::{Statement, Param, Expression, InfixOp, CastKind}; + +use self::environment::Environment; +use self::value::Value; + +mod environment; +mod value; + +pub struct Function { + pub(crate) params: Vec, + pub(crate) body: Vec, +} + +pub struct Engine { + pub(crate) global_environment: Environment, + pub(crate) function_table: HashMap, + pub(crate) scopes: Vec, +} + +impl Engine { + pub fn new() -> Self { + Self { + global_environment: Environment::new(), + function_table: HashMap::default(), + scopes: Vec::new(), + } + } +} + +pub fn eval(program: Vec) -> Result<(), Escape> { + let mut engine = Engine::new(); + for statement in program { + eval_statement(&mut engine, statement)?; + } + Ok(()) +} + +pub enum Escape { + Return(Value), +} + +pub fn eval_statement(engine: &mut Engine, statement: Statement) -> Result<(), Escape> { + match statement { + Statement::Function { .. } => eval_function(engine, statement)?, + Statement::Echo { .. } => eval_echo(engine, statement)?, + Statement::If { .. } => eval_if(engine, statement)?, + Statement::Return { .. } => return Err(Escape::Return(eval_return(engine, statement))), + _ => unimplemented!("{:?}", statement) + }; + + Ok(()) +} + +fn eval_function(engine: &mut Engine, statement: Statement) -> Result<(), Escape> { + let (name, params, body, return_type, by_ref) = match statement { + Statement::Function { name, params, body, return_type, by_ref } => (name, params, body, return_type, by_ref), + _ => unreachable!(), + }; + + let func = Function { + params, + body + }; + + engine.function_table.insert(name.name.into(), func); + + Ok(()) +} + +fn eval_echo(engine: &mut Engine, statement: Statement) -> Result<(), Escape> { + let values = match statement { + Statement::Echo { values } => values, + _ => unreachable!() + }; + + for value in values { + let value = eval_expression(engine, value); + + print!("{}", value); + } + + Ok(()) +} + +fn eval_if(engine: &mut Engine, statement: Statement) -> Result<(), Escape> { + let (condition, then, ..) = match statement { + Statement::If { condition, then, else_ifs, r#else } => (condition, then, else_ifs, r#else), + _ => unreachable!() + }; + + let condition = eval_expression(engine, condition); + + if condition.is_truthy() { + for statement in then { + eval_statement(engine, statement)?; + } + } + + Ok(()) +} + +fn eval_return(engine: &mut Engine, statement: Statement) -> Value { + let (value) = match statement { + Statement::Return { value } => value, + _ => unreachable!(), + }; + + if let Some(value) = value { + return eval_expression(engine, value); + } + + return Value::Null; +} + +fn eval_expression(engine: &mut Engine, expression: Expression) -> Value { + match expression { + Expression::Infix { .. } => eval_infix_expression(engine, expression), + Expression::Call { .. } => eval_call_expression(engine, expression), + Expression::Variable { .. } => eval_variable_expression(engine, expression), + Expression::Cast { .. } => eval_cast_expression(engine, expression), + Expression::Int { i } => Value::Int(i), + Expression::ConstantString { value } => Value::String(value.into()), + _ => panic!("unhandled expression: {:?}", expression) + } +} + +fn eval_infix_expression(engine: &mut Engine, expression: Expression) -> Value { + let (lhs, op, rhs) = match expression { + Expression::Infix { lhs, op, rhs } => (lhs, op, rhs), + _ => unreachable!(), + }; + + let lhs = eval_expression(engine, *lhs); + let rhs = eval_expression(engine, *rhs); + + match op { + InfixOp::Add => lhs + rhs, + InfixOp::Sub => lhs - rhs, + InfixOp::LessThan => Value::Bool(lhs < rhs), + InfixOp::Concat => { + match (lhs, rhs) { + (Value::String(a), Value::String(b)) => { + let mut s = String::with_capacity(a.len() + b.len()); + s.push_str(&a); + s.push_str(&b); + Value::String(s) + }, + _ => todo!() + } + }, + _ => todo!("infix: {:?}", op) + } +} + +fn eval_call_expression(engine: &mut Engine, expression: Expression) -> Value { + let (target, args) = match expression { + Expression::Call { target, args } => (target, args), + _ => unreachable!() + }; + + let target: String = match *target { + Expression::Identifier { name } => name.into(), + _ => unreachable!(), + }; + + if !engine.function_table.contains_key(&target) { + panic!("undefined function: {}", target); + } + + let mut arg_values = Vec::new(); + for arg in args { + let value = eval_expression(engine, arg.value); + arg_values.push(value); + } + + let func = engine.function_table.get(&target).unwrap().clone(); + let mut environment = Environment::new(); + + for (i, param) in func.params.clone().into_iter().enumerate() { + let name: String = match param.name { + Expression::Variable { name } => name.into(), + _ => todo!() + }; + + environment.set(&name, arg_values.get(i).unwrap().clone()); + } + + engine.scopes.push(environment); + + let mut return_value = Value::Null; + + for statement in func.body.clone() { + match eval_statement(engine, statement) { + Err(Escape::Return(value)) => { + return_value = value.clone(); + break; + }, + _ => {}, + } + } + + engine.scopes.pop(); + + return_value +} + +fn eval_variable_expression(engine: &mut Engine, expression: Expression) -> Value { + let name: String = match expression { + Expression::Variable { name } => name.into(), + _ => unreachable!(), + }; + + if let Some(scope) = engine.scopes.last() { + if let Some(value) = scope.get(&name) { + return value.clone(); + } else { + panic!("undefined variable: {}", name); + } + } else { + if let Some(value) = engine.global_environment.get(&name) { + return value.clone(); + } else { + panic!("undefined variable: {}", name); + } + } +} + +fn eval_cast_expression(engine: &mut Engine, expression: Expression) -> Value { + let (kind, value) = match expression { + Expression::Cast { kind, value } => (kind, value), + _ => unreachable!() + }; + + let value = eval_expression(engine, *value); + + match (kind, &value) { + (CastKind::String, Value::Int(i)) => Value::String(i.to_string()), + _ => value, + } +} \ No newline at end of file diff --git a/trunk_interpreter/src/engine/value.rs b/trunk_interpreter/src/engine/value.rs new file mode 100644 index 0000000..ad3c4b4 --- /dev/null +++ b/trunk_interpreter/src/engine/value.rs @@ -0,0 +1,52 @@ +use std::{ops::{Add, Sub}, fmt::Display}; + +#[derive(Debug, Clone, PartialOrd, PartialEq)] +pub enum Value { + Null, + Int(i64), + Float(f64), + Bool(bool), + String(String), +} + +impl Value { + pub fn is_truthy(&self) -> bool { + match self { + Value::Null => false, + Value::Bool(false) => false, + Value::Bool(true) => true, + _ => todo!(), + } + } +} + +impl Add for Value { + type Output = Value; + + fn add(self, rhs: Value) -> Self::Output { + match (self, rhs) { + (Value::Int(a), Value::Int(b)) => Value::Int(a + b), + _ => todo!(), + } + } +} + +impl Sub for Value { + type Output = Value; + + fn sub(self, rhs: Value) -> Self::Output { + match (self, rhs) { + (Value::Int(a), Value::Int(b)) => Value::Int(a - b), + _ => todo!(), + } + } +} + +impl Display for Value { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Value::String(s) => write!(f, "{}", s), + _ => todo!(), + } + } +} \ No newline at end of file diff --git a/trunk_interpreter/src/main.rs b/trunk_interpreter/src/main.rs new file mode 100644 index 0000000..7e46b9d --- /dev/null +++ b/trunk_interpreter/src/main.rs @@ -0,0 +1,23 @@ +use cmder::{Program}; + +mod cmd; +mod engine; + +fn main() { + let mut program = Program::new(); + + program + .bin_name("trunk") + .author("Ryan Chandler ") + .description("An alternative interpreter and runtime for PHP."); + + let run_cmd = program.subcommand("run"); + + run_cmd + .description("Run a PHP file through Trunk.") + .alias("r") + .argument("file", "The file you'd like to run.") + .action(cmd::run); + + program.parse(); +} diff --git a/trunk_lexer/src/byte_string.rs b/trunk_lexer/src/byte_string.rs index a10fda0..4068d86 100644 --- a/trunk_lexer/src/byte_string.rs +++ b/trunk_lexer/src/byte_string.rs @@ -1,6 +1,7 @@ use std::cmp::{Eq, PartialEq}; use std::fmt::{Debug, Formatter, Result}; use std::ops::Deref; +use std::str::from_utf8; /// A wrapper for Vec that provides a human-readable Debug impl and /// a few other conveniences. @@ -68,6 +69,12 @@ impl From for ByteString { } } +impl Into for ByteString { + fn into(self) -> String { + String::from(from_utf8(&self.0).unwrap()) + } +} + impl Deref for ByteString { type Target = Vec; diff --git a/trunk_parser/src/lib.rs b/trunk_parser/src/lib.rs index c0ee3c0..7542983 100644 --- a/trunk_parser/src/lib.rs +++ b/trunk_parser/src/lib.rs @@ -2,8 +2,6 @@ mod ast; mod parser; mod traverser; -pub use ast::{ - Block, Case, Catch, Expression, Identifier, InfixOp, MatchArm, Param, Program, Statement, Type, -}; +pub use ast::*; pub use parser::{ParseError, Parser}; pub use traverser::*;