// Copyright 2017 Jeremy Wall // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! The build stage of the ucg compiler. use std::cell::RefCell; use std::collections::hash_map::Entry; use std::collections::HashMap; use std::env; use std::error::Error; use std::fs::File; use std::io::Read; use std::ops::Deref; use std::path::PathBuf; use std::rc::Rc; use std::string::ToString; use regex; use simple_error; use unicode_segmentation::UnicodeSegmentation; use crate::ast::*; use crate::build::format::{ExpressionFormatter, FormatRenderer, SimpleFormatter}; use crate::build::scope::{find_in_fieldlist, Scope, ValueMap}; use crate::convert::ImporterRegistry; use crate::error; use crate::iter::OffsetStrIter; use crate::parse::parse; pub mod assets; pub mod format; pub mod ir; pub mod scope; mod stdlib; pub use self::ir::Val; enum ProcessingOpType { Map, Filter, } impl FuncDef { /// Expands a ucg function using the given arguments into a new Tuple. pub fn eval( &self, parent_builder: &FileBuilder, mut args: Vec>, ) -> Result, Box> { // Error conditions. If the args don't match the length and types of the argdefs then this is // func call error. if args.len() > self.argdefs.len() { return Err(error::BuildError::with_pos( "Func called with too many args", error::ErrorType::BadArgLen, self.pos.clone(), ) .to_boxed()); } // If the args don't match the types required by the expressions then that is a TypeFail. // If the expressions reference Symbols not defined in the FuncDef that is also an error. let mut build_output = HashMap::, Rc>::new(); for (i, arg) in args.drain(0..).enumerate() { build_output .entry(self.argdefs[i].clone()) .or_insert(arg.clone()); } let mut b = parent_builder.clone_builder(); if let Some(ref scope) = self.scope { b.scope = scope.spawn_child(); } // We clobber anything that used to be in the scope with the arguments. b.merge_build_output(build_output, true); Ok(b.eval_expr(self.fields.as_ref(), &b.scope.spawn_child())?) } } /// The result of a build. type BuildResult = Result<(), Box>; /// AssertCollector collects the results of assertions in the UCG AST. pub struct AssertCollector { pub counter: i32, pub success: bool, pub summary: String, pub failures: String, } /// Builder handles building ucg code for a single file. pub struct FileBuilder<'a> { working_dir: PathBuf, std: Rc>, import_path: &'a Vec, validate_mode: bool, pub assert_collector: AssertCollector, scope: Scope, import_registry: ImporterRegistry, // NOTE(jwall): We use interior mutability here because we need // our asset cache to be shared by multiple different sub-builders. // We use Rc to handle the reference counting for us and we use // RefCell to give us interior mutability. This sacrifices our // compile time memory safety for runtime checks. However it's // acceptable in this case since I can't figure out a better way to // handle it. // The assets are other parsed files from import statements. They // are keyed by the canonicalized import path. This acts as a cache // so multiple imports of the same file don't have to be parsed // multiple times. assets: Rc>, pub is_module: bool, pub last: Option>, pub out_lock: Option<(String, Rc)>, } macro_rules! eval_binary_expr { ($case:pat, $pos:ident, $rside:ident, $result:expr, $msg:expr) => { match $rside.as_ref() { $case => { return Ok(Rc::new($result)); } val => { return Err(error::BuildError::with_pos( format!("Expected {} but got ({})", $msg, val), error::ErrorType::TypeFail, $pos.clone(), ) .to_boxed()); } } }; } // TODO(jwall): Use the builder patter here. Just like AstWalker. impl<'a> FileBuilder<'a> { /// Constructs a new Builder. pub fn new>( working_dir: P, import_paths: &'a Vec, cache: Rc>, ) -> Self { let env_vars: Vec<(String, String)> = env::vars().collect(); let scope = scope::Scope::new(Rc::new(Val::Env(env_vars))); Self::new_with_scope(working_dir, import_paths, cache, scope) } /// Constructs a new Builder with a provided scope. pub fn new_with_scope>( working_dir: P, import_paths: &'a Vec, cache: Rc>, scope: Scope, ) -> Self { FileBuilder { // Our import stack is initialized with ourself. working_dir: working_dir.into(), std: Rc::new(stdlib::get_libs()), import_path: import_paths, validate_mode: false, assert_collector: AssertCollector { counter: 0, success: true, summary: String::new(), failures: String::new(), }, scope: scope, import_registry: ImporterRegistry::make_registry(), assets: cache, out_lock: None, is_module: false, last: None, } } pub fn clone_builder(&self) -> Self { FileBuilder { working_dir: self.working_dir.clone(), std: self.std.clone(), import_path: self.import_path, validate_mode: false, assert_collector: AssertCollector { counter: 0, success: true, summary: String::new(), failures: String::new(), }, assets: self.assets.clone(), // This is admittedly a little wasteful but we can live with it for now. import_registry: ImporterRegistry::make_registry(), scope: self.scope.spawn_clean(), out_lock: None, is_module: false, last: None, } } // TODO(jwall): With builder pattern pub fn set_build_output(&mut self, scope: ValueMap) { self.scope.build_output = scope; } /// Builds a ucg file at the named path. pub fn build>(&mut self, file: P) -> BuildResult { let file = file.into(); self.working_dir = file.parent().unwrap().to_path_buf(); let mut f = self.open_file(&Position::new(0, 0, 0), &file)?; let mut s = String::new(); f.read_to_string(&mut s)?; let input = OffsetStrIter::new(&s).with_src_file(file.clone()); let eval_result = self.eval_input(input); match eval_result { Ok(v) => { self.last = Some(v); Ok(()) } Err(e) => { let err = simple_error::SimpleError::new( format!( "Error building file: {}\n{}", file.to_string_lossy(), e.as_ref() ) .as_ref(), ); Err(Box::new(err)) } } } pub fn merge_build_output(&mut self, scope: ValueMap, clobber: bool) { for (name, value) in scope.iter() { if !clobber && !self.scope.build_output.contains_key(name) { self.scope.build_output.insert(name.clone(), value.clone()); } else { self.scope.build_output.insert(name.clone(), value.clone()); } } } pub fn set_strict(&mut self, to: bool) { self.scope.strict = to; } fn eval_tuple( &self, fields: &Vec<(Token, Expression)>, scope: &Scope, ) -> Result, Box> { let mut new_fields = Vec::<(String, Rc)>::new(); for &(ref name, ref expr) in fields.iter() { let val = self.eval_expr(expr, scope)?; new_fields.push((name.fragment.clone(), val)); } Ok(Rc::new(Val::Tuple(new_fields))) } fn eval_list(&self, def: &ListDef, scope: &Scope) -> Result, Box> { let mut vals = Vec::new(); for expr in def.elems.iter() { vals.push(self.eval_expr(expr, scope)?); } Ok(Rc::new(Val::List(vals))) } fn eval_value(&self, v: &Value, scope: &Scope) -> Result, Box> { match v { &Value::Empty(_) => Ok(Rc::new(Val::Empty)), &Value::Boolean(ref b) => Ok(Rc::new(Val::Boolean(b.val))), &Value::Int(ref i) => Ok(Rc::new(Val::Int(i.val))), &Value::Float(ref f) => Ok(Rc::new(Val::Float(f.val))), &Value::Str(ref s) => Ok(Rc::new(Val::Str(s.val.to_string()))), &Value::Symbol(ref s) => { scope .lookup_sym(&(s.into()), true) .ok_or(Box::new(error::BuildError::with_pos( format!("Unable to find binding {}", s.val,), error::ErrorType::NoSuchSymbol, v.pos().clone(), ))) } &Value::List(ref def) => self.eval_list(def, scope), &Value::Tuple(ref tuple) => self.eval_tuple(&tuple.val, scope), } } /// Returns a Val by name from previously built UCG. pub fn get_out_by_name(&self, name: &str) -> Option> { let key = PositionedItem { pos: Position::new(0, 0, 0), val: name.to_string(), }; self.scope.lookup_sym(&key, true) } /// Puts the builder in validation mode. /// /// Among other things this means that assertions will be evaluated and their results /// will be saved in a report for later output. pub fn enable_validate_mode(&mut self) { self.validate_mode = true; } /// Builds a list of parsed UCG Statements. pub fn eval_stmts(&mut self, ast: &Vec) -> BuildResult { for stmt in ast.iter() { self.eval_stmt(stmt)?; } Ok(()) } fn eval_input(&mut self, input: OffsetStrIter) -> Result, Box> { match parse(input.clone()) { Ok(stmts) => { //panic!("Successfully parsed {}", input); let mut out: Option> = None; for stmt in stmts.iter() { out = Some(self.eval_stmt(stmt)?); } match out { None => return Ok(Rc::new(Val::Empty)), Some(val) => Ok(val), } } Err(err) => { let cause = Box::new(simple_error::SimpleError::new(err)); Err(error::BuildError::with_pos( "Unable to parse input", error::ErrorType::ParseError, (&input).into(), ) .wrap_cause(cause) .to_boxed()) } } } /// Evaluate an input string as UCG. pub fn eval_string(&mut self, input: &str) -> Result, Box> { self.eval_input(OffsetStrIter::new(input)) } fn check_reserved_word(name: &str) -> bool { match name { "self" | "assert" | "true" | "false" | "let" | "import" | "as" | "select" | "func" | "module" | "env" | "map" | "filter" | "NULL" | "out" | "in" | "is" | "not" => true, _ => false, } } fn detect_import_cycle(&self, path: &str) -> bool { self.scope .import_stack .iter() .find(|p| *p == path) .is_some() } fn find_file>( &self, path: P, use_import_path: bool, ) -> Result> { // Try a relative path first. let path = path.into(); let mut normalized = self.working_dir.clone(); if path.is_relative() { normalized.push(&path); // First see if the normalized file exists or not. if !normalized.exists() && use_import_path { // If it does not then look for it in the list of import_paths for mut p in self.import_path.iter().cloned() { p.push(&path); if p.exists() { normalized = p; break; } } } } else { normalized = path; } match normalized.canonicalize() { Ok(p) => Ok(p), Err(e) => Err(error::BuildError::new( format!("Path not found {}", normalized.to_string_lossy()), error::ErrorType::OSError, ) .wrap_cause(Box::new(e)) .to_boxed()), } } fn eval_import(&self, def: &ImportDef) -> Result, Box> { // Look for a std file first. if def.path.fragment.starts_with("std/") { if self.std.contains_key(&def.path.fragment) { // Okay then this is a stdlib and it's special. // Introduce a scope so the above borrow is dropped before we modify // the cache below. // Only parse the file once on import. let path = PathBuf::from(&def.path.fragment); let maybe_asset = self.assets.borrow().get(&path)?; let result = match maybe_asset { Some(v) => v.clone(), None => { let mut b = self.clone_builder(); b.eval_string(self.std.get(&def.path.fragment).unwrap())?; b.get_outputs_as_val() } }; let mut mut_assets_cache = self.assets.borrow_mut(); mut_assets_cache.stash(path, result.clone())?; return Ok(result); } else { return Err(error::BuildError::with_pos( format!("No such import {} in the std library.", def.path.fragment), error::ErrorType::Unsupported, def.pos.clone(), ) .to_boxed()); } } // Try a relative path first. let normalized = match self.find_file(&def.path.fragment, true) { Ok(p) => p, Err(e) => { return Err(error::BuildError::with_pos( "No Such Import", error::ErrorType::ImportError, def.path.pos.clone(), ) .wrap_cause(e) .to_boxed()); } }; if self.detect_import_cycle(normalized.to_string_lossy().as_ref()) { return Err(error::BuildError::with_pos( format!( "Import Cycle Detected!!!! {} is already in import stack: {:?}", normalized.to_string_lossy(), self.scope.import_stack, ), error::ErrorType::Unsupported, def.pos.clone(), ) .to_boxed()); } // Introduce a scope so the above borrow is dropped before we modify // the cache below. // Only parse the file once on import. let maybe_asset = self.assets.borrow().get(&normalized)?; let result = match maybe_asset { Some(v) => v.clone(), None => { let mut b = self.clone_builder(); match b.build(&normalized) { Ok(_) => b.get_outputs_as_val(), Err(e) => { return Err(error::BuildError::with_pos( "Import failed", error::ErrorType::ImportError, def.pos.clone(), ) .wrap_cause(e) .to_boxed()); } } } }; let mut mut_assets_cache = self.assets.borrow_mut(); mut_assets_cache.stash(normalized.clone(), result.clone())?; return Ok(result); } fn eval_let(&mut self, def: &LetDef) -> Result, Box> { let child_scope = self.scope.clone(); let val = self.eval_expr(&def.value, &child_scope)?; let name = &def.name; if Self::check_reserved_word(&name.fragment) { return Err(error::BuildError::with_pos( format!("Let {} binding collides with reserved word", name.fragment), error::ErrorType::ReservedWordError, name.pos.clone(), ) .to_boxed()); } match self.scope.build_output.entry(name.into()) { Entry::Occupied(e) => { return Err(error::BuildError::with_pos( format!( "Binding \ for {:?} already \ exists", e.key(), ), error::ErrorType::DuplicateBinding, def.name.pos.clone(), ) .to_boxed()); } Entry::Vacant(e) => { e.insert(val.clone()); } } Ok(val) } fn eval_stmt(&mut self, stmt: &Statement) -> Result, Box> { let child_scope = self.scope.clone(); match stmt { &Statement::Assert(ref expr) => self.eval_assert(&expr, &child_scope), &Statement::Let(ref def) => self.eval_let(def), &Statement::Expression(ref expr) => self.eval_expr(expr, &child_scope), // Only one output can be used per file. Right now we enforce this by // having a single builder per file. &Statement::Output(ref pos, ref typ, ref expr) => { if let None = self.out_lock { let val = self.eval_expr(expr, &child_scope)?; self.out_lock = Some((typ.fragment.to_string(), val.clone())); Ok(val) } else { Err(error::BuildError::with_pos( format!("You can only have one output per file."), error::ErrorType::Unsupported, pos.clone(), ) .to_boxed()) } } } } fn add_vals( &self, lpos: &Position, rpos: &Position, left: Rc, right: Rc, ) -> Result, Box> { match *left { Val::Int(i) => { eval_binary_expr!(&Val::Int(ii), rpos, right, Val::Int(i + ii), "Integer") } Val::Float(f) => { eval_binary_expr!(&Val::Float(ff), rpos, right, Val::Float(f + ff), "Float") } Val::Str(ref s) => match right.as_ref() { &Val::Str(ref ss) => { return Ok(Rc::new(Val::Str([s.to_string(), ss.clone()].concat()))); } val => { return Err(error::BuildError::with_pos( format!( "Expected \ String \ but got \ {:?}", val ), error::ErrorType::TypeFail, rpos.clone(), ) .to_boxed()); } }, Val::List(ref l) => match right.as_ref() { &Val::List(ref r) => { let mut new_vec = Vec::new(); new_vec.extend(l.iter().cloned()); new_vec.extend(r.iter().cloned()); return Ok(Rc::new(Val::List(new_vec))); } val => { return Err(error::BuildError::with_pos( format!( "Expected \ List \ but got \ {:?}", val ), error::ErrorType::TypeFail, rpos.clone(), ) .to_boxed()); } }, ref expr => { return Err(error::BuildError::with_pos( format!("{} does not support the '+' operation", expr.type_name()), error::ErrorType::Unsupported, lpos.clone(), ) .to_boxed()); } } } fn subtract_vals( &self, lpos: &Position, rpos: &Position, left: Rc, right: Rc, ) -> Result, Box> { match *left { Val::Int(i) => { eval_binary_expr!(&Val::Int(ii), rpos, right, Val::Int(i - ii), "Integer") } Val::Float(f) => { eval_binary_expr!(&Val::Float(ff), rpos, right, Val::Float(f - ff), "Float") } ref expr => { return Err(error::BuildError::with_pos( format!("{} does not support the '-' operation", expr.type_name()), error::ErrorType::Unsupported, lpos.clone(), ) .to_boxed()); } } } fn multiply_vals( &self, lpos: &Position, rpos: &Position, left: Rc, right: Rc, ) -> Result, Box> { match *left { Val::Int(i) => { eval_binary_expr!(&Val::Int(ii), rpos, right, Val::Int(i * ii), "Integer") } Val::Float(f) => { eval_binary_expr!(&Val::Float(ff), rpos, right, Val::Float(f * ff), "Float") } ref expr => { return Err(error::BuildError::with_pos( format!("{} does not support the '*' operation", expr.type_name()), error::ErrorType::Unsupported, lpos.clone(), ) .to_boxed()); } } } fn mod_vals( &self, lpos: &Position, rpos: &Position, left: Rc, right: Rc, ) -> Result, Box> { match *left { Val::Int(i) => { eval_binary_expr!(&Val::Int(ii), rpos, right, Val::Int(i % ii), "Integer") } Val::Float(f) => { eval_binary_expr!(&Val::Float(ff), rpos, right, Val::Float(f % ff), "Float") } ref expr => { return Err(error::BuildError::with_pos( format!( "{} does not support the 'modulus' operation", expr.type_name() ), error::ErrorType::Unsupported, lpos.clone(), ) .to_boxed()); } } } fn divide_vals( &self, lpos: &Position, rpos: &Position, left: Rc, right: Rc, ) -> Result, Box> { match *left { Val::Int(i) => { eval_binary_expr!(&Val::Int(ii), rpos, right, Val::Int(i / ii), "Integer") } Val::Float(f) => { eval_binary_expr!(&Val::Float(ff), rpos, right, Val::Float(f / ff), "Float") } ref expr => { return Err(error::BuildError::with_pos( format!("{} does not support the '*' operation", expr.type_name()), error::ErrorType::Unsupported, lpos.clone(), ) .to_boxed()); } } } fn do_deep_equal( &self, pos: &Position, left: Rc, right: Rc, ) -> Result, Box> { match left.equal(right.as_ref()) { Ok(b) => Ok(Rc::new(Val::Boolean(b))), Err(e) => Err(error::BuildError::with_pos(e.msg, e.err_type, pos.clone()).to_boxed()), } } fn do_not_deep_equal( &self, pos: &Position, left: Rc, right: Rc, ) -> Result, Box> { match left.equal(right.as_ref()) { Ok(b) => Ok(Rc::new(Val::Boolean(!b))), Err(e) => Err(error::BuildError::with_pos(e.msg, e.err_type, pos.clone()).to_boxed()), } } fn do_gt( &self, pos: &Position, left: Rc, right: Rc, ) -> Result, Box> { // first ensure that left and right are numeric vals of the same type. if let &Val::Int(ref l) = left.as_ref() { if let &Val::Int(ref r) = right.as_ref() { return Ok(Rc::new(Val::Boolean(l > r))); } } if let &Val::Float(ref l) = left.as_ref() { if let &Val::Float(ref r) = right.as_ref() { return Ok(Rc::new(Val::Boolean(l > r))); } } Err(error::BuildError::with_pos( format!("Expected {} but got ({})", left.type_name(), right,), error::ErrorType::TypeFail, pos.clone(), ) .to_boxed()) } fn do_lt( &self, pos: &Position, left: Rc, right: Rc, ) -> Result, Box> { // first ensure that left and right are numeric vals of the same type. if let &Val::Int(ref l) = left.as_ref() { if let &Val::Int(ref r) = right.as_ref() { return Ok(Rc::new(Val::Boolean(l < r))); } } if let &Val::Float(ref l) = left.as_ref() { if let &Val::Float(ref r) = right.as_ref() { return Ok(Rc::new(Val::Boolean(l < r))); } } Err(error::BuildError::with_pos( format!("Expected {} but got ({})", left.type_name(), right,), error::ErrorType::TypeFail, pos.clone(), ) .to_boxed()) } fn do_ltequal( &self, pos: &Position, left: Rc, right: Rc, ) -> Result, Box> { if let &Val::Int(ref l) = left.as_ref() { if let &Val::Int(ref r) = right.as_ref() { return Ok(Rc::new(Val::Boolean(l <= r))); } } if let &Val::Float(ref l) = left.as_ref() { if let &Val::Float(ref r) = right.as_ref() { return Ok(Rc::new(Val::Boolean(l <= r))); } } Err(error::BuildError::with_pos( format!("Expected {} but got ({})", left.type_name(), right), error::ErrorType::TypeFail, pos.clone(), ) .to_boxed()) } fn do_gtequal( &self, pos: &Position, left: Rc, right: Rc, ) -> Result, Box> { if let &Val::Int(ref l) = left.as_ref() { if let &Val::Int(ref r) = right.as_ref() { return Ok(Rc::new(Val::Boolean(l >= r))); } } if let &Val::Float(ref l) = left.as_ref() { if let &Val::Float(ref r) = right.as_ref() { return Ok(Rc::new(Val::Boolean(l >= r))); } } Err(error::BuildError::with_pos( format!("Expected {} but got ({})", left.type_name(), right,), error::ErrorType::TypeFail, pos.clone(), ) .to_boxed()) } fn do_dot_lookup(&self, right: &Expression, scope: &Scope) -> Result, Box> { let pos = right.pos().clone(); let scope = scope.clone().use_curr_val(); match right { Expression::Copy(_) => return self.eval_expr(right, &scope), Expression::Call(_) => return self.eval_expr(right, &scope), Expression::Simple(Value::Symbol(ref s)) => { scope .lookup_sym(s, true) .ok_or(Box::new(error::BuildError::with_pos( format!("Unable to find binding {}", s.val,), error::ErrorType::NoSuchSymbol, pos, ))) } Expression::Simple(Value::Str(ref s)) => { scope .lookup_sym(s, false) .ok_or(Box::new(error::BuildError::with_pos( format!("Unable to find binding {}", s.val,), error::ErrorType::NoSuchSymbol, pos, ))) } Expression::Simple(Value::Int(ref i)) => { scope.lookup_idx(right.pos(), &Val::Int(i.val)) } _ => { let val = self.eval_expr(right, &scope)?; match val.as_ref() { Val::Int(i) => scope.lookup_idx(right.pos(), &Val::Int(*i)), Val::Str(ref s) => scope .lookup_sym(&PositionedItem::new(s.clone(), pos.clone()), false) .ok_or(Box::new(error::BuildError::with_pos( format!("Unable to find binding {}", s,), error::ErrorType::NoSuchSymbol, pos, ))), _ => Err(error::BuildError::with_pos( format!("Invalid selector lookup {}", val.type_name(),), error::ErrorType::NoSuchSymbol, pos, ) .to_boxed()), } } } } fn do_bool_operator( &self, kind: &BinaryExprType, left: &Expression, right: &Expression, scope: &Scope, ) -> Result, Box> { let left_pos = left.pos(); let left = self.eval_expr(left, scope)?; if let Val::Boolean(b) = left.as_ref() { let right_pos = right.pos(); let b = *b; if kind == &BinaryExprType::AND { if !b { // short circuit return Ok(Rc::new(Val::Boolean(b))); } let right = self.eval_expr(right, scope)?; if right.is_bool() { return Ok(right); } } else { if b { // short circuit return Ok(Rc::new(Val::Boolean(b))); } let right = self.eval_expr(right, scope)?; if right.is_bool() { return Ok(right); } } return Err(error::BuildError::with_pos( format!( "Expected boolean value for operator but got ({})", left.type_name() ), error::ErrorType::TypeFail, right_pos.clone(), ) .to_boxed()); } else { return Err(error::BuildError::with_pos( format!( "Expected boolean value for operator but got ({})", left.type_name() ), error::ErrorType::TypeFail, left_pos.clone(), ) .to_boxed()); } } fn do_element_check( &self, left: &Expression, right: &Expression, scope: &Scope, ) -> Result, Box> { // First we evaluate our right hand side so we have a something to search // inside for our left hand expression. let right_pos = right.pos(); let right = self.eval_expr(right, scope)?; // presence checks are only valid for tuples and lists. if !(right.is_tuple() || right.is_list()) { return Err(error::BuildError::with_pos( format!( "Invalid righthand type for in operator {}", right.type_name() ), error::ErrorType::TypeFail, right_pos.clone(), ) .to_boxed()); } if let &Val::List(ref els) = right.as_ref() { let left = self.eval_expr(left, scope)?; for val in els.iter() { if let Ok(b) = self.do_deep_equal(right_pos, left.clone(), val.clone()) { if let &Val::Boolean(b) = b.as_ref() { if b { // We found a match return Ok(Rc::new(Val::Boolean(true))); } } } } // We didn't find a match anywhere so return false. return Ok(Rc::new(Val::Boolean(false))); } else { // Handle our tuple case since this isn't a list. let child_scope = scope.spawn_child().set_curr_val(right.clone()); // Search for the field in our tuple or list. let maybe_val = self.do_dot_lookup(left, &child_scope); // Return the result of the search. return Ok(Rc::new(Val::Boolean(maybe_val.is_ok()))); } } fn eval_re_match( &self, left: Rc, left_pos: &Position, right: Rc, right_pos: &Position, negate: bool, ) -> Result, Box> { let re = if let Val::Str(ref s) = right.as_ref() { regex::Regex::new(s.as_ref())? } else { return Err(error::BuildError::with_pos( format!("Expected string for regex but got ({})", right.type_name()), error::ErrorType::TypeFail, right_pos.clone(), ) .to_boxed()); }; let tgt = if let Val::Str(ref s) = left.as_ref() { s.as_ref() } else { return Err(error::BuildError::with_pos( format!("Expected string but got ({})", left.type_name()), error::ErrorType::TypeFail, left_pos.clone(), ) .to_boxed()); }; return if negate { Ok(Rc::new(Val::Boolean(!re.is_match(tgt)))) } else { Ok(Rc::new(Val::Boolean(re.is_match(tgt)))) }; } fn eval_binary(&self, def: &BinaryOpDef, scope: &Scope) -> Result, Box> { let kind = &def.kind; if let &BinaryExprType::IN = kind { // TODO Should we support this operation on strings too? return self.do_element_check(&def.left, &def.right, scope); } if let &BinaryExprType::IS = kind { return self.eval_is_check(def, scope); } match kind { // We special case the boolean operators because we want them to short circuit. &BinaryExprType::AND | &BinaryExprType::OR => { return self.do_bool_operator(kind, &def.left, &def.right, scope); } _ => { // noop } } let left = self.eval_expr(&def.left, scope)?; let child_scope = scope.spawn_child().set_curr_val(left.clone()); if let &BinaryExprType::DOT = kind { return self.do_dot_lookup(&def.right, &child_scope); }; let right = match self.eval_expr(&def.right, scope) { Ok(v) => v, Err(e) => return Err(e), }; match kind { // Handle math and concatenation operators here &BinaryExprType::Add => self.add_vals(&def.pos, def.right.pos(), left, right), &BinaryExprType::Sub => self.subtract_vals(&def.pos, def.right.pos(), left, right), &BinaryExprType::Mul => self.multiply_vals(&def.pos, def.right.pos(), left, right), &BinaryExprType::Div => self.divide_vals(&def.pos, def.right.pos(), left, right), &BinaryExprType::Mod => self.mod_vals(&def.pos, def.right.pos(), left, right), // Handle Comparison operators here &BinaryExprType::Equal => self.do_deep_equal(&def.right.pos(), left, right), &BinaryExprType::GT => self.do_gt(&def.right.pos(), left, right), &BinaryExprType::LT => self.do_lt(&def.right.pos(), left, right), &BinaryExprType::GTEqual => self.do_gtequal(&def.right.pos(), left, right), &BinaryExprType::LTEqual => self.do_ltequal(&def.right.pos(), left, right), &BinaryExprType::NotEqual => self.do_not_deep_equal(&def.right.pos(), left, right), &BinaryExprType::REMatch => { self.eval_re_match(left, def.left.pos(), right, def.right.pos(), false) } &BinaryExprType::NotREMatch => { self.eval_re_match(left, def.left.pos(), right, def.right.pos(), true) } &BinaryExprType::IN | &BinaryExprType::IS | &BinaryExprType::DOT | &BinaryExprType::AND | &BinaryExprType::OR => panic!("Unreachable"), } } fn get_outputs_as_val(&mut self) -> Rc { let fields: Vec<(String, Rc)> = self .scope .build_output .drain() .map(|v| (v.0.val, v.1)) .collect(); Rc::new(Val::Tuple(fields)) } fn copy_from_base( &self, src_fields: &Vec<(String, Rc)>, overrides: &Vec<(Token, Expression)>, scope: &Scope, ) -> Result, Box> { let mut m = HashMap::)>::new(); // loop through fields and build up a hashmap let mut count = 0; for &(ref key, ref val) in src_fields.iter() { if let Entry::Vacant(v) = m.entry(key.clone()) { v.insert((count, val.clone())); count += 1; } else { return Err(error::BuildError::new( format!( "Duplicate \ field: {} in \ tuple", key ), error::ErrorType::TypeFail, ) .to_boxed()); } } for &(ref key, ref val) in overrides.iter() { let expr_result = self.eval_expr(val, scope)?; match m.entry(key.fragment.clone()) { // brand new field here. Entry::Vacant(v) => { v.insert((count, expr_result)); count += 1; } Entry::Occupied(mut v) => { // overriding field here. // Ensure that the new type matches the old type. let src_val = v.get().clone(); if src_val.1.type_equal(&expr_result) || src_val.1.is_empty() || expr_result.is_empty() { v.insert((src_val.0, expr_result)); } else { return Err(error::BuildError::with_pos( format!( "Expected type {} for field {} but got ({})", src_val.1.type_name(), key.fragment, expr_result.type_name() ), error::ErrorType::TypeFail, key.pos.clone(), ) .to_boxed()); } } }; } let mut new_fields: Vec<(String, (i32, Rc))> = m.drain().collect(); // We want to maintain our order for the fields to make comparing tuples // easier in later code. So we sort by the field order before constructing a new tuple. new_fields.sort_by(|a, b| { let ta = a.1.clone(); let tb = b.1.clone(); ta.0.cmp(&tb.0) }); return Ok(Rc::new(Val::Tuple( new_fields .iter() .map(|a| { let first = a.0.clone(); let t = a.1.clone(); (first, t.1) }) .collect(), ))); } fn eval_module_copy( &self, def: &CopyDef, mod_def: &ModuleDef, scope: &Scope, ) -> Result, Box> { let maybe_tpl = mod_def.clone().arg_tuple.unwrap().clone(); if let &Val::Tuple(ref src_fields) = maybe_tpl.as_ref() { // 1. First we create a builder. let mut b = self.clone_builder(); b.is_module = true; // 2. We construct an argument tuple by copying from the defs // argset. // Push our base tuple on the stack so the copy can use // self to reference it. let child_scope = scope.spawn_child().set_curr_val(maybe_tpl.clone()); let mod_args = self.copy_from_base(src_fields, &def.fields, &child_scope)?; // put our copied parameters tuple in our builder under the mod key. let mod_key = PositionedItem::new_with_pos(String::from("mod"), Position::new(0, 0, 0)); match b.scope.build_output.entry(mod_key) { Entry::Occupied(e) => { return Err(error::BuildError::with_pos( format!( "Binding \ for {:?} already \ exists in module", e.key(), ), error::ErrorType::DuplicateBinding, mod_def.pos.clone(), ) .to_boxed()); } Entry::Vacant(e) => { e.insert(mod_args.clone()); } } // 4. Evaluate all the statements using the builder. b.eval_stmts(&mod_def.statements)?; if let Some(ref expr) = mod_def.out_expr { // 5. Produce the out expression in the context of the statements // we evaluated previously. return b.eval_expr(expr, &b.scope); } else { // 5. Take all of the bindings in the module and construct a new // tuple using them. return Ok(b.get_outputs_as_val()); } } else { return Err(error::BuildError::with_pos( format!( "Weird value stored in our module parameters slot {:?}", mod_def.arg_tuple ), error::ErrorType::TypeFail, def.selector.pos().clone(), ) .to_boxed()); } } fn eval_copy(&self, def: &CopyDef, scope: &Scope) -> Result, Box> { let v = self.eval_value(&def.selector, scope)?; if let &Val::Tuple(ref src_fields) = v.as_ref() { let child_scope = scope.spawn_child().set_curr_val(v.clone()); return self.copy_from_base(&src_fields, &def.fields, &child_scope); } if let &Val::Module(ref mod_def) = v.as_ref() { return self.eval_module_copy(def, mod_def, scope); } Err(error::BuildError::with_pos( format!("Expected Tuple or Module but got ({})", v), error::ErrorType::TypeFail, def.selector.pos().clone(), ) .to_boxed()) } fn eval_format(&self, def: &FormatDef, scope: &Scope) -> Result, Box> { let tmpl = &def.template; return match &def.args { FormatArgs::List(ref args) => { let mut vals = Vec::new(); for v in args.iter() { let rcv = self.eval_expr(v, scope)?; vals.push(rcv.deref().clone()); } let formatter = SimpleFormatter::new(tmpl.clone(), vals); Ok(Rc::new(Val::Str(formatter.render(&def.pos)?))) } FormatArgs::Single(ref expr) => { let val = self.eval_expr(expr, scope)?; let mut builder = self.clone_builder(); builder.scope.build_output.insert( PositionedItem::new("item".to_string(), expr.pos().clone()), val, ); let formatter = ExpressionFormatter::new(tmpl.clone(), builder); Ok(Rc::new(Val::Str(formatter.render(&def.pos)?))) } }; } fn eval_call(&self, def: &CallDef, scope: &Scope) -> Result, Box> { let args = &def.arglist; let v = self.eval_value(&def.funcref, scope)?; let call_pos = def.pos.clone(); if let &Val::Func(ref def) = v.deref() { // Congratulations this is actually a function. let mut argvals: Vec> = Vec::new(); for arg in args.iter() { argvals.push(self.eval_expr(arg, scope)?); } return match def.eval(self, argvals) { Ok(v) => Ok(v), Err(e) => Err(error::BuildError::with_pos( format!("Func evaluation failed\nCaused by:\n\t{}", e), error::ErrorType::TypeFail, call_pos, ) .to_boxed()), }; } Err(error::BuildError::with_pos( // We should pretty print the selectors here. format!("{} is not a Function", v), error::ErrorType::TypeFail, def.pos.clone(), ) .to_boxed()) } fn eval_func_def(&self, def: &mut FuncDef, scope: &Scope) -> Result, Box> { def.scope = Some(scope.spawn_child()); Ok(Rc::new(Val::Func(def.clone()))) } fn file_dir(&self) -> PathBuf { return if self.working_dir.is_file() { // Only use the dirname portion if the root is a file. self.working_dir.parent().unwrap().to_path_buf() } else { // otherwise use clone of the root. self.working_dir.clone() }; } fn eval_module_def(&self, def: &ModuleDef, scope: &Scope) -> Result, Box> { let root = self.file_dir(); // Always work on a copy. The original should not be modified. let mut def = def.clone(); // First we rewrite the imports to be absolute paths. def.imports_to_absolute(root); // Then we create our tuple default. def.arg_tuple = Some(self.eval_tuple(&def.arg_set, scope)?); // Then we construct a new Val::Module Ok(Rc::new(Val::Module(def))) } fn eval_select(&self, def: &SelectDef, scope: &Scope) -> Result, Box> { let target = &def.val; // FIXME(jwall): Handle the no default case in here. let fields = &def.tuple; // First resolve the target expression. let v = self.eval_expr(target, scope)?; // Second ensure that the expression resolves to a string. if let &Val::Str(ref name) = v.deref() { // Third find the field with that name in the tuple. for &(ref fname, ref val_expr) in fields.iter() { if &fname.fragment == name { // Fourth return the result of evaluating that field. return self.eval_expr(val_expr, scope); } } // Otherwise return the default. match def.default.as_ref() { Some(e) => self.eval_expr(e, scope), None => { return Err(error::BuildError::with_pos( format!("Unhandled select case \"{}\" with no default", name), error::ErrorType::TypeFail, def.val.pos().clone(), ) .to_boxed()); } } } else if let &Val::Boolean(b) = v.deref() { for &(ref fname, ref val_expr) in fields.iter() { if &fname.fragment == "true" && b { // Fourth return the result of evaluating that field. return self.eval_expr(val_expr, scope); } else if &fname.fragment == "false" && !b { return self.eval_expr(val_expr, scope); } } // Otherwise return the default. match def.default.as_ref() { Some(e) => self.eval_expr(e, scope), None => { return Err(error::BuildError::with_pos( format!("Unhandled select case {} with no default", b), error::ErrorType::TypeFail, def.val.pos().clone(), ) .to_boxed()); } } } else { return Err(error::BuildError::with_pos( format!( "Expected String but got \ {} in Select expression", v.type_name() ), error::ErrorType::TypeFail, def.pos.clone(), ) .to_boxed()); } } fn eval_functional_list_processing( &self, elems: &Vec>, def: &FuncDef, typ: ProcessingOpType, ) -> Result, Box> { let mut out = Vec::new(); for item in elems.iter() { let argvals = vec![item.clone()]; let val = def.eval(self, argvals)?; match typ { ProcessingOpType::Map => { out.push(val.clone()); } ProcessingOpType::Filter => { if let &Val::Empty = val.as_ref() { // noop continue; } else if let &Val::Boolean(false) = val.as_ref() { // noop continue; } out.push(item.clone()); } } } return Ok(Rc::new(Val::List(out))); } fn eval_functional_tuple_processing( &self, fs: &Vec<(String, Rc)>, def: &FuncDef, typ: ProcessingOpType, ) -> Result, Box> { let mut out = Vec::new(); for &(ref name, ref val) in fs { let argvals = vec![Rc::new(Val::Str(name.clone())), val.clone()]; let result = def.eval(self, argvals)?; match typ { ProcessingOpType::Map => { if let &Val::List(ref fs) = result.as_ref() { if fs.len() == 2 { // index 0 should be a string for the new field name. // index 1 should be the val. let new_name = if let &Val::Str(ref s) = fs[0].as_ref() { s.clone() } else { return Err(error::BuildError::with_pos( format!( "map on tuple expects the first item out list to be a string but got size {}", fs[0].type_name() ), error::ErrorType::TypeFail, def.pos.clone(), ).to_boxed()); }; out.push((new_name, fs[1].clone())); } else { return Err(error::BuildError::with_pos( format!( "map on a tuple field expects a list of size 2 as output but got size {}", fs.len() ), error::ErrorType::TypeFail, def.pos.clone(), ).to_boxed()); } } else { return Err(error::BuildError::with_pos( format!( "map on a tuple field expects a list as output but got ({})", result.type_name() ), error::ErrorType::TypeFail, def.pos.clone(), ) .to_boxed()); } } ProcessingOpType::Filter => { if let &Val::Empty = result.as_ref() { // noop continue; } else if let &Val::Boolean(false) = result.as_ref() { // noop continue; } out.push((name.clone(), val.clone())); } } } Ok(Rc::new(Val::Tuple(out))) } fn eval_reduce_op(&self, def: &ReduceOpDef, scope: &Scope) -> Result, Box> { let maybe_target = self.eval_expr(&def.target, scope)?; let mut acc = self.eval_expr(&def.acc, scope)?; let maybe_mac = self.eval_expr(&def.func, scope)?; let funcdef = match maybe_mac.as_ref() { &Val::Func(ref funcdef) => funcdef, _ => { return Err(error::BuildError::with_pos( format!("Expected func but got {:?}", def.func), error::ErrorType::TypeFail, def.pos.clone(), ) .to_boxed()); } }; match maybe_target.as_ref() { &Val::List(ref elems) => { for item in elems.iter() { let argvals = vec![acc.clone(), item.clone()]; let result = funcdef.eval(self, argvals)?; acc = result; } } &Val::Tuple(ref fs) => { for &(ref name, ref val) in fs.iter() { let argvals = vec![acc.clone(), Rc::new(Val::Str(name.clone())), val.clone()]; let result = funcdef.eval(self, argvals)?; acc = result; } } &Val::Str(ref s) => { for gc in s.graphemes(true) { let argvals = vec![acc.clone(), Rc::new(Val::Str(gc.to_string()))]; let result = funcdef.eval(self, argvals)?; acc = result; } } other => { return Err(error::BuildError::with_pos( format!( "Expected List Str, or Tuple as target but got {:?}", other.type_name() ), error::ErrorType::TypeFail, def.target.pos().clone(), ) .to_boxed()); } } Ok(acc) } fn eval_functional_string_processing( &self, s: &str, def: &FuncDef, typ: ProcessingOpType, ) -> Result, Box> { let mut result = String::new(); for gc in s.graphemes(true) { let arg = Rc::new(Val::Str(gc.to_string())); let out = def.eval(self, vec![arg])?; match typ { ProcessingOpType::Filter => { match out.as_ref() { Val::Boolean(b) => { if *b { result.push_str(gc); } } Val::Empty => { // noop } _ => { return Err(error::BuildError::with_pos( format!( "Expected boolean or NULL for filter return but got ({})", out.type_name() ), error::ErrorType::TypeFail, def.pos.clone(), ) .to_boxed()); } } } ProcessingOpType::Map => match out.as_ref() { Val::Str(s) => { result.push_str(&s); } _ => { return Err(error::BuildError::with_pos( format!("Expected string map return but got ({})", out.type_name()), error::ErrorType::TypeFail, def.pos.clone(), ) .to_boxed()); } }, } } Ok(Rc::new(Val::Str(result))) } fn eval_functional_processing( &self, def: &MapFilterOpDef, typ: ProcessingOpType, scope: &Scope, ) -> Result, Box> { let maybe_target = self.eval_expr(&def.target, scope)?; let maybe_mac = self.eval_expr(&def.func, scope)?; let macdef = match maybe_mac.as_ref() { &Val::Func(ref macdef) => macdef, _ => { return Err(error::BuildError::with_pos( format!("Expected func but got {:?}", def.func), error::ErrorType::TypeFail, def.pos.clone(), ) .to_boxed()); } }; return match maybe_target.as_ref() { &Val::List(ref elems) => self.eval_functional_list_processing(elems, macdef, typ), &Val::Tuple(ref fs) => self.eval_functional_tuple_processing(fs, macdef, typ), &Val::Str(ref s) => self.eval_functional_string_processing(s, macdef, typ), other => Err(error::BuildError::with_pos( format!( "Expected List or Tuple as target but got {:?}", other.type_name() ), error::ErrorType::TypeFail, def.target.pos().clone(), ) .to_boxed()), }; } fn record_assert_result(&mut self, msg: &str, is_success: bool) { if !is_success { let msg = format!("{} - NOT OK: {}\n", self.assert_collector.counter, msg); self.assert_collector.summary.push_str(&msg); self.assert_collector.failures.push_str(&msg); self.assert_collector.success = false; } else { let msg = format!("{} - OK: {}\n", self.assert_collector.counter, msg); self.assert_collector.summary.push_str(&msg); } self.assert_collector.counter += 1; } fn eval_assert(&mut self, expr: &Expression, scope: &Scope) -> Result, Box> { if !self.validate_mode { // we are not in validate_mode so build_asserts are noops. return Ok(Rc::new(Val::Empty)); } let ok = match self.eval_expr(expr, scope) { Ok(v) => v, Err(e) => { // failure! let msg = format!("CompileError: {}\n", e); self.record_assert_result(&msg, false); return Ok(Rc::new(Val::Empty)); } }; match ok.as_ref() { &Val::Tuple(ref fs) => { let ok_field = match find_in_fieldlist("ok", fs) { Some(ref val) => match val.as_ref() { &Val::Boolean(b) => b, _ => { let msg = format!( "TYPE FAIL - Expected Boolean field ok in tuple {}, line: {}, column: {}", ok.as_ref(), expr.pos().line, expr.pos().column ); self.record_assert_result(&msg, false); return Ok(Rc::new(Val::Empty)); } }, None => { let msg = format!( "TYPE FAIL - Expected Boolean field ok in tuple {}, line: {}, column: {}", ok.as_ref(), expr.pos().line, expr.pos().column ); self.record_assert_result(&msg, false); return Ok(Rc::new(Val::Empty)); } }; let desc = match find_in_fieldlist("desc", fs) { Some(ref val) => match val.as_ref() { Val::Str(ref s) => s.clone(), _ => { let msg = format!( "TYPE FAIL - Expected String field desc in tuple {} line: {}, column: {}", ok, expr.pos().line, expr.pos().column ); self.record_assert_result(&msg, false); return Ok(Rc::new(Val::Empty)); } }, None => { let msg = format!( "TYPE FAIL - Expected String field desc in tuple {} line: {}, column: {}\n", ok, expr.pos().line, expr.pos().column ); self.record_assert_result(&msg, false); return Ok(Rc::new(Val::Empty)); } }; self.record_assert_result(&desc, ok_field); } &Val::Empty | &Val::Boolean(_) | &Val::Env(_) | &Val::Float(_) | &Val::Int(_) | &Val::Str(_) | &Val::List(_) | &Val::Func(_) | &Val::Module(_) => { // record an assertion type-failure result. let msg = format!( "TYPE FAIL - Expected tuple with ok and desc fields got {} at line: {} column: {}\n", ok, expr.pos().line, expr.pos().column ); self.record_assert_result(&msg, false); return Ok(Rc::new(Val::Empty)); } } Ok(ok) } fn open_file>(&self, pos: &Position, path: P) -> Result> { let path = path.into(); match File::open(&path) { Ok(f) => Ok(f), Err(e) => Err(error::BuildError::with_pos( format!("Error opening file {} {}", path.to_string_lossy(), e), error::ErrorType::TypeFail, pos.clone(), ) .to_boxed()), } } fn get_file_as_string(&self, pos: &Position, path: &str) -> Result> { let normalized = match self.find_file(path, false) { Ok(p) => p, Err(e) => { return Err(error::BuildError::with_pos( format!("Error finding file {} {}", path, e), error::ErrorType::TypeFail, pos.clone(), ) .to_boxed()); } }; let mut f = self.open_file(pos, normalized)?; let mut contents = String::new(); f.read_to_string(&mut contents)?; Ok(contents) } pub fn eval_include(&self, def: &IncludeDef) -> Result, Box> { return if def.typ.fragment == "str" { Ok(Rc::new(Val::Str( self.get_file_as_string(&def.path.pos, &def.path.fragment)?, ))) } else { let maybe_importer = self.import_registry.get_importer(&def.typ.fragment); match maybe_importer { Some(importer) => { let file_contents = self.get_file_as_string(&def.path.pos, &def.path.fragment)?; let val = if file_contents.len() == 0 { eprintln!("including an empty file. Use NULL as the result"); Rc::new(Val::Empty) } else { match importer.import(file_contents.as_bytes()) { Ok(v) => v, Err(e) => { let err = Box::new(error::BuildError::with_pos( format!( "{} include failed for {}", &def.typ.fragment, &def.path.fragment ), error::ErrorType::IncludeError, def.pos.clone(), )); return Err(err.wrap_cause(e).to_boxed()); } } }; Ok(val) } None => Err(error::BuildError::with_pos( format!("Unknown include conversion type {}", def.typ.fragment), error::ErrorType::Unsupported, def.typ.pos.clone(), ) .to_boxed()), } }; } fn eval_func_op(&self, def: &FuncOpDef, scope: &Scope) -> Result, Box> { match def { FuncOpDef::Filter(ref def) => { self.eval_functional_processing(def, ProcessingOpType::Filter, scope) } FuncOpDef::Map(ref def) => { self.eval_functional_processing(def, ProcessingOpType::Map, scope) } FuncOpDef::Reduce(ref def) => self.eval_reduce_op(def, scope), } } pub fn eval_range(&self, def: &RangeDef, scope: &Scope) -> Result, Box> { let start = self.eval_expr(&def.start, scope)?; let start = match start.as_ref() { &Val::Int(i) => i, _ => { return Err(error::BuildError::with_pos( format!( "Expected an integer for range start but got ({})", start.type_name() ), error::ErrorType::TypeFail, def.start.pos().clone(), ) .to_boxed()); } }; // See if there was a step. let step = match &def.step { Some(step) => { let step = self.eval_expr(&step, scope)?; match step.as_ref() { &Val::Int(i) => i, _ => { return Err(error::BuildError::with_pos( format!( "Expected an integer for range step but got ({})", step.type_name() ), error::ErrorType::TypeFail, def.start.pos().clone(), ) .to_boxed()); } } } None => 1, }; // Get the end. let end = self.eval_expr(&def.end, scope)?; let end = match end.as_ref() { &Val::Int(i) => i, _ => { return Err(error::BuildError::with_pos( format!( "Expected an integer for range start but got ({})", end.type_name() ), error::ErrorType::TypeFail, def.start.pos().clone(), ) .to_boxed()); } }; let vec = (start..end + 1) .step_by(step as usize) .map(|i| Rc::new(Val::Int(i))) .collect(); Ok(Rc::new(Val::List(vec))) } pub fn eval_is_check( &self, def: &BinaryOpDef, scope: &Scope, ) -> Result, Box> { let tval = self.eval_expr(def.right.as_ref(), scope)?; let typ = match tval.as_ref() { Val::Str(ref s) => s.clone(), _ => { return Err(error::BuildError::with_pos( format!("Expected string expression but got ({})", tval), error::ErrorType::TypeFail, def.right.pos().clone(), ) .to_boxed()); } }; let val = self.eval_expr(def.left.as_ref(), scope)?; let result = match typ.as_str() { "str" => val.is_str(), "bool" => val.is_bool(), "null" => val.is_empty(), "int" => val.is_int(), "float" => val.is_float(), "tuple" => val.is_tuple(), "list" => val.is_list(), "func" => val.is_func(), "module" => val.is_module(), other => { return Err(error::BuildError::with_pos( format!("Expected valid type name but got ({})", other), error::ErrorType::TypeFail, def.right.pos().clone(), ) .to_boxed()); } }; Ok(Rc::new(Val::Boolean(result))) } // Evals a single Expression in the context of a running Builder. // It does not mutate the builders collected state at all. pub fn eval_expr(&self, expr: &Expression, scope: &Scope) -> Result, Box> { match expr { &Expression::Simple(ref val) => self.eval_value(val, scope), &Expression::Binary(ref def) => self.eval_binary(def, scope), &Expression::Copy(ref def) => self.eval_copy(def, scope), &Expression::Range(ref def) => self.eval_range(def, scope), &Expression::Grouped(ref expr) => self.eval_expr(expr, scope), &Expression::Format(ref def) => self.eval_format(def, scope), &Expression::Call(ref def) => self.eval_call(def, scope), &Expression::Func(ref def) => { let mut def_clone = def.clone(); self.eval_func_def(&mut def_clone, scope) } &Expression::Module(ref def) => { let mut def_clone = def.clone(); self.eval_module_def(&mut def_clone, scope) } &Expression::Select(ref def) => self.eval_select(def, scope), &Expression::FuncOp(ref def) => self.eval_func_op(def, scope), &Expression::Include(ref def) => self.eval_include(def), &Expression::Import(ref def) => self.eval_import(def), &Expression::Fail(ref def) => { let err = self.eval_expr(&def.message, scope)?; return if let Val::Str(ref s) = err.as_ref() { Err(error::BuildError::with_pos( s.clone(), error::ErrorType::UserDefined, def.pos.clone(), ) .to_boxed()) } else { Err(error::BuildError::with_pos( format!( "Expected string for message but got ({})", def.message.as_ref() ), error::ErrorType::TypeFail, def.message.pos().clone(), ) .to_boxed()) }; } &Expression::Not(ref def) => { let val = self.eval_expr(&def.expr, scope)?; return if let Val::Boolean(b) = val.as_ref() { Ok(Rc::new(Val::Boolean(!b))) } else { Err(error::BuildError::with_pos( format!( "Expected boolean for expression but got ({})", def.expr.as_ref() ), error::ErrorType::TypeFail, def.expr.pos().clone(), ) .to_boxed()) }; } } } } #[cfg(test)] mod compile_test; #[cfg(test)] mod test;