diff --git a/src/ast.rs b/src/ast.rs index bf240ba..fd706f0 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -87,6 +87,7 @@ impl Position { #[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)] pub enum TokenType { EMPTY, + BOOLEAN, END, WS, COMMENT, @@ -330,6 +331,7 @@ impl SelectorDef { pub enum Value { // Constant Values Empty(Position), + Boolean(Positioned), Int(Positioned), Float(Positioned), String(Positioned), @@ -345,6 +347,7 @@ impl Value { pub fn type_name(&self) -> String { match self { &Value::Empty(_) => "EmptyValue".to_string(), + &Value::Boolean(_) => "Boolean".to_string(), &Value::Int(_) => "Integer".to_string(), &Value::Float(_) => "Float".to_string(), &Value::String(_) => "String".to_string(), @@ -375,6 +378,7 @@ impl Value { pub fn to_string(&self) -> String { match self { &Value::Empty(_) => "EmptyValue".to_string(), + &Value::Boolean(ref b) => format!("{}", b.val), &Value::Int(ref i) => format!("{}", i.val), &Value::Float(ref f) => format!("{}", f.val), &Value::String(ref s) => format!("{}", s.val), @@ -389,6 +393,7 @@ impl Value { pub fn pos(&self) -> &Position { match self { &Value::Empty(ref pos) => pos, + &Value::Boolean(ref b) => &b.pos, &Value::Int(ref i) => &i.pos, &Value::Float(ref f) => &f.pos, &Value::String(ref s) => &s.pos, @@ -405,6 +410,7 @@ impl Value { self, target, &Value::Empty(_), + &Value::Boolean(_), &Value::Int(_), &Value::Float(_), &Value::String(_), diff --git a/src/build.rs b/src/build.rs index f11f19a..881c85c 100644 --- a/src/build.rs +++ b/src/build.rs @@ -73,6 +73,7 @@ type BuildResult = Result<(), Box>; #[derive(PartialEq, Debug, Clone)] pub enum Val { Empty, + Boolean(bool), Int(i64), Float(f64), String(String), @@ -86,6 +87,7 @@ impl Val { pub fn type_name(&self) -> String { match self { &Val::Empty => "EmptyValue".to_string(), + &Val::Boolean(_) => "Boolean".to_string(), &Val::Int(_) => "Integer".to_string(), &Val::Float(_) => "Float".to_string(), &Val::String(_) => "String".to_string(), @@ -101,6 +103,7 @@ impl Val { self, target, &Val::Empty, + &Val::Boolean(_), &Val::Int(_), &Val::Float(_), &Val::String(_), @@ -172,6 +175,7 @@ impl Val { impl Display for Val { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { + &Val::Boolean(b) => write!(f, "Boolean({})", b), &Val::Empty => write!(f, "EmptyValue"), &Val::Float(ref ff) => write!(f, "Float({})", ff), &Val::Int(ref i) => write!(f, "Int({})", i), @@ -267,6 +271,7 @@ impl Builder { fn value_to_val(&self, v: &Value) -> Result, Box> { match v { &Value::Empty(_) => Ok(Rc::new(Val::Empty)), + &Value::Boolean(ref b) => Ok(Rc::new(Val::Boolean(b.val))), &Value::Int(ref i) => Ok(Rc::new(Val::Int(i.val))), &Value::Float(ref f) => Ok(Rc::new(Val::Float(f.val))), &Value::String(ref s) => Ok(Rc::new(Val::String(s.val.to_string()))), diff --git a/src/convert/env.rs b/src/convert/env.rs index 9f61b46..d77f245 100644 --- a/src/convert/env.rs +++ b/src/convert/env.rs @@ -63,6 +63,9 @@ impl EnvConverter { // Empty is a noop. return Ok(()); } + &Val::Boolean(b) => { + try!(write!(w, "{} ", if b { "true" } else { "false" })); + } &Val::Float(ref f) => { try!(write!(w, "{} ", f)); } diff --git a/src/convert/flags.rs b/src/convert/flags.rs index 21540ed..07b3965 100644 --- a/src/convert/flags.rs +++ b/src/convert/flags.rs @@ -66,6 +66,9 @@ impl FlagConverter { // Empty is a noop. return Ok(()); } + &Val::Boolean(b) => { + try!(write!(w, "{}", if b { "true" } else { "false" })); + } &Val::Float(ref f) => { try!(write!(w, "{} ", f)); } diff --git a/src/convert/json.rs b/src/convert/json.rs index 7414bbe..bc086bc 100644 --- a/src/convert/json.rs +++ b/src/convert/json.rs @@ -48,6 +48,7 @@ impl JsonConverter { fn convert_value(&self, v: &Val) -> Result { let jsn_val = match v { + &Val::Boolean(b) => serde_json::Value::Bool(b), &Val::Empty => serde_json::Value::Null, &Val::Float(f) => { let n = match serde_json::Number::from_f64(f) { diff --git a/src/lib.rs b/src/lib.rs index f41d0d7..1a55d86 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,6 +48,8 @@ //! //! The following words are reserved in ucg and can't be used as named bindings. //! +//! * true +//! * false //! * let //! * import //! * as @@ -60,7 +62,16 @@ //! //! ### Primitive types //! -//! ucg has a relatively simple syntax with 3 primitive types, Int, Float, and String. +//! ucg has a relatively simple syntax with a few primitive types, Null, Boolean, Int, Float, and String. +//! +//! ### Boolean +//! +//! A Boolean is either `true` or `false`. +//! +//! ```uct +//! true; +//! false; +//! ``` //! //! #### Int //! diff --git a/src/parse.rs b/src/parse.rs index 7e6a6d4..ed9a7ed 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -138,6 +138,16 @@ named!(number, ); // trace_macros!(false); +named!(boolean_value, + do_parse!( + b: match_type!(BOOLEAN) >> + (Value::Boolean(Positioned{ + val: b.fragment == "true", + pos: b.pos, + })) + ) +); + named!( field_value, do_parse!( @@ -205,6 +215,7 @@ named!(empty_value, named!(value, alt!( + boolean_value | empty_value | number | quoted_value | @@ -816,6 +827,19 @@ mod test { assert_parse!(empty_value("NULL"), Value::Empty(Position::new(1, 1))); } + #[test] + fn test_boolean_parsing() { + assert_parse!( + boolean_value("true"), + Value::Boolean(Positioned::new(true, 1, 1)) + ); + assert_parse!( + boolean_value("false"), + Value::Boolean(Positioned::new(false, 1, 1)) + ); + assert_error!(boolean_value("truth")); + } + #[test] fn test_symbol_parsing() { assert_parse!( diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 76eb7cc..e93f359 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -97,6 +97,21 @@ named!(digittok( Span ) -> Token, ) ); +named!(booleantok( Span ) -> Token, + do_parse!( + span: position!() >> + b: alt!( + tag!("true") | + tag!("false") + ) >> + (Token{ + typ: TokenType::BOOLEAN, + pos: Position::from(span), + fragment: b.fragment.to_string(), + }) + ) +); + /// do_tag_tok! is a helper macro to make building a simple tag token /// less code. macro_rules! do_tag_tok { @@ -299,6 +314,7 @@ named!(token( Span ) -> Token, semicolontok | leftsquarebracket | rightsquarebracket | + booleantok | lettok | selecttok | macrotok | @@ -369,6 +385,14 @@ pub fn token_clone(t: &Token) -> Result { /// nom macro that matches a Token by type and uses an optional conversion handler /// for the matched Token. macro_rules! match_type { + ($i:expr, BOOLEAN => $h:expr) => { + match_type!($i, TokenType::BOOLEAN, "Not a Boolean", $h) + }; + + ($i:expr, BOOLEAN) => { + match_type!($i, BOOLEAN => token_clone) + }; + ($i:expr, COMMENT => $h:expr) => { match_type!($i, TokenType::COMMENT, "Not a Comment", $h) }; @@ -645,19 +669,32 @@ mod tokenizer_test { } } + #[test] + fn test_boolean() { + let result = token(LocatedSpan::new("true")); + assert!( + result.is_done(), + format!("result {:?} is not a boolean", result) + ); + if let nom::IResult::Done(_, tok) = result { + assert_eq!(tok.fragment, "true"); + assert_eq!(tok.typ, TokenType::BOOLEAN); + } + } + #[test] fn test_tokenize_one_of_each() { let result = tokenize(LocatedSpan::new( "let import macro select as => [ ] { } ; = % / * \ - + - . ( ) , 1 . foo \"bar\" // comment\n ;", + + - . ( ) , 1 . foo \"bar\" // comment\n ; true false", )); assert!(result.is_ok(), format!("result {:?} is not ok", result)); let v = result.unwrap(); for (i, t) in v.iter().enumerate() { println!("{}: {:?}", i, t); } - assert_eq!(v.len(), 27); - assert_eq!(v[26].typ, TokenType::END); + assert_eq!(v.len(), 29); + assert_eq!(v[28].typ, TokenType::END); } #[test]