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