extern crate alloc;
use heapless::{FnvIndexMap, String as HeaplessString};
use alloc::string::ToString;
pub const EXP_RS_MAX_VARIABLES: usize = 16;
pub const EXP_RS_MAX_BATCH_PARAMS: usize = 64;
pub const EXP_RS_MAX_CONSTANTS: usize = 8;
pub const EXP_RS_MAX_ARRAYS: usize = 4;
pub const EXP_RS_MAX_ATTRIBUTES: usize = 4;
pub const EXP_RS_MAX_NESTED_ARRAYS: usize = 2;
pub const EXP_RS_MAX_AST_CACHE: usize = 16;
pub const EXP_RS_MAX_NATIVE_FUNCTIONS: usize = 64;
pub const EXP_RS_MAX_EXPRESSION_FUNCTIONS: usize = 8;
pub const EXP_RS_MAX_ATTR_KEYS: usize = 4;
pub const EXP_RS_MAX_KEY_LENGTH: usize = 32;
pub const EXP_RS_MAX_FUNCTION_NAME_LENGTH: usize = 32;
pub const EXP_RS_ERROR_BUFFER_SIZE: usize = 256;
pub type HString = HeaplessString<EXP_RS_MAX_KEY_LENGTH>;
pub type FunctionName = HeaplessString<EXP_RS_MAX_FUNCTION_NAME_LENGTH>;
pub type VariableMap = FnvIndexMap<HString, crate::Real, EXP_RS_MAX_VARIABLES>;
pub type ConstantMap = FnvIndexMap<HString, crate::Real, EXP_RS_MAX_CONSTANTS>;
pub type BatchParamMap = FnvIndexMap<HString, crate::Real, EXP_RS_MAX_BATCH_PARAMS>;
pub type ArrayMap = FnvIndexMap<HString, alloc::vec::Vec<crate::Real>, EXP_RS_MAX_ARRAYS>;
pub type AttributeMap = FnvIndexMap<
HString,
FnvIndexMap<HString, crate::Real, EXP_RS_MAX_ATTR_KEYS>,
EXP_RS_MAX_ATTRIBUTES,
>;
pub type NestedArrayMap = FnvIndexMap<
HString,
FnvIndexMap<usize, alloc::vec::Vec<crate::Real>, EXP_RS_MAX_NESTED_ARRAYS>,
EXP_RS_MAX_NESTED_ARRAYS,
>;
pub type NativeFunctionMap = FnvIndexMap<FunctionName, NativeFunction, EXP_RS_MAX_NATIVE_FUNCTIONS>;
pub type ExpressionFunctionMap =
FnvIndexMap<FunctionName, ExpressionFunction, EXP_RS_MAX_EXPRESSION_FUNCTIONS>;
#[cfg(test)]
use crate::Real;
#[cfg(not(test))]
use crate::{Real, String, Vec};
#[cfg(not(test))]
use alloc::rc::Rc;
#[cfg(test)]
use std::rc::Rc;
#[cfg(test)]
use std::string::String;
#[cfg(test)]
use std::vec::Vec;
#[derive(Debug)]
#[repr(C, align(8))]
pub enum AstExpr<'arena> {
Constant(Real),
Variable(&'arena str),
Function {
name: &'arena str,
args: &'arena [AstExpr<'arena>],
},
Array {
name: &'arena str,
index: &'arena AstExpr<'arena>,
},
Attribute {
base: &'arena str,
attr: &'arena str,
},
LogicalOp {
op: LogicalOperator,
left: &'arena AstExpr<'arena>,
right: &'arena AstExpr<'arena>,
},
Conditional {
condition: &'arena AstExpr<'arena>,
true_branch: &'arena AstExpr<'arena>,
false_branch: &'arena AstExpr<'arena>,
},
}
pub trait TryIntoHeaplessString {
fn try_into_heapless(self) -> Result<HString, crate::error::ExprError>;
}
impl TryIntoHeaplessString for &str {
fn try_into_heapless(self) -> Result<HString, crate::error::ExprError> {
HString::try_from(self).map_err(|_| {
crate::error::ExprError::StringTooLong(self.to_string(), EXP_RS_MAX_KEY_LENGTH)
})
}
}
impl TryIntoHeaplessString for alloc::string::String {
fn try_into_heapless(self) -> Result<HString, crate::error::ExprError> {
HString::try_from(self.as_str())
.map_err(|_| crate::error::ExprError::StringTooLong(self, EXP_RS_MAX_KEY_LENGTH))
}
}
pub trait TryIntoFunctionName {
fn try_into_function_name(self) -> Result<FunctionName, crate::error::ExprError>;
}
impl TryIntoFunctionName for &str {
fn try_into_function_name(self) -> Result<FunctionName, crate::error::ExprError> {
FunctionName::try_from(self).map_err(|_| {
crate::error::ExprError::StringTooLong(
self.to_string(),
EXP_RS_MAX_FUNCTION_NAME_LENGTH,
)
})
}
}
impl TryIntoFunctionName for alloc::string::String {
fn try_into_function_name(self) -> Result<FunctionName, crate::error::ExprError> {
FunctionName::try_from(self.as_str()).map_err(|_| {
crate::error::ExprError::StringTooLong(self, EXP_RS_MAX_FUNCTION_NAME_LENGTH)
})
}
}
impl<'arena> AstExpr<'arena> {
pub fn pow(&self, exp: Real) -> Real {
match self {
AstExpr::Constant(val) => {
#[cfg(all(feature = "libm", feature = "f32"))]
{
libm::powf(*val, *exp)
}
#[cfg(all(feature = "libm", not(feature = "f32")))]
{
libm::pow(*val, exp)
}
#[cfg(all(not(feature = "libm"), test))]
{
val.powf(*exp)
} #[cfg(all(not(feature = "libm"), not(test)))]
{
if exp == 0.0 {
1.0
} else if exp == 1.0 {
*val
} else if exp == 2.0 {
*val * *val
} else {
0.0
} }
}
_ => 0.0, }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::context::EvalContext;
use crate::error::ExprError;
use crate::eval::eval_ast;
use bumpalo::Bump;
use std::rc::Rc;
#[test]
fn test_eval_ast_array_and_attribute_errors() {
let arena = Bump::new();
let index_ast = arena.alloc(AstExpr::Constant(0.0));
let ast = AstExpr::Array {
name: "arr",
index: index_ast,
};
let err = eval_ast(&ast, None, &arena).unwrap_err();
match err {
ExprError::UnknownVariable { name } => assert_eq!(name, "arr"),
_ => panic!("Expected UnknownVariable error"),
}
let ast2 = AstExpr::Attribute {
base: "foo",
attr: "bar",
};
let err2 = eval_ast(&ast2, None, &arena).unwrap_err();
match err2 {
ExprError::AttributeNotFound { base, attr } => {
assert_eq!(base, "foo");
assert_eq!(attr, "bar");
}
_ => panic!("Expected AttributeNotFound error"),
}
}
#[test]
fn test_eval_ast_function_wrong_arity() {
let arena = Bump::new();
let mut ctx = EvalContext::new();
let _ = ctx.register_native_function("sin", 1, |args| args[0].sin());
let ctx = Rc::new(ctx);
let args = arena.alloc([AstExpr::Constant(1.0), AstExpr::Constant(2.0)]);
let ast = AstExpr::Function {
name: "sin",
args: args,
};
let err = eval_ast(&ast, Some(ctx), &arena).unwrap_err();
match err {
ExprError::InvalidFunctionCall {
name,
expected,
found,
} => {
assert_eq!(name, "sin");
assert_eq!(expected, 1);
assert_eq!(found, 2);
}
_ => panic!("Expected InvalidFunctionCall error"),
}
}
#[test]
fn test_eval_ast_unknown_function_and_variable() {
let arena = Bump::new();
let args = arena.alloc([AstExpr::Constant(1.0)]);
let ast = AstExpr::Function {
name: "notafunc",
args: args,
};
let err = eval_ast(&ast, None, &arena).unwrap_err();
match err {
ExprError::UnknownFunction { name } => assert_eq!(name, "notafunc"),
_ => panic!("Expected UnknownFunction error"),
}
let ast2 = AstExpr::Variable("notavar");
let err2 = eval_ast(&ast2, None, &arena).unwrap_err();
match err2 {
ExprError::UnknownVariable { name } => assert_eq!(name, "notavar"),
_ => panic!("Expected UnknownVariable error"),
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum ExprKind {
Constant,
Variable,
Function {
arity: usize,
},
Array,
Attribute,
LogicalOp,
Conditional,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum TokenKind {
Number,
Variable,
Operator,
Open,
Close,
Separator,
End,
Error,
Null,
}
#[derive(Clone, Debug, PartialEq)]
pub enum LogicalOperator {
And,
Or,
}
impl core::fmt::Display for LogicalOperator {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
LogicalOperator::And => write!(f, "&&"),
LogicalOperator::Or => write!(f, "||"),
}
}
}
#[derive(Clone)]
pub struct NativeFunction {
pub arity: usize,
pub implementation: Rc<dyn Fn(&[Real]) -> Real>,
pub name: FunctionName,
pub description: Option<String>,
}
use alloc::borrow::Cow;
pub struct ExpressionFunction {
pub name: FunctionName,
pub params: Vec<String>,
pub expression: String,
pub description: Option<String>,
pub param_buffer: Option<*mut [(crate::types::HString, crate::Real)]>,
}
impl Clone for ExpressionFunction {
fn clone(&self) -> Self {
Self {
name: self.name.clone(),
params: self.params.clone(),
expression: self.expression.clone(),
description: self.description.clone(),
param_buffer: self.param_buffer, }
}
}
#[doc(hidden)]
pub struct Variable<'a> {
pub name: Cow<'a, str>,
pub address: i8,
pub function: fn(Real, Real) -> Real,
pub context: Vec<AstExpr<'a>>,
}
impl<'a> Variable<'a> {
pub fn new(name: &'a str) -> Variable<'a> {
Variable {
name: Cow::Borrowed(name),
address: 0,
function: crate::functions::dummy,
context: Vec::<AstExpr<'a>>::new(),
}
}
}