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