use crate::diagnostics::{Error as DiagnosticError, Result};
use crate::eval::value::{Value, PrimitiveProcedure, PrimitiveImpl, ThreadSafeEnvironment};
use crate::effects::Effect;
use std::sync::Arc;
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub struct ExceptionObject {
pub exception_type: String,
pub value: Value,
pub message: Option<String>,
pub irritants: Vec<Value>,
pub continuable: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ErrorType {
General,
ReadError,
FileError,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ErrorObject {
pub message: String,
pub irritants: Vec<Value>,
pub error_type: ErrorType,
}
impl ExceptionObject {
pub fn new(exception_type: String, value: Value, continuable: bool) -> Self {
Self {
exception_type,
value,
message: None,
irritants: Vec::new(),
continuable,
}
}
pub fn error(message: String, irritants: Vec<Value>) -> Self {
Self {
exception_type: "error".to_string(),
value: Value::ErrorObject(Arc::new(ErrorObject::new(
message.clone(),
irritants.clone(),
))),
message: Some(message),
irritants,
continuable: false,
}
}
pub fn read_error(message: String, irritants: Vec<Value>) -> Self {
Self {
exception_type: "read-error".to_string(),
value: Value::ErrorObject(Arc::new(ErrorObject::read_error(
message.clone(),
irritants.clone(),
))),
message: Some(message),
irritants,
continuable: false,
}
}
pub fn file_error(message: String, irritants: Vec<Value>) -> Self {
Self {
exception_type: "file-error".to_string(),
value: Value::ErrorObject(Arc::new(ErrorObject::file_error(
message.clone(),
irritants.clone(),
))),
message: Some(message),
irritants,
continuable: false,
}
}
pub fn is_error(&self) -> bool {
matches!(self.value, Value::ErrorObject(_))
}
pub fn is_error_type(&self, error_type: &str) -> bool {
self.exception_type == error_type
}
}
impl ErrorObject {
pub fn new(message: String, irritants: Vec<Value>) -> Self {
Self {
message,
irritants,
error_type: ErrorType::General,
}
}
pub fn read_error(message: String, irritants: Vec<Value>) -> Self {
Self {
message,
irritants,
error_type: ErrorType::ReadError,
}
}
pub fn file_error(message: String, irritants: Vec<Value>) -> Self {
Self {
message,
irritants,
error_type: ErrorType::FileError,
}
}
pub fn is_read_error(&self) -> bool {
matches!(self.error_type, ErrorType::ReadError)
}
pub fn is_file_error(&self) -> bool {
matches!(self.error_type, ErrorType::FileError)
}
}
impl fmt::Display for ExceptionObject {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(msg) = &self.message {
write!(f, "{}: {}", self.exception_type, msg)
} else {
write!(f, "{}: {}", self.exception_type, self.value)
}
}
}
impl fmt::Display for ErrorObject {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}
pub fn create_exception_bindings(env: &Arc<ThreadSafeEnvironment>) {
bind_exception_raising(env);
bind_exception_predicates(env);
bind_error_object_accessors(env);
bind_exception_handling(env);
}
fn bind_exception_raising(env: &Arc<ThreadSafeEnvironment>) {
env.define("raise".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "raise".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_raise),
effects: vec![Effect::Error],
})));
env.define("raise-continuable".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "raise-continuable".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_raise_continuable),
effects: vec![Effect::Error],
})));
env.define("error".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "error".to_string(),
arity_min: 1,
arity_max: None,
implementation: PrimitiveImpl::RustFn(primitive_error),
effects: vec![Effect::Error],
})));
}
fn bind_exception_predicates(env: &Arc<ThreadSafeEnvironment>) {
env.define("error?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "error?".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_error_p),
effects: vec![Effect::Pure],
})));
env.define("error-object?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "error-object?".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_error_p),
effects: vec![Effect::Pure],
})));
env.define("read-error?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "read-error?".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_read_error_p),
effects: vec![Effect::Pure],
})));
env.define("file-error?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "file-error?".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_file_error_p),
effects: vec![Effect::Pure],
})));
}
fn bind_error_object_accessors(env: &Arc<ThreadSafeEnvironment>) {
env.define("error-object-message".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "error-object-message".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_error_object_message),
effects: vec![Effect::Pure],
})));
env.define("error-object-irritants".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "error-object-irritants".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_error_object_irritants),
effects: vec![Effect::Pure],
})));
}
fn bind_exception_handling(env: &Arc<ThreadSafeEnvironment>) {
env.define("with-exception-handler".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "with-exception-handler".to_string(),
arity_min: 2,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_with_exception_handler),
effects: vec![Effect::Error],
})));
}
pub fn create_error_object(message: String, irritants: Vec<Value>) -> Value {
Value::ErrorObject(Arc::new(ErrorObject::new(message, irritants)))
}
pub fn create_read_error_object(message: String, irritants: Vec<Value>) -> Value {
Value::ErrorObject(Arc::new(ErrorObject::read_error(message, irritants)))
}
pub fn create_file_error_object(message: String, irritants: Vec<Value>) -> Value {
Value::ErrorObject(Arc::new(ErrorObject::file_error(message, irritants)))
}
pub fn raise_read_error(message: String, irritants: Vec<Value>) -> Result<Value> {
let exception = ExceptionObject::read_error(message, irritants);
Err(Box::new(DiagnosticError::exception(exception)))
}
pub fn raise_file_error(message: String, irritants: Vec<Value>) -> Result<Value> {
let exception = ExceptionObject::file_error(message, irritants);
Err(Box::new(DiagnosticError::exception(exception)))
}
pub fn primitive_raise(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("raise expects 1 argument, got {}", args.len()),
None,
)));
}
let obj = &args[0];
let exception = if let Value::ErrorObject(_) = obj {
ExceptionObject::new("error".to_string(), obj.clone(), false)
} else {
ExceptionObject::new("exception".to_string(), obj.clone(), false)
};
Err(Box::new(DiagnosticError::exception(exception)))
}
fn primitive_raise_continuable(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("raise-continuable expects 1 argument, got {}", args.len()),
None,
)));
}
let obj = &args[0];
let exception = if let Value::ErrorObject(_) = obj {
ExceptionObject::new("error".to_string(), obj.clone(), true)
} else {
ExceptionObject::new("exception".to_string(), obj.clone(), true)
};
Err(Box::new(DiagnosticError::exception(exception)))
}
fn primitive_error(args: &[Value]) -> Result<Value> {
if args.is_empty() {
return Err(Box::new(DiagnosticError::runtime_error(
"error requires at least a message argument".to_string(),
None,
)));
}
let message = match &args[0] {
Value::Literal(crate::ast::Literal::String(s)) => s.clone(),
_ => return Err(Box::new(DiagnosticError::runtime_error(
"error message must be a string".to_string(),
None,
))),
};
let irritants = args[1..].to_vec();
let exception = ExceptionObject::error(message, irritants);
Err(Box::new(DiagnosticError::exception(exception)))
}
fn primitive_error_p(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("error? expects 1 argument, got {}", args.len()),
None,
)));
}
let is_error = matches!(args[0], Value::ErrorObject(_));
Ok(Value::boolean(is_error))
}
fn primitive_read_error_p(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("read-error? expects 1 argument, got {}", args.len()),
None,
)));
}
let is_read_error = match &args[0] {
Value::ErrorObject(error) => error.is_read_error(),
_ => false,
};
Ok(Value::boolean(is_read_error))
}
fn primitive_file_error_p(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("file-error? expects 1 argument, got {}", args.len()),
None,
)));
}
let is_file_error = match &args[0] {
Value::ErrorObject(error) => error.is_file_error(),
_ => false,
};
Ok(Value::boolean(is_file_error))
}
fn primitive_error_object_message(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("error-object-message expects 1 argument, got {}", args.len()),
None,
)));
}
match &args[0] {
Value::ErrorObject(error) => {
Ok(Value::string(error.message.clone()))
},
_ => Err(Box::new(DiagnosticError::runtime_error(
"error-object-message requires an error object".to_string(),
None,
))),
}
}
fn primitive_error_object_irritants(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("error-object-irritants expects 1 argument, got {}", args.len()),
None,
)));
}
match &args[0] {
Value::ErrorObject(error) => {
Ok(Value::list(error.irritants.clone()))
},
_ => Err(Box::new(DiagnosticError::runtime_error(
"error-object-irritants requires an error object".to_string(),
None,
))),
}
}
fn primitive_with_exception_handler(_args: &[Value]) -> Result<Value> {
Err(Box::new(DiagnosticError::runtime_error(
"with-exception-handler requires evaluator integration (implemented via guard syntax)".to_string(),
None,
)))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::Literal;
#[test]
fn test_exception_object_creation() {
let irritants = vec![Value::integer(42), Value::string("test")];
let exception = ExceptionObject::error("Test error".to_string(), irritants.clone());
assert_eq!(exception.exception_type, "error");
assert!(exception.is_error());
assert_eq!(exception.message, Some("Test error".to_string()));
assert_eq!(exception.irritants, irritants);
assert!(!exception.continuable);
}
#[test]
fn test_error_object_creation() {
let irritants = vec![Value::integer(1), Value::integer(2)];
let error = ErrorObject::new("Test message".to_string(), irritants.clone());
assert_eq!(error.message, "Test message");
assert_eq!(error.irritants, irritants);
}
#[test]
fn test_error_predicate() {
let error_obj = Value::ErrorObject(Arc::new(ErrorObject::new(
"test".to_string(),
vec![]
)));
let not_error = Value::integer(42);
let result = primitive_error_p(&[error_obj]).unwrap();
assert_eq!(result, Value::boolean(true));
let result = primitive_error_p(&[not_error]).unwrap();
assert_eq!(result, Value::boolean(false));
}
#[test]
fn test_error_object_message() {
let error_obj = Value::ErrorObject(Arc::new(ErrorObject::new(
"test message".to_string(),
vec![]
)));
let result = primitive_error_object_message(&[error_obj]).unwrap();
assert_eq!(result, Value::string("test message"));
}
#[test]
fn test_error_object_irritants() {
let irritants = vec![Value::integer(1), Value::string("test")];
let error_obj = Value::ErrorObject(Arc::new(ErrorObject::new(
"test".to_string(),
irritants.clone()
)));
let result = primitive_error_object_irritants(&[error_obj]).unwrap();
assert_eq!(result, Value::list(irritants));
}
#[test]
fn test_raise() {
let args = vec![Value::string("test exception")];
let result = primitive_raise(&args);
assert!(result.is_err());
if let Err(boxed_err) = result {
if let DiagnosticError::Exception { exception, .. } = boxed_err.as_ref() {
assert_eq!(exception.exception_type, "exception");
assert!(!exception.continuable);
} else {
panic!("Expected exception error");
}
} else {
panic!("Expected error result");
}
}
#[test]
fn test_raise_continuable() {
let args = vec![Value::integer(42)];
let result = primitive_raise_continuable(&args);
assert!(result.is_err());
if let Err(boxed_err) = result {
if let DiagnosticError::Exception { exception, .. } = boxed_err.as_ref() {
assert_eq!(exception.exception_type, "exception");
assert!(exception.continuable);
} else {
panic!("Expected exception error");
}
} else {
panic!("Expected error result");
}
}
#[test]
fn test_error_procedure() {
let args = vec![Value::string("Error message"), Value::integer(42)];
let result = primitive_error(&args);
assert!(result.is_err());
if let Err(boxed_err) = result {
if let DiagnosticError::Exception { exception, .. } = boxed_err.as_ref() {
assert_eq!(exception.exception_type, "error");
assert!(exception.is_error());
assert_eq!(exception.message, Some("Error message".to_string()));
assert_eq!(exception.irritants, vec![Value::integer(42)]);
} else {
panic!("Expected exception error");
}
} else {
panic!("Expected error result");
}
}
#[test]
fn test_error_arity_errors() {
let result = primitive_error_p(&[]);
assert!(result.is_err());
let result = primitive_error_p(&[Value::integer(1), Value::integer(2)]);
assert!(result.is_err());
let result = primitive_raise(&[]);
assert!(result.is_err());
let result = primitive_raise(&[Value::integer(1), Value::integer(2)]);
assert!(result.is_err());
let result = primitive_error(&[]);
assert!(result.is_err());
}
#[test]
fn test_error_types() {
assert_eq!(ErrorType::General, ErrorType::General);
assert_ne!(ErrorType::General, ErrorType::ReadError);
assert_ne!(ErrorType::General, ErrorType::FileError);
assert_ne!(ErrorType::ReadError, ErrorType::FileError);
}
#[test]
fn test_error_object_types() {
let general_error = ErrorObject::new("general".to_string(), vec![]);
assert!(!general_error.is_read_error());
assert!(!general_error.is_file_error());
assert_eq!(general_error.error_type, ErrorType::General);
let read_error = ErrorObject::read_error("read error".to_string(), vec![]);
assert!(read_error.is_read_error());
assert!(!read_error.is_file_error());
assert_eq!(read_error.error_type, ErrorType::ReadError);
let file_error = ErrorObject::file_error("file error".to_string(), vec![]);
assert!(!file_error.is_read_error());
assert!(file_error.is_file_error());
assert_eq!(file_error.error_type, ErrorType::FileError);
}
#[test]
fn test_exception_object_error_types() {
let irritants = vec![Value::integer(42)];
let general_exc = ExceptionObject::error("general error".to_string(), irritants.clone());
assert_eq!(general_exc.exception_type, "error");
assert!(general_exc.is_error());
let read_exc = ExceptionObject::read_error("read error".to_string(), irritants.clone());
assert_eq!(read_exc.exception_type, "read-error");
assert!(read_exc.is_error());
let file_exc = ExceptionObject::file_error("file error".to_string(), irritants.clone());
assert_eq!(file_exc.exception_type, "file-error");
assert!(file_exc.is_error());
}
#[test]
fn test_read_error_predicate() {
let read_error_obj = create_read_error_object("read error".to_string(), vec![]);
let result = primitive_read_error_p(&[read_error_obj]).unwrap();
assert_eq!(result, Value::boolean(true));
let general_error_obj = create_error_object("general error".to_string(), vec![]);
let result = primitive_read_error_p(&[general_error_obj]).unwrap();
assert_eq!(result, Value::boolean(false));
let file_error_obj = create_file_error_object("file error".to_string(), vec![]);
let result = primitive_read_error_p(&[file_error_obj]).unwrap();
assert_eq!(result, Value::boolean(false));
let non_error = Value::integer(42);
let result = primitive_read_error_p(&[non_error]).unwrap();
assert_eq!(result, Value::boolean(false));
}
#[test]
fn test_file_error_predicate() {
let file_error_obj = create_file_error_object("file error".to_string(), vec![]);
let result = primitive_file_error_p(&[file_error_obj]).unwrap();
assert_eq!(result, Value::boolean(true));
let general_error_obj = create_error_object("general error".to_string(), vec![]);
let result = primitive_file_error_p(&[general_error_obj]).unwrap();
assert_eq!(result, Value::boolean(false));
let read_error_obj = create_read_error_object("read error".to_string(), vec![]);
let result = primitive_file_error_p(&[read_error_obj]).unwrap();
assert_eq!(result, Value::boolean(false));
let non_error = Value::string("not an error");
let result = primitive_file_error_p(&[non_error]).unwrap();
assert_eq!(result, Value::boolean(false));
}
#[test]
fn test_helper_functions() {
let general = create_error_object("test".to_string(), vec![Value::integer(1)]);
match general {
Value::ErrorObject(err) => {
assert_eq!(err.message, "test");
assert_eq!(err.irritants, vec![Value::integer(1)]);
assert_eq!(err.error_type, ErrorType::General);
},
_ => panic!("Expected ErrorObject"),
}
let read_err = create_read_error_object("read test".to_string(), vec![]);
match read_err {
Value::ErrorObject(err) => {
assert_eq!(err.message, "read test");
assert_eq!(err.error_type, ErrorType::ReadError);
assert!(err.is_read_error());
},
_ => panic!("Expected ErrorObject"),
}
let file_err = create_file_error_object("file test".to_string(), vec![]);
match file_err {
Value::ErrorObject(err) => {
assert_eq!(err.message, "file test");
assert_eq!(err.error_type, ErrorType::FileError);
assert!(err.is_file_error());
},
_ => panic!("Expected ErrorObject"),
}
}
#[test]
fn test_raise_helper_functions() {
let result = raise_read_error("read error".to_string(), vec![Value::integer(42)]);
assert!(result.is_err());
if let Err(boxed_err) = result {
if let DiagnosticError::Exception { exception, .. } = boxed_err.as_ref() {
assert_eq!(exception.exception_type, "read-error");
assert!(exception.is_error());
} else {
panic!("Expected exception error");
}
} else {
panic!("Expected error result");
}
let result = raise_file_error("file error".to_string(), vec![Value::string("test.txt")]);
assert!(result.is_err());
if let Err(boxed_err) = result {
if let DiagnosticError::Exception { exception, .. } = boxed_err.as_ref() {
assert_eq!(exception.exception_type, "file-error");
assert!(exception.is_error());
} else {
panic!("Expected exception error");
}
} else {
panic!("Expected error result");
}
}
#[test]
fn test_error_object_message_with_types() {
let general_error = create_error_object("general message".to_string(), vec![]);
let result = primitive_error_object_message(&[general_error]).unwrap();
assert_eq!(result, Value::string("general message"));
let read_error = create_read_error_object("read message".to_string(), vec![]);
let result = primitive_error_object_message(&[read_error]).unwrap();
assert_eq!(result, Value::string("read message"));
let file_error = create_file_error_object("file message".to_string(), vec![]);
let result = primitive_error_object_message(&[file_error]).unwrap();
assert_eq!(result, Value::string("file message"));
}
#[test]
fn test_error_object_irritants_with_types() {
let irritants = vec![Value::integer(1), Value::string("test"), Value::boolean(true)];
let general_error = create_error_object("message".to_string(), irritants.clone());
let result = primitive_error_object_irritants(&[general_error]).unwrap();
assert_eq!(result, Value::list(irritants.clone()));
let read_error = create_read_error_object("message".to_string(), irritants.clone());
let result = primitive_error_object_irritants(&[read_error]).unwrap();
assert_eq!(result, Value::list(irritants.clone()));
let file_error = create_file_error_object("message".to_string(), irritants.clone());
let result = primitive_error_object_irritants(&[file_error]).unwrap();
assert_eq!(result, Value::list(irritants.clone()));
}
}