expression evaluation.

main
bog 2024-03-06 08:08:56 +01:00
parent 1cc295a63b
commit b72614afaf
8 changed files with 333 additions and 0 deletions

14
src/core/cmd.rs Normal file
View File

@ -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::*;
}

183
src/core/eval.rs Normal file
View File

@ -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(())
}
}

10
src/core/expr.rs Normal file
View File

@ -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>)
}

14
src/core/instr.rs Normal file
View File

@ -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>)
}

6
src/core/mod.rs Normal file
View File

@ -0,0 +1,6 @@
pub mod expr;
pub mod instr;
pub mod value;
pub mod eval;
pub mod sym;
pub mod cmd;

91
src/core/sym.rs Normal file
View File

@ -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)));
}
}

14
src/core/value.rs Normal file
View File

@ -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)
}

View File

@ -1,4 +1,5 @@
mod cli;
mod core;
use crate::cli::parse;