extern crate core;
use std::convert::TryFrom;
use std::fmt;
use std::sync::Arc;
use thiserror::Error;
mod macros;
#[doc(hidden)]
pub use macros::to_camel_case;
pub mod common;
mod container;
pub mod context;
mod env;
pub mod extensions;
pub mod parser;
pub use common::ast::IdedExpr;
use common::ast::SelectExpr;
pub use context::Context;
pub use functions::FunctionContext;
pub use objects::{ResolveResult, Value};
use parser::{Expression, ExpressionReferences, Parser};
pub use parser::{ParseError, ParseErrors};
pub mod functions;
mod magic;
pub mod objects;
mod resolvers;
#[cfg(feature = "chrono")]
mod duration;
#[cfg(feature = "chrono")]
pub use ser::{Duration, Timestamp};
pub use env::Env;
mod registry;
pub use env::StructDef;
pub use registry::StructType;
pub use registry::{TypeDecl, TypeRegistry};
mod ser;
pub use ser::to_value;
pub use ser::SerializationError;
#[cfg(feature = "json")]
mod json;
#[cfg(feature = "json")]
pub use json::ConvertToJsonError;
use magic::FromContext;
pub mod extractors {
pub use crate::magic::{Arguments, Identifier, This};
pub use crate::magic::{IntoFunction, IntoResolveResult};
}
#[derive(Clone, Debug, PartialEq)]
pub struct OverloadError {
function: Arc<String>,
argument_types: Arc<[String]>,
member_function: bool,
}
impl OverloadError {
fn new(function: &str, argument_types: Vec<String>, member_function: bool) -> Self {
Self {
function: Arc::new(function.to_owned()),
argument_types: argument_types.into(),
member_function,
}
}
pub fn function(&self) -> &str {
self.function.as_str()
}
pub fn argument_types(&self) -> &[String] {
&self.argument_types
}
pub fn is_member_function(&self) -> bool {
self.member_function
}
}
impl fmt::Display for OverloadError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let signature = if self.member_function && !self.argument_types.is_empty() {
format!(
"{}.({})",
self.argument_types[0],
self.argument_types[1..].join(", ")
)
} else {
format!("({})", self.argument_types.join(", "))
};
write!(
f,
"found no matching overload for '{}' applied to '{signature}'",
self.function
)
}
}
impl std::error::Error for OverloadError {}
#[derive(Error, Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum ExecutionError {
#[error("Invalid argument count: expected {expected}, got {actual}")]
InvalidArgumentCount { expected: usize, actual: usize },
#[error("Invalid argument type: {:?}", .target)]
UnsupportedTargetType { target: Value },
#[error("Method '{method}' not supported on type '{target:?}'")]
NotSupportedAsMethod { method: String, target: Value },
#[error("Unable to use value '{0:?}' as a key")]
UnsupportedKeyType(Value),
#[error("Unexpected type: got '{got}', want '{want}'")]
UnexpectedType { got: String, want: String },
#[error("No such key: {0}")]
NoSuchKey(Arc<String>),
#[error("{0}")]
NoSuchOverload(OverloadError),
#[error("Undeclared reference to '{0}'")]
UndeclaredReference(Arc<String>),
#[error("Missing argument or target")]
MissingArgumentOrTarget,
#[error("{0:?} can not be compared to {1:?}")]
ValuesNotComparable(Value, Value),
#[deprecated]
#[error("Unsupported unary operator '{0}': {1:?}")]
UnsupportedUnaryOperator(&'static str, Value),
#[error("Unsupported binary operator '{0}': {1:?}, {2:?}")]
UnsupportedBinaryOperator(&'static str, Value, Value),
#[deprecated]
#[error("Cannot use value as map index: {0:?}")]
UnsupportedMapIndex(Value),
#[deprecated]
#[error("Cannot use value as list index: {0:?}")]
UnsupportedListIndex(Value),
#[error("Cannot use value {0:?} to index {1:?}")]
UnsupportedIndex(Value, Value),
#[deprecated]
#[error("Unsupported function call identifier type: {0:?}")]
UnsupportedFunctionCallIdentifierType(Expression),
#[deprecated]
#[error("Unsupported fields construction: {0:?}")]
UnsupportedFieldsConstruction(SelectExpr),
#[error("Error executing function '{function}': {message}")]
FunctionError { function: String, message: String },
#[error("Division by zero of {0:?}")]
DivisionByZero(Value),
#[error("Remainder by zero of {0:?}")]
RemainderByZero(Value),
#[error("Overflow from binary operator '{0}': {1:?}, {2:?}")]
Overflow(&'static str, Value, Value),
#[error("Index out of bounds: {0:?}")]
IndexOutOfBounds(Value),
#[error("Failed with repeated key: {0:?}")]
DuplicateKey(Value),
#[error("InternalError: {0:?}")]
InternalError(String),
}
impl ExecutionError {
pub fn no_such_overload(function: &str, argument_types: Vec<String>) -> Self {
ExecutionError::NoSuchOverload(OverloadError::new(function, argument_types, false))
}
pub fn no_such_member_overload(function: &str, argument_types: Vec<String>) -> Self {
ExecutionError::NoSuchOverload(OverloadError::new(function, argument_types, true))
}
pub(crate) fn overload_for_values<'a>(
function: &str,
arguments: impl IntoIterator<Item = &'a dyn common::value::Val>,
member_function: bool,
) -> Self {
let argument_types = arguments
.into_iter()
.map(|argument| argument.get_type().name().to_owned())
.collect();
let overload = OverloadError::new(function, argument_types, member_function);
ExecutionError::NoSuchOverload(overload)
}
pub(crate) fn with_overload_context(self, context: ExecutionError) -> Self {
self.with_lazy_overload_context(|| context)
}
pub(crate) fn with_lazy_overload_context(
self,
context: impl FnOnce() -> ExecutionError,
) -> Self {
match self {
ExecutionError::NoSuchOverload(_)
| ExecutionError::ValuesNotComparable(_, _)
| ExecutionError::UnsupportedKeyType(_)
| ExecutionError::UnexpectedType { .. } => context(),
error => error,
}
}
pub(crate) fn values_not_comparable(
lhs: &dyn common::value::Val,
rhs: &dyn common::value::Val,
) -> Self {
ExecutionError::ValuesNotComparable(
Value::try_from(lhs).unwrap_or(Value::Null),
Value::try_from(rhs).unwrap_or(Value::Null),
)
}
pub(crate) fn unsupported_binary_operator(
operator: &'static str,
lhs: &dyn common::value::Val,
rhs: &dyn common::value::Val,
) -> Self {
ExecutionError::UnsupportedBinaryOperator(
operator,
Value::try_from(lhs).unwrap_or(Value::Null),
Value::try_from(rhs).unwrap_or(Value::Null),
)
}
pub fn no_such_key(name: &str) -> Self {
ExecutionError::NoSuchKey(Arc::new(name.to_string()))
}
pub fn undeclared_reference(name: &str) -> Self {
ExecutionError::UndeclaredReference(Arc::new(name.to_string()))
}
pub fn invalid_argument_count(expected: usize, actual: usize) -> Self {
ExecutionError::InvalidArgumentCount { expected, actual }
}
pub fn function_error<E: ToString>(function: &str, error: E) -> Self {
ExecutionError::FunctionError {
function: function.to_string(),
message: error.to_string(),
}
}
pub fn unsupported_target_type(target: Value) -> Self {
ExecutionError::UnsupportedTargetType { target }
}
pub fn not_supported_as_method(method: &str, target: Value) -> Self {
ExecutionError::NotSupportedAsMethod {
method: method.to_string(),
target,
}
}
pub fn unsupported_key_type(value: Value) -> Self {
ExecutionError::UnsupportedKeyType(value)
}
pub fn missing_argument_or_target() -> Self {
ExecutionError::MissingArgumentOrTarget
}
}
#[derive(Error, Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum DeclarationError {
#[error("Cannot add function '{function}': an overload with that name is already declared")]
OverloadConflict { function: String },
#[error("Cannot declare overload '{id}' of '{function}': one with the same id or signature is already declared")]
DuplicateOverload { function: String, id: String },
#[error("Cannot register type '{name}': another type with that name is already registered")]
TypeConflict { name: String },
#[error("Cannot register type '{name}': not a valid type name")]
InvalidTypeName { name: String },
#[error("Invalid container name '{name}': not an identifier, or several separated by dots")]
InvalidContainerName { name: String },
#[error("Cannot add macro '{function}': one for the same call style and argument count is already added")]
DuplicateMacro { function: String },
}
impl DeclarationError {
pub fn overload_conflict(function: &str) -> Self {
DeclarationError::OverloadConflict {
function: function.to_string(),
}
}
pub fn duplicate_overload(function: &str, id: &str) -> Self {
DeclarationError::DuplicateOverload {
function: function.to_string(),
id: id.to_string(),
}
}
pub fn type_conflict(name: &str) -> Self {
DeclarationError::TypeConflict {
name: name.to_string(),
}
}
pub fn invalid_type_name(name: &str) -> Self {
DeclarationError::InvalidTypeName {
name: name.to_string(),
}
}
pub fn invalid_container_name(name: &str) -> Self {
DeclarationError::InvalidContainerName {
name: name.to_string(),
}
}
pub fn duplicate_macro(function: &str) -> Self {
DeclarationError::DuplicateMacro {
function: function.to_string(),
}
}
}
#[derive(Debug)]
pub struct Program {
expression: Expression,
source_info: common::ast::SourceInfo,
}
impl Program {
pub fn compile(source: &str) -> Result<Program, ParseErrors> {
Program::parse_with(Parser::default(), source)
}
pub(crate) fn parse_with(parser: Parser, source: &str) -> Result<Program, ParseErrors> {
parser
.parse_with_source_info(source)
.map(|(expression, source_info)| Program {
expression,
source_info,
})
}
pub fn source_info(&self) -> &common::ast::SourceInfo {
&self.source_info
}
pub fn execute(&self, context: &Context) -> ResolveResult {
Value::resolve(&self.expression, context)
}
pub fn references(&self) -> ExpressionReferences<'_> {
self.expression.references()
}
pub fn expression(&self) -> &Expression {
&self.expression
}
}
impl TryFrom<&str> for Program {
type Error = ParseErrors;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Program::compile(value)
}
}
#[cfg(test)]
mod tests {
use crate::context::Context;
use crate::objects::{ResolveResult, Value};
use crate::{ExecutionError, Program};
use std::collections::HashMap;
use std::convert::TryInto;
pub(crate) fn test_script(script: &str, ctx: Option<Context>) -> ResolveResult {
let program = match Program::compile(script) {
Ok(p) => p,
Err(e) => panic!("{}", e),
};
program.execute(&ctx.unwrap_or_default())
}
#[test]
fn parse() {
Program::compile("1 + 1").unwrap();
}
#[test]
fn from_str() {
let input = "1.1";
let _p: Program = input.try_into().unwrap();
}
#[test]
fn variables() {
fn assert_output(script: &str, expected: ResolveResult) {
let mut ctx = Context::default();
ctx.add_variable_from_value("foo", HashMap::from([("bar", 1i64)]));
ctx.add_variable_from_value("arr", vec![1i64, 2, 3]);
ctx.add_variable_from_value("str", "foobar".to_string());
assert_eq!(test_script(script, Some(ctx)), expected);
}
assert_output("size([1, 2, 3]) == 3", Ok(true.into()));
assert_output("size([size([42]), 2, 3]) == 3", Ok(true.into()));
assert_output("size([]) == 3", Ok(false.into()));
assert_output("foo.bar == 1", Ok(true.into()));
assert_output("arr[0] == 1", Ok(true.into()));
assert_output(
"str[0]",
Err(ExecutionError::no_such_overload(
"_[_]",
vec!["string".to_owned(), "int".to_owned()],
)),
);
}
#[test]
fn references() {
let p = Program::compile("[1, 1].map(x, x * 2)").unwrap();
assert!(p.references().has_variable("x"));
assert_eq!(p.references().variables().len(), 1);
}
#[test]
fn test_execution_errors() {
let tests = vec![
(
"no such key",
"foo.baz.bar == 1",
ExecutionError::no_such_key("baz"),
),
(
"undeclared reference",
"missing == 1",
ExecutionError::undeclared_reference("missing"),
),
(
"undeclared method",
"1.missing()",
ExecutionError::undeclared_reference("missing"),
),
(
"undeclared function",
"missing(1)",
ExecutionError::undeclared_reference("missing"),
),
(
"unsupported key type",
"{null: true}",
ExecutionError::unsupported_key_type(Value::Null),
),
];
for (name, script, error) in tests {
let mut ctx = Context::default();
ctx.add_variable_from_value("foo", HashMap::from([("bar", 1)]));
let res = test_script(script, Some(ctx));
assert_eq!(res, error.into(), "{name}");
}
}
#[test]
fn no_such_overload_reports_the_call_and_runtime_types() {
let tests = [
(
"1 > \"1\"",
"found no matching overload for '_>_' applied to '(int, string)'",
),
(
"size(1, 2)",
"found no matching overload for 'size' applied to '(int, int)'",
),
(
"\"foobar\".contains(1)",
"found no matching overload for 'contains' applied to 'string.(int)'",
),
];
for (script, expected) in tests {
let error = test_script(script, None).expect_err(script);
assert_eq!(error.to_string(), expected, "{script}");
}
assert_eq!(
test_script("missing(1)", None),
Err(ExecutionError::undeclared_reference("missing"))
);
assert_eq!(
test_script("\"foobar\".missing(1)", None),
Err(ExecutionError::undeclared_reference("missing"))
);
let error = test_script("\"foobar\".contains(1)", None).unwrap_err();
let ExecutionError::NoSuchOverload(overload) = error else {
panic!("expected a no-such-overload error");
};
assert_eq!(overload.function(), "contains");
assert_eq!(overload.argument_types(), ["string", "int"]);
assert!(overload.is_member_function());
}
}