FEATURE: Parse out statements.

This commit is contained in:
Jeremy Wall 2018-08-13 20:37:58 -05:00
parent 30248e7e01
commit f3e769095d
8 changed files with 100 additions and 30 deletions

View File

@ -799,6 +799,9 @@ pub enum Statement {
// Assert statement // Assert statement
Assert(Token), Assert(Token),
// Identify an Expression for output.
Output(Token, Expression),
} }
#[cfg(test)] #[cfg(test)]

View File

@ -293,7 +293,7 @@ pub struct AssertCollector {
pub failures: String, pub failures: String,
} }
/// Builder handles building ucg code. /// Builder handles building ucg code for a single file..
pub struct Builder { pub struct Builder {
root: PathBuf, root: PathBuf,
validate_mode: bool, validate_mode: bool,
@ -306,10 +306,12 @@ pub struct Builder {
assets: ValueMap, assets: ValueMap,
// List of file paths we have already parsed. // List of file paths we have already parsed.
files: HashSet<String>, files: HashSet<String>,
/// out is our built output. /// build_output is our built output.
out: ValueMap, build_output: ValueMap,
/// last is the result of the last statement. /// last is the result of the last statement.
pub last: Option<Rc<Val>>, pub last: Option<Rc<Val>>,
// FIXME(jwall): This should be a per file mapping.
out_lock: Option<(String, Rc<Val>)>,
} }
macro_rules! eval_binary_expr { macro_rules! eval_binary_expr {
@ -330,7 +332,7 @@ macro_rules! eval_binary_expr {
} }
impl Builder { impl Builder {
// FIXME(jwall): This needs some unit tests. // TOOD(jwall): This needs some unit tests.
fn tuple_to_val(&self, fields: &Vec<(Token, Expression)>) -> Result<Rc<Val>, Box<Error>> { fn tuple_to_val(&self, fields: &Vec<(Token, Expression)>) -> Result<Rc<Val>, Box<Error>> {
let mut new_fields = Vec::<(Positioned<String>, Rc<Val>)>::new(); let mut new_fields = Vec::<(Positioned<String>, Rc<Val>)>::new();
for &(ref name, ref expr) in fields.iter() { for &(ref name, ref expr) in fields.iter() {
@ -403,7 +405,8 @@ impl Builder {
env: env, env: env,
assets: HashMap::new(), assets: HashMap::new(),
files: HashSet::new(), files: HashSet::new(),
out: scope, build_output: scope,
out_lock: None,
last: None, last: None,
} }
} }
@ -473,9 +476,10 @@ impl Builder {
if !self.files.contains(&key) { if !self.files.contains(&key) {
// Only parse the file once on import. // Only parse the file once on import.
if self.assets.get(&positioned_sym).is_none() { if self.assets.get(&positioned_sym).is_none() {
// FIXME(jwall): We should be sharing our assets collection.
let mut b = Self::new(normalized); let mut b = Self::new(normalized);
try!(b.build_file(&def.path.fragment)); try!(b.build_file(&def.path.fragment));
let fields: Vec<(Positioned<String>, Rc<Val>)> = b.out.drain().collect(); let fields: Vec<(Positioned<String>, Rc<Val>)> = b.build_output.drain().collect();
let result = Rc::new(Val::Tuple(fields)); let result = Rc::new(Val::Tuple(fields));
self.assets.entry(positioned_sym).or_insert(result.clone()); self.assets.entry(positioned_sym).or_insert(result.clone());
self.files.insert(def.path.fragment.clone()); self.files.insert(def.path.fragment.clone());
@ -504,7 +508,7 @@ impl Builder {
fn build_let(&mut self, def: &LetDef) -> Result<Rc<Val>, Box<Error>> { fn build_let(&mut self, def: &LetDef) -> Result<Rc<Val>, Box<Error>> {
let val = try!(self.eval_expr(&def.value)); let val = try!(self.eval_expr(&def.value));
let name = &def.name; let name = &def.name;
match self.out.entry(name.into()) { match self.build_output.entry(name.into()) {
Entry::Occupied(e) => { Entry::Occupied(e) => {
return Err(Box::new(error::Error::new( return Err(Box::new(error::Error::new(
format!( format!(
@ -531,6 +535,21 @@ impl Builder {
&Statement::Let(ref def) => self.build_let(def), &Statement::Let(ref def) => self.build_let(def),
&Statement::Import(ref def) => self.build_import(def), &Statement::Import(ref def) => self.build_import(def),
&Statement::Expression(ref expr) => self.eval_expr(expr), &Statement::Expression(ref expr) => self.eval_expr(expr),
// FIXME(jwall): Stash this into an output slot.
// Only one output can be used per file.
&Statement::Output(ref typ, ref expr) => {
if let None = self.out_lock {
let val = try!(self.eval_expr(expr));
self.out_lock = Some((typ.fragment.to_string(), val.clone()));
Ok(val)
} else {
Err(Box::new(error::Error::new(
format!("You can only have one output per file."),
error::ErrorType::DuplicateBinding,
typ.pos.clone(),
)))
}
}
} }
} }
@ -538,8 +557,8 @@ impl Builder {
if &sym.val == "env" { if &sym.val == "env" {
return Some(self.env.clone()); return Some(self.env.clone());
} }
if self.out.contains_key(sym) { if self.build_output.contains_key(sym) {
return Some(self.out[sym].clone()); return Some(self.build_output[sym].clone());
} }
if self.assets.contains_key(sym) { if self.assets.contains_key(sym) {
return Some(self.assets[sym].clone()); return Some(self.assets[sym].clone());
@ -1138,7 +1157,6 @@ impl Builder {
// we are not in validate_mode then build_asserts are noops. // we are not in validate_mode then build_asserts are noops.
return Ok(Rc::new(Val::Empty)); return Ok(Rc::new(Val::Empty));
} }
// FIXME(jwall): We need to append a semicolon to the expr.
let mut expr_as_stmt = String::new(); let mut expr_as_stmt = String::new();
let expr = &tok.fragment; let expr = &tok.fragment;
expr_as_stmt.push_str(expr); expr_as_stmt.push_str(expr);

View File

@ -369,7 +369,7 @@ fn test_eval_simple_expr() {
#[test] #[test]
fn test_eval_simple_lookup_expr() { fn test_eval_simple_lookup_expr() {
let mut b = Builder::new(std::env::current_dir().unwrap()); let mut b = Builder::new(std::env::current_dir().unwrap());
b.out b.build_output
.entry(value_node!("var1".to_string(), 1, 0)) .entry(value_node!("var1".to_string(), 1, 0))
.or_insert(Rc::new(Val::Int(1))); .or_insert(Rc::new(Val::Int(1)));
test_expr_to_val( test_expr_to_val(
@ -384,7 +384,7 @@ fn test_eval_simple_lookup_expr() {
#[test] #[test]
fn test_eval_simple_lookup_error() { fn test_eval_simple_lookup_error() {
let mut b = Builder::new(std::env::current_dir().unwrap()); let mut b = Builder::new(std::env::current_dir().unwrap());
b.out b.build_output
.entry(value_node!("var1".to_string(), 1, 0)) .entry(value_node!("var1".to_string(), 1, 0))
.or_insert(Rc::new(Val::Int(1))); .or_insert(Rc::new(Val::Int(1)));
let expr = Expression::Simple(Value::Symbol(value_node!("var".to_string(), 1, 1))); let expr = Expression::Simple(Value::Symbol(value_node!("var".to_string(), 1, 1)));
@ -394,7 +394,7 @@ fn test_eval_simple_lookup_error() {
#[test] #[test]
fn test_eval_selector_expr() { fn test_eval_selector_expr() {
let mut b = Builder::new(std::env::current_dir().unwrap()); let mut b = Builder::new(std::env::current_dir().unwrap());
b.out b.build_output
.entry(value_node!("var1".to_string(), 1, 0)) .entry(value_node!("var1".to_string(), 1, 0))
.or_insert(Rc::new(Val::Tuple(vec![( .or_insert(Rc::new(Val::Tuple(vec![(
value_node!("lvl1".to_string(), 1, 0), value_node!("lvl1".to_string(), 1, 0),
@ -403,10 +403,10 @@ fn test_eval_selector_expr() {
Rc::new(Val::Int(3)), Rc::new(Val::Int(3)),
)])), )])),
)]))); )])));
b.out b.build_output
.entry(value_node!("var2".to_string(), 1, 0)) .entry(value_node!("var2".to_string(), 1, 0))
.or_insert(Rc::new(Val::Int(2))); .or_insert(Rc::new(Val::Int(2)));
b.out b.build_output
.entry(value_node!("var3".to_string(), 1, 0)) .entry(value_node!("var3".to_string(), 1, 0))
.or_insert(Rc::new(Val::Tuple(vec![( .or_insert(Rc::new(Val::Tuple(vec![(
value_node!("lvl1".to_string(), 1, 0), value_node!("lvl1".to_string(), 1, 0),
@ -458,7 +458,7 @@ fn test_eval_selector_expr() {
#[test] #[test]
fn test_eval_selector_list_expr() { fn test_eval_selector_list_expr() {
let mut b = Builder::new(std::env::current_dir().unwrap()); let mut b = Builder::new(std::env::current_dir().unwrap());
b.out b.build_output
.entry(value_node!("var1".to_string(), 1, 1)) .entry(value_node!("var1".to_string(), 1, 1))
.or_insert(Rc::new(Val::List(vec![ .or_insert(Rc::new(Val::List(vec![
Rc::new(Val::Str("val1".to_string())), Rc::new(Val::Str("val1".to_string())),
@ -502,7 +502,7 @@ fn test_expr_copy_no_such_tuple() {
#[should_panic(expected = "Expected Tuple got Int(1)")] #[should_panic(expected = "Expected Tuple got Int(1)")]
fn test_expr_copy_not_a_tuple() { fn test_expr_copy_not_a_tuple() {
let mut b = Builder::new(std::env::current_dir().unwrap()); let mut b = Builder::new(std::env::current_dir().unwrap());
b.out b.build_output
.entry(value_node!("tpl1".to_string(), 1, 0)) .entry(value_node!("tpl1".to_string(), 1, 0))
.or_insert(Rc::new(Val::Int(1))); .or_insert(Rc::new(Val::Int(1)));
test_expr_to_val( test_expr_to_val(
@ -522,7 +522,7 @@ fn test_expr_copy_not_a_tuple() {
#[should_panic(expected = "Expected type Integer for field fld1 but got String")] #[should_panic(expected = "Expected type Integer for field fld1 but got String")]
fn test_expr_copy_field_type_error() { fn test_expr_copy_field_type_error() {
let mut b = Builder::new(std::env::current_dir().unwrap()); let mut b = Builder::new(std::env::current_dir().unwrap());
b.out b.build_output
.entry(value_node!("tpl1".to_string(), 1, 0)) .entry(value_node!("tpl1".to_string(), 1, 0))
.or_insert(Rc::new(Val::Tuple(vec![( .or_insert(Rc::new(Val::Tuple(vec![(
value_node!("fld1".to_string(), 1, 0), value_node!("fld1".to_string(), 1, 0),
@ -550,7 +550,7 @@ fn test_expr_copy_field_type_error() {
#[test] #[test]
fn test_expr_copy() { fn test_expr_copy() {
let mut b = Builder::new(std::env::current_dir().unwrap()); let mut b = Builder::new(std::env::current_dir().unwrap());
b.out b.build_output
.entry(value_node!("tpl1".to_string(), 1, 0)) .entry(value_node!("tpl1".to_string(), 1, 0))
.or_insert(Rc::new(Val::Tuple(vec![( .or_insert(Rc::new(Val::Tuple(vec![(
value_node!("fld1".to_string(), 1, 0), value_node!("fld1".to_string(), 1, 0),
@ -621,7 +621,7 @@ fn test_expr_copy() {
#[test] #[test]
fn test_macro_call() { fn test_macro_call() {
let mut b = Builder::new(std::env::current_dir().unwrap()); let mut b = Builder::new(std::env::current_dir().unwrap());
b.out b.build_output
.entry(value_node!("tstmac".to_string(), 1, 0)) .entry(value_node!("tstmac".to_string(), 1, 0))
.or_insert(Rc::new(Val::Macro(MacroDef { .or_insert(Rc::new(Val::Macro(MacroDef {
argdefs: vec![value_node!("arg1".to_string(), 1, 0)], argdefs: vec![value_node!("arg1".to_string(), 1, 0)],
@ -655,10 +655,10 @@ fn test_macro_call() {
#[should_panic(expected = "Unable to find arg1")] #[should_panic(expected = "Unable to find arg1")]
fn test_macro_hermetic() { fn test_macro_hermetic() {
let mut b = Builder::new(std::env::current_dir().unwrap()); let mut b = Builder::new(std::env::current_dir().unwrap());
b.out b.build_output
.entry(value_node!("arg1".to_string(), 1, 0)) .entry(value_node!("arg1".to_string(), 1, 0))
.or_insert(Rc::new(Val::Str("bar".to_string()))); .or_insert(Rc::new(Val::Str("bar".to_string())));
b.out b.build_output
.entry(value_node!("tstmac".to_string(), 1, 0)) .entry(value_node!("tstmac".to_string(), 1, 0))
.or_insert(Rc::new(Val::Macro(MacroDef { .or_insert(Rc::new(Val::Macro(MacroDef {
argdefs: vec![value_node!("arg2".to_string(), 1, 0)], argdefs: vec![value_node!("arg2".to_string(), 1, 0)],
@ -691,10 +691,10 @@ fn test_macro_hermetic() {
#[test] #[test]
fn test_select_expr() { fn test_select_expr() {
let mut b = Builder::new(std::env::current_dir().unwrap()); let mut b = Builder::new(std::env::current_dir().unwrap());
b.out b.build_output
.entry(value_node!("foo".to_string(), 1, 0)) .entry(value_node!("foo".to_string(), 1, 0))
.or_insert(Rc::new(Val::Str("bar".to_string()))); .or_insert(Rc::new(Val::Str("bar".to_string())));
b.out b.build_output
.entry(value_node!("baz".to_string(), 1, 0)) .entry(value_node!("baz".to_string(), 1, 0))
.or_insert(Rc::new(Val::Str("boo".to_string()))); .or_insert(Rc::new(Val::Str("boo".to_string())));
test_expr_to_val( test_expr_to_val(
@ -753,7 +753,7 @@ fn test_select_expr() {
#[should_panic(expected = "Expected String but got Integer in Select expression")] #[should_panic(expected = "Expected String but got Integer in Select expression")]
fn test_select_expr_not_a_string() { fn test_select_expr_not_a_string() {
let mut b = Builder::new(std::env::current_dir().unwrap()); let mut b = Builder::new(std::env::current_dir().unwrap());
b.out b.build_output
.entry(value_node!("foo".to_string(), 1, 0)) .entry(value_node!("foo".to_string(), 1, 0))
.or_insert(Rc::new(Val::Int(4))); .or_insert(Rc::new(Val::Int(4)));
test_expr_to_val( test_expr_to_val(
@ -805,7 +805,7 @@ fn test_build_file_string() {
let mut b = Builder::new(std::env::current_dir().unwrap()); let mut b = Builder::new(std::env::current_dir().unwrap());
b.eval_string("let foo = 1;").unwrap(); b.eval_string("let foo = 1;").unwrap();
let key = value_node!("foo".to_string(), 1, 0); let key = value_node!("foo".to_string(), 1, 0);
assert!(b.out.contains_key(&key)); assert!(b.build_output.contains_key(&key));
} }
#[test] #[test]

View File

@ -842,12 +842,29 @@ named!(assert_statement<TokenIter, Statement, error::Error>,
) )
); );
named!(out_statement<TokenIter, Statement, error::Error>,
do_parse!(
word!("out") >>
pos: pos >>
typ: match_type!(BAREWORD) >>
expr: add_return_error!(
nom::ErrorKind::Custom(
error::Error::new(
"Invalid syntax for assert",
error::ErrorType::ParseError, pos)),
expression) >>
punct!(";") >>
(Statement::Output(typ.clone(), expr.clone()))
)
);
//trace_macros!(true); //trace_macros!(true);
fn statement(i: TokenIter) -> nom::IResult<TokenIter, Statement, error::Error> { fn statement(i: TokenIter) -> nom::IResult<TokenIter, Statement, error::Error> {
return alt_peek!(i, return alt_peek!(i,
word!("assert") => trace_nom!(assert_statement) | word!("assert") => trace_nom!(assert_statement) |
word!("import") => trace_nom!(import_statement) | word!("import") => trace_nom!(import_statement) |
word!("let") => trace_nom!(let_statement) | word!("let") => trace_nom!(let_statement) |
word!("out") => trace_nom!(out_statement) |
trace_nom!(expression_statement) trace_nom!(expression_statement)
); );
} }

View File

@ -253,7 +253,6 @@ named!(compare_expression<OpListIter, Expression, error::Error>,
map_res!( map_res!(
do_parse!( do_parse!(
left: alt!(trace_nom!(math_expression) | trace_nom!(parse_expression)) >> left: alt!(trace_nom!(math_expression) | trace_nom!(parse_expression)) >>
// FIXME(jwall): Wrong type of combinator
typ: parse_compare_operator >> typ: parse_compare_operator >>
right: alt!(trace_nom!(math_expression) | trace_nom!(parse_expression)) >> right: alt!(trace_nom!(math_expression) | trace_nom!(parse_expression)) >>
(typ, left, right) (typ, left, right)

View File

@ -291,6 +291,24 @@ fn test_let_statement_parse() {
); );
} }
#[test]
fn test_out_statement_parse() {
assert_error!(out_statement("out"));
assert_error!(out_statement("out json"));
assert_error!(out_statement("out json foo"));
assert_parse!(
out_statement("out json 1.0;"),
Statement::Output(
Token {
pos: Position { line: 1, column: 5 },
fragment: "json".to_string(),
typ: TokenType::BAREWORD
},
Expression::Simple(Value::Float(value_node!(1.0, 1, 10)))
)
);
}
#[test] #[test]
fn test_expression_statement_parse() { fn test_expression_statement_parse() {
assert_error!(expression_statement("foo")); assert_error!(expression_statement("foo"));

View File

@ -253,6 +253,10 @@ named!(asserttok( Span ) -> Token,
do_tag_tok!(TokenType::BAREWORD, "assert", WS) do_tag_tok!(TokenType::BAREWORD, "assert", WS)
); );
named!(outtok( Span ) -> Token,
do_tag_tok!(TokenType::BAREWORD, "out", WS)
);
named!(astok( Span ) -> Token, named!(astok( Span ) -> Token,
do_tag_tok!(TokenType::BAREWORD, "as", WS) do_tag_tok!(TokenType::BAREWORD, "as", WS)
); );
@ -359,6 +363,7 @@ named!(token( Span ) -> Token,
rightsquarebracket | rightsquarebracket |
booleantok | booleantok |
lettok | lettok |
outtok |
selecttok | selecttok |
asserttok | asserttok |
macrotok | macrotok |

View File

@ -22,6 +22,16 @@ fn test_assert_token() {
} }
} }
#[test]
fn test_out_token() {
let result = outtok(LocatedSpan::new("out "));
assert!(result.is_done(), format!("result {:?} is not done", result));
if let nom::IResult::Done(_, tok) = result {
assert_eq!(tok.fragment, "out");
assert_eq!(tok.typ, TokenType::BAREWORD);
}
}
#[test] #[test]
fn test_escape_quoted() { fn test_escape_quoted() {
let result = escapequoted(LocatedSpan::new("foo \\\"bar\"")); let result = escapequoted(LocatedSpan::new("foo \\\"bar\""));
@ -103,7 +113,7 @@ fn test_lteqtok() {
#[test] #[test]
fn test_tokenize_one_of_each() { fn test_tokenize_one_of_each() {
let result = tokenize(LocatedSpan::new( let result = tokenize(LocatedSpan::new(
"map filter assert let import macro select as => [ ] { } ; = % / * \ "map out filter assert let import macro select as => [ ] { } ; = % / * \
+ - . ( ) , 1 . foo \"bar\" // comment\n ; true false == < > <= >= !=", + - . ( ) , 1 . foo \"bar\" // comment\n ; true false == < > <= >= !=",
)); ));
assert!(result.is_ok(), format!("result {:?} is not ok", result)); assert!(result.is_ok(), format!("result {:?} is not ok", result));
@ -111,8 +121,8 @@ fn test_tokenize_one_of_each() {
for (i, t) in v.iter().enumerate() { for (i, t) in v.iter().enumerate() {
println!("{}: {:?}", i, t); println!("{}: {:?}", i, t);
} }
assert_eq!(v.len(), 38); assert_eq!(v.len(), 39);
assert_eq!(v[37].typ, TokenType::END); assert_eq!(v[38].typ, TokenType::END);
} }
#[test] #[test]