From 86052d6d75d7ac321e9b6b83dbf3bf2f2377437f Mon Sep 17 00:00:00 2001 From: Iban Eguia Date: Wed, 26 Feb 2020 23:33:59 +0100 Subject: [PATCH] Moved test modules to their own files (#258) --- boa/src/builtins/{array.rs => array/mod.rs} | 640 +--------------- boa/src/builtins/array/tests.rs | 633 ++++++++++++++++ .../builtins/{boolean.rs => boolean/mod.rs} | 86 +-- boa/src/builtins/boolean/tests.rs | 79 ++ .../builtins/{function.rs => function/mod.rs} | 32 +- boa/src/builtins/function/tests.rs | 25 + boa/src/builtins/number.rs | 376 --------- boa/src/builtins/number/mod.rs | 162 ++++ boa/src/builtins/number/tests.rs | 213 ++++++ boa/src/builtins/{regexp.rs => regexp/mod.rs} | 114 +-- boa/src/builtins/regexp/tests.rs | 111 +++ boa/src/builtins/{string.rs => string/mod.rs} | 312 +------- boa/src/builtins/string/tests.rs | 305 ++++++++ boa/src/builtins/{symbol.rs => symbol/mod.rs} | 42 +- boa/src/builtins/symbol/tests.rs | 35 + boa/src/builtins/{value.rs => value/mod.rs} | 55 +- boa/src/builtins/value/tests.rs | 48 ++ boa/src/{exec.rs => exec/mod.rs} | 243 +----- boa/src/exec/tests.rs | 236 ++++++ boa/src/syntax/{lexer.rs => lexer/mod.rs} | 503 +----------- boa/src/syntax/lexer/tests.rs | 496 ++++++++++++ boa/src/syntax/{parser.rs => parser/mod.rs} | 713 +----------------- boa/src/syntax/parser/tests.rs | 691 +++++++++++++++++ boa_cli/Cargo.toml | 2 +- 24 files changed, 3073 insertions(+), 3079 deletions(-) rename boa/src/builtins/{array.rs => array/mod.rs} (55%) create mode 100644 boa/src/builtins/array/tests.rs rename boa/src/builtins/{boolean.rs => boolean/mod.rs} (53%) create mode 100644 boa/src/builtins/boolean/tests.rs rename boa/src/builtins/{function.rs => function/mod.rs} (83%) create mode 100644 boa/src/builtins/function/tests.rs delete mode 100644 boa/src/builtins/number.rs create mode 100644 boa/src/builtins/number/mod.rs create mode 100644 boa/src/builtins/number/tests.rs rename boa/src/builtins/{regexp.rs => regexp/mod.rs} (75%) create mode 100644 boa/src/builtins/regexp/tests.rs rename boa/src/builtins/{string.rs => string/mod.rs} (78%) create mode 100644 boa/src/builtins/string/tests.rs rename boa/src/builtins/{symbol.rs => symbol/mod.rs} (74%) create mode 100644 boa/src/builtins/symbol/tests.rs rename boa/src/builtins/{value.rs => value/mod.rs} (96%) create mode 100644 boa/src/builtins/value/tests.rs rename boa/src/{exec.rs => exec/mod.rs} (83%) create mode 100644 boa/src/exec/tests.rs rename boa/src/syntax/{lexer.rs => lexer/mod.rs} (61%) create mode 100644 boa/src/syntax/lexer/tests.rs rename boa/src/syntax/{parser.rs => parser/mod.rs} (64%) create mode 100644 boa/src/syntax/parser/tests.rs diff --git a/boa/src/builtins/array.rs b/boa/src/builtins/array/mod.rs similarity index 55% rename from boa/src/builtins/array.rs rename to boa/src/builtins/array/mod.rs index 5c4c540955..1dcd95761a 100644 --- a/boa/src/builtins/array.rs +++ b/boa/src/builtins/array/mod.rs @@ -1,3 +1,6 @@ +#[cfg(test)] +mod tests; + use crate::{ builtins::{ function::NativeFunctionData, @@ -748,640 +751,3 @@ pub fn create_constructor(global: &Value) -> Value { array_prototype.set_field_slice("constructor", array.clone()); array } - -#[cfg(test)] -mod tests { - use crate::exec::Executor; - use crate::forward; - use crate::realm::Realm; - - #[test] - fn is_array() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var empty = []; - var new_arr = new Array(); - var many = ["a", "b", "c"]; - "#; - forward(&mut engine, init); - assert_eq!(forward(&mut engine, "Array.isArray(empty)"), "true"); - assert_eq!(forward(&mut engine, "Array.isArray(new_arr)"), "true"); - assert_eq!(forward(&mut engine, "Array.isArray(many)"), "true"); - assert_eq!(forward(&mut engine, "Array.isArray([1, 2, 3])"), "true"); - assert_eq!(forward(&mut engine, "Array.isArray([])"), "true"); - assert_eq!(forward(&mut engine, "Array.isArray({})"), "false"); - // assert_eq!(forward(&mut engine, "Array.isArray(new Array)"), "true"); - assert_eq!(forward(&mut engine, "Array.isArray()"), "false"); - assert_eq!( - forward(&mut engine, "Array.isArray({ constructor: Array })"), - "false" - ); - assert_eq!( - forward( - &mut engine, - "Array.isArray({ push: Array.prototype.push, concat: Array.prototype.concat })" - ), - "false" - ); - assert_eq!(forward(&mut engine, "Array.isArray(17)"), "false"); - assert_eq!( - forward(&mut engine, "Array.isArray({ __proto__: Array.prototype })"), - "false" - ); - assert_eq!( - forward(&mut engine, "Array.isArray({ length: 0 })"), - "false" - ); - } - - #[test] - fn concat() { - //TODO: array display formatter - // let realm = Realm::create(); - // let mut engine = Executor::new(realm); - // let init = r#" - // var empty = new Array(); - // var one = new Array(1); - // "#; - // forward(&mut engine, init); - // // Empty ++ Empty - // let ee = forward(&mut engine, "empty.concat(empty)"); - // assert_eq!(ee, String::from("[]")); - // // Empty ++ NonEmpty - // let en = forward(&mut engine, "empty.concat(one)"); - // assert_eq!(en, String::from("[a]")); - // // NonEmpty ++ Empty - // let ne = forward(&mut engine, "one.concat(empty)"); - // assert_eq!(ne, String::from("a.b.c")); - // // NonEmpty ++ NonEmpty - // let nn = forward(&mut engine, "one.concat(one)"); - // assert_eq!(nn, String::from("a.b.c")); - } - - #[test] - fn join() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var empty = [ ]; - var one = ["a"]; - var many = ["a", "b", "c"]; - "#; - forward(&mut engine, init); - // Empty - let empty = forward(&mut engine, "empty.join('.')"); - assert_eq!(empty, String::from("")); - // One - let one = forward(&mut engine, "one.join('.')"); - assert_eq!(one, String::from("a")); - // Many - let many = forward(&mut engine, "many.join('.')"); - assert_eq!(many, String::from("a.b.c")); - } - - #[test] - fn to_string() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var empty = [ ]; - var one = ["a"]; - var many = ["a", "b", "c"]; - "#; - forward(&mut engine, init); - // Empty - let empty = forward(&mut engine, "empty.toString()"); - assert_eq!(empty, String::from("")); - // One - let one = forward(&mut engine, "one.toString()"); - assert_eq!(one, String::from("a")); - // Many - let many = forward(&mut engine, "many.toString()"); - assert_eq!(many, String::from("a,b,c")); - } - - #[test] - fn every() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - // taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every - let init = r#" - var empty = []; - - var array = [11, 23, 45]; - function callback(element) { - return element > 10; - } - function callback2(element) { - return element < 10; - } - - var appendArray = [1,2,3,4]; - function appendingCallback(elem,index,arr) { - arr.push('new'); - return elem !== "new"; - } - - var delArray = [1,2,3,4]; - function deletingCallback(elem,index,arr) { - arr.pop() - return elem < 3; - } - "#; - forward(&mut engine, init); - let result = forward(&mut engine, "array.every(callback);"); - assert_eq!(result, "true"); - - let result = forward(&mut engine, "empty.every(callback);"); - assert_eq!(result, "true"); - - let result = forward(&mut engine, "array.every(callback2);"); - assert_eq!(result, "false"); - - let result = forward(&mut engine, "appendArray.every(appendingCallback);"); - assert_eq!(result, "true"); - - let result = forward(&mut engine, "delArray.every(deletingCallback);"); - assert_eq!(result, "true"); - } - - #[test] - fn find() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - function comp(a) { - return a == "a"; - } - var many = ["a", "b", "c"]; - "#; - forward(&mut engine, init); - let found = forward(&mut engine, "many.find(comp)"); - assert_eq!(found, String::from("a")); - } - - #[test] - fn find_index() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - - let code = r#" - function comp(item) { - return item == 2; - } - var many = [1, 2, 3]; - var empty = []; - var missing = [4, 5, 6]; - "#; - - forward(&mut engine, code); - - let many = forward(&mut engine, "many.findIndex(comp)"); - assert_eq!(many, String::from("1")); - - let empty = forward(&mut engine, "empty.findIndex(comp)"); - assert_eq!(empty, String::from("-1")); - - let missing = forward(&mut engine, "missing.findIndex(comp)"); - assert_eq!(missing, String::from("-1")); - } - - #[test] - fn push() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var arr = [1, 2]; - "#; - forward(&mut engine, init); - - assert_eq!(forward(&mut engine, "arr.push()"), "2"); - assert_eq!(forward(&mut engine, "arr.push(3, 4)"), "4"); - assert_eq!(forward(&mut engine, "arr[2]"), "3"); - assert_eq!(forward(&mut engine, "arr[3]"), "4"); - } - - #[test] - fn pop() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var empty = [ ]; - var one = [1]; - var many = [1, 2, 3, 4]; - "#; - forward(&mut engine, init); - - assert_eq!( - forward(&mut engine, "empty.pop()"), - String::from("undefined") - ); - assert_eq!(forward(&mut engine, "one.pop()"), "1"); - assert_eq!(forward(&mut engine, "one.length"), "0"); - assert_eq!(forward(&mut engine, "many.pop()"), "4"); - assert_eq!(forward(&mut engine, "many[0]"), "1"); - assert_eq!(forward(&mut engine, "many.length"), "3"); - } - - #[test] - fn shift() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var empty = [ ]; - var one = [1]; - var many = [1, 2, 3, 4]; - "#; - forward(&mut engine, init); - - assert_eq!( - forward(&mut engine, "empty.shift()"), - String::from("undefined") - ); - assert_eq!(forward(&mut engine, "one.shift()"), "1"); - assert_eq!(forward(&mut engine, "one.length"), "0"); - assert_eq!(forward(&mut engine, "many.shift()"), "1"); - assert_eq!(forward(&mut engine, "many[0]"), "2"); - assert_eq!(forward(&mut engine, "many.length"), "3"); - } - - #[test] - fn unshift() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var arr = [3, 4]; - "#; - forward(&mut engine, init); - - assert_eq!(forward(&mut engine, "arr.unshift()"), "2"); - assert_eq!(forward(&mut engine, "arr.unshift(1, 2)"), "4"); - assert_eq!(forward(&mut engine, "arr[0]"), "1"); - assert_eq!(forward(&mut engine, "arr[1]"), "2"); - } - - #[test] - fn reverse() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var arr = [1, 2]; - var reversed = arr.reverse(); - "#; - forward(&mut engine, init); - assert_eq!(forward(&mut engine, "reversed[0]"), "2"); - assert_eq!(forward(&mut engine, "reversed[1]"), "1"); - assert_eq!(forward(&mut engine, "arr[0]"), "2"); - assert_eq!(forward(&mut engine, "arr[1]"), "1"); - } - - #[test] - fn index_of() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var empty = [ ]; - var one = ["a"]; - var many = ["a", "b", "c"]; - var duplicates = ["a", "b", "c", "a", "b"]; - "#; - forward(&mut engine, init); - - // Empty - let empty = forward(&mut engine, "empty.indexOf('a')"); - assert_eq!(empty, String::from("-1")); - - // One - let one = forward(&mut engine, "one.indexOf('a')"); - assert_eq!(one, String::from("0")); - // Missing from one - let missing_from_one = forward(&mut engine, "one.indexOf('b')"); - assert_eq!(missing_from_one, String::from("-1")); - - // First in many - let first_in_many = forward(&mut engine, "many.indexOf('a')"); - assert_eq!(first_in_many, String::from("0")); - // Second in many - let second_in_many = forward(&mut engine, "many.indexOf('b')"); - assert_eq!(second_in_many, String::from("1")); - - // First in duplicates - let first_in_many = forward(&mut engine, "duplicates.indexOf('a')"); - assert_eq!(first_in_many, String::from("0")); - // Second in duplicates - let second_in_many = forward(&mut engine, "duplicates.indexOf('b')"); - assert_eq!(second_in_many, String::from("1")); - - // Positive fromIndex greater than array length - let fromindex_greater_than_length = forward(&mut engine, "one.indexOf('a', 2)"); - assert_eq!(fromindex_greater_than_length, String::from("-1")); - // Positive fromIndex missed match - let fromindex_misses_match = forward(&mut engine, "many.indexOf('a', 1)"); - assert_eq!(fromindex_misses_match, String::from("-1")); - // Positive fromIndex matched - let fromindex_matches = forward(&mut engine, "many.indexOf('b', 1)"); - assert_eq!(fromindex_matches, String::from("1")); - // Positive fromIndex with duplicates - let first_in_many = forward(&mut engine, "duplicates.indexOf('a', 1)"); - assert_eq!(first_in_many, String::from("3")); - - // Negative fromIndex greater than array length - let fromindex_greater_than_length = forward(&mut engine, "one.indexOf('a', -2)"); - assert_eq!(fromindex_greater_than_length, String::from("0")); - // Negative fromIndex missed match - let fromindex_misses_match = forward(&mut engine, "many.indexOf('b', -1)"); - assert_eq!(fromindex_misses_match, String::from("-1")); - // Negative fromIndex matched - let fromindex_matches = forward(&mut engine, "many.indexOf('c', -1)"); - assert_eq!(fromindex_matches, String::from("2")); - // Negative fromIndex with duplicates - let second_in_many = forward(&mut engine, "duplicates.indexOf('b', -2)"); - assert_eq!(second_in_many, String::from("4")); - } - - #[test] - fn last_index_of() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var empty = [ ]; - var one = ["a"]; - var many = ["a", "b", "c"]; - var duplicates = ["a", "b", "c", "a", "b"]; - "#; - forward(&mut engine, init); - - // Empty - let empty = forward(&mut engine, "empty.lastIndexOf('a')"); - assert_eq!(empty, String::from("-1")); - - // One - let one = forward(&mut engine, "one.lastIndexOf('a')"); - assert_eq!(one, String::from("0")); - // Missing from one - let missing_from_one = forward(&mut engine, "one.lastIndexOf('b')"); - assert_eq!(missing_from_one, String::from("-1")); - - // First in many - let first_in_many = forward(&mut engine, "many.lastIndexOf('a')"); - assert_eq!(first_in_many, String::from("0")); - // Second in many - let second_in_many = forward(&mut engine, "many.lastIndexOf('b')"); - assert_eq!(second_in_many, String::from("1")); - - // 4th in duplicates - let first_in_many = forward(&mut engine, "duplicates.lastIndexOf('a')"); - assert_eq!(first_in_many, String::from("3")); - // 5th in duplicates - let second_in_many = forward(&mut engine, "duplicates.lastIndexOf('b')"); - assert_eq!(second_in_many, String::from("4")); - - // Positive fromIndex greater than array length - let fromindex_greater_than_length = forward(&mut engine, "one.lastIndexOf('a', 2)"); - assert_eq!(fromindex_greater_than_length, String::from("0")); - // Positive fromIndex missed match - let fromindex_misses_match = forward(&mut engine, "many.lastIndexOf('c', 1)"); - assert_eq!(fromindex_misses_match, String::from("-1")); - // Positive fromIndex matched - let fromindex_matches = forward(&mut engine, "many.lastIndexOf('b', 1)"); - assert_eq!(fromindex_matches, String::from("1")); - // Positive fromIndex with duplicates - let first_in_many = forward(&mut engine, "duplicates.lastIndexOf('a', 1)"); - assert_eq!(first_in_many, String::from("0")); - - // Negative fromIndex greater than array length - let fromindex_greater_than_length = forward(&mut engine, "one.lastIndexOf('a', -2)"); - assert_eq!(fromindex_greater_than_length, String::from("-1")); - // Negative fromIndex missed match - let fromindex_misses_match = forward(&mut engine, "many.lastIndexOf('c', -2)"); - assert_eq!(fromindex_misses_match, String::from("-1")); - // Negative fromIndex matched - let fromindex_matches = forward(&mut engine, "many.lastIndexOf('c', -1)"); - assert_eq!(fromindex_matches, String::from("2")); - // Negative fromIndex with duplicates - let second_in_many = forward(&mut engine, "duplicates.lastIndexOf('b', -2)"); - assert_eq!(second_in_many, String::from("1")); - } - - #[test] - fn fill() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - - forward(&mut engine, "var a = [1, 2, 3];"); - assert_eq!( - forward(&mut engine, "a.fill(4).join()"), - String::from("4,4,4") - ); - // make sure the array is modified - assert_eq!(forward(&mut engine, "a.join()"), String::from("4,4,4")); - - forward(&mut engine, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut engine, "a.fill(4, '1').join()"), - String::from("1,4,4") - ); - - forward(&mut engine, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut engine, "a.fill(4, 1, 2).join()"), - String::from("1,4,3") - ); - - forward(&mut engine, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut engine, "a.fill(4, 1, 1).join()"), - String::from("1,2,3") - ); - - forward(&mut engine, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut engine, "a.fill(4, 3, 3).join()"), - String::from("1,2,3") - ); - - forward(&mut engine, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut engine, "a.fill(4, -3, -2).join()"), - String::from("4,2,3") - ); - - // TODO: uncomment when NaN support is added - // forward(&mut engine, "a = [1, 2, 3];"); - // assert_eq!( - // forward(&mut engine, "a.fill(4, NaN, NaN).join()"), - // String::from("1,2,3") - // ); - - forward(&mut engine, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut engine, "a.fill(4, 3, 5).join()"), - String::from("1,2,3") - ); - - forward(&mut engine, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut engine, "a.fill(4, '1.2', '2.5').join()"), - String::from("1,4,3") - ); - - forward(&mut engine, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut engine, "a.fill(4, 'str').join()"), - String::from("4,4,4") - ); - - forward(&mut engine, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut engine, "a.fill(4, 'str', 'str').join()"), - String::from("1,2,3") - ); - - forward(&mut engine, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut engine, "a.fill(4, undefined, null).join()"), - String::from("1,2,3") - ); - - forward(&mut engine, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut engine, "a.fill(4, undefined, undefined).join()"), - String::from("4,4,4") - ); - - assert_eq!( - forward(&mut engine, "a.fill().join()"), - String::from("undefined,undefined,undefined") - ); - - // test object reference - forward(&mut engine, "a = (new Array(3)).fill({});"); - forward(&mut engine, "a[0].hi = 'hi';"); - assert_eq!(forward(&mut engine, "a[0].hi"), String::from("hi")); - } - - #[test] - fn inclues_value() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var empty = [ ]; - var one = ["a"]; - var many = ["a", "b", "c"]; - var duplicates = ["a", "b", "c", "a", "b"]; - var undefined = [undefined]; - "#; - forward(&mut engine, init); - - // Empty - let empty = forward(&mut engine, "empty.includes('a')"); - assert_eq!(empty, String::from("false")); - - // One - let one = forward(&mut engine, "one.includes('a')"); - assert_eq!(one, String::from("true")); - // Missing from one - let missing_from_one = forward(&mut engine, "one.includes('b')"); - assert_eq!(missing_from_one, String::from("false")); - - // In many - let first_in_many = forward(&mut engine, "many.includes('c')"); - assert_eq!(first_in_many, String::from("true")); - // Missing from many - let second_in_many = forward(&mut engine, "many.includes('d')"); - assert_eq!(second_in_many, String::from("false")); - - // In duplicates - let first_in_many = forward(&mut engine, "duplicates.includes('a')"); - assert_eq!(first_in_many, String::from("true")); - // Missing from duplicates - let second_in_many = forward(&mut engine, "duplicates.includes('d')"); - assert_eq!(second_in_many, String::from("false")); - } - - #[test] - fn map() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - - let js = r#" - var empty = []; - var one = ["x"]; - var many = ["x", "y", "z"]; - - // TODO: uncomment when `this` has been implemented - // var _this = { answer: 42 }; - - // function callbackThatUsesThis() { - // return 'The answer to life is: ' + this.answer; - // } - - var empty_mapped = empty.map(v => v + '_'); - var one_mapped = one.map(v => '_' + v); - var many_mapped = many.map(v => '_' + v + '_'); - "#; - - forward(&mut engine, js); - - // assert the old arrays have not been modified - assert_eq!(forward(&mut engine, "one[0]"), String::from("x")); - assert_eq!( - forward(&mut engine, "many[2] + many[1] + many[0]"), - String::from("zyx") - ); - - // NB: These tests need to be rewritten once `Display` has been implemented for `Array` - // Empty - assert_eq!( - forward(&mut engine, "empty_mapped.length"), - String::from("0") - ); - - // One - assert_eq!(forward(&mut engine, "one_mapped.length"), String::from("1")); - assert_eq!(forward(&mut engine, "one_mapped[0]"), String::from("_x")); - - // Many - assert_eq!( - forward(&mut engine, "many_mapped.length"), - String::from("3") - ); - assert_eq!( - forward( - &mut engine, - "many_mapped[0] + many_mapped[1] + many_mapped[2]" - ), - String::from("_x__y__z_") - ); - - // TODO: uncomment when `this` has been implemented - // One but it uses `this` inside the callback - // let one_with_this = forward(&mut engine, "one.map(callbackThatUsesThis, _this)[0];"); - // assert_eq!(one_with_this, String::from("The answer to life is: 42")) - } - - #[test] - fn slice() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var empty = [ ].slice(); - var one = ["a"].slice(); - var many1 = ["a", "b", "c", "d"].slice(1); - var many2 = ["a", "b", "c", "d"].slice(2, 3); - var many3 = ["a", "b", "c", "d"].slice(7); - "#; - forward(&mut engine, init); - - assert_eq!(forward(&mut engine, "empty.length"), "0"); - assert_eq!(forward(&mut engine, "one[0]"), "a"); - assert_eq!(forward(&mut engine, "many1[0]"), "b"); - assert_eq!(forward(&mut engine, "many1[1]"), "c"); - assert_eq!(forward(&mut engine, "many1[2]"), "d"); - assert_eq!(forward(&mut engine, "many1.length"), "3"); - assert_eq!(forward(&mut engine, "many2[0]"), "c"); - assert_eq!(forward(&mut engine, "many2.length"), "1"); - assert_eq!(forward(&mut engine, "many3.length"), "0"); - } -} diff --git a/boa/src/builtins/array/tests.rs b/boa/src/builtins/array/tests.rs new file mode 100644 index 0000000000..5e88ff5f7f --- /dev/null +++ b/boa/src/builtins/array/tests.rs @@ -0,0 +1,633 @@ +use crate::exec::Executor; +use crate::forward; +use crate::realm::Realm; + +#[test] +fn is_array() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var empty = []; + var new_arr = new Array(); + var many = ["a", "b", "c"]; + "#; + forward(&mut engine, init); + assert_eq!(forward(&mut engine, "Array.isArray(empty)"), "true"); + assert_eq!(forward(&mut engine, "Array.isArray(new_arr)"), "true"); + assert_eq!(forward(&mut engine, "Array.isArray(many)"), "true"); + assert_eq!(forward(&mut engine, "Array.isArray([1, 2, 3])"), "true"); + assert_eq!(forward(&mut engine, "Array.isArray([])"), "true"); + assert_eq!(forward(&mut engine, "Array.isArray({})"), "false"); + // assert_eq!(forward(&mut engine, "Array.isArray(new Array)"), "true"); + assert_eq!(forward(&mut engine, "Array.isArray()"), "false"); + assert_eq!( + forward(&mut engine, "Array.isArray({ constructor: Array })"), + "false" + ); + assert_eq!( + forward( + &mut engine, + "Array.isArray({ push: Array.prototype.push, concat: Array.prototype.concat })" + ), + "false" + ); + assert_eq!(forward(&mut engine, "Array.isArray(17)"), "false"); + assert_eq!( + forward(&mut engine, "Array.isArray({ __proto__: Array.prototype })"), + "false" + ); + assert_eq!( + forward(&mut engine, "Array.isArray({ length: 0 })"), + "false" + ); +} + +#[test] +fn concat() { + //TODO: array display formatter + // let realm = Realm::create(); + // let mut engine = Executor::new(realm); + // let init = r#" + // var empty = new Array(); + // var one = new Array(1); + // "#; + // forward(&mut engine, init); + // // Empty ++ Empty + // let ee = forward(&mut engine, "empty.concat(empty)"); + // assert_eq!(ee, String::from("[]")); + // // Empty ++ NonEmpty + // let en = forward(&mut engine, "empty.concat(one)"); + // assert_eq!(en, String::from("[a]")); + // // NonEmpty ++ Empty + // let ne = forward(&mut engine, "one.concat(empty)"); + // assert_eq!(ne, String::from("a.b.c")); + // // NonEmpty ++ NonEmpty + // let nn = forward(&mut engine, "one.concat(one)"); + // assert_eq!(nn, String::from("a.b.c")); +} + +#[test] +fn join() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var empty = [ ]; + var one = ["a"]; + var many = ["a", "b", "c"]; + "#; + forward(&mut engine, init); + // Empty + let empty = forward(&mut engine, "empty.join('.')"); + assert_eq!(empty, String::from("")); + // One + let one = forward(&mut engine, "one.join('.')"); + assert_eq!(one, String::from("a")); + // Many + let many = forward(&mut engine, "many.join('.')"); + assert_eq!(many, String::from("a.b.c")); +} + +#[test] +fn to_string() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var empty = [ ]; + var one = ["a"]; + var many = ["a", "b", "c"]; + "#; + forward(&mut engine, init); + // Empty + let empty = forward(&mut engine, "empty.toString()"); + assert_eq!(empty, String::from("")); + // One + let one = forward(&mut engine, "one.toString()"); + assert_eq!(one, String::from("a")); + // Many + let many = forward(&mut engine, "many.toString()"); + assert_eq!(many, String::from("a,b,c")); +} + +#[test] +fn every() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + // taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every + let init = r#" + var empty = []; + + var array = [11, 23, 45]; + function callback(element) { + return element > 10; + } + function callback2(element) { + return element < 10; + } + + var appendArray = [1,2,3,4]; + function appendingCallback(elem,index,arr) { + arr.push('new'); + return elem !== "new"; + } + + var delArray = [1,2,3,4]; + function deletingCallback(elem,index,arr) { + arr.pop() + return elem < 3; + } + "#; + forward(&mut engine, init); + let result = forward(&mut engine, "array.every(callback);"); + assert_eq!(result, "true"); + + let result = forward(&mut engine, "empty.every(callback);"); + assert_eq!(result, "true"); + + let result = forward(&mut engine, "array.every(callback2);"); + assert_eq!(result, "false"); + + let result = forward(&mut engine, "appendArray.every(appendingCallback);"); + assert_eq!(result, "true"); + + let result = forward(&mut engine, "delArray.every(deletingCallback);"); + assert_eq!(result, "true"); +} + +#[test] +fn find() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + function comp(a) { + return a == "a"; + } + var many = ["a", "b", "c"]; + "#; + forward(&mut engine, init); + let found = forward(&mut engine, "many.find(comp)"); + assert_eq!(found, String::from("a")); +} + +#[test] +fn find_index() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + + let code = r#" + function comp(item) { + return item == 2; + } + var many = [1, 2, 3]; + var empty = []; + var missing = [4, 5, 6]; + "#; + + forward(&mut engine, code); + + let many = forward(&mut engine, "many.findIndex(comp)"); + assert_eq!(many, String::from("1")); + + let empty = forward(&mut engine, "empty.findIndex(comp)"); + assert_eq!(empty, String::from("-1")); + + let missing = forward(&mut engine, "missing.findIndex(comp)"); + assert_eq!(missing, String::from("-1")); +} + +#[test] +fn push() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var arr = [1, 2]; + "#; + forward(&mut engine, init); + + assert_eq!(forward(&mut engine, "arr.push()"), "2"); + assert_eq!(forward(&mut engine, "arr.push(3, 4)"), "4"); + assert_eq!(forward(&mut engine, "arr[2]"), "3"); + assert_eq!(forward(&mut engine, "arr[3]"), "4"); +} + +#[test] +fn pop() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var empty = [ ]; + var one = [1]; + var many = [1, 2, 3, 4]; + "#; + forward(&mut engine, init); + + assert_eq!( + forward(&mut engine, "empty.pop()"), + String::from("undefined") + ); + assert_eq!(forward(&mut engine, "one.pop()"), "1"); + assert_eq!(forward(&mut engine, "one.length"), "0"); + assert_eq!(forward(&mut engine, "many.pop()"), "4"); + assert_eq!(forward(&mut engine, "many[0]"), "1"); + assert_eq!(forward(&mut engine, "many.length"), "3"); +} + +#[test] +fn shift() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var empty = [ ]; + var one = [1]; + var many = [1, 2, 3, 4]; + "#; + forward(&mut engine, init); + + assert_eq!( + forward(&mut engine, "empty.shift()"), + String::from("undefined") + ); + assert_eq!(forward(&mut engine, "one.shift()"), "1"); + assert_eq!(forward(&mut engine, "one.length"), "0"); + assert_eq!(forward(&mut engine, "many.shift()"), "1"); + assert_eq!(forward(&mut engine, "many[0]"), "2"); + assert_eq!(forward(&mut engine, "many.length"), "3"); +} + +#[test] +fn unshift() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var arr = [3, 4]; + "#; + forward(&mut engine, init); + + assert_eq!(forward(&mut engine, "arr.unshift()"), "2"); + assert_eq!(forward(&mut engine, "arr.unshift(1, 2)"), "4"); + assert_eq!(forward(&mut engine, "arr[0]"), "1"); + assert_eq!(forward(&mut engine, "arr[1]"), "2"); +} + +#[test] +fn reverse() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var arr = [1, 2]; + var reversed = arr.reverse(); + "#; + forward(&mut engine, init); + assert_eq!(forward(&mut engine, "reversed[0]"), "2"); + assert_eq!(forward(&mut engine, "reversed[1]"), "1"); + assert_eq!(forward(&mut engine, "arr[0]"), "2"); + assert_eq!(forward(&mut engine, "arr[1]"), "1"); +} + +#[test] +fn index_of() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var empty = [ ]; + var one = ["a"]; + var many = ["a", "b", "c"]; + var duplicates = ["a", "b", "c", "a", "b"]; + "#; + forward(&mut engine, init); + + // Empty + let empty = forward(&mut engine, "empty.indexOf('a')"); + assert_eq!(empty, String::from("-1")); + + // One + let one = forward(&mut engine, "one.indexOf('a')"); + assert_eq!(one, String::from("0")); + // Missing from one + let missing_from_one = forward(&mut engine, "one.indexOf('b')"); + assert_eq!(missing_from_one, String::from("-1")); + + // First in many + let first_in_many = forward(&mut engine, "many.indexOf('a')"); + assert_eq!(first_in_many, String::from("0")); + // Second in many + let second_in_many = forward(&mut engine, "many.indexOf('b')"); + assert_eq!(second_in_many, String::from("1")); + + // First in duplicates + let first_in_many = forward(&mut engine, "duplicates.indexOf('a')"); + assert_eq!(first_in_many, String::from("0")); + // Second in duplicates + let second_in_many = forward(&mut engine, "duplicates.indexOf('b')"); + assert_eq!(second_in_many, String::from("1")); + + // Positive fromIndex greater than array length + let fromindex_greater_than_length = forward(&mut engine, "one.indexOf('a', 2)"); + assert_eq!(fromindex_greater_than_length, String::from("-1")); + // Positive fromIndex missed match + let fromindex_misses_match = forward(&mut engine, "many.indexOf('a', 1)"); + assert_eq!(fromindex_misses_match, String::from("-1")); + // Positive fromIndex matched + let fromindex_matches = forward(&mut engine, "many.indexOf('b', 1)"); + assert_eq!(fromindex_matches, String::from("1")); + // Positive fromIndex with duplicates + let first_in_many = forward(&mut engine, "duplicates.indexOf('a', 1)"); + assert_eq!(first_in_many, String::from("3")); + + // Negative fromIndex greater than array length + let fromindex_greater_than_length = forward(&mut engine, "one.indexOf('a', -2)"); + assert_eq!(fromindex_greater_than_length, String::from("0")); + // Negative fromIndex missed match + let fromindex_misses_match = forward(&mut engine, "many.indexOf('b', -1)"); + assert_eq!(fromindex_misses_match, String::from("-1")); + // Negative fromIndex matched + let fromindex_matches = forward(&mut engine, "many.indexOf('c', -1)"); + assert_eq!(fromindex_matches, String::from("2")); + // Negative fromIndex with duplicates + let second_in_many = forward(&mut engine, "duplicates.indexOf('b', -2)"); + assert_eq!(second_in_many, String::from("4")); +} + +#[test] +fn last_index_of() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var empty = [ ]; + var one = ["a"]; + var many = ["a", "b", "c"]; + var duplicates = ["a", "b", "c", "a", "b"]; + "#; + forward(&mut engine, init); + + // Empty + let empty = forward(&mut engine, "empty.lastIndexOf('a')"); + assert_eq!(empty, String::from("-1")); + + // One + let one = forward(&mut engine, "one.lastIndexOf('a')"); + assert_eq!(one, String::from("0")); + // Missing from one + let missing_from_one = forward(&mut engine, "one.lastIndexOf('b')"); + assert_eq!(missing_from_one, String::from("-1")); + + // First in many + let first_in_many = forward(&mut engine, "many.lastIndexOf('a')"); + assert_eq!(first_in_many, String::from("0")); + // Second in many + let second_in_many = forward(&mut engine, "many.lastIndexOf('b')"); + assert_eq!(second_in_many, String::from("1")); + + // 4th in duplicates + let first_in_many = forward(&mut engine, "duplicates.lastIndexOf('a')"); + assert_eq!(first_in_many, String::from("3")); + // 5th in duplicates + let second_in_many = forward(&mut engine, "duplicates.lastIndexOf('b')"); + assert_eq!(second_in_many, String::from("4")); + + // Positive fromIndex greater than array length + let fromindex_greater_than_length = forward(&mut engine, "one.lastIndexOf('a', 2)"); + assert_eq!(fromindex_greater_than_length, String::from("0")); + // Positive fromIndex missed match + let fromindex_misses_match = forward(&mut engine, "many.lastIndexOf('c', 1)"); + assert_eq!(fromindex_misses_match, String::from("-1")); + // Positive fromIndex matched + let fromindex_matches = forward(&mut engine, "many.lastIndexOf('b', 1)"); + assert_eq!(fromindex_matches, String::from("1")); + // Positive fromIndex with duplicates + let first_in_many = forward(&mut engine, "duplicates.lastIndexOf('a', 1)"); + assert_eq!(first_in_many, String::from("0")); + + // Negative fromIndex greater than array length + let fromindex_greater_than_length = forward(&mut engine, "one.lastIndexOf('a', -2)"); + assert_eq!(fromindex_greater_than_length, String::from("-1")); + // Negative fromIndex missed match + let fromindex_misses_match = forward(&mut engine, "many.lastIndexOf('c', -2)"); + assert_eq!(fromindex_misses_match, String::from("-1")); + // Negative fromIndex matched + let fromindex_matches = forward(&mut engine, "many.lastIndexOf('c', -1)"); + assert_eq!(fromindex_matches, String::from("2")); + // Negative fromIndex with duplicates + let second_in_many = forward(&mut engine, "duplicates.lastIndexOf('b', -2)"); + assert_eq!(second_in_many, String::from("1")); +} + +#[test] +fn fill() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + + forward(&mut engine, "var a = [1, 2, 3];"); + assert_eq!( + forward(&mut engine, "a.fill(4).join()"), + String::from("4,4,4") + ); + // make sure the array is modified + assert_eq!(forward(&mut engine, "a.join()"), String::from("4,4,4")); + + forward(&mut engine, "a = [1, 2, 3];"); + assert_eq!( + forward(&mut engine, "a.fill(4, '1').join()"), + String::from("1,4,4") + ); + + forward(&mut engine, "a = [1, 2, 3];"); + assert_eq!( + forward(&mut engine, "a.fill(4, 1, 2).join()"), + String::from("1,4,3") + ); + + forward(&mut engine, "a = [1, 2, 3];"); + assert_eq!( + forward(&mut engine, "a.fill(4, 1, 1).join()"), + String::from("1,2,3") + ); + + forward(&mut engine, "a = [1, 2, 3];"); + assert_eq!( + forward(&mut engine, "a.fill(4, 3, 3).join()"), + String::from("1,2,3") + ); + + forward(&mut engine, "a = [1, 2, 3];"); + assert_eq!( + forward(&mut engine, "a.fill(4, -3, -2).join()"), + String::from("4,2,3") + ); + + // TODO: uncomment when NaN support is added + // forward(&mut engine, "a = [1, 2, 3];"); + // assert_eq!( + // forward(&mut engine, "a.fill(4, NaN, NaN).join()"), + // String::from("1,2,3") + // ); + + forward(&mut engine, "a = [1, 2, 3];"); + assert_eq!( + forward(&mut engine, "a.fill(4, 3, 5).join()"), + String::from("1,2,3") + ); + + forward(&mut engine, "a = [1, 2, 3];"); + assert_eq!( + forward(&mut engine, "a.fill(4, '1.2', '2.5').join()"), + String::from("1,4,3") + ); + + forward(&mut engine, "a = [1, 2, 3];"); + assert_eq!( + forward(&mut engine, "a.fill(4, 'str').join()"), + String::from("4,4,4") + ); + + forward(&mut engine, "a = [1, 2, 3];"); + assert_eq!( + forward(&mut engine, "a.fill(4, 'str', 'str').join()"), + String::from("1,2,3") + ); + + forward(&mut engine, "a = [1, 2, 3];"); + assert_eq!( + forward(&mut engine, "a.fill(4, undefined, null).join()"), + String::from("1,2,3") + ); + + forward(&mut engine, "a = [1, 2, 3];"); + assert_eq!( + forward(&mut engine, "a.fill(4, undefined, undefined).join()"), + String::from("4,4,4") + ); + + assert_eq!( + forward(&mut engine, "a.fill().join()"), + String::from("undefined,undefined,undefined") + ); + + // test object reference + forward(&mut engine, "a = (new Array(3)).fill({});"); + forward(&mut engine, "a[0].hi = 'hi';"); + assert_eq!(forward(&mut engine, "a[0].hi"), String::from("hi")); +} + +#[test] +fn inclues_value() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var empty = [ ]; + var one = ["a"]; + var many = ["a", "b", "c"]; + var duplicates = ["a", "b", "c", "a", "b"]; + var undefined = [undefined]; + "#; + forward(&mut engine, init); + + // Empty + let empty = forward(&mut engine, "empty.includes('a')"); + assert_eq!(empty, String::from("false")); + + // One + let one = forward(&mut engine, "one.includes('a')"); + assert_eq!(one, String::from("true")); + // Missing from one + let missing_from_one = forward(&mut engine, "one.includes('b')"); + assert_eq!(missing_from_one, String::from("false")); + + // In many + let first_in_many = forward(&mut engine, "many.includes('c')"); + assert_eq!(first_in_many, String::from("true")); + // Missing from many + let second_in_many = forward(&mut engine, "many.includes('d')"); + assert_eq!(second_in_many, String::from("false")); + + // In duplicates + let first_in_many = forward(&mut engine, "duplicates.includes('a')"); + assert_eq!(first_in_many, String::from("true")); + // Missing from duplicates + let second_in_many = forward(&mut engine, "duplicates.includes('d')"); + assert_eq!(second_in_many, String::from("false")); +} + +#[test] +fn map() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + + let js = r#" + var empty = []; + var one = ["x"]; + var many = ["x", "y", "z"]; + + // TODO: uncomment when `this` has been implemented + // var _this = { answer: 42 }; + + // function callbackThatUsesThis() { + // return 'The answer to life is: ' + this.answer; + // } + + var empty_mapped = empty.map(v => v + '_'); + var one_mapped = one.map(v => '_' + v); + var many_mapped = many.map(v => '_' + v + '_'); + "#; + + forward(&mut engine, js); + + // assert the old arrays have not been modified + assert_eq!(forward(&mut engine, "one[0]"), String::from("x")); + assert_eq!( + forward(&mut engine, "many[2] + many[1] + many[0]"), + String::from("zyx") + ); + + // NB: These tests need to be rewritten once `Display` has been implemented for `Array` + // Empty + assert_eq!( + forward(&mut engine, "empty_mapped.length"), + String::from("0") + ); + + // One + assert_eq!(forward(&mut engine, "one_mapped.length"), String::from("1")); + assert_eq!(forward(&mut engine, "one_mapped[0]"), String::from("_x")); + + // Many + assert_eq!( + forward(&mut engine, "many_mapped.length"), + String::from("3") + ); + assert_eq!( + forward( + &mut engine, + "many_mapped[0] + many_mapped[1] + many_mapped[2]" + ), + String::from("_x__y__z_") + ); + + // TODO: uncomment when `this` has been implemented + // One but it uses `this` inside the callback + // let one_with_this = forward(&mut engine, "one.map(callbackThatUsesThis, _this)[0];"); + // assert_eq!(one_with_this, String::from("The answer to life is: 42")) +} + +#[test] +fn slice() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var empty = [ ].slice(); + var one = ["a"].slice(); + var many1 = ["a", "b", "c", "d"].slice(1); + var many2 = ["a", "b", "c", "d"].slice(2, 3); + var many3 = ["a", "b", "c", "d"].slice(7); + "#; + forward(&mut engine, init); + + assert_eq!(forward(&mut engine, "empty.length"), "0"); + assert_eq!(forward(&mut engine, "one[0]"), "a"); + assert_eq!(forward(&mut engine, "many1[0]"), "b"); + assert_eq!(forward(&mut engine, "many1[1]"), "c"); + assert_eq!(forward(&mut engine, "many1[2]"), "d"); + assert_eq!(forward(&mut engine, "many1.length"), "3"); + assert_eq!(forward(&mut engine, "many2[0]"), "c"); + assert_eq!(forward(&mut engine, "many2.length"), "1"); + assert_eq!(forward(&mut engine, "many3.length"), "0"); +} diff --git a/boa/src/builtins/boolean.rs b/boa/src/builtins/boolean/mod.rs similarity index 53% rename from boa/src/builtins/boolean.rs rename to boa/src/builtins/boolean/mod.rs index 28a42eb21d..adcf546311 100644 --- a/boa/src/builtins/boolean.rs +++ b/boa/src/builtins/boolean/mod.rs @@ -1,3 +1,6 @@ +#[cfg(test)] +mod tests; + use crate::{ builtins::{ function::NativeFunctionData, @@ -86,86 +89,3 @@ pub fn this_boolean_value(value: &Value) -> Value { _ => to_value(false), } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::exec::Executor; - use crate::realm::Realm; - use crate::{builtins::value::same_value, forward, forward_val}; - - #[test] - fn check_boolean_constructor_is_function() { - let global = ValueData::new_obj(None); - let boolean_constructor = create_constructor(&global); - assert_eq!(boolean_constructor.is_function(), true); - } - - #[allow(clippy::result_unwrap_used)] - #[test] - /// Test the correct type is returned from call and construct - fn construct_and_call() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var one = new Boolean(1); - var zero = Boolean(0); - "#; - forward(&mut engine, init); - let one = forward_val(&mut engine, "one").unwrap(); - let zero = forward_val(&mut engine, "zero").unwrap(); - - assert_eq!(one.is_object(), true); - assert_eq!(zero.is_boolean(), true); - } - - #[test] - fn constructor_gives_true_instance() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var trueVal = new Boolean(true); - var trueNum = new Boolean(1); - var trueString = new Boolean("true"); - var trueBool = new Boolean(trueVal); - "#; - - forward(&mut engine, init); - let true_val = forward_val(&mut engine, "trueVal").expect("value expected"); - let true_num = forward_val(&mut engine, "trueNum").expect("value expected"); - let true_string = forward_val(&mut engine, "trueString").expect("value expected"); - let true_bool = forward_val(&mut engine, "trueBool").expect("value expected"); - - // Values should all be objects - assert_eq!(true_val.is_object(), true); - assert_eq!(true_num.is_object(), true); - assert_eq!(true_string.is_object(), true); - assert_eq!(true_bool.is_object(), true); - - // Values should all be truthy - assert_eq!(true_val.is_true(), true); - assert_eq!(true_num.is_true(), true); - assert_eq!(true_string.is_true(), true); - assert_eq!(true_bool.is_true(), true); - } - - #[test] - fn instances_have_correct_proto_set() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var boolInstance = new Boolean(true); - var boolProto = Boolean.prototype; - "#; - - forward(&mut engine, init); - let bool_instance = forward_val(&mut engine, "boolInstance").expect("value expected"); - let bool_prototype = forward_val(&mut engine, "boolProto").expect("value expected"); - - assert!(same_value( - &bool_instance.get_internal_slot("__proto__"), - &bool_prototype, - true - )); - } -} diff --git a/boa/src/builtins/boolean/tests.rs b/boa/src/builtins/boolean/tests.rs new file mode 100644 index 0000000000..bf5cd1a7b0 --- /dev/null +++ b/boa/src/builtins/boolean/tests.rs @@ -0,0 +1,79 @@ +use super::*; +use crate::exec::Executor; +use crate::realm::Realm; +use crate::{builtins::value::same_value, forward, forward_val}; + +#[test] +fn check_boolean_constructor_is_function() { + let global = ValueData::new_obj(None); + let boolean_constructor = create_constructor(&global); + assert_eq!(boolean_constructor.is_function(), true); +} + +/// Test the correct type is returned from call and construct +#[allow(clippy::result_unwrap_used)] +#[test] +fn construct_and_call() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var one = new Boolean(1); + var zero = Boolean(0); + "#; + forward(&mut engine, init); + let one = forward_val(&mut engine, "one").unwrap(); + let zero = forward_val(&mut engine, "zero").unwrap(); + + assert_eq!(one.is_object(), true); + assert_eq!(zero.is_boolean(), true); +} + +#[test] +fn constructor_gives_true_instance() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var trueVal = new Boolean(true); + var trueNum = new Boolean(1); + var trueString = new Boolean("true"); + var trueBool = new Boolean(trueVal); + "#; + + forward(&mut engine, init); + let true_val = forward_val(&mut engine, "trueVal").expect("value expected"); + let true_num = forward_val(&mut engine, "trueNum").expect("value expected"); + let true_string = forward_val(&mut engine, "trueString").expect("value expected"); + let true_bool = forward_val(&mut engine, "trueBool").expect("value expected"); + + // Values should all be objects + assert_eq!(true_val.is_object(), true); + assert_eq!(true_num.is_object(), true); + assert_eq!(true_string.is_object(), true); + assert_eq!(true_bool.is_object(), true); + + // Values should all be truthy + assert_eq!(true_val.is_true(), true); + assert_eq!(true_num.is_true(), true); + assert_eq!(true_string.is_true(), true); + assert_eq!(true_bool.is_true(), true); +} + +#[test] +fn instances_have_correct_proto_set() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var boolInstance = new Boolean(true); + var boolProto = Boolean.prototype; + "#; + + forward(&mut engine, init); + let bool_instance = forward_val(&mut engine, "boolInstance").expect("value expected"); + let bool_prototype = forward_val(&mut engine, "boolProto").expect("value expected"); + + assert!(same_value( + &bool_instance.get_internal_slot("__proto__"), + &bool_prototype, + true + )); +} diff --git a/boa/src/builtins/function.rs b/boa/src/builtins/function/mod.rs similarity index 83% rename from boa/src/builtins/function.rs rename to boa/src/builtins/function/mod.rs index d33e538b9c..798451d8d3 100644 --- a/boa/src/builtins/function.rs +++ b/boa/src/builtins/function/mod.rs @@ -1,3 +1,6 @@ +#[cfg(test)] +mod tests; + use crate::{ builtins::{ object::{internal_methods_trait::ObjectInternalMethods, Object}, @@ -129,32 +132,3 @@ pub fn create_unmapped_arguments_object(arguments_list: Vec) -> Value { to_value(obj) } - -#[cfg(test)] -mod tests { - use crate::exec::Executor; - use crate::realm::Realm; - use crate::{builtins::value::from_value, forward, forward_val}; - - #[allow(clippy::float_cmp)] - #[test] - fn check_arguments_object() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - function jason(a, b) { - return arguments[0]; - } - var val = jason(100, 6); - "#; - - forward(&mut engine, init); - let expected_return_val: f64 = 100.0; - let return_val = forward_val(&mut engine, "val").expect("value expected"); - assert_eq!(return_val.is_double(), true); - assert_eq!( - from_value::(return_val).expect("Could not convert value to f64"), - expected_return_val - ); - } -} diff --git a/boa/src/builtins/function/tests.rs b/boa/src/builtins/function/tests.rs new file mode 100644 index 0000000000..83c9f38a6a --- /dev/null +++ b/boa/src/builtins/function/tests.rs @@ -0,0 +1,25 @@ +use crate::exec::Executor; +use crate::realm::Realm; +use crate::{builtins::value::from_value, forward, forward_val}; + +#[allow(clippy::float_cmp)] +#[test] +fn check_arguments_object() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + function jason(a, b) { + return arguments[0]; + } + var val = jason(100, 6); + "#; + + forward(&mut engine, init); + let expected_return_val: f64 = 100.0; + let return_val = forward_val(&mut engine, "val").expect("value expected"); + assert_eq!(return_val.is_double(), true); + assert_eq!( + from_value::(return_val).expect("Could not convert value to f64"), + expected_return_val + ); +} diff --git a/boa/src/builtins/number.rs b/boa/src/builtins/number.rs deleted file mode 100644 index 362ecd3d42..0000000000 --- a/boa/src/builtins/number.rs +++ /dev/null @@ -1,376 +0,0 @@ -use crate::{ - builtins::{ - function::NativeFunctionData, - object::{internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, PROTOTYPE}, - value::{to_value, ResultValue, Value, ValueData}, - }, - exec::Interpreter, -}; -use std::{borrow::Borrow, f64, ops::Deref}; - -/// Helper function: to_number(value: &Value) -> Value -/// -/// Converts a Value to a Number. -fn to_number(value: &Value) -> Value { - match *value.deref().borrow() { - ValueData::Boolean(b) => { - if b { - to_value(1) - } else { - to_value(0) - } - } - ValueData::Function(_) | 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), - ValueData::Number(n) => to_value(n), - ValueData::String(ref s) => match s.parse::() { - Ok(n) => to_value(n), - Err(_) => to_value(f64::NAN), - }, - } -} - -/// Helper function: num_to_exponential(n: f64) -> String -/// -/// Formats a float as a ES6-style exponential number string. -fn num_to_exponential(n: f64) -> String { - match n.abs() { - x if x > 1.0 => format!("{:e}", n).replace("e", "e+"), - x if x == 0.0 => format!("{:e}", n).replace("e", "e+"), - _ => format!("{:e}", n), - } -} - -/// Number(arg) -/// -/// Create a new number [[Construct]] -pub fn make_number(this: &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)), - }; - this.set_internal_slot("NumberData", data); - Ok(this.clone()) -} - -/// Number() -/// -/// https://tc39.es/ecma262/#sec-number-constructor-number-value -pub fn call_number(_this: &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)), - }; - Ok(data) -} - -/// Number().toExponential() -/// -/// https://tc39.es/ecma262/#sec-number.prototype.toexponential -pub fn to_exponential(this: &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)) -} - -/// https://tc39.es/ecma262/#sec-number.prototype.tofixed -pub fn to_fixed(this: &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() { - x if x > 0 => n.to_int() as usize, - _ => 0, - }, - None => 0, - }; - let this_fixed_num = format!("{:.*}", precision, this_num); - Ok(to_value(this_fixed_num)) -} - -/// Number().toLocaleString() -/// -/// https://tc39.es/ecma262/#sec-number.prototype.tolocalestring -/// -/// Note that while this technically conforms to the Ecma standard, it does no actual -/// internationalization logic. -pub fn to_locale_string(this: &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)) -} - -/// Number().toPrecision(p) -/// -/// https://tc39.es/ecma262/#sec-number.prototype.toprecision -pub fn to_precision(this: &Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { - println!("Number::to_precision()"); - let this_num = to_number(this); - let _num_str_len = format!("{}", this_num.to_num()).len(); - let _precision = match args.get(0) { - Some(n) => match n.to_int() { - x if x > 0 => n.to_int() as usize, - _ => 0, - }, - None => 0, - }; - // TODO: Implement toPrecision - unimplemented!(); -} - -/// Number().toString() -/// -/// https://tc39.es/ecma262/#sec-number.prototype.tostring -pub fn to_string(this: &Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue { - Ok(to_value(format!("{}", to_number(this).to_num()))) -} - -/// Number().valueOf() -/// -/// https://tc39.es/ecma262/#sec-number.prototype.valueof -pub fn value_of(this: &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); - make_builtin_fn!(to_fixed, named "toFixed", with length 1, of number_prototype); - make_builtin_fn!(to_locale_string, named "toLocaleString", of number_prototype); - make_builtin_fn!(to_precision, named "toPrecision", with length 1, of number_prototype); - 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 -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{builtins::value::ValueData, exec::Executor, forward, forward_val, realm::Realm}; - use std::f64; - - #[test] - fn check_number_constructor_is_function() { - let global = ValueData::new_obj(None); - let number_constructor = create_constructor(&global); - assert_eq!(number_constructor.is_function(), true); - } - - #[test] - fn call_number() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var default_zero = Number(); - var int_one = Number(1); - var float_two = Number(2.1); - var str_three = Number('3.2'); - var bool_one = Number(true); - var bool_zero = Number(false); - var invalid_nan = Number("I am not a number"); - var from_exp = Number("2.34e+2"); - "#; - - forward(&mut engine, init); - let default_zero = forward_val(&mut engine, "default_zero").unwrap(); - let int_one = forward_val(&mut engine, "int_one").unwrap(); - let float_two = forward_val(&mut engine, "float_two").unwrap(); - let str_three = forward_val(&mut engine, "str_three").unwrap(); - let bool_one = forward_val(&mut engine, "bool_one").unwrap(); - let bool_zero = forward_val(&mut engine, "bool_zero").unwrap(); - let invalid_nan = forward_val(&mut engine, "invalid_nan").unwrap(); - let from_exp = forward_val(&mut engine, "from_exp").unwrap(); - - assert_eq!(default_zero.to_num(), f64::from(0)); - assert_eq!(int_one.to_num(), f64::from(1)); - assert_eq!(float_two.to_num(), f64::from(2.1)); - assert_eq!(str_three.to_num(), f64::from(3.2)); - assert_eq!(bool_one.to_num(), f64::from(1)); - assert!(invalid_nan.to_num().is_nan()); - assert_eq!(bool_zero.to_num(), f64::from(0)); - assert_eq!(from_exp.to_num(), f64::from(234)); - } - - #[test] - fn to_exponential() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var default_exp = Number().toExponential(); - var int_exp = Number(5).toExponential(); - var float_exp = Number(1.234).toExponential(); - var big_exp = Number(1234).toExponential(); - var nan_exp = Number("I am also not a number").toExponential(); - var noop_exp = Number("1.23e+2").toExponential(); - "#; - - forward(&mut engine, init); - let default_exp = forward(&mut engine, "default_exp"); - let int_exp = forward(&mut engine, "int_exp"); - let float_exp = forward(&mut engine, "float_exp"); - let big_exp = forward(&mut engine, "big_exp"); - let nan_exp = forward(&mut engine, "nan_exp"); - let noop_exp = forward(&mut engine, "noop_exp"); - - assert_eq!(default_exp, String::from("0e+0")); - assert_eq!(int_exp, String::from("5e+0")); - assert_eq!(float_exp, String::from("1.234e+0")); - assert_eq!(big_exp, String::from("1.234e+3")); - assert_eq!(nan_exp, String::from("NaN")); - assert_eq!(noop_exp, String::from("1.23e+2")); - } - - #[test] - fn to_fixed() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var default_fixed = Number().toFixed(); - var pos_fixed = Number("3.456e+4").toFixed(); - var neg_fixed = Number("3.456e-4").toFixed(); - var noop_fixed = Number(5).toFixed(); - var nan_fixed = Number("I am not a number").toFixed(); - "#; - - forward(&mut engine, init); - let default_fixed = forward(&mut engine, "default_fixed"); - let pos_fixed = forward(&mut engine, "pos_fixed"); - let neg_fixed = forward(&mut engine, "neg_fixed"); - let noop_fixed = forward(&mut engine, "noop_fixed"); - let nan_fixed = forward(&mut engine, "nan_fixed"); - - assert_eq!(default_fixed, String::from("0")); - assert_eq!(pos_fixed, String::from("34560")); - assert_eq!(neg_fixed, String::from("0")); - assert_eq!(noop_fixed, String::from("5")); - assert_eq!(nan_fixed, String::from("NaN")); - } - - #[test] - fn to_locale_string() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var default_locale = Number().toLocaleString(); - var small_locale = Number(5).toLocaleString(); - var big_locale = Number("345600").toLocaleString(); - var neg_locale = Number(-25).toLocaleString(); - "#; - - // TODO: We don't actually do any locale checking here - // To honor the spec we should print numbers according to user locale. - - forward(&mut engine, init); - let default_locale = forward(&mut engine, "default_locale"); - let small_locale = forward(&mut engine, "small_locale"); - let big_locale = forward(&mut engine, "big_locale"); - let neg_locale = forward(&mut engine, "neg_locale"); - - assert_eq!(default_locale, String::from("0")); - assert_eq!(small_locale, String::from("5")); - assert_eq!(big_locale, String::from("345600")); - assert_eq!(neg_locale, String::from("-25")); - } - - #[test] - #[ignore] - fn to_precision() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var default_precision = Number().toPrecision(); - var low_precision = Number(123456789).toPrecision(1); - var more_precision = Number(123456789).toPrecision(4); - var exact_precision = Number(123456789).toPrecision(9); - var over_precision = Number(123456789).toPrecision(50); - var neg_precision = Number(-123456789).toPrecision(4); - "#; - - forward(&mut engine, init); - let default_precision = forward(&mut engine, "default_precision"); - let low_precision = forward(&mut engine, "low_precision"); - let more_precision = forward(&mut engine, "more_precision"); - let exact_precision = forward(&mut engine, "exact_precision"); - let over_precision = forward(&mut engine, "over_precision"); - let neg_precision = forward(&mut engine, "neg_precision"); - - assert_eq!(default_precision, String::from("0")); - assert_eq!(low_precision, String::from("1e+8")); - assert_eq!(more_precision, String::from("1.235e+8")); - assert_eq!(exact_precision, String::from("123456789")); - assert_eq!( - over_precision, - String::from("123456789.00000000000000000000000000000000000000000") - ); - assert_eq!(neg_precision, String::from("-1.235e+8")); - } - - #[test] - fn to_string() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var default_string = Number().toString(); - var int_string = Number(123).toString(); - var float_string = Number(1.234).toString(); - var exp_string = Number("1.2e+4").toString(); - var neg_string = Number(-1.2).toString(); - "#; - - forward(&mut engine, init); - let default_string = forward(&mut engine, "default_string"); - let int_string = forward(&mut engine, "int_string"); - let float_string = forward(&mut engine, "float_string"); - let exp_string = forward(&mut engine, "exp_string"); - let neg_string = forward(&mut engine, "neg_string"); - - assert_eq!(default_string, String::from("0")); - assert_eq!(int_string, String::from("123")); - assert_eq!(float_string, String::from("1.234")); - assert_eq!(exp_string, String::from("12000")); - assert_eq!(neg_string, String::from("-1.2")); - } - - #[test] - fn value_of() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - // TODO: In addition to parsing numbers from strings, parse them bare As of October 2019 - // the parser does not understand scientific e.g., Xe+Y or -Xe-Y notation. - let init = r#" - var default_val = Number().valueOf(); - var int_val = Number("123").valueOf(); - var float_val = Number(1.234).valueOf(); - var exp_val = Number("1.2e+4").valueOf() - var neg_val = Number("-1.2e+4").valueOf() - "#; - - forward(&mut engine, init); - let default_val = forward_val(&mut engine, "default_val").unwrap(); - let int_val = forward_val(&mut engine, "int_val").unwrap(); - let float_val = forward_val(&mut engine, "float_val").unwrap(); - let exp_val = forward_val(&mut engine, "exp_val").unwrap(); - let neg_val = forward_val(&mut engine, "neg_val").unwrap(); - - assert_eq!(default_val.to_num(), f64::from(0)); - assert_eq!(int_val.to_num(), f64::from(123)); - assert_eq!(float_val.to_num(), f64::from(1.234)); - assert_eq!(exp_val.to_num(), f64::from(12000)); - assert_eq!(neg_val.to_num(), f64::from(-12000)); - } -} diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs new file mode 100644 index 0000000000..220bbb42da --- /dev/null +++ b/boa/src/builtins/number/mod.rs @@ -0,0 +1,162 @@ +#[cfg(test)] +mod tests; + +use crate::{ + builtins::{ + function::NativeFunctionData, + object::{internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, PROTOTYPE}, + value::{to_value, ResultValue, Value, ValueData}, + }, + exec::Interpreter, +}; +use std::{borrow::Borrow, f64, ops::Deref}; + +/// Helper function: to_number(value: &Value) -> Value +/// +/// Converts a Value to a Number. +fn to_number(value: &Value) -> Value { + match *value.deref().borrow() { + ValueData::Boolean(b) => { + if b { + to_value(1) + } else { + to_value(0) + } + } + ValueData::Function(_) | 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), + ValueData::Number(n) => to_value(n), + ValueData::String(ref s) => match s.parse::() { + Ok(n) => to_value(n), + Err(_) => to_value(f64::NAN), + }, + } +} + +/// Helper function: num_to_exponential(n: f64) -> String +/// +/// Formats a float as a ES6-style exponential number string. +fn num_to_exponential(n: f64) -> String { + match n.abs() { + x if x > 1.0 => format!("{:e}", n).replace("e", "e+"), + x if x == 0.0 => format!("{:e}", n).replace("e", "e+"), + _ => format!("{:e}", n), + } +} + +/// Number(arg) +/// +/// Create a new number [[Construct]] +pub fn make_number(this: &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)), + }; + this.set_internal_slot("NumberData", data); + Ok(this.clone()) +} + +/// Number() +/// +/// https://tc39.es/ecma262/#sec-number-constructor-number-value +pub fn call_number(_this: &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)), + }; + Ok(data) +} + +/// Number().toExponential() +/// +/// https://tc39.es/ecma262/#sec-number.prototype.toexponential +pub fn to_exponential(this: &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)) +} + +/// https://tc39.es/ecma262/#sec-number.prototype.tofixed +pub fn to_fixed(this: &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() { + x if x > 0 => n.to_int() as usize, + _ => 0, + }, + None => 0, + }; + let this_fixed_num = format!("{:.*}", precision, this_num); + Ok(to_value(this_fixed_num)) +} + +/// Number().toLocaleString() +/// +/// https://tc39.es/ecma262/#sec-number.prototype.tolocalestring +/// +/// Note that while this technically conforms to the Ecma standard, it does no actual +/// internationalization logic. +pub fn to_locale_string(this: &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)) +} + +/// Number().toPrecision(p) +/// +/// https://tc39.es/ecma262/#sec-number.prototype.toprecision +pub fn to_precision(this: &Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { + println!("Number::to_precision()"); + let this_num = to_number(this); + let _num_str_len = format!("{}", this_num.to_num()).len(); + let _precision = match args.get(0) { + Some(n) => match n.to_int() { + x if x > 0 => n.to_int() as usize, + _ => 0, + }, + None => 0, + }; + // TODO: Implement toPrecision + unimplemented!(); +} + +/// Number().toString() +/// +/// https://tc39.es/ecma262/#sec-number.prototype.tostring +pub fn to_string(this: &Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue { + Ok(to_value(format!("{}", to_number(this).to_num()))) +} + +/// Number().valueOf() +/// +/// https://tc39.es/ecma262/#sec-number.prototype.valueof +pub fn value_of(this: &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); + make_builtin_fn!(to_fixed, named "toFixed", with length 1, of number_prototype); + make_builtin_fn!(to_locale_string, named "toLocaleString", of number_prototype); + make_builtin_fn!(to_precision, named "toPrecision", with length 1, of number_prototype); + 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 +} diff --git a/boa/src/builtins/number/tests.rs b/boa/src/builtins/number/tests.rs new file mode 100644 index 0000000000..dd8b0802ae --- /dev/null +++ b/boa/src/builtins/number/tests.rs @@ -0,0 +1,213 @@ +use super::*; +use crate::{builtins::value::ValueData, exec::Executor, forward, forward_val, realm::Realm}; +use std::f64; + +#[test] +fn check_number_constructor_is_function() { + let global = ValueData::new_obj(None); + let number_constructor = create_constructor(&global); + assert_eq!(number_constructor.is_function(), true); +} + +#[test] +fn call_number() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var default_zero = Number(); + var int_one = Number(1); + var float_two = Number(2.1); + var str_three = Number('3.2'); + var bool_one = Number(true); + var bool_zero = Number(false); + var invalid_nan = Number("I am not a number"); + var from_exp = Number("2.34e+2"); + "#; + + forward(&mut engine, init); + let default_zero = forward_val(&mut engine, "default_zero").unwrap(); + let int_one = forward_val(&mut engine, "int_one").unwrap(); + let float_two = forward_val(&mut engine, "float_two").unwrap(); + let str_three = forward_val(&mut engine, "str_three").unwrap(); + let bool_one = forward_val(&mut engine, "bool_one").unwrap(); + let bool_zero = forward_val(&mut engine, "bool_zero").unwrap(); + let invalid_nan = forward_val(&mut engine, "invalid_nan").unwrap(); + let from_exp = forward_val(&mut engine, "from_exp").unwrap(); + + assert_eq!(default_zero.to_num(), f64::from(0)); + assert_eq!(int_one.to_num(), f64::from(1)); + assert_eq!(float_two.to_num(), f64::from(2.1)); + assert_eq!(str_three.to_num(), f64::from(3.2)); + assert_eq!(bool_one.to_num(), f64::from(1)); + assert!(invalid_nan.to_num().is_nan()); + assert_eq!(bool_zero.to_num(), f64::from(0)); + assert_eq!(from_exp.to_num(), f64::from(234)); +} + +#[test] +fn to_exponential() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var default_exp = Number().toExponential(); + var int_exp = Number(5).toExponential(); + var float_exp = Number(1.234).toExponential(); + var big_exp = Number(1234).toExponential(); + var nan_exp = Number("I am also not a number").toExponential(); + var noop_exp = Number("1.23e+2").toExponential(); + "#; + + forward(&mut engine, init); + let default_exp = forward(&mut engine, "default_exp"); + let int_exp = forward(&mut engine, "int_exp"); + let float_exp = forward(&mut engine, "float_exp"); + let big_exp = forward(&mut engine, "big_exp"); + let nan_exp = forward(&mut engine, "nan_exp"); + let noop_exp = forward(&mut engine, "noop_exp"); + + assert_eq!(default_exp, String::from("0e+0")); + assert_eq!(int_exp, String::from("5e+0")); + assert_eq!(float_exp, String::from("1.234e+0")); + assert_eq!(big_exp, String::from("1.234e+3")); + assert_eq!(nan_exp, String::from("NaN")); + assert_eq!(noop_exp, String::from("1.23e+2")); +} + +#[test] +fn to_fixed() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var default_fixed = Number().toFixed(); + var pos_fixed = Number("3.456e+4").toFixed(); + var neg_fixed = Number("3.456e-4").toFixed(); + var noop_fixed = Number(5).toFixed(); + var nan_fixed = Number("I am not a number").toFixed(); + "#; + + forward(&mut engine, init); + let default_fixed = forward(&mut engine, "default_fixed"); + let pos_fixed = forward(&mut engine, "pos_fixed"); + let neg_fixed = forward(&mut engine, "neg_fixed"); + let noop_fixed = forward(&mut engine, "noop_fixed"); + let nan_fixed = forward(&mut engine, "nan_fixed"); + + assert_eq!(default_fixed, String::from("0")); + assert_eq!(pos_fixed, String::from("34560")); + assert_eq!(neg_fixed, String::from("0")); + assert_eq!(noop_fixed, String::from("5")); + assert_eq!(nan_fixed, String::from("NaN")); +} + +#[test] +fn to_locale_string() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var default_locale = Number().toLocaleString(); + var small_locale = Number(5).toLocaleString(); + var big_locale = Number("345600").toLocaleString(); + var neg_locale = Number(-25).toLocaleString(); + "#; + + // TODO: We don't actually do any locale checking here + // To honor the spec we should print numbers according to user locale. + + forward(&mut engine, init); + let default_locale = forward(&mut engine, "default_locale"); + let small_locale = forward(&mut engine, "small_locale"); + let big_locale = forward(&mut engine, "big_locale"); + let neg_locale = forward(&mut engine, "neg_locale"); + + assert_eq!(default_locale, String::from("0")); + assert_eq!(small_locale, String::from("5")); + assert_eq!(big_locale, String::from("345600")); + assert_eq!(neg_locale, String::from("-25")); +} + +#[test] +#[ignore] +fn to_precision() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var default_precision = Number().toPrecision(); + var low_precision = Number(123456789).toPrecision(1); + var more_precision = Number(123456789).toPrecision(4); + var exact_precision = Number(123456789).toPrecision(9); + var over_precision = Number(123456789).toPrecision(50); + var neg_precision = Number(-123456789).toPrecision(4); + "#; + + forward(&mut engine, init); + let default_precision = forward(&mut engine, "default_precision"); + let low_precision = forward(&mut engine, "low_precision"); + let more_precision = forward(&mut engine, "more_precision"); + let exact_precision = forward(&mut engine, "exact_precision"); + let over_precision = forward(&mut engine, "over_precision"); + let neg_precision = forward(&mut engine, "neg_precision"); + + assert_eq!(default_precision, String::from("0")); + assert_eq!(low_precision, String::from("1e+8")); + assert_eq!(more_precision, String::from("1.235e+8")); + assert_eq!(exact_precision, String::from("123456789")); + assert_eq!( + over_precision, + String::from("123456789.00000000000000000000000000000000000000000") + ); + assert_eq!(neg_precision, String::from("-1.235e+8")); +} + +#[test] +fn to_string() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var default_string = Number().toString(); + var int_string = Number(123).toString(); + var float_string = Number(1.234).toString(); + var exp_string = Number("1.2e+4").toString(); + var neg_string = Number(-1.2).toString(); + "#; + + forward(&mut engine, init); + let default_string = forward(&mut engine, "default_string"); + let int_string = forward(&mut engine, "int_string"); + let float_string = forward(&mut engine, "float_string"); + let exp_string = forward(&mut engine, "exp_string"); + let neg_string = forward(&mut engine, "neg_string"); + + assert_eq!(default_string, String::from("0")); + assert_eq!(int_string, String::from("123")); + assert_eq!(float_string, String::from("1.234")); + assert_eq!(exp_string, String::from("12000")); + assert_eq!(neg_string, String::from("-1.2")); +} + +#[test] +fn value_of() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + // TODO: In addition to parsing numbers from strings, parse them bare As of October 2019 + // the parser does not understand scientific e.g., Xe+Y or -Xe-Y notation. + let init = r#" + var default_val = Number().valueOf(); + var int_val = Number("123").valueOf(); + var float_val = Number(1.234).valueOf(); + var exp_val = Number("1.2e+4").valueOf() + var neg_val = Number("-1.2e+4").valueOf() + "#; + + forward(&mut engine, init); + let default_val = forward_val(&mut engine, "default_val").unwrap(); + let int_val = forward_val(&mut engine, "int_val").unwrap(); + let float_val = forward_val(&mut engine, "float_val").unwrap(); + let exp_val = forward_val(&mut engine, "exp_val").unwrap(); + let neg_val = forward_val(&mut engine, "neg_val").unwrap(); + + assert_eq!(default_val.to_num(), f64::from(0)); + assert_eq!(int_val.to_num(), f64::from(123)); + assert_eq!(float_val.to_num(), f64::from(1.234)); + assert_eq!(exp_val.to_num(), f64::from(12000)); + assert_eq!(neg_val.to_num(), f64::from(-12000)); +} diff --git a/boa/src/builtins/regexp.rs b/boa/src/builtins/regexp/mod.rs similarity index 75% rename from boa/src/builtins/regexp.rs rename to boa/src/builtins/regexp/mod.rs index a0878860f5..889f237be5 100644 --- a/boa/src/builtins/regexp.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -356,116 +356,4 @@ pub fn create_constructor(global: &Value) -> Value { } #[cfg(test)] -mod tests { - use super::*; - use crate::exec::Executor; - use crate::forward; - use crate::realm::Realm; - - #[test] - fn test_constructors() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var constructed = new RegExp("[0-9]+(\\.[0-9]+)?"); - var literal = /[0-9]+(\.[0-9]+)?/; - var ctor_literal = new RegExp(/[0-9]+(\.[0-9]+)?/); - "#; - - forward(&mut engine, init); - assert_eq!(forward(&mut engine, "constructed.test('1.0')"), "true"); - assert_eq!(forward(&mut engine, "literal.test('1.0')"), "true"); - assert_eq!(forward(&mut engine, "ctor_literal.test('1.0')"), "true"); - } - - #[test] - fn check_regexp_constructor_is_function() { - let global = ValueData::new_obj(None); - let regexp_constructor = create_constructor(&global); - assert_eq!(regexp_constructor.is_function(), true); - } - - // TODO: uncomment this test when property getters are supported - - // #[test] - // fn test_flags() { - // let mut engine = Executor::new(); - // let init = r#" - // var re_gi = /test/gi; - // var re_sm = /test/sm; - // "#; - // - // forward(&mut engine, init); - // assert_eq!(forward(&mut engine, "re_gi.global"), "true"); - // assert_eq!(forward(&mut engine, "re_gi.ignoreCase"), "true"); - // assert_eq!(forward(&mut engine, "re_gi.multiline"), "false"); - // assert_eq!(forward(&mut engine, "re_gi.dotAll"), "false"); - // assert_eq!(forward(&mut engine, "re_gi.unicode"), "false"); - // assert_eq!(forward(&mut engine, "re_gi.sticky"), "false"); - // assert_eq!(forward(&mut engine, "re_gi.flags"), "gi"); - // - // assert_eq!(forward(&mut engine, "re_sm.global"), "false"); - // assert_eq!(forward(&mut engine, "re_sm.ignoreCase"), "false"); - // assert_eq!(forward(&mut engine, "re_sm.multiline"), "true"); - // assert_eq!(forward(&mut engine, "re_sm.dotAll"), "true"); - // assert_eq!(forward(&mut engine, "re_sm.unicode"), "false"); - // assert_eq!(forward(&mut engine, "re_sm.sticky"), "false"); - // assert_eq!(forward(&mut engine, "re_sm.flags"), "ms"); - // } - - #[test] - fn test_last_index() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var regex = /[0-9]+(\.[0-9]+)?/g; - "#; - - forward(&mut engine, init); - assert_eq!(forward(&mut engine, "regex.lastIndex"), "0"); - assert_eq!(forward(&mut engine, "regex.test('1.0foo')"), "true"); - assert_eq!(forward(&mut engine, "regex.lastIndex"), "3"); - assert_eq!(forward(&mut engine, "regex.test('1.0foo')"), "false"); - assert_eq!(forward(&mut engine, "regex.lastIndex"), "0"); - } - - #[test] - fn test_exec() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var re = /quick\s(brown).+?(jumps)/ig; - var result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog'); - "#; - - forward(&mut engine, init); - assert_eq!(forward(&mut engine, "result[0]"), "Quick Brown Fox Jumps"); - assert_eq!(forward(&mut engine, "result[1]"), "Brown"); - assert_eq!(forward(&mut engine, "result[2]"), "Jumps"); - assert_eq!(forward(&mut engine, "result.index"), "4"); - assert_eq!( - forward(&mut engine, "result.input"), - "The Quick Brown Fox Jumps Over The Lazy Dog" - ); - } - - #[test] - fn test_to_string() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - - assert_eq!( - forward(&mut engine, "(new RegExp('a+b+c')).toString()"), - "/a+b+c/" - ); - assert_eq!( - forward(&mut engine, "(new RegExp('bar', 'g')).toString()"), - "/bar/g" - ); - assert_eq!( - forward(&mut engine, "(new RegExp('\\\\n', 'g')).toString()"), - "/\\n/g" - ); - assert_eq!(forward(&mut engine, "/\\n/g.toString()"), "/\\n/g"); - } -} +mod tests; diff --git a/boa/src/builtins/regexp/tests.rs b/boa/src/builtins/regexp/tests.rs new file mode 100644 index 0000000000..62f00964ad --- /dev/null +++ b/boa/src/builtins/regexp/tests.rs @@ -0,0 +1,111 @@ +use super::*; +use crate::exec::Executor; +use crate::forward; +use crate::realm::Realm; + +#[test] +fn test_constructors() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var constructed = new RegExp("[0-9]+(\\.[0-9]+)?"); + var literal = /[0-9]+(\.[0-9]+)?/; + var ctor_literal = new RegExp(/[0-9]+(\.[0-9]+)?/); + "#; + + forward(&mut engine, init); + assert_eq!(forward(&mut engine, "constructed.test('1.0')"), "true"); + assert_eq!(forward(&mut engine, "literal.test('1.0')"), "true"); + assert_eq!(forward(&mut engine, "ctor_literal.test('1.0')"), "true"); +} + +#[test] +fn check_regexp_constructor_is_function() { + let global = ValueData::new_obj(None); + let regexp_constructor = create_constructor(&global); + assert_eq!(regexp_constructor.is_function(), true); +} + +// TODO: uncomment this test when property getters are supported + +// #[test] +// fn test_flags() { +// let mut engine = Executor::new(); +// let init = r#" +// var re_gi = /test/gi; +// var re_sm = /test/sm; +// "#; +// +// forward(&mut engine, init); +// assert_eq!(forward(&mut engine, "re_gi.global"), "true"); +// assert_eq!(forward(&mut engine, "re_gi.ignoreCase"), "true"); +// assert_eq!(forward(&mut engine, "re_gi.multiline"), "false"); +// assert_eq!(forward(&mut engine, "re_gi.dotAll"), "false"); +// assert_eq!(forward(&mut engine, "re_gi.unicode"), "false"); +// assert_eq!(forward(&mut engine, "re_gi.sticky"), "false"); +// assert_eq!(forward(&mut engine, "re_gi.flags"), "gi"); +// +// assert_eq!(forward(&mut engine, "re_sm.global"), "false"); +// assert_eq!(forward(&mut engine, "re_sm.ignoreCase"), "false"); +// assert_eq!(forward(&mut engine, "re_sm.multiline"), "true"); +// assert_eq!(forward(&mut engine, "re_sm.dotAll"), "true"); +// assert_eq!(forward(&mut engine, "re_sm.unicode"), "false"); +// assert_eq!(forward(&mut engine, "re_sm.sticky"), "false"); +// assert_eq!(forward(&mut engine, "re_sm.flags"), "ms"); +// } + +#[test] +fn test_last_index() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var regex = /[0-9]+(\.[0-9]+)?/g; + "#; + + forward(&mut engine, init); + assert_eq!(forward(&mut engine, "regex.lastIndex"), "0"); + assert_eq!(forward(&mut engine, "regex.test('1.0foo')"), "true"); + assert_eq!(forward(&mut engine, "regex.lastIndex"), "3"); + assert_eq!(forward(&mut engine, "regex.test('1.0foo')"), "false"); + assert_eq!(forward(&mut engine, "regex.lastIndex"), "0"); +} + +#[test] +fn test_exec() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var re = /quick\s(brown).+?(jumps)/ig; + var result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog'); + "#; + + forward(&mut engine, init); + assert_eq!(forward(&mut engine, "result[0]"), "Quick Brown Fox Jumps"); + assert_eq!(forward(&mut engine, "result[1]"), "Brown"); + assert_eq!(forward(&mut engine, "result[2]"), "Jumps"); + assert_eq!(forward(&mut engine, "result.index"), "4"); + assert_eq!( + forward(&mut engine, "result.input"), + "The Quick Brown Fox Jumps Over The Lazy Dog" + ); +} + +#[test] +fn test_to_string() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + + assert_eq!( + forward(&mut engine, "(new RegExp('a+b+c')).toString()"), + "/a+b+c/" + ); + assert_eq!( + forward(&mut engine, "(new RegExp('bar', 'g')).toString()"), + "/bar/g" + ); + assert_eq!( + forward(&mut engine, "(new RegExp('\\\\n', 'g')).toString()"), + "/\\n/g" + ); + assert_eq!(forward(&mut engine, "/\\n/g.toString()"), "/\\n/g"); +} diff --git a/boa/src/builtins/string.rs b/boa/src/builtins/string/mod.rs similarity index 78% rename from boa/src/builtins/string.rs rename to boa/src/builtins/string/mod.rs index 3135e44102..c3ffc89813 100644 --- a/boa/src/builtins/string.rs +++ b/boa/src/builtins/string/mod.rs @@ -1,3 +1,6 @@ +#[cfg(test)] +mod tests; + use crate::{ builtins::{ function::NativeFunctionData, @@ -881,312 +884,3 @@ pub fn create_constructor(global: &Value) -> Value { pub fn init(global: &Value) { global.set_field_slice("String", create_constructor(global)); } - -#[cfg(test)] -mod tests { - use super::*; - use crate::exec::Executor; - use crate::realm::Realm; - use crate::{forward, forward_val}; - - #[test] - fn check_string_constructor_is_function() { - let global = ValueData::new_obj(None); - let string_constructor = create_constructor(&global); - assert_eq!(string_constructor.is_function(), true); - } - - #[test] - // TODO: re-enable when getProperty() is finished; - // fn length() { - // //TEST262: https://github.com/tc39/test262/blob/master/test/built-ins/String/length.js - // let mut engine = Executor::new(); - // let init = r#" - // const a = new String(' '); - // const b = new String('\ud834\udf06'); - // const c = new String(' \b '); - // cosnt d = new String('中文长度') - // "#; - // forward(&mut engine, init); - // let a = forward(&mut engine, "a.length"); - // assert_eq!(a, String::from("1")); - // let b = forward(&mut engine, "b.length"); - // // TODO: fix this - // // unicode surrogate pair length should be 1 - // // utf16/usc2 length should be 2 - // // utf8 length should be 4 - // //assert_eq!(b, String::from("2")); - // let c = forward(&mut engine, "c.length"); - // assert_eq!(c, String::from("3")); - // let d = forward(&mut engine, "d.length"); - // assert_eq!(d, String::from("4")); - // } - #[test] - fn concat() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var hello = new String('Hello, '); - var world = new String('world! '); - var nice = new String('Have a nice day.'); - "#; - forward(&mut engine, init); - - // Todo: fix this - let _a = forward(&mut engine, "hello.concat(world, nice)"); - let _b = forward(&mut engine, "hello + world + nice"); - // assert_eq!(a, String::from("Hello, world! Have a nice day.")); - // assert_eq!(b, String::from("Hello, world! Have a nice day.")); - } - - #[allow(clippy::result_unwrap_used)] - #[test] - /// Test the correct type is returned from call and construct - fn construct_and_call() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var hello = new String('Hello'); - var world = String('world'); - "#; - forward(&mut engine, init); - let hello = forward_val(&mut engine, "hello").unwrap(); - let world = forward_val(&mut engine, "world").unwrap(); - - assert_eq!(hello.is_object(), true); - assert_eq!(world.is_string(), true); - } - - #[test] - fn repeat() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var empty = new String(''); - var en = new String('english'); - var zh = new String('中文'); - "#; - forward(&mut engine, init); - - let empty = String::from(""); - assert_eq!(forward(&mut engine, "empty.repeat(0)"), empty); - assert_eq!(forward(&mut engine, "empty.repeat(1)"), empty); - - assert_eq!(forward(&mut engine, "en.repeat(0)"), empty); - assert_eq!(forward(&mut engine, "zh.repeat(0)"), empty); - - assert_eq!( - forward(&mut engine, "en.repeat(1)"), - String::from("english") - ); - assert_eq!( - forward(&mut engine, "zh.repeat(2)"), - String::from("中文中文") - ); - } - - #[test] - fn replace() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var a = "abc"; - a = a.replace("a", "2"); - a - "#; - forward(&mut engine, init); - - let empty = String::from("2bc"); - assert_eq!(forward(&mut engine, "a"), empty); - } - - #[test] - fn replace_with_function() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var a = "ecmascript is cool"; - var p1, p2, p3; - var replacer = (match, cap1, cap2, cap3) => { - p1 = cap1; - p2 = cap2; - p3 = cap3; - return "awesome!"; - }; - - a = a.replace(/c(o)(o)(l)/, replacer); - a; - "#; - forward(&mut engine, init); - assert_eq!( - forward(&mut engine, "a"), - String::from("ecmascript is awesome!") - ); - - assert_eq!(forward(&mut engine, "p1"), String::from("o")); - assert_eq!(forward(&mut engine, "p2"), String::from("o")); - assert_eq!(forward(&mut engine, "p3"), String::from("l")); - } - - #[test] - fn starts_with() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var empty = new String(''); - var en = new String('english'); - var zh = new String('中文'); - - var emptyLiteral = ''; - var enLiteral = 'english'; - var zhLiteral = '中文'; - "#; - forward(&mut engine, init); - let pass = String::from("true"); - assert_eq!(forward(&mut engine, "empty.startsWith('')"), pass); - assert_eq!(forward(&mut engine, "en.startsWith('e')"), pass); - assert_eq!(forward(&mut engine, "zh.startsWith('中')"), pass); - - assert_eq!(forward(&mut engine, "emptyLiteral.startsWith('')"), pass); - assert_eq!(forward(&mut engine, "enLiteral.startsWith('e')"), pass); - assert_eq!(forward(&mut engine, "zhLiteral.startsWith('中')"), pass); - } - - #[test] - fn ends_with() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var empty = new String(''); - var en = new String('english'); - var zh = new String('中文'); - - var emptyLiteral = ''; - var enLiteral = 'english'; - var zhLiteral = '中文'; - "#; - forward(&mut engine, init); - let pass = String::from("true"); - assert_eq!(forward(&mut engine, "empty.endsWith('')"), pass); - assert_eq!(forward(&mut engine, "en.endsWith('h')"), pass); - assert_eq!(forward(&mut engine, "zh.endsWith('文')"), pass); - - assert_eq!(forward(&mut engine, "emptyLiteral.endsWith('')"), pass); - assert_eq!(forward(&mut engine, "enLiteral.endsWith('h')"), pass); - assert_eq!(forward(&mut engine, "zhLiteral.endsWith('文')"), pass); - } - - #[test] - fn match_all() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - - assert_eq!( - forward(&mut engine, "'aa'.matchAll(null).length"), - String::from("0") - ); - assert_eq!( - forward(&mut engine, "'aa'.matchAll(/b/).length"), - String::from("0") - ); - assert_eq!( - forward(&mut engine, "'aa'.matchAll(/a/).length"), - String::from("1") - ); - assert_eq!( - forward(&mut engine, "'aa'.matchAll(/a/g).length"), - String::from("2") - ); - - forward( - &mut engine, - "var groupMatches = 'test1test2'.matchAll(/t(e)(st(\\d?))/g)", - ); - assert_eq!( - forward(&mut engine, "groupMatches.length"), - String::from("2") - ); - assert_eq!( - forward(&mut engine, "groupMatches[0][1]"), - String::from("e") - ); - assert_eq!( - forward(&mut engine, "groupMatches[0][2]"), - String::from("st1") - ); - assert_eq!( - forward(&mut engine, "groupMatches[0][3]"), - String::from("1") - ); - assert_eq!( - forward(&mut engine, "groupMatches[1][3]"), - String::from("2") - ); - - assert_eq!( - forward( - &mut engine, - "'test1test2'.matchAll(/t(e)(st(\\d?))/).length" - ), - String::from("1") - ); - - let init = r#" - var regexp = RegExp('foo[a-z]*','g'); - var str = 'table football, foosball'; - var matches = str.matchAll(regexp); - "#; - forward(&mut engine, init); - assert_eq!( - forward(&mut engine, "matches[0][0]"), - String::from("football") - ); - assert_eq!(forward(&mut engine, "matches[0].index"), String::from("6")); - assert_eq!( - forward(&mut engine, "matches[1][0]"), - String::from("foosball") - ); - assert_eq!(forward(&mut engine, "matches[1].index"), String::from("16")); - } - - #[test] - fn test_match() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var str = new String('The Quick Brown Fox Jumps Over The Lazy Dog'); - var result1 = str.match(/quick\s(brown).+?(jumps)/i); - var result2 = str.match(/[A-Z]/g); - var result3 = str.match("T"); - var result4 = str.match(RegExp("B", 'g')); - "#; - - forward(&mut engine, init); - assert_eq!(forward(&mut engine, "result1[0]"), "Quick Brown Fox Jumps"); - assert_eq!(forward(&mut engine, "result1[1]"), "Brown"); - assert_eq!(forward(&mut engine, "result1[2]"), "Jumps"); - assert_eq!(forward(&mut engine, "result1.index"), "4"); - assert_eq!( - forward(&mut engine, "result1.input"), - "The Quick Brown Fox Jumps Over The Lazy Dog" - ); - - assert_eq!(forward(&mut engine, "result2[0]"), "T"); - assert_eq!(forward(&mut engine, "result2[1]"), "Q"); - assert_eq!(forward(&mut engine, "result2[2]"), "B"); - assert_eq!(forward(&mut engine, "result2[3]"), "F"); - assert_eq!(forward(&mut engine, "result2[4]"), "J"); - assert_eq!(forward(&mut engine, "result2[5]"), "O"); - assert_eq!(forward(&mut engine, "result2[6]"), "T"); - assert_eq!(forward(&mut engine, "result2[7]"), "L"); - assert_eq!(forward(&mut engine, "result2[8]"), "D"); - - assert_eq!(forward(&mut engine, "result3[0]"), "T"); - assert_eq!(forward(&mut engine, "result3.index"), "0"); - assert_eq!( - forward(&mut engine, "result3.input"), - "The Quick Brown Fox Jumps Over The Lazy Dog" - ); - assert_eq!(forward(&mut engine, "result4[0]"), "B"); - } -} diff --git a/boa/src/builtins/string/tests.rs b/boa/src/builtins/string/tests.rs new file mode 100644 index 0000000000..b64aa7a7e6 --- /dev/null +++ b/boa/src/builtins/string/tests.rs @@ -0,0 +1,305 @@ +use super::*; +use crate::exec::Executor; +use crate::realm::Realm; +use crate::{forward, forward_val}; + +#[test] +fn check_string_constructor_is_function() { + let global = ValueData::new_obj(None); + let string_constructor = create_constructor(&global); + assert_eq!(string_constructor.is_function(), true); +} + +#[test] +// TODO: re-enable when getProperty() is finished; +// fn length() { +// //TEST262: https://github.com/tc39/test262/blob/master/test/built-ins/String/length.js +// let mut engine = Executor::new(); +// let init = r#" +// const a = new String(' '); +// const b = new String('\ud834\udf06'); +// const c = new String(' \b '); +// cosnt d = new String('中文长度') +// "#; +// forward(&mut engine, init); +// let a = forward(&mut engine, "a.length"); +// assert_eq!(a, String::from("1")); +// let b = forward(&mut engine, "b.length"); +// // TODO: fix this +// // unicode surrogate pair length should be 1 +// // utf16/usc2 length should be 2 +// // utf8 length should be 4 +// //assert_eq!(b, String::from("2")); +// let c = forward(&mut engine, "c.length"); +// assert_eq!(c, String::from("3")); +// let d = forward(&mut engine, "d.length"); +// assert_eq!(d, String::from("4")); +// } +#[test] +fn concat() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var hello = new String('Hello, '); + var world = new String('world! '); + var nice = new String('Have a nice day.'); + "#; + forward(&mut engine, init); + + // Todo: fix this + let _a = forward(&mut engine, "hello.concat(world, nice)"); + let _b = forward(&mut engine, "hello + world + nice"); + // assert_eq!(a, String::from("Hello, world! Have a nice day.")); + // assert_eq!(b, String::from("Hello, world! Have a nice day.")); +} + +#[allow(clippy::result_unwrap_used)] +#[test] +/// Test the correct type is returned from call and construct +fn construct_and_call() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var hello = new String('Hello'); + var world = String('world'); + "#; + forward(&mut engine, init); + let hello = forward_val(&mut engine, "hello").unwrap(); + let world = forward_val(&mut engine, "world").unwrap(); + + assert_eq!(hello.is_object(), true); + assert_eq!(world.is_string(), true); +} + +#[test] +fn repeat() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var empty = new String(''); + var en = new String('english'); + var zh = new String('中文'); + "#; + forward(&mut engine, init); + + let empty = String::from(""); + assert_eq!(forward(&mut engine, "empty.repeat(0)"), empty); + assert_eq!(forward(&mut engine, "empty.repeat(1)"), empty); + + assert_eq!(forward(&mut engine, "en.repeat(0)"), empty); + assert_eq!(forward(&mut engine, "zh.repeat(0)"), empty); + + assert_eq!( + forward(&mut engine, "en.repeat(1)"), + String::from("english") + ); + assert_eq!( + forward(&mut engine, "zh.repeat(2)"), + String::from("中文中文") + ); +} + +#[test] +fn replace() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var a = "abc"; + a = a.replace("a", "2"); + a + "#; + forward(&mut engine, init); + + let empty = String::from("2bc"); + assert_eq!(forward(&mut engine, "a"), empty); +} + +#[test] +fn replace_with_function() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var a = "ecmascript is cool"; + var p1, p2, p3; + var replacer = (match, cap1, cap2, cap3) => { + p1 = cap1; + p2 = cap2; + p3 = cap3; + return "awesome!"; + }; + + a = a.replace(/c(o)(o)(l)/, replacer); + a; + "#; + forward(&mut engine, init); + assert_eq!( + forward(&mut engine, "a"), + String::from("ecmascript is awesome!") + ); + + assert_eq!(forward(&mut engine, "p1"), String::from("o")); + assert_eq!(forward(&mut engine, "p2"), String::from("o")); + assert_eq!(forward(&mut engine, "p3"), String::from("l")); +} + +#[test] +fn starts_with() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var empty = new String(''); + var en = new String('english'); + var zh = new String('中文'); + + var emptyLiteral = ''; + var enLiteral = 'english'; + var zhLiteral = '中文'; + "#; + forward(&mut engine, init); + let pass = String::from("true"); + assert_eq!(forward(&mut engine, "empty.startsWith('')"), pass); + assert_eq!(forward(&mut engine, "en.startsWith('e')"), pass); + assert_eq!(forward(&mut engine, "zh.startsWith('中')"), pass); + + assert_eq!(forward(&mut engine, "emptyLiteral.startsWith('')"), pass); + assert_eq!(forward(&mut engine, "enLiteral.startsWith('e')"), pass); + assert_eq!(forward(&mut engine, "zhLiteral.startsWith('中')"), pass); +} + +#[test] +fn ends_with() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var empty = new String(''); + var en = new String('english'); + var zh = new String('中文'); + + var emptyLiteral = ''; + var enLiteral = 'english'; + var zhLiteral = '中文'; + "#; + forward(&mut engine, init); + let pass = String::from("true"); + assert_eq!(forward(&mut engine, "empty.endsWith('')"), pass); + assert_eq!(forward(&mut engine, "en.endsWith('h')"), pass); + assert_eq!(forward(&mut engine, "zh.endsWith('文')"), pass); + + assert_eq!(forward(&mut engine, "emptyLiteral.endsWith('')"), pass); + assert_eq!(forward(&mut engine, "enLiteral.endsWith('h')"), pass); + assert_eq!(forward(&mut engine, "zhLiteral.endsWith('文')"), pass); +} + +#[test] +fn match_all() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + + assert_eq!( + forward(&mut engine, "'aa'.matchAll(null).length"), + String::from("0") + ); + assert_eq!( + forward(&mut engine, "'aa'.matchAll(/b/).length"), + String::from("0") + ); + assert_eq!( + forward(&mut engine, "'aa'.matchAll(/a/).length"), + String::from("1") + ); + assert_eq!( + forward(&mut engine, "'aa'.matchAll(/a/g).length"), + String::from("2") + ); + + forward( + &mut engine, + "var groupMatches = 'test1test2'.matchAll(/t(e)(st(\\d?))/g)", + ); + assert_eq!( + forward(&mut engine, "groupMatches.length"), + String::from("2") + ); + assert_eq!( + forward(&mut engine, "groupMatches[0][1]"), + String::from("e") + ); + assert_eq!( + forward(&mut engine, "groupMatches[0][2]"), + String::from("st1") + ); + assert_eq!( + forward(&mut engine, "groupMatches[0][3]"), + String::from("1") + ); + assert_eq!( + forward(&mut engine, "groupMatches[1][3]"), + String::from("2") + ); + + assert_eq!( + forward( + &mut engine, + "'test1test2'.matchAll(/t(e)(st(\\d?))/).length" + ), + String::from("1") + ); + + let init = r#" + var regexp = RegExp('foo[a-z]*','g'); + var str = 'table football, foosball'; + var matches = str.matchAll(regexp); + "#; + forward(&mut engine, init); + assert_eq!( + forward(&mut engine, "matches[0][0]"), + String::from("football") + ); + assert_eq!(forward(&mut engine, "matches[0].index"), String::from("6")); + assert_eq!( + forward(&mut engine, "matches[1][0]"), + String::from("foosball") + ); + assert_eq!(forward(&mut engine, "matches[1].index"), String::from("16")); +} + +#[test] +fn test_match() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var str = new String('The Quick Brown Fox Jumps Over The Lazy Dog'); + var result1 = str.match(/quick\s(brown).+?(jumps)/i); + var result2 = str.match(/[A-Z]/g); + var result3 = str.match("T"); + var result4 = str.match(RegExp("B", 'g')); + "#; + + forward(&mut engine, init); + assert_eq!(forward(&mut engine, "result1[0]"), "Quick Brown Fox Jumps"); + assert_eq!(forward(&mut engine, "result1[1]"), "Brown"); + assert_eq!(forward(&mut engine, "result1[2]"), "Jumps"); + assert_eq!(forward(&mut engine, "result1.index"), "4"); + assert_eq!( + forward(&mut engine, "result1.input"), + "The Quick Brown Fox Jumps Over The Lazy Dog" + ); + + assert_eq!(forward(&mut engine, "result2[0]"), "T"); + assert_eq!(forward(&mut engine, "result2[1]"), "Q"); + assert_eq!(forward(&mut engine, "result2[2]"), "B"); + assert_eq!(forward(&mut engine, "result2[3]"), "F"); + assert_eq!(forward(&mut engine, "result2[4]"), "J"); + assert_eq!(forward(&mut engine, "result2[5]"), "O"); + assert_eq!(forward(&mut engine, "result2[6]"), "T"); + assert_eq!(forward(&mut engine, "result2[7]"), "L"); + assert_eq!(forward(&mut engine, "result2[8]"), "D"); + + assert_eq!(forward(&mut engine, "result3[0]"), "T"); + assert_eq!(forward(&mut engine, "result3.index"), "0"); + assert_eq!( + forward(&mut engine, "result3.input"), + "The Quick Brown Fox Jumps Over The Lazy Dog" + ); + assert_eq!(forward(&mut engine, "result4[0]"), "B"); +} diff --git a/boa/src/builtins/symbol.rs b/boa/src/builtins/symbol/mod.rs similarity index 74% rename from boa/src/builtins/symbol.rs rename to boa/src/builtins/symbol/mod.rs index 9cc192f103..4e3ebae4cb 100644 --- a/boa/src/builtins/symbol.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -1,3 +1,6 @@ +#[cfg(test)] +mod tests; + use crate::{ builtins::{ object::{ @@ -75,42 +78,3 @@ pub fn create_constructor(global: &Value) -> Value { symbol_constructor_value } - -#[cfg(test)] -mod tests { - use super::*; - use crate::exec::Executor; - use crate::realm::Realm; - use crate::{forward, forward_val}; - - #[test] - fn check_symbol_constructor_is_function() { - let global: Gc = ValueData::new_obj(None); - let symbol_constructor = create_constructor(&global); - assert_eq!(symbol_constructor.is_function(), true); - } - - #[test] - fn call_symbol_and_check_return_type() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var sym = Symbol(); - "#; - forward(&mut engine, init); - let sym = forward_val(&mut engine, "sym").unwrap(); - assert_eq!(sym.is_symbol(), true); - } - - #[test] - fn print_symbol_expect_description() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - let init = r#" - var sym = Symbol("Hello"); - "#; - forward(&mut engine, init); - let sym = forward_val(&mut engine, "sym.toString()").unwrap(); - assert_eq!(sym.to_string(), "Symbol(Hello)"); - } -} diff --git a/boa/src/builtins/symbol/tests.rs b/boa/src/builtins/symbol/tests.rs new file mode 100644 index 0000000000..8854a0fd54 --- /dev/null +++ b/boa/src/builtins/symbol/tests.rs @@ -0,0 +1,35 @@ +use super::*; +use crate::exec::Executor; +use crate::realm::Realm; +use crate::{forward, forward_val}; + +#[test] +fn check_symbol_constructor_is_function() { + let global: Gc = ValueData::new_obj(None); + let symbol_constructor = create_constructor(&global); + assert_eq!(symbol_constructor.is_function(), true); +} + +#[test] +fn call_symbol_and_check_return_type() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var sym = Symbol(); + "#; + forward(&mut engine, init); + let sym = forward_val(&mut engine, "sym").unwrap(); + assert_eq!(sym.is_symbol(), true); +} + +#[test] +fn print_symbol_expect_description() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + let init = r#" + var sym = Symbol("Hello"); + "#; + forward(&mut engine, init); + let sym = forward_val(&mut engine, "sym.toString()").unwrap(); + assert_eq!(sym.to_string(), "Symbol(Hello)"); +} diff --git a/boa/src/builtins/value.rs b/boa/src/builtins/value/mod.rs similarity index 96% rename from boa/src/builtins/value.rs rename to boa/src/builtins/value/mod.rs index dcc6c4c697..f9a9050098 100644 --- a/boa/src/builtins/value.rs +++ b/boa/src/builtins/value/mod.rs @@ -1,3 +1,6 @@ +#[cfg(test)] +mod tests; + use crate::builtins::{ function::{Function, NativeFunction, NativeFunctionData}, object::{ @@ -1235,55 +1238,3 @@ pub fn same_value_non_number(x: &Value, y: &Value) -> bool { _ => false, } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn check_is_object() { - let val = ValueData::new_obj(None); - assert_eq!(val.is_object(), true); - } - - #[test] - fn check_string_to_value() { - let s = String::from("Hello"); - let v = s.to_value(); - assert_eq!(v.is_string(), true); - assert_eq!(v.is_null(), false); - } - - #[test] - fn check_undefined() { - let u = ValueData::Undefined; - assert_eq!(u.get_type(), "undefined"); - assert_eq!(u.to_string(), "undefined"); - } - - #[test] - fn check_get_set_field() { - let obj = ValueData::new_obj(None); - // Create string and convert it to a Value - let s = String::from("bar").to_value(); - obj.set_field_slice("foo", s); - assert_eq!(obj.get_field_slice("foo").to_string(), "bar"); - } - - #[test] - fn check_integer_is_true() { - assert_eq!(1.to_value().is_true(), true); - assert_eq!(0.to_value().is_true(), false); - assert_eq!((-1).to_value().is_true(), true); - } - - #[test] - fn check_number_is_true() { - assert_eq!(1.0.to_value().is_true(), true); - assert_eq!(0.1.to_value().is_true(), true); - assert_eq!(0.0.to_value().is_true(), false); - assert_eq!((-0.0).to_value().is_true(), false); - assert_eq!((-1.0).to_value().is_true(), true); - assert_eq!(NAN.to_value().is_true(), false); - } -} diff --git a/boa/src/builtins/value/tests.rs b/boa/src/builtins/value/tests.rs new file mode 100644 index 0000000000..0327dece7f --- /dev/null +++ b/boa/src/builtins/value/tests.rs @@ -0,0 +1,48 @@ +use super::*; + +#[test] +fn check_is_object() { + let val = ValueData::new_obj(None); + assert_eq!(val.is_object(), true); +} + +#[test] +fn check_string_to_value() { + let s = String::from("Hello"); + let v = s.to_value(); + assert_eq!(v.is_string(), true); + assert_eq!(v.is_null(), false); +} + +#[test] +fn check_undefined() { + let u = ValueData::Undefined; + assert_eq!(u.get_type(), "undefined"); + assert_eq!(u.to_string(), "undefined"); +} + +#[test] +fn check_get_set_field() { + let obj = ValueData::new_obj(None); + // Create string and convert it to a Value + let s = String::from("bar").to_value(); + obj.set_field_slice("foo", s); + assert_eq!(obj.get_field_slice("foo").to_string(), "bar"); +} + +#[test] +fn check_integer_is_true() { + assert_eq!(1.to_value().is_true(), true); + assert_eq!(0.to_value().is_true(), false); + assert_eq!((-1).to_value().is_true(), true); +} + +#[test] +fn check_number_is_true() { + assert_eq!(1.0.to_value().is_true(), true); + assert_eq!(0.1.to_value().is_true(), true); + assert_eq!(0.0.to_value().is_true(), false); + assert_eq!((-0.0).to_value().is_true(), false); + assert_eq!((-1.0).to_value().is_true(), true); + assert_eq!(NAN.to_value().is_true(), false); +} diff --git a/boa/src/exec.rs b/boa/src/exec/mod.rs similarity index 83% rename from boa/src/exec.rs rename to boa/src/exec/mod.rs index af4b80f72e..231e928132 100644 --- a/boa/src/exec.rs +++ b/boa/src/exec/mod.rs @@ -1,3 +1,6 @@ +#[cfg(test)] +mod tests; + use crate::{ builtins::{ array, @@ -781,243 +784,3 @@ impl Interpreter { Err(()) } } - -#[cfg(test)] -mod tests { - use crate::exec; - use crate::exec::Executor; - use crate::forward; - use crate::realm::Realm; - - #[test] - fn empty_let_decl_undefined() { - let scenario = r#" - let a; - a == undefined; - "#; - - let pass = String::from("true"); - - assert_eq!(exec(scenario), pass); - } - - #[test] - fn empty_var_decl_undefined() { - let scenario = r#" - let b; - b == undefined; - "#; - - let pass = String::from("true"); - - assert_eq!(exec(scenario), pass); - } - - #[test] - fn object_field_set() { - let scenario = r#" - let m = {}; - m['key'] = 22; - m['key'] - "#; - assert_eq!(exec(scenario), String::from("22")); - } - - #[test] - fn spread_with_arguments() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - - let scenario = r#" - const a = [1, "test", 3, 4]; - function foo(...a) { - return arguments; - } - - var result = foo(...a); - "#; - forward(&mut engine, scenario); - let one = forward(&mut engine, "result[0]"); - assert_eq!(one, String::from("1")); - - let two = forward(&mut engine, "result[1]"); - assert_eq!(two, String::from("test")); - - let three = forward(&mut engine, "result[2]"); - assert_eq!(three, String::from("3")); - - let four = forward(&mut engine, "result[3]"); - assert_eq!(four, String::from("4")); - } - - #[test] - fn array_rest_with_arguments() { - let realm = Realm::create(); - let mut engine = Executor::new(realm); - - let scenario = r#" - var b = [4, 5, 6] - var a = [1, 2, 3, ...b]; - "#; - forward(&mut engine, scenario); - let one = forward(&mut engine, "a"); - assert_eq!(one, String::from("[ 1, 2, 3, 4, 5, 6 ]")); - } - - #[test] - fn array_field_set() { - let element_changes = r#" - let m = [1, 2, 3]; - m[1] = 5; - m[1] - "#; - assert_eq!(exec(element_changes), String::from("5")); - - let length_changes = r#" - let m = [1, 2, 3]; - m[10] = 52; - m.length - "#; - assert_eq!(exec(length_changes), String::from("11")); - - let negative_index_wont_affect_length = r#" - let m = [1, 2, 3]; - m[-11] = 5; - m.length - "#; - assert_eq!(exec(negative_index_wont_affect_length), String::from("3")); - - let non_num_key_wont_affect_length = r#" - let m = [1, 2, 3]; - m["magic"] = 5; - m.length - "#; - assert_eq!(exec(non_num_key_wont_affect_length), String::from("3")); - } - - #[test] - fn test_tilde_operator() { - let float = r#" - let f = -1.2; - ~f - "#; - assert_eq!(exec(float), String::from("0")); - - let numeric = r#" - let f = 1789; - ~f - "#; - assert_eq!(exec(numeric), String::from("-1790")); - - // TODO: enable test after we have NaN - // let nan = r#" - // var m = NaN; - // ~m - // "#; - // assert_eq!(exec(nan), String::from("-1")); - - let object = r#" - let m = {}; - ~m - "#; - assert_eq!(exec(object), String::from("-1")); - - let boolean_true = r#" - ~true - "#; - assert_eq!(exec(boolean_true), String::from("-2")); - - let boolean_false = r#" - ~false - "#; - assert_eq!(exec(boolean_false), String::from("-1")); - } - - #[test] - fn test_early_return() { - let early_return = r#" - function early_return() { - if (true) { - return true; - } - return false; - } - early_return() - "#; - assert_eq!(exec(early_return), String::from("true")); - let early_return = r#" - function nested_fnct() { - return "nested"; - } - function outer_fnct() { - nested_fnct(); - return "outer"; - } - outer_fnct() - "#; - assert_eq!(exec(early_return), String::from("outer")); - } - - #[test] - fn test_short_circuit_evaluation() { - // OR operation - assert_eq!(exec("true || true"), String::from("true")); - assert_eq!(exec("true || false"), String::from("true")); - assert_eq!(exec("false || true"), String::from("true")); - assert_eq!(exec("false || false"), String::from("false")); - - // the second operand must NOT be evaluated if the first one resolve to `true`. - let short_circuit_eval = r#" - function add_one(counter) { - counter.value += 1; - return true; - } - let counter = { value: 0 }; - let _ = add_one(counter) || add_one(counter); - counter.value - "#; - assert_eq!(exec(short_circuit_eval), String::from("1")); - - // the second operand must be evaluated if the first one resolve to `false`. - let short_circuit_eval = r#" - function add_one(counter) { - counter.value += 1; - return false; - } - let counter = { value: 0 }; - let _ = add_one(counter) || add_one(counter); - counter.value - "#; - assert_eq!(exec(short_circuit_eval), String::from("2")); - - // AND operation - assert_eq!(exec("true && true"), String::from("true")); - assert_eq!(exec("true && false"), String::from("false")); - assert_eq!(exec("false && true"), String::from("false")); - assert_eq!(exec("false && false"), String::from("false")); - - // the second operand must be evaluated if the first one resolve to `true`. - let short_circuit_eval = r#" - function add_one(counter) { - counter.value += 1; - return true; - } - let counter = { value: 0 }; - let _ = add_one(counter) && add_one(counter); - counter.value - "#; - assert_eq!(exec(short_circuit_eval), String::from("2")); - - // the second operand must NOT be evaluated if the first one resolve to `false`. - let short_circuit_eval = r#" - function add_one(counter) { - counter.value += 1; - return false; - } - let counter = { value: 0 }; - let _ = add_one(counter) && add_one(counter); - counter.value - "#; - assert_eq!(exec(short_circuit_eval), String::from("1")); - } -} diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs new file mode 100644 index 0000000000..cf527ce451 --- /dev/null +++ b/boa/src/exec/tests.rs @@ -0,0 +1,236 @@ +use crate::exec; +use crate::exec::Executor; +use crate::forward; +use crate::realm::Realm; + +#[test] +fn empty_let_decl_undefined() { + let scenario = r#" + let a; + a == undefined; + "#; + + let pass = String::from("true"); + + assert_eq!(exec(scenario), pass); +} + +#[test] +fn empty_var_decl_undefined() { + let scenario = r#" + let b; + b == undefined; + "#; + + let pass = String::from("true"); + + assert_eq!(exec(scenario), pass); +} + +#[test] +fn object_field_set() { + let scenario = r#" + let m = {}; + m['key'] = 22; + m['key'] + "#; + assert_eq!(exec(scenario), String::from("22")); +} + +#[test] +fn spread_with_arguments() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + + let scenario = r#" + const a = [1, "test", 3, 4]; + function foo(...a) { + return arguments; + } + + var result = foo(...a); + "#; + forward(&mut engine, scenario); + let one = forward(&mut engine, "result[0]"); + assert_eq!(one, String::from("1")); + + let two = forward(&mut engine, "result[1]"); + assert_eq!(two, String::from("test")); + + let three = forward(&mut engine, "result[2]"); + assert_eq!(three, String::from("3")); + + let four = forward(&mut engine, "result[3]"); + assert_eq!(four, String::from("4")); +} + +#[test] +fn array_rest_with_arguments() { + let realm = Realm::create(); + let mut engine = Executor::new(realm); + + let scenario = r#" + var b = [4, 5, 6] + var a = [1, 2, 3, ...b]; + "#; + forward(&mut engine, scenario); + let one = forward(&mut engine, "a"); + assert_eq!(one, String::from("[ 1, 2, 3, 4, 5, 6 ]")); +} + +#[test] +fn array_field_set() { + let element_changes = r#" + let m = [1, 2, 3]; + m[1] = 5; + m[1] + "#; + assert_eq!(exec(element_changes), String::from("5")); + + let length_changes = r#" + let m = [1, 2, 3]; + m[10] = 52; + m.length + "#; + assert_eq!(exec(length_changes), String::from("11")); + + let negative_index_wont_affect_length = r#" + let m = [1, 2, 3]; + m[-11] = 5; + m.length + "#; + assert_eq!(exec(negative_index_wont_affect_length), String::from("3")); + + let non_num_key_wont_affect_length = r#" + let m = [1, 2, 3]; + m["magic"] = 5; + m.length + "#; + assert_eq!(exec(non_num_key_wont_affect_length), String::from("3")); +} + +#[test] +fn test_tilde_operator() { + let float = r#" + let f = -1.2; + ~f + "#; + assert_eq!(exec(float), String::from("0")); + + let numeric = r#" + let f = 1789; + ~f + "#; + assert_eq!(exec(numeric), String::from("-1790")); + + // TODO: enable test after we have NaN + // let nan = r#" + // var m = NaN; + // ~m + // "#; + // assert_eq!(exec(nan), String::from("-1")); + + let object = r#" + let m = {}; + ~m + "#; + assert_eq!(exec(object), String::from("-1")); + + let boolean_true = r#" + ~true + "#; + assert_eq!(exec(boolean_true), String::from("-2")); + + let boolean_false = r#" + ~false + "#; + assert_eq!(exec(boolean_false), String::from("-1")); +} + +#[test] +fn test_early_return() { + let early_return = r#" + function early_return() { + if (true) { + return true; + } + return false; + } + early_return() + "#; + assert_eq!(exec(early_return), String::from("true")); + let early_return = r#" + function nested_fnct() { + return "nested"; + } + function outer_fnct() { + nested_fnct(); + return "outer"; + } + outer_fnct() + "#; + assert_eq!(exec(early_return), String::from("outer")); +} + +#[test] +fn test_short_circuit_evaluation() { + // OR operation + assert_eq!(exec("true || true"), String::from("true")); + assert_eq!(exec("true || false"), String::from("true")); + assert_eq!(exec("false || true"), String::from("true")); + assert_eq!(exec("false || false"), String::from("false")); + + // the second operand must NOT be evaluated if the first one resolve to `true`. + let short_circuit_eval = r#" + function add_one(counter) { + counter.value += 1; + return true; + } + let counter = { value: 0 }; + let _ = add_one(counter) || add_one(counter); + counter.value + "#; + assert_eq!(exec(short_circuit_eval), String::from("1")); + + // the second operand must be evaluated if the first one resolve to `false`. + let short_circuit_eval = r#" + function add_one(counter) { + counter.value += 1; + return false; + } + let counter = { value: 0 }; + let _ = add_one(counter) || add_one(counter); + counter.value + "#; + assert_eq!(exec(short_circuit_eval), String::from("2")); + + // AND operation + assert_eq!(exec("true && true"), String::from("true")); + assert_eq!(exec("true && false"), String::from("false")); + assert_eq!(exec("false && true"), String::from("false")); + assert_eq!(exec("false && false"), String::from("false")); + + // the second operand must be evaluated if the first one resolve to `true`. + let short_circuit_eval = r#" + function add_one(counter) { + counter.value += 1; + return true; + } + let counter = { value: 0 }; + let _ = add_one(counter) && add_one(counter); + counter.value + "#; + assert_eq!(exec(short_circuit_eval), String::from("2")); + + // the second operand must NOT be evaluated if the first one resolve to `false`. + let short_circuit_eval = r#" + function add_one(counter) { + counter.value += 1; + return false; + } + let counter = { value: 0 }; + let _ = add_one(counter) && add_one(counter); + counter.value + "#; + assert_eq!(exec(short_circuit_eval), String::from("1")); +} diff --git a/boa/src/syntax/lexer.rs b/boa/src/syntax/lexer/mod.rs similarity index 61% rename from boa/src/syntax/lexer.rs rename to boa/src/syntax/lexer/mod.rs index dfb18b68ab..bd7675c2cc 100644 --- a/boa/src/syntax/lexer.rs +++ b/boa/src/syntax/lexer/mod.rs @@ -2,6 +2,10 @@ //! //! The Lexer splits its input source code into a sequence of input elements called tokens, represented by the [Token](../ast/token/struct.Token.html) structure. //! It also removes whitespace and comments and attaches them to the next token. + +#[cfg(test)] +mod tests; + use crate::syntax::ast::{ punc::Punctuator, token::{Token, TokenData}, @@ -692,502 +696,3 @@ impl<'a> Lexer<'a> { } } } - -#[allow(clippy::indexing_slicing)] -#[cfg(test)] -mod tests { - use super::*; - use crate::syntax::ast::keyword::Keyword; - - #[test] - fn check_single_line_comment() { - let s1 = "var \n//=\nx"; - let mut lexer = Lexer::new(s1); - lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].data, TokenData::Keyword(Keyword::Var)); - assert_eq!(lexer.tokens[1].data, TokenData::Comment("//=".to_owned())); - assert_eq!(lexer.tokens[2].data, TokenData::Identifier("x".to_string())); - } - - #[test] - fn check_multi_line_comment() { - let s = "var /* await \n break \n*/ x"; - let mut lexer = Lexer::new(s); - lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].data, TokenData::Keyword(Keyword::Var)); - assert_eq!( - lexer.tokens[1].data, - TokenData::Comment("/* await \n break \n*/".to_owned()) - ); - assert_eq!(lexer.tokens[2].data, TokenData::Identifier("x".to_string())); - } - - #[test] - fn check_string() { - let s = "'aaa' \"bbb\""; - let mut lexer = Lexer::new(s); - lexer.lex().expect("failed to lex"); - assert_eq!( - lexer.tokens[0].data, - TokenData::StringLiteral("aaa".to_string()) - ); - - assert_eq!( - lexer.tokens[1].data, - TokenData::StringLiteral("bbb".to_string()) - ); - } - - #[test] - fn check_punctuators() { - // https://tc39.es/ecma262/#sec-punctuators - let s = "{ ( ) [ ] . ... ; , < > <= >= == != === !== \ - + - * % -- << >> >>> & | ^ ! ~ && || ? : \ - = += -= *= &= **= ++ ** <<= >>= >>>= &= |= ^= =>"; - let mut lexer = Lexer::new(s); - lexer.lex().expect("failed to lex"); - assert_eq!( - lexer.tokens[0].data, - TokenData::Punctuator(Punctuator::OpenBlock) - ); - assert_eq!( - lexer.tokens[1].data, - TokenData::Punctuator(Punctuator::OpenParen) - ); - assert_eq!( - lexer.tokens[2].data, - TokenData::Punctuator(Punctuator::CloseParen) - ); - assert_eq!( - lexer.tokens[3].data, - TokenData::Punctuator(Punctuator::OpenBracket) - ); - assert_eq!( - lexer.tokens[4].data, - TokenData::Punctuator(Punctuator::CloseBracket) - ); - assert_eq!(lexer.tokens[5].data, TokenData::Punctuator(Punctuator::Dot)); - assert_eq!( - lexer.tokens[6].data, - TokenData::Punctuator(Punctuator::Spread) - ); - assert_eq!( - lexer.tokens[7].data, - TokenData::Punctuator(Punctuator::Semicolon) - ); - assert_eq!( - lexer.tokens[8].data, - TokenData::Punctuator(Punctuator::Comma) - ); - assert_eq!( - lexer.tokens[9].data, - TokenData::Punctuator(Punctuator::LessThan) - ); - assert_eq!( - lexer.tokens[10].data, - TokenData::Punctuator(Punctuator::GreaterThan) - ); - assert_eq!( - lexer.tokens[11].data, - TokenData::Punctuator(Punctuator::LessThanOrEq) - ); - assert_eq!( - lexer.tokens[12].data, - TokenData::Punctuator(Punctuator::GreaterThanOrEq) - ); - assert_eq!(lexer.tokens[13].data, TokenData::Punctuator(Punctuator::Eq)); - assert_eq!( - lexer.tokens[14].data, - TokenData::Punctuator(Punctuator::NotEq) - ); - assert_eq!( - lexer.tokens[15].data, - TokenData::Punctuator(Punctuator::StrictEq) - ); - assert_eq!( - lexer.tokens[16].data, - TokenData::Punctuator(Punctuator::StrictNotEq) - ); - assert_eq!( - lexer.tokens[17].data, - TokenData::Punctuator(Punctuator::Add) - ); - assert_eq!( - lexer.tokens[18].data, - TokenData::Punctuator(Punctuator::Sub) - ); - assert_eq!( - lexer.tokens[19].data, - TokenData::Punctuator(Punctuator::Mul) - ); - assert_eq!( - lexer.tokens[20].data, - TokenData::Punctuator(Punctuator::Mod) - ); - assert_eq!( - lexer.tokens[21].data, - TokenData::Punctuator(Punctuator::Dec) - ); - assert_eq!( - lexer.tokens[22].data, - TokenData::Punctuator(Punctuator::LeftSh) - ); - assert_eq!( - lexer.tokens[23].data, - TokenData::Punctuator(Punctuator::RightSh) - ); - assert_eq!( - lexer.tokens[24].data, - TokenData::Punctuator(Punctuator::URightSh) - ); - assert_eq!( - lexer.tokens[25].data, - TokenData::Punctuator(Punctuator::And) - ); - assert_eq!(lexer.tokens[26].data, TokenData::Punctuator(Punctuator::Or)); - assert_eq!( - lexer.tokens[27].data, - TokenData::Punctuator(Punctuator::Xor) - ); - assert_eq!( - lexer.tokens[28].data, - TokenData::Punctuator(Punctuator::Not) - ); - assert_eq!( - lexer.tokens[29].data, - TokenData::Punctuator(Punctuator::Neg) - ); - assert_eq!( - lexer.tokens[30].data, - TokenData::Punctuator(Punctuator::BoolAnd) - ); - assert_eq!( - lexer.tokens[31].data, - TokenData::Punctuator(Punctuator::BoolOr) - ); - assert_eq!( - lexer.tokens[32].data, - TokenData::Punctuator(Punctuator::Question) - ); - assert_eq!( - lexer.tokens[33].data, - TokenData::Punctuator(Punctuator::Colon) - ); - assert_eq!( - lexer.tokens[34].data, - TokenData::Punctuator(Punctuator::Assign) - ); - assert_eq!( - lexer.tokens[35].data, - TokenData::Punctuator(Punctuator::AssignAdd) - ); - assert_eq!( - lexer.tokens[36].data, - TokenData::Punctuator(Punctuator::AssignSub) - ); - assert_eq!( - lexer.tokens[37].data, - TokenData::Punctuator(Punctuator::AssignMul) - ); - assert_eq!( - lexer.tokens[38].data, - TokenData::Punctuator(Punctuator::AssignAnd) - ); - assert_eq!( - lexer.tokens[39].data, - TokenData::Punctuator(Punctuator::AssignPow) - ); - assert_eq!( - lexer.tokens[40].data, - TokenData::Punctuator(Punctuator::Inc) - ); - assert_eq!( - lexer.tokens[41].data, - TokenData::Punctuator(Punctuator::Pow) - ); - assert_eq!( - lexer.tokens[42].data, - TokenData::Punctuator(Punctuator::AssignLeftSh) - ); - assert_eq!( - lexer.tokens[43].data, - TokenData::Punctuator(Punctuator::AssignRightSh) - ); - assert_eq!( - lexer.tokens[44].data, - TokenData::Punctuator(Punctuator::AssignURightSh) - ); - assert_eq!( - lexer.tokens[45].data, - TokenData::Punctuator(Punctuator::AssignAnd) - ); - assert_eq!( - lexer.tokens[46].data, - TokenData::Punctuator(Punctuator::AssignOr) - ); - assert_eq!( - lexer.tokens[47].data, - TokenData::Punctuator(Punctuator::AssignXor) - ); - assert_eq!( - lexer.tokens[48].data, - TokenData::Punctuator(Punctuator::Arrow) - ); - } - - #[test] - fn check_keywords() { - // https://tc39.es/ecma262/#sec-keywords - let s = "await break case catch class const continue debugger default delete \ - do else export extends finally for function if import in instanceof \ - new return super switch this throw try typeof var void while with yield"; - - let mut lexer = Lexer::new(s); - lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].data, TokenData::Keyword(Keyword::Await)); - assert_eq!(lexer.tokens[1].data, TokenData::Keyword(Keyword::Break)); - assert_eq!(lexer.tokens[2].data, TokenData::Keyword(Keyword::Case)); - assert_eq!(lexer.tokens[3].data, TokenData::Keyword(Keyword::Catch)); - assert_eq!(lexer.tokens[4].data, TokenData::Keyword(Keyword::Class)); - assert_eq!(lexer.tokens[5].data, TokenData::Keyword(Keyword::Const)); - assert_eq!(lexer.tokens[6].data, TokenData::Keyword(Keyword::Continue)); - assert_eq!(lexer.tokens[7].data, TokenData::Keyword(Keyword::Debugger)); - assert_eq!(lexer.tokens[8].data, TokenData::Keyword(Keyword::Default)); - assert_eq!(lexer.tokens[9].data, TokenData::Keyword(Keyword::Delete)); - assert_eq!(lexer.tokens[10].data, TokenData::Keyword(Keyword::Do)); - assert_eq!(lexer.tokens[11].data, TokenData::Keyword(Keyword::Else)); - assert_eq!(lexer.tokens[12].data, TokenData::Keyword(Keyword::Export)); - assert_eq!(lexer.tokens[13].data, TokenData::Keyword(Keyword::Extends)); - assert_eq!(lexer.tokens[14].data, TokenData::Keyword(Keyword::Finally)); - assert_eq!(lexer.tokens[15].data, TokenData::Keyword(Keyword::For)); - assert_eq!(lexer.tokens[16].data, TokenData::Keyword(Keyword::Function)); - assert_eq!(lexer.tokens[17].data, TokenData::Keyword(Keyword::If)); - assert_eq!(lexer.tokens[18].data, TokenData::Keyword(Keyword::Import)); - assert_eq!(lexer.tokens[19].data, TokenData::Keyword(Keyword::In)); - assert_eq!( - lexer.tokens[20].data, - TokenData::Keyword(Keyword::InstanceOf) - ); - assert_eq!(lexer.tokens[21].data, TokenData::Keyword(Keyword::New)); - assert_eq!(lexer.tokens[22].data, TokenData::Keyword(Keyword::Return)); - assert_eq!(lexer.tokens[23].data, TokenData::Keyword(Keyword::Super)); - assert_eq!(lexer.tokens[24].data, TokenData::Keyword(Keyword::Switch)); - assert_eq!(lexer.tokens[25].data, TokenData::Keyword(Keyword::This)); - assert_eq!(lexer.tokens[26].data, TokenData::Keyword(Keyword::Throw)); - assert_eq!(lexer.tokens[27].data, TokenData::Keyword(Keyword::Try)); - assert_eq!(lexer.tokens[28].data, TokenData::Keyword(Keyword::TypeOf)); - assert_eq!(lexer.tokens[29].data, TokenData::Keyword(Keyword::Var)); - assert_eq!(lexer.tokens[30].data, TokenData::Keyword(Keyword::Void)); - assert_eq!(lexer.tokens[31].data, TokenData::Keyword(Keyword::While)); - assert_eq!(lexer.tokens[32].data, TokenData::Keyword(Keyword::With)); - assert_eq!(lexer.tokens[33].data, TokenData::Keyword(Keyword::Yield)); - } - - #[test] - fn check_variable_definition_tokens() { - let s = "let a = 'hello';"; - let mut lexer = Lexer::new(s); - lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].data, TokenData::Keyword(Keyword::Let)); - assert_eq!(lexer.tokens[1].data, TokenData::Identifier("a".to_string())); - assert_eq!( - lexer.tokens[2].data, - TokenData::Punctuator(Punctuator::Assign) - ); - assert_eq!( - lexer.tokens[3].data, - TokenData::StringLiteral("hello".to_string()) - ); - } - - #[test] - fn check_positions() { - let s = "console.log(\"hello world\"); // Test"; - // ------123456789 - let mut lexer = Lexer::new(s); - lexer.lex().expect("failed to lex"); - // The first column is 1 (not zero indexed) - assert_eq!(lexer.tokens[0].pos.column_number, 1); - assert_eq!(lexer.tokens[0].pos.line_number, 1); - // Dot Token starts on column 8 - assert_eq!(lexer.tokens[1].pos.column_number, 8); - assert_eq!(lexer.tokens[1].pos.line_number, 1); - // Log Token starts on column 9 - assert_eq!(lexer.tokens[2].pos.column_number, 9); - assert_eq!(lexer.tokens[2].pos.line_number, 1); - // Open parenthesis token starts on column 12 - assert_eq!(lexer.tokens[3].pos.column_number, 12); - assert_eq!(lexer.tokens[3].pos.line_number, 1); - // String token starts on column 13 - assert_eq!(lexer.tokens[4].pos.column_number, 13); - assert_eq!(lexer.tokens[4].pos.line_number, 1); - // Close parenthesis token starts on column 26 - assert_eq!(lexer.tokens[5].pos.column_number, 26); - assert_eq!(lexer.tokens[5].pos.line_number, 1); - // Semi Colon token starts on column 27 - assert_eq!(lexer.tokens[6].pos.column_number, 27); - assert_eq!(lexer.tokens[6].pos.line_number, 1); - // Comment start on column 29 - // Semi Colon token starts on column 27 - assert_eq!(lexer.tokens[7].pos.column_number, 29); - assert_eq!(lexer.tokens[7].pos.line_number, 1); - } - - #[test] - fn check_line_numbers() { - let s = "// Copyright (C) 2017 Ecma International. All rights reserved.\n\ - // This code is governed by the BSD license found in the LICENSE file.\n\ - /*---\n\ - description: |\n \ - Collection of assertion functions used throughout test262\n\ - defines: [assert]\n\ - ---*/\n\n\n\ - function assert(mustBeTrue, message) {"; - - let mut lexer = Lexer::new(s); - lexer.lex().expect("failed to lex"); - // The first column is 1 (not zero indexed), first line is also 1 - assert_eq!(lexer.tokens[0].pos.column_number, 1); - assert_eq!(lexer.tokens[0].pos.line_number, 1); - // Second comment starts on line 2 - assert_eq!(lexer.tokens[1].pos.column_number, 1); - assert_eq!(lexer.tokens[1].pos.line_number, 2); - // Multiline comment starts on line 3 - assert_eq!(lexer.tokens[2].pos.column_number, 1); - assert_eq!(lexer.tokens[2].pos.line_number, 3); - // Function Token is on line 10 - assert_eq!(lexer.tokens[3].pos.column_number, 1); - assert_eq!(lexer.tokens[3].pos.line_number, 10); - } - - // Increment/Decrement - #[test] - fn check_decrement_advances_lexer_2_places() { - // Here we want an example of decrementing an integer - let s = "let a = b--;"; - let mut lexer = Lexer::new(s); - lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[4].data, TokenData::Punctuator(Punctuator::Dec)); - // Decrementing means adding 2 characters '--', the lexer should consume it as a single token - // and move the curser forward by 2, meaning the next token should be a semicolon - assert_eq!( - lexer.tokens[5].data, - TokenData::Punctuator(Punctuator::Semicolon) - ); - } - - #[test] - fn numbers() { - let mut lexer = Lexer::new( - "1 2 0x34 056 7.89 42. 5e3 5e+3 5e-3 0b10 0O123 0999 1.0e1 1.0e-1 1.0E1 1E1", - ); - lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0)); - assert_eq!(lexer.tokens[1].data, TokenData::NumericLiteral(2.0)); - assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(52.0)); - assert_eq!(lexer.tokens[3].data, TokenData::NumericLiteral(46.0)); - assert_eq!(lexer.tokens[4].data, TokenData::NumericLiteral(7.89)); - assert_eq!(lexer.tokens[5].data, TokenData::NumericLiteral(42.0)); - assert_eq!(lexer.tokens[6].data, TokenData::NumericLiteral(5000.0)); - assert_eq!(lexer.tokens[7].data, TokenData::NumericLiteral(5000.0)); - assert_eq!(lexer.tokens[8].data, TokenData::NumericLiteral(0.005)); - assert_eq!(lexer.tokens[9].data, TokenData::NumericLiteral(2.0)); - assert_eq!(lexer.tokens[10].data, TokenData::NumericLiteral(83.0)); - assert_eq!(lexer.tokens[11].data, TokenData::NumericLiteral(999.0)); - assert_eq!(lexer.tokens[12].data, TokenData::NumericLiteral(10.0)); - assert_eq!(lexer.tokens[13].data, TokenData::NumericLiteral(0.1)); - assert_eq!(lexer.tokens[14].data, TokenData::NumericLiteral(10.0)); - assert_eq!(lexer.tokens[14].data, TokenData::NumericLiteral(10.0)); - } - - #[test] - fn test_single_number_without_semicolon() { - let mut lexer = Lexer::new("1"); - lexer.lex().expect("failed to lex"); - } - - #[test] - fn test_number_followed_by_dot() { - let mut lexer = Lexer::new("1.."); - lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0)); - assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Dot)); - } - - #[test] - fn test_regex_literal() { - let mut lexer = Lexer::new("/(?:)/"); - lexer.lex().expect("failed to lex"); - assert_eq!( - lexer.tokens[0].data, - TokenData::RegularExpressionLiteral("(?:)".to_string(), "".to_string()) - ); - } - - #[test] - fn test_regex_literal_flags() { - let mut lexer = Lexer::new(r"/\/[^\/]*\/*/gmi"); - lexer.lex().expect("failed to lex"); - assert_eq!( - lexer.tokens[0].data, - TokenData::RegularExpressionLiteral("\\/[^\\/]*\\/*".to_string(), "gmi".to_string()) - ); - } - - #[test] - fn test_addition_no_spaces() { - let mut lexer = Lexer::new("1+1"); - lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0)); - assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add)); - assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(1.0)); - } - - #[test] - fn test_addition_no_spaces_left_side() { - let mut lexer = Lexer::new("1+ 1"); - lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0)); - assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add)); - assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(1.0)); - } - - #[test] - fn test_addition_no_spaces_right_side() { - let mut lexer = Lexer::new("1 +1"); - lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0)); - assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add)); - assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(1.0)); - } - - #[test] - fn test_addition_no_spaces_e_number_left_side() { - let mut lexer = Lexer::new("1e2+ 1"); - lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(100.0)); - assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add)); - assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(1.0)); - } - - #[test] - fn test_addition_no_spaces_e_number_right_side() { - let mut lexer = Lexer::new("1 +1e3"); - lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0)); - assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add)); - assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(1000.0)); - } - - #[test] - fn test_addition_no_spaces_e_number() { - let mut lexer = Lexer::new("1e3+1e11"); - lexer.lex().expect("failed to lex"); - assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1000.0)); - assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add)); - assert_eq!( - lexer.tokens[2].data, - TokenData::NumericLiteral(100_000_000_000.0) - ); - } -} diff --git a/boa/src/syntax/lexer/tests.rs b/boa/src/syntax/lexer/tests.rs new file mode 100644 index 0000000000..c79652b000 --- /dev/null +++ b/boa/src/syntax/lexer/tests.rs @@ -0,0 +1,496 @@ +//! Tests for the lexer. +#![allow(clippy::indexing_slicing)] + +use super::*; +use crate::syntax::ast::keyword::Keyword; + +#[test] +fn check_single_line_comment() { + let s1 = "var \n//=\nx"; + let mut lexer = Lexer::new(s1); + lexer.lex().expect("failed to lex"); + assert_eq!(lexer.tokens[0].data, TokenData::Keyword(Keyword::Var)); + assert_eq!(lexer.tokens[1].data, TokenData::Comment("//=".to_owned())); + assert_eq!(lexer.tokens[2].data, TokenData::Identifier("x".to_string())); +} + +#[test] +fn check_multi_line_comment() { + let s = "var /* await \n break \n*/ x"; + let mut lexer = Lexer::new(s); + lexer.lex().expect("failed to lex"); + assert_eq!(lexer.tokens[0].data, TokenData::Keyword(Keyword::Var)); + assert_eq!( + lexer.tokens[1].data, + TokenData::Comment("/* await \n break \n*/".to_owned()) + ); + assert_eq!(lexer.tokens[2].data, TokenData::Identifier("x".to_string())); +} + +#[test] +fn check_string() { + let s = "'aaa' \"bbb\""; + let mut lexer = Lexer::new(s); + lexer.lex().expect("failed to lex"); + assert_eq!( + lexer.tokens[0].data, + TokenData::StringLiteral("aaa".to_string()) + ); + + assert_eq!( + lexer.tokens[1].data, + TokenData::StringLiteral("bbb".to_string()) + ); +} + +#[test] +fn check_punctuators() { + // https://tc39.es/ecma262/#sec-punctuators + let s = "{ ( ) [ ] . ... ; , < > <= >= == != === !== \ + + - * % -- << >> >>> & | ^ ! ~ && || ? : \ + = += -= *= &= **= ++ ** <<= >>= >>>= &= |= ^= =>"; + let mut lexer = Lexer::new(s); + lexer.lex().expect("failed to lex"); + assert_eq!( + lexer.tokens[0].data, + TokenData::Punctuator(Punctuator::OpenBlock) + ); + assert_eq!( + lexer.tokens[1].data, + TokenData::Punctuator(Punctuator::OpenParen) + ); + assert_eq!( + lexer.tokens[2].data, + TokenData::Punctuator(Punctuator::CloseParen) + ); + assert_eq!( + lexer.tokens[3].data, + TokenData::Punctuator(Punctuator::OpenBracket) + ); + assert_eq!( + lexer.tokens[4].data, + TokenData::Punctuator(Punctuator::CloseBracket) + ); + assert_eq!(lexer.tokens[5].data, TokenData::Punctuator(Punctuator::Dot)); + assert_eq!( + lexer.tokens[6].data, + TokenData::Punctuator(Punctuator::Spread) + ); + assert_eq!( + lexer.tokens[7].data, + TokenData::Punctuator(Punctuator::Semicolon) + ); + assert_eq!( + lexer.tokens[8].data, + TokenData::Punctuator(Punctuator::Comma) + ); + assert_eq!( + lexer.tokens[9].data, + TokenData::Punctuator(Punctuator::LessThan) + ); + assert_eq!( + lexer.tokens[10].data, + TokenData::Punctuator(Punctuator::GreaterThan) + ); + assert_eq!( + lexer.tokens[11].data, + TokenData::Punctuator(Punctuator::LessThanOrEq) + ); + assert_eq!( + lexer.tokens[12].data, + TokenData::Punctuator(Punctuator::GreaterThanOrEq) + ); + assert_eq!(lexer.tokens[13].data, TokenData::Punctuator(Punctuator::Eq)); + assert_eq!( + lexer.tokens[14].data, + TokenData::Punctuator(Punctuator::NotEq) + ); + assert_eq!( + lexer.tokens[15].data, + TokenData::Punctuator(Punctuator::StrictEq) + ); + assert_eq!( + lexer.tokens[16].data, + TokenData::Punctuator(Punctuator::StrictNotEq) + ); + assert_eq!( + lexer.tokens[17].data, + TokenData::Punctuator(Punctuator::Add) + ); + assert_eq!( + lexer.tokens[18].data, + TokenData::Punctuator(Punctuator::Sub) + ); + assert_eq!( + lexer.tokens[19].data, + TokenData::Punctuator(Punctuator::Mul) + ); + assert_eq!( + lexer.tokens[20].data, + TokenData::Punctuator(Punctuator::Mod) + ); + assert_eq!( + lexer.tokens[21].data, + TokenData::Punctuator(Punctuator::Dec) + ); + assert_eq!( + lexer.tokens[22].data, + TokenData::Punctuator(Punctuator::LeftSh) + ); + assert_eq!( + lexer.tokens[23].data, + TokenData::Punctuator(Punctuator::RightSh) + ); + assert_eq!( + lexer.tokens[24].data, + TokenData::Punctuator(Punctuator::URightSh) + ); + assert_eq!( + lexer.tokens[25].data, + TokenData::Punctuator(Punctuator::And) + ); + assert_eq!(lexer.tokens[26].data, TokenData::Punctuator(Punctuator::Or)); + assert_eq!( + lexer.tokens[27].data, + TokenData::Punctuator(Punctuator::Xor) + ); + assert_eq!( + lexer.tokens[28].data, + TokenData::Punctuator(Punctuator::Not) + ); + assert_eq!( + lexer.tokens[29].data, + TokenData::Punctuator(Punctuator::Neg) + ); + assert_eq!( + lexer.tokens[30].data, + TokenData::Punctuator(Punctuator::BoolAnd) + ); + assert_eq!( + lexer.tokens[31].data, + TokenData::Punctuator(Punctuator::BoolOr) + ); + assert_eq!( + lexer.tokens[32].data, + TokenData::Punctuator(Punctuator::Question) + ); + assert_eq!( + lexer.tokens[33].data, + TokenData::Punctuator(Punctuator::Colon) + ); + assert_eq!( + lexer.tokens[34].data, + TokenData::Punctuator(Punctuator::Assign) + ); + assert_eq!( + lexer.tokens[35].data, + TokenData::Punctuator(Punctuator::AssignAdd) + ); + assert_eq!( + lexer.tokens[36].data, + TokenData::Punctuator(Punctuator::AssignSub) + ); + assert_eq!( + lexer.tokens[37].data, + TokenData::Punctuator(Punctuator::AssignMul) + ); + assert_eq!( + lexer.tokens[38].data, + TokenData::Punctuator(Punctuator::AssignAnd) + ); + assert_eq!( + lexer.tokens[39].data, + TokenData::Punctuator(Punctuator::AssignPow) + ); + assert_eq!( + lexer.tokens[40].data, + TokenData::Punctuator(Punctuator::Inc) + ); + assert_eq!( + lexer.tokens[41].data, + TokenData::Punctuator(Punctuator::Pow) + ); + assert_eq!( + lexer.tokens[42].data, + TokenData::Punctuator(Punctuator::AssignLeftSh) + ); + assert_eq!( + lexer.tokens[43].data, + TokenData::Punctuator(Punctuator::AssignRightSh) + ); + assert_eq!( + lexer.tokens[44].data, + TokenData::Punctuator(Punctuator::AssignURightSh) + ); + assert_eq!( + lexer.tokens[45].data, + TokenData::Punctuator(Punctuator::AssignAnd) + ); + assert_eq!( + lexer.tokens[46].data, + TokenData::Punctuator(Punctuator::AssignOr) + ); + assert_eq!( + lexer.tokens[47].data, + TokenData::Punctuator(Punctuator::AssignXor) + ); + assert_eq!( + lexer.tokens[48].data, + TokenData::Punctuator(Punctuator::Arrow) + ); +} + +#[test] +fn check_keywords() { + // https://tc39.es/ecma262/#sec-keywords + let s = "await break case catch class const continue debugger default delete \ + do else export extends finally for function if import in instanceof \ + new return super switch this throw try typeof var void while with yield"; + + let mut lexer = Lexer::new(s); + lexer.lex().expect("failed to lex"); + assert_eq!(lexer.tokens[0].data, TokenData::Keyword(Keyword::Await)); + assert_eq!(lexer.tokens[1].data, TokenData::Keyword(Keyword::Break)); + assert_eq!(lexer.tokens[2].data, TokenData::Keyword(Keyword::Case)); + assert_eq!(lexer.tokens[3].data, TokenData::Keyword(Keyword::Catch)); + assert_eq!(lexer.tokens[4].data, TokenData::Keyword(Keyword::Class)); + assert_eq!(lexer.tokens[5].data, TokenData::Keyword(Keyword::Const)); + assert_eq!(lexer.tokens[6].data, TokenData::Keyword(Keyword::Continue)); + assert_eq!(lexer.tokens[7].data, TokenData::Keyword(Keyword::Debugger)); + assert_eq!(lexer.tokens[8].data, TokenData::Keyword(Keyword::Default)); + assert_eq!(lexer.tokens[9].data, TokenData::Keyword(Keyword::Delete)); + assert_eq!(lexer.tokens[10].data, TokenData::Keyword(Keyword::Do)); + assert_eq!(lexer.tokens[11].data, TokenData::Keyword(Keyword::Else)); + assert_eq!(lexer.tokens[12].data, TokenData::Keyword(Keyword::Export)); + assert_eq!(lexer.tokens[13].data, TokenData::Keyword(Keyword::Extends)); + assert_eq!(lexer.tokens[14].data, TokenData::Keyword(Keyword::Finally)); + assert_eq!(lexer.tokens[15].data, TokenData::Keyword(Keyword::For)); + assert_eq!(lexer.tokens[16].data, TokenData::Keyword(Keyword::Function)); + assert_eq!(lexer.tokens[17].data, TokenData::Keyword(Keyword::If)); + assert_eq!(lexer.tokens[18].data, TokenData::Keyword(Keyword::Import)); + assert_eq!(lexer.tokens[19].data, TokenData::Keyword(Keyword::In)); + assert_eq!( + lexer.tokens[20].data, + TokenData::Keyword(Keyword::InstanceOf) + ); + assert_eq!(lexer.tokens[21].data, TokenData::Keyword(Keyword::New)); + assert_eq!(lexer.tokens[22].data, TokenData::Keyword(Keyword::Return)); + assert_eq!(lexer.tokens[23].data, TokenData::Keyword(Keyword::Super)); + assert_eq!(lexer.tokens[24].data, TokenData::Keyword(Keyword::Switch)); + assert_eq!(lexer.tokens[25].data, TokenData::Keyword(Keyword::This)); + assert_eq!(lexer.tokens[26].data, TokenData::Keyword(Keyword::Throw)); + assert_eq!(lexer.tokens[27].data, TokenData::Keyword(Keyword::Try)); + assert_eq!(lexer.tokens[28].data, TokenData::Keyword(Keyword::TypeOf)); + assert_eq!(lexer.tokens[29].data, TokenData::Keyword(Keyword::Var)); + assert_eq!(lexer.tokens[30].data, TokenData::Keyword(Keyword::Void)); + assert_eq!(lexer.tokens[31].data, TokenData::Keyword(Keyword::While)); + assert_eq!(lexer.tokens[32].data, TokenData::Keyword(Keyword::With)); + assert_eq!(lexer.tokens[33].data, TokenData::Keyword(Keyword::Yield)); +} + +#[test] +fn check_variable_definition_tokens() { + let s = "let a = 'hello';"; + let mut lexer = Lexer::new(s); + lexer.lex().expect("failed to lex"); + assert_eq!(lexer.tokens[0].data, TokenData::Keyword(Keyword::Let)); + assert_eq!(lexer.tokens[1].data, TokenData::Identifier("a".to_string())); + assert_eq!( + lexer.tokens[2].data, + TokenData::Punctuator(Punctuator::Assign) + ); + assert_eq!( + lexer.tokens[3].data, + TokenData::StringLiteral("hello".to_string()) + ); +} + +#[test] +fn check_positions() { + let s = "console.log(\"hello world\"); // Test"; + // ------123456789 + let mut lexer = Lexer::new(s); + lexer.lex().expect("failed to lex"); + // The first column is 1 (not zero indexed) + assert_eq!(lexer.tokens[0].pos.column_number, 1); + assert_eq!(lexer.tokens[0].pos.line_number, 1); + // Dot Token starts on column 8 + assert_eq!(lexer.tokens[1].pos.column_number, 8); + assert_eq!(lexer.tokens[1].pos.line_number, 1); + // Log Token starts on column 9 + assert_eq!(lexer.tokens[2].pos.column_number, 9); + assert_eq!(lexer.tokens[2].pos.line_number, 1); + // Open parenthesis token starts on column 12 + assert_eq!(lexer.tokens[3].pos.column_number, 12); + assert_eq!(lexer.tokens[3].pos.line_number, 1); + // String token starts on column 13 + assert_eq!(lexer.tokens[4].pos.column_number, 13); + assert_eq!(lexer.tokens[4].pos.line_number, 1); + // Close parenthesis token starts on column 26 + assert_eq!(lexer.tokens[5].pos.column_number, 26); + assert_eq!(lexer.tokens[5].pos.line_number, 1); + // Semi Colon token starts on column 27 + assert_eq!(lexer.tokens[6].pos.column_number, 27); + assert_eq!(lexer.tokens[6].pos.line_number, 1); + // Comment start on column 29 + // Semi Colon token starts on column 27 + assert_eq!(lexer.tokens[7].pos.column_number, 29); + assert_eq!(lexer.tokens[7].pos.line_number, 1); +} + +#[test] +fn check_line_numbers() { + let s = "// Copyright (C) 2017 Ecma International. All rights reserved.\n\ + // This code is governed by the BSD license found in the LICENSE file.\n\ + /*---\n\ + description: |\n \ + Collection of assertion functions used throughout test262\n\ + defines: [assert]\n\ + ---*/\n\n\n\ + function assert(mustBeTrue, message) {"; + + let mut lexer = Lexer::new(s); + lexer.lex().expect("failed to lex"); + // The first column is 1 (not zero indexed), first line is also 1 + assert_eq!(lexer.tokens[0].pos.column_number, 1); + assert_eq!(lexer.tokens[0].pos.line_number, 1); + // Second comment starts on line 2 + assert_eq!(lexer.tokens[1].pos.column_number, 1); + assert_eq!(lexer.tokens[1].pos.line_number, 2); + // Multiline comment starts on line 3 + assert_eq!(lexer.tokens[2].pos.column_number, 1); + assert_eq!(lexer.tokens[2].pos.line_number, 3); + // Function Token is on line 10 + assert_eq!(lexer.tokens[3].pos.column_number, 1); + assert_eq!(lexer.tokens[3].pos.line_number, 10); +} + +// Increment/Decrement +#[test] +fn check_decrement_advances_lexer_2_places() { + // Here we want an example of decrementing an integer + let s = "let a = b--;"; + let mut lexer = Lexer::new(s); + lexer.lex().expect("failed to lex"); + assert_eq!(lexer.tokens[4].data, TokenData::Punctuator(Punctuator::Dec)); + // Decrementing means adding 2 characters '--', the lexer should consume it as a single token + // and move the curser forward by 2, meaning the next token should be a semicolon + assert_eq!( + lexer.tokens[5].data, + TokenData::Punctuator(Punctuator::Semicolon) + ); +} + +#[test] +fn numbers() { + let mut lexer = + Lexer::new("1 2 0x34 056 7.89 42. 5e3 5e+3 5e-3 0b10 0O123 0999 1.0e1 1.0e-1 1.0E1 1E1"); + lexer.lex().expect("failed to lex"); + assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0)); + assert_eq!(lexer.tokens[1].data, TokenData::NumericLiteral(2.0)); + assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(52.0)); + assert_eq!(lexer.tokens[3].data, TokenData::NumericLiteral(46.0)); + assert_eq!(lexer.tokens[4].data, TokenData::NumericLiteral(7.89)); + assert_eq!(lexer.tokens[5].data, TokenData::NumericLiteral(42.0)); + assert_eq!(lexer.tokens[6].data, TokenData::NumericLiteral(5000.0)); + assert_eq!(lexer.tokens[7].data, TokenData::NumericLiteral(5000.0)); + assert_eq!(lexer.tokens[8].data, TokenData::NumericLiteral(0.005)); + assert_eq!(lexer.tokens[9].data, TokenData::NumericLiteral(2.0)); + assert_eq!(lexer.tokens[10].data, TokenData::NumericLiteral(83.0)); + assert_eq!(lexer.tokens[11].data, TokenData::NumericLiteral(999.0)); + assert_eq!(lexer.tokens[12].data, TokenData::NumericLiteral(10.0)); + assert_eq!(lexer.tokens[13].data, TokenData::NumericLiteral(0.1)); + assert_eq!(lexer.tokens[14].data, TokenData::NumericLiteral(10.0)); + assert_eq!(lexer.tokens[14].data, TokenData::NumericLiteral(10.0)); +} + +#[test] +fn test_single_number_without_semicolon() { + let mut lexer = Lexer::new("1"); + lexer.lex().expect("failed to lex"); +} + +#[test] +fn test_number_followed_by_dot() { + let mut lexer = Lexer::new("1.."); + lexer.lex().expect("failed to lex"); + assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0)); + assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Dot)); +} + +#[test] +fn test_regex_literal() { + let mut lexer = Lexer::new("/(?:)/"); + lexer.lex().expect("failed to lex"); + assert_eq!( + lexer.tokens[0].data, + TokenData::RegularExpressionLiteral("(?:)".to_string(), "".to_string()) + ); +} + +#[test] +fn test_regex_literal_flags() { + let mut lexer = Lexer::new(r"/\/[^\/]*\/*/gmi"); + lexer.lex().expect("failed to lex"); + assert_eq!( + lexer.tokens[0].data, + TokenData::RegularExpressionLiteral("\\/[^\\/]*\\/*".to_string(), "gmi".to_string()) + ); +} + +#[test] +fn test_addition_no_spaces() { + let mut lexer = Lexer::new("1+1"); + lexer.lex().expect("failed to lex"); + assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0)); + assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add)); + assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(1.0)); +} + +#[test] +fn test_addition_no_spaces_left_side() { + let mut lexer = Lexer::new("1+ 1"); + lexer.lex().expect("failed to lex"); + assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0)); + assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add)); + assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(1.0)); +} + +#[test] +fn test_addition_no_spaces_right_side() { + let mut lexer = Lexer::new("1 +1"); + lexer.lex().expect("failed to lex"); + assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0)); + assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add)); + assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(1.0)); +} + +#[test] +fn test_addition_no_spaces_e_number_left_side() { + let mut lexer = Lexer::new("1e2+ 1"); + lexer.lex().expect("failed to lex"); + assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(100.0)); + assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add)); + assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(1.0)); +} + +#[test] +fn test_addition_no_spaces_e_number_right_side() { + let mut lexer = Lexer::new("1 +1e3"); + lexer.lex().expect("failed to lex"); + assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0)); + assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add)); + assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(1000.0)); +} + +#[test] +fn test_addition_no_spaces_e_number() { + let mut lexer = Lexer::new("1e3+1e11"); + lexer.lex().expect("failed to lex"); + assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1000.0)); + assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add)); + assert_eq!( + lexer.tokens[2].data, + TokenData::NumericLiteral(100_000_000_000.0) + ); +} diff --git a/boa/src/syntax/parser.rs b/boa/src/syntax/parser/mod.rs similarity index 64% rename from boa/src/syntax/parser.rs rename to boa/src/syntax/parser/mod.rs index dc1a9d0618..7fdd712033 100644 --- a/boa/src/syntax/parser.rs +++ b/boa/src/syntax/parser/mod.rs @@ -1,11 +1,15 @@ -use crate::syntax::ast::constant::Const; -use crate::syntax::ast::expr::{Expr, ExprDef}; -use crate::syntax::ast::keyword::Keyword; -use crate::syntax::ast::op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, Operator, UnaryOp}; -use crate::syntax::ast::punc::Punctuator; -use crate::syntax::ast::token::{Token, TokenData}; -use std::collections::btree_map::BTreeMap; -use std::fmt; +#[cfg(test)] +mod tests; + +use crate::syntax::ast::{ + constant::Const, + expr::{Expr, ExprDef}, + keyword::Keyword, + op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, Operator, UnaryOp}, + punc::Punctuator, + token::{Token, TokenData}, +}; +use std::{collections::btree_map::BTreeMap, fmt}; /// `ParseError` is an enum which represents errors encounted during parsing an expression #[derive(Debug, Clone)] @@ -904,696 +908,3 @@ impl Parser { self.expect(TokenData::Punctuator(p), routine) } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::syntax::ast::{constant::Const, op::BinOp}; - use crate::syntax::{ - ast::expr::{Expr, ExprDef}, - lexer::Lexer, - }; - - fn create_bin_op(op: BinOp, exp1: Expr, exp2: Expr) -> Expr { - Expr::new(ExprDef::BinOp(op, Box::new(exp1), Box::new(exp2))) - } - - #[allow(clippy::result_unwrap_used)] - fn check_parser(js: &str, expr: &[Expr]) { - let mut lexer = Lexer::new(js); - lexer.lex().expect("failed to lex"); - - assert_eq!( - Parser::new(lexer.tokens).parse_all().unwrap(), - Expr::new(ExprDef::Block(expr.into())) - ); - } - - fn check_invalid(js: &str) { - let mut lexer = Lexer::new(js); - lexer.lex().expect("failed to lex"); - - assert!(Parser::new(lexer.tokens).parse_all().is_err()); - } - - #[test] - fn check_string() { - use crate::syntax::ast::constant::Const; - - // Check empty string - check_parser( - "\"\"", - &[Expr::new(ExprDef::Const(Const::String(String::new())))], - ); - - // Check non-empty string - check_parser( - "\"hello\"", - &[Expr::new(ExprDef::Const(Const::String(String::from( - "hello", - ))))], - ); - } - #[test] - fn check_object_short_function() { - // Testing short function syntax - let mut object_properties: BTreeMap = BTreeMap::new(); - object_properties.insert( - String::from("a"), - Expr::new(ExprDef::Const(Const::Bool(true))), - ); - object_properties.insert( - String::from("b"), - Expr::new(ExprDef::FunctionDecl( - None, - vec![], - Box::new(Expr::new(ExprDef::Block(vec![]))), - )), - ); - - check_parser( - "{ - a: true, - b() {} - }; - ", - &[Expr::new(ExprDef::ObjectDecl(Box::new(object_properties)))], - ); - } - - #[test] - fn check_object_short_function_arguments() { - // Testing short function syntax - let mut object_properties: BTreeMap = BTreeMap::new(); - object_properties.insert( - String::from("a"), - Expr::new(ExprDef::Const(Const::Bool(true))), - ); - object_properties.insert( - String::from("b"), - Expr::new(ExprDef::FunctionDecl( - None, - vec![Expr::new(ExprDef::Local(String::from("test")))], - Box::new(Expr::new(ExprDef::Block(vec![]))), - )), - ); - - check_parser( - "{ - a: true, - b(test) {} - }; - ", - &[Expr::new(ExprDef::ObjectDecl(Box::new(object_properties)))], - ); - } - #[test] - fn check_array() { - use crate::syntax::ast::constant::Const; - - // Check empty array - check_parser("[]", &[Expr::new(ExprDef::ArrayDecl(vec![]))]); - - // Check array with empty slot - check_parser( - "[,]", - &[Expr::new(ExprDef::ArrayDecl(vec![Expr::new( - ExprDef::Const(Const::Undefined), - )]))], - ); - - // Check numeric array - check_parser( - "[1, 2, 3]", - &[Expr::new(ExprDef::ArrayDecl(vec![ - Expr::new(ExprDef::Const(Const::Num(1.0))), - Expr::new(ExprDef::Const(Const::Num(2.0))), - Expr::new(ExprDef::Const(Const::Num(3.0))), - ]))], - ); - - // Check numeric array with trailing comma - check_parser( - "[1, 2, 3,]", - &[Expr::new(ExprDef::ArrayDecl(vec![ - Expr::new(ExprDef::Const(Const::Num(1.0))), - Expr::new(ExprDef::Const(Const::Num(2.0))), - Expr::new(ExprDef::Const(Const::Num(3.0))), - ]))], - ); - - // Check numeric array with an elision - check_parser( - "[1, 2, , 3]", - &[Expr::new(ExprDef::ArrayDecl(vec![ - Expr::new(ExprDef::Const(Const::Num(1.0))), - Expr::new(ExprDef::Const(Const::Num(2.0))), - Expr::new(ExprDef::Const(Const::Undefined)), - Expr::new(ExprDef::Const(Const::Num(3.0))), - ]))], - ); - - // Check numeric array with repeated elision - check_parser( - "[1, 2, ,, 3]", - &[Expr::new(ExprDef::ArrayDecl(vec![ - Expr::new(ExprDef::Const(Const::Num(1.0))), - Expr::new(ExprDef::Const(Const::Num(2.0))), - Expr::new(ExprDef::Const(Const::Undefined)), - Expr::new(ExprDef::Const(Const::Undefined)), - Expr::new(ExprDef::Const(Const::Num(3.0))), - ]))], - ); - - // Check combined array - check_parser( - "[1, \"a\", 2]", - &[Expr::new(ExprDef::ArrayDecl(vec![ - Expr::new(ExprDef::Const(Const::Num(1.0))), - Expr::new(ExprDef::Const(Const::String(String::from("a")))), - Expr::new(ExprDef::Const(Const::Num(2.0))), - ]))], - ); - - // Check combined array with empty string - check_parser( - "[1, \"\", 2]", - &[Expr::new(ExprDef::ArrayDecl(vec![ - Expr::new(ExprDef::Const(Const::Num(1.0))), - Expr::new(ExprDef::Const(Const::String(String::new()))), - Expr::new(ExprDef::Const(Const::Num(2.0))), - ]))], - ); - } - - #[test] - fn check_declarations() { - use crate::syntax::ast::constant::Const; - - // Check `var` declaration - check_parser( - "var a = 5;", - &[Expr::new(ExprDef::VarDecl(vec![( - String::from("a"), - Some(Expr::new(ExprDef::Const(Const::Num(5.0)))), - )]))], - ); - - // Check `var` declaration with no spaces - check_parser( - "var a=5;", - &[Expr::new(ExprDef::VarDecl(vec![( - String::from("a"), - Some(Expr::new(ExprDef::Const(Const::Num(5.0)))), - )]))], - ); - - // Check empty `var` declaration - check_parser( - "var a;", - &[Expr::new(ExprDef::VarDecl(vec![(String::from("a"), None)]))], - ); - - // Check multiple `var` declaration - check_parser( - "var a = 5, b, c = 6;", - &[Expr::new(ExprDef::VarDecl(vec![ - ( - String::from("a"), - Some(Expr::new(ExprDef::Const(Const::Num(5.0)))), - ), - (String::from("b"), None), - ( - String::from("c"), - Some(Expr::new(ExprDef::Const(Const::Num(6.0)))), - ), - ]))], - ); - - // Check `let` declaration - check_parser( - "let a = 5;", - &[Expr::new(ExprDef::LetDecl(vec![( - String::from("a"), - Some(Expr::new(ExprDef::Const(Const::Num(5.0)))), - )]))], - ); - - // Check `let` declaration with no spaces - check_parser( - "let a=5;", - &[Expr::new(ExprDef::LetDecl(vec![( - String::from("a"), - Some(Expr::new(ExprDef::Const(Const::Num(5.0)))), - )]))], - ); - - // Check empty `let` declaration - check_parser( - "let a;", - &[Expr::new(ExprDef::LetDecl(vec![(String::from("a"), None)]))], - ); - - // Check multiple `let` declaration - check_parser( - "let a = 5, b, c = 6;", - &[Expr::new(ExprDef::LetDecl(vec![ - ( - String::from("a"), - Some(Expr::new(ExprDef::Const(Const::Num(5.0)))), - ), - (String::from("b"), None), - ( - String::from("c"), - Some(Expr::new(ExprDef::Const(Const::Num(6.0)))), - ), - ]))], - ); - - // Check `const` declaration - check_parser( - "const a = 5;", - &[Expr::new(ExprDef::ConstDecl(vec![( - String::from("a"), - Expr::new(ExprDef::Const(Const::Num(5.0))), - )]))], - ); - - // Check `const` declaration with no spaces - check_parser( - "const a=5;", - &[Expr::new(ExprDef::ConstDecl(vec![( - String::from("a"), - Expr::new(ExprDef::Const(Const::Num(5.0))), - )]))], - ); - - // Check empty `const` declaration - check_invalid("const a;"); - - // Check multiple `const` declaration - check_parser( - "const a = 5, c = 6;", - &[Expr::new(ExprDef::ConstDecl(vec![ - ( - String::from("a"), - Expr::new(ExprDef::Const(Const::Num(5.0))), - ), - ( - String::from("c"), - Expr::new(ExprDef::Const(Const::Num(6.0))), - ), - ]))], - ); - } - - #[test] - fn check_operations() { - use crate::syntax::ast::{constant::Const, op::BinOp}; - - fn create_bin_op(op: BinOp, exp1: Expr, exp2: Expr) -> Expr { - Expr::new(ExprDef::BinOp(op, Box::new(exp1), Box::new(exp2))) - } - - // Check numeric operations - check_parser( - "a + b", - &[create_bin_op( - BinOp::Num(NumOp::Add), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - check_parser( - "a+1", - &[create_bin_op( - BinOp::Num(NumOp::Add), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Const(Const::Num(1.0))), - )], - ); - check_parser( - "a - b", - &[create_bin_op( - BinOp::Num(NumOp::Sub), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - check_parser( - "a-1", - &[create_bin_op( - BinOp::Num(NumOp::Sub), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Const(Const::Num(1.0))), - )], - ); - check_parser( - "a / b", - &[create_bin_op( - BinOp::Num(NumOp::Div), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - check_parser( - "a/2", - &[create_bin_op( - BinOp::Num(NumOp::Div), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Const(Const::Num(2.0))), - )], - ); - check_parser( - "a * b", - &[create_bin_op( - BinOp::Num(NumOp::Mul), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - check_parser( - "a*2", - &[create_bin_op( - BinOp::Num(NumOp::Mul), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Const(Const::Num(2.0))), - )], - ); - check_parser( - "a ** b", - &[create_bin_op( - BinOp::Num(NumOp::Pow), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - check_parser( - "a**2", - &[create_bin_op( - BinOp::Num(NumOp::Pow), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Const(Const::Num(2.0))), - )], - ); - check_parser( - "a % b", - &[create_bin_op( - BinOp::Num(NumOp::Mod), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - check_parser( - "a%2", - &[create_bin_op( - BinOp::Num(NumOp::Mod), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Const(Const::Num(2.0))), - )], - ); - - // Check complex numeric operations - check_parser( - "a + d*(b-3)+1", - &[create_bin_op( - BinOp::Num(NumOp::Add), - Expr::new(ExprDef::Local(String::from("a"))), - create_bin_op( - BinOp::Num(NumOp::Add), - // FIXME: shouldn't the last addition be on the right? - Expr::new(ExprDef::Const(Const::Num(1.0))), - create_bin_op( - BinOp::Num(NumOp::Mul), - Expr::new(ExprDef::Local(String::from("d"))), - create_bin_op( - BinOp::Num(NumOp::Sub), - Expr::new(ExprDef::Local(String::from("b"))), - Expr::new(ExprDef::Const(Const::Num(3.0))), - ), - ), - ), - )], - ); - - // Check bitwise operations - check_parser( - "a & b", - &[create_bin_op( - BinOp::Bit(BitOp::And), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - check_parser( - "a&b", - &[create_bin_op( - BinOp::Bit(BitOp::And), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - - check_parser( - "a | b", - &[create_bin_op( - BinOp::Bit(BitOp::Or), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - check_parser( - "a|b", - &[create_bin_op( - BinOp::Bit(BitOp::Or), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - - check_parser( - "a ^ b", - &[create_bin_op( - BinOp::Bit(BitOp::Xor), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - check_parser( - "a^b", - &[create_bin_op( - BinOp::Bit(BitOp::Xor), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - - check_parser( - "a << b", - &[create_bin_op( - BinOp::Bit(BitOp::Shl), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - check_parser( - "a<> b", - &[create_bin_op( - BinOp::Bit(BitOp::Shr), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - check_parser( - "a>>b", - &[create_bin_op( - BinOp::Bit(BitOp::Shr), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - - // Check assign ops - check_parser( - "a += b", - &[create_bin_op( - BinOp::Assign(AssignOp::Add), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - check_parser( - "a -= b", - &[create_bin_op( - BinOp::Assign(AssignOp::Sub), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - check_parser( - "a *= b", - &[create_bin_op( - BinOp::Assign(AssignOp::Mul), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - check_parser( - "a **= b", - &[create_bin_op( - BinOp::Assign(AssignOp::Pow), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - check_parser( - "a /= b", - &[create_bin_op( - BinOp::Assign(AssignOp::Div), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - check_parser( - "a %= b", - &[create_bin_op( - BinOp::Assign(AssignOp::Mod), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - check_parser( - "a &= b", - &[create_bin_op( - BinOp::Assign(AssignOp::And), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - check_parser( - "a |= b", - &[create_bin_op( - BinOp::Assign(AssignOp::Or), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - check_parser( - "a ^= b", - &[create_bin_op( - BinOp::Assign(AssignOp::Xor), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - check_parser( - "a <<= b", - &[create_bin_op( - BinOp::Assign(AssignOp::Shl), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - check_parser( - "a >>= b", - &[create_bin_op( - BinOp::Assign(AssignOp::Shr), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - )], - ); - check_parser( - "a %= 10 / 2", - &[create_bin_op( - BinOp::Assign(AssignOp::Mod), - Expr::new(ExprDef::Local(String::from("a"))), - create_bin_op( - BinOp::Num(NumOp::Div), - Expr::new(ExprDef::Const(Const::Num(10.0))), - Expr::new(ExprDef::Const(Const::Num(2.0))), - ), - )], - ); - } - - #[test] - fn check_function_declarations() { - check_parser( - "function foo(a) { return a; }", - &[Expr::new(ExprDef::FunctionDecl( - Some(String::from("foo")), - vec![Expr::new(ExprDef::Local(String::from("a")))], - Box::new(Expr::new(ExprDef::Block(vec![Expr::new(ExprDef::Return( - Some(Box::new(Expr::new(ExprDef::Local(String::from("a"))))), - ))]))), - ))], - ); - - check_parser( - "function (a, ...b) {}", - &[Expr::new(ExprDef::FunctionDecl( - None, - vec![ - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::UnaryOp( - UnaryOp::Spread, - Box::new(Expr::new(ExprDef::Local(String::from("b")))), - )), - ], - Box::new(Expr::new(ExprDef::ObjectDecl(Box::new(BTreeMap::new())))), - ))], - ); - - check_parser( - "(...a) => {}", - &[Expr::new(ExprDef::ArrowFunctionDecl( - vec![Expr::new(ExprDef::UnaryOp( - UnaryOp::Spread, - Box::new(Expr::new(ExprDef::Local(String::from("a")))), - ))], - Box::new(Expr::new(ExprDef::ObjectDecl(Box::new(BTreeMap::new())))), - ))], - ); - - check_parser( - "(a, b, ...c) => {}", - &[Expr::new(ExprDef::ArrowFunctionDecl( - vec![ - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - Expr::new(ExprDef::UnaryOp( - UnaryOp::Spread, - Box::new(Expr::new(ExprDef::Local(String::from("c")))), - )), - ], - Box::new(Expr::new(ExprDef::ObjectDecl(Box::new(BTreeMap::new())))), - ))], - ); - - check_parser( - "(a, b) => { return a + b; }", - &[Expr::new(ExprDef::ArrowFunctionDecl( - vec![ - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - ], - Box::new(Expr::new(ExprDef::Block(vec![Expr::new(ExprDef::Return( - Some(Box::new(create_bin_op( - BinOp::Num(NumOp::Add), - Expr::new(ExprDef::Local(String::from("a"))), - Expr::new(ExprDef::Local(String::from("b"))), - ))), - ))]))), - ))], - ); - } -} diff --git a/boa/src/syntax/parser/tests.rs b/boa/src/syntax/parser/tests.rs new file mode 100644 index 0000000000..b327c4e56f --- /dev/null +++ b/boa/src/syntax/parser/tests.rs @@ -0,0 +1,691 @@ +//! Tests for the parser. + +use super::*; +use crate::syntax::ast::{constant::Const, op::BinOp}; +use crate::syntax::{ + ast::expr::{Expr, ExprDef}, + lexer::Lexer, +}; + +fn create_bin_op(op: BinOp, exp1: Expr, exp2: Expr) -> Expr { + Expr::new(ExprDef::BinOp(op, Box::new(exp1), Box::new(exp2))) +} + +#[allow(clippy::result_unwrap_used)] +fn check_parser(js: &str, expr: &[Expr]) { + let mut lexer = Lexer::new(js); + lexer.lex().expect("failed to lex"); + + assert_eq!( + Parser::new(lexer.tokens).parse_all().unwrap(), + Expr::new(ExprDef::Block(expr.into())) + ); +} + +fn check_invalid(js: &str) { + let mut lexer = Lexer::new(js); + lexer.lex().expect("failed to lex"); + + assert!(Parser::new(lexer.tokens).parse_all().is_err()); +} + +#[test] +fn check_string() { + use crate::syntax::ast::constant::Const; + + // Check empty string + check_parser( + "\"\"", + &[Expr::new(ExprDef::Const(Const::String(String::new())))], + ); + + // Check non-empty string + check_parser( + "\"hello\"", + &[Expr::new(ExprDef::Const(Const::String(String::from( + "hello", + ))))], + ); +} +#[test] +fn check_object_short_function() { + // Testing short function syntax + let mut object_properties: BTreeMap = BTreeMap::new(); + object_properties.insert( + String::from("a"), + Expr::new(ExprDef::Const(Const::Bool(true))), + ); + object_properties.insert( + String::from("b"), + Expr::new(ExprDef::FunctionDecl( + None, + vec![], + Box::new(Expr::new(ExprDef::Block(vec![]))), + )), + ); + + check_parser( + "{ + a: true, + b() {} + }; + ", + &[Expr::new(ExprDef::ObjectDecl(Box::new(object_properties)))], + ); +} + +#[test] +fn check_object_short_function_arguments() { + // Testing short function syntax + let mut object_properties: BTreeMap = BTreeMap::new(); + object_properties.insert( + String::from("a"), + Expr::new(ExprDef::Const(Const::Bool(true))), + ); + object_properties.insert( + String::from("b"), + Expr::new(ExprDef::FunctionDecl( + None, + vec![Expr::new(ExprDef::Local(String::from("test")))], + Box::new(Expr::new(ExprDef::Block(vec![]))), + )), + ); + + check_parser( + "{ + a: true, + b(test) {} + }; + ", + &[Expr::new(ExprDef::ObjectDecl(Box::new(object_properties)))], + ); +} +#[test] +fn check_array() { + use crate::syntax::ast::constant::Const; + + // Check empty array + check_parser("[]", &[Expr::new(ExprDef::ArrayDecl(vec![]))]); + + // Check array with empty slot + check_parser( + "[,]", + &[Expr::new(ExprDef::ArrayDecl(vec![Expr::new( + ExprDef::Const(Const::Undefined), + )]))], + ); + + // Check numeric array + check_parser( + "[1, 2, 3]", + &[Expr::new(ExprDef::ArrayDecl(vec![ + Expr::new(ExprDef::Const(Const::Num(1.0))), + Expr::new(ExprDef::Const(Const::Num(2.0))), + Expr::new(ExprDef::Const(Const::Num(3.0))), + ]))], + ); + + // Check numeric array with trailing comma + check_parser( + "[1, 2, 3,]", + &[Expr::new(ExprDef::ArrayDecl(vec![ + Expr::new(ExprDef::Const(Const::Num(1.0))), + Expr::new(ExprDef::Const(Const::Num(2.0))), + Expr::new(ExprDef::Const(Const::Num(3.0))), + ]))], + ); + + // Check numeric array with an elision + check_parser( + "[1, 2, , 3]", + &[Expr::new(ExprDef::ArrayDecl(vec![ + Expr::new(ExprDef::Const(Const::Num(1.0))), + Expr::new(ExprDef::Const(Const::Num(2.0))), + Expr::new(ExprDef::Const(Const::Undefined)), + Expr::new(ExprDef::Const(Const::Num(3.0))), + ]))], + ); + + // Check numeric array with repeated elision + check_parser( + "[1, 2, ,, 3]", + &[Expr::new(ExprDef::ArrayDecl(vec![ + Expr::new(ExprDef::Const(Const::Num(1.0))), + Expr::new(ExprDef::Const(Const::Num(2.0))), + Expr::new(ExprDef::Const(Const::Undefined)), + Expr::new(ExprDef::Const(Const::Undefined)), + Expr::new(ExprDef::Const(Const::Num(3.0))), + ]))], + ); + + // Check combined array + check_parser( + "[1, \"a\", 2]", + &[Expr::new(ExprDef::ArrayDecl(vec![ + Expr::new(ExprDef::Const(Const::Num(1.0))), + Expr::new(ExprDef::Const(Const::String(String::from("a")))), + Expr::new(ExprDef::Const(Const::Num(2.0))), + ]))], + ); + + // Check combined array with empty string + check_parser( + "[1, \"\", 2]", + &[Expr::new(ExprDef::ArrayDecl(vec![ + Expr::new(ExprDef::Const(Const::Num(1.0))), + Expr::new(ExprDef::Const(Const::String(String::new()))), + Expr::new(ExprDef::Const(Const::Num(2.0))), + ]))], + ); +} + +#[test] +fn check_declarations() { + use crate::syntax::ast::constant::Const; + + // Check `var` declaration + check_parser( + "var a = 5;", + &[Expr::new(ExprDef::VarDecl(vec![( + String::from("a"), + Some(Expr::new(ExprDef::Const(Const::Num(5.0)))), + )]))], + ); + + // Check `var` declaration with no spaces + check_parser( + "var a=5;", + &[Expr::new(ExprDef::VarDecl(vec![( + String::from("a"), + Some(Expr::new(ExprDef::Const(Const::Num(5.0)))), + )]))], + ); + + // Check empty `var` declaration + check_parser( + "var a;", + &[Expr::new(ExprDef::VarDecl(vec![(String::from("a"), None)]))], + ); + + // Check multiple `var` declaration + check_parser( + "var a = 5, b, c = 6;", + &[Expr::new(ExprDef::VarDecl(vec![ + ( + String::from("a"), + Some(Expr::new(ExprDef::Const(Const::Num(5.0)))), + ), + (String::from("b"), None), + ( + String::from("c"), + Some(Expr::new(ExprDef::Const(Const::Num(6.0)))), + ), + ]))], + ); + + // Check `let` declaration + check_parser( + "let a = 5;", + &[Expr::new(ExprDef::LetDecl(vec![( + String::from("a"), + Some(Expr::new(ExprDef::Const(Const::Num(5.0)))), + )]))], + ); + + // Check `let` declaration with no spaces + check_parser( + "let a=5;", + &[Expr::new(ExprDef::LetDecl(vec![( + String::from("a"), + Some(Expr::new(ExprDef::Const(Const::Num(5.0)))), + )]))], + ); + + // Check empty `let` declaration + check_parser( + "let a;", + &[Expr::new(ExprDef::LetDecl(vec![(String::from("a"), None)]))], + ); + + // Check multiple `let` declaration + check_parser( + "let a = 5, b, c = 6;", + &[Expr::new(ExprDef::LetDecl(vec![ + ( + String::from("a"), + Some(Expr::new(ExprDef::Const(Const::Num(5.0)))), + ), + (String::from("b"), None), + ( + String::from("c"), + Some(Expr::new(ExprDef::Const(Const::Num(6.0)))), + ), + ]))], + ); + + // Check `const` declaration + check_parser( + "const a = 5;", + &[Expr::new(ExprDef::ConstDecl(vec![( + String::from("a"), + Expr::new(ExprDef::Const(Const::Num(5.0))), + )]))], + ); + + // Check `const` declaration with no spaces + check_parser( + "const a=5;", + &[Expr::new(ExprDef::ConstDecl(vec![( + String::from("a"), + Expr::new(ExprDef::Const(Const::Num(5.0))), + )]))], + ); + + // Check empty `const` declaration + check_invalid("const a;"); + + // Check multiple `const` declaration + check_parser( + "const a = 5, c = 6;", + &[Expr::new(ExprDef::ConstDecl(vec![ + ( + String::from("a"), + Expr::new(ExprDef::Const(Const::Num(5.0))), + ), + ( + String::from("c"), + Expr::new(ExprDef::Const(Const::Num(6.0))), + ), + ]))], + ); +} + +#[test] +fn check_operations() { + use crate::syntax::ast::{constant::Const, op::BinOp}; + + fn create_bin_op(op: BinOp, exp1: Expr, exp2: Expr) -> Expr { + Expr::new(ExprDef::BinOp(op, Box::new(exp1), Box::new(exp2))) + } + + // Check numeric operations + check_parser( + "a + b", + &[create_bin_op( + BinOp::Num(NumOp::Add), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + check_parser( + "a+1", + &[create_bin_op( + BinOp::Num(NumOp::Add), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Const(Const::Num(1.0))), + )], + ); + check_parser( + "a - b", + &[create_bin_op( + BinOp::Num(NumOp::Sub), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + check_parser( + "a-1", + &[create_bin_op( + BinOp::Num(NumOp::Sub), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Const(Const::Num(1.0))), + )], + ); + check_parser( + "a / b", + &[create_bin_op( + BinOp::Num(NumOp::Div), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + check_parser( + "a/2", + &[create_bin_op( + BinOp::Num(NumOp::Div), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Const(Const::Num(2.0))), + )], + ); + check_parser( + "a * b", + &[create_bin_op( + BinOp::Num(NumOp::Mul), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + check_parser( + "a*2", + &[create_bin_op( + BinOp::Num(NumOp::Mul), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Const(Const::Num(2.0))), + )], + ); + check_parser( + "a ** b", + &[create_bin_op( + BinOp::Num(NumOp::Pow), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + check_parser( + "a**2", + &[create_bin_op( + BinOp::Num(NumOp::Pow), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Const(Const::Num(2.0))), + )], + ); + check_parser( + "a % b", + &[create_bin_op( + BinOp::Num(NumOp::Mod), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + check_parser( + "a%2", + &[create_bin_op( + BinOp::Num(NumOp::Mod), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Const(Const::Num(2.0))), + )], + ); + + // Check complex numeric operations + check_parser( + "a + d*(b-3)+1", + &[create_bin_op( + BinOp::Num(NumOp::Add), + Expr::new(ExprDef::Local(String::from("a"))), + create_bin_op( + BinOp::Num(NumOp::Add), + // FIXME: shouldn't the last addition be on the right? + Expr::new(ExprDef::Const(Const::Num(1.0))), + create_bin_op( + BinOp::Num(NumOp::Mul), + Expr::new(ExprDef::Local(String::from("d"))), + create_bin_op( + BinOp::Num(NumOp::Sub), + Expr::new(ExprDef::Local(String::from("b"))), + Expr::new(ExprDef::Const(Const::Num(3.0))), + ), + ), + ), + )], + ); + + // Check bitwise operations + check_parser( + "a & b", + &[create_bin_op( + BinOp::Bit(BitOp::And), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + check_parser( + "a&b", + &[create_bin_op( + BinOp::Bit(BitOp::And), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + + check_parser( + "a | b", + &[create_bin_op( + BinOp::Bit(BitOp::Or), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + check_parser( + "a|b", + &[create_bin_op( + BinOp::Bit(BitOp::Or), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + + check_parser( + "a ^ b", + &[create_bin_op( + BinOp::Bit(BitOp::Xor), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + check_parser( + "a^b", + &[create_bin_op( + BinOp::Bit(BitOp::Xor), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + + check_parser( + "a << b", + &[create_bin_op( + BinOp::Bit(BitOp::Shl), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + check_parser( + "a<> b", + &[create_bin_op( + BinOp::Bit(BitOp::Shr), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + check_parser( + "a>>b", + &[create_bin_op( + BinOp::Bit(BitOp::Shr), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + + // Check assign ops + check_parser( + "a += b", + &[create_bin_op( + BinOp::Assign(AssignOp::Add), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + check_parser( + "a -= b", + &[create_bin_op( + BinOp::Assign(AssignOp::Sub), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + check_parser( + "a *= b", + &[create_bin_op( + BinOp::Assign(AssignOp::Mul), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + check_parser( + "a **= b", + &[create_bin_op( + BinOp::Assign(AssignOp::Pow), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + check_parser( + "a /= b", + &[create_bin_op( + BinOp::Assign(AssignOp::Div), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + check_parser( + "a %= b", + &[create_bin_op( + BinOp::Assign(AssignOp::Mod), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + check_parser( + "a &= b", + &[create_bin_op( + BinOp::Assign(AssignOp::And), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + check_parser( + "a |= b", + &[create_bin_op( + BinOp::Assign(AssignOp::Or), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + check_parser( + "a ^= b", + &[create_bin_op( + BinOp::Assign(AssignOp::Xor), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + check_parser( + "a <<= b", + &[create_bin_op( + BinOp::Assign(AssignOp::Shl), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + check_parser( + "a >>= b", + &[create_bin_op( + BinOp::Assign(AssignOp::Shr), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + )], + ); + check_parser( + "a %= 10 / 2", + &[create_bin_op( + BinOp::Assign(AssignOp::Mod), + Expr::new(ExprDef::Local(String::from("a"))), + create_bin_op( + BinOp::Num(NumOp::Div), + Expr::new(ExprDef::Const(Const::Num(10.0))), + Expr::new(ExprDef::Const(Const::Num(2.0))), + ), + )], + ); +} + +#[test] +fn check_function_declarations() { + check_parser( + "function foo(a) { return a; }", + &[Expr::new(ExprDef::FunctionDecl( + Some(String::from("foo")), + vec![Expr::new(ExprDef::Local(String::from("a")))], + Box::new(Expr::new(ExprDef::Block(vec![Expr::new(ExprDef::Return( + Some(Box::new(Expr::new(ExprDef::Local(String::from("a"))))), + ))]))), + ))], + ); + + check_parser( + "function (a, ...b) {}", + &[Expr::new(ExprDef::FunctionDecl( + None, + vec![ + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::UnaryOp( + UnaryOp::Spread, + Box::new(Expr::new(ExprDef::Local(String::from("b")))), + )), + ], + Box::new(Expr::new(ExprDef::ObjectDecl(Box::new(BTreeMap::new())))), + ))], + ); + + check_parser( + "(...a) => {}", + &[Expr::new(ExprDef::ArrowFunctionDecl( + vec![Expr::new(ExprDef::UnaryOp( + UnaryOp::Spread, + Box::new(Expr::new(ExprDef::Local(String::from("a")))), + ))], + Box::new(Expr::new(ExprDef::ObjectDecl(Box::new(BTreeMap::new())))), + ))], + ); + + check_parser( + "(a, b, ...c) => {}", + &[Expr::new(ExprDef::ArrowFunctionDecl( + vec![ + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + Expr::new(ExprDef::UnaryOp( + UnaryOp::Spread, + Box::new(Expr::new(ExprDef::Local(String::from("c")))), + )), + ], + Box::new(Expr::new(ExprDef::ObjectDecl(Box::new(BTreeMap::new())))), + ))], + ); + + check_parser( + "(a, b) => { return a + b; }", + &[Expr::new(ExprDef::ArrowFunctionDecl( + vec![ + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + ], + Box::new(Expr::new(ExprDef::Block(vec![Expr::new(ExprDef::Return( + Some(Box::new(create_bin_op( + BinOp::Num(NumOp::Add), + Expr::new(ExprDef::Local(String::from("a"))), + Expr::new(ExprDef::Local(String::from("b"))), + ))), + ))]))), + ))], + ); +} diff --git a/boa_cli/Cargo.toml b/boa_cli/Cargo.toml index cd18ebb746..7c43dc11f6 100644 --- a/boa_cli/Cargo.toml +++ b/boa_cli/Cargo.toml @@ -11,5 +11,5 @@ exclude = ["../.vscode/*", "../Dockerfile", "../Makefile", "../.editorConfig"] edition = "2018" [dependencies] -Boa = { path = "../boa" } +Boa = { path = "../boa", default-features = false } structopt = "0.3.9"