use crate::ast::{Expr, Ref};
use crate::builtins;
use crate::lexer::Span;
use crate::value::Value;
use anyhow::Result;
use super::helpers::try_coerce_to_number;
pub(super) fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("azure.policy.fn.min", (fn_min, super::MAX_VARIADIC_ARGS));
m.insert("azure.policy.fn.max", (fn_max, super::MAX_VARIADIC_ARGS));
m.insert("azure.policy.fn.float", (fn_float, 1));
m.insert("azure.policy.fn.int_div", (fn_int_div, 2));
m.insert("azure.policy.fn.int_mod", (fn_int_mod, 2));
}
fn fn_min(_span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let values = if args.len() == 1 {
args.first().map_or(args, |first| match *first {
Value::Array(ref arr) => arr.as_ref().as_slice(),
_ => args,
})
} else {
args
};
if values.is_empty() {
return Ok(Value::Undefined);
}
let mut result: Option<&Value> = None;
for v in values {
match *v {
Value::Number(_) => {
result = Some(match result {
Some(current) if v >= current => current,
_ => v,
});
}
_ => return Ok(Value::Undefined),
}
}
Ok(result.cloned().unwrap_or(Value::Undefined))
}
fn fn_max(_span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let values = if args.len() == 1 {
args.first().map_or(args, |first| match *first {
Value::Array(ref arr) => arr.as_ref().as_slice(),
_ => args,
})
} else {
args
};
if values.is_empty() {
return Ok(Value::Undefined);
}
let mut result: Option<&Value> = None;
for v in values {
match *v {
Value::Number(_) => {
result = Some(match result {
Some(current) if v <= current => current,
_ => v,
});
}
_ => return Ok(Value::Undefined),
}
}
Ok(result.cloned().unwrap_or(Value::Undefined))
}
fn fn_float(_span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let Some(arg) = args.first() else {
return Ok(Value::Undefined);
};
match *arg {
Value::Number(ref n) => Ok(n.as_f64().map_or(Value::Undefined, Value::from)),
Value::String(ref s) => Ok(try_coerce_to_number(s)
.and_then(|n| n.as_f64())
.map_or(Value::Undefined, Value::from)),
_ => Ok(Value::Undefined),
}
}
fn fn_int_div(_span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
#[allow(clippy::pattern_type_mismatch)]
let [a, b] = args
else {
return Ok(Value::Undefined);
};
let (a, b) = (extract_i64(a), extract_i64(b));
match (a, b) {
(Some(a), Some(b)) => a
.checked_div(b)
.map_or(Ok(Value::Undefined), |r| Ok(Value::from(r))),
_ => Ok(Value::Undefined),
}
}
fn fn_int_mod(_span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
#[allow(clippy::pattern_type_mismatch)]
let [a, b] = args
else {
return Ok(Value::Undefined);
};
let (a, b) = (extract_i64(a), extract_i64(b));
match (a, b) {
(Some(a), Some(b)) => a
.checked_rem(b)
.map_or(Ok(Value::Undefined), |r| Ok(Value::from(r))),
_ => Ok(Value::Undefined),
}
}
fn extract_i64(v: &Value) -> Option<i64> {
match *v {
Value::Number(ref n) => n.as_i64().or_else(|| n.as_f64().map(f64_to_i64)),
_ => None,
}
}
#[expect(clippy::as_conversions)]
const fn f64_to_i64(x: f64) -> i64 {
x as i64
}