pub mod metadata;
use crate::engine::code::bytecode::Instruction;
use crate::engine::code::debug::Pc2LineTable;
use crate::engine::value::{JsString, PrimitiveValue};
use oxide_regexp::CompiledRegExp;
use metadata::*;
use std::{error::Error, fmt, rc::Rc};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UnlinkedConstantError {
InvalidTemplateObject,
}
impl fmt::Display for UnlinkedConstantError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidTemplateObject => formatter.write_str(
"a template object requires equal non-empty cooked and raw segment lists",
),
}
}
}
impl Error for UnlinkedConstantError {}
#[derive(Debug)]
enum UnlinkedConstantKind {
Primitive(PrimitiveValue),
AtomString(PrimitiveValue),
RegExp {
pattern: JsString,
program: Rc<CompiledRegExp>,
},
TemplateObject {
cooked: Box<[Option<JsString>]>,
raw: Box<[JsString]>,
},
Child(Box<UnlinkedFunction>),
}
#[derive(Debug)]
pub struct UnlinkedConstant(UnlinkedConstantKind);
impl UnlinkedConstant {
pub fn primitive<T: TryInto<PrimitiveValue>>(value: T) -> Result<Self, UnlinkedConstantError>
where
UnlinkedConstantError: From<T::Error>,
{
let primitive = value.try_into().map_err(UnlinkedConstantError::from)?;
Ok(Self(UnlinkedConstantKind::Primitive(primitive)))
}
#[must_use]
pub fn atom_string(value: crate::engine::value::JsString) -> Self {
Self(UnlinkedConstantKind::AtomString(PrimitiveValue::String(
value,
)))
}
#[must_use]
pub fn regexp(pattern: JsString, program: Rc<CompiledRegExp>) -> Self {
Self(UnlinkedConstantKind::RegExp { pattern, program })
}
pub fn template_object(
cooked: Vec<Option<JsString>>,
raw: Vec<JsString>,
) -> Result<Self, UnlinkedConstantError> {
if cooked.is_empty() || cooked.len() != raw.len() {
return Err(UnlinkedConstantError::InvalidTemplateObject);
}
Ok(Self(UnlinkedConstantKind::TemplateObject {
cooked: cooked.into_boxed_slice(),
raw: raw.into_boxed_slice(),
}))
}
#[must_use]
pub fn child(function: UnlinkedFunction) -> Self {
Self(UnlinkedConstantKind::Child(Box::new(function)))
}
#[must_use]
#[cfg_attr(not(test), allow(dead_code))]
pub fn as_primitive(&self) -> Option<&PrimitiveValue> {
match &self.0 {
UnlinkedConstantKind::Primitive(value) | UnlinkedConstantKind::AtomString(value) => {
Some(value)
}
UnlinkedConstantKind::RegExp { .. }
| UnlinkedConstantKind::TemplateObject { .. }
| UnlinkedConstantKind::Child(_) => None,
}
}
#[must_use]
#[cfg_attr(not(test), allow(dead_code))]
pub fn as_regexp(&self) -> Option<(&JsString, &Rc<CompiledRegExp>)> {
match &self.0 {
UnlinkedConstantKind::RegExp { pattern, program } => Some((pattern, program)),
UnlinkedConstantKind::Primitive(_)
| UnlinkedConstantKind::AtomString(_)
| UnlinkedConstantKind::TemplateObject { .. }
| UnlinkedConstantKind::Child(_) => None,
}
}
#[must_use]
pub fn as_template_object(&self) -> Option<(&[Option<JsString>], &[JsString])> {
match &self.0 {
UnlinkedConstantKind::TemplateObject { cooked, raw } => Some((cooked, raw)),
_ => None,
}
}
#[must_use]
#[cfg_attr(not(test), allow(dead_code))]
pub fn as_child(&self) -> Option<&UnlinkedFunction> {
match &self.0 {
UnlinkedConstantKind::Primitive(_)
| UnlinkedConstantKind::AtomString(_)
| UnlinkedConstantKind::RegExp { .. }
| UnlinkedConstantKind::TemplateObject { .. } => None,
UnlinkedConstantKind::Child(function) => Some(function),
}
}
}
impl TryFrom<PrimitiveValue> for UnlinkedConstant {
type Error = UnlinkedConstantError;
fn try_from(value: PrimitiveValue) -> Result<Self, Self::Error> {
Self::primitive(value)
}
}
#[derive(Debug)]
pub struct UnlinkedFunction {
code: Vec<Instruction>,
constants: Vec<UnlinkedConstant>,
metadata: FunctionMetadata,
parameter_environment: Option<ParameterEnvironmentLayout>,
func_name: Option<crate::engine::value::JsString>,
argument_definitions: Vec<UnlinkedVariableDefinition>,
local_definitions: Vec<UnlinkedVariableDefinition>,
closure_variables: Vec<ClosureVariable>,
eval_environments: Vec<EvalEnvironment<JsString>>,
debug: Option<UnlinkedFunctionDebug>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UnlinkedVariableDefinition {
pub name: Option<crate::engine::value::JsString>,
pub is_lexical: bool,
pub is_const: bool,
pub is_parameter_initializer: bool,
pub kind: ClosureVariableKind,
}
impl UnlinkedVariableDefinition {
#[must_use]
pub const fn ordinary(name: Option<crate::engine::value::JsString>) -> Self {
Self {
name,
is_lexical: false,
is_const: false,
is_parameter_initializer: false,
kind: ClosureVariableKind::Normal,
}
}
#[must_use]
pub const fn lexical(name: Option<crate::engine::value::JsString>, is_const: bool) -> Self {
Self {
name,
is_lexical: true,
is_const,
is_parameter_initializer: false,
kind: ClosureVariableKind::Normal,
}
}
#[must_use]
pub const fn with_parameter_initializer(mut self, value: bool) -> Self {
self.is_parameter_initializer = value;
self
}
fn function_name(name: Option<crate::engine::value::JsString>, is_const: bool) -> Self {
Self {
name,
is_lexical: false,
is_const,
is_parameter_initializer: false,
kind: ClosureVariableKind::FunctionName,
}
}
fn arg_eval_variable_object() -> Self {
Self {
name: Some(crate::engine::value::JsString::from_static("<arg_var>")),
is_lexical: false,
is_const: false,
is_parameter_initializer: false,
kind: ClosureVariableKind::ArgEvalVariableObject,
}
}
pub fn with_object() -> Self {
Self {
name: Some(crate::engine::value::JsString::from_static("<with>")),
is_lexical: false,
is_const: false,
is_parameter_initializer: false,
kind: ClosureVariableKind::WithObject,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UnlinkedFunctionDebug {
pub filename: crate::engine::value::JsString,
pub pc2line: Option<Pc2LineTable>,
pub source: Option<Box<[u8]>>,
}
impl UnlinkedFunction {
#[must_use]
pub fn new(
code: Vec<Instruction>,
constants: Vec<UnlinkedConstant>,
metadata: FunctionMetadata,
argument_definitions: Vec<UnlinkedVariableDefinition>,
local_definitions: Vec<UnlinkedVariableDefinition>,
closure_variables: Vec<ClosureVariable>,
) -> Self {
Self {
code,
constants,
metadata,
parameter_environment: None,
func_name: None,
argument_definitions,
local_definitions,
closure_variables,
eval_environments: Vec::new(),
debug: None,
}
}
#[must_use]
pub fn with_eval_environments(
mut self,
eval_environments: Vec<EvalEnvironment<JsString>>,
) -> Self {
self.eval_environments = eval_environments;
self
}
#[must_use]
pub fn with_parameter_environment(
mut self,
layout: Option<ParameterEnvironmentLayout>,
) -> Self {
self.parameter_environment = layout;
if let Some(index) = self
.parameter_environment
.as_ref()
.and_then(|layout| layout.arg_eval_variable_object_local)
&& let Some(definition) = self.local_definitions.get_mut(usize::from(index))
{
*definition = UnlinkedVariableDefinition::arg_eval_variable_object();
}
self
}
#[must_use]
pub fn with_name(mut self, name: Option<crate::engine::value::JsString>) -> Self {
self.func_name = name.clone();
if let Some(index) = self.metadata.function_name_local
&& let Some(definition) = self.local_definitions.get_mut(usize::from(index))
{
*definition = UnlinkedVariableDefinition::function_name(name, self.metadata.strict);
}
self
}
#[must_use]
pub fn with_debug(mut self, debug: UnlinkedFunctionDebug) -> Self {
self.debug = Some(debug);
self
}
#[must_use]
#[cfg_attr(not(test), allow(dead_code))]
pub fn code(&self) -> &[Instruction] {
&self.code
}
#[must_use]
#[cfg_attr(not(test), allow(dead_code))]
pub fn constants(&self) -> &[UnlinkedConstant] {
&self.constants
}
#[must_use]
#[cfg_attr(not(test), allow(dead_code))]
pub const fn metadata(&self) -> &FunctionMetadata {
&self.metadata
}
#[must_use]
#[cfg_attr(not(test), allow(dead_code))]
pub const fn parameter_environment(&self) -> Option<&ParameterEnvironmentLayout> {
self.parameter_environment.as_ref()
}
#[must_use]
#[cfg_attr(not(test), allow(dead_code))]
pub fn func_name(&self) -> Option<&crate::engine::value::JsString> {
self.func_name.as_ref()
}
#[must_use]
#[cfg_attr(not(test), allow(dead_code))]
pub fn closure_variables(&self) -> &[ClosureVariable] {
&self.closure_variables
}
#[must_use]
#[cfg_attr(not(test), allow(dead_code))]
pub fn argument_definitions(&self) -> &[UnlinkedVariableDefinition] {
&self.argument_definitions
}
#[must_use]
#[cfg_attr(not(test), allow(dead_code))]
pub fn local_definitions(&self) -> &[UnlinkedVariableDefinition] {
&self.local_definitions
}
#[must_use]
#[cfg_attr(not(test), allow(dead_code))]
pub fn eval_environments(&self) -> &[EvalEnvironment<JsString>] {
&self.eval_environments
}
pub(crate) fn take_sources(&mut self, sources: &mut Vec<Option<Box<[u8]>>>) {
for constant in &mut self.constants {
if let UnlinkedConstantKind::Child(child) = &mut constant.0 {
child.take_sources(sources);
}
}
sources.push(self.debug.as_mut().and_then(|debug| debug.source.take()));
}
#[cfg_attr(not(test), allow(dead_code))]
#[must_use]
pub const fn debug(&self) -> Option<&UnlinkedFunctionDebug> {
self.debug.as_ref()
}
}
impl From<std::convert::Infallible> for UnlinkedConstantError {
fn from(value: std::convert::Infallible) -> Self {
match value {}
}
}