pub mod lexer;
mod options;
pub use options::{CompileOptions, DEFAULT_EVAL_FILENAME, EvalCompileContext};
mod model;
mod names;
use model::ir::function::FunctionTree;
#[cfg(test)]
use model::ir::function::{FunctionIrOptions, FunctionSourceInfo, SuperCapabilities};
mod parser;
use parser::context::Parser;
#[cfg(test)]
use pseudo_binding::HOME_OBJECT_LOCAL_NAME;
#[cfg(test)]
use pseudo_binding::NEW_TARGET_LOCAL_NAME;
#[cfg(test)]
use model::bindings::BindingKind;
#[cfg(test)]
use model::bindings::BindingStorage;
use model::ir::{IrConstant, IrOp};
#[cfg(test)]
use model::scope::{IrScope, ScopeId, ScopeKind};
#[cfg(test)]
use parser::builder::FunctionBuilder;
mod resolution;
#[cfg(test)]
use resolution::ensure_closure_variable;
#[cfg(test)]
use resolution::ensure_string_constant;
use resolution::resolve_identifiers;
mod flow;
mod lowering;
mod optimize;
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) mod register;
mod relocation;
use crate::engine::api::error::{Error, ErrorKind};
#[cfg(test)]
use crate::engine::code::bytecode::DetachedBytecode;
use crate::engine::code::bytecode::{Instruction, MAX_LOCAL_SLOTS};
use crate::engine::code::debug::DebugInfoMode;
#[cfg(test)]
use crate::source::SourceOffset;
use crate::engine::code::function::UnlinkedFunction;
use crate::engine::code::module::UnlinkedModule;
use crate::engine::compiler::lexer::Span;
use crate::engine::value::JsString;
#[cfg(test)]
use crate::engine::value::PrimitiveValue as Value;
use crate::source::text::SourceText;
#[cfg(test)]
use lowering::lower_detached_script;
use lowering::lower_unlinked_tree;
mod arrow;
mod class;
mod destructuring;
mod function;
mod generator;
mod module;
mod object_literal;
mod optional_chain;
mod private_reference;
mod pseudo_binding;
pub(crate) mod stack_guard;
mod template;
#[cfg(test)]
use pseudo_binding::{ACTIVE_FUNCTION_LOCAL_NAME, THIS_LOCAL_NAME};
#[cfg(test)]
pub fn compile_script(source: &str) -> Result<DetachedBytecode<Value>, Error> {
let mut tree = Parser::parse(source, JsString::from_static(DEFAULT_EVAL_FILENAME))?;
resolve_identifiers(&mut tree)?;
if let Some(error) = tree.pending_unsupported.take() {
return Err(error);
}
if tree.functions.len() != 1 {
return Err(Error::unsupported(
"nested function bytecode requires runtime publication; use Context::compile or Context::eval",
tree.functions[1].source.span,
));
}
lower_detached_script(tree)
}
#[cfg_attr(not(test), allow(dead_code))]
pub fn compile_unlinked_script(source: &str) -> Result<UnlinkedFunction, Error> {
compile_unlinked_script_with_filename(source, DEFAULT_EVAL_FILENAME, DebugInfoMode::Full)
}
pub fn compile_unlinked_script_with_filename(
source: &str,
filename: &str,
debug_info: DebugInfoMode,
) -> Result<UnlinkedFunction, Error> {
let mut tree = Parser::parse(source, JsString::try_from_utf8(filename)?)?;
resolve_identifiers(&mut tree)?;
if let Some(error) = tree.pending_unsupported.take() {
return Err(error);
}
lower_unlinked_tree(tree, debug_info)
}
pub fn validate_source_length(source_len: usize) -> Result<(), Error> {
i32::try_from(source_len).map(|_| ()).map_err(|_| {
Error::new(
ErrorKind::JsInternal,
"source is too large for QuickJS debug metadata",
)
})
}
pub fn compile_unlinked_script_bytes_with_filename(
source: &[u8],
filename: &str,
debug_info: DebugInfoMode,
) -> Result<UnlinkedFunction, Error> {
validate_source_length(source.len())?;
let source = SourceText::try_from_raw_bytes(source)?;
compile_unlinked_script_source_with_filename(&source, filename, debug_info)
}
pub fn compile_unlinked_script_source_with_filename(
source: &SourceText,
filename: &str,
debug_info: DebugInfoMode,
) -> Result<UnlinkedFunction, Error> {
let mut tree = Parser::parse_script_source(source, JsString::try_from_utf8(filename)?)?;
resolve_identifiers(&mut tree)?;
if let Some(error) = tree.pending_unsupported.take() {
return Err(error);
}
lower_unlinked_tree(tree, debug_info)
}
#[cfg_attr(not(test), allow(dead_code))]
pub fn compile_unlinked_module_with_filename(
source: &str,
filename: &str,
debug_info: DebugInfoMode,
) -> Result<UnlinkedModule, Error> {
compile_unlinked_module_with_name(source, JsString::try_from_utf8(filename)?, debug_info)
}
pub fn compile_unlinked_module_with_name(
source: &str,
name: JsString,
debug_info: DebugInfoMode,
) -> Result<UnlinkedModule, Error> {
let tree = Parser::parse_module(source, name)?;
finish_unlinked_module_tree(tree, debug_info)
}
pub fn compile_unlinked_module_bytes_with_name(
source: &[u8],
name: JsString,
debug_info: DebugInfoMode,
) -> Result<UnlinkedModule, Error> {
validate_source_length(source.len())?;
let source = SourceText::try_from_raw_bytes(source)?;
let tree = Parser::parse_module_source(&source, name)?;
finish_unlinked_module_tree(tree, debug_info)
}
fn finish_unlinked_module_tree(
mut tree: FunctionTree,
debug_info: DebugInfoMode,
) -> Result<UnlinkedModule, Error> {
resolve_identifiers(&mut tree)?;
if let Some(error) = tree.pending_unsupported.take() {
return Err(error);
}
let module = tree
.module
.take()
.ok_or_else(|| Error::internal("module compiler produced no module record"))?;
let has_top_level_await = tree.functions.first().is_some_and(|function| {
function
.ops
.iter()
.any(|operation| matches!(operation.op, IrOp::Bytecode(Instruction::Await)))
});
let function = lower_unlinked_tree(tree, debug_info)?;
module::finish_module(function, has_top_level_await, module)
}
#[cfg_attr(not(test), allow(dead_code))]
pub fn compile_unlinked_eval_with_filename(
source: &str,
filename: &str,
debug_info: DebugInfoMode,
context: EvalCompileContext,
) -> Result<UnlinkedFunction, Error> {
let mut tree = Parser::parse_eval(source, JsString::try_from_utf8(filename)?, context)?;
resolve_identifiers(&mut tree)?;
if let Some(error) = tree.pending_unsupported.take() {
return Err(error);
}
lower_unlinked_tree(tree, debug_info)
}
pub fn compile_unlinked_eval_source_with_filename(
source: &SourceText,
filename: &str,
debug_info: DebugInfoMode,
context: EvalCompileContext,
) -> Result<UnlinkedFunction, Error> {
let mut tree = Parser::parse_eval_source(source, JsString::try_from_utf8(filename)?, context)?;
resolve_identifiers(&mut tree)?;
if let Some(error) = tree.pending_unsupported.take() {
return Err(error);
}
lower_unlinked_tree(tree, debug_info)
}
const MAX_LOCAL_VARIABLES: usize = MAX_LOCAL_SLOTS as usize;
const MAX_BYTECODE_STACK: usize = 65_534;
const MAX_CALL_ARGUMENTS: usize = 65_535;
const EVAL_RET_LOCAL_NAME: &str = "<ret>";
const EVAL_VARIABLE_OBJECT_LOCAL_NAME: &str = "<var>";
const ARG_EVAL_VARIABLE_OBJECT_LOCAL_NAME: &str = "<arg_var>";
const WITH_OBJECT_LOCAL_NAME: &str = "<with>";
const FINALLY_EVAL_RET_LOCAL_NAME: &str = "<finally-ret>";
#[cfg(test)]
mod tests;