use crate::ast::{AssignmentOperator, BinaryOperator, Expression, Statement, Type};
use crate::error::{Error, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Value {
Null,
Boolean(bool),
Number(f64),
String(String),
Array(Vec<Value>),
Object(HashMap<String, Value>),
Function(Function),
NativeFunction(NativeFunction),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Function {
pub name: Option<String>,
pub parameters: Vec<String>,
pub body: Box<crate::ast::Statement>,
pub closure: HashMap<String, Value>,
}
#[derive(Clone)]
pub struct NativeFunction {
pub name: String,
pub arity: Option<usize>,
pub function: fn(&[Value]) -> Result<Value>,
}
impl fmt::Debug for NativeFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NativeFunction")
.field("name", &self.name)
.field("arity", &self.arity)
.finish()
}
}
impl PartialEq for NativeFunction {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.arity == other.arity
}
}
impl Serialize for NativeFunction {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&format!("NativeFunction({})", self.name))
}
}
impl<'de> Deserialize<'de> for NativeFunction {
fn deserialize<D>(_deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Err(serde::de::Error::custom(
"Cannot deserialize native functions",
))
}
}
impl Value {
pub fn native_function(
name: impl Into<String>,
arity: Option<usize>,
function: fn(&[Value]) -> Result<Value>,
) -> Value {
Value::NativeFunction(NativeFunction {
name: name.into(),
arity,
function,
})
}
pub fn type_name(&self) -> &'static str {
match self {
Value::Null => "null",
Value::Boolean(_) => "boolean",
Value::Number(_) => "number",
Value::String(_) => "string",
Value::Array(_) => "array",
Value::Object(_) => "object",
Value::Function(_) => "function",
Value::NativeFunction(_) => "native_function",
}
}
pub fn is_truthy(&self) -> bool {
match self {
Value::Null => false,
Value::Boolean(b) => *b,
Value::Number(n) => *n != 0.0 && !n.is_nan(),
Value::String(s) => !s.is_empty(),
Value::Array(arr) => !arr.is_empty(),
Value::Object(obj) => !obj.is_empty(),
Value::Function(_) | Value::NativeFunction(_) => true,
}
}
pub fn is_falsy(&self) -> bool {
!self.is_truthy()
}
pub fn to_boolean(&self) -> bool {
self.is_truthy()
}
pub fn to_number(&self) -> Result<f64> {
match self {
Value::Number(n) => Ok(*n),
Value::Boolean(b) => Ok(if *b { 1.0 } else { 0.0 }),
Value::String(s) => {
s.parse::<f64>()
.map_err(|_| Error::type_error("number", &format!("string '{}'", s)))
}
Value::Null => Ok(0.0),
_ => Err(Error::type_error("number", self.type_name())),
}
}
pub fn to_string(&self) -> String {
match self {
Value::Null => "null".to_string(),
Value::Boolean(b) => b.to_string(),
Value::Number(n) => {
if n.fract() == 0.0 && n.is_finite() {
format!("{}", *n as i64)
} else {
n.to_string()
}
}
Value::String(s) => s.clone(),
Value::Array(arr) => {
let elements: Vec<String> = arr.iter().map(|v| v.to_string()).collect();
format!("[{}]", elements.join(", "))
}
Value::Object(obj) => {
let pairs: Vec<String> = obj
.iter()
.map(|(k, v)| format!("{}: {}", k, v.to_string()))
.collect();
format!("{{{}}}", pairs.join(", "))
}
Value::Function(f) => {
format!("function {}({})",
f.name.as_deref().unwrap_or("anonymous"),
f.parameters.join(", ")
)
}
Value::NativeFunction(f) => format!("native function {}", f.name),
}
}
pub fn len(&self) -> Result<usize> {
match self {
Value::Array(arr) => Ok(arr.len()),
Value::String(s) => Ok(s.chars().count()),
Value::Object(obj) => Ok(obj.len()),
_ => Err(Error::type_error("array, string, or object", self.type_name())),
}
}
pub fn is_empty(&self) -> Result<bool> {
self.len().map(|len| len == 0)
}
pub fn get_index(&self, index: &Value) -> Result<Value> {
match (self, index) {
(Value::Array(arr), Value::Number(n)) => {
let idx = *n as i64;
if idx < 0 {
let positive_idx = arr.len() as i64 + idx;
if positive_idx >= 0 {
arr.get(positive_idx as usize)
.cloned()
.ok_or_else(|| Error::runtime_error("Array index out of bounds"))
} else {
Err(Error::runtime_error("Array index out of bounds"))
}
} else {
arr.get(idx as usize)
.cloned()
.ok_or_else(|| Error::runtime_error("Array index out of bounds"))
}
}
(Value::String(s), Value::Number(n)) => {
let chars: Vec<char> = s.chars().collect();
let idx = *n as i64;
if idx < 0 {
let positive_idx = chars.len() as i64 + idx;
if positive_idx >= 0 {
chars
.get(positive_idx as usize)
.map(|&c| Value::String(c.to_string()))
.ok_or_else(|| Error::runtime_error("String index out of bounds"))
} else {
Err(Error::runtime_error("String index out of bounds"))
}
} else {
chars
.get(idx as usize)
.map(|&c| Value::String(c.to_string()))
.ok_or_else(|| Error::runtime_error("String index out of bounds"))
}
}
(Value::Object(obj), Value::String(key)) => {
Ok(obj.get(key).cloned().unwrap_or(Value::Null))
}
_ => Err(Error::type_error(
"array, string, or object with appropriate index type",
&format!("{}[{}]", self.type_name(), index.type_name()),
)),
}
}
pub fn set_index(&mut self, index: &Value, value: Value) -> Result<()> {
let self_type_name = self.type_name();
match (self, index) {
(Value::Array(arr), Value::Number(n)) => {
let idx = *n as usize;
if idx >= arr.len() {
arr.resize(idx + 1, Value::Null);
}
arr[idx] = value;
Ok(())
}
(Value::Object(obj), Value::String(key)) => {
obj.insert(key.clone(), value);
Ok(())
}
_ => Err(Error::type_error(
"array or object with appropriate index type",
&format!("{}[{}]", self_type_name, index.type_name()),
)),
}
}
}
impl AssignmentOperator {
pub fn to_binary_op(&self) -> Option<BinaryOperator> {
match self {
AssignmentOperator::Assign => None,
AssignmentOperator::PlusAssign => Some(BinaryOperator::Add),
AssignmentOperator::MinusAssign => Some(BinaryOperator::Subtract),
AssignmentOperator::MultiplyAssign => Some(BinaryOperator::Multiply),
AssignmentOperator::DivideAssign => Some(BinaryOperator::Divide),
AssignmentOperator::ModuloAssign => Some(BinaryOperator::Modulo),
}
}
}
impl Type {
pub fn is_compatible_with(&self, other: &Type) -> bool {
match (self, other) {
(Type::Any, _) | (_, Type::Any) => true,
(Type::String, Type::String) => true,
(Type::Number, Type::Number) => true,
(Type::Boolean, Type::Boolean) => true,
(Type::Null, Type::Null) => true,
(Type::Array(a), Type::Array(b)) => a.is_compatible_with(b),
(Type::Object(a), Type::Object(b)) => {
a.iter().all(|(key, type_a)| {
b.get(key).map_or(false, |type_b| type_a.is_compatible_with(type_b))
})
}
(
Type::Function {
parameters: params_a,
return_type: ret_a,
},
Type::Function {
parameters: params_b,
return_type: ret_b,
},
) => {
params_a.len() == params_b.len()
&& params_a
.iter()
.zip(params_b.iter())
.all(|(a, b)| a.is_compatible_with(b))
&& ret_a.is_compatible_with(ret_b)
}
_ => false,
}
}
pub fn default_value(&self) -> Value {
match self {
Type::String => Value::String(String::new()),
Type::Number => Value::Number(0.0),
Type::Boolean => Value::Boolean(false),
Type::Array(_) => Value::Array(Vec::new()),
Type::Object(_) => Value::Object(HashMap::new()),
Type::Null | Type::Any => Value::Null,
Type::Function { .. } => Value::Null, }
}
}
pub trait Visitor<T> {
fn visit_statement(&mut self, stmt: &Statement) -> T;
fn visit_expression(&mut self, expr: &Expression) -> T;
}
pub trait VisitorMut<T> {
fn visit_statement(&mut self, stmt: &mut Statement) -> T;
fn visit_expression(&mut self, expr: &mut Expression) -> T;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::BinaryOperator;
#[test]
fn test_operator_precedence() {
assert!(BinaryOperator::Multiply.precedence() > BinaryOperator::Add.precedence());
assert!(BinaryOperator::Power.precedence() > BinaryOperator::Multiply.precedence());
assert!(BinaryOperator::And.precedence() < BinaryOperator::Equal.precedence());
}
#[test]
fn test_type_compatibility() {
assert!(Type::Any.is_compatible_with(&Type::String));
assert!(Type::String.is_compatible_with(&Type::Any));
assert!(!Type::String.is_compatible_with(&Type::Number));
let array_str = Type::Array(Box::new(Type::String));
let array_num = Type::Array(Box::new(Type::Number));
assert!(!array_str.is_compatible_with(&array_num));
}
#[test]
fn test_expression_properties() {
let literal = Expression::Literal(Value::Number(42.0));
assert!(literal.is_literal());
assert!(literal.is_constant());
let var = Expression::Variable("x".to_string());
assert!(!var.is_literal());
assert!(!var.is_constant());
}
}