cleanup: fix some selector vs symbol parsing issues

Add test coverage of selector parsing.
This commit is contained in:
Jeremy Wall 2017-11-15 22:41:55 -06:00
parent 3ab5d27fc7
commit e975dea201
6 changed files with 204 additions and 39 deletions

View File

@ -2,13 +2,13 @@ let dbhost = "localhost";
let dbname = "testdb";
let mk_db_conn = macro (host, port, db) => {
conn_string = "@:@/@" % (host, port, db)
host = host,
port = port,
db = db,
conn_string = "@:@/@" % (host, port, db)
};
let db_conn = mk_db_conn(host, 3306, dbame);
let db_conn = mk_db_conn(dbhost, 3306, dbname);
let server_config = {
dbconn = db_conn.conn_string,

View File

@ -241,24 +241,29 @@ impl MacroDef {
-> HashSet<String> {
let mut bad_symbols = HashSet::new();
if let &Value::Symbol(ref name) = val {
let mut ok = true;
let mut ok = false;
for arg in self.argdefs.iter() {
ok &= arg.val == name.val
if arg.val == name.val {
ok = true;
}
}
if !ok {
bad_symbols.insert(name.val.clone());
}
} else if let &Value::Selector(ref sel_node) = val {
let list = &sel_node.val;
let mut ok = true;
let mut ok = false;
if list.len() > 0 {
// We only look to see if the first selector item exists.
// This is because only the first one is a symbol all of the
// rest of the items in the selector are fields in a tuple.
// But we don't know at this time of the value passed into
// this macro is a tuple since this isn't a callsite.
println!("checking selector head {}", list[0].fragment);
for arg in self.argdefs.iter() {
ok &= arg.val == list[0].fragment
if arg.val == list[0].fragment {
ok = true;
}
}
if !ok {
bad_symbols.insert(list[0].fragment.to_string());

View File

@ -231,7 +231,7 @@ pub struct Builder {
/// out is our built output.
out: ValueMap,
/// last is the result of the last statement.
last: Option<Rc<Val>>,
pub last: Option<Rc<Val>>,
}
macro_rules! eval_binary_expr {
@ -294,6 +294,17 @@ impl Builder {
}
}
pub fn get_out_by_name(&self, name: &str) -> Option<Rc<Val>> {
let key = Positioned {
pos: Position {
line: 0,
column: 0,
},
val: name.to_string(),
};
self.lookup_sym(&key)
}
pub fn build(&mut self, ast: &Vec<Statement>) -> BuildResult {
for stmt in ast.iter() {
try!(self.build_stmt(stmt));
@ -303,7 +314,7 @@ impl Builder {
pub fn build_file_string(&mut self, name: &str, input: String) -> BuildResult {
match parse(Span::new(&input)) {
nom::IResult::Done(_, stmts) => {
nom::IResult::Done(_span, stmts) => {
for stmt in stmts.iter() {
try!(self.build_stmt(stmt));
}
@ -324,6 +335,7 @@ impl Builder {
// TODO(jwall): It would be nice to be able to do this with streaming
try!(f.read_to_string(&mut s));
self.build_file_string(name, s)
// TODO(jwall): Call an output converter.
}
fn build_stmt(&mut self, stmt: &Statement) -> BuildResult {
@ -923,8 +935,16 @@ mod test {
}))
.or_insert(Rc::new(Val::Int(2)));
b.out
.entry(Positioned::new("var3".to_string(), Position{line: 1, column: 0}))
.or_insert(Rc::new(Val::Tuple(vec![(Positioned::new("lvl1".to_string(), Position{line: 1, column: 0}),
.entry(Positioned::new("var3".to_string(),
Position {
line: 1,
column: 0,
}))
.or_insert(Rc::new(Val::Tuple(vec![(Positioned::new("lvl1".to_string(),
Position {
line: 1,
column: 0,
}),
Rc::new(Val::Int(4)))])));
test_expr_to_val(vec![
(Expression::Simple(Value::Selector(make_value_node(vec![Token::new("var1", Position{line: 1, column: 1})], 1, 1))), Val::Tuple(

View File

@ -24,6 +24,7 @@ pub mod ast;
pub mod tokenizer;
pub mod parse;
pub mod build;
pub mod convert;
mod format;
pub use ast::Value;

View File

@ -13,11 +13,19 @@
// limitations under the License.
#[macro_use]
extern crate clap;
extern crate ucglib;
use ucglib::build;
use std::fs::File;
use std::rc::Rc;
use std::io;
use std::process;
use ucglib::build::Val;
use ucglib::build;
//use ucglib::convert::get_converter;
use ucglib::convert::ConverterRunner;
// TODO(jwall): List the target output types automatically.
fn do_flags<'a>() -> clap::ArgMatches<'a> {
clap_app!(
ucg =>
@ -26,6 +34,9 @@ fn do_flags<'a>() -> clap::ArgMatches<'a> {
(about: "Universal Configuration Grammar compiler.")
(@subcommand build =>
(about: "Compile a specific ucg file.")
(@arg sym: --sym +takes_value "Specify a specific let binding in the ucg file to output.")
(@arg target: --target -t +required +takes_value "Target output type.")
(@arg out: --out -o +takes_value "Output file to write to.")
(@arg INPUT: +required "Input ucg file to build.")
)
(@subcommand validate =>
@ -36,18 +47,53 @@ fn do_flags<'a>() -> clap::ArgMatches<'a> {
.get_matches()
}
fn run_converter(c: ConverterRunner, v: Rc<Val>, f: &str) -> io::Result<()> {
let file = File::create(f);
c.convert(v, Box::new(file.unwrap()))
}
fn main() {
// TODO(jwall): Read and build an actual file.
let app = do_flags();
if let Some(matches) = app.subcommand_matches("build") {
let file = matches.value_of("INPUT").unwrap();
let out = matches.value_of("out").unwrap();
let sym = matches.value_of("sym");
let target = matches.value_of("target").unwrap();
let mut builder = build::Builder::new();
builder.build_file(file).unwrap();
println!("Build successful");
match ConverterRunner::new(target) {
Ok(converter) => {
let result = builder.build_file(file);
if !result.is_ok() {
eprintln!("{:?}", result.err());
process::exit(1);
}
let val = match sym {
Some(sym_name) => builder.get_out_by_name(sym_name),
None => builder.last,
};
match val {
Some(value) => {
run_converter(converter, value, out).unwrap();
println!("Build successful");
process::exit(0);
}
None => {
eprintln!("Build results in no value.");
process::exit(1);
}
}
}
Err(msg) => {
eprintln!("{}", msg);
process::exit(1);
}
}
} else if let Some(matches) = app.subcommand_matches("validate") {
let file = matches.value_of("INPUT").unwrap();
let mut builder = build::Builder::new();
builder.build_file(file).unwrap();
println!("File Validates");
process::exit(0);
}
}

View File

@ -15,6 +15,9 @@ use std::str::FromStr;
use std::error::Error;
use std::borrow::Borrow;
use nom::IResult;
use nom::InputLength;
use ast::*;
use tokenizer::*;
@ -163,7 +166,27 @@ named!(
)
);
named!(value( Span ) -> Value, alt!(number | quoted_value | symbol | tuple));
pub fn selector_or_symbol(input: Span) -> IResult<Span, Value> {
let sym = do_parse!(input,
sym: symbol >>
not!(dottok) >>
(sym)
);
match sym {
IResult::Incomplete(i) => {
return IResult::Incomplete(i);
},
IResult::Error(_) => {
// TODO(jwall): Maybe be smarter about the error reporting here?
return ws!(input, selector_value);
},
IResult::Done(rest, val) => {
return IResult::Done(rest, val);
}
}
}
named!(value( Span ) -> Value, alt!(number | quoted_value | tuple | selector_or_symbol ));
fn value_to_expression(v: Value) -> ParseResult<Expression> {
Ok(Expression::Simple(v))
@ -504,19 +527,90 @@ named!(statement( Span ) -> Statement,
)
);
named!(pub parse( Span ) -> Vec<Statement>, many1!(ws!(statement)));
pub fn parse(input: Span) -> IResult<Span, Vec<Statement>> {
let mut out = Vec::new();
let mut i = input;
loop {
match ws!(i, statement) {
IResult::Error(e) => {
return IResult::Error(e);
},
IResult::Incomplete(i) => {
return IResult::Incomplete(i);
},
IResult::Done(rest, stmt) => {
out.push(stmt);
i = rest;
if i.input_len() == 0 {
break;
}
}
}
}
return IResult::Done(i, out);
}
//named!(pub parse( Span ) -> Vec<Statement>, many1!());
#[cfg(test)]
mod test {
use super::{Statement, Expression, Value, MacroDef, SelectDef, CallDef};
use super::{number, symbol, parse, field_value, tuple, grouped_expression};
use super::{number, symbol, parse, field_value, selector_value, selector_or_symbol, tuple, grouped_expression};
use super::{copy_expression, macro_expression, select_expression};
use super::{format_expression, call_expression, expression};
use super::{expression_statement, let_statement, import_statement, statement};
use ast::*;
use nom_locate::LocatedSpan;
use nom::IResult;
use nom::{Needed, IResult};
#[test]
fn test_symbol_parsing() {
assert_eq!(symbol(LocatedSpan::new("foo")),
IResult::Done(LocatedSpan{fragment: "", offset: 3, line: 1},
Value::Symbol(value_node!("foo".to_string(), Position{line: 1, column: 1}))) );
assert_eq!(symbol(LocatedSpan::new("foo-bar")),
IResult::Done(LocatedSpan{fragment: "", offset: 7, line: 1},
Value::Symbol(value_node!("foo-bar".to_string(), Position{line: 1, column: 1}))) );
assert_eq!(symbol(LocatedSpan::new("foo_bar")),
IResult::Done(LocatedSpan{fragment: "", offset: 7, line: 1},
Value::Symbol(value_node!("foo_bar".to_string(), Position{line: 1, column: 1}))) );
}
#[test]
fn test_selector_parsing() {
assert_eq!(selector_value(LocatedSpan::new("foo.")),
IResult::Incomplete(Needed::Unknown)
);
assert_eq!(selector_value(LocatedSpan::new("foo.bar ")),
IResult::Done(LocatedSpan{fragment: "", offset: 8, line: 1},
Value::Selector(value_node!(vec![Token{fragment:"foo".to_string(), pos: Position{line: 1, column: 1}},
Token{fragment:"bar".to_string(), pos: Position{line: 1, column: 5}}],
Position{line: 1, column: 0})))
);
assert_eq!(selector_value(LocatedSpan::new("foo.bar;")),
IResult::Done(LocatedSpan{fragment: ";", offset: 7, line: 1},
Value::Selector(value_node!(vec![Token{fragment:"foo".to_string(), pos: Position{line: 1, column: 1}},
Token{fragment:"bar".to_string(), pos: Position{line: 1, column: 5}}],
Position{line: 1, column: 0})))
);
}
#[test]
fn test_selector_or_symbol_parsing() {
assert_eq!(selector_or_symbol(LocatedSpan::new("foo.")),
IResult::Incomplete(Needed::Unknown)
);
assert_eq!(selector_or_symbol(LocatedSpan::new("foo")),
IResult::Done(LocatedSpan{fragment: "", offset: 3, line: 1},
Value::Symbol(value_node!("foo".to_string(), Position{line: 1, column: 1}))) );
assert_eq!(selector_or_symbol(LocatedSpan::new("foo.bar ")),
IResult::Done(LocatedSpan{fragment: "", offset: 8, line: 1},
Value::Selector(value_node!(vec![Token{fragment:"foo".to_string(), pos: Position{line: 1, column: 1}},
Token{fragment:"bar".to_string(), pos: Position{line: 1, column: 5}}],
Position{line: 1, column: 0})))
);
}
#[test]
fn test_statement_parse() {
@ -744,19 +838,27 @@ mod test {
#[test]
fn test_expression_parse() {
assert_eq!(expression(LocatedSpan::new("1")),
IResult::Done(LocatedSpan {
fragment: "",
offset: 1,
line: 1,
},
Expression::Simple(Value::Int(value_node!(1, Position{line: 1, column: 1})))));
IResult::Done(LocatedSpan {
fragment: "",
offset: 1,
line: 1,
},
Expression::Simple(Value::Int(value_node!(1, Position{line: 1, column: 1})))));
assert_eq!(expression(LocatedSpan::new("foo")),
IResult::Done(LocatedSpan {
fragment: "",
offset: 3,
line: 1,
},
Expression::Simple(Value::Symbol(value_node!("foo".to_string(), Position{line: 1, column: 1})))));
assert_eq!(expression(LocatedSpan::new("foo.bar ")),
IResult::Done(LocatedSpan {
fragment: "",
offset: 3,
offset: 8,
line: 1,
},
Expression::Simple(Value::Symbol(value_node!("foo".to_string(), Position{line: 1, column: 1})))));
Expression::Simple(Value::Selector(make_value_node(vec![Token::new("foo", Position{line: 1, column: 1}),
Token::new("bar", Position{line: 1, column: 5})], 1, 0)))));
assert_eq!(expression(LocatedSpan::new("1 + 1")),
IResult::Done(LocatedSpan {
fragment: "",
@ -1237,6 +1339,10 @@ mod test {
IResult::Done(LocatedSpan { offset: 10, line: 1, fragment: "" },
(Token::new("foo", Position{line: 1, column: 1}),
Expression::Simple(Value::Symbol(value_node!("bar".to_string(), Position{line: 1, column: 7}))))) );
assert_eq!(field_value(LocatedSpan::new("foo = bar.baz ")),
IResult::Done(LocatedSpan { offset: 14, line: 1, fragment: "" },
(Token::new("foo", Position{line: 1, column: 1}),
Expression::Simple(Value::Selector(make_value_node(vec![Token::new("bar", Position{line: 1, column: 7}), Token::new("baz", Position{line: 1, column: 11})], 1, 6))))));
}
#[test]
@ -1257,19 +1363,6 @@ mod test {
Value::Float(value_node!(0.1, Position{line: 1, column: 1}))) );
}
#[test]
fn test_symbol_parsing() {
assert_eq!(symbol(LocatedSpan::new("foo")),
IResult::Done(LocatedSpan{fragment: "", offset: 3, line: 1},
Value::Symbol(value_node!("foo".to_string(), Position{line: 1, column: 1}))) );
assert_eq!(symbol(LocatedSpan::new("foo-bar")),
IResult::Done(LocatedSpan{fragment: "", offset: 7, line: 1},
Value::Symbol(value_node!("foo-bar".to_string(), Position{line: 1, column: 1}))) );
assert_eq!(symbol(LocatedSpan::new("foo_bar")),
IResult::Done(LocatedSpan{fragment: "", offset: 7, line: 1},
Value::Symbol(value_node!("foo_bar".to_string(), Position{line: 1, column: 1}))) );
}
#[test]
fn test_parse() {
let bad_input = LocatedSpan::new("import mylib as lib;");