use super::error::{ BadChildCount, IncompatibleValues, InvalidValue, MissingAttribute, MissingChild, UnknownVariable, }; use super::{util, Context, Value}; use roxmltree::Node; use std::cell::RefCell; use std::error::Error; use std::rc::Rc; #[derive(Clone, Debug)] pub enum Instruction { Value(String), Assign(String, Box), Integer(String), IntegerCast(Box), Float(String), FloatCast(Box), String(String), StringCast(Box), Array(Vec), Add(Vec), Subtract(Vec), Multiply(Vec), Divide(Vec), And(Vec), Or(Vec), Not(Box), Equal(Box, Box), Greater(Box, Box), Lower(Box, Box), Call(Box, Vec), CallNamed(String, Vec), Return(Box), If(Box, Vec), IfElse(Box, Vec, Vec), For { variable: String, from: Box, to: Box, step: Box, body: Vec, }, Each(String, Box, Vec), While(Box, Vec), Handle(Vec, Vec, String), } impl Instruction { pub fn new(node: Node) -> Result> { Ok(match util::tag_name(&node).as_str() { "value" => Instruction::Value( node.attribute("variable") .and_then(|a| Some(String::from(a))) .ok_or(MissingAttribute("value", "variable"))?, ), "assign" => Instruction::Assign( String::from( node.attribute("variable") .ok_or(MissingAttribute("assign", "variable"))?, ), Box::new(Instruction::new( node.first_element_child() .ok_or(MissingChild("assign", "value"))?, )?), ), "integer" => { if let Some(v) = node.attribute("value") { Instruction::Integer(String::from(v)) } else if let Some(n) = node.first_element_child() { Instruction::IntegerCast(Box::new(Instruction::new(n)?)) } else { Err(MissingAttribute("integer", "value"))? } } "float" => { if let Some(v) = node.attribute("value") { Instruction::Float(String::from(v)) } else if let Some(n) = node.first_element_child() { Instruction::FloatCast(Box::new(Instruction::new(n)?)) } else { Err(MissingAttribute("float", "value"))? } } "string" => { if let Some(v) = node.attribute("value") { Instruction::String(String::from(v)) } else if let Some(n) = node.first_element_child() { Instruction::StringCast(Box::new(Instruction::new(n)?)) } else { Err(MissingAttribute("string", "value"))? } } "array" => Instruction::Array(Instruction::from_children(node)?), "add" => Instruction::Add(Instruction::from_children(node)?), "subtract" => Instruction::Subtract(Instruction::from_children(node)?), "multiply" => Instruction::Multiply(Instruction::from_children(node)?), "divide" => Instruction::Divide(Instruction::from_children(node)?), "and" => Instruction::And(Instruction::from_children(node)?), "or" => Instruction::Or(Instruction::from_children(node)?), "not" => Instruction::Not(Box::new(Instruction::new( node.first_element_child() .ok_or(MissingAttribute("not", "value"))?, )?)), "equal" => { let children: Vec = node.children().filter(Node::is_element).collect(); if children.len() == 2 { Instruction::Equal( Box::new(Instruction::new(children[0])?), Box::new(Instruction::new(children[1])?), ) } else { Err(BadChildCount("equal", children.len()))? } } "greater" => { let children: Vec = node.children().filter(Node::is_element).collect(); if children.len() == 2 { Instruction::Greater( Box::new(Instruction::new(children[0])?), Box::new(Instruction::new(children[1])?), ) } else { Err(BadChildCount("greater", children.len()))? } } "lower" => { let children: Vec = node.children().filter(Node::is_element).collect(); if children.len() == 2 { Instruction::Lower( Box::new(Instruction::new(children[0])?), Box::new(Instruction::new(children[1])?), ) } else { Err(BadChildCount("lower", children.len()))? } } "call" => { if let Some(function) = node.attribute("function") { Instruction::CallNamed( String::from(function), Instruction::from_children( util::find_node(&node, "arguments") .ok_or(MissingChild("call", "arguments"))?, )?, ) } else { Instruction::Call( Box::new(Instruction::new( node.first_element_child() .ok_or(MissingChild("call", "function"))?, )?), Instruction::from_children( util::find_node(&node, "arguments") .ok_or(MissingChild("call", "arguments"))?, )?, ) } } "return" => Instruction::Return(Box::new(Instruction::new( node.first_element_child() .ok_or(MissingChild("return", "value"))?, )?)), "if" => { if let Some(else_node) = node.children().find(|n| util::tag_name(n) == "else") { Instruction::IfElse( Box::new(Instruction::new( node.first_element_child() .ok_or(MissingChild("if", "condition"))?, )?), Instruction::from_children( util::find_node(&node, "then").ok_or(MissingChild("if", "then"))?, )?, Instruction::from_children(else_node)?, ) } else { Instruction::If( Box::new(Instruction::new( node.first_element_child() .ok_or(MissingChild("if", "condition"))?, )?), Instruction::from_children( util::find_node(&node, "then").ok_or(MissingChild("if", "then"))?, )?, ) } } "for" => Instruction::For { variable: String::from( node.attribute("variable") .ok_or(MissingAttribute("for", "variable"))?, ), from: Box::new(Instruction::new( util::find_node(&node, "from") .and_then(|n| n.first_element_child()) .ok_or(MissingChild("for", "from"))?, )?), to: Box::new(Instruction::new( util::find_node(&node, "to") .and_then(|n| n.first_element_child()) .ok_or(MissingChild("for", "to"))?, )?), step: Box::new(Instruction::new( util::find_node(&node, "step") .and_then(|n| n.first_element_child()) .ok_or(MissingChild("for", "step"))?, )?), body: Instruction::from_children( util::find_node(&node, "do").ok_or(MissingChild("for", "do"))?, )?, }, "each" => Instruction::Each( String::from( node.attribute("variable") .ok_or(MissingAttribute("each", "variable"))?, ), Box::new(Instruction::new( node.first_element_child() .ok_or(MissingChild("each", "array"))?, )?), Instruction::from_children( util::find_node(&node, "do").ok_or(MissingChild("each", "from"))?, )?, ), "while" => Instruction::While( Box::new(Instruction::new( node.first_element_child() .ok_or(MissingChild("while", "condition"))?, )?), Instruction::from_children( util::find_node(&node, "do").ok_or(MissingChild("while", "from"))?, )?, ), "handle" => Instruction::Handle( Instruction::from_children( util::find_node(&node, "try").ok_or(MissingChild("handle", "try"))?, )?, Instruction::from_children( util::find_node(&node, "catch").ok_or(MissingChild("handle", "try"))?, )?, util::find_node(&node, "catch") .ok_or(MissingChild("handle", "try"))? .attribute("variable") .ok_or(MissingAttribute("catch", "variable"))? .to_string(), ), tag => Err(format!("unknown tag '{}'", tag))?, }) } pub fn from_children(node: Node) -> Result, Box> { node.children() .filter(Node::is_element) .map(Instruction::new) .collect() } fn add(vals: Vec) -> Result> { if vals.iter().all(|v| matches!(v, Value::Integer(_))) { Ok(Value::Integer( vals.iter() .map(|v| { if let Value::Integer(i) = v { Ok(i) } else { Err(InvalidValue("add"))? } }) .sum::>>()?, )) } else if vals .iter() .all(|v| matches!(v, Value::Integer(_)) || matches!(v, Value::Float(_))) { Ok(Value::Float( vals.iter() .map(|v| { if let Value::Integer(i) = v { Ok(*i as f64) } else if let Value::Float(f) = v { Ok(*f) } else { Err(InvalidValue("add"))? } }) .sum::>>()?, )) } else if vals.iter().all(|v| { matches!(v, Value::Integer(_)) || matches!(v, Value::Float(_)) || matches!(v, Value::String(_)) }) { Ok(Value::String( vals.iter() .map(|v| { Ok(if let Value::String(s) = v { s.to_string() } else if let Value::Integer(i) = v { i.to_string() } else if let Value::Float(f) = v { f.to_string() } else { Err(InvalidValue("add"))? }) }) .collect::, Box>>()? .join(""), )) } else { Err(InvalidValue("add"))? } } fn subtract(vals: Vec) -> Result> { Ok(if vals.iter().all(|v| matches!(v, Value::Integer(_))) { let first = match vals.first().ok_or(BadChildCount("subtract", 0usize))? { Value::Integer(i) => i, _ => Err(InvalidValue("subtract"))?, }; Value::Integer( *first - vals .iter() .skip(1) .map(|v| { if let Value::Integer(i) = v { Ok(i) } else { Err(InvalidValue("subtract"))? } }) .sum::>>()?, ) } else if vals .iter() .all(|v| matches!(v, Value::Integer(_)) || matches!(v, Value::Float(_))) { let first = match vals.first().ok_or(BadChildCount("subtract", 0usize))? { Value::Integer(v) => *v as f64, Value::Float(v) => *v, _ => Err(InvalidValue("subtract"))?, }; Value::Float( first - vals .iter() .skip(1) .map(|val| { Ok(match val { Value::Integer(v) => *v as f64, Value::Float(v) => *v, _ => Err(InvalidValue("subtract"))?, }) }) .sum::>>()?, ) } else { Err(InvalidValue("subtract"))? }) } fn multiply(vals: Vec) -> Result> { if vals.iter().all(|v| matches!(v, Value::Integer(_))) { Ok(Value::Integer( vals.iter() .map(|v| { if let Value::Integer(i) = v { Ok(i) } else { Err(InvalidValue("multiply"))? } }) .product::>>()?, )) } else if vals .iter() .all(|v| matches!(v, Value::Integer(_)) || matches!(v, Value::Float(_))) { Ok(Value::Float( vals.iter() .map(|val| { Ok(match val { Value::Integer(v) => *v as f64, Value::Float(v) => *v, _ => Err(InvalidValue("multiply"))?, }) }) .product::>>()?, )) } else { Err(InvalidValue("multiply"))? } } fn divide(vals: Vec) -> Result> { if vals .iter() .all(|v| matches!(v, Value::Integer(_)) || matches!(v, Value::Float(_))) { let first = match vals.first().ok_or(BadChildCount("divide", 0))? { Value::Integer(v) => *v as f64, Value::Float(v) => *v, _ => Err(InvalidValue("divide"))?, }; Ok(Value::Float( first * vals .iter() .skip(1) .map(|val| { Ok(match val { Value::Integer(v) => 1.0 / (*v as f64), Value::Float(v) => 1.0 / *v, _ => Err(InvalidValue("divide"))?, }) }) .product::>>()?, )) } else { Err(InvalidValue("divide"))? } } fn and(vals: Vec) -> Value { Value::Integer(if vals.iter().all(Value::to_bool) { 1 } else { 0 }) } fn or(vals: Vec) -> Value { Value::Integer(if vals.iter().any(Value::to_bool) { 1 } else { 0 }) } fn compare(v1: Value, v2: Value) -> Result> { use std::cmp::Ordering; match v1 { Value::Integer(i1) => match v2 { Value::Integer(i2) => Ok(i1 - i2), Value::Float(f2) => Ok( match (i1 as f64).partial_cmp(&f2).ok_or(IncompatibleValues)? { Ordering::Less => -1, Ordering::Equal => 0, Ordering::Greater => 1, }, ), _ => Err(IncompatibleValues)?, }, Value::Float(f1) => match v2 { Value::Integer(i2) => Ok( match f1.partial_cmp(&(i2 as f64)).ok_or(IncompatibleValues)? { Ordering::Less => -1, Ordering::Equal => 0, Ordering::Greater => 1, }, ), Value::Float(f2) => Ok(match f1.partial_cmp(&f2).ok_or(IncompatibleValues)? { Ordering::Less => -1, Ordering::Equal => 0, Ordering::Greater => 1, }), _ => Err(IncompatibleValues)?, }, Value::String(s1) => { if let Value::String(s2) = v2 { Ok(match s1.cmp(&s2) { Ordering::Less => -1, Ordering::Equal => 0, Ordering::Greater => 1, }) } else { Err(IncompatibleValues)? } } _ => Err(IncompatibleValues)?, } } fn run_all( ins: &Vec, ctx: &mut Context, globals: &Context, ) -> Result>, Box> { ins.iter().map(|i| i.run(ctx, globals)).collect() } pub fn run( &self, ctx: &mut Context, globals: &Context, ) -> Result, Box> { Ok(if let None = ctx.value(&String::from("__return")) { match self { Instruction::Value(key) => { Some(match ctx.value(key).ok_or(UnknownVariable(key.clone()))? { Value::Array(vecrc) => Value::Array(Rc::clone(vecrc)), val => val.clone(), }) } Instruction::Assign(key, ins) => { let v = ins.run(ctx, globals)?.ok_or(InvalidValue("assign"))?; ctx.assign(key.clone(), v); None } Instruction::Integer(val) => Some(Value::Integer(val.parse()?)), Instruction::IntegerCast(ins) => Some(Value::Integer( match ins.run(ctx, globals)?.ok_or(InvalidValue("integer"))? { Value::Integer(i) => i, Value::Float(f) => f as i64, Value::String(s) => s.parse()?, _ => Err(InvalidValue("integer"))?, }, )), Instruction::Float(val) => Some(Value::Float(val.parse()?)), Instruction::FloatCast(ins) => Some(Value::Float( match ins.run(ctx, globals)?.ok_or(InvalidValue("float"))? { Value::Integer(i) => i as f64, Value::Float(f) => f, Value::String(s) => s.parse()?, _ => Err(InvalidValue("float"))?, }, )), Instruction::String(val) => Some(Value::String(val.clone())), Instruction::StringCast(ins) => Some(Value::String( match ins.run(ctx, globals)?.ok_or(InvalidValue("string"))? { Value::Integer(i) => i.to_string(), Value::Float(f) => f.to_string(), Value::String(s) => s, _ => Err(InvalidValue("string"))?, }, )), Instruction::Array(args) => Some(Value::Array(Rc::new(RefCell::new( Instruction::run_all(args, ctx, globals)?.ok_or(InvalidValue("array"))?, )))), Instruction::Add(args) => { let vals = Instruction::run_all(args, ctx, globals)?.ok_or(InvalidValue("add"))?; Some(Instruction::add(vals)?) } Instruction::Subtract(args) => { let vals = Instruction::run_all(args, ctx, globals)? .ok_or(InvalidValue("subtract"))?; Some(Instruction::subtract(vals)?) } Instruction::Multiply(args) => { let vals = Instruction::run_all(args, ctx, globals)? .ok_or(InvalidValue("multiply"))?; Some(Instruction::multiply(vals)?) } Instruction::Divide(args) => { let vals = Instruction::run_all(args, ctx, globals)?.ok_or(InvalidValue("divide"))?; Some(Instruction::divide(vals)?) } Instruction::And(args) => { let vals = Instruction::run_all(args, ctx, globals)?.ok_or(InvalidValue("and"))?; Some(Instruction::and(vals)) } Instruction::Or(args) => { let vals = Instruction::run_all(args, ctx, globals)?.ok_or(InvalidValue("or"))?; Some(Instruction::or(vals)) } Instruction::Not(arg) => Some(Value::Integer( if arg.run(ctx, globals)?.ok_or(InvalidValue("not"))?.to_bool() { 0 } else { 1 }, )), Instruction::Equal(v1, v2) => Some(Value::Integer( if Instruction::compare( v1.run(ctx, globals)?.ok_or(InvalidValue("equal"))?, v2.run(ctx, globals)?.ok_or(InvalidValue("equal"))?, )? == 0 { 1 } else { 0 }, )), Instruction::Greater(v1, v2) => Some(Value::Integer( if Instruction::compare( v1.run(ctx, globals)?.ok_or(InvalidValue("greater"))?, v2.run(ctx, globals)?.ok_or(InvalidValue("greater"))?, )? > 0 { 1 } else { 0 }, )), Instruction::Lower(v1, v2) => Some(Value::Integer( if Instruction::compare( v1.run(ctx, globals)?.ok_or(InvalidValue("lower"))?, v2.run(ctx, globals)?.ok_or(InvalidValue("lower"))?, )? < 0 { 1 } else { 0 }, )), Instruction::Call(fct_ins, args) => { let vals = Instruction::run_all(args, ctx, globals)?.ok_or(InvalidValue("call"))?; let fct_val = fct_ins.run(ctx, globals)?.ok_or(InvalidValue("call"))?; if let Value::Function(f) = fct_val { let mut fun_ctx = Context::new(Some(globals)); f.run(vals, &mut fun_ctx, globals)? } else if let Value::StdFunction(f) = fct_val { f(vals)? } else { Err(InvalidValue("call"))? } } Instruction::CallNamed(fct_name, args) => { let vals: Vec = Instruction::run_all(args, ctx, globals)?.ok_or(InvalidValue("call"))?; let fct_val = ctx .value(&fct_name) .ok_or(UnknownVariable(fct_name.clone()))?; if let Value::Function(f) = fct_val { let mut local = ctx.clone(); f.run(vals, &mut local, globals)? } else if let Value::StdFunction(f) = fct_val { f(vals)? } else { Err(InvalidValue("call"))? } } Instruction::Return(ins) => { let v = ins.run(ctx, globals)?.ok_or(InvalidValue("return"))?; ctx.assign(String::from("__return"), v); None } Instruction::If(cond, then) => { if cond.run(ctx, globals)?.ok_or(InvalidValue("if"))?.to_bool() { for i in then { i.run(ctx, globals)?; } } None } Instruction::IfElse(cond, then, els) => { if cond.run(ctx, globals)?.ok_or(InvalidValue("if"))?.to_bool() { for i in then { i.run(ctx, globals)?; } } else { for i in els { i.run(ctx, globals)?; } } None } Instruction::For { variable, from, to, step, body, } => { if let Value::Integer(f) = from.run(ctx, globals)?.ok_or(InvalidValue("for"))? { if let Value::Integer(t) = to.run(ctx, globals)?.ok_or(InvalidValue("for"))? { if let Value::Integer(s) = step.run(ctx, globals)?.ok_or(InvalidValue("for"))? { for i in (f..t).step_by(s.try_into()?) { ctx.assign(variable.clone(), Value::Integer(i)); for ins in body { ins.run(ctx, globals)?; } } } } } None } Instruction::Each(variable, array_ins, body) => { if let Value::Array(v) = array_ins.run(ctx, globals)?.ok_or(InvalidValue("each"))? { for i in v.borrow().iter() { ctx.assign(variable.clone(), i.clone()); for ins in body { ins.run(ctx, globals)?; } } } else { Err(InvalidValue("each"))? } None } Instruction::While(cond, body) => { while cond .run(ctx, globals)? .ok_or(InvalidValue("while"))? .to_bool() { for ins in body { ins.run(ctx, globals)?; } } None } Instruction::Handle(try_block, catch_block, variable) => { for ins in try_block { if let Err(e) = ins.run(ctx, globals) { ctx.assign(variable.clone(), Value::String(e.to_string())); for ins in catch_block { ins.run(ctx, globals)?; } break; } } None } } } else { None }) } }