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
211 HeapValue::Range {
213 start,
214 end,
215 inclusive,
216 } => ExternalValue::Range {
217 start: start.as_ref().map(|v| Box::new(nb_to_external(v, schemas))),
218 end: end.as_ref().map(|v| Box::new(nb_to_external(v, schemas))),
219 inclusive: *inclusive,
220 },
221 HeapValue::Enum(e) => ExternalValue::Enum {
222 name: e.enum_name.clone(),
223 variant: e.variant.clone(),
224 data: Box::new(match &e.payload {
225 crate::enums::EnumPayload::Unit => ExternalValue::None,
226 crate::enums::EnumPayload::Tuple(nbs) => {
227 if nbs.len() == 1 {
228 nb_to_external(&nbs[0], schemas)
229 } else {
230 ExternalValue::Array(
231 nbs.iter().map(|v| nb_to_external(v, schemas)).collect(),
232 )
233 }
234 }
235 crate::enums::EnumPayload::Struct(fields) => {
236 let mut map = BTreeMap::new();
237 for (k, v) in fields {
238 map.insert(k.clone(), nb_to_external(v, schemas));
239 }
240 ExternalValue::Object(map)
241 }
242 }),
243 },
244 HeapValue::Some(v) => nb_to_external(v, schemas),
245 HeapValue::Ok(v) => ExternalValue::Ok(Box::new(nb_to_external(v, schemas))),
246 HeapValue::Err(v) => ExternalValue::Error(format!("{:?}", nb_to_external(v, schemas))),
247
248 HeapValue::Future(id) => ExternalValue::Opaque(format!("<future:{id}>")),
250 HeapValue::TaskGroup { kind, task_ids } => {
251 ExternalValue::Opaque(format!("<task_group:kind={kind},tasks={}>", task_ids.len()))
252 }
253
254 HeapValue::TraitObject { value, .. } => nb_to_external(value, schemas),
256
257 HeapValue::ExprProxy(s) => ExternalValue::Opaque(format!("<expr_proxy:{s}>")),
259 HeapValue::FilterExpr(_) => ExternalValue::Opaque("<filter_expr>".to_string()),
260
261 HeapValue::Time(t) => ExternalValue::Time(t.to_rfc3339()),
263 HeapValue::Duration(d) => {
264 let secs = d.value as u64;
265 ExternalValue::Duration { secs, nanos: 0 }
266 }
267 HeapValue::TimeSpan(ts) => ExternalValue::Duration {
268 secs: ts.num_seconds().unsigned_abs(),
269 nanos: (ts.subsec_nanos().unsigned_abs()),
270 },
271 HeapValue::Timeframe(tf) => ExternalValue::String(format!("{tf:?}")),
272
273 HeapValue::TimeReference(_) => ExternalValue::Opaque("<time_reference>".to_string()),
275 HeapValue::DateTimeExpr(_) => ExternalValue::Opaque("<datetime_expr>".to_string()),
276 HeapValue::DataDateTimeRef(_) => ExternalValue::Opaque("<data_datetime_ref>".to_string()),
277 HeapValue::TypeAnnotation(_) => ExternalValue::Opaque("<type_annotation>".to_string()),
278 HeapValue::TypeAnnotatedValue { type_name, value } => {
279 let inner = nb_to_external(value, schemas);
280 ExternalValue::TypedObject {
281 name: type_name.clone(),
282 fields: BTreeMap::from([("value".to_string(), inner)]),
283 }
284 }
285 HeapValue::PrintResult(pr) => ExternalValue::String(pr.rendered.clone()),
286 HeapValue::SimulationCall(data) => ExternalValue::Opaque(format!(
287 "<simulation_call:{} params={}>",
288 data.name,
289 data.params.len()
290 )),
291 HeapValue::FunctionRef { name, .. } => {
292 ExternalValue::Opaque(format!("<function_ref:{name}>"))
293 }
294 HeapValue::DataReference(data) => {
295 let mut fields = BTreeMap::new();
296 fields.insert(
297 "datetime".to_string(),
298 ExternalValue::Time(data.datetime.to_rfc3339()),
299 );
300 fields.insert("id".to_string(), ExternalValue::String(data.id.clone()));
301 fields.insert(
302 "timeframe".to_string(),
303 ExternalValue::String(format!("{:?}", data.timeframe)),
304 );
305 ExternalValue::Object(fields)
306 }
307
308 HeapValue::Instant(t) => ExternalValue::Opaque(format!("<instant:{:?}>", t.elapsed())),
309
310 HeapValue::IoHandle(data) => {
311 let status = if data.is_open() { "open" } else { "closed" };
312 ExternalValue::Opaque(format!("<io_handle:{}:{}>", data.path, status))
313 }
314
315 HeapValue::NativeScalar(v) => {
316 if let Some(i) = v.as_i64() {
317 ExternalValue::Int(i)
318 } else {
319 ExternalValue::Number(v.as_f64())
320 }
321 }
322 HeapValue::NativeView(v) => ExternalValue::Opaque(format!(
323 "<{}:{}@0x{:x}>",
324 if v.mutable { "cmut" } else { "cview" },
325 v.layout.name,
326 v.ptr
327 )),
328 HeapValue::HashMap(d) => {
329 let mut fields = BTreeMap::new();
330 for (k, v) in d.keys.iter().zip(d.values.iter()) {
331 fields.insert(format!("{}", k), nb_to_external(v, schemas));
332 }
333 ExternalValue::Object(fields)
334 }
335 HeapValue::Set(d) => {
336 ExternalValue::Array(d.items.iter().map(|v| nb_to_external(v, schemas)).collect())
337 }
338 HeapValue::Deque(d) => {
339 ExternalValue::Array(d.items.iter().map(|v| nb_to_external(v, schemas)).collect())
340 }
341 HeapValue::PriorityQueue(d) => {
342 ExternalValue::Array(d.items.iter().map(|v| nb_to_external(v, schemas)).collect())
343 }
344 HeapValue::Content(node) => ExternalValue::String(format!("{}", node)),
345 HeapValue::SharedCell(arc) => nb_to_external(&arc.read().unwrap(), schemas),
346 HeapValue::IntArray(a) => {
347 ExternalValue::Array(a.iter().map(|&v| ExternalValue::Int(v)).collect())
348 }
349 HeapValue::FloatArray(a) => {
350 ExternalValue::Array(a.iter().map(|&v| ExternalValue::Number(v)).collect())
351 }
352 HeapValue::BoolArray(a) => {
353 ExternalValue::Array(a.iter().map(|&v| ExternalValue::Bool(v != 0)).collect())
354 }
355 HeapValue::I8Array(a) => {
356 ExternalValue::Array(a.iter().map(|&v| ExternalValue::Int(v as i64)).collect())
357 }
358 HeapValue::I16Array(a) => {
359 ExternalValue::Array(a.iter().map(|&v| ExternalValue::Int(v as i64)).collect())
360 }
361 HeapValue::I32Array(a) => {
362 ExternalValue::Array(a.iter().map(|&v| ExternalValue::Int(v as i64)).collect())
363 }
364 HeapValue::U8Array(a) => {
365 ExternalValue::Array(a.iter().map(|&v| ExternalValue::Int(v as i64)).collect())
366 }
367 HeapValue::U16Array(a) => {
368 ExternalValue::Array(a.iter().map(|&v| ExternalValue::Int(v as i64)).collect())
369 }
370 HeapValue::U32Array(a) => {
371 ExternalValue::Array(a.iter().map(|&v| ExternalValue::Int(v as i64)).collect())
372 }
373 HeapValue::U64Array(a) => {
374 ExternalValue::Array(a.iter().map(|&v| ExternalValue::Int(v as i64)).collect())
375 }
376 HeapValue::F32Array(a) => {
377 ExternalValue::Array(a.iter().map(|&v| ExternalValue::Number(v as f64)).collect())
378 }
379 HeapValue::Matrix(m) => {
380 ExternalValue::Opaque(format!("<Mat<number>:{}x{}>", m.rows, m.cols))
381 }
382 HeapValue::Iterator(_) => ExternalValue::Opaque("<iterator>".to_string()),
383 HeapValue::Generator(_) => ExternalValue::Opaque("<generator>".to_string()),
384 HeapValue::Mutex(_) => ExternalValue::Opaque("<mutex>".to_string()),
385 HeapValue::Atomic(a) => {
386 ExternalValue::Int(a.inner.load(std::sync::atomic::Ordering::Relaxed))
387 }
388 HeapValue::Channel(c) => {
389 if c.is_sender() {
390 ExternalValue::Opaque("<channel:sender>".to_string())
391 } else {
392 ExternalValue::Opaque("<channel:receiver>".to_string())
393 }
394 }
395 HeapValue::Lazy(l) => {
396 if let Ok(guard) = l.value.lock() {
397 if let Some(val) = guard.as_ref() {
398 return nb_to_external(val, schemas);
399 }
400 }
401 ExternalValue::Opaque("<lazy:uninitialized>".to_string())
402 }
403 }
404}
405
406pub fn external_to_nb(ev: &ExternalValue, schemas: &dyn SchemaLookup) -> ValueWord {
411 let _ = schemas; match ev {
413 ExternalValue::Number(n) => ValueWord::from_f64(*n),
414 ExternalValue::Int(i) => ValueWord::from_i64(*i),
415 ExternalValue::Bool(b) => ValueWord::from_bool(*b),
416 ExternalValue::String(s) => ValueWord::from_string(std::sync::Arc::new(s.clone())),
417 ExternalValue::None => ValueWord::none(),
418 ExternalValue::Unit => ValueWord::unit(),
419 ExternalValue::Array(items) => {
420 let nbs: Vec<ValueWord> = items.iter().map(|v| external_to_nb(v, schemas)).collect();
421 ValueWord::from_array(std::sync::Arc::new(nbs))
422 }
423 ExternalValue::Decimal(s) => {
424 if let Ok(d) = s.parse::<rust_decimal::Decimal>() {
425 ValueWord::from_decimal(d)
426 } else {
427 ValueWord::from_string(std::sync::Arc::new(s.clone()))
428 }
429 }
430 ExternalValue::Ok(inner) => ValueWord::from_ok(external_to_nb(inner, schemas)),
431 ExternalValue::Error(msg) => {
432 ValueWord::from_err(ValueWord::from_string(std::sync::Arc::new(msg.clone())))
433 }
434 ExternalValue::Range {
435 start,
436 end,
437 inclusive,
438 } => ValueWord::from_range(
439 start.as_ref().map(|v| external_to_nb(v, schemas)),
440 end.as_ref().map(|v| external_to_nb(v, schemas)),
441 *inclusive,
442 ),
443 ExternalValue::Object(_)
445 | ExternalValue::TypedObject { .. }
446 | ExternalValue::Enum { .. }
447 | ExternalValue::Duration { .. }
448 | ExternalValue::Time(_)
449 | ExternalValue::DataTable { .. }
450 | ExternalValue::Opaque(_) => ValueWord::none(),
451 }
452}
453
454impl fmt::Display for ExternalValue {
455 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
456 match self {
457 ExternalValue::Number(n) => {
458 if n.is_nan() {
459 write!(f, "NaN")
460 } else if n.is_infinite() {
461 if n.is_sign_positive() {
462 write!(f, "Infinity")
463 } else {
464 write!(f, "-Infinity")
465 }
466 } else if *n == (*n as i64) as f64 && n.is_finite() {
467 write!(f, "{}", *n as i64)
469 } else {
470 write!(f, "{n}")
471 }
472 }
473 ExternalValue::Int(i) => write!(f, "{i}"),
474 ExternalValue::Bool(b) => write!(f, "{b}"),
475 ExternalValue::String(s) => write!(f, "{s}"),
476 ExternalValue::None => write!(f, "none"),
477 ExternalValue::Unit => write!(f, "()"),
478 ExternalValue::Array(items) => {
479 write!(f, "[")?;
480 for (i, item) in items.iter().enumerate() {
481 if i > 0 {
482 write!(f, ", ")?;
483 }
484 write!(f, "{item}")?;
485 }
486 write!(f, "]")
487 }
488 ExternalValue::Object(fields) => {
489 write!(f, "{{")?;
490 for (i, (k, v)) in fields.iter().enumerate() {
491 if i > 0 {
492 write!(f, ", ")?;
493 }
494 write!(f, "{k}: {v}")?;
495 }
496 write!(f, "}}")
497 }
498 ExternalValue::TypedObject { name, fields } => {
499 write!(f, "{name} {{")?;
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::Enum {
509 name,
510 variant,
511 data,
512 } => {
513 write!(f, "{name}::{variant}")?;
514 if **data != ExternalValue::None {
515 write!(f, "({data})")?;
516 }
517 Ok(())
518 }
519 ExternalValue::Duration { secs, nanos } => {
520 if *nanos > 0 {
521 write!(f, "{secs}.{:09}s", nanos)
522 } else {
523 write!(f, "{secs}s")
524 }
525 }
526 ExternalValue::Time(iso) => write!(f, "{iso}"),
527 ExternalValue::Decimal(d) => write!(f, "{d}"),
528 ExternalValue::Error(msg) => write!(f, "Error({msg})"),
529 ExternalValue::Ok(inner) => write!(f, "Ok({inner})"),
530 ExternalValue::Range {
531 start,
532 end,
533 inclusive,
534 } => {
535 if let Some(s) = start {
536 write!(f, "{s}")?;
537 }
538 if *inclusive {
539 write!(f, "..=")?;
540 } else {
541 write!(f, "..")?;
542 }
543 if let Some(e) = end {
544 write!(f, "{e}")?;
545 }
546 Ok(())
547 }
548 ExternalValue::DataTable { rows, columns } => {
549 write!(f, "DataTable({rows} rows, {} cols)", columns.len())
550 }
551 ExternalValue::Opaque(desc) => write!(f, "{desc}"),
552 }
553 }
554}
555
556#[cfg(test)]
557mod tests {
558 use super::*;
559 use crate::value_word::ValueWord;
560
561 #[test]
562 fn test_number_roundtrip() {
563 let nb = ValueWord::from_f64(3.14);
564 let ev = nb_to_external(&nb, &NoSchemaLookup);
565 assert!(matches!(ev, ExternalValue::Number(n) if (n - 3.14).abs() < f64::EPSILON));
566 let back = external_to_nb(&ev, &NoSchemaLookup);
567 assert!((back.as_f64().unwrap() - 3.14).abs() < f64::EPSILON);
568 }
569
570 #[test]
571 fn test_int_roundtrip() {
572 let nb = ValueWord::from_i64(42);
573 let ev = nb_to_external(&nb, &NoSchemaLookup);
574 assert_eq!(ev, ExternalValue::Int(42));
575 let back = external_to_nb(&ev, &NoSchemaLookup);
576 assert_eq!(back.as_i64().unwrap(), 42);
577 }
578
579 #[test]
580 fn test_bool_roundtrip() {
581 let nb = ValueWord::from_bool(true);
582 let ev = nb_to_external(&nb, &NoSchemaLookup);
583 assert_eq!(ev, ExternalValue::Bool(true));
584 }
585
586 #[test]
587 fn test_string_roundtrip() {
588 let nb = ValueWord::from_string(std::sync::Arc::new("hello".to_string()));
589 let ev = nb_to_external(&nb, &NoSchemaLookup);
590 assert_eq!(ev, ExternalValue::String("hello".to_string()));
591 let back = external_to_nb(&ev, &NoSchemaLookup);
592 assert_eq!(back.as_str().unwrap(), "hello");
593 }
594
595 #[test]
596 fn test_none_and_unit() {
597 assert_eq!(
598 nb_to_external(&ValueWord::none(), &NoSchemaLookup),
599 ExternalValue::None
600 );
601 assert_eq!(
602 nb_to_external(&ValueWord::unit(), &NoSchemaLookup),
603 ExternalValue::Unit
604 );
605 }
606
607 #[test]
608 fn test_function_is_opaque() {
609 let nb = ValueWord::from_function(42);
610 let ev = nb_to_external(&nb, &NoSchemaLookup);
611 assert!(matches!(ev, ExternalValue::Opaque(_)));
612 }
613
614 #[test]
615 fn test_array() {
616 let arr = vec![ValueWord::from_i64(1), ValueWord::from_i64(2)];
617 let nb = ValueWord::from_array(std::sync::Arc::new(arr));
618 let ev = nb_to_external(&nb, &NoSchemaLookup);
619 assert_eq!(
620 ev,
621 ExternalValue::Array(vec![ExternalValue::Int(1), ExternalValue::Int(2)])
622 );
623 }
624
625 #[test]
626 fn test_display() {
627 assert_eq!(format!("{}", ExternalValue::Number(3.14)), "3.14");
628 assert_eq!(format!("{}", ExternalValue::Int(42)), "42");
629 assert_eq!(format!("{}", ExternalValue::Bool(true)), "true");
630 assert_eq!(format!("{}", ExternalValue::String("hi".into())), "hi");
631 assert_eq!(format!("{}", ExternalValue::None), "none");
632 assert_eq!(format!("{}", ExternalValue::Unit), "()");
633 assert_eq!(
634 format!(
635 "{}",
636 ExternalValue::Array(vec![ExternalValue::Int(1), ExternalValue::Int(2)])
637 ),
638 "[1, 2]"
639 );
640 }
641
642 #[test]
643 fn test_serde_json_roundtrip() {
644 let ev = ExternalValue::TypedObject {
645 name: "Candle".to_string(),
646 fields: BTreeMap::from([
647 ("open".to_string(), ExternalValue::Number(100.0)),
648 ("close".to_string(), ExternalValue::Number(105.5)),
649 ]),
650 };
651 let json = serde_json::to_string(&ev).unwrap();
652 let back: ExternalValue = serde_json::from_str(&json).unwrap();
653 assert_eq!(ev, back);
654 }
655}