mirror of https://github.com/boa-dev/boa.git
Iban Eguia
5 years ago
committed by
GitHub
24 changed files with 3073 additions and 3079 deletions
@ -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"); |
||||
} |
@ -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 |
||||
)); |
||||
} |
@ -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::<f64>(return_val).expect("Could not convert value to f64"), |
||||
expected_return_val |
||||
); |
||||
} |
@ -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::<f64>() { |
||||
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)); |
||||
} |
||||
} |
@ -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::<f64>() { |
||||
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 |
||||
} |
@ -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)); |
||||
} |
@ -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"); |
||||
} |
@ -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"); |
||||
} |
@ -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> = 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)"); |
||||
} |
@ -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); |
||||
} |
@ -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")); |
||||
} |
@ -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) |
||||
); |
||||
} |
@ -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<String, Expr> = 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<String, Expr> = 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::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"))), |
||||
))), |
||||
))]))), |
||||
))], |
||||
); |
||||
} |
Loading…
Reference in new issue