mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
mod function
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
committed by
Anand Krishnamoorthi
parent
b30fc2599c
commit
a8a1d4b820
@@ -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
@@ -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
@@ -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", ¶ms[0], &args[0])?;
|
let v1 = ensure_numeric("numbers.range", ¶ms[0], &args[0].clone())?;
|
||||||
let v2 = ensure_numeric("numbers.range", ¶ms[1], &args[1])?;
|
let v2 = ensure_numeric("numbers.range", ¶ms[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", ¶ms[0], &args[0])?.round(),
|
ensure_numeric("round", ¶ms[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,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
@@ -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, ¶ms[..], &args[..])
|
|
||||||
|
let is_randn = name == "rand.intn";
|
||||||
|
let v = builtin(span, ¶ms[..], &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
@@ -415,7 +415,7 @@ impl<'source> Lexer<'source> {
|
|||||||
// grouping characters
|
// grouping characters
|
||||||
'{' | '}' | '[' | ']' | '(' | ')' |
|
'{' | '}' | '[' | ']' | '(' | ')' |
|
||||||
// arith operator
|
// arith operator
|
||||||
'+' | '-' | '*' | '/' |
|
'+' | '-' | '*' | '/' | '%' |
|
||||||
// bin operator
|
// bin operator
|
||||||
'&' | '|' |
|
'&' | '|' |
|
||||||
// separators
|
// separators
|
||||||
|
|||||||
+4
-3
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user