Builtin functions for numbers (WIP)

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2023-02-13 03:33:40 -08:00
committed by Anand Krishnamoorthi
parent 24911e6807
commit b30fc2599c
8 changed files with 322 additions and 29 deletions
+29 -1
View File
@@ -2,5 +2,33 @@
// Licensed under the MIT License.
mod comparison;
mod numbers;
pub use self::comparison::*;
pub use self::comparison::compare;
use crate::ast::Expr;
use crate::lexer::Span;
use crate::value::Value;
use std::collections::HashMap;
use anyhow::Result;
use lazy_static::lazy_static;
pub type BuiltinFcn = fn(&Span, &[Expr], &[Value]) -> Result<Value>;
#[rustfmt::skip]
lazy_static! {
pub static ref BUILTINS: HashMap<&'static str, BuiltinFcn> = {
let mut m : HashMap<&'static str, BuiltinFcn> = HashMap::new();
// numbers
m.insert("abs", numbers::abs);
m.insert("ceil", numbers::ceil);
m.insert("floor", numbers::floor);
m.insert("numbers.range", numbers::range);
m.insert("round", numbers::round);
m
};
}
+91
View File
@@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::ast::Expr;
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: &'static 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").as_str()))
}
})
}
pub fn abs(span: &Span, params: &[Expr], args: &[Value]) -> Result<Value> {
ensure_args_count(span, "abs", params, args, 1)?;
Ok(Value::from_float(
ensure_numeric("abs", &params[0], &args[0])?.abs(),
))
}
pub fn ceil(span: &Span, params: &[Expr], args: &[Value]) -> Result<Value> {
ensure_args_count(span, "ceil", params, args, 1)?;
Ok(Value::from_float(
ensure_numeric("ceil", &params[0], &args[0])?.ceil(),
))
}
pub fn floor(span: &Span, params: &[Expr], args: &[Value]) -> Result<Value> {
ensure_args_count(span, "floor", params, args, 1)?;
Ok(Value::from_float(
ensure_numeric("floor", &params[0], &args[0])?.floor(),
))
}
pub fn range(span: &Span, params: &[Expr], args: &[Value]) -> Result<Value> {
ensure_args_count(span, "numbers.range", params, args, 2)?;
let v1 = ensure_numeric("numbers.range", &params[0], &args[0])?;
let v2 = ensure_numeric("numbers.range", &params[1], &args[1])?;
if v1 != v1.floor() || v2 != v2.floor() {
// TODO: OPA returns undefined here.
// Can we emit a warning?
return Ok(Value::Undefined);
}
let incr = if v2 >= v1 { 1 } else { -1 } as Float;
let mut values = vec![];
values.reserve((v2 - v1).abs() as usize + 1);
let mut v = v1;
while v != v2 {
values.push(Value::from_float(v));
v += incr;
}
values.push(Value::from_float(v));
Ok(Value::from_array(values))
}
pub fn round(span: &Span, params: &[Expr], args: &[Value]) -> Result<Value> {
ensure_args_count(span, "round", params, args, 1)?;
Ok(Value::from_float(
ensure_numeric("round", &params[0], &args[0])?.round(),
))
}
+22 -1
View File
@@ -917,6 +917,19 @@ impl<'source> Interpreter<'source> {
}
}
fn eval_builtin_call(
&mut self,
span: &'source Span<'source>,
builtin: builtins::BuiltinFcn,
params: &'source Vec<Expr<'source>>,
) -> Result<Value> {
let mut args = vec![];
for p in params {
args.push(self.eval_expr(p)?);
}
builtin(span, &params[..], &args[..])
}
fn eval_call(
&mut self,
span: &'source Span<'source>,
@@ -926,9 +939,17 @@ impl<'source> Interpreter<'source> {
let fcn_rule = match self.lookup_function(fcn) {
Ok(r) => r,
_ => {
// Look up builtin function.
// 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 Err(span
.source
.error(span.line, span.col, "could not find function"))
.error(span.line, span.col, "could not find function"));
}
};
+10 -8
View File
@@ -12,6 +12,8 @@ use serde::de::{self, Deserializer};
use serde::ser::{SerializeMap, Serializer};
use serde::{Deserialize, Serialize};
pub type Float = f64;
// TODO: rego uses BigNum which has arbitrary precision. But there seems
// to be some bugs with it e.g ((a + b) -a) == b doesn't return true for large
// values of a and b.
@@ -21,23 +23,23 @@ use serde::{Deserialize, Serialize};
// For now we use OrderedFloat<f64>. We can't use f64 directly since it doesn't
// implement Ord trait.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Number(pub OrderedFloat<f64>);
pub struct Number(pub OrderedFloat<Float>);
impl Serialize for Number {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let n_f64 = self.0 .0;
let n_i64 = n_f64 as i64;
let n_u64 = n_f64 as u64;
let n_float = self.0 .0;
let n_i64 = n_float as i64;
let n_u64 = n_float as u64;
if n_u64 as f64 == n_f64 {
if n_u64 as f64 == n_float {
serializer.serialize_u64(n_u64)
} else if n_i64 as f64 == n_f64 {
} else if n_i64 as f64 == n_float {
serializer.serialize_i64(n_i64)
} else {
serializer.serialize_f64(n_f64)
serializer.serialize_f64(n_float)
}
}
}
@@ -166,7 +168,7 @@ impl Value {
}
impl Value {
pub fn from_f64(v: f64) -> Value {
pub fn from_float(v: Float) -> Value {
Value::Number(Number(OrderedFloat(v)))
}