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