diff --git a/src/executor.rs b/src/executor.rs index 75930cd..49e3bd5 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; use std::borrow::Cow; use std::fmt::Display; use std::error::Error; +use std::io::{self, Read, Write}; #[derive(Debug)] pub enum RuntimeError { @@ -16,6 +17,7 @@ pub enum RuntimeError { FunctionUndefined(String), NotAVariable(String), ParseFail(String, Type), + IO(io::Error), } impl Display for RuntimeError { @@ -31,6 +33,7 @@ impl Display for RuntimeError { Self::FunctionUndefined(ident) => write!(f, "function `{ident}` was not defined"), Self::NotAVariable(ident) => write!(f, "`{ident}` is a function but was attempted to be used like a variable"), Self::ParseFail(s, t) => write!(f, "`\"{s}\"` couldn't be parsed into {}", t), + Self::IO(e) => write!(f, "{e}"), } } } @@ -60,16 +63,44 @@ enum Object { } /// Executes an input of ParseTrees -pub struct Executor>> { +pub struct Executor<'a, I> +where + I: Iterator>, +{ exprs: I, globals: HashMap, + stdout: Box, + stdin: Box, } -impl>> Executor { +impl<'a, I> Executor<'a, I> +where + I: Iterator>, +{ pub fn new(exprs: I) -> Self { Self { exprs, globals: HashMap::new(), + stdout: Box::new(io::stdout()), + stdin: Box::new(io::stdin()), + } + } + + pub fn stdout(self, writer: impl Write + 'a) -> Self { + Self { + exprs: self.exprs, + globals: self.globals, + stdout: Box::new(writer), + stdin: self.stdin, + } + } + + pub fn stdin(self, reader: impl Read + 'a) -> Self { + Self { + exprs: self.exprs, + globals: self.globals, + stdout: self.stdout, + stdin: Box::new(reader), } } @@ -265,7 +296,7 @@ impl>> Executor { } }, ParseTree::Constant(value) => Ok(value), - ParseTree::ToInt(x) => match self.exec(x, locals)? { + ParseTree::IntCast(x) => match self.exec(x, locals)? { Value::Int(x) => Ok(Value::Int(x)), Value::Float(x) => Ok(Value::Int(x as i64)), Value::Bool(x) => Ok(Value::Int(if x { 1 } else { 0 })), @@ -275,7 +306,7 @@ impl>> Executor { } x => Err(RuntimeError::NoOverloadForTypes("int".into(), vec![x])), }, - ParseTree::ToFloat(x) => match self.exec(x, locals)? { + ParseTree::FloatCast(x) => match self.exec(x, locals)? { Value::Int(x) => Ok(Value::Float(x as f64)), Value::Float(x) => Ok(Value::Float(x)), Value::Bool(x) => Ok(Value::Float(if x { 1.0 } else { 0.0 })), @@ -285,19 +316,25 @@ impl>> Executor { } x => Err(RuntimeError::NoOverloadForTypes("float".into(), vec![x])), }, - ParseTree::ToBool(x) => match self.exec(x, locals)? { + ParseTree::BoolCast(x) => match self.exec(x, locals)? { Value::Int(x) => Ok(Value::Bool(x != 0)), Value::Float(x) => Ok(Value::Bool(x != 0.0)), Value::Bool(x) => Ok(Value::Bool(x)), Value::String(x) => Ok(Value::Bool(!x.is_empty())), x => Err(RuntimeError::NoOverloadForTypes("bool".into(), vec![x])), }, - ParseTree::ToString(x) => Ok(Value::String(format!("{}", self.exec(x, locals)?))), + ParseTree::StringCast(x) => Ok(Value::String(format!("{}", self.exec(x, locals)?))), + ParseTree::Print(x) => match self.exec(x, locals)? { + x => { + writeln!(self.stdout, "{x}").map_err(|e| RuntimeError::IO(e)); + Ok(Value::Nil) + } + } } } } -impl>> Iterator for Executor { +impl<'a, I: Iterator>> Iterator for Executor<'a, I> { type Item = Result; fn next(&mut self) -> Option { diff --git a/src/parser.rs b/src/parser.rs index 64285f9..45e6468 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -70,10 +70,13 @@ pub(crate) enum ParseTree { Constant(Value), // Type Casts - ToInt(Box), - ToFloat(Box), - ToBool(Box), - ToString(Box), + IntCast(Box), + FloatCast(Box), + BoolCast(Box), + StringCast(Box), + + // Misc + Print(Box), } impl ParseTree { @@ -217,10 +220,11 @@ impl ParseTree { Box::new(ParseTree::parse(tokens, globals, locals)?) )), Op::Not => Ok(ParseTree::Not(Box::new(ParseTree::parse(tokens, globals, locals)?))), - Op::IntCast => Ok(ParseTree::ToInt(Box::new(ParseTree::parse(tokens, globals, locals)?))), - Op::FloatCast => Ok(ParseTree::ToFloat(Box::new(ParseTree::parse(tokens, globals, locals)?))), - Op::BoolCast => Ok(ParseTree::ToBool(Box::new(ParseTree::parse(tokens, globals, locals)?))), - Op::StringCast => Ok(ParseTree::ToString(Box::new(ParseTree::parse(tokens, globals, locals)?))), + Op::IntCast => Ok(ParseTree::IntCast(Box::new(ParseTree::parse(tokens, globals, locals)?))), + Op::FloatCast => Ok(ParseTree::FloatCast(Box::new(ParseTree::parse(tokens, globals, locals)?))), + Op::BoolCast => Ok(ParseTree::BoolCast(Box::new(ParseTree::parse(tokens, globals, locals)?))), + Op::StringCast => Ok(ParseTree::StringCast(Box::new(ParseTree::parse(tokens, globals, locals)?))), + Op::Print => Ok(ParseTree::Print(Box::new(ParseTree::parse(tokens, globals, locals)?))) } } } diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 850d0a6..6fc705a 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -64,6 +64,7 @@ pub(crate) enum Op { FloatCast, BoolCast, StringCast, + Print, } #[derive(Debug, Clone)] @@ -121,6 +122,9 @@ impl Token { "bool" => Ok(Token::Operator(Op::BoolCast)), "string" => Ok(Token::Operator(Op::StringCast)), + // misc + "print" => Ok(Token::Operator(Op::Print)), + // then variable length keywords _ => { if s.starts_with(":") {