diff --git a/src/builtins/compare.rs b/src/builtins/compare.rs deleted file mode 100644 index 747b4b5..0000000 --- a/src/builtins/compare.rs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use crate::ast::*; -use crate::value::{Value::*, *}; - -pub fn eq(v1: &Value, v2: &Value) -> Value { - match (v1, v2) { - (Undefined, _) | (_, Undefined) => Undefined, - _ => Bool(v1 == v2), - } -} - -pub fn ne(v1: &Value, v2: &Value) -> Value { - match (v1, v2) { - (Undefined, _) | (_, Undefined) => Undefined, - _ => Bool(v1 != v2), - } -} - -pub fn compare(op: &BoolOp, v1: &Value, v2: &Value) -> Value { - match op { - BoolOp::Eq => eq(v1, v2), - BoolOp::Ne => ne(v1, v2), - BoolOp::Ge => Bool(v1 >= v2), - BoolOp::Gt => Bool(v1 > v2), - BoolOp::Le => Bool(v1 <= v2), - BoolOp::Lt => Bool(v1 < v2), - } -} diff --git a/src/builtins/comparison.rs b/src/builtins/comparison.rs new file mode 100644 index 0000000..50c72c3 --- /dev/null +++ b/src/builtins/comparison.rs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::ast::BoolOp; +use crate::value::Value; + +use anyhow::Result; + +/// compare two values +/// +/// When comparing values of different kinds, the following order is honored. +/// That is, null is less than all other kinds of values. bool is greater than +/// null, but less than other kinds of values, and so on. +/// +/// 1. null +/// 2. bool +/// 3. number +/// 4. string +/// 5. Array +/// 6. Object +/// 7. Set +/// +/// Scalar types like null, bool, number, string follow ordering same as what is seen in other languages when compared with values of the same kind. +/// +/// When comparing arrays, each item from the first array is compared with the corresponding item from the second array. The ordering is determined by +// the ordering of the first non-equal items. In case all the compared items +/// are equal, the ordering is determined by comparing the length of the first array +/// with the length of the second array. The smaller array is 'Less' than the larger array. +/// +/// Sets compare similar to arrays. +/// +/// When comparing objects, each entry (key, value) from the first object is compared with the corresponding +/// entry from the second object. The ordering is determined by the ordering of the first non-equal entries. In case all the compared entries are equal, +/// the ordering is determined by comparing the length of the first object with the length of the second object. The smaller object is 'Less' then the larger object. +/// +/// Undefined values are a special case. Comparing an Undefined value with +/// any other value results in Undefined. +/// +/// # Arguments +/// * `op` - The comparison operation to perform. +/// * `v1` - The first value. +/// * `v2` - The second value. +pub fn compare(op: &BoolOp, v1: &Value, v2: &Value) -> Result { + // Handle undefined values. + if v1 == &Value::Undefined || v2 == &Value::Undefined { + return Ok(Value::Undefined); + } + + // Rely on generated comparison operators. + // The variants of Value enum are specified in the order necessary to + // obtain the desired semantics. + Ok(Value::Bool(match op { + BoolOp::Eq => v1 == v2, + BoolOp::Ne => v1 != v2, + BoolOp::Lt => v1 < v2, + BoolOp::Le => v1 <= v2, + BoolOp::Gt => v1 > v2, + BoolOp::Ge => v1 >= v2, + })) +} diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 824402e..cd73d89 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -mod compare; +mod comparison; -pub use self::compare::*; +pub use self::comparison::*; diff --git a/src/interpreter.rs b/src/interpreter.rs index 14adf22..eb4dc27 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -262,7 +262,7 @@ impl<'source> Interpreter<'source> { ) -> Result { let lhs = self.eval_expr(lhs_expr)?; let rhs = self.eval_expr(rhs_expr)?; - Ok(builtins::compare(op, &lhs, &rhs)) + builtins::compare(op, &lhs, &rhs) } fn eval_bin_expr( @@ -531,10 +531,23 @@ impl<'source> Interpreter<'source> { if loops.is_empty() { if !stmts.is_empty() { // Evaluate the current statement whose loop expressions have been hoisted. - if !self.eval_stmt(&stmts[0])? { - return Ok(false); - } - self.eval_stmts(&stmts[1..]) + // Save the current scope and restore it after evaluating the statements so + // that the effects of the current loop iteration are cleared. + let scope_saved = match self.scopes.last() { + Some(scope) => scope.clone(), + _ => bail!("internal error: missing scope"), + }; + let result = if self.eval_stmt(&stmts[0])? { + self.eval_stmts(&stmts[1..]) + } else { + Ok(false) + }; + + match self.scopes.last_mut() { + Some(scope) => *scope = scope_saved, + _ => bail!("internal error: missing scope"), + }; + result } else { self.eval_stmts(stmts) } @@ -568,7 +581,7 @@ impl<'source> Interpreter<'source> { )); } } - self.loop_var_values.remove(loop_expr.expr); + // Return true if at least on iteration returned true Ok(result) } @@ -1617,6 +1630,15 @@ impl<'source> Interpreter<'source> { if let Some(data) = data { self.data = data.clone(); } + // Ensure that each module has an empty object + for m in &self.modules { + let path = Parser::get_path_ref_components(&m.package.refr)?; + let path: Vec<&str> = path.iter().map(|s| s.text()).collect(); + let vref = Self::make_or_get_value_mut(&mut self.data, &path[..])?; + if *vref == Value::Undefined { + *vref = Value::new_object(); + } + } self.check_default_rules()?; self.update_function_table()?; diff --git a/src/value.rs b/src/value.rs index 5d07639..22510ed 100644 --- a/src/value.rs +++ b/src/value.rs @@ -87,8 +87,8 @@ pub enum Value { // Json data types. serde will automatically map json to these variants. Null, Bool(bool), - String(String), Number(Number), + String(String), Array(Rc>), Object(Rc>), diff --git a/tests/interpreter/cases/builtins/compare.yaml b/tests/interpreter/cases/builtins/compare.yaml index b1b7798..3260acc 100644 --- a/tests/interpreter/cases/builtins/compare.yaml +++ b/tests/interpreter/cases/builtins/compare.yaml @@ -2,233 +2,568 @@ # Licensed under the MIT License. cases: - - note: number-equals + - note: undefined data: {} modules: - | package test + import future.keywords.if + # All the following rules will be undefined. + x if false - v = 1 - r = [ - # Comparison with number - v == 1, - v == 2, - - # Comparison with primitives - v == null, - v == "hello", - v == true, - v == false, - - # Comparison with arrays - v == [], - v == [v], - - # Comparison with sets - v == set(), - v == { v }, - - # Comparison with objects - v == {}, - v == { "a": v}, - ] - sort_bindings: false + v1 = x == 1 + v2 = 1 == x + + v3 = x != 1 + v4 = 1 != x + + v5 = x < 1 + v6 = 1 < x + + v7 = x <= 1 + v8 = 1 <= x + + v9 = x > 1 + v10 = 1 > x + + v11 = x >= 1 + v12 = 1 >= x query: data.test - want_result: - v: 1 - r: [ true, false, false, false, false, false, - false, false, false, false, false, false] - - - note: null-equals + want_result: {} + + - note: eq data: {} modules: - | package test - v = null - r = [ - # Comparison with number - v == 1, - v == 2, - - # Comparison with primitives - v == null, - v == "hello", - v == true, - v == false, - - # Comparison with arrays - v == [], - v == [v], - - # Comparison with sets - v == set(), - v == { v }, - - # Comparison with objects - v == {}, - v == { "a": v}, - ] - sort_bindings: false - query: data.test - want_result: - v: null - r: [ false, false, true, false, false, false, - false, false, false, false, false, false] - - - note: string-equals - data: {} - modules: - - | - package test - - v = "hello" - r = [ - # Comparison with number - v == 1, - v == 2, - - # Comparison with primitives - v == null, - v == "hello", - v == true, - v == false, - - # Comparison with arrays - v == [], - v == [v], - - # Comparison with sets - v == set(), - v == { v }, - - # Comparison with objects - v == {}, - v == { "a": v}, - ] - sort_bindings: false - query: data.test - want_result: - v: "hello" - r: [ false, false, false, true, false, false, - false, false, false, false, false, false] - - - note: true-equals - data: {} - modules: - - | - package test - - v = true - r = [ - # Comparison with number - v == 1, - v == 2, - - # Comparison with primitives - v == null, - v == "hello", - v == true, - v == false, - - # Comparison with arrays - v == [], - v == [v], - - # Comparison with sets - v == set(), - v == { v }, - - # Comparison with objects - v == {}, - v == { "a": v}, - ] - sort_bindings: false - query: data.test - want_result: - v: true - r: [ false, false, false, false, true, false, - false, false, false, false, false, false] - - - - note: false-equals - data: {} - modules: - - | - package test - - v = false - r = [ - # Comparison with number - v == 1, # true - v == 1, # false - - # Comparison with primitives - v == null, - v == "hello", - v == true, - v == false, - - # Comparison with arrays - v == [], - v == [v], - - # Comparison with sets - v == set(), - v == { v }, - - # Comparison with objects - v == {}, - v == { "a": v}, - ] - sort_bindings: false - query: data.test - want_result: - v: false - r: [ false, false, false, false, false, true, - false, false, false, false, false, false] - - - note: undefined-equals - data: {} - modules: - - | - package test - import future.keywords - - v = 1 if false - r = [ - # Comparison with number - v == 1, - v == 2, - - # Comparison with primitives - v == null, - v == "hello", - v == true, - v == false, - - # Comparison with arrays - v == [], - v == [v], - - # Comparison with sets - v == set(), - v == { v }, - - # Comparison with objects - v == {}, - v == { "a": v}, + # true + values = [ + 1, + -1, + null, + "hello", + `hello`, + true, + false, + [], + [1, 2, 3], + [4,"abc", null], + set(), + {1, 2, 3}, + {4, "abc", null}, + {}, + {"a" : 1}, ] - r2 = 1 if v - - # This variable should appear in output since it is not undefined. - ok = true - - sort_bindings: false - query: data.test + values_1 = values + # Evaluate rule so that rule[x] = [ values equal to x ] + results[x] = y { + x = values[_] + y = [ z | + z := values_1[_] + x == z + ] + } + query: data.test.results want_result: - ok: true + object!: + - key: 1 + value: [ 1 ] + - key: -1 + value: [ -1 ] + - key: null + value: [null] + - key: "hello" + value: ["hello", "hello"] + - key: true + value: [true] + - key: false + value: [false] + - key: [] + value: [[]] + - key: [1, 2, 3] + value: [[1, 2, 3]] + - key: [4, "abc", null] + value: [[4, "abc", null]] + - key: + set!: [] + value: + - set!: [] + - key: + set!: [ 1, 2, 3] + value: + - set!: [1, 2, 3] + - key: + set!: [4, "abc", null] + value: + - set!: [4, "abc", null] + - key: + object!: [] + value: + - object!: [] + - key: + object!: + - key: a + value: 1 + value: + - a: 1 - - + - note: eq-undefined + data: {} + modules: + - | + package test + import future.keywords.if + # All the following rules will be undefined. + x if false + y = x == 1 + z = 1 == x + query: data.test + want_result: {} + + - note: ne + data: {} + modules: + - | + package test + + # true + values = [ + 1, + -1, + null, + "hello", + `hello`, + true, + false, + [], + [1, 2, 3], + [4,"abc", null], + set(), + {1, 2, 3}, + {4, "abc", null}, + {}, + {"a" : 1}, + ] + + values_1 = values + # Evaluate rule so that rule[x] = [ values equal to x ] + results[x] = y { + x = values[_] + y = [ z | + z := values_1[_] + # Use != and ne + not x != z + ] + } + query: data.test.results + want_result: + object!: + - key: 1 + value: [ 1 ] + - key: -1 + value: [ -1 ] + - key: null + value: [null] + - key: "hello" + value: ["hello", "hello"] + - key: true + value: [true] + - key: false + value: [false] + - key: [] + value: [[]] + - key: [1, 2, 3] + value: [[1, 2, 3]] + - key: [4, "abc", null] + value: [[4, "abc", null]] + - key: + set!: [] + value: + - set!: [] + - key: + set!: [ 1, 2, 3] + value: + - set!: [1, 2, 3] + - key: + set!: [4, "abc", null] + value: + - set!: [4, "abc", null] + - key: + object!: [] + value: + - object!: [] + - key: + object!: + - key: a + value: 1 + value: + - a: 1 + + + - note: order + data: {} + modules: + - | + package test + + v = [ + null, + false, + 0, + "", + [], + {}, + set() + ] + + results = { + "lt" : { x : y | + x = v[_] + y = [z | z = v[_]; x < z] + }, + "le" : { x : y | + x = v[_] + y = [z | z = v[_]; x <= z] + }, + "gt" : { x : y | + x = v[_] + y = [z | z = v[_]; x > z] + }, + "ge" : { x : y | + x = v[_] + y = [z | z = v[_]; x >= z] + } + } + query: data.test.results + want_result: + lt: + object!: + - key: null + value: [false, 0, "", [], {}, {"set!":[]}] + - key: false + value: [0, "", [], {}, {"set!":[]}] + - key: 0 + value: ["", [], {}, {"set!":[]}] + - key: "" + value: [[], {}, {"set!":[]}] + - key: [] + value: [{}, {"set!":[]}] + - key: {} + value: [{"set!":[]}] + - key: {"set!":[]} + value: [] + le: + object!: + - key: null + value: [null, false, 0, "", [], {}, {"set!":[]}] + - key: false + value: [false, 0, "", [], {}, {"set!":[]}] + - key: 0 + value: [0, "", [], {}, {"set!":[]}] + - key: "" + value: ["", [], {}, {"set!":[]}] + - key: [] + value: [[], {}, {"set!":[]}] + - key: {} + value: [{}, {"set!":[]}] + - key: {"set!":[]} + value: [{"set!":[]}] + gt: + object!: + - key: null + value: [] + - key: false + value: [null] + - key: 0 + value: [null, false] + - key: "" + value: [null, false, 0] + - key: [] + value: [null, false, 0, ""] + - key: {} + value: [null, false, 0, "", []] + - key: {"set!":[]} + value: [null, false, 0, "", [], {}] + ge: + object!: + - key: null + value: [null] + - key: false + value: [null, false] + - key: 0 + value: [null, false, 0] + - key: "" + value: [null, false, 0, ""] + - key: [] + value: [null, false, 0, "", []] + - key: {} + value: [null, false, 0, "", [], {}] + - key: {"set!":[]} + value: [null, false, 0, "", [], {}, {"set!":[]}] + + - note: arrays + data: {} + modules: + - | + package test + + v = [ [], [1, 2], [0, 1, 2], [1, 2, 3]] + + results = { + "lt" : { x : y | + x = v[_] + y = [z | z = v[_]; x < z] + }, + "le" : { x : y | + x = v[_] + y = [z | z = v[_]; x <= z] + }, + "gt" : { x : y | + x = v[_] + y = [z | z = v[_]; x > z] + }, + "ge" : { x : y | + x = v[_] + y = [z | z = v[_]; x >= z] + } + } + query: data.test.results + want_result: + lt: + object!: + - key: [] + value: [[1,2], [0, 1, 2], [1, 2, 3]] + - key: [1, 2] + value: [[1, 2, 3]] + - key: [0, 1, 2] + value: [ [1,2], [1, 2, 3]] + - key: [1, 2, 3] + value: [] + le: + object!: + - key: [] + value: [[], [1,2], [0, 1, 2], [1, 2, 3]] + - key: [1, 2] + value: [[1, 2], [1, 2, 3]] + - key: [0, 1, 2] + value: [[1,2], [0, 1, 2], [1, 2, 3]] + - key: [1, 2, 3] + value: [[1, 2, 3]] + gt: + object!: + - key: [] + value: [] + - key: [1, 2] + value: [[], [0, 1, 2]] + - key: [0, 1, 2] + value: [[]] + - key: [1, 2, 3] + value: [[], [1, 2], [0, 1, 2]] + ge: + object!: + - key: [] + value: [[]] + - key: [1, 2] + value: [[], [1, 2], [0, 1, 2]] + - key: [0, 1, 2] + value: [[], [0, 1, 2]] + - key: [1, 2, 3] + value: [[], [1, 2], [0, 1, 2], [1, 2, 3]] + + - note: sets + data: {} + modules: + - | + package test + + v = [ set(), {1, 2}, {0, 1, 2}, {1, 2, 3}] + + results = { + "lt" : { x : y | + x = v[_] + y = [z | z = v[_]; x < z] + }, + "le" : { x : y | + x = v[_] + y = [z | z = v[_]; x <= z] + }, + "gt" : { x : y | + x = v[_] + y = [z | z = v[_]; x > z] + }, + "ge" : { x : y | + x = v[_] + y = [z | z = v[_]; x >= z] + } + } + query: data.test.results + want_result: + lt: + object!: + - key: + set!: [] + value: + - set!: [1,2] + - set!: [0, 1, 2] + - set!: [1, 2, 3] + - key: + set!: [1, 2] + value: + - set!: [1, 2, 3] + - key: + set!: [0, 1, 2] + value: + - set!: [1,2] + - set!: [1, 2, 3] + - key: + set!: [1, 2, 3] + value: [] + le: + object!: + - key: + set!: [] + value: + - set!: [] + - set!: [1,2] + - set!: [0, 1, 2] + - set!: [1, 2, 3] + - key: + set!: [1, 2] + value: + - set!: [1, 2] + - set!: [1, 2, 3] + - key: + set!: [0, 1, 2] + value: + - set!: [1,2] + - set!: [0, 1, 2] + - set!: [1, 2, 3] + - key: + set!: [1, 2, 3] + value: + - set!: [1, 2, 3] + gt: + object!: + - key: + set!: [] + value: [] + - key: + set!: [1, 2] + value: + - set!: [] + - set!: [0, 1, 2] + - key: + set!: [0, 1, 2] + value: + - set!: [] + - key: + set!: [1, 2, 3] + value: + - set!: [] + - set!: [1, 2] + - set!: [0, 1, 2] + ge: + object!: + - key: + set!: [] + value: + - set!: [] + - key: + set!: [1, 2] + value: + - set!: [] + - set!: [1, 2] + - set!: [0, 1, 2] + - key: + set!: [0, 1, 2] + value: + - set!: [] + - set!: [0, 1, 2] + - key: + set!: [1, 2, 3] + value: + - set!: [] + - set!: [1, 2] + - set!: [0, 1, 2] + - set!: [1, 2, 3] + + - note: objects + data: {} + modules: + - | + package test + + v = [{}, {"a":1}, {"a":2}, {"a":1, "b":0}, {"b":0}] + + results = { + "lt" : { x : y | + x = v[_] + y = [z | z = v[_]; x < z] + }, + "le" : { x : y | + x = v[_] + y = [z | z = v[_]; x <= z] + }, + "gt" : { x : y | + x = v[_] + y = [z | z = v[_]; x > z] + }, + "ge" : { x : y | + x = v[_] + y = [z | z = v[_]; x >= z] + } + } + query: data.test.results + want_result: + lt: + object!: + - key: {} + value: [ {"a":1}, {"a":2}, {"a":1, "b":0}, {"b":0}] + - key: {"a":1} + value: [{"a":2}, {"a":1, "b":0}, {"b":0}] + - key: {"a":2} + value: [{"b":0}] + - key: {"a":1, "b":0} + value: [{"a":2}, {"b":0}] + - key: {"b":0} + value: [] + le: + object!: + - key: {} + value: [ {}, {"a":1}, {"a":2}, {"a":1, "b":0}, {"b":0}] + - key: {"a":1} + value: [{"a":1}, {"a":2}, {"a":1, "b":0}, {"b":0}] + - key: {"a":2} + value: [{"a":2}, {"b":0}] + - key: {"a":1, "b":0} + value: [{"a":2}, {"a":1, "b":0}, {"b":0}] + - key: {"b":0} + value: [{"b":0}] + gt: + object!: + - key: {} + value: [] + - key: {"a":1} + value: [{}] + - key: {"a":2} + value: [{}, {"a":1}, {"a":1, "b":0}] + - key: {"a":1, "b":0} + value: [{}, {"a":1}] + - key: {"b":0} + value: [ {}, {"a":1}, {"a":2}, {"a":1, "b":0}] + ge: + object!: + - key: {} + value: [{}] + - key: {"a":1} + value: [{}, {"a":1}] + - key: {"a":2} + value: [{}, {"a":1}, {"a":2}, {"a":1, "b":0}] + - key: {"a":1, "b":0} + value: [{}, {"a":1}, {"a":1, "b":0}] + - key: {"b":0} + value: [ {}, {"a":1}, {"a":2}, {"a":1, "b":0}, {"b":0}] + +