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#[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("gas exhausted")]
42 GasExhausted,
43
44 #[error("execution cancelled")]
53 Cancelled,
54
55 #[error("{0}")]
58 MemoryLimitExceeded(cljrs_gc::MemoryLimitExceeded),
59
60 #[error("out of range")]
61 OutOfRange,
62
63 #[error("transient already persisted")]
64 TransientAlreadyPersisted,
65
66 #[error("could not parse value")]
67 Parse,
68
69 #[error("thrown exception")]
70 Thrown(Value),
71}
72
73pub type ValueResult<T> = Result<T, ValueError>;
74
75#[derive(Clone, Debug)]
76pub struct ExceptionInfo {
77 pub(crate) error: ValueError,
78 pub(crate) message: String,
79 pub(crate) data: Option<MapValue>,
80 pub(crate) cause: Option<GcPtr<ExceptionInfo>>,
81}
82
83impl ExceptionInfo {
84 pub fn new(
85 error: ValueError,
86 message: String,
87 data: Option<MapValue>,
88 cause: Option<GcPtr<ExceptionInfo>>,
89 ) -> Self {
90 Self {
91 error,
92 message,
93 data: data.as_ref().cloned(),
94 cause: cause.as_ref().cloned(),
95 }
96 }
97
98 pub fn type_name(&self) -> &'static str {
101 match self.error {
102 ValueError::WrongType { .. } => "WrongType",
103 ValueError::IndexOutOfBounds { .. } => "IndexOutOfBounds",
104 ValueError::ArityError { .. } => "ArityError",
105 ValueError::NotCallable { .. } => "NotCallable",
106 ValueError::OddMap { .. } => "OddMap",
107 ValueError::Unsupported => "Unsupported",
108 ValueError::Other(_) => "Other",
109 ValueError::GasExhausted => "GasExhausted",
110 ValueError::Cancelled => "Cancelled",
111 ValueError::MemoryLimitExceeded(_) => "MemoryLimitExceeded",
112 ValueError::OutOfRange => "OutOfRange",
113 ValueError::TransientAlreadyPersisted => "TransientAlreadyPersisted",
114 ValueError::Parse => "ParseError",
115 ValueError::Thrown(_) => "Thrown",
116 }
117 }
118
119 pub fn to_via_map(&self) -> ValueResult<Value> {
122 let map = TransientMap::new();
123 map.assoc(
124 Value::keyword(Keyword::simple("type")),
125 Value::string(self.type_name()),
126 )?;
127 map.assoc(
128 Value::keyword(Keyword::simple("message")),
129 Value::string(&self.message),
130 )?;
131 if let Some(info) = self.data.as_ref() {
132 map.assoc(
133 Value::keyword(Keyword::simple("data")),
134 Value::Map(info.clone()),
135 )?;
136 }
137 Ok(Value::Map(MapValue::Hash(GcPtr::new(map.persistent()?))))
139 }
140
141 pub fn to_map(&self) -> ValueResult<Value> {
142 let map = TransientMap::new();
143 map.assoc(
144 Value::keyword(Keyword::simple("cause")),
145 self.cause
146 .as_ref()
147 .map(|c| Value::Str(GcPtr::new(c.get().message.to_string())))
148 .unwrap_or(Value::Str(GcPtr::new(self.message.to_string()))),
149 )?;
150 if let Some(info) = self.data.as_ref() {
151 map.assoc(
152 Value::keyword(Keyword::simple("data")),
153 Value::Map(info.clone()),
154 )?;
155 }
156 let via = TransientVector::new();
157 via.append(self.to_via_map()?);
158 let mut cur = self.cause.as_ref();
159 while let Some(e) = cur {
160 via.append(e.get().to_via_map()?);
161 cur = e.get().cause.as_ref();
162 }
163 map.assoc(
164 Value::keyword(Keyword::simple("via")),
165 Value::Vector(GcPtr::new(via.persistent()?)),
166 );
167 let backtrace = Backtrace::capture();
168 if matches!(backtrace.status(), BacktraceStatus::Captured) {
169 map.assoc(
171 Value::keyword(Keyword::simple("trace")),
172 Value::string(format!("{}", backtrace)),
173 )?;
174 }
175 Ok(Value::Map(MapValue::Hash(GcPtr::new(map.persistent()?))))
176 }
177
178 pub fn message(&self) -> String {
179 self.message.to_string()
180 }
181
182 pub fn data(&self) -> Option<MapValue> {
183 self.data.as_ref().cloned()
184 }
185
186 pub fn cause(&self) -> Option<GcPtr<ExceptionInfo>> {
187 self.cause.as_ref().cloned()
188 }
189}
190
191impl Trace for ExceptionInfo {
192 fn trace(&self, visitor: &mut MarkVisitor) {
193 if let Some(data) = &self.data {
194 data.trace(visitor);
195 }
196 if let ValueError::Thrown(value) = &self.error {
197 value.trace(visitor);
198 }
199 if let Some(cause) = self.cause.as_ref() {
200 visitor.visit(cause);
201 }
202 }
203
204 fn gc_size_extra(&self) -> usize {
205 self.message.capacity()
206 }
207}
208
209impl ClojureHash for ExceptionInfo {
210 fn clojure_hash(&self) -> u32 {
211 let msg_hash = hash_string(self.message.as_ref());
212 let cause_hash = self
213 .cause
214 .as_ref()
215 .map(|c| c.get().clojure_hash())
216 .unwrap_or(0);
217 hash_combine_ordered(msg_hash, cause_hash)
218 }
219}