mirror of
https://github.com/zaphar/ucg.git
synced 2025-07-22 18:19:54 -04:00
FEATURE: UCG Parameterized Modules
closes #10 Squashed commit of the following: commit 3101c2bb9a385ed9e84481d36906a3e3ce93e571 Author: Jeremy Wall <jeremy@marzhillstudios.com> Date: Wed Nov 21 20:10:31 2018 -0600 FEATURE: Module evaluation * handle evaluating the module definition. * Handle performing a module instantiation via the copy syntax. commit 4ca863896b416e39f0c8eacc53384b9c514f6f14 Author: Jeremy Wall <jeremy@marzhillstudios.com> Date: Tue Nov 20 18:38:19 2018 -0600 FEATURE: Add module parsing expression parsing to ucg. changes toward issue #10
This commit is contained in:
parent
a9b374bf33
commit
fa96c7c0ef
@ -26,6 +26,7 @@ Some words are reserved in ucg and can not be used as a named binding.
|
||||
* as
|
||||
* select
|
||||
* macro
|
||||
* module
|
||||
* env
|
||||
* map
|
||||
* filter
|
||||
|
11
examples/module_example/modules/host_module.ucg
Normal file
11
examples/module_example/modules/host_module.ucg
Normal file
@ -0,0 +1,11 @@
|
||||
let host_mod = module{
|
||||
hostname="",
|
||||
mem=2048,
|
||||
cpu=2,
|
||||
} => {
|
||||
let config = {
|
||||
hostname = mod.hostname,
|
||||
memory_size = mod.mem,
|
||||
cpu_count = mod.cpu,
|
||||
};
|
||||
};
|
25
examples/module_example/modules/site_module.ucg
Normal file
25
examples/module_example/modules/site_module.ucg
Normal file
@ -0,0 +1,25 @@
|
||||
let site_mod = module{
|
||||
hostname="",
|
||||
port=0,
|
||||
db="localhost",
|
||||
db_user="admin",
|
||||
db_pass="password"
|
||||
} => { // mods do not close over their environment.
|
||||
|
||||
// this binding is not visible outside of the module. should be at the time of definition?
|
||||
// or path should be rewritten to be absolue before instantiation.
|
||||
import "../../shared.ucg" as shared;
|
||||
|
||||
// processing should be delayed till module instantiation.
|
||||
let base_config = shared.mk_site_config(mod.hostname, mod.port);
|
||||
|
||||
let config = base_config{ // processing should also be delayed.
|
||||
dbs = [
|
||||
{
|
||||
database = mod.db,
|
||||
user = mod.db_user,
|
||||
pass = mod.db_pass,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
12
examples/module_example/modules/unified.ucg
Normal file
12
examples/module_example/modules/unified.ucg
Normal file
@ -0,0 +1,12 @@
|
||||
let composed = module{} => {
|
||||
import "host_module.ucg" as host_mod;
|
||||
import "site_module.ucg" as site_mod;
|
||||
|
||||
let site_conf = site_mod.site_mod{hostname="example.com", port=80};
|
||||
let host_conf = host_mod.host_mod{hostname="example.com"};
|
||||
};
|
||||
|
||||
let unified = composed{};
|
||||
|
||||
let site_conf = unified.site_conf;
|
||||
let host_conf = unified.host_conf;
|
3
examples/module_example/test_mod_host.ucg
Normal file
3
examples/module_example/test_mod_host.ucg
Normal file
@ -0,0 +1,3 @@
|
||||
import "modules/unified.ucg" as unified;
|
||||
|
||||
out yaml unified.host_conf;
|
3
examples/module_example/test_mod_site.ucg
Normal file
3
examples/module_example/test_mod_site.ucg
Normal file
@ -0,0 +1,3 @@
|
||||
import "modules/unified.ucg" as unified;
|
||||
|
||||
out json unified.site_conf;
|
@ -1 +1,7 @@
|
||||
let port = 3306;
|
||||
let port = 3306;
|
||||
|
||||
let mk_site_config = macro(hostname, port) => {
|
||||
base_url = "https://@/" % (hostname),
|
||||
port = port,
|
||||
dbs = [],
|
||||
};
|
@ -1 +1 @@
|
||||
--port 8080 --listen '0.0.0.0' --verbose --dir 'some/dir' --dir 'some/other/dir' --log.debug true--log.format 'json'
|
||||
--port 8080 --listen '0.0.0.0' --verbose --dir 'some/dir' --dir 'some/other/dir' --log.debug true --log.format 'json'
|
45
integration_tests/modules_test.ucg
Normal file
45
integration_tests/modules_test.ucg
Normal file
@ -0,0 +1,45 @@
|
||||
let test_empty_mod = module {
|
||||
} => {
|
||||
};
|
||||
|
||||
let empty_mod_instance = test_empty_mod{};
|
||||
|
||||
let test_simple_mod = module {
|
||||
arg = "value",
|
||||
} => {
|
||||
let value = mod.arg;
|
||||
};
|
||||
|
||||
let simple_mod_instance = test_simple_mod{};
|
||||
assert |
|
||||
simple_mod_instance.value == "value";
|
||||
|;
|
||||
|
||||
let simple_mod_with_args = test_simple_mod{arg = "othervalue"};
|
||||
assert |
|
||||
simple_mod_with_args.value == "othervalue";
|
||||
|;
|
||||
|
||||
let embedded_mod = module {
|
||||
deep_value = "None",
|
||||
} => {
|
||||
let embedded_def = module {
|
||||
deep_value = "None",
|
||||
} => {
|
||||
let value = mod.deep_value;
|
||||
};
|
||||
|
||||
let embedded = embedded_def{deep_value = mod.deep_value};
|
||||
};
|
||||
|
||||
let embedded_default_params = embedded_mod{};
|
||||
|
||||
assert |
|
||||
embedded_default_params.embedded.value == "None";
|
||||
|;
|
||||
|
||||
let embedded_with_params = embedded_mod{deep_value = "Some"};
|
||||
|
||||
assert |
|
||||
embedded_with_params.embedded.value == "Some";
|
||||
|;
|
@ -24,9 +24,13 @@ use std::convert::Into;
|
||||
use std::fmt;
|
||||
use std::hash::Hash;
|
||||
use std::hash::Hasher;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
use abortable_parser;
|
||||
|
||||
use build::Val;
|
||||
|
||||
macro_rules! enum_type_equality {
|
||||
( $slf:ident, $r:expr, $( $l:pat ),* ) => {
|
||||
match $slf {
|
||||
@ -612,6 +616,10 @@ impl MacroDef {
|
||||
// noop
|
||||
continue;
|
||||
}
|
||||
&Expression::Module(_) => {
|
||||
// noop
|
||||
continue;
|
||||
}
|
||||
&Expression::ListOp(_) => {
|
||||
// noop
|
||||
continue;
|
||||
@ -705,6 +713,43 @@ pub struct ListOpDef {
|
||||
pub pos: Position,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct ModuleDef {
|
||||
pub pos: Position,
|
||||
pub arg_set: FieldList,
|
||||
// FIXME(jwall): this should probably be moved to a Val::Module IR type.
|
||||
pub arg_tuple: Option<Rc<Val>>,
|
||||
pub statements: Vec<Statement>,
|
||||
}
|
||||
|
||||
impl ModuleDef {
|
||||
pub fn new<P: Into<Position>>(arg_set: FieldList, stmts: Vec<Statement>, pos: P) -> Self {
|
||||
ModuleDef {
|
||||
pos: pos.into(),
|
||||
arg_set: arg_set,
|
||||
// TODO(jwall): Should this get moved to a Val version of our ModuleDef?
|
||||
arg_tuple: None,
|
||||
statements: stmts,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn imports_to_absolute(&mut self, base: PathBuf) {
|
||||
for stmt in self.statements.iter_mut() {
|
||||
if let &mut Statement::Import(ref mut def) = stmt {
|
||||
let mut path = PathBuf::from(&def.path.fragment);
|
||||
if path.is_relative() {
|
||||
def.path.fragment = base
|
||||
.join(path)
|
||||
.canonicalize()
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes a ucg expression. Expressions compute a value from.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Expression {
|
||||
@ -724,6 +769,7 @@ pub enum Expression {
|
||||
Macro(MacroDef),
|
||||
Select(SelectDef),
|
||||
ListOp(ListOpDef),
|
||||
Module(ModuleDef),
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
@ -738,6 +784,7 @@ impl Expression {
|
||||
&Expression::Format(ref def) => &def.pos,
|
||||
&Expression::Call(ref def) => &def.pos,
|
||||
&Expression::Macro(ref def) => &def.pos,
|
||||
&Expression::Module(ref def) => &def.pos,
|
||||
&Expression::Select(ref def) => &def.pos,
|
||||
&Expression::ListOp(ref def) => &def.pos,
|
||||
}
|
||||
@ -774,6 +821,9 @@ impl fmt::Display for Expression {
|
||||
&Expression::Macro(_) => {
|
||||
try!(write!(w, "<Macro>"));
|
||||
}
|
||||
&Expression::Module(_) => {
|
||||
try!(write!(w, "<Module>"));
|
||||
}
|
||||
&Expression::Select(_) => {
|
||||
try!(write!(w, "<Select>"));
|
||||
}
|
||||
@ -783,21 +833,21 @@ impl fmt::Display for Expression {
|
||||
}
|
||||
|
||||
/// Encodes a let statement in the UCG AST.
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct LetDef {
|
||||
pub name: Token,
|
||||
pub value: Expression,
|
||||
}
|
||||
|
||||
/// Encodes an import statement in the UCG AST.
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct ImportDef {
|
||||
pub path: Token,
|
||||
pub name: Token,
|
||||
}
|
||||
|
||||
/// Encodes a parsed statement in the UCG AST.
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Statement {
|
||||
// simple expression
|
||||
Expression(Expression),
|
||||
|
@ -48,6 +48,11 @@ fn test_macros() {
|
||||
assert_build(include_str!("../../integration_tests/macros_test.ucg"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_modules() {
|
||||
assert_build(include_str!("../../integration_tests/modules_test.ucg"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_selectors() {
|
||||
assert_build(include_str!("../../integration_tests/selectors_test.ucg"));
|
||||
|
@ -20,6 +20,7 @@ pub enum Val {
|
||||
List(Vec<Rc<Val>>),
|
||||
Tuple(Vec<(PositionedItem<String>, Rc<Val>)>),
|
||||
Macro(MacroDef),
|
||||
Module(ModuleDef),
|
||||
}
|
||||
|
||||
impl Val {
|
||||
@ -34,6 +35,7 @@ impl Val {
|
||||
&Val::List(_) => "List".to_string(),
|
||||
&Val::Tuple(_) => "Tuple".to_string(),
|
||||
&Val::Macro(_) => "Macro".to_string(),
|
||||
&Val::Module(_) => "Module".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +51,8 @@ impl Val {
|
||||
&Val::Str(_),
|
||||
&Val::List(_),
|
||||
&Val::Tuple(_),
|
||||
&Val::Macro(_)
|
||||
&Val::Macro(_),
|
||||
&Val::Module(_)
|
||||
)
|
||||
}
|
||||
|
||||
@ -105,6 +108,11 @@ impl Val {
|
||||
error::ErrorType::TypeFail,
|
||||
pos,
|
||||
)),
|
||||
(&Val::Module(_), &Val::Module(_)) => Err(error::BuildError::new(
|
||||
format!("Module are not comparable in file: {}", file_name),
|
||||
error::ErrorType::TypeFail,
|
||||
pos,
|
||||
)),
|
||||
(me, tgt) => Err(error::BuildError::new(
|
||||
format!("Types differ for {}, {} in file: {}", me, tgt, file_name),
|
||||
error::ErrorType::TypeFail,
|
||||
@ -188,6 +196,7 @@ impl Display for Val {
|
||||
write!(f, "]")
|
||||
}
|
||||
&Val::Macro(_) => write!(f, "Macro(..)"),
|
||||
&Val::Module(_) => write!(f, "Module{{..}}"),
|
||||
&Val::Tuple(ref def) => {
|
||||
try!(write!(f, "Tuple(\n"));
|
||||
for v in def.iter() {
|
||||
|
247
src/build/mod.rs
247
src/build/mod.rs
@ -110,11 +110,14 @@ pub struct Builder<'a> {
|
||||
/// 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.
|
||||
// FIXME(jwall): This probably needs to be running in a separate thread
|
||||
// with some sort of RPC mechanism instead.
|
||||
assets: Rc<RefCell<assets::Cache>>,
|
||||
/// build_output is our built output.
|
||||
build_output: ValueMap,
|
||||
/// last is the result of the last statement.
|
||||
pub stack: Option<Vec<Rc<Val>>>,
|
||||
pub is_module: bool,
|
||||
pub last: Option<Rc<Val>>,
|
||||
pub out_lock: Option<(String, Rc<Val>)>,
|
||||
}
|
||||
@ -223,6 +226,7 @@ impl<'a> Builder<'a> {
|
||||
build_output: scope,
|
||||
out_lock: None,
|
||||
stack: None,
|
||||
is_module: false,
|
||||
last: None,
|
||||
}
|
||||
}
|
||||
@ -301,20 +305,26 @@ impl<'a> Builder<'a> {
|
||||
|
||||
fn build_import(&mut self, def: &ImportDef) -> Result<Rc<Val>, Box<Error>> {
|
||||
let sym = &def.name;
|
||||
let mut normalized = self.root.to_path_buf();
|
||||
normalized.push(&def.path.fragment);
|
||||
let mut normalized = self.root.clone();
|
||||
let import_path = PathBuf::from(&def.path.fragment);
|
||||
if import_path.is_relative() {
|
||||
normalized.push(&def.path.fragment);
|
||||
} else {
|
||||
normalized = import_path;
|
||||
}
|
||||
normalized = try!(normalized.canonicalize());
|
||||
eprintln!("processing import for {}", normalized.to_string_lossy());
|
||||
// Introduce a scope so the above borrow is dropped before we modify
|
||||
// the cache below.
|
||||
// Only parse the file once on import.
|
||||
let mut shared_assets = self.assets.borrow_mut();
|
||||
let result = match try!(shared_assets.get(&normalized)) {
|
||||
let maybe_asset = try!(self.assets.borrow().get(&normalized));
|
||||
let result = match maybe_asset {
|
||||
Some(v) => v.clone(),
|
||||
None => {
|
||||
let mut b = Self::new(normalized.clone(), self.assets.clone());
|
||||
let filepath = normalized.to_str().unwrap().clone();
|
||||
try!(b.build_file(filepath));
|
||||
let fields: Vec<(PositionedItem<String>, Rc<Val>)> =
|
||||
b.build_output.drain().collect();
|
||||
Rc::new(Val::Tuple(fields))
|
||||
b.get_outputs_as_val()
|
||||
}
|
||||
};
|
||||
let key = sym.into();
|
||||
@ -326,7 +336,8 @@ impl<'a> Builder<'a> {
|
||||
)));
|
||||
}
|
||||
self.build_output.insert(key, result.clone());
|
||||
try!(shared_assets.stash(normalized.clone(), result.clone()));
|
||||
let mut mut_assets_cache = self.assets.borrow_mut();
|
||||
try!(mut_assets_cache.stash(normalized.clone(), result.clone()));
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
@ -383,7 +394,7 @@ impl<'a> Builder<'a> {
|
||||
return Some(self.env.clone());
|
||||
}
|
||||
if &sym.val == "self" {
|
||||
// TODO(jwall): we need to look at the current tuple in the stack.
|
||||
eprintln!("XXX: In tuple self is {:?}", self.peek_val());
|
||||
return self.peek_val();
|
||||
}
|
||||
if self.build_output.contains_key(sym) {
|
||||
@ -823,83 +834,143 @@ impl<'a> Builder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_outputs_as_val(&mut self) -> Rc<Val> {
|
||||
let fields: Vec<(PositionedItem<String>, Rc<Val>)> = self.build_output.drain().collect();
|
||||
Rc::new(Val::Tuple(fields))
|
||||
}
|
||||
|
||||
fn copy_from_base(
|
||||
&mut self,
|
||||
src_fields: &Vec<(PositionedItem<String>, Rc<Val>)>,
|
||||
overrides: &Vec<(Token, Expression)>,
|
||||
) -> Result<Rc<Val>, Box<Error>> {
|
||||
let mut m = HashMap::<PositionedItem<String>, (i32, Rc<Val>)>::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 {
|
||||
self.pop_val();
|
||||
return Err(Box::new(error::BuildError::new(
|
||||
format!(
|
||||
"Duplicate \
|
||||
field: {} in \
|
||||
tuple",
|
||||
key.val
|
||||
),
|
||||
error::ErrorType::TypeFail,
|
||||
key.pos.clone(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
for &(ref key, ref val) in overrides.iter() {
|
||||
let expr_result = try!(self.eval_expr(val));
|
||||
match m.entry(key.into()) {
|
||||
// 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) {
|
||||
v.insert((src_val.0, expr_result));
|
||||
} else {
|
||||
self.pop_val();
|
||||
return Err(Box::new(error::BuildError::new(
|
||||
format!(
|
||||
"Expected type {} for field {} but got {}",
|
||||
src_val.1.type_name(),
|
||||
key.fragment,
|
||||
expr_result.type_name()
|
||||
),
|
||||
error::ErrorType::TypeFail,
|
||||
key.pos.clone(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
self.pop_val();
|
||||
let mut new_fields: Vec<(PositionedItem<String>, (i32, Rc<Val>))> = 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_copy(&mut self, def: &CopyDef) -> Result<Rc<Val>, Box<Error>> {
|
||||
let v = try!(self.lookup_selector(&def.selector.sel));
|
||||
if let &Val::Tuple(ref src_fields) = v.as_ref() {
|
||||
self.push_val(v.clone());
|
||||
let mut m = HashMap::<PositionedItem<String>, (i32, Rc<Val>)>::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 {
|
||||
self.pop_val();
|
||||
return Err(Box::new(error::BuildError::new(
|
||||
format!(
|
||||
"Duplicate \
|
||||
field: {} in \
|
||||
tuple",
|
||||
key.val
|
||||
),
|
||||
error::ErrorType::TypeFail,
|
||||
key.pos.clone(),
|
||||
)));
|
||||
return self.copy_from_base(&src_fields, &def.fields);
|
||||
}
|
||||
if let &Val::Module(ref mod_def) = v.as_ref() {
|
||||
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::new(self.root.clone(), self.assets.clone());
|
||||
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.
|
||||
b.push_val(maybe_tpl.clone());
|
||||
let mod_args = try!(self.copy_from_base(src_fields, &def.fields));
|
||||
// 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.build_output.entry(mod_key) {
|
||||
Entry::Occupied(e) => {
|
||||
return Err(Box::new(error::BuildError::new(
|
||||
format!(
|
||||
"Binding \
|
||||
for {:?} already \
|
||||
exists in module",
|
||||
e.key(),
|
||||
),
|
||||
error::ErrorType::DuplicateBinding,
|
||||
mod_def.pos.clone(),
|
||||
)));
|
||||
}
|
||||
Entry::Vacant(e) => {
|
||||
e.insert(mod_args.clone());
|
||||
}
|
||||
}
|
||||
// 4. Evaluate all the statements using the builder.
|
||||
try!(b.build(&mod_def.statements));
|
||||
// 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(Box::new(error::BuildError::new(
|
||||
format!(
|
||||
"Weird value stored in our module parameters slot {:?}",
|
||||
mod_def.arg_tuple
|
||||
),
|
||||
error::ErrorType::TypeFail,
|
||||
def.selector.pos.clone(),
|
||||
)));
|
||||
}
|
||||
for &(ref key, ref val) in def.fields.iter() {
|
||||
// TODO(jwall): Allow the special value self to refer to the base tuple.
|
||||
let expr_result = try!(self.eval_expr(val));
|
||||
match m.entry(key.into()) {
|
||||
// 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) {
|
||||
v.insert((src_val.0, expr_result));
|
||||
} else {
|
||||
self.pop_val();
|
||||
return Err(Box::new(error::BuildError::new(
|
||||
format!(
|
||||
"Expected type {} for field {} but got {}",
|
||||
src_val.1.type_name(),
|
||||
key.fragment,
|
||||
expr_result.type_name()
|
||||
),
|
||||
error::ErrorType::TypeFail,
|
||||
key.pos.clone(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
self.pop_val();
|
||||
let mut new_fields: Vec<(PositionedItem<String>, (i32, Rc<Val>))> = 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(),
|
||||
)));
|
||||
}
|
||||
Err(Box::new(error::BuildError::new(
|
||||
format!("Expected Tuple got {}", v),
|
||||
format!("Expected Tuple or Module got {}", v),
|
||||
error::ErrorType::TypeFail,
|
||||
def.selector.pos.clone(),
|
||||
)))
|
||||
@ -917,6 +988,7 @@ impl<'a> Builder<'a> {
|
||||
Ok(Rc::new(Val::Str(try!(formatter.render(&def.pos)))))
|
||||
}
|
||||
|
||||
// FIXME(jwall): Handle module calls as well?
|
||||
fn eval_call(&mut self, def: &CallDef) -> Result<Rc<Val>, Box<Error>> {
|
||||
let sel = &def.macroref;
|
||||
let args = &def.arglist;
|
||||
@ -958,6 +1030,24 @@ impl<'a> Builder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_module_def(&mut self, def: &ModuleDef) -> Result<Rc<Val>, Box<Error>> {
|
||||
// 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.
|
||||
let root = if self.root.is_file() {
|
||||
// Only use the dirname portion if the root is a file.
|
||||
self.root.parent().unwrap().to_path_buf()
|
||||
} else {
|
||||
// otherwise use clone of the root..
|
||||
self.root.clone()
|
||||
};
|
||||
def.imports_to_absolute(root);
|
||||
// Then we create our tuple default.
|
||||
def.arg_tuple = Some(try!(self.tuple_to_val(&def.arg_set)));
|
||||
// Then we construct a new Val::Module
|
||||
Ok(Rc::new(Val::Module(def)))
|
||||
}
|
||||
|
||||
fn eval_select(&mut self, def: &SelectDef) -> Result<Rc<Val>, Box<Error>> {
|
||||
let target = &def.val;
|
||||
let def_expr = &def.default;
|
||||
@ -1105,6 +1195,7 @@ impl<'a> Builder<'a> {
|
||||
&Expression::Format(ref def) => self.eval_format(def),
|
||||
&Expression::Call(ref def) => self.eval_call(def),
|
||||
&Expression::Macro(ref def) => self.eval_macro_def(def),
|
||||
&Expression::Module(ref def) => self.eval_module_def(def),
|
||||
&Expression::Select(ref def) => self.eval_select(def),
|
||||
&Expression::ListOp(ref def) => self.eval_list_op(def),
|
||||
}
|
||||
|
@ -161,7 +161,7 @@ fn test_expr_copy_no_such_tuple() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Expected Tuple got Int(1)")]
|
||||
#[should_panic(expected = "Expected Tuple or Module got Int(1)")]
|
||||
fn test_expr_copy_not_a_tuple() {
|
||||
let cache = Rc::new(RefCell::new(MemoryCache::new()));
|
||||
let mut b = Builder::new(std::env::current_dir().unwrap(), cache);
|
||||
|
@ -82,6 +82,10 @@ impl EnvConverter {
|
||||
// This is ignored
|
||||
eprintln!("Skipping macro...");
|
||||
}
|
||||
&Val::Module(ref _def) => {
|
||||
// This is ignored
|
||||
eprintln!("Skipping module...");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -98,6 +98,10 @@ impl FlagConverter {
|
||||
// This is ignored
|
||||
eprintln!("Skipping macro...");
|
||||
}
|
||||
&Val::Module(ref _def) => {
|
||||
// This is ignored
|
||||
eprintln!("Skipping module...");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -71,6 +71,10 @@ impl JsonConverter {
|
||||
eprintln!("Skipping macro encoding as null...");
|
||||
serde_json::Value::Null
|
||||
}
|
||||
&Val::Module(_) => {
|
||||
eprintln!("Skipping module encoding as null...");
|
||||
serde_json::Value::Null
|
||||
}
|
||||
&Val::List(ref l) => try!(self.convert_list(l)),
|
||||
&Val::Tuple(ref t) => try!(self.convert_tuple(t)),
|
||||
};
|
||||
|
@ -65,6 +65,10 @@ impl TomlConverter {
|
||||
let err = SimpleError::new("Macros are not allowed in Toml Conversions!");
|
||||
return Err(Box::new(err));
|
||||
}
|
||||
&Val::Module(_) => {
|
||||
let err = SimpleError::new("Modules are not allowed in Toml Conversions!");
|
||||
return Err(Box::new(err));
|
||||
}
|
||||
&Val::List(ref l) => try!(self.convert_list(l)),
|
||||
&Val::Tuple(ref t) => try!(self.convert_tuple(t)),
|
||||
};
|
||||
|
@ -54,6 +54,10 @@ impl YamlConverter {
|
||||
eprintln!("Skipping macro encoding as null...");
|
||||
serde_yaml::Value::Null
|
||||
}
|
||||
&Val::Module(_) => {
|
||||
eprintln!("Skipping module encoding as null...");
|
||||
serde_yaml::Value::Null
|
||||
}
|
||||
&Val::List(ref l) => try!(self.convert_list(l)),
|
||||
&Val::Tuple(ref t) => try!(self.convert_tuple(t)),
|
||||
};
|
||||
|
@ -76,7 +76,10 @@ fn build_file(
|
||||
validate: bool,
|
||||
cache: Rc<RefCell<Cache>>,
|
||||
) -> Result<build::Builder, Box<Error>> {
|
||||
let root = PathBuf::from(file);
|
||||
let mut root = PathBuf::from(file);
|
||||
if root.is_relative() {
|
||||
root = std::env::current_dir().unwrap().join(root);
|
||||
}
|
||||
let mut builder = build::Builder::new(root.parent().unwrap(), cache);
|
||||
if validate {
|
||||
builder.enable_validate_mode();
|
||||
|
@ -557,6 +557,37 @@ make_fn!(
|
||||
separated!(punct!(","), symbol)
|
||||
);
|
||||
|
||||
fn module_expression(input: SliceIter<Token>) -> Result<SliceIter<Token>, Expression> {
|
||||
let parsed = do_each!(input,
|
||||
pos => pos,
|
||||
_ => word!("module"),
|
||||
_ => punct!("{"),
|
||||
arglist => trace_nom!(optional!(field_list)),
|
||||
_ => optional!(punct!(",")),
|
||||
_ => punct!("}"),
|
||||
_ => punct!("=>"),
|
||||
_ => punct!("{"),
|
||||
stmt_list => trace_nom!(repeat!(statement)),
|
||||
_ => punct!("}"),
|
||||
(pos, arglist, stmt_list)
|
||||
);
|
||||
match parsed {
|
||||
Result::Abort(e) => Result::Abort(e),
|
||||
Result::Fail(e) => Result::Fail(e),
|
||||
Result::Incomplete(offset) => Result::Incomplete(offset),
|
||||
Result::Complete(rest, (pos, arglist, stmt_list)) => {
|
||||
let def = ModuleDef::new(arglist.unwrap_or_else(|| Vec::new()), stmt_list, pos);
|
||||
//eprintln!(
|
||||
// "module def at: {:?} arg_typle len {} stmts len {}",
|
||||
// def.pos,
|
||||
// def.arg_set.len(),
|
||||
// def.statements.len()
|
||||
//);
|
||||
Result::Complete(rest, Expression::Module(def))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn macro_expression(input: SliceIter<Token>) -> Result<SliceIter<Token>, Expression> {
|
||||
let parsed = do_each!(input,
|
||||
pos => pos,
|
||||
@ -691,11 +722,11 @@ make_fn!(
|
||||
|
||||
fn call_expression(input: SliceIter<Token>) -> Result<SliceIter<Token>, Expression> {
|
||||
let parsed = do_each!(input.clone(),
|
||||
macroname => trace_nom!(selector_value),
|
||||
callee_name => trace_nom!(selector_value),
|
||||
_ => punct!("("),
|
||||
args => optional!(separated!(punct!(","), trace_nom!(expression))),
|
||||
_ => punct!(")"),
|
||||
(macroname, args)
|
||||
(callee_name, args)
|
||||
);
|
||||
match parsed {
|
||||
Result::Abort(e) => Result::Abort(e),
|
||||
@ -806,6 +837,7 @@ make_fn!(
|
||||
alt_peek!(
|
||||
either!(word!("map"), word!("filter")) => trace_nom!(list_op_expression) |
|
||||
word!("macro") => trace_nom!(macro_expression) |
|
||||
word!("module") => trace_nom!(module_expression) |
|
||||
word!("select") => trace_nom!(select_expression) |
|
||||
punct!("(") => trace_nom!(grouped_expression) |
|
||||
trace_nom!(unprefixed_expression))
|
||||
@ -846,7 +878,7 @@ make_fn!(
|
||||
name => wrap_err!(match_type!(BAREWORD), "Expected name for binding"),
|
||||
_ => punct!("="),
|
||||
// TODO(jwall): Wrap this error with an appropriate abortable_parser::Error
|
||||
val => with_err!(trace_nom!(expression), "Expected Expression"),
|
||||
val => wrap_err!(trace_nom!(expression), "Expected Expression"),
|
||||
_ => punct!(";"),
|
||||
(tuple_to_let(name, val))
|
||||
)
|
||||
@ -904,7 +936,7 @@ make_fn!(
|
||||
do_each!(
|
||||
_ => word!("out"),
|
||||
typ => wrap_err!(must!(match_type!(BAREWORD)), "Expected converter name"),
|
||||
expr => with_err!(must!(expression), "Expected Expression to export"),
|
||||
expr => wrap_err!(must!(expression), "Expected Expression to export"),
|
||||
_ => must!(punct!(";")),
|
||||
(Statement::Output(typ.clone(), expr.clone()))
|
||||
)
|
||||
|
@ -928,6 +928,28 @@ fn test_select_parse() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_module_expression_parsing() {
|
||||
assert_fail!(module_expression("foo"));
|
||||
assert_fail!(module_expression("module"));
|
||||
assert_fail!(module_expression("module("));
|
||||
assert_fail!(module_expression("module["));
|
||||
assert_fail!(module_expression("module {"));
|
||||
assert_fail!(module_expression("module {}"));
|
||||
assert_fail!(module_expression("module {} =>"));
|
||||
assert_fail!(module_expression("module {} => {"));
|
||||
|
||||
assert_parse!(
|
||||
module_expression("module {} => {}"),
|
||||
Expression::Module(ModuleDef {
|
||||
pos: Position::new(1, 1, 0),
|
||||
arg_set: Vec::new(),
|
||||
arg_tuple: None,
|
||||
statements: Vec::new(),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_macro_expression_parsing() {
|
||||
assert_fail!(macro_expression("foo"));
|
||||
|
@ -268,6 +268,10 @@ make_fn!(macrotok<OffsetStrIter, Token>,
|
||||
do_text_token_tok!(TokenType::BAREWORD, "macro", WS)
|
||||
);
|
||||
|
||||
make_fn!(moduletok<OffsetStrIter, Token>,
|
||||
do_text_token_tok!(TokenType::BAREWORD, "module", WS)
|
||||
);
|
||||
|
||||
make_fn!(lettok<OffsetStrIter, Token>,
|
||||
do_text_token_tok!(TokenType::BAREWORD, "let", WS)
|
||||
);
|
||||
@ -386,6 +390,7 @@ fn token<'a>(input: OffsetStrIter<'a>) -> Result<OffsetStrIter<'a>, Token> {
|
||||
selecttok,
|
||||
asserttok,
|
||||
macrotok,
|
||||
moduletok,
|
||||
importtok,
|
||||
astok,
|
||||
maptok,
|
||||
|
Loading…
x
Reference in New Issue
Block a user