Skip to main content

catallaxy_stdlib/
lib.rs

1//! Native implementations of the Catallaxy standard library.
2//!
3//! Each native receives the calling thread's [`Vm`] and its arguments (the receiver first for
4//! methods). A missing optional argument arrives as the empty marker; use [`opt`].
5
6mod coll;
7mod conc;
8mod core;
9mod sys;
10
11use std::io::Write;
12use std::sync::OnceLock;
13
14use catallaxy_runtime::class::class_of;
15use catallaxy_runtime::object::{as_list, kind_of, str_of};
16use catallaxy_runtime::ops;
17use catallaxy_runtime::ownership;
18use catallaxy_runtime::{panic, ObjKind, RtResult, Trap, Value};
19use catallaxy_vm::{NativeFn, Vm};
20use parking_lot::Mutex;
21
22/// Command-line arguments passed to the program (after the script path).
23pub static ARGS: OnceLock<Vec<String>> = OnceLock::new();
24
25static STDOUT: Mutex<Option<std::io::BufWriter<std::io::Stdout>>> = Mutex::new(None);
26
27/// Writes to the buffered standard output.
28pub fn write_out(s: &str) {
29    let mut g = STDOUT.lock();
30    let w = g.get_or_insert_with(|| std::io::BufWriter::with_capacity(1 << 16, std::io::stdout()));
31    let _ = w.write_all(s.as_bytes());
32}
33
34/// Flushes buffered standard output (at exit, before reading input, before errors).
35pub fn flush() {
36    if let Some(w) = STDOUT.lock().as_mut() {
37        let _ = w.flush();
38    }
39}
40
41static EXTENSIONS: std::sync::LazyLock<parking_lot::RwLock<rustc_hash::FxHashMap<String, NativeFn>>> =
42    std::sync::LazyLock::new(Default::default);
43
44/// Registers a native function implemented by a Rust extension crate. Catallaxy code declares it
45/// with `native fn name(...) -> T` (a top-level `native fn` uses its plain name; a `native fn` in
46/// a class `C` uses `C.name`). Must be called before the program is loaded.
47pub fn register_native(name: &str, f: NativeFn) {
48    EXTENSIONS.write().insert(name.to_string(), f);
49}
50
51/// Looks up a native implementation by name: the standard library, then extensions.
52pub fn lookup(name: &str) -> Option<NativeFn> {
53    core::lookup(name)
54        .or_else(|| coll::lookup(name))
55        .or_else(|| conc::lookup(name))
56        .or_else(|| sys::lookup(name))
57        .or_else(|| EXTENSIONS.read().get(name).copied())
58}
59
60// ---- argument helpers ------------------------------------------------------------------------
61
62pub(crate) fn opt(v: Value) -> Option<Value> {
63    if v.is_empty_marker() {
64        None
65    } else {
66        Some(v)
67    }
68}
69
70pub(crate) fn arg(args: &[Value], i: usize) -> Option<Value> {
71    args.get(i).copied().and_then(opt)
72}
73
74pub(crate) fn s<'a>(v: Value) -> RtResult<&'a str> {
75    match str_of(v) {
76        Some(s) => Ok(s),
77        None => panic(format!("TypeError: expected String, got {}", class_of(v).name)),
78    }
79}
80
81pub(crate) fn int(v: Value) -> RtResult<i64> {
82    if v.is_small_int() {
83        return Ok(v.as_small_int());
84    }
85    if ops::is_int(v) {
86        return match ops::int_as_i64(v) {
87            Some(i) => Ok(i),
88            None => panic("RangeError: integer too large"),
89        };
90    }
91    panic(format!("TypeError: expected Int, got {}", class_of(v).name))
92}
93
94pub(crate) fn float(v: Value) -> RtResult<f64> {
95    if v.is_float() {
96        Ok(v.as_f64())
97    } else if ops::is_int(v) {
98        Ok(ops::int_to_f64(v))
99    } else {
100        panic(format!("TypeError: expected Float, got {}", class_of(v).name))
101    }
102}
103
104pub(crate) fn new_str(vm: &mut Vm, s: &str) -> Value {
105    vm.m.alloc_str(s)
106}
107
108pub(crate) fn new_list(vm: &mut Vm, items: Vec<Value>) -> Value {
109    vm.m.alloc_list(items)
110}
111
112/// A snapshot of a list's items (with ownership checks).
113pub(crate) fn list_items(vm: &Vm, l: Value) -> RtResult<Vec<Value>> {
114    if kind_of(l) != ObjKind::List {
115        return panic(format!("TypeError: expected List, got {}", class_of(l).name));
116    }
117    ownership::with_read(&vm.m, l, || unsafe { as_list(l).items() }.clone())
118}
119
120pub(crate) fn to_s(vm: &mut Vm, v: Value) -> RtResult<String> {
121    ops::to_s(vm, v)
122}
123
124pub(crate) fn ok(vm: &mut Vm, v: Value) -> Value {
125    catallaxy_vm::slow::make_result(vm, true, v)
126}
127
128pub(crate) fn err(vm: &mut Vm, msg: &str) -> Value {
129    let s = new_str(vm, msg);
130    catallaxy_vm::slow::make_result(vm, false, s)
131}
132
133pub(crate) fn exit_trap(code: i32) -> Trap {
134    Trap::Exit(code)
135}
136
137/// Runs a blocking operation with the thread in native state.
138pub(crate) fn blocking<R>(vm: &Vm, f: impl FnOnce() -> R) -> R {
139    flush();
140    catallaxy_gc::blocking(&vm.m.rec, f)
141}