use super::*;
#[allow(dead_code)] mod dragon;
mod fdlibm;
mod math;
mod numbers;
mod regex;
pub(crate) mod render;
pub(crate) mod string;
mod units;
pub(crate) use numbers::{
binary_op, logical_left, logical_not, logical_right, negate, units_equal,
};
pub(crate) use regex::compile as compile_regex;
pub(crate) use render::render_value;
use render::{render_value_limited, to_pkl_string};
const ERROR_VALUE_LIMIT: usize = 80;
pub(super) fn error_with_values(message: impl Into<String>, values: &[(&str, &Value)]) -> Error {
let mut message = message.into();
let width = values.iter().map(|(name, _)| name.len()).max().unwrap_or(0);
for (name, value) in values {
message.push('\n');
message.push_str(name);
message.extend(std::iter::repeat_n(' ', width - name.len()));
message.push_str(": ");
message.push_str(&render_value_limited(value, ERROR_VALUE_LIMIT));
}
Error::Eval(message)
}
pub(super) fn typed_object(type_name: &str, members: ObjectMap) -> Value {
let source = ObjectSource {
entries: Vec::new().into(),
captured: SourceScope::default(),
body_members: HashSet::default(),
is_open: false,
is_abstract: false,
type_name: Some(type_name.to_string()),
type_identity: (type_name == "RegexMatch").then(|| "pkl:base#RegexMatch".to_string()),
parent_type_names: Vec::new(),
parent_type_identities: Vec::new(),
entry_scopes: Vec::new(),
evaluated_properties: members.keys().cloned().collect(),
elements: Vec::new(),
mapping_value_types: Vec::new(),
deprecated: IndexMap::new(),
poisoned_members: None,
kind: ObjectKind::Object,
is_parsed_json: false,
prototype: None,
};
Value::Object(Arc::new(members), Some(Arc::new(source)))
}
fn is_builtin_regex_match(value: &Value) -> bool {
matches!(value, Value::Object(_, Some(source)) if source.type_identity.as_deref() == Some("pkl:base#RegexMatch"))
}
fn is_typed_object(value: &Value, type_name: &str) -> bool {
matches!(value, Value::Object(_, Some(source)) if source.type_name.as_deref() == Some(type_name))
}
pub(super) fn type_mismatch(expected: &str, actual: &Value) -> Error {
error_with_values(
format!(
"Expected value of type `{expected}`, but got type `{}`.",
actual.type_name()
),
&[("Value", actual)],
)
}
pub(super) fn operator_not_defined(op: &str, left: &Value, right: &Value) -> Error {
error_with_values(
format!(
"Operator `{op}` is not defined for operand types `{}` and `{}`.",
left.type_name(),
right.type_name()
),
&[("Left operand", left), ("Right operand", right)],
)
}
pub(super) fn expected_positive(n: i64) -> Error {
Error::Eval(format!("Expected a positive number, but got `{n}`."))
}
pub(super) struct Args<'a> {
method: &'a str,
args: &'a [Value],
}
impl<'a> Args<'a> {
fn get(&self, index: usize) -> Result<&'a Value> {
self.args.get(index).ok_or_else(|| {
Error::Eval(format!(
"Too few arguments for method `{}`: expected more than {}.",
self.method,
self.args.len()
))
})
}
pub(super) fn string(&self, index: usize) -> Result<&'a str> {
match self.get(index)? {
Value::String(s) => Ok(s),
other => Err(type_mismatch("String", other)),
}
}
pub(super) fn int(&self, index: usize) -> Result<i64> {
match self.get(index)? {
Value::Int(n) => Ok(*n),
other => Err(type_mismatch("Int", other)),
}
}
pub(super) fn function(&self, index: usize) -> Result<&'a Value> {
match self.get(index)? {
lambda @ Value::Lambda(..) => Ok(lambda),
other => Err(type_mismatch("Function", other)),
}
}
pub(super) fn value(&self, index: usize) -> Result<&'a Value> {
self.get(index)
}
}
pub(super) fn check_arity(args: &[Value], expected: usize) -> Result<()> {
if args.len() == expected {
return Ok(());
}
Err(Error::Eval(format!(
"Expected {expected} function arguments but got {}.",
args.len()
)))
}
pub(super) fn index(value: &Value, key: &Value) -> Option<Result<Value>> {
match value {
Value::String(s) => Some(string::subscript(s, key)),
_ => None,
}
}
pub(super) fn module(name: &str) -> Option<Value> {
match name {
"math" => Some(math::module()),
_ => None,
}
}
impl Evaluator {
pub(super) fn value_to_string(&mut self, value: &Value, depth: usize) -> Result<String> {
if let Value::Object(members, _) = value {
if is_builtin_regex_match(value)
&& let Some(Value::String(s)) = members.get("value")
{
return Ok(s.to_string());
}
if let Some(Value::Lambda(params, ..)) = members.get("toString")
&& params.is_empty()
&& let Some(result) = self.eval_object_method_call(value, "toString", &[], depth)?
{
return Ok(to_pkl_string(&result));
}
}
Ok(to_pkl_string(value))
}
#[inline(never)]
pub(super) fn stdlib_property(&mut self, value: &Value, name: &str) -> Option<Result<Value>> {
match value {
Value::String(s) => string::property(s, name),
Value::Regex(regex) => regex::property(regex, name),
Value::Int(n) => numbers::int_property(*n, name),
Value::Float(f) => numbers::float_property(*f, name),
Value::Duration(d) => units::duration_property(d, name),
Value::DataSize(d) => units::data_size_property(d, name),
_ => None,
}
}
#[inline(never)]
pub(super) fn stdlib_method(
&mut self,
value: &Value,
name: &str,
args: &[Value],
depth: usize,
) -> Option<Result<Value>> {
if name == "toString" {
if let Value::Object(members, _) = value
&& matches!(members.get("toString"), Some(Value::Lambda(..)))
{
return None;
}
return Some(check_arity(args, 0).and_then(|()| {
Ok(Value::String(match value {
Value::String(s) => Arc::clone(s),
_ => self.value_to_string(value, depth)?.into(),
}))
}));
}
match value {
Value::String(s) => self.string_method(s, name, args, depth),
Value::Regex(regex) => self.regex_method(regex, name, args),
Value::Int(n) => numbers::int_method(*n, name, args),
Value::Float(f) => numbers::float_method(*f, name, args),
Value::Bool(b) => numbers::bool_method(*b, name, args),
Value::Duration(d) => units::duration_method(d, name, args),
Value::DataSize(d) => units::data_size_method(d, name, args),
Value::Object(..) if is_typed_object(value, math::MODULE) => math::call(name, args),
_ => None,
}
}
pub(super) fn call_predicate(
&mut self,
lambda: &Value,
args: &[Value],
depth: usize,
) -> Result<bool> {
match self.invoke_lambda(lambda, args, depth)? {
Value::Bool(b) => Ok(b),
other => Err(type_mismatch("Boolean", &other)),
}
}
}