Rust编写的JavaScript引擎,该项目是一个试验性质的项目。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

568 lines
22 KiB

//! This module implements the `Node` structure, which composes the AST.
pub mod array;
pub mod await_expr;
pub mod block;
pub mod break_node;
pub mod call;
pub mod conditional;
pub mod declaration;
pub mod field;
pub mod identifier;
pub mod iteration;
pub mod new;
pub mod object;
pub mod operator;
pub mod return_smt;
pub mod spread;
pub mod statement_list;
pub mod switch;
pub mod throw;
pub mod try_node;
pub use self::{
array::ArrayDecl,
await_expr::AwaitExpr,
block::Block,
break_node::Break,
call::Call,
conditional::{ConditionalOp, If},
declaration::{
ArrowFunctionDecl, AsyncFunctionDecl, AsyncFunctionExpr, ConstDecl, ConstDeclList,
FunctionDecl, FunctionExpr, LetDecl, LetDeclList, VarDecl, VarDeclList,
},
field::{GetConstField, GetField},
identifier::Identifier,
iteration::{Continue, DoWhileLoop, ForInLoop, ForLoop, ForOfLoop, WhileLoop},
new::New,
object::Object,
operator::{Assign, BinOp, UnaryOp},
return_smt::Return,
spread::Spread,
statement_list::{RcStatementList, StatementList},
switch::{Case, Switch},
throw::Throw,
try_node::{Catch, Finally, Try},
};
use super::Const;
use crate::{
exec::Executable,
gc::{empty_trace, Finalize, Trace},
BoaProfiler, Context, Result, Value,
};
use std::{
cmp::Ordering,
fmt::{self, Display},
};
#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub enum Node {
/// Array declaration node. [More information](./array/struct.ArrayDecl.html).
ArrayDecl(ArrayDecl),
/// An arrow function expression node. [More information](./arrow_function/struct.ArrowFunctionDecl.html).
ArrowFunctionDecl(ArrowFunctionDecl),
/// An assignment operator node. [More information](./operator/struct.Assign.html).
Assign(Assign),
/// An async function declaration node. [More information](./declaration/struct.AsyncFunctionDecl.html).
AsyncFunctionDecl(AsyncFunctionDecl),
/// An async function expression node. [More information](./declaration/struct.AsyncFunctionExpr.html).
AsyncFunctionExpr(AsyncFunctionExpr),
/// An await expression node. [More information](./await_expr/struct.AwaitExpression.html).
AwaitExpr(AwaitExpr),
/// A binary operator node. [More information](./operator/struct.BinOp.html).
BinOp(BinOp),
/// A Block node. [More information](./block/struct.Block.html).
Block(Block),
/// A break node. [More information](./break/struct.Break.html).
Break(Break),
/// A function call. [More information](./expression/struct.Call.html).
Call(Call),
/// A javascript conditional operand ( x ? y : z ). [More information](./conditional/struct.ConditionalOp.html).
ConditionalOp(ConditionalOp),
/// Literals represent values in JavaScript.
///
/// 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
Const(Const),
/// A constant declaration list. [More information](./declaration/struct.ConstDeclList.html).
ConstDeclList(ConstDeclList),
/// A continue statement. [More information](./iteration/struct.Continue.html).
Continue(Continue),
/// A do ... while statement. [More information](./iteration/struct.DoWhileLoop.html).
DoWhileLoop(DoWhileLoop),
/// A function declaration node. [More information](./declaration/struct.FunctionDecl.html).
FunctionDecl(FunctionDecl),
/// A function expression node. [More information](./declaration/struct.FunctionExpr.html).
FunctionExpr(FunctionExpr),
/// Provides access to an object types' constant properties. [More information](./declaration/struct.GetConstField.html).
GetConstField(GetConstField),
/// Provides access to object fields. [More information](./declaration/struct.GetField.html).
GetField(GetField),
/// A `for` statement. [More information](./iteration/struct.ForLoop.html).
ForLoop(ForLoop),
/// A `for...of` or `for..in` statement. [More information](./iteration/struct.ForIn.html).
ForInLoop(ForInLoop),
/// A `for...of` statement. [More information](./iteration/struct.ForOf.html).
ForOfLoop(ForOfLoop),
/// An 'if' statement. [More information](./conditional/struct.If.html).
If(If),
/// A `let` declaration list. [More information](./declaration/struct.LetDeclList.html).
LetDeclList(LetDeclList),
/// A local identifier node. [More information](./identifier/struct.Identifier.html).
Identifier(Identifier),
/// A `new` expression. [More information](./expression/struct.New.html).
New(New),
/// An object. [More information](./object/struct.Object.html).
Object(Object),
/// A return statement. [More information](./object/struct.Return.html).
Return(Return),
/// A switch {case} statement. [More information](./switch/struct.Switch.html).
Switch(Switch),
/// A spread (...x) statement. [More information](./spread/struct.Spread.html).
Spread(Spread),
/// A throw statement. [More information](./throw/struct.Throw.html).
Throw(Throw),
/// A `try...catch` node. [More information](./try_node/struct.Try.htl).
Try(Try),
/// The JavaScript `this` keyword refers to the object it belongs to.
///
/// A property of an execution context (global, function or eval) that,
/// in non–strict 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,
/// Unary operation node. [More information](./operator/struct.UnaryOp.html)
UnaryOp(UnaryOp),
/// Array declaration node. [More information](./declaration/struct.VarDeclList.html).
VarDeclList(VarDeclList),
/// A 'while {...}' node. [More information](./iteration/struct.WhileLoop.html).
WhileLoop(WhileLoop),
}
impl Display for Node {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.display(f, 0)
}
}
impl From<Const> for Node {
fn from(c: Const) -> Self {
Self::Const(c)
}
}
impl Node {
/// Returns a node ordering based on the hoistability of each node.
pub(crate) fn hoistable_order(a: &Node, b: &Node) -> Ordering {
match (a, b) {
(Node::FunctionDecl(_), Node::FunctionDecl(_)) => Ordering::Equal,
(_, Node::FunctionDecl(_)) => Ordering::Greater,
(Node::FunctionDecl(_), _) => Ordering::Less,
(_, _) => Ordering::Equal,
}
}
/// Creates a `This` AST node.
pub fn this() -> Self {
Self::This
}
/// Implements the display formatting with indentation.
fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result {
let indent = " ".repeat(indentation);
match *self {
Self::Block(_) => {}
_ => write!(f, "{}", indent)?,
}
match *self {
Self::Call(ref expr) => Display::fmt(expr, f),
Self::Const(ref c) => write!(f, "{}", c),
Self::ConditionalOp(ref cond_op) => Display::fmt(cond_op, f),
Self::ForLoop(ref for_loop) => for_loop.display(f, indentation),
Self::ForOfLoop(ref for_of) => for_of.display(f, indentation),
Self::ForInLoop(ref for_in) => for_in.display(f, indentation),
Self::This => write!(f, "this"),
Self::Try(ref try_catch) => try_catch.display(f, indentation),
Self::Break(ref break_smt) => Display::fmt(break_smt, f),
Self::Continue(ref cont) => Display::fmt(cont, f),
Self::Spread(ref spread) => Display::fmt(spread, f),
Self::Block(ref block) => block.display(f, indentation),
Self::Identifier(ref s) => Display::fmt(s, f),
Self::New(ref expr) => Display::fmt(expr, f),
Self::GetConstField(ref get_const_field) => Display::fmt(get_const_field, f),
Self::GetField(ref get_field) => Display::fmt(get_field, f),
Self::WhileLoop(ref while_loop) => while_loop.display(f, indentation),
Self::DoWhileLoop(ref do_while) => do_while.display(f, indentation),
Self::If(ref if_smt) => if_smt.display(f, indentation),
Self::Switch(ref switch) => switch.display(f, indentation),
Self::Object(ref obj) => obj.display(f, indentation),
Self::ArrayDecl(ref arr) => Display::fmt(arr, f),
Self::VarDeclList(ref list) => Display::fmt(list, f),
Self::FunctionDecl(ref decl) => decl.display(f, indentation),
Self::FunctionExpr(ref expr) => expr.display(f, indentation),
Self::ArrowFunctionDecl(ref decl) => decl.display(f, indentation),
Self::BinOp(ref op) => Display::fmt(op, f),
Self::UnaryOp(ref op) => Display::fmt(op, f),
Self::Return(ref ret) => Display::fmt(ret, f),
Self::Throw(ref throw) => Display::fmt(throw, f),
Self::Assign(ref op) => Display::fmt(op, f),
Self::LetDeclList(ref decl) => Display::fmt(decl, f),
Self::ConstDeclList(ref decl) => Display::fmt(decl, f),
Self::AsyncFunctionDecl(ref decl) => decl.display(f, indentation),
Self::AsyncFunctionExpr(ref expr) => expr.display(f, indentation),
Self::AwaitExpr(ref expr) => expr.display(f, indentation),
}
}
}
impl Executable for Node {
fn run(&self, context: &mut Context) -> Result<Value> {
let _timer = BoaProfiler::global().start_event("Executable", "exec");
match *self {
Node::AsyncFunctionDecl(ref decl) => decl.run(context),
Node::AsyncFunctionExpr(ref function_expr) => function_expr.run(context),
Node::AwaitExpr(ref expr) => expr.run(context),
Node::Call(ref call) => call.run(context),
Node::Const(Const::Null) => Ok(Value::null()),
Node::Const(Const::Num(num)) => Ok(Value::rational(num)),
Node::Const(Const::Int(num)) => Ok(Value::integer(num)),
Node::Const(Const::BigInt(ref num)) => Ok(Value::from(num.clone())),
Node::Const(Const::Undefined) => Ok(Value::Undefined),
// we can't move String from Const into value, because const is a garbage collected value
// Which means Drop() get's called on Const, but str will be gone at that point.
// Do Const values need to be garbage collected? We no longer need them once we've generated Values
Node::Const(Const::String(ref value)) => Ok(Value::string(value.to_string())),
Node::Const(Const::Bool(value)) => Ok(Value::boolean(value)),
Node::Block(ref block) => block.run(context),
Node::Identifier(ref identifier) => identifier.run(context),
Node::GetConstField(ref get_const_field_node) => get_const_field_node.run(context),
Node::GetField(ref get_field) => get_field.run(context),
Node::WhileLoop(ref while_loop) => while_loop.run(context),
Node::DoWhileLoop(ref do_while) => do_while.run(context),
Node::ForLoop(ref for_loop) => for_loop.run(context),
Node::ForOfLoop(ref for_of_loop) => for_of_loop.run(context),
Node::ForInLoop(ref for_in_loop) => for_in_loop.run(context),
Node::If(ref if_smt) => if_smt.run(context),
Node::ConditionalOp(ref op) => op.run(context),
Node::Switch(ref switch) => switch.run(context),
Node::Object(ref obj) => obj.run(context),
Node::ArrayDecl(ref arr) => arr.run(context),
// <https://tc39.es/ecma262/#sec-createdynamicfunction>
Node::FunctionDecl(ref decl) => decl.run(context),
// <https://tc39.es/ecma262/#sec-createdynamicfunction>
Node::FunctionExpr(ref function_expr) => function_expr.run(context),
Node::ArrowFunctionDecl(ref decl) => decl.run(context),
Node::BinOp(ref op) => op.run(context),
Node::UnaryOp(ref op) => op.run(context),
Node::New(ref call) => call.run(context),
Node::Return(ref ret) => ret.run(context),
Node::Throw(ref throw) => throw.run(context),
Node::Assign(ref op) => op.run(context),
Node::VarDeclList(ref decl) => decl.run(context),
Node::LetDeclList(ref decl) => decl.run(context),
Node::ConstDeclList(ref decl) => decl.run(context),
Node::Spread(ref spread) => spread.run(context),
Node::This => {
// Will either return `this` binding or undefined
context
.realm()
.environment
.get_this_binding()
.map_err(|e| e.to_error(context))
}
Node::Try(ref try_node) => try_node.run(context),
Node::Break(ref break_node) => break_node.run(context),
Node::Continue(ref continue_node) => continue_node.run(context),
}
}
}
/// Utility to join multiple Nodes into a single string.
fn join_nodes<N>(f: &mut fmt::Formatter<'_>, nodes: &[N]) -> fmt::Result
where
N: Display,
{
let mut first = true;
for e in nodes {
if !first {
f.write_str(", ")?;
}
first = false;
Display::fmt(e, f)?;
}
Ok(())
}
/// "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 = "deser", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Trace, Finalize)]
pub struct FormalParameter {
name: Box<str>,
init: Option<Node>,
is_rest_param: bool,
}
impl FormalParameter {
/// Creates a new formal parameter.
pub(in crate::syntax) fn new<N>(name: N, init: Option<Node>, is_rest_param: bool) -> Self
where
N: Into<Box<str>>,
{
Self {
name: name.into(),
init,
is_rest_param,
}
}
/// Gets the name of the formal parameter.
pub fn name(&self) -> &str {
&self.name
}
/// Gets the initialization node of the formal parameter, if any.
pub fn init(&self) -> Option<&Node> {
self.init.as_ref()
}
/// Gets wether the parameter is a rest parameter.
pub fn is_rest_param(&self) -> bool {
self.is_rest_param
}
}
impl Display for FormalParameter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_rest_param {
write!(f, "...")?;
}
write!(f, "{}", self.name)?;
if let Some(n) = self.init.as_ref() {
write!(f, " = {}", n)?;
}
Ok(())
}
}
/// A JavaScript property is a characteristic of an object, often describing attributes associated with a data structure.
///
/// 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 = "deser", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Trace, Finalize)]
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(Box<str>),
/// 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(Box<str>, Node),
/// 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(MethodDefinitionKind, Box<str>, FunctionExpr),
/// 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(Node),
}
impl PropertyDefinition {
/// Creates an `IdentifierReference` property definition.
pub fn identifier_reference<I>(ident: I) -> Self
where
I: Into<Box<str>>,
{
Self::IdentifierReference(ident.into())
}
/// Creates a `Property` definition.
pub fn property<N, V>(name: N, value: V) -> Self
where
N: Into<Box<str>>,
V: Into<Node>,
{
Self::Property(name.into(), value.into())
}
/// Creates a `MethodDefinition`.
pub fn method_definition<N>(kind: MethodDefinitionKind, name: N, body: FunctionExpr) -> Self
where
N: Into<Box<str>>,
{
Self::MethodDefinition(kind, name.into(), body)
}
/// Creates a `SpreadObject`.
pub fn spread_object<O>(obj: O) -> Self
where
O: Into<Node>,
{
Self::SpreadObject(obj.into())
}
}
/// Method definition kinds.
///
/// 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 = "deser", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Copy, Finalize)]
pub enum MethodDefinitionKind {
/// 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,
/// 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,
/// 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,
// TODO: support other method definition kinds, like `Generator`.
}
unsafe impl Trace for MethodDefinitionKind {
empty_trace!();
}