Lock down numbers

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2023-02-18 01:13:25 -08:00
committed by Anand Krishnamoorthi
parent a8a1d4b820
commit 12c3d26476
10 changed files with 445 additions and 143 deletions
-5
View File
@@ -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.
+14 -1
View File
@@ -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,
}
}
+24 -38
View File
@@ -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, &params[0], &args[0])?;
let n = ensure_numeric(fcn, &params[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,
}))
}
+62 -11
View File
@@ -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, &params[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, &params[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
View 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
View 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()))
}
})
}
+34 -28
View File
@@ -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, &params[..], &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)
}
+6
View File
@@ -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()?;