1extern crate alloc;
2
3use core::ffi::c_void;
4use alloc::{string::{String, ToString}, vec::Vec, boxed::Box};
5
6pub use crate::atom::{AtomIndex, AtomTableOps};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct ProcessId(pub u32);
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct PortId(pub u32);
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct RefId(pub u64);
19
20#[derive(Debug, Clone, PartialEq)]
21pub struct FunctionRef {
22 pub module: AtomIndex,
23 pub function: AtomIndex,
24 pub arity: u8,
25}
26
27#[derive(Debug, Clone, PartialEq)]
28pub struct ResourceRef {
29 pub type_name: String,
30 pub ptr: *mut c_void,
31}
32
33#[derive(Debug, Clone, PartialEq)]
40pub enum TermValue {
41 SmallInt(i32),
43 Atom(AtomIndex),
44 Nil,
45
46 Pid(ProcessId),
48 Port(PortId),
49 Reference(RefId),
50
51 Tuple(Vec<TermValue>),
53 List(Box<TermValue>, Box<TermValue>), Map(Vec<(TermValue, TermValue)>), Binary(Vec<u8>),
56
57 Function(FunctionRef),
59 Resource(ResourceRef),
60 Float(f64),
61
62 Invalid,
64}
65
66#[derive(Copy, Clone, Debug, PartialEq, Eq)]
71#[repr(transparent)]
72pub struct Term(pub usize);
73
74#[repr(C)]
76pub struct Context {
77 pub _private: [u8; 0],
78}
79
80#[repr(C)]
82pub struct GlobalContext {
83 pub _private: [u8; 0],
84}
85
86#[repr(C)]
88pub struct Heap {
89 pub _private: [u8; 0],
90}
91
92#[derive(Debug, Copy, Clone, PartialEq, Eq)]
95enum TermType {
96 SmallInt,
97 Atom,
98 Nil,
99 Pid,
100 Port,
101 Reference,
102 Tuple,
103 List,
104 Map,
105 Binary,
106 Function,
107 Resource,
108 Float,
109 Invalid,
110}
111
112impl Term {
113 const TERM_PRIMARY_MASK: usize = 0x3;
115 const TERM_PRIMARY_IMMED: usize = 0x3;
116 const TERM_PRIMARY_LIST: usize = 0x1;
117 const TERM_PRIMARY_BOXED: usize = 0x2;
118
119 const TERM_IMMED_TAG_MASK: usize = 0xF;
120 const TERM_INTEGER_TAG: usize = 0xF;
121 const TERM_ATOM_TAG: usize = 0xB;
122 const TERM_PID_TAG: usize = 0x3;
123 const TERM_PORT_TAG: usize = 0x7;
124
125 const TERM_NIL: usize = 0x3B;
126
127 const TERM_BOXED_TAG_MASK: usize = 0x3F;
128 const TERM_BOXED_TUPLE: usize = 0x00;
129 const TERM_BOXED_POSITIVE_INTEGER: usize = 0x08;
130 const TERM_BOXED_REF: usize = 0x10;
131 const TERM_BOXED_FUN: usize = 0x18;
132 const TERM_BOXED_FLOAT: usize = 0x20;
133 const TERM_BOXED_REFC_BINARY: usize = 0x28;
134 const TERM_BOXED_HEAP_BINARY: usize = 0x30;
135 const TERM_BOXED_SUB_BINARY: usize = 0x38;
136 const TERM_BOXED_MAP: usize = 0x40;
137 const TERM_BOXED_RESOURCE: usize = 0x48;
138
139 pub fn raw(self) -> usize {
141 self.0
142 }
143
144 pub fn from_raw(raw: usize) -> Self {
146 Term(raw)
147 }
148
149 fn decode_type(self) -> TermType {
151 if self.0 == Self::TERM_NIL {
152 return TermType::Nil;
153 }
154
155 match self.0 & Self::TERM_PRIMARY_MASK {
156 Self::TERM_PRIMARY_IMMED => {
157 match self.0 & Self::TERM_IMMED_TAG_MASK {
158 Self::TERM_INTEGER_TAG => TermType::SmallInt,
159 Self::TERM_ATOM_TAG => TermType::Atom,
160 Self::TERM_PID_TAG => TermType::Pid,
161 Self::TERM_PORT_TAG => TermType::Port,
162 _ => TermType::Invalid,
163 }
164 }
165 Self::TERM_PRIMARY_LIST => TermType::List,
166 Self::TERM_PRIMARY_BOXED => {
167 let boxed_ptr = (self.0 & !Self::TERM_PRIMARY_MASK) as *const usize;
168 if boxed_ptr.is_null() {
169 return TermType::Invalid;
170 }
171
172 let header = unsafe { *boxed_ptr };
173 match header & Self::TERM_BOXED_TAG_MASK {
174 Self::TERM_BOXED_TUPLE => TermType::Tuple,
175 Self::TERM_BOXED_POSITIVE_INTEGER => TermType::SmallInt,
176 Self::TERM_BOXED_REF => TermType::Reference,
177 Self::TERM_BOXED_FUN => TermType::Function,
178 Self::TERM_BOXED_FLOAT => TermType::Float,
179 Self::TERM_BOXED_REFC_BINARY |
180 Self::TERM_BOXED_HEAP_BINARY |
181 Self::TERM_BOXED_SUB_BINARY => TermType::Binary,
182 Self::TERM_BOXED_MAP => TermType::Map,
183 Self::TERM_BOXED_RESOURCE => TermType::Resource,
184 _ => TermType::Invalid,
185 }
186 }
187 _ => TermType::Invalid,
188 }
189 }
190
191 fn extract_small_int(self) -> NifResult<i32> {
194 match self.decode_type() {
195 TermType::SmallInt => {
196 let raw_value = (self.0 & !0xF) as i32 >> 4;
197 Ok(raw_value)
198 }
199 _ => Err(NifError::BadArg),
200 }
201 }
202
203 fn extract_atom_index(self) -> NifResult<AtomIndex> {
204 match self.decode_type() {
205 TermType::Atom => Ok(AtomIndex((self.0 >> 4) as u32)),
206 _ => Err(NifError::BadArg),
207 }
208 }
209
210 fn extract_tuple_arity(self) -> NifResult<usize> {
211 match self.decode_type() {
212 TermType::Tuple => {
213 let boxed_ptr = (self.0 & !Self::TERM_PRIMARY_MASK) as *const usize;
214 let header = unsafe { *boxed_ptr };
215 Ok((header >> 6) as usize)
216 }
217 _ => Err(NifError::BadArg),
218 }
219 }
220
221 fn extract_tuple_element(self, index: usize) -> NifResult<Term> {
222 let arity = self.extract_tuple_arity()?;
223 if index >= arity {
224 return Err(NifError::BadArg);
225 }
226
227 let boxed_ptr = (self.0 & !Self::TERM_PRIMARY_MASK) as *const usize;
228 let element = unsafe { *boxed_ptr.add(1 + index) };
229 Ok(Term(element))
230 }
231
232 fn extract_list_head(self) -> NifResult<Term> {
233 match self.decode_type() {
234 TermType::List => {
235 let list_ptr = (self.0 & !Self::TERM_PRIMARY_MASK) as *const usize;
236 let head = unsafe { *list_ptr };
237 Ok(Term(head))
238 }
239 _ => Err(NifError::BadArg),
240 }
241 }
242
243 fn extract_list_tail(self) -> NifResult<Term> {
244 match self.decode_type() {
245 TermType::List => {
246 let list_ptr = (self.0 & !Self::TERM_PRIMARY_MASK) as *const usize;
247 let tail = unsafe { *list_ptr.add(1) };
248 Ok(Term(tail))
249 }
250 _ => Err(NifError::BadArg),
251 }
252 }
253
254 fn extract_binary_data(self) -> NifResult<&'static [u8]> {
255 match self.decode_type() {
256 TermType::Binary => {
257 let boxed_ptr = (self.0 & !Self::TERM_PRIMARY_MASK) as *const usize;
258 let size = unsafe { *boxed_ptr.add(1) };
259 let data_ptr = unsafe { boxed_ptr.add(2) as *const u8 };
260 Ok(unsafe { core::slice::from_raw_parts(data_ptr, size) })
261 }
262 _ => Err(NifError::BadArg),
263 }
264 }
265
266 fn extract_map_size(self) -> NifResult<usize> {
267 match self.decode_type() {
268 TermType::Map => {
269 let boxed_ptr = (self.0 & !Self::TERM_PRIMARY_MASK) as *const usize;
270 let size = unsafe { *boxed_ptr.add(1) };
271 Ok(size)
272 }
273 _ => Err(NifError::BadArg),
274 }
275 }
276
277 fn extract_map_key(self, _index: usize) -> NifResult<Term> {
278 Err(NifError::Other("map traversal not implemented"))
280 }
281
282 fn extract_map_value(self, _index: usize) -> NifResult<Term> {
283 Err(NifError::Other("map traversal not implemented"))
285 }
286
287 fn extract_resource_ptr(self) -> NifResult<*mut c_void> {
288 match self.decode_type() {
289 TermType::Resource => {
290 let ptr = (self.0 & !Self::TERM_PRIMARY_MASK) as *mut c_void;
291 Ok(ptr)
292 }
293 _ => Err(NifError::BadArg),
294 }
295 }
296
297 fn encode_small_int(value: i32) -> NifResult<Self> {
300 if value >= -(1 << 27) && value < (1 << 27) {
301 Ok(Term(((value as usize) << 4) | Self::TERM_INTEGER_TAG))
302 } else {
303 Err(NifError::Other("integer too large for small int"))
304 }
305 }
306
307 fn encode_atom(AtomIndex(index): AtomIndex) -> NifResult<Self> {
308 Ok(Term(((index as usize) << 4) | Self::TERM_ATOM_TAG))
309 }
310
311 fn encode_nil() -> Self {
312 Term(Self::TERM_NIL)
313 }
314
315 #[allow(dead_code)]
316 fn encode_tuple(_elements: Vec<Term>, _heap: &mut Heap) -> NifResult<Self> {
317 Err(NifError::Other("tuple encoding not implemented"))
319 }
320
321 #[allow(dead_code)]
322 fn encode_list(_head: Term, _tail: Term, _heap: &mut Heap) -> NifResult<Self> {
323 Err(NifError::Other("list encoding not implemented"))
325 }
326
327 #[allow(dead_code)]
328 fn encode_binary(_data: &[u8], _heap: &mut Heap) -> NifResult<Self> {
329 Err(NifError::Other("binary encoding not implemented"))
331 }
332
333 #[allow(dead_code)]
334 fn encode_map(_pairs: Vec<(Term, Term)>, _heap: &mut Heap) -> NifResult<Self> {
335 Err(NifError::Other("map encoding not implemented"))
337 }
338}
339
340impl Term {
343 pub fn to_value(self) -> NifResult<TermValue> {
345 match self.decode_type() {
346 TermType::SmallInt => {
347 let val = self.extract_small_int()?;
348 Ok(TermValue::SmallInt(val))
349 }
350 TermType::Atom => {
351 let index = self.extract_atom_index()?;
352 Ok(TermValue::Atom(index))
353 }
354 TermType::Nil => Ok(TermValue::Nil),
355 TermType::Tuple => {
356 let arity = self.extract_tuple_arity()?;
357 let mut elements = Vec::with_capacity(arity);
358 for i in 0..arity {
359 let elem_term = self.extract_tuple_element(i)?;
360 elements.push(elem_term.to_value()?);
361 }
362 Ok(TermValue::Tuple(elements))
363 }
364 TermType::List => {
365 let head_term = self.extract_list_head()?;
366 let tail_term = self.extract_list_tail()?;
367 Ok(TermValue::List(
368 Box::new(head_term.to_value()?),
369 Box::new(tail_term.to_value()?)
370 ))
371 }
372 TermType::Binary => {
373 let data = self.extract_binary_data()?;
374 Ok(TermValue::Binary(data.to_vec()))
375 }
376 TermType::Map => {
377 let size = self.extract_map_size()?;
378 let mut pairs = Vec::with_capacity(size);
379 for i in 0..size {
380 let key_term = self.extract_map_key(i)?;
381 let val_term = self.extract_map_value(i)?;
382 pairs.push((key_term.to_value()?, val_term.to_value()?));
383 }
384 Ok(TermValue::Map(pairs))
385 }
386 TermType::Resource => {
387 let ptr = self.extract_resource_ptr()?;
388 Ok(TermValue::Resource(ResourceRef {
389 type_name: "unknown".into(),
390 ptr,
391 }))
392 }
393 TermType::Pid => {
394 let id = (self.0 >> 4) as u32; Ok(TermValue::Pid(ProcessId(id)))
396 }
397 TermType::Port => {
398 let id = (self.0 >> 4) as u32; Ok(TermValue::Port(PortId(id)))
400 }
401 _ => Ok(TermValue::Invalid),
402 }
403 }
404
405 #[allow(dead_code)]
407 pub fn from_value(value: TermValue, heap: &mut Heap) -> NifResult<Self> {
408 match value {
409 TermValue::SmallInt(i) => Self::encode_small_int(i),
410 TermValue::Atom(idx) => Self::encode_atom(idx),
411 TermValue::Nil => Ok(Self::encode_nil()),
412
413 TermValue::Tuple(elements) => {
414 let term_elements: Result<Vec<Term>, NifError> = elements
415 .into_iter()
416 .map(|elem| Self::from_value(elem, heap))
417 .collect();
418 Self::encode_tuple(term_elements?, heap)
419 }
420
421 TermValue::List(head, tail) => {
422 let head_term = Self::from_value(*head, heap)?;
423 let tail_term = Self::from_value(*tail, heap)?;
424 Self::encode_list(head_term, tail_term, heap)
425 }
426
427 TermValue::Binary(data) => {
428 Self::encode_binary(&data, heap)
429 }
430
431 TermValue::Map(pairs) => {
432 let term_pairs: Result<Vec<(Term, Term)>, NifError> = pairs
433 .into_iter()
434 .map(|(k, v)| Ok((Self::from_value(k, heap)?, Self::from_value(v, heap)?)))
435 .collect();
436 Self::encode_map(term_pairs?, heap)
437 }
438
439 _ => Err(NifError::Other("unsupported term type for encoding")),
440 }
441 }
442}
443
444impl TermValue {
447 pub fn as_int(&self) -> Option<i32> {
449 match self {
450 TermValue::SmallInt(i) => Some(*i),
451 _ => None,
452 }
453 }
454
455 pub fn as_atom(&self) -> Option<AtomIndex> {
457 match self {
458 TermValue::Atom(idx) => Some(*idx),
459 _ => None,
460 }
461 }
462
463 pub fn as_tuple(&self) -> Option<&[TermValue]> {
465 match self {
466 TermValue::Tuple(elements) => Some(elements),
467 _ => None,
468 }
469 }
470
471 pub fn as_list(&self) -> Option<(&TermValue, &TermValue)> {
473 match self {
474 TermValue::List(head, tail) => Some((head, tail)),
475 _ => None,
476 }
477 }
478
479 pub fn is_nil(&self) -> bool {
481 matches!(self, TermValue::Nil)
482 }
483
484 pub fn is_empty_list(&self) -> bool {
486 self.is_nil()
487 }
488
489 pub fn fold_list<T, F>(&self, init: T, f: F) -> T
491 where
492 F: Fn(T, &TermValue) -> T,
493 {
494 match self {
495 TermValue::Nil => init,
496 TermValue::List(head, tail) => {
497 let acc = f(init, head);
498 tail.fold_list(acc, f)
499 }
500 _ => init, }
502 }
503
504 pub fn map_list<F>(&self, f: F) -> TermValue
506 where
507 F: Fn(&TermValue) -> TermValue + Clone,
508 {
509 match self {
510 TermValue::Nil => TermValue::Nil,
511 TermValue::List(head, tail) => {
512 TermValue::List(
513 Box::new(f(head)),
514 Box::new(tail.map_list(f))
515 )
516 }
517 _ => self.clone(), }
519 }
520
521 pub fn filter_list<F>(&self, predicate: F) -> TermValue
523 where
524 F: Fn(&TermValue) -> bool + Clone,
525 {
526 match self {
527 TermValue::Nil => TermValue::Nil,
528 TermValue::List(head, tail) => {
529 let filtered_tail = tail.filter_list(predicate.clone());
530 if predicate(head) {
531 TermValue::List(head.clone(), Box::new(filtered_tail))
532 } else {
533 filtered_tail
534 }
535 }
536 _ => self.clone(),
537 }
538 }
539
540 pub fn list_length(&self) -> usize {
542 self.fold_list(0, |acc, _| acc + 1)
543 }
544
545 pub fn list_to_vec(&self) -> Vec<TermValue> {
547 let mut result = Vec::new();
548 let mut current = self;
549
550 loop {
551 match current {
552 TermValue::Nil => break,
553 TermValue::List(head, tail) => {
554 result.push((**head).clone());
555 current = tail;
556 }
557 _ => break,
558 }
559 }
560
561 result
562 }
563
564 pub fn map_get(&self, key: &TermValue) -> Option<&TermValue> {
566 match self {
567 TermValue::Map(pairs) => {
568 pairs.iter()
569 .find(|(k, _)| k == key)
570 .map(|(_, v)| v)
571 }
572 _ => None,
573 }
574 }
575
576 pub fn map_set(&self, key: TermValue, value: TermValue) -> TermValue {
578 match self {
579 TermValue::Map(pairs) => {
580 let mut new_pairs = pairs.clone();
581
582 if let Some(pos) = new_pairs.iter().position(|(k, _)| k == &key) {
584 new_pairs[pos] = (key, value);
585 } else {
586 new_pairs.push((key, value));
587 }
588
589 TermValue::Map(new_pairs)
590 }
591 _ => self.clone(),
592 }
593 }
594
595 pub fn from_iter<I>(iter: I) -> TermValue
597 where
598 I: IntoIterator<Item = TermValue>,
599 I::IntoIter: DoubleEndedIterator,
600 {
601 iter.into_iter()
602 .rev()
603 .fold(TermValue::Nil, |acc, elem| {
604 TermValue::List(Box::new(elem), Box::new(acc))
605 })
606 }
607
608 pub fn from_vec(elements: Vec<TermValue>) -> TermValue {
610 Self::from_iter(elements)
611 }
612}
613
614impl TermValue {
617 pub fn int(value: i32) -> Self {
618 TermValue::SmallInt(value)
619 }
620
621 pub fn atom<T: AtomTableOps>(name: &str, table: &T) -> Self {
623 let index = table.ensure_atom_str(name).unwrap_or_else(|_| AtomIndex(0));
624 TermValue::Atom(index)
625 }
626
627 pub fn tuple(elements: Vec<TermValue>) -> Self {
628 TermValue::Tuple(elements)
629 }
630
631 pub fn list(elements: Vec<TermValue>) -> Self {
632 Self::from_vec(elements)
633 }
634
635 pub fn binary(data: Vec<u8>) -> Self {
636 TermValue::Binary(data)
637 }
638
639 pub fn map(pairs: Vec<(TermValue, TermValue)>) -> Self {
640 TermValue::Map(pairs)
641 }
642
643 pub fn pid(id: u32) -> Self {
644 TermValue::Pid(ProcessId(id))
645 }
646
647 pub fn port(id: u32) -> Self {
648 TermValue::Port(PortId(id))
649 }
650
651 pub fn reference(id: u64) -> Self {
652 TermValue::Reference(RefId(id))
653 }
654
655 pub fn float(value: f64) -> Self {
656 TermValue::Float(value)
657 }
658}
659
660impl TermValue {
663 pub fn to_int_or(&self, default: i32) -> i32 {
665 self.as_int().unwrap_or(default)
666 }
667
668 pub fn tuple_get(&self, index: usize) -> Option<&TermValue> {
670 self.as_tuple()?.get(index)
671 }
672
673 pub fn tuple_arity(&self) -> usize {
675 self.as_tuple().map(|t| t.len()).unwrap_or(0)
676 }
677
678 pub fn sum_list(&self) -> i32 {
680 self.fold_list(0, |acc, elem| {
681 acc + elem.as_int().unwrap_or(0)
682 })
683 }
684
685 pub fn double_ints(&self) -> TermValue {
687 self.map_list(|elem| {
688 match elem.as_int() {
689 Some(i) => TermValue::int(i * 2),
690 None => elem.clone(),
691 }
692 })
693 }
694
695 pub fn is_atom_str<T: AtomTableOps>(&self, name: &str, table: &T) -> bool {
697 match self.as_atom() {
698 Some(idx) => table.atom_equals_str(idx, name),
699 None => false,
700 }
701 }
702
703 pub fn as_atom_str<T: AtomTableOps>(&self, table: &T) -> Option<String> {
705 match self.as_atom() {
706 Some(idx) => {
707 if let Ok(atom_ref) = table.get_atom_string(idx) {
709 if let Ok(atom_str) = atom_ref.as_str() {
710 return Some(atom_str.to_string());
711 }
712 }
713
714 let common_atoms = ["ok", "error", "true", "false", "undefined", "badarg", "nil"];
716 for atom_name in &common_atoms {
717 if table.atom_equals_str(idx, atom_name) {
718 return Some(atom_name.to_string());
719 }
720 }
721 None
722 }
723 None => None,
724 }
725 }
726}
727
728#[derive(Debug, Clone, PartialEq, Eq)]
731pub enum TermError {
732 WrongType,
734 OutOfBounds,
736 OutOfMemory,
738 InvalidUtf8,
740 Other(String),
742}
743
744#[derive(Debug, Clone, PartialEq, Eq)]
745pub enum NifError {
746 BadArg,
747 BadArity,
748 OutOfMemory,
749 SystemLimit,
750 InvalidTerm,
751 Other(&'static str),
752}
753
754impl From<&'static str> for NifError {
755 fn from(s: &'static str) -> Self {
756 NifError::Other(s)
757 }
758}
759
760pub type NifResult<T> = core::result::Result<T, NifError>;
761
762#[macro_export]
767macro_rules! atom_with_table {
768 ($name:literal, $table:expr) => {
769 TermValue::atom($name, $table)
770 };
771}
772
773#[macro_export]
774macro_rules! tuple {
775 ($($elem:expr),* $(,)?) => {
776 TermValue::tuple(alloc::vec![$($elem),*])
777 };
778}
779
780#[macro_export]
781macro_rules! list {
782 ($($elem:expr),* $(,)?) => {
783 TermValue::list(alloc::vec![$($elem),*])
784 };
785}
786
787#[macro_export]
788macro_rules! map {
789 ($($key:expr => $val:expr),* $(,)?) => {
790 TermValue::map(alloc::vec![$(($key, $val)),*])
791 };
792}