use crate::Error;
use crate::builtinops::BuiltinOp;
use crate::evaluator::intooperation::OperationFn;
pub(crate) type NumberType = i64;
#[cfg(any(feature = "scheme", feature = "jsonlogic"))]
pub(crate) const SYMBOL_SPECIAL_CHARS: &str = "+-*/<>=!?_$";
#[cfg(any(feature = "scheme", feature = "jsonlogic"))]
pub(crate) fn is_valid_symbol(name: &str) -> bool {
let mut chars = name.chars();
match chars.next() {
None => false, Some(first_char) => {
if first_char.is_ascii_digit() {
return false;
}
if first_char == '-'
&& let Some(second_char) = chars.next()
&& second_char.is_ascii_digit()
{
return false;
}
name.chars()
.all(|c| c.is_alphanumeric() || SYMBOL_SPECIAL_CHARS.contains(c))
}
}
}
#[derive(Clone)]
pub enum Value {
Number(NumberType),
Symbol(String),
String(String),
Bool(bool),
List(Vec<Value>),
PrecompiledOp {
op: &'static BuiltinOp,
op_id: String,
args: Vec<Value>,
},
BuiltinFunction {
id: String,
func: std::sync::Arc<OperationFn>,
},
Function {
params: Vec<String>,
body: Box<Value>,
env: crate::evaluator::Environment,
},
Unspecified,
}
impl std::fmt::Debug for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Value::Number(n) => write!(f, "Number({n})"),
Value::Symbol(s) => write!(f, "Symbol({s})"),
Value::String(s) => write!(f, "String(\"{s}\")"),
Value::Bool(b) => write!(f, "Bool({b})"),
Value::List(list) => {
write!(f, "List(")?;
for (i, v) in list.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{v:?}")?;
}
write!(f, ")")
}
Value::PrecompiledOp { op_id, args, .. } => {
write!(f, "PrecompiledOp({op_id}, args=[")?;
for (i, a) in args.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{a:?}")?;
}
write!(f, "])")
}
Value::BuiltinFunction { id, .. } => write!(f, "BuiltinFunction({id})"),
Value::Function { params, body, .. } => {
write!(f, "Function(params={params:?}, body={body:?})")
}
Value::Unspecified => write!(f, "Unspecified"),
}
}
}
impl From<&str> for Value {
fn from(s: &str) -> Self {
Value::String(s.to_owned())
}
}
impl From<String> for Value {
fn from(s: String) -> Self {
Value::String(s)
}
}
impl From<bool> for Value {
fn from(b: bool) -> Self {
Value::Bool(b)
}
}
macro_rules! impl_from_integer {
($int_type:ty) => {
impl From<$int_type> for Value {
fn from(n: $int_type) -> Self {
Value::Number(n as i64)
}
}
};
}
impl_from_integer!(i8);
impl_from_integer!(i16);
impl_from_integer!(i32);
impl_from_integer!(NumberType); impl_from_integer!(u8);
impl_from_integer!(u16);
impl_from_integer!(u32);
impl<T: Into<Value>> From<Vec<T>> for Value {
fn from(v: Vec<T>) -> Self {
Value::List(v.into_iter().map(|x| x.into()).collect())
}
}
impl<T: Into<Value>, const N: usize> From<[T; N]> for Value {
fn from(arr: [T; N]) -> Self {
Value::List(arr.into_iter().map(|x| x.into()).collect())
}
}
impl<T: Into<Value> + Clone> From<&[T]> for Value {
fn from(slice: &[T]) -> Self {
Value::List(slice.iter().cloned().map(|x| x.into()).collect())
}
}
impl std::convert::TryInto<NumberType> for Value {
type Error = Error;
fn try_into(self) -> Result<NumberType, Error> {
if let Value::Number(n) = self {
Ok(n)
} else {
Err(Error::TypeError("expected number".into()))
}
}
}
impl std::convert::TryInto<bool> for Value {
type Error = Error;
fn try_into(self) -> Result<bool, Error> {
if let Value::Bool(b) = self {
Ok(b)
} else {
Err(Error::TypeError("expected boolean".into()))
}
}
}
#[cfg_attr(not(test), expect(dead_code))]
pub(crate) fn sym<S: AsRef<str>>(name: S) -> Value {
Value::Symbol(name.as_ref().to_owned())
}
#[cfg_attr(not(test), expect(dead_code))]
pub(crate) fn val<T: Into<Value>>(value: T) -> Value {
value.into()
}
#[cfg_attr(not(test), expect(dead_code))]
pub(crate) fn nil() -> Value {
Value::List(vec![])
}
impl std::fmt::Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Value::Number(n) => write!(f, "{n}"),
Value::Symbol(s) => write!(f, "{s}"),
Value::String(s) => {
write!(f, "\"")?;
for ch in s.chars() {
match ch {
'"' => write!(f, "\\\"")?,
'\\' => write!(f, "\\\\")?,
'\n' => write!(f, "\\n")?,
'\t' => write!(f, "\\t")?,
'\r' => write!(f, "\\r")?,
c => write!(f, "{c}")?,
}
}
write!(f, "\"")
}
Value::Bool(b) => write!(f, "{}", if *b { "#t" } else { "#f" }),
Value::List(elements) => {
write!(f, "(")?;
for (i, elem) in elements.iter().enumerate() {
if i > 0 {
write!(f, " ")?;
}
write!(f, "{elem}")?;
}
write!(f, ")")
}
Value::BuiltinFunction { id, .. } => write!(f, "#<builtin-function:{id}>"),
Value::PrecompiledOp { .. } => {
write!(f, "{}", self.to_uncompiled_form())
}
Value::Function { .. } => write!(f, "#<function>"),
Value::Unspecified => write!(f, "#<unspecified>"),
}
}
}
impl Value {
pub(crate) fn to_uncompiled_form(&self) -> Value {
match self {
Value::PrecompiledOp { op, args, .. } => {
let mut elements = vec![Value::Symbol(op.scheme_id.to_owned())];
for arg in args {
elements.push(arg.to_uncompiled_form()); }
Value::List(elements)
}
Value::List(elements) => {
Value::List(
elements
.iter()
.map(|elem| elem.to_uncompiled_form())
.collect(),
)
}
other => other.clone(), }
}
pub(crate) fn is_nil(&self) -> bool {
matches!(self, Value::List(list) if list.is_empty())
}
}
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Value::Number(a), Value::Number(b)) => a == b,
(Value::Symbol(a), Value::Symbol(b)) => a == b,
(Value::String(a), Value::String(b)) => a == b,
(Value::Bool(a), Value::Bool(b)) => a == b,
(Value::List(a), Value::List(b)) => a == b,
(
Value::PrecompiledOp {
op_id: id1,
args: args1,
..
},
Value::PrecompiledOp {
op_id: id2,
args: args2,
..
},
) => {
id1 == id2 && args1 == args2
}
(Value::BuiltinFunction { id: id1, .. }, Value::BuiltinFunction { id: id2, .. }) => {
id1 == id2
}
(
Value::Function {
params: p1,
body: b1,
env: e1,
},
Value::Function {
params: p2,
body: b2,
env: e2,
},
) => p1 == p2 && b1 == b2 && e1 == e2,
(Value::Unspecified, _) | (_, Value::Unspecified) => false, _ => false, }
}
}
#[cfg(test)]
mod helper_function_tests {
use super::*;
#[test]
fn test_helper_functions_data_driven() {
let test_cases = vec![
(val(42), Value::Number(42)),
(val(-17), Value::Number(-17)),
(val(-0), Value::Number(0)),
(val(4294967295u32), Value::Number(4294967295)),
(val(2147483647i32), Value::Number(2147483647)),
(val(255u8), Value::Number(255)),
(val(-128i8), Value::Number(-128)),
(val(65535u16), Value::Number(65535)),
(val(-32768i16), Value::Number(-32768)),
(val(NumberType::MAX), Value::Number(NumberType::MAX)),
(val(NumberType::MIN), Value::Number(NumberType::MIN)),
(val(true), Value::Bool(true)),
(val("hello"), Value::String("hello".to_owned())),
(val(""), Value::String(String::new())),
(sym("foo-bar?"), Value::Symbol("foo-bar?".to_owned())),
(sym("-"), Value::Symbol("-".to_owned())),
(sym(String::from("test")), Value::Symbol("test".to_owned())),
(nil(), Value::List(vec![])),
(
val([1, 2, 3]),
Value::List(vec![Value::Number(1), Value::Number(2), Value::Number(3)]),
),
(
val(["hello", "world"]),
Value::List(vec![
Value::String("hello".to_owned()),
Value::String("world".to_owned()),
]),
),
(
val([true, false, true]),
Value::List(vec![
Value::Bool(true),
Value::Bool(false),
Value::Bool(true),
]),
),
(
val(vec![sym("operation"), val(42), val("result"), val(true)]),
Value::List(vec![
Value::Symbol("operation".to_owned()),
Value::Number(42),
Value::String("result".to_owned()),
Value::Bool(true),
]),
),
(
Value::from(&[1i64, 2, 3][..]),
Value::List(vec![Value::Number(1), Value::Number(2), Value::Number(3)]),
),
];
run_helper_function_tests(test_cases);
}
fn run_helper_function_tests(test_cases: Vec<(Value, Value)>) {
for (i, (actual, expected)) in test_cases.iter().enumerate() {
assert!(
!(actual != expected),
"Test case {} failed:\n Expected: {:?}\n Got: {:?}",
i + 1,
expected,
actual
);
}
}
#[test]
fn test_unspecified_values() {
let unspec = Value::Unspecified;
assert_ne!(unspec, unspec);
assert_ne!(unspec, Value::Unspecified);
assert_ne!(unspec, val(42));
}
#[test]
#[cfg(feature = "scheme")]
fn test_debug_display_and_equality() {
use crate::evaluator::{create_global_env, eval};
use crate::scheme::parse_scheme;
let mut env = create_global_env();
eval(&parse_scheme("(define f +)").unwrap(), &mut env).unwrap();
let builtin_fn = eval(&parse_scheme("f").unwrap(), &mut env).unwrap();
let func = eval(&parse_scheme("(lambda (x) (+ x 1))").unwrap(), &mut env).unwrap();
let precompiled = parse_scheme("(+ 1 2)").unwrap();
let list_val = val([1, 2, 3]);
let debug_cases: Vec<(&Value, &str)> = vec![
(&list_val, "List("),
(&precompiled, "PrecompiledOp"),
(&builtin_fn, "BuiltinFunction"),
(&func, "Function"),
(&Value::Unspecified, "Unspecified"),
];
for (value, expected) in &debug_cases {
assert!(
format!("{value:?}").contains(expected),
"Debug of {value:?} should contain '{expected}'"
);
}
let display_cases: Vec<(&Value, &str)> = vec![
(&builtin_fn, "#<builtin-function:"),
(&func, "#<function>"),
(&Value::Unspecified, "#<unspecified>"),
];
for (value, expected) in &display_cases {
assert!(
format!("{value}").contains(expected),
"Display of {value} should contain '{expected}'"
);
}
let mut env2 = create_global_env();
eval(&parse_scheme("(define f2 +)").unwrap(), &mut env2).unwrap();
assert!(builtin_fn == eval(&parse_scheme("f2").unwrap(), &mut env2).unwrap());
let func_b = eval(&parse_scheme("(lambda (x) (+ x 1))").unwrap(), &mut env).unwrap();
assert!(func == func_b);
assert!(precompiled == parse_scheme("(+ 1 2)").unwrap());
assert!(builtin_fn != func);
let error_cases: Vec<(crate::Error, &str)> = vec![
(
crate::Error::arity_error_with_expr(2, 3, "x".into()),
"Arity error",
),
(crate::Error::ParseError("x".into()), "Parse error"),
(crate::Error::arity_error(2, 3), "Arity error"),
];
for (error, expected) in &error_cases {
assert!(
format!("{error}").contains(expected),
"Error '{error}' should contain '{expected}'"
);
}
}
}