1use crate::slot::ValueSlot;
9use crate::value_word::{NanTag, ValueWord};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16#[repr(u8)]
17pub enum ScalarKind {
18 I8 = 0,
19 U8 = 1,
20 I16 = 2,
21 U16 = 3,
22 I32 = 4,
23 U32 = 5,
24 I64 = 6,
25 U64 = 7,
26 I128 = 8,
27 U128 = 9,
28 F32 = 10,
29 F64 = 11,
30 Bool = 12,
31 None = 13,
32 Unit = 14,
33}
34
35impl ScalarKind {
36 #[inline]
38 pub fn is_integer(self) -> bool {
39 matches!(
40 self,
41 ScalarKind::I8
42 | ScalarKind::U8
43 | ScalarKind::I16
44 | ScalarKind::U16
45 | ScalarKind::I32
46 | ScalarKind::U32
47 | ScalarKind::I64
48 | ScalarKind::U64
49 | ScalarKind::I128
50 | ScalarKind::U128
51 )
52 }
53
54 #[inline]
56 pub fn is_float(self) -> bool {
57 matches!(self, ScalarKind::F32 | ScalarKind::F64)
58 }
59
60 #[inline]
62 pub fn is_numeric(self) -> bool {
63 self.is_integer() || self.is_float()
64 }
65
66 #[inline]
68 pub fn is_unsigned_integer(self) -> bool {
69 matches!(
70 self,
71 ScalarKind::U8 | ScalarKind::U16 | ScalarKind::U32 | ScalarKind::U64 | ScalarKind::U128
72 )
73 }
74
75 #[inline]
77 pub fn is_signed_integer(self) -> bool {
78 matches!(
79 self,
80 ScalarKind::I8 | ScalarKind::I16 | ScalarKind::I32 | ScalarKind::I64 | ScalarKind::I128
81 )
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq)]
93#[repr(C)]
94pub struct TypedScalar {
95 pub kind: ScalarKind,
96 pub payload_lo: u64,
97 pub payload_hi: u64,
99}
100
101impl TypedScalar {
102 #[inline]
104 pub fn i64(v: i64) -> Self {
105 Self {
106 kind: ScalarKind::I64,
107 payload_lo: v as u64,
108 payload_hi: 0,
109 }
110 }
111
112 #[inline]
114 pub fn f64(v: f64) -> Self {
115 Self {
116 kind: ScalarKind::F64,
117 payload_lo: v.to_bits(),
118 payload_hi: 0,
119 }
120 }
121
122 #[inline]
124 pub fn f64_from_bits(bits: u64) -> Self {
125 Self {
126 kind: ScalarKind::F64,
127 payload_lo: bits,
128 payload_hi: 0,
129 }
130 }
131
132 #[inline]
134 pub fn bool(v: bool) -> Self {
135 Self {
136 kind: ScalarKind::Bool,
137 payload_lo: v as u64,
138 payload_hi: 0,
139 }
140 }
141
142 #[inline]
144 pub fn none() -> Self {
145 Self {
146 kind: ScalarKind::None,
147 payload_lo: 0,
148 payload_hi: 0,
149 }
150 }
151
152 #[inline]
154 pub fn unit() -> Self {
155 Self {
156 kind: ScalarKind::Unit,
157 payload_lo: 0,
158 payload_hi: 0,
159 }
160 }
161
162 #[inline]
164 pub fn i8(v: i8) -> Self {
165 Self {
166 kind: ScalarKind::I8,
167 payload_lo: v as i64 as u64,
168 payload_hi: 0,
169 }
170 }
171
172 #[inline]
174 pub fn u8(v: u8) -> Self {
175 Self {
176 kind: ScalarKind::U8,
177 payload_lo: v as u64,
178 payload_hi: 0,
179 }
180 }
181
182 #[inline]
184 pub fn i16(v: i16) -> Self {
185 Self {
186 kind: ScalarKind::I16,
187 payload_lo: v as i64 as u64,
188 payload_hi: 0,
189 }
190 }
191
192 #[inline]
194 pub fn u16(v: u16) -> Self {
195 Self {
196 kind: ScalarKind::U16,
197 payload_lo: v as u64,
198 payload_hi: 0,
199 }
200 }
201
202 #[inline]
204 pub fn i32(v: i32) -> Self {
205 Self {
206 kind: ScalarKind::I32,
207 payload_lo: v as i64 as u64,
208 payload_hi: 0,
209 }
210 }
211
212 #[inline]
214 pub fn u32(v: u32) -> Self {
215 Self {
216 kind: ScalarKind::U32,
217 payload_lo: v as u64,
218 payload_hi: 0,
219 }
220 }
221
222 #[inline]
224 pub fn u64(v: u64) -> Self {
225 Self {
226 kind: ScalarKind::U64,
227 payload_lo: v,
228 payload_hi: 0,
229 }
230 }
231
232 #[inline]
234 pub fn f32(v: f32) -> Self {
235 Self {
236 kind: ScalarKind::F32,
237 payload_lo: f64::from(v).to_bits(),
238 payload_hi: 0,
239 }
240 }
241
242 #[inline]
245 pub fn as_i64(&self) -> Option<i64> {
246 if self.kind == ScalarKind::U64 {
247 i64::try_from(self.payload_lo).ok()
248 } else if self.kind.is_integer() {
249 Some(self.payload_lo as i64)
250 } else {
251 Option::None
252 }
253 }
254
255 #[inline]
257 pub fn as_u64(&self) -> Option<u64> {
258 if self.kind.is_unsigned_integer() {
259 Some(self.payload_lo)
260 } else if self.kind.is_signed_integer() {
261 Some(self.payload_lo)
263 } else {
264 Option::None
265 }
266 }
267
268 #[inline]
270 pub fn as_f64(&self) -> Option<f64> {
271 match self.kind {
272 ScalarKind::F64 => Some(f64::from_bits(self.payload_lo)),
273 ScalarKind::F32 => Some(f64::from_bits(self.payload_lo)),
274 _ => Option::None,
275 }
276 }
277
278 #[inline]
280 pub fn as_bool(&self) -> Option<bool> {
281 if self.kind == ScalarKind::Bool {
282 Some(self.payload_lo != 0)
283 } else {
284 Option::None
285 }
286 }
287
288 #[inline]
291 pub fn to_f64_lossy(&self) -> Option<f64> {
292 match self.kind {
293 ScalarKind::F64 | ScalarKind::F32 => Some(f64::from_bits(self.payload_lo)),
294 k if k.is_unsigned_integer() => Some(self.payload_lo as f64),
295 k if k.is_signed_integer() => Some(self.payload_lo as i64 as f64),
296 _ => Option::None,
297 }
298 }
299}
300
301impl ValueWord {
313 #[inline]
318 pub fn to_typed_scalar(&self) -> Option<TypedScalar> {
319 match self.tag() {
320 NanTag::F64 => {
321 let bits = self.raw_bits();
322 Some(TypedScalar {
323 kind: ScalarKind::F64,
324 payload_lo: bits,
325 payload_hi: 0,
326 })
327 }
328 NanTag::I48 => {
329 let i = unsafe { self.as_i64_unchecked() };
330 Some(TypedScalar::i64(i))
331 }
332 NanTag::Bool => {
333 let b = unsafe { self.as_bool_unchecked() };
334 Some(TypedScalar::bool(b))
335 }
336 NanTag::None => Some(TypedScalar::none()),
337 NanTag::Unit => Some(TypedScalar::unit()),
338 NanTag::Heap | NanTag::Function | NanTag::ModuleFunction | NanTag::Ref => Option::None,
339 }
340 }
341
342 #[inline]
349 pub fn from_typed_scalar(ts: TypedScalar) -> Self {
350 match ts.kind {
351 ScalarKind::I64 => ValueWord::from_i64(ts.payload_lo as i64),
352 ScalarKind::I8 | ScalarKind::I16 | ScalarKind::I32 => {
353 ValueWord::from_i64(ts.payload_lo as i64)
355 }
356 ScalarKind::U8 | ScalarKind::U16 | ScalarKind::U32 => {
357 ValueWord::from_i64(ts.payload_lo as i64)
359 }
360 ScalarKind::U64 => {
361 if ts.payload_lo <= i64::MAX as u64 {
362 ValueWord::from_i64(ts.payload_lo as i64)
363 } else {
364 ValueWord::from_native_u64(ts.payload_lo)
365 }
366 }
367 ScalarKind::I128 | ScalarKind::U128 => {
368 ValueWord::from_i64(ts.payload_lo as i64)
370 }
371 ScalarKind::F64 => ValueWord::from_f64(f64::from_bits(ts.payload_lo)),
372 ScalarKind::F32 => ValueWord::from_f64(f64::from_bits(ts.payload_lo)),
373 ScalarKind::Bool => ValueWord::from_bool(ts.payload_lo != 0),
374 ScalarKind::None => ValueWord::none(),
375 ScalarKind::Unit => ValueWord::unit(),
376 }
377 }
378}
379
380impl ValueSlot {
385 #[inline]
390 pub fn from_typed_scalar(ts: TypedScalar) -> (Self, bool) {
391 match ts.kind {
392 ScalarKind::I8 | ScalarKind::I16 | ScalarKind::I32 | ScalarKind::I64 => {
393 (ValueSlot::from_int(ts.payload_lo as i64), false)
394 }
395 ScalarKind::U8 | ScalarKind::U16 | ScalarKind::U32 => {
396 (ValueSlot::from_int(ts.payload_lo as i64), false)
397 }
398 ScalarKind::U64 => (ValueSlot::from_u64(ts.payload_lo), false),
399 ScalarKind::I128 | ScalarKind::U128 => {
400 (ValueSlot::from_int(ts.payload_lo as i64), false)
401 }
402 ScalarKind::F64 | ScalarKind::F32 => {
403 (ValueSlot::from_number(f64::from_bits(ts.payload_lo)), false)
404 }
405 ScalarKind::Bool => (ValueSlot::from_bool(ts.payload_lo != 0), false),
406 ScalarKind::None | ScalarKind::Unit => (ValueSlot::none(), false),
407 }
408 }
409}
410
411#[cfg(test)]
416mod tests {
417 use super::*;
418 use crate::tags::{I48_MAX, I48_MIN};
419
420 #[test]
421 fn round_trip_i64() {
422 let vw = ValueWord::from_i64(42);
423 let ts = vw.to_typed_scalar().unwrap();
424 assert_eq!(ts.kind, ScalarKind::I64);
425 assert_eq!(ts.payload_lo, 42u64);
426 assert_eq!(ts.payload_hi, 0);
427 let vw2 = ValueWord::from_typed_scalar(ts);
428 assert_eq!(vw.raw_bits(), vw2.raw_bits());
429 }
430
431 #[test]
432 fn round_trip_negative_i64() {
433 let vw = ValueWord::from_i64(-99);
434 let ts = vw.to_typed_scalar().unwrap();
435 assert_eq!(ts.kind, ScalarKind::I64);
436 assert_eq!(ts.payload_lo as i64, -99);
437 let vw2 = ValueWord::from_typed_scalar(ts);
438 assert_eq!(vw.raw_bits(), vw2.raw_bits());
439 }
440
441 #[test]
442 fn round_trip_i48_max() {
443 let vw = ValueWord::from_i64(I48_MAX);
444 let ts = vw.to_typed_scalar().unwrap();
445 assert_eq!(ts.kind, ScalarKind::I64);
446 assert_eq!(ts.payload_lo as i64, I48_MAX);
447 let vw2 = ValueWord::from_typed_scalar(ts);
448 assert_eq!(vw.raw_bits(), vw2.raw_bits());
449 }
450
451 #[test]
452 fn round_trip_i48_min() {
453 let vw = ValueWord::from_i64(I48_MIN);
454 let ts = vw.to_typed_scalar().unwrap();
455 assert_eq!(ts.kind, ScalarKind::I64);
456 assert_eq!(ts.payload_lo as i64, I48_MIN);
457 let vw2 = ValueWord::from_typed_scalar(ts);
458 assert_eq!(vw.raw_bits(), vw2.raw_bits());
459 }
460
461 #[test]
462 fn round_trip_f64() {
463 let vw = ValueWord::from_f64(3.14);
464 let ts = vw.to_typed_scalar().unwrap();
465 assert_eq!(ts.kind, ScalarKind::F64);
466 assert_eq!(f64::from_bits(ts.payload_lo), 3.14);
467 let vw2 = ValueWord::from_typed_scalar(ts);
468 assert_eq!(vw.raw_bits(), vw2.raw_bits());
469 }
470
471 #[test]
472 fn round_trip_f64_nan() {
473 let vw = ValueWord::from_f64(f64::NAN);
474 let ts = vw.to_typed_scalar().unwrap();
475 assert_eq!(ts.kind, ScalarKind::F64);
476 assert!(f64::from_bits(ts.payload_lo).is_nan());
477 let vw2 = ValueWord::from_typed_scalar(ts);
478 assert_eq!(vw.raw_bits(), vw2.raw_bits());
480 }
481
482 #[test]
483 fn round_trip_f64_infinity() {
484 let vw = ValueWord::from_f64(f64::INFINITY);
485 let ts = vw.to_typed_scalar().unwrap();
486 assert_eq!(ts.kind, ScalarKind::F64);
487 assert_eq!(f64::from_bits(ts.payload_lo), f64::INFINITY);
488 let vw2 = ValueWord::from_typed_scalar(ts);
489 assert_eq!(vw.raw_bits(), vw2.raw_bits());
490 }
491
492 #[test]
493 fn round_trip_f64_neg_zero() {
494 let vw = ValueWord::from_f64(-0.0);
495 let ts = vw.to_typed_scalar().unwrap();
496 assert_eq!(ts.kind, ScalarKind::F64);
497 assert_eq!(ts.payload_lo, (-0.0f64).to_bits());
499 let vw2 = ValueWord::from_typed_scalar(ts);
500 assert_eq!(vw.raw_bits(), vw2.raw_bits());
501 }
502
503 #[test]
504 fn round_trip_bool_true() {
505 let vw = ValueWord::from_bool(true);
506 let ts = vw.to_typed_scalar().unwrap();
507 assert_eq!(ts.kind, ScalarKind::Bool);
508 assert_eq!(ts.payload_lo, 1);
509 let vw2 = ValueWord::from_typed_scalar(ts);
510 assert_eq!(vw.raw_bits(), vw2.raw_bits());
511 }
512
513 #[test]
514 fn round_trip_bool_false() {
515 let vw = ValueWord::from_bool(false);
516 let ts = vw.to_typed_scalar().unwrap();
517 assert_eq!(ts.kind, ScalarKind::Bool);
518 assert_eq!(ts.payload_lo, 0);
519 let vw2 = ValueWord::from_typed_scalar(ts);
520 assert_eq!(vw.raw_bits(), vw2.raw_bits());
521 }
522
523 #[test]
524 fn round_trip_none() {
525 let vw = ValueWord::none();
526 let ts = vw.to_typed_scalar().unwrap();
527 assert_eq!(ts.kind, ScalarKind::None);
528 let vw2 = ValueWord::from_typed_scalar(ts);
529 assert_eq!(vw.raw_bits(), vw2.raw_bits());
530 }
531
532 #[test]
533 fn round_trip_unit() {
534 let vw = ValueWord::unit();
535 let ts = vw.to_typed_scalar().unwrap();
536 assert_eq!(ts.kind, ScalarKind::Unit);
537 let vw2 = ValueWord::from_typed_scalar(ts);
538 assert_eq!(vw.raw_bits(), vw2.raw_bits());
539 }
540
541 #[test]
542 fn heap_value_returns_none() {
543 let vw = ValueWord::from_string(std::sync::Arc::new("hello".to_string()));
544 assert!(vw.to_typed_scalar().is_none());
545 }
546
547 #[test]
548 fn typed_scalar_convenience_constructors() {
549 assert_eq!(TypedScalar::i64(42).kind, ScalarKind::I64);
550 assert_eq!(TypedScalar::i64(42).payload_lo, 42);
551 assert_eq!(TypedScalar::f64(1.5).kind, ScalarKind::F64);
552 assert_eq!(TypedScalar::bool(true).payload_lo, 1);
553 assert_eq!(TypedScalar::none().kind, ScalarKind::None);
554 assert_eq!(TypedScalar::unit().kind, ScalarKind::Unit);
555 }
556
557 #[test]
558 fn scalar_kind_classification() {
559 assert!(ScalarKind::I64.is_integer());
560 assert!(ScalarKind::U32.is_integer());
561 assert!(!ScalarKind::F64.is_integer());
562 assert!(!ScalarKind::Bool.is_integer());
563
564 assert!(ScalarKind::F64.is_float());
565 assert!(ScalarKind::F32.is_float());
566 assert!(!ScalarKind::I64.is_float());
567
568 assert!(ScalarKind::I64.is_numeric());
569 assert!(ScalarKind::F64.is_numeric());
570 assert!(!ScalarKind::Bool.is_numeric());
571 assert!(!ScalarKind::None.is_numeric());
572 }
573
574 #[test]
575 fn value_slot_from_typed_scalar() {
576 let (slot, is_heap) = ValueSlot::from_typed_scalar(TypedScalar::i64(-42));
578 assert!(!is_heap);
579 assert_eq!(slot.as_i64(), -42);
580
581 let (slot, is_heap) = ValueSlot::from_typed_scalar(TypedScalar::f64(3.14));
583 assert!(!is_heap);
584 assert_eq!(slot.as_f64(), 3.14);
585
586 let (slot, is_heap) = ValueSlot::from_typed_scalar(TypedScalar::bool(true));
588 assert!(!is_heap);
589 assert!(slot.as_bool());
590
591 let (slot, is_heap) = ValueSlot::from_typed_scalar(TypedScalar::none());
593 assert!(!is_heap);
594 assert_eq!(slot.raw(), 0);
595 }
596
597 #[test]
598 fn to_f64_lossy_works() {
599 assert_eq!(TypedScalar::f64(3.14).to_f64_lossy(), Some(3.14));
600 assert_eq!(TypedScalar::i64(42).to_f64_lossy(), Some(42.0));
601 assert_eq!(TypedScalar::bool(true).to_f64_lossy(), Option::None);
602 assert_eq!(TypedScalar::none().to_f64_lossy(), Option::None);
603 }
604
605 #[test]
606 fn typed_scalar_extraction_methods() {
607 assert_eq!(TypedScalar::i64(42).as_i64(), Some(42));
608 assert_eq!(TypedScalar::f64(3.14).as_i64(), Option::None);
609 assert_eq!(TypedScalar::f64(3.14).as_f64(), Some(3.14));
610 assert_eq!(TypedScalar::i64(42).as_f64(), Option::None);
611 assert_eq!(TypedScalar::bool(true).as_bool(), Some(true));
612 assert_eq!(TypedScalar::i64(1).as_bool(), Option::None);
613 }
614}