Skip to main content

pepl_stdlib/modules/
record.rs

1//! `record` stdlib module — immutable record operations.
2//!
3//! Functions: get, set, has, keys, values.
4
5use std::collections::BTreeMap;
6
7use crate::error::StdlibError;
8use crate::module::StdlibModule;
9use crate::value::Value;
10
11/// The `record` stdlib module.
12pub struct RecordModule;
13
14impl RecordModule {
15    pub fn new() -> Self {
16        Self
17    }
18}
19
20impl Default for RecordModule {
21    fn default() -> Self {
22        Self::new()
23    }
24}
25
26impl StdlibModule for RecordModule {
27    fn name(&self) -> &'static str {
28        "record"
29    }
30
31    fn has_function(&self, function: &str) -> bool {
32        matches!(function, "get" | "set" | "has" | "keys" | "values")
33    }
34
35    fn call(&self, function: &str, args: Vec<Value>) -> Result<Value, StdlibError> {
36        match function {
37            "get" => self.get(args),
38            "set" => self.set(args),
39            "has" => self.has(args),
40            "keys" => self.keys(args),
41            "values" => self.values(args),
42            _ => Err(StdlibError::unknown_function("record", function)),
43        }
44    }
45}
46
47impl RecordModule {
48    /// record.get(rec, key) → any
49    /// Returns the value for `key`, or Nil if not present.
50    fn get(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
51        if args.len() != 2 {
52            return Err(StdlibError::wrong_args("record.get", 2, args.len()));
53        }
54        let fields = extract_record("record.get", &args[0], 1)?;
55        let key = extract_string("record.get", &args[1], 2)?;
56        Ok(fields.get(key).cloned().unwrap_or(Value::Nil))
57    }
58
59    /// record.set(rec, key, value) → record
60    /// Returns a new record with the key set to value.
61    fn set(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
62        if args.len() != 3 {
63            return Err(StdlibError::wrong_args("record.set", 3, args.len()));
64        }
65        let fields = extract_record("record.set", &args[0], 1)?;
66        let key = extract_string("record.set", &args[1], 2)?;
67        let mut new_fields = fields.clone();
68        new_fields.insert(key.to_string(), args[2].clone());
69        Ok(Value::record(new_fields))
70    }
71
72    /// record.has(rec, key) → bool
73    fn has(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
74        if args.len() != 2 {
75            return Err(StdlibError::wrong_args("record.has", 2, args.len()));
76        }
77        let fields = extract_record("record.has", &args[0], 1)?;
78        let key = extract_string("record.has", &args[1], 2)?;
79        Ok(Value::Bool(fields.contains_key(key)))
80    }
81
82    /// record.keys(rec) → list<string>
83    /// Returns keys in deterministic BTreeMap order.
84    fn keys(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
85        if args.len() != 1 {
86            return Err(StdlibError::wrong_args("record.keys", 1, args.len()));
87        }
88        let fields = extract_record("record.keys", &args[0], 1)?;
89        let keys: Vec<Value> = fields.keys().map(|k| Value::String(k.clone())).collect();
90        Ok(Value::List(keys))
91    }
92
93    /// record.values(rec) → list<any>
94    /// Returns values in deterministic BTreeMap order.
95    fn values(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
96        if args.len() != 1 {
97            return Err(StdlibError::wrong_args("record.values", 1, args.len()));
98        }
99        let fields = extract_record("record.values", &args[0], 1)?;
100        let values: Vec<Value> = fields.values().cloned().collect();
101        Ok(Value::List(values))
102    }
103}
104
105// ── Helpers ──────────────────────────────────────────────────────────────────
106
107fn extract_record<'a>(
108    func: &str,
109    val: &'a Value,
110    pos: usize,
111) -> Result<&'a BTreeMap<String, Value>, StdlibError> {
112    match val {
113        Value::Record { fields, .. } => Ok(fields),
114        _ => Err(StdlibError::type_mismatch(
115            func,
116            pos,
117            "record",
118            val.type_name(),
119        )),
120    }
121}
122
123fn extract_string<'a>(func: &str, val: &'a Value, pos: usize) -> Result<&'a str, StdlibError> {
124    match val {
125        Value::String(s) => Ok(s),
126        _ => Err(StdlibError::type_mismatch(
127            func,
128            pos,
129            "string",
130            val.type_name(),
131        )),
132    }
133}