Skip to main content

pepl_stdlib/modules/
convert.rs

1//! `convert` stdlib module — type conversion utilities.
2//!
3//! Functions: to_string, to_number, parse_int, parse_float, to_bool.
4
5use crate::error::StdlibError;
6use crate::module::StdlibModule;
7use crate::value::Value;
8
9/// The `convert` stdlib module.
10pub struct ConvertModule;
11
12impl ConvertModule {
13    pub fn new() -> Self {
14        Self
15    }
16}
17
18impl Default for ConvertModule {
19    fn default() -> Self {
20        Self::new()
21    }
22}
23
24impl StdlibModule for ConvertModule {
25    fn name(&self) -> &'static str {
26        "convert"
27    }
28
29    fn has_function(&self, function: &str) -> bool {
30        matches!(
31            function,
32            "to_string" | "to_number" | "parse_int" | "parse_float" | "to_bool"
33        )
34    }
35
36    fn call(&self, function: &str, args: Vec<Value>) -> Result<Value, StdlibError> {
37        match function {
38            "to_string" => self.to_string_fn(args),
39            "to_number" => self.to_number(args),
40            "parse_int" => self.parse_int(args),
41            "parse_float" => self.parse_float(args),
42            "to_bool" => self.to_bool(args),
43            _ => Err(StdlibError::unknown_function("convert", function)),
44        }
45    }
46}
47
48impl ConvertModule {
49    /// convert.to_string(value) → string
50    /// Always succeeds — uses Value's Display impl.
51    fn to_string_fn(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
52        if args.len() != 1 {
53            return Err(StdlibError::wrong_args("convert.to_string", 1, args.len()));
54        }
55        Ok(Value::String(format!("{}", args[0])))
56    }
57
58    /// convert.to_number(value) → Result<number, string>
59    /// - Number → Ok(identity)
60    /// - Bool → Ok(0 or 1)
61    /// - String → parse as f64
62    /// - Nil → Err("cannot convert nil to number")
63    /// - Other → Err
64    fn to_number(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
65        if args.len() != 1 {
66            return Err(StdlibError::wrong_args("convert.to_number", 1, args.len()));
67        }
68        match &args[0] {
69            Value::Number(n) => Ok(Value::Number(*n).ok()),
70            Value::Bool(b) => Ok(Value::Number(if *b { 1.0 } else { 0.0 }).ok()),
71            Value::String(s) => match s.trim().parse::<f64>() {
72                Ok(n) if n.is_finite() => Ok(Value::Number(n).ok()),
73                _ => Ok(Value::String(format!("cannot convert '{}' to number", s)).err()),
74            },
75            other => {
76                Ok(Value::String(format!("cannot convert {} to number", other.type_name())).err())
77            }
78        }
79    }
80
81    /// convert.parse_int(s) → Result<number, string>
82    /// Parses an integer string. Rejects floats.
83    fn parse_int(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
84        if args.len() != 1 {
85            return Err(StdlibError::wrong_args("convert.parse_int", 1, args.len()));
86        }
87        let s = extract_string("convert.parse_int", &args[0], 1)?;
88        match s.trim().parse::<i64>() {
89            Ok(n) => Ok(Value::Number(n as f64).ok()),
90            Err(_) => Ok(Value::String(format!("cannot parse '{}' as integer", s)).err()),
91        }
92    }
93
94    /// convert.parse_float(s) → Result<number, string>
95    fn parse_float(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
96        if args.len() != 1 {
97            return Err(StdlibError::wrong_args(
98                "convert.parse_float",
99                1,
100                args.len(),
101            ));
102        }
103        let s = extract_string("convert.parse_float", &args[0], 1)?;
104        match s.trim().parse::<f64>() {
105            Ok(n) if n.is_finite() => Ok(Value::Number(n).ok()),
106            _ => Ok(Value::String(format!("cannot parse '{}' as float", s)).err()),
107        }
108    }
109
110    /// convert.to_bool(value) → bool
111    /// Uses truthiness: false, nil, 0, "" are falsy; everything else is truthy.
112    fn to_bool(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
113        if args.len() != 1 {
114            return Err(StdlibError::wrong_args("convert.to_bool", 1, args.len()));
115        }
116        Ok(Value::Bool(args[0].is_truthy()))
117    }
118}
119
120// ── Helpers ──────────────────────────────────────────────────────────────────
121
122fn extract_string<'a>(func: &str, val: &'a Value, pos: usize) -> Result<&'a str, StdlibError> {
123    match val {
124        Value::String(s) => Ok(s),
125        _ => Err(StdlibError::type_mismatch(
126            func,
127            pos,
128            "string",
129            val.type_name(),
130        )),
131    }
132}