Skip to main content

ion_core/
stdlib.rs

1//! Built-in standard library modules.
2//!
3//! These modules are automatically registered in every Engine instance
4//! and provide namespaced access to common functions and constants.
5//! The same functions remain available as top-level builtins for
6//! backwards compatibility.
7
8use crate::module::Module;
9use crate::value::Value;
10
11/// Build the `math` stdlib module.
12///
13/// Functions: abs, min, max, floor, ceil, round, sqrt, pow, clamp, log, log2, log10, sin, cos, tan, atan2
14/// Constants: PI, E, INF, NAN, TAU
15pub fn math_module() -> Module {
16    let mut m = Module::new("math");
17
18    // Constants
19    m.set("PI", Value::Float(std::f64::consts::PI));
20    m.set("E", Value::Float(std::f64::consts::E));
21    m.set("TAU", Value::Float(std::f64::consts::TAU));
22    m.set("INF", Value::Float(f64::INFINITY));
23    m.set("NAN", Value::Float(f64::NAN));
24
25    m.register_fn("abs", |args: &[Value]| {
26        if args.len() != 1 {
27            return Err(ion_str!("math::abs takes 1 argument"));
28        }
29        match &args[0] {
30            Value::Int(n) => Ok(Value::Int(n.abs())),
31            Value::Float(n) => Ok(Value::Float(n.abs())),
32            _ => Err(format!(
33                "{}{}",
34                ion_str!("math::abs not supported for "),
35                args[0].type_name()
36            )),
37        }
38    });
39
40    m.register_fn("min", |args: &[Value]| {
41        if args.len() < 2 {
42            return Err(ion_str!("math::min requires at least 2 arguments"));
43        }
44        let mut best = args[0].clone();
45        for arg in &args[1..] {
46            match (&best, arg) {
47                (Value::Int(a), Value::Int(b)) if b < a => best = arg.clone(),
48                (Value::Float(a), Value::Float(b)) if b < a => best = arg.clone(),
49                (Value::Int(a), Value::Float(b)) if *b < (*a as f64) => best = arg.clone(),
50                (Value::Float(a), Value::Int(b)) if (*b as f64) < *a => best = arg.clone(),
51                (Value::Int(_), Value::Int(_))
52                | (Value::Float(_), Value::Float(_))
53                | (Value::Int(_), Value::Float(_))
54                | (Value::Float(_), Value::Int(_)) => {}
55                _ => return Err(ion_str!("math::min requires numeric arguments")),
56            }
57        }
58        Ok(best)
59    });
60
61    m.register_fn("max", |args: &[Value]| {
62        if args.len() < 2 {
63            return Err(ion_str!("math::max requires at least 2 arguments"));
64        }
65        let mut best = args[0].clone();
66        for arg in &args[1..] {
67            match (&best, arg) {
68                (Value::Int(a), Value::Int(b)) if b > a => best = arg.clone(),
69                (Value::Float(a), Value::Float(b)) if b > a => best = arg.clone(),
70                (Value::Int(a), Value::Float(b)) if *b > (*a as f64) => best = arg.clone(),
71                (Value::Float(a), Value::Int(b)) if (*b as f64) > *a => best = arg.clone(),
72                (Value::Int(_), Value::Int(_))
73                | (Value::Float(_), Value::Float(_))
74                | (Value::Int(_), Value::Float(_))
75                | (Value::Float(_), Value::Int(_)) => {}
76                _ => return Err(ion_str!("math::max requires numeric arguments")),
77            }
78        }
79        Ok(best)
80    });
81
82    m.register_fn("floor", |args: &[Value]| match &args[0] {
83        Value::Float(n) => Ok(Value::Float(n.floor())),
84        Value::Int(n) => Ok(Value::Int(*n)),
85        _ => Err(format!(
86            "{}{}",
87            ion_str!("math::floor not supported for "),
88            args[0].type_name()
89        )),
90    });
91
92    m.register_fn("ceil", |args: &[Value]| match &args[0] {
93        Value::Float(n) => Ok(Value::Float(n.ceil())),
94        Value::Int(n) => Ok(Value::Int(*n)),
95        _ => Err(format!(
96            "{}{}",
97            ion_str!("math::ceil not supported for "),
98            args[0].type_name()
99        )),
100    });
101
102    m.register_fn("round", |args: &[Value]| match &args[0] {
103        Value::Float(n) => Ok(Value::Float(n.round())),
104        Value::Int(n) => Ok(Value::Int(*n)),
105        _ => Err(format!(
106            "{}{}",
107            ion_str!("math::round not supported for "),
108            args[0].type_name()
109        )),
110    });
111
112    m.register_fn("sqrt", |args: &[Value]| {
113        let n = args[0]
114            .as_float()
115            .ok_or(ion_str!("math::sqrt requires a number"))?;
116        Ok(Value::Float(n.sqrt()))
117    });
118
119    m.register_fn("pow", |args: &[Value]| {
120        if args.len() != 2 {
121            return Err(ion_str!("math::pow takes 2 arguments"));
122        }
123        match (&args[0], &args[1]) {
124            (Value::Int(base), Value::Int(exp)) => {
125                if *exp >= 0 {
126                    Ok(Value::Int(base.pow(*exp as u32)))
127                } else {
128                    Ok(Value::Float((*base as f64).powi(*exp as i32)))
129                }
130            }
131            _ => {
132                let b = args[0]
133                    .as_float()
134                    .ok_or(ion_str!("math::pow requires numeric arguments"))?;
135                let e = args[1]
136                    .as_float()
137                    .ok_or(ion_str!("math::pow requires numeric arguments"))?;
138                Ok(Value::Float(b.powf(e)))
139            }
140        }
141    });
142
143    m.register_fn("clamp", |args: &[Value]| {
144        if args.len() != 3 {
145            return Err(ion_str!(
146                "math::clamp requires 3 arguments: value, min, max"
147            ));
148        }
149        match (&args[0], &args[1], &args[2]) {
150            (Value::Int(v), Value::Int(lo), Value::Int(hi)) => Ok(Value::Int(*v.max(lo).min(hi))),
151            (Value::Float(v), Value::Float(lo), Value::Float(hi)) => {
152                Ok(Value::Float(v.max(*lo).min(*hi)))
153            }
154            _ => {
155                let v = args[0]
156                    .as_float()
157                    .ok_or(ion_str!("math::clamp requires numeric arguments"))?;
158                let lo = args[1]
159                    .as_float()
160                    .ok_or(ion_str!("math::clamp requires numeric arguments"))?;
161                let hi = args[2]
162                    .as_float()
163                    .ok_or(ion_str!("math::clamp requires numeric arguments"))?;
164                Ok(Value::Float(v.max(lo).min(hi)))
165            }
166        }
167    });
168
169    // Trigonometry
170    m.register_fn("sin", |args: &[Value]| {
171        let n = args[0]
172            .as_float()
173            .ok_or(ion_str!("math::sin requires a number"))?;
174        Ok(Value::Float(n.sin()))
175    });
176
177    m.register_fn("cos", |args: &[Value]| {
178        let n = args[0]
179            .as_float()
180            .ok_or(ion_str!("math::cos requires a number"))?;
181        Ok(Value::Float(n.cos()))
182    });
183
184    m.register_fn("tan", |args: &[Value]| {
185        let n = args[0]
186            .as_float()
187            .ok_or(ion_str!("math::tan requires a number"))?;
188        Ok(Value::Float(n.tan()))
189    });
190
191    m.register_fn("atan2", |args: &[Value]| {
192        if args.len() != 2 {
193            return Err(ion_str!("math::atan2 takes 2 arguments"));
194        }
195        let y = args[0]
196            .as_float()
197            .ok_or(ion_str!("math::atan2 requires numeric arguments"))?;
198        let x = args[1]
199            .as_float()
200            .ok_or(ion_str!("math::atan2 requires numeric arguments"))?;
201        Ok(Value::Float(y.atan2(x)))
202    });
203
204    // Logarithms
205    m.register_fn("log", |args: &[Value]| {
206        let n = args[0]
207            .as_float()
208            .ok_or(ion_str!("math::log requires a number"))?;
209        Ok(Value::Float(n.ln()))
210    });
211
212    m.register_fn("log2", |args: &[Value]| {
213        let n = args[0]
214            .as_float()
215            .ok_or(ion_str!("math::log2 requires a number"))?;
216        Ok(Value::Float(n.log2()))
217    });
218
219    m.register_fn("log10", |args: &[Value]| {
220        let n = args[0]
221            .as_float()
222            .ok_or(ion_str!("math::log10 requires a number"))?;
223        Ok(Value::Float(n.log10()))
224    });
225
226    // Rounding/check
227    m.register_fn("is_nan", |args: &[Value]| match &args[0] {
228        Value::Float(n) => Ok(Value::Bool(n.is_nan())),
229        Value::Int(_) => Ok(Value::Bool(false)),
230        _ => Err(format!(
231            "{}{}",
232            ion_str!("math::is_nan not supported for "),
233            args[0].type_name()
234        )),
235    });
236
237    m.register_fn("is_inf", |args: &[Value]| match &args[0] {
238        Value::Float(n) => Ok(Value::Bool(n.is_infinite())),
239        Value::Int(_) => Ok(Value::Bool(false)),
240        _ => Err(format!(
241            "{}{}",
242            ion_str!("math::is_inf not supported for "),
243            args[0].type_name()
244        )),
245    });
246
247    m
248}
249
250/// Build the `json` stdlib module.
251///
252/// Functions: encode, decode, pretty
253pub fn json_module() -> Module {
254    let mut m = Module::new("json");
255
256    m.register_fn("encode", |args: &[Value]| {
257        if args.len() != 1 {
258            return Err(ion_str!("json::encode takes 1 argument"));
259        }
260        let json = args[0].to_json();
261        Ok(Value::Str(json.to_string()))
262    });
263
264    m.register_fn("decode", |args: &[Value]| {
265        if args.len() != 1 {
266            return Err(ion_str!("json::decode takes 1 argument"));
267        }
268        let s = args[0]
269            .as_str()
270            .ok_or_else(|| ion_str!("json::decode requires a string"))?;
271        let json: serde_json::Value = serde_json::from_str(s)
272            .map_err(|e| format!("{}{}", ion_str!("json::decode error: "), e))?;
273        Ok(Value::from_json(json))
274    });
275
276    m.register_fn("pretty", |args: &[Value]| {
277        if args.len() != 1 {
278            return Err(ion_str!("json::pretty takes 1 argument"));
279        }
280        let json = args[0].to_json();
281        serde_json::to_string_pretty(&json)
282            .map(Value::Str)
283            .map_err(|e| format!("{}{}", ion_str!("json::pretty error: "), e))
284    });
285
286    #[cfg(feature = "msgpack")]
287    m.register_fn("msgpack_encode", |args: &[Value]| {
288        if args.len() != 1 {
289            return Err(ion_str!("json::msgpack_encode takes 1 argument"));
290        }
291        args[0].to_msgpack().map(Value::Bytes)
292    });
293
294    #[cfg(feature = "msgpack")]
295    m.register_fn("msgpack_decode", |args: &[Value]| {
296        if args.len() != 1 {
297            return Err(ion_str!("json::msgpack_decode takes 1 argument"));
298        }
299        let data = match &args[0] {
300            Value::Bytes(b) => b,
301            _ => return Err(ion_str!("json::msgpack_decode requires bytes")),
302        };
303        Value::from_msgpack(data)
304    });
305
306    m
307}
308
309/// Build the `io` stdlib module.
310///
311/// Functions: print, println, input (placeholder)
312pub fn io_module() -> Module {
313    let mut m = Module::new("io");
314
315    m.register_fn("print", |args: &[Value]| {
316        let parts: Vec<String> = args.iter().map(|a| a.to_string()).collect();
317        print!("{}", parts.join(" "));
318        Ok(Value::Unit)
319    });
320
321    m.register_fn("println", |args: &[Value]| {
322        let parts: Vec<String> = args.iter().map(|a| a.to_string()).collect();
323        println!("{}", parts.join(" "));
324        Ok(Value::Unit)
325    });
326
327    m.register_fn("eprintln", |args: &[Value]| {
328        let parts: Vec<String> = args.iter().map(|a| a.to_string()).collect();
329        eprintln!("{}", parts.join(" "));
330        Ok(Value::Unit)
331    });
332
333    m
334}
335
336/// Build the `str` stdlib module.
337///
338/// Functions: join
339pub fn string_module() -> Module {
340    let mut m = Module::new("string");
341
342    m.register_fn("join", |args: &[Value]| {
343        if args.is_empty() || args.len() > 2 {
344            return Err(ion_str!(
345                "string::join requires 1-2 arguments: list, [separator]"
346            ));
347        }
348        let items = match &args[0] {
349            Value::List(items) => items,
350            _ => return Err(ion_str!("string::join requires a list as first argument")),
351        };
352        let sep = if args.len() > 1 {
353            args[1].as_str().unwrap_or("").to_string()
354        } else {
355            String::new()
356        };
357        let parts: Vec<String> = items.iter().map(|v| format!("{}", v)).collect();
358        Ok(Value::Str(parts.join(&sep)))
359    });
360
361    m
362}
363
364/// Register all stdlib modules in the given environment.
365pub fn register_stdlib(env: &mut crate::env::Env) {
366    let math = math_module();
367    env.define(math.name.clone(), math.to_value(), false);
368
369    let json = json_module();
370    env.define(json.name.clone(), json.to_value(), false);
371
372    let io = io_module();
373    env.define(io.name.clone(), io.to_value(), false);
374
375    let string_mod = string_module();
376    env.define(string_mod.name.clone(), string_mod.to_value(), false);
377}