mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Builtin functions for numbers (WIP)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
committed by
Anand Krishnamoorthi
parent
24911e6807
commit
b30fc2599c
+29
-1
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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", ¶ms[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", ¶ms[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", ¶ms[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", ¶ms[0], &args[0])?;
|
||||
let v2 = ensure_numeric("numbers.range", ¶ms[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", ¶ms[0], &args[0])?.round(),
|
||||
))
|
||||
}
|
||||
+22
-1
@@ -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, ¶ms[..], &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
@@ -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)))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user