From a8a1d4b820308f33b416b2a5db01b8c74ea080e4 Mon Sep 17 00:00:00 2001 From: Anand Krishnamoorthi Date: Thu, 16 Feb 2023 21:53:36 -0800 Subject: [PATCH] mod function Signed-off-by: Anand Krishnamoorthi --- src/ast.rs | 1 + src/builtins/mod.rs | 7 +- src/builtins/numbers.rs | 40 +++++- src/builtins/sets.rs | 26 ++++ src/interpreter.rs | 53 +++----- src/lexer.rs | 2 +- src/parser.rs | 7 +- tests/interpreter/cases/builtins/numbers.yaml | 114 ++++++++++++++++++ 8 files changed, 202 insertions(+), 48 deletions(-) create mode 100644 src/builtins/sets.rs diff --git a/src/ast.rs b/src/ast.rs index 3d86217..31f11eb 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -15,6 +15,7 @@ pub enum ArithOp { Sub, Mul, Div, + Mod, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 922786f..d8089ad 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -1,10 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -mod comparison; -mod numbers; - -pub use self::comparison::compare; +pub mod comparison; +pub mod numbers; +pub mod sets; use crate::ast::Expr; use crate::lexer::Span; diff --git a/src/builtins/numbers.rs b/src/builtins/numbers.rs index 12a91c8..85c3e69 100644 --- a/src/builtins/numbers.rs +++ b/src/builtins/numbers.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::ast::Expr; +use crate::ast::{ArithOp, Expr}; use crate::lexer::Span; use crate::value::{Float, Value}; @@ -28,12 +28,14 @@ fn ensure_args_count( Ok(()) } -fn ensure_numeric(fcn: &'static str, arg: &Expr, v: &Value) -> Result { - Ok(match v { +fn ensure_numeric(fcn: &str, arg: &Expr, v: &Value) -> Result { + Ok(match &v { Value::Number(n) => n.0 .0, _ => { 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 { pub fn range(span: &Span, params: &[Expr], args: &[Value]) -> Result { ensure_args_count(span, "numbers.range", params, args, 2)?; - let v1 = ensure_numeric("numbers.range", ¶ms[0], &args[0])?; - let v2 = ensure_numeric("numbers.range", ¶ms[1], &args[1])?; + let v1 = ensure_numeric("numbers.range", ¶ms[0], &args[0].clone())?; + let v2 = ensure_numeric("numbers.range", ¶ms[1], &args[1].clone())?; if v1 != v1.floor() || v2 != v2.floor() { // TODO: OPA returns undefined here. @@ -89,3 +91,29 @@ pub fn round(span: &Span, params: &[Expr], args: &[Value]) -> Result { ensure_numeric("round", ¶ms[0], &args[0])?.round(), )) } + +pub fn arithmetic_operation( + op: &ArithOp, + expr1: &Expr, + expr2: &Expr, + v1: Value, + v2: Value, +) -> Result { + 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, + })) +} diff --git a/src/builtins/sets.rs b/src/builtins/sets.rs new file mode 100644 index 0000000..dc2a5e3 --- /dev/null +++ b/src/builtins/sets.rs @@ -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>> { + 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 { + let s1 = ensure_set("difference", expr1, v1)?; + let s2 = ensure_set("difference", expr2, v2)?; + Ok(Value::from_set(s1.difference(&s2).cloned().collect())) +} diff --git a/src/interpreter.rs b/src/interpreter.rs index e2795f6..73d6459 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -29,6 +29,7 @@ pub struct Interpreter<'source> { default_rules: HashMap, Option)>>, processed: BTreeSet<&'source Rule<'source>>, active_rules: Vec<&'source Rule<'source>>, + intns: BTreeMap, Value>, } #[derive(Debug, Clone)] @@ -61,6 +62,7 @@ impl<'source> Interpreter<'source> { default_rules: HashMap::new(), processed: BTreeSet::new(), active_rules: vec![], + intns: BTreeMap::new(), }) } @@ -262,7 +264,7 @@ impl<'source> Interpreter<'source> { ) -> Result { let lhs = self.eval_expr(lhs_expr)?; let rhs = self.eval_expr(rhs_expr)?; - builtins::compare(op, &lhs, &rhs) + builtins::comparison::compare(op, &lhs, &rhs) } fn eval_bin_expr( @@ -303,39 +305,15 @@ impl<'source> Interpreter<'source> { lhs: &'source Expr<'source>, rhs: &'source Expr<'source>, ) -> Result { - 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)?; - // Handle special case for set difference. - if let (Value::Set(lhs), ArithOp::Sub, Value::Set(rhs)) = (&lhs, op, &rhs) { - return Ok(Value::from_set(lhs.difference(rhs).cloned().collect())); + match (op, &lhs_value, &rhs_value) { + (ArithOp::Sub, Value::Set(_), _) | (ArithOp::Sub, _, Value::Set(_)) => { + 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( @@ -920,6 +898,7 @@ impl<'source> Interpreter<'source> { fn eval_builtin_call( &mut self, span: &'source Span<'source>, + name: String, builtin: builtins::BuiltinFcn, params: &'source Vec>, ) -> Result { @@ -927,7 +906,13 @@ impl<'source> Interpreter<'source> { for p in params { 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( @@ -943,7 +928,7 @@ impl<'source> Interpreter<'source> { // TODO: handle with modifier if let Ok(path) = Self::get_path_string(fcn, None) { 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); } } diff --git a/src/lexer.rs b/src/lexer.rs index a646485..616628f 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -415,7 +415,7 @@ impl<'source> Lexer<'source> { // grouping characters '{' | '}' | '[' | ']' | '(' | ')' | // arith operator - '+' | '-' | '*' | '/' | + '+' | '-' | '*' | '/' | '%' | // bin operator '&' | '|' | // separators diff --git a/src/parser.rs b/src/parser.rs index b7f1668..8072c4f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -555,7 +555,7 @@ impl<'source> Parser<'source> { self.parse_ref() } - fn parse_mul_div_expr(&mut self) -> Result> { + fn parse_mul_div_mod_expr(&mut self) -> Result> { let start = self.tok.1.start; let mut expr = self.parse_term()?; @@ -565,6 +565,7 @@ impl<'source> Parser<'source> { let op = match self.tok.1.text() { "*" => ArithOp::Mul, "/" => ArithOp::Div, + "%" => ArithOp::Mod, _ => return Ok(expr), }; self.next_token()?; @@ -581,7 +582,7 @@ impl<'source> Parser<'source> { fn parse_arith_expr(&mut self) -> Result> { let start = self.tok.1.start; - let mut expr = self.parse_mul_div_expr()?; + let mut expr = self.parse_mul_div_mod_expr()?; loop { let mut span = self.tok.1.clone(); @@ -592,7 +593,7 @@ impl<'source> Parser<'source> { _ => return Ok(expr), }; self.next_token()?; - let right = self.parse_mul_div_expr()?; + let right = self.parse_mul_div_mod_expr()?; span.end = self.end; expr = Expr::ArithExpr { span, diff --git a/tests/interpreter/cases/builtins/numbers.yaml b/tests/interpreter/cases/builtins/numbers.yaml index d4393f6..955972b 100644 --- a/tests/interpreter/cases/builtins/numbers.yaml +++ b/tests/interpreter/cases/builtins/numbers.yaml @@ -142,3 +142,117 @@ cases: x = numbers.range(1, "2") query: data.test.x 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