feat: add dependency

This commit is contained in:
2023-01-20 22:36:19 +08:00
parent 68e8d103b4
commit cf8e579f27
644 changed files with 150099 additions and 14 deletions

View File

@@ -0,0 +1,25 @@
[package]
name = "boa_ast"
description = "Abstract Syntax Tree definition for the Boa JavaScript engine."
keywords = ["javascript", "js", "syntax", "ast"]
categories = ["parser-implementations", "compilers"]
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
[features]
serde = ["boa_interner/serde", "dep:serde"]
fuzz = ["arbitrary", "boa_interner/fuzz", "num-bigint/arbitrary"]
[dependencies]
boa_interner.workspace = true
boa_macros.workspace = true
rustc-hash = "1.1.0"
serde = { version = "1.0.152", features = ["derive"], optional = true }
bitflags = "1.3.2"
num-bigint = "0.4.3"
arbitrary = { version = "1", optional = true, features = ["derive"] }

View File

@@ -0,0 +1,97 @@
//! The [`Declaration`] Parse Node, as defined by the [spec].
//!
//! ECMAScript declarations include:
//! - [Lexical][lex] declarations (`let`, `const`).
//! - [Function][fun] declarations (`function`, `async function`).
//! - [Class][class] declarations.
//!
//! See [*Difference between statements and declarations*][diff] for an explanation on why `Declaration`s
//! and `Statement`s are distinct nodes.
//!
//! [spec]: https://tc39.es/ecma262/#prod-Declaration
//! [lex]: https://tc39.es/ecma262/#prod-LexicalDeclaration
//! [fun]: https://tc39.es/ecma262/#prod-HoistableDeclaration
//! [class]: https://tc39.es/ecma262/#prod-ClassDeclaration
//! [diff]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements#difference_between_statements_and_declarations
use super::function::{AsyncFunction, AsyncGenerator, Class, Function, Generator};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
mod variable;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
pub use variable::*;
/// The `Declaration` Parse Node.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum Declaration {
/// See [`Function`]
Function(Function),
/// See [`Generator`]
Generator(Generator),
/// See [`AsyncFunction`]
AsyncFunction(AsyncFunction),
/// See [`AsyncGenerator`]
AsyncGenerator(AsyncGenerator),
/// See [`Class`]
Class(Class),
/// See [`LexicalDeclaration`]
Lexical(LexicalDeclaration),
}
impl ToIndentedString for Declaration {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
match self {
Self::Function(f) => f.to_indented_string(interner, indentation),
Self::Generator(g) => g.to_indented_string(interner, indentation),
Self::AsyncFunction(af) => af.to_indented_string(interner, indentation),
Self::AsyncGenerator(ag) => ag.to_indented_string(interner, indentation),
Self::Class(c) => c.to_indented_string(interner, indentation),
Self::Lexical(l) => {
let mut s = l.to_interned_string(interner);
s.push(';');
s
}
}
}
}
impl VisitWith for Declaration {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Function(f) => visitor.visit_function(f),
Self::Generator(g) => visitor.visit_generator(g),
Self::AsyncFunction(af) => visitor.visit_async_function(af),
Self::AsyncGenerator(ag) => visitor.visit_async_generator(ag),
Self::Class(c) => visitor.visit_class(c),
Self::Lexical(ld) => visitor.visit_lexical_declaration(ld),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Function(f) => visitor.visit_function_mut(f),
Self::Generator(g) => visitor.visit_generator_mut(g),
Self::AsyncFunction(af) => visitor.visit_async_function_mut(af),
Self::AsyncGenerator(ag) => visitor.visit_async_generator_mut(ag),
Self::Class(c) => visitor.visit_class_mut(c),
Self::Lexical(ld) => visitor.visit_lexical_declaration_mut(ld),
}
}
}

View File

@@ -0,0 +1,385 @@
//! Variable related declarations.
use core::ops::ControlFlow;
use std::convert::TryFrom;
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
expression::{Expression, Identifier},
join_nodes,
pattern::Pattern,
Statement,
};
use boa_interner::{Interner, ToInternedString};
use super::Declaration;
/// A [`var`][var] statement, also called [`VariableStatement`][varstmt] in the spec.
///
/// The scope of a variable declared with `var` is its current execution context, which is either
/// the enclosing function or, for variables declared outside any function, global. If you
/// re-declare a ECMAScript variable, it will not lose its value.
///
/// Although a bit confusing, `VarDeclaration`s are not considered [`Declaration`]s by the spec.
/// This is partly because it has very different semantics from `let` and `const` declarations, but
/// also because a `var` statement can be labelled just like any other [`Statement`]:
///
/// ```javascript
/// label: var a = 5;
/// a;
/// ```
///
/// returns `5` as the value of the statement list, while:
///
/// ```javascript
/// label: let a = 5;
/// a;
/// ```
/// throws a `SyntaxError`.
///
/// `var` declarations, wherever they occur, are processed before any code is executed. This is
/// called <code>[hoisting]</code>.
///
/// [var]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var
/// [varstmt]: https://tc39.es/ecma262/#prod-VariableStatement
/// [hoisting]: https://developer.mozilla.org/en-US/docs/Glossary/Hoisting
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct VarDeclaration(pub VariableList);
impl From<VarDeclaration> for Statement {
fn from(var: VarDeclaration) -> Self {
Self::Var(var)
}
}
impl ToInternedString for VarDeclaration {
fn to_interned_string(&self, interner: &Interner) -> String {
format!("var {}", self.0.to_interned_string(interner))
}
}
impl VisitWith for VarDeclaration {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_variable_list(&self.0)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_variable_list_mut(&mut self.0)
}
}
/// A **[lexical declaration]** defines variables that are scoped to the lexical environment of
/// the variable declaration.
///
/// [lexical declaration]: https://tc39.es/ecma262/#sec-let-and-const-declarations
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum LexicalDeclaration {
/// A <code>[const]</code> variable creates a constant whose scope can be either global or local
/// to the block in which it is declared.
///
/// An initializer for a constant is required. You must specify its value in the same statement
/// in which it's declared. (This makes sense, given that it can't be changed later)
///
/// [const]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const
Const(VariableList),
/// A <code>[let]</code> variable is limited to a scope of a block statement, or expression on
/// which it is used, unlike the `var` keyword, which defines a variable globally, or locally to
/// an entire function regardless of block scope.
///
/// Just like const, `let` does not create properties of the window object when declared
/// globally (in the top-most scope).
///
/// If a let declaration does not have an initializer, the variable is assigned the value `undefined`.
///
/// [let]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
Let(VariableList),
}
impl LexicalDeclaration {
/// Gets the inner variable list of the `LexicalDeclaration`
#[must_use]
pub const fn variable_list(&self) -> &VariableList {
match self {
Self::Const(list) | Self::Let(list) => list,
}
}
}
impl From<LexicalDeclaration> for Declaration {
fn from(lex: LexicalDeclaration) -> Self {
Self::Lexical(lex)
}
}
impl ToInternedString for LexicalDeclaration {
fn to_interned_string(&self, interner: &Interner) -> String {
format!(
"{} {}",
match &self {
Self::Let(_) => "let",
Self::Const(_) => "const",
},
self.variable_list().to_interned_string(interner)
)
}
}
impl VisitWith for LexicalDeclaration {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Const(vars) | Self::Let(vars) => visitor.visit_variable_list(vars),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Const(vars) | Self::Let(vars) => visitor.visit_variable_list_mut(vars),
}
}
}
/// List of variables in a variable declaration.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct VariableList {
list: Box<[Variable]>,
}
impl VariableList {
/// Creates a variable list if the provided list of [`Variable`] is not empty.
#[must_use]
pub fn new(list: Box<[Variable]>) -> Option<Self> {
if list.is_empty() {
return None;
}
Some(Self { list })
}
}
impl AsRef<[Variable]> for VariableList {
fn as_ref(&self) -> &[Variable] {
&self.list
}
}
impl ToInternedString for VariableList {
fn to_interned_string(&self, interner: &Interner) -> String {
join_nodes(interner, self.list.as_ref())
}
}
impl VisitWith for VariableList {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for variable in self.list.iter() {
try_break!(visitor.visit_variable(variable));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for variable in self.list.iter_mut() {
try_break!(visitor.visit_variable_mut(variable));
}
ControlFlow::Continue(())
}
}
/// The error returned by the [`VariableList::try_from`] function.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct TryFromVariableListError(());
impl std::fmt::Display for TryFromVariableListError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
"provided list of variables cannot be empty".fmt(f)
}
}
impl TryFrom<Box<[Variable]>> for VariableList {
type Error = TryFromVariableListError;
fn try_from(value: Box<[Variable]>) -> Result<Self, Self::Error> {
Self::new(value).ok_or(TryFromVariableListError(()))
}
}
impl TryFrom<Vec<Variable>> for VariableList {
type Error = TryFromVariableListError;
fn try_from(value: Vec<Variable>) -> Result<Self, Self::Error> {
Self::try_from(value.into_boxed_slice())
}
}
/// Variable represents a variable declaration of some kind.
///
/// For `let` and `const` declarations this type represents a [`LexicalBinding`][spec1]
///
/// For `var` declarations this type represents a [`VariableDeclaration`][spec2]
///
/// More information:
/// - [ECMAScript reference: 14.3 Declarations and the Variable Statement][spec3]
///
/// [spec1]: https://tc39.es/ecma262/#prod-LexicalBinding
/// [spec2]: https://tc39.es/ecma262/#prod-VariableDeclaration
/// [spec3]: https://tc39.es/ecma262/#sec-declarations-and-the-variable-statement
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Variable {
binding: Binding,
init: Option<Expression>,
}
impl ToInternedString for Variable {
fn to_interned_string(&self, interner: &Interner) -> String {
let mut buf = self.binding.to_interned_string(interner);
if let Some(ref init) = self.init {
buf.push_str(&format!(" = {}", init.to_interned_string(interner)));
}
buf
}
}
impl Variable {
/// Creates a new variable declaration from a `BindingIdentifier`.
#[inline]
#[must_use]
pub const fn from_identifier(ident: Identifier, init: Option<Expression>) -> Self {
Self {
binding: Binding::Identifier(ident),
init,
}
}
/// Creates a new variable declaration from a `Pattern`.
#[inline]
#[must_use]
pub const fn from_pattern(pattern: Pattern, init: Option<Expression>) -> Self {
Self {
binding: Binding::Pattern(pattern),
init,
}
}
/// Gets the variable declaration binding.
#[must_use]
pub const fn binding(&self) -> &Binding {
&self.binding
}
/// Gets the initialization expression for the variable declaration, if any.
#[inline]
#[must_use]
pub const fn init(&self) -> Option<&Expression> {
self.init.as_ref()
}
}
impl VisitWith for Variable {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_binding(&self.binding));
if let Some(init) = &self.init {
try_break!(visitor.visit_expression(init));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_binding_mut(&mut self.binding));
if let Some(init) = &mut self.init {
try_break!(visitor.visit_expression_mut(init));
}
ControlFlow::Continue(())
}
}
/// Binding represents either an individual binding or a binding pattern.
///
/// More information:
/// - [ECMAScript reference: 14.3 Declarations and the Variable Statement][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-declarations-and-the-variable-statement
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum Binding {
/// A single identifier binding.
Identifier(Identifier),
/// A pattern binding.
Pattern(Pattern),
}
impl From<Identifier> for Binding {
fn from(id: Identifier) -> Self {
Self::Identifier(id)
}
}
impl From<Pattern> for Binding {
fn from(pat: Pattern) -> Self {
Self::Pattern(pat)
}
}
impl ToInternedString for Binding {
fn to_interned_string(&self, interner: &Interner) -> String {
match self {
Self::Identifier(id) => id.to_interned_string(interner),
Self::Pattern(ref pattern) => pattern.to_interned_string(interner),
}
}
}
impl VisitWith for Binding {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Identifier(id) => visitor.visit_identifier(id),
Self::Pattern(pattern) => visitor.visit_pattern(pattern),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Identifier(id) => visitor.visit_identifier_mut(id),
Self::Pattern(pattern) => visitor.visit_pattern_mut(pattern),
}
}
}

View File

@@ -0,0 +1,348 @@
//! Property access expressions, as defined by the [spec].
//!
//! [Property access expressions][access] provide two ways to access properties of an object: *dot notation*
//! and *bracket notation*.
//! - *Dot notation* is mostly used when the name of the property is static, and a valid Javascript
//! identifier e.g. `obj.prop`, `arr.$val`.
//! - *Bracket notation* is used when the name of the property is either variable, not a valid
//! identifier or a symbol e.g. `arr[var]`, `arr[5]`, `arr[Symbol.iterator]`.
//!
//! A property access expression can be represented by a [`SimplePropertyAccess`] (`x.y`), a
//! [`PrivatePropertyAccess`] (`x.#y`) or a [`SuperPropertyAccess`] (`super["y"]`), each of them with
//! slightly different semantics overall.
//!
//! [spec]: https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-property-accessors
//! [access]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_Accessors
use crate::expression::Expression;
use crate::function::PrivateName;
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
/// A property access field.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum PropertyAccessField {
/// A constant property field, such as `x.prop`.
Const(Sym),
/// An expression property field, such as `x["val"]`.
Expr(Box<Expression>),
}
impl From<Sym> for PropertyAccessField {
#[inline]
fn from(id: Sym) -> Self {
Self::Const(id)
}
}
impl From<Expression> for PropertyAccessField {
#[inline]
fn from(expr: Expression) -> Self {
Self::Expr(Box::new(expr))
}
}
impl VisitWith for PropertyAccessField {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Const(sym) => visitor.visit_sym(sym),
Self::Expr(expr) => visitor.visit_expression(expr),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Const(sym) => visitor.visit_sym_mut(sym),
Self::Expr(expr) => visitor.visit_expression_mut(&mut *expr),
}
}
}
/// A property access expression.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum PropertyAccess {
/// A simple property access (`x.prop`).
Simple(SimplePropertyAccess),
/// A property access of a private property (`x.#priv`).
Private(PrivatePropertyAccess),
/// A property access of a `super` reference. (`super["prop"]`).
Super(SuperPropertyAccess),
}
impl ToInternedString for PropertyAccess {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
match self {
Self::Simple(s) => s.to_interned_string(interner),
Self::Private(p) => p.to_interned_string(interner),
Self::Super(s) => s.to_interned_string(interner),
}
}
}
impl From<PropertyAccess> for Expression {
#[inline]
fn from(access: PropertyAccess) -> Self {
Self::PropertyAccess(access)
}
}
impl VisitWith for PropertyAccess {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Simple(spa) => visitor.visit_simple_property_access(spa),
Self::Private(ppa) => visitor.visit_private_property_access(ppa),
Self::Super(supa) => visitor.visit_super_property_access(supa),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Simple(spa) => visitor.visit_simple_property_access_mut(spa),
Self::Private(ppa) => visitor.visit_private_property_access_mut(ppa),
Self::Super(supa) => visitor.visit_super_property_access_mut(supa),
}
}
}
/// A simple property access, where the target object is an [`Expression`].
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct SimplePropertyAccess {
target: Box<Expression>,
field: PropertyAccessField,
}
impl SimplePropertyAccess {
/// Gets the target object of the property access.
#[inline]
#[must_use]
pub const fn target(&self) -> &Expression {
&self.target
}
/// Gets the accessed field of the target object.
#[inline]
#[must_use]
pub const fn field(&self) -> &PropertyAccessField {
&self.field
}
/// Creates a `PropertyAccess` AST Expression.
pub fn new<F>(target: Expression, field: F) -> Self
where
F: Into<PropertyAccessField>,
{
Self {
target: target.into(),
field: field.into(),
}
}
}
impl ToInternedString for SimplePropertyAccess {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
let target = self.target.to_interned_string(interner);
match self.field {
PropertyAccessField::Const(sym) => format!("{target}.{}", interner.resolve_expect(sym)),
PropertyAccessField::Expr(ref expr) => {
format!("{target}[{}]", expr.to_interned_string(interner))
}
}
}
}
impl From<SimplePropertyAccess> for PropertyAccess {
#[inline]
fn from(access: SimplePropertyAccess) -> Self {
Self::Simple(access)
}
}
impl VisitWith for SimplePropertyAccess {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.target));
visitor.visit_property_access_field(&self.field)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.target));
visitor.visit_property_access_field_mut(&mut self.field)
}
}
/// An access expression to a class object's [private fields][mdn].
///
/// Private property accesses differ slightly from plain property accesses, since the accessed
/// property must be prefixed by `#`, and the bracket notation is not allowed. For example,
/// `this.#a` is a valid private property access.
///
/// This expression corresponds to the [`MemberExpression.PrivateIdentifier`][spec] production.
///
/// [spec]: https://tc39.es/ecma262/#prod-MemberExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct PrivatePropertyAccess {
target: Box<Expression>,
field: PrivateName,
}
impl PrivatePropertyAccess {
/// Creates a `GetPrivateField` AST Expression.
#[inline]
#[must_use]
pub fn new(value: Expression, field: PrivateName) -> Self {
Self {
target: value.into(),
field,
}
}
/// Gets the original object from where to get the field from.
#[inline]
#[must_use]
pub const fn target(&self) -> &Expression {
&self.target
}
/// Gets the name of the field to retrieve.
#[inline]
#[must_use]
pub const fn field(&self) -> PrivateName {
self.field
}
}
impl ToInternedString for PrivatePropertyAccess {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
format!(
"{}.#{}",
self.target.to_interned_string(interner),
interner.resolve_expect(self.field.description())
)
}
}
impl From<PrivatePropertyAccess> for PropertyAccess {
#[inline]
fn from(access: PrivatePropertyAccess) -> Self {
Self::Private(access)
}
}
impl VisitWith for PrivatePropertyAccess {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.target));
visitor.visit_private_name(&self.field)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.target));
visitor.visit_private_name_mut(&mut self.field)
}
}
/// A property access of an object's parent, as defined by the [spec].
///
/// A `SuperPropertyAccess` is much like a regular [`PropertyAccess`], but where its `target` object
/// is not a regular object, but a reference to the parent object of the current object ([`super`][mdn]).
///
/// [spec]: https://tc39.es/ecma262/#prod-SuperProperty
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct SuperPropertyAccess {
field: PropertyAccessField,
}
impl SuperPropertyAccess {
/// Creates a new property access field node.
#[must_use]
pub const fn new(field: PropertyAccessField) -> Self {
Self { field }
}
/// Gets the name of the field to retrieve.
#[inline]
#[must_use]
pub const fn field(&self) -> &PropertyAccessField {
&self.field
}
}
impl ToInternedString for SuperPropertyAccess {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
match &self.field {
PropertyAccessField::Const(field) => {
format!("super.{}", interner.resolve_expect(*field))
}
PropertyAccessField::Expr(field) => {
format!("super[{}]", field.to_interned_string(interner))
}
}
}
}
impl From<SuperPropertyAccess> for PropertyAccess {
#[inline]
fn from(access: SuperPropertyAccess) -> Self {
Self::Super(access)
}
}
impl VisitWith for SuperPropertyAccess {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_property_access_field(&self.field)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_property_access_field_mut(&mut self.field)
}
}

View File

@@ -0,0 +1,71 @@
//! Await expression Expression.
use core::ops::ControlFlow;
use super::Expression;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
/// An await expression is used within an async function to pause execution and wait for a
/// promise to resolve.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AwaitExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Await {
target: Box<Expression>,
}
impl Await {
/// Return the target expression that should be awaited.
#[inline]
#[must_use]
pub const fn target(&self) -> &Expression {
&self.target
}
}
impl<T> From<T> for Await
where
T: Into<Box<Expression>>,
{
fn from(e: T) -> Self {
Self { target: e.into() }
}
}
impl ToInternedString for Await {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
format!("await {}", self.target.to_indented_string(interner, 0))
}
}
impl From<Await> for Expression {
#[inline]
fn from(awaitexpr: Await) -> Self {
Self::Await(awaitexpr)
}
}
impl VisitWith for Await {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_expression(&self.target)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_expression_mut(&mut self.target)
}
}

View File

@@ -0,0 +1,164 @@
use crate::join_nodes;
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
use super::Expression;
/// Calling the function actually performs the specified actions with the indicated parameters.
///
/// Defining a function does not execute it. Defining it simply names the function and
/// specifies what to do when the function is called. Functions must be in scope when they are
/// called, but the function declaration can be hoisted. The scope of a function is the
/// function in which it is declared (or the entire program, if it is declared at the top
/// level).
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-CallExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#Calling_functions
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Call {
function: Box<Expression>,
args: Box<[Expression]>,
}
impl Call {
/// Creates a new `Call` AST Expression.
#[inline]
#[must_use]
pub fn new(function: Expression, args: Box<[Expression]>) -> Self {
Self {
function: function.into(),
args,
}
}
/// Gets the target function of this call expression.
#[inline]
#[must_use]
pub const fn function(&self) -> &Expression {
&self.function
}
/// Retrieves the arguments passed to the function.
#[inline]
#[must_use]
pub const fn args(&self) -> &[Expression] {
&self.args
}
}
impl ToInternedString for Call {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
format!(
"{}({})",
self.function.to_interned_string(interner),
join_nodes(interner, &self.args)
)
}
}
impl From<Call> for Expression {
#[inline]
fn from(call: Call) -> Self {
Self::Call(call)
}
}
impl VisitWith for Call {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.function));
for expr in self.args.iter() {
try_break!(visitor.visit_expression(expr));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.function));
for expr in self.args.iter_mut() {
try_break!(visitor.visit_expression_mut(expr));
}
ControlFlow::Continue(())
}
}
/// The `super` keyword is used to access and call functions on an object's parent.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-SuperCall
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct SuperCall {
args: Box<[Expression]>,
}
impl SuperCall {
/// Creates a new `SuperCall` AST node.
pub fn new<A>(args: A) -> Self
where
A: Into<Box<[Expression]>>,
{
Self { args: args.into() }
}
/// Retrieves the arguments of the super call.
#[must_use]
pub const fn arguments(&self) -> &[Expression] {
&self.args
}
}
impl ToInternedString for SuperCall {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
format!("super({})", join_nodes(interner, &self.args))
}
}
impl From<SuperCall> for Expression {
#[inline]
fn from(call: SuperCall) -> Self {
Self::SuperCall(call)
}
}
impl VisitWith for SuperCall {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for expr in self.args.iter() {
try_break!(visitor.visit_expression(expr));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for expr in self.args.iter_mut() {
try_break!(visitor.visit_expression_mut(expr));
}
ControlFlow::Continue(())
}
}

View File

@@ -0,0 +1,122 @@
//! Local identifier Expression.
use crate::{
visitor::{VisitWith, Visitor, VisitorMut},
ToStringEscaped,
};
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
use super::Expression;
/// List of reserved keywords exclusive to strict mode.
pub const RESERVED_IDENTIFIERS_STRICT: [Sym; 9] = [
Sym::IMPLEMENTS,
Sym::INTERFACE,
Sym::LET,
Sym::PACKAGE,
Sym::PRIVATE,
Sym::PROTECTED,
Sym::PUBLIC,
Sym::STATIC,
Sym::YIELD,
];
/// An `identifier` is a sequence of characters in the code that identifies a variable,
/// function, or property.
///
/// In ECMAScript, identifiers are case-sensitive and can contain Unicode letters, $, _, and
/// digits (0-9), but may not start with a digit.
///
/// An identifier differs from a string in that a string is data, while an identifier is part
/// of the code. In JavaScript, there is no way to convert identifiers to strings, but
/// sometimes it is possible to parse strings into identifiers.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-Identifier
/// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/Identifier
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(transparent)
)]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct Identifier {
ident: Sym,
}
impl PartialEq<Sym> for Identifier {
#[inline]
fn eq(&self, other: &Sym) -> bool {
self.ident == *other
}
}
impl PartialEq<Identifier> for Sym {
#[inline]
fn eq(&self, other: &Identifier) -> bool {
*self == other.ident
}
}
impl Identifier {
/// Creates a new identifier AST Expression.
#[inline]
#[must_use]
pub const fn new(ident: Sym) -> Self {
Self { ident }
}
/// Retrieves the identifier's string symbol in the interner.
#[inline]
#[must_use]
pub const fn sym(self) -> Sym {
self.ident
}
}
impl ToInternedString for Identifier {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
interner.resolve_expect(self.ident).join(
String::from,
ToStringEscaped::to_string_escaped,
true,
)
}
}
impl From<Sym> for Identifier {
#[inline]
fn from(sym: Sym) -> Self {
Self { ident: sym }
}
}
impl From<Identifier> for Expression {
#[inline]
fn from(local: Identifier) -> Self {
Self::Identifier(local)
}
}
impl VisitWith for Identifier {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_sym(&self.ident)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_sym_mut(&mut self.ident)
}
}

View File

@@ -0,0 +1,223 @@
//! Array declaration Expression.
use crate::expression::operator::assign::AssignTarget;
use crate::expression::Expression;
use crate::pattern::{ArrayPattern, ArrayPatternElement, Pattern};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
/// An array is an ordered collection of data (either primitive or object depending upon the
/// language).
///
/// Arrays are used to store multiple values in a single variable.
/// This is compared to a variable that can store only one value.
///
/// Each item in an array has a number attached to it, called a numeric index, that allows you
/// to access it. In JavaScript, arrays start at index zero and can be manipulated with various
/// methods.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ArrayLiteral
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct ArrayLiteral {
arr: Box<[Option<Expression>]>,
has_trailing_comma_spread: bool,
}
impl ArrayLiteral {
/// Creates a new array literal.
pub fn new<A>(array: A, has_trailing_comma_spread: bool) -> Self
where
A: Into<Box<[Option<Expression>]>>,
{
Self {
arr: array.into(),
has_trailing_comma_spread,
}
}
/// Indicates if a spread operator in the array literal has a trailing comma.
/// This is a syntax error in some cases.
#[must_use]
pub const fn has_trailing_comma_spread(&self) -> bool {
self.has_trailing_comma_spread
}
/// Converts this `ArrayLiteral` into an [`ArrayPattern`].
#[must_use]
pub fn to_pattern(&self, strict: bool) -> Option<ArrayPattern> {
if self.has_trailing_comma_spread() {
return None;
}
let mut bindings = Vec::new();
for (i, expr) in self.arr.iter().enumerate() {
let expr = if let Some(expr) = expr {
expr
} else {
bindings.push(ArrayPatternElement::Elision);
continue;
};
match expr {
Expression::Identifier(ident) => {
if strict && *ident == Sym::ARGUMENTS {
return None;
}
bindings.push(ArrayPatternElement::SingleName {
ident: *ident,
default_init: None,
});
}
Expression::Spread(spread) => {
match spread.target() {
Expression::Identifier(ident) => {
bindings.push(ArrayPatternElement::SingleNameRest { ident: *ident });
}
Expression::PropertyAccess(access) => {
bindings.push(ArrayPatternElement::PropertyAccessRest {
access: access.clone(),
});
}
Expression::ArrayLiteral(array) => {
let pattern = array.to_pattern(strict)?.into();
bindings.push(ArrayPatternElement::PatternRest { pattern });
}
Expression::ObjectLiteral(object) => {
let pattern = object.to_pattern(strict)?.into();
bindings.push(ArrayPatternElement::PatternRest { pattern });
}
_ => return None,
}
if i + 1 != self.arr.len() {
return None;
}
}
Expression::Assign(assign) => match assign.lhs() {
AssignTarget::Identifier(ident) => {
bindings.push(ArrayPatternElement::SingleName {
ident: *ident,
default_init: Some(assign.rhs().clone()),
});
}
AssignTarget::Access(access) => {
bindings.push(ArrayPatternElement::PropertyAccess {
access: access.clone(),
});
}
AssignTarget::Pattern(pattern) => match pattern {
Pattern::Object(pattern) => {
bindings.push(ArrayPatternElement::Pattern {
pattern: Pattern::Object(pattern.clone()),
default_init: Some(assign.rhs().clone()),
});
}
Pattern::Array(pattern) => {
bindings.push(ArrayPatternElement::Pattern {
pattern: Pattern::Array(pattern.clone()),
default_init: Some(assign.rhs().clone()),
});
}
},
},
Expression::ArrayLiteral(array) => {
let pattern = array.to_pattern(strict)?.into();
bindings.push(ArrayPatternElement::Pattern {
pattern,
default_init: None,
});
}
Expression::ObjectLiteral(object) => {
let pattern = object.to_pattern(strict)?.into();
bindings.push(ArrayPatternElement::Pattern {
pattern,
default_init: None,
});
}
Expression::PropertyAccess(access) => {
bindings.push(ArrayPatternElement::PropertyAccess {
access: access.clone(),
});
}
_ => return None,
}
}
Some(ArrayPattern::new(bindings.into()))
}
}
impl AsRef<[Option<Expression>]> for ArrayLiteral {
#[inline]
fn as_ref(&self) -> &[Option<Expression>] {
&self.arr
}
}
impl<T> From<T> for ArrayLiteral
where
T: Into<Box<[Option<Expression>]>>,
{
fn from(decl: T) -> Self {
Self {
arr: decl.into(),
has_trailing_comma_spread: false,
}
}
}
impl ToInternedString for ArrayLiteral {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
let mut buf = String::from("[");
let mut first = true;
for e in &*self.arr {
if first {
first = false;
} else {
buf.push_str(", ");
}
if let Some(e) = e {
buf.push_str(&e.to_interned_string(interner));
}
}
buf.push(']');
buf
}
}
impl From<ArrayLiteral> for Expression {
#[inline]
fn from(arr: ArrayLiteral) -> Self {
Self::ArrayLiteral(arr)
}
}
impl VisitWith for ArrayLiteral {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for expr in self.arr.iter().flatten() {
try_break!(visitor.visit_expression(expr));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for expr in self.arr.iter_mut().flatten() {
try_break!(visitor.visit_expression_mut(expr));
}
ControlFlow::Continue(())
}
}

View File

@@ -0,0 +1,202 @@
//! This module contains all literal expressions, which represents the primitive values in ECMAScript.
//!
//! More information:
//! - [ECMAScript reference][spec]
//! - [MDN documentation][mdn]
//!
//! [spec]: https://tc39.es/ecma262/#sec-primary-expression-literals
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Literals
mod array;
mod object;
mod template;
pub use array::ArrayLiteral;
use core::ops::ControlFlow;
pub use object::ObjectLiteral;
pub use template::{TemplateElement, TemplateLiteral};
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, Sym, ToInternedString};
use num_bigint::BigInt;
use super::Expression;
/// Literals represent values in ECMAScript.
///
/// These are fixed values **not variables** that you literally provide in your script.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-primary-expression-literals
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Literals
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum Literal {
/// A string literal is zero or more characters enclosed in double (`"`) or single (`'`) quotation marks.
///
/// A string must be delimited by quotation marks of the same type (that is, either both single quotation marks, or both double quotation marks).
/// You can call any of the String object's methods on a string literal value.
/// ECMAScript automatically converts the string literal to a temporary String object,
/// calls the method, then discards the temporary String object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-string-value
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#String_literals
String(Sym),
/// A floating-point number literal.
///
/// The exponent part is an "`e`" or "`E`" followed by an integer, which can be signed (preceded by "`+`" or "`-`").
/// A floating-point literal must have at least one digit, and either a decimal point or "`e`" (or "`E`").
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-number-value
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Floating-point_literals
Num(f64),
/// Integer types can be expressed in decimal (base 10), hexadecimal (base 16), octal (base 8) and binary (base 2).
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-number-value
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Numeric_literals
Int(i32),
/// BigInt provides a way to represent whole numbers larger than the largest number ECMAScript
/// can reliably represent with the `Number` primitive.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-bigint-value
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Numeric_literals
BigInt(Box<BigInt>),
/// The Boolean type has two literal values: `true` and `false`.
///
/// The Boolean object is a wrapper around the primitive Boolean data type.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-boolean-value
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Boolean_literals
Bool(bool),
/// In JavaScript, `null` is marked as one of the primitive values, cause it's behaviour is seemingly primitive.
///
/// In computer science, a null value represents a reference that points,
/// generally intentionally, to a nonexistent or invalid object or address.
/// The meaning of a null reference varies among language implementations.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-null-value
/// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/null
Null,
}
impl From<Sym> for Literal {
#[inline]
fn from(string: Sym) -> Self {
Self::String(string)
}
}
impl From<f64> for Literal {
#[inline]
fn from(num: f64) -> Self {
Self::Num(num)
}
}
impl From<i32> for Literal {
#[inline]
fn from(i: i32) -> Self {
Self::Int(i)
}
}
impl From<BigInt> for Literal {
#[inline]
fn from(i: BigInt) -> Self {
Self::BigInt(Box::new(i))
}
}
impl From<Box<BigInt>> for Literal {
#[inline]
fn from(i: Box<BigInt>) -> Self {
Self::BigInt(i)
}
}
impl From<bool> for Literal {
#[inline]
fn from(b: bool) -> Self {
Self::Bool(b)
}
}
impl From<Literal> for Expression {
#[inline]
fn from(lit: Literal) -> Self {
Self::Literal(lit)
}
}
impl ToInternedString for Literal {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
match *self {
Self::String(st) => {
format!("\"{}\"", interner.resolve_expect(st))
}
Self::Num(num) => num.to_string(),
Self::Int(num) => num.to_string(),
Self::BigInt(ref num) => num.to_string(),
Self::Bool(v) => v.to_string(),
Self::Null => "null".to_owned(),
}
}
}
impl VisitWith for Literal {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Self::String(sym) = self {
visitor.visit_sym(sym)
} else {
ControlFlow::Continue(())
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Self::String(sym) = self {
visitor.visit_sym_mut(sym)
} else {
ControlFlow::Continue(())
}
}
}

View File

@@ -0,0 +1,326 @@
//! Object Expression.
use crate::{
block_to_string,
expression::{operator::assign::AssignTarget, Expression, RESERVED_IDENTIFIERS_STRICT},
function::Function,
join_nodes,
pattern::{ObjectPattern, ObjectPatternElement},
property::{MethodDefinition, PropertyDefinition, PropertyName},
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
};
use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// Objects in ECMAScript may be defined as an unordered collection of related data, of
/// primitive or reference types, in the form of “key: value” pairs.
///
/// Objects can be initialized using `new Object()`, `Object.create()`, or using the literal
/// notation.
///
/// An object initializer is an expression that describes the initialization of an
/// [`Object`][object]. Objects consist of properties, which are used to describe an object.
/// Values of object properties can either contain [`primitive`][primitive] data types or other
/// objects.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ObjectLiteral
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer
/// [object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object
/// [primitive]: https://developer.mozilla.org/en-US/docs/Glossary/primitive
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct ObjectLiteral {
properties: Box<[PropertyDefinition]>,
}
impl ObjectLiteral {
/// Gets the object literal properties
#[inline]
#[must_use]
pub const fn properties(&self) -> &[PropertyDefinition] {
&self.properties
}
/// Converts the object literal into an [`ObjectPattern`].
#[must_use]
pub fn to_pattern(&self, strict: bool) -> Option<ObjectPattern> {
let mut bindings = Vec::new();
let mut excluded_keys = Vec::new();
for (i, property) in self.properties.iter().enumerate() {
match property {
PropertyDefinition::IdentifierReference(ident) if strict && *ident == Sym::EVAL => {
return None
}
PropertyDefinition::IdentifierReference(ident) => {
if strict && RESERVED_IDENTIFIERS_STRICT.contains(&ident.sym()) {
return None;
}
excluded_keys.push(*ident);
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Literal(ident.sym()),
default_init: None,
});
}
PropertyDefinition::Property(name, expr) => match (name, expr) {
(PropertyName::Literal(name), Expression::Identifier(ident))
if *name == *ident =>
{
if strict && *name == Sym::EVAL {
return None;
}
if strict && RESERVED_IDENTIFIERS_STRICT.contains(name) {
return None;
}
excluded_keys.push(*ident);
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Literal(*name),
default_init: None,
});
}
(PropertyName::Literal(name), Expression::Identifier(ident)) => {
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Literal(*name),
default_init: None,
});
}
(PropertyName::Literal(name), Expression::ObjectLiteral(object)) => {
let pattern = object.to_pattern(strict)?.into();
bindings.push(ObjectPatternElement::Pattern {
name: PropertyName::Literal(*name),
pattern,
default_init: None,
});
}
(PropertyName::Literal(name), Expression::ArrayLiteral(array)) => {
let pattern = array.to_pattern(strict)?.into();
bindings.push(ObjectPatternElement::Pattern {
name: PropertyName::Literal(*name),
pattern,
default_init: None,
});
}
(_, Expression::Assign(assign)) => match assign.lhs() {
AssignTarget::Identifier(ident) => {
if let Some(name) = name.literal() {
if name == *ident {
if strict && name == Sym::EVAL {
return None;
}
if strict && RESERVED_IDENTIFIERS_STRICT.contains(&name) {
return None;
}
excluded_keys.push(*ident);
}
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Literal(name),
default_init: Some(assign.rhs().clone()),
});
} else {
return None;
}
}
AssignTarget::Pattern(pattern) => {
bindings.push(ObjectPatternElement::Pattern {
name: name.clone(),
pattern: pattern.clone(),
default_init: Some(assign.rhs().clone()),
});
}
AssignTarget::Access(access) => {
bindings.push(ObjectPatternElement::AssignmentPropertyAccess {
name: name.clone(),
access: access.clone(),
default_init: Some(assign.rhs().clone()),
});
}
},
(_, Expression::PropertyAccess(access)) => {
bindings.push(ObjectPatternElement::AssignmentPropertyAccess {
name: name.clone(),
access: access.clone(),
default_init: None,
});
}
(PropertyName::Computed(name), Expression::Identifier(ident)) => {
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Computed(name.clone()),
default_init: None,
});
}
_ => return None,
},
PropertyDefinition::SpreadObject(spread) => {
match spread {
Expression::Identifier(ident) => {
bindings.push(ObjectPatternElement::RestProperty {
ident: *ident,
excluded_keys: excluded_keys.clone(),
});
}
Expression::PropertyAccess(access) => {
bindings.push(ObjectPatternElement::AssignmentRestPropertyAccess {
access: access.clone(),
excluded_keys: excluded_keys.clone(),
});
}
_ => return None,
}
if i + 1 != self.properties.len() {
return None;
}
}
PropertyDefinition::MethodDefinition(_, _) => return None,
PropertyDefinition::CoverInitializedName(ident, expr) => {
if strict && [Sym::EVAL, Sym::ARGUMENTS].contains(&ident.sym()) {
return None;
}
bindings.push(ObjectPatternElement::SingleName {
ident: *ident,
name: PropertyName::Literal(ident.sym()),
default_init: Some(expr.clone()),
});
}
}
}
Some(ObjectPattern::new(bindings.into()))
}
}
impl ToIndentedString for ObjectLiteral {
fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String {
let mut buf = "{\n".to_owned();
let indentation = " ".repeat(indent_n + 1);
for property in self.properties().iter() {
buf.push_str(&match property {
PropertyDefinition::IdentifierReference(ident) => {
format!("{indentation}{},\n", interner.resolve_expect(ident.sym()))
}
PropertyDefinition::Property(key, value) => {
let value = if let Expression::Function(f) = value {
Function::new(None, f.parameters().clone(), f.body().clone()).into()
} else {
value.clone()
};
format!(
"{indentation}{}: {},\n",
key.to_interned_string(interner),
value.to_no_indent_string(interner, indent_n + 1)
)
}
PropertyDefinition::SpreadObject(key) => {
format!("{indentation}...{},\n", key.to_interned_string(interner))
}
PropertyDefinition::MethodDefinition(key, method) => {
format!(
"{indentation}{}{}({}) {},\n",
match &method {
MethodDefinition::Get(_) => "get ",
MethodDefinition::Set(_) => "set ",
_ => "",
},
key.to_interned_string(interner),
match &method {
MethodDefinition::Get(expression)
| MethodDefinition::Set(expression)
| MethodDefinition::Ordinary(expression) => {
join_nodes(interner, expression.parameters().as_ref())
}
MethodDefinition::Generator(expression) => {
join_nodes(interner, expression.parameters().as_ref())
}
MethodDefinition::AsyncGenerator(expression) => {
join_nodes(interner, expression.parameters().as_ref())
}
MethodDefinition::Async(expression) => {
join_nodes(interner, expression.parameters().as_ref())
}
},
match &method {
MethodDefinition::Get(expression)
| MethodDefinition::Set(expression)
| MethodDefinition::Ordinary(expression) => {
block_to_string(expression.body(), interner, indent_n + 1)
}
MethodDefinition::Generator(expression) => {
block_to_string(expression.body(), interner, indent_n + 1)
}
MethodDefinition::AsyncGenerator(expression) => {
block_to_string(expression.body(), interner, indent_n + 1)
}
MethodDefinition::Async(expression) => {
block_to_string(expression.body(), interner, indent_n + 1)
}
},
)
}
PropertyDefinition::CoverInitializedName(ident, expr) => {
format!(
"{indentation}{} = {},\n",
interner.resolve_expect(ident.sym()),
expr.to_no_indent_string(interner, indent_n + 1)
)
}
});
}
buf.push_str(&format!("{}}}", " ".repeat(indent_n)));
buf
}
}
impl<T> From<T> for ObjectLiteral
where
T: Into<Box<[PropertyDefinition]>>,
{
fn from(props: T) -> Self {
Self {
properties: props.into(),
}
}
}
impl From<ObjectLiteral> for Expression {
#[inline]
fn from(obj: ObjectLiteral) -> Self {
Self::ObjectLiteral(obj)
}
}
impl VisitWith for ObjectLiteral {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for pd in self.properties.iter() {
try_break!(visitor.visit_property_definition(pd));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for pd in self.properties.iter_mut() {
try_break!(visitor.visit_property_definition_mut(pd));
}
ControlFlow::Continue(())
}
}

View File

@@ -0,0 +1,133 @@
//! Template literal Expression.
use core::ops::ControlFlow;
use std::borrow::Cow;
use boa_interner::{Interner, Sym, ToInternedString};
use crate::{
expression::Expression,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
ToStringEscaped,
};
/// Template literals are string literals allowing embedded expressions.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
/// [spec]: https://tc39.es/ecma262/#sec-template-literals
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct TemplateLiteral {
elements: Box<[TemplateElement]>,
}
impl From<TemplateLiteral> for Expression {
#[inline]
fn from(tem: TemplateLiteral) -> Self {
Self::TemplateLiteral(tem)
}
}
/// An element found within a [`TemplateLiteral`].
///
/// The [spec] doesn't define an element akin to `TemplateElement`. However, the AST defines this
/// node as the equivalent of the components found in a template literal.
///
/// [spec]: https://tc39.es/ecma262/#sec-template-literals
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum TemplateElement {
/// A simple string.
String(Sym),
/// An expression that is evaluated and replaced by its string representation.
Expr(Expression),
}
impl TemplateLiteral {
/// Creates a new `TemplateLiteral` from a list of [`TemplateElement`]s.
#[inline]
#[must_use]
pub fn new(elements: Box<[TemplateElement]>) -> Self {
Self { elements }
}
/// Gets the element list of this `TemplateLiteral`.
#[must_use]
pub const fn elements(&self) -> &[TemplateElement] {
&self.elements
}
}
impl ToInternedString for TemplateLiteral {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
let mut buf = "`".to_owned();
for elt in self.elements.iter() {
match elt {
TemplateElement::String(s) => buf.push_str(&interner.resolve_expect(*s).join(
Cow::Borrowed,
|utf16| Cow::Owned(utf16.to_string_escaped()),
true,
)),
TemplateElement::Expr(n) => {
buf.push_str(&format!("${{{}}}", n.to_interned_string(interner)));
}
}
}
buf.push('`');
buf
}
}
impl VisitWith for TemplateLiteral {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for element in self.elements.iter() {
try_break!(visitor.visit_template_element(element));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for element in self.elements.iter_mut() {
try_break!(visitor.visit_template_element_mut(element));
}
ControlFlow::Continue(())
}
}
impl VisitWith for TemplateElement {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::String(sym) => visitor.visit_sym(sym),
Self::Expr(expr) => visitor.visit_expression(expr),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::String(sym) => visitor.visit_sym_mut(sym),
Self::Expr(expr) => visitor.visit_expression_mut(expr),
}
}
}

View File

@@ -0,0 +1,332 @@
//! The [`Expression`] Parse Node, as defined by the [spec].
//!
//! ECMAScript expressions include:
//! - [Primary][primary] expressions (`this`, function expressions, literals).
//! - [Left hand side][lhs] expressions (accessors, `new` operator, `super`).
//! - [operator] expressions.
//!
//! [spec]: https://tc39.es/ecma262/#prod-Expression
//! [primary]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#primary_expressions
//! [lhs]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#left-hand-side_expressions
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
use self::{
access::PropertyAccess,
literal::{ArrayLiteral, Literal, ObjectLiteral, TemplateLiteral},
operator::{Assign, Binary, Conditional, Unary},
};
use super::{
function::{ArrowFunction, AsyncFunction, AsyncGenerator, Class, Function, Generator},
function::{AsyncArrowFunction, FormalParameterList},
Statement,
};
mod r#await;
mod call;
mod identifier;
mod new;
mod optional;
mod spread;
mod tagged_template;
mod r#yield;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
pub use call::{Call, SuperCall};
pub use identifier::{Identifier, RESERVED_IDENTIFIERS_STRICT};
pub use new::New;
pub use optional::{Optional, OptionalOperation, OptionalOperationKind};
pub use r#await::Await;
pub use r#yield::Yield;
pub use spread::Spread;
pub use tagged_template::TaggedTemplate;
pub mod access;
pub mod literal;
pub mod operator;
/// The `Expression` Parse Node.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, PartialEq)]
pub enum Expression {
/// The ECMAScript `this` keyword refers to the object it belongs to.
///
/// A property of an execution context (global, function or eval) that,
/// in nonstrict mode, is always a reference to an object and in strict
/// mode can be any value.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-this-keyword
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
This,
/// See [`Identifier`].
Identifier(Identifier),
/// See [`Literal`].
Literal(Literal),
/// See [`ArrayLiteral`].
ArrayLiteral(ArrayLiteral),
/// See [`ObjectLiteral`].
ObjectLiteral(ObjectLiteral),
/// See [`Spread`],
Spread(Spread),
/// See [`Function`].
Function(Function),
/// See [`ArrowFunction`].
ArrowFunction(ArrowFunction),
/// See [`AsyncArrowFunction`].
AsyncArrowFunction(AsyncArrowFunction),
/// See [`Generator`].
Generator(Generator),
/// See [`AsyncFunction`].
AsyncFunction(AsyncFunction),
/// See [`AsyncGenerator`].
AsyncGenerator(AsyncGenerator),
/// See [`Class`].
Class(Box<Class>),
// TODO: Extract regexp literal Expression
// RegExpLiteral,
/// See [`TemplateLiteral`].
TemplateLiteral(TemplateLiteral),
/// See [`PropertyAccess`].
PropertyAccess(PropertyAccess),
/// See [`New`].
New(New),
/// See [`Call`].
Call(Call),
/// See [`SuperCall`].
SuperCall(SuperCall),
/// See [`Optional`].
Optional(Optional),
// TODO: Import calls
/// See [`TaggedTemplate`].
TaggedTemplate(TaggedTemplate),
/// The `new.target` pseudo-property expression.
NewTarget,
// TODO: import.meta
/// See [`Assign`].
Assign(Assign),
/// See [`Unary`].
Unary(Unary),
/// See [`Binary`].
Binary(Binary),
/// See [`Conditional`].
Conditional(Conditional),
/// See [`Await`].
Await(Await),
/// See [`Yield`].
Yield(Yield),
/// A FormalParameterList.
///
/// This is only used in the parser itself.
/// It is not a valid expression node.
#[doc(hidden)]
FormalParameterList(FormalParameterList),
}
impl Expression {
/// Implements the display formatting with indentation.
///
/// This will not prefix the value with any indentation. If you want to prefix this with proper
/// indents, use [`to_indented_string()`](Self::to_indented_string).
pub(crate) fn to_no_indent_string(&self, interner: &Interner, indentation: usize) -> String {
match self {
Self::This => "this".to_owned(),
Self::Identifier(id) => id.to_interned_string(interner),
Self::Literal(lit) => lit.to_interned_string(interner),
Self::ArrayLiteral(arr) => arr.to_interned_string(interner),
Self::ObjectLiteral(o) => o.to_indented_string(interner, indentation),
Self::Spread(sp) => sp.to_interned_string(interner),
Self::Function(f) => f.to_indented_string(interner, indentation),
Self::AsyncArrowFunction(f) => f.to_indented_string(interner, indentation),
Self::ArrowFunction(arrf) => arrf.to_indented_string(interner, indentation),
Self::Class(cl) => cl.to_indented_string(interner, indentation),
Self::Generator(gen) => gen.to_indented_string(interner, indentation),
Self::AsyncFunction(asf) => asf.to_indented_string(interner, indentation),
Self::AsyncGenerator(asgen) => asgen.to_indented_string(interner, indentation),
Self::TemplateLiteral(tem) => tem.to_interned_string(interner),
Self::PropertyAccess(prop) => prop.to_interned_string(interner),
Self::New(new) => new.to_interned_string(interner),
Self::Call(call) => call.to_interned_string(interner),
Self::SuperCall(supc) => supc.to_interned_string(interner),
Self::Optional(opt) => opt.to_interned_string(interner),
Self::NewTarget => "new.target".to_owned(),
Self::TaggedTemplate(tag) => tag.to_interned_string(interner),
Self::Assign(assign) => assign.to_interned_string(interner),
Self::Unary(unary) => unary.to_interned_string(interner),
Self::Binary(bin) => bin.to_interned_string(interner),
Self::Conditional(cond) => cond.to_interned_string(interner),
Self::Await(aw) => aw.to_interned_string(interner),
Self::Yield(yi) => yi.to_interned_string(interner),
Self::FormalParameterList(_) => unreachable!(),
}
}
/// Returns if the expression is a function definition according to the spec.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-isfunctiondefinition
#[must_use]
#[inline]
pub const fn is_function_definition(&self) -> bool {
matches!(
self,
Self::ArrowFunction(_)
| Self::AsyncArrowFunction(_)
| Self::Function(_)
| Self::Generator(_)
| Self::AsyncGenerator(_)
| Self::AsyncFunction(_)
| Self::Class(_)
)
}
/// Returns if the expression is a function definition without a name.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-isanonymousfunctiondefinition
#[must_use]
#[inline]
pub const fn is_anonymous_function_definition(&self) -> bool {
match self {
Self::ArrowFunction(f) => f.name().is_none(),
Self::AsyncArrowFunction(f) => f.name().is_none(),
Self::Function(f) => f.name().is_none(),
Self::Generator(f) => f.name().is_none(),
Self::AsyncGenerator(f) => f.name().is_none(),
Self::AsyncFunction(f) => f.name().is_none(),
Self::Class(f) => f.name().is_none(),
_ => false,
}
}
}
impl From<Expression> for Statement {
#[inline]
fn from(expr: Expression) -> Self {
Self::Expression(expr)
}
}
impl ToIndentedString for Expression {
#[inline]
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
self.to_no_indent_string(interner, indentation)
}
}
impl VisitWith for Expression {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Identifier(id) => visitor.visit_identifier(id),
Self::Literal(lit) => visitor.visit_literal(lit),
Self::ArrayLiteral(arlit) => visitor.visit_array_literal(arlit),
Self::ObjectLiteral(olit) => visitor.visit_object_literal(olit),
Self::Spread(sp) => visitor.visit_spread(sp),
Self::Function(f) => visitor.visit_function(f),
Self::ArrowFunction(af) => visitor.visit_arrow_function(af),
Self::AsyncArrowFunction(af) => visitor.visit_async_arrow_function(af),
Self::Generator(g) => visitor.visit_generator(g),
Self::AsyncFunction(af) => visitor.visit_async_function(af),
Self::AsyncGenerator(ag) => visitor.visit_async_generator(ag),
Self::Class(c) => visitor.visit_class(c),
Self::TemplateLiteral(tlit) => visitor.visit_template_literal(tlit),
Self::PropertyAccess(pa) => visitor.visit_property_access(pa),
Self::New(n) => visitor.visit_new(n),
Self::Call(c) => visitor.visit_call(c),
Self::SuperCall(sc) => visitor.visit_super_call(sc),
Self::Optional(opt) => visitor.visit_optional(opt),
Self::TaggedTemplate(tt) => visitor.visit_tagged_template(tt),
Self::Assign(a) => visitor.visit_assign(a),
Self::Unary(u) => visitor.visit_unary(u),
Self::Binary(b) => visitor.visit_binary(b),
Self::Conditional(c) => visitor.visit_conditional(c),
Self::Await(a) => visitor.visit_await(a),
Self::Yield(y) => visitor.visit_yield(y),
Self::FormalParameterList(fpl) => visitor.visit_formal_parameter_list(fpl),
Self::This | Self::NewTarget => {
// do nothing; can be handled as special case by visitor
ControlFlow::Continue(())
}
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Identifier(id) => visitor.visit_identifier_mut(id),
Self::Literal(lit) => visitor.visit_literal_mut(lit),
Self::ArrayLiteral(arlit) => visitor.visit_array_literal_mut(arlit),
Self::ObjectLiteral(olit) => visitor.visit_object_literal_mut(olit),
Self::Spread(sp) => visitor.visit_spread_mut(sp),
Self::Function(f) => visitor.visit_function_mut(f),
Self::ArrowFunction(af) => visitor.visit_arrow_function_mut(af),
Self::AsyncArrowFunction(af) => visitor.visit_async_arrow_function_mut(af),
Self::Generator(g) => visitor.visit_generator_mut(g),
Self::AsyncFunction(af) => visitor.visit_async_function_mut(af),
Self::AsyncGenerator(ag) => visitor.visit_async_generator_mut(ag),
Self::Class(c) => visitor.visit_class_mut(c),
Self::TemplateLiteral(tlit) => visitor.visit_template_literal_mut(tlit),
Self::PropertyAccess(pa) => visitor.visit_property_access_mut(pa),
Self::New(n) => visitor.visit_new_mut(n),
Self::Call(c) => visitor.visit_call_mut(c),
Self::SuperCall(sc) => visitor.visit_super_call_mut(sc),
Self::Optional(opt) => visitor.visit_optional_mut(opt),
Self::TaggedTemplate(tt) => visitor.visit_tagged_template_mut(tt),
Self::Assign(a) => visitor.visit_assign_mut(a),
Self::Unary(u) => visitor.visit_unary_mut(u),
Self::Binary(b) => visitor.visit_binary_mut(b),
Self::Conditional(c) => visitor.visit_conditional_mut(c),
Self::Await(a) => visitor.visit_await_mut(a),
Self::Yield(y) => visitor.visit_yield_mut(y),
Self::FormalParameterList(fpl) => visitor.visit_formal_parameter_list_mut(fpl),
Self::This | Self::NewTarget => {
// do nothing; can be handled as special case by visitor
ControlFlow::Continue(())
}
}
}
}

View File

@@ -0,0 +1,87 @@
use crate::expression::Call;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
use super::Expression;
/// The `new` operator lets developers create an instance of a user-defined object type or of
/// one of the built-in object types that has a constructor function.
///
/// The new keyword does the following things:
/// - Creates a blank, plain JavaScript object;
/// - Links (sets the constructor of) this object to another object;
/// - Passes the newly created object from Step 1 as the this context;
/// - Returns this if the function doesn't return its own object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-NewExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct New {
call: Call,
}
impl New {
/// Gets the constructor of the new expression.
#[inline]
#[must_use]
pub const fn constructor(&self) -> &Expression {
self.call.function()
}
/// Retrieves the arguments passed to the constructor.
#[inline]
#[must_use]
pub const fn arguments(&self) -> &[Expression] {
self.call.args()
}
/// Returns the inner call expression.
#[must_use]
pub const fn call(&self) -> &Call {
&self.call
}
}
impl From<Call> for New {
#[inline]
fn from(call: Call) -> Self {
Self { call }
}
}
impl ToInternedString for New {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
format!("new {}", self.call.to_interned_string(interner))
}
}
impl From<New> for Expression {
#[inline]
fn from(new: New) -> Self {
Self::New(new)
}
}
impl VisitWith for New {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_call(&self.call)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_call_mut(&mut self.call)
}
}

View File

@@ -0,0 +1,191 @@
//! Assignment expression nodes, as defined by the [spec].
//!
//! An [assignment operator][mdn] assigns a value to its left operand based on the value of its right
//! operand. Almost any [`LeftHandSideExpression`][lhs] Parse Node can be the target of a simple
//! assignment expression (`=`). However, the compound assignment operations such as `%=` or `??=`
//! only allow ["simple"][simple] left hand side expressions as an assignment target.
//!
//! [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators
//! [lhs]: https://tc39.es/ecma262/#prod-LeftHandSideExpression
//! [simple]: https://tc39.es/ecma262/#sec-static-semantics-assignmenttargettype
mod op;
use core::ops::ControlFlow;
pub use op::*;
use boa_interner::{Interner, ToInternedString};
use crate::{
expression::{access::PropertyAccess, identifier::Identifier, Expression},
pattern::Pattern,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
};
/// An assignment operator expression.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Assign {
op: AssignOp,
lhs: Box<AssignTarget>,
rhs: Box<Expression>,
}
impl Assign {
/// Creates an `Assign` AST Expression.
#[inline]
#[must_use]
pub fn new(op: AssignOp, lhs: AssignTarget, rhs: Expression) -> Self {
Self {
op,
lhs: Box::new(lhs),
rhs: Box::new(rhs),
}
}
/// Gets the operator of the assignment operation.
#[inline]
#[must_use]
pub const fn op(&self) -> AssignOp {
self.op
}
/// Gets the left hand side of the assignment operation.
#[inline]
#[must_use]
pub const fn lhs(&self) -> &AssignTarget {
&self.lhs
}
/// Gets the right hand side of the assignment operation.
#[inline]
#[must_use]
pub const fn rhs(&self) -> &Expression {
&self.rhs
}
}
impl ToInternedString for Assign {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
format!(
"{} {} {}",
self.lhs.to_interned_string(interner),
self.op,
self.rhs.to_interned_string(interner)
)
}
}
impl From<Assign> for Expression {
#[inline]
fn from(op: Assign) -> Self {
Self::Assign(op)
}
}
impl VisitWith for Assign {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_assign_target(&self.lhs));
visitor.visit_expression(&self.rhs)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_assign_target_mut(&mut self.lhs));
visitor.visit_expression_mut(&mut self.rhs)
}
}
/// The valid left-hand-side expressions of an assignment operator. Also called
/// [`LeftHandSideExpression`][spec] in the spec.
///
/// [spec]: hhttps://tc39.es/ecma262/#prod-LeftHandSideExpression
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum AssignTarget {
/// A simple identifier, such as `a`.
Identifier(Identifier),
/// A property access, such as `a.prop`.
Access(PropertyAccess),
/// A pattern assignment, such as `{a, b, ...c}`.
Pattern(Pattern),
}
impl AssignTarget {
/// Converts the left-hand-side Expression of an assignment expression into an [`AssignTarget`].
/// Returns `None` if the given Expression is an invalid left-hand-side for a assignment expression.
#[must_use]
pub fn from_expression(
expression: &Expression,
strict: bool,
destructure: bool,
) -> Option<Self> {
match expression {
Expression::Identifier(id) => Some(Self::Identifier(*id)),
Expression::PropertyAccess(access) => Some(Self::Access(access.clone())),
Expression::ObjectLiteral(object) if destructure => {
let pattern = object.to_pattern(strict)?;
Some(Self::Pattern(pattern.into()))
}
Expression::ArrayLiteral(array) if destructure => {
let pattern = array.to_pattern(strict)?;
Some(Self::Pattern(pattern.into()))
}
_ => None,
}
}
}
impl ToInternedString for AssignTarget {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
match self {
Self::Identifier(id) => id.to_interned_string(interner),
Self::Access(access) => access.to_interned_string(interner),
Self::Pattern(pattern) => pattern.to_interned_string(interner),
}
}
}
impl From<Identifier> for AssignTarget {
#[inline]
fn from(target: Identifier) -> Self {
Self::Identifier(target)
}
}
impl VisitWith for AssignTarget {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Identifier(id) => visitor.visit_identifier(id),
Self::Access(pa) => visitor.visit_property_access(pa),
Self::Pattern(pat) => visitor.visit_pattern(pat),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Identifier(id) => visitor.visit_identifier_mut(id),
Self::Access(pa) => visitor.visit_property_access_mut(pa),
Self::Pattern(pat) => visitor.visit_pattern_mut(pat),
}
}
}

View File

@@ -0,0 +1,244 @@
/// An assignment operator assigns a value to its left operand based on the value of its right operand.
///
/// The simple assignment operator is equal (`=`), which assigns the value of its right operand to its
/// left operand. That is, `x = y` assigns the value of `y to x`.
///
/// There are also compound assignment operators that are shorthand for the operations
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Assignment
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AssignOp {
/// The assignment operator assigns the value of the right operand to the left operand.
///
/// Syntax: `x = y`
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment
Assign,
/// The addition assignment operator adds the value of the right operand to a variable and assigns the result to the variable.
///
/// Syntax: `x += y`
///
/// The types of the two operands determine the behavior of the addition assignment operator. Addition or concatenation is possible.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Addition_assignment
Add,
/// The subtraction assignment operator subtracts the value of the right operand from a variable and assigns the result to the variable.
///
/// Syntax: `x -= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Subtraction_assignment
Sub,
/// The multiplication assignment operator multiplies a variable by the value of the right operand and assigns the result to the variable.
///
/// Syntax: `x *= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Multiplication_assignment
Mul,
/// The division assignment operator divides a variable by the value of the right operand and assigns the result to the variable.
///
/// Syntax: `x /= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Division_assignment
Div,
/// The remainder assignment operator divides a variable by the value of the right operand and assigns the remainder to the variable.
///
/// Syntax: `x %= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Remainder_assignment
Mod,
/// The exponentiation assignment operator raises the value of a variable to the power of the right operand.
///
/// Syntax: `x ** y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Exponentiation_assignment
Exp,
/// The bitwise AND assignment operator uses the binary representation of both operands, does a bitwise AND operation on
/// them and assigns the result to the variable.
///
/// Syntax: `x &= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Bitwise_AND_assignment
And,
/// The bitwise OR assignment operator uses the binary representation of both operands, does a bitwise OR operation on
/// them and assigns the result to the variable.
///
/// Syntax: `x |= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Bitwise_OR_assignment
Or,
/// The bitwise XOR assignment operator uses the binary representation of both operands, does a bitwise XOR operation on
/// them and assigns the result to the variable.
///
/// Syntax: `x ^= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Bitwise_XOR_assignment
Xor,
/// The left shift assignment operator moves the specified amount of bits to the left and assigns the result to the variable.
///
/// Syntax: `x <<= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Left_shift_assignment
Shl,
/// The right shift assignment operator moves the specified amount of bits to the right and assigns the result to the variable.
///
/// Syntax: `x >>= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Right_shift_assignment
Shr,
/// The unsigned right shift assignment operator moves the specified amount of bits to the right and assigns the result to the variable.
///
/// Syntax: `x >>>= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Unsigned_right_shift_assignment
Ushr,
/// The logical and assignment operator only assigns if the target variable is truthy.
///
/// Syntax: `x &&= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND_assignment
BoolAnd,
/// The logical or assignment operator only assigns if the target variable is falsy.
///
/// Syntax: `x ||= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR_assignment
BoolOr,
/// The logical nullish assignment operator only assigns if the target variable is nullish (null or undefined).
///
/// Syntax: `x ??= y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_nullish_assignment
Coalesce,
}
impl AssignOp {
/// Retrieves the operation as a static string.
const fn as_str(self) -> &'static str {
match self {
Self::Assign => "=",
Self::Add => "+=",
Self::Sub => "-=",
Self::Mul => "*=",
Self::Exp => "**=",
Self::Div => "/=",
Self::Mod => "%=",
Self::And => "&=",
Self::Or => "|=",
Self::Xor => "^=",
Self::Shl => "<<=",
Self::Shr => ">>=",
Self::Ushr => ">>>=",
Self::BoolAnd => "&&=",
Self::BoolOr => "||=",
Self::Coalesce => "??=",
}
}
}
impl std::fmt::Display for AssignOp {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}

View File

@@ -0,0 +1,111 @@
//! Binary expression nodes.
//!
//! A Binary expression comprises any operation between two expressions (excluding assignments),
//! such as:
//! - [Logic operations][logic] (`||`, `&&`).
//! - [Relational math][relat] (`==`, `<`).
//! - [Bit manipulation][bit] (`^`, `|`).
//! - [Arithmetic][arith] (`+`, `%`).
//! - The [comma operator][comma] (`,`)
//!
//! [logic]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#binary_logical_operators
//! [relat]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#relational_operators
//! [bit]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#binary_bitwise_operators
//! [arith]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#arithmetic_operators
//! [comma]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator
mod op;
use core::ops::ControlFlow;
pub use op::*;
use boa_interner::{Interner, ToInternedString};
use crate::{
expression::Expression,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
};
/// Binary operations require two operands, one before the operator and one after the operator.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Binary {
op: BinaryOp,
lhs: Box<Expression>,
rhs: Box<Expression>,
}
impl Binary {
/// Creates a `BinOp` AST Expression.
#[inline]
#[must_use]
pub fn new(op: BinaryOp, lhs: Expression, rhs: Expression) -> Self {
Self {
op,
lhs: Box::new(lhs),
rhs: Box::new(rhs),
}
}
/// Gets the binary operation of the Expression.
#[inline]
#[must_use]
pub const fn op(&self) -> BinaryOp {
self.op
}
/// Gets the left hand side of the binary operation.
#[inline]
#[must_use]
pub const fn lhs(&self) -> &Expression {
&self.lhs
}
/// Gets the right hand side of the binary operation.
#[inline]
#[must_use]
pub const fn rhs(&self) -> &Expression {
&self.rhs
}
}
impl ToInternedString for Binary {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
format!(
"{} {} {}",
self.lhs.to_interned_string(interner),
self.op,
self.rhs.to_interned_string(interner)
)
}
}
impl From<Binary> for Expression {
#[inline]
fn from(op: Binary) -> Self {
Self::Binary(op)
}
}
impl VisitWith for Binary {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.lhs));
visitor.visit_expression(&self.rhs)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.lhs));
visitor.visit_expression_mut(&mut self.rhs)
}
}

View File

@@ -0,0 +1,578 @@
//! This module implements various structure for logic handling.
use std::fmt::{Display, Formatter, Result};
/// This represents a binary operation between two values.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BinaryOp {
/// Numeric operation.
///
/// see: [`NumOp`](enum.NumOp.html)
Arithmetic(ArithmeticOp),
/// Bitwise operation.
///
/// see: [`BitOp`](enum.BitOp.html).
Bitwise(BitwiseOp),
/// Comparative operation.
///
/// see: [`CompOp`](enum.CompOp.html).
Relational(RelationalOp),
/// Logical operation.
///
/// see: [`LogOp`](enum.LogOp.html).
Logical(LogicalOp),
/// Comma operation.
Comma,
}
impl From<ArithmeticOp> for BinaryOp {
#[inline]
fn from(op: ArithmeticOp) -> Self {
Self::Arithmetic(op)
}
}
impl From<BitwiseOp> for BinaryOp {
#[inline]
fn from(op: BitwiseOp) -> Self {
Self::Bitwise(op)
}
}
impl From<RelationalOp> for BinaryOp {
#[inline]
fn from(op: RelationalOp) -> Self {
Self::Relational(op)
}
}
impl From<LogicalOp> for BinaryOp {
#[inline]
fn from(op: LogicalOp) -> Self {
Self::Logical(op)
}
}
impl BinaryOp {
/// Retrieves the operation as a static string.
const fn as_str(self) -> &'static str {
match self {
Self::Arithmetic(ref op) => op.as_str(),
Self::Bitwise(ref op) => op.as_str(),
Self::Relational(ref op) => op.as_str(),
Self::Logical(ref op) => op.as_str(),
Self::Comma => ",",
}
}
}
impl Display for BinaryOp {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{}", self.as_str())
}
}
/// Arithmetic operators take numerical values (either literals or variables)
/// as their operands and return a single numerical value.
///
/// More information:
/// - [MDN documentation][mdn]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Arithmetic
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ArithmeticOp {
/// The addition operator produces the sum of numeric operands or string concatenation.
///
/// Syntax: `x + y`
///
/// More information:
/// - [ECMAScript reference][spec].
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-addition-operator-plus
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Addition
Add,
/// The subtraction operator subtracts the two operands, producing their difference.
///
/// Syntax: `x - y`
///
/// More information:
/// - [ECMAScript reference][spec].
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-subtraction-operator-minus
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Subtraction
Sub,
/// The division operator produces the quotient of its operands where the left operand
/// is the dividend and the right operand is the divisor.
///
/// Syntax: `x / y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-MultiplicativeOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Division
Div,
/// The multiplication operator produces the product of the operands.
///
/// Syntax: `x * y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-MultiplicativeExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Multiplication
Mul,
/// The exponentiation operator returns the result of raising the first operand to
/// the power of the second operand.
///
/// Syntax: `x ** y`
///
/// The exponentiation operator is right-associative. a ** b ** c is equal to a ** (b ** c).
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-exp-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Exponentiation
Exp,
/// The remainder operator returns the remainder left over when one operand is divided by a second operand.
///
/// Syntax: `x % y`
///
/// The remainder operator always takes the sign of the dividend.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-MultiplicativeOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Remainder
Mod,
}
impl ArithmeticOp {
/// Retrieves the operation as a static string.
const fn as_str(self) -> &'static str {
match self {
Self::Add => "+",
Self::Sub => "-",
Self::Div => "/",
Self::Mul => "*",
Self::Exp => "**",
Self::Mod => "%",
}
}
}
impl Display for ArithmeticOp {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{}", self.as_str())
}
}
/// A bitwise operator is an operator used to perform bitwise operations
/// on bit patterns or binary numerals that involve the manipulation of individual bits.
///
/// More information:
/// - [MDN documentation][mdn]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Bitwise
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BitwiseOp {
/// Performs the AND operation on each pair of bits. a AND b yields 1 only if both a and b are 1.
///
/// Syntax: `x & y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-BitwiseANDExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_AND
And,
/// Performs the OR operation on each pair of bits. a OR b yields 1 if either a or b is 1.
///
/// Syntax: `x | y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-BitwiseORExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_OR
Or,
/// Performs the XOR operation on each pair of bits. a XOR b yields 1 if a and b are different.
///
/// Syntax: `x ^ y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-BitwiseXORExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_XOR
Xor,
/// This operator shifts the first operand the specified number of bits to the left.
///
/// Syntax: `x << y`
///
/// Excess bits shifted off to the left are discarded. Zero bits are shifted in from the right.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-left-shift-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Left_shift
Shl,
/// This operator shifts the first operand the specified number of bits to the right.
///
/// Syntax: `x >> y`
///
/// Excess bits shifted off to the right are discarded. Copies of the leftmost bit
/// are shifted in from the left. Since the new leftmost bit has the same value as
/// the previous leftmost bit, the sign bit (the leftmost bit) does not change.
/// Hence the name "sign-propagating".
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-signed-right-shift-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Right_shift
Shr,
/// This operator shifts the first operand the specified number of bits to the right.
///
/// Syntax: `x >>> y`
///
/// Excess bits shifted off to the right are discarded. Zero bits are shifted in
/// from the left. The sign bit becomes 0, so the result is always non-negative.
/// Unlike the other bitwise operators, zero-fill right shift returns an unsigned 32-bit integer.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-unsigned-right-shift-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Unsigned_right_shift
UShr,
}
impl BitwiseOp {
/// Retrieves the operation as a static string.
const fn as_str(self) -> &'static str {
match self {
Self::And => "&",
Self::Or => "|",
Self::Xor => "^",
Self::Shl => "<<",
Self::Shr => ">>",
Self::UShr => ">>>",
}
}
}
impl Display for BitwiseOp {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{}", self.as_str())
}
}
/// A relational operator compares its operands and returns a logical value based on whether the relation is true.
///
/// The operands can be numerical, string, logical, or object values. Strings are compared based on standard
/// lexicographical ordering, using Unicode values. In most cases, if the two operands are not of the same type,
/// JavaScript attempts to convert them to an appropriate type for the comparison. This behavior generally results in
/// comparing the operands numerically. The sole exceptions to type conversion within comparisons involve the `===` and `!==`
/// operators, which perform strict equality and inequality comparisons. These operators do not attempt to convert the operands
/// to compatible types before checking equality.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: tc39.es/ecma262/#sec-testing-and-comparison-operations
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Comparison
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RelationalOp {
/// The equality operator converts the operands if they are not of the same type, then applies
/// strict comparison.
///
/// Syntax: `y == y`
///
/// If both operands are objects, then JavaScript compares internal references which are equal
/// when operands refer to the same object in memory.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-abstract-equality-comparison
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Equality
Equal,
/// The inequality operator returns `true` if the operands are not equal.
///
/// Syntax: `x != y`
///
/// If the two operands are not of the same type, JavaScript attempts to convert the operands
/// to an appropriate type for the comparison. If both operands are objects, then JavaScript
/// compares internal references which are not equal when operands refer to different objects
/// in memory.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-EqualityExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Inequality
NotEqual,
/// The identity operator returns `true` if the operands are strictly equal **with no type
/// conversion**.
///
/// Syntax: `x === y`
///
/// Returns `true` if the operands are equal and of the same type.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-strict-equality-comparison
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Identity
StrictEqual,
/// The non-identity operator returns `true` if the operands **are not equal and/or not of the
/// same type**.
///
/// Syntax: `x !== y`
///
/// Returns `true` if the operands are of the same type but not equal, or are of different type.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-EqualityExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Nonidentity>
StrictNotEqual,
/// The greater than operator returns `true` if the left operand is greater than the right
/// operand.
///
/// Syntax: `x > y`
///
/// Returns `true` if the left operand is greater than the right operand.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-RelationalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Greater_than_operator
GreaterThan,
/// The greater than or equal operator returns `true` if the left operand is greater than or
/// equal to the right operand.
///
/// Syntax: `x >= y`
///
/// Returns `true` if the left operand is greater than the right operand.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-RelationalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Greater_than_operator
GreaterThanOrEqual,
/// The less than operator returns `true` if the left operand is less than the right operand.
///
/// Syntax: `x < y`
///
/// Returns `true` if the left operand is less than the right operand.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-RelationalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Less_than_operator
LessThan,
/// The less than or equal operator returns `true` if the left operand is less than or equal to
/// the right operand.
///
/// Syntax: `x <= y`
///
/// Returns `true` if the left operand is less than or equal to the right operand.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-RelationalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Less_than_or_equal_operator
LessThanOrEqual,
/// The `in` operator returns `true` if the specified property is in the specified object or
/// its prototype chain.
///
/// Syntax: `prop in object`
///
/// Returns `true` the specified property is in the specified object or its prototype chain.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-RelationalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in
In,
/// The `instanceof` operator returns `true` if the specified object is an instance of the
/// right hand side object.
///
/// Syntax: `obj instanceof Object`
///
/// Returns `true` the `prototype` property of the right hand side constructor appears anywhere
/// in the prototype chain of the object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-RelationalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof
InstanceOf,
}
impl RelationalOp {
/// Retrieves the operation as a static string.
const fn as_str(self) -> &'static str {
match self {
Self::Equal => "==",
Self::NotEqual => "!=",
Self::StrictEqual => "===",
Self::StrictNotEqual => "!==",
Self::GreaterThan => ">",
Self::GreaterThanOrEqual => ">=",
Self::LessThan => "<",
Self::LessThanOrEqual => "<=",
Self::In => "in",
Self::InstanceOf => "instanceof",
}
}
}
impl Display for RelationalOp {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{}", self.as_str())
}
}
/// Logical operators are typically used with Boolean (logical) values; when they are, they return a Boolean value.
///
/// However, the `&&` and `||` operators actually return the value of one of the specified operands,
/// so if these operators are used with non-Boolean values, they may return a non-Boolean value.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-binary-logical-operators
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Logical
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LogicalOp {
/// The logical AND operator returns the value of the first operand if it can be coerced into `false`;
/// otherwise, it returns the second operand.
///
/// Syntax: `x && y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-LogicalANDExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators#Logical_AND
And,
/// The logical OR operator returns the value the first operand if it can be coerced into `true`;
/// otherwise, it returns the second operand.
///
/// Syntax: `x || y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-LogicalORExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators#Logical_OR
Or,
/// The nullish coalescing operator is a logical operator that returns the second operand
/// when its first operand is null or undefined, and otherwise returns its first operand.
///
/// Syntax: `x ?? y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-CoalesceExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator
Coalesce,
}
impl LogicalOp {
/// Retrieves the operation as a static string.
const fn as_str(self) -> &'static str {
match self {
Self::And => "&&",
Self::Or => "||",
Self::Coalesce => "??",
}
}
}
impl Display for LogicalOp {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{}", self.as_str())
}
}

View File

@@ -0,0 +1,103 @@
use crate::{
expression::Expression,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
};
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
/// The `conditional` (ternary) operation is the only ECMAScript operation that takes three
/// operands.
///
/// This operation takes three operands: a condition followed by a question mark (`?`),
/// then an expression to execute `if` the condition is truthy followed by a colon (`:`),
/// and finally the expression to execute if the condition is `false`.
/// This operator is frequently used as a shortcut for the `if` statement.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ConditionalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Literals
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Conditional {
condition: Box<Expression>,
if_true: Box<Expression>,
if_false: Box<Expression>,
}
impl Conditional {
/// Gets the condition of the `Conditional` expression.
#[inline]
#[must_use]
pub const fn condition(&self) -> &Expression {
&self.condition
}
/// Gets the expression returned if `condition` is truthy.
#[inline]
#[must_use]
pub const fn if_true(&self) -> &Expression {
&self.if_true
}
/// Gets the expression returned if `condition` is falsy.
#[inline]
#[must_use]
pub const fn if_false(&self) -> &Expression {
&self.if_false
}
/// Creates a `Conditional` AST Expression.
#[inline]
#[must_use]
pub fn new(condition: Expression, if_true: Expression, if_false: Expression) -> Self {
Self {
condition: Box::new(condition),
if_true: Box::new(if_true),
if_false: Box::new(if_false),
}
}
}
impl ToInternedString for Conditional {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
format!(
"{} ? {} : {}",
self.condition().to_interned_string(interner),
self.if_true().to_interned_string(interner),
self.if_false().to_interned_string(interner)
)
}
}
impl From<Conditional> for Expression {
#[inline]
fn from(cond_op: Conditional) -> Self {
Self::Conditional(cond_op)
}
}
impl VisitWith for Conditional {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.condition));
try_break!(visitor.visit_expression(&self.if_true));
visitor.visit_expression(&self.if_false)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.condition));
try_break!(visitor.visit_expression_mut(&mut self.if_true));
visitor.visit_expression_mut(&mut self.if_false)
}
}

View File

@@ -0,0 +1,20 @@
//! Operator expression nodes.
//!
//! An [operator][op] expression is an expression that takes an operator (such as `+`, `typeof`, `+=`)
//! and one or more expressions and returns an expression as a result.
//! An operator expression can be any of the following:
//!
//! - A [`Unary`] expression.
//! - An [`Assign`] expression.
//! - A [`Binary`] expression.
//! - A [`Conditional`] expression.
//!
//! [op]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators
mod conditional;
pub mod assign;
pub mod binary;
pub mod unary;
pub use self::{assign::Assign, binary::Binary, conditional::Conditional, unary::Unary};

View File

@@ -0,0 +1,102 @@
//! Unary expression nodes.
//!
//! A Binary expression comprises any operation applied to a single expression. Some examples include:
//!
//! - [Increment and decrement operations][inc] (`++`, `--`).
//! - The [`delete`][del] operator.
//! - The [bitwise NOT][not] operator (`~`).
//!
//! The full list of valid unary operators is defined in [`UnaryOp`].
//!
//! [inc]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators#increment_and_decrement
//! [del]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete
//! [not]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_NOT
mod op;
use core::ops::ControlFlow;
pub use op::*;
use boa_interner::{Interner, ToInternedString};
use crate::expression::Expression;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
/// A unary expression is an operation with only one operand.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-UnaryExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Unary_operators
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Unary {
op: UnaryOp,
target: Box<Expression>,
}
impl Unary {
/// Creates a new `UnaryOp` AST Expression.
#[inline]
#[must_use]
pub fn new(op: UnaryOp, target: Expression) -> Self {
Self {
op,
target: Box::new(target),
}
}
/// Gets the unary operation of the Expression.
#[inline]
#[must_use]
pub const fn op(&self) -> UnaryOp {
self.op
}
/// Gets the target of this unary operator.
#[inline]
#[must_use]
pub fn target(&self) -> &Expression {
self.target.as_ref()
}
}
impl ToInternedString for Unary {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
let space = match self.op {
UnaryOp::TypeOf | UnaryOp::Delete | UnaryOp::Void => " ",
_ => "",
};
format!(
"{}{space}{}",
self.op,
self.target.to_interned_string(interner)
)
}
}
impl From<Unary> for Expression {
#[inline]
fn from(op: Unary) -> Self {
Self::Unary(op)
}
}
impl VisitWith for Unary {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_expression(&self.target)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_expression_mut(&mut self.target)
}
}

View File

@@ -0,0 +1,216 @@
/// A unary operator is one that takes a single operand/argument and performs an operation.
///
/// A unary operation is an operation with only one operand. This operand comes either
/// before or after the operator. Unary operators are more efficient than standard JavaScript
/// function calls.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-UnaryExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Unary
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UnaryOp {
/// The increment operator increments (adds one to) its operand and returns a value.
///
/// Syntax: `++x`
///
/// This operator increments and returns the value after incrementing.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-postfix-increment-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Increment
IncrementPost,
/// The increment operator increments (adds one to) its operand and returns a value.
///
/// Syntax: `x++`
///
/// This operator increments and returns the value before incrementing.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-prefix-increment-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Increment
IncrementPre,
/// The decrement operator decrements (subtracts one from) its operand and returns a value.
///
/// Syntax: `--x`
///
/// This operator decrements and returns the value before decrementing.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-postfix-decrement-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Decrement
DecrementPost,
/// The decrement operator decrements (subtracts one from) its operand and returns a value.
///
/// Syntax: `x--`
///
/// This operator decrements the operand and returns the value after decrementing.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-prefix-decrement-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Decrement
DecrementPre,
/// The unary negation operator precedes its operand and negates it.
///
/// Syntax: `-x`
///
/// Converts non-numbers data types to numbers like unary plus,
/// however, it performs an additional operation, negation.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-unary-minus-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Unary_negation
Minus,
/// The unary plus operator attempts to convert the operand into a number, if it isn't already.
///
/// Syntax: `+x`
///
/// Although unary negation (`-`) also can convert non-numbers, unary plus is the fastest and preferred
/// way of converting something into a number, because it does not perform any other operations on the number.
/// It can convert `string` representations of integers and floats, as well as the non-string values `true`, `false`, and `null`.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-unary-plus-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Unary_plus
Plus,
/// Returns `false` if its single operand can be converted to `true`; otherwise, returns `true`.
///
/// Syntax: `!x`
///
/// Boolean values simply get inverted: `!true === false` and `!false === true`.
/// Non-boolean values get converted to boolean values first, then are negated.
/// This means that it is possible to use a couple of NOT operators in series to explicitly
/// force the conversion of any value to the corresponding boolean primitive.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-logical-not-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators#Logical_NOT
Not,
/// Performs the NOT operator on each bit.
///
/// Syntax: `~x`
///
/// NOT `a` yields the inverted value (or one's complement) of `a`.
/// Bitwise NOTing any number x yields -(x + 1). For example, ~-5 yields 4.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-bitwise-not-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_NOT
Tilde,
/// The `typeof` operator returns a string indicating the type of the unevaluated operand.
///
/// Syntax: `typeof x` or `typeof(x)`
///
/// The `typeof` is a JavaScript keyword that will return the type of a variable when you call it.
/// You can use this to validate function parameters or check if variables are defined.
/// There are other uses as well.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-typeof-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof
TypeOf,
/// The JavaScript `delete` operator removes a property from an object.
///
/// Syntax: `delete x`
///
/// Unlike what common belief suggests, the delete operator has nothing to do with
/// directly freeing memory. Memory management is done indirectly via breaking references.
/// If no more references to the same property are held, it is eventually released automatically.
///
/// The `delete` operator returns `true` for all cases except when the property is an
/// [own](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty)
/// [non-configurable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cant_delete)
/// property, in which case, `false` is returned in non-strict mode.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-delete-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete
Delete,
/// The `void` operator evaluates the given `expression` and then returns `undefined`.
///
/// Syntax: `void x`
///
/// This operator allows evaluating expressions that produce a value into places where an
/// expression that evaluates to `undefined` is desired.
/// The `void` operator is often used merely to obtain the `undefined` primitive value, usually using `void(0)`
/// (which is equivalent to `void 0`). In these cases, the global variable undefined can be used.
///
/// When using an [immediately-invoked function expression](https://developer.mozilla.org/en-US/docs/Glossary/IIFE),
/// `void` can be used to force the function keyword to be treated as an expression instead of a declaration.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-void-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void
Void,
}
impl UnaryOp {
/// Retrieves the operation as a static string.
const fn as_str(self) -> &'static str {
match self {
Self::IncrementPost | Self::IncrementPre => "++",
Self::DecrementPost | Self::DecrementPre => "--",
Self::Plus => "+",
Self::Minus => "-",
Self::Not => "!",
Self::Tilde => "~",
Self::Delete => "delete",
Self::TypeOf => "typeof",
Self::Void => "void",
}
}
}
impl std::fmt::Display for UnaryOp {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}

View File

@@ -0,0 +1,249 @@
use super::{access::PropertyAccessField, Expression};
use crate::{
function::PrivateName,
join_nodes, try_break,
visitor::{VisitWith, Visitor, VisitorMut},
};
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
/// List of valid operations in an [`Optional`] chain.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum OptionalOperationKind {
/// A property access (`a?.prop`).
SimplePropertyAccess {
/// The field accessed.
field: PropertyAccessField,
},
/// A private property access (`a?.#prop`).
PrivatePropertyAccess {
/// The private property accessed.
field: PrivateName,
},
/// A function call (`a?.(arg)`).
Call {
/// The args passed to the function call.
args: Box<[Expression]>,
},
}
impl VisitWith for OptionalOperationKind {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::SimplePropertyAccess { field } => visitor.visit_property_access_field(field),
Self::PrivatePropertyAccess { field } => visitor.visit_private_name(field),
Self::Call { args } => {
for arg in args.iter() {
try_break!(visitor.visit_expression(arg));
}
ControlFlow::Continue(())
}
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::SimplePropertyAccess { field } => visitor.visit_property_access_field_mut(field),
Self::PrivatePropertyAccess { field } => visitor.visit_private_name_mut(field),
Self::Call { args } => {
for arg in args.iter_mut() {
try_break!(visitor.visit_expression_mut(arg));
}
ControlFlow::Continue(())
}
}
}
}
/// Operation within an [`Optional`] chain.
///
/// An operation within an `Optional` chain can be either shorted or non-shorted. A shorted operation
/// (`?.item`) will force the expression to return `undefined` if the target is `undefined` or `null`.
/// In contrast, a non-shorted operation (`.prop`) will try to access the property, even if the target
/// is `undefined` or `null`.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct OptionalOperation {
kind: OptionalOperationKind,
shorted: bool,
}
impl OptionalOperation {
/// Creates a new `OptionalOperation`.
#[inline]
#[must_use]
pub const fn new(kind: OptionalOperationKind, shorted: bool) -> Self {
Self { kind, shorted }
}
/// Gets the kind of operation.
#[inline]
#[must_use]
pub const fn kind(&self) -> &OptionalOperationKind {
&self.kind
}
/// Returns `true` if the operation short-circuits the [`Optional`] chain when the target is
/// `undefined` or `null`.
#[inline]
#[must_use]
pub const fn shorted(&self) -> bool {
self.shorted
}
}
impl ToInternedString for OptionalOperation {
fn to_interned_string(&self, interner: &Interner) -> String {
let mut buf = if self.shorted {
String::from("?.")
} else {
if let OptionalOperationKind::SimplePropertyAccess {
field: PropertyAccessField::Const(name),
} = &self.kind
{
return format!(".{}", interner.resolve_expect(*name));
}
if let OptionalOperationKind::PrivatePropertyAccess { field } = &self.kind {
return format!(".#{}", interner.resolve_expect(field.description()));
}
String::new()
};
buf.push_str(&match &self.kind {
OptionalOperationKind::SimplePropertyAccess { field } => match field {
PropertyAccessField::Const(name) => interner.resolve_expect(*name).to_string(),
PropertyAccessField::Expr(expr) => {
format!("[{}]", expr.to_interned_string(interner))
}
},
OptionalOperationKind::PrivatePropertyAccess { field } => {
format!("#{}", interner.resolve_expect(field.description()))
}
OptionalOperationKind::Call { args } => format!("({})", join_nodes(interner, args)),
});
buf
}
}
impl VisitWith for OptionalOperation {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_optional_operation_kind(&self.kind)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_optional_operation_kind_mut(&mut self.kind)
}
}
/// An optional chain expression, as defined by the [spec].
///
/// [Optional chaining][mdn] allows for short-circuiting property accesses and function calls, which
/// will return `undefined` instead of returning an error if the access target or the call is
/// either `undefined` or `null`.
///
/// An example of optional chaining:
///
/// ```Javascript
/// const adventurer = {
/// name: 'Alice',
/// cat: {
/// name: 'Dinah'
/// }
/// };
///
/// console.log(adventurer.cat?.name); // Dinah
/// console.log(adventurer.dog?.name); // undefined
/// ```
///
/// [spec]: https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#prod-OptionalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Optional {
target: Box<Expression>,
chain: Box<[OptionalOperation]>,
}
impl VisitWith for Optional {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.target));
for op in self.chain.iter() {
try_break!(visitor.visit_optional_operation(op));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.target));
for op in self.chain.iter_mut() {
try_break!(visitor.visit_optional_operation_mut(op));
}
ControlFlow::Continue(())
}
}
impl Optional {
/// Creates a new `Optional` expression.
#[inline]
#[must_use]
pub fn new(target: Expression, chain: Box<[OptionalOperation]>) -> Self {
Self {
target: Box::new(target),
chain,
}
}
/// Gets the target of this `Optional` expression.
#[inline]
#[must_use]
pub fn target(&self) -> &Expression {
self.target.as_ref()
}
/// Gets the chain of accesses and calls that will be applied to the target at runtime.
#[inline]
#[must_use]
pub fn chain(&self) -> &[OptionalOperation] {
self.chain.as_ref()
}
}
impl From<Optional> for Expression {
fn from(opt: Optional) -> Self {
Self::Optional(opt)
}
}
impl ToInternedString for Optional {
fn to_interned_string(&self, interner: &Interner) -> String {
let mut buf = self.target.to_interned_string(interner);
for item in &*self.chain {
buf.push_str(&item.to_interned_string(interner));
}
buf
}
}

View File

@@ -0,0 +1,78 @@
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use super::Expression;
/// The `spread` operator allows an iterable such as an array expression or string to be
/// expanded.
///
/// Syntax: `...x`
///
/// It expands array expressions or strings in places where zero or more arguments (for
/// function calls) or elements (for array literals)
/// are expected, or an object expression to be expanded in places where zero or more key-value
/// pairs (for object literals) are expected.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-SpreadElement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Spread {
target: Box<Expression>,
}
impl Spread {
/// Gets the target expression to be expanded by the spread operator.
#[inline]
#[must_use]
pub const fn target(&self) -> &Expression {
&self.target
}
/// Creates a `Spread` AST Expression.
#[inline]
#[must_use]
pub fn new(target: Expression) -> Self {
Self {
target: Box::new(target),
}
}
}
impl ToInternedString for Spread {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
format!("...{}", self.target().to_interned_string(interner))
}
}
impl From<Spread> for Expression {
#[inline]
fn from(spread: Spread) -> Self {
Self::Spread(spread)
}
}
impl VisitWith for Spread {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_expression(&self.target)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_expression_mut(&mut self.target)
}
}

View File

@@ -0,0 +1,132 @@
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use super::Expression;
/// A [`TaggedTemplate`][moz] expression, as defined by the [spec].
///
/// `TaggedTemplate`s are a type of template literals that are parsed by a custom function to generate
/// arbitrary objects from the inner strings and expressions.
///
/// [moz]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates
/// [spec]: https://tc39.es/ecma262/#sec-tagged-templates
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct TaggedTemplate {
tag: Box<Expression>,
raws: Box<[Sym]>,
cookeds: Box<[Option<Sym>]>,
exprs: Box<[Expression]>,
}
impl TaggedTemplate {
/// Creates a new tagged template with a tag, the list of raw strings, the cooked strings and
/// the expressions.
#[inline]
#[must_use]
pub fn new(
tag: Expression,
raws: Box<[Sym]>,
cookeds: Box<[Option<Sym>]>,
exprs: Box<[Expression]>,
) -> Self {
Self {
tag: tag.into(),
raws,
cookeds,
exprs,
}
}
/// Gets the tag function of the template.
#[inline]
#[must_use]
pub const fn tag(&self) -> &Expression {
&self.tag
}
/// Gets the inner raw strings of the template.
#[inline]
#[must_use]
pub const fn raws(&self) -> &[Sym] {
&self.raws
}
/// Gets the cooked strings of the template.
#[inline]
#[must_use]
pub const fn cookeds(&self) -> &[Option<Sym>] {
&self.cookeds
}
/// Gets the interpolated expressions of the template.
#[inline]
#[must_use]
pub const fn exprs(&self) -> &[Expression] {
&self.exprs
}
}
impl ToInternedString for TaggedTemplate {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
let mut buf = format!("{}`", self.tag.to_interned_string(interner));
for (&raw, expr) in self.raws.iter().zip(self.exprs.iter()) {
buf.push_str(&format!(
"{}${{{}}}",
interner.resolve_expect(raw),
expr.to_interned_string(interner)
));
}
buf.push('`');
buf
}
}
impl From<TaggedTemplate> for Expression {
#[inline]
fn from(template: TaggedTemplate) -> Self {
Self::TaggedTemplate(template)
}
}
impl VisitWith for TaggedTemplate {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.tag));
for raw in self.raws.iter() {
try_break!(visitor.visit_sym(raw));
}
for cooked in self.cookeds.iter().flatten() {
try_break!(visitor.visit_sym(cooked));
}
for expr in self.exprs.iter() {
try_break!(visitor.visit_expression(expr));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.tag));
for raw in self.raws.iter_mut() {
try_break!(visitor.visit_sym_mut(raw));
}
for cooked in self.cookeds.iter_mut().flatten() {
try_break!(visitor.visit_sym_mut(cooked));
}
for expr in self.exprs.iter_mut() {
try_break!(visitor.visit_expression_mut(expr));
}
ControlFlow::Continue(())
}
}

View File

@@ -0,0 +1,90 @@
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use super::Expression;
/// The `yield` keyword is used to pause and resume a generator function
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-YieldExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Yield {
target: Option<Box<Expression>>,
delegate: bool,
}
impl Yield {
/// Gets the target expression of this `Yield` statement.
#[inline]
pub fn target(&self) -> Option<&Expression> {
self.target.as_ref().map(Box::as_ref)
}
/// Returns `true` if this `Yield` statement delegates to another generator or iterable object.
#[inline]
#[must_use]
pub const fn delegate(&self) -> bool {
self.delegate
}
/// Creates a `Yield` AST Expression.
#[inline]
#[must_use]
pub fn new(expr: Option<Expression>, delegate: bool) -> Self {
Self {
target: expr.map(Box::new),
delegate,
}
}
}
impl From<Yield> for Expression {
#[inline]
fn from(r#yield: Yield) -> Self {
Self::Yield(r#yield)
}
}
impl ToInternedString for Yield {
#[inline]
fn to_interned_string(&self, interner: &Interner) -> String {
let y = if self.delegate { "yield*" } else { "yield" };
if let Some(ex) = self.target() {
format!("{y} {}", ex.to_interned_string(interner))
} else {
y.to_owned()
}
}
}
impl VisitWith for Yield {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(expr) = &self.target {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(expr) = &mut self.target {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
}

View File

@@ -0,0 +1,118 @@
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
expression::{Expression, Identifier},
join_nodes, StatementList,
};
use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow;
use super::FormalParameterList;
/// An arrow function expression, as defined by the [spec].
///
/// An [arrow function][mdn] expression is a syntactically compact alternative to a regular function
/// expression. Arrow function expressions are ill suited as methods, and they cannot be used as
/// constructors. Arrow functions cannot be used as constructors and will throw an error when
/// used with new.
///
/// [spec]: https://tc39.es/ecma262/#prod-ArrowFunction
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct ArrowFunction {
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
}
impl ArrowFunction {
/// Creates a new `ArrowFunctionDecl` AST Expression.
#[inline]
#[must_use]
pub const fn new(
name: Option<Identifier>,
params: FormalParameterList,
body: StatementList,
) -> Self {
Self {
name,
parameters: params,
body,
}
}
/// Gets the name of the function declaration.
#[inline]
#[must_use]
pub const fn name(&self) -> Option<Identifier> {
self.name
}
/// Sets the name of the function declaration.
#[inline]
pub fn set_name(&mut self, name: Option<Identifier>) {
self.name = name;
}
/// Gets the list of parameters of the arrow function.
#[inline]
#[must_use]
pub const fn parameters(&self) -> &FormalParameterList {
&self.parameters
}
/// Gets the body of the arrow function.
#[inline]
#[must_use]
pub const fn body(&self) -> &StatementList {
&self.body
}
}
impl ToIndentedString for ArrowFunction {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = format!("({}", join_nodes(interner, self.parameters.as_ref()));
if self.body().statements().is_empty() {
buf.push_str(") => {}");
} else {
buf.push_str(&format!(
") => {{\n{}{}}}",
self.body.to_indented_string(interner, indentation + 1),
" ".repeat(indentation)
));
}
buf
}
}
impl From<ArrowFunction> for Expression {
fn from(decl: ArrowFunction) -> Self {
Self::ArrowFunction(decl)
}
}
impl VisitWith for ArrowFunction {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(ident) = &self.name {
try_break!(visitor.visit_identifier(ident));
}
try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_statement_list(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(ident) = &mut self.name {
try_break!(visitor.visit_identifier_mut(ident));
}
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_statement_list_mut(&mut self.body)
}
}

View File

@@ -0,0 +1,118 @@
use std::ops::ControlFlow;
use super::FormalParameterList;
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
expression::{Expression, Identifier},
join_nodes, StatementList,
};
use boa_interner::{Interner, ToIndentedString};
/// An async arrow function expression, as defined by the [spec].
///
/// An [async arrow function][mdn] expression is a syntactically compact alternative to a regular function
/// expression. Arrow function expressions are ill suited as methods, and they cannot be used as
/// constructors. Arrow functions cannot be used as constructors and will throw an error when
/// used with new.
///
/// [spec]: https://tc39.es/ecma262/#prod-AsyncArrowFunction
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct AsyncArrowFunction {
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
}
impl AsyncArrowFunction {
/// Creates a new `AsyncArrowFunction` AST Expression.
#[inline]
#[must_use]
pub const fn new(
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
) -> Self {
Self {
name,
parameters,
body,
}
}
/// Gets the name of the function declaration.
#[inline]
#[must_use]
pub const fn name(&self) -> Option<Identifier> {
self.name
}
/// Sets the name of the function declaration.
#[inline]
pub fn set_name(&mut self, name: Option<Identifier>) {
self.name = name;
}
/// Gets the list of parameters of the arrow function.
#[inline]
#[must_use]
pub const fn parameters(&self) -> &FormalParameterList {
&self.parameters
}
/// Gets the body of the arrow function.
#[inline]
#[must_use]
pub const fn body(&self) -> &StatementList {
&self.body
}
}
impl ToIndentedString for AsyncArrowFunction {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = format!("async ({}", join_nodes(interner, self.parameters.as_ref()));
if self.body().statements().is_empty() {
buf.push_str(") => {}");
} else {
buf.push_str(&format!(
") => {{\n{}{}}}",
self.body.to_indented_string(interner, indentation + 1),
" ".repeat(indentation)
));
}
buf
}
}
impl From<AsyncArrowFunction> for Expression {
fn from(decl: AsyncArrowFunction) -> Self {
Self::AsyncArrowFunction(decl)
}
}
impl VisitWith for AsyncArrowFunction {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(ident) = &self.name {
try_break!(visitor.visit_identifier(ident));
}
try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_statement_list(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(ident) = &mut self.name {
try_break!(visitor.visit_identifier_mut(ident));
}
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_statement_list_mut(&mut self.body)
}
}

View File

@@ -0,0 +1,138 @@
//! Async Function Expression.
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
expression::{Expression, Identifier},
join_nodes, Declaration, StatementList,
};
use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow;
use super::FormalParameterList;
/// An async function definition, as defined by the [spec].
///
/// An [async function][mdn] is a function where await expressions are allowed within it.
/// The async and await keywords enable asynchronous programming on Javascript without the use
/// of promise chains.
///
/// [spec]: https://tc39.es/ecma262/#sec-async-function-definitions
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct AsyncFunction {
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
has_binding_identifier: bool,
}
impl AsyncFunction {
/// Creates a new function expression
#[inline]
#[must_use]
pub const fn new(
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
has_binding_identifier: bool,
) -> Self {
Self {
name,
parameters,
body,
has_binding_identifier,
}
}
/// Gets the name of the function declaration.
#[inline]
#[must_use]
pub const fn name(&self) -> Option<Identifier> {
self.name
}
/// Gets the list of parameters of the function declaration.
#[inline]
#[must_use]
pub const fn parameters(&self) -> &FormalParameterList {
&self.parameters
}
/// Gets the body of the function declaration.
#[inline]
#[must_use]
pub const fn body(&self) -> &StatementList {
&self.body
}
/// Returns whether the function expression has a binding identifier.
#[inline]
#[must_use]
pub const fn has_binding_identifier(&self) -> bool {
self.has_binding_identifier
}
}
impl ToIndentedString for AsyncFunction {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = "async function".to_owned();
if let Some(name) = self.name {
buf.push_str(&format!(" {}", interner.resolve_expect(name.sym())));
}
buf.push_str(&format!(
"({}",
join_nodes(interner, self.parameters.as_ref())
));
if self.body().statements().is_empty() {
buf.push_str(") {}");
} else {
buf.push_str(&format!(
") {{\n{}{}}}",
self.body.to_indented_string(interner, indentation + 1),
" ".repeat(indentation)
));
}
buf
}
}
impl From<AsyncFunction> for Expression {
#[inline]
fn from(expr: AsyncFunction) -> Self {
Self::AsyncFunction(expr)
}
}
impl From<AsyncFunction> for Declaration {
#[inline]
fn from(f: AsyncFunction) -> Self {
Self::AsyncFunction(f)
}
}
impl VisitWith for AsyncFunction {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(ident) = &self.name {
try_break!(visitor.visit_identifier(ident));
}
try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_statement_list(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(ident) = &mut self.name {
try_break!(visitor.visit_identifier_mut(ident));
}
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_statement_list_mut(&mut self.body)
}
}

View File

@@ -0,0 +1,130 @@
//! Async Generator Expression
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
block_to_string,
expression::{Expression, Identifier},
join_nodes, Declaration, StatementList,
};
use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow;
use super::FormalParameterList;
/// An async generator definition, as defined by the [spec].
///
/// An [async generator][mdn] combines async functions with generators, making it possible to use
/// `await` and `yield` expressions within the definition of the function.
///
/// [spec]: https://tc39.es/ecma262/#sec-async-generator-function-definitions
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function*
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct AsyncGenerator {
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
has_binding_identifier: bool,
}
impl AsyncGenerator {
/// Creates a new async generator expression
#[inline]
#[must_use]
pub const fn new(
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
has_binding_identifier: bool,
) -> Self {
Self {
name,
parameters,
body,
has_binding_identifier,
}
}
/// Gets the name of the async generator expression
#[inline]
#[must_use]
pub const fn name(&self) -> Option<Identifier> {
self.name
}
/// Gets the list of parameters of the async generator expression
#[inline]
#[must_use]
pub const fn parameters(&self) -> &FormalParameterList {
&self.parameters
}
/// Gets the body of the async generator expression
#[inline]
#[must_use]
pub const fn body(&self) -> &StatementList {
&self.body
}
/// Returns whether the function expression has a binding identifier.
#[inline]
#[must_use]
pub const fn has_binding_identifier(&self) -> bool {
self.has_binding_identifier
}
}
impl ToIndentedString for AsyncGenerator {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = "async function*".to_owned();
if let Some(name) = self.name {
buf.push_str(&format!(" {}", interner.resolve_expect(name.sym())));
}
buf.push_str(&format!(
"({}) {}",
join_nodes(interner, self.parameters.as_ref()),
block_to_string(&self.body, interner, indentation)
));
buf
}
}
impl From<AsyncGenerator> for Expression {
#[inline]
fn from(expr: AsyncGenerator) -> Self {
Self::AsyncGenerator(expr)
}
}
impl From<AsyncGenerator> for Declaration {
#[inline]
fn from(f: AsyncGenerator) -> Self {
Self::AsyncGenerator(f)
}
}
impl VisitWith for AsyncGenerator {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(ident) = &self.name {
try_break!(visitor.visit_identifier(ident));
}
try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_statement_list(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(ident) = &mut self.name {
try_break!(visitor.visit_identifier_mut(ident));
}
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_statement_list_mut(&mut self.body)
}
}

View File

@@ -0,0 +1,579 @@
use super::Function;
use crate::{
block_to_string,
expression::{Expression, Identifier},
join_nodes,
property::{MethodDefinition, PropertyName},
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
Declaration, StatementList, ToStringEscaped,
};
use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
use std::borrow::Cow;
use std::hash::Hash;
/// A class declaration, as defined by the [spec].
///
/// A [class][mdn] declaration defines a class with the specified methods, fields, and optional constructor.
/// Classes can be used to create objects, which can also be created through literals (using `{}`).
///
/// [spec]: https://tc39.es/ecma262/#sec-class-definitions
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Class {
name: Option<Identifier>,
super_ref: Option<Expression>,
pub(crate) constructor: Option<Function>,
pub(crate) elements: Box<[ClassElement]>,
has_binding_identifier: bool,
}
impl Class {
/// Creates a new class declaration.
#[inline]
#[must_use]
pub fn new(
name: Option<Identifier>,
super_ref: Option<Expression>,
constructor: Option<Function>,
elements: Box<[ClassElement]>,
has_binding_identifier: bool,
) -> Self {
Self {
name,
super_ref,
constructor,
elements,
has_binding_identifier,
}
}
/// Returns the name of the class.
#[inline]
#[must_use]
pub const fn name(&self) -> Option<Identifier> {
self.name
}
/// Returns the super class ref of the class.
#[inline]
#[must_use]
pub const fn super_ref(&self) -> Option<&Expression> {
self.super_ref.as_ref()
}
/// Returns the constructor of the class.
#[inline]
#[must_use]
pub const fn constructor(&self) -> Option<&Function> {
self.constructor.as_ref()
}
/// Gets the list of all fields defined on the class.
#[inline]
#[must_use]
pub const fn elements(&self) -> &[ClassElement] {
&self.elements
}
/// Returns whether the class has a binding identifier.
#[inline]
#[must_use]
pub const fn has_binding_identifier(&self) -> bool {
self.has_binding_identifier
}
}
impl ToIndentedString for Class {
fn to_indented_string(&self, interner: &Interner, indent_n: usize) -> String {
let class_name = self.name.map_or(Cow::Borrowed(""), |s| {
interner.resolve_expect(s.sym()).join(
Cow::Borrowed,
|utf16| Cow::Owned(utf16.to_string_escaped()),
true,
)
});
if self.elements.is_empty() && self.constructor().is_none() {
return format!(
"class {class_name}{} {{}}",
self.super_ref
.as_ref()
.map_or_else(String::new, |sup| format!(
" extends {}",
sup.to_interned_string(interner)
))
);
}
let indentation = " ".repeat(indent_n + 1);
let mut buf = format!(
"class {class_name}{} {{\n",
self.super_ref
.as_ref()
.map_or_else(String::new, |sup| format!(
"extends {}",
sup.to_interned_string(interner)
))
);
if let Some(expr) = &self.constructor {
buf.push_str(&format!(
"{indentation}constructor({}) {}\n",
join_nodes(interner, expr.parameters().as_ref()),
block_to_string(expr.body(), interner, indent_n + 1)
));
}
for element in self.elements.iter() {
buf.push_str(&match element {
ClassElement::MethodDefinition(name, method) => {
format!(
"{indentation}{}{}({}) {}\n",
match &method {
MethodDefinition::Get(_) => "get ",
MethodDefinition::Set(_) => "set ",
_ => "",
},
name.to_interned_string(interner),
match &method {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Generator(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::AsyncGenerator(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Async(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
},
match &method {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::Generator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::AsyncGenerator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::Async(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
},
)
}
ClassElement::StaticMethodDefinition(name, method) => {
format!(
"{indentation}static {}{}({}) {}\n",
match &method {
MethodDefinition::Get(_) => "get ",
MethodDefinition::Set(_) => "set ",
_ => "",
},
name.to_interned_string(interner),
match &method {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Generator(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::AsyncGenerator(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Async(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
},
match &method {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::Generator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::AsyncGenerator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::Async(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
},
)
}
ClassElement::FieldDefinition(name, field) => match field {
Some(expr) => {
format!(
"{indentation}{} = {};\n",
name.to_interned_string(interner),
expr.to_no_indent_string(interner, indent_n + 1)
)
}
None => {
format!("{indentation}{};\n", name.to_interned_string(interner),)
}
},
ClassElement::StaticFieldDefinition(name, field) => match field {
Some(expr) => {
format!(
"{indentation}static {} = {};\n",
name.to_interned_string(interner),
expr.to_no_indent_string(interner, indent_n + 1)
)
}
None => {
format!(
"{indentation}static {};\n",
name.to_interned_string(interner),
)
}
},
ClassElement::PrivateMethodDefinition(name, method) => {
format!(
"{indentation}{}#{}({}) {}\n",
match &method {
MethodDefinition::Get(_) => "get ",
MethodDefinition::Set(_) => "set ",
_ => "",
},
interner.resolve_expect(name.description()),
match &method {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Generator(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::AsyncGenerator(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Async(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
},
match &method {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::Generator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::AsyncGenerator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::Async(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
},
)
}
ClassElement::PrivateStaticMethodDefinition(name, method) => {
format!(
"{indentation}static {}#{}({}) {}\n",
match &method {
MethodDefinition::Get(_) => "get ",
MethodDefinition::Set(_) => "set ",
_ => "",
},
interner.resolve_expect(name.description()),
match &method {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Generator(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::AsyncGenerator(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
MethodDefinition::Async(expr) => {
join_nodes(interner, expr.parameters().as_ref())
}
},
match &method {
MethodDefinition::Get(expr)
| MethodDefinition::Set(expr)
| MethodDefinition::Ordinary(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::Generator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::AsyncGenerator(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
MethodDefinition::Async(expr) => {
block_to_string(expr.body(), interner, indent_n + 1)
}
},
)
}
ClassElement::PrivateFieldDefinition(name, field) => match field {
Some(expr) => {
format!(
"{indentation}#{} = {};\n",
interner.resolve_expect(name.description()),
expr.to_no_indent_string(interner, indent_n + 1)
)
}
None => {
format!(
"{indentation}#{};\n",
interner.resolve_expect(name.description()),
)
}
},
ClassElement::PrivateStaticFieldDefinition(name, field) => match field {
Some(expr) => {
format!(
"{indentation}static #{} = {};\n",
interner.resolve_expect(name.description()),
expr.to_no_indent_string(interner, indent_n + 1)
)
}
None => {
format!(
"{indentation}static #{};\n",
interner.resolve_expect(name.description()),
)
}
},
ClassElement::StaticBlock(statement_list) => {
format!(
"{indentation}static {}\n",
block_to_string(statement_list, interner, indent_n + 1)
)
}
});
}
buf.push('}');
buf
}
}
impl From<Class> for Expression {
fn from(expr: Class) -> Self {
Self::Class(Box::new(expr))
}
}
impl From<Class> for Declaration {
fn from(f: Class) -> Self {
Self::Class(f)
}
}
impl VisitWith for Class {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(ident) = &self.name {
try_break!(visitor.visit_identifier(ident));
}
if let Some(expr) = &self.super_ref {
try_break!(visitor.visit_expression(expr));
}
if let Some(func) = &self.constructor {
try_break!(visitor.visit_function(func));
}
for elem in self.elements.iter() {
try_break!(visitor.visit_class_element(elem));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(ident) = &mut self.name {
try_break!(visitor.visit_identifier_mut(ident));
}
if let Some(expr) = &mut self.super_ref {
try_break!(visitor.visit_expression_mut(expr));
}
if let Some(func) = &mut self.constructor {
try_break!(visitor.visit_function_mut(func));
}
for elem in self.elements.iter_mut() {
try_break!(visitor.visit_class_element_mut(elem));
}
ControlFlow::Continue(())
}
}
/// An element that can be within a [`Class`], as defined by the [spec].
///
/// [spec]: https://tc39.es/ecma262/#prod-ClassElement
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum ClassElement {
/// A method definition, including `get` and `set` accessors.
MethodDefinition(PropertyName, MethodDefinition),
/// A static method definition, accessible from the class constructor object.
StaticMethodDefinition(PropertyName, MethodDefinition),
/// A field definition.
FieldDefinition(PropertyName, Option<Expression>),
/// A static field definition, accessible from the class constructor object
StaticFieldDefinition(PropertyName, Option<Expression>),
/// A private method definition, only accessible inside the class declaration.
PrivateMethodDefinition(PrivateName, MethodDefinition),
/// A private static method definition, only accessible from static methods and fields inside
/// the class declaration.
PrivateStaticMethodDefinition(PrivateName, MethodDefinition),
/// A private field definition, only accessible inside the class declaration.
PrivateFieldDefinition(PrivateName, Option<Expression>),
/// A private static field definition, only accessible from static methods and fields inside the
/// class declaration.
PrivateStaticFieldDefinition(PrivateName, Option<Expression>),
/// A static block, where a class can have initialization logic for its static fields.
StaticBlock(StatementList),
}
impl VisitWith for ClassElement {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::MethodDefinition(pn, md) | Self::StaticMethodDefinition(pn, md) => {
try_break!(visitor.visit_property_name(pn));
visitor.visit_method_definition(md)
}
Self::FieldDefinition(pn, maybe_expr) | Self::StaticFieldDefinition(pn, maybe_expr) => {
try_break!(visitor.visit_property_name(pn));
if let Some(expr) = maybe_expr {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
Self::PrivateMethodDefinition(name, md)
| Self::PrivateStaticMethodDefinition(name, md) => {
try_break!(visitor.visit_private_name(name));
visitor.visit_method_definition(md)
}
Self::PrivateFieldDefinition(name, maybe_expr)
| Self::PrivateStaticFieldDefinition(name, maybe_expr) => {
try_break!(visitor.visit_private_name(name));
if let Some(expr) = maybe_expr {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
Self::StaticBlock(sl) => visitor.visit_statement_list(sl),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::MethodDefinition(pn, md) | Self::StaticMethodDefinition(pn, md) => {
try_break!(visitor.visit_property_name_mut(pn));
visitor.visit_method_definition_mut(md)
}
Self::FieldDefinition(pn, maybe_expr) | Self::StaticFieldDefinition(pn, maybe_expr) => {
try_break!(visitor.visit_property_name_mut(pn));
if let Some(expr) = maybe_expr {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
Self::PrivateMethodDefinition(name, md)
| Self::PrivateStaticMethodDefinition(name, md) => {
try_break!(visitor.visit_private_name_mut(name));
visitor.visit_method_definition_mut(md)
}
Self::PrivateFieldDefinition(name, maybe_expr)
| Self::PrivateStaticFieldDefinition(name, maybe_expr) => {
try_break!(visitor.visit_private_name_mut(name));
if let Some(expr) = maybe_expr {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
Self::StaticBlock(sl) => visitor.visit_statement_list_mut(sl),
}
}
}
/// A private name as defined by the [spec].
///
/// [spec]: https://tc39.es/ecma262/#sec-private-names
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PrivateName {
/// The `[[Description]]` internal slot of the private name.
description: Sym,
/// The indices of the private name are included to ensure that private names are unique.
pub(crate) indices: (usize, usize),
}
impl PrivateName {
/// Create a new private name.
#[inline]
#[must_use]
pub const fn new(description: Sym) -> Self {
Self {
description,
indices: (0, 0),
}
}
/// Get the description of the private name.
#[inline]
#[must_use]
pub const fn description(&self) -> Sym {
self.description
}
}
impl VisitWith for PrivateName {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_sym(&self.description)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_sym_mut(&mut self.description)
}
}

View File

@@ -0,0 +1,132 @@
use crate::{
block_to_string,
expression::{Expression, Identifier},
join_nodes, Declaration, StatementList,
};
use core::ops::ControlFlow;
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, ToIndentedString};
use super::FormalParameterList;
/// A generator definition, as defined by the [spec].
///
/// [Generators][mdn] are "resumable functions", which can be suspended during execution and
/// resumed at any later time. The main feature of a generator are `yield` expressions, which
/// specifies the value returned when a generator is suspended, and the point from which
/// the execution will resume.
///
/// [spec]: https://tc39.es/ecma262/#sec-generator-function-definitions
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Generator {
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
has_binding_identifier: bool,
}
impl Generator {
/// Creates a new generator expression
#[inline]
#[must_use]
pub const fn new(
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
has_binding_identifier: bool,
) -> Self {
Self {
name,
parameters,
body,
has_binding_identifier,
}
}
/// Gets the name of the generator declaration.
#[inline]
#[must_use]
pub const fn name(&self) -> Option<Identifier> {
self.name
}
/// Gets the list of parameters of the generator declaration.
#[inline]
#[must_use]
pub const fn parameters(&self) -> &FormalParameterList {
&self.parameters
}
/// Gets the body of the generator declaration.
#[inline]
#[must_use]
pub const fn body(&self) -> &StatementList {
&self.body
}
/// Returns whether the function expression has a binding identifier.
#[inline]
#[must_use]
pub const fn has_binding_identifier(&self) -> bool {
self.has_binding_identifier
}
}
impl ToIndentedString for Generator {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = "function*".to_owned();
if let Some(name) = self.name {
buf.push_str(&format!(" {}", interner.resolve_expect(name.sym())));
}
buf.push_str(&format!(
"({}) {}",
join_nodes(interner, self.parameters.as_ref()),
block_to_string(&self.body, interner, indentation)
));
buf
}
}
impl From<Generator> for Expression {
#[inline]
fn from(expr: Generator) -> Self {
Self::Generator(expr)
}
}
impl From<Generator> for Declaration {
#[inline]
fn from(f: Generator) -> Self {
Self::Generator(f)
}
}
impl VisitWith for Generator {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(ident) = &self.name {
try_break!(visitor.visit_identifier(ident));
}
try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_statement_list(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(ident) = &mut self.name {
try_break!(visitor.visit_identifier_mut(ident));
}
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_statement_list_mut(&mut self.body)
}
}

View File

@@ -0,0 +1,184 @@
//! Functions and classes nodes, as defined by the [spec].
//!
//! [Functions][func] are mainly subprograms that can be called by external code to execute a sequence of
//! statements (the *body* of the function). Javascript functions fall in several categories:
//!
//! - [`Function`]s.
//! - [`ArrowFunction`]s.
//! - [`AsyncArrowFunction`]s.
//! - [`Generator`]s.
//! - [`AsyncFunction`]s.
//! - [`AsyncGenerator`]s.
//!
//! All of them can be declared in either [declaration][decl] form or [expression][expr] form,
//! except from `ArrowFunction`s and `AsyncArrowFunction`s, which can only be declared in expression form.
//!
//! This module also contains [`Class`]es, which are templates for creating objects. Classes
//! can also be declared in either declaration or expression form.
//!
//! [spec]: https://tc39.es/ecma262/#sec-ecmascript-language-functions-and-classes
//! [func]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions
//! [decl]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function
//! [expr]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function
mod arrow_function;
mod async_arrow_function;
mod async_function;
mod async_generator;
mod class;
mod generator;
mod parameters;
pub use arrow_function::ArrowFunction;
pub use async_arrow_function::AsyncArrowFunction;
pub use async_function::AsyncFunction;
pub use async_generator::AsyncGenerator;
pub use class::{Class, ClassElement, PrivateName};
use core::ops::ControlFlow;
pub use generator::Generator;
pub use parameters::{FormalParameter, FormalParameterList, FormalParameterListFlags};
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{block_to_string, join_nodes, StatementList};
use boa_interner::{Interner, ToIndentedString};
use super::expression::{Expression, Identifier};
use super::Declaration;
/// A function definition, as defined by the [spec].
///
/// By default, functions return `undefined`. To return any other value, the function must have
/// a return statement that specifies the value to return.
///
/// More information:
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-function-definitions
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Function {
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
has_binding_identifier: bool,
}
impl Function {
/// Creates a new function expression.
#[inline]
#[must_use]
pub const fn new(
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
) -> Self {
Self {
name,
parameters,
body,
has_binding_identifier: false,
}
}
/// Creates a new function expression with an expression binding identifier.
#[inline]
#[must_use]
pub const fn new_with_binding_identifier(
name: Option<Identifier>,
parameters: FormalParameterList,
body: StatementList,
has_binding_identifier: bool,
) -> Self {
Self {
name,
parameters,
body,
has_binding_identifier,
}
}
/// Gets the name of the function declaration.
#[inline]
#[must_use]
pub const fn name(&self) -> Option<Identifier> {
self.name
}
/// Gets the list of parameters of the function declaration.
#[inline]
#[must_use]
pub const fn parameters(&self) -> &FormalParameterList {
&self.parameters
}
/// Gets the body of the function declaration.
#[inline]
#[must_use]
pub const fn body(&self) -> &StatementList {
&self.body
}
/// Returns whether the function expression has a binding identifier.
#[inline]
#[must_use]
pub const fn has_binding_identifier(&self) -> bool {
self.has_binding_identifier
}
}
impl ToIndentedString for Function {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = "function".to_owned();
if let Some(name) = self.name {
buf.push_str(&format!(" {}", interner.resolve_expect(name.sym())));
}
buf.push_str(&format!(
"({}) {}",
join_nodes(interner, self.parameters.as_ref()),
block_to_string(&self.body, interner, indentation)
));
buf
}
}
impl From<Function> for Expression {
#[inline]
fn from(expr: Function) -> Self {
Self::Function(expr)
}
}
impl From<Function> for Declaration {
#[inline]
fn from(f: Function) -> Self {
Self::Function(f)
}
}
impl VisitWith for Function {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(ident) = &self.name {
try_break!(visitor.visit_identifier(ident));
}
try_break!(visitor.visit_formal_parameter_list(&self.parameters));
visitor.visit_statement_list(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(ident) = &mut self.name {
try_break!(visitor.visit_identifier_mut(ident));
}
try_break!(visitor.visit_formal_parameter_list_mut(&mut self.parameters));
visitor.visit_statement_list_mut(&mut self.body)
}
}

View File

@@ -0,0 +1,287 @@
use crate::{
declaration::{Binding, Variable},
expression::Expression,
operations::bound_names,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
};
use bitflags::bitflags;
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
use rustc_hash::FxHashSet;
/// A list of `FormalParameter`s that describes the parameters of a function, as defined by the [spec].
///
/// [spec]: https://tc39.es/ecma262/#prod-FormalParameterList
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq)]
pub struct FormalParameterList {
parameters: Box<[FormalParameter]>,
flags: FormalParameterListFlags,
length: u32,
}
impl FormalParameterList {
/// Creates a new empty formal parameter list.
#[must_use]
pub fn new() -> Self {
Self {
parameters: Box::new([]),
flags: FormalParameterListFlags::default(),
length: 0,
}
}
/// Creates a `FormalParameterList` from a list of [`FormalParameter`]s.
#[must_use]
pub fn from_parameters(parameters: Vec<FormalParameter>) -> Self {
let mut flags = FormalParameterListFlags::default();
let mut length = 0;
let mut names = FxHashSet::default();
for parameter in &parameters {
let parameter_names = bound_names(parameter);
for name in parameter_names {
if name == Sym::ARGUMENTS {
flags |= FormalParameterListFlags::HAS_ARGUMENTS;
}
if names.contains(&name) {
flags |= FormalParameterListFlags::HAS_DUPLICATES;
} else {
names.insert(name);
}
}
if parameter.is_rest_param() {
flags |= FormalParameterListFlags::HAS_REST_PARAMETER;
}
if parameter.init().is_some() {
flags |= FormalParameterListFlags::HAS_EXPRESSIONS;
}
if parameter.is_rest_param() || parameter.init().is_some() || !parameter.is_identifier()
{
flags.remove(FormalParameterListFlags::IS_SIMPLE);
}
if !(flags.contains(FormalParameterListFlags::HAS_EXPRESSIONS)
|| parameter.is_rest_param()
|| parameter.init().is_some())
{
length += 1;
}
}
Self {
parameters: parameters.into(),
flags,
length,
}
}
/// Returns the length of the parameter list.
/// Note that this is not equal to the length of the parameters slice.
#[must_use]
pub const fn length(&self) -> u32 {
self.length
}
/// Returns the parameter list flags.
#[must_use]
pub const fn flags(&self) -> FormalParameterListFlags {
self.flags
}
/// Indicates if the parameter list is simple.
#[must_use]
pub const fn is_simple(&self) -> bool {
self.flags.contains(FormalParameterListFlags::IS_SIMPLE)
}
/// Indicates if the parameter list has duplicate parameters.
#[must_use]
pub const fn has_duplicates(&self) -> bool {
self.flags
.contains(FormalParameterListFlags::HAS_DUPLICATES)
}
/// Indicates if the parameter list has a rest parameter.
#[must_use]
pub const fn has_rest_parameter(&self) -> bool {
self.flags
.contains(FormalParameterListFlags::HAS_REST_PARAMETER)
}
/// Indicates if the parameter list has expressions in it's parameters.
#[must_use]
pub const fn has_expressions(&self) -> bool {
self.flags
.contains(FormalParameterListFlags::HAS_EXPRESSIONS)
}
/// Indicates if the parameter list has parameters named 'arguments'.
#[must_use]
pub const fn has_arguments(&self) -> bool {
self.flags.contains(FormalParameterListFlags::HAS_ARGUMENTS)
}
}
impl From<Vec<FormalParameter>> for FormalParameterList {
fn from(parameters: Vec<FormalParameter>) -> Self {
Self::from_parameters(parameters)
}
}
impl From<FormalParameter> for FormalParameterList {
fn from(parameter: FormalParameter) -> Self {
Self::from_parameters(vec![parameter])
}
}
impl AsRef<[FormalParameter]> for FormalParameterList {
fn as_ref(&self) -> &[FormalParameter] {
&self.parameters
}
}
impl VisitWith for FormalParameterList {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for parameter in self.parameters.iter() {
try_break!(visitor.visit_formal_parameter(parameter));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for parameter in self.parameters.iter_mut() {
try_break!(visitor.visit_formal_parameter_mut(parameter));
}
// TODO recompute flags
ControlFlow::Continue(())
}
}
#[cfg(feature = "fuzz")]
impl<'a> arbitrary::Arbitrary<'a> for FormalParameterList {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let params: Vec<FormalParameter> = u.arbitrary()?;
Ok(Self::from(params))
}
}
bitflags! {
/// Flags for a [`FormalParameterList`].
#[allow(clippy::unsafe_derive_deserialize)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FormalParameterListFlags: u8 {
/// Has only identifier parameters with no initialization expressions.
const IS_SIMPLE = 0b0000_0001;
/// Has any duplicate parameters.
const HAS_DUPLICATES = 0b0000_0010;
/// Has a rest parameter.
const HAS_REST_PARAMETER = 0b0000_0100;
/// Has any initialization expression.
const HAS_EXPRESSIONS = 0b0000_1000;
/// Has an argument with the name `arguments`.
const HAS_ARGUMENTS = 0b0001_0000;
}
}
impl Default for FormalParameterListFlags {
fn default() -> Self {
Self::empty().union(Self::IS_SIMPLE)
}
}
/// "Formal parameter" is a fancy way of saying "function parameter".
///
/// In the declaration of a function, the parameters must be identifiers,
/// not any value like numbers, strings, or objects.
/// ```text
/// function foo(formalParameter1, formalParameter2) {
/// }
/// ```
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-FormalParameter
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Missing_formal_parameter
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct FormalParameter {
variable: Variable,
is_rest_param: bool,
}
impl FormalParameter {
/// Creates a new formal parameter.
pub fn new<D>(variable: D, is_rest_param: bool) -> Self
where
D: Into<Variable>,
{
Self {
variable: variable.into(),
is_rest_param,
}
}
/// Gets the variable of the formal parameter
#[must_use]
pub const fn variable(&self) -> &Variable {
&self.variable
}
/// Gets the initialization node of the formal parameter, if any.
#[must_use]
pub const fn init(&self) -> Option<&Expression> {
self.variable.init()
}
/// Returns `true` if the parameter is a rest parameter.
#[must_use]
pub const fn is_rest_param(&self) -> bool {
self.is_rest_param
}
/// Returns `true` if the parameter is an identifier.
#[must_use]
pub const fn is_identifier(&self) -> bool {
matches!(&self.variable.binding(), Binding::Identifier(_))
}
}
impl ToInternedString for FormalParameter {
fn to_interned_string(&self, interner: &Interner) -> String {
let mut buf = if self.is_rest_param {
"...".to_owned()
} else {
String::new()
};
buf.push_str(&self.variable.to_interned_string(interner));
buf
}
}
impl VisitWith for FormalParameter {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_variable(&self.variable)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_variable_mut(&mut self.variable)
}
}

View File

@@ -0,0 +1,625 @@
//! The `Keyword` AST node, which represents reserved words of the ECMAScript language.
//!
//! The [specification][spec] defines keywords as tokens that match an `IdentifierName`, but also
//! have special meaning in ECMAScript. In ECMAScript, you cannot use these reserved words as variables,
//! labels, or function names.
//!
//! The [MDN documentation][mdn] contains a more extensive explanation about keywords.
//!
//! [spec]: https://tc39.es/ecma262/#sec-keywords-and-reserved-words
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords
use crate::expression::operator::binary::{BinaryOp, RelationalOp};
use boa_interner::{Interner, Sym};
use boa_macros::utf16;
use std::{convert::TryFrom, error, fmt, str::FromStr};
/// List of keywords recognized by the JavaScript grammar.
///
/// See the [module-level documentation][self] for more details.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Keyword {
/// The `await` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AwaitExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
Await,
/// The `async` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AsyncMethod
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Async,
/// The `break` keyword.
///
/// More information:
/// - [break `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-BreakStatement
/// [node]: ../node/enum.Node.html#variant.Break
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/break
Break,
/// The `case` keyword.
///
/// More information:
/// - [switch `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-CaseClause
/// [node]: ../node/enum.Node.html#variant.Switch
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch
Case,
/// The `catch` keyword.
///
/// More information:
/// - [try `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-Catch
/// [node]: ../node/enum.Node.html#variant.Try
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch
Catch,
/// The `class` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ClassDeclaration
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/class
Class,
/// The `continue` keyword.
///
/// More information:
/// - [continue `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ContinueStatement
/// [node]: ../node/enum.Node.html#variant.Continue
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/continue
Continue,
/// The `const` keyword.
///
/// More information:
/// - [const `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-let-and-const-declarations
/// [node]: ../node/enum.Node.html#variant.ConstDecl
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const
Const,
/// The `debugger` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-debugger-statement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger
Debugger,
/// The `default` keyword.
///
/// More information:
/// - [switch `Node` documentation][node]
/// - [ECMAScript reference default clause][spec-clause]
/// - [ECMAScript reference default export][spec-export]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.Switch
/// [spec-clause]: https://tc39.es/ecma262/#prod-DefaultClause
/// [spec-export]: https://tc39.es/ecma262/#prod-ImportedDefaultBinding
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/default
Default,
/// The `delete` keyword.
///
/// More information:
/// - [delete `UnaryOp` documentation][unary]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-delete-operator
/// [unary]: ../op/enum.UnaryOp.html#variant.Delete
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete
Delete,
/// The `do` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-do-while-statement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/do...while
Do,
/// The `else` keyword.
///
/// More information:
/// - [if `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.If
/// [spec]: https://tc39.es/ecma262/#prod-IfStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else
Else,
/// The `enum` keyword.
///
/// Future reserved keyword.
Enum,
/// The `export` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-exports
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export
Export,
/// The `extends` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ClassHeritage
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/extends
Extends,
/// The `false` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-BooleanLiteral
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean
False,
/// The `finally` keyword.
///
/// More information:
/// - [try `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.Try
/// [spec]: https://tc39.es/ecma262/#prod-Finally
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch
Finally,
/// The `for` keyword.
///
/// More information:
/// - [for loop `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.ForLoop
/// [spec]: https://tc39.es/ecma262/#prod-ForDeclaration
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for
For,
/// The `function` keyword.
///
/// More information:
/// - [function `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.FunctionDecl
/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-function
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function
Function,
/// The `if` keyword.
///
/// More information:
/// - [if `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.If
/// [spec]: https://tc39.es/ecma262/#prod-IfStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else
If,
/// The `in` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-RelationalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in
In,
/// The `instanceof` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-instanceofoperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof
InstanceOf,
/// The `import` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-imports
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
Import,
/// The `let` keyword.
///
/// More information:
/// - [let `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.LetDecl
/// [spec]: https://tc39.es/ecma262/#sec-let-and-const-declarations
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
Let,
/// The `new` keyword.
///
/// More information:
/// - [new `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.New
/// [spec]: https://tc39.es/ecma262/#prod-NewExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new
New,
/// The `null` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-NullLiteral
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/null
Null,
/// The `of` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-for-in-and-for-of-statements
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of
Of,
/// The `return` keyword
///
/// More information:
/// - [return `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.Return
/// [spec]: https://tc39.es/ecma262/#prod-ReturnStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/return
Return,
/// The `super` keyword
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-super-keyword
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super
Super,
/// The `switch` keyword.
///
/// More information:
/// - [switch `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.Switch
/// [spec]: https://tc39.es/ecma262/#prod-SwitchStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch
Switch,
/// The `this` keyword.
///
/// More information:
/// - [this `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.This
/// [spec]: https://tc39.es/ecma262/#sec-this-keyword
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
This,
/// The `throw` keyword.
///
/// More information:
/// - [throw `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.Throw
/// [spec]: https://tc39.es/ecma262/#sec-throw-statement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/throw
Throw,
/// The `true` keyword
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-BooleanLiteral
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean
True,
/// The `try` keyword.
///
/// More information:
/// - [try `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.Try
/// [spec]: https://tc39.es/ecma262/#prod-TryStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch
Try,
/// The `typeof` keyword.
///
/// More information:
/// - [typeof `UnaryOp` documentation][unary]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [unary]: ../op/enum.UnaryOp.html#variant.TypeOf
/// [spec]: https://tc39.es/ecma262/#sec-typeof-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof
TypeOf,
/// The `var` keyword.
///
/// More information:
/// - [var `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.VarDecl
/// [spec]: https://tc39.es/ecma262/#prod-VariableStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var
Var,
/// The `void` keyword.
///
/// More information:
/// - [void `UnaryOp` documentation][unary]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [unary]: ../op/enum.UnaryOp.html#variant.Void
/// [spec]: https://tc39.es/ecma262/#sec-void-operator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void
Void,
/// The `while` keyword.
///
/// More information:
/// - [while `Node` documentation][node]
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [node]: ../node/enum.Node.html#variant.While
/// [spec]: https://tc39.es/ecma262/#prod-grammar-notation-WhileStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/while
While,
/// The `with` keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-WithStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with
With,
/// The 'yield' keyword.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-YieldExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield
Yield,
}
impl Keyword {
/// Gets the keyword as a binary operation, if this keyword is the `in` or the `instanceof`
/// keywords.
#[must_use]
pub const fn as_binary_op(self) -> Option<BinaryOp> {
match self {
Self::In => Some(BinaryOp::Relational(RelationalOp::In)),
Self::InstanceOf => Some(BinaryOp::Relational(RelationalOp::InstanceOf)),
_ => None,
}
}
/// Gets the keyword as a tuple of strings.
#[must_use]
pub const fn as_str(self) -> (&'static str, &'static [u16]) {
match self {
Self::Await => ("await", utf16!("await")),
Self::Async => ("async", utf16!("async")),
Self::Break => ("break", utf16!("break")),
Self::Case => ("case", utf16!("case")),
Self::Catch => ("catch", utf16!("catch")),
Self::Class => ("class", utf16!("class")),
Self::Continue => ("continue", utf16!("continue")),
Self::Const => ("const", utf16!("const")),
Self::Debugger => ("debugger", utf16!("debugger")),
Self::Default => ("default", utf16!("default")),
Self::Delete => ("delete", utf16!("delete")),
Self::Do => ("do", utf16!("do")),
Self::Else => ("else", utf16!("else")),
Self::Enum => ("enum", utf16!("enum")),
Self::Extends => ("extends", utf16!("extends")),
Self::Export => ("export", utf16!("export")),
Self::False => ("false", utf16!("false")),
Self::Finally => ("finally", utf16!("finally")),
Self::For => ("for", utf16!("for")),
Self::Function => ("function", utf16!("function")),
Self::If => ("if", utf16!("if")),
Self::In => ("in", utf16!("in")),
Self::InstanceOf => ("instanceof", utf16!("instanceof")),
Self::Import => ("import", utf16!("import")),
Self::Let => ("let", utf16!("let")),
Self::New => ("new", utf16!("new")),
Self::Null => ("null", utf16!("null")),
Self::Of => ("of", utf16!("of")),
Self::Return => ("return", utf16!("return")),
Self::Super => ("super", utf16!("super")),
Self::Switch => ("switch", utf16!("switch")),
Self::This => ("this", utf16!("this")),
Self::Throw => ("throw", utf16!("throw")),
Self::True => ("true", utf16!("true")),
Self::Try => ("try", utf16!("try")),
Self::TypeOf => ("typeof", utf16!("typeof")),
Self::Var => ("var", utf16!("var")),
Self::Void => ("void", utf16!("void")),
Self::While => ("while", utf16!("while")),
Self::With => ("with", utf16!("with")),
Self::Yield => ("yield", utf16!("yield")),
}
}
// TODO: promote all keywords to statics inside Interner
/// Converts the keyword to a symbol in the given interner.
pub fn to_sym(self, interner: &mut Interner) -> Sym {
let (utf8, utf16) = self.as_str();
interner.get_or_intern_static(utf8, utf16)
}
}
// TODO: Should use a proper Error
impl TryFrom<Keyword> for BinaryOp {
type Error = String;
fn try_from(value: Keyword) -> Result<Self, Self::Error> {
value
.as_binary_op()
.ok_or_else(|| format!("No binary operation for {value}"))
}
}
/// The error type which is returned from parsing a [`str`] into a [`Keyword`].
#[derive(Debug, Clone, Copy)]
pub struct KeywordError;
impl fmt::Display for KeywordError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid token")
}
}
// This is important for other errors to wrap this one.
impl error::Error for KeywordError {
fn description(&self) -> &str {
"invalid token"
}
}
impl FromStr for Keyword {
type Err = KeywordError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"await" => Ok(Self::Await),
"async" => Ok(Self::Async),
"break" => Ok(Self::Break),
"case" => Ok(Self::Case),
"catch" => Ok(Self::Catch),
"class" => Ok(Self::Class),
"continue" => Ok(Self::Continue),
"const" => Ok(Self::Const),
"debugger" => Ok(Self::Debugger),
"default" => Ok(Self::Default),
"delete" => Ok(Self::Delete),
"do" => Ok(Self::Do),
"else" => Ok(Self::Else),
"enum" => Ok(Self::Enum),
"extends" => Ok(Self::Extends),
"export" => Ok(Self::Export),
"false" => Ok(Self::False),
"finally" => Ok(Self::Finally),
"for" => Ok(Self::For),
"function" => Ok(Self::Function),
"if" => Ok(Self::If),
"in" => Ok(Self::In),
"instanceof" => Ok(Self::InstanceOf),
"import" => Ok(Self::Import),
"let" => Ok(Self::Let),
"new" => Ok(Self::New),
"null" => Ok(Self::Null),
"of" => Ok(Self::Of),
"return" => Ok(Self::Return),
"super" => Ok(Self::Super),
"switch" => Ok(Self::Switch),
"this" => Ok(Self::This),
"throw" => Ok(Self::Throw),
"true" => Ok(Self::True),
"try" => Ok(Self::Try),
"typeof" => Ok(Self::TypeOf),
"var" => Ok(Self::Var),
"void" => Ok(Self::Void),
"while" => Ok(Self::While),
"with" => Ok(Self::With),
"yield" => Ok(Self::Yield),
_ => Err(KeywordError),
}
}
}
impl fmt::Display for Keyword {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.as_str().0, f)
}
}

View File

@@ -0,0 +1,174 @@
//! Boa's **`boa_ast`** crate implements an ECMAScript abstract syntax tree.
//!
//! # Crate Overview
//! **`boa_ast`** contains representations of [**Parse Nodes**][grammar] as defined by the ECMAScript
//! spec. Some `Parse Node`s are not represented by Boa's AST, because a lot of grammar productions
//! are only used to throw [**Early Errors**][early], and don't influence the evaluation of the AST
//! itself.
//!
//! Boa's AST is mainly split in three main components: [`Declaration`]s, [`Expression`]s and
//! [`Statement`]s, with [`StatementList`] being the primordial Parse Node that combines
//! all of them to create a proper AST.
//!
//! # About Boa
//! Boa is an open-source, experimental ECMAScript Engine written in Rust for lexing, parsing and executing ECMAScript/JavaScript. Currently, Boa
//! supports some of the [language][boa-conformance]. More information can be viewed at [Boa's website][boa-web].
//!
//! Try out the most recent release with Boa's live demo [playground][boa-playground].
//!
//! # Boa Crates
//! - **`boa_ast`** - Boa's ECMAScript Abstract Syntax Tree.
//! - **`boa_engine`** - Boa's implementation of ECMAScript builtin objects and execution.
//! - **`boa_gc`** - Boa's garbage collector.
//! - **`boa_interner`** - Boa's string interner.
//! - **`boa_parser`** - Boa's lexer and parser.
//! - **`boa_profiler`** - Boa's code profiler.
//! - **`boa_unicode`** - Boa's Unicode identifier.
//! - **`boa_icu_provider`** - Boa's ICU4X data provider.
//!
//! [grammar]: https://tc39.es/ecma262/#sec-syntactic-grammar
//! [early]: https://tc39.es/ecma262/#sec-static-semantic-rules
//! [boa-conformance]: https://boa-dev.github.io/boa/test262/
//! [boa-web]: https://boa-dev.github.io/
//! [boa-playground]: https://boa-dev.github.io/boa/playground/
#![doc(
html_logo_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo.svg"
)]
#![cfg_attr(not(test), forbid(clippy::unwrap_used))]
#![warn(missing_docs, clippy::dbg_macro)]
#![deny(
// rustc lint groups https://doc.rust-lang.org/rustc/lints/groups.html
warnings,
future_incompatible,
let_underscore,
nonstandard_style,
rust_2018_compatibility,
rust_2018_idioms,
rust_2021_compatibility,
unused,
// rustc allowed-by-default lints https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html
macro_use_extern_crate,
meta_variable_misuse,
missing_abi,
missing_copy_implementations,
missing_debug_implementations,
non_ascii_idents,
noop_method_call,
single_use_lifetimes,
trivial_casts,
trivial_numeric_casts,
unreachable_pub,
unsafe_op_in_unsafe_fn,
unused_crate_dependencies,
unused_import_braces,
unused_lifetimes,
unused_qualifications,
unused_tuple_struct_fields,
variant_size_differences,
// rustdoc lints https://doc.rust-lang.org/rustdoc/lints.html
rustdoc::broken_intra_doc_links,
rustdoc::private_intra_doc_links,
rustdoc::missing_crate_level_docs,
rustdoc::private_doc_tests,
rustdoc::invalid_codeblock_attributes,
rustdoc::invalid_rust_codeblocks,
rustdoc::bare_urls,
// clippy categories https://doc.rust-lang.org/clippy/
clippy::all,
clippy::correctness,
clippy::suspicious,
clippy::style,
clippy::complexity,
clippy::perf,
clippy::pedantic,
clippy::nursery,
)]
#![allow(
clippy::module_name_repetitions,
clippy::too_many_lines,
clippy::option_if_let_else,
clippy::use_self
)]
mod position;
mod punctuator;
mod statement_list;
pub mod declaration;
pub mod expression;
pub mod function;
pub mod keyword;
pub mod operations;
pub mod pattern;
pub mod property;
pub mod statement;
pub mod visitor;
use boa_interner::{Interner, ToIndentedString, ToInternedString};
pub use self::{
declaration::Declaration,
expression::Expression,
keyword::Keyword,
position::{Position, Span},
punctuator::Punctuator,
statement::Statement,
statement_list::{StatementList, StatementListItem},
};
/// Utility to join multiple Nodes into a single string.
fn join_nodes<N>(interner: &Interner, nodes: &[N]) -> String
where
N: ToInternedString,
{
let mut first = true;
let mut buf = String::new();
for e in nodes {
if first {
first = false;
} else {
buf.push_str(", ");
}
buf.push_str(&e.to_interned_string(interner));
}
buf
}
/// Displays the body of a block or statement list.
///
/// This includes the curly braces at the start and end. This will not indent the first brace,
/// but will indent the last brace.
fn block_to_string(body: &StatementList, interner: &Interner, indentation: usize) -> String {
if body.statements().is_empty() {
"{}".to_owned()
} else {
format!(
"{{\n{}{}}}",
body.to_indented_string(interner, indentation + 1),
" ".repeat(indentation)
)
}
}
/// Utility trait that adds a `UTF-16` escaped representation to every [`[u16]`][slice].
trait ToStringEscaped {
/// Decodes `self` as an `UTF-16` encoded string, escaping any unpaired surrogates by its
/// codepoint value.
fn to_string_escaped(&self) -> String;
}
impl ToStringEscaped for [u16] {
fn to_string_escaped(&self) -> String {
char::decode_utf16(self.iter().copied())
.map(|r| match r {
Ok(c) => String::from(c),
Err(e) => format!("\\u{:04X}", e.unpaired_surrogate()),
})
.collect()
}
}

View File

@@ -0,0 +1,769 @@
//! Definitions of various **Syntax-Directed Operations** used in the [spec].
//!
//! [spec]: https://tc39.es/ecma262/#sec-syntax-directed-operations
use core::ops::ControlFlow;
use std::convert::Infallible;
use boa_interner::Sym;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::{
declaration::VarDeclaration,
expression::{access::SuperPropertyAccess, Await, Identifier, SuperCall, Yield},
function::{
ArrowFunction, AsyncArrowFunction, AsyncFunction, AsyncGenerator, Class, ClassElement,
Function, Generator, PrivateName,
},
property::{MethodDefinition, PropertyDefinition},
statement::LabelledItem,
try_break,
visitor::{NodeRef, VisitWith, Visitor, VisitorMut},
Declaration, Expression, Statement, StatementList, StatementListItem,
};
/// Represents all the possible symbols searched for by the [`Contains`][contains] operation.
///
/// [contains]: https://tc39.es/ecma262/#sec-syntax-directed-operations-contains
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ContainsSymbol {
/// A node with the `super` keyword (`super(args)` or `super.prop`).
Super,
/// A super property access (`super.prop`).
SuperProperty,
/// A super constructor call (`super(args)`).
SuperCall,
/// A yield expression (`yield 5`).
YieldExpression,
/// An await expression (`await 4`).
AwaitExpression,
/// The new target expression (`new.target`).
NewTarget,
/// The body of a class definition.
ClassBody,
/// The super class of a class definition.
ClassHeritage,
/// A this expression (`this`).
This,
/// A method definition.
MethodDefinition,
/// The BindingIdentifier "eval" or "arguments".
EvalOrArguments,
}
/// Returns `true` if the node contains the given symbol.
///
/// This is equivalent to the [`Contains`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-contains
#[must_use]
pub fn contains<N>(node: &N, symbol: ContainsSymbol) -> bool
where
N: VisitWith,
{
/// Visitor used by the function to search for a specific symbol in a node.
#[derive(Debug, Clone, Copy)]
struct ContainsVisitor(ContainsSymbol);
impl<'ast> Visitor<'ast> for ContainsVisitor {
type BreakTy = ();
fn visit_identifier(&mut self, node: &'ast Identifier) -> ControlFlow<Self::BreakTy> {
if self.0 == ContainsSymbol::EvalOrArguments
&& (node.sym() == Sym::EVAL || node.sym() == Sym::ARGUMENTS)
{
return ControlFlow::Break(());
}
ControlFlow::Continue(())
}
fn visit_function(&mut self, _: &'ast Function) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_async_function(&mut self, _: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_generator(&mut self, _: &'ast Generator) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_async_generator(&mut self, _: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_class(&mut self, node: &'ast Class) -> ControlFlow<Self::BreakTy> {
if !node.elements().is_empty() && self.0 == ContainsSymbol::ClassBody {
return ControlFlow::Break(());
}
if node.super_ref().is_some() && self.0 == ContainsSymbol::ClassHeritage {
return ControlFlow::Break(());
}
node.visit_with(self)
}
// `ComputedPropertyContains`: https://tc39.es/ecma262/#sec-static-semantics-computedpropertycontains
fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> {
match node {
ClassElement::MethodDefinition(name, _)
| ClassElement::StaticMethodDefinition(name, _)
| ClassElement::FieldDefinition(name, _)
| ClassElement::StaticFieldDefinition(name, _) => name.visit_with(self),
_ => ControlFlow::Continue(()),
}
}
fn visit_property_definition(
&mut self,
node: &'ast PropertyDefinition,
) -> ControlFlow<Self::BreakTy> {
if let PropertyDefinition::MethodDefinition(name, _) = node {
if self.0 == ContainsSymbol::MethodDefinition {
return ControlFlow::Break(());
}
return name.visit_with(self);
}
node.visit_with(self)
}
fn visit_arrow_function(
&mut self,
node: &'ast ArrowFunction,
) -> ControlFlow<Self::BreakTy> {
if ![
ContainsSymbol::NewTarget,
ContainsSymbol::SuperProperty,
ContainsSymbol::SuperCall,
ContainsSymbol::Super,
ContainsSymbol::This,
]
.contains(&self.0)
{
return ControlFlow::Continue(());
}
node.visit_with(self)
}
fn visit_async_arrow_function(
&mut self,
node: &'ast AsyncArrowFunction,
) -> ControlFlow<Self::BreakTy> {
if ![
ContainsSymbol::NewTarget,
ContainsSymbol::SuperProperty,
ContainsSymbol::SuperCall,
ContainsSymbol::Super,
ContainsSymbol::This,
]
.contains(&self.0)
{
return ControlFlow::Continue(());
}
node.visit_with(self)
}
fn visit_super_property_access(
&mut self,
node: &'ast SuperPropertyAccess,
) -> ControlFlow<Self::BreakTy> {
if [ContainsSymbol::SuperProperty, ContainsSymbol::Super].contains(&self.0) {
return ControlFlow::Break(());
}
node.visit_with(self)
}
fn visit_super_call(&mut self, node: &'ast SuperCall) -> ControlFlow<Self::BreakTy> {
if [ContainsSymbol::SuperCall, ContainsSymbol::Super].contains(&self.0) {
return ControlFlow::Break(());
}
node.visit_with(self)
}
fn visit_yield(&mut self, node: &'ast Yield) -> ControlFlow<Self::BreakTy> {
if self.0 == ContainsSymbol::YieldExpression {
return ControlFlow::Break(());
}
node.visit_with(self)
}
fn visit_await(&mut self, node: &'ast Await) -> ControlFlow<Self::BreakTy> {
if self.0 == ContainsSymbol::AwaitExpression {
return ControlFlow::Break(());
}
node.visit_with(self)
}
fn visit_expression(&mut self, node: &'ast Expression) -> ControlFlow<Self::BreakTy> {
if node == &Expression::This && self.0 == ContainsSymbol::This {
return ControlFlow::Break(());
}
if node == &Expression::NewTarget && self.0 == ContainsSymbol::NewTarget {
return ControlFlow::Break(());
}
node.visit_with(self)
}
}
node.visit_with(&mut ContainsVisitor(symbol)).is_break()
}
/// Returns true if the node contains an identifier reference with name `arguments`.
///
/// This is equivalent to the [`ContainsArguments`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-containsarguments
#[must_use]
pub fn contains_arguments<N>(node: &N) -> bool
where
N: VisitWith,
{
/// Visitor used by the function to search for an identifier with the name `arguments`.
#[derive(Debug, Clone, Copy)]
struct ContainsArgsVisitor;
impl<'ast> Visitor<'ast> for ContainsArgsVisitor {
type BreakTy = ();
fn visit_identifier(&mut self, node: &'ast Identifier) -> ControlFlow<Self::BreakTy> {
if node.sym() == Sym::ARGUMENTS {
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
}
fn visit_function(&mut self, _: &'ast Function) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_async_function(&mut self, _: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_generator(&mut self, _: &'ast Generator) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_async_generator(&mut self, _: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> {
match node {
ClassElement::MethodDefinition(name, _)
| ClassElement::StaticMethodDefinition(name, _) => return name.visit_with(self),
_ => {}
}
node.visit_with(self)
}
fn visit_property_definition(
&mut self,
node: &'ast PropertyDefinition,
) -> ControlFlow<Self::BreakTy> {
if let PropertyDefinition::MethodDefinition(name, _) = node {
name.visit_with(self)
} else {
node.visit_with(self)
}
}
}
node.visit_with(&mut ContainsArgsVisitor).is_break()
}
/// Returns `true` if `method` has a super call in its parameters or body.
///
/// This is equivalent to the [`HasDirectSuper`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-hasdirectsuper
#[must_use]
#[inline]
pub fn has_direct_super(method: &MethodDefinition) -> bool {
match method {
MethodDefinition::Get(f) | MethodDefinition::Set(f) | MethodDefinition::Ordinary(f) => {
contains(f, ContainsSymbol::SuperCall)
}
MethodDefinition::Generator(f) => contains(f, ContainsSymbol::SuperCall),
MethodDefinition::AsyncGenerator(f) => contains(f, ContainsSymbol::SuperCall),
MethodDefinition::Async(f) => contains(f, ContainsSymbol::SuperCall),
}
}
/// A container that [`BoundNamesVisitor`] can use to push the found identifiers.
trait IdentList {
fn add(&mut self, value: Identifier, function: bool);
}
impl IdentList for Vec<Identifier> {
fn add(&mut self, value: Identifier, _function: bool) {
self.push(value);
}
}
impl IdentList for Vec<(Identifier, bool)> {
fn add(&mut self, value: Identifier, function: bool) {
self.push((value, function));
}
}
impl IdentList for FxHashSet<Identifier> {
fn add(&mut self, value: Identifier, _function: bool) {
self.insert(value);
}
}
/// The [`Visitor`] used to obtain the bound names of a node.
#[derive(Debug)]
struct BoundNamesVisitor<'a, T: IdentList>(&'a mut T);
impl<'ast, T: IdentList> Visitor<'ast> for BoundNamesVisitor<'_, T> {
type BreakTy = Infallible;
fn visit_identifier(&mut self, node: &'ast Identifier) -> ControlFlow<Self::BreakTy> {
self.0.add(*node, false);
ControlFlow::Continue(())
}
fn visit_expression(&mut self, _: &'ast Expression) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
// TODO: add "*default" for module default functions without name
fn visit_function(&mut self, node: &'ast Function) -> ControlFlow<Self::BreakTy> {
if let Some(ident) = node.name() {
self.0.add(ident, true);
}
ControlFlow::Continue(())
}
fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow<Self::BreakTy> {
if let Some(ident) = node.name() {
self.0.add(ident, false);
}
ControlFlow::Continue(())
}
fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> {
if let Some(ident) = node.name() {
self.0.add(ident, false);
}
ControlFlow::Continue(())
}
fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> {
if let Some(ident) = node.name() {
self.0.add(ident, false);
}
ControlFlow::Continue(())
}
fn visit_class(&mut self, node: &'ast Class) -> ControlFlow<Self::BreakTy> {
if let Some(ident) = node.name() {
self.0.add(ident, false);
}
ControlFlow::Continue(())
}
}
/// Returns a list with the bound names of an AST node, which may contain duplicates.
///
/// This is equivalent to the [`BoundNames`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-boundnames
#[must_use]
pub fn bound_names<'a, N>(node: &'a N) -> Vec<Identifier>
where
&'a N: Into<NodeRef<'a>>,
{
let mut names = Vec::new();
BoundNamesVisitor(&mut names).visit(node.into());
names
}
/// The [`Visitor`] used to obtain the lexically declared names of a node.
#[derive(Debug)]
struct LexicallyDeclaredNamesVisitor<'a, T: IdentList>(&'a mut T);
impl<'ast, T: IdentList> Visitor<'ast> for LexicallyDeclaredNamesVisitor<'_, T> {
type BreakTy = Infallible;
fn visit_expression(&mut self, _: &'ast Expression) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow<Self::BreakTy> {
if let Statement::Labelled(labelled) = node {
return self.visit_labelled(labelled);
}
ControlFlow::Continue(())
}
fn visit_declaration(&mut self, node: &'ast Declaration) -> ControlFlow<Self::BreakTy> {
BoundNamesVisitor(self.0).visit_declaration(node)
}
fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow<Self::BreakTy> {
match node {
LabelledItem::Function(f) => BoundNamesVisitor(self.0).visit_function(f),
LabelledItem::Statement(_) => ControlFlow::Continue(()),
}
}
fn visit_function(&mut self, node: &'ast Function) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_arrow_function(&mut self, node: &'ast ArrowFunction) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_async_arrow_function(
&mut self,
node: &'ast AsyncArrowFunction,
) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> {
if let ClassElement::StaticBlock(stmts) = node {
top_level_lexicals(stmts, self.0);
}
ControlFlow::Continue(())
}
// TODO: ScriptBody : StatementList
// 1. Return TopLevelLexicallyDeclaredNames of StatementList.
// But we don't have that node yet. In the meantime, use `top_level_lexically_declared_names` directly.
}
/// Returns a list with the lexical bindings of a node, which may contain duplicates.
///
/// This is equivalent to the [`LexicallyDeclaredNames`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-lexicallydeclarednames
#[must_use]
pub fn lexically_declared_names<'a, N>(node: &'a N) -> Vec<Identifier>
where
&'a N: Into<NodeRef<'a>>,
{
let mut names = Vec::new();
LexicallyDeclaredNamesVisitor(&mut names).visit(node.into());
names
}
/// Returns a list with the lexical bindings of a node, which may contain duplicates.
///
/// If a declared name originates from a function declaration it is flagged as `true` in the returned
/// list. (See [B.3.2.4 Changes to Block Static Semantics: Early Errors])
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-lexicallydeclarednames
/// [changes]: https://tc39.es/ecma262/#sec-block-duplicates-allowed-static-semantics
#[must_use]
pub fn lexically_declared_names_legacy<'a, N>(node: &'a N) -> Vec<(Identifier, bool)>
where
&'a N: Into<NodeRef<'a>>,
{
let mut names = Vec::new();
LexicallyDeclaredNamesVisitor(&mut names).visit(node.into());
names
}
/// The [`Visitor`] used to obtain the var declared names of a node.
#[derive(Debug)]
struct VarDeclaredNamesVisitor<'a>(&'a mut FxHashSet<Identifier>);
impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> {
type BreakTy = Infallible;
fn visit_expression(&mut self, _: &'ast Expression) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_declaration(&mut self, _: &'ast Declaration) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
fn visit_var_declaration(&mut self, node: &'ast VarDeclaration) -> ControlFlow<Self::BreakTy> {
BoundNamesVisitor(self.0).visit_var_declaration(node)
}
fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow<Self::BreakTy> {
match node {
LabelledItem::Function(_) => ControlFlow::Continue(()),
LabelledItem::Statement(stmt) => stmt.visit_with(self),
}
}
fn visit_function(&mut self, node: &'ast Function) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_arrow_function(&mut self, node: &'ast ArrowFunction) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_async_arrow_function(
&mut self,
node: &'ast AsyncArrowFunction,
) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> {
if let ClassElement::StaticBlock(stmts) = node {
top_level_vars(stmts, self.0);
}
node.visit_with(self)
}
// TODO: ScriptBody : StatementList
// 1. Return TopLevelVarDeclaredNames of StatementList.
// But we don't have that node yet. In the meantime, use `top_level_var_declared_names` directly.
}
/// Returns a set with the var bindings of a node, with no duplicates.
///
/// This is equivalent to the [`VarDeclaredNames`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-vardeclarednames
#[must_use]
pub fn var_declared_names<'a, N>(node: &'a N) -> FxHashSet<Identifier>
where
&'a N: Into<NodeRef<'a>>,
{
let mut names = FxHashSet::default();
VarDeclaredNamesVisitor(&mut names).visit(node.into());
names
}
/// Utility function that collects the top level lexicals of a statement list into `names`.
fn top_level_lexicals<T: IdentList>(stmts: &StatementList, names: &mut T) {
for stmt in stmts.statements() {
if let StatementListItem::Declaration(decl) = stmt {
match decl {
// Note
// At the top level of a function, or script, function declarations are treated like
// var declarations rather than like lexical declarations.
Declaration::Function(_)
| Declaration::Generator(_)
| Declaration::AsyncFunction(_)
| Declaration::AsyncGenerator(_) => {}
Declaration::Class(class) => {
BoundNamesVisitor(names).visit_class(class);
}
Declaration::Lexical(decl) => {
BoundNamesVisitor(names).visit_lexical_declaration(decl);
}
}
}
}
}
/// Returns a list with the lexical bindings of a top-level statement list, which may contain duplicates.
///
/// This is equivalent to the [`TopLevelLexicallyDeclaredNames`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevellexicallydeclarednames
#[must_use]
#[inline]
pub fn top_level_lexically_declared_names(stmts: &StatementList) -> Vec<Identifier> {
let mut names = Vec::new();
top_level_lexicals(stmts, &mut names);
names
}
/// Utility function that collects the top level vars of a statement list into `names`.
fn top_level_vars(stmts: &StatementList, names: &mut FxHashSet<Identifier>) {
for stmt in stmts.statements() {
match stmt {
StatementListItem::Declaration(decl) => {
match decl {
// Note
// At the top level of a function, or script, function declarations are treated like
// var declarations rather than like lexical declarations.
Declaration::Function(f) => {
BoundNamesVisitor(names).visit_function(f);
}
Declaration::Generator(f) => {
BoundNamesVisitor(names).visit_generator(f);
}
Declaration::AsyncFunction(f) => {
BoundNamesVisitor(names).visit_async_function(f);
}
Declaration::AsyncGenerator(f) => {
BoundNamesVisitor(names).visit_async_generator(f);
}
Declaration::Class(_) | Declaration::Lexical(_) => {}
}
}
StatementListItem::Statement(stmt) => {
let mut stmt = Some(stmt);
while let Some(Statement::Labelled(labelled)) = stmt {
match labelled.item() {
LabelledItem::Function(f) => {
BoundNamesVisitor(names).visit_function(f);
stmt = None;
}
LabelledItem::Statement(s) => stmt = Some(s),
}
}
if let Some(stmt) = stmt {
VarDeclaredNamesVisitor(names).visit(stmt);
}
}
}
}
}
/// Returns a list with the var bindings of a top-level statement list, with no duplicates.
///
/// This is equivalent to the [`TopLevelVarDeclaredNames`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevelvardeclarednames
#[must_use]
#[inline]
pub fn top_level_var_declared_names(stmts: &StatementList) -> FxHashSet<Identifier> {
let mut names = FxHashSet::default();
top_level_vars(stmts, &mut names);
names
}
/// Resolves the private names of a class and all of the contained classes and private identifiers.
pub fn class_private_name_resolver(node: &mut Class, top_level_class_index: usize) -> bool {
/// Visitor used by the function to search for an identifier with the name `arguments`.
#[derive(Debug, Clone)]
struct ClassPrivateNameResolver {
private_environments_stack: Vec<FxHashMap<Sym, PrivateName>>,
top_level_class_index: usize,
}
impl<'ast> VisitorMut<'ast> for ClassPrivateNameResolver {
type BreakTy = ();
#[inline]
fn visit_class_mut(&mut self, node: &'ast mut Class) -> ControlFlow<Self::BreakTy> {
let mut names = FxHashMap::default();
for element in node.elements.iter_mut() {
match element {
ClassElement::PrivateMethodDefinition(name, _)
| ClassElement::PrivateStaticMethodDefinition(name, _)
| ClassElement::PrivateFieldDefinition(name, _)
| ClassElement::PrivateStaticFieldDefinition(name, _) => {
name.indices = (
self.top_level_class_index,
self.private_environments_stack.len(),
);
names.insert(name.description(), *name);
}
_ => {}
}
}
self.private_environments_stack.push(names);
for element in node.elements.iter_mut() {
match element {
ClassElement::MethodDefinition(name, method)
| ClassElement::StaticMethodDefinition(name, method) => {
try_break!(self.visit_property_name_mut(name));
try_break!(self.visit_method_definition_mut(method));
}
ClassElement::PrivateMethodDefinition(_, method)
| ClassElement::PrivateStaticMethodDefinition(_, method) => {
try_break!(self.visit_method_definition_mut(method));
}
ClassElement::FieldDefinition(name, expression)
| ClassElement::StaticFieldDefinition(name, expression) => {
try_break!(self.visit_property_name_mut(name));
if let Some(expression) = expression {
try_break!(self.visit_expression_mut(expression));
}
}
ClassElement::PrivateFieldDefinition(_, expression)
| ClassElement::PrivateStaticFieldDefinition(_, expression) => {
if let Some(expression) = expression {
try_break!(self.visit_expression_mut(expression));
}
}
ClassElement::StaticBlock(statement_list) => {
try_break!(self.visit_statement_list_mut(statement_list));
}
}
}
if let Some(function) = &mut node.constructor {
try_break!(self.visit_function_mut(function));
}
self.private_environments_stack.pop();
ControlFlow::Continue(())
}
#[inline]
fn visit_private_name_mut(
&mut self,
node: &'ast mut PrivateName,
) -> ControlFlow<Self::BreakTy> {
let mut found = false;
for environment in self.private_environments_stack.iter().rev() {
if let Some(n) = environment.get(&node.description()) {
found = true;
node.indices = n.indices;
break;
}
}
if found {
ControlFlow::Continue(())
} else {
ControlFlow::Break(())
}
}
}
let mut visitor = ClassPrivateNameResolver {
private_environments_stack: Vec::new(),
top_level_class_index,
};
visitor.visit_class_mut(node).is_continue()
}

View File

@@ -0,0 +1,787 @@
//! A pattern binding or assignment node.
//!
//! A [`Pattern`] Corresponds to the [`BindingPattern`][spec1] and the [`AssignmentPattern`][spec2]
//! nodes, each of which is used in different situations and have slightly different grammars.
//! For example, a variable declaration combined with a destructuring expression is a `BindingPattern`:
//!
//! ```Javascript
//! const obj = { a: 1, b: 2 };
//! const { a, b } = obj; // BindingPattern
//! ```
//!
//! On the other hand, a simple destructuring expression with already declared variables is called
//! an `AssignmentPattern`:
//!
//! ```Javascript
//! let a = 1;
//! let b = 3;
//! [a, b] = [b, a]; // AssignmentPattern
//! ```
//!
//! [spec1]: https://tc39.es/ecma262/#prod-BindingPattern
//! [spec2]: https://tc39.es/ecma262/#prod-AssignmentPattern
//! [destr]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
use crate::{
expression::{access::PropertyAccess, Identifier},
property::PropertyName,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
Expression,
};
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
/// An object or array pattern binding or assignment.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum Pattern {
/// An object pattern (`let {a, b, c} = object`).
Object(ObjectPattern),
/// An array pattern (`[a, b, c] = array`).
Array(ArrayPattern),
}
impl From<ObjectPattern> for Pattern {
fn from(obj: ObjectPattern) -> Self {
Self::Object(obj)
}
}
impl From<ArrayPattern> for Pattern {
fn from(obj: ArrayPattern) -> Self {
Self::Array(obj)
}
}
impl From<Vec<ObjectPatternElement>> for Pattern {
fn from(elements: Vec<ObjectPatternElement>) -> Self {
ObjectPattern::new(elements.into()).into()
}
}
impl From<Vec<ArrayPatternElement>> for Pattern {
fn from(elements: Vec<ArrayPatternElement>) -> Self {
ArrayPattern::new(elements.into()).into()
}
}
impl ToInternedString for Pattern {
fn to_interned_string(&self, interner: &Interner) -> String {
match &self {
Self::Object(o) => o.to_interned_string(interner),
Self::Array(a) => a.to_interned_string(interner),
}
}
}
impl VisitWith for Pattern {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Object(op) => visitor.visit_object_pattern(op),
Self::Array(ap) => visitor.visit_array_pattern(ap),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Object(op) => visitor.visit_object_pattern_mut(op),
Self::Array(ap) => visitor.visit_array_pattern_mut(ap),
}
}
}
/// An object binding or assignment pattern.
///
/// Corresponds to the [`ObjectBindingPattern`][spec1] and the [`ObjectAssignmentPattern`][spec2]
/// Parse Nodes.
///
/// For more information on what is a valid binding in an object pattern, see [`ObjectPatternElement`].
///
/// [spec1]: https://tc39.es/ecma262/#prod-ObjectBindingPattern
/// [spec2]: https://tc39.es/ecma262/#prod-ObjectAssignmentPattern
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct ObjectPattern(Box<[ObjectPatternElement]>);
impl From<Vec<ObjectPatternElement>> for ObjectPattern {
fn from(elements: Vec<ObjectPatternElement>) -> Self {
Self(elements.into())
}
}
impl ToInternedString for ObjectPattern {
fn to_interned_string(&self, interner: &Interner) -> String {
let mut buf = "{".to_owned();
for (i, binding) in self.0.iter().enumerate() {
let binding = binding.to_interned_string(interner);
let str = if i == self.0.len() - 1 {
format!("{binding} ")
} else {
format!("{binding},")
};
buf.push_str(&str);
}
if self.0.is_empty() {
buf.push(' ');
}
buf.push('}');
buf
}
}
impl ObjectPattern {
/// Creates a new object binding pattern.
#[inline]
#[must_use]
pub fn new(bindings: Box<[ObjectPatternElement]>) -> Self {
Self(bindings)
}
/// Gets the bindings for the object binding pattern.
#[inline]
#[must_use]
pub const fn bindings(&self) -> &[ObjectPatternElement] {
&self.0
}
/// Returns true if the object binding pattern has a rest element.
#[inline]
#[must_use]
pub const fn has_rest(&self) -> bool {
matches!(
self.0.last(),
Some(ObjectPatternElement::RestProperty { .. })
)
}
}
impl VisitWith for ObjectPattern {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for elem in self.0.iter() {
try_break!(visitor.visit_object_pattern_element(elem));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for elem in self.0.iter_mut() {
try_break!(visitor.visit_object_pattern_element_mut(elem));
}
ControlFlow::Continue(())
}
}
/// An array binding or assignment pattern.
///
/// Corresponds to the [`ArrayBindingPattern`][spec1] and the [`ArrayAssignmentPattern`][spec2]
/// Parse Nodes.
///
/// For more information on what is a valid binding in an array pattern, see [`ArrayPatternElement`].
///
/// [spec1]: https://tc39.es/ecma262/#prod-ArrayBindingPattern
/// [spec2]: https://tc39.es/ecma262/#prod-ArrayAssignmentPattern
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct ArrayPattern(Box<[ArrayPatternElement]>);
impl From<Vec<ArrayPatternElement>> for ArrayPattern {
fn from(elements: Vec<ArrayPatternElement>) -> Self {
Self(elements.into())
}
}
impl ToInternedString for ArrayPattern {
fn to_interned_string(&self, interner: &Interner) -> String {
let mut buf = "[".to_owned();
for (i, binding) in self.0.iter().enumerate() {
if i == self.0.len() - 1 {
match binding {
ArrayPatternElement::Elision => {
buf.push_str(&format!("{}, ", binding.to_interned_string(interner)));
}
_ => buf.push_str(&format!("{} ", binding.to_interned_string(interner))),
}
} else {
buf.push_str(&format!("{},", binding.to_interned_string(interner)));
}
}
buf.push(']');
buf
}
}
impl ArrayPattern {
/// Creates a new array binding pattern.
#[inline]
#[must_use]
pub fn new(bindings: Box<[ArrayPatternElement]>) -> Self {
Self(bindings)
}
/// Gets the bindings for the array binding pattern.
#[inline]
#[must_use]
pub const fn bindings(&self) -> &[ArrayPatternElement] {
&self.0
}
}
impl VisitWith for ArrayPattern {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for elem in self.0.iter() {
try_break!(visitor.visit_array_pattern_element(elem));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for elem in self.0.iter_mut() {
try_break!(visitor.visit_array_pattern_element_mut(elem));
}
ControlFlow::Continue(())
}
}
/// The different types of bindings that an [`ObjectPattern`] may contain.
///
/// Corresponds to the [`BindingProperty`][spec1] and the [`AssignmentProperty`][spec2] nodes.
///
/// [spec1]: https://tc39.es/ecma262/#prod-BindingProperty
/// [spec2]: https://tc39.es/ecma262/#prod-AssignmentProperty
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum ObjectPatternElement {
/// SingleName represents one of the following properties:
///
/// - `SingleName` with an identifier and an optional default initializer.
/// - `BindingProperty` with an property name and a `SingleNameBinding` as the `BindingElement`.
///
/// More information:
/// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - SingleNameBinding][spec1]
/// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - BindingProperty][spec2]
///
/// [spec1]: https://tc39.es/ecma262/#prod-SingleNameBinding
/// [spec2]: https://tc39.es/ecma262/#prod-BindingProperty
SingleName {
/// The identifier name of the property to be destructured.
name: PropertyName,
/// The variable name where the property value will be stored.
ident: Identifier,
/// An optional default value for the variable, in case the property doesn't exist.
default_init: Option<Expression>,
},
/// RestProperty represents a `BindingRestProperty` with an identifier.
///
/// It also includes a list of the property keys that should be excluded from the rest,
/// because they where already assigned.
///
/// More information:
/// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - BindingRestProperty][spec1]
///
/// [spec1]: https://tc39.es/ecma262/#prod-BindingRestProperty
RestProperty {
/// The variable name where the unassigned properties will be stored.
ident: Identifier,
/// A list of the excluded property keys that were already destructured.
excluded_keys: Vec<Identifier>,
},
/// AssignmentGetField represents an AssignmentProperty with an expression field member expression AssignmentElement.
///
/// Note: According to the spec this is not part of an ObjectBindingPattern.
/// This is only used when a object literal is used to cover an AssignmentPattern.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentProperty
AssignmentPropertyAccess {
/// The identifier name of the property to be destructured.
name: PropertyName,
/// The property access where the property value will be destructured.
access: PropertyAccess,
/// An optional default value for the variable, in case the property doesn't exist.
default_init: Option<Expression>,
},
/// AssignmentRestProperty represents a rest property with a DestructuringAssignmentTarget.
///
/// Note: According to the spec this is not part of an ObjectBindingPattern.
/// This is only used when a object literal is used to cover an AssignmentPattern.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentRestProperty
AssignmentRestPropertyAccess {
/// The property access where the unassigned properties will be stored.
access: PropertyAccess,
/// A list of the excluded property keys that were already destructured.
excluded_keys: Vec<Identifier>,
},
/// Pattern represents a property with a `Pattern` as the element.
///
/// Additionally to the identifier of the new property and the nested pattern,
/// this may also include an optional default initializer.
///
/// More information:
/// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - BindingProperty][spec1]
///
/// [spec1]: https://tc39.es/ecma262/#prod-BindingProperty
Pattern {
/// The identifier name of the property to be destructured.
name: PropertyName,
/// The pattern where the property value will be destructured.
pattern: Pattern,
/// An optional default value for the variable, in case the property doesn't exist.
default_init: Option<Expression>,
},
}
impl ToInternedString for ObjectPatternElement {
fn to_interned_string(&self, interner: &Interner) -> String {
match self {
Self::SingleName {
ident,
name,
default_init,
} => {
let mut buf = match name {
PropertyName::Literal(name) if name == ident => {
format!(" {}", interner.resolve_expect(ident.sym()))
}
PropertyName::Literal(name) => {
format!(
" {} : {}",
interner.resolve_expect(*name),
interner.resolve_expect(ident.sym())
)
}
PropertyName::Computed(node) => {
format!(
" [{}] : {}",
node.to_interned_string(interner),
interner.resolve_expect(ident.sym())
)
}
};
if let Some(ref init) = default_init {
buf.push_str(&format!(" = {}", init.to_interned_string(interner)));
}
buf
}
Self::RestProperty {
ident,
excluded_keys: _,
} => {
format!(" ... {}", interner.resolve_expect(ident.sym()))
}
Self::AssignmentRestPropertyAccess { access, .. } => {
format!(" ... {}", access.to_interned_string(interner))
}
Self::AssignmentPropertyAccess {
name,
access,
default_init,
} => {
let mut buf = match name {
PropertyName::Literal(name) => {
format!(
" {} : {}",
interner.resolve_expect(*name),
access.to_interned_string(interner)
)
}
PropertyName::Computed(node) => {
format!(
" [{}] : {}",
node.to_interned_string(interner),
access.to_interned_string(interner)
)
}
};
if let Some(init) = &default_init {
buf.push_str(&format!(" = {}", init.to_interned_string(interner)));
}
buf
}
Self::Pattern {
name,
pattern,
default_init,
} => {
let mut buf = match name {
PropertyName::Literal(name) => {
format!(
" {} : {}",
interner.resolve_expect(*name),
pattern.to_interned_string(interner),
)
}
PropertyName::Computed(node) => {
format!(
" [{}] : {}",
node.to_interned_string(interner),
pattern.to_interned_string(interner),
)
}
};
if let Some(ref init) = default_init {
buf.push_str(&format!(" = {}", init.to_interned_string(interner)));
}
buf
}
}
}
}
impl VisitWith for ObjectPatternElement {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::SingleName {
name,
ident,
default_init,
} => {
try_break!(visitor.visit_property_name(name));
try_break!(visitor.visit_identifier(ident));
if let Some(expr) = default_init {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
Self::RestProperty { ident, .. } => visitor.visit_identifier(ident),
Self::AssignmentPropertyAccess {
name,
access,
default_init,
} => {
try_break!(visitor.visit_property_name(name));
try_break!(visitor.visit_property_access(access));
if let Some(expr) = default_init {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
Self::AssignmentRestPropertyAccess { access, .. } => {
visitor.visit_property_access(access)
}
Self::Pattern {
name,
pattern,
default_init,
} => {
try_break!(visitor.visit_property_name(name));
try_break!(visitor.visit_pattern(pattern));
if let Some(expr) = default_init {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::SingleName {
name,
ident,
default_init,
} => {
try_break!(visitor.visit_property_name_mut(name));
try_break!(visitor.visit_identifier_mut(ident));
if let Some(expr) = default_init {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
Self::RestProperty { ident, .. } => visitor.visit_identifier_mut(ident),
Self::AssignmentPropertyAccess {
name,
access,
default_init,
} => {
try_break!(visitor.visit_property_name_mut(name));
try_break!(visitor.visit_property_access_mut(access));
if let Some(expr) = default_init {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
Self::AssignmentRestPropertyAccess { access, .. } => {
visitor.visit_property_access_mut(access)
}
Self::Pattern {
name,
pattern,
default_init,
} => {
try_break!(visitor.visit_property_name_mut(name));
try_break!(visitor.visit_pattern_mut(pattern));
if let Some(expr) = default_init {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
}
}
}
/// The different types of bindings that an array binding pattern may contain.
///
/// Corresponds to the [`BindingElement`][spec1] and the [`AssignmentElement`][spec2] nodes.
///
/// [spec1]: https://tc39.es/ecma262/#prod-BindingElement
/// [spec2]: https://tc39.es/ecma262/#prod-AssignmentElement
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum ArrayPatternElement {
/// Elision represents the elision of an item in the array binding pattern.
///
/// An `Elision` may occur at multiple points in the pattern and may be multiple elisions.
/// This variant strictly represents one elision. If there are multiple, this should be used multiple times.
///
/// More information:
/// - [ECMAScript reference: 13.2.4 Array Initializer - Elision][spec1]
///
/// [spec1]: https://tc39.es/ecma262/#prod-Elision
Elision,
/// SingleName represents a `SingleName` with an identifier and an optional default initializer.
///
/// More information:
/// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - SingleNameBinding][spec1]
///
/// [spec1]: https://tc39.es/ecma262/#prod-SingleNameBinding
SingleName {
/// The variable name where the index element will be stored.
ident: Identifier,
/// An optional default value for the variable, in case the index element doesn't exist.
default_init: Option<Expression>,
},
/// PropertyAccess represents a binding with a property accessor.
///
/// Note: According to the spec this is not part of an ArrayBindingPattern.
/// This is only used when a array literal is used as the left-hand-side of an assignment expression.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
PropertyAccess {
/// The property access where the index element will be stored.
access: PropertyAccess,
},
/// Pattern represents a `Pattern` in an `Element` of an array pattern.
///
/// The pattern and the optional default initializer are both stored in the DeclarationPattern.
///
/// More information:
/// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - BindingElement][spec1]
///
/// [spec1]: https://tc39.es/ecma262/#prod-BindingElement
Pattern {
/// The pattern where the index element will be stored.
pattern: Pattern,
/// An optional default value for the pattern, in case the index element doesn't exist.
default_init: Option<Expression>,
},
/// SingleNameRest represents a `BindingIdentifier` in a `BindingRestElement` of an array pattern.
///
/// More information:
/// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - BindingRestElement][spec1]
///
/// [spec1]: https://tc39.es/ecma262/#prod-BindingRestElement
SingleNameRest {
/// The variable where the unassigned index elements will be stored.
ident: Identifier,
},
/// PropertyAccess represents a rest (spread operator) with a property accessor.
///
/// Note: According to the spec this is not part of an ArrayBindingPattern.
/// This is only used when a array literal is used as the left-hand-side of an assignment expression.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
PropertyAccessRest {
/// The property access where the unassigned index elements will be stored.
access: PropertyAccess,
},
/// PatternRest represents a `Pattern` in a `RestElement` of an array pattern.
///
/// More information:
/// - [ECMAScript reference: 14.3.3 Destructuring Binding Patterns - BindingRestElement][spec1]
///
/// [spec1]: https://tc39.es/ecma262/#prod-BindingRestElement
PatternRest {
/// The pattern where the unassigned index elements will be stored.
pattern: Pattern,
},
}
impl ToInternedString for ArrayPatternElement {
fn to_interned_string(&self, interner: &Interner) -> String {
match self {
Self::Elision => " ".to_owned(),
Self::SingleName {
ident,
default_init,
} => {
let mut buf = format!(" {}", interner.resolve_expect(ident.sym()));
if let Some(ref init) = default_init {
buf.push_str(&format!(" = {}", init.to_interned_string(interner)));
}
buf
}
Self::PropertyAccess { access } => {
format!(" {}", access.to_interned_string(interner))
}
Self::Pattern {
pattern,
default_init,
} => {
let mut buf = format!(" {}", pattern.to_interned_string(interner));
if let Some(init) = default_init {
buf.push_str(&format!(" = {}", init.to_interned_string(interner)));
}
buf
}
Self::SingleNameRest { ident } => {
format!(" ... {}", interner.resolve_expect(ident.sym()))
}
Self::PropertyAccessRest { access } => {
format!(" ... {}", access.to_interned_string(interner))
}
Self::PatternRest { pattern } => {
format!(" ... {}", pattern.to_interned_string(interner))
}
}
}
}
impl VisitWith for ArrayPatternElement {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::SingleName {
ident,
default_init,
} => {
try_break!(visitor.visit_identifier(ident));
if let Some(expr) = default_init {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
Self::PropertyAccess { access } | Self::PropertyAccessRest { access } => {
visitor.visit_property_access(access)
}
Self::Pattern {
pattern,
default_init,
} => {
try_break!(visitor.visit_pattern(pattern));
if let Some(expr) = default_init {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
Self::SingleNameRest { ident } => visitor.visit_identifier(ident),
Self::PatternRest { pattern } => visitor.visit_pattern(pattern),
Self::Elision => {
// special case to be handled by user
ControlFlow::Continue(())
}
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::SingleName {
ident,
default_init,
} => {
try_break!(visitor.visit_identifier_mut(ident));
if let Some(expr) = default_init {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
Self::PropertyAccess { access } | Self::PropertyAccessRest { access } => {
visitor.visit_property_access_mut(access)
}
Self::Pattern {
pattern,
default_init,
} => {
try_break!(visitor.visit_pattern_mut(pattern));
if let Some(expr) = default_init {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
Self::SingleNameRest { ident } => visitor.visit_identifier_mut(ident),
Self::PatternRest { pattern } => visitor.visit_pattern_mut(pattern),
Self::Elision => {
// special case to be handled by user
ControlFlow::Continue(())
}
}
}
}

View File

@@ -0,0 +1,302 @@
use std::{cmp::Ordering, fmt, num::NonZeroU32};
/// A position in the ECMAScript source code.
///
/// Stores both the column number and the line number.
///
/// ## Similar Implementations
/// [V8: Location](https://cs.chromium.org/chromium/src/v8/src/parsing/scanner.h?type=cs&q=isValid+Location&g=0&l=216)
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Position {
/// Line number.
line_number: NonZeroU32,
/// Column number.
column_number: NonZeroU32,
}
impl Position {
/// Creates a new `Position`.
#[inline]
#[track_caller]
#[must_use]
pub fn new(line_number: u32, column_number: u32) -> Self {
Self {
line_number: NonZeroU32::new(line_number).expect("line number cannot be 0"),
column_number: NonZeroU32::new(column_number).expect("column number cannot be 0"),
}
}
/// Gets the line number of the position.
#[inline]
#[must_use]
pub const fn line_number(self) -> u32 {
self.line_number.get()
}
/// Gets the column number of the position.
#[inline]
#[must_use]
pub const fn column_number(self) -> u32 {
self.column_number.get()
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.line_number, self.column_number)
}
}
/// A span in the ECMAScript source code.
///
/// Stores a start position and an end position.
///
/// Note that spans are of the form [start, end) i.e. that the start position is inclusive
/// and the end position is exclusive.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Span {
start: Position,
end: Position,
}
impl Span {
/// Creates a new `Span`.
///
/// # Panics
///
/// Panics if the start position is bigger than the end position.
#[inline]
#[track_caller]
#[must_use]
pub fn new(start: Position, end: Position) -> Self {
assert!(start <= end, "a span cannot start after its end");
Self { start, end }
}
/// Gets the starting position of the span.
#[inline]
#[must_use]
pub const fn start(self) -> Position {
self.start
}
/// Gets the final position of the span.
#[inline]
#[must_use]
pub const fn end(self) -> Position {
self.end
}
/// Checks if this span inclusively contains another span or position.
pub fn contains<S>(self, other: S) -> bool
where
S: Into<Self>,
{
let other = other.into();
self.start <= other.start && self.end >= other.end
}
}
impl From<Position> for Span {
fn from(pos: Position) -> Self {
Self {
start: pos,
end: pos,
}
}
}
impl PartialOrd for Span {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self == other {
Some(Ordering::Equal)
} else if self.end < other.start {
Some(Ordering::Less)
} else if self.start > other.end {
Some(Ordering::Greater)
} else {
None
}
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}..{}]", self.start, self.end)
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::similar_names)]
#![allow(unused_must_use)]
use super::{Position, Span};
/// Checks that we cannot create a position with 0 as the column.
#[test]
#[should_panic]
fn invalid_position_column() {
Position::new(10, 0);
}
/// Checks that we cannot create a position with 0 as the line.
#[test]
#[should_panic]
fn invalid_position_line() {
Position::new(0, 10);
}
/// Checks that the `PartialEq` implementation of `Position` is consistent.
#[test]
fn position_equality() {
assert_eq!(Position::new(10, 50), Position::new(10, 50));
assert_ne!(Position::new(10, 50), Position::new(10, 51));
assert_ne!(Position::new(10, 50), Position::new(11, 50));
assert_ne!(Position::new(10, 50), Position::new(11, 51));
}
/// Checks that the `PartialOrd` implementation of `Position` is consistent.
#[test]
fn position_order() {
assert!(Position::new(10, 50) < Position::new(10, 51));
assert!(Position::new(9, 50) < Position::new(10, 50));
assert!(Position::new(10, 50) < Position::new(11, 51));
assert!(Position::new(10, 50) < Position::new(11, 49));
assert!(Position::new(10, 51) > Position::new(10, 50));
assert!(Position::new(10, 50) > Position::new(9, 50));
assert!(Position::new(11, 51) > Position::new(10, 50));
assert!(Position::new(11, 49) > Position::new(10, 50));
}
/// Checks that the position getters actually retrieve correct values.
#[test]
fn position_getters() {
let pos = Position::new(10, 50);
assert_eq!(pos.line_number(), 10);
assert_eq!(pos.column_number(), 50);
}
/// Checks that the string representation of a position is correct.
#[test]
fn position_to_string() {
let pos = Position::new(10, 50);
assert_eq!("10:50", pos.to_string());
assert_eq!("10:50", pos.to_string());
}
/// Checks that we cannot create an invalid span.
#[test]
#[should_panic]
fn invalid_span() {
let a = Position::new(10, 30);
let b = Position::new(10, 50);
Span::new(b, a);
}
/// Checks that we can create valid spans.
#[test]
fn span_creation() {
let a = Position::new(10, 30);
let b = Position::new(10, 50);
let _ = Span::new(a, b);
let _ = Span::new(a, a);
let _ = Span::from(a);
}
/// Checks that the `PartialEq` implementation of `Span` is consistent.
#[test]
fn span_equality() {
let a = Position::new(10, 50);
let b = Position::new(10, 52);
let c = Position::new(11, 20);
let span_ab = Span::new(a, b);
let span_ab_2 = Span::new(a, b);
let span_ac = Span::new(a, c);
let span_bc = Span::new(b, c);
assert_eq!(span_ab, span_ab_2);
assert_ne!(span_ab, span_ac);
assert_ne!(span_ab, span_bc);
assert_ne!(span_bc, span_ac);
let span_a = Span::from(a);
let span_aa = Span::new(a, a);
assert_eq!(span_a, span_aa);
}
/// Checks that the getters retrieve the correct value.
#[test]
fn span_getters() {
let a = Position::new(10, 50);
let b = Position::new(10, 52);
let span = Span::new(a, b);
assert_eq!(span.start(), a);
assert_eq!(span.end(), b);
}
/// Checks that the `Span::contains()` method works properly.
#[test]
fn span_contains() {
let a = Position::new(10, 50);
let b = Position::new(10, 52);
let c = Position::new(11, 20);
let d = Position::new(12, 5);
let span_ac = Span::new(a, c);
assert!(span_ac.contains(b));
let span_ab = Span::new(a, b);
let span_cd = Span::new(c, d);
assert!(!span_ab.contains(span_cd));
assert!(span_ab.contains(b));
let span_ad = Span::new(a, d);
let span_bc = Span::new(b, c);
assert!(span_ad.contains(span_bc));
assert!(!span_bc.contains(span_ad));
let span_ac = Span::new(a, c);
let span_bd = Span::new(b, d);
assert!(!span_ac.contains(span_bd));
assert!(!span_bd.contains(span_ac));
}
/// Checks that the string representation of a span is correct.
#[test]
fn span_to_string() {
let a = Position::new(10, 50);
let b = Position::new(11, 20);
let span = Span::new(a, b);
assert_eq!("[10:50..11:20]", span.to_string());
assert_eq!("[10:50..11:20]", span.to_string());
}
/// Checks that the ordering of spans is correct.
#[test]
fn span_ordering() {
let a = Position::new(10, 50);
let b = Position::new(10, 52);
let c = Position::new(11, 20);
let d = Position::new(12, 5);
let span_ab = Span::new(a, b);
let span_cd = Span::new(c, d);
assert!(span_ab < span_cd);
assert!(span_cd > span_ab);
}
}

View File

@@ -0,0 +1,371 @@
//! Property definition related types, used in object literals and class definitions.
use crate::function::PrivateName;
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
use super::{
expression::{literal::Literal, Identifier},
function::{AsyncFunction, AsyncGenerator, Function, Generator},
Expression,
};
/// Describes the definition of a property within an object literal.
///
/// A property has a name (a string) and a value (primitive, method, or object reference).
/// Note that when we say that "a property holds an object", that is shorthand for "a property holds an object reference".
/// This distinction matters because the original referenced object remains unchanged when you change the property's value.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition
/// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/property/JavaScript
// TODO: Support all features: https://tc39.es/ecma262/#prod-PropertyDefinition
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum PropertyDefinition {
/// Puts a variable into an object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-IdentifierReference
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Property_definitions
IdentifierReference(Identifier),
/// Binds a property name to a JavaScript value.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Property_definitions
Property(PropertyName, Expression),
/// A property of an object can also refer to a function or a getter or setter method.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Method_definitions
MethodDefinition(PropertyName, MethodDefinition),
/// The Rest/Spread Properties for ECMAScript proposal (stage 4) adds spread properties to object literals.
/// It copies own enumerable properties from a provided object onto a new object.
///
/// Shallow-cloning (excluding `prototype`) or merging objects is now possible using a shorter syntax than `Object.assign()`.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Spread_properties
SpreadObject(Expression),
/// Cover grammar for when an object literal is used as an object binding pattern.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-CoverInitializedName
CoverInitializedName(Identifier, Expression),
}
impl VisitWith for PropertyDefinition {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::IdentifierReference(id) => visitor.visit_identifier(id),
Self::Property(pn, expr) => {
try_break!(visitor.visit_property_name(pn));
visitor.visit_expression(expr)
}
Self::MethodDefinition(pn, md) => {
try_break!(visitor.visit_property_name(pn));
visitor.visit_method_definition(md)
}
Self::SpreadObject(expr) => visitor.visit_expression(expr),
Self::CoverInitializedName(id, expr) => {
try_break!(visitor.visit_identifier(id));
visitor.visit_expression(expr)
}
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::IdentifierReference(id) => visitor.visit_identifier_mut(id),
Self::Property(pn, expr) => {
try_break!(visitor.visit_property_name_mut(pn));
visitor.visit_expression_mut(expr)
}
Self::MethodDefinition(pn, md) => {
try_break!(visitor.visit_property_name_mut(pn));
visitor.visit_method_definition_mut(md)
}
Self::SpreadObject(expr) => visitor.visit_expression_mut(expr),
Self::CoverInitializedName(id, expr) => {
try_break!(visitor.visit_identifier_mut(id));
visitor.visit_expression_mut(expr)
}
}
}
}
/// Method definition.
///
/// Starting with ECMAScript 2015, a shorter syntax for method definitions on objects initializers is introduced.
/// It is a shorthand for a function assigned to the method's name.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum MethodDefinition {
/// The `get` syntax binds an object property to a function that will be called when that property is looked up.
///
/// Sometimes it is desirable to allow access to a property that returns a dynamically computed value,
/// or you may want to reflect the status of an internal variable without requiring the use of explicit method calls.
/// In JavaScript, this can be accomplished with the use of a getter.
///
/// It is not possible to simultaneously have a getter bound to a property and have that property actually hold a value,
/// although it is possible to use a getter and a setter in conjunction to create a type of pseudo-property.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get
Get(Function),
/// The `set` syntax binds an object property to a function to be called when there is an attempt to set that property.
///
/// In JavaScript, a setter can be used to execute a function whenever a specified property is attempted to be changed.
/// Setters are most often used in conjunction with getters to create a type of pseudo-property.
/// It is not possible to simultaneously have a setter on a property that holds an actual value.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set
Set(Function),
/// Starting with ECMAScript 2015, you are able to define own methods in a shorter syntax, similar to the getters and setters.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#Method_definition_syntax
Ordinary(Function),
/// Starting with ECMAScript 2015, you are able to define own methods in a shorter syntax, similar to the getters and setters.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions#generator_methods
Generator(Generator),
/// Async generators can be used to define a method
///
/// More information
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AsyncGeneratorMethod
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions#async_generator_methods
AsyncGenerator(AsyncGenerator),
/// Async function can be used to define a method
///
/// More information
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-AsyncMethod
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions#async_methods
Async(AsyncFunction),
}
impl VisitWith for MethodDefinition {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Get(f) | Self::Set(f) | Self::Ordinary(f) => visitor.visit_function(f),
Self::Generator(g) => visitor.visit_generator(g),
Self::AsyncGenerator(ag) => visitor.visit_async_generator(ag),
Self::Async(af) => visitor.visit_async_function(af),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Get(f) | Self::Set(f) | Self::Ordinary(f) => visitor.visit_function_mut(f),
Self::Generator(g) => visitor.visit_generator_mut(g),
Self::AsyncGenerator(ag) => visitor.visit_async_generator_mut(ag),
Self::Async(af) => visitor.visit_async_function_mut(af),
}
}
}
/// `PropertyName` can be either a literal or computed.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-PropertyName
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum PropertyName {
/// A `Literal` property name can be either an identifier, a string or a numeric literal.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-LiteralPropertyName
Literal(Sym),
/// A `Computed` property name is an expression that gets evaluated and converted into a property name.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ComputedPropertyName
Computed(Expression),
}
impl PropertyName {
/// Returns the literal property name if it exists.
#[must_use]
pub const fn literal(&self) -> Option<Sym> {
if let Self::Literal(sym) = self {
Some(*sym)
} else {
None
}
}
/// Returns the expression if the property name is computed.
#[must_use]
pub const fn computed(&self) -> Option<&Expression> {
if let Self::Computed(expr) = self {
Some(expr)
} else {
None
}
}
/// Returns either the literal property name or the computed const string property name.
#[must_use]
pub const fn prop_name(&self) -> Option<Sym> {
match self {
Self::Literal(sym) | Self::Computed(Expression::Literal(Literal::String(sym))) => {
Some(*sym)
}
Self::Computed(_) => None,
}
}
}
impl ToInternedString for PropertyName {
fn to_interned_string(&self, interner: &Interner) -> String {
match self {
Self::Literal(key) => interner.resolve_expect(*key).to_string(),
Self::Computed(key) => key.to_interned_string(interner),
}
}
}
impl From<Sym> for PropertyName {
fn from(name: Sym) -> Self {
Self::Literal(name)
}
}
impl From<Expression> for PropertyName {
fn from(name: Expression) -> Self {
Self::Computed(name)
}
}
impl VisitWith for PropertyName {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Literal(sym) => visitor.visit_sym(sym),
Self::Computed(expr) => visitor.visit_expression(expr),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Literal(sym) => visitor.visit_sym_mut(sym),
Self::Computed(expr) => visitor.visit_expression_mut(expr),
}
}
}
/// `ClassElementName` can be either a property name or a private identifier.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ClassElementName
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq)]
pub enum ClassElementName {
/// A public property.
PropertyName(PropertyName),
/// A private property.
PrivateIdentifier(PrivateName),
}
impl ClassElementName {
/// Returns the property name if it exists.
#[must_use]
pub const fn literal(&self) -> Option<Sym> {
if let Self::PropertyName(name) = self {
name.literal()
} else {
None
}
}
}

View File

@@ -0,0 +1,285 @@
//! The `Punctuator` enum, which contains all punctuators used in ECMAScript.
//!
//! More information:
//! - [ECMAScript Reference][spec]
//!
//! [spec]: https://tc39.es/ecma262/#prod-Punctuator
use crate::expression::operator::{
assign::AssignOp,
binary::{ArithmeticOp, BinaryOp, BitwiseOp, LogicalOp, RelationalOp},
};
use std::{
convert::TryInto,
fmt::{Display, Error, Formatter},
};
/// All of the punctuators used in ECMAScript.
///
/// More information:
/// - [ECMAScript Reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-Punctuator
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Punctuator {
/// `+`
Add,
/// `&`
And,
/// `=>`
Arrow,
/// `=`
Assign,
/// `+=`
AssignAdd,
/// `&=`
AssignAnd,
/// `&&=`
AssignBoolAnd,
/// `||=`
AssignBoolOr,
/// `??=`,
AssignCoalesce,
/// `/=`
AssignDiv,
/// `<<=`
AssignLeftSh,
/// `%=`
AssignMod,
/// `*=`
AssignMul,
/// `|=`
AssignOr,
/// `**=`
AssignPow,
/// `>>=`
AssignRightSh,
/// `-=`
AssignSub,
/// `>>>=`
AssignURightSh,
/// `^=`
AssignXor,
/// `&&`
BoolAnd,
/// `||`
BoolOr,
/// `}`
CloseBlock,
/// `]`
CloseBracket,
/// `)`
CloseParen,
/// `??`
Coalesce,
/// `:`
Colon,
/// `,`
Comma,
/// `--`
Dec,
/// `/`
Div,
/// `.`
Dot,
/// `==`
Eq,
/// `>`
GreaterThan,
/// `>=`
GreaterThanOrEq,
/// `++`
Inc,
/// `<<`
LeftSh,
/// `<`
LessThan,
/// `<=`
LessThanOrEq,
/// `%`
Mod,
/// `*`
Mul,
/// `~`
Neg,
/// `!`
Not,
/// `!=`
NotEq,
/// `{`
OpenBlock,
/// `[`
OpenBracket,
/// `(`
OpenParen,
/// `?.`
Optional,
/// `|`
Or,
/// `**`
Exp,
/// `?`
Question,
/// `>>`
RightSh,
/// `;`
Semicolon,
/// `...`
Spread,
/// `===`
StrictEq,
/// `!==`
StrictNotEq,
/// `-`
Sub,
/// `>>>`
URightSh,
/// `^`
Xor,
}
impl Punctuator {
/// Attempts to convert a punctuator (`+`, `=`...) to an Assign Operator
///
/// If there is no match, `None` will be returned.
#[must_use]
pub const fn as_assign_op(self) -> Option<AssignOp> {
match self {
Self::Assign => Some(AssignOp::Assign),
Self::AssignAdd => Some(AssignOp::Add),
Self::AssignAnd => Some(AssignOp::And),
Self::AssignBoolAnd => Some(AssignOp::BoolAnd),
Self::AssignBoolOr => Some(AssignOp::BoolOr),
Self::AssignCoalesce => Some(AssignOp::Coalesce),
Self::AssignDiv => Some(AssignOp::Div),
Self::AssignLeftSh => Some(AssignOp::Shl),
Self::AssignMod => Some(AssignOp::Mod),
Self::AssignMul => Some(AssignOp::Mul),
Self::AssignOr => Some(AssignOp::Or),
Self::AssignPow => Some(AssignOp::Exp),
Self::AssignRightSh => Some(AssignOp::Shr),
Self::AssignSub => Some(AssignOp::Sub),
Self::AssignURightSh => Some(AssignOp::Ushr),
Self::AssignXor => Some(AssignOp::Xor),
_ => None,
}
}
/// Attempts to convert a punctuator (`+`, `=`...) to a Binary Operator
///
/// If there is no match, `None` will be returned.
#[must_use]
pub const fn as_binary_op(self) -> Option<BinaryOp> {
match self {
Self::Add => Some(BinaryOp::Arithmetic(ArithmeticOp::Add)),
Self::Sub => Some(BinaryOp::Arithmetic(ArithmeticOp::Sub)),
Self::Mul => Some(BinaryOp::Arithmetic(ArithmeticOp::Mul)),
Self::Div => Some(BinaryOp::Arithmetic(ArithmeticOp::Div)),
Self::Mod => Some(BinaryOp::Arithmetic(ArithmeticOp::Mod)),
Self::And => Some(BinaryOp::Bitwise(BitwiseOp::And)),
Self::Or => Some(BinaryOp::Bitwise(BitwiseOp::Or)),
Self::Xor => Some(BinaryOp::Bitwise(BitwiseOp::Xor)),
Self::BoolAnd => Some(BinaryOp::Logical(LogicalOp::And)),
Self::BoolOr => Some(BinaryOp::Logical(LogicalOp::Or)),
Self::Coalesce => Some(BinaryOp::Logical(LogicalOp::Coalesce)),
Self::Eq => Some(BinaryOp::Relational(RelationalOp::Equal)),
Self::NotEq => Some(BinaryOp::Relational(RelationalOp::NotEqual)),
Self::StrictEq => Some(BinaryOp::Relational(RelationalOp::StrictEqual)),
Self::StrictNotEq => Some(BinaryOp::Relational(RelationalOp::StrictNotEqual)),
Self::LessThan => Some(BinaryOp::Relational(RelationalOp::LessThan)),
Self::GreaterThan => Some(BinaryOp::Relational(RelationalOp::GreaterThan)),
Self::GreaterThanOrEq => Some(BinaryOp::Relational(RelationalOp::GreaterThanOrEqual)),
Self::LessThanOrEq => Some(BinaryOp::Relational(RelationalOp::LessThanOrEqual)),
Self::LeftSh => Some(BinaryOp::Bitwise(BitwiseOp::Shl)),
Self::RightSh => Some(BinaryOp::Bitwise(BitwiseOp::Shr)),
Self::URightSh => Some(BinaryOp::Bitwise(BitwiseOp::UShr)),
Self::Comma => Some(BinaryOp::Comma),
_ => None,
}
}
/// Retrieves the punctuator as a static string.
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Add => "+",
Self::And => "&",
Self::Arrow => "=>",
Self::Assign => "=",
Self::AssignAdd => "+=",
Self::AssignAnd => "&=",
Self::AssignBoolAnd => "&&=",
Self::AssignBoolOr => "||=",
Self::AssignCoalesce => "??=",
Self::AssignDiv => "/=",
Self::AssignLeftSh => "<<=",
Self::AssignMod => "%=",
Self::AssignMul => "*=",
Self::AssignOr => "|=",
Self::AssignPow => "**=",
Self::AssignRightSh => ">>=",
Self::AssignSub => "-=",
Self::AssignURightSh => ">>>=",
Self::AssignXor => "^=",
Self::BoolAnd => "&&",
Self::BoolOr => "||",
Self::Coalesce => "??",
Self::CloseBlock => "}",
Self::CloseBracket => "]",
Self::CloseParen => ")",
Self::Colon => ":",
Self::Comma => ",",
Self::Dec => "--",
Self::Div => "/",
Self::Dot => ".",
Self::Eq => "==",
Self::GreaterThan => ">",
Self::GreaterThanOrEq => ">=",
Self::Inc => "++",
Self::LeftSh => "<<",
Self::LessThan => "<",
Self::LessThanOrEq => "<=",
Self::Mod => "%",
Self::Mul => "*",
Self::Neg => "~",
Self::Not => "!",
Self::NotEq => "!=",
Self::OpenBlock => "{",
Self::OpenBracket => "[",
Self::OpenParen => "(",
Self::Optional => "?.",
Self::Or => "|",
Self::Exp => "**",
Self::Question => "?",
Self::RightSh => ">>",
Self::Semicolon => ";",
Self::Spread => "...",
Self::StrictEq => "===",
Self::StrictNotEq => "!==",
Self::Sub => "-",
Self::URightSh => ">>>",
Self::Xor => "^",
}
}
}
impl TryInto<BinaryOp> for Punctuator {
type Error = String;
fn try_into(self) -> Result<BinaryOp, Self::Error> {
self.as_binary_op()
.ok_or_else(|| format!("No binary operation for {self}"))
}
}
impl Display for Punctuator {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
write!(f, "{}", self.as_str())
}
}
impl From<Punctuator> for Box<str> {
fn from(p: Punctuator) -> Self {
p.as_str().into()
}
}

View File

@@ -0,0 +1,85 @@
//! Block AST node.
use crate::{
visitor::{VisitWith, Visitor, VisitorMut},
Statement, StatementList,
};
use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow;
/// A `block` statement (or compound statement in other languages) is used to group zero or
/// more statements.
///
/// The block statement is often called compound statement in other languages.
/// It allows you to use multiple statements where ECMAScript expects only one statement.
/// Combining statements into blocks is a common practice in ECMAScript. The opposite behavior
/// is possible using an empty statement, where you provide no statement, although one is
/// required.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-BlockStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq, Default)]
pub struct Block {
#[cfg_attr(feature = "serde", serde(flatten))]
statements: StatementList,
}
impl Block {
/// Gets the list of statements and declarations in this block.
#[inline]
#[must_use]
pub const fn statement_list(&self) -> &StatementList {
&self.statements
}
}
impl<T> From<T> for Block
where
T: Into<StatementList>,
{
fn from(list: T) -> Self {
Self {
statements: list.into(),
}
}
}
impl ToIndentedString for Block {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
format!(
"{{\n{}{}}}",
self.statements
.to_indented_string(interner, indentation + 1),
" ".repeat(indentation)
)
}
}
impl From<Block> for Statement {
#[inline]
fn from(block: Block) -> Self {
Self::Block(block)
}
}
impl VisitWith for Block {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_statement_list(&self.statements)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_statement_list_mut(&mut self.statements)
}
}

View File

@@ -0,0 +1,119 @@
//! If statement
use crate::{
expression::Expression,
statement::Statement,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// The `if` statement executes a statement if a specified condition is [`truthy`][truthy]. If
/// the condition is [`falsy`][falsy], another statement can be executed.
///
/// Multiple `if...else` statements can be nested to create an else if clause.
///
/// Note that there is no elseif (in one word) keyword in JavaScript.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-IfStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else
/// [truthy]: https://developer.mozilla.org/en-US/docs/Glossary/truthy
/// [falsy]: https://developer.mozilla.org/en-US/docs/Glossary/falsy
/// [expression]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Expressions
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct If {
condition: Expression,
body: Box<Statement>,
else_node: Option<Box<Statement>>,
}
impl If {
/// Gets the condition of the if statement.
#[inline]
#[must_use]
pub const fn cond(&self) -> &Expression {
&self.condition
}
/// Gets the body to execute if the condition is true.
#[inline]
#[must_use]
pub const fn body(&self) -> &Statement {
&self.body
}
/// Gets the `else` node, if it has one.
#[inline]
pub fn else_node(&self) -> Option<&Statement> {
self.else_node.as_ref().map(Box::as_ref)
}
/// Creates an `If` AST node.
#[inline]
#[must_use]
pub fn new(condition: Expression, body: Statement, else_node: Option<Statement>) -> Self {
Self {
condition,
body: body.into(),
else_node: else_node.map(Box::new),
}
}
}
impl ToIndentedString for If {
fn to_indented_string(&self, interner: &Interner, indent: usize) -> String {
let mut buf = format!("if ({}) ", self.cond().to_interned_string(interner));
match self.else_node() {
Some(else_e) => {
buf.push_str(&format!(
"{} else {}",
self.body().to_indented_string(interner, indent),
else_e.to_indented_string(interner, indent)
));
}
None => {
buf.push_str(&self.body().to_indented_string(interner, indent));
}
}
buf
}
}
impl From<If> for Statement {
fn from(if_stm: If) -> Self {
Self::If(if_stm)
}
}
impl VisitWith for If {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.condition));
try_break!(visitor.visit_statement(&self.body));
if let Some(stmt) = &self.else_node {
try_break!(visitor.visit_statement(stmt));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.condition));
try_break!(visitor.visit_statement_mut(&mut self.body));
if let Some(stmt) = &mut self.else_node {
try_break!(visitor.visit_statement_mut(stmt));
}
ControlFlow::Continue(())
}
}

View File

@@ -0,0 +1,79 @@
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::Statement;
/// The `break` statement terminates the current loop, switch, or label statement and transfers
/// program control to the statement following the terminated statement.
///
/// The break statement includes an optional label that allows the program to break out of a
/// labeled statement. The break statement needs to be nested within the referenced label. The
/// labeled statement can be any block statement; it does not have to be preceded by a loop
/// statement.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-BreakStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/break
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Break {
label: Option<Sym>,
}
impl Break {
/// Creates a `Break` AST node.
#[must_use]
pub const fn new(label: Option<Sym>) -> Self {
Self { label }
}
/// Gets the label of the break statement, if any.
#[must_use]
pub const fn label(&self) -> Option<Sym> {
self.label
}
}
impl ToInternedString for Break {
fn to_interned_string(&self, interner: &Interner) -> String {
self.label.map_or_else(
|| "break".to_owned(),
|label| format!("break {}", interner.resolve_expect(label)),
)
}
}
impl From<Break> for Statement {
fn from(break_smt: Break) -> Self {
Self::Break(break_smt)
}
}
impl VisitWith for Break {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(sym) = &self.label {
visitor.visit_sym(sym)
} else {
ControlFlow::Continue(())
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(sym) = &mut self.label {
visitor.visit_sym_mut(sym)
} else {
ControlFlow::Continue(())
}
}
}

View File

@@ -0,0 +1,77 @@
use crate::statement::Statement;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, Sym, ToInternedString};
use core::ops::ControlFlow;
/// The `continue` statement terminates execution of the statements in the current iteration of
/// the current or labeled loop, and continues execution of the loop with the next iteration.
///
/// The continue statement can include an optional label that allows the program to jump to the
/// next iteration of a labeled loop statement instead of the current loop. In this case, the
/// continue statement needs to be nested within this labeled statement.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ContinueStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/continue
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Continue {
label: Option<Sym>,
}
impl Continue {
/// Creates a `Continue` AST node.
#[must_use]
pub const fn new(label: Option<Sym>) -> Self {
Self { label }
}
/// Gets the label of this `Continue` statement.
#[must_use]
pub const fn label(&self) -> Option<Sym> {
self.label
}
}
impl ToInternedString for Continue {
fn to_interned_string(&self, interner: &Interner) -> String {
self.label.map_or_else(
|| "continue".to_owned(),
|label| format!("continue {}", interner.resolve_expect(label)),
)
}
}
impl From<Continue> for Statement {
fn from(cont: Continue) -> Self {
Self::Continue(cont)
}
}
impl VisitWith for Continue {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(sym) = &self.label {
visitor.visit_sym(sym)
} else {
ControlFlow::Continue(())
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(sym) = &mut self.label {
visitor.visit_sym_mut(sym)
} else {
ControlFlow::Continue(())
}
}
}

View File

@@ -0,0 +1,87 @@
use crate::{
expression::Expression,
statement::Statement,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// The `do...while` statement creates a loop that executes a specified statement until the
/// test condition evaluates to false.
///
/// The condition is evaluated after executing the statement, resulting in the specified
/// statement executing at least once.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-do-while-statement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/do...while
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct DoWhileLoop {
body: Box<Statement>,
condition: Expression,
}
impl DoWhileLoop {
/// Gets the body of the do-while loop.
#[inline]
#[must_use]
pub const fn body(&self) -> &Statement {
&self.body
}
/// Gets the condition of the do-while loop.
#[inline]
#[must_use]
pub const fn cond(&self) -> &Expression {
&self.condition
}
/// Creates a `DoWhileLoop` AST node.
#[inline]
#[must_use]
pub fn new(body: Statement, condition: Expression) -> Self {
Self {
body: body.into(),
condition,
}
}
}
impl ToIndentedString for DoWhileLoop {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
format!(
"do {} while ({})",
self.body().to_indented_string(interner, indentation),
self.cond().to_interned_string(interner)
)
}
}
impl From<DoWhileLoop> for Statement {
fn from(do_while: DoWhileLoop) -> Self {
Self::DoWhileLoop(do_while)
}
}
impl VisitWith for DoWhileLoop {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_statement(&self.body));
visitor.visit_expression(&self.condition)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_statement_mut(&mut self.body));
visitor.visit_expression_mut(&mut self.condition)
}
}

View File

@@ -0,0 +1,98 @@
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
expression::Expression,
statement::{iteration::IterableLoopInitializer, Statement},
};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// A `for...in` loop statement, as defined by the [spec].
///
/// [`for...in`][forin] statements loop over all enumerable string properties of an object, including
/// inherited properties.
///
/// [forin]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in
/// [spec]: https://tc39.es/ecma262/#prod-ForInOfStatement
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct ForInLoop {
initializer: IterableLoopInitializer,
target: Expression,
body: Box<Statement>,
}
impl ForInLoop {
/// Creates a new `ForInLoop`.
#[inline]
#[must_use]
pub fn new(initializer: IterableLoopInitializer, target: Expression, body: Statement) -> Self {
Self {
initializer,
target,
body: body.into(),
}
}
/// Gets the initializer of the for...in loop.
#[inline]
#[must_use]
pub const fn initializer(&self) -> &IterableLoopInitializer {
&self.initializer
}
/// Gets the target object of the for...in loop.
#[inline]
#[must_use]
pub const fn target(&self) -> &Expression {
&self.target
}
/// Gets the body of the for...in loop.
#[inline]
#[must_use]
pub const fn body(&self) -> &Statement {
&self.body
}
}
impl ToIndentedString for ForInLoop {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = format!(
"for ({} in {}) ",
self.initializer.to_interned_string(interner),
self.target.to_interned_string(interner)
);
buf.push_str(&self.body().to_indented_string(interner, indentation));
buf
}
}
impl From<ForInLoop> for Statement {
#[inline]
fn from(for_in: ForInLoop) -> Self {
Self::ForInLoop(for_in)
}
}
impl VisitWith for ForInLoop {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_iterable_loop_initializer(&self.initializer));
try_break!(visitor.visit_expression(&self.target));
visitor.visit_statement(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_iterable_loop_initializer_mut(&mut self.initializer));
try_break!(visitor.visit_expression_mut(&mut self.target));
visitor.visit_statement_mut(&mut self.body)
}
}

View File

@@ -0,0 +1,263 @@
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
declaration::{LexicalDeclaration, VarDeclaration},
statement::Statement,
Expression,
};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// The `for` statement creates a loop that consists of three optional expressions.
///
/// A [`for`][mdn] loop repeats until a specified condition evaluates to `false`.
/// The JavaScript for loop is similar to the Java and C for loop.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-ForDeclaration
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct ForLoop {
#[cfg_attr(feature = "serde", serde(flatten))]
inner: Box<InnerForLoop>,
}
impl ForLoop {
/// Creates a new for loop AST node.
#[inline]
#[must_use]
pub fn new(
init: Option<ForLoopInitializer>,
condition: Option<Expression>,
final_expr: Option<Expression>,
body: Statement,
) -> Self {
Self {
inner: Box::new(InnerForLoop::new(init, condition, final_expr, body)),
}
}
/// Gets the initialization node.
#[inline]
#[must_use]
pub const fn init(&self) -> Option<&ForLoopInitializer> {
self.inner.init()
}
/// Gets the loop condition node.
#[inline]
#[must_use]
pub const fn condition(&self) -> Option<&Expression> {
self.inner.condition()
}
/// Gets the final expression node.
#[inline]
#[must_use]
pub const fn final_expr(&self) -> Option<&Expression> {
self.inner.final_expr()
}
/// Gets the body of the for loop.
#[inline]
#[must_use]
pub const fn body(&self) -> &Statement {
self.inner.body()
}
}
impl ToIndentedString for ForLoop {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = String::from("for (");
if let Some(init) = self.init() {
buf.push_str(&init.to_interned_string(interner));
}
buf.push_str("; ");
if let Some(condition) = self.condition() {
buf.push_str(&condition.to_interned_string(interner));
}
buf.push_str("; ");
if let Some(final_expr) = self.final_expr() {
buf.push_str(&final_expr.to_interned_string(interner));
}
buf.push_str(&format!(
") {}",
self.inner.body().to_indented_string(interner, indentation)
));
buf
}
}
impl From<ForLoop> for Statement {
#[inline]
fn from(for_loop: ForLoop) -> Self {
Self::ForLoop(for_loop)
}
}
impl VisitWith for ForLoop {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(fli) = &self.inner.init {
try_break!(visitor.visit_for_loop_initializer(fli));
}
if let Some(expr) = &self.inner.condition {
try_break!(visitor.visit_expression(expr));
}
if let Some(expr) = &self.inner.final_expr {
try_break!(visitor.visit_expression(expr));
}
visitor.visit_statement(&self.inner.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(fli) = &mut self.inner.init {
try_break!(visitor.visit_for_loop_initializer_mut(fli));
}
if let Some(expr) = &mut self.inner.condition {
try_break!(visitor.visit_expression_mut(expr));
}
if let Some(expr) = &mut self.inner.final_expr {
try_break!(visitor.visit_expression_mut(expr));
}
visitor.visit_statement_mut(&mut self.inner.body)
}
}
/// Inner structure to avoid multiple indirections in the heap.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
struct InnerForLoop {
init: Option<ForLoopInitializer>,
condition: Option<Expression>,
final_expr: Option<Expression>,
body: Statement,
}
impl InnerForLoop {
/// Creates a new inner for loop.
#[inline]
const fn new(
init: Option<ForLoopInitializer>,
condition: Option<Expression>,
final_expr: Option<Expression>,
body: Statement,
) -> Self {
Self {
init,
condition,
final_expr,
body,
}
}
/// Gets the initialization node.
#[inline]
const fn init(&self) -> Option<&ForLoopInitializer> {
self.init.as_ref()
}
/// Gets the loop condition node.
#[inline]
const fn condition(&self) -> Option<&Expression> {
self.condition.as_ref()
}
/// Gets the final expression node.
#[inline]
const fn final_expr(&self) -> Option<&Expression> {
self.final_expr.as_ref()
}
/// Gets the body of the for loop.
#[inline]
const fn body(&self) -> &Statement {
&self.body
}
}
/// A [`ForLoop`] initializer, as defined by the [spec].
///
/// A `ForLoop` initializer differs a lot from an
/// [`IterableLoopInitializer`][super::IterableLoopInitializer], since it can contain any arbitrary
/// expression instead of only accessors and patterns. Additionally, it can also contain many variable
/// declarations instead of only one.
///
/// [spec]: https://tc39.es/ecma262/#prod-ForStatement
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum ForLoopInitializer {
/// An expression initializer.
Expression(Expression),
/// A var declaration initializer.
Var(VarDeclaration),
/// A lexical declaration initializer.
Lexical(LexicalDeclaration),
}
impl ToInternedString for ForLoopInitializer {
fn to_interned_string(&self, interner: &Interner) -> String {
match self {
Self::Var(var) => var.to_interned_string(interner),
Self::Lexical(lex) => lex.to_interned_string(interner),
Self::Expression(expr) => expr.to_interned_string(interner),
}
}
}
impl From<Expression> for ForLoopInitializer {
#[inline]
fn from(expr: Expression) -> Self {
Self::Expression(expr)
}
}
impl From<LexicalDeclaration> for ForLoopInitializer {
#[inline]
fn from(list: LexicalDeclaration) -> Self {
Self::Lexical(list)
}
}
impl From<VarDeclaration> for ForLoopInitializer {
#[inline]
fn from(list: VarDeclaration) -> Self {
Self::Var(list)
}
}
impl VisitWith for ForLoopInitializer {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Expression(expr) => visitor.visit_expression(expr),
Self::Var(vd) => visitor.visit_var_declaration(vd),
Self::Lexical(ld) => visitor.visit_lexical_declaration(ld),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Expression(expr) => visitor.visit_expression_mut(expr),
Self::Var(vd) => visitor.visit_var_declaration_mut(vd),
Self::Lexical(ld) => visitor.visit_lexical_declaration_mut(ld),
}
}
}

View File

@@ -0,0 +1,115 @@
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
expression::Expression,
statement::{iteration::IterableLoopInitializer, Statement},
};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// A `for...of` loop statement, as defined by the [spec].
///
/// [`for..of`][forof] statements loop over a sequence of values obtained from an iterable object (Array,
/// String, Map, generators).
///
/// This type combines `for..of` and [`for await...of`][forawait] statements in a single structure,
/// since `for await...of` is essentially the same statement but with async iterable objects
/// as the source of iteration.
///
/// [forof]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of
/// [spec]: https://tc39.es/ecma262/#prod-ForInOfStatement
/// [forawait]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct ForOfLoop {
init: IterableLoopInitializer,
iterable: Expression,
body: Box<Statement>,
r#await: bool,
}
impl ForOfLoop {
/// Creates a new "for of" loop AST node.
#[inline]
#[must_use]
pub fn new(
init: IterableLoopInitializer,
iterable: Expression,
body: Statement,
r#await: bool,
) -> Self {
Self {
init,
iterable,
body: body.into(),
r#await,
}
}
/// Gets the initializer of the for...of loop.
#[inline]
#[must_use]
pub const fn initializer(&self) -> &IterableLoopInitializer {
&self.init
}
/// Gets the iterable expression of the for...of loop.
#[inline]
#[must_use]
pub const fn iterable(&self) -> &Expression {
&self.iterable
}
/// Gets the body to execute in the for...of loop.
#[inline]
#[must_use]
pub const fn body(&self) -> &Statement {
&self.body
}
/// Returns true if this "for...of" loop is an "for await...of" loop.
#[inline]
#[must_use]
pub const fn r#await(&self) -> bool {
self.r#await
}
}
impl ToIndentedString for ForOfLoop {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
format!(
"for ({} of {}) {}",
self.init.to_interned_string(interner),
self.iterable.to_interned_string(interner),
self.body().to_indented_string(interner, indentation)
)
}
}
impl From<ForOfLoop> for Statement {
#[inline]
fn from(for_of: ForOfLoop) -> Self {
Self::ForOfLoop(for_of)
}
}
impl VisitWith for ForOfLoop {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_iterable_loop_initializer(&self.init));
try_break!(visitor.visit_expression(&self.iterable));
visitor.visit_statement(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_iterable_loop_initializer_mut(&mut self.init));
try_break!(visitor.visit_expression_mut(&mut self.iterable));
visitor.visit_statement_mut(&mut self.body)
}
}

View File

@@ -0,0 +1,94 @@
//! Iteration nodes
mod r#break;
mod r#continue;
mod do_while_loop;
mod for_in_loop;
mod for_loop;
mod for_of_loop;
mod while_loop;
use crate::{
declaration::Binding,
expression::{access::PropertyAccess, Identifier},
pattern::Pattern,
};
use core::ops::ControlFlow;
pub use self::{
do_while_loop::DoWhileLoop,
for_in_loop::ForInLoop,
for_loop::{ForLoop, ForLoopInitializer},
for_of_loop::ForOfLoop,
r#break::Break,
r#continue::Continue,
while_loop::WhileLoop,
};
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, ToInternedString};
/// A `for-in`, `for-of` and `for-await-of` loop initializer.
///
/// The [spec] specifies only single bindings for the listed types of loops, which makes us
/// unable to use plain `LexicalDeclaration`s or `VarStatement`s as initializers, since those
/// can have more than one binding.
///
/// [spec]: https://tc39.es/ecma262/#prod-ForInOfStatement
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum IterableLoopInitializer {
/// An already declared variable.
Identifier(Identifier),
/// A property access.
Access(PropertyAccess),
/// A new var declaration.
Var(Binding),
/// A new let declaration.
Let(Binding),
/// A new const declaration.
Const(Binding),
/// A pattern with already declared variables.
Pattern(Pattern),
}
impl ToInternedString for IterableLoopInitializer {
fn to_interned_string(&self, interner: &Interner) -> String {
let (binding, pre) = match self {
Self::Identifier(ident) => return ident.to_interned_string(interner),
Self::Pattern(pattern) => return pattern.to_interned_string(interner),
Self::Access(access) => return access.to_interned_string(interner),
Self::Var(binding) => (binding, "var"),
Self::Let(binding) => (binding, "let"),
Self::Const(binding) => (binding, "const"),
};
format!("{pre} {}", binding.to_interned_string(interner))
}
}
impl VisitWith for IterableLoopInitializer {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Identifier(id) => visitor.visit_identifier(id),
Self::Access(pa) => visitor.visit_property_access(pa),
Self::Var(b) | Self::Let(b) | Self::Const(b) => visitor.visit_binding(b),
Self::Pattern(p) => visitor.visit_pattern(p),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Identifier(id) => visitor.visit_identifier_mut(id),
Self::Access(pa) => visitor.visit_property_access_mut(pa),
Self::Var(b) | Self::Let(b) | Self::Const(b) => visitor.visit_binding_mut(b),
Self::Pattern(p) => visitor.visit_pattern_mut(p),
}
}
}

View File

@@ -0,0 +1,88 @@
use crate::{
expression::Expression,
statement::Statement,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// The `while` statement creates a loop that executes a specified statement as long as the
/// test condition evaluates to `true`.
///
/// The condition is evaluated before executing the statement.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-grammar-notation-WhileStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/while
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct WhileLoop {
condition: Expression,
body: Box<Statement>,
}
impl WhileLoop {
/// Creates a `WhileLoop` AST node.
#[inline]
#[must_use]
pub fn new(condition: Expression, body: Statement) -> Self {
Self {
condition,
body: body.into(),
}
}
/// Gets the condition of the while loop.
#[inline]
#[must_use]
pub const fn condition(&self) -> &Expression {
&self.condition
}
/// Gets the body of the while loop.
#[inline]
#[must_use]
pub const fn body(&self) -> &Statement {
&self.body
}
}
impl ToIndentedString for WhileLoop {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
format!(
"while ({}) {}",
self.condition().to_interned_string(interner),
self.body().to_indented_string(interner, indentation)
)
}
}
impl From<WhileLoop> for Statement {
#[inline]
fn from(while_loop: WhileLoop) -> Self {
Self::WhileLoop(while_loop)
}
}
impl VisitWith for WhileLoop {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.condition));
visitor.visit_statement(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.condition));
visitor.visit_statement_mut(&mut self.body)
}
}

View File

@@ -0,0 +1,154 @@
use crate::{
function::Function,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
Statement,
};
use boa_interner::{Interner, Sym, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// The set of Parse Nodes that can be preceded by a label, as defined by the [spec].
///
/// Semantically, a [`Labelled`] statement should only wrap [`Statement`] nodes. However,
/// old ECMAScript implementations supported [labelled function declarations][label-fn] as an extension
/// of the grammar. In the ECMAScript 2015 spec, the production of `LabelledStatement` was
/// modified to include labelled [`Function`]s as a valid node.
///
/// [spec]: https://tc39.es/ecma262/#prod-LabelledItem
/// [label-fn]: https://tc39.es/ecma262/#sec-labelled-function-declarations
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum LabelledItem {
/// A labelled [`Function`].
Function(Function),
/// A labelled [`Statement`].
Statement(Statement),
}
impl LabelledItem {
pub(crate) fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
match self {
Self::Function(f) => f.to_indented_string(interner, indentation),
Self::Statement(stmt) => stmt.to_indented_string(interner, indentation),
}
}
}
impl ToInternedString for LabelledItem {
fn to_interned_string(&self, interner: &Interner) -> String {
self.to_indented_string(interner, 0)
}
}
impl From<Function> for LabelledItem {
fn from(f: Function) -> Self {
Self::Function(f)
}
}
impl From<Statement> for LabelledItem {
fn from(stmt: Statement) -> Self {
Self::Statement(stmt)
}
}
impl VisitWith for LabelledItem {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Function(f) => visitor.visit_function(f),
Self::Statement(s) => visitor.visit_statement(s),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Function(f) => visitor.visit_function_mut(f),
Self::Statement(s) => visitor.visit_statement_mut(s),
}
}
}
/// Labelled statement nodes, as defined by the [spec].
///
/// The method [`Labelled::item`] doesn't return a [`Statement`] for compatibility reasons.
/// See [`LabelledItem`] for more information.
///
/// [spec]: https://tc39.es/ecma262/#sec-labelled-statements
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Labelled {
item: Box<LabelledItem>,
label: Sym,
}
impl Labelled {
/// Creates a new `Labelled` statement.
#[inline]
#[must_use]
pub fn new(item: LabelledItem, label: Sym) -> Self {
Self {
item: Box::new(item),
label,
}
}
/// Gets the labelled item.
#[inline]
#[must_use]
pub const fn item(&self) -> &LabelledItem {
&self.item
}
/// Gets the label name.
#[inline]
#[must_use]
pub const fn label(&self) -> Sym {
self.label
}
pub(crate) fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
format!(
"{}: {}",
interner.resolve_expect(self.label),
self.item.to_indented_string(interner, indentation)
)
}
}
impl ToInternedString for Labelled {
fn to_interned_string(&self, interner: &Interner) -> String {
self.to_indented_string(interner, 0)
}
}
impl From<Labelled> for Statement {
fn from(labelled: Labelled) -> Self {
Self::Labelled(labelled)
}
}
impl VisitWith for Labelled {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_labelled_item(&self.item));
visitor.visit_sym(&self.label)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_labelled_item_mut(&mut self.item));
visitor.visit_sym_mut(&mut self.label)
}
}

View File

@@ -0,0 +1,241 @@
//! The [`Statement`] Parse Node, as defined by the [spec].
//!
//! ECMAScript [statements] are mainly composed of control flow operations, such as [`If`],
//! [`WhileLoop`], and [`Break`]. However, it also contains statements such as [`VarDeclaration`],
//! [`Block`] or [`Expression`] which are not strictly used for control flow.
//!
//! [spec]: https://tc39.es/ecma262/#prod-Statement
//! [statements]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements
mod block;
mod r#if;
mod labelled;
mod r#return;
mod switch;
mod throw;
mod r#try;
pub mod iteration;
pub use self::{
block::Block,
iteration::{Break, Continue, DoWhileLoop, ForInLoop, ForLoop, ForOfLoop, WhileLoop},
labelled::{Labelled, LabelledItem},
r#if::If,
r#return::Return,
r#try::{Catch, ErrorHandler, Finally, Try},
switch::{Case, Switch},
throw::Throw,
};
use core::ops::ControlFlow;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use super::{declaration::VarDeclaration, expression::Expression};
/// The `Statement` Parse Node.
///
/// See the [module level documentation][self] for more information.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum Statement {
/// See [`Block`].
Block(Block),
/// See [`VarDeclaration`]
Var(VarDeclaration),
/// An empty statement.
///
/// Empty statements do nothing, just return undefined.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-EmptyStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/Empty
Empty,
/// See [`Expression`].
Expression(Expression),
/// See [`If`].
If(If),
/// See [`DoWhileLoop`].
DoWhileLoop(DoWhileLoop),
/// See [`WhileLoop`].
WhileLoop(WhileLoop),
/// See [`ForLoop`].
ForLoop(ForLoop),
/// See [`ForInLoop`].
ForInLoop(ForInLoop),
/// See [`ForOfLoop`].
ForOfLoop(ForOfLoop),
/// See[`Switch`].
Switch(Switch),
/// See [`Continue`].
Continue(Continue),
/// See [`Break`].
Break(Break),
/// See [`Return`].
Return(Return),
// TODO: Possibly add `with` statements.
/// See [`Labelled`].
Labelled(Labelled),
/// See [`Throw`].
Throw(Throw),
/// See [`Try`].
Try(Try),
}
impl Statement {
/// Implements the display formatting with indentation.
///
/// This will not prefix the value with any indentation. If you want to prefix this with proper
/// indents, use [`to_indented_string()`](Self::to_indented_string).
pub(super) fn to_no_indent_string(&self, interner: &Interner, indentation: usize) -> String {
let mut s = match self {
Self::Block(block) => return block.to_indented_string(interner, indentation),
Self::Var(var) => var.to_interned_string(interner),
Self::Empty => return ";".to_owned(),
Self::Expression(expr) => expr.to_indented_string(interner, indentation),
Self::If(if_smt) => return if_smt.to_indented_string(interner, indentation),
Self::DoWhileLoop(do_while) => do_while.to_indented_string(interner, indentation),
Self::WhileLoop(while_loop) => {
return while_loop.to_indented_string(interner, indentation)
}
Self::ForLoop(for_loop) => return for_loop.to_indented_string(interner, indentation),
Self::ForInLoop(for_in) => return for_in.to_indented_string(interner, indentation),
Self::ForOfLoop(for_of) => return for_of.to_indented_string(interner, indentation),
Self::Switch(switch) => return switch.to_indented_string(interner, indentation),
Self::Continue(cont) => cont.to_interned_string(interner),
Self::Break(break_smt) => break_smt.to_interned_string(interner),
Self::Return(ret) => ret.to_interned_string(interner),
Self::Labelled(labelled) => return labelled.to_interned_string(interner),
Self::Throw(throw) => throw.to_interned_string(interner),
Self::Try(try_catch) => return try_catch.to_indented_string(interner, indentation),
};
s.push(';');
s
}
/// Abstract operation [`IsLabelledFunction`][spec].
///
/// This recursively checks if this `Statement` is a labelled function, since adding
/// several labels in a function should not change the return value of the abstract operation:
///
/// ```Javascript
/// l1: l2: l3: l4: function f(){ }
/// ```
///
/// This should return `true` for that snippet.
///
/// [spec]: https://tc39.es/ecma262/#sec-islabelledfunction
#[inline]
#[must_use]
pub fn is_labelled_function(&self) -> bool {
match self {
Self::Labelled(stmt) => match stmt.item() {
LabelledItem::Function(_) => true,
LabelledItem::Statement(stmt) => stmt.is_labelled_function(),
},
_ => false,
}
}
}
impl ToIndentedString for Statement {
/// Creates a string of the value of the node with the given indentation. For example, an
/// indent level of 2 would produce this:
///
/// ```js
/// function hello() {
/// console.log("hello");
/// }
/// hello();
/// a = 2;
/// ```
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = match *self {
Self::Block(_) => String::new(),
_ => " ".repeat(indentation),
};
buf.push_str(&self.to_no_indent_string(interner, indentation));
buf
}
}
impl VisitWith for Statement {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Block(b) => visitor.visit_block(b),
Self::Var(v) => visitor.visit_var_declaration(v),
Self::Empty => {
// do nothing; there is nothing to visit here
ControlFlow::Continue(())
}
Self::Expression(e) => visitor.visit_expression(e),
Self::If(i) => visitor.visit_if(i),
Self::DoWhileLoop(dw) => visitor.visit_do_while_loop(dw),
Self::WhileLoop(w) => visitor.visit_while_loop(w),
Self::ForLoop(f) => visitor.visit_for_loop(f),
Self::ForInLoop(fi) => visitor.visit_for_in_loop(fi),
Self::ForOfLoop(fo) => visitor.visit_for_of_loop(fo),
Self::Switch(s) => visitor.visit_switch(s),
Self::Continue(c) => visitor.visit_continue(c),
Self::Break(b) => visitor.visit_break(b),
Self::Return(r) => visitor.visit_return(r),
Self::Labelled(l) => visitor.visit_labelled(l),
Self::Throw(th) => visitor.visit_throw(th),
Self::Try(tr) => visitor.visit_try(tr),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Block(b) => visitor.visit_block_mut(b),
Self::Var(v) => visitor.visit_var_declaration_mut(v),
Self::Empty => {
// do nothing; there is nothing to visit here
ControlFlow::Continue(())
}
Self::Expression(e) => visitor.visit_expression_mut(e),
Self::If(i) => visitor.visit_if_mut(i),
Self::DoWhileLoop(dw) => visitor.visit_do_while_loop_mut(dw),
Self::WhileLoop(w) => visitor.visit_while_loop_mut(w),
Self::ForLoop(f) => visitor.visit_for_loop_mut(f),
Self::ForInLoop(fi) => visitor.visit_for_in_loop_mut(fi),
Self::ForOfLoop(fo) => visitor.visit_for_of_loop_mut(fo),
Self::Switch(s) => visitor.visit_switch_mut(s),
Self::Continue(c) => visitor.visit_continue_mut(c),
Self::Break(b) => visitor.visit_break_mut(b),
Self::Return(r) => visitor.visit_return_mut(r),
Self::Labelled(l) => visitor.visit_labelled_mut(l),
Self::Throw(th) => visitor.visit_throw_mut(th),
Self::Try(tr) => visitor.visit_try_mut(tr),
}
}
}

View File

@@ -0,0 +1,84 @@
use crate::{
expression::Expression,
statement::Statement,
visitor::{VisitWith, Visitor, VisitorMut},
};
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
/// The `return` statement ends function execution and specifies a value to be returned to the
/// function caller.
///
/// Syntax: `return [expression];`
///
/// `expression`:
/// > The expression whose value is to be returned. If omitted, `undefined` is returned instead.
///
/// When a `return` statement is used in a function body, the execution of the function is
/// stopped. If specified, a given value is returned to the function caller.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ReturnStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/return
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Return {
target: Option<Expression>,
}
impl Return {
/// Gets the target expression value of this `Return` statement.
#[must_use]
pub const fn target(&self) -> Option<&Expression> {
self.target.as_ref()
}
/// Creates a `Return` AST node.
#[must_use]
pub const fn new(expression: Option<Expression>) -> Self {
Self { target: expression }
}
}
impl From<Return> for Statement {
fn from(return_smt: Return) -> Self {
Self::Return(return_smt)
}
}
impl ToInternedString for Return {
fn to_interned_string(&self, interner: &Interner) -> String {
self.target().map_or_else(
|| "return".to_owned(),
|ex| format!("return {}", ex.to_interned_string(interner)),
)
}
}
impl VisitWith for Return {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(expr) = &self.target {
visitor.visit_expression(expr)
} else {
ControlFlow::Continue(())
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(expr) = &mut self.target {
visitor.visit_expression_mut(expr)
} else {
ControlFlow::Continue(())
}
}
}

View File

@@ -0,0 +1,189 @@
//! Switch node.
//!
use crate::{
expression::Expression,
statement::Statement,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
StatementList,
};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// A case clause inside a [`Switch`] statement, as defined by the [spec].
///
/// Even though every [`Case`] body is a [`StatementList`], it doesn't create a new lexical
/// environment. This means any variable declared in a `Case` will be considered as part of the
/// lexical environment of the parent [`Switch`] block.
///
/// [spec]: https://tc39.es/ecma262/#prod-CaseClause
/// [truthy]: https://developer.mozilla.org/en-US/docs/Glossary/Truthy
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Case {
condition: Expression,
body: StatementList,
}
impl Case {
/// Creates a `Case` AST node.
#[inline]
#[must_use]
pub const fn new(condition: Expression, body: StatementList) -> Self {
Self { condition, body }
}
/// Gets the condition of the case.
#[inline]
#[must_use]
pub const fn condition(&self) -> &Expression {
&self.condition
}
/// Gets the statement listin the body of the case.
#[inline]
#[must_use]
pub const fn body(&self) -> &StatementList {
&self.body
}
}
impl VisitWith for Case {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.condition));
visitor.visit_statement_list(&self.body)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.condition));
visitor.visit_statement_list_mut(&mut self.body)
}
}
/// The `switch` statement evaluates an expression, matching the expression's value to a case
/// clause, and executes statements associated with that case, as well as statements in cases
/// that follow the matching case.
///
/// A `switch` statement first evaluates its expression. It then looks for the first case
/// clause whose expression evaluates to the same value as the result of the input expression
/// (using the strict comparison, `===`) and transfers control to that clause, executing the
/// associated statements. (If multiple cases match the provided value, the first case that
/// matches is selected, even if the cases are not equal to each other.)
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-SwitchStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Switch {
val: Expression,
cases: Box<[Case]>,
default: Option<StatementList>,
}
impl Switch {
/// Creates a `Switch` AST node.
#[inline]
#[must_use]
pub fn new(val: Expression, cases: Box<[Case]>, default: Option<StatementList>) -> Self {
Self {
val,
cases,
default,
}
}
/// Gets the value to switch.
#[inline]
#[must_use]
pub const fn val(&self) -> &Expression {
&self.val
}
/// Gets the list of cases for the switch statement.
#[inline]
#[must_use]
pub const fn cases(&self) -> &[Case] {
&self.cases
}
/// Gets the default statement list, if any.
#[inline]
#[must_use]
pub const fn default(&self) -> Option<&StatementList> {
self.default.as_ref()
}
}
impl ToIndentedString for Switch {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let indent = " ".repeat(indentation);
let mut buf = format!("switch ({}) {{\n", self.val().to_interned_string(interner));
for e in self.cases().iter() {
buf.push_str(&format!(
"{} case {}:\n{}",
indent,
e.condition().to_interned_string(interner),
e.body().to_indented_string(interner, indentation + 2)
));
}
if let Some(ref default) = self.default {
buf.push_str(&format!(
"{indent} default:\n{}",
default.to_indented_string(interner, indentation + 2)
));
}
buf.push_str(&format!("{indent}}}"));
buf
}
}
impl From<Switch> for Statement {
#[inline]
fn from(switch: Switch) -> Self {
Self::Switch(switch)
}
}
impl VisitWith for Switch {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_expression(&self.val));
for case in self.cases.iter() {
try_break!(visitor.visit_case(case));
}
if let Some(sl) = &self.default {
try_break!(visitor.visit_statement_list(sl));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_expression_mut(&mut self.val));
for case in self.cases.iter_mut() {
try_break!(visitor.visit_case_mut(case));
}
if let Some(sl) = &mut self.default {
try_break!(visitor.visit_statement_list_mut(sl));
}
ControlFlow::Continue(())
}
}

View File

@@ -0,0 +1,70 @@
use crate::{
statement::Statement,
visitor::{VisitWith, Visitor, VisitorMut},
Expression,
};
use boa_interner::{Interner, ToInternedString};
use core::ops::ControlFlow;
/// The `throw` statement throws a user-defined exception.
///
/// Syntax: `throw expression;`
///
/// Execution of the current function will stop (the statements after throw won't be executed),
/// and control will be passed to the first catch block in the call stack. If no catch block
/// exists among caller functions, the program will terminate.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-ThrowStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/throw
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Throw {
target: Expression,
}
impl Throw {
/// Gets the target expression of this `Throw` statement.
#[must_use]
pub const fn target(&self) -> &Expression {
&self.target
}
/// Creates a `Throw` AST node.
#[must_use]
pub const fn new(target: Expression) -> Self {
Self { target }
}
}
impl ToInternedString for Throw {
fn to_interned_string(&self, interner: &Interner) -> String {
format!("throw {}", self.target.to_interned_string(interner))
}
}
impl From<Throw> for Statement {
fn from(trw: Throw) -> Self {
Self::Throw(trw)
}
}
impl VisitWith for Throw {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_expression(&self.target)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_expression_mut(&mut self.target)
}
}

View File

@@ -0,0 +1,256 @@
//! Error handling statements
use crate::try_break;
use crate::visitor::{VisitWith, Visitor, VisitorMut};
use crate::{
declaration::Binding,
statement::{Block, Statement},
};
use boa_interner::{Interner, ToIndentedString, ToInternedString};
use core::ops::ControlFlow;
/// The `try...catch` statement marks a block of statements to try and specifies a response
/// should an exception be thrown.
///
/// The `try` statement consists of a `try`-block, which contains one or more statements. `{}`
/// must always be used, even for single statements. At least one `catch`-block, or a
/// `finally`-block, must be present.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-TryStatement
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Try {
block: Block,
handler: ErrorHandler,
}
/// The type of error handler in a [`Try`] statement.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum ErrorHandler {
/// A [`Catch`] error handler.
Catch(Catch),
/// A [`Finally`] error handler.
Finally(Finally),
/// A [`Catch`] and [`Finally`] error handler.
Full(Catch, Finally),
}
impl Try {
/// Creates a new `Try` AST node.
#[inline]
#[must_use]
pub const fn new(block: Block, handler: ErrorHandler) -> Self {
Self { block, handler }
}
/// Gets the `try` block.
#[inline]
#[must_use]
pub const fn block(&self) -> &Block {
&self.block
}
/// Gets the `catch` block, if any.
#[inline]
#[must_use]
pub const fn catch(&self) -> Option<&Catch> {
match &self.handler {
ErrorHandler::Catch(c) | ErrorHandler::Full(c, _) => Some(c),
ErrorHandler::Finally(_) => None,
}
}
/// Gets the `finally` block, if any.
#[inline]
#[must_use]
pub const fn finally(&self) -> Option<&Finally> {
match &self.handler {
ErrorHandler::Finally(f) | ErrorHandler::Full(_, f) => Some(f),
ErrorHandler::Catch(_) => None,
}
}
}
impl ToIndentedString for Try {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = format!(
"{}try {}",
" ".repeat(indentation),
self.block.to_indented_string(interner, indentation)
);
if let Some(catch) = self.catch() {
buf.push_str(&catch.to_indented_string(interner, indentation));
}
if let Some(finally) = self.finally() {
buf.push_str(&finally.to_indented_string(interner, indentation));
}
buf
}
}
impl From<Try> for Statement {
#[inline]
fn from(try_catch: Try) -> Self {
Self::Try(try_catch)
}
}
impl VisitWith for Try {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
try_break!(visitor.visit_block(&self.block));
if let Some(catch) = &self.catch() {
try_break!(visitor.visit_catch(catch));
}
if let Some(finally) = &self.finally() {
try_break!(visitor.visit_finally(finally));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
try_break!(visitor.visit_block_mut(&mut self.block));
match &mut self.handler {
ErrorHandler::Catch(c) => try_break!(visitor.visit_catch_mut(c)),
ErrorHandler::Finally(f) => try_break!(visitor.visit_finally_mut(f)),
ErrorHandler::Full(c, f) => {
try_break!(visitor.visit_catch_mut(c));
try_break!(visitor.visit_finally_mut(f));
}
}
ControlFlow::Continue(())
}
}
/// Catch block.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Catch {
parameter: Option<Binding>,
block: Block,
}
impl Catch {
/// Creates a new catch block.
#[inline]
#[must_use]
pub const fn new(parameter: Option<Binding>, block: Block) -> Self {
Self { parameter, block }
}
/// Gets the parameter of the catch block.
#[inline]
#[must_use]
pub const fn parameter(&self) -> Option<&Binding> {
self.parameter.as_ref()
}
/// Retrieves the catch execution block.
#[inline]
#[must_use]
pub const fn block(&self) -> &Block {
&self.block
}
}
impl ToIndentedString for Catch {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = " catch".to_owned();
if let Some(ref param) = self.parameter {
buf.push_str(&format!("({})", param.to_interned_string(interner)));
}
buf.push_str(&format!(
" {}",
self.block.to_indented_string(interner, indentation)
));
buf
}
}
impl VisitWith for Catch {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
if let Some(binding) = &self.parameter {
try_break!(visitor.visit_binding(binding));
}
visitor.visit_block(&self.block)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
if let Some(binding) = &mut self.parameter {
try_break!(visitor.visit_binding_mut(binding));
}
visitor.visit_block_mut(&mut self.block)
}
}
/// Finally block.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub struct Finally {
block: Block,
}
impl Finally {
/// Gets the finally block.
#[inline]
#[must_use]
pub const fn block(&self) -> &Block {
&self.block
}
}
impl ToIndentedString for Finally {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
format!(
" finally {}",
self.block.to_indented_string(interner, indentation)
)
}
}
impl From<Block> for Finally {
#[inline]
fn from(block: Block) -> Self {
Self { block }
}
}
impl VisitWith for Finally {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
visitor.visit_block(&self.block)
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
visitor.visit_block_mut(&mut self.block)
}
}

View File

@@ -0,0 +1,215 @@
//! Statement list node.
use super::Declaration;
use crate::{
statement::Statement,
try_break,
visitor::{VisitWith, Visitor, VisitorMut},
};
use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow;
use std::cmp::Ordering;
/// An item inside a [`StatementList`] Parse Node, as defined by the [spec].
///
/// Items in a `StatementList` can be either [`Declaration`]s (functions, classes, let/const declarations)
/// or [`Statement`]s (if, while, var statement).
///
/// [spec]: https://tc39.es/ecma262/#prod-StatementListItem
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, PartialEq)]
pub enum StatementListItem {
/// See [`Statement`].
Statement(Statement),
/// See [`Declaration`].
Declaration(Declaration),
}
impl StatementListItem {
/// Returns a node ordering based on the hoistability of each statement.
#[must_use]
pub const fn hoistable_order(a: &Self, b: &Self) -> Ordering {
match (a, b) {
(
Self::Declaration(Declaration::Function(_)),
Self::Declaration(Declaration::Function(_)),
) => Ordering::Equal,
(_, Self::Declaration(Declaration::Function(_))) => Ordering::Greater,
(Self::Declaration(Declaration::Function(_)), _) => Ordering::Less,
(_, _) => Ordering::Equal,
}
}
}
impl ToIndentedString for StatementListItem {
/// Creates a string of the value of the node with the given indentation. For example, an
/// indent level of 2 would produce this:
///
/// ```js
/// function hello() {
/// console.log("hello");
/// }
/// hello();
/// a = 2;
/// ```
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = " ".repeat(indentation);
match self {
Self::Statement(stmt) => {
buf.push_str(&stmt.to_no_indent_string(interner, indentation));
}
Self::Declaration(decl) => {
buf.push_str(&decl.to_indented_string(interner, indentation));
}
}
buf
}
}
impl From<Statement> for StatementListItem {
#[inline]
fn from(stmt: Statement) -> Self {
Self::Statement(stmt)
}
}
impl From<Declaration> for StatementListItem {
#[inline]
fn from(decl: Declaration) -> Self {
Self::Declaration(decl)
}
}
impl VisitWith for StatementListItem {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
match self {
Self::Statement(statement) => visitor.visit_statement(statement),
Self::Declaration(declaration) => visitor.visit_declaration(declaration),
}
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
match self {
Self::Statement(statement) => visitor.visit_statement_mut(statement),
Self::Declaration(declaration) => visitor.visit_declaration_mut(declaration),
}
}
}
/// List of statements.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-StatementList
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq)]
pub struct StatementList {
statements: Box<[StatementListItem]>,
strict: bool,
}
impl StatementList {
/// Creates a new `StatementList` AST node.
#[must_use]
pub fn new<S>(statements: S, strict: bool) -> Self
where
S: Into<Box<[StatementListItem]>>,
{
Self {
statements: statements.into(),
strict,
}
}
/// Gets the list of statements.
#[inline]
#[must_use]
pub const fn statements(&self) -> &[StatementListItem] {
&self.statements
}
/// Get the strict mode.
#[inline]
#[must_use]
pub const fn strict(&self) -> bool {
self.strict
}
}
impl From<Box<[StatementListItem]>> for StatementList {
#[inline]
fn from(stm: Box<[StatementListItem]>) -> Self {
Self {
statements: stm,
strict: false,
}
}
}
impl From<Vec<StatementListItem>> for StatementList {
#[inline]
fn from(stm: Vec<StatementListItem>) -> Self {
Self {
statements: stm.into(),
strict: false,
}
}
}
impl ToIndentedString for StatementList {
fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String {
let mut buf = String::new();
// Print statements
for item in self.statements.iter() {
// We rely on the node to add the correct indent.
buf.push_str(&item.to_indented_string(interner, indentation));
buf.push('\n');
}
buf
}
}
impl VisitWith for StatementList {
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
for statement in self.statements.iter() {
try_break!(visitor.visit_statement_list_item(statement));
}
ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
for statement in self.statements.iter_mut() {
try_break!(visitor.visit_statement_list_item_mut(statement));
}
ControlFlow::Continue(())
}
}
#[cfg(feature = "fuzz")]
impl<'a> arbitrary::Arbitrary<'a> for StatementList {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(Self {
statements: u.arbitrary()?,
strict: false, // disable strictness; this is *not* in source data
})
}
}

View File

@@ -0,0 +1,569 @@
//! ECMAScript Abstract Syntax Tree visitors.
//!
//! This module contains visitors which can be used to inspect or modify AST nodes. This allows for
//! fine-grained manipulation of ASTs for analysis, rewriting, or instrumentation.
use std::ops::ControlFlow;
use crate::{
declaration::{
Binding, Declaration, LexicalDeclaration, VarDeclaration, Variable, VariableList,
},
expression::{
access::{
PrivatePropertyAccess, PropertyAccess, PropertyAccessField, SimplePropertyAccess,
SuperPropertyAccess,
},
literal::{ArrayLiteral, Literal, ObjectLiteral, TemplateElement, TemplateLiteral},
operator::{
assign::{Assign, AssignTarget},
Binary, Conditional, Unary,
},
Await, Call, Expression, Identifier, New, Optional, OptionalOperation,
OptionalOperationKind, Spread, SuperCall, TaggedTemplate, Yield,
},
function::{
ArrowFunction, AsyncArrowFunction, AsyncFunction, AsyncGenerator, Class, ClassElement,
FormalParameter, FormalParameterList, Function, Generator, PrivateName,
},
pattern::{ArrayPattern, ArrayPatternElement, ObjectPattern, ObjectPatternElement, Pattern},
property::{MethodDefinition, PropertyDefinition, PropertyName},
statement::{
iteration::{
Break, Continue, DoWhileLoop, ForInLoop, ForLoop, ForLoopInitializer, ForOfLoop,
IterableLoopInitializer, WhileLoop,
},
Block, Case, Catch, Finally, If, Labelled, LabelledItem, Return, Statement, Switch, Throw,
Try,
},
StatementList, StatementListItem,
};
use boa_interner::Sym;
/// `Try`-like conditional unwrapping of `ControlFlow`.
#[macro_export]
macro_rules! try_break {
($expr:expr) => {
match $expr {
core::ops::ControlFlow::Continue(c) => c,
core::ops::ControlFlow::Break(b) => return core::ops::ControlFlow::Break(b),
}
};
}
/// Creates the default visit function implementation for a particular type
macro_rules! define_visit {
($fn_name:ident, $type_name:ident) => {
#[doc = concat!("Visits a `", stringify!($type_name), "` with this visitor")]
fn $fn_name(&mut self, node: &'ast $type_name) -> ControlFlow<Self::BreakTy> {
node.visit_with(self)
}
};
}
/// Creates the default mutable visit function implementation for a particular type
macro_rules! define_visit_mut {
($fn_name:ident, $type_name:ident) => {
#[doc = concat!("Visits a `", stringify!($type_name), "` with this visitor, mutably")]
fn $fn_name(&mut self, node: &'ast mut $type_name) -> ControlFlow<Self::BreakTy> {
node.visit_with_mut(self)
}
};
}
/// Generates the `NodeRef` and `NodeMutRef` enums from a list of variants.
macro_rules! node_ref {
(
$(
$Variant:ident
),*
$(,)?
) => {
/// A reference to a node visitable by a [`Visitor`].
#[derive(Debug, Clone, Copy)]
#[allow(missing_docs)]
pub enum NodeRef<'a> {
$(
$Variant(&'a $Variant)
),*
}
$(
impl<'a> From<&'a $Variant> for NodeRef<'a> {
fn from(node: &'a $Variant) -> NodeRef<'a> {
Self::$Variant(node)
}
}
)*
/// A mutable reference to a node visitable by a [`VisitorMut`].
#[derive(Debug)]
#[allow(missing_docs)]
pub enum NodeRefMut<'a> {
$(
$Variant(&'a mut $Variant)
),*
}
$(
impl<'a> From<&'a mut $Variant> for NodeRefMut<'a> {
fn from(node: &'a mut $Variant) -> NodeRefMut<'a> {
Self::$Variant(node)
}
}
)*
}
}
node_ref! {
StatementList,
StatementListItem,
Statement,
Declaration,
Function,
Generator,
AsyncFunction,
AsyncGenerator,
Class,
LexicalDeclaration,
Block,
VarDeclaration,
Expression,
If,
DoWhileLoop,
WhileLoop,
ForLoop,
ForInLoop,
ForOfLoop,
Switch,
Continue,
Break,
Return,
Labelled,
Throw,
Try,
Identifier,
FormalParameterList,
ClassElement,
PrivateName,
VariableList,
Variable,
Binding,
Pattern,
Literal,
ArrayLiteral,
ObjectLiteral,
Spread,
ArrowFunction,
AsyncArrowFunction,
TemplateLiteral,
PropertyAccess,
New,
Call,
SuperCall,
Optional,
TaggedTemplate,
Assign,
Unary,
Binary,
Conditional,
Await,
Yield,
ForLoopInitializer,
IterableLoopInitializer,
Case,
Sym,
LabelledItem,
Catch,
Finally,
FormalParameter,
PropertyName,
MethodDefinition,
ObjectPattern,
ArrayPattern,
PropertyDefinition,
TemplateElement,
SimplePropertyAccess,
PrivatePropertyAccess,
SuperPropertyAccess,
OptionalOperation,
AssignTarget,
ObjectPatternElement,
ArrayPatternElement,
PropertyAccessField,
OptionalOperationKind,
}
/// Represents an AST visitor.
///
/// This implementation is based largely on [chalk](https://github.com/rust-lang/chalk/blob/23d39c90ceb9242fbd4c43e9368e813e7c2179f7/chalk-ir/src/visit.rs)'s
/// visitor pattern.
pub trait Visitor<'ast>: Sized {
/// Type which will be propagated from the visitor if completing early.
type BreakTy;
define_visit!(visit_statement_list, StatementList);
define_visit!(visit_statement_list_item, StatementListItem);
define_visit!(visit_statement, Statement);
define_visit!(visit_declaration, Declaration);
define_visit!(visit_function, Function);
define_visit!(visit_generator, Generator);
define_visit!(visit_async_function, AsyncFunction);
define_visit!(visit_async_generator, AsyncGenerator);
define_visit!(visit_class, Class);
define_visit!(visit_lexical_declaration, LexicalDeclaration);
define_visit!(visit_block, Block);
define_visit!(visit_var_declaration, VarDeclaration);
define_visit!(visit_expression, Expression);
define_visit!(visit_if, If);
define_visit!(visit_do_while_loop, DoWhileLoop);
define_visit!(visit_while_loop, WhileLoop);
define_visit!(visit_for_loop, ForLoop);
define_visit!(visit_for_in_loop, ForInLoop);
define_visit!(visit_for_of_loop, ForOfLoop);
define_visit!(visit_switch, Switch);
define_visit!(visit_continue, Continue);
define_visit!(visit_break, Break);
define_visit!(visit_return, Return);
define_visit!(visit_labelled, Labelled);
define_visit!(visit_throw, Throw);
define_visit!(visit_try, Try);
define_visit!(visit_identifier, Identifier);
define_visit!(visit_formal_parameter_list, FormalParameterList);
define_visit!(visit_class_element, ClassElement);
define_visit!(visit_private_name, PrivateName);
define_visit!(visit_variable_list, VariableList);
define_visit!(visit_variable, Variable);
define_visit!(visit_binding, Binding);
define_visit!(visit_pattern, Pattern);
define_visit!(visit_literal, Literal);
define_visit!(visit_array_literal, ArrayLiteral);
define_visit!(visit_object_literal, ObjectLiteral);
define_visit!(visit_spread, Spread);
define_visit!(visit_arrow_function, ArrowFunction);
define_visit!(visit_async_arrow_function, AsyncArrowFunction);
define_visit!(visit_template_literal, TemplateLiteral);
define_visit!(visit_property_access, PropertyAccess);
define_visit!(visit_new, New);
define_visit!(visit_call, Call);
define_visit!(visit_super_call, SuperCall);
define_visit!(visit_optional, Optional);
define_visit!(visit_tagged_template, TaggedTemplate);
define_visit!(visit_assign, Assign);
define_visit!(visit_unary, Unary);
define_visit!(visit_binary, Binary);
define_visit!(visit_conditional, Conditional);
define_visit!(visit_await, Await);
define_visit!(visit_yield, Yield);
define_visit!(visit_for_loop_initializer, ForLoopInitializer);
define_visit!(visit_iterable_loop_initializer, IterableLoopInitializer);
define_visit!(visit_case, Case);
define_visit!(visit_sym, Sym);
define_visit!(visit_labelled_item, LabelledItem);
define_visit!(visit_catch, Catch);
define_visit!(visit_finally, Finally);
define_visit!(visit_formal_parameter, FormalParameter);
define_visit!(visit_property_name, PropertyName);
define_visit!(visit_method_definition, MethodDefinition);
define_visit!(visit_object_pattern, ObjectPattern);
define_visit!(visit_array_pattern, ArrayPattern);
define_visit!(visit_property_definition, PropertyDefinition);
define_visit!(visit_template_element, TemplateElement);
define_visit!(visit_simple_property_access, SimplePropertyAccess);
define_visit!(visit_private_property_access, PrivatePropertyAccess);
define_visit!(visit_super_property_access, SuperPropertyAccess);
define_visit!(visit_optional_operation, OptionalOperation);
define_visit!(visit_assign_target, AssignTarget);
define_visit!(visit_object_pattern_element, ObjectPatternElement);
define_visit!(visit_array_pattern_element, ArrayPatternElement);
define_visit!(visit_property_access_field, PropertyAccessField);
define_visit!(visit_optional_operation_kind, OptionalOperationKind);
/// Generic entry point for a node that is visitable by a `Visitor`.
///
/// This is usually used for generic functions that need to visit an unnamed AST node.
fn visit<N: Into<NodeRef<'ast>>>(&mut self, node: N) -> ControlFlow<Self::BreakTy> {
let node = node.into();
match node {
NodeRef::StatementList(n) => self.visit_statement_list(n),
NodeRef::StatementListItem(n) => self.visit_statement_list_item(n),
NodeRef::Statement(n) => self.visit_statement(n),
NodeRef::Declaration(n) => self.visit_declaration(n),
NodeRef::Function(n) => self.visit_function(n),
NodeRef::Generator(n) => self.visit_generator(n),
NodeRef::AsyncFunction(n) => self.visit_async_function(n),
NodeRef::AsyncGenerator(n) => self.visit_async_generator(n),
NodeRef::Class(n) => self.visit_class(n),
NodeRef::LexicalDeclaration(n) => self.visit_lexical_declaration(n),
NodeRef::Block(n) => self.visit_block(n),
NodeRef::VarDeclaration(n) => self.visit_var_declaration(n),
NodeRef::Expression(n) => self.visit_expression(n),
NodeRef::If(n) => self.visit_if(n),
NodeRef::DoWhileLoop(n) => self.visit_do_while_loop(n),
NodeRef::WhileLoop(n) => self.visit_while_loop(n),
NodeRef::ForLoop(n) => self.visit_for_loop(n),
NodeRef::ForInLoop(n) => self.visit_for_in_loop(n),
NodeRef::ForOfLoop(n) => self.visit_for_of_loop(n),
NodeRef::Switch(n) => self.visit_switch(n),
NodeRef::Continue(n) => self.visit_continue(n),
NodeRef::Break(n) => self.visit_break(n),
NodeRef::Return(n) => self.visit_return(n),
NodeRef::Labelled(n) => self.visit_labelled(n),
NodeRef::Throw(n) => self.visit_throw(n),
NodeRef::Try(n) => self.visit_try(n),
NodeRef::Identifier(n) => self.visit_identifier(n),
NodeRef::FormalParameterList(n) => self.visit_formal_parameter_list(n),
NodeRef::ClassElement(n) => self.visit_class_element(n),
NodeRef::PrivateName(n) => self.visit_private_name(n),
NodeRef::VariableList(n) => self.visit_variable_list(n),
NodeRef::Variable(n) => self.visit_variable(n),
NodeRef::Binding(n) => self.visit_binding(n),
NodeRef::Pattern(n) => self.visit_pattern(n),
NodeRef::Literal(n) => self.visit_literal(n),
NodeRef::ArrayLiteral(n) => self.visit_array_literal(n),
NodeRef::ObjectLiteral(n) => self.visit_object_literal(n),
NodeRef::Spread(n) => self.visit_spread(n),
NodeRef::ArrowFunction(n) => self.visit_arrow_function(n),
NodeRef::AsyncArrowFunction(n) => self.visit_async_arrow_function(n),
NodeRef::TemplateLiteral(n) => self.visit_template_literal(n),
NodeRef::PropertyAccess(n) => self.visit_property_access(n),
NodeRef::New(n) => self.visit_new(n),
NodeRef::Call(n) => self.visit_call(n),
NodeRef::SuperCall(n) => self.visit_super_call(n),
NodeRef::Optional(n) => self.visit_optional(n),
NodeRef::TaggedTemplate(n) => self.visit_tagged_template(n),
NodeRef::Assign(n) => self.visit_assign(n),
NodeRef::Unary(n) => self.visit_unary(n),
NodeRef::Binary(n) => self.visit_binary(n),
NodeRef::Conditional(n) => self.visit_conditional(n),
NodeRef::Await(n) => self.visit_await(n),
NodeRef::Yield(n) => self.visit_yield(n),
NodeRef::ForLoopInitializer(n) => self.visit_for_loop_initializer(n),
NodeRef::IterableLoopInitializer(n) => self.visit_iterable_loop_initializer(n),
NodeRef::Case(n) => self.visit_case(n),
NodeRef::Sym(n) => self.visit_sym(n),
NodeRef::LabelledItem(n) => self.visit_labelled_item(n),
NodeRef::Catch(n) => self.visit_catch(n),
NodeRef::Finally(n) => self.visit_finally(n),
NodeRef::FormalParameter(n) => self.visit_formal_parameter(n),
NodeRef::PropertyName(n) => self.visit_property_name(n),
NodeRef::MethodDefinition(n) => self.visit_method_definition(n),
NodeRef::ObjectPattern(n) => self.visit_object_pattern(n),
NodeRef::ArrayPattern(n) => self.visit_array_pattern(n),
NodeRef::PropertyDefinition(n) => self.visit_property_definition(n),
NodeRef::TemplateElement(n) => self.visit_template_element(n),
NodeRef::SimplePropertyAccess(n) => self.visit_simple_property_access(n),
NodeRef::PrivatePropertyAccess(n) => self.visit_private_property_access(n),
NodeRef::SuperPropertyAccess(n) => self.visit_super_property_access(n),
NodeRef::OptionalOperation(n) => self.visit_optional_operation(n),
NodeRef::AssignTarget(n) => self.visit_assign_target(n),
NodeRef::ObjectPatternElement(n) => self.visit_object_pattern_element(n),
NodeRef::ArrayPatternElement(n) => self.visit_array_pattern_element(n),
NodeRef::PropertyAccessField(n) => self.visit_property_access_field(n),
NodeRef::OptionalOperationKind(n) => self.visit_optional_operation_kind(n),
}
}
}
/// Represents an AST visitor which can modify AST content.
///
/// This implementation is based largely on [chalk](https://github.com/rust-lang/chalk/blob/23d39c90ceb9242fbd4c43e9368e813e7c2179f7/chalk-ir/src/visit.rs)'s
/// visitor pattern.
pub trait VisitorMut<'ast>: Sized {
/// Type which will be propagated from the visitor if completing early.
type BreakTy;
define_visit_mut!(visit_statement_list_mut, StatementList);
define_visit_mut!(visit_statement_list_item_mut, StatementListItem);
define_visit_mut!(visit_statement_mut, Statement);
define_visit_mut!(visit_declaration_mut, Declaration);
define_visit_mut!(visit_function_mut, Function);
define_visit_mut!(visit_generator_mut, Generator);
define_visit_mut!(visit_async_function_mut, AsyncFunction);
define_visit_mut!(visit_async_generator_mut, AsyncGenerator);
define_visit_mut!(visit_class_mut, Class);
define_visit_mut!(visit_lexical_declaration_mut, LexicalDeclaration);
define_visit_mut!(visit_block_mut, Block);
define_visit_mut!(visit_var_declaration_mut, VarDeclaration);
define_visit_mut!(visit_expression_mut, Expression);
define_visit_mut!(visit_if_mut, If);
define_visit_mut!(visit_do_while_loop_mut, DoWhileLoop);
define_visit_mut!(visit_while_loop_mut, WhileLoop);
define_visit_mut!(visit_for_loop_mut, ForLoop);
define_visit_mut!(visit_for_in_loop_mut, ForInLoop);
define_visit_mut!(visit_for_of_loop_mut, ForOfLoop);
define_visit_mut!(visit_switch_mut, Switch);
define_visit_mut!(visit_continue_mut, Continue);
define_visit_mut!(visit_break_mut, Break);
define_visit_mut!(visit_return_mut, Return);
define_visit_mut!(visit_labelled_mut, Labelled);
define_visit_mut!(visit_throw_mut, Throw);
define_visit_mut!(visit_try_mut, Try);
define_visit_mut!(visit_identifier_mut, Identifier);
define_visit_mut!(visit_formal_parameter_list_mut, FormalParameterList);
define_visit_mut!(visit_class_element_mut, ClassElement);
define_visit_mut!(visit_private_name_mut, PrivateName);
define_visit_mut!(visit_variable_list_mut, VariableList);
define_visit_mut!(visit_variable_mut, Variable);
define_visit_mut!(visit_binding_mut, Binding);
define_visit_mut!(visit_pattern_mut, Pattern);
define_visit_mut!(visit_literal_mut, Literal);
define_visit_mut!(visit_array_literal_mut, ArrayLiteral);
define_visit_mut!(visit_object_literal_mut, ObjectLiteral);
define_visit_mut!(visit_spread_mut, Spread);
define_visit_mut!(visit_arrow_function_mut, ArrowFunction);
define_visit_mut!(visit_async_arrow_function_mut, AsyncArrowFunction);
define_visit_mut!(visit_template_literal_mut, TemplateLiteral);
define_visit_mut!(visit_property_access_mut, PropertyAccess);
define_visit_mut!(visit_new_mut, New);
define_visit_mut!(visit_call_mut, Call);
define_visit_mut!(visit_super_call_mut, SuperCall);
define_visit_mut!(visit_optional_mut, Optional);
define_visit_mut!(visit_tagged_template_mut, TaggedTemplate);
define_visit_mut!(visit_assign_mut, Assign);
define_visit_mut!(visit_unary_mut, Unary);
define_visit_mut!(visit_binary_mut, Binary);
define_visit_mut!(visit_conditional_mut, Conditional);
define_visit_mut!(visit_await_mut, Await);
define_visit_mut!(visit_yield_mut, Yield);
define_visit_mut!(visit_for_loop_initializer_mut, ForLoopInitializer);
define_visit_mut!(visit_iterable_loop_initializer_mut, IterableLoopInitializer);
define_visit_mut!(visit_case_mut, Case);
define_visit_mut!(visit_sym_mut, Sym);
define_visit_mut!(visit_labelled_item_mut, LabelledItem);
define_visit_mut!(visit_catch_mut, Catch);
define_visit_mut!(visit_finally_mut, Finally);
define_visit_mut!(visit_formal_parameter_mut, FormalParameter);
define_visit_mut!(visit_property_name_mut, PropertyName);
define_visit_mut!(visit_method_definition_mut, MethodDefinition);
define_visit_mut!(visit_object_pattern_mut, ObjectPattern);
define_visit_mut!(visit_array_pattern_mut, ArrayPattern);
define_visit_mut!(visit_property_definition_mut, PropertyDefinition);
define_visit_mut!(visit_template_element_mut, TemplateElement);
define_visit_mut!(visit_simple_property_access_mut, SimplePropertyAccess);
define_visit_mut!(visit_private_property_access_mut, PrivatePropertyAccess);
define_visit_mut!(visit_super_property_access_mut, SuperPropertyAccess);
define_visit_mut!(visit_optional_operation_mut, OptionalOperation);
define_visit_mut!(visit_assign_target_mut, AssignTarget);
define_visit_mut!(visit_object_pattern_element_mut, ObjectPatternElement);
define_visit_mut!(visit_array_pattern_element_mut, ArrayPatternElement);
define_visit_mut!(visit_property_access_field_mut, PropertyAccessField);
define_visit_mut!(visit_optional_operation_kind_mut, OptionalOperationKind);
/// Generic entry point for a node that is visitable by a `VisitorMut`.
///
/// This is usually used for generic functions that need to visit an unnamed AST node.
fn visit<N: Into<NodeRefMut<'ast>>>(&mut self, node: N) -> ControlFlow<Self::BreakTy> {
let node = node.into();
match node {
NodeRefMut::StatementList(n) => self.visit_statement_list_mut(n),
NodeRefMut::StatementListItem(n) => self.visit_statement_list_item_mut(n),
NodeRefMut::Statement(n) => self.visit_statement_mut(n),
NodeRefMut::Declaration(n) => self.visit_declaration_mut(n),
NodeRefMut::Function(n) => self.visit_function_mut(n),
NodeRefMut::Generator(n) => self.visit_generator_mut(n),
NodeRefMut::AsyncFunction(n) => self.visit_async_function_mut(n),
NodeRefMut::AsyncGenerator(n) => self.visit_async_generator_mut(n),
NodeRefMut::Class(n) => self.visit_class_mut(n),
NodeRefMut::LexicalDeclaration(n) => self.visit_lexical_declaration_mut(n),
NodeRefMut::Block(n) => self.visit_block_mut(n),
NodeRefMut::VarDeclaration(n) => self.visit_var_declaration_mut(n),
NodeRefMut::Expression(n) => self.visit_expression_mut(n),
NodeRefMut::If(n) => self.visit_if_mut(n),
NodeRefMut::DoWhileLoop(n) => self.visit_do_while_loop_mut(n),
NodeRefMut::WhileLoop(n) => self.visit_while_loop_mut(n),
NodeRefMut::ForLoop(n) => self.visit_for_loop_mut(n),
NodeRefMut::ForInLoop(n) => self.visit_for_in_loop_mut(n),
NodeRefMut::ForOfLoop(n) => self.visit_for_of_loop_mut(n),
NodeRefMut::Switch(n) => self.visit_switch_mut(n),
NodeRefMut::Continue(n) => self.visit_continue_mut(n),
NodeRefMut::Break(n) => self.visit_break_mut(n),
NodeRefMut::Return(n) => self.visit_return_mut(n),
NodeRefMut::Labelled(n) => self.visit_labelled_mut(n),
NodeRefMut::Throw(n) => self.visit_throw_mut(n),
NodeRefMut::Try(n) => self.visit_try_mut(n),
NodeRefMut::Identifier(n) => self.visit_identifier_mut(n),
NodeRefMut::FormalParameterList(n) => self.visit_formal_parameter_list_mut(n),
NodeRefMut::ClassElement(n) => self.visit_class_element_mut(n),
NodeRefMut::PrivateName(n) => self.visit_private_name_mut(n),
NodeRefMut::VariableList(n) => self.visit_variable_list_mut(n),
NodeRefMut::Variable(n) => self.visit_variable_mut(n),
NodeRefMut::Binding(n) => self.visit_binding_mut(n),
NodeRefMut::Pattern(n) => self.visit_pattern_mut(n),
NodeRefMut::Literal(n) => self.visit_literal_mut(n),
NodeRefMut::ArrayLiteral(n) => self.visit_array_literal_mut(n),
NodeRefMut::ObjectLiteral(n) => self.visit_object_literal_mut(n),
NodeRefMut::Spread(n) => self.visit_spread_mut(n),
NodeRefMut::ArrowFunction(n) => self.visit_arrow_function_mut(n),
NodeRefMut::AsyncArrowFunction(n) => self.visit_async_arrow_function_mut(n),
NodeRefMut::TemplateLiteral(n) => self.visit_template_literal_mut(n),
NodeRefMut::PropertyAccess(n) => self.visit_property_access_mut(n),
NodeRefMut::New(n) => self.visit_new_mut(n),
NodeRefMut::Call(n) => self.visit_call_mut(n),
NodeRefMut::SuperCall(n) => self.visit_super_call_mut(n),
NodeRefMut::Optional(n) => self.visit_optional_mut(n),
NodeRefMut::TaggedTemplate(n) => self.visit_tagged_template_mut(n),
NodeRefMut::Assign(n) => self.visit_assign_mut(n),
NodeRefMut::Unary(n) => self.visit_unary_mut(n),
NodeRefMut::Binary(n) => self.visit_binary_mut(n),
NodeRefMut::Conditional(n) => self.visit_conditional_mut(n),
NodeRefMut::Await(n) => self.visit_await_mut(n),
NodeRefMut::Yield(n) => self.visit_yield_mut(n),
NodeRefMut::ForLoopInitializer(n) => self.visit_for_loop_initializer_mut(n),
NodeRefMut::IterableLoopInitializer(n) => self.visit_iterable_loop_initializer_mut(n),
NodeRefMut::Case(n) => self.visit_case_mut(n),
NodeRefMut::Sym(n) => self.visit_sym_mut(n),
NodeRefMut::LabelledItem(n) => self.visit_labelled_item_mut(n),
NodeRefMut::Catch(n) => self.visit_catch_mut(n),
NodeRefMut::Finally(n) => self.visit_finally_mut(n),
NodeRefMut::FormalParameter(n) => self.visit_formal_parameter_mut(n),
NodeRefMut::PropertyName(n) => self.visit_property_name_mut(n),
NodeRefMut::MethodDefinition(n) => self.visit_method_definition_mut(n),
NodeRefMut::ObjectPattern(n) => self.visit_object_pattern_mut(n),
NodeRefMut::ArrayPattern(n) => self.visit_array_pattern_mut(n),
NodeRefMut::PropertyDefinition(n) => self.visit_property_definition_mut(n),
NodeRefMut::TemplateElement(n) => self.visit_template_element_mut(n),
NodeRefMut::SimplePropertyAccess(n) => self.visit_simple_property_access_mut(n),
NodeRefMut::PrivatePropertyAccess(n) => self.visit_private_property_access_mut(n),
NodeRefMut::SuperPropertyAccess(n) => self.visit_super_property_access_mut(n),
NodeRefMut::OptionalOperation(n) => self.visit_optional_operation_mut(n),
NodeRefMut::AssignTarget(n) => self.visit_assign_target_mut(n),
NodeRefMut::ObjectPatternElement(n) => self.visit_object_pattern_element_mut(n),
NodeRefMut::ArrayPatternElement(n) => self.visit_array_pattern_element_mut(n),
NodeRefMut::PropertyAccessField(n) => self.visit_property_access_field_mut(n),
NodeRefMut::OptionalOperationKind(n) => self.visit_optional_operation_kind_mut(n),
}
}
}
/// Denotes that a type may be visited, providing a method which allows a visitor to traverse its
/// private fields.
pub trait VisitWith {
/// Visit this node with the provided visitor.
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>;
/// Visit this node with the provided visitor mutably, allowing the visitor to modify private
/// fields.
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>;
}
// implementation for Sym as it is out-of-crate
impl VisitWith for Sym {
fn visit_with<'a, V>(&'a self, _visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
core::ops::ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, _visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{
core::ops::ControlFlow::Continue(())
}
}