diff --git a/src/core/cmd.rs b/src/core/cmd.rs new file mode 100644 index 0000000..4363062 --- /dev/null +++ b/src/core/cmd.rs @@ -0,0 +1,14 @@ +use crate::core::value::Value; +use crate::core::expr::Expr; + +pub trait Command { + fn params(&self) -> Vec { vec![] } + fn requires(&self) -> Vec { vec![] } + fn provides(&self) -> Vec { vec![] } + fn call(&mut self, args: Vec) -> Value; +} + +#[cfg(test)] +mod test { + use super::*; +} diff --git a/src/core/eval.rs b/src/core/eval.rs new file mode 100644 index 0000000..5a59ffb --- /dev/null +++ b/src/core/eval.rs @@ -0,0 +1,183 @@ +use std::collections::HashMap; +use std::error::Error; +use crate::core::{ + value::Value, + expr::Expr, + sym::Sym, + cmd::Command +}; + +pub struct Evaluator { + sym: Sym, + commands: HashMap> +} + +impl Evaluator { + pub fn new(sym: Sym) -> Self { + Self { + sym, + commands: HashMap::new() + } + } + + pub fn run(&mut self, expr: &Expr) -> Result> { + match expr { + Expr::BuiltIn(value) => Ok(value.clone()), + Expr::Ident(value) => { + if let Some(val) = self.sym.get(value) { + Ok(val) + } else { + Err(format!("'{}' is not declared", value).into()) + } + }, + + Expr::Call(value, args) => { + let mut val_args: Vec = vec![]; + + for arg in args { + val_args.push(self.run(arg)?); + } + + let mut name = String::from(""); + + match self.sym.get(value) { + Some(Value::Command(n)) => {name = n;} + None => { + return Err( + format!( + "command '{}' is not declared", + value).into() + ); + }, + _ => { + return Err( + format!( + "'{}' is not a command", + value).into() + ); + } + } + + match self.commands.get_mut(&name) { + Some(cmd) => { + Ok(cmd.call(val_args)) + }, + + None => Err( + format!( + "command '{}' is not declared", + value).into() + ) + } + }, + + _ => Err(format!("unknown expr {:?}", expr).into()) + } + } + + pub fn new_command(&mut self, name: &str, cmd: Box) { + self.commands.insert(name.to_string(), cmd); + } + + pub fn sym(&mut self, callback: T) + where T: Fn(&mut Sym) { + callback(&mut self.sym); + } +} + +mod test { + use super::*; + type TestResult = Result<(), Box>; + + fn test_eval_init(expr: Expr, value: Value, init: T) + -> TestResult +where T: Fn(&mut Evaluator) { + let sym = Sym::new(); + let mut eval = Evaluator::new(sym); + init(&mut eval); + let res = eval.run(&expr)?; + + assert_eq!(res, value); + Ok(()) + } + + fn test_eval(expr: Expr, value: Value) -> TestResult { + test_eval_init(expr, value, |_| {}) + } + + #[test] + fn builtins() -> TestResult { + test_eval( + Expr::BuiltIn(Value::Int(42)), + Value::Int(42) + )?; + + test_eval( + Expr::BuiltIn(Value::String("hello".to_string())), + Value::String("hello".to_string()) + )?; + + test_eval( + Expr::BuiltIn(Value::Symbol("hello".to_string())), + Value::Symbol("hello".to_string()), + )?; + + test_eval( + Expr::BuiltIn(Value::Array(vec![ + Value::Bool(true), + Value::Float(3.2), + ])), + + Value::Array(vec![ + Value::Bool(true), + Value::Float(3.2), + ]), + )?; + + Ok(()) + } + + #[test] + fn ident() -> TestResult { + test_eval_init( + Expr::Ident(String::from("X")), + Value::Symbol(String::from("hello")), + |eval| { + eval.sym(|s| { + s.assign("X", Value::Symbol("hello".to_string())); + }) + } + )?; + + Ok(()) + } + + #[test] + fn command() -> TestResult { + struct MockedCmd { + } + + impl Command for MockedCmd { + fn call(&mut self, args: Vec) -> Value { + if let Some(Value::Int(val)) = args.get(0) { + Value::Int(val * 2) + } else { + Value::Int(0) + } + } + } + + test_eval_init( + Expr::Call(String::from("X"), vec![Expr::BuiltIn(Value::Int(12))]), + Value::Int(24), + |eval| { + eval.new_command("CMD", Box::new(MockedCmd {})); + eval.sym(|s| { + s.assign("X", Value::Command(String::from("CMD"))); + }) + } + )?; + + Ok(()) + } +} diff --git a/src/core/expr.rs b/src/core/expr.rs new file mode 100644 index 0000000..a82d925 --- /dev/null +++ b/src/core/expr.rs @@ -0,0 +1,10 @@ +use crate::core::value::Value; + +#[derive(Debug)] +#[derive(PartialEq)] +#[derive(Clone)] +pub enum Expr { + BuiltIn(Value), + Ident(String), + Call(String, Vec) +} diff --git a/src/core/instr.rs b/src/core/instr.rs new file mode 100644 index 0000000..414bb67 --- /dev/null +++ b/src/core/instr.rs @@ -0,0 +1,14 @@ +use crate::core::expr::Expr; + +#[derive(Debug)] +#[derive(PartialEq)] +#[derive(Clone)] +pub enum Instr { + Module(Vec), + Expr(Expr), + Assign(String, Vec), + AssignIf(String, Vec), + AssignArray(String, Vec), + Command(String, Vec, Vec, Vec), + Task(String, Vec) +} diff --git a/src/core/mod.rs b/src/core/mod.rs new file mode 100644 index 0000000..915cf6b --- /dev/null +++ b/src/core/mod.rs @@ -0,0 +1,6 @@ +pub mod expr; +pub mod instr; +pub mod value; +pub mod eval; +pub mod sym; +pub mod cmd; diff --git a/src/core/sym.rs b/src/core/sym.rs new file mode 100644 index 0000000..f13a04e --- /dev/null +++ b/src/core/sym.rs @@ -0,0 +1,91 @@ +use std::collections::HashMap; +use crate::core::value::Value; + +pub struct Sym { + table: HashMap +} + +impl Sym { + pub fn new() -> Self { + Self { + table: HashMap::new() + } + } + + pub fn get(&self, identifier: &str) -> Option { + self.table.get(identifier).cloned() + } + + pub fn assign(&mut self, identifier: &str, value: Value) { + self.table.insert(identifier.to_string(), value); + } + + pub fn assign_array(&mut self, identifier: &str, value: Value) { + match self.table.get(identifier) { + Some(Value::Array(arr)) => { + let mut new_arr: Vec = vec![]; + new_arr.extend(arr.clone()); + new_arr.push(value.clone()); + self.table.insert(identifier.to_string(), Value::Array(new_arr)); + }, + + Some(val) => { + let mut new_arr: Vec = vec![val.clone()]; + new_arr.push(value.clone()); + self.table.insert(identifier.to_string(), Value::Array(new_arr)); + }, + None => { + self.table.insert(identifier.to_string(), Value::Array(vec![ + value.clone() + ])); + } + } + } + + pub fn assign_if(&mut self, identifier: &str, value: Value) { + if self.table.get(identifier).is_none() { + self.table.insert(identifier.to_string(), value); + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn assign() { + let mut sym = Sym::new(); + assert!(sym.get("X").is_none()); + sym.assign("X", Value::Int(34)); + assert_eq!(sym.get("X"), Some(Value::Int(34))); + } + + #[test] + fn assign_array() { + let mut sym = Sym::new(); + assert!(sym.get("X").is_none()); + sym.assign_array("X", Value::Float(6.28)); + + assert_eq!(sym.get("X"), Some(Value::Array(vec![ + Value::Float(6.28), + ]))); + + sym.assign_array("X", Value::Bool(false)); + + assert_eq!(sym.get("X"), Some(Value::Array(vec![ + Value::Float(6.28), + Value::Bool(false) + ]))); + } + + #[test] + fn assign_if() { + let mut sym = Sym::new(); + assert!(sym.get("X").is_none()); + sym.assign_if("X", Value::Float(6.28)); + assert_eq!(sym.get("X"), Some(Value::Float(6.28))); + sym.assign_if("X", Value::Int(79)); + assert_eq!(sym.get("X"), Some(Value::Float(6.28))); + } +} diff --git a/src/core/value.rs b/src/core/value.rs new file mode 100644 index 0000000..4261857 --- /dev/null +++ b/src/core/value.rs @@ -0,0 +1,14 @@ +use crate::core::cmd::Command; + +#[derive(Debug)] +#[derive(PartialEq)] +#[derive(Clone)] +pub enum Value { + Int(i32), + Float(f64), + Bool(bool), + String(String), + Symbol(String), + Array(Vec), + Command(String) +} diff --git a/src/main.rs b/src/main.rs index 0545a4a..7eeb05d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ mod cli; +mod core; use crate::cli::parse;