Skip to main content

quickjs_oxide/engine/compiler/
options.rs

1//! Source compilation request options.
2
3use crate::engine::code::function::metadata::{
4    ClosureVariableKind, EvalCallerProfile, EvalCallerVariableTarget, EvalKind, EvalRootBinding,
5    EvalScopeKind,
6};
7use crate::engine::value::JsString;
8
9/// Default filename used by the Rust convenience compile/eval APIs.
10pub const DEFAULT_EVAL_FILENAME: &str = "<input>";
11
12/// Named source compilation options.
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct CompileOptions {
15    pub filename: String,
16    /// The code is freed once nothing names it (a REPL's line), instead of
17    /// living as long as the runtime.
18    pub transient: bool,
19}
20
21impl CompileOptions {
22    #[must_use]
23    pub fn new(filename: impl Into<String>) -> Self {
24        Self {
25            filename: filename.into(),
26            transient: false,
27        }
28    }
29
30    /// These options with [`CompileOptions::transient`] set.
31    #[must_use]
32    pub fn transient(mut self) -> Self {
33        self.transient = true;
34        self
35    }
36}
37
38impl Default for CompileOptions {
39    fn default() -> Self {
40        Self::new(DEFAULT_EVAL_FILENAME)
41    }
42}
43
44/// Internal compilation context for one synthetic eval root.
45///
46/// A direct context compiles against the caller bindings it lists; the front
47/// end's tests use it, and the register lowering declines direct eval code (a
48/// direct `eval` runs as indirect eval). Indirect eval has no external bindings and resolves against the defining
49/// realm's global environment. `caller_strict` is ignored for indirect eval,
50/// matching QuickJS's `JS_EVAL_TYPE_INDIRECT` path.
51#[derive(Clone, Debug, PartialEq, Eq)]
52pub struct EvalCompileContext {
53    pub kind: EvalKind,
54    pub caller_strict: bool,
55    pub bindings: Box<[EvalRootBinding<JsString>]>,
56    pub caller_profile: EvalCallerProfile,
57    pub super_call_allowed: bool,
58    pub super_allowed: bool,
59    pub arguments_forbidden: bool,
60}
61
62impl EvalCompileContext {
63    #[cfg_attr(not(test), allow(dead_code))]
64    pub fn direct(caller_strict: bool, bindings: Vec<EvalRootBinding<JsString>>) -> Self {
65        let scope_count = bindings
66            .iter()
67            .map(|binding| usize::from(binding.scope) + 1)
68            .max()
69            .unwrap_or(0);
70        let mut scope_kinds = vec![EvalScopeKind::FunctionRoot; scope_count];
71        for binding in &bindings {
72            if binding.kind == ClosureVariableKind::WithObject {
73                scope_kinds[usize::from(binding.scope)] = EvalScopeKind::With;
74            } else if binding.is_catch_parameter {
75                scope_kinds[usize::from(binding.scope)] = EvalScopeKind::Catch;
76            }
77        }
78        for (scope, scope_kind) in scope_kinds.iter_mut().enumerate() {
79            let has_parameter_object = bindings.iter().any(|binding| {
80                usize::from(binding.scope) == scope
81                    && binding.kind == ClosureVariableKind::ArgEvalVariableObject
82            });
83            let has_body_object = bindings.iter().any(|binding| {
84                usize::from(binding.scope) == scope
85                    && binding.kind == ClosureVariableKind::EvalVariableObject
86            });
87            if has_parameter_object && !has_body_object {
88                *scope_kind = EvalScopeKind::Parameter;
89            }
90        }
91        let variable_target = if caller_strict {
92            EvalCallerVariableTarget::StrictLocal
93        } else {
94            bindings
95                .iter()
96                .position(|binding| {
97                    matches!(
98                        binding.kind,
99                        ClosureVariableKind::EvalVariableObject
100                            | ClosureVariableKind::ArgEvalVariableObject
101                    )
102                })
103                .and_then(|index| u16::try_from(index).ok())
104                .map_or(
105                    EvalCallerVariableTarget::Global,
106                    EvalCallerVariableTarget::ExternalBinding,
107                )
108        };
109        Self::direct_with_profile(
110            caller_strict,
111            bindings,
112            EvalCallerProfile {
113                scope_kinds: scope_kinds.into_boxed_slice(),
114                variable_target,
115            },
116            false,
117            false,
118        )
119    }
120
121    pub fn direct_with_profile(
122        caller_strict: bool,
123        bindings: Vec<EvalRootBinding<JsString>>,
124        caller_profile: EvalCallerProfile,
125        super_call_allowed: bool,
126        super_allowed: bool,
127    ) -> Self {
128        Self {
129            kind: EvalKind::Direct,
130            caller_strict,
131            bindings: bindings.into_boxed_slice(),
132            caller_profile,
133            super_call_allowed,
134            super_allowed,
135            arguments_forbidden: false,
136        }
137    }
138
139    pub fn indirect() -> Self {
140        Self {
141            kind: EvalKind::Indirect,
142            caller_strict: false,
143            bindings: Box::new([]),
144            caller_profile: EvalCallerProfile {
145                scope_kinds: Box::new([]),
146                variable_target: EvalCallerVariableTarget::Global,
147            },
148            super_call_allowed: false,
149            super_allowed: false,
150            arguments_forbidden: false,
151        }
152    }
153}