REFACTOR: Clarify the notion of working directory vs file.

This commit is contained in:
Jeremy Wall 2019-01-23 20:56:59 -06:00
parent b51231b3cb
commit 3f69896a08
2 changed files with 71 additions and 62 deletions

View File

@ -53,6 +53,7 @@ impl MacroDef {
/// Expands a ucg Macro using the given arguments into a new Tuple. /// Expands a ucg Macro using the given arguments into a new Tuple.
pub fn eval( pub fn eval(
&self, &self,
// TODO(jwall): This should come from the macrodef instead.
root: PathBuf, root: PathBuf,
parent_builder: &FileBuilder, parent_builder: &FileBuilder,
mut args: Vec<Rc<Val>>, mut args: Vec<Rc<Val>>,
@ -71,15 +72,13 @@ impl MacroDef {
} }
// If the args don't match the types required by the expressions then that is a TypeFail. // 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 MacroDef that is also an error. // If the expressions reference Symbols not defined in the MacroDef that is also an error.
// TODO(jwall): We should probably enforce that the Expression Symbols must be in argdefs rules
// at Macro definition time not evaluation time.
let mut build_output = HashMap::<PositionedItem<String>, Rc<Val>>::new(); let mut build_output = HashMap::<PositionedItem<String>, Rc<Val>>::new();
for (i, arg) in args.drain(0..).enumerate() { for (i, arg) in args.drain(0..).enumerate() {
build_output build_output
.entry(self.argdefs[i].clone()) .entry(self.argdefs[i].clone())
.or_insert(arg.clone()); .or_insert(arg.clone());
} }
let mut b = parent_builder.clone_builder(root); let mut b = parent_builder.clone_builder();
if let Some(ref scope) = self.scope { if let Some(ref scope) = self.scope {
b.scope = scope.spawn_child(); b.scope = scope.spawn_child();
} }
@ -102,7 +101,10 @@ pub struct AssertCollector {
/// Builder handles building ucg code for a single file. /// Builder handles building ucg code for a single file.
pub struct FileBuilder<'a> { pub struct FileBuilder<'a> {
file: PathBuf, // FIXME(jwall): This should probably become a working directory instead.
working_dir: PathBuf,
// FIXME(jwall): All of the below is build context shared amongst
// various builders.
std: Rc<HashMap<String, &'static str>>, std: Rc<HashMap<String, &'static str>>,
import_path: &'a Vec<PathBuf>, import_path: &'a Vec<PathBuf>,
validate_mode: bool, validate_mode: bool,
@ -148,28 +150,26 @@ macro_rules! eval_binary_expr {
impl<'a> FileBuilder<'a> { impl<'a> FileBuilder<'a> {
/// Constructs a new Builder. /// Constructs a new Builder.
pub fn new<P: Into<PathBuf>>( pub fn new<P: Into<PathBuf>>(
file: P, working_dir: P,
import_paths: &'a Vec<PathBuf>, import_paths: &'a Vec<PathBuf>,
cache: Rc<RefCell<assets::Cache>>, cache: Rc<RefCell<assets::Cache>>,
) -> Self { ) -> Self {
let env_vars: Vec<(String, String)> = env::vars().collect(); let env_vars: Vec<(String, String)> = env::vars().collect();
let scope = scope::Scope::new(Rc::new(Val::Env(env_vars))); let scope = scope::Scope::new(Rc::new(Val::Env(env_vars)));
Self::new_with_scope(file, import_paths, cache, scope) Self::new_with_scope(working_dir, import_paths, cache, scope)
} }
/// Constructs a new Builder with a provided scope. /// Constructs a new Builder with a provided scope.
pub fn new_with_scope<P: Into<PathBuf>>( pub fn new_with_scope<P: Into<PathBuf>>(
file: P, working_dir: P,
import_paths: &'a Vec<PathBuf>, import_paths: &'a Vec<PathBuf>,
cache: Rc<RefCell<assets::Cache>>, cache: Rc<RefCell<assets::Cache>>,
scope: Scope, scope: Scope,
) -> Self { ) -> Self {
let file = file.into();
let std = Rc::new(stdlib::get_libs());
FileBuilder { FileBuilder {
// Our import stack is initialized with ourself. // Our import stack is initialized with ourself.
file: file, working_dir: working_dir.into(),
std: std, std: Rc::new(stdlib::get_libs()),
import_path: import_paths, import_path: import_paths,
validate_mode: false, validate_mode: false,
assert_collector: AssertCollector { assert_collector: AssertCollector {
@ -188,9 +188,9 @@ impl<'a> FileBuilder<'a> {
} }
} }
pub fn clone_builder<P: Into<PathBuf>>(&self, file: P) -> Self { pub fn clone_builder(&self) -> Self {
FileBuilder { FileBuilder {
file: file.into(), working_dir: self.working_dir.clone(),
std: self.std.clone(), std: self.std.clone(),
import_path: self.import_path, import_path: self.import_path,
validate_mode: false, validate_mode: false,
@ -211,10 +211,39 @@ impl<'a> FileBuilder<'a> {
} }
} }
// TODO(jwall): With builder pattern
pub fn set_build_output(&mut self, scope: ValueMap) { pub fn set_build_output(&mut self, scope: ValueMap) {
self.scope.build_output = scope; self.scope.build_output = scope;
} }
/// Builds a ucg file at the named path.
pub fn build<P: Into<PathBuf>>(&mut self, file: P) -> BuildResult {
let file = file.into();
self.working_dir = file.parent().unwrap().to_path_buf();
let mut f = File::open(&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) { pub fn merge_build_output(&mut self, scope: ValueMap, clobber: bool) {
for (name, value) in scope.iter() { for (name, value) in scope.iter() {
if !clobber && !self.scope.build_output.contains_key(name) { if !clobber && !self.scope.build_output.contains_key(name) {
@ -317,38 +346,12 @@ impl<'a> FileBuilder<'a> {
} }
} }
// TODO Non file builder specific.
/// Evaluate an input string as UCG. /// Evaluate an input string as UCG.
pub fn eval_string(&mut self, input: &str) -> Result<Rc<Val>, Box<dyn Error>> { pub fn eval_string(&mut self, input: &str) -> Result<Rc<Val>, Box<dyn Error>> {
self.eval_input(OffsetStrIter::new(input)) self.eval_input(OffsetStrIter::new(input))
} }
// FileBuilder specific
/// Builds a ucg file at the named path.
pub fn build(&mut self) -> BuildResult {
let mut f = File::open(&self.file)?;
let mut s = String::new();
f.read_to_string(&mut s)?;
let input = OffsetStrIter::new(&s).with_src_file(self.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{}",
self.file.to_string_lossy(),
e.as_ref()
)
.as_ref(),
);
Err(Box::new(err))
}
}
}
fn check_reserved_word(name: &str) -> bool { fn check_reserved_word(name: &str) -> bool {
match name { match name {
"self" | "assert" | "true" | "false" | "let" | "import" | "as" | "select" | "macro" "self" | "assert" | "true" | "false" | "let" | "import" | "as" | "select" | "macro"
@ -372,7 +375,8 @@ impl<'a> FileBuilder<'a> {
) -> Result<PathBuf, Box<dyn Error>> { ) -> Result<PathBuf, Box<dyn Error>> {
// Try a relative path first. // Try a relative path first.
let path = path.into(); let path = path.into();
let mut normalized = self.file.parent().unwrap().to_path_buf(); // TODO(jwall): Change this to take a root directory.
let mut normalized = self.working_dir.clone();
if path.is_relative() { if path.is_relative() {
normalized.push(&path); normalized.push(&path);
// First see if the normalized file exists or not. // First see if the normalized file exists or not.
@ -405,7 +409,8 @@ impl<'a> FileBuilder<'a> {
let result = match maybe_asset { let result = match maybe_asset {
Some(v) => v.clone(), Some(v) => v.clone(),
None => { None => {
let mut b = self.clone_builder(self.file.clone()); // TODO(jwall): This does not need to be a FileBuilder specifically
let mut b = self.clone_builder();
b.eval_string(self.std.get(&def.path.fragment).unwrap())?; b.eval_string(self.std.get(&def.path.fragment).unwrap())?;
b.get_outputs_as_val() b.get_outputs_as_val()
} }
@ -441,8 +446,8 @@ impl<'a> FileBuilder<'a> {
let result = match maybe_asset { let result = match maybe_asset {
Some(v) => v.clone(), Some(v) => v.clone(),
None => { None => {
let mut b = self.clone_builder(normalized.clone()); let mut b = self.clone_builder();
b.build()?; b.build(&normalized)?;
b.get_outputs_as_val() b.get_outputs_as_val()
} }
}; };
@ -1099,7 +1104,8 @@ impl<'a> FileBuilder<'a> {
let maybe_tpl = mod_def.clone().arg_tuple.unwrap().clone(); let maybe_tpl = mod_def.clone().arg_tuple.unwrap().clone();
if let &Val::Tuple(ref src_fields) = maybe_tpl.as_ref() { if let &Val::Tuple(ref src_fields) = maybe_tpl.as_ref() {
// 1. First we create a builder. // 1. First we create a builder.
let mut b = self.clone_builder(self.file.clone()); // TODO(jwall): This file should optionally come from the module def itself.
let mut b = self.clone_builder();
b.is_module = true; b.is_module = true;
// 2. We construct an argument tuple by copying from the defs // 2. We construct an argument tuple by copying from the defs
// argset. // argset.
@ -1172,7 +1178,7 @@ impl<'a> FileBuilder<'a> {
for arg in args.iter() { for arg in args.iter() {
argvals.push(self.eval_expr(arg, scope)?); argvals.push(self.eval_expr(arg, scope)?);
} }
return Ok(m.eval(self.file.clone(), self, argvals)?); return Ok(m.eval(self.working_dir.clone(), self, argvals)?);
} }
Err(Box::new(error::BuildError::new( Err(Box::new(error::BuildError::new(
// We should pretty print the selectors here. // We should pretty print the selectors here.
@ -1187,17 +1193,19 @@ impl<'a> FileBuilder<'a> {
Ok(Rc::new(Val::Macro(def.clone()))) Ok(Rc::new(Val::Macro(def.clone())))
} }
// TODO(jwall): This stays with the FileBuilder specifically.
fn file_dir(&self) -> PathBuf { fn file_dir(&self) -> PathBuf {
return if self.file.is_file() { return if self.working_dir.is_file() {
// Only use the dirname portion if the root is a file. // Only use the dirname portion if the root is a file.
self.file.parent().unwrap().to_path_buf() self.working_dir.parent().unwrap().to_path_buf()
} else { } else {
// otherwise use clone of the root.. // otherwise use clone of the root.
self.file.clone() self.working_dir.clone()
}; };
} }
fn eval_module_def(&self, def: &ModuleDef, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> { fn eval_module_def(&self, def: &ModuleDef, scope: &Scope) -> Result<Rc<Val>, Box<dyn Error>> {
// TODO(jwall): This should actually be passed in to here.
let root = self.file_dir(); let root = self.file_dir();
// Always work on a copy. The original should not be modified. // Always work on a copy. The original should not be modified.
let mut def = def.clone(); let mut def = def.clone();
@ -1259,7 +1267,7 @@ impl<'a> FileBuilder<'a> {
let mut out = Vec::new(); let mut out = Vec::new();
for item in elems.iter() { for item in elems.iter() {
let argvals = vec![item.clone()]; let argvals = vec![item.clone()];
let val = def.eval(self.file.clone(), self, argvals)?; let val = def.eval(self.working_dir.clone(), self, argvals)?;
match typ { match typ {
ProcessingOpType::Map => { ProcessingOpType::Map => {
out.push(val.clone()); out.push(val.clone());
@ -1288,7 +1296,7 @@ impl<'a> FileBuilder<'a> {
let mut out = Vec::new(); let mut out = Vec::new();
for &(ref name, ref val) in fs { for &(ref name, ref val) in fs {
let argvals = vec![Rc::new(Val::Str(name.val.clone())), val.clone()]; let argvals = vec![Rc::new(Val::Str(name.val.clone())), val.clone()];
let result = def.eval(self.file.clone(), self, argvals)?; let result = def.eval(self.working_dir.clone(), self, argvals)?;
match typ { match typ {
ProcessingOpType::Map => { ProcessingOpType::Map => {
if let &Val::List(ref fs) = result.as_ref() { if let &Val::List(ref fs) = result.as_ref() {
@ -1365,7 +1373,7 @@ impl<'a> FileBuilder<'a> {
&Val::List(ref elems) => { &Val::List(ref elems) => {
for item in elems.iter() { for item in elems.iter() {
let argvals = vec![acc.clone(), item.clone()]; let argvals = vec![acc.clone(), item.clone()];
let result = macdef.eval(self.file.clone(), self, argvals)?; let result = macdef.eval(self.working_dir.clone(), self, argvals)?;
acc = result; acc = result;
} }
} }
@ -1376,14 +1384,14 @@ impl<'a> FileBuilder<'a> {
Rc::new(Val::Str(name.val.clone())), Rc::new(Val::Str(name.val.clone())),
val.clone(), val.clone(),
]; ];
let result = macdef.eval(self.file.clone(), self, argvals)?; let result = macdef.eval(self.working_dir.clone(), self, argvals)?;
acc = result; acc = result;
} }
} }
&Val::Str(ref s) => { &Val::Str(ref s) => {
for gc in s.graphemes(true) { for gc in s.graphemes(true) {
let argvals = vec![acc.clone(), Rc::new(Val::Str(gc.to_string()))]; let argvals = vec![acc.clone(), Rc::new(Val::Str(gc.to_string()))];
let result = macdef.eval(self.file.clone(), self, argvals)?; let result = macdef.eval(self.working_dir.clone(), self, argvals)?;
acc = result; acc = result;
} }
} }
@ -1410,7 +1418,7 @@ impl<'a> FileBuilder<'a> {
let mut result = String::new(); let mut result = String::new();
for gc in s.graphemes(true) { for gc in s.graphemes(true) {
let arg = Rc::new(Val::Str(gc.to_string())); let arg = Rc::new(Val::Str(gc.to_string()));
let out = def.eval(self.file.clone(), self, vec![arg])?; let out = def.eval(self.working_dir.clone(), self, vec![arg])?;
match typ { match typ {
ProcessingOpType::Filter => { ProcessingOpType::Filter => {
match out.as_ref() { match out.as_ref() {

View File

@ -87,14 +87,14 @@ fn build_file<'a>(
) -> Result<build::FileBuilder<'a>, Box<dyn Error>> { ) -> Result<build::FileBuilder<'a>, Box<dyn Error>> {
let mut file_path_buf = PathBuf::from(file); let mut file_path_buf = PathBuf::from(file);
if file_path_buf.is_relative() { if file_path_buf.is_relative() {
file_path_buf = std::env::current_dir().unwrap().join(file_path_buf); file_path_buf = std::env::current_dir()?.join(file_path_buf);
} }
let mut builder = build::FileBuilder::new(file_path_buf, import_paths, cache); let mut builder = build::FileBuilder::new(std::env::current_dir()?, import_paths, cache);
builder.set_strict(strict); builder.set_strict(strict);
if validate { if validate {
builder.enable_validate_mode(); builder.enable_validate_mode();
} }
builder.build()?; builder.build(file_path_buf)?;
if validate { if validate {
println!("{}", builder.assert_collector.summary); println!("{}", builder.assert_collector.summary);
} }
@ -247,12 +247,13 @@ fn inspect_command(
let file = matches.value_of("INPUT").unwrap(); let file = matches.value_of("INPUT").unwrap();
let sym = matches.value_of("expr"); let sym = matches.value_of("expr");
let target = matches.value_of("target").unwrap(); let target = matches.value_of("target").unwrap();
let mut builder = build::FileBuilder::new(file, import_paths, cache); let mut builder =
build::FileBuilder::new(std::env::current_dir().unwrap(), import_paths, cache);
builder.set_strict(strict); builder.set_strict(strict);
match registry.get_converter(target) { match registry.get_converter(target) {
Some(converter) => { Some(converter) => {
// TODO(jwall): We should warn if this is a test file. // TODO(jwall): We should warn if this is a test file.
let result = builder.build(); let result = builder.build(file);
if !result.is_ok() { if !result.is_ok() {
eprintln!("{:?}", result.err().unwrap()); eprintln!("{:?}", result.err().unwrap());
process::exit(1); process::exit(1);
@ -266,7 +267,7 @@ fn inspect_command(
} else { } else {
sym_name.to_owned() sym_name.to_owned()
}; };
let mut builder = builder.clone_builder("/eval"); let mut builder = builder.clone_builder();
match builder.eval_string(&normalized) { match builder.eval_string(&normalized) {
Ok(v) => Some(v.clone()), Ok(v) => Some(v.clone()),
Err(e) => { Err(e) => {