Skip to main content

pepl_stdlib/modules/
core.rs

1//! The `core` module — 4 functions.
2//!
3//! | Function | Signature | Description |
4//! |----------|-----------|-------------|
5//! | `core.log` | `(value: any) -> nil` | Debug logging (no-op in production) |
6//! | `core.assert` | `(condition: bool, message?: string) -> nil` | Trap if false |
7//! | `core.type_of` | `(value: any) -> string` | Returns type name |
8//! | `core.capability` | `(name: string) -> bool` | Check capability availability |
9
10use crate::error::StdlibError;
11use crate::module::StdlibModule;
12use crate::value::Value;
13
14/// The `core` stdlib module.
15pub struct CoreModule;
16
17impl CoreModule {
18    pub fn new() -> Self {
19        Self
20    }
21}
22
23impl Default for CoreModule {
24    fn default() -> Self {
25        Self::new()
26    }
27}
28
29impl StdlibModule for CoreModule {
30    fn name(&self) -> &'static str {
31        "core"
32    }
33
34    fn has_function(&self, function: &str) -> bool {
35        matches!(function, "log" | "assert" | "type_of" | "capability")
36    }
37
38    fn call(&self, function: &str, args: Vec<Value>) -> Result<Value, StdlibError> {
39        match function {
40            "log" => self.log(args),
41            "assert" => self.assert(args),
42            "type_of" => self.type_of(args),
43            "capability" => self.capability(args),
44            _ => Err(StdlibError::unknown_function("core", function)),
45        }
46    }
47}
48
49impl CoreModule {
50    /// `core.log(value: any) -> nil`
51    ///
52    /// Debug logging. In production this is a no-op. In dev/test, the value
53    /// is printed to stderr. Always returns `Nil`.
54    fn log(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
55        if args.len() != 1 {
56            return Err(StdlibError::wrong_args("core.log", 1, args.len()));
57        }
58        // No-op in production — the value is consumed but not output.
59        // A dev/test host can intercept this via a log callback.
60        Ok(Value::Nil)
61    }
62
63    /// `core.assert(condition: bool, message?: string) -> nil`
64    ///
65    /// Traps (returns error) if condition is false. The optional message
66    /// provides context in test output.
67    fn assert(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
68        if args.is_empty() || args.len() > 2 {
69            return Err(StdlibError::wrong_args("core.assert", 1, args.len()));
70        }
71
72        let condition = match &args[0] {
73            Value::Bool(b) => *b,
74            other => {
75                return Err(StdlibError::type_mismatch(
76                    "core.assert",
77                    1,
78                    "bool",
79                    other.type_name(),
80                ));
81            }
82        };
83
84        if let Some(msg_val) = args.get(1) {
85            if !matches!(msg_val, Value::String(_)) {
86                return Err(StdlibError::type_mismatch(
87                    "core.assert",
88                    2,
89                    "string",
90                    msg_val.type_name(),
91                ));
92            }
93        }
94
95        if !condition {
96            let message = match args.get(1) {
97                Some(Value::String(s)) => s.clone(),
98                _ => "assertion failed".to_string(),
99            };
100            return Err(StdlibError::AssertionFailed { message });
101        }
102
103        Ok(Value::Nil)
104    }
105
106    /// `core.type_of(value: any) -> string`
107    ///
108    /// Returns the type name: "number", "string", "bool", "nil", "list",
109    /// "record" (or declared type name for named records/sum variants).
110    fn type_of(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
111        if args.len() != 1 {
112            return Err(StdlibError::wrong_args("core.type_of", 1, args.len()));
113        }
114        Ok(Value::String(args[0].type_name().to_string()))
115    }
116
117    /// `core.capability(name: string) -> bool`
118    ///
119    /// Returns whether a declared optional capability is available at runtime.
120    /// In Phase 0, no capabilities are declared, so this always returns `false`.
121    fn capability(&self, args: Vec<Value>) -> Result<Value, StdlibError> {
122        if args.len() != 1 {
123            return Err(StdlibError::wrong_args("core.capability", 1, args.len()));
124        }
125        match &args[0] {
126            Value::String(_) => {
127                // Phase 0: no capabilities are ever available
128                Ok(Value::Bool(false))
129            }
130            other => Err(StdlibError::type_mismatch(
131                "core.capability",
132                1,
133                "string",
134                other.type_name(),
135            )),
136        }
137    }
138}