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 {}