mirror of https://github.com/boa-dev/boa.git
Iban Eguia
5 years ago
committed by
GitHub
123 changed files with 7947 additions and 5891 deletions
@ -1,68 +0,0 @@
|
||||
//! This module implements the global `Error` object.
|
||||
//!
|
||||
//! Error objects are thrown when runtime errors occur.
|
||||
//! The Error object can also be used as a base object for user-defined exceptions.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-error-objects
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
|
||||
|
||||
use super::function::make_constructor_fn; |
||||
use crate::{ |
||||
builtins::{ |
||||
object::ObjectKind, |
||||
value::{ResultValue, Value}, |
||||
}, |
||||
exec::Interpreter, |
||||
}; |
||||
|
||||
/// Create a new error object.
|
||||
pub fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
if !args.is_empty() { |
||||
this.set_field_slice( |
||||
"message", |
||||
Value::from( |
||||
args.get(0) |
||||
.expect("failed getting error message") |
||||
.to_string(), |
||||
), |
||||
); |
||||
} |
||||
// This value is used by console.log and other routines to match Object type
|
||||
// to its Javascript Identifier (global constructor method name)
|
||||
this.set_kind(ObjectKind::Error); |
||||
Ok(Value::undefined()) |
||||
} |
||||
|
||||
/// `Error.prototype.toString()`
|
||||
///
|
||||
/// The toString() method returns a string representing the specified Error object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-error.prototype.tostring
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString
|
||||
pub fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
let name = this.get_field_slice("name"); |
||||
let message = this.get_field_slice("message"); |
||||
Ok(Value::from(format!("{}: {}", name, message))) |
||||
} |
||||
|
||||
/// Create a new `Error` object.
|
||||
pub fn create(global: &Value) -> Value { |
||||
let prototype = Value::new_object(Some(global)); |
||||
prototype.set_field_slice("message", Value::from("")); |
||||
prototype.set_field_slice("name", Value::from("Error")); |
||||
make_builtin_fn!(to_string, named "toString", of prototype); |
||||
make_constructor_fn(make_error, global, prototype) |
||||
} |
||||
|
||||
/// Initialise the global object with the `Error` object.
|
||||
pub fn init(global: &Value) { |
||||
global.set_field_slice("Error", create(global)); |
||||
} |
@ -0,0 +1,84 @@
|
||||
//! This module implements the global `Error` object.
|
||||
//!
|
||||
//! Error objects are thrown when runtime errors occur.
|
||||
//! The Error object can also be used as a base object for user-defined exceptions.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-error-objects
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
|
||||
|
||||
use crate::{ |
||||
builtins::{ |
||||
function::{make_builtin_fn, make_constructor_fn}, |
||||
object::ObjectKind, |
||||
value::{ResultValue, Value}, |
||||
}, |
||||
exec::Interpreter, |
||||
}; |
||||
|
||||
// mod eval;
|
||||
pub(crate) mod range; |
||||
// mod reference;
|
||||
// mod syntax;
|
||||
// mod type_err;
|
||||
// mod uri;
|
||||
|
||||
pub(crate) use self::range::RangeError; |
||||
|
||||
/// Built-in `Error` object.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct Error; |
||||
|
||||
impl Error { |
||||
/// Create a new error object.
|
||||
pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
if !args.is_empty() { |
||||
this.set_field( |
||||
"message", |
||||
Value::from( |
||||
args.get(0) |
||||
.expect("failed getting error message") |
||||
.to_string(), |
||||
), |
||||
); |
||||
} |
||||
// This value is used by console.log and other routines to match Object type
|
||||
// to its Javascript Identifier (global constructor method name)
|
||||
this.set_kind(ObjectKind::Error); |
||||
Ok(Value::undefined()) |
||||
} |
||||
|
||||
/// `Error.prototype.toString()`
|
||||
///
|
||||
/// The toString() method returns a string representing the specified Error object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-error.prototype.tostring
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString
|
||||
#[allow(clippy::wrong_self_convention)] |
||||
pub(crate) fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
let name = this.get_field("name"); |
||||
let message = this.get_field("message"); |
||||
Ok(Value::from(format!("{}: {}", name, message))) |
||||
} |
||||
|
||||
/// Create a new `Error` object.
|
||||
pub(crate) fn create(global: &Value) -> Value { |
||||
let prototype = Value::new_object(Some(global)); |
||||
prototype.set_field("message", Value::from("")); |
||||
prototype.set_field("name", Value::from("Error")); |
||||
make_builtin_fn(Self::to_string, "toString", &prototype, 0); |
||||
make_constructor_fn(Self::make_error, global, prototype) |
||||
} |
||||
|
||||
/// Initialise the global object with the `Error` object.
|
||||
pub(crate) fn init(global: &Value) { |
||||
global.set_field("Error", Self::create(global)); |
||||
} |
||||
} |
@ -0,0 +1,95 @@
|
||||
//! This module implements the global `RangeError` object.
|
||||
//!
|
||||
//! Indicates a value that is not in the set or range of allowable values.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-rangeerror
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError
|
||||
|
||||
use crate::{ |
||||
builtins::{ |
||||
function::make_builtin_fn, |
||||
function::make_constructor_fn, |
||||
object::ObjectKind, |
||||
value::{ResultValue, Value}, |
||||
}, |
||||
exec::Interpreter, |
||||
}; |
||||
|
||||
/// JavaScript `RangeError` impleentation.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct RangeError; |
||||
|
||||
impl RangeError { |
||||
/// Create a new error object.
|
||||
pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
if !args.is_empty() { |
||||
this.set_field( |
||||
"message", |
||||
Value::from( |
||||
args.get(0) |
||||
.expect("failed getting error message") |
||||
.to_string(), |
||||
), |
||||
); |
||||
} |
||||
// This value is used by console.log and other routines to match Object type
|
||||
// to its Javascript Identifier (global constructor method name)
|
||||
this.set_kind(ObjectKind::Error); |
||||
Ok(Value::undefined()) |
||||
} |
||||
|
||||
/// `Error.prototype.toString()`
|
||||
///
|
||||
/// The toString() method returns a string representing the specified Error object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-error.prototype.tostring
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString
|
||||
#[allow(clippy::wrong_self_convention)] |
||||
pub(crate) fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
let name = this.get_field("name"); |
||||
let message = this.get_field("message"); |
||||
Ok(Value::from(format!("{}: {}", name, message))) |
||||
} |
||||
|
||||
/// Create a new `RangeError` object.
|
||||
pub(crate) fn create(global: &Value) -> Value { |
||||
let prototype = Value::new_object(Some(global)); |
||||
prototype.set_field("message", Value::from("")); |
||||
prototype.set_field("name", Value::from("RangeError")); |
||||
make_builtin_fn(Self::to_string, "toString", &prototype, 0); |
||||
make_constructor_fn(Self::make_error, global, prototype) |
||||
} |
||||
|
||||
/// Runs a `new RangeError(message)`.
|
||||
pub(crate) fn run_new<M>(message: M, interpreter: &mut Interpreter) -> ResultValue |
||||
where |
||||
M: Into<String>, |
||||
{ |
||||
use crate::{ |
||||
exec::Executable, |
||||
syntax::ast::{ |
||||
node::{Call, Identifier, New}, |
||||
Const, |
||||
}, |
||||
}; |
||||
|
||||
New::from(Call::new( |
||||
Identifier::from("RangeError"), |
||||
vec![Const::from(message.into()).into()], |
||||
)) |
||||
.run(interpreter) |
||||
} |
||||
|
||||
/// Initialise the global object with the `RangeError` object.
|
||||
pub(crate) fn init(global: &Value) { |
||||
global.set_field("RangeError", Self::create(global)); |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,26 @@
|
||||
//! Array declaration execution.
|
||||
|
||||
use super::{Executable, Interpreter}; |
||||
use crate::{ |
||||
builtins::{Array, ResultValue}, |
||||
syntax::ast::node::{ArrayDecl, Node}, |
||||
}; |
||||
|
||||
impl Executable for ArrayDecl { |
||||
fn run(&self, interpreter: &mut Interpreter) -> ResultValue { |
||||
let array = Array::new_array(interpreter)?; |
||||
let mut elements = Vec::new(); |
||||
for elem in self.as_ref() { |
||||
if let Node::Spread(ref x) = elem { |
||||
let val = x.run(interpreter)?; |
||||
let mut vals = interpreter.extract_array_properties(&val).unwrap(); |
||||
elements.append(&mut vals); |
||||
continue; // Don't push array after spread
|
||||
} |
||||
elements.push(elem.run(interpreter)?); |
||||
} |
||||
Array::add_to_array_object(&array, &elements)?; |
||||
|
||||
Ok(array) |
||||
} |
||||
} |
@ -0,0 +1,34 @@
|
||||
//! Block statement execution.
|
||||
|
||||
use super::{Executable, Interpreter}; |
||||
use crate::{ |
||||
builtins::value::{ResultValue, Value}, |
||||
environment::lexical_environment::new_declarative_environment, |
||||
syntax::ast::node::Block, |
||||
}; |
||||
|
||||
impl Executable for Block { |
||||
fn run(&self, interpreter: &mut Interpreter) -> ResultValue { |
||||
{ |
||||
let env = &mut interpreter.realm_mut().environment; |
||||
env.push(new_declarative_environment(Some( |
||||
env.get_current_environment_ref().clone(), |
||||
))); |
||||
} |
||||
|
||||
let mut obj = Value::null(); |
||||
for statement in self.statements() { |
||||
obj = statement.run(interpreter)?; |
||||
|
||||
// early return
|
||||
if interpreter.is_return { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
// pop the block env
|
||||
let _ = interpreter.realm_mut().environment.pop(); |
||||
|
||||
Ok(obj) |
||||
} |
||||
} |
@ -0,0 +1,130 @@
|
||||
//! Declaration execution.
|
||||
|
||||
use super::{Executable, Interpreter}; |
||||
use crate::{ |
||||
builtins::{ |
||||
function::ThisMode, |
||||
value::{ResultValue, Value}, |
||||
}, |
||||
environment::lexical_environment::VariableScope, |
||||
syntax::ast::node::{ |
||||
ArrowFunctionDecl, ConstDeclList, FunctionDecl, FunctionExpr, LetDeclList, VarDeclList, |
||||
}, |
||||
}; |
||||
|
||||
impl Executable for FunctionDecl { |
||||
fn run(&self, interpreter: &mut Interpreter) -> ResultValue { |
||||
let val = interpreter.create_function( |
||||
self.parameters().to_vec(), |
||||
self.body().to_vec(), |
||||
ThisMode::NonLexical, |
||||
); |
||||
|
||||
// Set the name and assign it in the current environment
|
||||
val.set_field("name", self.name()); |
||||
interpreter.realm_mut().environment.create_mutable_binding( |
||||
self.name().to_owned(), |
||||
false, |
||||
VariableScope::Function, |
||||
); |
||||
|
||||
interpreter |
||||
.realm_mut() |
||||
.environment |
||||
.initialize_binding(self.name(), val.clone()); |
||||
|
||||
Ok(val) |
||||
} |
||||
} |
||||
|
||||
impl Executable for FunctionExpr { |
||||
fn run(&self, interpreter: &mut Interpreter) -> ResultValue { |
||||
let val = interpreter.create_function( |
||||
self.parameters().to_vec(), |
||||
self.body().to_vec(), |
||||
ThisMode::NonLexical, |
||||
); |
||||
|
||||
if let Some(name) = self.name() { |
||||
val.set_field("name", Value::from(name)); |
||||
} |
||||
|
||||
Ok(val) |
||||
} |
||||
} |
||||
|
||||
impl Executable for VarDeclList { |
||||
fn run(&self, interpreter: &mut Interpreter) -> ResultValue { |
||||
for var in self.as_ref() { |
||||
let val = match var.init() { |
||||
Some(v) => v.run(interpreter)?, |
||||
None => Value::undefined(), |
||||
}; |
||||
let environment = &mut interpreter.realm_mut().environment; |
||||
|
||||
if environment.has_binding(var.name()) { |
||||
if var.init().is_some() { |
||||
environment.set_mutable_binding(var.name(), val, true); |
||||
} |
||||
} else { |
||||
environment.create_mutable_binding( |
||||
var.name().to_owned(), |
||||
false, |
||||
VariableScope::Function, |
||||
); |
||||
environment.initialize_binding(var.name(), val); |
||||
} |
||||
} |
||||
Ok(Value::undefined()) |
||||
} |
||||
} |
||||
|
||||
impl Executable for ConstDeclList { |
||||
fn run(&self, interpreter: &mut Interpreter) -> ResultValue { |
||||
for decl in self.as_ref() { |
||||
let val = decl.init().run(interpreter)?; |
||||
|
||||
interpreter |
||||
.realm_mut() |
||||
.environment |
||||
.create_immutable_binding(decl.name().to_owned(), false, VariableScope::Block); |
||||
|
||||
interpreter |
||||
.realm_mut() |
||||
.environment |
||||
.initialize_binding(decl.name(), val); |
||||
} |
||||
Ok(Value::undefined()) |
||||
} |
||||
} |
||||
|
||||
impl Executable for LetDeclList { |
||||
fn run(&self, interpreter: &mut Interpreter) -> ResultValue { |
||||
for var in self.as_ref() { |
||||
let val = match var.init() { |
||||
Some(v) => v.run(interpreter)?, |
||||
None => Value::undefined(), |
||||
}; |
||||
interpreter.realm_mut().environment.create_mutable_binding( |
||||
var.name().to_owned(), |
||||
false, |
||||
VariableScope::Block, |
||||
); |
||||
interpreter |
||||
.realm_mut() |
||||
.environment |
||||
.initialize_binding(var.name(), val); |
||||
} |
||||
Ok(Value::undefined()) |
||||
} |
||||
} |
||||
|
||||
impl Executable for ArrowFunctionDecl { |
||||
fn run(&self, interpreter: &mut Interpreter) -> ResultValue { |
||||
Ok(interpreter.create_function( |
||||
self.params().to_vec(), |
||||
self.body().to_vec(), |
||||
ThisMode::Lexical, |
||||
)) |
||||
} |
||||
} |
@ -0,0 +1,81 @@
|
||||
//! Expression execution.
|
||||
|
||||
use super::{Executable, Interpreter}; |
||||
use crate::{ |
||||
builtins::{ |
||||
object::{INSTANCE_PROTOTYPE, PROTOTYPE}, |
||||
value::{ResultValue, Value, ValueData}, |
||||
}, |
||||
syntax::ast::node::{Call, New, Node}, |
||||
}; |
||||
|
||||
impl Executable for Call { |
||||
fn run(&self, interpreter: &mut Interpreter) -> ResultValue { |
||||
let (mut this, func) = match self.expr() { |
||||
Node::GetConstField(ref obj, ref field) => { |
||||
let mut obj = obj.run(interpreter)?; |
||||
if obj.get_type() != "object" || obj.get_type() != "symbol" { |
||||
obj = interpreter |
||||
.to_object(&obj) |
||||
.expect("failed to convert to object"); |
||||
} |
||||
(obj.clone(), obj.get_field(field)) |
||||
} |
||||
Node::GetField(ref obj, ref field) => { |
||||
let obj = obj.run(interpreter)?; |
||||
let field = field.run(interpreter)?; |
||||
(obj.clone(), obj.get_field(field.to_string())) |
||||
} |
||||
_ => ( |
||||
interpreter.realm().global_obj.clone(), |
||||
self.expr().run(interpreter)?, |
||||
), // 'this' binding should come from the function's self-contained environment
|
||||
}; |
||||
let mut v_args = Vec::with_capacity(self.args().len()); |
||||
for arg in self.args() { |
||||
if let Node::Spread(ref x) = arg { |
||||
let val = x.run(interpreter)?; |
||||
let mut vals = interpreter.extract_array_properties(&val).unwrap(); |
||||
v_args.append(&mut vals); |
||||
break; // after spread we don't accept any new arguments
|
||||
} |
||||
v_args.push(arg.run(interpreter)?); |
||||
} |
||||
|
||||
// execute the function call itself
|
||||
let fnct_result = interpreter.call(&func, &mut this, &v_args); |
||||
|
||||
// unset the early return flag
|
||||
interpreter.is_return = false; |
||||
|
||||
fnct_result |
||||
} |
||||
} |
||||
|
||||
impl Executable for New { |
||||
fn run(&self, interpreter: &mut Interpreter) -> ResultValue { |
||||
// let (callee, args) = match call.as_ref() {
|
||||
// Node::Call(callee, args) => (callee, args),
|
||||
// _ => unreachable!("Node::New(ref call): 'call' must only be Node::Call type."),
|
||||
// };
|
||||
|
||||
let func_object = self.expr().run(interpreter)?; |
||||
let mut v_args = Vec::with_capacity(self.args().len()); |
||||
for arg in self.args() { |
||||
v_args.push(arg.run(interpreter)?); |
||||
} |
||||
let mut this = Value::new_object(None); |
||||
// Create a blank object, then set its __proto__ property to the [Constructor].prototype
|
||||
this.set_internal_slot(INSTANCE_PROTOTYPE, func_object.get_field(PROTOTYPE)); |
||||
|
||||
match func_object.data() { |
||||
ValueData::Object(ref o) => o.clone().borrow_mut().func.as_ref().unwrap().construct( |
||||
&mut func_object.clone(), |
||||
&v_args, |
||||
interpreter, |
||||
&mut this, |
||||
), |
||||
_ => Ok(Value::undefined()), |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,42 @@
|
||||
//! Iteration node execution.
|
||||
|
||||
use super::{Executable, Interpreter}; |
||||
use crate::{ |
||||
builtins::value::{ResultValue, Value}, |
||||
environment::lexical_environment::new_declarative_environment, |
||||
syntax::ast::node::ForLoop, |
||||
}; |
||||
|
||||
impl Executable for ForLoop { |
||||
fn run(&self, interpreter: &mut Interpreter) -> ResultValue { |
||||
// Create the block environment.
|
||||
{ |
||||
let env = &mut interpreter.realm_mut().environment; |
||||
env.push(new_declarative_environment(Some( |
||||
env.get_current_environment_ref().clone(), |
||||
))); |
||||
} |
||||
|
||||
if let Some(init) = self.init() { |
||||
init.run(interpreter)?; |
||||
} |
||||
|
||||
while self |
||||
.condition() |
||||
.map(|cond| cond.run(interpreter).map(|v| v.is_true())) |
||||
.transpose()? |
||||
.unwrap_or(true) |
||||
{ |
||||
self.body().run(interpreter)?; |
||||
|
||||
if let Some(final_expr) = self.final_expr() { |
||||
final_expr.run(interpreter)?; |
||||
} |
||||
} |
||||
|
||||
// pop the block env
|
||||
let _ = interpreter.realm_mut().environment.pop(); |
||||
|
||||
Ok(Value::undefined()) |
||||
} |
||||
} |
@ -0,0 +1,215 @@
|
||||
//! Operator execution.
|
||||
|
||||
use super::{Executable, Interpreter}; |
||||
use crate::{ |
||||
builtins::value::{ResultValue, Value}, |
||||
environment::lexical_environment::VariableScope, |
||||
syntax::ast::{ |
||||
node::{Assign, BinOp, Node, UnaryOp}, |
||||
op::{self, AssignOp, BitOp, CompOp, LogOp, NumOp}, |
||||
}, |
||||
}; |
||||
use std::borrow::BorrowMut; |
||||
|
||||
impl Executable for Assign { |
||||
fn run(&self, interpreter: &mut Interpreter) -> ResultValue { |
||||
let val = self.rhs().run(interpreter)?; |
||||
match self.lhs() { |
||||
Node::Identifier(ref name) => { |
||||
let environment = &mut interpreter.realm_mut().environment; |
||||
|
||||
if environment.has_binding(name.as_ref()) { |
||||
// Binding already exists
|
||||
environment.set_mutable_binding(name.as_ref(), val.clone(), true); |
||||
} else { |
||||
environment.create_mutable_binding( |
||||
name.as_ref().to_owned(), |
||||
true, |
||||
VariableScope::Function, |
||||
); |
||||
environment.initialize_binding(name.as_ref(), val.clone()); |
||||
} |
||||
} |
||||
Node::GetConstField(ref obj, ref field) => { |
||||
let val_obj = obj.run(interpreter)?; |
||||
val_obj.set_field(field, val.clone()); |
||||
} |
||||
Node::GetField(ref obj, ref field) => { |
||||
let val_obj = obj.run(interpreter)?; |
||||
let val_field = field.run(interpreter)?; |
||||
val_obj.set_field(val_field, val.clone()); |
||||
} |
||||
_ => (), |
||||
} |
||||
Ok(val) |
||||
} |
||||
} |
||||
|
||||
impl Executable for BinOp { |
||||
fn run(&self, interpreter: &mut Interpreter) -> ResultValue { |
||||
match self.op() { |
||||
op::BinOp::Num(op) => { |
||||
let v_a = self.lhs().run(interpreter)?; |
||||
let v_b = self.rhs().run(interpreter)?; |
||||
Ok(match op { |
||||
NumOp::Add => v_a + v_b, |
||||
NumOp::Sub => v_a - v_b, |
||||
NumOp::Mul => v_a * v_b, |
||||
NumOp::Exp => v_a.as_num_to_power(v_b), |
||||
NumOp::Div => v_a / v_b, |
||||
NumOp::Mod => v_a % v_b, |
||||
}) |
||||
} |
||||
op::BinOp::Bit(op) => { |
||||
let v_a = self.lhs().run(interpreter)?; |
||||
let v_b = self.rhs().run(interpreter)?; |
||||
Ok(match op { |
||||
BitOp::And => v_a & v_b, |
||||
BitOp::Or => v_a | v_b, |
||||
BitOp::Xor => v_a ^ v_b, |
||||
BitOp::Shl => v_a << v_b, |
||||
BitOp::Shr => v_a >> v_b, |
||||
// TODO Fix
|
||||
BitOp::UShr => v_a >> v_b, |
||||
}) |
||||
} |
||||
op::BinOp::Comp(op) => { |
||||
let mut v_a = self.lhs().run(interpreter)?; |
||||
let mut v_b = self.rhs().run(interpreter)?; |
||||
Ok(Value::from(match op { |
||||
CompOp::Equal => v_a.equals(v_b.borrow_mut(), interpreter), |
||||
CompOp::NotEqual => !v_a.equals(v_b.borrow_mut(), interpreter), |
||||
CompOp::StrictEqual => v_a.strict_equals(&v_b), |
||||
CompOp::StrictNotEqual => !v_a.strict_equals(&v_b), |
||||
CompOp::GreaterThan => v_a.to_number() > v_b.to_number(), |
||||
CompOp::GreaterThanOrEqual => v_a.to_number() >= v_b.to_number(), |
||||
CompOp::LessThan => v_a.to_number() < v_b.to_number(), |
||||
CompOp::LessThanOrEqual => v_a.to_number() <= v_b.to_number(), |
||||
CompOp::In => { |
||||
if !v_b.is_object() { |
||||
panic!("TypeError: {} is not an Object.", v_b); |
||||
} |
||||
let key = interpreter.to_property_key(&mut v_a); |
||||
interpreter.has_property(&mut v_b, &key) |
||||
} |
||||
})) |
||||
} |
||||
op::BinOp::Log(op) => { |
||||
// turn a `Value` into a `bool`
|
||||
let to_bool = |value| bool::from(&value); |
||||
Ok(match op { |
||||
LogOp::And => Value::from( |
||||
to_bool(self.lhs().run(interpreter)?) |
||||
&& to_bool(self.rhs().run(interpreter)?), |
||||
), |
||||
LogOp::Or => Value::from( |
||||
to_bool(self.lhs().run(interpreter)?) |
||||
|| to_bool(self.rhs().run(interpreter)?), |
||||
), |
||||
}) |
||||
} |
||||
op::BinOp::Assign(op) => match self.lhs() { |
||||
Node::Identifier(ref name) => { |
||||
let v_a = interpreter |
||||
.realm() |
||||
.environment |
||||
.get_binding_value(name.as_ref()); |
||||
let v_b = self.rhs().run(interpreter)?; |
||||
let value = Self::run_assign(op, v_a, v_b); |
||||
interpreter.realm.environment.set_mutable_binding( |
||||
name.as_ref(), |
||||
value.clone(), |
||||
true, |
||||
); |
||||
Ok(value) |
||||
} |
||||
Node::GetConstField(ref obj, ref field) => { |
||||
let v_r_a = obj.run(interpreter)?; |
||||
let v_a = v_r_a.get_field(field); |
||||
let v_b = self.rhs().run(interpreter)?; |
||||
let value = Self::run_assign(op, v_a, v_b); |
||||
v_r_a.set_field(&field.clone(), value.clone()); |
||||
Ok(value) |
||||
} |
||||
_ => Ok(Value::undefined()), |
||||
}, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl BinOp { |
||||
/// Runs the assignment operators.
|
||||
fn run_assign(op: AssignOp, v_a: Value, v_b: Value) -> Value { |
||||
match op { |
||||
AssignOp::Add => v_a + v_b, |
||||
AssignOp::Sub => v_a - v_b, |
||||
AssignOp::Mul => v_a * v_b, |
||||
AssignOp::Exp => v_a.as_num_to_power(v_b), |
||||
AssignOp::Div => v_a / v_b, |
||||
AssignOp::Mod => v_a % v_b, |
||||
AssignOp::And => v_a & v_b, |
||||
AssignOp::Or => v_a | v_b, |
||||
AssignOp::Xor => v_a ^ v_b, |
||||
AssignOp::Shl => v_a << v_b, |
||||
AssignOp::Shr => v_a << v_b, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl Executable for UnaryOp { |
||||
fn run(&self, interpreter: &mut Interpreter) -> ResultValue { |
||||
let v_a = self.target().run(interpreter)?; |
||||
|
||||
Ok(match self.op() { |
||||
op::UnaryOp::Minus => -v_a, |
||||
op::UnaryOp::Plus => Value::from(v_a.to_number()), |
||||
op::UnaryOp::IncrementPost => { |
||||
let ret = v_a.clone(); |
||||
interpreter.set_value(self.target(), Value::from(v_a.to_number() + 1.0))?; |
||||
ret |
||||
} |
||||
op::UnaryOp::IncrementPre => { |
||||
interpreter.set_value(self.target(), Value::from(v_a.to_number() + 1.0))? |
||||
} |
||||
op::UnaryOp::DecrementPost => { |
||||
let ret = v_a.clone(); |
||||
interpreter.set_value(self.target(), Value::from(v_a.to_number() - 1.0))?; |
||||
ret |
||||
} |
||||
op::UnaryOp::DecrementPre => { |
||||
interpreter.set_value(self.target(), Value::from(v_a.to_number() - 1.0))? |
||||
} |
||||
op::UnaryOp::Not => !v_a, |
||||
op::UnaryOp::Tilde => { |
||||
let num_v_a = v_a.to_number(); |
||||
// NOTE: possible UB: https://github.com/rust-lang/rust/issues/10184
|
||||
Value::from(if num_v_a.is_nan() { |
||||
-1 |
||||
} else { |
||||
!(num_v_a as i32) |
||||
}) |
||||
} |
||||
op::UnaryOp::Void => Value::undefined(), |
||||
op::UnaryOp::Delete => match *self.target() { |
||||
Node::GetConstField(ref obj, ref field) => { |
||||
Value::boolean(obj.run(interpreter)?.remove_property(field)) |
||||
} |
||||
Node::GetField(ref obj, ref field) => Value::boolean( |
||||
obj.run(interpreter)? |
||||
.remove_property(&field.run(interpreter)?.to_string()), |
||||
), |
||||
Node::Identifier(_) => Value::boolean(false), |
||||
Node::ArrayDecl(_) |
||||
| Node::Block(_) |
||||
| Node::Const(_) |
||||
| Node::FunctionDecl(_) |
||||
| Node::FunctionExpr(_) |
||||
| Node::New(_) |
||||
| Node::Object(_) |
||||
| Node::UnaryOp(_) => Value::boolean(true), |
||||
_ => panic!("SyntaxError: wrong delete argument {}", self), |
||||
}, |
||||
op::UnaryOp::TypeOf => Value::from(v_a.get_type()), |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,26 @@
|
||||
//! Statement list execution.
|
||||
|
||||
use super::{Executable, Interpreter}; |
||||
use crate::{ |
||||
builtins::value::{ResultValue, Value}, |
||||
syntax::ast::node::StatementList, |
||||
}; |
||||
|
||||
impl Executable for StatementList { |
||||
fn run(&self, interpreter: &mut Interpreter) -> ResultValue { |
||||
let mut obj = Value::null(); |
||||
for (i, item) in self.statements().iter().enumerate() { |
||||
let val = item.run(interpreter)?; |
||||
// early return
|
||||
if interpreter.is_return { |
||||
obj = val; |
||||
break; |
||||
} |
||||
if i + 1 == self.statements().len() { |
||||
obj = val; |
||||
} |
||||
} |
||||
|
||||
Ok(obj) |
||||
} |
||||
} |
@ -0,0 +1,54 @@
|
||||
//! Try..catch node execution.
|
||||
|
||||
use super::{Executable, Interpreter}; |
||||
use crate::{ |
||||
builtins::value::ResultValue, |
||||
environment::lexical_environment::{new_declarative_environment, VariableScope}, |
||||
syntax::ast::node::Try, |
||||
}; |
||||
|
||||
#[cfg(test)] |
||||
mod tests; |
||||
|
||||
impl Executable for Try { |
||||
fn run(&self, interpreter: &mut Interpreter) -> ResultValue { |
||||
let res = self.block().run(interpreter).map_or_else( |
||||
|err| { |
||||
if let Some(catch) = self.catch() { |
||||
{ |
||||
let env = &mut interpreter.realm_mut().environment; |
||||
env.push(new_declarative_environment(Some( |
||||
env.get_current_environment_ref().clone(), |
||||
))); |
||||
|
||||
if let Some(param) = catch.parameter() { |
||||
env.create_mutable_binding( |
||||
param.to_owned(), |
||||
false, |
||||
VariableScope::Block, |
||||
); |
||||
|
||||
env.initialize_binding(param, err); |
||||
} |
||||
} |
||||
|
||||
let res = catch.block().run(interpreter); |
||||
|
||||
// pop the block env
|
||||
let _ = interpreter.realm_mut().environment.pop(); |
||||
|
||||
res |
||||
} else { |
||||
Err(err) |
||||
} |
||||
}, |
||||
Ok, |
||||
); |
||||
|
||||
if let Some(finally) = self.finally() { |
||||
finally.run(interpreter)?; |
||||
} |
||||
|
||||
res |
||||
} |
||||
} |
@ -0,0 +1,95 @@
|
||||
use crate::exec; |
||||
|
||||
#[test] |
||||
fn simple_try() { |
||||
let scenario = r#" |
||||
let a = 10; |
||||
try { |
||||
a = 20; |
||||
} catch { |
||||
a = 30; |
||||
} |
||||
|
||||
a; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "20"); |
||||
} |
||||
|
||||
#[test] |
||||
fn finally() { |
||||
let scenario = r#" |
||||
let a = 10; |
||||
try { |
||||
a = 20; |
||||
} finally { |
||||
a = 30; |
||||
} |
||||
|
||||
a; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "30"); |
||||
} |
||||
|
||||
#[test] |
||||
fn catch_finally() { |
||||
let scenario = r#" |
||||
let a = 10; |
||||
try { |
||||
a = 20; |
||||
} catch { |
||||
a = 40; |
||||
} finally { |
||||
a = 30; |
||||
} |
||||
|
||||
a; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "30"); |
||||
} |
||||
|
||||
#[test] |
||||
fn catch() { |
||||
let scenario = r#" |
||||
let a = 10; |
||||
try { |
||||
throw "error"; |
||||
} catch { |
||||
a = 20; |
||||
} |
||||
|
||||
a; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "20"); |
||||
} |
||||
|
||||
#[test] |
||||
fn catch_binding() { |
||||
let scenario = r#" |
||||
let a = 10; |
||||
try { |
||||
throw 20; |
||||
} catch(err) { |
||||
a = err; |
||||
} |
||||
|
||||
a; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "20"); |
||||
} |
||||
|
||||
#[test] |
||||
fn catch_binding_finally() { |
||||
let scenario = r#" |
||||
let a = 10; |
||||
try { |
||||
throw 20; |
||||
} catch(err) { |
||||
a = err; |
||||
} finally { |
||||
a = 30; |
||||
} |
||||
|
||||
a; |
||||
"#; |
||||
assert_eq!(&exec(scenario), "30"); |
||||
} |
@ -0,0 +1,60 @@
|
||||
//! Array declaration node.
|
||||
|
||||
use super::{join_nodes, Node}; |
||||
use gc::{Finalize, Trace}; |
||||
use std::fmt; |
||||
|
||||
#[cfg(feature = "serde")] |
||||
use serde::{Deserialize, Serialize}; |
||||
|
||||
/// 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(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct ArrayDecl { |
||||
#[cfg_attr(feature = "serde", serde(flatten))] |
||||
arr: Box<[Node]>, |
||||
} |
||||
|
||||
impl AsRef<[Node]> for ArrayDecl { |
||||
fn as_ref(&self) -> &[Node] { |
||||
&self.arr |
||||
} |
||||
} |
||||
|
||||
impl<T> From<T> for ArrayDecl |
||||
where |
||||
T: Into<Box<[Node]>>, |
||||
{ |
||||
fn from(decl: T) -> Self { |
||||
Self { arr: decl.into() } |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for ArrayDecl { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
f.write_str("[")?; |
||||
join_nodes(f, &self.arr)?; |
||||
f.write_str("]") |
||||
} |
||||
} |
||||
|
||||
impl From<ArrayDecl> for Node { |
||||
fn from(arr: ArrayDecl) -> Self { |
||||
Self::ArrayDecl(arr) |
||||
} |
||||
} |
@ -0,0 +1,68 @@
|
||||
//! Block AST node.
|
||||
|
||||
use super::{Node, StatementList}; |
||||
use gc::{Finalize, Trace}; |
||||
use std::fmt; |
||||
|
||||
#[cfg(feature = "serde")] |
||||
use serde::{Deserialize, Serialize}; |
||||
|
||||
/// 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 JavaScript expects only one statement.
|
||||
/// Combining statements into blocks is a common practice in JavaScript. 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(Serialize, Deserialize))] |
||||
#[cfg_attr(feature = "serde", serde(transparent))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct Block { |
||||
#[cfg_attr(feature = "serde", serde(flatten))] |
||||
statements: StatementList, |
||||
} |
||||
|
||||
impl Block { |
||||
/// Gets the list of statements in this block.
|
||||
pub(crate) fn statements(&self) -> &[Node] { |
||||
self.statements.statements() |
||||
} |
||||
|
||||
/// Implements the display formatting with indentation.
|
||||
pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { |
||||
writeln!(f, "{{")?; |
||||
self.statements.display(f, indentation + 1)?; |
||||
write!(f, "{}}}", " ".repeat(indentation)) |
||||
} |
||||
} |
||||
|
||||
impl<T> From<T> for Block |
||||
where |
||||
T: Into<StatementList>, |
||||
{ |
||||
fn from(list: T) -> Self { |
||||
Self { |
||||
statements: list.into(), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for Block { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
self.display(f, 0) |
||||
} |
||||
} |
||||
|
||||
impl From<Block> for Node { |
||||
fn from(block: Block) -> Self { |
||||
Self::Block(block) |
||||
} |
||||
} |
@ -0,0 +1,550 @@
|
||||
//! Declaration nodes.
|
||||
|
||||
use super::{join_nodes, FormalParameter, Identifier, Node, StatementList}; |
||||
use gc::{Finalize, Trace}; |
||||
use std::fmt; |
||||
|
||||
#[cfg(feature = "serde")] |
||||
use serde::{Deserialize, Serialize}; |
||||
|
||||
/// The `var` statement declares a variable, optionally initializing it to a value.
|
||||
///
|
||||
/// var declarations, wherever they occur, are processed before any code is executed. This is
|
||||
/// called hoisting, and is discussed further below.
|
||||
///
|
||||
/// 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 JavaScript variable, it will not lose its value.
|
||||
///
|
||||
/// Assigning a value to an undeclared variable implicitly creates it as a global variable (it
|
||||
/// becomes a property of the global object) when the assignment is executed.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#prod-VariableStatement
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct VarDeclList { |
||||
#[cfg_attr(feature = "serde", serde(flatten))] |
||||
vars: Box<[VarDecl]>, |
||||
} |
||||
|
||||
impl<T> From<T> for VarDeclList |
||||
where |
||||
T: Into<Box<[VarDecl]>>, |
||||
{ |
||||
fn from(list: T) -> Self { |
||||
Self { vars: list.into() } |
||||
} |
||||
} |
||||
|
||||
impl From<VarDecl> for VarDeclList { |
||||
fn from(decl: VarDecl) -> Self { |
||||
Self { |
||||
vars: Box::new([decl]), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl AsRef<[VarDecl]> for VarDeclList { |
||||
fn as_ref(&self) -> &[VarDecl] { |
||||
&self.vars |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for VarDeclList { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
if !self.vars.is_empty() { |
||||
write!(f, "var ")?; |
||||
join_nodes(f, &self.vars) |
||||
} else { |
||||
Ok(()) |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<VarDeclList> for Node { |
||||
fn from(list: VarDeclList) -> Self { |
||||
Self::VarDeclList(list) |
||||
} |
||||
} |
||||
|
||||
/// Individual variable declaration.
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct VarDecl { |
||||
name: Identifier, |
||||
init: Option<Node>, |
||||
} |
||||
|
||||
impl fmt::Display for VarDecl { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
fmt::Display::fmt(&self.name, f)?; |
||||
if let Some(ref init) = self.init { |
||||
write!(f, " = {}", init)?; |
||||
} |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
impl VarDecl { |
||||
/// Creates a new variable declaration.
|
||||
pub(in crate::syntax) fn new<N, I>(name: N, init: I) -> Self |
||||
where |
||||
N: Into<Identifier>, |
||||
I: Into<Option<Node>>, |
||||
{ |
||||
Self { |
||||
name: name.into(), |
||||
init: init.into(), |
||||
} |
||||
} |
||||
|
||||
/// Gets the name of the variable.
|
||||
pub fn name(&self) -> &str { |
||||
self.name.as_ref() |
||||
} |
||||
|
||||
/// Gets the initialization node for the variable, if any.
|
||||
pub fn init(&self) -> Option<&Node> { |
||||
self.init.as_ref() |
||||
} |
||||
} |
||||
|
||||
/// The `function` expression defines a function with the specified parameters.
|
||||
///
|
||||
/// A function created with a function expression is a `Function` object and has all the
|
||||
/// properties, methods and behavior of `Function`.
|
||||
///
|
||||
/// A function can also be created using a declaration (see function expression).
|
||||
///
|
||||
/// 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:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-function
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct FunctionExpr { |
||||
name: Option<Box<str>>, |
||||
parameters: Box<[FormalParameter]>, |
||||
body: StatementList, |
||||
} |
||||
|
||||
impl FunctionExpr { |
||||
/// Creates a new function expression
|
||||
pub(in crate::syntax) fn new<N, P, B>(name: N, parameters: P, body: B) -> Self |
||||
where |
||||
N: Into<Option<Box<str>>>, |
||||
P: Into<Box<[FormalParameter]>>, |
||||
B: Into<StatementList>, |
||||
{ |
||||
Self { |
||||
name: name.into(), |
||||
parameters: parameters.into(), |
||||
body: body.into(), |
||||
} |
||||
} |
||||
|
||||
/// Gets the name of the function declaration.
|
||||
pub fn name(&self) -> Option<&str> { |
||||
self.name.as_ref().map(Box::as_ref) |
||||
} |
||||
|
||||
/// Gets the list of parameters of the function declaration.
|
||||
pub fn parameters(&self) -> &[FormalParameter] { |
||||
&self.parameters |
||||
} |
||||
|
||||
/// Gets the body of the function declaration.
|
||||
pub fn body(&self) -> &[Node] { |
||||
self.body.statements() |
||||
} |
||||
|
||||
/// Implements the display formatting with indentation.
|
||||
pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { |
||||
f.write_str("function")?; |
||||
if let Some(ref name) = self.name { |
||||
write!(f, " {}", name)?; |
||||
} |
||||
f.write_str("(")?; |
||||
join_nodes(f, &self.parameters)?; |
||||
f.write_str(") {{")?; |
||||
|
||||
self.body.display(f, indentation + 1)?; |
||||
|
||||
writeln!(f, "}}") |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for FunctionExpr { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
self.display(f, 0) |
||||
} |
||||
} |
||||
|
||||
impl From<FunctionExpr> for Node { |
||||
fn from(expr: FunctionExpr) -> Self { |
||||
Self::FunctionExpr(expr) |
||||
} |
||||
} |
||||
|
||||
/// The `function` declaration (function statement) defines a function with the specified
|
||||
/// parameters.
|
||||
///
|
||||
/// A function created with a function declaration is a `Function` object and has all the
|
||||
/// properties, methods and behavior of `Function`.
|
||||
///
|
||||
/// A function can also be created using an expression (see [function expression][func_expr]).
|
||||
///
|
||||
/// 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:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-function
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function
|
||||
/// [func_expr]: ../enum.Node.html#variant.FunctionExpr
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct FunctionDecl { |
||||
name: Box<str>, |
||||
parameters: Box<[FormalParameter]>, |
||||
body: StatementList, |
||||
} |
||||
|
||||
impl FunctionDecl { |
||||
/// Creates a new function declaration.
|
||||
pub(in crate::syntax) fn new<N, P, B>(name: N, parameters: P, body: B) -> Self |
||||
where |
||||
N: Into<Box<str>>, |
||||
P: Into<Box<[FormalParameter]>>, |
||||
B: Into<StatementList>, |
||||
{ |
||||
Self { |
||||
name: name.into(), |
||||
parameters: parameters.into(), |
||||
body: body.into(), |
||||
} |
||||
} |
||||
|
||||
/// Gets the name of the function declaration.
|
||||
pub fn name(&self) -> &str { |
||||
&self.name |
||||
} |
||||
|
||||
/// Gets the list of parameters of the function declaration.
|
||||
pub fn parameters(&self) -> &[FormalParameter] { |
||||
&self.parameters |
||||
} |
||||
|
||||
/// Gets the body of the function declaration.
|
||||
pub fn body(&self) -> &[Node] { |
||||
self.body.statements() |
||||
} |
||||
|
||||
/// Implements the display formatting with indentation.
|
||||
pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { |
||||
write!(f, "function {}(", self.name)?; |
||||
join_nodes(f, &self.parameters)?; |
||||
f.write_str(") {{")?; |
||||
|
||||
self.body.display(f, indentation + 1)?; |
||||
|
||||
writeln!(f, "}}") |
||||
} |
||||
} |
||||
|
||||
impl From<FunctionDecl> for Node { |
||||
fn from(decl: FunctionDecl) -> Self { |
||||
Self::FunctionDecl(decl) |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for FunctionDecl { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
self.display(f, 0) |
||||
} |
||||
} |
||||
|
||||
/// An arrow function 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.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [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(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct ArrowFunctionDecl { |
||||
params: Box<[FormalParameter]>, |
||||
body: StatementList, |
||||
} |
||||
|
||||
impl ArrowFunctionDecl { |
||||
/// Creates a new `ArrowFunctionDecl` AST node.
|
||||
pub(in crate::syntax) fn new<P, B>(params: P, body: B) -> Self |
||||
where |
||||
P: Into<Box<[FormalParameter]>>, |
||||
B: Into<StatementList>, |
||||
{ |
||||
Self { |
||||
params: params.into(), |
||||
body: body.into(), |
||||
} |
||||
} |
||||
|
||||
/// Gets the list of parameters of the arrow function.
|
||||
pub(crate) fn params(&self) -> &[FormalParameter] { |
||||
&self.params |
||||
} |
||||
|
||||
/// Gets the body of the arrow function.
|
||||
pub(crate) fn body(&self) -> &[Node] { |
||||
&self.body.statements() |
||||
} |
||||
|
||||
/// Implements the display formatting with indentation.
|
||||
pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { |
||||
write!(f, "(")?; |
||||
join_nodes(f, &self.params)?; |
||||
f.write_str(") => ")?; |
||||
self.body.display(f, indentation) |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for ArrowFunctionDecl { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
self.display(f, 0) |
||||
} |
||||
} |
||||
|
||||
impl From<ArrowFunctionDecl> for Node { |
||||
fn from(decl: ArrowFunctionDecl) -> Self { |
||||
Self::ArrowFunctionDecl(decl) |
||||
} |
||||
} |
||||
|
||||
/// The `const` statements are block-scoped, much like variables defined using the `let`
|
||||
/// keyword.
|
||||
///
|
||||
/// This declaration creates a constant whose scope can be either global or local to the block
|
||||
/// in which it is declared. Global constants do not become properties of the window object,
|
||||
/// unlike var variables.
|
||||
///
|
||||
/// 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.)
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-let-and-const-declarations
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const
|
||||
/// [identifier]: https://developer.mozilla.org/en-US/docs/Glossary/identifier
|
||||
/// [expression]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Expressions
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct ConstDeclList { |
||||
#[cfg_attr(feature = "serde", serde(flatten))] |
||||
list: Box<[ConstDecl]>, |
||||
} |
||||
|
||||
impl<T> From<T> for ConstDeclList |
||||
where |
||||
T: Into<Box<[ConstDecl]>>, |
||||
{ |
||||
fn from(list: T) -> Self { |
||||
Self { list: list.into() } |
||||
} |
||||
} |
||||
|
||||
impl From<ConstDecl> for ConstDeclList { |
||||
fn from(decl: ConstDecl) -> Self { |
||||
Self { |
||||
list: Box::new([decl]), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl AsRef<[ConstDecl]> for ConstDeclList { |
||||
fn as_ref(&self) -> &[ConstDecl] { |
||||
&self.list |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for ConstDeclList { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
if !self.list.is_empty() { |
||||
write!(f, "const ")?; |
||||
join_nodes(f, &self.list) |
||||
} else { |
||||
Ok(()) |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<ConstDeclList> for Node { |
||||
fn from(list: ConstDeclList) -> Self { |
||||
Self::ConstDeclList(list) |
||||
} |
||||
} |
||||
|
||||
/// Individual constant declaration.
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct ConstDecl { |
||||
name: Identifier, |
||||
init: Node, |
||||
} |
||||
|
||||
impl fmt::Display for ConstDecl { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
write!(f, "{} = {}", self.name, self.init) |
||||
} |
||||
} |
||||
|
||||
impl ConstDecl { |
||||
/// Creates a new variable declaration.
|
||||
pub(in crate::syntax) fn new<N, I>(name: N, init: I) -> Self |
||||
where |
||||
N: Into<Identifier>, |
||||
I: Into<Node>, |
||||
{ |
||||
Self { |
||||
name: name.into(), |
||||
init: init.into(), |
||||
} |
||||
} |
||||
|
||||
/// Gets the name of the variable.
|
||||
pub fn name(&self) -> &str { |
||||
self.name.as_ref() |
||||
} |
||||
|
||||
/// Gets the initialization node for the variable, if any.
|
||||
pub fn init(&self) -> &Node { |
||||
&self.init |
||||
} |
||||
} |
||||
|
||||
/// The `let` statement declares a block scope local variable, optionally initializing it to a
|
||||
/// value.
|
||||
///
|
||||
///
|
||||
/// `let` allows you to declare variables that are 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 the `let` does not create properties of the window object when declared
|
||||
/// globally (in the top-most scope).
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-let-and-const-declarations
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct LetDeclList { |
||||
#[cfg_attr(feature = "serde", serde(flatten))] |
||||
list: Box<[LetDecl]>, |
||||
} |
||||
|
||||
impl<T> From<T> for LetDeclList |
||||
where |
||||
T: Into<Box<[LetDecl]>>, |
||||
{ |
||||
fn from(list: T) -> Self { |
||||
Self { list: list.into() } |
||||
} |
||||
} |
||||
|
||||
impl From<LetDecl> for LetDeclList { |
||||
fn from(decl: LetDecl) -> Self { |
||||
Self { |
||||
list: Box::new([decl]), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl AsRef<[LetDecl]> for LetDeclList { |
||||
fn as_ref(&self) -> &[LetDecl] { |
||||
&self.list |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for LetDeclList { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
if !self.list.is_empty() { |
||||
write!(f, "let ")?; |
||||
join_nodes(f, &self.list) |
||||
} else { |
||||
Ok(()) |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<LetDeclList> for Node { |
||||
fn from(list: LetDeclList) -> Self { |
||||
Self::LetDeclList(list) |
||||
} |
||||
} |
||||
|
||||
/// Individual constant declaration.
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct LetDecl { |
||||
name: Identifier, |
||||
init: Option<Node>, |
||||
} |
||||
|
||||
impl fmt::Display for LetDecl { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
fmt::Display::fmt(&self.name, f)?; |
||||
if let Some(ref init) = self.init { |
||||
write!(f, " = {}", init)?; |
||||
} |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
impl LetDecl { |
||||
/// Creates a new variable declaration.
|
||||
pub(in crate::syntax) fn new<N, I>(name: N, init: I) -> Self |
||||
where |
||||
N: Into<Identifier>, |
||||
I: Into<Option<Node>>, |
||||
{ |
||||
Self { |
||||
name: name.into(), |
||||
init: init.into(), |
||||
} |
||||
} |
||||
|
||||
/// Gets the name of the variable.
|
||||
pub fn name(&self) -> &str { |
||||
self.name.as_ref() |
||||
} |
||||
|
||||
/// Gets the initialization node for the variable, if any.
|
||||
pub fn init(&self) -> Option<&Node> { |
||||
self.init.as_ref() |
||||
} |
||||
} |
@ -0,0 +1,118 @@
|
||||
//! Expression nodes.
|
||||
|
||||
use super::{join_nodes, Node}; |
||||
use gc::{Finalize, Trace}; |
||||
use std::fmt; |
||||
|
||||
#[cfg(feature = "serde")] |
||||
use serde::{Deserialize, Serialize}; |
||||
|
||||
/// 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(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct Call { |
||||
expr: Box<Node>, |
||||
args: Box<[Node]>, |
||||
} |
||||
|
||||
impl Call { |
||||
/// Creates a new `Call` AST node.
|
||||
pub fn new<E, A>(expr: E, args: A) -> Self |
||||
where |
||||
E: Into<Node>, |
||||
A: Into<Box<[Node]>>, |
||||
{ |
||||
Self { |
||||
expr: Box::new(expr.into()), |
||||
args: args.into(), |
||||
} |
||||
} |
||||
|
||||
/// Gets the name of the function call.
|
||||
pub fn expr(&self) -> &Node { |
||||
&self.expr |
||||
} |
||||
|
||||
/// Retrieves the arguments passed to the function.
|
||||
pub fn args(&self) -> &[Node] { |
||||
&self.args |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for Call { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
write!(f, "{}(", self.expr)?; |
||||
join_nodes(f, &self.args)?; |
||||
f.write_str(")") |
||||
} |
||||
} |
||||
|
||||
impl From<Call> for Node { |
||||
fn from(call: Call) -> Self { |
||||
Self::Call(call) |
||||
} |
||||
} |
||||
|
||||
/// 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(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct New { |
||||
call: Call, |
||||
} |
||||
|
||||
impl New { |
||||
/// Gets the name of the function call.
|
||||
pub fn expr(&self) -> &Node { |
||||
&self.call.expr() |
||||
} |
||||
|
||||
/// Retrieves the arguments passed to the function.
|
||||
pub fn args(&self) -> &[Node] { |
||||
&self.call.args() |
||||
} |
||||
} |
||||
|
||||
impl From<Call> for New { |
||||
fn from(call: Call) -> Self { |
||||
Self { call } |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for New { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
write!(f, "new {}", self.call) |
||||
} |
||||
} |
||||
|
||||
impl From<New> for Node { |
||||
fn from(new: New) -> Self { |
||||
Self::New(new) |
||||
} |
||||
} |
@ -0,0 +1,58 @@
|
||||
//! Local identifier node.
|
||||
|
||||
use super::Node; |
||||
use gc::{Finalize, Trace}; |
||||
use std::fmt; |
||||
|
||||
#[cfg(feature = "serde")] |
||||
use serde::{Deserialize, Serialize}; |
||||
|
||||
/// An `identifier` is a sequence of characters in the code that identifies a variable,
|
||||
/// function, or property.
|
||||
///
|
||||
/// In JavaScript, 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(Serialize, Deserialize))] |
||||
#[cfg_attr(feature = "serde", serde(transparent))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct Identifier { |
||||
ident: Box<str>, |
||||
} |
||||
|
||||
impl fmt::Display for Identifier { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
fmt::Display::fmt(&self.ident, f) |
||||
} |
||||
} |
||||
|
||||
impl AsRef<str> for Identifier { |
||||
fn as_ref(&self) -> &str { |
||||
&self.ident |
||||
} |
||||
} |
||||
|
||||
impl<T> From<T> for Identifier |
||||
where |
||||
T: Into<Box<str>>, |
||||
{ |
||||
fn from(stm: T) -> Self { |
||||
Self { ident: stm.into() } |
||||
} |
||||
} |
||||
|
||||
impl From<Identifier> for Node { |
||||
fn from(local: Identifier) -> Self { |
||||
Self::Identifier(local) |
||||
} |
||||
} |
@ -0,0 +1,139 @@
|
||||
use super::Node; |
||||
use gc::{Finalize, Trace}; |
||||
use std::fmt; |
||||
|
||||
#[cfg(feature = "serde")] |
||||
use serde::{Deserialize, Serialize}; |
||||
|
||||
/// The `for` statement creates a loop that consists of three optional expressions.
|
||||
///
|
||||
/// A `for` 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]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [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(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct ForLoop { |
||||
#[cfg_attr(feature = "serde", serde(flatten))] |
||||
inner: Box<InnerForLoop>, |
||||
} |
||||
|
||||
impl ForLoop { |
||||
/// Creates a new for loop AST node.
|
||||
pub(in crate::syntax) fn new<I, C, E, B>(init: I, condition: C, final_expr: E, body: B) -> Self |
||||
where |
||||
I: Into<Option<Node>>, |
||||
C: Into<Option<Node>>, |
||||
E: Into<Option<Node>>, |
||||
B: Into<Node>, |
||||
{ |
||||
Self { |
||||
inner: Box::new(InnerForLoop::new(init, condition, final_expr, body)), |
||||
} |
||||
} |
||||
|
||||
/// Gets the initialization node.
|
||||
pub fn init(&self) -> Option<&Node> { |
||||
self.inner.init() |
||||
} |
||||
|
||||
/// Gets the loop condition node.
|
||||
pub fn condition(&self) -> Option<&Node> { |
||||
self.inner.condition() |
||||
} |
||||
|
||||
/// Gets the final expression node.
|
||||
pub fn final_expr(&self) -> Option<&Node> { |
||||
self.inner.final_expr() |
||||
} |
||||
|
||||
/// Gets the body of the for loop.
|
||||
pub fn body(&self) -> &Node { |
||||
self.inner.body() |
||||
} |
||||
|
||||
pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { |
||||
f.write_str("for (")?; |
||||
if let Some(init) = self.init() { |
||||
fmt::Display::fmt(init, f)?; |
||||
} |
||||
f.write_str(";")?; |
||||
if let Some(condition) = self.condition() { |
||||
fmt::Display::fmt(condition, f)?; |
||||
} |
||||
f.write_str(";")?; |
||||
if let Some(final_expr) = self.final_expr() { |
||||
fmt::Display::fmt(final_expr, f)?; |
||||
} |
||||
writeln!(f, ") {{")?; |
||||
|
||||
self.inner.body().display(f, indentation + 1)?; |
||||
|
||||
write!(f, "}}") |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for ForLoop { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
self.display(f, 0) |
||||
} |
||||
} |
||||
|
||||
impl From<ForLoop> for Node { |
||||
fn from(for_loop: ForLoop) -> Self { |
||||
Self::ForLoop(for_loop) |
||||
} |
||||
} |
||||
|
||||
/// Inner structure to avoid multiple indirections in the heap.
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
struct InnerForLoop { |
||||
init: Option<Node>, |
||||
condition: Option<Node>, |
||||
final_expr: Option<Node>, |
||||
body: Node, |
||||
} |
||||
|
||||
impl InnerForLoop { |
||||
/// Creates a new inner for loop.
|
||||
fn new<I, C, E, B>(init: I, condition: C, final_expr: E, body: B) -> Self |
||||
where |
||||
I: Into<Option<Node>>, |
||||
C: Into<Option<Node>>, |
||||
E: Into<Option<Node>>, |
||||
B: Into<Node>, |
||||
{ |
||||
Self { |
||||
init: init.into(), |
||||
condition: condition.into(), |
||||
final_expr: final_expr.into(), |
||||
body: body.into(), |
||||
} |
||||
} |
||||
|
||||
/// Gets the initialization node.
|
||||
fn init(&self) -> Option<&Node> { |
||||
self.init.as_ref() |
||||
} |
||||
|
||||
/// Gets the loop condition node.
|
||||
fn condition(&self) -> Option<&Node> { |
||||
self.condition.as_ref() |
||||
} |
||||
|
||||
/// Gets the final expression node.
|
||||
fn final_expr(&self) -> Option<&Node> { |
||||
self.final_expr.as_ref() |
||||
} |
||||
|
||||
/// Gets the body of the for loop.
|
||||
fn body(&self) -> &Node { |
||||
&self.body |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,168 @@
|
||||
use super::Node; |
||||
use crate::syntax::ast::op; |
||||
use gc::{Finalize, Trace}; |
||||
use std::fmt; |
||||
|
||||
#[cfg(feature = "serde")] |
||||
use serde::{Deserialize, Serialize}; |
||||
|
||||
/// An assignment operator assigns a value to its left operand based on the value of its right
|
||||
/// operand.
|
||||
///
|
||||
/// Assignment operator (`=`), assigns the value of its right operand to its left operand.
|
||||
///
|
||||
/// 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/Assignment_Operators
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct Assign { |
||||
lhs: Box<Node>, |
||||
rhs: Box<Node>, |
||||
} |
||||
|
||||
impl Assign { |
||||
/// Creates an `Assign` AST node.
|
||||
pub(in crate::syntax) fn new<L, R>(lhs: L, rhs: R) -> Self |
||||
where |
||||
L: Into<Node>, |
||||
R: Into<Node>, |
||||
{ |
||||
Self { |
||||
lhs: Box::new(lhs.into()), |
||||
rhs: Box::new(rhs.into()), |
||||
} |
||||
} |
||||
|
||||
/// Gets the left hand side of the assignment operation.
|
||||
pub fn lhs(&self) -> &Node { |
||||
&self.lhs |
||||
} |
||||
|
||||
/// Gets the right hand side of the assignment operation.
|
||||
pub fn rhs(&self) -> &Node { |
||||
&self.rhs |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for Assign { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
write!(f, "{} = {}", self.lhs, self.rhs) |
||||
} |
||||
} |
||||
|
||||
impl From<Assign> for Node { |
||||
fn from(op: Assign) -> Self { |
||||
Self::Assign(op) |
||||
} |
||||
} |
||||
|
||||
/// Binary operators requires two operands, one before the operator and one after the operator.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Operators
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct BinOp { |
||||
op: op::BinOp, |
||||
lhs: Box<Node>, |
||||
rhs: Box<Node>, |
||||
} |
||||
|
||||
impl BinOp { |
||||
/// Creates a `BinOp` AST node.
|
||||
pub(in crate::syntax) fn new<O, L, R>(op: O, lhs: L, rhs: R) -> Self |
||||
where |
||||
O: Into<op::BinOp>, |
||||
L: Into<Node>, |
||||
R: Into<Node>, |
||||
{ |
||||
Self { |
||||
op: op.into(), |
||||
lhs: Box::new(lhs.into()), |
||||
rhs: Box::new(rhs.into()), |
||||
} |
||||
} |
||||
|
||||
/// Gets the binary operation of the node.
|
||||
pub fn op(&self) -> op::BinOp { |
||||
self.op |
||||
} |
||||
|
||||
/// Gets the left hand side of the binary operation.
|
||||
pub fn lhs(&self) -> &Node { |
||||
&self.lhs |
||||
} |
||||
|
||||
/// Gets the right hand side of the binary operation.
|
||||
pub fn rhs(&self) -> &Node { |
||||
&self.rhs |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for BinOp { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
write!(f, "{} {} {}", self.lhs, self.op, self.rhs) |
||||
} |
||||
} |
||||
|
||||
impl From<BinOp> for Node { |
||||
fn from(op: BinOp) -> Self { |
||||
Self::BinOp(op) |
||||
} |
||||
} |
||||
|
||||
/// A unary operation 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(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct UnaryOp { |
||||
op: op::UnaryOp, |
||||
target: Box<Node>, |
||||
} |
||||
|
||||
impl UnaryOp { |
||||
/// Creates a new `UnaryOp` AST node.
|
||||
pub(in crate::syntax) fn new<V>(op: op::UnaryOp, target: V) -> Self |
||||
where |
||||
V: Into<Node>, |
||||
{ |
||||
Self { |
||||
op, |
||||
target: Box::new(target.into()), |
||||
} |
||||
} |
||||
|
||||
/// Gets the unary operation of the node.
|
||||
pub fn op(&self) -> op::UnaryOp { |
||||
self.op |
||||
} |
||||
|
||||
/// Gets the target of this unary operator.
|
||||
pub fn target(&self) -> &Node { |
||||
self.target.as_ref() |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for UnaryOp { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
write!(f, "{}{}", self.op, self.target) |
||||
} |
||||
} |
||||
|
||||
impl From<UnaryOp> for Node { |
||||
fn from(op: UnaryOp) -> Self { |
||||
Self::UnaryOp(op) |
||||
} |
||||
} |
@ -0,0 +1,67 @@
|
||||
//! Statement list node.
|
||||
|
||||
use super::Node; |
||||
use gc::{Finalize, Trace}; |
||||
use std::fmt; |
||||
|
||||
#[cfg(feature = "serde")] |
||||
use serde::{Deserialize, Serialize}; |
||||
|
||||
/// List of statements.
|
||||
///
|
||||
/// Similar to `Node::Block` but without the braces.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#prod-StatementList
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct StatementList { |
||||
#[cfg_attr(feature = "serde", serde(flatten))] |
||||
statements: Box<[Node]>, |
||||
} |
||||
|
||||
impl StatementList { |
||||
/// Gets the list of statements.
|
||||
pub fn statements(&self) -> &[Node] { |
||||
&self.statements |
||||
} |
||||
|
||||
/// Implements the display formatting with indentation.
|
||||
pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { |
||||
let indent = " ".repeat(indentation); |
||||
// Print statements
|
||||
for node in self.statements.iter() { |
||||
f.write_str(&indent)?; |
||||
node.display(f, indentation + 1)?; |
||||
|
||||
match node { |
||||
Node::Block(_) |
||||
| Node::If(_, _, _) |
||||
| Node::Switch(_, _, _) |
||||
| Node::WhileLoop(_, _) => {} |
||||
_ => write!(f, ";")?, |
||||
} |
||||
writeln!(f)?; |
||||
} |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
impl<T> From<T> for StatementList |
||||
where |
||||
T: Into<Box<[Node]>>, |
||||
{ |
||||
fn from(stm: T) -> Self { |
||||
Self { |
||||
statements: stm.into(), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for StatementList { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
self.display(f, 0) |
||||
} |
||||
} |
@ -0,0 +1,172 @@
|
||||
use super::{Block, Identifier, Node}; |
||||
use gc::{Finalize, Trace}; |
||||
use std::fmt; |
||||
|
||||
#[cfg(feature = "serde")] |
||||
use serde::{Deserialize, Serialize}; |
||||
|
||||
/// 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(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct Try { |
||||
block: Block, |
||||
catch: Option<Catch>, |
||||
finally: Option<Finally>, |
||||
} |
||||
|
||||
impl Try { |
||||
/// Creates a new `Try` AST node.
|
||||
pub(in crate::syntax) fn new<B>( |
||||
block: B, |
||||
catch: Option<Catch>, |
||||
finally: Option<Finally>, |
||||
) -> Self |
||||
where |
||||
B: Into<Block>, |
||||
{ |
||||
assert!( |
||||
catch.is_some() || finally.is_some(), |
||||
"one of catch or finally must be pressent" |
||||
); |
||||
|
||||
Self { |
||||
block: block.into(), |
||||
catch, |
||||
finally, |
||||
} |
||||
} |
||||
|
||||
/// Gets the `try` block.
|
||||
pub fn block(&self) -> &Block { |
||||
&self.block |
||||
} |
||||
|
||||
/// Gets the `catch` block, if any.
|
||||
pub fn catch(&self) -> Option<&Catch> { |
||||
self.catch.as_ref() |
||||
} |
||||
|
||||
/// Gets the `finally` block, if any.
|
||||
pub fn finally(&self) -> Option<&Block> { |
||||
self.finally.as_ref().map(Finally::block) |
||||
} |
||||
|
||||
/// Implements the display formatting with indentation.
|
||||
pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { |
||||
write!(f, "{}try ", " ".repeat(indentation))?; |
||||
self.block.display(f, indentation)?; |
||||
|
||||
if let Some(ref catch) = self.catch { |
||||
catch.display(f, indentation)?; |
||||
} |
||||
|
||||
if let Some(ref finally) = self.finally { |
||||
finally.display(f, indentation)?; |
||||
} |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for Try { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
self.display(f, 0) |
||||
} |
||||
} |
||||
|
||||
impl From<Try> for Node { |
||||
fn from(try_catch: Try) -> Self { |
||||
Self::Try(try_catch) |
||||
} |
||||
} |
||||
|
||||
/// Catch block.
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct Catch { |
||||
parameter: Option<Identifier>, |
||||
block: Block, |
||||
} |
||||
|
||||
impl Catch { |
||||
/// Creates a new catch block.
|
||||
pub(in crate::syntax) fn new<OI, I, B>(parameter: OI, block: B) -> Self |
||||
where |
||||
OI: Into<Option<I>>, |
||||
I: Into<Identifier>, |
||||
B: Into<Block>, |
||||
{ |
||||
Self { |
||||
parameter: parameter.into().map(I::into), |
||||
block: block.into(), |
||||
} |
||||
} |
||||
|
||||
/// Gets the parameter of the catch block.
|
||||
pub fn parameter(&self) -> Option<&str> { |
||||
self.parameter.as_ref().map(Identifier::as_ref) |
||||
} |
||||
|
||||
/// Retrieves the catch execution block.
|
||||
pub fn block(&self) -> &Block { |
||||
&self.block |
||||
} |
||||
|
||||
/// Implements the display formatting with indentation.
|
||||
pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { |
||||
f.write_str(" catch")?; |
||||
if let Some(ref param) = self.parameter { |
||||
write!(f, "({})", param)?; |
||||
} |
||||
f.write_str(" ")?; |
||||
self.block.display(f, indentation) |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for Catch { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
self.display(f, 0) |
||||
} |
||||
} |
||||
|
||||
/// Finally block.
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct Finally { |
||||
block: Block, |
||||
} |
||||
|
||||
impl Finally { |
||||
/// Gets the finally block.
|
||||
pub fn block(&self) -> &Block { |
||||
&self.block |
||||
} |
||||
|
||||
/// Implements the display formatting with indentation.
|
||||
pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { |
||||
f.write_str(" finally ")?; |
||||
self.block.display(f, indentation) |
||||
} |
||||
} |
||||
|
||||
impl<T> From<T> for Finally |
||||
where |
||||
T: Into<Block>, |
||||
{ |
||||
fn from(block: T) -> Self { |
||||
Self { |
||||
block: block.into(), |
||||
} |
||||
} |
||||
} |
@ -1,36 +0,0 @@
|
||||
//! This module implements the `Pos` structure, which represents a position in the source code.
|
||||
|
||||
#[cfg(feature = "serde")] |
||||
use serde::{Deserialize, Serialize}; |
||||
|
||||
/// A position in the Javascript 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(Serialize, Deserialize))] |
||||
#[derive(Clone, Copy, PartialEq, Debug)] |
||||
pub struct Position { |
||||
// Column number
|
||||
pub column_number: u64, |
||||
// Line number
|
||||
pub line_number: u64, |
||||
} |
||||
|
||||
impl Position { |
||||
/// Creates a new `Position`.
|
||||
///
|
||||
/// Positions are usually created by a [`Token`](struct.token/Token.html).
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `line_number` - The line number the token starts at
|
||||
/// * `column_number` - The column number the token starts at
|
||||
pub fn new(line_number: u64, column_number: u64) -> Self { |
||||
Self { |
||||
line_number, |
||||
column_number, |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,291 @@
|
||||
//! This module implements the `Pos` structure, which represents a position in the source code.
|
||||
|
||||
use std::{cmp::Ordering, fmt, num::NonZeroU32}; |
||||
|
||||
#[cfg(feature = "serde")] |
||||
use serde::{Deserialize, Serialize}; |
||||
|
||||
/// A position in the JavaScript 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(Serialize, Deserialize))] |
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] |
||||
pub struct Position { |
||||
/// Line number.
|
||||
line_number: NonZeroU32, |
||||
/// Column number.
|
||||
column_number: NonZeroU32, |
||||
} |
||||
|
||||
impl Position { |
||||
/// Creates a new `Position`.
|
||||
#[inline] |
||||
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] |
||||
pub fn line_number(self) -> u32 { |
||||
self.line_number.get() |
||||
} |
||||
|
||||
/// Gets the column number of the position.
|
||||
#[inline] |
||||
pub 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 JavaScript source code.
|
||||
///
|
||||
/// Stores a start position and an end position.
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
||||
pub struct Span { |
||||
start: Position, |
||||
end: Position, |
||||
} |
||||
|
||||
impl Span { |
||||
/// Creates a new `Span`.
|
||||
#[inline] |
||||
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] |
||||
pub fn start(self) -> Position { |
||||
self.start |
||||
} |
||||
|
||||
/// Gets the final position of the span.
|
||||
#[inline] |
||||
pub fn end(self) -> Position { |
||||
self.end |
||||
} |
||||
|
||||
/// Checks if this span inclusively contains another span or position.
|
||||
#[inline] |
||||
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 { |
||||
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 retreive 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", format!("{}", pos)); |
||||
} |
||||
|
||||
/// 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]", format!("{}", span)); |
||||
} |
||||
|
||||
/// 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); |
||||
} |
||||
} |
@ -1,10 +1,10 @@
|
||||
use crate::syntax::{ast::node::Node, parser::tests::check_parser}; |
||||
use crate::syntax::{ast::Const, parser::tests::check_parser}; |
||||
|
||||
#[test] |
||||
fn check_string() { |
||||
// Check empty string
|
||||
check_parser("\"\"", vec![Node::const_node("")]); |
||||
check_parser("\"\"", vec![Const::from("").into()]); |
||||
|
||||
// Check non-empty string
|
||||
check_parser("\"hello\"", vec![Node::const_node("hello")]); |
||||
check_parser("\"hello\"", vec![Const::from("hello").into()]); |
||||
} |
||||
|
@ -1,203 +0,0 @@
|
||||
//! Block statement parsing.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript specification][spec]
|
||||
//!
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-block
|
||||
|
||||
use super::{declaration::Declaration, Statement}; |
||||
use crate::syntax::{ |
||||
ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind}, |
||||
parser::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, |
||||
}; |
||||
|
||||
/// A `BlockStatement` is equivalent to a `Block`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript specification][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#prod-BlockStatement
|
||||
pub(super) type BlockStatement = Block; |
||||
|
||||
/// Variable declaration list parsing.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [ECMAScript specification][spec]
|
||||
///
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block
|
||||
/// [spec]: https://tc39.es/ecma262/#prod-Block
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(super) struct Block { |
||||
allow_yield: AllowYield, |
||||
allow_await: AllowAwait, |
||||
allow_return: AllowReturn, |
||||
} |
||||
|
||||
impl Block { |
||||
/// Creates a new `Block` parser.
|
||||
pub(super) fn new<Y, A, R>(allow_yield: Y, allow_await: A, allow_return: R) -> Self |
||||
where |
||||
Y: Into<AllowYield>, |
||||
A: Into<AllowAwait>, |
||||
R: Into<AllowReturn>, |
||||
{ |
||||
Self { |
||||
allow_yield: allow_yield.into(), |
||||
allow_await: allow_await.into(), |
||||
allow_return: allow_return.into(), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl TokenParser for Block { |
||||
type Output = Node; |
||||
|
||||
fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { |
||||
cursor.expect(Punctuator::OpenBlock, "block")?; |
||||
if let Some(tk) = cursor.peek(0) { |
||||
if tk.kind == TokenKind::Punctuator(Punctuator::CloseBlock) { |
||||
cursor.next(); |
||||
return Ok(Node::Block(Box::new([]))); |
||||
} |
||||
} |
||||
|
||||
let statement_list = |
||||
StatementList::new(self.allow_yield, self.allow_await, self.allow_return, true) |
||||
.parse(cursor) |
||||
.map(Node::block)?; |
||||
cursor.expect(Punctuator::CloseBlock, "block")?; |
||||
|
||||
Ok(statement_list) |
||||
} |
||||
} |
||||
|
||||
/// Reads a list of statements.
|
||||
///
|
||||
/// If `break_when_closingbrase` is `true`, it will stop as soon as it finds a `}` character.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript specification][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#prod-StatementList
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(in crate::syntax::parser) struct StatementList { |
||||
allow_yield: AllowYield, |
||||
allow_await: AllowAwait, |
||||
allow_return: AllowReturn, |
||||
break_when_closingbrase: bool, |
||||
} |
||||
|
||||
impl StatementList { |
||||
/// Creates a new `StatementList` parser.
|
||||
pub(in crate::syntax::parser) fn new<Y, A, R>( |
||||
allow_yield: Y, |
||||
allow_await: A, |
||||
allow_return: R, |
||||
break_when_closingbrase: bool, |
||||
) -> Self |
||||
where |
||||
Y: Into<AllowYield>, |
||||
A: Into<AllowAwait>, |
||||
R: Into<AllowReturn>, |
||||
{ |
||||
Self { |
||||
allow_yield: allow_yield.into(), |
||||
allow_await: allow_await.into(), |
||||
allow_return: allow_return.into(), |
||||
break_when_closingbrase, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl TokenParser for StatementList { |
||||
type Output = Vec<Node>; |
||||
|
||||
fn parse(self, cursor: &mut Cursor<'_>) -> Result<Self::Output, ParseError> { |
||||
let mut items = Vec::new(); |
||||
|
||||
loop { |
||||
match cursor.peek(0) { |
||||
Some(token) if token.kind == TokenKind::Punctuator(Punctuator::CloseBlock) => { |
||||
if self.break_when_closingbrase { |
||||
break; |
||||
} else { |
||||
return Err(ParseError::Unexpected(token.clone(), None)); |
||||
} |
||||
} |
||||
None => { |
||||
if self.break_when_closingbrase { |
||||
return Err(ParseError::AbruptEnd); |
||||
} else { |
||||
break; |
||||
} |
||||
} |
||||
_ => {} |
||||
} |
||||
|
||||
let item = |
||||
StatementListItem::new(self.allow_yield, self.allow_await, self.allow_return) |
||||
.parse(cursor)?; |
||||
items.push(item); |
||||
|
||||
// move the cursor forward for any consecutive semicolon.
|
||||
while cursor.next_if(Punctuator::Semicolon).is_some() {} |
||||
} |
||||
|
||||
Ok(items) |
||||
} |
||||
} |
||||
|
||||
/// Statement list item parsing
|
||||
///
|
||||
/// A statement list item can either be an statement or a declaration.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [ECMAScript specification][spec]
|
||||
///
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements
|
||||
/// [spec]: https://tc39.es/ecma262/#prod-StatementListItem
|
||||
#[derive(Debug, Clone, Copy)] |
||||
struct StatementListItem { |
||||
allow_yield: AllowYield, |
||||
allow_await: AllowAwait, |
||||
allow_return: AllowReturn, |
||||
} |
||||
|
||||
impl StatementListItem { |
||||
/// Creates a new `StatementListItem` parser.
|
||||
fn new<Y, A, R>(allow_yield: Y, allow_await: A, allow_return: R) -> Self |
||||
where |
||||
Y: Into<AllowYield>, |
||||
A: Into<AllowAwait>, |
||||
R: Into<AllowReturn>, |
||||
{ |
||||
Self { |
||||
allow_yield: allow_yield.into(), |
||||
allow_await: allow_await.into(), |
||||
allow_return: allow_return.into(), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl TokenParser for StatementListItem { |
||||
type Output = Node; |
||||
|
||||
fn parse(self, cursor: &mut Cursor<'_>) -> ParseResult { |
||||
let tok = cursor.peek(0).ok_or(ParseError::AbruptEnd)?; |
||||
|
||||
match tok.kind { |
||||
TokenKind::Keyword(Keyword::Function) |
||||
| TokenKind::Keyword(Keyword::Const) |
||||
| TokenKind::Keyword(Keyword::Let) => { |
||||
Declaration::new(self.allow_yield, self.allow_await).parse(cursor) |
||||
} |
||||
_ => { |
||||
Statement::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor) |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,78 @@
|
||||
//! Block statement parsing.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [MDN documentation][mdn]
|
||||
//! - [ECMAScript specification][spec]
|
||||
//!
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-block
|
||||
|
||||
#[cfg(test)] |
||||
mod tests; |
||||
|
||||
use super::StatementList; |
||||
use crate::syntax::{ |
||||
ast::{node, Punctuator, TokenKind}, |
||||
parser::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser}, |
||||
}; |
||||
|
||||
/// A `BlockStatement` is equivalent to a `Block`.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript specification][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#prod-BlockStatement
|
||||
pub(super) type BlockStatement = Block; |
||||
|
||||
/// Variable declaration list parsing.
|
||||
///
|
||||
/// More information:
|
||||
/// - [MDN documentation][mdn]
|
||||
/// - [ECMAScript specification][spec]
|
||||
///
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block
|
||||
/// [spec]: https://tc39.es/ecma262/#prod-Block
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(super) struct Block { |
||||
allow_yield: AllowYield, |
||||
allow_await: AllowAwait, |
||||
allow_return: AllowReturn, |
||||
} |
||||
|
||||
impl Block { |
||||
/// Creates a new `Block` parser.
|
||||
pub(super) fn new<Y, A, R>(allow_yield: Y, allow_await: A, allow_return: R) -> Self |
||||
where |
||||
Y: Into<AllowYield>, |
||||
A: Into<AllowAwait>, |
||||
R: Into<AllowReturn>, |
||||
{ |
||||
Self { |
||||
allow_yield: allow_yield.into(), |
||||
allow_await: allow_await.into(), |
||||
allow_return: allow_return.into(), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl TokenParser for Block { |
||||
type Output = node::Block; |
||||
|
||||
fn parse(self, cursor: &mut Cursor<'_>) -> Result<Self::Output, ParseError> { |
||||
cursor.expect(Punctuator::OpenBlock, "block")?; |
||||
if let Some(tk) = cursor.peek(0) { |
||||
if tk.kind == TokenKind::Punctuator(Punctuator::CloseBlock) { |
||||
cursor.next(); |
||||
return Ok(node::Block::from(vec![])); |
||||
} |
||||
} |
||||
|
||||
let statement_list = |
||||
StatementList::new(self.allow_yield, self.allow_await, self.allow_return, true) |
||||
.parse(cursor) |
||||
.map(node::Block::from)?; |
||||
cursor.expect(Punctuator::CloseBlock, "block")?; |
||||
|
||||
Ok(statement_list) |
||||
} |
||||
} |
@ -0,0 +1,104 @@
|
||||
//! Block statement parsing tests.
|
||||
|
||||
use crate::syntax::{ |
||||
ast::{ |
||||
node::{ |
||||
Assign, Block, Call, FunctionDecl, Identifier, Node, UnaryOp, VarDecl, VarDeclList, |
||||
}, |
||||
op, Const, |
||||
}, |
||||
parser::tests::check_parser, |
||||
}; |
||||
|
||||
/// Helper function to check a block.
|
||||
// TODO: #[track_caller]: https://github.com/rust-lang/rust/issues/47809
|
||||
fn check_block<B>(js: &str, block: B) |
||||
where |
||||
B: Into<Box<[Node]>>, |
||||
{ |
||||
check_parser(js, vec![Block::from(block.into()).into()]); |
||||
} |
||||
|
||||
#[test] |
||||
fn empty() { |
||||
check_block("{}", vec![]); |
||||
} |
||||
|
||||
#[test] |
||||
fn non_empty() { |
||||
check_block( |
||||
r"{ |
||||
var a = 10; |
||||
a++; |
||||
}", |
||||
vec![ |
||||
VarDeclList::from(vec![VarDecl::new("a", Some(Const::from(10).into()))]).into(), |
||||
UnaryOp::new(op::UnaryOp::IncrementPost, Identifier::from("a")).into(), |
||||
], |
||||
); |
||||
|
||||
check_block( |
||||
r"{ |
||||
function hello() { |
||||
return 10 |
||||
} |
||||
|
||||
var a = hello(); |
||||
a++; |
||||
}", |
||||
vec![ |
||||
FunctionDecl::new( |
||||
"hello".to_owned().into_boxed_str(), |
||||
vec![], |
||||
vec![Node::return_node(Const::from(10))], |
||||
) |
||||
.into(), |
||||
VarDeclList::from(vec![VarDecl::new( |
||||
"a", |
||||
Node::from(Call::new(Identifier::from("hello"), vec![])), |
||||
)]) |
||||
.into(), |
||||
UnaryOp::new(op::UnaryOp::IncrementPost, Identifier::from("a")).into(), |
||||
], |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn hoisting() { |
||||
check_block( |
||||
r"{ |
||||
var a = hello(); |
||||
a++; |
||||
|
||||
function hello() { return 10 } |
||||
}", |
||||
vec![ |
||||
FunctionDecl::new( |
||||
"hello".to_owned().into_boxed_str(), |
||||
vec![], |
||||
vec![Node::return_node(Const::from(10))], |
||||
) |
||||
.into(), |
||||
VarDeclList::from(vec![VarDecl::new( |
||||
"a", |
||||
Node::from(Call::new(Identifier::from("hello"), vec![])), |
||||
)]) |
||||
.into(), |
||||
UnaryOp::new(op::UnaryOp::IncrementPost, Identifier::from("a")).into(), |
||||
], |
||||
); |
||||
|
||||
check_block( |
||||
r"{ |
||||
a = 10; |
||||
a++; |
||||
|
||||
var a; |
||||
}", |
||||
vec![ |
||||
Assign::new(Identifier::from("a"), Const::from(10)).into(), |
||||
UnaryOp::new(op::UnaryOp::IncrementPost, Identifier::from("a")).into(), |
||||
VarDeclList::from(vec![VarDecl::new("a", None)]).into(), |
||||
], |
||||
); |
||||
} |
@ -1,92 +1,121 @@
|
||||
use crate::syntax::{ast::node::Node, parser::tests::check_parser}; |
||||
use crate::syntax::{ |
||||
ast::{ |
||||
node::{Block, Node}, |
||||
Const, |
||||
}, |
||||
parser::tests::check_parser, |
||||
}; |
||||
|
||||
#[test] |
||||
fn check_inline() { |
||||
fn inline() { |
||||
check_parser( |
||||
"while (true) break;", |
||||
vec![Node::while_loop(Node::const_node(true), Node::Break(None))], |
||||
vec![Node::while_loop(Const::from(true), Node::Break(None))], |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn check_new_line() { |
||||
fn new_line() { |
||||
check_parser( |
||||
"while (true) |
||||
break;", |
||||
vec![Node::while_loop(Node::const_node(true), Node::Break(None))], |
||||
vec![Node::while_loop(Const::from(true), Node::Break(None))], |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn check_inline_block_semicolon_insertion() { |
||||
fn inline_block_semicolon_insertion() { |
||||
check_parser( |
||||
"while (true) {break}", |
||||
vec![Node::while_loop( |
||||
Node::const_node(true), |
||||
Node::block(vec![Node::Break(None)]), |
||||
Const::from(true), |
||||
Block::from(vec![Node::Break(None)]), |
||||
)], |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn check_new_line_semicolon_insertion() { |
||||
fn new_line_semicolon_insertion() { |
||||
check_parser( |
||||
"while (true) { |
||||
break test |
||||
}", |
||||
vec![Node::while_loop( |
||||
Node::const_node(true), |
||||
Node::block(vec![Node::break_node("test")]), |
||||
Const::from(true), |
||||
Block::from(vec![Node::break_node("test")]), |
||||
)], |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn check_inline_block() { |
||||
fn inline_block() { |
||||
check_parser( |
||||
"while (true) {break;}", |
||||
vec![Node::while_loop( |
||||
Node::const_node(true), |
||||
Node::block(vec![Node::Break(None)]), |
||||
Const::from(true), |
||||
Block::from(vec![Node::Break(None)]), |
||||
)], |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn check_new_line_block() { |
||||
fn new_line_block() { |
||||
check_parser( |
||||
"while (true) { |
||||
break test; |
||||
}", |
||||
vec![Node::while_loop( |
||||
Node::const_node(true), |
||||
Node::block(vec![Node::break_node("test")]), |
||||
Const::from(true), |
||||
Block::from(vec![Node::break_node("test")]), |
||||
)], |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn check_new_line_block_empty() { |
||||
fn reserved_label() { |
||||
check_parser( |
||||
"while (true) { |
||||
break await; |
||||
}", |
||||
vec![Node::while_loop( |
||||
Const::from(true), |
||||
Block::from(vec![Node::break_node("await")]), |
||||
)], |
||||
); |
||||
|
||||
check_parser( |
||||
"while (true) { |
||||
break yield; |
||||
}", |
||||
vec![Node::while_loop( |
||||
Const::from(true), |
||||
Block::from(vec![Node::break_node("yield")]), |
||||
)], |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn new_line_block_empty() { |
||||
check_parser( |
||||
"while (true) { |
||||
break; |
||||
}", |
||||
vec![Node::while_loop( |
||||
Node::const_node(true), |
||||
Node::block(vec![Node::Break(None)]), |
||||
Const::from(true), |
||||
Block::from(vec![Node::Break(None)]), |
||||
)], |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn check_new_line_block_empty_semicolon_insertion() { |
||||
fn new_line_block_empty_semicolon_insertion() { |
||||
check_parser( |
||||
"while (true) { |
||||
break
|
||||
}", |
||||
vec![Node::while_loop( |
||||
Node::const_node(true), |
||||
Node::block(vec![Node::Break(None)]), |
||||
Const::from(true), |
||||
Block::from(vec![Node::Break(None)]), |
||||
)], |
||||
); |
||||
} |
||||
|
@ -1,98 +1,121 @@
|
||||
use crate::syntax::{ast::node::Node, parser::tests::check_parser}; |
||||
use crate::syntax::{ |
||||
ast::{ |
||||
node::{Block, Node}, |
||||
Const, |
||||
}, |
||||
parser::tests::check_parser, |
||||
}; |
||||
|
||||
#[test] |
||||
fn check_inline() { |
||||
fn inline() { |
||||
check_parser( |
||||
"while (true) continue;", |
||||
vec![Node::while_loop( |
||||
Node::const_node(true), |
||||
Node::Continue(None), |
||||
)], |
||||
vec![Node::while_loop(Const::from(true), Node::Continue(None))], |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn check_new_line() { |
||||
fn new_line() { |
||||
check_parser( |
||||
"while (true) |
||||
continue;", |
||||
vec![Node::while_loop( |
||||
Node::const_node(true), |
||||
Node::Continue(None), |
||||
)], |
||||
vec![Node::while_loop(Const::from(true), Node::Continue(None))], |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn check_inline_block_semicolon_insertion() { |
||||
fn inline_block_semicolon_insertion() { |
||||
check_parser( |
||||
"while (true) {continue}", |
||||
vec![Node::while_loop( |
||||
Node::const_node(true), |
||||
Node::block(vec![Node::Continue(None)]), |
||||
Const::from(true), |
||||
Block::from(vec![Node::Continue(None)]), |
||||
)], |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn check_new_line_semicolon_insertion() { |
||||
fn new_line_semicolon_insertion() { |
||||
check_parser( |
||||
"while (true) { |
||||
continue test |
||||
}", |
||||
vec![Node::while_loop( |
||||
Node::const_node(true), |
||||
Node::block(vec![Node::continue_node("test")]), |
||||
Const::from(true), |
||||
Block::from(vec![Node::continue_node("test")]), |
||||
)], |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn check_inline_block() { |
||||
fn inline_block() { |
||||
check_parser( |
||||
"while (true) {continue;}", |
||||
vec![Node::while_loop( |
||||
Node::const_node(true), |
||||
Node::block(vec![Node::Continue(None)]), |
||||
Const::from(true), |
||||
Block::from(vec![Node::Continue(None)]), |
||||
)], |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn check_new_line_block() { |
||||
fn new_line_block() { |
||||
check_parser( |
||||
"while (true) { |
||||
continue test; |
||||
}", |
||||
vec![Node::while_loop( |
||||
Node::const_node(true), |
||||
Node::block(vec![Node::continue_node("test")]), |
||||
Const::from(true), |
||||
Block::from(vec![Node::continue_node("test")]), |
||||
)], |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn reserved_label() { |
||||
check_parser( |
||||
"while (true) { |
||||
continue await; |
||||
}", |
||||
vec![Node::while_loop( |
||||
Const::from(true), |
||||
Block::from(vec![Node::continue_node("await")]), |
||||
)], |
||||
); |
||||
|
||||
check_parser( |
||||
"while (true) { |
||||
continue yield; |
||||
}", |
||||
vec![Node::while_loop( |
||||
Const::from(true), |
||||
Block::from(vec![Node::continue_node("yield")]), |
||||
)], |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn check_new_line_block_empty() { |
||||
fn new_line_block_empty() { |
||||
check_parser( |
||||
"while (true) { |
||||
continue; |
||||
}", |
||||
vec![Node::while_loop( |
||||
Node::const_node(true), |
||||
Node::block(vec![Node::Continue(None)]), |
||||
Const::from(true), |
||||
Block::from(vec![Node::Continue(None)]), |
||||
)], |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn check_new_line_block_empty_semicolon_insertion() { |
||||
fn new_line_block_empty_semicolon_insertion() { |
||||
check_parser( |
||||
"while (true) { |
||||
continue
|
||||
}", |
||||
vec![Node::while_loop( |
||||
Node::const_node(true), |
||||
Node::block(vec![Node::Continue(None)]), |
||||
Const::from(true), |
||||
Block::from(vec![Node::Continue(None)]), |
||||
)], |
||||
); |
||||
} |
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue