✨ expression evaluation.
parent
1cc295a63b
commit
b72614afaf
|
@ -0,0 +1,14 @@
|
||||||
|
use crate::core::value::Value;
|
||||||
|
use crate::core::expr::Expr;
|
||||||
|
|
||||||
|
pub trait Command {
|
||||||
|
fn params(&self) -> Vec<Expr> { vec![] }
|
||||||
|
fn requires(&self) -> Vec<Expr> { vec![] }
|
||||||
|
fn provides(&self) -> Vec<Expr> { vec![] }
|
||||||
|
fn call(&mut self, args: Vec<Value>) -> Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
}
|
|
@ -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<String, Box<dyn Command>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Evaluator {
|
||||||
|
pub fn new(sym: Sym) -> Self {
|
||||||
|
Self {
|
||||||
|
sym,
|
||||||
|
commands: HashMap::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self, expr: &Expr) -> Result<Value, Box<dyn Error>> {
|
||||||
|
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<Value> = 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<dyn Command>) {
|
||||||
|
self.commands.insert(name.to_string(), cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sym<T>(&mut self, callback: T)
|
||||||
|
where T: Fn(&mut Sym) {
|
||||||
|
callback(&mut self.sym);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
type TestResult = Result<(), Box<dyn std::error::Error>>;
|
||||||
|
|
||||||
|
fn test_eval_init<T>(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>) -> 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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Expr>)
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
use crate::core::expr::Expr;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum Instr {
|
||||||
|
Module(Vec<Instr>),
|
||||||
|
Expr(Expr),
|
||||||
|
Assign(String, Vec<Expr>),
|
||||||
|
AssignIf(String, Vec<Expr>),
|
||||||
|
AssignArray(String, Vec<Expr>),
|
||||||
|
Command(String, Vec<Expr>, Vec<Expr>, Vec<Instr>),
|
||||||
|
Task(String, Vec<Instr>)
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
pub mod expr;
|
||||||
|
pub mod instr;
|
||||||
|
pub mod value;
|
||||||
|
pub mod eval;
|
||||||
|
pub mod sym;
|
||||||
|
pub mod cmd;
|
|
@ -0,0 +1,91 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use crate::core::value::Value;
|
||||||
|
|
||||||
|
pub struct Sym {
|
||||||
|
table: HashMap<String, Value>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sym {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
table: HashMap::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, identifier: &str) -> Option<Value> {
|
||||||
|
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<Value> = 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<Value> = 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)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Value>),
|
||||||
|
Command(String)
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
mod cli;
|
mod cli;
|
||||||
|
mod core;
|
||||||
|
|
||||||
use crate::cli::parse;
|
use crate::cli::parse;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue