Skip to main content

oxide_core/
host.rs

1//! The host boundary: what the core asks of its embedder. `quickjs-oxide-core` reaches
2//! the platform only through this trait, so it stays dependency-free and a port
3//! supplies its own implementation.
4
5/// Which output stream `print` and the console write to.
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum Stream {
8    Stdout,
9    Stderr,
10}
11
12/// What [`Host::promise_rejection`] reports (ECMAScript's
13/// `HostPromiseRejectionTracker` operations).
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub enum RejectionEvent {
16    /// A promise was rejected with no handler attached (`"reject"`).
17    Rejected,
18    /// A handler was attached to a promise already rejected without one
19    /// (`"handle"`).
20    Handled,
21}
22
23/// The embedder's services. Every method has a default suitable for a
24/// standalone runtime: the process's streams, the system clock and UTC.
25pub trait Host {
26    /// Writes host output (`print`): UTF-8, with a lone surrogate of the
27    /// JavaScript string encoded as is (WTF-8), as QuickJS's
28    /// `JS_ToCStringLen` writes it. The default writes to the process's
29    /// streams.
30    fn write(&mut self, stream: Stream, text: &[u8]) {
31        use std::io::Write;
32        match stream {
33            Stream::Stdout => {
34                let mut out = std::io::stdout().lock();
35                let _ = out.write_all(text);
36                let _ = out.flush();
37            }
38            Stream::Stderr => {
39                let mut out = std::io::stderr().lock();
40                let _ = out.write_all(text);
41                let _ = out.flush();
42            }
43        }
44    }
45
46    /// Milliseconds since the Unix epoch (`Date.now()`).
47    fn now_ms(&mut self) -> f64 {
48        std::time::SystemTime::now()
49            .duration_since(std::time::UNIX_EPOCH)
50            .map_or(0.0, |elapsed| elapsed.as_millis() as f64)
51    }
52
53    /// UTC minus local time, in minutes, at `utc_ms`
54    /// (`Date.prototype.getTimezoneOffset`'s sign). The default is UTC.
55    fn local_tz_offset_minutes(&mut self, _utc_ms: f64) -> i32 {
56        0
57    }
58
59    /// The initial state of `Math.random`. The default derives it from the
60    /// system clock, as QuickJS seeds from host time.
61    fn random_seed(&mut self) -> u64 {
62        crate::clock::time_seed()
63    }
64
65    /// `HostPromiseRejectionTracker(promise, operation)`. The promise is a
66    /// value of the runtime; while rejection tracking is on
67    /// ([`Runtime::set_track_rejections`](crate::Runtime::set_track_rejections))
68    /// the runtime also keeps every unhandled rejected promise alive in
69    /// [`Runtime::unhandled_rejections`](crate::Runtime::unhandled_rejections).
70    /// The default ignores the event.
71    fn promise_rejection(&mut self, event: RejectionEvent, promise: crate::Value) {
72        let _ = (event, promise);
73    }
74
75    /// The text of the file at `path`, for the CLI's `load` and `read`
76    /// (`qjs --std`). The default reads the process's file system.
77    fn read_file(&mut self, path: &str) -> Option<String> {
78        std::fs::read_to_string(path).ok()
79    }
80
81    /// Polled about every thousand loop back-edges and calls while a
82    /// script runs: `true` stops the script with an uncatchable
83    /// `InternalError: interrupted`. The default never interrupts.
84    fn interrupt(&mut self) -> bool {
85        false
86    }
87}
88
89/// The default host: the process's streams, the system clock, UTC.
90#[derive(Debug, Default)]
91pub struct SystemHost;
92
93impl Host for SystemHost {}