DEV: Integration tests all pass now.

This commit is contained in:
Jeremy Wall 2019-09-02 10:10:24 -05:00
parent 2b64c2b4e0
commit b3fd37a6b5
9 changed files with 171 additions and 69 deletions

View File

@ -70,3 +70,14 @@ 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",
};

View File

@ -190,7 +190,7 @@ where
pub fn eval_stmts(&mut self, ast: Vec<Statement>) -> 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();
}

View File

@ -22,7 +22,7 @@ use super::OpPointer;
/// A Cache of Op codes.
pub struct Ops {
ops: BTreeMap<String, Rc<PositionMap>>,
ops: BTreeMap<PathBuf, Rc<PositionMap>>,
}
impl Ops {
@ -32,12 +32,12 @@ impl Ops {
}
}
pub fn entry<'a, S: Into<String>>(&'a mut self, path: S) -> Entry<'a> {
pub fn entry<'a, P: Into<PathBuf>>(&'a mut self, path: P) -> Entry<'a> {
Entry(self.ops.entry(path.into()))
}
}
pub struct Entry<'a>(btree_map::Entry<'a, String, Rc<PositionMap>>);
pub struct Entry<'a>(btree_map::Entry<'a, PathBuf, Rc<PositionMap>>);
impl<'a> Entry<'a> {
pub fn get_pointer_or_else<F: FnOnce() -> Result<PositionMap, Error>, P: Into<PathBuf>>(

View File

@ -70,14 +70,18 @@ impl<Stdout: Write, Stderr: Write> Environment<Stdout, Stderr> {
self.val_cache.insert(path.clone(), val);
}
pub fn get_ops_for_path(&mut self, path: &String) -> Result<OpPointer, Error> {
self.op_cache.entry(path).get_pointer_or_else(
pub fn get_ops_for_path<P>(&mut self, path: P) -> Result<OpPointer, Error>
where
P: Into<PathBuf> + 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<Stdout: Write, Stderr: Write> Environment<Stdout, Stderr> {
let ops = super::translate::AST::translate(stmts, &root);
Ok(ops)
},
&path,
path_copy,
)
}

View File

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

View File

@ -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<PathBuf>,
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<P: Into<PathBuf>>(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<P: AsRef<Path>, O, E>(
pub fn handle<P, WP, O, E>(
&mut self,
path: Option<P>,
h: Hook,
stack: &mut Vec<(Rc<Value>, Position)>,
env: Rc<RefCell<Environment<O, E>>>,
import_stack: &mut Vec<String>,
working_dir: WP,
pos: Position,
) -> Result<(), Error>
where
P: AsRef<Path>,
WP: Into<PathBuf> + 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<P: Into<PathBuf>>(
fn normalize_path<P, BP>(
&self,
path: P,
base_path: BP,
use_import_path: bool,
pos: Position,
) -> Result<PathBuf, Error> {
path: P,
) -> Result<PathBuf, Error>
where
BP: Into<PathBuf>,
P: Into<PathBuf>,
{
// 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<P, BP>(
&self,
base_path: BP,
path: P,
use_import_path: bool,
pos: Position,
) -> Result<PathBuf, Error>
where
P: Into<PathBuf>,
BP: Into<PathBuf>,
{
// 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<String, Error> {
fn get_file_as_string<P: Into<PathBuf>>(&self, base_path: P, path: &str, pos: Position) -> Result<String, Error> {
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<O, E>(
fn import<P, O, E>(
&mut self,
base_path: P,
stack: &mut Vec<(Rc<Value>, Position)>,
env: Rc<RefCell<Environment<O, E>>>,
import_stack: &mut Vec<String>,
@ -146,31 +170,37 @@ impl Builtins {
where
O: std::io::Write,
E: std::io::Write,
P: Into<PathBuf> + 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<O, E>(
fn include<P, O, E>(
&self,
base_path: P,
stack: &mut Vec<(Rc<Value>, Position)>,
env: Rc<RefCell<Environment<O, E>>>,
pos: Position,
@ -190,6 +221,7 @@ impl Builtins {
where
O: std::io::Write,
E: std::io::Write,
P: Into<PathBuf> + 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)

View File

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

View File

@ -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<Value>, Position)>,
symbols: Stack,
import_stack: Vec<String>,
@ -54,7 +56,6 @@ where
pub last: Option<(Rc<Value>, Position)>,
self_stack: Vec<(Rc<Value>, Position)>,
reserved_words: BTreeSet<&'static str>,
out_lock: bool,
}
impl<'a, O, E> VM<O, E>
@ -62,12 +63,21 @@ where
O: std::io::Write,
E: std::io::Write,
{
pub fn new(ops: Rc<PositionMap>, env: Rc<RefCell<Environment<O, E>>>) -> Self {
Self::with_pointer(OpPointer::new(ops), env)
pub fn new<P: Into<PathBuf>>(
ops: Rc<PositionMap>,
env: Rc<RefCell<Environment<O, E>>>,
working_dir: P,
) -> Self {
Self::with_pointer(OpPointer::new(ops), env, working_dir)
}
pub fn with_pointer(ops: OpPointer, env: Rc<RefCell<Environment<O, E>>>) -> Self {
pub fn with_pointer<P: Into<PathBuf>>(
ops: OpPointer,
env: Rc<RefCell<Environment<O, E>>>,
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<String>) -> 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,
)
}

View File

@ -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{