mod function

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2023-02-16 21:53:36 -08:00
committed by Anand Krishnamoorthi
parent b30fc2599c
commit a8a1d4b820
8 changed files with 202 additions and 48 deletions
+1
View File
@@ -15,6 +15,7 @@ pub enum ArithOp {
Sub, Sub,
Mul, Mul,
Div, Div,
Mod,
} }
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
+3 -4
View File
@@ -1,10 +1,9 @@
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT License. // Licensed under the MIT License.
mod comparison; pub mod comparison;
mod numbers; pub mod numbers;
pub mod sets;
pub use self::comparison::compare;
use crate::ast::Expr; use crate::ast::Expr;
use crate::lexer::Span; use crate::lexer::Span;
+34 -6
View File
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT License. // Licensed under the MIT License.
use crate::ast::Expr; use crate::ast::{ArithOp, Expr};
use crate::lexer::Span; use crate::lexer::Span;
use crate::value::{Float, Value}; use crate::value::{Float, Value};
@@ -28,12 +28,14 @@ fn ensure_args_count(
Ok(()) Ok(())
} }
fn ensure_numeric(fcn: &'static str, arg: &Expr, v: &Value) -> Result<Float> { fn ensure_numeric(fcn: &str, arg: &Expr, v: &Value) -> Result<Float> {
Ok(match v { Ok(match &v {
Value::Number(n) => n.0 .0, Value::Number(n) => n.0 .0,
_ => { _ => {
let span = arg.span(); let span = arg.span();
bail!(span.error(format!("`{fcn}` expects numeric argument").as_str())) bail!(
span.error(format!("`{fcn}` expects numeric argument. Got `{v}` instead").as_str())
)
} }
}) })
} }
@@ -61,8 +63,8 @@ pub fn floor(span: &Span, params: &[Expr], args: &[Value]) -> Result<Value> {
pub fn range(span: &Span, params: &[Expr], args: &[Value]) -> Result<Value> { pub fn range(span: &Span, params: &[Expr], args: &[Value]) -> Result<Value> {
ensure_args_count(span, "numbers.range", params, args, 2)?; ensure_args_count(span, "numbers.range", params, args, 2)?;
let v1 = ensure_numeric("numbers.range", &params[0], &args[0])?; let v1 = ensure_numeric("numbers.range", &params[0], &args[0].clone())?;
let v2 = ensure_numeric("numbers.range", &params[1], &args[1])?; let v2 = ensure_numeric("numbers.range", &params[1], &args[1].clone())?;
if v1 != v1.floor() || v2 != v2.floor() { if v1 != v1.floor() || v2 != v2.floor() {
// TODO: OPA returns undefined here. // TODO: OPA returns undefined here.
@@ -89,3 +91,29 @@ pub fn round(span: &Span, params: &[Expr], args: &[Value]) -> Result<Value> {
ensure_numeric("round", &params[0], &args[0])?.round(), ensure_numeric("round", &params[0], &args[0])?.round(),
)) ))
} }
pub fn arithmetic_operation(
op: &ArithOp,
expr1: &Expr,
expr2: &Expr,
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)?;
Ok(Value::from_float(match op {
ArithOp::Add => v1 + v2,
ArithOp::Sub => v1 - v2,
ArithOp::Mul => v1 * v2,
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 => v1 % v2,
}))
}
+26
View File
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::ast::Expr;
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()))
}
+19 -34
View File
@@ -29,6 +29,7 @@ pub struct Interpreter<'source> {
default_rules: HashMap<String, Vec<(&'source Rule<'source>, Option<String>)>>, default_rules: HashMap<String, Vec<(&'source Rule<'source>, Option<String>)>>,
processed: BTreeSet<&'source Rule<'source>>, processed: BTreeSet<&'source Rule<'source>>,
active_rules: Vec<&'source Rule<'source>>, active_rules: Vec<&'source Rule<'source>>,
intns: BTreeMap<Vec<Value>, Value>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -61,6 +62,7 @@ impl<'source> Interpreter<'source> {
default_rules: HashMap::new(), default_rules: HashMap::new(),
processed: BTreeSet::new(), processed: BTreeSet::new(),
active_rules: vec![], active_rules: vec![],
intns: BTreeMap::new(),
}) })
} }
@@ -262,7 +264,7 @@ impl<'source> Interpreter<'source> {
) -> Result<Value> { ) -> Result<Value> {
let lhs = self.eval_expr(lhs_expr)?; let lhs = self.eval_expr(lhs_expr)?;
let rhs = self.eval_expr(rhs_expr)?; let rhs = self.eval_expr(rhs_expr)?;
builtins::compare(op, &lhs, &rhs) builtins::comparison::compare(op, &lhs, &rhs)
} }
fn eval_bin_expr( fn eval_bin_expr(
@@ -303,39 +305,15 @@ impl<'source> Interpreter<'source> {
lhs: &'source Expr<'source>, lhs: &'source Expr<'source>,
rhs: &'source Expr<'source>, rhs: &'source Expr<'source>,
) -> Result<Value> { ) -> Result<Value> {
let lhs = self.eval_expr(lhs)?; let lhs_value = self.eval_expr(lhs)?;
let rhs = self.eval_expr(rhs)?; let rhs_value = self.eval_expr(rhs)?;
// Handle special case for set difference. match (op, &lhs_value, &rhs_value) {
if let (Value::Set(lhs), ArithOp::Sub, Value::Set(rhs)) = (&lhs, op, &rhs) { (ArithOp::Sub, Value::Set(_), _) | (ArithOp::Sub, _, Value::Set(_)) => {
return Ok(Value::from_set(lhs.difference(rhs).cloned().collect())); builtins::sets::difference(lhs, rhs, lhs_value, rhs_value)
}
_ => builtins::numbers::arithmetic_operation(op, lhs, rhs, lhs_value, rhs_value),
} }
let lhs = if let Value::Number(number) = lhs {
number.0
} else {
return Err(anyhow!("expect {:?} to be a number", lhs));
};
let rhs = if let Value::Number(number) = rhs {
number.0
} else {
return Err(anyhow!("expect {:?} to be a number", rhs));
};
let result = match op {
ArithOp::Add => lhs + rhs,
ArithOp::Sub => lhs - rhs,
ArithOp::Mul => lhs * rhs,
ArithOp::Div => lhs / rhs,
};
info!(
"eval_arith_expr, op: {:?}, lhs: {:?}, rhs: {:?}",
op, lhs, rhs
);
Ok(Value::Number(Number(result)))
} }
fn eval_assign_expr( fn eval_assign_expr(
@@ -920,6 +898,7 @@ impl<'source> Interpreter<'source> {
fn eval_builtin_call( fn eval_builtin_call(
&mut self, &mut self,
span: &'source Span<'source>, span: &'source Span<'source>,
name: String,
builtin: builtins::BuiltinFcn, builtin: builtins::BuiltinFcn,
params: &'source Vec<Expr<'source>>, params: &'source Vec<Expr<'source>>,
) -> Result<Value> { ) -> Result<Value> {
@@ -927,7 +906,13 @@ impl<'source> Interpreter<'source> {
for p in params { for p in params {
args.push(self.eval_expr(p)?); args.push(self.eval_expr(p)?);
} }
builtin(span, &params[..], &args[..])
let is_randn = name == "rand.intn";
let v = builtin(span, &params[..], &args[..])?;
if is_randn {
self.intns.insert(args, v.clone());
}
Ok(v)
} }
fn eval_call( fn eval_call(
@@ -943,7 +928,7 @@ impl<'source> Interpreter<'source> {
// TODO: handle with modifier // TODO: handle with modifier
if let Ok(path) = Self::get_path_string(fcn, None) { if let Ok(path) = Self::get_path_string(fcn, None) {
if let Some(builtin) = builtins::BUILTINS.get(path.as_str()) { if let Some(builtin) = builtins::BUILTINS.get(path.as_str()) {
return self.eval_builtin_call(span, *builtin, params); return self.eval_builtin_call(span, path, *builtin, params);
} }
} }
+1 -1
View File
@@ -415,7 +415,7 @@ impl<'source> Lexer<'source> {
// grouping characters // grouping characters
'{' | '}' | '[' | ']' | '(' | ')' | '{' | '}' | '[' | ']' | '(' | ')' |
// arith operator // arith operator
'+' | '-' | '*' | '/' | '+' | '-' | '*' | '/' | '%' |
// bin operator // bin operator
'&' | '|' | '&' | '|' |
// separators // separators
+4 -3
View File
@@ -555,7 +555,7 @@ impl<'source> Parser<'source> {
self.parse_ref() self.parse_ref()
} }
fn parse_mul_div_expr(&mut self) -> Result<Expr<'source>> { fn parse_mul_div_mod_expr(&mut self) -> Result<Expr<'source>> {
let start = self.tok.1.start; let start = self.tok.1.start;
let mut expr = self.parse_term()?; let mut expr = self.parse_term()?;
@@ -565,6 +565,7 @@ impl<'source> Parser<'source> {
let op = match self.tok.1.text() { let op = match self.tok.1.text() {
"*" => ArithOp::Mul, "*" => ArithOp::Mul,
"/" => ArithOp::Div, "/" => ArithOp::Div,
"%" => ArithOp::Mod,
_ => return Ok(expr), _ => return Ok(expr),
}; };
self.next_token()?; self.next_token()?;
@@ -581,7 +582,7 @@ impl<'source> Parser<'source> {
fn parse_arith_expr(&mut self) -> Result<Expr<'source>> { fn parse_arith_expr(&mut self) -> Result<Expr<'source>> {
let start = self.tok.1.start; let start = self.tok.1.start;
let mut expr = self.parse_mul_div_expr()?; let mut expr = self.parse_mul_div_mod_expr()?;
loop { loop {
let mut span = self.tok.1.clone(); let mut span = self.tok.1.clone();
@@ -592,7 +593,7 @@ impl<'source> Parser<'source> {
_ => return Ok(expr), _ => return Ok(expr),
}; };
self.next_token()?; self.next_token()?;
let right = self.parse_mul_div_expr()?; let right = self.parse_mul_div_mod_expr()?;
span.end = self.end; span.end = self.end;
expr = Expr::ArithExpr { expr = Expr::ArithExpr {
span, span,
@@ -142,3 +142,117 @@ cases:
x = numbers.range(1, "2") x = numbers.range(1, "2")
query: data.test.x query: data.test.x
error: "`numbers.range` expects numeric argument" error: "`numbers.range` expects numeric argument"
- note: round
data: {}
modules:
- |
package test
x = [round(-9.4), round(-9.5), round(-9.6),
round(9.4), round(9.5), round(9.6)]
query: data.test.x
want_result: [-9, -10, -10, 9, 10, 10]
- note: round-extra-args
data: {}
modules:
- |
package test
x = round(-9, 10)
query: data.test.x
error: "`round` expects 1 argument"
- note: round-invalid-type
data: {}
modules:
- |
package test
x = round("-9")
query: data.test.x
error: "`round` expects numeric argument"
- note: div
data: {}
modules:
- |
package test
x = 1/3
query: data.test.x
want_result: 0.3333333333333333
- note: div-non-numeric
data: {}
modules:
- |
package test
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
- note: mod-non-numeric
data: {}
modules:
- |
package test
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
x = a % 10
query: data.test
want_result: {}
- note: mod-by-zero
data: {}
modules:
- |
package test
x = 1%0
query: data.test
want_result: {}
- note: all
data: {}
modules:
- |
package test
x = 16 / 2 % 5 / 2
query: data.test
want_result:
x: 1.5