diff --git a/integration_tests/types_test.ucg b/integration_tests/types_test.ucg index 6317b62..bf774fd 100644 --- a/integration_tests/types_test.ucg +++ b/integration_tests/types_test.ucg @@ -69,4 +69,15 @@ let name = "foo"; assert t.ok{ test = (name) in {foo="foo"}, desc = "bareword collisions with field names still works for `in` operator", +}; + +assert t.ok{ + test = "foo" in ["foo"], + desc = "List presence checks work", +}; + +let foo_string = "foo"; +assert t.ok{ + test = foo_string in ["foo"], + desc = "List presence checks work", }; \ No newline at end of file diff --git a/src/build/mod.rs b/src/build/mod.rs index 7249790..4a572f7 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -190,7 +190,7 @@ where pub fn eval_stmts(&mut self, ast: Vec) -> BuildResult { // We should probably stash this in an op_cache somewhere? let ops = translate::AST::translate(ast, &self.working_dir); - let mut vm = VM::new(Rc::new(ops), self.environment.clone()); + let mut vm = VM::new(Rc::new(ops), self.environment.clone(), &self.working_dir); if self.validate_mode { vm.enable_validate_mode(); } @@ -227,7 +227,11 @@ where &mut ops_map, &self.working_dir, ); - let mut vm = VM::new(Rc::new(ops_map), self.environment.clone()); + let mut vm = VM::new( + Rc::new(ops_map), + self.environment.clone(), + &self.working_dir, + ); if self.validate_mode { vm.enable_validate_mode(); } diff --git a/src/build/opcode/cache.rs b/src/build/opcode/cache.rs index 5dad8de..604027b 100644 --- a/src/build/opcode/cache.rs +++ b/src/build/opcode/cache.rs @@ -22,7 +22,7 @@ use super::OpPointer; /// A Cache of Op codes. pub struct Ops { - ops: BTreeMap>, + ops: BTreeMap>, } impl Ops { @@ -32,12 +32,12 @@ impl Ops { } } - pub fn entry<'a, S: Into>(&'a mut self, path: S) -> Entry<'a> { + pub fn entry<'a, P: Into>(&'a mut self, path: P) -> Entry<'a> { Entry(self.ops.entry(path.into())) } } -pub struct Entry<'a>(btree_map::Entry<'a, String, Rc>); +pub struct Entry<'a>(btree_map::Entry<'a, PathBuf, Rc>); impl<'a> Entry<'a> { pub fn get_pointer_or_else Result, P: Into>( diff --git a/src/build/opcode/environment.rs b/src/build/opcode/environment.rs index 08df900..189aded 100644 --- a/src/build/opcode/environment.rs +++ b/src/build/opcode/environment.rs @@ -70,14 +70,18 @@ impl Environment { self.val_cache.insert(path.clone(), val); } - pub fn get_ops_for_path(&mut self, path: &String) -> Result { - self.op_cache.entry(path).get_pointer_or_else( + pub fn get_ops_for_path

(&mut self, path: P) -> Result + where + P: Into + Clone, + { + let path_copy = path.clone(); + self.op_cache.entry(path.clone()).get_pointer_or_else( || { // FIXME(jwall): We need to do proper error handling here. - let p = PathBuf::from(&path); + let p = path.into(); let root = p.parent().unwrap(); // first we read in the file - let mut f = File::open(&path)?; + let mut f = File::open(&p)?; // then we parse it let mut contents = String::new(); f.read_to_string(&mut contents)?; @@ -88,7 +92,7 @@ impl Environment { let ops = super::translate::AST::translate(stmts, &root); Ok(ops) }, - &path, + path_copy, ) } diff --git a/src/build/opcode/error.rs b/src/build/opcode/error.rs index 2d573bf..1473bf2 100644 --- a/src/build/opcode/error.rs +++ b/src/build/opcode/error.rs @@ -58,7 +58,6 @@ macro_rules! decorate_call { match $result { Ok(v) => Ok(v), Err(mut e) => { - dbg!(&$pos); e.push_call_stack($pos.clone()); Err(e) } diff --git a/src/build/opcode/runtime.rs b/src/build/opcode/runtime.rs index bb8e3ea..524feb6 100644 --- a/src/build/opcode/runtime.rs +++ b/src/build/opcode/runtime.rs @@ -16,6 +16,7 @@ use std::fs::File; use std::io::Read; use std::path::{Path, PathBuf}; use std::rc::Rc; +use std::fmt::Debug; use regex::Regex; @@ -29,7 +30,6 @@ use Composite::{List, Tuple}; use Primitive::{Bool, Empty, Int, Str}; pub struct Builtins { - working_dir: PathBuf, import_path: Vec, validate_mode: bool, } @@ -37,12 +37,7 @@ pub struct Builtins { impl Builtins { pub fn new() -> Self { // FIXME(jwall): This should probably be injected in. - Self::with_working_dir(std::env::current_dir().unwrap()) - } - - pub fn with_working_dir>(path: P) -> Self { Self { - working_dir: path.into(), import_path: Vec::new(), validate_mode: false, } @@ -50,7 +45,6 @@ impl Builtins { pub fn clone(&self) -> Self { Self { - working_dir: self.working_dir.clone(), import_path: self.import_path.clone(), validate_mode: self.validate_mode, } @@ -60,22 +54,25 @@ impl Builtins { self.validate_mode = true; } - pub fn handle, O, E>( + pub fn handle( &mut self, path: Option

, h: Hook, stack: &mut Vec<(Rc, Position)>, env: Rc>>, import_stack: &mut Vec, + working_dir: WP, pos: Position, ) -> Result<(), Error> where + P: AsRef, + WP: Into + Clone + Debug, O: std::io::Write, E: std::io::Write, { match h { - Hook::Import => self.import(stack, env, import_stack, pos), - Hook::Include => self.include(stack, env, pos), + Hook::Import => self.import(working_dir, stack, env, import_stack, pos), + Hook::Include => self.include(working_dir, stack, env, pos), Hook::Assert => self.assert(stack, env), Hook::Convert => self.convert(stack, env, pos), Hook::Out => self.out(path, stack, env, pos), @@ -88,15 +85,23 @@ impl Builtins { } } - fn find_file>( + fn normalize_path( &self, - path: P, + base_path: BP, use_import_path: bool, - pos: Position, - ) -> Result { + path: P, + ) -> Result + where + BP: Into, + P: Into, + { // Try a relative path first. let path = path.into(); - let mut normalized = self.working_dir.clone(); + // stdlib paths are special + if path.starts_with("std/") { + return Ok(path); + } + let mut normalized = base_path.into(); if path.is_relative() { normalized.push(&path); // First see if the normalized file exists or not. @@ -115,6 +120,23 @@ impl Builtins { } else { normalized = path; } + Ok(normalized.canonicalize()?) + } + + fn find_file( + &self, + base_path: BP, + path: P, + use_import_path: bool, + pos: Position, + ) -> Result + where + P: Into, + BP: Into, + { + // Try a relative path first. + // FIXME(jwall): Use import paths if desired. + let normalized = self.normalize_path(base_path, use_import_path, path)?; match normalized.canonicalize() { Ok(p) => Ok(p), Err(_e) => Err(Error::new( @@ -124,10 +146,11 @@ impl Builtins { } } - fn get_file_as_string(&self, path: &str, pos: Position) -> Result { + fn get_file_as_string>(&self, base_path: P, path: &str, pos: Position) -> Result { let sep = format!("{}", std::path::MAIN_SEPARATOR); let raw_path = path.replace("/", &sep); - let normalized = self.find_file(raw_path, false, pos)?; + // FIXME(jwall): import paths? + let normalized = self.find_file(base_path, raw_path, false, pos)?; // TODO(jwall): Proper error here let mut f = File::open(normalized)?; let mut contents = String::new(); @@ -136,8 +159,9 @@ impl Builtins { Ok(contents) } - fn import( + fn import( &mut self, + base_path: P, stack: &mut Vec<(Rc, Position)>, env: Rc>>, import_stack: &mut Vec, @@ -146,31 +170,37 @@ impl Builtins { where O: std::io::Write, E: std::io::Write, + P: Into + Clone + Debug, { let path = stack.pop(); if let Some((val, path_pos)) = path { if let &Value::P(Str(ref path)) = val.as_ref() { + // TODO(jwall): A bit hacky we should probably change import stacks to be pathbufs. + let normalized = decorate_error!(path_pos => self.normalize_path(base_path, false, path))?; + let path = normalized.to_string_lossy().to_string(); if import_stack .iter() - .find(|p| *p == path) + .find(|p| *p == &path) .is_some() { return Err(Error::new( - format!("You can only have one output per file"), + format!("Import cycle detected: {} in {:?}", path, import_stack), pos)); } - import_stack.push(path.clone()); - let mut borrowed_env = env.borrow_mut(); - match borrowed_env.get_cached_path_val(path) { + let val = { + env.borrow_mut().get_cached_path_val(&path) + }; + match val { Some(v) => { stack.push((v, path_pos)); } None => { let op_pointer = - decorate_error!(path_pos => borrowed_env.get_ops_for_path(path))?; - let mut vm = VM::with_pointer(op_pointer, env.clone()); + decorate_error!(path_pos => env.borrow_mut().get_ops_for_path(&normalized))?; + // TODO(jwall): What if we don't have a base path? + let mut vm = VM::with_pointer(op_pointer, env.clone(), normalized.parent().unwrap()); vm.run()?; let result = Rc::new(vm.symbols_to_tuple(true)); - borrowed_env.update_path_val(&path, result.clone()); + env.borrow_mut().update_path_val(&path, result.clone()); stack.push((result, pos)); } } @@ -181,8 +211,9 @@ impl Builtins { unreachable!(); } - fn include( + fn include( &self, + base_path: P, stack: &mut Vec<(Rc, Position)>, env: Rc>>, pos: Position, @@ -190,6 +221,7 @@ impl Builtins { where O: std::io::Write, E: std::io::Write, + P: Into + Clone + Debug, { let path = stack.pop(); let typ = stack.pop(); @@ -216,14 +248,14 @@ impl Builtins { }; if typ == "str" { stack.push(( - Rc::new(P(Str(self.get_file_as_string(&path, pos.clone())?))), + Rc::new(P(Str(self.get_file_as_string(base_path, &path, pos.clone())?))), pos.clone(), )); } else { stack.push(( Rc::new(match env.borrow().importer_registry.get_importer(&typ) { Some(importer) => { - let contents = self.get_file_as_string(&path, pos.clone())?; + let contents = self.get_file_as_string(base_path, &path, pos.clone())?; if contents.len() == 0 { eprintln!("including an empty file. Use NULL as the result"); P(Empty) diff --git a/src/build/opcode/translate.rs b/src/build/opcode/translate.rs index 608d956..296d823 100644 --- a/src/build/opcode/translate.rs +++ b/src/build/opcode/translate.rs @@ -13,7 +13,10 @@ // limitations under the License. use std::path::Path; -use crate::ast::{BinaryExprType, Expression, FormatArgs, Position, Statement, Token, Value}; +use crate::ast::{ + BinaryExprType, BinaryOpDef, Expression, FormatArgs, Position, PositionedItem, SelectDef, + Statement, Token, TokenType, Value, +}; use crate::ast::{FuncOpDef, TemplatePart}; use crate::build::format::{ExpressionTemplate, SimpleTemplate, TemplateParser}; use crate::build::opcode::Primitive; @@ -186,18 +189,38 @@ impl AST { ops.push(Op::Mod, def.pos); } BinaryExprType::IN => { - // Dot expressions expect the left side to be pushed first - Self::translate_expr(*def.right, &mut ops, root); + // Dot expressions expect the right side to be pushed first + Self::translate_expr(*def.right.clone(), &mut ops, root); // Symbols on the left side should be converted to strings to satisfy // the Index operation contract. - // FIXME(jwall): List checks. - match *def.left { + // FIXME(jwall): List checks should not use symbol translation. + match *def.left.clone() { Expression::Simple(Value::Symbol(name)) => { - Self::translate_expr( - Expression::Simple(Value::Str(name)), - &mut ops, - root, - ); + // We really just want an expression that turns a symbol + // into a name if the subject is a tuple and doesn't + // otherwise + let new_expr = Expression::Select(SelectDef { + val: Box::new(Expression::Binary(BinaryOpDef { + kind: BinaryExprType::IS, + right: Box::new(Expression::Simple(Value::Str( + PositionedItem::new( + "tuple".to_owned(), + def.left.pos().clone(), + ), + ))), + left: def.right.clone(), + pos: def.left.pos().clone(), + })), + default: Some(Box::new(Expression::Simple(Value::Symbol( + name.clone(), + )))), + tuple: vec![( + Token::new("true", TokenType::BAREWORD, def.right.pos()), + Expression::Simple(Value::Str(name)), + )], + pos: def.left.pos().clone(), + }); + Self::translate_expr(new_expr, &mut ops, root); } expr => { Self::translate_expr(expr, &mut ops, root); diff --git a/src/build/opcode/vm.rs b/src/build/opcode/vm.rs index 03d2e86..c96c551 100644 --- a/src/build/opcode/vm.rs +++ b/src/build/opcode/vm.rs @@ -13,6 +13,7 @@ // limitations under the License. use std::cell::RefCell; use std::collections::BTreeSet; +use std::path::PathBuf; use std::rc::Rc; use crate::ast::Position; @@ -45,6 +46,7 @@ where O: std::io::Write, E: std::io::Write, { + working_dir: PathBuf, stack: Vec<(Rc, Position)>, symbols: Stack, import_stack: Vec, @@ -54,7 +56,6 @@ where pub last: Option<(Rc, Position)>, self_stack: Vec<(Rc, Position)>, reserved_words: BTreeSet<&'static str>, - out_lock: bool, } impl<'a, O, E> VM @@ -62,12 +63,21 @@ where O: std::io::Write, E: std::io::Write, { - pub fn new(ops: Rc, env: Rc>>) -> Self { - Self::with_pointer(OpPointer::new(ops), env) + pub fn new>( + ops: Rc, + env: Rc>>, + working_dir: P, + ) -> Self { + Self::with_pointer(OpPointer::new(ops), env, working_dir) } - pub fn with_pointer(ops: OpPointer, env: Rc>>) -> Self { + pub fn with_pointer>( + ops: OpPointer, + env: Rc>>, + working_dir: P, + ) -> Self { Self { + working_dir: working_dir.into(), stack: Vec::new(), symbols: Stack::new(), import_stack: Vec::new(), @@ -77,9 +87,14 @@ where last: None, self_stack: Vec::new(), reserved_words: construct_reserved_word_set(), - out_lock: false, } } + + pub fn to_new_pointer(mut self, ops: OpPointer) -> Self { + self.ops = ops; + self + } + pub fn with_import_stack(mut self, imports: Vec) -> Self { self.import_stack = imports; self @@ -89,21 +104,26 @@ where self.runtime.enable_validate_mode(); } - pub fn to_scoped(self, symbols: Stack) -> Self { + pub fn clean_copy(&self) -> Self { Self { + working_dir: self.working_dir.clone(), stack: Vec::new(), - symbols: symbols, - import_stack: self.import_stack.clone(), + symbols: Stack::new(), + import_stack: Vec::new(), runtime: self.runtime.clone(), ops: self.ops.clone(), env: self.env.clone(), - last: self.last, - self_stack: self.self_stack, - reserved_words: self.reserved_words, - out_lock: self.out_lock, + last: None, + self_stack: self.self_stack.clone(), + reserved_words: self.reserved_words.clone(), } } + pub fn to_scoped(mut self, symbols: Stack) -> Self { + self.symbols = symbols; + self + } + pub fn symbols_to_tuple(&self, include_mod: bool) -> Value { let mut flds = Vec::new(); let mut pos_list = Vec::new(); @@ -183,6 +203,9 @@ where Op::PopSelf => self.op_pop_self()?, }; } + if let Some(p) = self.ops.path.as_ref() { + self.import_stack.push(p.to_string_lossy().to_string()); + } Ok(()) } @@ -425,7 +448,7 @@ where ref snapshot, } = f; // use the captured scope snapshot for the function. - let mut vm = Self::with_pointer(ptr.clone(), env) + let mut vm = Self::with_pointer(ptr.clone(), env, std::env::current_dir()?) .to_scoped(snapshot.clone()) .with_import_stack(import_stack.clone()); for nm in bindings.iter() { @@ -442,7 +465,9 @@ where fn op_new_scope(&mut self, jp: i32, ptr: OpPointer) -> Result<(), Error> { let scope_snapshot = self.symbols.snapshot(); - let mut vm = Self::with_pointer(ptr, self.env.clone()) + let mut vm = self + .clean_copy() + .to_new_pointer(ptr) .to_scoped(scope_snapshot) .with_import_stack(self.import_stack.clone()); vm.run()?; @@ -809,7 +834,7 @@ where } &C(List(ref elems, _)) => { for e in elems { - if e == &right { + if dbg!(e) == dbg!(&right) { self.push(Rc::new(P(Bool(true))), pos)?; return Ok(()); } @@ -905,7 +930,9 @@ where &val_pos, )?; if let Some(ptr) = pkg_ptr { - let mut pkg_vm = Self::with_pointer(ptr.clone(), self.env.clone()) + let mut pkg_vm = self + .clean_copy() + .to_new_pointer(ptr.clone()) .with_import_stack(self.import_stack.clone()); pkg_vm.run()?; let (pkg_func, val_pos) = pkg_vm.pop()?; @@ -919,8 +946,9 @@ where )?; } - // TODO(jwall): We should have a notion of a call stack here. - let mut vm = Self::with_pointer(ptr.clone(), self.env.clone()) + let mut vm = self + .clean_copy() + .to_new_pointer(ptr.clone()) .with_import_stack(self.import_stack.clone()); vm.push(Rc::new(S("mod".to_owned())), pos.clone())?; vm.push(Rc::new(C(Tuple(flds, flds_pos_list))), pos.clone())?; @@ -1137,6 +1165,7 @@ where &mut self.stack, self.env.clone(), &mut self.import_stack, + &self.working_dir, pos, ) } diff --git a/std/tests/tuples_test.ucg b/std/tests/tuples_test.ucg index ae3e759..9bd710c 100644 --- a/std/tests/tuples_test.ucg +++ b/std/tests/tuples_test.ucg @@ -38,7 +38,7 @@ assert t.equal{ assert t.ok{ test = tpl.has_fields{tpl={foo=1, bar=2}, fields=["foo", "bar"]}, - desc = "tuple has fields has foo and bar fields", + desc = "tuple has foo and bar fields", }; assert t.not_ok{