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
@@ -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
@@ -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
@@ -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,
|
||||
}))
|
||||
}
|
||||
|
||||
+62
-11
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
@@ -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()?;
|
||||
|
||||
Reference in New Issue
Block a user