mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Lock down numbers
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
committed by
Anand Krishnamoorthi
parent
a8a1d4b820
commit
12c3d26476
@@ -13,6 +13,7 @@ serde_json = "1.0.89"
|
||||
log = "0.4.17"
|
||||
env_logger="0.10.0"
|
||||
lazy_static = "1.4.0"
|
||||
rand = "0.8.5"
|
||||
|
||||
[dev-dependencies]
|
||||
serde_yaml = "0.9.16"
|
||||
|
||||
@@ -41,11 +41,6 @@ use anyhow::Result;
|
||||
/// * `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.
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
pub mod comparison;
|
||||
pub mod numbers;
|
||||
pub mod sets;
|
||||
pub mod utils;
|
||||
|
||||
use crate::ast::Expr;
|
||||
use crate::lexer::Span;
|
||||
@@ -26,8 +27,20 @@ lazy_static! {
|
||||
m.insert("ceil", numbers::ceil);
|
||||
m.insert("floor", numbers::floor);
|
||||
m.insert("numbers.range", numbers::range);
|
||||
m.insert("round", numbers::round);
|
||||
m.insert("rand.intn", numbers::intn);
|
||||
m.insert("round", numbers::round);
|
||||
|
||||
// sets
|
||||
m.insert("intersection", sets::intersection_of_set_of_sets);
|
||||
m.insert("union", sets::union_of_set_of_sets);
|
||||
|
||||
m
|
||||
};
|
||||
}
|
||||
|
||||
pub fn must_cache(path: &str) -> Option<&'static str> {
|
||||
match path {
|
||||
"rand.intn" => Some("rand.intn"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,43 +2,12 @@
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::ast::{ArithOp, Expr};
|
||||
use crate::builtins::utils::{ensure_args_count, ensure_numeric, ensure_string};
|
||||
use crate::lexer::Span;
|
||||
use crate::value::{Float, Value};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
fn ensure_args_count(
|
||||
span: &Span,
|
||||
fcn: &'static str,
|
||||
params: &[Expr],
|
||||
args: &[Value],
|
||||
expected: usize,
|
||||
) -> Result<()> {
|
||||
if args.len() != expected {
|
||||
let span = match args.len() > expected {
|
||||
false => span,
|
||||
true => params[args.len() - 1].span(),
|
||||
};
|
||||
if expected == 1 {
|
||||
bail!(span.error(format!("`{fcn}` expects 1 argument").as_str()))
|
||||
} else {
|
||||
bail!(span.error(format!("`{fcn}` expects {expected} arguments").as_str()))
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_numeric(fcn: &str, arg: &Expr, v: &Value) -> Result<Float> {
|
||||
Ok(match &v {
|
||||
Value::Number(n) => n.0 .0,
|
||||
_ => {
|
||||
let span = arg.span();
|
||||
bail!(
|
||||
span.error(format!("`{fcn}` expects numeric argument. Got `{v}` instead").as_str())
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
use anyhow::Result;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
pub fn abs(span: &Span, params: &[Expr], args: &[Value]) -> Result<Value> {
|
||||
ensure_args_count(span, "abs", params, args, 1)?;
|
||||
@@ -92,6 +61,25 @@ pub fn round(span: &Span, params: &[Expr], args: &[Value]) -> Result<Value> {
|
||||
))
|
||||
}
|
||||
|
||||
pub fn intn(span: &Span, params: &[Expr], args: &[Value]) -> Result<Value> {
|
||||
let fcn = "rand.intn";
|
||||
ensure_args_count(span, fcn, params, args, 2)?;
|
||||
let _ = ensure_string(fcn, ¶ms[0], &args[0])?;
|
||||
let n = ensure_numeric(fcn, ¶ms[0], &args[1])?;
|
||||
if n != n.floor() || n < 0 as Float {
|
||||
return Ok(Value::Undefined);
|
||||
}
|
||||
|
||||
if n == 0.0 {
|
||||
return Ok(Value::from_float(0 as Float));
|
||||
}
|
||||
|
||||
// TODO: bounds checking; arbitrary precision
|
||||
let mut rng = thread_rng();
|
||||
let v = rng.gen_range(0..n as u64);
|
||||
Ok(Value::from_float(v as f64))
|
||||
}
|
||||
|
||||
pub fn arithmetic_operation(
|
||||
op: &ArithOp,
|
||||
expr1: &Expr,
|
||||
@@ -99,10 +87,6 @@ pub fn arithmetic_operation(
|
||||
v1: Value,
|
||||
v2: Value,
|
||||
) -> Result<Value> {
|
||||
if v1 == Value::Undefined || v2 == Value::Undefined {
|
||||
return Ok(Value::Undefined);
|
||||
}
|
||||
|
||||
let op_name = format!("{:?}", op).to_lowercase();
|
||||
let v1 = ensure_numeric(op_name.as_str(), expr1, &v1)?;
|
||||
let v2 = ensure_numeric(op_name.as_str(), expr2, &v2)?;
|
||||
@@ -114,6 +98,8 @@ pub fn arithmetic_operation(
|
||||
ArithOp::Div if v2 == 0.0 => return Ok(Value::Undefined),
|
||||
ArithOp::Div => v1 / v2,
|
||||
ArithOp::Mod if v2 == 0.0 => return Ok(Value::Undefined),
|
||||
ArithOp::Mod if v1.floor() != v1 => return Ok(Value::Undefined),
|
||||
ArithOp::Mod if v2.floor() != v2 => return Ok(Value::Undefined),
|
||||
ArithOp::Mod => v1 % v2,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -2,25 +2,76 @@
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::ast::Expr;
|
||||
use crate::builtins::utils::{ensure_args_count, ensure_set};
|
||||
use crate::lexer::Span;
|
||||
use crate::value::Value;
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::rc::Rc;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
fn ensure_set(fcn: &str, arg: &Expr, v: Value) -> Result<Rc<BTreeSet<Value>>> {
|
||||
Ok(match v {
|
||||
Value::Set(s) => s,
|
||||
_ => {
|
||||
let span = arg.span();
|
||||
bail!(span.error(format!("`{fcn}` expects set argument. Got `{v}` instead").as_str()))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn difference(expr1: &Expr, expr2: &Expr, v1: Value, v2: Value) -> Result<Value> {
|
||||
let s1 = ensure_set("difference", expr1, v1)?;
|
||||
let s2 = ensure_set("difference", expr2, v2)?;
|
||||
Ok(Value::from_set(s1.difference(&s2).cloned().collect()))
|
||||
}
|
||||
|
||||
pub fn intersection(expr1: &Expr, expr2: &Expr, v1: Value, v2: Value) -> Result<Value> {
|
||||
let s1 = ensure_set("intersection", expr1, v1)?;
|
||||
let s2 = ensure_set("intersection", expr2, v2)?;
|
||||
Ok(Value::from_set(s1.intersection(&s2).cloned().collect()))
|
||||
}
|
||||
|
||||
pub fn union(expr1: &Expr, expr2: &Expr, v1: Value, v2: Value) -> Result<Value> {
|
||||
let s1 = ensure_set("union", expr1, v1)?;
|
||||
let s2 = ensure_set("union", expr2, v2)?;
|
||||
Ok(Value::from_set(s1.union(&s2).cloned().collect()))
|
||||
}
|
||||
|
||||
pub fn intersection_of_set_of_sets(span: &Span, params: &[Expr], args: &[Value]) -> Result<Value> {
|
||||
let name = "intersection";
|
||||
ensure_args_count(span, name, params, args, 1)?;
|
||||
let set = ensure_set(name, ¶ms[0], args[0].clone())?;
|
||||
|
||||
let mut res = BTreeSet::new();
|
||||
let mut first = true;
|
||||
|
||||
for s in set.iter() {
|
||||
let s = match s {
|
||||
Value::Set(s) => s,
|
||||
_ => bail!(
|
||||
span.error(format!("`{name}` expects set of sets. Got `{}`", args[0]).as_str())
|
||||
),
|
||||
};
|
||||
|
||||
if first {
|
||||
res = (**s).clone();
|
||||
first = false;
|
||||
} else {
|
||||
res = res.intersection(s).cloned().collect();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::from_set(res))
|
||||
}
|
||||
|
||||
pub fn union_of_set_of_sets(span: &Span, params: &[Expr], args: &[Value]) -> Result<Value> {
|
||||
let name = "union";
|
||||
ensure_args_count(span, name, params, args, 1)?;
|
||||
let set = ensure_set(name, ¶ms[0], args[0].clone())?;
|
||||
|
||||
let mut res = BTreeSet::new();
|
||||
|
||||
for s in set.iter() {
|
||||
let s = match s {
|
||||
Value::Set(s) => s,
|
||||
_ => bail!(
|
||||
span.error(format!("`{name}` expects set of sets. Got `{}`", args[0]).as_str())
|
||||
),
|
||||
};
|
||||
|
||||
res = res.union(s).cloned().collect();
|
||||
}
|
||||
|
||||
Ok(Value::from_set(res))
|
||||
}
|
||||
|
||||
19
src/builtins/strings.rs
Normal file
19
src/builtins/strings.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::value::Value;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
fn ensure_numeric(fcn: &str, arg: &Expr, v: &Value) -> Result<Float> {
|
||||
Ok(match &v {
|
||||
Value::Number(n) => n.0 .0,
|
||||
_ => {
|
||||
let span = arg.span();
|
||||
bail!(
|
||||
span.error(format!("`{fcn}` expects numeric argument. Got `{v}` instead").as_str())
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
64
src/builtins/utils.rs
Normal file
64
src/builtins/utils.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
use crate::ast::Expr;
|
||||
use crate::lexer::Span;
|
||||
use crate::value::{Float, Value};
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::rc::Rc;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
pub fn ensure_args_count(
|
||||
span: &Span,
|
||||
fcn: &'static str,
|
||||
params: &[Expr],
|
||||
args: &[Value],
|
||||
expected: usize,
|
||||
) -> Result<()> {
|
||||
if args.len() != expected {
|
||||
let span = match args.len() > expected {
|
||||
false => span,
|
||||
true => params[args.len() - 1].span(),
|
||||
};
|
||||
if expected == 1 {
|
||||
bail!(span.error(format!("`{fcn}` expects 1 argument").as_str()))
|
||||
} else {
|
||||
bail!(span.error(format!("`{fcn}` expects {expected} arguments").as_str()))
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn ensure_numeric(fcn: &str, arg: &Expr, v: &Value) -> Result<Float> {
|
||||
Ok(match &v {
|
||||
Value::Number(n) => n.0 .0,
|
||||
_ => {
|
||||
let span = arg.span();
|
||||
bail!(
|
||||
span.error(format!("`{fcn}` expects numeric argument. Got `{v}` instead").as_str())
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ensure_string(fcn: &str, arg: &Expr, v: &Value) -> Result<String> {
|
||||
Ok(match &v {
|
||||
Value::String(s) => s.clone(),
|
||||
_ => {
|
||||
let span = arg.span();
|
||||
bail!(span.error(format!("`{fcn}` expects string argument. Got `{v}` instead").as_str()))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ensure_set(fcn: &str, arg: &Expr, v: Value) -> Result<Rc<BTreeSet<Value>>> {
|
||||
Ok(match v {
|
||||
Value::Set(s) => s,
|
||||
_ => {
|
||||
let span = arg.span();
|
||||
bail!(span.error(format!("`{fcn}` expects set argument. Got `{v}` instead").as_str()))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -29,7 +29,7 @@ pub struct Interpreter<'source> {
|
||||
default_rules: HashMap<String, Vec<(&'source Rule<'source>, Option<String>)>>,
|
||||
processed: BTreeSet<&'source Rule<'source>>,
|
||||
active_rules: Vec<&'source Rule<'source>>,
|
||||
intns: BTreeMap<Vec<Value>, Value>,
|
||||
builtins_cache: BTreeMap<(&'static str, Vec<Value>), Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -62,7 +62,7 @@ impl<'source> Interpreter<'source> {
|
||||
default_rules: HashMap::new(),
|
||||
processed: BTreeSet::new(),
|
||||
active_rules: vec![],
|
||||
intns: BTreeMap::new(),
|
||||
builtins_cache: BTreeMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -264,6 +264,11 @@ impl<'source> Interpreter<'source> {
|
||||
) -> Result<Value> {
|
||||
let lhs = self.eval_expr(lhs_expr)?;
|
||||
let rhs = self.eval_expr(rhs_expr)?;
|
||||
|
||||
if lhs == Value::Undefined || rhs == Value::Undefined {
|
||||
return Ok(Value::Undefined);
|
||||
}
|
||||
|
||||
builtins::comparison::compare(op, &lhs, &rhs)
|
||||
}
|
||||
|
||||
@@ -273,30 +278,17 @@ impl<'source> Interpreter<'source> {
|
||||
lhs: &'source Expr<'source>,
|
||||
rhs: &'source Expr<'source>,
|
||||
) -> Result<Value> {
|
||||
let lhs = self.eval_expr(lhs)?;
|
||||
let rhs = self.eval_expr(rhs)?;
|
||||
let lhs_value = self.eval_expr(lhs)?;
|
||||
let rhs_value = self.eval_expr(rhs)?;
|
||||
|
||||
let lhs = if let Value::Set(set) = lhs {
|
||||
set
|
||||
} else {
|
||||
return Err(anyhow!("expect {:?} to be a set", lhs));
|
||||
};
|
||||
if lhs_value == Value::Undefined || rhs_value == Value::Undefined {
|
||||
return Ok(Value::Undefined);
|
||||
}
|
||||
|
||||
let rhs = if let Value::Set(set) = rhs {
|
||||
set
|
||||
} else {
|
||||
return Err(anyhow!("expect {:?} to be a set", rhs));
|
||||
};
|
||||
|
||||
info!(
|
||||
"eval_bin_expr, op: {:?}, lhs: {:?}, rhs: {:?}",
|
||||
op, lhs, rhs
|
||||
);
|
||||
|
||||
Ok(Value::from_set(match op {
|
||||
BinOp::Or => lhs.union(&rhs).cloned().collect(),
|
||||
BinOp::And => lhs.intersection(&rhs).cloned().collect(),
|
||||
}))
|
||||
match op {
|
||||
BinOp::Or => builtins::sets::union(lhs, rhs, lhs_value, rhs_value),
|
||||
BinOp::And => builtins::sets::intersection(lhs, rhs, lhs_value, rhs_value),
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_arith_expr(
|
||||
@@ -308,6 +300,10 @@ impl<'source> Interpreter<'source> {
|
||||
let lhs_value = self.eval_expr(lhs)?;
|
||||
let rhs_value = self.eval_expr(rhs)?;
|
||||
|
||||
if lhs_value == Value::Undefined || rhs_value == Value::Undefined {
|
||||
return Ok(Value::Undefined);
|
||||
}
|
||||
|
||||
match (op, &lhs_value, &rhs_value) {
|
||||
(ArithOp::Sub, Value::Set(_), _) | (ArithOp::Sub, _, Value::Set(_)) => {
|
||||
builtins::sets::difference(lhs, rhs, lhs_value, rhs_value)
|
||||
@@ -904,13 +900,23 @@ impl<'source> Interpreter<'source> {
|
||||
) -> Result<Value> {
|
||||
let mut args = vec![];
|
||||
for p in params {
|
||||
args.push(self.eval_expr(p)?);
|
||||
match self.eval_expr(p)? {
|
||||
// If any argument is undefined, then the call is undefined.
|
||||
Value::Undefined => return Ok(Value::Undefined),
|
||||
p => args.push(p),
|
||||
}
|
||||
}
|
||||
|
||||
let cache = builtins::must_cache(name.as_str());
|
||||
if let Some(name) = &cache {
|
||||
if let Some(v) = self.builtins_cache.get(&(name, args.clone())) {
|
||||
return Ok(v.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let is_randn = name == "rand.intn";
|
||||
let v = builtin(span, ¶ms[..], &args[..])?;
|
||||
if is_randn {
|
||||
self.intns.insert(args, v.clone());
|
||||
if let Some(name) = cache {
|
||||
self.builtins_cache.insert((name, args), v.clone());
|
||||
}
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
@@ -524,6 +524,12 @@ impl<'source> Parser<'source> {
|
||||
}
|
||||
"(" if possible_fcn => {
|
||||
self.next_token()?;
|
||||
if self.tok.1.text() == ")" {
|
||||
return Err(self
|
||||
.tok
|
||||
.1
|
||||
.error("at least one argument required for function calls"));
|
||||
}
|
||||
let mut args = vec![self.parse_in_expr()?];
|
||||
while self.tok.1.text() == "," {
|
||||
self.next_token()?;
|
||||
|
||||
@@ -7,10 +7,15 @@ cases:
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x = abs(-9)
|
||||
query: data.test.x
|
||||
want_result: 9
|
||||
|
||||
x = [abs(-9), abs(9), abs(-9.1), abs(9.1)]
|
||||
|
||||
# Undefined
|
||||
y { false }
|
||||
z = abs(y)
|
||||
query: data.test
|
||||
want_result:
|
||||
x: [9, 9, 9.1, 9.1]
|
||||
|
||||
- note: abs-extra-args
|
||||
data: {}
|
||||
modules:
|
||||
@@ -19,7 +24,7 @@ cases:
|
||||
x = abs(-9, 10)
|
||||
query: data.test.x
|
||||
error: "`abs` expects 1 argument"
|
||||
|
||||
|
||||
- note: abs-invalid-type
|
||||
data: {}
|
||||
modules:
|
||||
@@ -34,10 +39,14 @@ cases:
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x = [ceil(9.1), ceil(-9.1)]
|
||||
query: data.test.x
|
||||
want_result: [10, -9]
|
||||
|
||||
x = [ceil(9.1), ceil(-9.1), ceil(9), ceil(-9)]
|
||||
# Undefined
|
||||
y { false }
|
||||
z = ceil(y)
|
||||
query: data.test
|
||||
want_result:
|
||||
x: [10, -9, 9, -9]
|
||||
|
||||
- note: ceil-extra-args
|
||||
data: {}
|
||||
modules:
|
||||
@@ -46,7 +55,7 @@ cases:
|
||||
x = ceil(-9, 10)
|
||||
query: data.test.x
|
||||
error: "`ceil` expects 1 argument"
|
||||
|
||||
|
||||
- note: ceil-invalid-type
|
||||
data: {}
|
||||
modules:
|
||||
@@ -55,16 +64,20 @@ cases:
|
||||
x = ceil("-9")
|
||||
query: data.test.x
|
||||
error: "`ceil` expects numeric argument"
|
||||
|
||||
|
||||
- note: floor
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x = [floor(9.1), floor(-9.1)]
|
||||
query: data.test.x
|
||||
want_result: [9, -10]
|
||||
|
||||
x = [floor(9.1), floor(-9.1), floor(9), floor(-9)]
|
||||
# Undefined
|
||||
y { false }
|
||||
z = floor(y)
|
||||
query: data.test
|
||||
want_result:
|
||||
x: [9, -10, 9, -9]
|
||||
|
||||
- note: floor-extra-args
|
||||
data: {}
|
||||
modules:
|
||||
@@ -73,7 +86,7 @@ cases:
|
||||
x = floor(-9, 10)
|
||||
query: data.test.x
|
||||
error: "`floor` expects 1 argument"
|
||||
|
||||
|
||||
- note: floor-invalid-type
|
||||
data: {}
|
||||
modules:
|
||||
@@ -82,7 +95,7 @@ cases:
|
||||
x = floor("-9")
|
||||
query: data.test.x
|
||||
error: "`floor` expects numeric argument"
|
||||
|
||||
|
||||
- note: numbers.range
|
||||
data: {}
|
||||
modules:
|
||||
@@ -93,19 +106,24 @@ cases:
|
||||
r3 = numbers.range(-1, -5)
|
||||
r4 = numbers.range(-5, -1)
|
||||
|
||||
# Non-integer start and end result in Undefined.
|
||||
r5 = numbers.range(1.01, 5)
|
||||
r6 = numbers.range(1, 5.01)
|
||||
|
||||
# Single item range
|
||||
r7 = numbers.range(8, 8)
|
||||
r5 = numbers.range(8, 8)
|
||||
|
||||
# Non-integer start and end result in Undefined.
|
||||
r6 = numbers.range(1.01, 5)
|
||||
r7 = numbers.range(1, 5.01)
|
||||
|
||||
y { false }
|
||||
r8 = numbers.range(y, 10)
|
||||
r9 = numbers.range(10, y)
|
||||
|
||||
query: data.test
|
||||
want_result:
|
||||
r1: [1, 2, 3, 4, 5]
|
||||
r2: [5, 4, 3, 2, 1]
|
||||
r3: [-1, -2, -3, -4, -5]
|
||||
r4: [-5, -4, -3, -2, -1]
|
||||
r7: [8]
|
||||
r5: [8]
|
||||
|
||||
- note: numbers.range-less-args
|
||||
data: {}
|
||||
@@ -115,7 +133,7 @@ cases:
|
||||
x = numbers.range(1)
|
||||
query: data.test.x
|
||||
error: "`numbers.range` expects 2 arguments"
|
||||
|
||||
|
||||
- note: numbers.range-more-args
|
||||
data: {}
|
||||
modules:
|
||||
@@ -124,7 +142,7 @@ cases:
|
||||
x = numbers.range(1, 2, 3)
|
||||
query: data.test.x
|
||||
error: "`numbers.range` expects 2 arguments"
|
||||
|
||||
|
||||
- note: numbers.range-invalid-start
|
||||
data: {}
|
||||
modules:
|
||||
@@ -133,7 +151,7 @@ cases:
|
||||
x = numbers.range("1", 2)
|
||||
query: data.test.x
|
||||
error: "`numbers.range` expects numeric argument"
|
||||
|
||||
|
||||
- note: numbers.range-invalid-end
|
||||
data: {}
|
||||
modules:
|
||||
@@ -149,10 +167,15 @@ cases:
|
||||
- |
|
||||
package test
|
||||
x = [round(-9.4), round(-9.5), round(-9.6),
|
||||
round(9.4), round(9.5), round(9.6)]
|
||||
round(9.4), round(9.5), round(9.6),
|
||||
round(8), round(-8)]
|
||||
|
||||
# Undefined
|
||||
y { false }
|
||||
z = round(y)
|
||||
query: data.test.x
|
||||
want_result: [-9, -10, -10, 9, 10, 10]
|
||||
|
||||
want_result: [-9, -10, -10, 9, 10, 10, 8, -8]
|
||||
|
||||
- note: round-extra-args
|
||||
data: {}
|
||||
modules:
|
||||
@@ -161,7 +184,7 @@ cases:
|
||||
x = round(-9, 10)
|
||||
query: data.test.x
|
||||
error: "`round` expects 1 argument"
|
||||
|
||||
|
||||
- note: round-invalid-type
|
||||
data: {}
|
||||
modules:
|
||||
@@ -171,15 +194,96 @@ cases:
|
||||
query: data.test.x
|
||||
error: "`round` expects numeric argument"
|
||||
|
||||
- note: rand.intn
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x = rand.intn("x", 50000)
|
||||
y = rand.intn("y", 50000)
|
||||
z = [p |
|
||||
p := rand.intn("x", 50000)
|
||||
]
|
||||
a = rand.intn("x", 25000)
|
||||
|
||||
results = [
|
||||
x == z[0],
|
||||
x != y,
|
||||
x != a,
|
||||
rand.intn("b", 0)
|
||||
]
|
||||
query: data.test.results
|
||||
want_result: [true, true, true, 0]
|
||||
|
||||
- note: rand.intn-undefined
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
y { false }
|
||||
r1 = rand.intn(y, 10)
|
||||
r2 = rand.intn(10, y)
|
||||
r3 = rand.intn("a", 10.3)
|
||||
query: data.test
|
||||
want_result: {}
|
||||
|
||||
- note: rand.intn-extra-args
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x = rand.intn("abc", 10, 11)
|
||||
query: data.test.x
|
||||
error: "`rand.intn` expects 2 arguments"
|
||||
|
||||
- note: rand.intn-less-args
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x = rand.intn("abc")
|
||||
query: data.test.x
|
||||
error: "`rand.intn` expects 2 arguments"
|
||||
|
||||
- note: rand.intn-invalid-type-1
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x = rand.intn(1, 2)
|
||||
query: data.test.x
|
||||
error: "`rand.intn` expects string argument"
|
||||
|
||||
- note: rand.intn-invalid-type-2
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x = rand.intn("a", "b")
|
||||
query: data.test.x
|
||||
error: "`rand.intn` expects numeric argument"
|
||||
|
||||
|
||||
- note: div
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import future.keywords.if
|
||||
|
||||
x = 1/3
|
||||
query: data.test.x
|
||||
want_result: 0.3333333333333333
|
||||
|
||||
|
||||
# Undefined
|
||||
y if false
|
||||
a = 1 / 0
|
||||
b = y / 1
|
||||
c = 1 / y
|
||||
d = 13.3 % 3
|
||||
e = 13 % 3.1
|
||||
query: data.test
|
||||
want_result:
|
||||
x: 0.3333333333333333
|
||||
|
||||
- note: div-non-numeric
|
||||
data: {}
|
||||
modules:
|
||||
@@ -188,36 +292,23 @@ cases:
|
||||
x = "1" / 9
|
||||
query: data.test.x
|
||||
error: "`div` expects numeric argument."
|
||||
|
||||
- note: div-undefined
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import future.keywords.if
|
||||
a if false
|
||||
x = a / 1
|
||||
query: data.test
|
||||
want_result: {}
|
||||
|
||||
- note: div-by-zero
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x = 1/0
|
||||
query: data.test
|
||||
want_result: {}
|
||||
|
||||
- note: mod
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x = 10%3
|
||||
query: data.test.x
|
||||
want_result: 1
|
||||
|
||||
|
||||
# Undefined
|
||||
y { false }
|
||||
a = 1 % 0
|
||||
b = y % 1
|
||||
c = 1 % y
|
||||
query: data.test
|
||||
want_result:
|
||||
x : 1
|
||||
|
||||
- note: mod-non-numeric
|
||||
data: {}
|
||||
modules:
|
||||
@@ -226,14 +317,13 @@ cases:
|
||||
x = "1" % 9
|
||||
query: data.test.x
|
||||
error: "`mod` expects numeric argument."
|
||||
|
||||
|
||||
- note: mod-undefined
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import future.keywords.if
|
||||
a if false
|
||||
a { false }
|
||||
x = a % 10
|
||||
query: data.test
|
||||
want_result: {}
|
||||
@@ -246,7 +336,76 @@ cases:
|
||||
x = 1%0
|
||||
query: data.test
|
||||
want_result: {}
|
||||
|
||||
|
||||
- note: mul
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x = 3 * -1 * 4.5
|
||||
# Undefined
|
||||
y { false }
|
||||
a = 1 * y
|
||||
b = y * 1
|
||||
query: data.test
|
||||
want_result:
|
||||
x: -13.5
|
||||
|
||||
- note: mul-non-numeric
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x = "1" * 9
|
||||
query: data.test.x
|
||||
error: "`mul` expects numeric argument."
|
||||
|
||||
- note: add
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x = 3 + -1 + 4.5
|
||||
# Undefined
|
||||
y { false }
|
||||
a = 1 + y
|
||||
b = y + 1
|
||||
query: data.test
|
||||
want_result:
|
||||
x: 6.5
|
||||
|
||||
- note: add-non-numeric
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x = "1" + 9
|
||||
query: data.test.x
|
||||
error: "`add` expects numeric argument."
|
||||
|
||||
- note: sub
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x = 3 - -1 - 4.5
|
||||
# Undefined
|
||||
y { false }
|
||||
a = 1 - y
|
||||
b = y - 1
|
||||
query: data.test
|
||||
want_result:
|
||||
x: -0.5
|
||||
|
||||
- note: sub-non-numeric
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
x = "1" - 9
|
||||
query: data.test.x
|
||||
error: "`sub` expects numeric argument."
|
||||
|
||||
- note: all
|
||||
data: {}
|
||||
modules:
|
||||
@@ -256,3 +415,5 @@ cases:
|
||||
query: data.test
|
||||
want_result:
|
||||
x: 1.5
|
||||
|
||||
# TODO: Lockdown associativity and precedence of operators.
|
||||
|
||||
Reference in New Issue
Block a user