mirror of
https://github.com/zaphar/ucg.git
synced 2025-07-22 18:19:54 -04:00
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.
This commit is contained in:
parent
5a03cc33ef
commit
8a6935c3da
@ -86,6 +86,7 @@ impl Position {
|
|||||||
/// Defines the types of tokens in UCG syntax.
|
/// Defines the types of tokens in UCG syntax.
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
|
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
|
||||||
pub enum TokenType {
|
pub enum TokenType {
|
||||||
|
EMPTY,
|
||||||
END,
|
END,
|
||||||
WS,
|
WS,
|
||||||
COMMENT,
|
COMMENT,
|
||||||
@ -328,6 +329,7 @@ impl SelectorDef {
|
|||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
// Constant Values
|
// Constant Values
|
||||||
|
Empty(Position),
|
||||||
Int(Positioned<i64>),
|
Int(Positioned<i64>),
|
||||||
Float(Positioned<f64>),
|
Float(Positioned<f64>),
|
||||||
String(Positioned<String>),
|
String(Positioned<String>),
|
||||||
@ -342,6 +344,7 @@ impl Value {
|
|||||||
/// Returns the type name of the Value it is called on as a string.
|
/// Returns the type name of the Value it is called on as a string.
|
||||||
pub fn type_name(&self) -> String {
|
pub fn type_name(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
|
&Value::Empty(_) => "EmptyValue".to_string(),
|
||||||
&Value::Int(_) => "Integer".to_string(),
|
&Value::Int(_) => "Integer".to_string(),
|
||||||
&Value::Float(_) => "Float".to_string(),
|
&Value::Float(_) => "Float".to_string(),
|
||||||
&Value::String(_) => "String".to_string(),
|
&Value::String(_) => "String".to_string(),
|
||||||
@ -371,6 +374,7 @@ impl Value {
|
|||||||
/// Returns a stringified version of the Value.
|
/// Returns a stringified version of the Value.
|
||||||
pub fn to_string(&self) -> String {
|
pub fn to_string(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
|
&Value::Empty(_) => "EmptyValue".to_string(),
|
||||||
&Value::Int(ref i) => format!("{}", i.val),
|
&Value::Int(ref i) => format!("{}", i.val),
|
||||||
&Value::Float(ref f) => format!("{}", f.val),
|
&Value::Float(ref f) => format!("{}", f.val),
|
||||||
&Value::String(ref s) => format!("{}", s.val),
|
&Value::String(ref s) => format!("{}", s.val),
|
||||||
@ -384,6 +388,7 @@ impl Value {
|
|||||||
/// Returns the position for a Value.
|
/// Returns the position for a Value.
|
||||||
pub fn pos(&self) -> &Position {
|
pub fn pos(&self) -> &Position {
|
||||||
match self {
|
match self {
|
||||||
|
&Value::Empty(ref pos) => pos,
|
||||||
&Value::Int(ref i) => &i.pos,
|
&Value::Int(ref i) => &i.pos,
|
||||||
&Value::Float(ref f) => &f.pos,
|
&Value::Float(ref f) => &f.pos,
|
||||||
&Value::String(ref s) => &s.pos,
|
&Value::String(ref s) => &s.pos,
|
||||||
@ -399,6 +404,7 @@ impl Value {
|
|||||||
enum_type_equality!(
|
enum_type_equality!(
|
||||||
self,
|
self,
|
||||||
target,
|
target,
|
||||||
|
&Value::Empty(_),
|
||||||
&Value::Int(_),
|
&Value::Int(_),
|
||||||
&Value::Float(_),
|
&Value::Float(_),
|
||||||
&Value::String(_),
|
&Value::String(_),
|
||||||
|
@ -72,6 +72,7 @@ type BuildResult = Result<(), Box<Error>>;
|
|||||||
/// The Intermediate representation of a compiled UCG AST.
|
/// The Intermediate representation of a compiled UCG AST.
|
||||||
#[derive(PartialEq, Debug, Clone)]
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
pub enum Val {
|
pub enum Val {
|
||||||
|
Empty,
|
||||||
Int(i64),
|
Int(i64),
|
||||||
Float(f64),
|
Float(f64),
|
||||||
String(String),
|
String(String),
|
||||||
@ -84,6 +85,7 @@ impl Val {
|
|||||||
/// Returns the Type of a Val as a string.
|
/// Returns the Type of a Val as a string.
|
||||||
pub fn type_name(&self) -> String {
|
pub fn type_name(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
|
&Val::Empty => "EmptyValue".to_string(),
|
||||||
&Val::Int(_) => "Integer".to_string(),
|
&Val::Int(_) => "Integer".to_string(),
|
||||||
&Val::Float(_) => "Float".to_string(),
|
&Val::Float(_) => "Float".to_string(),
|
||||||
&Val::String(_) => "String".to_string(),
|
&Val::String(_) => "String".to_string(),
|
||||||
@ -98,6 +100,7 @@ impl Val {
|
|||||||
enum_type_equality!(
|
enum_type_equality!(
|
||||||
self,
|
self,
|
||||||
target,
|
target,
|
||||||
|
&Val::Empty,
|
||||||
&Val::Int(_),
|
&Val::Int(_),
|
||||||
&Val::Float(_),
|
&Val::Float(_),
|
||||||
&Val::String(_),
|
&Val::String(_),
|
||||||
@ -155,6 +158,7 @@ impl Val {
|
|||||||
impl Display for Val {
|
impl Display for Val {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
&Val::Empty => write!(f, "EmptyValue"),
|
||||||
&Val::Float(ref ff) => write!(f, "Float({})", ff),
|
&Val::Float(ref ff) => write!(f, "Float({})", ff),
|
||||||
&Val::Int(ref i) => write!(f, "Int({})", i),
|
&Val::Int(ref i) => write!(f, "Int({})", i),
|
||||||
&Val::String(ref s) => write!(f, "String({})", s),
|
&Val::String(ref s) => write!(f, "String({})", s),
|
||||||
@ -248,6 +252,7 @@ impl Builder {
|
|||||||
|
|
||||||
fn value_to_val(&self, v: &Value) -> Result<Rc<Val>, Box<Error>> {
|
fn value_to_val(&self, v: &Value) -> Result<Rc<Val>, Box<Error>> {
|
||||||
match v {
|
match v {
|
||||||
|
&Value::Empty(_) => Ok(Rc::new(Val::Empty)),
|
||||||
&Value::Int(ref i) => Ok(Rc::new(Val::Int(i.val))),
|
&Value::Int(ref i) => Ok(Rc::new(Val::Int(i.val))),
|
||||||
&Value::Float(ref f) => Ok(Rc::new(Val::Float(f.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()))),
|
&Value::String(ref s) => Ok(Rc::new(Val::String(s.val.to_string()))),
|
||||||
|
@ -40,6 +40,10 @@ impl EnvConverter {
|
|||||||
eprintln!("Skipping embedded tuple...");
|
eprintln!("Skipping embedded tuple...");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
if let &Val::Empty = val.as_ref() {
|
||||||
|
eprintln!("Skipping empty variable: {}", name);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
try!(write!(w, "{}=", name.val));
|
try!(write!(w, "{}=", name.val));
|
||||||
try!(self.write(&val, w));
|
try!(self.write(&val, w));
|
||||||
}
|
}
|
||||||
@ -55,6 +59,10 @@ impl EnvConverter {
|
|||||||
|
|
||||||
fn write(&self, v: &Val, w: &mut Write) -> Result<()> {
|
fn write(&self, v: &Val, w: &mut Write) -> Result<()> {
|
||||||
match v {
|
match v {
|
||||||
|
&Val::Empty => {
|
||||||
|
// Empty is a noop.
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
&Val::Float(ref f) => {
|
&Val::Float(ref f) => {
|
||||||
try!(write!(w, "{} ", f));
|
try!(write!(w, "{} ", f));
|
||||||
}
|
}
|
||||||
|
@ -28,8 +28,21 @@ impl FlagConverter {
|
|||||||
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<()> {
|
fn write(&self, pfx: &str, v: &Val, w: &mut Write) -> Result<()> {
|
||||||
match v {
|
match v {
|
||||||
|
&Val::Empty => {
|
||||||
|
// Empty is a noop.
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
&Val::Float(ref f) => {
|
&Val::Float(ref f) => {
|
||||||
try!(write!(w, "{} ", f));
|
try!(write!(w, "{} ", f));
|
||||||
}
|
}
|
||||||
@ -45,15 +58,15 @@ impl FlagConverter {
|
|||||||
}
|
}
|
||||||
&Val::Tuple(ref flds) => {
|
&Val::Tuple(ref flds) => {
|
||||||
for &(ref name, ref val) in flds.iter() {
|
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() {
|
if val.is_tuple() {
|
||||||
let new_pfx = format!("{}{}.", pfx, name);
|
let new_pfx = format!("{}{}.", pfx, name);
|
||||||
try!(self.write(&new_pfx, val, w));
|
try!(self.write(&new_pfx, val, w));
|
||||||
} else {
|
} else {
|
||||||
if name.val.chars().count() > 1 || pfx.chars().count() > 0 {
|
try!(self.write_flag_name(pfx, &name.val, w));
|
||||||
try!(write!(w, "--{}{} ", pfx, name.val));
|
|
||||||
} else {
|
|
||||||
try!(write!(w, "-{} ", name.val));
|
|
||||||
}
|
|
||||||
// TODO(jwall): What if the value is a tuple?
|
// TODO(jwall): What if the value is a tuple?
|
||||||
try!(self.write(pfx, &val, w));
|
try!(self.write(pfx, &val, w));
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,7 @@ impl JsonConverter {
|
|||||||
|
|
||||||
fn convert_value(&self, v: &Val) -> Result<serde_json::Value> {
|
fn convert_value(&self, v: &Val) -> Result<serde_json::Value> {
|
||||||
let jsn_val = match v {
|
let jsn_val = match v {
|
||||||
|
&Val::Empty => serde_json::Value::Null,
|
||||||
&Val::Float(f) => {
|
&Val::Float(f) => {
|
||||||
let n = match serde_json::Number::from_f64(f) {
|
let n = match serde_json::Number::from_f64(f) {
|
||||||
Some(n) => n,
|
Some(n) => n,
|
||||||
|
40
src/parse.rs
40
src/parse.rs
@ -195,8 +195,17 @@ named!(list_value<TokenIter, Value, ParseError>,
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
named!(empty_value<TokenIter, Value, ParseError>,
|
||||||
|
do_parse!(
|
||||||
|
pos: pos >>
|
||||||
|
match_type!(EMPTY) >>
|
||||||
|
(Value::Empty(pos))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
named!(value<TokenIter, Value, ParseError>,
|
named!(value<TokenIter, Value, ParseError>,
|
||||||
alt!(
|
alt!(
|
||||||
|
empty_value |
|
||||||
number |
|
number |
|
||||||
quoted_value |
|
quoted_value |
|
||||||
list_value |
|
list_value |
|
||||||
@ -355,17 +364,17 @@ fn tuple_to_copy(t: (SelectorDef, FieldList)) -> ParseResult<Expression> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
named!(copy_expression<TokenIter, Expression, ParseError>,
|
named!(copy_expression<TokenIter, Expression, ParseError>,
|
||||||
map_res!(
|
map_res!(
|
||||||
do_parse!(
|
do_parse!(
|
||||||
pos: pos >>
|
pos: pos >>
|
||||||
selector: selector_list >>
|
selector: selector_list >>
|
||||||
punct!("{") >>
|
punct!("{") >>
|
||||||
fields: field_list >>
|
fields: field_list >>
|
||||||
punct!("}") >>
|
punct!("}") >>
|
||||||
(SelectorDef::new(selector, pos.line, pos.column as usize), fields)
|
(SelectorDef::new(selector, pos.line, pos.column as usize), fields)
|
||||||
),
|
),
|
||||||
tuple_to_copy
|
tuple_to_copy
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
fn tuple_to_macro(mut t: (Position, Vec<Value>, Value)) -> ParseResult<Expression> {
|
fn tuple_to_macro(mut t: (Position, Vec<Value>, Value)) -> ParseResult<Expression> {
|
||||||
@ -702,6 +711,11 @@ mod test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_null_parsing() {
|
||||||
|
assert_parse!(empty_value("NULL"), Value::Empty(Position::new(1, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_symbol_parsing() {
|
fn test_symbol_parsing() {
|
||||||
assert_parse!(
|
assert_parse!(
|
||||||
@ -924,6 +938,10 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_expression_parse() {
|
fn test_expression_parse() {
|
||||||
|
assert_parse!(
|
||||||
|
expression("NULL"),
|
||||||
|
Expression::Simple(Value::Empty(Position::new(1, 1)))
|
||||||
|
);
|
||||||
assert_parse!(
|
assert_parse!(
|
||||||
expression("\"foo\""),
|
expression("\"foo\""),
|
||||||
Expression::Simple(Value::String(value_node!("foo".to_string(), 1, 1)))
|
Expression::Simple(Value::String(value_node!("foo".to_string(), 1, 1)))
|
||||||
|
@ -117,6 +117,10 @@ macro_rules! do_tag_tok {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
named!(emptytok( Span ) -> Token,
|
||||||
|
do_tag_tok!(TokenType::EMPTY, "NULL")
|
||||||
|
);
|
||||||
|
|
||||||
named!(commatok( Span ) -> Token,
|
named!(commatok( Span ) -> Token,
|
||||||
do_tag_tok!(TokenType::PUNCT, ",")
|
do_tag_tok!(TokenType::PUNCT, ",")
|
||||||
);
|
);
|
||||||
@ -268,6 +272,7 @@ named!(whitespace( Span ) -> Token,
|
|||||||
named!(token( Span ) -> Token,
|
named!(token( Span ) -> Token,
|
||||||
alt!(
|
alt!(
|
||||||
strtok |
|
strtok |
|
||||||
|
emptytok | // This must come before the barewordtok
|
||||||
barewordtok |
|
barewordtok |
|
||||||
digittok |
|
digittok |
|
||||||
commatok |
|
commatok |
|
||||||
@ -358,6 +363,14 @@ macro_rules! match_type {
|
|||||||
match_type!($i, BAREWORD => token_clone)
|
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) => {
|
($i:expr, STR => $h:expr) => {
|
||||||
match_type!($i, TokenType::QUOTED, "Not a String", $h)
|
match_type!($i, TokenType::QUOTED, "Not a String", $h)
|
||||||
};
|
};
|
||||||
@ -571,6 +584,16 @@ mod tokenizer_test {
|
|||||||
use nom;
|
use nom;
|
||||||
use nom_locate::LocatedSpan;
|
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]
|
#[test]
|
||||||
fn test_escape_quoted() {
|
fn test_escape_quoted() {
|
||||||
let result = escapequoted(LocatedSpan::new("foo \\\"bar\""));
|
let result = escapequoted(LocatedSpan::new("foo \\\"bar\""));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user