// Copyright 2019 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. use std::cell::RefCell; use std::convert::{TryFrom, TryInto}; use std::fs::File; use std::io::Read; use std::path::{Path, PathBuf}; use std::rc::Rc; use regex::Regex; use super::environment::Environment; use super::pointer::OpPointer; use super::Value::{C, F, P, S}; use super::VM; use super::{Composite, Error, Hook, Primitive, Value}; use crate::ast::Position; use crate::build::ir::Val; use crate::build::AssertCollector; use crate::iter::OffsetStrIter; use crate::parse::parse; use Composite::{List, Tuple}; use Primitive::{Bool, Empty, Int, Str}; pub struct Builtins { assert_results: AssertCollector, working_dir: PathBuf, import_path: Vec, } impl Builtins { pub fn new() -> Self { Self::with_working_dir(std::env::current_dir().unwrap()) } pub fn with_working_dir>(path: P) -> Self { Self { assert_results: AssertCollector::new(), working_dir: path.into(), import_path: Vec::new(), } } pub fn clone(&self) -> Self { Self { assert_results: AssertCollector::new(), working_dir: self.working_dir.clone(), import_path: self.import_path.clone(), } } pub fn handle, O, E>( &mut self, path: P, h: Hook, stack: &mut Vec>, env: Rc>>, ) -> Result<(), Error> where O: std::io::Write, E: std::io::Write, { match h { Hook::Import => self.import(stack, env), Hook::Include => self.include(stack, env), Hook::Assert => self.assert(stack), Hook::Convert => self.convert(stack, env), Hook::Out => self.out(path, stack, env), Hook::Map => self.map(path, stack, env), Hook::Filter => self.filter(path, stack, env), Hook::Reduce => self.reduce(path, stack, env), Hook::Regex => self.regex(stack), Hook::Range => self.range(stack), Hook::Trace(pos) => self.trace(stack, pos, env), } } 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 { // TODO(jwall): Support importing from a zip file in this // 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(dbg!(Error {})), } } fn get_file_as_string(&self, path: &str) -> Result { let sep = format!("{}", std::path::MAIN_SEPARATOR); let raw_path = path.replace("/", &sep); let normalized = match self.find_file(raw_path, false) { Ok(p) => p, Err(_e) => { return Err(dbg!(Error {})); } }; let mut f = File::open(normalized).unwrap(); let mut contents = String::new(); f.read_to_string(&mut contents).unwrap(); Ok(contents) } fn import( &mut self, stack: &mut Vec>, env: Rc>>, ) -> Result<(), Error> where O: std::io::Write, E: std::io::Write, { let path = stack.pop(); if let Some(val) = path { if let &Value::P(Str(ref path)) = val.as_ref() { let mut borrowed_env = env.borrow_mut(); let val_cache = &mut borrowed_env.val_cache; if val_cache.contains_key(path) { stack.push(val_cache[path].clone()); } else { let op_pointer = env.borrow_mut().op_cache.entry(path).get_pointer_or_else( || { // FIXME(jwall): We need to do proper error handling here. let p = PathBuf::from(&path); let root = p.parent().unwrap(); // first we read in the file let mut f = File::open(&path).unwrap(); // then we parse it let mut contents = String::new(); f.read_to_string(&mut contents).unwrap(); let iter = OffsetStrIter::new(&contents).with_src_file(&p); let stmts = parse(iter, None).unwrap(); // then we create an ops from it let ops = super::translate::AST::translate(stmts, &root); ops }, &path, ); let mut vm = VM::with_pointer(path, op_pointer, env.clone()); vm.run()?; let result = Rc::new(vm.symbols_to_tuple(true)); val_cache.insert(path.clone(), result.clone()); stack.push(result); } return Ok(()); } } return Err(dbg!(Error {})); } fn include( &self, stack: &mut Vec>, env: Rc>>, ) -> Result<(), Error> where O: std::io::Write, E: std::io::Write, { let path = stack.pop(); let typ = stack.pop(); let path = if let Some(val) = path { if let &Value::P(Str(ref path)) = val.as_ref() { path.clone() } else { return Err(dbg!(Error {})); } } else { return Err(dbg!(Error {})); }; let typ = if let Some(val) = typ.as_ref() { if let &Value::P(Str(ref typ)) = val.as_ref() { typ.clone() } else { return Err(dbg!(Error {})); } } else { return Err(dbg!(Error {})); }; if typ == "str" { stack.push(Rc::new(P(Str(self.get_file_as_string(&path)?)))); } else { stack.push(Rc::new( match env.borrow().importer_registry.get_importer(&typ) { Some(importer) => { let contents = self.get_file_as_string(&path)?; if contents.len() == 0 { eprintln!("including an empty file. Use NULL as the result"); P(Empty) } else { match importer.import(contents.as_bytes()) { Ok(v) => v.try_into()?, Err(_e) => return Err(dbg!(Error {})), } } } None => return Err(dbg!(Error {})), }, )); } return Err(dbg!(Error {})); } fn assert(&mut self, stack: &mut Vec>) -> Result<(), Error> { let tuple = stack.pop(); if let Some(val) = tuple.clone() { if let &Value::C(Tuple(ref tuple)) = val.as_ref() { // look for the description field let mut desc = None; // look for the ok field. let mut ok = None; for &(ref name, ref val) in tuple.iter() { if name == "description" { desc = Some(val.clone()); } if name == "ok" { ok = Some(val.clone()); } } if let (Some(ok), Some(desc)) = (ok, desc) { if let (&Value::P(Bool(ref b)), &Value::P(Str(ref desc))) = (ok.as_ref(), desc.as_ref()) { self.assert_results.record_assert_result(desc, *b); return Ok(()); } } } } let msg = format!( "TYPE FAIL - Expected tuple with ok and desc fields got {:?} at line: {} column: {}\n", tuple, "TODO", "TODO" ); self.assert_results.record_assert_result(&msg, false); return Ok(()); } fn out, O, E>( &self, path: P, stack: &mut Vec>, env: Rc>>, ) -> Result<(), Error> where O: std::io::Write, E: std::io::Write, { let val = stack.pop(); if let Some(val) = val { let val = val.try_into()?; if let Some(c_type_val) = stack.pop() { if let &Value::S(ref c_type) = c_type_val.as_ref() { if let Some(c) = env.borrow().converter_registry.get_converter(c_type) { match c.convert(Rc::new(val), &mut File::create(path)?) { Ok(_) => { // noop } Err(_e) => return Err(dbg!(Error {})), } return Ok(()); } } } } return Err(dbg!(Error {})); } fn convert( &self, stack: &mut Vec>, env: Rc>>, ) -> Result<(), Error> where O: std::io::Write, E: std::io::Write, { let val = stack.pop(); if let Some(val) = val { let val = val.try_into()?; if let Some(c_type_val) = stack.pop() { if let &Value::S(ref c_type) = c_type_val.as_ref() { if let Some(c) = env.borrow().converter_registry.get_converter(c_type) { let mut buf: Vec = Vec::new(); match c.convert(Rc::new(val), &mut buf) { Ok(_) => { stack .push(Rc::new(P(Str( String::from_utf8_lossy(buf.as_slice()).to_string() )))); } Err(_e) => return Err(dbg!(Error {})), } return Ok(()); } } } } return Err(dbg!(Error {})); } fn map, O, E>( &self, path: P, stack: &mut Vec>, env: Rc>>, ) -> Result<(), Error> where O: std::io::Write, E: std::io::Write, { // get the list from the stack let list = if let Some(list) = stack.pop() { list } else { return Err(dbg!(Error {})); }; // get the func ptr from the stack let fptr = if let Some(ptr) = stack.pop() { ptr } else { return Err(dbg!(Error {})); }; let f = if let &F(ref f) = fptr.as_ref() { f } else { return Err(dbg!(Error {})); }; match list.as_ref() { &C(List(ref elems)) => { let mut result_elems = Vec::new(); for e in elems.iter() { // push function argument on the stack. stack.push(e.clone()); // call function and push it's result on the stack. result_elems.push(VM::fcall_impl( path.as_ref().to_owned(), f, stack, env.clone(), )?); } stack.push(Rc::new(C(List(result_elems)))); } &C(Tuple(ref _flds)) => { let mut new_fields = Vec::new(); for (ref name, ref val) in _flds { stack.push(val.clone()); stack.push(Rc::new(P(Str(name.clone())))); let result = VM::fcall_impl(path.as_ref().to_owned(), f, stack, env.clone())?; if let &C(List(ref fval)) = result.as_ref() { // we expect them to be a list of exactly 2 items. if fval.len() != 2 { return Err(dbg!(Error {})); } let name = match fval[0].as_ref() { &P(Str(ref name)) => name.clone(), _ => return Err(dbg!(Error {})), }; new_fields.push((name, fval[1].clone())); } } stack.push(Rc::new(C(Tuple(dbg!(new_fields))))); } &P(Str(ref s)) => { let mut buf = String::new(); for c in s.chars() { stack.push(Rc::new(P(Str(c.to_string())))); // call function and push it's result on the stack. let result = VM::fcall_impl(path.as_ref().to_owned(), f, stack, env.clone())?; if let &P(Str(ref s)) = result.as_ref() { buf.push_str(s); } else { return Err(dbg!(Error {})); } } stack.push(Rc::new(P(Str(buf)))); } _ => return Err(dbg!(Error {})), }; Ok(()) } fn filter, O, E>( &self, path: P, stack: &mut Vec>, env: Rc>>, ) -> Result<(), Error> where O: std::io::Write, E: std::io::Write, { // get the list from the stack let list = if let Some(list) = stack.pop() { list } else { return Err(dbg!(Error {})); }; // get the func ptr from the stack let fptr = if let Some(ptr) = stack.pop() { ptr } else { return Err(dbg!(Error {})); }; let f = if let &F(ref f) = fptr.as_ref() { f } else { return dbg!(Err(Error {})); }; let elems = match list.as_ref() { &C(List(ref elems)) => { let mut result_elems = Vec::new(); for e in elems.iter() { // push function argument on the stack. stack.push(e.clone()); // call function and push it's result on the stack. let condition = VM::fcall_impl(path.as_ref().to_owned(), f, stack, env.clone())?; // Check for empty or boolean results and only push e back in // if they are non empty and true match condition.as_ref() { &P(Empty) | &P(Bool(false)) => { continue; } _ => result_elems.push(e.clone()), } } stack.push(Rc::new(C(List(result_elems)))); } &C(Tuple(ref _flds)) => { let mut new_fields = Vec::new(); for (ref name, ref val) in _flds { stack.push(val.clone()); stack.push(Rc::new(P(Str(name.clone())))); let condition = VM::fcall_impl(path.as_ref().to_owned(), f, stack, env.clone())?; // Check for empty or boolean results and only push e back in // if they are non empty and true match condition.as_ref() { &P(Empty) | &P(Bool(false)) => { continue; } _ => new_fields.push((name.clone(), val.clone())), } } stack.push(Rc::new(C(Tuple(dbg!(new_fields))))); } &P(Str(ref s)) => { let mut buf = String::new(); for c in s.chars() { stack.push(Rc::new(P(Str(c.to_string())))); // call function and push it's result on the stack. let condition = VM::fcall_impl(path.as_ref().to_owned(), f, stack, env.clone())?; // Check for empty or boolean results and only push c back in // if they are non empty and true match condition.as_ref() { &P(Empty) | &P(Bool(false)) => { continue; } _ => buf.push(c), } } stack.push(Rc::new(P(Str(buf)))); } _ => return Err(dbg!(Error {})), }; Ok(()) } fn regex(&self, stack: &mut Vec>) -> Result<(), Error> { // 1. get left side (string) let left_str = if let Some(val) = stack.pop() { if let &P(Str(ref s)) = val.as_ref() { s.clone() } else { return dbg!(Err(Error {})); } } else { return dbg!(Err(Error {})); }; // 2. get right side (string) let right_str = if let Some(val) = stack.pop() { if let &P(Str(ref s)) = val.as_ref() { s.clone() } else { return dbg!(Err(Error {})); } } else { return dbg!(Err(Error {})); }; // 3. compare via regex let rex = Regex::new(&right_str)?; stack.push(Rc::new(P(Bool(rex.find(&left_str).is_some())))); Ok(()) } fn reduce, O, E>( &self, path: P, stack: &mut Vec>, env: Rc>>, ) -> Result<(), Error> where O: std::io::Write, E: std::io::Write, { // get the list from the stack let list = if let Some(list) = stack.pop() { list } else { return dbg!(Err(Error {})); }; // Get the accumulator from the stack let mut acc = if let Some(acc) = stack.pop() { acc } else { return dbg!(Err(Error {})); }; // get the func ptr from the stack let fptr = if let Some(ptr) = stack.pop() { ptr } else { return dbg!(Err(Error {})); }; let f = if let &F(ref f) = fptr.as_ref() { f } else { return dbg!(Err(Error {})); }; match list.as_ref() { &C(List(ref elems)) => { for e in dbg!(elems).iter() { // push function arguments on the stack. stack.push(dbg!(e.clone())); stack.push(dbg!(acc.clone())); // call function and push it's result on the stack. acc = VM::fcall_impl(path.as_ref().to_owned(), f, stack, env.clone())?; } } &C(Tuple(ref _flds)) => { for (ref name, ref val) in _flds.iter() { // push function arguments on the stack. stack.push(val.clone()); stack.push(Rc::new(P(Str(name.clone())))); stack.push(dbg!(acc.clone())); // call function and push it's result on the stack. acc = VM::fcall_impl(path.as_ref().to_owned(), f, stack, env.clone())?; } } &P(Str(ref _s)) => { for c in _s.chars() { // push function arguments on the stack. stack.push(dbg!(Rc::new(P(Str(c.to_string()))))); stack.push(dbg!(acc.clone())); // call function and push it's result on the stack. acc = VM::fcall_impl(path.as_ref().to_owned(), f, stack, env.clone())?; } } _ => return Err(dbg!(Error {})), }; // push the acc on the stack as our result stack.push(dbg!(acc)); Ok(()) } fn range(&self, stack: &mut Vec>) -> Result<(), Error> { let start = if let Some(start) = stack.pop() { start } else { return dbg!(Err(Error {})); }; let step = if let Some(step) = stack.pop() { if let &P(Empty) = step.as_ref() { Rc::new(P(Int(1))) } else { step } } else { return dbg!(Err(Error {})); }; let end = if let Some(end) = stack.pop() { end } else { return dbg!(Err(Error {})); }; let mut elems = Vec::new(); match (start.as_ref(), step.as_ref(), end.as_ref()) { (&P(Int(start)), &P(Int(step)), &P(Int(end))) => { let mut num = start; loop { if num > end { break; } elems.push(Rc::new(P(Int(num)))); num += step; } } _ => { return dbg!(Err(Error {})); } } stack.push(Rc::new(C(List(elems)))); Ok(()) } fn trace( &mut self, stack: &mut Vec>, pos: Position, env: Rc>>, ) -> Result<(), Error> where O: std::io::Write, E: std::io::Write, { let val = if let Some(val) = dbg!(stack.pop()) { val } else { return Err(dbg!(Error {})); }; let expr = stack.pop(); let expr_pretty = match expr { Some(ref expr) => match dbg!(expr.as_ref()) { &P(Str(ref expr)) => expr.clone(), _ => return Err(dbg!(Error {})), }, _ => return Err(dbg!(Error {})), }; let writable_val: Val = TryFrom::try_from(val.clone())?; if let Err(_) = writeln!( &mut env.borrow_mut().stderr, "TRACE: {} = {} at {}", expr_pretty, writable_val, pos ) { return Err(dbg!(Error {})); }; stack.push(val); Ok(()) } }