From 8a6935c3da61abe6e31c7b52ae3379e832698847 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Mon, 12 Mar 2018 20:29:31 -0500 Subject: [PATCH] FEATURE: NULL or empty values in the type system. * Flags with empty values are considered not having a value they are set to. * Json outputs empty values as null. * Env ignores variable with empty values. --- src/ast.rs | 6 ++++++ src/build.rs | 5 +++++ src/convert/env.rs | 8 ++++++++ src/convert/flags.rs | 23 ++++++++++++++++++----- src/convert/json.rs | 1 + src/parse.rs | 40 +++++++++++++++++++++++++++++----------- src/tokenizer.rs | 23 +++++++++++++++++++++++ 7 files changed, 90 insertions(+), 16 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index c17e6d7..4b922e0 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -86,6 +86,7 @@ impl Position { /// Defines the types of tokens in UCG syntax. #[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)] pub enum TokenType { + EMPTY, END, WS, COMMENT, @@ -328,6 +329,7 @@ impl SelectorDef { #[derive(Debug, PartialEq, Clone)] pub enum Value { // Constant Values + Empty(Position), Int(Positioned), Float(Positioned), String(Positioned), @@ -342,6 +344,7 @@ impl Value { /// Returns the type name of the Value it is called on as a string. pub fn type_name(&self) -> String { match self { + &Value::Empty(_) => "EmptyValue".to_string(), &Value::Int(_) => "Integer".to_string(), &Value::Float(_) => "Float".to_string(), &Value::String(_) => "String".to_string(), @@ -371,6 +374,7 @@ impl Value { /// Returns a stringified version of the Value. pub fn to_string(&self) -> String { match self { + &Value::Empty(_) => "EmptyValue".to_string(), &Value::Int(ref i) => format!("{}", i.val), &Value::Float(ref f) => format!("{}", f.val), &Value::String(ref s) => format!("{}", s.val), @@ -384,6 +388,7 @@ impl Value { /// Returns the position for a Value. pub fn pos(&self) -> &Position { match self { + &Value::Empty(ref pos) => pos, &Value::Int(ref i) => &i.pos, &Value::Float(ref f) => &f.pos, &Value::String(ref s) => &s.pos, @@ -399,6 +404,7 @@ impl Value { enum_type_equality!( self, target, + &Value::Empty(_), &Value::Int(_), &Value::Float(_), &Value::String(_), diff --git a/src/build.rs b/src/build.rs index 5611645..f64ed90 100644 --- a/src/build.rs +++ b/src/build.rs @@ -72,6 +72,7 @@ type BuildResult = Result<(), Box>; /// The Intermediate representation of a compiled UCG AST. #[derive(PartialEq, Debug, Clone)] pub enum Val { + Empty, Int(i64), Float(f64), String(String), @@ -84,6 +85,7 @@ impl Val { /// Returns the Type of a Val as a string. pub fn type_name(&self) -> String { match self { + &Val::Empty => "EmptyValue".to_string(), &Val::Int(_) => "Integer".to_string(), &Val::Float(_) => "Float".to_string(), &Val::String(_) => "String".to_string(), @@ -98,6 +100,7 @@ impl Val { enum_type_equality!( self, target, + &Val::Empty, &Val::Int(_), &Val::Float(_), &Val::String(_), @@ -155,6 +158,7 @@ impl Val { impl Display for Val { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { + &Val::Empty => write!(f, "EmptyValue"), &Val::Float(ref ff) => write!(f, "Float({})", ff), &Val::Int(ref i) => write!(f, "Int({})", i), &Val::String(ref s) => write!(f, "String({})", s), @@ -248,6 +252,7 @@ impl Builder { fn value_to_val(&self, v: &Value) -> Result, Box> { match v { + &Value::Empty(_) => Ok(Rc::new(Val::Empty)), &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 b4bf24d..9f61b46 100644 --- a/src/convert/env.rs +++ b/src/convert/env.rs @@ -40,6 +40,10 @@ impl EnvConverter { eprintln!("Skipping embedded tuple..."); return Ok(()); } + if let &Val::Empty = val.as_ref() { + eprintln!("Skipping empty variable: {}", name); + return Ok(()); + } try!(write!(w, "{}=", name.val)); try!(self.write(&val, w)); } @@ -55,6 +59,10 @@ impl EnvConverter { fn write(&self, v: &Val, w: &mut Write) -> Result<()> { match v { + &Val::Empty => { + // Empty is a noop. + return Ok(()); + } &Val::Float(ref f) => { try!(write!(w, "{} ", f)); } diff --git a/src/convert/flags.rs b/src/convert/flags.rs index eaf84a4..aea753b 100644 --- a/src/convert/flags.rs +++ b/src/convert/flags.rs @@ -28,8 +28,21 @@ impl FlagConverter { FlagConverter {} } + fn write_flag_name(&self, pfx: &str, name: &str, w: &mut Write) -> Result<()> { + if name.chars().count() > 1 || pfx.chars().count() > 0 { + try!(write!(w, "--{}{} ", pfx, name)); + } else { + try!(write!(w, "-{} ", name)); + } + return Ok(()); + } + fn write(&self, pfx: &str, v: &Val, w: &mut Write) -> Result<()> { match v { + &Val::Empty => { + // Empty is a noop. + return Ok(()); + } &Val::Float(ref f) => { try!(write!(w, "{} ", f)); } @@ -45,15 +58,15 @@ impl FlagConverter { } &Val::Tuple(ref flds) => { for &(ref name, ref val) in flds.iter() { + if let &Val::Empty = val.as_ref() { + try!(self.write_flag_name(pfx, &name.val, w)); + continue; + } if val.is_tuple() { let new_pfx = format!("{}{}.", pfx, name); try!(self.write(&new_pfx, val, w)); } else { - if name.val.chars().count() > 1 || pfx.chars().count() > 0 { - try!(write!(w, "--{}{} ", pfx, name.val)); - } else { - try!(write!(w, "-{} ", name.val)); - } + try!(self.write_flag_name(pfx, &name.val, w)); // TODO(jwall): What if the value is a tuple? try!(self.write(pfx, &val, w)); } diff --git a/src/convert/json.rs b/src/convert/json.rs index 6076d2a..7414bbe 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::Empty => serde_json::Value::Null, &Val::Float(f) => { let n = match serde_json::Number::from_f64(f) { Some(n) => n, diff --git a/src/parse.rs b/src/parse.rs index ef352e4..f040c4c 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -195,8 +195,17 @@ named!(list_value, ) ); +named!(empty_value, + do_parse!( + pos: pos >> + match_type!(EMPTY) >> + (Value::Empty(pos)) + ) +); + named!(value, alt!( + empty_value | number | quoted_value | list_value | @@ -355,17 +364,17 @@ fn tuple_to_copy(t: (SelectorDef, FieldList)) -> ParseResult { } named!(copy_expression, - map_res!( - do_parse!( - pos: pos >> - selector: selector_list >> - punct!("{") >> - fields: field_list >> - punct!("}") >> - (SelectorDef::new(selector, pos.line, pos.column as usize), fields) - ), - tuple_to_copy - ) + map_res!( + do_parse!( + pos: pos >> + selector: selector_list >> + punct!("{") >> + fields: field_list >> + punct!("}") >> + (SelectorDef::new(selector, pos.line, pos.column as usize), fields) + ), + tuple_to_copy + ) ); fn tuple_to_macro(mut t: (Position, Vec, Value)) -> ParseResult { @@ -702,6 +711,11 @@ mod test { } } + #[test] + fn test_null_parsing() { + assert_parse!(empty_value("NULL"), Value::Empty(Position::new(1, 1))); + } + #[test] fn test_symbol_parsing() { assert_parse!( @@ -924,6 +938,10 @@ mod test { #[test] fn test_expression_parse() { + assert_parse!( + expression("NULL"), + Expression::Simple(Value::Empty(Position::new(1, 1))) + ); assert_parse!( expression("\"foo\""), Expression::Simple(Value::String(value_node!("foo".to_string(), 1, 1))) diff --git a/src/tokenizer.rs b/src/tokenizer.rs index cc4e211..ced1129 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -117,6 +117,10 @@ macro_rules! do_tag_tok { } } +named!(emptytok( Span ) -> Token, + do_tag_tok!(TokenType::EMPTY, "NULL") +); + named!(commatok( Span ) -> Token, do_tag_tok!(TokenType::PUNCT, ",") ); @@ -268,6 +272,7 @@ named!(whitespace( Span ) -> Token, named!(token( Span ) -> Token, alt!( strtok | + emptytok | // This must come before the barewordtok barewordtok | digittok | commatok | @@ -358,6 +363,14 @@ macro_rules! match_type { match_type!($i, BAREWORD => token_clone) }; + ($i:expr, EMPTY => $h:expr) => { + match_type!($i, TokenType::EMPTY, "Not NULL", $h) + }; + + ($i:expr, EMPTY) => { + match_type!($i, EMPTY => token_clone) + }; + ($i:expr, STR => $h:expr) => { match_type!($i, TokenType::QUOTED, "Not a String", $h) }; @@ -571,6 +584,16 @@ mod tokenizer_test { use nom; use nom_locate::LocatedSpan; + #[test] + fn test_empty_token() { + let result = emptytok(LocatedSpan::new("NULL")); + assert!(result.is_done(), format!("result {:?} is not done", result)); + if let nom::IResult::Done(_, tok) = result { + assert_eq!(tok.fragment, "NULL"); + assert_eq!(tok.typ, TokenType::EMPTY); + } + } + #[test] fn test_escape_quoted() { let result = escapequoted(LocatedSpan::new("foo \\\"bar\""));