Browse Source

Implemented function expressions (#382)

pull/383/head
Iban Eguia 4 years ago committed by GitHub
parent
commit
9c9c4638e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 37
      boa/src/exec/mod.rs
  2. 84
      boa/src/exec/tests.rs
  3. 292
      boa/src/syntax/ast/node.rs
  4. 2
      boa/src/syntax/parser/expression/primary/function_expression.rs
  5. 2
      boa/src/syntax/parser/expression/primary/object_initializer/mod.rs
  6. 8
      boa/src/syntax/parser/expression/primary/object_initializer/tests.rs

37
boa/src/exec/mod.rs

@ -297,17 +297,42 @@ impl Executor for Interpreter {
val.set_field_slice("length", to_value(args.len())); val.set_field_slice("length", to_value(args.len()));
// Set the name and assign it in the current environment // Set the name and assign it in the current environment
if name.is_some() { val.set_field_slice("name", to_value(name.clone()));
self.realm.environment.create_mutable_binding( self.realm.environment.create_mutable_binding(
name.clone().expect("No name was supplied"), name.clone(),
false, false,
VariableScope::Function, VariableScope::Function,
); );
self.realm.environment.initialize_binding( self.realm.environment.initialize_binding(name, val.clone());
name.as_ref().expect("Could not get name as reference"),
val.clone(), Ok(val)
) }
// <https://tc39.es/ecma262/#sec-createdynamicfunction>
Node::FunctionExpr(ref name, ref args, ref expr) => {
// Todo: Function.prototype doesn't exist yet, so the prototype right now is the Object.prototype
// let proto = &self
// .realm
// .environment
// .get_global_object()
// .expect("Could not get the global object")
// .get_field_slice("Object")
// .get_field_slice("Prototype");
let func = FunctionObject::create_ordinary(
args.clone(), // TODO: args shouldn't need to be a reference it should be passed by value
self.realm.environment.get_current_environment().clone(),
FunctionBody::Ordinary(*expr.clone()),
ThisMode::NonLexical,
);
let mut new_func = Object::function();
new_func.set_call(func);
let val = to_value(new_func);
val.set_field_slice("length", to_value(args.len()));
if let Some(name) = name {
val.set_field_slice("name", to_value(name.clone()));
} }
Ok(val) Ok(val)

84
boa/src/exec/tests.rs

@ -47,7 +47,7 @@ fn object_field_set() {
m['key'] = 22; m['key'] = 22;
m['key'] m['key']
"#; "#;
assert_eq!(exec(scenario), String::from("22")); assert_eq!(&exec(scenario), "22");
} }
#[test] #[test]
@ -98,28 +98,28 @@ fn array_field_set() {
m[1] = 5; m[1] = 5;
m[1] m[1]
"#; "#;
assert_eq!(exec(element_changes), String::from("5")); assert_eq!(&exec(element_changes), "5");
let length_changes = r#" let length_changes = r#"
let m = [1, 2, 3]; let m = [1, 2, 3];
m[10] = 52; m[10] = 52;
m.length m.length
"#; "#;
assert_eq!(exec(length_changes), String::from("11")); assert_eq!(&exec(length_changes), "11");
let negative_index_wont_affect_length = r#" let negative_index_wont_affect_length = r#"
let m = [1, 2, 3]; let m = [1, 2, 3];
m[-11] = 5; m[-11] = 5;
m.length m.length
"#; "#;
assert_eq!(exec(negative_index_wont_affect_length), String::from("3")); assert_eq!(&exec(negative_index_wont_affect_length), "3");
let non_num_key_wont_affect_length = r#" let non_num_key_wont_affect_length = r#"
let m = [1, 2, 3]; let m = [1, 2, 3];
m["magic"] = 5; m["magic"] = 5;
m.length m.length
"#; "#;
assert_eq!(exec(non_num_key_wont_affect_length), String::from("3")); assert_eq!(&exec(non_num_key_wont_affect_length), "3");
} }
#[test] #[test]
@ -128,36 +128,36 @@ fn tilde_operator() {
let f = -1.2; let f = -1.2;
~f ~f
"#; "#;
assert_eq!(exec(float), String::from("0")); assert_eq!(&exec(float), "0");
let numeric = r#" let numeric = r#"
let f = 1789; let f = 1789;
~f ~f
"#; "#;
assert_eq!(exec(numeric), String::from("-1790")); assert_eq!(&exec(numeric), "-1790");
// TODO: enable test after we have NaN // TODO: enable test after we have NaN
// let nan = r#" // let nan = r#"
// var m = NaN; // var m = NaN;
// ~m // ~m
// "#; // "#;
// assert_eq!(exec(nan), String::from("-1")); // assert_eq!(&exec(nan), "-1");
let object = r#" let object = r#"
let m = {}; let m = {};
~m ~m
"#; "#;
assert_eq!(exec(object), String::from("-1")); assert_eq!(&exec(object), "-1");
let boolean_true = r#" let boolean_true = r#"
~true ~true
"#; "#;
assert_eq!(exec(boolean_true), String::from("-2")); assert_eq!(&exec(boolean_true), "-2");
let boolean_false = r#" let boolean_false = r#"
~false ~false
"#; "#;
assert_eq!(exec(boolean_false), String::from("-1")); assert_eq!(&exec(boolean_false), "-1");
} }
#[test] #[test]
@ -171,7 +171,7 @@ fn early_return() {
} }
early_return() early_return()
"#; "#;
assert_eq!(exec(early_return), String::from("true")); assert_eq!(&exec(early_return), "true");
let early_return = r#" let early_return = r#"
function nested_fnct() { function nested_fnct() {
@ -183,7 +183,7 @@ fn early_return() {
} }
outer_fnct() outer_fnct()
"#; "#;
assert_eq!(exec(early_return), String::from("outer")); assert_eq!(&exec(early_return), "outer");
} }
#[test] #[test]
@ -204,7 +204,7 @@ fn short_circuit_evaluation() {
let _ = add_one(counter) || add_one(counter); let _ = add_one(counter) || add_one(counter);
counter.value counter.value
"#; "#;
assert_eq!(exec(short_circuit_eval), String::from("1")); assert_eq!(&exec(short_circuit_eval), "1");
// the second operand must be evaluated if the first one resolve to `false`. // the second operand must be evaluated if the first one resolve to `false`.
let short_circuit_eval = r#" let short_circuit_eval = r#"
@ -216,7 +216,7 @@ fn short_circuit_evaluation() {
let _ = add_one(counter) || add_one(counter); let _ = add_one(counter) || add_one(counter);
counter.value counter.value
"#; "#;
assert_eq!(exec(short_circuit_eval), String::from("2")); assert_eq!(&exec(short_circuit_eval), "2");
// AND operation // AND operation
assert_eq!(exec("true && true"), String::from("true")); assert_eq!(exec("true && true"), String::from("true"));
@ -234,7 +234,7 @@ fn short_circuit_evaluation() {
let _ = add_one(counter) && add_one(counter); let _ = add_one(counter) && add_one(counter);
counter.value counter.value
"#; "#;
assert_eq!(exec(short_circuit_eval), String::from("2")); assert_eq!(&exec(short_circuit_eval), "2");
// the second operand must NOT be evaluated if the first one resolve to `false`. // the second operand must NOT be evaluated if the first one resolve to `false`.
let short_circuit_eval = r#" let short_circuit_eval = r#"
@ -246,7 +246,7 @@ fn short_circuit_evaluation() {
let _ = add_one(counter) && add_one(counter); let _ = add_one(counter) && add_one(counter);
counter.value counter.value
"#; "#;
assert_eq!(exec(short_circuit_eval), String::from("1")); assert_eq!(&exec(short_circuit_eval), "1");
} }
#[test] #[test]
@ -256,7 +256,7 @@ fn assign_operator_precedence() {
a = a + 1; a = a + 1;
a a
"#; "#;
assert_eq!(exec(src), String::from("2")); assert_eq!(&exec(src), "2");
} }
#[test] #[test]
@ -269,7 +269,7 @@ fn do_while_loop() {
a a
"#; "#;
assert_eq!(exec(simple_one), String::from("10")); assert_eq!(&exec(simple_one), "10");
let multiline_statement = r#" let multiline_statement = r#"
pow = 0; pow = 0;
@ -280,7 +280,7 @@ fn do_while_loop() {
} while (pow < 8); } while (pow < 8);
b b
"#; "#;
assert_eq!(exec(multiline_statement), String::from("256")); assert_eq!(&exec(multiline_statement), "256");
let body_is_executed_at_least_once = r#" let body_is_executed_at_least_once = r#"
a = 0; a = 0;
@ -291,7 +291,7 @@ fn do_while_loop() {
while (false); while (false);
a a
"#; "#;
assert_eq!(exec(body_is_executed_at_least_once), String::from("1")); assert_eq!(&exec(body_is_executed_at_least_once), "1");
} }
#[test] #[test]
@ -300,7 +300,7 @@ fn do_while_post_inc() {
var i = 0; var i = 0;
do {} while(i++ < 10) i; do {} while(i++ < 10) i;
"#; "#;
assert_eq!(exec(with_post_incrementors), String::from("11")); assert_eq!(&exec(with_post_incrementors), "11");
} }
#[test] #[test]
@ -313,7 +313,7 @@ fn test_for_loop() {
} }
b b
"#; "#;
assert_eq!(exec(simple), String::from("hello")); assert_eq!(&exec(simple), "hello");
let without_init_and_inc_step = r#" let without_init_and_inc_step = r#"
let a = 0; let a = 0;
@ -325,7 +325,7 @@ fn test_for_loop() {
a a
"#; "#;
assert_eq!(exec(without_init_and_inc_step), String::from("45")); assert_eq!(&exec(without_init_and_inc_step), "45");
let body_should_not_execute_on_false_condition = r#" let body_should_not_execute_on_false_condition = r#"
let a = 0 let a = 0
@ -345,7 +345,7 @@ fn test_for_loop() {
i i
"#; "#;
assert_eq!(exec(inner_scope), String::from("undefined")); assert_eq!(&exec(inner_scope), "undefined");
} }
#[test] #[test]
@ -355,40 +355,40 @@ fn unary_pre() {
++a; ++a;
a; a;
"#; "#;
assert_eq!(exec(unary_inc), String::from("6")); assert_eq!(&exec(unary_inc), "6");
let unary_dec = r#" let unary_dec = r#"
let a = 5; let a = 5;
--a; --a;
a; a;
"#; "#;
assert_eq!(exec(unary_dec), String::from("4")); assert_eq!(&exec(unary_dec), "4");
let inc_obj_prop = r#" let inc_obj_prop = r#"
const a = { b: 5 }; const a = { b: 5 };
++a.b; ++a.b;
a['b']; a['b'];
"#; "#;
assert_eq!(exec(inc_obj_prop), String::from("6")); assert_eq!(&exec(inc_obj_prop), "6");
let inc_obj_field = r#" let inc_obj_field = r#"
const a = { b: 5 }; const a = { b: 5 };
++a['b']; ++a['b'];
a.b; a.b;
"#; "#;
assert_eq!(exec(inc_obj_field), String::from("6")); assert_eq!(&exec(inc_obj_field), "6");
let execs_before_inc = r#" let execs_before_inc = r#"
let a = 5; let a = 5;
++a === 6; ++a === 6;
"#; "#;
assert_eq!(exec(execs_before_inc), String::from("true")); assert_eq!(&exec(execs_before_inc), "true");
let execs_before_dec = r#" let execs_before_dec = r#"
let a = 5; let a = 5;
--a === 4; --a === 4;
"#; "#;
assert_eq!(exec(execs_before_dec), String::from("true")); assert_eq!(&exec(execs_before_dec), "true");
} }
#[test] #[test]
@ -398,40 +398,40 @@ fn unary_post() {
a++; a++;
a; a;
"#; "#;
assert_eq!(exec(unary_inc), String::from("6")); assert_eq!(&exec(unary_inc), "6");
let unary_dec = r#" let unary_dec = r#"
let a = 5; let a = 5;
a--; a--;
a; a;
"#; "#;
assert_eq!(exec(unary_dec), String::from("4")); assert_eq!(&exec(unary_dec), "4");
let inc_obj_prop = r#" let inc_obj_prop = r#"
const a = { b: 5 }; const a = { b: 5 };
a.b++; a.b++;
a['b']; a['b'];
"#; "#;
assert_eq!(exec(inc_obj_prop), String::from("6")); assert_eq!(&exec(inc_obj_prop), "6");
let inc_obj_field = r#" let inc_obj_field = r#"
const a = { b: 5 }; const a = { b: 5 };
a['b']++; a['b']++;
a.b; a.b;
"#; "#;
assert_eq!(exec(inc_obj_field), String::from("6")); assert_eq!(&exec(inc_obj_field), "6");
let execs_after_inc = r#" let execs_after_inc = r#"
let a = 5; let a = 5;
a++ === 5; a++ === 5;
"#; "#;
assert_eq!(exec(execs_after_inc), String::from("true")); assert_eq!(&exec(execs_after_inc), "true");
let execs_after_dec = r#" let execs_after_dec = r#"
let a = 5; let a = 5;
a-- === 5; a-- === 5;
"#; "#;
assert_eq!(exec(execs_after_dec), String::from("true")); assert_eq!(&exec(execs_after_dec), "true");
} }
#[cfg(test)] #[cfg(test)]
@ -444,7 +444,7 @@ mod in_operator {
var p = 'a'; var p = 'a';
p in o p in o
"#; "#;
assert_eq!(exec(p_in_o), String::from("true")); assert_eq!(&exec(p_in_o), "true");
} }
#[test] #[test]
@ -454,7 +454,7 @@ mod in_operator {
var p = 'toString'; var p = 'toString';
p in o p in o
"#; "#;
assert_eq!(exec(p_in_o), String::from("true")); assert_eq!(&exec(p_in_o), "true");
} }
#[test] #[test]
@ -464,7 +464,7 @@ mod in_operator {
var p = 'b'; var p = 'b';
p in o p in o
"#; "#;
assert_eq!(exec(p_not_in_o), String::from("false")); assert_eq!(&exec(p_not_in_o), "false");
} }
#[test] #[test]
@ -476,7 +476,7 @@ mod in_operator {
var a = ['a']; var a = ['a'];
n in a n in a
"#; "#;
assert_eq!(exec(num_in_array), String::from("true")); assert_eq!(&exec(num_in_array), "true");
} }
#[test] #[test]
@ -490,7 +490,7 @@ mod in_operator {
o[sym] = 'hello'; o[sym] = 'hello';
sym in o sym in o
"#; "#;
assert_eq!(exec(sym_in_object), String::from("true")); assert_eq!(&exec(sym_in_object), "true");
} }
#[test] #[test]

292
boa/src/syntax/ast/node.rs

@ -14,13 +14,15 @@ use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] #[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub enum Node { pub enum Node {
/// An array is an ordered collection of data (either primitive or object depending upon the language). /// 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. /// Arrays are used to store multiple values in a single variable.
/// This is compared to a variable that can store only one value. /// 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. /// Each item in an array has a number attached to it, called a numeric index, that allows you
/// In JavaScript, arrays start at index zero and can be manipulated with various methods. /// to access it. In JavaScript, arrays start at index zero and can be manipulated with various
/// methods.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -30,10 +32,12 @@ pub enum Node {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
ArrayDecl(Box<[Node]>), ArrayDecl(Box<[Node]>),
/// An arrow function expression is a syntactically compact alternative to a regular function expression. /// 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 function expressions are ill suited as methods, and they cannot be used as
/// Arrow functions cannot be used as constructors and will throw an error when used with new. /// constructors. Arrow functions cannot be used as constructors and will throw an error when
/// used with new.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -43,7 +47,8 @@ pub enum Node {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
ArrowFunctionDecl(Box<[FormalParameter]>, Box<Node>), ArrowFunctionDecl(Box<[FormalParameter]>, Box<Node>),
/// An assignment operator assigns a value to its left operand based on the value of its right operand. /// 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. /// Assignment operator (`=`), assigns the value of its right operand to its left operand.
/// ///
@ -63,12 +68,14 @@ pub enum Node {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Operators /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Operators
BinOp(BinOp, Box<Node>, Box<Node>), BinOp(BinOp, Box<Node>, Box<Node>),
/// A `block` statement (or compound statement in other languages) is used to group zero or more statements. /// 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. /// The block statement is often called compound statement in other languages.
/// It allows you to use multiple statements where JavaScript expects only one statement. /// 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, /// Combining statements into blocks is a common practice in JavaScript. The opposite behavior
/// where you provide no statement, although one is required. /// is possible using an empty statement, where you provide no statement, although one is
/// required.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -78,11 +85,13 @@ pub enum Node {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block
Block(Box<[Node]>), Block(Box<[Node]>),
/// The `break` statement terminates the current loop, switch, or label statement and transfers program control to the statement following the terminated statement. /// The `break` statement terminates the current loop, switch, or label statement and transfers
/// program control to the statement following the terminated statement.
/// ///
/// The break statement includes an optional label that allows the program to break out of a labeled statement. /// The break statement includes an optional label that allows the program to break out of a
/// The break statement needs to be nested within the referenced label. The labeled statement can be any block statement; /// labeled statement. The break statement needs to be nested within the referenced label. The
/// it does not have to be preceded by a loop statement. /// labeled statement can be any block statement; it does not have to be preceded by a loop
/// statement.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -94,9 +103,11 @@ pub enum Node {
/// Calling the function actually performs the specified actions with the indicated parameters. /// 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. /// Defining a function does not execute it. Defining it simply names the function and
/// Functions must be in scope when they are called, but the function declaration can be hoisted /// specifies what to do when the function is called. Functions must be in scope when they are
/// 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). /// 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: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -106,11 +117,13 @@ pub enum Node {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#Calling_functions /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#Calling_functions
Call(Box<Node>, Box<[Node]>), Call(Box<Node>, Box<[Node]>),
/// The `conditional` (ternary) operator is the only JavaScript operator that takes three operands. /// The `conditional` (ternary) operator is the only JavaScript operator that takes three
/// operands.
/// ///
/// This operator is the only JavaScript operator that takes three operands: a condition followed by a question mark (`?`), /// This operator is the only JavaScript operator that takes three operands: a condition
/// then an expression to execute `if` the condition is truthy followed by a colon (`:`), and finally the expression to execute if the condition is `falsy`. /// followed by a question mark (`?`), then an expression to execute `if` the condition is
/// This operator is frequently used as a shortcut for the `if` statement. /// truthy followed by a colon (`:`), and finally the expression to execute if the condition
/// is `false`. This operator is frequently used as a shortcut for the `if` statement.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -132,13 +145,15 @@ pub enum Node {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Literals /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Literals
Const(Const), Const(Const),
/// The `const` statements are block-scoped, much like variables defined using the `let` keyword. /// 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. /// This declaration creates a constant whose scope can be either global or local to the block
/// Global constants do not become properties of the window object, unlike var variables. /// 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. /// An initializer for a constant is required. You must specify its value in the same statement
/// (This makes sense, given that it can't be changed later.) /// in which it's declared. (This makes sense, given that it can't be changed later.)
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -150,11 +165,12 @@ pub enum Node {
/// [expression]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Expressions /// [expression]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Expressions
ConstDecl(Box<[(String, Node)]>), ConstDecl(Box<[(String, Node)]>),
/// The `continue` statement terminates execution of the statements in the current iteration of the current or labeled loop, /// The `continue` statement terminates execution of the statements in the current iteration of
/// and continues execution of the loop with the next iteration. /// the current or labeled loop, and continues execution of the loop with the next iteration.
/// ///
/// The continue statement can include an optional label that allows the program to jump to the next iteration of a labeled /// The continue statement can include an optional label that allows the program to jump to the
/// loop statement instead of the current loop. In this case, the continue statement needs to be nested within this labeled statement. /// next iteration of a labeled loop statement instead of the current loop. In this case, the
/// continue statement needs to be nested within this labeled statement.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -164,9 +180,11 @@ pub enum Node {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/continue /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/continue
Continue(Option<String>), Continue(Option<String>),
/// The `do...while` statement creates a loop that executes a specified statement until the test condition evaluates to false. /// The `do...while` statement creates a loop that executes a specified statement until the
/// test condition evaluates to false.
/// ///
/// The condition is evaluated after executing the statement, resulting in the specified statement executing at least once. /// The condition is evaluated after executing the statement, resulting in the specified
/// statement executing at least once.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -176,13 +194,16 @@ pub enum Node {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/do...while /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/do...while
DoWhileLoop(Box<Node>, Box<Node>), DoWhileLoop(Box<Node>, Box<Node>),
/// The `function` declaration (function statement) defines a function with the specified parameters. /// 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 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). /// A function can also be created using an expression (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. /// 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: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -190,20 +211,40 @@ pub enum Node {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-function /// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-function
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function
FunctionDecl(Option<String>, Box<[FormalParameter]>, Box<Node>), FunctionDecl(String, Box<[FormalParameter]>, Box<Node>),
/// This property accessor provides access to an object's properties by using the [dot notation][mdn]. /// 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
FunctionExpr(Option<String>, Box<[FormalParameter]>, Box<Node>),
/// This property accessor provides access to an object's properties by using the
/// [dot notation][mdn].
/// ///
/// In the object.property syntax, the property must be a valid JavaScript identifier. /// In the object.property syntax, the property must be a valid JavaScript identifier.
/// (In the ECMAScript standard, the names of properties are technically "IdentifierNames", not "Identifiers", /// (In the ECMAScript standard, the names of properties are technically "IdentifierNames", not
/// so reserved words can be used but are not recommended). /// "Identifiers", so reserved words can be used but are not recommended).
/// ///
/// One can think of an object as an associative array (a.k.a. map, dictionary, hash, lookup table). /// One can think of an object as an associative array (a.k.a. map, dictionary, hash, lookup
/// The keys in this array are the names of the object's properties. /// table). The keys in this array are the names of the object's properties.
/// ///
/// It's typical when speaking of an object's properties to make a distinction between properties and methods. However, /// It's typical when speaking of an object's properties to make a distinction between
/// the property/method distinction is little more than a convention. A method is simply a property that can be called (for example, /// properties and methods. However, the property/method distinction is little more than a
/// if it has a reference to a Function instance as its value). /// convention. A method is simply a property that can be called (for example, if it has a
/// reference to a Function instance as its value).
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -213,16 +254,20 @@ pub enum Node {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#Dot_notation /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#Dot_notation
GetConstField(Box<Node>, String), GetConstField(Box<Node>, String),
/// This property accessor provides access to an object's properties by using the [bracket notation][mdn]. /// This property accessor provides access to an object's properties by using the
/// [bracket notation][mdn].
/// ///
/// In the object[property_name] syntax, the property_name is just a string or [Symbol][symbol]. So, it can be any string, including '1foo', '!bar!', or even ' ' (a space). /// In the object[property_name] syntax, the property_name is just a string or
/// [Symbol][symbol]. So, it can be any string, including '1foo', '!bar!', or even ' ' (a
/// space).
/// ///
/// One can think of an object as an associative array (a.k.a. map, dictionary, hash, lookup table). /// One can think of an object as an associative array (a.k.a. map, dictionary, hash, lookup
/// The keys in this array are the names of the object's properties. /// table). The keys in this array are the names of the object's properties.
/// ///
/// It's typical when speaking of an object's properties to make a distinction between properties and methods. However, /// It's typical when speaking of an object's properties to make a distinction between
/// the property/method distinction is little more than a convention. A method is simply a property that can be called (for example, /// properties and methods. However, the property/method distinction is little more than a
/// if it has a reference to a Function instance as its value). /// convention. A method is simply a property that can be called (for example, if it has a
/// reference to a Function instance as its value).
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -251,7 +296,8 @@ pub enum Node {
Box<Node>, Box<Node>,
), ),
/// The `if` statement executes a statement if a specified condition is [`truthy`][truthy]. If the condition is [`falsy`][falsy], another statement can be executed. /// The `if` statement executes a statement if a specified condition is [`truthy`][truthy]. If
/// the condition is [`falsy`][falsy], another statement can be executed.
/// ///
/// Multiple `if...else` statements can be nested to create an else if clause. /// Multiple `if...else` statements can be nested to create an else if clause.
/// ///
@ -268,13 +314,16 @@ pub enum Node {
/// [expression]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Expressions /// [expression]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Expressions
If(Box<Node>, Box<Node>, Option<Box<Node>>), If(Box<Node>, Box<Node>, Option<Box<Node>>),
/// The `let` statement declares a block scope local variable, optionally initializing it to a value. /// 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 /// `let` allows you to declare variables that are limited to a scope of a block statement, or
/// it is used, unlike the `var` keyword, which defines a variable globally, or locally to an entire function regardless of block scope. /// 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). /// Just like const the `let` does not create properties of the window object when declared
/// globally (in the top-most scope).
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -284,12 +333,15 @@ pub enum Node {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
LetDecl(Box<[(String, Option<Node>)]>), LetDecl(Box<[(String, Option<Node>)]>),
/// An `identifier` is a sequence of characters in the code that identifies a variable, function, or property. /// 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. /// 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 /// An identifier differs from a string in that a string is data, while an identifier is part
/// to convert identifiers to strings, but sometimes it is possible to parse strings into identifiers. /// 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: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -299,7 +351,8 @@ pub enum Node {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/Identifier /// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/Identifier
Local(String), Local(String),
/// 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` 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: /// The new keyword does the following things:
/// - Creates a blank, plain JavaScript object; /// - Creates a blank, plain JavaScript object;
@ -315,13 +368,16 @@ pub enum Node {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new
New(Box<Node>), New(Box<Node>),
/// Objects in JavaScript may be defined as an unordered collection of related data, of primitive or reference types, in the form of “key: value” pairs. /// Objects in JavaScript may be defined as an unordered collection of related data, of
/// primitive or reference types, in the form of “key: value” pairs.
/// ///
/// Objects can be initialized using `new Object()`, `Object.create()`, or using the literal notation. /// Objects can be initialized using `new Object()`, `Object.create()`, or using the literal
/// notation.
/// ///
/// An object initializer is an expression that describes the initialization of an [`Object`][object]. /// An object initializer is an expression that describes the initialization of an
/// Objects consist of properties, which are used to describe an object. Values of object properties can either /// [`Object`][object]. Objects consist of properties, which are used to describe an object.
/// contain [`primitive`][primitive] data types or other objects. /// Values of object properties can either contain [`primitive`][primitive] data types or other
/// objects.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -333,15 +389,17 @@ pub enum Node {
/// [primitive]: https://developer.mozilla.org/en-US/docs/Glossary/primitive /// [primitive]: https://developer.mozilla.org/en-US/docs/Glossary/primitive
Object(Box<[PropertyDefinition]>), Object(Box<[PropertyDefinition]>),
/// The `return` statement ends function execution and specifies a value to be returned to the function caller. /// The `return` statement ends function execution and specifies a value to be returned to the
/// function caller.
/// ///
/// Syntax: `return [expression];` /// Syntax: `return [expression];`
/// ///
/// `expression`: /// `expression`:
/// > The expression whose value is to be returned. If omitted, `undefined` is returned instead. /// > The expression whose value is to be returned. If omitted, `undefined` is returned
/// > nstead.
/// ///
/// When a `return` statement is used in a function body, the execution of the function is stopped. /// When a `return` statement is used in a function body, the execution of the function is
/// If specified, a given value is returned to the function caller. /// stopped. If specified, a given value is returned to the function caller.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -351,13 +409,15 @@ pub enum Node {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/return /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/return
Return(Option<Box<Node>>), Return(Option<Box<Node>>),
/// The `switch` statement evaluates an expression, matching the expression's value to a case clause, /// The `switch` statement evaluates an expression, matching the expression's value to a case
/// and executes statements associated with that case, as well as statements in cases that follow the matching case. /// clause, and executes statements associated with that case, as well as statements in cases
/// that follow the matching case.
/// ///
/// A `switch` statement first evaluates its expression. It then looks for the first case clause whose expression evaluates /// A `switch` statement first evaluates its expression. It then looks for the first case
/// to the same value as the result of the input expression (using the strict comparison, `===`) and transfers control to that clause, /// clause whose expression evaluates to the same value as the result of the input expression
/// executing the associated statements. (If multiple cases match the provided value, the first case that matches is selected, even if /// (using the strict comparison, `===`) and transfers control to that clause, executing the
/// the cases are not equal to each other.) /// associated statements. (If multiple cases match the provided value, the first case that
/// matches is selected, even if the cases are not equal to each other.)
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -367,12 +427,15 @@ pub enum Node {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch
Switch(Box<Node>, Box<[(Node, Box<[Node]>)]>, Option<Box<Node>>), Switch(Box<Node>, Box<[(Node, Box<[Node]>)]>, Option<Box<Node>>),
/// The `spread` operator allows an iterable such as an array expression or string to be expanded. /// The `spread` operator allows an iterable such as an array expression or string to be
/// expanded.
/// ///
/// Syntax: `...x` /// Syntax: `...x`
/// ///
/// It expands array expressions or strings in places where zero or more arguments (for function calls) or elements (for array literals) /// It expands array expressions or strings in places where zero or more arguments (for
/// are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected. /// function calls) or elements (for array literals)
/// are expected, or an object expression to be expanded in places where zero or more key-value
/// pairs (for object literals) are expected.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -395,8 +458,8 @@ pub enum Node {
/// Syntax: `throw expression;` /// Syntax: `throw expression;`
/// ///
/// Execution of the current function will stop (the statements after throw won't be executed), /// Execution of the current function will stop (the statements after throw won't be executed),
/// and control will be passed to the first catch block in the call stack. If no catch block exists among /// and control will be passed to the first catch block in the call stack. If no catch block
/// caller functions, the program will terminate. /// exists among caller functions, the program will terminate.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -420,10 +483,12 @@ pub enum Node {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof
TypeOf(Box<Node>), TypeOf(Box<Node>),
/// The `try...catch` statement marks a block of statements to try and specifies a response should an exception be thrown. /// 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, /// The `try` statement consists of a `try`-block, which contains one or more statements. `{}`
/// even for single statements. At least one `catch`-block, or a `finally`-block, must be present. /// must always be used, even for single statements. At least one `catch`-block, or a
/// `finally`-block, must be present.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -464,13 +529,15 @@ pub enum Node {
/// The `var` statement declares a variable, optionally initializing it to a value. /// 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. /// 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, /// The scope of a variable declared with var is its current execution context, which is either
/// for variables declared outside any function, global. If you re-declare a JavaScript variable, it will not lose its value. /// 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 /// Assigning a value to an undeclared variable implicitly creates it as a global variable (it
/// (it becomes a property of the global object) when the assignment is executed. /// becomes a property of the global object) when the assignment is executed.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
@ -480,7 +547,8 @@ pub enum Node {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var
VarDecl(Box<[(String, Option<Node>)]>), VarDecl(Box<[(String, Option<Node>)]>),
/// The `while` statement creates a loop that executes a specified statement as long as the test condition evaluates to `true`. /// The `while` statement creates a loop that executes a specified statement as long as the
/// test condition evaluates to `true`.
/// ///
/// The condition is evaluated before executing the statement. /// The condition is evaluated before executing the statement.
/// ///
@ -636,14 +704,24 @@ impl Node {
} }
/// Creates a `FunctionDecl` AST node. /// Creates a `FunctionDecl` AST node.
pub fn function_decl<ON, N, P, B>(name: ON, params: P, body: B) -> Self pub fn function_decl<N, P, B>(name: N, params: P, body: B) -> Self
where
N: Into<String>,
P: Into<Box<[FormalParameter]>>,
B: Into<Box<Self>>,
{
Self::FunctionDecl(name.into(), params.into(), body.into())
}
/// Creates a `FunctionDecl` AST node.
pub fn function_expr<ON, N, P, B>(name: ON, params: P, body: B) -> Self
where where
N: Into<String>, N: Into<String>,
ON: Into<Option<N>>, ON: Into<Option<N>>,
P: Into<Box<[FormalParameter]>>, P: Into<Box<[FormalParameter]>>,
B: Into<Box<Self>>, B: Into<Box<Self>>,
{ {
Self::FunctionDecl(name.into().map(N::into), params.into(), body.into()) Self::FunctionExpr(name.into().map(N::into), params.into(), body.into())
} }
/// Creates a `GetConstField` AST node. /// Creates a `GetConstField` AST node.
@ -990,18 +1068,24 @@ impl Node {
f.write_str("]") f.write_str("]")
} }
Self::FunctionDecl(ref name, ref _args, ref node) => { Self::FunctionDecl(ref name, ref _args, ref node) => {
write!(f, "function {} {{", name)?;
//join_nodes(f, args)?; TODO: port
f.write_str("} ")?;
node.display(f, indentation + 1)
}
Self::FunctionExpr(ref name, ref args, ref node) => {
write!(f, "function ")?; write!(f, "function ")?;
if let Some(func_name) = name { if let Some(func_name) = name {
write!(f, "{}", func_name)?; write!(f, "{}", func_name)?;
} }
write!(f, "{{")?; write!(f, "{{")?;
//join_nodes(f, args)?; TODO: port join_nodes(f, args)?;
f.write_str("} ")?; f.write_str("} ")?;
node.display(f, indentation + 1) node.display(f, indentation + 1)
} }
Self::ArrowFunctionDecl(ref _args, ref node) => { Self::ArrowFunctionDecl(ref args, ref node) => {
write!(f, "(")?; write!(f, "(")?;
//join_nodes(f, args)?; TODO: port join_nodes(f, args)?;
f.write_str(") => ")?; f.write_str(") => ")?;
node.display(f, indentation) node.display(f, indentation)
} }
@ -1038,7 +1122,10 @@ impl Node {
} }
/// Utility to join multiple Nodes into a single string. /// Utility to join multiple Nodes into a single string.
fn join_nodes(f: &mut fmt::Formatter<'_>, nodes: &[Node]) -> fmt::Result { fn join_nodes<N>(f: &mut fmt::Formatter<'_>, nodes: &[N]) -> fmt::Result
where
N: fmt::Display,
{
let mut first = true; let mut first = true;
for e in nodes { for e in nodes {
if !first { if !first {
@ -1086,6 +1173,19 @@ impl FormalParameter {
} }
} }
impl fmt::Display for FormalParameter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_rest_param {
write!(f, "...")?;
}
write!(f, "{}", self.name)?;
if let Some(n) = self.init.as_ref() {
write!(f, " = {}", n)?;
}
Ok(())
}
}
/// A JavaScript property is a characteristic of an object, often describing attributes associated with a data structure. /// A JavaScript property is a characteristic of an object, often describing attributes associated with a data structure.
/// ///
/// A property has a name (a string) and a value (primitive, method, or object reference). /// A property has a name (a string) and a value (primitive, method, or object reference).

2
boa/src/syntax/parser/expression/primary/function_expression.rs

@ -55,6 +55,6 @@ impl TokenParser for FunctionExpression {
cursor.expect(Punctuator::CloseBlock, "function expression")?; cursor.expect(Punctuator::CloseBlock, "function expression")?;
Ok(Node::function_decl::<_, &String, _, _>(name, params, body)) Ok(Node::function_expr::<_, &String, _, _>(name, params, body))
} }
} }

2
boa/src/syntax/parser/expression/primary/object_initializer/mod.rs

@ -242,7 +242,7 @@ impl TokenParser for MethodDefinition {
Ok(node::PropertyDefinition::MethodDefinition( Ok(node::PropertyDefinition::MethodDefinition(
methodkind, methodkind,
prop_name, prop_name,
Node::function_decl::<_, String, _, _>(None, params, body), Node::function_expr::<_, String, _, _>(None, params, body),
)) ))
} }
} }

8
boa/src/syntax/parser/expression/primary/object_initializer/tests.rs

@ -32,7 +32,7 @@ fn check_object_short_function() {
PropertyDefinition::method_definition( PropertyDefinition::method_definition(
MethodDefinitionKind::Ordinary, MethodDefinitionKind::Ordinary,
"b", "b",
Node::function_decl::<_, String, _, _>(None, Vec::new(), Node::statement_list(vec![])), Node::function_expr::<_, String, _, _>(None, Vec::new(), Node::statement_list(vec![])),
), ),
]; ];
@ -57,7 +57,7 @@ fn check_object_short_function_arguments() {
PropertyDefinition::method_definition( PropertyDefinition::method_definition(
MethodDefinitionKind::Ordinary, MethodDefinitionKind::Ordinary,
"b", "b",
Node::FunctionDecl( Node::FunctionExpr(
None, None,
Box::new([FormalParameter::new("test", None, false)]), Box::new([FormalParameter::new("test", None, false)]),
Box::new(Node::StatementList(Box::new([]))), Box::new(Node::StatementList(Box::new([]))),
@ -85,7 +85,7 @@ fn check_object_getter() {
PropertyDefinition::method_definition( PropertyDefinition::method_definition(
MethodDefinitionKind::Get, MethodDefinitionKind::Get,
"b", "b",
Node::FunctionDecl( Node::FunctionExpr(
None, None,
Box::new([]), Box::new([]),
Box::new(Node::statement_list(Vec::new())), Box::new(Node::statement_list(Vec::new())),
@ -113,7 +113,7 @@ fn check_object_setter() {
PropertyDefinition::method_definition( PropertyDefinition::method_definition(
MethodDefinitionKind::Set, MethodDefinitionKind::Set,
"b", "b",
Node::function_decl::<_, String, _, _>( Node::function_expr::<_, String, _, _>(
None, None,
vec![FormalParameter::new("test", None, false)], vec![FormalParameter::new("test", None, false)],
Node::statement_list(Vec::new()), Node::statement_list(Vec::new()),

Loading…
Cancel
Save