use crate::bif::Bif;
use crate::closure::Closure;
use crate::context::FeelContext;
use crate::errors::*;
use crate::names::Name;
use crate::strings::ToFeelString;
use crate::types::FeelType;
use crate::FunctionBody;
use dmntk_common::{Jsonify, Result};
use dmntk_feel_number::FeelNumber;
use dmntk_feel_temporal::{FeelDate, FeelDateTime, FeelDaysAndTimeDuration, FeelTime, FeelYearsAndMonthsDuration};
use std::collections::BTreeMap;
use std::fmt;
use std::ops::Deref;
use std::str::FromStr;
#[macro_export]
macro_rules! value_null {
($module:expr, $function:literal, $format:literal, $($arguments:tt)*) => {
Value::Null(Some(format!("[{}::{}] {}", $module, $function, format!($format, $($arguments)*))))
};
($format:literal, $($arguments:tt)*) => {
Value::Null(Some(format!($format, $($arguments)*)))
};
($argument:expr) => {
Value::Null(Some(format!("{}", $argument)))
};
() => {
Value::Null(None)
};
}
#[macro_export]
macro_rules! value_number {
($n:expr) => {{
Value::Number($n.into())
}};
($n:expr, $s:expr) => {
Value::Number(FeelNumber::new($n, $s))
};
}
#[macro_export]
macro_rules! value_string {
($s:literal) => {{
Value::String($s.to_string())
}};
($s:expr) => {{
Value::String($s)
}};
}
pub const VALUE_TRUE: Value = Value::Boolean(true);
pub const VALUE_FALSE: Value = Value::Boolean(false);
const INVALID_COERCION: &str = "after coercion";
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Boolean(bool),
BuiltInFunction(Bif),
Context(FeelContext),
ContextEntry(Name, Box<Value>),
ContextEntryKey(Name),
ContextType(FeelType),
ContextTypeEntry(Name, FeelType),
ContextTypeEntryKey(Name),
Date(FeelDate),
DateTime(FeelDateTime),
DaysAndTimeDuration(FeelDaysAndTimeDuration),
ExpressionList(Values),
ExternalJavaFunction(
String,
String,
),
ExternalPmmlFunction(
String,
String,
),
FeelType(FeelType),
FormalParameter(Name, FeelType),
FormalParameters(Vec<(Name, FeelType)>),
FunctionBody(
FunctionBody,
bool,
),
FunctionDefinition(
Vec<(Name, FeelType)>,
FunctionBody,
bool,
Closure,
FeelContext,
FeelType,
),
IntervalEnd(Box<Value>, bool),
IntervalStart(Box<Value>, bool),
Irrelevant,
List(Values),
NamedParameter(Box<Value>, Box<Value>),
NamedParameters(BTreeMap<Name, (Value, usize)>),
NegatedCommaList(Values),
Null(Option<String>),
Number(FeelNumber),
ParameterName(Name),
ParameterTypes(Vec<Value>),
PositionalParameters(Values),
QualifiedNameSegment(Name),
Range(Box<Value>, bool, Box<Value>, bool),
String(String),
Time(FeelTime),
UnaryGreater(Box<Value>),
UnaryGreaterOrEqual(Box<Value>),
UnaryLess(Box<Value>),
UnaryLessOrEqual(Box<Value>),
YearsAndMonthsDuration(FeelYearsAndMonthsDuration),
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Boolean(value) => write!(f, "{value}"),
Value::BuiltInFunction(_) => write!(f, "BuiltInFunction"),
Value::Context(context) => write!(f, "{context}"),
Value::ContextEntry(_, _) => write!(f, "ContextEntry"),
Value::ContextEntryKey(name) => write!(f, "{name}"),
Value::ContextType(_) => write!(f, "ContextType"),
Value::ContextTypeEntry(name, feel_type) => write!(f, "{name}: {feel_type}"),
Value::ContextTypeEntryKey(name) => write!(f, "{name}"),
Value::Date(date) => write!(f, "{date}"),
Value::DateTime(date_time) => write!(f, "{date_time}"),
Value::DaysAndTimeDuration(dt_duration) => write!(f, "{dt_duration}"),
Value::ExpressionList(items) => write!(f, "{}", values_to_string(items)),
Value::ExternalJavaFunction(class_name, method_signature) => {
write!(f, "ExternalJavaFunction({class_name}, {method_signature})")
}
Value::ExternalPmmlFunction(iri, model_name) => {
write!(f, "ExternalPmmlFunction({iri}, {model_name})")
}
Value::FeelType(feel_type) => write!(f, "type({feel_type})"),
Value::FormalParameter(_, _) => write!(f, "FormalParameter"),
Value::FormalParameters(_) => write!(f, "FormalParameters"),
Value::FunctionBody(_, external) => write!(f, "FunctionBody{}", if *external { " (external)" } else { "" }),
Value::FunctionDefinition(parameters, _body, external, closure, closure_ctx, return_type) => {
write!(f, "FunctionDefinition({parameters:?},_,{external},{closure},{closure_ctx},{return_type})")
}
Value::IntervalEnd(_, _) => write!(f, "IntervalEnd"),
Value::IntervalStart(_, _) => write!(f, "IntervalStart"),
Value::Irrelevant => write!(f, "Irrelevant"),
Value::List(items) => write!(f, "{}", values_to_string(items)),
Value::NamedParameter(_, _) => write!(f, "NamedParameter"),
Value::NamedParameters(_) => write!(f, "NamedParameters"),
Value::NegatedCommaList(_) => write!(f, "NegatedCommaList"),
Value::Number(value) => write!(f, "{value}"),
Value::Null(trace) => write!(f, "null{}", trace.as_ref().map_or("".to_string(), |s| format!("({s})"))),
Value::ParameterName(_) => write!(f, "ParameterName"),
Value::ParameterTypes(_) => write!(f, "ParameterTypes"),
Value::PositionalParameters(_) => write!(f, "PositionalParameters"),
Value::QualifiedNameSegment(_) => write!(f, "QualifiedNameSegment"),
Value::Range(v1, c1, v2, c2) => write!(f, "{}{}..{}{}", if *c1 { '[' } else { '(' }, v1, v2, if *c2 { ']' } else { ')' }),
Value::String(s) => write!(f, "\"{s}\""),
Value::Time(time) => write!(f, "{time}"),
Value::UnaryGreater(value) => write!(f, "UnaryGreater({value})"),
Value::UnaryGreaterOrEqual(value) => write!(f, "UnaryGreaterOrEqual({value})"),
Value::UnaryLess(value) => write!(f, "UnaryLess({value})"),
Value::UnaryLessOrEqual(value) => write!(f, "UnaryLessOrEqual({value})"),
Value::YearsAndMonthsDuration(ym_duration) => write!(f, "{ym_duration}"),
}
}
}
impl ToFeelString for Value {
fn to_feel_string(&self) -> String {
match self {
Value::Context(context) => context.to_feel_string(),
Value::List(items) => values_to_feel_string(items),
Value::String(value) => format!("\"{}\"", value.replace('"', "\\\"")),
other => other.to_string(),
}
}
}
impl Jsonify for Value {
fn jsonify(&self) -> String {
match self {
Value::Boolean(value) => format!("{value}"),
Value::Number(value) => value.jsonify(),
Value::String(s) => format!(r#""{s}""#),
Value::Date(date) => format!(r#""{}""#, date),
Value::Time(time) => format!(r#""{}""#, time),
Value::DateTime(date_time) => format!(r#""{}""#, date_time),
Value::DaysAndTimeDuration(dt_duration) => format!(r#""{}""#, dt_duration),
Value::YearsAndMonthsDuration(ym_duration) => format!(r#""{}""#, ym_duration),
Value::ExpressionList(items) => values_to_jsonify(items),
Value::Context(ctx) => ctx.jsonify(),
Value::ContextEntryKey(name) => name.to_string(),
Value::List(items) => values_to_jsonify(items),
range @ Value::Range(..) => format!(r#""{}""#, range),
Value::Null(message) => {
if let Some(details) = message {
format!(r#""null({details})""#)
} else {
"null".to_string()
}
}
_ => format!("jsonify trait not implemented for value: {self}"),
}
}
}
impl Value {
pub fn is_null(&self) -> bool {
matches!(self, Value::Null(_))
}
pub fn is_true(&self) -> bool {
matches!(self, Value::Boolean(true))
}
pub fn is_number(&self) -> bool {
matches!(self, Value::Number(_))
}
pub fn is_invalid_coercion(&self) -> bool {
if let Value::Null(Some(message)) = self {
message == INVALID_COERCION
} else {
false
}
}
pub fn type_of(&self) -> FeelType {
match self {
Value::Boolean(_) => FeelType::Boolean,
Value::BuiltInFunction(_) => FeelType::Any,
Value::Context(context) => {
let mut entries = BTreeMap::new();
for (name, value) in context.deref() {
entries.insert(name.clone(), value.type_of());
}
FeelType::Context(entries)
}
Value::ContextEntry(_, _) => FeelType::Any,
Value::ContextEntryKey(_) => FeelType::Any,
Value::ContextType(feel_type) => feel_type.clone(),
Value::ContextTypeEntry(_, feel_type) => feel_type.clone(),
Value::ContextTypeEntryKey(_) => FeelType::Any,
Value::Date(_) => FeelType::Date,
Value::DateTime(_) => FeelType::DateTime,
Value::DaysAndTimeDuration(_) => FeelType::DaysAndTimeDuration,
Value::ExpressionList(_) => FeelType::Any,
Value::ExternalJavaFunction(_, _) => FeelType::Any,
Value::ExternalPmmlFunction(_, _) => FeelType::Any,
Value::FeelType(feel_type) => feel_type.clone(),
Value::FormalParameter(_, feel_type) => feel_type.clone(),
Value::FormalParameters(_) => FeelType::Any,
Value::FunctionBody(_, _) => FeelType::Any,
Value::FunctionDefinition(parameters, _, _, _, _, result_type) => {
let parameter_types = parameters.iter().map(|(_, feel_type)| feel_type.clone()).collect();
FeelType::Function(parameter_types, Box::new(result_type.clone()))
}
Value::IntervalEnd(interval_end, _) => interval_end.type_of(),
Value::IntervalStart(interval_start, _) => interval_start.type_of(),
Value::Irrelevant => FeelType::Any,
Value::List(values) => {
if values.is_empty() {
FeelType::List(Box::new(FeelType::Null))
} else {
let item_type = values[0].type_of();
for item in values {
if !item.type_of().is_conformant(&item_type) {
return FeelType::List(Box::new(FeelType::Any));
}
}
FeelType::List(Box::new(item_type))
}
}
Value::NamedParameter(_, _) => FeelType::Any,
Value::NamedParameters(_) => FeelType::Any,
Value::NegatedCommaList(_) => FeelType::Any,
Value::Null(_) => FeelType::Null,
Value::Number(_) => FeelType::Number,
Value::ParameterName(_) => FeelType::Any,
Value::ParameterTypes(_) => FeelType::Any,
Value::PositionalParameters(_) => FeelType::Any,
Value::QualifiedNameSegment(_) => FeelType::Any,
Value::Range(range_start, _, range_end, _) => {
let range_start_type = range_start.type_of();
let range_end_type = range_end.type_of();
if range_start_type == range_end_type {
return FeelType::Range(Box::new(range_start_type));
}
FeelType::Range(Box::new(FeelType::Any))
}
Value::String(_) => FeelType::String,
Value::Time(_) => FeelType::Time,
Value::UnaryGreater(_) => FeelType::Boolean,
Value::UnaryGreaterOrEqual(_) => FeelType::Boolean,
Value::UnaryLess(_) => FeelType::Boolean,
Value::UnaryLessOrEqual(_) => FeelType::Boolean,
Value::YearsAndMonthsDuration(_) => FeelType::YearsAndMonthsDuration,
}
}
pub fn is_conformant(&self, target_type: &FeelType) -> bool {
if matches!(target_type, FeelType::Any) {
return true;
}
match self {
Value::Null(_) => true,
Value::Boolean(_) => matches!(target_type, FeelType::Boolean),
Value::Number(_) => matches!(target_type, FeelType::Number),
Value::String(_) => matches!(target_type, FeelType::String),
Value::Date(_) => matches!(target_type, FeelType::Date),
Value::Time(_) => matches!(target_type, FeelType::Time),
Value::DateTime(_) => matches!(target_type, FeelType::DateTime),
Value::DaysAndTimeDuration(_) => matches!(target_type, FeelType::DaysAndTimeDuration),
Value::YearsAndMonthsDuration(_) => {
matches!(target_type, FeelType::YearsAndMonthsDuration)
}
Value::Range(r_start, _, r_end, _) => {
if let FeelType::Range(range_type) = target_type {
return r_start.is_conformant(range_type) && r_end.is_conformant(range_type);
}
false
}
Value::List(items) => {
if let FeelType::List(list_type) = target_type {
for item in items {
if !item.is_conformant(list_type) {
return false;
}
}
return true;
}
false
}
Value::Context(context) => {
if let FeelType::Context(type_context) = target_type {
for (name, entry_type) in type_context.iter() {
if let Some(entry_value) = context.get(name) {
if !entry_value.is_conformant(entry_type) {
return false;
}
} else {
return false;
}
}
return true;
}
false
}
Value::FunctionDefinition(parameters, _, _, _, _, result_type) => {
if let FeelType::Function(t_parameters, t_result) = target_type {
if parameters.len() != t_parameters.len() {
return false;
}
if !result_type.is_conformant(t_result) {
return false;
}
for (i, (_, parameter_type)) in parameters.iter().enumerate() {
if !parameter_type.is_conformant(&t_parameters[i]) {
return false;
}
}
return true;
}
false
}
_ => false,
}
}
pub fn coerced(&self, target_type: &FeelType) -> Value {
if let Value::FunctionDefinition(_, _, _, _, _, _) = self {
return self.clone();
}
if let Value::BuiltInFunction(bif) = self {
if bif.feel_type().is_conformant(target_type) {
return self.clone();
}
}
if self.is_conformant(target_type) {
return self.clone();
}
match self {
Value::List(items) => {
if items.len() == 1 {
let value = items[0].clone();
if value.is_conformant(target_type) {
return value;
}
}
}
value => {
if let FeelType::List(list_type) = target_type {
if value.is_conformant(list_type) {
return Value::List(vec![value.clone()]);
}
}
}
}
value_null!(INVALID_COERCION)
}
pub fn try_from_xsd_integer(text: &str) -> Result<Self> {
let value = text.parse::<FeelNumber>().map_err(|_| err_invalid_xsd_integer(text))?;
Ok(Value::Number(value))
}
pub fn try_from_xsd_decimal(text: &str) -> Result<Self> {
let value = text.parse::<FeelNumber>().map_err(|_| err_invalid_xsd_decimal(text))?;
Ok(Value::Number(value))
}
pub fn try_from_xsd_double(text: &str) -> Result<Self> {
let value = text.parse::<FeelNumber>().map_err(|_| err_invalid_xsd_double(text))?;
Ok(Value::Number(value))
}
pub fn try_from_xsd_boolean(text: &str) -> Result<Self> {
match text {
"true" | "1" => Ok(Value::Boolean(true)),
"false" | "0" => Ok(Value::Boolean(false)),
_ => Err(err_invalid_xsd_boolean(text)),
}
}
pub fn try_from_xsd_date(text: &str) -> Result<Self> {
if let Ok(feel_date) = FeelDate::from_str(text) {
return Ok(Value::Date(feel_date));
}
Err(err_invalid_xsd_date(text))
}
pub fn try_from_xsd_time(text: &str) -> Result<Self> {
if let Ok(feel_time) = FeelTime::from_str(text) {
return Ok(Value::Time(feel_time));
}
Err(err_invalid_xsd_time(text))
}
pub fn try_from_xsd_date_time(text: &str) -> Result<Self> {
Ok(Value::DateTime(FeelDateTime::try_from(text).map_err(|_| err_invalid_xsd_date_time(text))?))
}
pub fn try_from_xsd_duration(text: &str) -> Result<Self> {
if let Ok(ym_duration) = FeelYearsAndMonthsDuration::try_from(text) {
return Ok(Value::YearsAndMonthsDuration(ym_duration));
}
if let Ok(dt_duration) = FeelDaysAndTimeDuration::try_from(text) {
return Ok(Value::DaysAndTimeDuration(dt_duration));
}
Err(err_invalid_xsd_duration(text))
}
}
pub type Values = Vec<Value>;
pub fn values_to_string(values: &Values) -> String {
format!("[{}]", values.iter().map(|value| value.to_string()).collect::<Vec<String>>().join(", "))
}
pub fn values_to_feel_string(values: &Values) -> String {
format!("[{}]", values.iter().map(|value| value.to_feel_string()).collect::<Vec<String>>().join(", "))
}
pub fn values_to_jsonify(values: &Values) -> String {
format!("[{}]", values.iter().map(|value| value.jsonify()).collect::<Vec<String>>().join(", "))
}