Skip to main content

cljrs_value/
error.rs

1#![allow(unused)]
2
3use crate::collections::{TransientMap, TransientVector};
4use crate::hash::{hash_combine_ordered, hash_string};
5use crate::{ClojureHash, Keyword, MapValue, PersistentArrayMap, Value};
6use cljrs_gc::{GcPtr, GcVisitor, MarkVisitor, Trace};
7use std::backtrace::{Backtrace, BacktraceStatus};
8
9/// Value-level errors: type mismatches, arity errors, out-of-bounds, etc.
10///
11/// These are deliberately free of miette/NamedSource so they can be
12/// constructed without source-location context.  The evaluator wraps them
13/// in `CljxError::EvalError` when it has a span.
14#[derive(Debug, thiserror::Error, Clone)]
15pub enum ValueError {
16    #[error("wrong type: expected {expected}, got {got}")]
17    WrongType { expected: &'static str, got: String },
18
19    #[error("index out of bounds: {idx} >= {count}")]
20    IndexOutOfBounds { idx: usize, count: usize },
21
22    #[error("arity error: {name} expects {expected}, got {got}")]
23    ArityError {
24        name: String,
25        expected: String,
26        got: usize,
27    },
28
29    #[error("cannot call non-function value: {value}")]
30    NotCallable { value: String },
31
32    #[error("map must have an even number of forms, got {count}")]
33    OddMap { count: usize },
34
35    #[error("this feature is not yet supported")]
36    Unsupported,
37
38    #[error("{0}")]
39    Other(String),
40
41    #[error("out of range")]
42    OutOfRange,
43
44    #[error("transient already persisted")]
45    TransientAlreadyPersisted,
46
47    #[error("could not parse value")]
48    Parse,
49
50    #[error("thrown exception")]
51    Thrown(Value),
52}
53
54pub type ValueResult<T> = Result<T, ValueError>;
55
56#[derive(Clone, Debug)]
57pub struct ExceptionInfo {
58    pub(crate) error: ValueError,
59    pub(crate) message: String,
60    pub(crate) data: Option<MapValue>,
61    pub(crate) cause: Option<GcPtr<ExceptionInfo>>,
62}
63
64impl ExceptionInfo {
65    pub fn new(
66        error: ValueError,
67        message: String,
68        data: Option<MapValue>,
69        cause: Option<GcPtr<ExceptionInfo>>,
70    ) -> Self {
71        Self {
72            error,
73            message,
74            data: data.as_ref().cloned(),
75            cause: cause.as_ref().cloned(),
76        }
77    }
78
79    fn to_via_map(&self) -> ValueResult<Value> {
80        let map = TransientMap::new();
81        map.assoc(
82            Value::keyword(Keyword::simple("type")),
83            Value::string(match self.error {
84                ValueError::WrongType { .. } => "WrongType",
85                ValueError::IndexOutOfBounds { .. } => "IndexOutOfBounds",
86                ValueError::ArityError { .. } => "ArityError",
87                ValueError::NotCallable { .. } => "NotCallable",
88                ValueError::OddMap { .. } => "OddMap",
89                ValueError::Unsupported => "Unsupported",
90                ValueError::Other(_) => "Other",
91                ValueError::OutOfRange => "OutOfRange",
92                ValueError::TransientAlreadyPersisted => "TransientAlreadyPersisted",
93                ValueError::Parse => "ParseError",
94                ValueError::Thrown(_) => "Thrown",
95            }),
96        )?;
97        map.assoc(
98            Value::keyword(Keyword::simple("message")),
99            Value::string(&self.message),
100        )?;
101        if let Some(info) = self.data.as_ref() {
102            map.assoc(
103                Value::keyword(Keyword::simple("data")),
104                Value::Map(info.clone()),
105            )?;
106        }
107        // TODO, add :at (source location)
108        Ok(Value::Map(MapValue::Hash(GcPtr::new(map.persistent()?))))
109    }
110
111    pub fn to_map(&self) -> ValueResult<Value> {
112        let map = TransientMap::new();
113        map.assoc(
114            Value::keyword(Keyword::simple("cause")),
115            self.cause
116                .as_ref()
117                .map(|c| Value::Str(GcPtr::new(c.get().message.to_string())))
118                .unwrap_or(Value::Str(GcPtr::new(self.message.to_string()))),
119        )?;
120        if let Some(info) = self.data.as_ref() {
121            map.assoc(
122                Value::keyword(Keyword::simple("data")),
123                Value::Map(info.clone()),
124            )?;
125        }
126        let via = TransientVector::new();
127        via.append(self.to_via_map()?);
128        let mut cur = self.cause.as_ref();
129        while let Some(e) = cur {
130            via.append(e.get().to_via_map()?);
131            cur = e.get().cause.as_ref();
132        }
133        map.assoc(
134            Value::keyword(Keyword::simple("via")),
135            Value::Vector(GcPtr::new(via.persistent()?)),
136        );
137        let backtrace = Backtrace::capture();
138        if matches!(backtrace.status(), BacktraceStatus::Captured) {
139            // TODO, turn frames() into vector once stable?
140            map.assoc(
141                Value::keyword(Keyword::simple("trace")),
142                Value::string(format!("{}", backtrace)),
143            )?;
144        }
145        Ok(Value::Map(MapValue::Hash(GcPtr::new(map.persistent()?))))
146    }
147
148    pub fn message(&self) -> String {
149        self.message.to_string()
150    }
151
152    pub fn data(&self) -> Option<MapValue> {
153        self.data.as_ref().cloned()
154    }
155
156    pub fn cause(&self) -> Option<GcPtr<ExceptionInfo>> {
157        self.cause.as_ref().cloned()
158    }
159}
160
161impl Trace for ExceptionInfo {
162    fn trace(&self, visitor: &mut MarkVisitor) {
163        if let Some(cause) = self.cause.as_ref() {
164            visitor.visit(cause);
165        }
166    }
167}
168
169impl ClojureHash for ExceptionInfo {
170    fn clojure_hash(&self) -> u32 {
171        let msg_hash = hash_string(self.message.as_ref());
172        let cause_hash = self
173            .cause
174            .as_ref()
175            .map(|c| c.get().clojure_hash())
176            .unwrap_or(0);
177        hash_combine_ordered(msg_hash, cause_hash)
178    }
179}