Merge pull request #7 from anakrish/main

Implement comparison operators. Formalize semantics.
This commit is contained in:
Ming-Wei Shih
2023-02-13 13:23:00 -08:00
committed by GitHub
6 changed files with 641 additions and 254 deletions

View File

@@ -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),
}
}

View File

@@ -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<Value> {
// 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,
}))
}

View File

@@ -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::*;

View File

@@ -262,7 +262,7 @@ impl<'source> Interpreter<'source> {
) -> Result<Value> {
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()?;

View File

@@ -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<Vec<Value>>),
Object(Rc<BTreeMap<Value, Value>>),

View File

@@ -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}]