From f02babf0bd254b37920206b0ce0089bb9b01ba50 Mon Sep 17 00:00:00 2001 From: Jason Williams <936006+jasonwilliams@users.noreply.github.com> Date: Sun, 3 May 2020 08:35:12 +0100 Subject: [PATCH] Refactor old function with new function object (#255) Co-authored-by: Iban Eguia Co-authored-by: Jason Williams Co-authored-by: Iban Eguia --- .vscode/tasks.json | 66 +-- boa/benches/exec.rs | 43 +- boa/src/builtins/array/mod.rs | 114 +++--- boa/src/builtins/boolean/mod.rs | 18 +- boa/src/builtins/console/mod.rs | 73 ++-- boa/src/builtins/error.rs | 11 +- boa/src/builtins/function/mod.rs | 384 ++++++++++++++---- boa/src/builtins/json/mod.rs | 5 +- boa/src/builtins/math/mod.rs | 63 ++- boa/src/builtins/mod.rs | 73 +++- boa/src/builtins/number/mod.rs | 33 +- .../builtins/object/internal_methods_trait.rs | 116 +++++- boa/src/builtins/object/mod.rs | 154 +++++-- boa/src/builtins/regexp/mod.rs | 69 ++-- boa/src/builtins/string/mod.rs | 87 ++-- boa/src/builtins/string/tests.rs | 3 +- boa/src/builtins/symbol/mod.rs | 32 +- boa/src/builtins/value/mod.rs | 191 +++------ .../function_environment_record.rs | 2 +- boa/src/environment/lexical_environment.rs | 3 +- boa/src/exec/mod.rs | 250 +++++------- boa/src/lib.rs | 2 +- boa/src/realm.rs | 12 +- boa/src/syntax/ast/constant.rs | 6 +- boa/src/syntax/ast/node.rs | 88 ++-- boa/src/syntax/lexer/tests.rs | 5 +- 26 files changed, 1082 insertions(+), 821 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 6ffc51f9f46..d35f8352665 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -7,17 +7,15 @@ "type": "process", "label": "Cargo Run", "command": "cargo", - "args": [ - "run", - "./tests/js/test.js" - ], - "problemMatcher": [ - "$rustc" - ], + "args": ["run", "./tests/js/test.js"], + "problemMatcher": ["$rustc"], "group": { "kind": "build", "isDefault": true }, + "options": { + "env": { "RUST_BACKTRACE": "full" } + }, "presentation": { "clear": true } @@ -26,19 +24,9 @@ "type": "process", "label": "Get Tokens", "command": "cargo", - "args": [ - "run", - "--", - "-t=Debug", - "./tests/js/test.js" - ], - "problemMatcher": [ - "$rustc" - ], - "group": { - "kind": "build", - "isDefault": true - }, + "args": ["run", "--", "-t=Debug", "./tests/js/test.js"], + "problemMatcher": ["$rustc"], + "group": "build", "presentation": { "clear": true } @@ -47,19 +35,9 @@ "type": "process", "label": "Get AST", "command": "cargo", - "args": [ - "run", - "--", - "-a=Debug", - "./tests/js/test.js" - ], - "problemMatcher": [ - "$rustc" - ], - "group": { - "kind": "build", - "isDefault": true - }, + "args": ["run", "--", "-a=Debug", "./tests/js/test.js"], + "problemMatcher": ["$rustc"], + "group": "build", "presentation": { "clear": true } @@ -68,12 +46,8 @@ "type": "process", "label": "Cargo Test", "command": "cargo", - "args": [ - "test" - ], - "problemMatcher": [ - "$rustc" - ], + "args": ["test"], + "problemMatcher": ["$rustc"], "group": { "kind": "test", "isDefault": true @@ -86,17 +60,9 @@ "type": "process", "label": "Cargo Test Build", "command": "cargo", - "args": [ - "test", - "--no-run" - ], - "problemMatcher": [ - "$rustc" - ], - "group": { - "kind": "build", - "isDefault": true - } + "args": ["test", "--no-run"], + "problemMatcher": ["$rustc"], + "group": "build" } ] } diff --git a/boa/benches/exec.rs b/boa/benches/exec.rs index d372ce3d93a..ff564aac0f9 100644 --- a/boa/benches/exec.rs +++ b/boa/benches/exec.rs @@ -26,29 +26,30 @@ fn symbol_creation(c: &mut Criterion) { }); } -static FOR_LOOP: &str = r#" -let a = 10; -let b = "hello"; -for (;;) { - a += 5; +// TODO: implement for loops. +// static FOR_LOOP: &str = r#" +// let a = 10; +// let b = "hello"; +// for (;;) { +// a += 5; - if a < 50 { - b += "world"; - } +// if (a < 50) { +// b += "world"; +// } - if (a > 100) { - break; - } -} -let c = a; -let d = b; -"#; +// if (a > 100) { +// break; +// } +// } +// let c = a; +// let d = b; +// "#; -fn for_loop_execution(c: &mut Criterion) { - c.bench_function("For loop (Execution)", move |b| { - b.iter(|| exec(black_box(FOR_LOOP))) - }); -} +// fn for_loop_execution(c: &mut Criterion) { +// c.bench_function("For loop (Execution)", move |b| { +// b.iter(|| exec(black_box(FOR_LOOP))) +// }); +// } static FIBONACCI: &str = r#" let num = 12; @@ -73,7 +74,7 @@ criterion_group!( execution, create_realm, symbol_creation, - for_loop_execution, + // for_loop_execution, fibonacci ); criterion_main!(execution); diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index eee445e76a5..c0faf3f3546 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -14,8 +14,7 @@ mod tests; use crate::{ builtins::{ - function::NativeFunctionData, - object::{Object, ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE}, + object::{Object, ObjectInternalMethods, ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE}, property::Property, value::{from_value, to_value, undefined, ResultValue, Value, ValueData}, }, @@ -24,6 +23,7 @@ use crate::{ use gc::Gc; use std::borrow::Borrow; use std::cmp::{max, min}; +use std::ops::Deref; /// Creates a new `Array` instance. pub(crate) fn new_array(interpreter: &Interpreter) -> ResultValue { @@ -97,7 +97,7 @@ pub(crate) fn add_to_array_object(array_ptr: &Value, add_values: &[Value]) -> Re } /// Create a new array -pub fn make_array(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn make_array(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // Make a new Object which will internally represent the Array (mapping // between indices and values): this creates an Object with no prototype @@ -157,7 +157,7 @@ pub fn make_array(this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result /// /// [spec]: https://tc39.es/ecma262/#sec-array.isarray /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray -pub fn is_array(_this: &Value, args: &[Value], _interpreter: &mut Interpreter) -> ResultValue { +pub fn is_array(_this: &mut Value, args: &[Value], _interpreter: &mut Interpreter) -> ResultValue { let value_true = Gc::new(ValueData::Boolean(true)); let value_false = Gc::new(ValueData::Boolean(false)); @@ -167,7 +167,7 @@ pub fn is_array(_this: &Value, args: &[Value], _interpreter: &mut Interpreter) - // 1. ValueData::Object(ref obj) => { // 2. - if obj.borrow().kind == ObjectKind::Array { + if (*obj).deref().borrow().kind == ObjectKind::Array { return Ok(value_true); } Ok(value_false) @@ -192,7 +192,7 @@ pub fn is_array(_this: &Value, args: &[Value], _interpreter: &mut Interpreter) - /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.concat /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat -pub fn concat(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn concat(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { if args.is_empty() { // If concat is called with no arguments, it returns the original array return Ok(this.clone()); @@ -231,7 +231,7 @@ pub fn concat(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.push /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push -pub fn push(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn push(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let new_array = add_to_array_object(this, args)?; Ok(new_array.get_field_slice("length")) } @@ -246,7 +246,7 @@ pub fn push(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.pop /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop -pub fn pop(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn pop(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { let curr_length: i32 = from_value(this.get_field_slice("length")).expect("Could not convert argument to i32"); if curr_length < 1 { @@ -269,7 +269,7 @@ pub fn pop(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.foreach /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach -pub fn for_each(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { +pub fn for_each(this: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { if args.is_empty() { return Err(to_value( "Missing argument for Array.prototype.forEach".to_string(), @@ -277,16 +277,16 @@ pub fn for_each(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> } let callback_arg = args.get(0).expect("Could not get `callbackFn` argument."); - let this_arg = args.get(1).cloned().unwrap_or_else(undefined); + let mut this_arg = args.get(1).cloned().unwrap_or_else(undefined); let length: i32 = from_value(this.get_field_slice("length")).expect("Could not get `length` property."); for i in 0..length { let element = this.get_field_slice(&i.to_string()); - let arguments = vec![element.clone(), to_value(i), this.clone()]; + let arguments = [element, to_value(i), this.clone()]; - interpreter.call(callback_arg, &this_arg, arguments)?; + interpreter.call(callback_arg, &mut this_arg, &arguments)?; } Ok(Gc::new(ValueData::Undefined)) @@ -304,7 +304,7 @@ pub fn for_each(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.join /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join -pub fn join(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn join(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let separator = if args.is_empty() { String::from(",") } else { @@ -334,7 +334,7 @@ pub fn join(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.tostring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toString -pub fn to_string(this: &Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue { +pub fn to_string(this: &mut Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue { let method_name = "join"; let mut arguments = vec![to_value(",")]; // 2. @@ -350,10 +350,10 @@ pub fn to_string(this: &Value, _args: &[Value], _ctx: &mut Interpreter) -> Resul .get_field_slice("toString"); method = from_value(method).expect("failed to get Object.prototype.toString"); - arguments = vec![]; + arguments = Vec::new(); } // 4. - let join_result = _ctx.call(&method, this, arguments); + let join_result = _ctx.call(&method, this, &arguments); let match_string = match join_result { Ok(v) => match *v { ValueData::String(ref s) => (*s).clone(), @@ -376,7 +376,7 @@ pub fn to_string(this: &Value, _args: &[Value], _ctx: &mut Interpreter) -> Resul /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.reverse /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse #[allow(clippy::else_if_without_else)] -pub fn reverse(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn reverse(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { let len: i32 = from_value(this.get_field_slice("length")).expect("Could not convert argument to i32"); let middle: i32 = len.wrapping_div(2); @@ -415,7 +415,7 @@ pub fn reverse(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.shift /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift -pub fn shift(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn shift(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { let len: i32 = from_value(this.get_field_slice("length")).expect("Could not convert argument to i32"); @@ -458,7 +458,7 @@ pub fn shift(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.unshift /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift -pub fn unshift(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn unshift(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let len: i32 = from_value(this.get_field_slice("length")).expect("Could not convert argument to i32"); let arg_c: i32 = args.len() as i32; @@ -503,14 +503,14 @@ pub fn unshift(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.every /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every -pub fn every(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { +pub fn every(this: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { if args.is_empty() { return Err(to_value( "missing callback when calling function Array.prototype.every".to_string(), )); } let callback = &args[0]; - let this_arg = if args.len() > 1 { + let mut this_arg = if args.len() > 1 { args[1].clone() } else { Gc::new(ValueData::Undefined) @@ -520,8 +520,10 @@ pub fn every(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Res let mut len = max_len; while i < len { let element = this.get_field_slice(&i.to_string()); - let arguments = vec![element.clone(), to_value(i), this.clone()]; - let result = interpreter.call(callback, &this_arg, arguments)?.is_true(); + let arguments = [element, to_value(i), this.clone()]; + let result = interpreter + .call(callback, &mut this_arg, &arguments)? + .is_true(); if !result { return Ok(to_value(false)); } @@ -542,7 +544,7 @@ pub fn every(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Res /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.map /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map -pub fn map(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { +pub fn map(this: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { if args.is_empty() { return Err(to_value( "missing argument 0 when calling function Array.prototype.map", @@ -550,7 +552,7 @@ pub fn map(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Resul } let callback = args.get(0).cloned().unwrap_or_else(undefined); - let this_val = args.get(1).cloned().unwrap_or_else(undefined); + let mut this_val = args.get(1).cloned().unwrap_or_else(undefined); let length: i32 = from_value(this.get_field_slice("length")).expect("Could not get `length` property."); @@ -560,11 +562,10 @@ pub fn map(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Resul let values = (0..length) .map(|idx| { let element = this.get_field_slice(&idx.to_string()); - - let args = vec![element, to_value(idx), new.clone()]; + let args = [element, to_value(idx), new.clone()]; interpreter - .call(&callback, &this_val, args) + .call(&callback, &mut this_val, &args) .unwrap_or_else(|_| undefined()) }) .collect::>(); @@ -591,7 +592,7 @@ pub fn map(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Resul /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.indexof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf -pub fn index_of(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn index_of(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { // If no arguments, return -1. Not described in spec, but is what chrome does. if args.is_empty() { return Ok(to_value(-1)); @@ -646,7 +647,7 @@ pub fn index_of(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValu /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.lastindexof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf -pub fn last_index_of(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn last_index_of(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { // If no arguments, return -1. Not described in spec, but is what chrome does. if args.is_empty() { return Ok(to_value(-1)); @@ -695,14 +696,14 @@ pub fn last_index_of(this: &Value, args: &[Value], _: &mut Interpreter) -> Resul /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.find /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find -pub fn find(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { +pub fn find(this: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { if args.is_empty() { return Err(to_value( "missing callback when calling function Array.prototype.find".to_string(), )); } let callback = &args[0]; - let this_arg = if args.len() > 1 { + let mut this_arg = if args.len() > 1 { args[1].clone() } else { Gc::new(ValueData::Undefined) @@ -710,8 +711,8 @@ pub fn find(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Resu let len: i32 = from_value(this.get_field_slice("length")).unwrap(); for i in 0..len { let element = this.get_field_slice(&i.to_string()); - let arguments = vec![element.clone(), to_value(i), this.clone()]; - let result = interpreter.call(callback, &this_arg, arguments)?; + let arguments = [element.clone(), to_value(i), this.clone()]; + let result = interpreter.call(callback, &mut this_arg, &arguments)?; if result.is_true() { return Ok(element); } @@ -731,7 +732,7 @@ pub fn find(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Resu /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.findindex /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex -pub fn find_index(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { +pub fn find_index(this: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { if args.is_empty() { return Err(to_value( "Missing argument for Array.prototype.findIndex".to_string(), @@ -740,7 +741,7 @@ pub fn find_index(this: &Value, args: &[Value], interpreter: &mut Interpreter) - let predicate_arg = args.get(0).expect("Could not get `predicate` argument."); - let this_arg = args + let mut this_arg = args .get(1) .cloned() .unwrap_or_else(|| Gc::new(ValueData::Undefined)); @@ -750,9 +751,9 @@ pub fn find_index(this: &Value, args: &[Value], interpreter: &mut Interpreter) - for i in 0..length { let element = this.get_field_slice(&i.to_string()); - let arguments = vec![element.clone(), to_value(i), this.clone()]; + let arguments = [element, to_value(i), this.clone()]; - let result = interpreter.call(predicate_arg, &this_arg, arguments)?; + let result = interpreter.call(predicate_arg, &mut this_arg, &arguments)?; if result.is_true() { return Ok(Gc::new(ValueData::Rational(f64::from(i)))); @@ -773,7 +774,7 @@ pub fn find_index(this: &Value, args: &[Value], interpreter: &mut Interpreter) - /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.fill /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill -pub fn fill(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn fill(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let len: i32 = from_value(this.get_field_slice("length")).expect("Could not get argument"); let default_value = undefined(); let value = args.get(0).unwrap_or(&default_value); @@ -812,7 +813,7 @@ pub fn fill(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.includes /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes -pub fn includes_value(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn includes_value(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let search_element = args .get(0) .cloned() @@ -846,7 +847,7 @@ pub fn includes_value(this: &Value, args: &[Value], _: &mut Interpreter) -> Resu /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.slice /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice -pub fn slice(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { +pub fn slice(this: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { let new_array = new_array(interpreter)?; let len: i32 = from_value(this.get_field_slice("length")).expect("Could not convert argument to i32"); @@ -895,7 +896,7 @@ pub fn slice(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Res /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.filter /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter -pub fn filter(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { +pub fn filter(this: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { if args.is_empty() { return Err(to_value( "missing argument 0 when calling function Array.prototype.filter", @@ -903,7 +904,7 @@ pub fn filter(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Re } let callback = args.get(0).cloned().unwrap_or_else(undefined); - let this_val = args.get(1).cloned().unwrap_or_else(undefined); + let mut this_val = args.get(1).cloned().unwrap_or_else(undefined); let length: i32 = from_value(this.get_field_slice("length")).expect("Could not get `length` property."); @@ -914,10 +915,10 @@ pub fn filter(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Re .filter_map(|idx| { let element = this.get_field_slice(&idx.to_string()); - let args = vec![element.clone(), to_value(idx), new.clone()]; + let args = [element.clone(), to_value(idx), new.clone()]; let callback_result = interpreter - .call(&callback, &this_val, args) + .call(&callback, &mut this_val, &args) .unwrap_or_else(|_| undefined()); if callback_result.is_true() { @@ -946,14 +947,14 @@ pub fn filter(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Re /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.some /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some -pub fn some(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { +pub fn some(this: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { if args.is_empty() { return Err(to_value( "missing callback when calling function Array.prototype.some".to_string(), )); } let callback = &args[0]; - let this_arg = if args.len() > 1 { + let mut this_arg = if args.len() > 1 { args[1].clone() } else { Gc::new(ValueData::Undefined) @@ -963,8 +964,10 @@ pub fn some(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Resu let mut len = max_len; while i < len { let element = this.get_field_slice(&i.to_string()); - let arguments = vec![element.clone(), to_value(i), this.clone()]; - let result = interpreter.call(callback, &this_arg, arguments)?.is_true(); + let arguments = [element, to_value(i), this.clone()]; + let result = interpreter + .call(callback, &mut this_arg, &arguments)? + .is_true(); if result { return Ok(to_value(true)); } @@ -977,14 +980,6 @@ pub fn some(this: &Value, args: &[Value], interpreter: &mut Interpreter) -> Resu /// Create a new `Array` object. pub fn create_constructor(global: &Value) -> Value { - // Create Constructor - let object_prototype = global.get_field_slice("Object").get_field_slice(PROTOTYPE); - let mut array_constructor = Object::create(object_prototype); - array_constructor.kind = ObjectKind::Function; - array_constructor.set_internal_method("construct", make_array); - // Todo: add call function - array_constructor.set_internal_method("call", make_array); - // Create prototype let array_prototype = ValueData::new_obj(None); let length = Property::default().value(to_value(0_i32)); @@ -1011,10 +1006,9 @@ pub fn create_constructor(global: &Value) -> Value { make_builtin_fn!(slice, named "slice", with length 2, of array_prototype); make_builtin_fn!(some, named "some", with length 2, of array_prototype); - let array = to_value(array_constructor); + let array = make_constructor_fn!(make_array, make_array, global, array_prototype); + // Static Methods make_builtin_fn!(is_array, named "isArray", with length 1, of array); - array.set_field_slice(PROTOTYPE, to_value(array_prototype.clone())); - array_prototype.set_field_slice("constructor", array.clone()); array } diff --git a/boa/src/builtins/boolean/mod.rs b/boa/src/builtins/boolean/mod.rs index 3b0e7387819..83b017c8c6d 100644 --- a/boa/src/builtins/boolean/mod.rs +++ b/boa/src/builtins/boolean/mod.rs @@ -14,7 +14,6 @@ mod tests; use crate::{ builtins::{ - function::NativeFunctionData, object::{internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, PROTOTYPE}, value::{to_value, ResultValue, Value, ValueData}, }, @@ -23,7 +22,7 @@ use crate::{ use std::{borrow::Borrow, ops::Deref}; /// Create a new boolean object - [[Construct]] -pub fn construct_boolean(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn construct_boolean(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { this.set_kind(ObjectKind::Boolean); // Get the argument, if any @@ -38,7 +37,7 @@ pub fn construct_boolean(this: &Value, args: &[Value], _: &mut Interpreter) -> R } /// Return a boolean literal [[Call]] -pub fn call_boolean(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn call_boolean(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { // Get the argument, if any match args.get(0) { Some(ref value) => Ok(to_boolean(value)), @@ -54,7 +53,7 @@ pub fn call_boolean(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultVal /// /// [spec]: https://tc39.es/ecma262/#sec-boolean-object /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/toString -pub fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { let b = this_boolean_value(this); Ok(to_value(b.to_string())) } @@ -67,16 +66,12 @@ pub fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue /// /// [spec]: https://tc39.es/ecma262/#sec-boolean.prototype.valueof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/valueOf -pub fn value_of(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn value_of(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { Ok(this_boolean_value(this)) } /// Create a new `Boolean` object pub fn create_constructor(global: &Value) -> Value { - let mut boolean = Object::default(); - boolean.kind = ObjectKind::Function; - boolean.set_internal_method("construct", construct_boolean); - boolean.set_internal_method("call", call_boolean); // Create Prototype // https://tc39.es/ecma262/#sec-properties-of-the-boolean-prototype-object let boolean_prototype = ValueData::new_obj(Some(global)); @@ -84,10 +79,7 @@ pub fn create_constructor(global: &Value) -> Value { make_builtin_fn!(to_string, named "toString", of boolean_prototype); make_builtin_fn!(value_of, named "valueOf", of boolean_prototype); - let boolean_value = to_value(boolean); - boolean_prototype.set_field_slice("constructor", to_value(boolean_value.clone())); - boolean_value.set_field_slice(PROTOTYPE, boolean_prototype); - boolean_value + make_constructor_fn!(construct_boolean, call_boolean, global, boolean_prototype) } // === Utility Functions === diff --git a/boa/src/builtins/console/mod.rs b/boa/src/builtins/console/mod.rs index 92d10d538cb..7fd4d52abbb 100644 --- a/boa/src/builtins/console/mod.rs +++ b/boa/src/builtins/console/mod.rs @@ -18,7 +18,6 @@ mod tests; use crate::{ builtins::{ - function::NativeFunctionData, object::InternalState, value::{display_obj, from_value, to_value, FromValue, ResultValue, Value, ValueData}, }, @@ -37,7 +36,7 @@ pub struct ConsoleState { impl ConsoleState { fn new() -> Self { - ConsoleState { + Self { count_map: HashMap::new(), timer_map: HashMap::new(), groups: vec![], @@ -149,7 +148,7 @@ pub fn formatter(data: &[Value]) -> String { /// /// [spec]: https://console.spec.whatwg.org/#assert /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/assert -pub fn assert(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn assert(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let assertion = get_arg_at_index::(args, 0).unwrap_or_default(); if !assertion { @@ -182,7 +181,7 @@ pub fn assert(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue /// /// [spec]: https://console.spec.whatwg.org/#clear /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/clear -pub fn clear(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn clear(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { this.with_internal_state_mut(|state: &mut ConsoleState| { state.groups.clear(); }); @@ -200,7 +199,7 @@ pub fn clear(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://console.spec.whatwg.org/#debug /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/debug -pub fn debug(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn debug(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { this.with_internal_state_ref(|state| logger(LogMessage::Log(formatter(&args[..])), state)); Ok(Gc::new(ValueData::Undefined)) } @@ -215,7 +214,7 @@ pub fn debug(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://console.spec.whatwg.org/#error /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/error -pub fn error(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { this.with_internal_state_ref(|state| logger(LogMessage::Error(formatter(&args[..])), state)); Ok(Gc::new(ValueData::Undefined)) } @@ -230,7 +229,7 @@ pub fn error(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://console.spec.whatwg.org/#info /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/info -pub fn info(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn info(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { this.with_internal_state_ref(|state| logger(LogMessage::Info(formatter(&args[..])), state)); Ok(Gc::new(ValueData::Undefined)) } @@ -245,7 +244,7 @@ pub fn info(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://console.spec.whatwg.org/#log /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/log -pub fn log(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn log(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { this.with_internal_state_ref(|state| logger(LogMessage::Log(formatter(&args[..])), state)); Ok(Gc::new(ValueData::Undefined)) } @@ -260,7 +259,7 @@ pub fn log(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://console.spec.whatwg.org/#trace /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/trace -pub fn trace(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn trace(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { if !args.is_empty() { this.with_internal_state_ref(|state| logger(LogMessage::Log(formatter(&args[..])), state)); @@ -286,7 +285,7 @@ pub fn trace(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://console.spec.whatwg.org/#warn /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/warn -pub fn warn(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn warn(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { this.with_internal_state_ref(|state| logger(LogMessage::Warn(formatter(&args[..])), state)); Ok(Gc::new(ValueData::Undefined)) } @@ -301,7 +300,7 @@ pub fn warn(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://console.spec.whatwg.org/#count /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/count -pub fn count(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn count(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let label = get_arg_at_index::(args, 0).unwrap_or_else(|| "default".to_string()); this.with_internal_state_mut(|state: &mut ConsoleState| { @@ -325,7 +324,7 @@ pub fn count(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://console.spec.whatwg.org/#countreset /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/countReset -pub fn count_reset(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn count_reset(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let label = get_arg_at_index::(args, 0).unwrap_or_else(|| "default".to_string()); this.with_internal_state_mut(|state: &mut ConsoleState| { @@ -355,7 +354,7 @@ fn system_time_in_ms() -> u128 { /// /// [spec]: https://console.spec.whatwg.org/#time /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/time -pub fn time(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn time(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let label = get_arg_at_index::(args, 0).unwrap_or_else(|| "default".to_string()); this.with_internal_state_mut(|state: &mut ConsoleState| { @@ -383,7 +382,7 @@ pub fn time(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://console.spec.whatwg.org/#timelog /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/timeLog -pub fn time_log(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn time_log(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let label = get_arg_at_index::(args, 0).unwrap_or_else(|| "default".to_string()); this.with_internal_state_mut(|state: &mut ConsoleState| { @@ -415,7 +414,7 @@ pub fn time_log(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValu /// /// [spec]: https://console.spec.whatwg.org/#timeend /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/timeEnd -pub fn time_end(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn time_end(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let label = get_arg_at_index::(args, 0).unwrap_or_else(|| "default".to_string()); this.with_internal_state_mut(|state: &mut ConsoleState| { @@ -446,7 +445,7 @@ pub fn time_end(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValu /// /// [spec]: https://console.spec.whatwg.org/#group /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/group -pub fn group(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn group(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let group_label = formatter(args); this.with_internal_state_mut(|state: &mut ConsoleState| { @@ -467,7 +466,7 @@ pub fn group(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://console.spec.whatwg.org/#groupend /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/groupEnd -pub fn group_end(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn group_end(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { this.with_internal_state_mut(|state: &mut ConsoleState| { state.groups.pop(); }); @@ -485,7 +484,7 @@ pub fn group_end(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue /// /// [spec]: https://console.spec.whatwg.org/#dir /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/dir -pub fn dir(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn dir(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { this.with_internal_state_mut(|state: &mut ConsoleState| { logger( LogMessage::Info(display_obj( @@ -502,25 +501,25 @@ pub fn dir(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// Create a new `console` object pub fn create_constructor(global: &Value) -> Value { let console = ValueData::new_obj(Some(global)); - console.set_field_slice("assert", to_value(assert as NativeFunctionData)); - console.set_field_slice("clear", to_value(clear as NativeFunctionData)); - console.set_field_slice("debug", to_value(debug as NativeFunctionData)); - console.set_field_slice("error", to_value(error as NativeFunctionData)); - console.set_field_slice("info", to_value(info as NativeFunctionData)); - console.set_field_slice("log", to_value(log as NativeFunctionData)); - console.set_field_slice("trace", to_value(trace as NativeFunctionData)); - console.set_field_slice("warn", to_value(warn as NativeFunctionData)); - console.set_field_slice("exception", to_value(error as NativeFunctionData)); - console.set_field_slice("count", to_value(count as NativeFunctionData)); - console.set_field_slice("countReset", to_value(count_reset as NativeFunctionData)); - console.set_field_slice("group", to_value(group as NativeFunctionData)); - console.set_field_slice("groupCollapsed", to_value(group as NativeFunctionData)); - console.set_field_slice("groupEnd", to_value(group_end as NativeFunctionData)); - console.set_field_slice("time", to_value(time as NativeFunctionData)); - console.set_field_slice("timeLog", to_value(time_log as NativeFunctionData)); - console.set_field_slice("timeEnd", to_value(time_end as NativeFunctionData)); - console.set_field_slice("dir", to_value(dir as NativeFunctionData)); - console.set_field_slice("dirxml", to_value(dir as NativeFunctionData)); + make_builtin_fn!(assert, named "assert", of console); + make_builtin_fn!(clear, named "clear", of console); + make_builtin_fn!(debug, named "debug", of console); + make_builtin_fn!(error, named "error", of console); + make_builtin_fn!(info, named "info", of console); + make_builtin_fn!(log, named "log", of console); + make_builtin_fn!(trace, named "trace", of console); + make_builtin_fn!(warn, named "warn", of console); + make_builtin_fn!(error, named "exception", of console); + make_builtin_fn!(count, named "count", of console); + make_builtin_fn!(count_reset, named "countReset", of console); + make_builtin_fn!(group, named "group", of console); + make_builtin_fn!(group, named "groupCollapsed", of console); + make_builtin_fn!(group_end , named "groupEnd", of console); + make_builtin_fn!(time, named "time", of console); + make_builtin_fn!(time_log, named "timeLog", of console); + make_builtin_fn!(time_end, named "timeEnd", of console); + make_builtin_fn!(dir, named "dir", of console); + make_builtin_fn!(dir, named "dirxml", of console); console.set_internal_state(ConsoleState::new()); console } diff --git a/boa/src/builtins/error.rs b/boa/src/builtins/error.rs index c2b987eb33b..690993a9489 100644 --- a/boa/src/builtins/error.rs +++ b/boa/src/builtins/error.rs @@ -12,8 +12,7 @@ use crate::{ builtins::{ - function::NativeFunctionData, - object::{ObjectKind, PROTOTYPE}, + object::{internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, PROTOTYPE}, value::{to_value, ResultValue, Value, ValueData}, }, exec::Interpreter, @@ -21,7 +20,7 @@ use crate::{ use gc::Gc; /// Create a new error object. -pub fn make_error(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { if !args.is_empty() { this.set_field_slice( "message", @@ -48,7 +47,7 @@ pub fn make_error(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultVa /// /// [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: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { +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(to_value(format!("{}: {}", name, message))) @@ -60,9 +59,7 @@ pub fn _create(global: &Value) -> Value { prototype.set_field_slice("message", to_value("")); prototype.set_field_slice("name", to_value("Error")); make_builtin_fn!(to_string, named "toString", of prototype); - let error = to_value(make_error as NativeFunctionData); - error.set_field_slice(PROTOTYPE, prototype); - error + make_constructor_fn!(make_error, global, prototype) } /// Initialise the global object with the `Error` object. diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index b06fd03dbf0..0c1b2a71f72 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -1,134 +1,327 @@ -//! This module implements the global `Function` object. +//! This module implements the global `Function` object as well as creates Native Functions. //! -//! `Every JavaScript `function` is actually a `Function` object. +//! Objects wrap `Function`s and expose them via call/construct slots. +//! +//! `The `Function` object is used for matching text with a pattern. //! //! More information: //! - [ECMAScript reference][spec] //! - [MDN documentation][mdn] //! -//! [spec]: https://tc39.es/ecma262/#sec-function-object +//! [spec]: https://tc39.es/ecma262/#sec-function-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function -#[cfg(test)] -mod tests; - use crate::{ builtins::{ - object::{internal_methods_trait::ObjectInternalMethods, Object}, + array, + object::{Object, ObjectInternalMethods, ObjectKind, PROTOTYPE}, property::Property, value::{to_value, ResultValue, Value, ValueData}, }, - exec::Interpreter, + environment::lexical_environment::{new_function_environment, Environment}, + exec::Executor, syntax::ast::node::{FormalParameter, Node}, + Interpreter, }; -use gc::{custom_trace, Gc}; +use gc::{unsafe_empty_trace, Gc, Trace}; use gc_derive::{Finalize, Trace}; use std::fmt::{self, Debug}; -use std::ops::Deref; -/// fn(this, arguments, ctx) -pub type NativeFunctionData = fn(&Value, &[Value], &mut Interpreter) -> ResultValue; +/// _fn(this, arguments, ctx) -> ResultValue_ - The signature of a built-in function +pub type NativeFunctionData = fn(&mut Value, &[Value], &mut Interpreter) -> ResultValue; -/// A Javascript `Function` object instance. -/// -/// A member of the Object type that may be invoked as a subroutine +/// Sets the ConstructorKind +#[derive(Debug, Copy, Clone)] +pub enum ConstructorKind { + Base, + Derived, +} + +/// Defines how this references are interpreted within the formal parameters and code body of the function. /// -/// In our implementation, Function is extending Object by holding an object field which some extra data +/// Arrow functions don't define a `this` and thus are lexical, `function`s do define a this and thus are NonLexical #[derive(Trace, Finalize, Debug, Clone)] -pub enum Function { - /// A native javascript function - NativeFunc(NativeFunction), - /// A regular javascript function - RegularFunc(RegularFunction), +pub enum ThisMode { + Lexical, + NonLexical, } -/// Represents a regular javascript function in memory. -#[derive(Trace, Finalize, Debug, Clone)] -pub struct RegularFunction { - /// The fields associated with the function - pub object: Object, - /// This function's expression - pub node: Node, - /// The argument declarations of the function - pub args: Vec, +/// FunctionBody is specific to this interpreter, it will either be Rust code or JavaScript code (AST Node) +#[derive(Clone, Finalize)] +pub enum FunctionBody { + BuiltIn(NativeFunctionData), + Ordinary(Node), } -impl RegularFunction { - /// Make a new regular function - #[allow(clippy::cast_possible_wrap)] - pub fn new(node: Node, f_args: Vec) -> Self { - let mut args = vec![]; - for i in f_args { - let node = if let Some(init) = &i.init { - init.deref().clone() - } else { - Node::Local(i.name.clone()) - }; - args.push(node); +impl Debug for FunctionBody { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::BuiltIn(_) => write!(f, "native code"), + Self::Ordinary(node) => write!(f, "{}", node), } - - let mut object = Object::default(); - object.properties.insert( - "arguments".to_string(), - Property::default().value(Gc::new(ValueData::Integer(args.len() as i32))), - ); - Self { object, node, args } } } -/// Represents a native javascript function in memory -#[derive(Finalize, Clone)] -pub struct NativeFunction { - /// The fields associated with the function - pub object: Object, - /// The callable function data - pub data: NativeFunctionData, +/// `Trace` implementation for `FunctionBody`. +/// +/// This is indeed safe, but we need to mark this as an empty trace because neither +// `NativeFunctionData` nor Node hold any GC'd objects, but Gc doesn't know that. So we need to +/// signal it manually. `rust-gc` does not have a `Trace` implementation for `fn(_, _, _)`. +/// +/// +unsafe impl Trace for FunctionBody { + unsafe_empty_trace!(); +} + +/// Signal what sort of function this is +#[derive(Clone, Debug, Copy, Finalize)] +pub enum FunctionKind { + BuiltIn, + Ordinary, } -impl NativeFunction { - /// Make a new native function with the given function data - pub fn new(data: NativeFunctionData) -> Self { - let object = Object::default(); - Self { object, data } +/// Waiting on until we can derive Copy +unsafe impl Trace for FunctionKind { + unsafe_empty_trace!(); +} + +/// Boa representation of a Function Object. +/// +/// +#[derive(Trace, Finalize, Clone)] +pub struct Function { + /// Call/Construct Function body + pub body: FunctionBody, + /// Formal Paramaters + pub params: Vec, + /// This Mode + pub this_mode: ThisMode, + /// Function kind + pub kind: FunctionKind, + // Environment, built-in functions don't need Environments + pub environment: Option, +} + +impl Function { + /// This will create an ordinary function object + /// + /// + pub fn create_ordinary( + parameter_list: Vec, + scope: Environment, + body: FunctionBody, + this_mode: ThisMode, + ) -> Self { + Self { + body, + environment: Some(scope), + params: parameter_list, + kind: FunctionKind::Ordinary, + this_mode, + } + } + + /// This will create a built-in function object + /// + /// + pub fn create_builtin(parameter_list: Vec, body: FunctionBody) -> Self { + Self { + body, + params: parameter_list, + this_mode: ThisMode::NonLexical, + kind: FunctionKind::BuiltIn, + environment: None, + } + } + + /// This will handle calls for both ordinary and built-in functions + /// + /// + /// + pub fn call( + &self, + this: &mut Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object) + args_list: &[Value], + interpreter: &mut Interpreter, + this_obj: &mut Value, + ) -> ResultValue { + match self.kind { + FunctionKind::BuiltIn => match &self.body { + FunctionBody::BuiltIn(func) => func(this_obj, args_list, interpreter), + FunctionBody::Ordinary(_) => { + panic!("Builtin function should not have Ordinary Function body") + } + }, + FunctionKind::Ordinary => { + // Create a new Function environment who's parent is set to the scope of the function declaration (self.environment) + // + let local_env = new_function_environment( + this.clone(), + this_obj.clone(), + Some(self.environment.as_ref().unwrap().clone()), + ); + + // Add argument bindings to the function environment + for i in 0..self.params.len() { + let param = self.params.get(i).expect("Could not get param"); + // Rest Parameters + if param.is_rest_param { + self.add_rest_param(param, i, args_list, interpreter, &local_env); + break; + } + + let value = args_list.get(i).expect("Could not get value"); + self.add_arguments_to_environment(param, value.clone(), &local_env); + } + + // Add arguments object + let arguments_obj = create_unmapped_arguments_object(args_list); + local_env + .borrow_mut() + .create_mutable_binding("arguments".to_string(), false); + local_env + .borrow_mut() + .initialize_binding("arguments", arguments_obj); + + interpreter.realm.environment.push(local_env); + + // Call body should be set before reaching here + let result = match &self.body { + FunctionBody::Ordinary(ref body) => interpreter.run(body), + _ => panic!("Ordinary function should not have BuiltIn Function body"), + }; + + // local_env gets dropped here, its no longer needed + interpreter.realm.environment.pop(); + result + } + } + } + + pub fn construct( + &self, + this: &mut Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object) + args_list: &[Value], + interpreter: &mut Interpreter, + this_obj: &mut Value, + ) -> ResultValue { + match self.kind { + FunctionKind::BuiltIn => match &self.body { + FunctionBody::BuiltIn(func) => func(this_obj, args_list, interpreter), + FunctionBody::Ordinary(_) => { + panic!("Builtin function should not have Ordinary Function body") + } + }, + FunctionKind::Ordinary => { + // Create a new Function environment who's parent is set to the scope of the function declaration (self.environment) + // + let local_env = new_function_environment( + this.clone(), + this_obj.clone(), + Some(self.environment.as_ref().unwrap().clone()), + ); + + // Add argument bindings to the function environment + for (i, param) in self.params.iter().enumerate() { + // Rest Parameters + if param.is_rest_param { + self.add_rest_param(param, i, args_list, interpreter, &local_env); + break; + } + + let value = args_list.get(i).expect("Could not get value"); + self.add_arguments_to_environment(param, value.clone(), &local_env); + } + + // Add arguments object + let arguments_obj = create_unmapped_arguments_object(args_list); + local_env + .borrow_mut() + .create_mutable_binding("arguments".to_string(), false); + local_env + .borrow_mut() + .initialize_binding("arguments", arguments_obj); + + interpreter.realm.environment.push(local_env); + + // Call body should be set before reaching here + let result = match &self.body { + FunctionBody::Ordinary(ref body) => interpreter.run(body), + _ => panic!("Ordinary function should not have BuiltIn Function body"), + }; + + // local_env gets dropped here, its no longer needed + interpreter.realm.environment.pop(); + result + } + } + } + + // Adds the final rest parameters to the Environment as an array + fn add_rest_param( + &self, + param: &FormalParameter, + index: usize, + args_list: &[Value], + interpreter: &mut Interpreter, + local_env: &Environment, + ) { + // Create array of values + let array = array::new_array(interpreter).unwrap(); + array::add_to_array_object(&array, &args_list[index..]).unwrap(); + + // Create binding + local_env + .borrow_mut() + .create_mutable_binding(param.name.clone(), false); + + // Set Binding to value + local_env + .borrow_mut() + .initialize_binding(¶m.name, array); + } + + // Adds an argument to the environment + fn add_arguments_to_environment( + &self, + param: &FormalParameter, + value: Value, + local_env: &Environment, + ) { + // Create binding + local_env + .borrow_mut() + .create_mutable_binding(param.name.clone(), false); + + // Set Binding to value + local_env + .borrow_mut() + .initialize_binding(¶m.name, value); } } -impl Debug for NativeFunction { +impl Debug for Function { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{{")?; - for (key, val) in self.object.properties.iter() { - write!( - f, - "{}: {}", - key, - val.value - .as_ref() - .unwrap_or(&Gc::new(ValueData::Undefined)) - .clone() - )?; - } + write!(f, "[Not implemented]")?; write!(f, "}}") } } -unsafe impl gc::Trace for NativeFunction { - custom_trace!(this, mark(&this.object)); -} - -/// Create a new `Function` object -pub fn _create() -> Value { - let function: Object = Object::default(); - to_value(function) -} -/// Initialise the global object with the `Function` object -pub fn init(global: &Value) { - let global_ptr = global; - global_ptr.set_field_slice("Function", _create()); +/// Function Prototype. +/// +/// +pub fn create_function_prototype() { + let mut function_prototype: Object = Object::default(); + // Set Kind to function (for historical & compatibility reasons) + // + function_prototype.kind = ObjectKind::Function; } -/// Arguments -/// https://tc39.es/ecma262/#sec-createunmappedargumentsobject -pub fn create_unmapped_arguments_object(arguments_list: Vec) -> Value { +/// Arguments. +/// +/// +pub fn create_unmapped_arguments_object(arguments_list: &[Value]) -> Value { let len = arguments_list.len(); let mut obj = Object::default(); obj.set_internal_slot("ParameterMap", Gc::new(ValueData::Undefined)); @@ -153,3 +346,16 @@ pub fn create_unmapped_arguments_object(arguments_list: Vec) -> Value { to_value(obj) } + +/// Create new function `[[Construct]]` +/// +// This gets called when a new Function() is created. +pub fn make_function(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + this.set_kind(ObjectKind::Function); + Ok(this.clone()) +} + +pub fn create_constructor(global: &Value) -> Value { + let proto = ValueData::new_obj(Some(global)); + make_constructor_fn!(make_function, make_function, global, proto) +} diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index c0e6e55ec8f..f82b437ac40 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -13,7 +13,6 @@ //! [json]: https://www.json.org/json-en.html //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON -use crate::builtins::function::NativeFunctionData; use crate::builtins::value::{to_value, ResultValue, Value, ValueData}; use crate::exec::Interpreter; use serde_json::{self, Value as JSONValue}; @@ -34,7 +33,7 @@ mod tests; /// [spec]: https://tc39.es/ecma262/#sec-json.parse /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse // TODO: implement optional revever argument. -pub fn parse(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn parse(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { match serde_json::from_str::( &args .get(0) @@ -63,7 +62,7 @@ pub fn parse(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-json.stringify /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify -pub fn stringify(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn stringify(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let obj = args.get(0).expect("cannot get argument for JSON.stringify"); let json = obj.to_json().to_string(); Ok(to_value(json)) diff --git a/boa/src/builtins/math/mod.rs b/boa/src/builtins/math/mod.rs index 0d3d52d61a3..6cb2b06ec0d 100644 --- a/boa/src/builtins/math/mod.rs +++ b/boa/src/builtins/math/mod.rs @@ -12,10 +12,7 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math use crate::{ - builtins::{ - function::NativeFunctionData, - value::{from_value, to_value, ResultValue, Value, ValueData}, - }, + builtins::value::{from_value, to_value, ResultValue, Value, ValueData}, exec::Interpreter, }; use rand::random; @@ -32,7 +29,7 @@ mod tests; /// /// [spec]: https://tc39.es/ecma262/#sec-math.abs /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/abs -pub fn abs(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn abs(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -50,7 +47,7 @@ pub fn abs(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.acos /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acos -pub fn acos(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn acos(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -68,7 +65,7 @@ pub fn acos(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.acosh /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acosh -pub fn acosh(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn acosh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -86,7 +83,7 @@ pub fn acosh(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.asin /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asin -pub fn asin(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn asin(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -104,7 +101,7 @@ pub fn asin(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.asinh /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asinh -pub fn asinh(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn asinh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -122,7 +119,7 @@ pub fn asinh(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.atan /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan -pub fn atan(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn atan(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -140,7 +137,7 @@ pub fn atan(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.atanh /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atanh -pub fn atanh(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn atanh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -158,7 +155,7 @@ pub fn atanh(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.atan2 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2 -pub fn atan2(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn atan2(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -176,7 +173,7 @@ pub fn atan2(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.cbrt /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cbrt -pub fn cbrt(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn cbrt(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -194,7 +191,7 @@ pub fn cbrt(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.ceil /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil -pub fn ceil(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn ceil(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -212,7 +209,7 @@ pub fn ceil(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.cos /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cos -pub fn cos(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn cos(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -230,7 +227,7 @@ pub fn cos(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.cosh /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cosh -pub fn cosh(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn cosh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -248,7 +245,7 @@ pub fn cosh(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.exp /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/exp -pub fn exp(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn exp(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -266,7 +263,7 @@ pub fn exp(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.floor /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor -pub fn floor(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn floor(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -284,7 +281,7 @@ pub fn floor(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.log /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log -pub fn log(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn log(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -307,7 +304,7 @@ pub fn log(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.log10 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log10 -pub fn log10(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn log10(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -330,7 +327,7 @@ pub fn log10(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.log2 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log2 -pub fn log2(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn log2(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -353,7 +350,7 @@ pub fn log2(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.max /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max -pub fn max(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn max(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let mut max = f64::NEG_INFINITY; for arg in args { let num = arg.to_num(); @@ -370,7 +367,7 @@ pub fn max(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.min /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min -pub fn min(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn min(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let mut max = f64::INFINITY; for arg in args { let num = arg.to_num(); @@ -387,7 +384,7 @@ pub fn min(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.pow /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow -pub fn pow(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn pow(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.len() >= 2 { let num: f64 = from_value(args.get(0).expect("Could not get argument").clone()) .expect("Could not convert argument to f64"); @@ -407,7 +404,7 @@ pub fn pow(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.random /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random -pub fn _random(_: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn _random(_: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(random::())) } @@ -419,7 +416,7 @@ pub fn _random(_: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.round /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round -pub fn round(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn round(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -437,7 +434,7 @@ pub fn round(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.sign /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign -pub fn sign(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn sign(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -460,7 +457,7 @@ pub fn sign(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.sin /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sin -pub fn sin(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn sin(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -478,7 +475,7 @@ pub fn sin(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.sinh /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sinh -pub fn sinh(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn sinh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -496,7 +493,7 @@ pub fn sinh(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.sqrt /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sqrt -pub fn sqrt(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn sqrt(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -506,7 +503,7 @@ pub fn sqrt(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { })) } /// Get the tangent of a number -pub fn tan(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn tan(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -524,7 +521,7 @@ pub fn tan(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.tanh /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tanh -pub fn tanh(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn tanh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { @@ -542,7 +539,7 @@ pub fn tanh(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-math.trunc /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc -pub fn trunc(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn trunc(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(if args.is_empty() { f64::NAN } else { diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index ac50ac3ddd5..0dc00ed5727 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -5,15 +5,82 @@ /// If no length is provided, the length will be set to 0. macro_rules! make_builtin_fn { ($fn:ident, named $name:expr, with length $l:tt, of $p:ident) => { - let $fn = to_value($fn as NativeFunctionData); - $fn.set_field_slice("length", to_value($l)); - $p.set_field_slice($name, $fn); + let func = crate::builtins::function::Function::create_builtin( + vec![], + crate::builtins::function::FunctionBody::BuiltIn($fn), + ); + + let mut new_func = crate::builtins::object::Object::function(); + new_func.set_call(func); + let new_func_obj = to_value(new_func); + new_func_obj.set_field_slice("length", to_value($l)); + $p.set_field_slice($name, new_func_obj); }; ($fn:ident, named $name:expr, of $p:ident) => { make_builtin_fn!($fn, named $name, with length 0, of $p); }; } +/// Macro to create a new constructor function +/// +/// Either (construct_body, global, prototype) +macro_rules! make_constructor_fn { + ($body:ident, $global:ident, $proto:ident) => {{ + // Create the native function + let constructor_fn = crate::builtins::function::Function::create_builtin( + vec![], + crate::builtins::function::FunctionBody::BuiltIn($body), + ); + + // Get reference to Function.prototype + let func_prototype = $global + .get_field_slice("Function") + .get_field_slice(PROTOTYPE); + + // Create the function object and point its instance prototype to Function.prototype + let mut constructor_obj = Object::function(); + constructor_obj.set_construct(constructor_fn); + + constructor_obj.set_internal_slot("__proto__", func_prototype); + let constructor_val = to_value(constructor_obj); + + // Set proto.constructor -> constructor_obj + $proto.set_field_slice("constructor", constructor_val.clone()); + constructor_val.set_field_slice(PROTOTYPE, $proto); + + constructor_val + }}; + ($construct_body:ident, $call_body:ident, $global:ident, $proto:ident) => {{ + // Create the native functions + let construct_fn = crate::builtins::function::Function::create_builtin( + vec![], + crate::builtins::function::FunctionBody::BuiltIn($construct_body), + ); + let call_fn = crate::builtins::function::Function::create_builtin( + vec![], + crate::builtins::function::FunctionBody::BuiltIn($call_body), + ); + + // Get reference to Function.prototype + let func_prototype = $global + .get_field_slice("Function") + .get_field_slice(PROTOTYPE); + + // Create the function object and point its instance prototype to Function.prototype + let mut constructor_obj = Object::function(); + constructor_obj.set_construct(construct_fn); + constructor_obj.set_call(call_fn); + constructor_obj.set_internal_slot("__proto__", func_prototype); + let constructor_val = to_value(constructor_obj); + + // Set proto.constructor -> constructor_obj + $proto.set_field_slice("constructor", constructor_val.clone()); + constructor_val.set_field_slice(PROTOTYPE, $proto); + + constructor_val + }}; +} + pub mod array; pub mod boolean; pub mod console; diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index 4b3b36c0a7d..dcc5550641a 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -18,8 +18,7 @@ mod tests; use crate::{ builtins::{ - function::NativeFunctionData, - object::{internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, PROTOTYPE}, + object::{internal_methods_trait::ObjectInternalMethods, Object, PROTOTYPE}, value::{to_value, ResultValue, Value, ValueData}, }, exec::Interpreter, @@ -36,7 +35,7 @@ fn to_number(value: &Value) -> Value { to_value(0) } } - ValueData::Function(_) | ValueData::Symbol(_) | ValueData::Undefined => to_value(f64::NAN), + ValueData::Symbol(_) | ValueData::Undefined => to_value(f64::NAN), ValueData::Integer(i) => to_value(f64::from(i)), ValueData::Object(ref o) => (o).deref().borrow().get_internal_slot("NumberData"), ValueData::Null => to_value(0), @@ -58,7 +57,7 @@ fn num_to_exponential(n: f64) -> String { } /// Create a new number `[[Construct]]` -pub fn make_number(this: &Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { +pub fn make_number(this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { let data = match args.get(0) { Some(ref value) => to_number(value), None => to_number(&to_value(0)), @@ -70,7 +69,7 @@ pub fn make_number(this: &Value, args: &[Value], _ctx: &mut Interpreter) -> Resu /// `Number()` function. /// /// More Information https://tc39.es/ecma262/#sec-number-constructor-number-value -pub fn call_number(_this: &Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { +pub fn call_number(_this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { let data = match args.get(0) { Some(ref value) => to_number(value), None => to_number(&to_value(0)), @@ -88,7 +87,7 @@ pub fn call_number(_this: &Value, args: &[Value], _ctx: &mut Interpreter) -> Res /// /// [spec]: https://tc39.es/ecma262/#sec-number.prototype.toexponential /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toExponential -pub fn to_exponential(this: &Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue { +pub fn to_exponential(this: &mut Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue { let this_num = to_number(this).to_num(); let this_str_num = num_to_exponential(this_num); Ok(to_value(this_str_num)) @@ -104,7 +103,7 @@ pub fn to_exponential(this: &Value, _args: &[Value], _ctx: &mut Interpreter) -> /// /// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tofixed /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed -pub fn to_fixed(this: &Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { +pub fn to_fixed(this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { let this_num = to_number(this).to_num(); let precision = match args.get(0) { Some(n) => match n.to_int() { @@ -130,7 +129,7 @@ pub fn to_fixed(this: &Value, args: &[Value], _ctx: &mut Interpreter) -> ResultV /// /// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tolocalestring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString -pub fn to_locale_string(this: &Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue { +pub fn to_locale_string(this: &mut Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue { let this_num = to_number(this).to_num(); let this_str_num = format!("{}", this_num); Ok(to_value(this_str_num)) @@ -146,7 +145,7 @@ pub fn to_locale_string(this: &Value, _args: &[Value], _ctx: &mut Interpreter) - /// /// [spec]: https://tc39.es/ecma262/#sec-number.prototype.toexponential /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toPrecision -pub fn to_precision(this: &Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { +pub fn to_precision(this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { let this_num = to_number(this); let _num_str_len = format!("{}", this_num.to_num()).len(); let _precision = match args.get(0) { @@ -170,7 +169,7 @@ pub fn to_precision(this: &Value, args: &[Value], _ctx: &mut Interpreter) -> Res /// /// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tostring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toString -pub fn to_string(this: &Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue { +pub fn to_string(this: &mut Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue { Ok(to_value(format!("{}", to_number(this).to_num()))) } @@ -184,20 +183,13 @@ pub fn to_string(this: &Value, _args: &[Value], _ctx: &mut Interpreter) -> Resul /// /// [spec]: https://tc39.es/ecma262/#sec-number.prototype.valueof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/valueOf -pub fn value_of(this: &Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue { +pub fn value_of(this: &mut Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue { Ok(to_number(this)) } /// Create a new `Number` object pub fn create_constructor(global: &Value) -> Value { - let mut number_constructor = Object::default(); - number_constructor.kind = ObjectKind::Function; - - number_constructor.set_internal_method("construct", make_number); - number_constructor.set_internal_method("call", call_number); - let number_prototype = ValueData::new_obj(Some(global)); - number_prototype.set_internal_slot("NumberData", to_value(0)); make_builtin_fn!(to_exponential, named "toExponential", with length 1, of number_prototype); @@ -207,8 +199,5 @@ pub fn create_constructor(global: &Value) -> Value { make_builtin_fn!(to_string, named "toString", with length 1, of number_prototype); make_builtin_fn!(value_of, named "valueOf", of number_prototype); - let number = to_value(number_constructor); - number_prototype.set_field_slice("constructor", number.clone()); - number.set_field_slice(PROTOTYPE, number_prototype); - number + make_constructor_fn!(make_number, call_number, global, number_prototype) } diff --git a/boa/src/builtins/object/internal_methods_trait.rs b/boa/src/builtins/object/internal_methods_trait.rs index 6d8a2a5a5e3..a41ec5d9c7f 100644 --- a/boa/src/builtins/object/internal_methods_trait.rs +++ b/boa/src/builtins/object/internal_methods_trait.rs @@ -8,7 +8,7 @@ use crate::builtins::{ object::{Object, INSTANCE_PROTOTYPE}, property::Property, - value::{to_value, Value, ValueData}, + value::{same_value, to_value, Value, ValueData}, }; use gc::Gc; use std::borrow::Borrow; @@ -39,7 +39,7 @@ pub trait ObjectInternalMethods { // the parent value variant should be an object // In the unlikely event it isn't return false return match *parent { - ValueData::Object(ref obj) => obj.borrow().has_property(val), + ValueData::Object(ref obj) => (*obj).deref().borrow().has_property(val), _ => false, }; } @@ -168,6 +168,116 @@ pub trait ObjectInternalMethods { } } + fn define_own_property(&mut self, property_key: String, desc: Property) -> bool { + let mut current = self.get_own_property(&to_value(property_key.to_string())); + let extensible = self.is_extensible(); + + // https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor + // There currently isn't a property, lets create a new one + if current.value.is_none() || current.value.as_ref().expect("failed").is_undefined() { + if !extensible { + return false; + } + + self.insert_property(property_key, desc); + return true; + } + // If every field is absent we don't need to set anything + if desc.is_none() { + return true; + } + + // 4 + if !current.configurable.unwrap_or(false) { + if desc.configurable.is_some() && desc.configurable.expect("unable to get prop desc") { + return false; + } + + if desc.enumerable.is_some() + && (desc.enumerable.as_ref().expect("unable to get prop desc") + != current + .enumerable + .as_ref() + .expect("unable to get prop desc")) + { + return false; + } + } + + // 5 + if desc.is_generic_descriptor() { + // 6 + } else if current.is_data_descriptor() != desc.is_data_descriptor() { + // a + if !current.configurable.expect("unable to get prop desc") { + return false; + } + // b + if current.is_data_descriptor() { + // Convert to accessor + current.value = None; + current.writable = None; + } else { + // c + // convert to data + current.get = None; + current.set = None; + } + + self.insert_property(property_key.clone(), current); + // 7 + } else if current.is_data_descriptor() && desc.is_data_descriptor() { + // a + if !current.configurable.expect("unable to get prop desc") + && !current.writable.expect("unable to get prop desc") + { + if desc.writable.is_some() && desc.writable.expect("unable to get prop desc") { + return false; + } + + if desc.value.is_some() + && !same_value( + &desc.value.clone().unwrap(), + ¤t.value.clone().unwrap(), + false, + ) + { + return false; + } + + return true; + } + // 8 + } else { + if !current.configurable.unwrap() { + if desc.set.is_some() + && !same_value( + &desc.set.clone().unwrap(), + ¤t.set.clone().unwrap(), + false, + ) + { + return false; + } + + if desc.get.is_some() + && !same_value( + &desc.get.clone().unwrap(), + ¤t.get.clone().unwrap(), + false, + ) + { + return false; + } + } + + return true; + } + // 9 + self.insert_property(property_key, desc); + true + } + /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p /// The specification returns a Property Descriptor or Undefined. These are 2 separate types and we can't do that here. fn get_own_property(&self, prop: &Value) -> Property; @@ -181,8 +291,6 @@ pub trait ObjectInternalMethods { self.get_internal_slot(INSTANCE_PROTOTYPE) } - fn define_own_property(&mut self, property_key: String, desc: Property) -> bool; - /// Utility function to get an immutable internal slot or Null fn get_internal_slot(&self, name: &str) -> Value; diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 5fd2ec13c9f..ea1f271d4fe 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -15,14 +15,16 @@ use crate::{ builtins::{ - function::NativeFunctionData, + function::Function, property::Property, value::{from_value, same_value, to_value, ResultValue, Value, ValueData}, }, exec::Interpreter, }; -use gc::Gc; +use gc::{unsafe_empty_trace, Gc, Trace}; use gc_derive::{Finalize, Trace}; +use std::fmt::{self, Debug}; +use std::fmt::{Display, Error, Formatter}; use std::{borrow::Borrow, collections::HashMap, ops::Deref}; pub use internal_methods_trait::ObjectInternalMethods; @@ -38,7 +40,7 @@ pub static PROTOTYPE: &str = "prototype"; pub static INSTANCE_PROTOTYPE: &str = "__proto__"; /// The internal representation of an JavaScript object. -#[derive(Trace, Finalize, Debug, Clone)] +#[derive(Trace, Finalize, Clone)] pub struct Object { /// The type of the object. pub kind: ObjectKind, @@ -50,6 +52,26 @@ pub struct Object { pub sym_properties: Box>, /// Some rust object that stores internal state pub state: Option>, + /// [[Call]] + pub call: Option, + /// [[Construct]] + pub construct: Option, +} + +impl Debug for Object { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln!(f, "{{")?; + writeln!(f, "\tkind: {}", self.kind)?; + writeln!(f, "\tstate: {:?}", self.state)?; + writeln!(f, "\tcall: {:?}", self.call)?; + writeln!(f, "\tconstruct: {:?}", self.construct)?; + writeln!(f, "\tproperties: {{")?; + for (key, _) in self.properties.iter() { + writeln!(f, "\t\t{}", key)?; + } + writeln!(f, "\t }}")?; + write!(f, "}}") + } } impl ObjectInternalMethods for Object { @@ -146,7 +168,7 @@ impl ObjectInternalMethods for Object { } } ValueData::Symbol(ref sym) => { - let sym_id = sym + let sym_id = (**sym) .borrow() .get_internal_slot("SymbolData") .to_string() @@ -318,6 +340,24 @@ impl Object { properties: Box::new(HashMap::new()), sym_properties: Box::new(HashMap::new()), state: None, + call: None, + construct: None, + }; + + object.set_internal_slot("extensible", to_value(true)); + object + } + + /// Return a new ObjectData struct, with `kind` set to Ordinary + pub fn function() -> Self { + let mut object = Self { + kind: ObjectKind::Function, + internal_slots: Box::new(HashMap::new()), + properties: Box::new(HashMap::new()), + sym_properties: Box::new(HashMap::new()), + state: None, + call: None, + construct: None, }; object.set_internal_slot("extensible", to_value(true)); @@ -340,17 +380,14 @@ impl Object { obj } - /// Utility function to set an internal slot which is a function. - pub fn set_internal_method(&mut self, name: &str, val: NativeFunctionData) { - self.internal_slots.insert(name.to_string(), to_value(val)); + /// Set [[Call]] + pub fn set_call(&mut self, val: Function) { + self.call = Some(val); } - /// Utility function to set a method on this object. - /// - /// The native function will live in the `properties` field of the Object. - pub fn set_method(&mut self, name: &str, val: NativeFunctionData) { - self.properties - .insert(name.to_string(), Property::default().value(to_value(val))); + /// set [[Construct]] + pub fn set_construct(&mut self, val: Function) { + self.construct = Some(val); } /// Return a new Boolean object whose `[[BooleanData]]` internal slot is set to argument. @@ -361,6 +398,8 @@ impl Object { properties: Box::new(HashMap::new()), sym_properties: Box::new(HashMap::new()), state: None, + call: None, + construct: None, }; obj.internal_slots @@ -376,6 +415,8 @@ impl Object { properties: Box::new(HashMap::new()), sym_properties: Box::new(HashMap::new()), state: None, + call: None, + construct: None, }; obj.internal_slots @@ -391,6 +432,8 @@ impl Object { properties: Box::new(HashMap::new()), sym_properties: Box::new(HashMap::new()), state: None, + call: None, + construct: None, }; obj.internal_slots @@ -409,14 +452,34 @@ impl Object { ValueData::Boolean(_) => Ok(Self::from_boolean(value)), ValueData::Rational(_) => Ok(Self::from_number(value)), ValueData::String(_) => Ok(Self::from_string(value)), - ValueData::Object(ref obj) => Ok(obj.borrow().clone()), + ValueData::Object(ref obj) => Ok((*obj).deref().borrow().clone()), _ => Err(()), } } + + /// It determines if Object is a callable function with a [[Call]] internal method. + /// + /// More information: + /// - [EcmaScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-iscallable + pub fn is_callable(&self) -> bool { + self.call.is_some() + } + + /// It determines if Object is a function object with a [[Construct]] internal method. + /// + /// More information: + /// - [EcmaScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-isconstructor + pub fn is_constructor(&self) -> bool { + self.construct.is_some() + } } /// Defines the different types of objects. -#[derive(Trace, Finalize, Clone, Debug, Eq, PartialEq)] +#[derive(Finalize, Debug, Copy, Clone, Eq, PartialEq)] pub enum ObjectKind { Function, Array, @@ -428,19 +491,50 @@ pub enum ObjectKind { Number, } +impl Display for ObjectKind { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!( + f, + "{}", + match self { + Self::Function => "Function", + Self::Array => "Array", + Self::String => "String", + Self::Symbol => "Symbol", + Self::Error => "Error", + Self::Ordinary => "Ordinary", + Self::Boolean => "Boolean", + Self::Number => "Number", + } + ) + } +} + +/// `Trace` implementation for `ObjectKind`. +/// +/// This is indeed safe, but we need to mark this as an empty trace because neither +// `NativeFunctionData` nor Node hold any GC'd objects, but Gc doesn't know that. So we need to +/// signal it manually. `rust-gc` does not have a `Trace` implementation for `fn(_, _, _)`. +/// +/// +/// Waiting on until we can derive Copy +unsafe impl Trace for ObjectKind { + unsafe_empty_trace!(); +} + /// Create a new object. -pub fn make_object(_: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn make_object(_: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { Ok(Gc::new(ValueData::Undefined)) } /// Get the `prototype` of an object. -pub fn get_proto_of(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn get_proto_of(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let obj = args.get(0).expect("Cannot get object"); Ok(obj.get_field_slice(INSTANCE_PROTOTYPE)) } /// Set the `prototype` of an object. -pub fn set_proto_of(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn set_proto_of(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let obj = args.get(0).expect("Cannot get object").clone(); let proto = args.get(1).expect("Cannot get object").clone(); obj.set_internal_slot(INSTANCE_PROTOTYPE, proto); @@ -448,7 +542,7 @@ pub fn set_proto_of(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultVal } /// Define a property in an object -pub fn define_prop(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn define_prop(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let obj = args.get(0).expect("Cannot get object"); let prop = from_value::(args.get(1).expect("Cannot get object").clone()) .expect("Cannot get object"); @@ -468,7 +562,7 @@ pub fn define_prop(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValu /// /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.tostring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString -pub fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { Ok(to_value(this.to_string())) } @@ -483,7 +577,7 @@ pub fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue /// /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.hasownproperty /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty -pub fn has_own_prop(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn has_own_prop(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let prop = if args.is_empty() { None } else { @@ -496,14 +590,22 @@ pub fn has_own_prop(this: &Value, args: &[Value], _: &mut Interpreter) -> Result /// Create a new `Object` object. pub fn create_constructor(_: &Value) -> Value { - let object = to_value(make_object as NativeFunctionData); + let mut constructor_obj = Object::function(); + // Create the native function + let constructor_fn = crate::builtins::function::Function::create_builtin( + vec![], + crate::builtins::function::FunctionBody::BuiltIn(make_object), + ); + constructor_obj.set_construct(constructor_fn); + let object = to_value(constructor_obj); // Prototype chain ends here VV - let mut prototype = Object::default(); - prototype.set_method("hasOwnProperty", has_own_prop); - prototype.set_method("toString", to_string); + let prototype = to_value(Object::default()); + object.set_field_slice(PROTOTYPE, prototype.clone()); + + make_builtin_fn!(has_own_prop, named "hasOwnProperty", of prototype); + make_builtin_fn!(to_string, named "toString", of prototype); object.set_field_slice("length", to_value(1_i32)); - object.set_field_slice(PROTOTYPE, to_value(prototype)); make_builtin_fn!(set_proto_of, named "setPrototypeOf", with length 2, of object); make_builtin_fn!(get_proto_of, named "getPrototypeOf", with length 1, of object); make_builtin_fn!(define_prop, named "defineProperty", with length 3, of object); diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index fb8467e06b0..08057887b0c 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -16,8 +16,7 @@ use regex::Regex; use crate::{ builtins::{ - function::NativeFunctionData, - object::{InternalState, Object, ObjectKind, PROTOTYPE}, + object::{InternalState, Object, ObjectInternalMethods, ObjectKind, PROTOTYPE}, property::Property, value::{from_value, to_value, FromValue, ResultValue, Value, ValueData}, }, @@ -66,7 +65,7 @@ fn get_argument(args: &[Value], idx: usize) -> Result { } /// Create a new `RegExp` -pub fn make_regexp(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn make_regexp(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { if args.is_empty() { return Err(Gc::new(ValueData::Undefined)); } @@ -181,7 +180,7 @@ pub fn make_regexp(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultV /// /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.dotAll /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/dotAll -fn get_dot_all(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { +fn get_dot_all(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { this.with_internal_state_ref(|regex: &RegExp| Ok(to_value(regex.dot_all))) } @@ -196,7 +195,7 @@ fn get_dot_all(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.flags /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/flags /// [flags]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Advanced_searching_with_flags_2 -fn get_flags(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { +fn get_flags(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { this.with_internal_state_ref(|regex: &RegExp| Ok(to_value(regex.flags.clone()))) } @@ -210,7 +209,7 @@ fn get_flags(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.global /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/global -fn get_global(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { +fn get_global(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { this.with_internal_state_ref(|regex: &RegExp| Ok(to_value(regex.global))) } @@ -224,7 +223,7 @@ fn get_global(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.ignorecase /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/ignoreCase -fn get_ignore_case(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { +fn get_ignore_case(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { this.with_internal_state_ref(|regex: &RegExp| Ok(to_value(regex.ignore_case))) } @@ -238,7 +237,7 @@ fn get_ignore_case(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValu /// /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.multiline /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/multiline -fn get_multiline(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { +fn get_multiline(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { this.with_internal_state_ref(|regex: &RegExp| Ok(to_value(regex.multiline))) } @@ -253,7 +252,7 @@ fn get_multiline(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue /// /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.source /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/source -fn get_source(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { +fn get_source(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { Ok(this.get_internal_slot("OriginalSource")) } @@ -267,7 +266,7 @@ fn get_source(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.sticky /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/sticky -fn get_sticky(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { +fn get_sticky(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { this.with_internal_state_ref(|regex: &RegExp| Ok(to_value(regex.sticky))) } @@ -282,15 +281,10 @@ fn get_sticky(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.unicode /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/unicode -fn get_unicode(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { +fn get_unicode(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { this.with_internal_state_ref(|regex: &RegExp| Ok(to_value(regex.unicode))) } -/// Helper function. -fn _make_prop(getter: NativeFunctionData) -> Property { - Property::default().get(to_value(getter)) -} - /// `RegExp.prototype.test( string )` /// /// The `test()` method executes a search for a match between a regular expression and a specified string. @@ -303,7 +297,7 @@ fn _make_prop(getter: NativeFunctionData) -> Property { /// /// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.test /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test -pub fn test(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn test(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let arg_str = get_argument::(args, 0)?; let mut last_index = from_value::(this.get_field_slice("lastIndex")).map_err(to_value)?; @@ -337,7 +331,7 @@ pub fn test(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.exec /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec -pub fn exec(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn exec(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let arg_str = get_argument::(args, 0)?; let mut last_index = from_value::(this.get_field_slice("lastIndex")).map_err(to_value)?; @@ -387,7 +381,7 @@ pub fn exec(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype-@@match /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/@@match -pub fn r#match(this: &Value, arg: String, ctx: &mut Interpreter) -> ResultValue { +pub fn r#match(this: &mut Value, arg: String, ctx: &mut Interpreter) -> ResultValue { let (matcher, flags) = this.with_internal_state_ref(|regex: &RegExp| (regex.matcher.clone(), regex.flags.clone())); if flags.contains('g') { @@ -414,7 +408,7 @@ pub fn r#match(this: &Value, arg: String, ctx: &mut Interpreter) -> ResultValue /// /// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.tostring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/toString -pub fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { let body = from_value::(this.get_internal_slot("OriginalSource")).map_err(to_value)?; let flags = this.with_internal_state_ref(|regex: &RegExp| regex.flags.clone()); Ok(to_value(format!("/{}/{}", body, flags))) @@ -431,7 +425,7 @@ pub fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue /// [spec]: https://tc39.es/ecma262/#sec-regexp-prototype-matchall /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/@@matchAll // TODO: it's returning an array, it should return an iterator -pub fn match_all(this: &Value, arg_str: String) -> ResultValue { +pub fn match_all(this: &mut Value, arg_str: String) -> ResultValue { let matches: Vec = this.with_internal_state_ref(|regex: &RegExp| { let mut matches = Vec::new(); @@ -473,32 +467,23 @@ pub fn match_all(this: &Value, arg_str: String) -> ResultValue { /// Create a new `RegExp` object. pub fn create_constructor(global: &Value) -> Value { - // Create constructor function - let mut regexp_constructor = Object::default(); - regexp_constructor.kind = ObjectKind::Function; - regexp_constructor.set_internal_method("construct", make_regexp); - // Todo: add call function, currently call points to contructor, this is wrong - regexp_constructor.set_internal_method("call", make_regexp); - // Create prototype let proto = ValueData::new_obj(Some(global)); + proto.set_field_slice("lastIndex", to_value(0)); + make_builtin_fn!(test, named "test", with length 1, of proto); make_builtin_fn!(exec, named "exec", with length 1, of proto); make_builtin_fn!(to_string, named "toString", of proto); - proto.set_field_slice("lastIndex", to_value(0)); - proto.set_prop_slice("dotAll", _make_prop(get_dot_all)); - proto.set_prop_slice("flags", _make_prop(get_flags)); - proto.set_prop_slice("global", _make_prop(get_global)); - proto.set_prop_slice("ignoreCase", _make_prop(get_ignore_case)); - proto.set_prop_slice("multiline", _make_prop(get_multiline)); - proto.set_prop_slice("source", _make_prop(get_source)); - proto.set_prop_slice("sticky", _make_prop(get_sticky)); - proto.set_prop_slice("unicode", _make_prop(get_unicode)); - - let regexp = to_value(regexp_constructor); - regexp.set_field_slice(PROTOTYPE, proto.clone()); - proto.set_field_slice("constructor", regexp.clone()); - regexp + make_builtin_fn!(get_dot_all, named "dotAll", of proto); + make_builtin_fn!(get_flags, named "flags", of proto); + make_builtin_fn!(get_global, named "global", of proto); + make_builtin_fn!(get_ignore_case, named "ignoreCase", of proto); + make_builtin_fn!(get_multiline, named "multiline", of proto); + make_builtin_fn!(get_source, named "source", of proto); + make_builtin_fn!(get_sticky, named "sticky", of proto); + make_builtin_fn!(get_unicode, named "unicode", of proto); + + make_constructor_fn!(make_regexp, make_regexp, global, proto) } #[cfg(test)] diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index 5c0e3aca217..7187cfd8f28 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -14,8 +14,7 @@ mod tests; use crate::{ builtins::{ - function::NativeFunctionData, - object::{Object, ObjectKind, PROTOTYPE}, + object::{internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, PROTOTYPE}, property::Property, regexp::{make_regexp, match_all as regexp_match_all, r#match as regexp_match}, value::{from_value, to_value, ResultValue, Value, ValueData}, @@ -32,7 +31,7 @@ use std::{ /// Create new string [[Construct]] // This gets called when a new String() is created, it's called by exec:346 -pub fn make_string(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn make_string(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { // If we're constructing a string, we should set the initial length // To do this we need to convert the string back to a Rust String, then get the .len() // let a: String = from_value(args.get(0).expect("failed to get argument for String method").clone()).unwrap(); @@ -53,7 +52,7 @@ pub fn make_string(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultV /// Call new string [[Call]] /// /// More information: [ECMAScript reference](https://tc39.es/ecma262/#sec-string-constructor-string-value) -pub fn call_string(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn call_string(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let arg = match args.get(0) { Some(v) => v.clone(), None => Gc::new(ValueData::Undefined), @@ -67,7 +66,7 @@ pub fn call_string(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValu } /// Get the string value to a primitive string -pub fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { // Get String from String Object and send it back as a new value let primitive_val = this.get_internal_slot("StringData"); Ok(to_value(format!("{}", primitive_val))) @@ -89,7 +88,7 @@ pub fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue /// /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.charat /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charAt -pub fn char_at(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn char_at(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value let primitive_val = ctx.value_to_rust_string(this); @@ -133,7 +132,7 @@ pub fn char_at(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultVal /// /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.charcodeat /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt -pub fn char_code_at(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn char_code_at(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value let primitive_val: String = ctx.value_to_rust_string(this); @@ -175,7 +174,7 @@ pub fn char_code_at(this: &Value, args: &[Value], ctx: &mut Interpreter) -> Resu /// /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.concat /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/concat -pub fn concat(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn concat(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value let mut new_str = ctx.value_to_rust_string(this); @@ -199,7 +198,7 @@ pub fn concat(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValu /// /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.repeat /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat -pub fn repeat(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn repeat(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value let primitive_val: String = ctx.value_to_rust_string(this); @@ -223,7 +222,7 @@ pub fn repeat(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValu /// /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.slice /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice -pub fn slice(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn slice(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value let primitive_val: String = ctx.value_to_rust_string(this); @@ -280,7 +279,7 @@ pub fn slice(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue /// /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.startswith /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith -pub fn starts_with(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn starts_with(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value let primitive_val: String = ctx.value_to_rust_string(this); @@ -325,7 +324,7 @@ pub fn starts_with(this: &Value, args: &[Value], ctx: &mut Interpreter) -> Resul /// /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.endswith /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith -pub fn ends_with(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn ends_with(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value let primitive_val: String = ctx.value_to_rust_string(this); @@ -372,7 +371,7 @@ pub fn ends_with(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultV /// /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.includes /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes -pub fn includes(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn includes(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value let primitive_val: String = ctx.value_to_rust_string(this); @@ -437,7 +436,7 @@ fn get_regex_string(value: &Value) -> String { /// /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.replace /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace -pub fn replace(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn replace(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // TODO: Support Symbol replacer let primitive_val: String = ctx.value_to_rust_string(this); if args.is_empty() { @@ -494,7 +493,7 @@ pub fn replace(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultVal result } - ValueData::Function(_) => { + ValueData::Object(_) => { // This will return the matched substring first, then captured parenthesized groups later let mut results: Vec = caps .iter() @@ -510,7 +509,7 @@ pub fn replace(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultVal // Push the whole string being examined results.push(to_value(primitive_val.to_string())); - let result = ctx.call(&replace_object, &this, results).unwrap(); + let result = ctx.call(&replace_object, this, &results).unwrap(); ctx.value_to_rust_string(&result) } @@ -539,7 +538,7 @@ pub fn replace(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultVal /// /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.indexof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf -pub fn index_of(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn index_of(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value let primitive_val: String = ctx.value_to_rust_string(this); @@ -591,7 +590,7 @@ pub fn index_of(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultVa /// /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.lastindexof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/lastIndexOf -pub fn last_index_of(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn last_index_of(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value let primitive_val: String = ctx.value_to_rust_string(this); @@ -643,9 +642,9 @@ pub fn last_index_of(this: &Value, args: &[Value], ctx: &mut Interpreter) -> Res /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.match /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match /// [regex]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions -pub fn r#match(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - let re = make_regexp(&to_value(Object::default()), &[args[0].clone()], ctx)?; - regexp_match(&re, ctx.value_to_rust_string(this), ctx) +pub fn r#match(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + let mut re = make_regexp(&mut to_value(Object::default()), &[args[0].clone()], ctx)?; + regexp_match(&mut re, ctx.value_to_rust_string(this), ctx) } /// Abstract method `StringPad`. @@ -701,7 +700,7 @@ fn string_pad( /// /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.padend /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd -pub fn pad_end(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn pad_end(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { let primitive_val: String = ctx.value_to_rust_string(this); if args.is_empty() { return Err(to_value("padEnd requires maxLength argument")); @@ -735,7 +734,7 @@ pub fn pad_end(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultVal /// /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.padstart /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart -pub fn pad_start(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn pad_start(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { let primitive_val: String = ctx.value_to_rust_string(this); if args.is_empty() { return Err(to_value("padStart requires maxLength argument")); @@ -788,7 +787,7 @@ fn is_trimmable_whitespace(c: char) -> bool { /// /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.trim /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim -pub fn trim(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn trim(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { let this_str: String = ctx.value_to_rust_string(this); Ok(to_value(this_str.trim_matches(is_trimmable_whitespace))) } @@ -805,7 +804,7 @@ pub fn trim(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { /// /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.trimstart /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimStart -pub fn trim_start(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn trim_start(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { let this_str: String = ctx.value_to_rust_string(this); Ok(to_value( this_str.trim_start_matches(is_trimmable_whitespace), @@ -824,7 +823,7 @@ pub fn trim_start(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultVal /// /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.trimend /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimEnd -pub fn trim_end(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn trim_end(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { let this_str: String = ctx.value_to_rust_string(this); Ok(to_value(this_str.trim_end_matches(is_trimmable_whitespace))) } @@ -839,7 +838,7 @@ pub fn trim_end(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue /// /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.tolowercase /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase -pub fn to_lowercase(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn to_lowercase(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value let this_str: String = ctx.value_to_rust_string(this); @@ -860,7 +859,7 @@ pub fn to_lowercase(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultV /// /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.toUppercase /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase -pub fn to_uppercase(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn to_uppercase(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value let this_str: String = ctx.value_to_rust_string(this); @@ -879,7 +878,7 @@ pub fn to_uppercase(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultV /// /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.substring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substring -pub fn substring(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn substring(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value let primitive_val: String = ctx.value_to_rust_string(this); @@ -930,7 +929,7 @@ pub fn substring(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultV /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.substr /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substr /// -pub fn substr(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn substr(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value let primitive_val: String = ctx.value_to_rust_string(this); @@ -987,7 +986,7 @@ pub fn substr(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValu /// /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.value_of /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/valueOf -pub fn value_of(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn value_of(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // Use the to_string method because it is specified to do the same thing in this case to_string(this, args, ctx) } @@ -1005,12 +1004,12 @@ pub fn value_of(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultVa /// [regex]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions /// [cg]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Groups_and_Ranges // TODO: update this method to return iterator -pub fn match_all(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - let re: Value = match args.get(0) { +pub fn match_all(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + let mut re: Value = match args.get(0) { Some(arg) => { if arg == &Gc::new(ValueData::Null) { make_regexp( - &to_value(Object::default()), + &mut to_value(Object::default()), &[ to_value(ctx.value_to_rust_string(arg)), to_value(String::from("g")), @@ -1019,7 +1018,7 @@ pub fn match_all(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultV ) } else if arg == &Gc::new(ValueData::Undefined) { make_regexp( - &to_value(Object::default()), + &mut to_value(Object::default()), &[Gc::new(ValueData::Undefined), to_value(String::from("g"))], ctx, ) @@ -1028,26 +1027,17 @@ pub fn match_all(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultV } } None => make_regexp( - &to_value(Object::default()), + &mut to_value(Object::default()), &[to_value(String::new()), to_value(String::from("g"))], ctx, ), }?; - regexp_match_all(&re, ctx.value_to_rust_string(this)) + regexp_match_all(&mut re, ctx.value_to_rust_string(this)) } /// Create a new `String` object pub fn create_constructor(global: &Value) -> Value { - // Create constructor function object - let mut string_constructor = Object::default(); - string_constructor.kind = ObjectKind::Function; - - string_constructor.set_internal_method("construct", make_string); - // Todo: add call internal method (should be easy) - // Currently call points to the constructor function, this is wrong - string_constructor.set_internal_method("call", call_string); - // Create prototype let proto = ValueData::new_obj(Some(global)); let prop = Property::default().value(to_value(0_i32)); @@ -1077,10 +1067,7 @@ pub fn create_constructor(global: &Value) -> Value { make_builtin_fn!(match_all, named "matchAll", with length 1, of proto); make_builtin_fn!(replace, named "replace", with length 2, of proto); - let string = to_value(string_constructor); - proto.set_field_slice("constructor", string.clone()); - string.set_field_slice(PROTOTYPE, proto); - string + make_constructor_fn!(make_string, call_string, global, proto) } /// Initialise the `String` object on the global object diff --git a/boa/src/builtins/string/tests.rs b/boa/src/builtins/string/tests.rs index 08807ad62d5..c2d7c0970ec 100644 --- a/boa/src/builtins/string/tests.rs +++ b/boa/src/builtins/string/tests.rs @@ -10,7 +10,7 @@ fn check_string_constructor_is_function() { assert_eq!(string_constructor.is_function(), true); } -#[test] +// #[test] // TODO: re-enable when getProperty() is finished; // fn length() { // //TEST262: https://github.com/tc39/test262/blob/master/test/built-ins/String/length.js @@ -35,6 +35,7 @@ fn check_string_constructor_is_function() { // let d = forward(&mut engine, "d.length"); // assert_eq!(d, String::from("4")); // } + #[test] fn concat() { let realm = Realm::create(); diff --git a/boa/src/builtins/symbol/mod.rs b/boa/src/builtins/symbol/mod.rs index f1cce302825..1615ca0127b 100644 --- a/boa/src/builtins/symbol/mod.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -41,7 +41,7 @@ use rand::random; /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-symbol-description -pub fn call_symbol(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { +pub fn call_symbol(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // From an implementation and specificaition perspective Symbols are similar to Objects. // They have internal slots to hold the SymbolData and Description, they also have methods and a prototype. // So we start by creating an Object @@ -66,7 +66,9 @@ pub fn call_symbol(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultVa .get_field_slice(PROTOTYPE); sym_instance.set_internal_slot(INSTANCE_PROTOTYPE, proto); - Ok(Gc::new(ValueData::Symbol(GcCell::new(sym_instance)))) + Ok(Gc::new(ValueData::Symbol(Box::new(GcCell::new( + sym_instance, + ))))) } /// `Symbol.prototype.toString()` @@ -79,7 +81,7 @@ pub fn call_symbol(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultVa /// /// [spec]: https://tc39.es/ecma262/#sec-symbol.prototype.tostring /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toString -pub fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { +pub fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { let s: Value = this.get_internal_slot("Description"); let full_string = format!(r#"Symbol({})"#, s.to_string()); Ok(to_value(full_string)) @@ -96,24 +98,8 @@ pub fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue /// [spec]: https://tc39.es/ecma262/#sec-symbol-constructor /// [mdn]: pub fn create_constructor(global: &Value) -> Value { - // Create Symbol constructor (or function in Symbol's case) - let mut symbol_constructor = Object::default(); - symbol_constructor.set_internal_method("call", call_symbol); - - // Create prototype - let mut symbol_prototype = Object::default(); - - // Symbol.prototype[[Prototype]] points to Object.prototype - // Symbol Constructor -> Symbol Prototype -> Object Prototype - let object_prototype = global.get_field_slice("Object").get_field_slice(PROTOTYPE); - symbol_prototype.set_internal_slot(INSTANCE_PROTOTYPE, object_prototype); - symbol_prototype.set_method("toString", to_string); - - let symbol_prototype_val = to_value(symbol_prototype); - - let symbol_constructor_value = to_value(symbol_constructor); - symbol_prototype_val.set_field_slice("construcotor", symbol_constructor_value.clone()); - symbol_constructor_value.set_field_slice(PROTOTYPE, symbol_prototype_val); - - symbol_constructor_value + // Create prototype object + let proto = ValueData::new_obj(Some(global)); + make_builtin_fn!(to_string, named "toString", of proto); + make_constructor_fn!(call_symbol, call_symbol, global, proto) } diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index cb39ba0a777..8e8bdadd5ba 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -6,7 +6,7 @@ mod tests; use crate::builtins::{ - function::{Function, NativeFunction, NativeFunctionData}, + function::Function, object::{ internal_methods_trait::ObjectInternalMethods, InternalState, InternalStateCell, Object, ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE, @@ -52,11 +52,9 @@ pub enum ValueData { /// `Number` - A 32-bit integer, such as `42` Integer(i32), /// `Object` - An object, such as `Math`, represented by a binary tree of string keys to Javascript values - Object(GcCell), - /// `Function` - A runnable block of code, such as `Math.sqrt`, which can take some variables and return a useful value or act upon an object - Function(Box>), + Object(Box>), /// `Symbol` - A Symbol Type - Internally Symbols are similar to objects, except there are no properties, only internal slots - Symbol(GcCell), + Symbol(Box>), } impl ValueData { @@ -66,10 +64,10 @@ impl ValueData { let obj_proto = glob.get_field_slice("Object").get_field_slice(PROTOTYPE); let obj = Object::create(obj_proto); - Gc::new(Self::Object(GcCell::new(obj))) + Gc::new(Self::Object(Box::new(GcCell::new(obj)))) } else { let obj = Object::default(); - Gc::new(Self::Object(GcCell::new(obj))) + Gc::new(Self::Object(Box::new(GcCell::new(obj)))) } } @@ -82,7 +80,7 @@ impl ValueData { obj.internal_slots .insert(INSTANCE_PROTOTYPE.to_string(), proto); - Gc::new(Self::Object(GcCell::new(obj))) + Gc::new(Self::Object(Box::new(GcCell::new(obj)))) } /// This will tell us if we can exten an object or not, not properly implemented yet @@ -117,8 +115,10 @@ impl ValueData { /// Returns true if the value is a function pub fn is_function(&self) -> bool { match *self { - Self::Function(_) => true, - Self::Object(ref o) => o.deref().borrow().get_internal_slot("call").is_function(), + Self::Object(ref o) => { + let borrowed_obj = o.borrow(); + borrowed_obj.is_callable() || borrowed_obj.is_constructor() + } _ => false, } } @@ -207,14 +207,14 @@ impl ValueData { /// Converts the value into a 64-bit floating point number pub fn to_num(&self) -> f64 { match *self { - Self::Object(_) | Self::Symbol(_) | Self::Undefined | Self::Function(_) => NAN, + Self::Object(_) | Self::Symbol(_) | Self::Undefined => NAN, Self::String(ref str) => match FromStr::from_str(str) { Ok(num) => num, Err(_) => NAN, }, Self::Rational(num) => num, Self::Boolean(true) => 1.0, - Self::Boolean(false) | ValueData::Null => 0.0, + Self::Boolean(false) | Self::Null => 0.0, Self::Integer(num) => f64::from(num), } } @@ -226,8 +226,7 @@ impl ValueData { | Self::Undefined | Self::Symbol(_) | Self::Null - | Self::Boolean(false) - | Self::Function(_) => 0, + | Self::Boolean(false) => 0, Self::String(ref str) => match FromStr::from_str(str) { Ok(num) => num, Err(_) => 0, @@ -251,11 +250,6 @@ impl ValueData { pub fn remove_prop(&self, field: &str) { match *self { Self::Object(ref obj) => obj.borrow_mut().deref_mut().properties.remove(field), - // Accesing .object on borrow() seems to automatically dereference it, so we don't need the * - Self::Function(ref func) => match func.borrow_mut().deref_mut() { - Function::NativeFunc(ref mut func) => func.object.properties.remove(field), - Function::RegularFunc(ref mut func) => func.object.properties.remove(field), - }, _ => None, }; } @@ -272,6 +266,10 @@ impl ValueData { } } + if self.is_undefined() { + return None; + } + let obj: Object = match *self { Self::Object(ref obj) => { let hash = obj.clone(); @@ -279,11 +277,6 @@ impl ValueData { // into_inner will consume the wrapped value and remove it from the hashmap hash.into_inner() } - // Accesing .object on borrow() seems to automatically dereference it, so we don't need the * - Self::Function(ref func) => match func.clone().into_inner() { - Function::NativeFunc(ref func) => func.object.clone(), - Function::RegularFunc(ref func) => func.object.clone(), - }, Self::Symbol(ref obj) => { let hash = obj.clone(); hash.into_inner() @@ -313,11 +306,6 @@ impl ValueData { ) { let obj: Option = match self { Self::Object(ref obj) => Some(obj.borrow_mut().deref_mut().clone()), - // Accesing .object on borrow() seems to automatically dereference it, so we don't need the * - Self::Function(ref func) => match func.borrow_mut().deref_mut() { - Function::NativeFunc(ref mut func) => Some(func.object.clone()), - Function::RegularFunc(ref mut func) => Some(func.object.clone()), - }, _ => None, }; @@ -473,47 +461,33 @@ impl ValueData { /// Set the field in the value /// Field could be a Symbol, so we need to accept a Value (not a string) pub fn set_field(&self, field: Value, val: Value) -> Value { - match *self { - Self::Object(ref obj) => { - if obj.borrow().kind == ObjectKind::Array { - if let Ok(num) = field.to_string().parse::() { - if num > 0 { - let len: i32 = from_value(self.get_field_slice("length")) - .expect("Could not convert argument to i32"); - if len < (num + 1) as i32 { - self.set_field_slice("length", to_value(num + 1)); - } + if let Self::Object(ref obj) = *self { + if obj.borrow().kind == ObjectKind::Array { + if let Ok(num) = field.to_string().parse::() { + if num > 0 { + let len: i32 = from_value(self.get_field_slice("length")) + .expect("Could not convert argument to i32"); + if len < (num + 1) as i32 { + self.set_field_slice("length", to_value(num + 1)); } } } - - // Symbols get saved into a different bucket to general properties - if field.is_symbol() { - obj.borrow_mut().set(field, val.clone()); - } else { - obj.borrow_mut() - .set(to_value(field.to_string()), val.clone()); - } } - Self::Function(ref func) => { - match *func.borrow_mut().deref_mut() { - Function::NativeFunc(ref mut f) => f - .object - .properties - .insert(field.to_string(), Property::default().value(val.clone())), - Function::RegularFunc(ref mut f) => f - .object - .properties - .insert(field.to_string(), Property::default().value(val.clone())), - }; + + // Symbols get saved into a different bucket to general properties + if field.is_symbol() { + obj.borrow_mut().set(field, val.clone()); + } else { + obj.borrow_mut() + .set(to_value(field.to_string()), val.clone()); } - _ => (), } + val } /// Set the field in the value - pub fn set_field_slice<'a>(&self, field: &'a str, val: Value) -> Value { + pub fn set_field_slice(&self, field: &str, val: Value) -> Value { // set_field used to accept strings, but now Symbols accept it needs to accept a value // So this function will now need to Box strings back into values (at least for now) let f = Gc::new(Self::String(field.to_string())); @@ -531,36 +505,22 @@ impl ValueData { } /// Set the kind of an object - pub fn set_kind(&self, kind: ObjectKind) -> ObjectKind { + pub fn set_kind(&self, kind: ObjectKind) { if let Self::Object(ref obj) = *self { - obj.borrow_mut().kind = kind.clone(); + (*obj.deref().borrow_mut()).kind = kind; } - kind } /// Set the property in the value pub fn set_prop(&self, field: String, prop: Property) -> Property { - match *self { - Self::Object(ref obj) => { - obj.borrow_mut().properties.insert(field, prop.clone()); - } - Self::Function(ref func) => { - match *func.borrow_mut().deref_mut() { - Function::NativeFunc(ref mut f) => { - f.object.properties.insert(field, prop.clone()) - } - Function::RegularFunc(ref mut f) => { - f.object.properties.insert(field, prop.clone()) - } - }; - } - _ => (), + if let Self::Object(ref obj) = *self { + obj.borrow_mut().properties.insert(field, prop.clone()); } prop } /// Set the property in the value - pub fn set_prop_slice<'t>(&self, field: &'t str, prop: Property) -> Property { + pub fn set_prop_slice(&self, field: &str, prop: Property) -> Property { self.set_prop(field.to_string(), prop) } @@ -573,6 +533,21 @@ impl ValueData { } } + /// Consume the function and return a Value + pub fn from_func(native_func: Function) -> Value { + // Object with Kind set to function + let mut new_func = crate::builtins::object::Object::function(); + // Get Length + let length = native_func.params.len(); + // Set [[Call]] internal slot + new_func.set_call(native_func); + // Wrap Object in GC'd Value + let new_func_val = to_value(new_func); + // Set length to parameters + new_func_val.set_field_slice("length", to_value(length)); + new_func_val + } + /// Convert from a JSON value to a JS value pub fn from_json(json: JSONValue) -> Self { match json { @@ -593,7 +568,7 @@ impl ValueData { "length".to_string(), Property::default().value(to_value(vs.len() as i32)), ); - Self::Object(GcCell::new(new_obj)) + Self::Object(Box::new(GcCell::new(new_obj))) } JSONValue::Object(obj) => { let mut new_obj = Object::default(); @@ -604,7 +579,7 @@ impl ValueData { ); } - Self::Object(GcCell::new(new_obj)) + Self::Object(Box::new(GcCell::new(new_obj))) } JSONValue::Null => Self::Null, } @@ -613,12 +588,9 @@ impl ValueData { /// Conversts the `Value` to `JSON`. pub fn to_json(&self) -> JSONValue { match *self { - ValueData::Null - | ValueData::Symbol(_) - | ValueData::Undefined - | ValueData::Function(_) => JSONValue::Null, - ValueData::Boolean(b) => JSONValue::Bool(b), - ValueData::Object(ref obj) => { + Self::Null | Self::Symbol(_) | Self::Undefined => JSONValue::Null, + Self::Boolean(b) => JSONValue::Bool(b), + Self::Object(ref obj) => { let new_obj = obj .borrow() .properties @@ -646,12 +618,11 @@ impl ValueData { Self::Symbol(_) => "symbol", Self::Null => "null", Self::Undefined => "undefined", - Self::Function(_) => "function", Self::Object(ref o) => { - if o.deref().borrow().get_internal_slot("call").is_null() { - "object" - } else { + if o.deref().borrow().is_callable() { "function" + } else { + "object" } } } @@ -880,19 +851,6 @@ impl Display for ValueData { ), Self::Object(_) => write!(f, "{}", log_string_from(self, true)), Self::Integer(v) => write!(f, "{}", v), - Self::Function(ref v) => match *v.borrow() { - Function::NativeFunc(_) => write!(f, "function() {{ [native code] }}"), - Function::RegularFunc(ref rf) => { - write!(f, "function{}(", if rf.args.is_empty() { "" } else { " " })?; - for (index, arg) in rf.args.iter().enumerate() { - write!(f, "{}", arg)?; - if index + 1 != rf.args.len() { - write!(f, ", ")?; - } - } - write!(f, ") {}", rf.node) - } - }, } } } @@ -1124,7 +1082,7 @@ impl FromValue for Vec { impl ToValue for Object { fn to_value(&self) -> Value { - Gc::new(ValueData::Object(GcCell::new(self.clone()))) + Gc::new(ValueData::Object(Box::new(GcCell::new(self.clone())))) } } @@ -1132,10 +1090,6 @@ impl FromValue for Object { fn from_value(v: Value) -> Result { match *v { ValueData::Object(ref obj) => Ok(obj.clone().into_inner()), - ValueData::Function(ref func) => Ok(match *func.borrow().deref() { - Function::NativeFunc(ref data) => data.object.clone(), - Function::RegularFunc(ref data) => data.object.clone(), - }), _ => Err("Value is not a valid object"), } } @@ -1182,25 +1136,6 @@ impl FromValue for Option { } } -impl ToValue for NativeFunctionData { - fn to_value(&self) -> Value { - Gc::new(ValueData::Function(Box::new(GcCell::new( - Function::NativeFunc(NativeFunction::new(*self)), - )))) - } -} -impl FromValue for NativeFunctionData { - fn from_value(v: Value) -> Result { - match *v { - ValueData::Function(ref func) => match *func.borrow() { - Function::NativeFunc(ref data) => Ok(data.data), - _ => Err("Value is not a native function"), - }, - _ => Err("Value is not a function"), - } - } -} - /// A utility function that just calls `FromValue::from_value` pub fn from_value(v: Value) -> Result { FromValue::from_value(v) diff --git a/boa/src/environment/function_environment_record.rs b/boa/src/environment/function_environment_record.rs index e18abcc327f..08e4c2355ee 100644 --- a/boa/src/environment/function_environment_record.rs +++ b/boa/src/environment/function_environment_record.rs @@ -41,7 +41,7 @@ pub struct FunctionEnvironmentRecord { /// If the value is "lexical", this is an ArrowFunction and does not have a local this value. pub this_binding_status: BindingStatus, /// The function object whose invocation caused this Environment Record to be created. - pub function_object: Value, + pub function: Value, /// If the associated function has super property accesses and is not an ArrowFunction, /// [[HomeObject]] is the object that the function is bound to as a method. /// The default value for [[HomeObject]] is undefined. diff --git a/boa/src/environment/lexical_environment.rs b/boa/src/environment/lexical_environment.rs index 77e1840fca7..8a784750d65 100644 --- a/boa/src/environment/lexical_environment.rs +++ b/boa/src/environment/lexical_environment.rs @@ -229,11 +229,10 @@ pub fn new_function_environment( new_target: Value, outer: Option, ) -> Environment { - debug_assert!(f.is_function()); debug_assert!(new_target.is_object() || new_target.is_undefined()); Gc::new(GcCell::new(Box::new(FunctionEnvironmentRecord { env_rec: HashMap::new(), - function_object: f, + function: f, this_binding_status: BindingStatus::Uninitialized, // hardcoding to unitialized for now until short functions are properly supported home_object: Gc::new(ValueData::Undefined), new_target, diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 6b2487b856f..f3ed0040fb4 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -6,17 +6,15 @@ mod tests; use crate::{ builtins::{ array, - function::{create_unmapped_arguments_object, Function, RegularFunction}, + function::{Function as FunctionObject, FunctionBody, ThisMode}, object::{ - internal_methods_trait::ObjectInternalMethods, ObjectKind, INSTANCE_PROTOTYPE, + internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE, }, property::Property, value::{from_value, to_value, ResultValue, Value, ValueData}, }, - environment::lexical_environment::{ - new_declarative_environment, new_function_environment, VariableScope, - }, + environment::lexical_environment::{new_declarative_environment, VariableScope}, realm::Realm, syntax::ast::{ constant::Const, @@ -24,10 +22,10 @@ use crate::{ op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp}, }, }; -use gc::{Gc, GcCell}; +use gc::Gc; use std::{ - borrow::Borrow, - ops::{Deref, DerefMut}, + borrow::{Borrow, BorrowMut}, + ops::Deref, }; /// An execution engine @@ -124,7 +122,7 @@ impl Executor for Interpreter { .get_field_slice(&val_field.borrow().to_string())) } Node::Call(ref callee, ref args) => { - let (this, func) = match callee.deref() { + let (mut this, func) = match callee.deref() { Node::GetConstField(ref obj, ref field) => { let mut obj = self.run(obj)?; if obj.get_type() != "object" || obj.get_type() != "symbol" { @@ -154,7 +152,7 @@ impl Executor for Interpreter { } // execute the function call itself - let fnct_result = self.call(&func, &this, v_args); + let fnct_result = self.call(&func, &mut this, &v_args); // unset the early return flag self.is_return = false; @@ -258,29 +256,68 @@ impl Executor for Interpreter { array::add_to_array_object(&array, &elements)?; Ok(array) } + // Node::FunctionDecl(ref name, ref args, ref expr) => { - let function = - Function::RegularFunc(RegularFunction::new(*expr.clone(), args.to_vec())); - let val = Gc::new(ValueData::Function(Box::new(GcCell::new(function)))); + // 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())); + + // Set the name and assign it in the current environment if name.is_some() { self.realm.environment.create_mutable_binding( name.clone().expect("No name was supplied"), false, VariableScope::Function, ); + self.realm.environment.initialize_binding( name.as_ref().expect("Could not get name as reference"), val.clone(), ) } + Ok(val) } Node::ArrowFunctionDecl(ref args, ref expr) => { - let function = - Function::RegularFunc(RegularFunction::new(*expr.clone(), args.to_vec())); - Ok(Gc::new(ValueData::Function(Box::new(GcCell::new( - function, - ))))) + // 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::Lexical, + ); + + 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())); + + Ok(val) } Node::BinOp(BinOp::Num(ref op), ref a, ref b) => { let v_r_a = self.run(a)?; @@ -331,10 +368,10 @@ impl Executor for Interpreter { })) } Node::BinOp(BinOp::Comp(ref op), ref a, ref b) => { - let v_r_a = self.run(a)?; - let v_r_b = self.run(b)?; - let v_a = v_r_a.borrow(); - let v_b = v_r_b.borrow(); + let mut v_r_a = self.run(a)?; + let mut v_r_b = self.run(b)?; + let mut v_a = v_r_a.borrow_mut(); + let mut v_b = v_r_b.borrow_mut(); Ok(to_value(match *op { CompOp::Equal if v_a.is_object() => v_r_a == v_r_b, CompOp::Equal => v_a == v_b, @@ -352,8 +389,8 @@ impl Executor for Interpreter { if !v_b.is_object() { panic!("TypeError: {} is not an Object.", v_b); } - let key = self.to_property_key(v_a); - self.has_property(v_b, &key) + let key = self.to_property_key(&mut v_a); + self.has_property(&mut v_b, &key) } })) } @@ -399,54 +436,19 @@ impl Executor for Interpreter { for arg in args.iter() { v_args.push(self.run(arg)?); } - let this = ValueData::new_obj(None); + let mut this = ValueData::new_obj(None); // Create a blank object, then set its __proto__ property to the [Constructor].prototype this.borrow().set_internal_slot( INSTANCE_PROTOTYPE, func_object.borrow().get_field_slice(PROTOTYPE), ); - let construct = func_object.get_internal_slot("construct"); - - match *construct { - ValueData::Function(ref inner_func) => match inner_func.clone().into_inner() { - Function::NativeFunc(ref ntv) => { - let func = ntv.data; - match func(&this, &v_args, self) { - Ok(_) => Ok(this), - Err(ref v) => Err(v.clone()), - } - } - Function::RegularFunc(ref data) => { - // Create new scope - let env = &mut self.realm.environment; - env.push(new_function_environment( - construct.clone(), - this, - Some(env.get_current_environment_ref().clone()), - )); - - for i in 0..data.args.len() { - let arg_expr = - data.args.get(i).expect("Could not get data argument"); - let name = match arg_expr.deref() { - Node::Local(ref n) => Some(n), - _ => None, - } - .expect("Could not get argument"); - let expr = v_args.get(i).expect("Could not get argument"); - env.create_mutable_binding( - name.clone(), - false, - VariableScope::Function, - ); - env.initialize_binding(name, expr.to_owned()); - } - let result = self.run(&data.node); - self.realm.environment.pop(); - result - } - }, + match *(func_object.borrow()).deref() { + ValueData::Object(ref o) => (*o.deref().clone().borrow_mut()) + .construct + .as_ref() + .unwrap() + .construct(&mut func_object.clone(), &v_args, self, &mut this), _ => Ok(Gc::new(ValueData::Undefined)), } } @@ -542,11 +544,17 @@ impl Executor for Interpreter { Ok(to_value(match *val { ValueData::Undefined => "undefined", ValueData::Symbol(_) => "symbol", - ValueData::Null | ValueData::Object(_) => "object", + ValueData::Null => "object", ValueData::Boolean(_) => "boolean", ValueData::Rational(_) | ValueData::Integer(_) => "number", ValueData::String(_) => "string", - ValueData::Function(_) => "function", + ValueData::Object(ref o) => { + if o.deref().borrow().is_callable() { + "function" + } else { + "object" + } + } })) } Node::StatementList(ref list) => { @@ -591,88 +599,25 @@ impl Interpreter { } /// https://tc39.es/ecma262/#sec-call - pub(crate) fn call(&mut self, f: &Value, v: &Value, arguments_list: Vec) -> ResultValue { + pub(crate) fn call( + &mut self, + f: &Value, + this: &mut Value, + arguments_list: &[Value], + ) -> ResultValue { // All functions should be objects, and eventually will be. // During this transition call will support both native functions and function objects match (*f).deref() { - ValueData::Object(ref obj) => { - let func: Value = obj.borrow_mut().deref_mut().get_internal_slot("call"); - if !func.is_undefined() { - return self.call(&func, v, arguments_list); - } - // TODO: error object should be here - Err(Gc::new(ValueData::Undefined)) - } - ValueData::Function(ref inner_func) => match *inner_func.deref().borrow() { - Function::NativeFunc(ref ntv) => { - let func = ntv.data; - func(v, &arguments_list, self) - } - Function::RegularFunc(ref data) => { - let env = &mut self.realm.environment; - // New target (second argument) is only needed for constructors, just pass undefined - let undefined = Gc::new(ValueData::Undefined); - env.push(new_function_environment( - f.clone(), - undefined, - Some(env.get_current_environment_ref().clone()), - )); - for i in 0..data.args.len() { - let arg_expr = data.args.get(i).expect("Could not get data argument"); - match arg_expr.deref() { - Node::Local(ref name) => { - let expr: &Value = - arguments_list.get(i).expect("Could not get argument"); - self.realm.environment.create_mutable_binding( - name.clone(), - false, - VariableScope::Function, - ); - self.realm - .environment - .initialize_binding(name, expr.clone()); - } - Node::Spread(ref expr) => { - if let Node::Local(ref name) = expr.deref() { - let array = array::new_array(self)?; - array::add_to_array_object(&array, &arguments_list[i..])?; - - self.realm.environment.create_mutable_binding( - name.clone(), - false, - VariableScope::Function, - ); - self.realm.environment.initialize_binding(name, array); - } else { - panic!("Unsupported function argument declaration") - } - } - _ => panic!("Unsupported function argument declaration"), - } - } - - // Add arguments object - let arguments_obj = create_unmapped_arguments_object(arguments_list); - self.realm.environment.create_mutable_binding( - "arguments".to_string(), - false, - VariableScope::Function, - ); - self.realm - .environment - .initialize_binding("arguments", arguments_obj); - - let result = self.run(&data.node); - self.realm.environment.pop(); - result - } + ValueData::Object(ref obj) => match (*obj).deref().borrow().call { + Some(ref func) => func.call(&mut f.clone(), arguments_list, self, this), + None => panic!("Expected function"), }, _ => Err(Gc::new(ValueData::Undefined)), } } /// https://tc39.es/ecma262/#sec-ordinarytoprimitive - fn ordinary_to_primitive(&mut self, o: &Value, hint: &str) -> Value { + fn ordinary_to_primitive(&mut self, o: &mut Value, hint: &str) -> Value { debug_assert!(o.get_type() == "object"); debug_assert!(hint == "string" || hint == "number"); let method_names: Vec<&str> = if hint == "string" { @@ -683,7 +628,7 @@ impl Interpreter { for name in method_names.iter() { let method: Value = o.get_field_slice(name); if method.is_function() { - let result = self.call(&method, &o, vec![]); + let result = self.call(&method, o, &[]); match result { Ok(val) => { if val.is_object() { @@ -704,7 +649,7 @@ impl Interpreter { /// The abstract operation ToPrimitive takes an input argument and an optional argument PreferredType. /// https://tc39.es/ecma262/#sec-toprimitive #[allow(clippy::wrong_self_convention)] - pub fn to_primitive(&mut self, input: &Value, preferred_type: Option<&str>) -> Value { + pub fn to_primitive(&mut self, input: &mut Value, preferred_type: Option<&str>) -> Value { let mut hint: &str; match (*input).deref() { ValueData::Object(_) => { @@ -723,7 +668,7 @@ impl Interpreter { hint = "number"; }; - self.ordinary_to_primitive(&input, hint) + self.ordinary_to_primitive(input, hint) } _ => input.clone(), } @@ -740,7 +685,7 @@ impl Interpreter { ValueData::Integer(ref num) => to_value(num.to_string()), ValueData::String(ref string) => to_value(string.clone()), ValueData::Object(_) => { - let prim_value = self.to_primitive(value, Some("string")); + let prim_value = self.to_primitive(&mut (value.clone()), Some("string")); self.to_string(&prim_value) } _ => to_value("function(){...}"), @@ -750,7 +695,7 @@ impl Interpreter { /// The abstract operation ToPropertyKey takes argument argument. It converts argument to a value that can be used as a property key. /// https://tc39.es/ecma262/#sec-topropertykey #[allow(clippy::wrong_self_convention)] - pub fn to_property_key(&mut self, value: &Value) -> Value { + pub fn to_property_key(&mut self, value: &mut Value) -> Value { let key = self.to_primitive(value, Some("string")); if key.is_symbol() { key @@ -760,7 +705,7 @@ impl Interpreter { } /// https://tc39.es/ecma262/#sec-hasproperty - pub fn has_property(&self, obj: &Value, key: &Value) -> bool { + pub fn has_property(&self, obj: &mut Value, key: &Value) -> bool { if let Some(obj) = obj.as_object() { if !Property::is_property_key(key) { false @@ -777,10 +722,9 @@ impl Interpreter { #[allow(clippy::wrong_self_convention)] pub fn to_object(&mut self, value: &Value) -> ResultValue { match *value.deref().borrow() { - ValueData::Undefined - | ValueData::Function(_) - | ValueData::Integer(_) - | ValueData::Null => Err(Gc::new(ValueData::Undefined)), + ValueData::Undefined | ValueData::Integer(_) | ValueData::Null => { + Err(Gc::new(ValueData::Undefined)) + } ValueData::Boolean(_) => { let proto = self .realm @@ -825,7 +769,7 @@ impl Interpreter { ValueData::Integer(ref num) => num.to_string(), ValueData::String(ref string) => string.clone(), ValueData::Object(_) => { - let prim_value = self.to_primitive(value, Some("string")); + let prim_value = self.to_primitive(&mut (value.clone()), Some("string")); self.to_string(&prim_value).to_string() } _ => String::from("undefined"), @@ -846,7 +790,7 @@ impl Interpreter { ValueData::Integer(num) => f64::from(num), ValueData::String(ref string) => string.parse::().unwrap(), ValueData::Object(_) => { - let prim_value = self.to_primitive(value, Some("number")); + let prim_value = self.to_primitive(&mut (value.clone()), Some("number")); self.to_string(&prim_value) .to_string() .parse::() diff --git a/boa/src/lib.rs b/boa/src/lib.rs index 96a0b7a1fef..8f9bfda549d 100644 --- a/boa/src/lib.rs +++ b/boa/src/lib.rs @@ -21,7 +21,7 @@ rust_2018_compatibility, rust_2018_idioms, future_incompatible, - nonstandard_style + nonstandard_style, )] #![warn(clippy::perf, clippy::single_match_else, clippy::dbg_macro)] #![allow( diff --git a/boa/src/realm.rs b/boa/src/realm.rs index edc6a6bb2de..801a0f97ab2 100644 --- a/boa/src/realm.rs +++ b/boa/src/realm.rs @@ -20,7 +20,8 @@ use crate::{ use gc::{Gc, GcCell}; use std::collections::{hash_map::HashMap, hash_set::HashSet}; -/// Representation of a Realm. +/// Representation of a Realm. +/// /// In the specification these are called Realm Records. #[derive(Debug)] pub struct Realm { @@ -54,14 +55,13 @@ impl Realm { fn create_instrinsics(&self) { let global = &self.global_obj; // Create intrinsics, add global objects here - function::init(global); - global.set_field_slice("Array", array::create_constructor(global)); global.set_field_slice("Boolean", boolean::create_constructor(global)); global.set_field_slice("JSON", json::create_constructor(global)); global.set_field_slice("Math", math::create_constructor(global)); global.set_field_slice("Number", number::create_constructor(global)); global.set_field_slice("Object", object::create_constructor(global)); + global.set_field_slice("Function", function::create_constructor(global)); global.set_field_slice("RegExp", regexp::create_constructor(global)); global.set_field_slice("String", string::create_constructor(global)); global.set_field_slice("Symbol", symbol::create_constructor(global)); @@ -70,8 +70,12 @@ impl Realm { /// Utility to add a function to the global object pub fn register_global_func(self, func_name: &str, func: NativeFunctionData) -> Self { + let func = crate::builtins::function::Function::create_builtin( + vec![], + crate::builtins::function::FunctionBody::BuiltIn(func), + ); self.global_obj - .set_field(func_name.to_value(), func.to_value()); + .set_field(func_name.to_value(), ValueData::from_func(func)); self } diff --git a/boa/src/syntax/ast/constant.rs b/boa/src/syntax/ast/constant.rs index d17903d9eac..683a3a77d09 100644 --- a/boa/src/syntax/ast/constant.rs +++ b/boa/src/syntax/ast/constant.rs @@ -103,19 +103,19 @@ pub enum Const { impl From<&str> for Const { fn from(s: &str) -> Self { - Const::String(s.into()) + Self::String(s.into()) } } impl From<&String> for Const { fn from(s: &String) -> Self { - Const::String(s.clone()) + Self::String(s.clone()) } } impl From for Const { fn from(s: String) -> Self { - Const::String(s) + Self::String(s) } } diff --git a/boa/src/syntax/ast/node.rs b/boa/src/syntax/ast/node.rs index bcda24e21b9..f83bcbee28a 100644 --- a/boa/src/syntax/ast/node.rs +++ b/boa/src/syntax/ast/node.rs @@ -532,7 +532,7 @@ impl Node { /// Creates an `ArrayDecl` AST node. pub fn array_decl(nodes: N) -> Self where - N: Into>, + N: Into>, { Self::ArrayDecl(nodes.into()) } @@ -541,7 +541,7 @@ impl Node { pub fn arrow_function_decl(params: P, body: B) -> Self where P: Into>, - B: Into>, + B: Into>, { Self::ArrowFunctionDecl(params.into(), body.into()) } @@ -549,8 +549,8 @@ impl Node { /// Creates an `Assign` AST node. pub fn assign(lhs: L, rhs: R) -> Self where - L: Into>, - R: Into>, + L: Into>, + R: Into>, { Self::Assign(lhs.into(), rhs.into()) } @@ -559,8 +559,8 @@ impl Node { pub fn bin_op(op: O, lhs: L, rhs: R) -> Self where O: Into, - L: Into>, - R: Into>, + L: Into>, + R: Into>, { Self::BinOp(op.into(), lhs.into(), rhs.into()) } @@ -568,7 +568,7 @@ impl Node { /// Creates a `Block` AST node. pub fn block(nodes: N) -> Self where - N: Into>, + N: Into>, { Self::Block(nodes.into()) } @@ -585,8 +585,8 @@ impl Node { /// Creates a `Call` AST node. pub fn call(function: F, params: P) -> Self where - F: Into>, - P: Into>, + F: Into>, + P: Into>, { Self::Call(function.into(), params.into()) } @@ -594,9 +594,9 @@ impl Node { /// Creates a `ConditionalOp` AST node. pub fn conditional_op(condition: C, if_true: T, if_false: F) -> Self where - C: Into>, - T: Into>, - F: Into>, + C: Into>, + T: Into>, + F: Into>, { Self::ConditionalOp(condition.into(), if_true.into(), if_false.into()) } @@ -612,7 +612,7 @@ impl Node { /// Creates a `ConstDecl` AST node. pub fn const_decl(decl: D) -> Self where - D: Into>, + D: Into>, { Self::ConstDecl(decl.into()) } @@ -629,8 +629,8 @@ impl Node { /// Creates a `DoWhileLoop` AST node. pub fn do_while_loop(body: B, condition: C) -> Self where - B: Into>, - C: Into>, + B: Into>, + C: Into>, { Self::DoWhileLoop(body.into(), condition.into()) } @@ -641,7 +641,7 @@ impl Node { N: Into, ON: Into>, P: Into>, - B: Into>, + B: Into>, { Self::FunctionDecl(name.into().map(N::into), params.into(), body.into()) } @@ -649,7 +649,7 @@ impl Node { /// Creates a `GetConstField` AST node. pub fn get_const_field(value: V, label: L) -> Self where - V: Into>, + V: Into>, L: Into, { Self::GetConstField(value.into(), label.into()) @@ -658,8 +658,8 @@ impl Node { /// Creates a `GetField` AST node. pub fn get_field(value: V, field: F) -> Self where - V: Into>, - F: Into>, + V: Into>, + F: Into>, { Self::GetField(value.into(), field.into()) } @@ -670,10 +670,10 @@ impl Node { OI: Into>, OC: Into>, OS: Into>, - I: Into>, - C: Into>, - S: Into>, - B: Into>, + I: Into>, + C: Into>, + S: Into>, + B: Into>, { Self::ForLoop( init.into().map(I::into), @@ -686,9 +686,9 @@ impl Node { /// Creates an `If` AST node. pub fn if_node(condition: C, body: B, else_node: OE) -> Self where - C: Into>, - B: Into>, - E: Into>, + C: Into>, + B: Into>, + E: Into>, OE: Into>, { Self::If(condition.into(), body.into(), else_node.into().map(E::into)) @@ -697,7 +697,7 @@ impl Node { /// Creates a `LetDecl` AST node. pub fn let_decl(init: I) -> Self where - I: Into)>>, + I: Into)>>, { Self::LetDecl(init.into()) } @@ -713,7 +713,7 @@ impl Node { /// Creates a `New` AST node. pub fn new(node: N) -> Self where - N: Into>, + N: Into>, { Self::New(node.into()) } @@ -729,7 +729,7 @@ impl Node { /// Creates a `Return` AST node. pub fn return_node(expr: OE) -> Self where - E: Into>, + E: Into>, OE: Into>, { Self::Return(expr.into().map(E::into)) @@ -738,10 +738,10 @@ impl Node { /// Creates a `Switch` AST node. pub fn switch(val: V, cases: C, default: OD) -> Self where - V: Into>, - C: Into)>>, + V: Into>, + C: Into)>>, OD: Into>, - D: Into>, + D: Into>, { Self::Switch(val.into(), cases.into(), default.into().map(D::into)) } @@ -749,7 +749,7 @@ impl Node { /// Creates a `Spread` AST node. pub fn spread(val: V) -> Self where - V: Into>, + V: Into>, { Self::Spread(val.into()) } @@ -757,7 +757,7 @@ impl Node { /// Creates a `StatementList` AST node. pub fn statement_list(list: L) -> Self where - L: Into>, + L: Into>, { Self::StatementList(list.into()) } @@ -765,7 +765,7 @@ impl Node { /// Creates a `Throw` AST node. pub fn throw(val: V) -> Self where - V: Into>, + V: Into>, { Self::Throw(val.into()) } @@ -773,7 +773,7 @@ impl Node { /// Creates a `TypeOf` AST node. pub fn type_of(expr: E) -> Self where - E: Into>, + E: Into>, { Self::TypeOf(expr.into()) } @@ -781,13 +781,13 @@ impl Node { /// Creates a `Try` AST node. pub fn try_node(try_node: T, catch: OC, param: OP, finally: OF) -> Self where - T: Into>, + T: Into>, OC: Into>, OP: Into>, OF: Into>, - C: Into>, - P: Into>, - F: Into>, + C: Into>, + P: Into>, + F: Into>, { let catch = catch.into().map(C::into); let finally = finally.into().map(F::into); @@ -808,7 +808,7 @@ impl Node { /// Creates a `UnaryOp` AST node. pub fn unary_op(op: UnaryOp, val: V) -> Self where - V: Into>, + V: Into>, { Self::UnaryOp(op, val.into()) } @@ -816,7 +816,7 @@ impl Node { /// Creates a `VarDecl` AST node. pub fn var_decl(init: I) -> Self where - I: Into)>>, + I: Into)>>, { Self::VarDecl(init.into()) } @@ -824,8 +824,8 @@ impl Node { /// Creates a `WhileLoop` AST node. pub fn while_loop(condition: C, body: B) -> Self where - C: Into>, - B: Into>, + C: Into>, + B: Into>, { Self::WhileLoop(condition.into(), body.into()) } diff --git a/boa/src/syntax/lexer/tests.rs b/boa/src/syntax/lexer/tests.rs index 907a3f877b7..ce750df2a63 100644 --- a/boa/src/syntax/lexer/tests.rs +++ b/boa/src/syntax/lexer/tests.rs @@ -420,7 +420,10 @@ fn hexadecimal_edge_case() { TokenKind::Identifier(String::from("ff")) ); - assert_eq!(lexer.tokens[3].kind, TokenKind::numeric_literal(0xffffff)); + assert_eq!( + lexer.tokens[3].kind, + TokenKind::numeric_literal(0x00ff_ffff) + ); } #[test]