uni_core/primitives/
now.rs

1// RUST CONCEPT: Get current date/time from platform TimeSource
2// Returns a date record with all components
3use crate::compat::Rc;
4use crate::interpreter::Interpreter;
5use crate::value::{RuntimeError, Value};
6
7#[cfg(not(target_os = "none"))]
8use std::cell::RefCell;
9#[cfg(target_os = "none")]
10use core::cell::RefCell;
11
12// RUST CONCEPT: Platform-specific imports for no_std
13#[cfg(target_os = "none")]
14use crate::compat::ToString;
15
16#[cfg(target_os = "none")]
17use alloc::vec;
18
19// RUST CONCEPT: now primitive
20// Stack: ( -- date_record )
21// Gets current date/time from TimeSource and returns as a date record
22pub fn now_builtin(interp: &mut Interpreter) -> Result<(), RuntimeError> {
23    // Check if we have a time source
24    let time_source = interp.time_source.as_ref().ok_or_else(|| {
25        RuntimeError::TypeError(
26            "now: no time source available - platform must inject TimeSource".to_string(),
27        )
28    })?;
29
30    // Get date components from time source
31    let components = time_source.now();
32
33    // Look up the date record type in the dictionary
34    // Record types are stored with the key "<record-type:typename>"
35    let date_type_key = interp.intern_atom("<record-type:date>");
36    let date_entry = interp.dictionary.get(&date_type_key).ok_or_else(|| {
37        RuntimeError::TypeError(
38            "now: date record type not found - prelude not loaded?".to_string(),
39        )
40    })?;
41
42    // Verify it's a record type
43    match &date_entry.value {
44        Value::RecordType { .. } => {},
45        _ => {
46            return Err(RuntimeError::TypeError(
47                "now: 'date' is not a record type".to_string(),
48            ))
49        }
50    }
51
52    // Create field values vector
53    let field_values = vec![
54        Value::Int32(components.year),
55        Value::Int32(components.month as i32),
56        Value::Int32(components.day as i32),
57        Value::Int32(components.hour as i32),
58        Value::Int32(components.minute as i32),
59        Value::Int32(components.second as i32),
60        Value::Int32(components.offset_minutes),
61    ];
62
63    // Create the record
64    // Use just "date" as the type name, not the internal key
65    let date_type_name = interp.intern_atom("date");
66    let record = Value::Record {
67        type_name: date_type_name,
68        fields: Rc::new(RefCell::new(field_values)),
69    };
70
71    interp.push(record);
72    Ok(())
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use crate::evaluator::execute_string;
79
80    #[test]
81    #[cfg(feature = "std")]
82    fn test_now_with_time_source() {
83        use crate::builtins::register_builtins;
84        use crate::hardware::linux_time::LinuxTimeSource;
85
86        let mut interp = Interpreter::new();
87        register_builtins(&mut interp);
88
89        // Inject time source
90        interp.set_time_source(Box::new(LinuxTimeSource::new()));
91
92        // Load prelude to get date record type
93        // Use list builder to create field names list
94        execute_string(r#"
95            "year" "month" "day" "hour" "minute" "second" "offset" 7 list "date" make-record-type drop
96        "#, &mut interp).unwrap();
97
98        // Call now
99        now_builtin(&mut interp).unwrap();
100
101        // Should have a record on the stack
102        assert_eq!(interp.stack.len(), 1);
103
104        let value = interp.pop().unwrap();
105        match value {
106            Value::Record { type_name, fields } => {
107                assert_eq!(&*type_name, "date");
108                assert_eq!(fields.borrow().len(), 7);
109            }
110            _ => panic!("Expected record, got {:?}", value),
111        }
112    }
113
114    #[test]
115    fn test_now_without_time_source() {
116        let mut interp = Interpreter::new();
117
118        // No time source
119        let result = now_builtin(&mut interp);
120        assert!(matches!(result, Err(RuntimeError::TypeError(_))));
121    }
122}