diff --git a/TODO.md b/TODO.md index 604e661..2753b7f 100644 --- a/TODO.md +++ b/TODO.md @@ -2,9 +2,7 @@ ## Boolean operations and type -* equality (for everything) * contains (for lists or strings) -* less than or greater than (for numeric types) ## Query Language (Experimental) @@ -26,6 +24,7 @@ Some options here could be: # Minor Fixes and Polish +* Casting between types? * Better error messages. * Allow trailing commas. * Flags should allow different seperators for prefixed flags. diff --git a/src/ast.rs b/src/ast.rs index 199b305..edced00 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -74,8 +74,6 @@ pub enum TokenType { PUNCT, } -// FIXME(jwall): We should probably implement copy for this. - /// Defines a Token representing a building block of UCG syntax. /// /// Token's are passed to the parser stage to be parsed into an AST. @@ -604,6 +602,12 @@ pub enum BinaryExprType { Sub, Mul, Div, + Equal, + GT, + LT, + NotEqual, + GTEqual, + LTEqual, } /// Represents an expression with a left and a right side. diff --git a/src/build.rs b/src/build.rs index 881c85c..f11eeef 100644 --- a/src/build.rs +++ b/src/build.rs @@ -113,6 +113,65 @@ impl Val { ) } + // TODO(jwall): Unit Tests for this. + pub fn equal(&self, target: &Self, pos: Position) -> Result { + // first we do a type equality comparison + match (self, target) { + // Empty values are always equal. + (&Val::Empty, &Val::Empty) => Ok(true), + (&Val::Int(ref i), &Val::Int(ref ii)) => Ok(i == ii), + (&Val::Float(ref f), &Val::Float(ref ff)) => Ok(f == ff), + (&Val::Boolean(ref b), &Val::Boolean(ref bb)) => Ok(b == bb), + (&Val::String(ref s), &Val::String(ref ss)) => Ok(s == ss), + (&Val::List(ref ldef), &Val::List(ref lldef)) => { + if ldef.len() != lldef.len() { + Ok(false) + } else { + for (i, v) in ldef.iter().enumerate() { + // TODO(jwall): We should probably do a slightly better error message here. + try!(v.equal(lldef[i].as_ref(), pos.clone())); + } + Ok(true) + } + } + (&Val::Tuple(ref ldef), &Val::Tuple(ref lldef)) => { + if ldef.len() != lldef.len() { + Ok(false) + } else { + for (i, v) in ldef.iter().enumerate() { + let field_target = &lldef[i]; + eprintln!( + "left field: '{}', right field: '{}'", + v.0.val, field_target.0.val + ); + if v.0.val != field_target.0.val { + // field name equality + eprintln!("Field Not equal!!!"); + return Ok(false); + } else { + eprintln!("Field Equal!!!"); + // field value equality. + if !try!(v.1.equal(field_target.1.as_ref(), v.0.pos.clone())) { + return Ok(false); + } + } + } + Ok(true) + } + } + (&Val::Macro(_), &Val::Macro(_)) => Err(error::Error::new( + "Macros are not comparable", + error::ErrorType::TypeFail, + pos, + )), + (me, tgt) => Err(error::Error::new( + format!("Types differ for {}, {}", me, tgt), + error::ErrorType::TypeFail, + pos, + )), + } + } + /// Returns the fields if this Val is a tuple. None otherwise. pub fn get_fields(&self) -> Option<&Vec<(Positioned, Rc)>> { if let &Val::Tuple(ref fs) = self { @@ -676,6 +735,128 @@ impl Builder { } } + fn do_deep_equal( + &self, + pos: &Position, + left: Rc, + right: Rc, + ) -> Result, Box> { + Ok(Rc::new(Val::Boolean(try!( + left.equal(right.as_ref(), pos.clone()) + )))) + } + + fn do_not_deep_equal( + &self, + pos: &Position, + left: Rc, + right: Rc, + ) -> Result, Box> { + Ok(Rc::new(Val::Boolean(!try!( + left.equal(right.as_ref(), pos.clone()) + )))) + } + + fn do_gt(&self, pos: &Position, left: Rc, right: Rc) -> Result, Box> { + // first ensure that left and right are numeric vals of the same type. + if let &Val::Int(ref l) = left.as_ref() { + if let &Val::Int(ref r) = right.as_ref() { + return Ok(Rc::new(Val::Boolean(l > r))); + } + } + if let &Val::Float(ref l) = left.as_ref() { + if let &Val::Float(ref r) = right.as_ref() { + return Ok(Rc::new(Val::Boolean(l > r))); + } + } + Err(Box::new(error::Error::new( + format!( + "Incompatible types for numeric comparison {} with {}", + left.type_name(), + right.type_name() + ), + error::ErrorType::TypeFail, + pos.clone(), + ))) + } + + fn do_lt(&self, pos: &Position, left: Rc, right: Rc) -> Result, Box> { + // first ensure that left and right are numeric vals of the same type. + if let &Val::Int(ref l) = left.as_ref() { + if let &Val::Int(ref r) = right.as_ref() { + return Ok(Rc::new(Val::Boolean(l < r))); + } + } + if let &Val::Float(ref l) = left.as_ref() { + if let &Val::Float(ref r) = right.as_ref() { + return Ok(Rc::new(Val::Boolean(l < r))); + } + } + Err(Box::new(error::Error::new( + format!( + "Incompatible types for numeric comparison {} with {}", + left.type_name(), + right.type_name() + ), + error::ErrorType::TypeFail, + pos.clone(), + ))) + } + + fn do_ltequal( + &self, + pos: &Position, + left: Rc, + right: Rc, + ) -> Result, Box> { + if let &Val::Int(ref l) = left.as_ref() { + if let &Val::Int(ref r) = right.as_ref() { + return Ok(Rc::new(Val::Boolean(l <= r))); + } + } + if let &Val::Float(ref l) = left.as_ref() { + if let &Val::Float(ref r) = right.as_ref() { + return Ok(Rc::new(Val::Boolean(l <= r))); + } + } + Err(Box::new(error::Error::new( + format!( + "Incompatible types for numeric comparison {} with {}", + left.type_name(), + right.type_name() + ), + error::ErrorType::TypeFail, + pos.clone(), + ))) + } + + fn do_gtequal( + &self, + pos: &Position, + left: Rc, + right: Rc, + ) -> Result, Box> { + if let &Val::Int(ref l) = left.as_ref() { + if let &Val::Int(ref r) = right.as_ref() { + return Ok(Rc::new(Val::Boolean(l >= r))); + } + } + if let &Val::Float(ref l) = left.as_ref() { + if let &Val::Float(ref r) = right.as_ref() { + return Ok(Rc::new(Val::Boolean(l >= r))); + } + } + Err(Box::new(error::Error::new( + format!( + "Incompatible types for numeric comparison {} with {}", + left.type_name(), + right.type_name() + ), + error::ErrorType::TypeFail, + pos.clone(), + ))) + } + fn eval_binary(&self, def: &BinaryOpDef) -> Result, Box> { let kind = &def.kind; let v = &def.left; @@ -687,6 +868,12 @@ impl Builder { &BinaryExprType::Sub => self.subtract_vals(&def.pos, left, right), &BinaryExprType::Mul => self.multiply_vals(&def.pos, left, right), &BinaryExprType::Div => self.divide_vals(&def.pos, left, right), + &BinaryExprType::Equal => self.do_deep_equal(&def.pos, left, right), + &BinaryExprType::GT => self.do_gt(&def.pos, left, right), + &BinaryExprType::LT => self.do_lt(&def.pos, left, right), + &BinaryExprType::GTEqual => self.do_gtequal(&def.pos, left, right), + &BinaryExprType::LTEqual => self.do_ltequal(&def.pos, left, right), + &BinaryExprType::NotEqual => self.do_not_deep_equal(&def.pos, left, right), } } @@ -1147,6 +1334,223 @@ mod test { ); } + #[test] + fn test_eval_equal_exprs() { + let b = Builder::new(); + test_expr_to_val( + vec![ + ( + Expression::Binary(BinaryOpDef { + kind: BinaryExprType::Equal, + left: Value::Int(value_node!(2, 1, 1)), + right: Box::new(Expression::Simple(Value::Int(value_node!(2, 1, 1)))), + pos: Position::new(1, 0), + }), + Val::Boolean(true), + ), + ( + Expression::Binary(BinaryOpDef { + kind: BinaryExprType::Equal, + left: Value::Float(value_node!(2.0, 1, 1)), + right: Box::new(Expression::Simple(Value::Float(value_node!(2.0, 1, 1)))), + pos: Position::new(1, 0), + }), + Val::Boolean(true), + ), + ( + Expression::Binary(BinaryOpDef { + kind: BinaryExprType::Equal, + left: Value::String(value_node!("foo".to_string(), 1, 1)), + right: Box::new(Expression::Simple(Value::String(value_node!( + "foo".to_string(), + 1, + 1 + )))), + pos: Position::new(1, 0), + }), + Val::Boolean(true), + ), + ( + Expression::Binary(BinaryOpDef { + kind: BinaryExprType::Equal, + left: Value::Tuple(value_node!( + vec![ + ( + make_tok!("bar", 1, 1), + Expression::Simple(Value::Int(value_node!(1, 1, 1))), + ), + ( + make_tok!("foo", 1, 1), + Expression::Simple(Value::String(value_node!( + "blah".to_string(), + 1, + 1 + ))), + ), + ], + 1, + 1 + )), + right: Box::new(Expression::Simple(Value::Tuple(value_node!( + vec![ + ( + make_tok!("bar", 1, 1), + Expression::Simple(Value::Int(value_node!(1, 1, 1))), + ), + ( + make_tok!("foo", 1, 1), + Expression::Simple(Value::String(value_node!( + "blah".to_string(), + 1, + 1 + ))), + ), + ], + 1, + 1 + )))), + pos: Position::new(1, 0), + }), + Val::Boolean(true), + ), + ( + Expression::Binary(BinaryOpDef { + kind: BinaryExprType::Equal, + left: Value::Tuple(value_node!( + vec![ + ( + make_tok!("bar", 1, 1), + Expression::Simple(Value::Int(value_node!(1, 1, 1))), + ), + ( + make_tok!("foo", 1, 1), + Expression::Simple(Value::String(value_node!( + "blah".to_string(), + 1, + 1 + ))), + ), + ], + 1, + 1 + )), + right: Box::new(Expression::Simple(Value::Tuple(value_node!( + vec![ + ( + make_tok!("bar", 1, 1), + Expression::Simple(Value::Int(value_node!(1, 1, 1))), + ), + ( + make_tok!("foo", 1, 1), + Expression::Simple(Value::String(value_node!( + "blush".to_string(), + 1, + 1 + ))), + ), + ], + 1, + 1 + )))), + pos: Position::new(1, 0), + }), + Val::Boolean(false), + ), + ( + Expression::Binary(BinaryOpDef { + kind: BinaryExprType::Equal, + left: Value::Tuple(value_node!( + vec![ + ( + make_tok!("bar", 1, 1), + Expression::Simple(Value::Int(value_node!(1, 1, 1))), + ), + ( + make_tok!("foo", 1, 1), + Expression::Simple(Value::String(value_node!( + "blah".to_string(), + 1, + 1 + ))), + ), + ], + 1, + 1 + )), + right: Box::new(Expression::Simple(Value::Tuple(value_node!( + vec![ + ( + make_tok!("bosh", 1, 1), + Expression::Simple(Value::Int(value_node!(1, 1, 1))), + ), + ( + make_tok!("foo", 1, 1), + Expression::Simple(Value::String(value_node!( + "blah".to_string(), + 1, + 1 + ))), + ), + ], + 1, + 1 + )))), + pos: Position::new(1, 0), + }), + Val::Boolean(false), + ), + ( + Expression::Binary(BinaryOpDef { + kind: BinaryExprType::Equal, + left: Value::Tuple(value_node!( + vec![ + ( + make_tok!("bar", 1, 1), + Expression::Simple(Value::Int(value_node!(1, 1, 1))), + ), + ( + make_tok!("foo", 1, 1), + Expression::Simple(Value::String(value_node!( + "blah".to_string(), + 1, + 1 + ))), + ), + ], + 1, + 1 + )), + right: Box::new(Expression::Simple(Value::Tuple(value_node!( + vec![ + ( + make_tok!("bash", 1, 1), + Expression::Simple(Value::Int(value_node!(1, 1, 1))), + ), + ( + make_tok!("bar", 1, 1), + Expression::Simple(Value::Int(value_node!(1, 1, 1))), + ), + ( + make_tok!("foo", 1, 1), + Expression::Simple(Value::String(value_node!( + "blah".to_string(), + 1, + 1 + ))), + ), + ], + 1, + 1 + )))), + pos: Position::new(1, 0), + }), + Val::Boolean(false), + ), + ], + b, + ); + } + #[test] fn test_eval_simple_expr() { test_expr_to_val( diff --git a/src/lib.rs b/src/lib.rs index 1a55d86..4137fc0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,7 +97,7 @@ //! character. //! //! ``` ucg -//! "foo"; // a smiple string +//! "foo"; // a simple string //! "I'm a \"fine\" looking string"; // escaped quotes in a string. //! ``` //! @@ -216,16 +216,75 @@ //! //! #### Binary operators //! -//! ucg supports the following operators, +, -, *, /; Each one is type safe and infers the types from the values they operate on. -//! The operators expect both the left and right operands to be of the same type. All of the operators are valid on integers and floats. -//! The + operator can additionally concatenate strings or arrays. +//! ##### Numeric operators +//! +//! ucg supports the following numeric operators, `+`, `-`, `*`, `/` Each one is type safe and infers the types +//! from the values they operate on. The operators expect both the left and right operands to be of the same +//! type. +//! +//! ```ucg +//! 1 + 1; // result is 2 +//! ``` +//! +//! ##### Concatenation +//! +//! ucg supports concatenation using the `+` operator. It is typesafe expecting both sides to be of the same type. +//! You can concatenate strings or lists but not tuples. +//! +//! ```ucg +//! "foo " + "bar" // result is "foo bar" +//! [1,2] + [3,4]; // result is [1,2,3,4] +//! ``` +//! +//! ##### Comparison +//! +//! ucg supports comparison using the `==`, `!=`, `>`, `<`, `>=`, `<=` operators. They are type safe and expect both +//! sides to be of the same type. +//! +//! The `>`, `<`, `>=`, and `>=` operators are only supported on numeric types. +//! +//! ``ucg +//! 1 > 2; // result is false +//! 2 < 3; // result is true +//! 10 > "9"; // This is a compile error. +//! ``` +//! +//! The equality operators `==` and `!=` are supported for all types and will perform deep equal comparisons on complex +//! types. +//! +//! ```ucg +//! let tpl1 = { +//! foo = "bar", +//! one = 1 +//! }; +//! let tpl2 = tpl1{}; // copy the tpl1 tuple +//! tpl1 == tpl2; // returns true +//! let tpl3 = tpl1{duck="quack"}; +//! tpl1 == tpl3; // returns false +//! ``` +//! +//! Note that tuple fields are ordered so a tuple will only be equal if the fields are both in the same order and +//! have the same values in them. +//! +//! ##### Boolean +//! +//! ucg supports the following operators, `+`, `-`, `*`, `/`, `==`, `!=`, `>`, `<`, `>=`, `<=`, `&&`, `||` Each one is type safe and +//! infers the types from the values they operate on. The operators expect both the left and right operands to be of the same +//! type. All of the operators except `&&` and `||` are valid on integers and floats. The + operator can additionally concatenate +//! strings or arrays. The `&&` and `||` operators are only valid on booleans. //! //! ```ucg //! 1 + 1; // result is 2 //! "foo " + "bar" // result is "foo bar" //! [1,2] + [3,4]; // result is [1,2,3,4] +//! 1 == 1; // result is true. +//! 1 > 1; // result is false +//! 1 >= 1; // result is true; //! ``` //! +//! The Equality comparison can be done on any type and will perform a deep equal comparison. +//! +//! //! #### Copy expressions //! //! ucg Tuples support a form of reuse with copy on write semantics. You can copy a tuple and selectively overwrite fields or add new diff --git a/src/parse.rs b/src/parse.rs index 685b7fb..51b4bfe 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -286,6 +286,30 @@ named!(div_expression, do_binary_expr!(punct!("/"), BinaryExprType::Div) ); +named!(eqeq_expression, + do_binary_expr!(punct!("=="), BinaryExprType::Equal) +); + +named!(not_eqeq_expression, + do_binary_expr!(punct!("!="), BinaryExprType::NotEqual) +); + +named!(lt_eqeq_expression, + do_binary_expr!(punct!("<="), BinaryExprType::LTEqual) +); + +named!(gt_eqeq_expression, + do_binary_expr!(punct!(">="), BinaryExprType::GTEqual) +); + +named!(gt_expression, + do_binary_expr!(punct!(">"), BinaryExprType::GT) +); + +named!(lt_expression, + do_binary_expr!(punct!("<"), BinaryExprType::LT) +); + fn expression_to_grouped_expression(e: Expression) -> ParseResult { Ok(Expression::Grouped(Box::new(e))) } @@ -652,6 +676,12 @@ named!(expression, complete!(sub_expression) | complete!(mul_expression) | complete!(div_expression) | + complete!(eqeq_expression) | + complete!(not_eqeq_expression) | + complete!(lt_eqeq_expression) | + complete!(gt_eqeq_expression) | + complete!(gt_expression) | + complete!(lt_expression) | complete!(grouped_expression) | complete!(macro_expression) | complete!(format_expression) | @@ -1135,7 +1165,42 @@ mod test { pos: Position::new(1, 1), }) ); - + assert_parse!( + expression("1 > 1"), + Expression::Binary(BinaryOpDef { + kind: BinaryExprType::GT, + left: Value::Int(value_node!(1, 1, 1)), + right: Box::new(Expression::Simple(Value::Int(value_node!(1, 1, 5)))), + pos: Position::new(1, 1), + }) + ); + assert_parse!( + expression("1 < 1"), + Expression::Binary(BinaryOpDef { + kind: BinaryExprType::LT, + left: Value::Int(value_node!(1, 1, 1)), + right: Box::new(Expression::Simple(Value::Int(value_node!(1, 1, 5)))), + pos: Position::new(1, 1), + }) + ); + assert_parse!( + expression("1 <= 1"), + Expression::Binary(BinaryOpDef { + kind: BinaryExprType::LTEqual, + left: Value::Int(value_node!(1, 1, 1)), + right: Box::new(Expression::Simple(Value::Int(value_node!(1, 1, 5)))), + pos: Position::new(1, 1), + }) + ); + assert_parse!( + expression("1 >= 1"), + Expression::Binary(BinaryOpDef { + kind: BinaryExprType::GTEqual, + left: Value::Int(value_node!(1, 1, 1)), + right: Box::new(Expression::Simple(Value::Int(value_node!(1, 1, 5)))), + pos: Position::new(1, 1), + }) + ); assert_parse!( expression("1+1"), Expression::Binary(BinaryOpDef { diff --git a/src/tokenizer.rs b/src/tokenizer.rs index aca0cc4..d0f00be 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -181,6 +181,32 @@ named!(pcttok( Span ) -> Token, do_tag_tok!(TokenType::PUNCT, "%") ); +// TODO(jwall): Comparison operators. + +named!(eqeqtok( Span ) -> Token, + do_tag_tok!(TokenType::PUNCT, "==") +); + +named!(notequaltok( Span ) -> Token, + do_tag_tok!(TokenType::PUNCT, "!=") +); + +named!(gttok( Span ) -> Token, + do_tag_tok!(TokenType::PUNCT, ">") +); + +named!(gtequaltok( Span ) -> Token, + do_tag_tok!(TokenType::PUNCT, ">=") +); + +named!(ltequaltok( Span ) -> Token, + do_tag_tok!(TokenType::PUNCT, "<=") +); + +named!(lttok( Span ) -> Token, + do_tag_tok!(TokenType::PUNCT, "<") +); + named!(equaltok( Span ) -> Token, do_tag_tok!(TokenType::PUNCT, "=") ); @@ -310,6 +336,12 @@ named!(token( Span ) -> Token, comment | // Note comment must come before slashtok slashtok | pcttok | + eqeqtok | + notequaltok | + complete!(gtequaltok) | + complete!(ltequaltok) | + gttok | + lttok | fatcommatok | // Note fatcommatok must come before equaltok equaltok | semicolontok | @@ -670,32 +702,68 @@ mod tokenizer_test { } } - #[test] - fn test_boolean() { - let result = token(LocatedSpan::new("true")); + macro_rules! assert_token { + ($input:expr, $typ:expr, $msg:expr) => { + let result = token(LocatedSpan::new($input)); assert!( result.is_done(), - format!("result {:?} is not a boolean", result) + format!("result {:?} is not a {}", result, $msg) ); if let nom::IResult::Done(_, tok) = result { - assert_eq!(tok.fragment, "true"); - assert_eq!(tok.typ, TokenType::BOOLEAN); + assert_eq!(tok.fragment, $input); + assert_eq!(tok.typ, $typ); } + } + } + + #[test] + fn test_boolean() { + assert_token!("true", TokenType::BOOLEAN, "boolean"); + } + + #[test] + fn test_eqeqtok() { + assert_token!("==", TokenType::PUNCT, "=="); + } + + #[test] + fn test_notequaltok() { + assert_token!("!=", TokenType::PUNCT, "!="); + } + + #[test] + fn test_gttok() { + assert_token!(">", TokenType::PUNCT, ">"); + } + + #[test] + fn test_lttok() { + assert_token!("<", TokenType::PUNCT, "<"); + } + + #[test] + fn test_gteqtok() { + assert_token!(">=", TokenType::PUNCT, ">="); + } + + #[test] + fn test_lteqtok() { + assert_token!("<=", TokenType::PUNCT, "<="); } #[test] fn test_tokenize_one_of_each() { let result = tokenize(LocatedSpan::new( "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)); let v = result.unwrap(); for (i, t) in v.iter().enumerate() { println!("{}: {:?}", i, t); } - assert_eq!(v.len(), 29); - assert_eq!(v[28].typ, TokenType::END); + assert_eq!(v.len(), 35); + assert_eq!(v[34].typ, TokenType::END); } #[test]