1use crate::heap_value::HeapValue;
13use crate::tags;
14use crate::value_word::ValueWord;
15use std::collections::BTreeMap;
16use std::fmt;
17
18#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
24#[serde(tag = "type", content = "value")]
25pub enum ExternalValue {
26 Number(f64),
28 Int(i64),
30 Bool(bool),
32 String(String),
34 None,
36 Unit,
38 Array(Vec<ExternalValue>),
40 Object(BTreeMap<String, ExternalValue>),
42 TypedObject {
44 name: String,
45 fields: BTreeMap<String, ExternalValue>,
46 },
47 Enum {
49 name: String,
50 variant: String,
51 data: Box<ExternalValue>,
52 },
53 Duration { secs: u64, nanos: u32 },
55 Time(String),
57 Decimal(String),
59 Error(String),
61 Ok(Box<ExternalValue>),
63 Range {
65 start: Option<Box<ExternalValue>>,
66 end: Option<Box<ExternalValue>>,
67 inclusive: bool,
68 },
69 DataTable { rows: usize, columns: Vec<String> },
71 Opaque(String),
73}
74
75pub trait SchemaLookup {
81 fn type_name(&self, schema_id: u64) -> Option<&str>;
83
84 fn field_names(&self, schema_id: u64) -> Option<Vec<&str>>;
86}
87
88pub struct NoSchemaLookup;
91
92impl SchemaLookup for NoSchemaLookup {
93 fn type_name(&self, _schema_id: u64) -> Option<&str> {
94 std::option::Option::None
95 }
96 fn field_names(&self, _schema_id: u64) -> Option<Vec<&str>> {
97 std::option::Option::None
98 }
99}
100
101pub fn nb_to_external(nb: &ValueWord, schemas: &dyn SchemaLookup) -> ExternalValue {
106 let bits = nb.raw_bits();
107
108 if !tags::is_tagged(bits) {
109 let f = f64::from_bits(bits);
110 return if f.is_nan() {
111 ExternalValue::Number(f64::NAN)
112 } else {
113 ExternalValue::Number(f)
114 };
115 }
116
117 match tags::get_tag(bits) {
118 tags::TAG_INT => ExternalValue::Int(tags::sign_extend_i48(tags::get_payload(bits))),
119 tags::TAG_BOOL => ExternalValue::Bool(tags::get_payload(bits) != 0),
120 tags::TAG_NONE => ExternalValue::None,
121 tags::TAG_UNIT => ExternalValue::Unit,
122 tags::TAG_FUNCTION => {
123 ExternalValue::Opaque(format!("<function:{}>", tags::get_payload(bits) as u16))
124 }
125 tags::TAG_MODULE_FN => {
126 ExternalValue::Opaque(format!("<module_fn:{}>", tags::get_payload(bits) as u32))
127 }
128 tags::TAG_REF => ExternalValue::Opaque("<ref>".to_string()),
129 tags::TAG_HEAP => {
130 if let Some(hv) = nb.as_heap_ref() {
131 heap_to_external(hv, schemas)
132 } else {
133 ExternalValue::Opaque("<invalid_heap>".to_string())
134 }
135 }
136 _ => ExternalValue::Opaque("<unknown_tag>".to_string()),
137 }
138}
139
140fn heap_to_external(hv: &HeapValue, schemas: &dyn SchemaLookup) -> ExternalValue {
142 match hv {
143 HeapValue::String(s) => ExternalValue::String((**s).clone()),
144 HeapValue::Array(arr) => {
145 let items: Vec<ExternalValue> =
146 arr.iter().map(|v| nb_to_external(v, schemas)).collect();
147 ExternalValue::Array(items)
148 }
149 HeapValue::TypedObject {
150 schema_id,
151 slots,
152 heap_mask,
153 } => {
154 let type_name = schemas
155 .type_name(*schema_id)
156 .unwrap_or("unknown")
157 .to_string();
158 let field_names_opt = schemas.field_names(*schema_id);
159
160 let mut fields = BTreeMap::new();
161 let names: Vec<String> = if let Some(names) = field_names_opt {
162 names.iter().map(|s| s.to_string()).collect()
163 } else {
164 (0..slots.len()).map(|i| format!("_{i}")).collect()
165 };
166
167 for (i, name) in names.into_iter().enumerate() {
168 if i >= slots.len() {
169 break;
170 }
171 let is_heap = (heap_mask >> i) & 1 == 1;
172 let ev = if is_heap {
173 let nb_val = slots[i].as_heap_nb();
175 nb_to_external(&nb_val, schemas)
176 } else {
177 ExternalValue::Number(slots[i].as_f64())
179 };
180 fields.insert(name, ev);
181 }
182
183 ExternalValue::TypedObject {
184 name: type_name,
185 fields,
186 }
187 }
188 HeapValue::Closure { function_id, .. } => {
189 ExternalValue::Opaque(format!("<closure:{function_id}>"))
190 }
191 HeapValue::Decimal(d) => ExternalValue::Decimal(d.to_string()),
192 HeapValue::BigInt(i) => ExternalValue::Int(*i),
193 HeapValue::HostClosure(_) => ExternalValue::Opaque("<host_closure>".to_string()),
194
195 HeapValue::DataTable(dt) => ExternalValue::DataTable {
197 rows: dt.row_count(),
198 columns: dt.column_names().iter().map(|s| s.to_string()).collect(),
199 },
200 HeapValue::TypedTable { table, .. } => ExternalValue::DataTable {
201 rows: table.row_count(),
202 columns: table.column_names().iter().map(|s| s.to_string()).collect(),
203 },
204 HeapValue::RowView { .. } => ExternalValue::Opaque("<row_view>".to_string()),
205 HeapValue::ColumnRef { .. } => ExternalValue::Opaque("<column_ref>".to_string()),
206 HeapValue::IndexedTable { table, .. } => ExternalValue::DataTable {
207 rows: table.row_count(),
208 columns: table.column_names().iter().map(|s| s.to_string()).collect(),
209 },
210 HeapValue::ProjectedRef(..) => ExternalValue::Opaque("<ref>".to_string()),
211
212 HeapValue::Range {
214 start,
215 end,
216 inclusive,
217 } => ExternalValue::Range {
218 start: start.as_ref().map(|v| Box::new(nb_to_external(v, schemas))),
219 end: end.as_ref().map(|v| Box::new(nb_to_external(v, schemas))),
220 inclusive: *inclusive,
221 },
222 HeapValue::Enum(e) => ExternalValue::Enum {
223 name: e.enum_name.clone(),
224 variant: e.variant.clone(),
225 data: Box::new(match &e.payload {
226 crate::enums::EnumPayload::Unit => ExternalValue::None,
227 crate::enums::EnumPayload::Tuple(nbs) => {
228 if nbs.len() == 1 {
229 nb_to_external(&nbs[0], schemas)
230 } else {
231 ExternalValue::Array(
232 nbs.iter().map(|v| nb_to_external(v, schemas)).collect(),
233 )
234 }
235 }
236 crate::enums::EnumPayload::Struct(fields) => {
237 let mut map = BTreeMap::new();
238 for (k, v) in fields {
239 map.insert(k.clone(), nb_to_external(v, schemas));
240 }
241 ExternalValue::Object(map)
242 }
243 }),
244 },
245 HeapValue::Some(v) => nb_to_external(v, schemas),
246 HeapValue::Ok(v) => ExternalValue::Ok(Box::new(nb_to_external(v, schemas))),
247 HeapValue::Err(v) => ExternalValue::Error(format!("{:?}", nb_to_external(v, schemas))),
248
249 HeapValue::Future(id) => ExternalValue::Opaque(format!("<future:{id}>")),
251 HeapValue::TaskGroup { kind, task_ids } => {
252 ExternalValue::Opaque(format!("<task_group:kind={kind},tasks={}>", task_ids.len()))
253 }
254
255 HeapValue::TraitObject { value, .. } => nb_to_external(value, schemas),
257
258 HeapValue::ExprProxy(s) => ExternalValue::Opaque(format!("<expr_proxy:{s}>")),
260 HeapValue::FilterExpr(_) => ExternalValue::Opaque("<filter_expr>".to_string()),
261
262 HeapValue::Time(t) => ExternalValue::Time(t.to_rfc3339()),
264 HeapValue::Duration(d) => {
265 let secs = d.value as u64;
266 ExternalValue::Duration { secs, nanos: 0 }
267 }
268 HeapValue::TimeSpan(ts) => ExternalValue::Duration {
269 secs: ts.num_seconds().unsigned_abs(),
270 nanos: (ts.subsec_nanos().unsigned_abs()),
271 },
272 HeapValue::Timeframe(tf) => ExternalValue::String(format!("{tf:?}")),
273
274 HeapValue::TimeReference(_) => ExternalValue::Opaque("<time_reference>".to_string()),
276 HeapValue::DateTimeExpr(_) => ExternalValue::Opaque("<datetime_expr>".to_string()),
277 HeapValue::DataDateTimeRef(_) => ExternalValue::Opaque("<data_datetime_ref>".to_string()),
278 HeapValue::TypeAnnotation(_) => ExternalValue::Opaque("<type_annotation>".to_string()),
279 HeapValue::TypeAnnotatedValue { type_name, value } => {
280 let inner = nb_to_external(value, schemas);
281 ExternalValue::TypedObject {
282 name: type_name.clone(),
283 fields: BTreeMap::from([("value".to_string(), inner)]),
284 }
285 }
286 HeapValue::PrintResult(pr) => ExternalValue::String(pr.rendered.clone()),
287 HeapValue::SimulationCall(data) => ExternalValue::Opaque(format!(
288 "<simulation_call:{} params={}>",
289 data.name,
290 data.params.len()
291 )),
292 HeapValue::FunctionRef { name, .. } => {
293 ExternalValue::Opaque(format!("<function_ref:{name}>"))
294 }
295 HeapValue::DataReference(data) => {
296 let mut fields = BTreeMap::new();
297 fields.insert(
298 "datetime".to_string(),
299 ExternalValue::Time(data.datetime.to_rfc3339()),
300 );
301 fields.insert("id".to_string(), ExternalValue::String(data.id.clone()));
302 fields.insert(
303 "timeframe".to_string(),
304 ExternalValue::String(format!("{:?}", data.timeframe)),
305 );
306 ExternalValue::Object(fields)
307 }
308
309 HeapValue::Instant(t) => ExternalValue::Opaque(format!("<instant:{:?}>", t.elapsed())),
310
311 HeapValue::IoHandle(data) => {
312 let status = if data.is_open() { "open" } else { "closed" };
313 ExternalValue::Opaque(format!("<io_handle:{}:{}>", data.path, status))
314 }
315
316 HeapValue::NativeScalar(v) => {
317 if let Some(i) = v.as_i64() {
318 ExternalValue::Int(i)
319 } else {
320 ExternalValue::Number(v.as_f64())
321 }
322 }
323 HeapValue::NativeView(v) => ExternalValue::Opaque(format!(
324 "<{}:{}@0x{:x}>",
325 if v.mutable { "cmut" } else { "cview" },
326 v.layout.name,
327 v.ptr
328 )),
329 HeapValue::HashMap(d) => {
330 let mut fields = BTreeMap::new();
331 for (k, v) in d.keys.iter().zip(d.values.iter()) {
332 fields.insert(format!("{}", k), nb_to_external(v, schemas));
333 }
334 ExternalValue::Object(fields)
335 }
336 HeapValue::Set(d) => {
337 ExternalValue::Array(d.items.iter().map(|v| nb_to_external(v, schemas)).collect())
338 }
339 HeapValue::Deque(d) => {
340 ExternalValue::Array(d.items.iter().map(|v| nb_to_external(v, schemas)).collect())
341 }
342 HeapValue::PriorityQueue(d) => {
343 ExternalValue::Array(d.items.iter().map(|v| nb_to_external(v, schemas)).collect())
344 }
345 HeapValue::Content(node) => ExternalValue::String(format!("{}", node)),
346 HeapValue::SharedCell(arc) => nb_to_external(&arc.read().unwrap(), schemas),
347 HeapValue::IntArray(a) => {
348 ExternalValue::Array(a.iter().map(|&v| ExternalValue::Int(v)).collect())
349 }
350 HeapValue::FloatArray(a) => {
351 ExternalValue::Array(a.iter().map(|&v| ExternalValue::Number(v)).collect())
352 }
353 HeapValue::BoolArray(a) => {
354 ExternalValue::Array(a.iter().map(|&v| ExternalValue::Bool(v != 0)).collect())
355 }
356 HeapValue::I8Array(a) => {
357 ExternalValue::Array(a.iter().map(|&v| ExternalValue::Int(v as i64)).collect())
358 }
359 HeapValue::I16Array(a) => {
360 ExternalValue::Array(a.iter().map(|&v| ExternalValue::Int(v as i64)).collect())
361 }
362 HeapValue::I32Array(a) => {
363 ExternalValue::Array(a.iter().map(|&v| ExternalValue::Int(v as i64)).collect())
364 }
365 HeapValue::U8Array(a) => {
366 ExternalValue::Array(a.iter().map(|&v| ExternalValue::Int(v as i64)).collect())
367 }
368 HeapValue::U16Array(a) => {
369 ExternalValue::Array(a.iter().map(|&v| ExternalValue::Int(v as i64)).collect())
370 }
371 HeapValue::U32Array(a) => {
372 ExternalValue::Array(a.iter().map(|&v| ExternalValue::Int(v as i64)).collect())
373 }
374 HeapValue::U64Array(a) => {
375 ExternalValue::Array(a.iter().map(|&v| ExternalValue::Int(v as i64)).collect())
376 }
377 HeapValue::F32Array(a) => {
378 ExternalValue::Array(a.iter().map(|&v| ExternalValue::Number(v as f64)).collect())
379 }
380 HeapValue::Matrix(m) => {
381 ExternalValue::Opaque(format!("<Mat<number>:{}x{}>", m.rows, m.cols))
382 }
383 HeapValue::Iterator(_) => ExternalValue::Opaque("<iterator>".to_string()),
384 HeapValue::Generator(_) => ExternalValue::Opaque("<generator>".to_string()),
385 HeapValue::Mutex(_) => ExternalValue::Opaque("<mutex>".to_string()),
386 HeapValue::Atomic(a) => {
387 ExternalValue::Int(a.inner.load(std::sync::atomic::Ordering::Relaxed))
388 }
389 HeapValue::Channel(c) => {
390 if c.is_sender() {
391 ExternalValue::Opaque("<channel:sender>".to_string())
392 } else {
393 ExternalValue::Opaque("<channel:receiver>".to_string())
394 }
395 }
396 HeapValue::Lazy(l) => {
397 if let Ok(guard) = l.value.lock() {
398 if let Some(val) = guard.as_ref() {
399 return nb_to_external(val, schemas);
400 }
401 }
402 ExternalValue::Opaque("<lazy:uninitialized>".to_string())
403 }
404 HeapValue::Char(c) => ExternalValue::String(c.to_string()),
405 HeapValue::FloatArraySlice {
406 parent,
407 offset,
408 len,
409 } => {
410 let slice = &parent.data[*offset as usize..(*offset + *len) as usize];
411 ExternalValue::Array(slice.iter().map(|&v| ExternalValue::Number(v)).collect())
412 }
413 }
414}
415
416pub fn external_to_nb(ev: &ExternalValue, schemas: &dyn SchemaLookup) -> ValueWord {
421 let _ = schemas; match ev {
423 ExternalValue::Number(n) => ValueWord::from_f64(*n),
424 ExternalValue::Int(i) => ValueWord::from_i64(*i),
425 ExternalValue::Bool(b) => ValueWord::from_bool(*b),
426 ExternalValue::String(s) => ValueWord::from_string(std::sync::Arc::new(s.clone())),
427 ExternalValue::None => ValueWord::none(),
428 ExternalValue::Unit => ValueWord::unit(),
429 ExternalValue::Array(items) => {
430 let nbs: Vec<ValueWord> = items.iter().map(|v| external_to_nb(v, schemas)).collect();
431 ValueWord::from_array(std::sync::Arc::new(nbs))
432 }
433 ExternalValue::Decimal(s) => {
434 if let Ok(d) = s.parse::<rust_decimal::Decimal>() {
435 ValueWord::from_decimal(d)
436 } else {
437 ValueWord::from_string(std::sync::Arc::new(s.clone()))
438 }
439 }
440 ExternalValue::Ok(inner) => ValueWord::from_ok(external_to_nb(inner, schemas)),
441 ExternalValue::Error(msg) => {
442 ValueWord::from_err(ValueWord::from_string(std::sync::Arc::new(msg.clone())))
443 }
444 ExternalValue::Range {
445 start,
446 end,
447 inclusive,
448 } => ValueWord::from_range(
449 start.as_ref().map(|v| external_to_nb(v, schemas)),
450 end.as_ref().map(|v| external_to_nb(v, schemas)),
451 *inclusive,
452 ),
453 ExternalValue::Object(_)
455 | ExternalValue::TypedObject { .. }
456 | ExternalValue::Enum { .. }
457 | ExternalValue::Duration { .. }
458 | ExternalValue::Time(_)
459 | ExternalValue::DataTable { .. }
460 | ExternalValue::Opaque(_) => ValueWord::none(),
461 }
462}
463
464impl fmt::Display for ExternalValue {
465 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
466 match self {
467 ExternalValue::Number(n) => {
468 if n.is_nan() {
469 write!(f, "NaN")
470 } else if n.is_infinite() {
471 if n.is_sign_positive() {
472 write!(f, "Infinity")
473 } else {
474 write!(f, "-Infinity")
475 }
476 } else if *n == (*n as i64) as f64 && n.is_finite() {
477 write!(f, "{}", *n as i64)
479 } else {
480 write!(f, "{n}")
481 }
482 }
483 ExternalValue::Int(i) => write!(f, "{i}"),
484 ExternalValue::Bool(b) => write!(f, "{b}"),
485 ExternalValue::String(s) => write!(f, "{s}"),
486 ExternalValue::None => write!(f, "none"),
487 ExternalValue::Unit => write!(f, "()"),
488 ExternalValue::Array(items) => {
489 write!(f, "[")?;
490 for (i, item) in items.iter().enumerate() {
491 if i > 0 {
492 write!(f, ", ")?;
493 }
494 write!(f, "{item}")?;
495 }
496 write!(f, "]")
497 }
498 ExternalValue::Object(fields) => {
499 write!(f, "{{")?;
500 for (i, (k, v)) in fields.iter().enumerate() {
501 if i > 0 {
502 write!(f, ", ")?;
503 }
504 write!(f, "{k}: {v}")?;
505 }
506 write!(f, "}}")
507 }
508 ExternalValue::TypedObject { name, fields } => {
509 write!(f, "{name} {{")?;
510 for (i, (k, v)) in fields.iter().enumerate() {
511 if i > 0 {
512 write!(f, ", ")?;
513 }
514 write!(f, "{k}: {v}")?;
515 }
516 write!(f, "}}")
517 }
518 ExternalValue::Enum {
519 name,
520 variant,
521 data,
522 } => {
523 write!(f, "{name}::{variant}")?;
524 if **data != ExternalValue::None {
525 write!(f, "({data})")?;
526 }
527 Ok(())
528 }
529 ExternalValue::Duration { secs, nanos } => {
530 if *nanos > 0 {
531 write!(f, "{secs}.{:09}s", nanos)
532 } else {
533 write!(f, "{secs}s")
534 }
535 }
536 ExternalValue::Time(iso) => write!(f, "{iso}"),
537 ExternalValue::Decimal(d) => write!(f, "{d}"),
538 ExternalValue::Error(msg) => write!(f, "Error({msg})"),
539 ExternalValue::Ok(inner) => write!(f, "Ok({inner})"),
540 ExternalValue::Range {
541 start,
542 end,
543 inclusive,
544 } => {
545 if let Some(s) = start {
546 write!(f, "{s}")?;
547 }
548 if *inclusive {
549 write!(f, "..=")?;
550 } else {
551 write!(f, "..")?;
552 }
553 if let Some(e) = end {
554 write!(f, "{e}")?;
555 }
556 Ok(())
557 }
558 ExternalValue::DataTable { rows, columns } => {
559 write!(f, "DataTable({rows} rows, {} cols)", columns.len())
560 }
561 ExternalValue::Opaque(desc) => write!(f, "{desc}"),
562 }
563 }
564}
565
566#[cfg(test)]
567mod tests {
568 use super::*;
569 use crate::value_word::ValueWord;
570
571 #[test]
572 fn test_number_roundtrip() {
573 let nb = ValueWord::from_f64(3.14);
574 let ev = nb_to_external(&nb, &NoSchemaLookup);
575 assert!(matches!(ev, ExternalValue::Number(n) if (n - 3.14).abs() < f64::EPSILON));
576 let back = external_to_nb(&ev, &NoSchemaLookup);
577 assert!((back.as_f64().unwrap() - 3.14).abs() < f64::EPSILON);
578 }
579
580 #[test]
581 fn test_int_roundtrip() {
582 let nb = ValueWord::from_i64(42);
583 let ev = nb_to_external(&nb, &NoSchemaLookup);
584 assert_eq!(ev, ExternalValue::Int(42));
585 let back = external_to_nb(&ev, &NoSchemaLookup);
586 assert_eq!(back.as_i64().unwrap(), 42);
587 }
588
589 #[test]
590 fn test_bool_roundtrip() {
591 let nb = ValueWord::from_bool(true);
592 let ev = nb_to_external(&nb, &NoSchemaLookup);
593 assert_eq!(ev, ExternalValue::Bool(true));
594 }
595
596 #[test]
597 fn test_string_roundtrip() {
598 let nb = ValueWord::from_string(std::sync::Arc::new("hello".to_string()));
599 let ev = nb_to_external(&nb, &NoSchemaLookup);
600 assert_eq!(ev, ExternalValue::String("hello".to_string()));
601 let back = external_to_nb(&ev, &NoSchemaLookup);
602 assert_eq!(back.as_str().unwrap(), "hello");
603 }
604
605 #[test]
606 fn test_none_and_unit() {
607 assert_eq!(
608 nb_to_external(&ValueWord::none(), &NoSchemaLookup),
609 ExternalValue::None
610 );
611 assert_eq!(
612 nb_to_external(&ValueWord::unit(), &NoSchemaLookup),
613 ExternalValue::Unit
614 );
615 }
616
617 #[test]
618 fn test_function_is_opaque() {
619 let nb = ValueWord::from_function(42);
620 let ev = nb_to_external(&nb, &NoSchemaLookup);
621 assert!(matches!(ev, ExternalValue::Opaque(_)));
622 }
623
624 #[test]
625 fn test_array() {
626 let arr = vec![ValueWord::from_i64(1), ValueWord::from_i64(2)];
627 let nb = ValueWord::from_array(std::sync::Arc::new(arr));
628 let ev = nb_to_external(&nb, &NoSchemaLookup);
629 assert_eq!(
630 ev,
631 ExternalValue::Array(vec![ExternalValue::Int(1), ExternalValue::Int(2)])
632 );
633 }
634
635 #[test]
636 fn test_display() {
637 assert_eq!(format!("{}", ExternalValue::Number(3.14)), "3.14");
638 assert_eq!(format!("{}", ExternalValue::Int(42)), "42");
639 assert_eq!(format!("{}", ExternalValue::Bool(true)), "true");
640 assert_eq!(format!("{}", ExternalValue::String("hi".into())), "hi");
641 assert_eq!(format!("{}", ExternalValue::None), "none");
642 assert_eq!(format!("{}", ExternalValue::Unit), "()");
643 assert_eq!(
644 format!(
645 "{}",
646 ExternalValue::Array(vec![ExternalValue::Int(1), ExternalValue::Int(2)])
647 ),
648 "[1, 2]"
649 );
650 }
651
652 #[test]
653 fn test_serde_json_roundtrip() {
654 let ev = ExternalValue::TypedObject {
655 name: "Candle".to_string(),
656 fields: BTreeMap::from([
657 ("open".to_string(), ExternalValue::Number(100.0)),
658 ("close".to_string(), ExternalValue::Number(105.5)),
659 ]),
660 };
661 let json = serde_json::to_string(&ev).unwrap();
662 let back: ExternalValue = serde_json::from_str(&json).unwrap();
663 assert_eq!(ev, back);
664 }
665}