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
|
* as
|
||||||
* select
|
* select
|
||||||
* macro
|
* macro
|
||||||
|
* module
|
||||||
* env
|
* env
|
||||||
* map
|
* map
|
||||||
* filter
|
* 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::fmt;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::hash::Hasher;
|
use std::hash::Hasher;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use abortable_parser;
|
use abortable_parser;
|
||||||
|
|
||||||
|
use build::Val;
|
||||||
|
|
||||||
macro_rules! enum_type_equality {
|
macro_rules! enum_type_equality {
|
||||||
( $slf:ident, $r:expr, $( $l:pat ),* ) => {
|
( $slf:ident, $r:expr, $( $l:pat ),* ) => {
|
||||||
match $slf {
|
match $slf {
|
||||||
@ -612,6 +616,10 @@ impl MacroDef {
|
|||||||
// noop
|
// noop
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
&Expression::Module(_) => {
|
||||||
|
// noop
|
||||||
|
continue;
|
||||||
|
}
|
||||||
&Expression::ListOp(_) => {
|
&Expression::ListOp(_) => {
|
||||||
// noop
|
// noop
|
||||||
continue;
|
continue;
|
||||||
@ -705,6 +713,43 @@ pub struct ListOpDef {
|
|||||||
pub pos: Position,
|
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.
|
/// Encodes a ucg expression. Expressions compute a value from.
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum Expression {
|
pub enum Expression {
|
||||||
@ -724,6 +769,7 @@ pub enum Expression {
|
|||||||
Macro(MacroDef),
|
Macro(MacroDef),
|
||||||
Select(SelectDef),
|
Select(SelectDef),
|
||||||
ListOp(ListOpDef),
|
ListOp(ListOpDef),
|
||||||
|
Module(ModuleDef),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Expression {
|
impl Expression {
|
||||||
@ -738,6 +784,7 @@ impl Expression {
|
|||||||
&Expression::Format(ref def) => &def.pos,
|
&Expression::Format(ref def) => &def.pos,
|
||||||
&Expression::Call(ref def) => &def.pos,
|
&Expression::Call(ref def) => &def.pos,
|
||||||
&Expression::Macro(ref def) => &def.pos,
|
&Expression::Macro(ref def) => &def.pos,
|
||||||
|
&Expression::Module(ref def) => &def.pos,
|
||||||
&Expression::Select(ref def) => &def.pos,
|
&Expression::Select(ref def) => &def.pos,
|
||||||
&Expression::ListOp(ref def) => &def.pos,
|
&Expression::ListOp(ref def) => &def.pos,
|
||||||
}
|
}
|
||||||
@ -774,6 +821,9 @@ impl fmt::Display for Expression {
|
|||||||
&Expression::Macro(_) => {
|
&Expression::Macro(_) => {
|
||||||
try!(write!(w, "<Macro>"));
|
try!(write!(w, "<Macro>"));
|
||||||
}
|
}
|
||||||
|
&Expression::Module(_) => {
|
||||||
|
try!(write!(w, "<Module>"));
|
||||||
|
}
|
||||||
&Expression::Select(_) => {
|
&Expression::Select(_) => {
|
||||||
try!(write!(w, "<Select>"));
|
try!(write!(w, "<Select>"));
|
||||||
}
|
}
|
||||||
@ -783,21 +833,21 @@ impl fmt::Display for Expression {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Encodes a let statement in the UCG AST.
|
/// Encodes a let statement in the UCG AST.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct LetDef {
|
pub struct LetDef {
|
||||||
pub name: Token,
|
pub name: Token,
|
||||||
pub value: Expression,
|
pub value: Expression,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encodes an import statement in the UCG AST.
|
/// Encodes an import statement in the UCG AST.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct ImportDef {
|
pub struct ImportDef {
|
||||||
pub path: Token,
|
pub path: Token,
|
||||||
pub name: Token,
|
pub name: Token,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encodes a parsed statement in the UCG AST.
|
/// Encodes a parsed statement in the UCG AST.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum Statement {
|
pub enum Statement {
|
||||||
// simple expression
|
// simple expression
|
||||||
Expression(Expression),
|
Expression(Expression),
|
||||||
|
@ -48,6 +48,11 @@ fn test_macros() {
|
|||||||
assert_build(include_str!("../../integration_tests/macros_test.ucg"));
|
assert_build(include_str!("../../integration_tests/macros_test.ucg"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_modules() {
|
||||||
|
assert_build(include_str!("../../integration_tests/modules_test.ucg"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_selectors() {
|
fn test_selectors() {
|
||||||
assert_build(include_str!("../../integration_tests/selectors_test.ucg"));
|
assert_build(include_str!("../../integration_tests/selectors_test.ucg"));
|
||||||
|
@ -20,6 +20,7 @@ pub enum Val {
|
|||||||
List(Vec<Rc<Val>>),
|
List(Vec<Rc<Val>>),
|
||||||
Tuple(Vec<(PositionedItem<String>, Rc<Val>)>),
|
Tuple(Vec<(PositionedItem<String>, Rc<Val>)>),
|
||||||
Macro(MacroDef),
|
Macro(MacroDef),
|
||||||
|
Module(ModuleDef),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Val {
|
impl Val {
|
||||||
@ -34,6 +35,7 @@ impl Val {
|
|||||||
&Val::List(_) => "List".to_string(),
|
&Val::List(_) => "List".to_string(),
|
||||||
&Val::Tuple(_) => "Tuple".to_string(),
|
&Val::Tuple(_) => "Tuple".to_string(),
|
||||||
&Val::Macro(_) => "Macro".to_string(),
|
&Val::Macro(_) => "Macro".to_string(),
|
||||||
|
&Val::Module(_) => "Module".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +51,8 @@ impl Val {
|
|||||||
&Val::Str(_),
|
&Val::Str(_),
|
||||||
&Val::List(_),
|
&Val::List(_),
|
||||||
&Val::Tuple(_),
|
&Val::Tuple(_),
|
||||||
&Val::Macro(_)
|
&Val::Macro(_),
|
||||||
|
&Val::Module(_)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,6 +108,11 @@ impl Val {
|
|||||||
error::ErrorType::TypeFail,
|
error::ErrorType::TypeFail,
|
||||||
pos,
|
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(
|
(me, tgt) => Err(error::BuildError::new(
|
||||||
format!("Types differ for {}, {} in file: {}", me, tgt, file_name),
|
format!("Types differ for {}, {} in file: {}", me, tgt, file_name),
|
||||||
error::ErrorType::TypeFail,
|
error::ErrorType::TypeFail,
|
||||||
@ -188,6 +196,7 @@ impl Display for Val {
|
|||||||
write!(f, "]")
|
write!(f, "]")
|
||||||
}
|
}
|
||||||
&Val::Macro(_) => write!(f, "Macro(..)"),
|
&Val::Macro(_) => write!(f, "Macro(..)"),
|
||||||
|
&Val::Module(_) => write!(f, "Module{{..}}"),
|
||||||
&Val::Tuple(ref def) => {
|
&Val::Tuple(ref def) => {
|
||||||
try!(write!(f, "Tuple(\n"));
|
try!(write!(f, "Tuple(\n"));
|
||||||
for v in def.iter() {
|
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
|
/// 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
|
/// so multiple imports of the same file don't have to be parsed
|
||||||
/// multiple times.
|
/// 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>>,
|
assets: Rc<RefCell<assets::Cache>>,
|
||||||
/// build_output is our built output.
|
/// build_output is our built output.
|
||||||
build_output: ValueMap,
|
build_output: ValueMap,
|
||||||
/// last is the result of the last statement.
|
/// last is the result of the last statement.
|
||||||
pub stack: Option<Vec<Rc<Val>>>,
|
pub stack: Option<Vec<Rc<Val>>>,
|
||||||
|
pub is_module: bool,
|
||||||
pub last: Option<Rc<Val>>,
|
pub last: Option<Rc<Val>>,
|
||||||
pub out_lock: Option<(String, Rc<Val>)>,
|
pub out_lock: Option<(String, Rc<Val>)>,
|
||||||
}
|
}
|
||||||
@ -223,6 +226,7 @@ impl<'a> Builder<'a> {
|
|||||||
build_output: scope,
|
build_output: scope,
|
||||||
out_lock: None,
|
out_lock: None,
|
||||||
stack: None,
|
stack: None,
|
||||||
|
is_module: false,
|
||||||
last: None,
|
last: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -301,20 +305,26 @@ impl<'a> Builder<'a> {
|
|||||||
|
|
||||||
fn build_import(&mut self, def: &ImportDef) -> Result<Rc<Val>, Box<Error>> {
|
fn build_import(&mut self, def: &ImportDef) -> Result<Rc<Val>, Box<Error>> {
|
||||||
let sym = &def.name;
|
let sym = &def.name;
|
||||||
let mut normalized = self.root.to_path_buf();
|
let mut normalized = self.root.clone();
|
||||||
normalized.push(&def.path.fragment);
|
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());
|
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.
|
// Only parse the file once on import.
|
||||||
let mut shared_assets = self.assets.borrow_mut();
|
let maybe_asset = try!(self.assets.borrow().get(&normalized));
|
||||||
let result = match try!(shared_assets.get(&normalized)) {
|
let result = match maybe_asset {
|
||||||
Some(v) => v.clone(),
|
Some(v) => v.clone(),
|
||||||
None => {
|
None => {
|
||||||
let mut b = Self::new(normalized.clone(), self.assets.clone());
|
let mut b = Self::new(normalized.clone(), self.assets.clone());
|
||||||
let filepath = normalized.to_str().unwrap().clone();
|
let filepath = normalized.to_str().unwrap().clone();
|
||||||
try!(b.build_file(filepath));
|
try!(b.build_file(filepath));
|
||||||
let fields: Vec<(PositionedItem<String>, Rc<Val>)> =
|
b.get_outputs_as_val()
|
||||||
b.build_output.drain().collect();
|
|
||||||
Rc::new(Val::Tuple(fields))
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let key = sym.into();
|
let key = sym.into();
|
||||||
@ -326,7 +336,8 @@ impl<'a> Builder<'a> {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
self.build_output.insert(key, result.clone());
|
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);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,7 +394,7 @@ impl<'a> Builder<'a> {
|
|||||||
return Some(self.env.clone());
|
return Some(self.env.clone());
|
||||||
}
|
}
|
||||||
if &sym.val == "self" {
|
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();
|
return self.peek_val();
|
||||||
}
|
}
|
||||||
if self.build_output.contains_key(sym) {
|
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>> {
|
fn eval_copy(&mut self, def: &CopyDef) -> Result<Rc<Val>, Box<Error>> {
|
||||||
let v = try!(self.lookup_selector(&def.selector.sel));
|
let v = try!(self.lookup_selector(&def.selector.sel));
|
||||||
if let &Val::Tuple(ref src_fields) = v.as_ref() {
|
if let &Val::Tuple(ref src_fields) = v.as_ref() {
|
||||||
self.push_val(v.clone());
|
self.push_val(v.clone());
|
||||||
let mut m = HashMap::<PositionedItem<String>, (i32, Rc<Val>)>::new();
|
return self.copy_from_base(&src_fields, &def.fields);
|
||||||
// loop through fields and build up a hashmap
|
}
|
||||||
let mut count = 0;
|
if let &Val::Module(ref mod_def) = v.as_ref() {
|
||||||
for &(ref key, ref val) in src_fields.iter() {
|
let maybe_tpl = mod_def.clone().arg_tuple.unwrap().clone();
|
||||||
if let Entry::Vacant(v) = m.entry(key.clone()) {
|
if let &Val::Tuple(ref src_fields) = maybe_tpl.as_ref() {
|
||||||
v.insert((count, val.clone()));
|
// 1. First we create a builder.
|
||||||
count += 1;
|
let mut b = Self::new(self.root.clone(), self.assets.clone());
|
||||||
} else {
|
b.is_module = true;
|
||||||
self.pop_val();
|
// 2. We construct an argument tuple by copying from the defs
|
||||||
return Err(Box::new(error::BuildError::new(
|
// argset.
|
||||||
format!(
|
// Push our base tuple on the stack so the copy can use
|
||||||
"Duplicate \
|
// self to reference it.
|
||||||
field: {} in \
|
b.push_val(maybe_tpl.clone());
|
||||||
tuple",
|
let mod_args = try!(self.copy_from_base(src_fields, &def.fields));
|
||||||
key.val
|
// put our copied parameters tuple in our builder under the mod key.
|
||||||
),
|
let mod_key =
|
||||||
error::ErrorType::TypeFail,
|
PositionedItem::new_with_pos(String::from("mod"), Position::new(0, 0, 0));
|
||||||
key.pos.clone(),
|
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(
|
Err(Box::new(error::BuildError::new(
|
||||||
format!("Expected Tuple got {}", v),
|
format!("Expected Tuple or Module got {}", v),
|
||||||
error::ErrorType::TypeFail,
|
error::ErrorType::TypeFail,
|
||||||
def.selector.pos.clone(),
|
def.selector.pos.clone(),
|
||||||
)))
|
)))
|
||||||
@ -917,6 +988,7 @@ impl<'a> Builder<'a> {
|
|||||||
Ok(Rc::new(Val::Str(try!(formatter.render(&def.pos)))))
|
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>> {
|
fn eval_call(&mut self, def: &CallDef) -> Result<Rc<Val>, Box<Error>> {
|
||||||
let sel = &def.macroref;
|
let sel = &def.macroref;
|
||||||
let args = &def.arglist;
|
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>> {
|
fn eval_select(&mut self, def: &SelectDef) -> Result<Rc<Val>, Box<Error>> {
|
||||||
let target = &def.val;
|
let target = &def.val;
|
||||||
let def_expr = &def.default;
|
let def_expr = &def.default;
|
||||||
@ -1105,6 +1195,7 @@ impl<'a> Builder<'a> {
|
|||||||
&Expression::Format(ref def) => self.eval_format(def),
|
&Expression::Format(ref def) => self.eval_format(def),
|
||||||
&Expression::Call(ref def) => self.eval_call(def),
|
&Expression::Call(ref def) => self.eval_call(def),
|
||||||
&Expression::Macro(ref def) => self.eval_macro_def(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::Select(ref def) => self.eval_select(def),
|
||||||
&Expression::ListOp(ref def) => self.eval_list_op(def),
|
&Expression::ListOp(ref def) => self.eval_list_op(def),
|
||||||
}
|
}
|
||||||
|
@ -161,7 +161,7 @@ fn test_expr_copy_no_such_tuple() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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() {
|
fn test_expr_copy_not_a_tuple() {
|
||||||
let cache = Rc::new(RefCell::new(MemoryCache::new()));
|
let cache = Rc::new(RefCell::new(MemoryCache::new()));
|
||||||
let mut b = Builder::new(std::env::current_dir().unwrap(), cache);
|
let mut b = Builder::new(std::env::current_dir().unwrap(), cache);
|
||||||
|
@ -82,6 +82,10 @@ impl EnvConverter {
|
|||||||
// This is ignored
|
// This is ignored
|
||||||
eprintln!("Skipping macro...");
|
eprintln!("Skipping macro...");
|
||||||
}
|
}
|
||||||
|
&Val::Module(ref _def) => {
|
||||||
|
// This is ignored
|
||||||
|
eprintln!("Skipping module...");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -98,6 +98,10 @@ impl FlagConverter {
|
|||||||
// This is ignored
|
// This is ignored
|
||||||
eprintln!("Skipping macro...");
|
eprintln!("Skipping macro...");
|
||||||
}
|
}
|
||||||
|
&Val::Module(ref _def) => {
|
||||||
|
// This is ignored
|
||||||
|
eprintln!("Skipping module...");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,10 @@ impl JsonConverter {
|
|||||||
eprintln!("Skipping macro encoding as null...");
|
eprintln!("Skipping macro encoding as null...");
|
||||||
serde_json::Value::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::List(ref l) => try!(self.convert_list(l)),
|
||||||
&Val::Tuple(ref t) => try!(self.convert_tuple(t)),
|
&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!");
|
let err = SimpleError::new("Macros are not allowed in Toml Conversions!");
|
||||||
return Err(Box::new(err));
|
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::List(ref l) => try!(self.convert_list(l)),
|
||||||
&Val::Tuple(ref t) => try!(self.convert_tuple(t)),
|
&Val::Tuple(ref t) => try!(self.convert_tuple(t)),
|
||||||
};
|
};
|
||||||
|
@ -54,6 +54,10 @@ impl YamlConverter {
|
|||||||
eprintln!("Skipping macro encoding as null...");
|
eprintln!("Skipping macro encoding as null...");
|
||||||
serde_yaml::Value::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::List(ref l) => try!(self.convert_list(l)),
|
||||||
&Val::Tuple(ref t) => try!(self.convert_tuple(t)),
|
&Val::Tuple(ref t) => try!(self.convert_tuple(t)),
|
||||||
};
|
};
|
||||||
|
@ -76,7 +76,10 @@ fn build_file(
|
|||||||
validate: bool,
|
validate: bool,
|
||||||
cache: Rc<RefCell<Cache>>,
|
cache: Rc<RefCell<Cache>>,
|
||||||
) -> Result<build::Builder, Box<Error>> {
|
) -> 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);
|
let mut builder = build::Builder::new(root.parent().unwrap(), cache);
|
||||||
if validate {
|
if validate {
|
||||||
builder.enable_validate_mode();
|
builder.enable_validate_mode();
|
||||||
|
@ -557,6 +557,37 @@ make_fn!(
|
|||||||
separated!(punct!(","), symbol)
|
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> {
|
fn macro_expression(input: SliceIter<Token>) -> Result<SliceIter<Token>, Expression> {
|
||||||
let parsed = do_each!(input,
|
let parsed = do_each!(input,
|
||||||
pos => pos,
|
pos => pos,
|
||||||
@ -691,11 +722,11 @@ make_fn!(
|
|||||||
|
|
||||||
fn call_expression(input: SliceIter<Token>) -> Result<SliceIter<Token>, Expression> {
|
fn call_expression(input: SliceIter<Token>) -> Result<SliceIter<Token>, Expression> {
|
||||||
let parsed = do_each!(input.clone(),
|
let parsed = do_each!(input.clone(),
|
||||||
macroname => trace_nom!(selector_value),
|
callee_name => trace_nom!(selector_value),
|
||||||
_ => punct!("("),
|
_ => punct!("("),
|
||||||
args => optional!(separated!(punct!(","), trace_nom!(expression))),
|
args => optional!(separated!(punct!(","), trace_nom!(expression))),
|
||||||
_ => punct!(")"),
|
_ => punct!(")"),
|
||||||
(macroname, args)
|
(callee_name, args)
|
||||||
);
|
);
|
||||||
match parsed {
|
match parsed {
|
||||||
Result::Abort(e) => Result::Abort(e),
|
Result::Abort(e) => Result::Abort(e),
|
||||||
@ -806,6 +837,7 @@ make_fn!(
|
|||||||
alt_peek!(
|
alt_peek!(
|
||||||
either!(word!("map"), word!("filter")) => trace_nom!(list_op_expression) |
|
either!(word!("map"), word!("filter")) => trace_nom!(list_op_expression) |
|
||||||
word!("macro") => trace_nom!(macro_expression) |
|
word!("macro") => trace_nom!(macro_expression) |
|
||||||
|
word!("module") => trace_nom!(module_expression) |
|
||||||
word!("select") => trace_nom!(select_expression) |
|
word!("select") => trace_nom!(select_expression) |
|
||||||
punct!("(") => trace_nom!(grouped_expression) |
|
punct!("(") => trace_nom!(grouped_expression) |
|
||||||
trace_nom!(unprefixed_expression))
|
trace_nom!(unprefixed_expression))
|
||||||
@ -846,7 +878,7 @@ make_fn!(
|
|||||||
name => wrap_err!(match_type!(BAREWORD), "Expected name for binding"),
|
name => wrap_err!(match_type!(BAREWORD), "Expected name for binding"),
|
||||||
_ => punct!("="),
|
_ => punct!("="),
|
||||||
// TODO(jwall): Wrap this error with an appropriate abortable_parser::Error
|
// 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!(";"),
|
_ => punct!(";"),
|
||||||
(tuple_to_let(name, val))
|
(tuple_to_let(name, val))
|
||||||
)
|
)
|
||||||
@ -904,7 +936,7 @@ make_fn!(
|
|||||||
do_each!(
|
do_each!(
|
||||||
_ => word!("out"),
|
_ => word!("out"),
|
||||||
typ => wrap_err!(must!(match_type!(BAREWORD)), "Expected converter name"),
|
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!(";")),
|
_ => must!(punct!(";")),
|
||||||
(Statement::Output(typ.clone(), expr.clone()))
|
(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]
|
#[test]
|
||||||
fn test_macro_expression_parsing() {
|
fn test_macro_expression_parsing() {
|
||||||
assert_fail!(macro_expression("foo"));
|
assert_fail!(macro_expression("foo"));
|
||||||
|
@ -268,6 +268,10 @@ make_fn!(macrotok<OffsetStrIter, Token>,
|
|||||||
do_text_token_tok!(TokenType::BAREWORD, "macro", WS)
|
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>,
|
make_fn!(lettok<OffsetStrIter, Token>,
|
||||||
do_text_token_tok!(TokenType::BAREWORD, "let", WS)
|
do_text_token_tok!(TokenType::BAREWORD, "let", WS)
|
||||||
);
|
);
|
||||||
@ -386,6 +390,7 @@ fn token<'a>(input: OffsetStrIter<'a>) -> Result<OffsetStrIter<'a>, Token> {
|
|||||||
selecttok,
|
selecttok,
|
||||||
asserttok,
|
asserttok,
|
||||||
macrotok,
|
macrotok,
|
||||||
|
moduletok,
|
||||||
importtok,
|
importtok,
|
||||||
astok,
|
astok,
|
||||||
maptok,
|
maptok,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user