Skip to main content

oxide_core/vm/
compile.rs

1//! Code compiled at run time: `eval`, the `Function` constructors,
2//! `$262.evalScript` and `Function.prototype.toString`'s source text, through
3//! the compile host the embedder installs.
4//!
5//! The core has no front end. The embedder installs a [`CompileFn`] that
6//! compiles and lowers source into a [`Compiler`], the part of the runtime a
7//! lowering needs (the heap, the code store, the realm), which the natives
8//! can lend while the loop holds the code store: units compiled at run time
9//! are published into the heap, which frees them once unreachable.
10//!
11//! A compilation keeps what it makes alive until it ends: a unit published
12//! but not yet instantiated is pinned, and the atoms it interns (keys, names,
13//! string constants) are held. Before the program runs, its atoms are pinned
14//! for good instead.
15
16use super::native::CallSite;
17use super::{Cx, Throw, error, ops};
18use crate::ErrorKind;
19use crate::bytecode::{CodeStore, CodeUnit, CodeUnitId, Units, ValidateError, validate};
20use crate::heap::hash::FxHashMap;
21use crate::heap::{AtomMode, BytecodeTemplate, Heap, HeapError};
22use crate::runtime::{Intrinsic, Realm};
23use crate::value::{ObjectId, Value};
24
25/// The code store a compilation publishes into: owned before the program
26/// runs (units go in the static store), shared while it runs (units go in
27/// the heap).
28enum Store<'a> {
29    Owned(&'a mut CodeStore),
30    Shared(&'a CodeStore),
31}
32
33/// The runtime's parts a compilation uses. Dropping it ends the
34/// compilation: its pinned units and held atoms are released (a
35/// [`Compiler::reborrow`] ends nothing).
36pub struct Compiler<'a> {
37    heap: &'a mut Heap,
38    code: Store<'a>,
39    realm: &'a Realm,
40    /// The atom mode before the compilation, restored after it.
41    mode: AtomMode,
42    /// Where the compilation's pinned units and held atoms start.
43    pins: usize,
44    held: usize,
45    /// Whether dropping it ends the compilation (not a reborrow's).
46    ends: bool,
47}
48
49impl Drop for Compiler<'_> {
50    fn drop(&mut self) {
51        if !self.ends {
52            return;
53        }
54        self.heap.set_atom_mode(self.mode);
55        self.heap.code.unpin(self.pins);
56        self.heap.release_held_atoms(self.held);
57    }
58}
59
60impl<'a> Compiler<'a> {
61    fn with(heap: &'a mut Heap, code: Store<'a>, realm: &'a Realm, mode: AtomMode) -> Self {
62        let pins = heap.code.pin_mark();
63        let held = heap.held_atoms_mark();
64        let mode = heap.set_atom_mode(mode);
65        Compiler {
66            heap,
67            code,
68            realm,
69            mode,
70            pins,
71            held,
72            ends: true,
73        }
74    }
75
76    pub(crate) fn new(heap: &'a mut Heap, code: &'a CodeStore, realm: &'a Realm) -> Self {
77        Compiler::with(heap, Store::Shared(code), realm, AtomMode::Hold)
78    }
79
80    pub(crate) fn owned(heap: &'a mut Heap, code: &'a mut CodeStore, realm: &'a Realm) -> Self {
81        Compiler::with(heap, Store::Owned(code), realm, AtomMode::Pin)
82    }
83
84    /// The same parts for a shorter borrow (a host callback's view).
85    pub fn reborrow(&mut self) -> Compiler<'_> {
86        let code = match &mut self.code {
87            Store::Owned(code) => Store::Owned(code),
88            Store::Shared(code) => Store::Shared(code),
89        };
90        Compiler {
91            heap: self.heap,
92            code,
93            realm: self.realm,
94            mode: self.mode,
95            pins: self.pins,
96            held: self.held,
97            ends: false,
98        }
99    }
100
101    /// [`NativeCx::new_host_function`](crate::NativeCx::new_host_function).
102    pub fn new_host_function(
103        &mut self,
104        name: &str,
105        length: u32,
106        constructor: bool,
107        data: &[Value],
108        body: std::rc::Rc<super::native::HostFn>,
109    ) -> Result<ObjectId, HeapError> {
110        crate::embed::host_function(self.heap, self.realm, name, length, constructor, data, body)
111    }
112
113    /// [`NativeCx::new_host_object`](crate::NativeCx::new_host_object).
114    pub fn new_host_object(
115        &mut self,
116        prototype: Option<ObjectId>,
117        data: std::rc::Rc<dyn std::any::Any>,
118    ) -> Result<ObjectId, HeapError> {
119        crate::embed::host_object(self.heap, prototype, data)
120    }
121
122    /// Holds `value` for the embedder until released (the heap's handle
123    /// table).
124    pub fn hold(&mut self, value: Value) -> crate::Handle {
125        self.heap.handles().create(value)
126    }
127
128    /// A new error object of `kind` with `message` and an empty `stack`: an
129    /// error a host callback raises, with no JavaScript frame, as QuickJS's.
130    pub fn new_error(&mut self, kind: ErrorKind, message: &str) -> Result<Value, HeapError> {
131        let prototype = self.realm.intrinsic(kind.prototype());
132        let message = self.heap.alloc_string(message)?;
133        let mark = self.heap.roots().mark();
134        self.heap.roots().push(Value::string(message));
135        let result = (|| {
136            let object = self
137                .heap
138                .alloc_object(crate::ObjectKind::Ordinary, Some(prototype), 1)?;
139            self.heap
140                .set_object_flag(object, crate::heap::object::flags::ERROR);
141            let key = crate::runtime::Names::key(self.realm.names.message);
142            self.heap.add_property(
143                object,
144                key,
145                crate::PropertyFlags::data(true, false, true),
146                Value::string(message),
147            )?;
148            let empty = self.heap.intern("")?;
149            let stack = crate::runtime::Names::key(self.realm.names.stack);
150            self.heap.add_property(
151                object,
152                stack,
153                crate::PropertyFlags::data(true, false, true),
154                Value::string(empty),
155            )?;
156            Ok(Value::object(object))
157        })();
158        self.heap.roots().truncate(mark);
159        result
160    }
161
162    /// [`Compiler::new_error`] located at `file:line:column` (a module's
163    /// compile error): `fileName`, `lineNumber`, `columnNumber`, and a
164    /// `stack` of the location alone.
165    pub fn new_located_error(
166        &mut self,
167        kind: ErrorKind,
168        message: &str,
169        file: &str,
170        line: u32,
171        column: u32,
172    ) -> Result<Value, HeapError> {
173        let error = self.new_error(kind, message)?;
174        let object = error.as_object().expect("an error object");
175        let names = self.realm.names;
176        let mark = self.heap.roots().mark();
177        self.heap.roots().push(error);
178        let result = (|| {
179            let flags = crate::PropertyFlags::data(true, false, true);
180            let stack_key = crate::runtime::Names::key(names.stack);
181            // `fileName`, `lineNumber` and `columnNumber` precede `stack`,
182            // as QuickJS's `build_backtrace` defines them: the stack is
183            // re-added after them.
184            self.heap.delete_property(object, stack_key)?;
185            let file_text = self.heap.alloc_string(file)?;
186            self.heap.add_property(
187                object,
188                crate::runtime::Names::key(names.file_name),
189                flags,
190                Value::string(file_text),
191            )?;
192            self.heap.add_property(
193                object,
194                crate::runtime::Names::key(names.line_number),
195                flags,
196                Value::int32(line as i32),
197            )?;
198            self.heap.add_property(
199                object,
200                crate::runtime::Names::key(names.column_number),
201                flags,
202                Value::int32(column as i32),
203            )?;
204            let stack = self
205                .heap
206                .alloc_string(&format!("    at {file}:{line}:{column}\n"))?;
207            self.heap
208                .add_property(object, stack_key, flags, Value::string(stack))?;
209            Ok(error)
210        })();
211        self.heap.roots().truncate(mark);
212        result
213    }
214
215    pub fn heap(&self) -> &Heap {
216        self.heap
217    }
218
219    pub fn heap_mut(&mut self) -> &mut Heap {
220        self.heap
221    }
222
223    /// Every published unit, static and dynamic.
224    pub fn units(&self) -> Units<'_> {
225        let code = match &self.code {
226            Store::Owned(code) => &**code,
227            Store::Shared(code) => *code,
228        };
229        Units {
230            code,
231            dynamic: &self.heap.code,
232        }
233    }
234
235    pub fn intrinsic(&self, intrinsic: Intrinsic) -> ObjectId {
236        self.realm.intrinsic(intrinsic)
237    }
238
239    /// Validates `unit`, reserves its cache entries and publishes it: into
240    /// the code store before the program runs (its key atoms pinned), into
241    /// the heap (pinned until the compilation ends, its bytes charged to the
242    /// collection trigger) while it runs. Its children must be published
243    /// first.
244    pub fn publish(&mut self, mut unit: CodeUnit) -> Result<CodeUnitId, ValidateError> {
245        validate::validate(&unit, &|id| self.units().lookup(id))?;
246        self.heap.ics.reserve(&mut unit);
247        match &mut self.code {
248            // A static unit lives as long as the runtime, and so do its keys,
249            // wherever they were interned.
250            Store::Owned(code) => {
251                for &atom in &unit.key_atoms() {
252                    self.heap.pin_atom(atom);
253                }
254                Ok(code.push(unit))
255            }
256            Store::Shared(_) => {
257                let (id, bytes) = self.heap.code.publish(unit);
258                self.heap.gc.allocated += bytes;
259                Ok(id)
260            }
261        }
262    }
263
264    /// A function object for the published unit `root`, with a bytecode
265    /// record for it and every unit below it. `constants` holds one table per
266    /// unit, in the pre-order of [`Units::tree`].
267    pub fn instantiate(
268        &mut self,
269        root: CodeUnitId,
270        constants: &[&[Value]],
271    ) -> Result<ObjectId, HeapError> {
272        let code = match &self.code {
273            Store::Owned(code) => &**code,
274            Store::Shared(code) => *code,
275        };
276        let code = Units {
277            code,
278            dynamic: &self.heap.code,
279        };
280        let order = code.tree(root);
281        assert_eq!(order.len(), constants.len(), "one constant table per unit");
282        let position: FxHashMap<CodeUnitId, u32> = order
283            .iter()
284            .enumerate()
285            .map(|(index, &unit)| (unit, index as u32))
286            .collect();
287        let children: Vec<Vec<u32>> = order
288            .iter()
289            .map(|&id| {
290                let unit = code.get(id);
291                unit.children.iter().map(|child| position[child]).collect()
292            })
293            .collect();
294        let tree: Vec<BytecodeTemplate<'_>> = order
295            .iter()
296            .zip(constants)
297            .zip(&children)
298            .map(|((&unit, &constants), children)| {
299                assert_eq!(
300                    constants.len(),
301                    code.get(unit).constant_count as usize,
302                    "constant table length"
303                );
304                BytecodeTemplate {
305                    unit,
306                    constants,
307                    children,
308                }
309            })
310            .collect();
311        let prototype = self.realm.intrinsic(Intrinsic::FunctionPrototype);
312        self.heap
313            .alloc_bytecode_function(Some(prototype), None, &tree)
314    }
315}
316
317impl Compiler<'_> {
318    /// A tagged template's frozen site object.
319    pub fn template_object(
320        &mut self,
321        cooked: &[Value],
322        raw: &[Value],
323    ) -> Result<ObjectId, HeapError> {
324        use crate::heap::object::flags;
325        let prototype = self.intrinsic(Intrinsic::ArrayPrototype);
326        let names = self.realm.names;
327        let heap = &mut *self.heap;
328        let mark = heap.roots().mark();
329        // An atom interned outside a compilation is weak.
330        for &value in cooked.iter().chain(raw) {
331            heap.roots().push(value);
332        }
333        let result = (|| {
334            let raw_array = heap.alloc_array(Some(prototype), raw.len() as u32)?;
335            heap.roots().push(Value::object(raw_array));
336            let array = heap.alloc_array(Some(prototype), cooked.len() as u32)?;
337            heap.roots().push(Value::object(array));
338            for (target, values) in [(raw_array, raw), (array, cooked)] {
339                for (index, &value) in values.iter().enumerate() {
340                    let key =
341                        crate::PropertyKey::index(index as u32).ok_or(HeapError::OutOfMemory)?;
342                    heap.add_property(
343                        target,
344                        key,
345                        crate::PropertyFlags::data(false, true, false),
346                        value,
347                    )?;
348                }
349                heap.set_array_length(target, values.len() as u32);
350            }
351            let raw_key = crate::runtime::Names::key(names.raw);
352            heap.add_property(
353                array,
354                raw_key,
355                crate::PropertyFlags::data(false, false, false),
356                Value::object(raw_array),
357            )?;
358            for target in [raw_array, array] {
359                heap.set_object_flag(target, flags::SPARSE | flags::LENGTH_READONLY);
360                heap.clear_object_flag(target, flags::EXTENSIBLE);
361            }
362            Ok(array)
363        })();
364        heap.roots().truncate(mark);
365        result
366    }
367}
368
369/// What to compile.
370#[derive(Clone, Copy, Debug, PartialEq, Eq)]
371pub enum CompileKind {
372    /// A script (`$262.evalScript`): its declarations are global.
373    Script,
374    /// Indirect eval code (`eval`, and the `Function` constructors' source):
375    /// the root returns the completion value.
376    IndirectEval,
377}
378
379/// A compilation request.
380#[derive(Clone, Copy, Debug)]
381pub struct CompileRequest<'s> {
382    pub source: &'s [u16],
383    pub kind: CompileKind,
384    pub filename: &'s str,
385}
386
387/// A failed compilation: the error to throw.
388#[derive(Clone, Debug)]
389pub struct CompileError {
390    pub kind: ErrorKind,
391    pub message: String,
392    /// A value the embedder threw instead (a module loader's own error
393    /// object): thrown as is, `kind` and `message` unused. It must stay
394    /// rooted until the core throws it (a handle the embedder releases
395    /// after the entry).
396    pub thrown: Option<Value>,
397    /// Where in which source the error is (a syntax error's file, line and
398    /// column, one-based): the error's `stack` starts with it, as
399    /// QuickJS's does.
400    pub location: Option<(Box<str>, u32, u32)>,
401}
402
403impl CompileError {
404    pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
405        CompileError {
406            kind,
407            message: message.into(),
408            thrown: None,
409            location: None,
410        }
411    }
412
413    /// The error located at `line` and `column` of `file`.
414    pub fn at(mut self, file: &str, line: u32, column: u32) -> Self {
415        self.location = Some((file.into(), line, column));
416        self
417    }
418
419    pub fn thrown(value: Value) -> Self {
420        CompileError {
421            kind: ErrorKind::Internal,
422            message: String::new(),
423            thrown: Some(value),
424            location: None,
425        }
426    }
427}
428
429/// Throws a compile failure.
430pub(crate) fn throw_failure(cx: &mut Cx<'_>, failure: CompileError) -> Throw {
431    match (failure.thrown, failure.location) {
432        (Some(value), _) => Throw(value),
433        (None, Some((file, line, column))) => {
434            error::throw_located(cx, failure.kind, &failure.message, &file, line, column)
435        }
436        (None, None) => error::throw(cx, failure.kind, &failure.message),
437    }
438}
439
440/// The embedder's compiler: the root function of the compiled code, to be
441/// called with the global object as `this`.
442pub type CompileFn = fn(&mut Compiler<'_>, &CompileRequest<'_>) -> Result<ObjectId, CompileError>;
443
444/// Compiles `source` through the host, throwing its error.
445pub(crate) fn compile(
446    cx: &mut Cx<'_>,
447    source: &[u16],
448    kind: CompileKind,
449) -> Result<ObjectId, Throw> {
450    compile_named(cx, source, kind, "<input>")
451}
452
453/// [`compile`] with the filename the code's positions name.
454pub(crate) fn compile_named(
455    cx: &mut Cx<'_>,
456    source: &[u16],
457    kind: CompileKind,
458    filename: &str,
459) -> Result<ObjectId, Throw> {
460    let Some(compile) = cx.host.compile else {
461        return Err(super::object::unsupported(cx, "eval"));
462    };
463    let request = CompileRequest {
464        source,
465        kind,
466        filename,
467    };
468    let result = compile(
469        &mut Compiler::new(cx.heap, cx.static_code, cx.realm),
470        &request,
471    );
472    match result {
473        Ok(function) => Ok(function),
474        Err(failure) => Err(throw_failure(cx, failure)),
475    }
476}
477
478/// Compiles and runs `source` on the global object.
479fn run(cx: &mut Cx<'_>, source: &[u16], kind: CompileKind) -> Result<Value, Throw> {
480    let function = compile(cx, source, kind)?;
481    let mark = cx.heap.roots().mark();
482    cx.heap.roots().push(Value::object(function));
483    let global = Value::object(cx.realm.global());
484    let result = super::call(cx, Value::object(function), global, &[]);
485    cx.heap.roots().truncate(mark);
486    result
487}
488
489/// `%eval%(x)`: a direct eval runs as an indirect one.
490pub(crate) fn eval(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
491    let source = site.arg(cx, 0);
492    let Some(string) = source.as_string() else {
493        return Ok(source);
494    };
495    let units = ops::units(cx.heap, string);
496    run(cx, &units, CompileKind::IndirectEval)
497}
498
499/// Which `Function` constructor runs.
500#[derive(Clone, Copy, Debug, PartialEq, Eq)]
501pub(crate) enum FunctionKind {
502    Normal,
503    Generator,
504    Async,
505    AsyncGenerator,
506}
507
508/// `CreateDynamicFunction`, as QuickJS does it: the parameters and body in
509/// `(function anonymous(…\n) {\n…\n})`, evaluated as indirect eval code.
510pub(crate) fn function_constructor(
511    cx: &mut Cx<'_>,
512    site: CallSite,
513    kind: FunctionKind,
514) -> Result<Value, Throw> {
515    let mut source: Vec<u16> = Vec::new();
516    let text = |source: &mut Vec<u16>, text: &str| source.extend(text.encode_utf16());
517    text(&mut source, "(");
518    if matches!(kind, FunctionKind::Async | FunctionKind::AsyncGenerator) {
519        text(&mut source, "async ");
520    }
521    text(&mut source, "function");
522    if matches!(kind, FunctionKind::Generator | FunctionKind::AsyncGenerator) {
523        text(&mut source, "*");
524    }
525    text(&mut source, " anonymous(");
526    let parameters = site.argc.saturating_sub(1);
527    for index in 0..parameters {
528        if index > 0 {
529            text(&mut source, ",");
530        }
531        let parameter = site.arg(cx, index);
532        let parameter = ops::to_string(cx, parameter)?;
533        source.extend(ops::units(cx.heap, parameter.as_string().unwrap()));
534    }
535    text(&mut source, "\n) {\n");
536    if site.argc > 0 {
537        let body = site.arg(cx, site.argc - 1);
538        let body = ops::to_string(cx, body)?;
539        source.extend(ops::units(cx.heap, body.as_string().unwrap()));
540    }
541    text(&mut source, "\n})");
542    let function = run(cx, &source, CompileKind::IndirectEval)?;
543    let Some(object) = function.as_object() else {
544        return Err(error::type_error(cx, "not an object"));
545    };
546    let default = match kind {
547        FunctionKind::Normal => Intrinsic::FunctionPrototype,
548        FunctionKind::Generator => Intrinsic::GeneratorFunctionPrototype,
549        FunctionKind::Async => Intrinsic::AsyncFunctionPrototype,
550        FunctionKind::AsyncGenerator => Intrinsic::AsyncGeneratorFunctionPrototype,
551    };
552    if site.construct && !site.new_target.is_undefined() {
553        let mark = cx.heap.roots().mark();
554        cx.heap.roots().push(function);
555        let prototype = super::object::prototype_from_constructor(cx, site.new_target, default);
556        cx.heap.roots().truncate(mark);
557        let prototype = prototype?;
558        if !super::object::set_prototype_of(cx, object, Value::object(prototype))? {
559            return Err(error::type_error(cx, "prototype is immutable"));
560        }
561    }
562    Ok(function)
563}
564
565/// The unit flags of a bytecode function (its kind), or `None` for any
566/// other function.
567pub(crate) fn function_kind_flags(cx: &Cx<'_>, function: ObjectId) -> Option<u16> {
568    if cx.heap.object_kind(function) != crate::ObjectKind::Function {
569        return None;
570    }
571    let bytecode = cx.heap.function_bytecode(function)?;
572    let unit = cx.heap.bytecode_unit(bytecode);
573    Some(cx.unit(unit).flags)
574}
575
576/// The source text of a bytecode function, if it was recorded.
577pub(crate) fn function_source(cx: &Cx<'_>, function: ObjectId) -> Option<Vec<u16>> {
578    if cx.heap.object_kind(function) != crate::ObjectKind::Function {
579        return None;
580    }
581    let bytecode = cx.heap.function_bytecode(function)?;
582    let unit = cx.heap.bytecode_unit(bytecode);
583    let unit = cx.unit(unit);
584    Some(wtf8_units(unit.source.as_ref()?))
585}
586
587/// The UTF-16 units of source `bytes` as QuickJS's `JS_NewStringLen` reads
588/// them (`Function.prototype.toString` of a function's debug source): a
589/// surrogate's three-byte form gives the lone unit back, and an invalid
590/// sequence gives one U+FFFD, skipping its continuation bytes, the next
591/// byte and that byte's continuation bytes.
592fn wtf8_units(bytes: &[u8]) -> Vec<u16> {
593    let continuation = |byte: u8| (0x80..0xc0).contains(&byte);
594    let mut units = Vec::with_capacity(bytes.len());
595    let mut index = 0;
596    while index < bytes.len() {
597        let lead = bytes[index];
598        if lead < 0x80 {
599            units.push(u16::from(lead));
600            index += 1;
601            continue;
602        }
603        // QuickJS's `unicode_from_utf8`.
604        let (length, mask, minimum) = match lead {
605            0xc0..=0xdf => (1, 0x1f, 0x80),
606            0xe0..=0xef => (2, 0x0f, 0x800),
607            0xf0..=0xf7 => (3, 0x07, 0x1_0000),
608            0xf8..=0xfb => (4, 0x03, 0x20_0000),
609            0xfc..=0xfd => (5, 0x01, 0x400_0000),
610            _ => (0, 0, 0),
611        };
612        let decoded = (length > 0 && index + length < bytes.len())
613            .then(|| &bytes[index + 1..=index + length])
614            .filter(|tail| tail.iter().all(|&byte| continuation(byte)))
615            .map(|tail| {
616                tail.iter().fold(u32::from(lead & mask), |code, &byte| {
617                    code << 6 | u32::from(byte & 0x3f)
618                })
619            })
620            .filter(|&code| code >= minimum && code <= 0x10_ffff);
621        match decoded {
622            Some(code) if code >= 0x1_0000 => {
623                let code = code - 0x1_0000;
624                units.push(0xd800 | (code >> 10) as u16);
625                units.push(0xdc00 | (code & 0x3ff) as u16);
626                index += length + 1;
627            }
628            Some(code) => {
629                units.push(code as u16);
630                index += length + 1;
631            }
632            None => {
633                units.push(0xfffd);
634                while index < bytes.len() && continuation(bytes[index]) {
635                    index += 1;
636                }
637                if index < bytes.len() {
638                    index += 1;
639                    while index < bytes.len() && continuation(bytes[index]) {
640                        index += 1;
641                    }
642                }
643            }
644        }
645    }
646    units
647}
648
649/// `$262.evalScript(source)`: the source as a separate script. qjs's
650/// `load(path)` (`qjs --std`): the file, run as a script.
651pub(crate) fn load(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
652    load_script(cx, site).map(|_| Value::UNDEFINED)
653}
654
655/// qjs's `__loadScript(path)`: the file, run as a script; its completion
656/// value.
657pub(crate) fn load_script(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
658    let path = site.arg(cx, 0);
659    let path = ops::to_string(cx, path)?;
660    let path = cx.heap.string_text(path.as_string().unwrap());
661    let Some(text) = cx.host.hooks.read_file(&path) else {
662        let message = format!("could not load '{path}'");
663        return Err(error::throw(cx, ErrorKind::Reference, &message));
664    };
665    let units: Vec<u16> = text.encode_utf16().collect();
666    let function = compile_named(cx, &units, CompileKind::Script, &path)?;
667    let mark = cx.heap.roots().mark();
668    cx.heap.roots().push(Value::object(function));
669    let global = Value::object(cx.realm.global());
670    let result = super::call(cx, Value::object(function), global, &[]);
671    cx.heap.roots().truncate(mark);
672    result
673}
674
675/// qjs's `read(path)` (`qjs --std`): the file's text.
676pub(crate) fn read(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
677    let path = site.arg(cx, 0);
678    let path = ops::to_string(cx, path)?;
679    let path = cx.heap.string_text(path.as_string().unwrap());
680    let Some(text) = cx.host.hooks.read_file(&path) else {
681        let message = format!("could not read '{path}'");
682        return Err(error::throw(cx, ErrorKind::Error, &message));
683    };
684    crate::builtins::string_text(cx, &text)
685}
686
687pub(crate) fn eval_script(cx: &mut Cx<'_>, site: CallSite) -> Result<Value, Throw> {
688    let source = site.arg(cx, 0);
689    let source = ops::to_string(cx, source)?;
690    let units = ops::units(cx.heap, source.as_string().unwrap());
691    run(cx, &units, CompileKind::Script)
692}
693
694#[cfg(test)]
695mod tests {
696    use crate::{Runtime, RuntimeOptions};
697
698    #[test]
699    fn a_compilation_holds_each_atom_once_until_it_ends() {
700        let mut runtime = Runtime::new(RuntimeOptions::default()).unwrap();
701        let mut compiler = runtime.transient_compiler();
702        let mark = compiler.heap().held_atoms_mark();
703        let atom = compiler.heap_mut().intern("held once").unwrap();
704        for _ in 0..3 {
705            assert_eq!(compiler.heap_mut().intern("held once").unwrap(), atom);
706        }
707        assert_eq!(
708            compiler.heap_mut().intern_latin1(b"held once").unwrap(),
709            atom
710        );
711        assert_eq!(compiler.heap().held_atoms_mark(), mark + 1);
712        // A reborrow (a host callback's view) ends nothing when dropped.
713        {
714            let mut view = compiler.reborrow();
715            view.heap_mut().intern("seen by the view").unwrap();
716        }
717        compiler.heap_mut().collect();
718        assert_eq!(compiler.heap().string_text(atom), "held once");
719        assert_eq!(compiler.heap().held_atoms_mark(), mark + 2);
720        drop(compiler);
721        assert_eq!(runtime.heap().held_atoms_mark(), mark);
722    }
723}