Browse Source

Moved test modules to their own files (#258)

pull/257/head
Iban Eguia 5 years ago committed by GitHub
parent
commit
86052d6d75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 640
      boa/src/builtins/array/mod.rs
  2. 633
      boa/src/builtins/array/tests.rs
  3. 86
      boa/src/builtins/boolean/mod.rs
  4. 79
      boa/src/builtins/boolean/tests.rs
  5. 32
      boa/src/builtins/function/mod.rs
  6. 25
      boa/src/builtins/function/tests.rs
  7. 376
      boa/src/builtins/number.rs
  8. 162
      boa/src/builtins/number/mod.rs
  9. 213
      boa/src/builtins/number/tests.rs
  10. 114
      boa/src/builtins/regexp/mod.rs
  11. 111
      boa/src/builtins/regexp/tests.rs
  12. 312
      boa/src/builtins/string/mod.rs
  13. 305
      boa/src/builtins/string/tests.rs
  14. 42
      boa/src/builtins/symbol/mod.rs
  15. 35
      boa/src/builtins/symbol/tests.rs
  16. 55
      boa/src/builtins/value/mod.rs
  17. 48
      boa/src/builtins/value/tests.rs
  18. 243
      boa/src/exec/mod.rs
  19. 236
      boa/src/exec/tests.rs
  20. 503
      boa/src/syntax/lexer/mod.rs
  21. 496
      boa/src/syntax/lexer/tests.rs
  22. 713
      boa/src/syntax/parser/mod.rs
  23. 691
      boa/src/syntax/parser/tests.rs
  24. 2
      boa_cli/Cargo.toml

640
boa/src/builtins/array.rs → boa/src/builtins/array/mod.rs

@ -1,3 +1,6 @@
#[cfg(test)]
mod tests;
use crate::{ use crate::{
builtins::{ builtins::{
function::NativeFunctionData, function::NativeFunctionData,
@ -748,640 +751,3 @@ pub fn create_constructor(global: &Value) -> Value {
array_prototype.set_field_slice("constructor", array.clone()); array_prototype.set_field_slice("constructor", array.clone());
array array
} }
#[cfg(test)]
mod tests {
use crate::exec::Executor;
use crate::forward;
use crate::realm::Realm;
#[test]
fn is_array() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = [];
var new_arr = new Array();
var many = ["a", "b", "c"];
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "Array.isArray(empty)"), "true");
assert_eq!(forward(&mut engine, "Array.isArray(new_arr)"), "true");
assert_eq!(forward(&mut engine, "Array.isArray(many)"), "true");
assert_eq!(forward(&mut engine, "Array.isArray([1, 2, 3])"), "true");
assert_eq!(forward(&mut engine, "Array.isArray([])"), "true");
assert_eq!(forward(&mut engine, "Array.isArray({})"), "false");
// assert_eq!(forward(&mut engine, "Array.isArray(new Array)"), "true");
assert_eq!(forward(&mut engine, "Array.isArray()"), "false");
assert_eq!(
forward(&mut engine, "Array.isArray({ constructor: Array })"),
"false"
);
assert_eq!(
forward(
&mut engine,
"Array.isArray({ push: Array.prototype.push, concat: Array.prototype.concat })"
),
"false"
);
assert_eq!(forward(&mut engine, "Array.isArray(17)"), "false");
assert_eq!(
forward(&mut engine, "Array.isArray({ __proto__: Array.prototype })"),
"false"
);
assert_eq!(
forward(&mut engine, "Array.isArray({ length: 0 })"),
"false"
);
}
#[test]
fn concat() {
//TODO: array display formatter
// let realm = Realm::create();
// let mut engine = Executor::new(realm);
// let init = r#"
// var empty = new Array();
// var one = new Array(1);
// "#;
// forward(&mut engine, init);
// // Empty ++ Empty
// let ee = forward(&mut engine, "empty.concat(empty)");
// assert_eq!(ee, String::from("[]"));
// // Empty ++ NonEmpty
// let en = forward(&mut engine, "empty.concat(one)");
// assert_eq!(en, String::from("[a]"));
// // NonEmpty ++ Empty
// let ne = forward(&mut engine, "one.concat(empty)");
// assert_eq!(ne, String::from("a.b.c"));
// // NonEmpty ++ NonEmpty
// let nn = forward(&mut engine, "one.concat(one)");
// assert_eq!(nn, String::from("a.b.c"));
}
#[test]
fn join() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = [ ];
var one = ["a"];
var many = ["a", "b", "c"];
"#;
forward(&mut engine, init);
// Empty
let empty = forward(&mut engine, "empty.join('.')");
assert_eq!(empty, String::from(""));
// One
let one = forward(&mut engine, "one.join('.')");
assert_eq!(one, String::from("a"));
// Many
let many = forward(&mut engine, "many.join('.')");
assert_eq!(many, String::from("a.b.c"));
}
#[test]
fn to_string() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = [ ];
var one = ["a"];
var many = ["a", "b", "c"];
"#;
forward(&mut engine, init);
// Empty
let empty = forward(&mut engine, "empty.toString()");
assert_eq!(empty, String::from(""));
// One
let one = forward(&mut engine, "one.toString()");
assert_eq!(one, String::from("a"));
// Many
let many = forward(&mut engine, "many.toString()");
assert_eq!(many, String::from("a,b,c"));
}
#[test]
fn every() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
// taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every
let init = r#"
var empty = [];
var array = [11, 23, 45];
function callback(element) {
return element > 10;
}
function callback2(element) {
return element < 10;
}
var appendArray = [1,2,3,4];
function appendingCallback(elem,index,arr) {
arr.push('new');
return elem !== "new";
}
var delArray = [1,2,3,4];
function deletingCallback(elem,index,arr) {
arr.pop()
return elem < 3;
}
"#;
forward(&mut engine, init);
let result = forward(&mut engine, "array.every(callback);");
assert_eq!(result, "true");
let result = forward(&mut engine, "empty.every(callback);");
assert_eq!(result, "true");
let result = forward(&mut engine, "array.every(callback2);");
assert_eq!(result, "false");
let result = forward(&mut engine, "appendArray.every(appendingCallback);");
assert_eq!(result, "true");
let result = forward(&mut engine, "delArray.every(deletingCallback);");
assert_eq!(result, "true");
}
#[test]
fn find() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
function comp(a) {
return a == "a";
}
var many = ["a", "b", "c"];
"#;
forward(&mut engine, init);
let found = forward(&mut engine, "many.find(comp)");
assert_eq!(found, String::from("a"));
}
#[test]
fn find_index() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let code = r#"
function comp(item) {
return item == 2;
}
var many = [1, 2, 3];
var empty = [];
var missing = [4, 5, 6];
"#;
forward(&mut engine, code);
let many = forward(&mut engine, "many.findIndex(comp)");
assert_eq!(many, String::from("1"));
let empty = forward(&mut engine, "empty.findIndex(comp)");
assert_eq!(empty, String::from("-1"));
let missing = forward(&mut engine, "missing.findIndex(comp)");
assert_eq!(missing, String::from("-1"));
}
#[test]
fn push() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var arr = [1, 2];
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "arr.push()"), "2");
assert_eq!(forward(&mut engine, "arr.push(3, 4)"), "4");
assert_eq!(forward(&mut engine, "arr[2]"), "3");
assert_eq!(forward(&mut engine, "arr[3]"), "4");
}
#[test]
fn pop() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = [ ];
var one = [1];
var many = [1, 2, 3, 4];
"#;
forward(&mut engine, init);
assert_eq!(
forward(&mut engine, "empty.pop()"),
String::from("undefined")
);
assert_eq!(forward(&mut engine, "one.pop()"), "1");
assert_eq!(forward(&mut engine, "one.length"), "0");
assert_eq!(forward(&mut engine, "many.pop()"), "4");
assert_eq!(forward(&mut engine, "many[0]"), "1");
assert_eq!(forward(&mut engine, "many.length"), "3");
}
#[test]
fn shift() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = [ ];
var one = [1];
var many = [1, 2, 3, 4];
"#;
forward(&mut engine, init);
assert_eq!(
forward(&mut engine, "empty.shift()"),
String::from("undefined")
);
assert_eq!(forward(&mut engine, "one.shift()"), "1");
assert_eq!(forward(&mut engine, "one.length"), "0");
assert_eq!(forward(&mut engine, "many.shift()"), "1");
assert_eq!(forward(&mut engine, "many[0]"), "2");
assert_eq!(forward(&mut engine, "many.length"), "3");
}
#[test]
fn unshift() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var arr = [3, 4];
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "arr.unshift()"), "2");
assert_eq!(forward(&mut engine, "arr.unshift(1, 2)"), "4");
assert_eq!(forward(&mut engine, "arr[0]"), "1");
assert_eq!(forward(&mut engine, "arr[1]"), "2");
}
#[test]
fn reverse() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var arr = [1, 2];
var reversed = arr.reverse();
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "reversed[0]"), "2");
assert_eq!(forward(&mut engine, "reversed[1]"), "1");
assert_eq!(forward(&mut engine, "arr[0]"), "2");
assert_eq!(forward(&mut engine, "arr[1]"), "1");
}
#[test]
fn index_of() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = [ ];
var one = ["a"];
var many = ["a", "b", "c"];
var duplicates = ["a", "b", "c", "a", "b"];
"#;
forward(&mut engine, init);
// Empty
let empty = forward(&mut engine, "empty.indexOf('a')");
assert_eq!(empty, String::from("-1"));
// One
let one = forward(&mut engine, "one.indexOf('a')");
assert_eq!(one, String::from("0"));
// Missing from one
let missing_from_one = forward(&mut engine, "one.indexOf('b')");
assert_eq!(missing_from_one, String::from("-1"));
// First in many
let first_in_many = forward(&mut engine, "many.indexOf('a')");
assert_eq!(first_in_many, String::from("0"));
// Second in many
let second_in_many = forward(&mut engine, "many.indexOf('b')");
assert_eq!(second_in_many, String::from("1"));
// First in duplicates
let first_in_many = forward(&mut engine, "duplicates.indexOf('a')");
assert_eq!(first_in_many, String::from("0"));
// Second in duplicates
let second_in_many = forward(&mut engine, "duplicates.indexOf('b')");
assert_eq!(second_in_many, String::from("1"));
// Positive fromIndex greater than array length
let fromindex_greater_than_length = forward(&mut engine, "one.indexOf('a', 2)");
assert_eq!(fromindex_greater_than_length, String::from("-1"));
// Positive fromIndex missed match
let fromindex_misses_match = forward(&mut engine, "many.indexOf('a', 1)");
assert_eq!(fromindex_misses_match, String::from("-1"));
// Positive fromIndex matched
let fromindex_matches = forward(&mut engine, "many.indexOf('b', 1)");
assert_eq!(fromindex_matches, String::from("1"));
// Positive fromIndex with duplicates
let first_in_many = forward(&mut engine, "duplicates.indexOf('a', 1)");
assert_eq!(first_in_many, String::from("3"));
// Negative fromIndex greater than array length
let fromindex_greater_than_length = forward(&mut engine, "one.indexOf('a', -2)");
assert_eq!(fromindex_greater_than_length, String::from("0"));
// Negative fromIndex missed match
let fromindex_misses_match = forward(&mut engine, "many.indexOf('b', -1)");
assert_eq!(fromindex_misses_match, String::from("-1"));
// Negative fromIndex matched
let fromindex_matches = forward(&mut engine, "many.indexOf('c', -1)");
assert_eq!(fromindex_matches, String::from("2"));
// Negative fromIndex with duplicates
let second_in_many = forward(&mut engine, "duplicates.indexOf('b', -2)");
assert_eq!(second_in_many, String::from("4"));
}
#[test]
fn last_index_of() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = [ ];
var one = ["a"];
var many = ["a", "b", "c"];
var duplicates = ["a", "b", "c", "a", "b"];
"#;
forward(&mut engine, init);
// Empty
let empty = forward(&mut engine, "empty.lastIndexOf('a')");
assert_eq!(empty, String::from("-1"));
// One
let one = forward(&mut engine, "one.lastIndexOf('a')");
assert_eq!(one, String::from("0"));
// Missing from one
let missing_from_one = forward(&mut engine, "one.lastIndexOf('b')");
assert_eq!(missing_from_one, String::from("-1"));
// First in many
let first_in_many = forward(&mut engine, "many.lastIndexOf('a')");
assert_eq!(first_in_many, String::from("0"));
// Second in many
let second_in_many = forward(&mut engine, "many.lastIndexOf('b')");
assert_eq!(second_in_many, String::from("1"));
// 4th in duplicates
let first_in_many = forward(&mut engine, "duplicates.lastIndexOf('a')");
assert_eq!(first_in_many, String::from("3"));
// 5th in duplicates
let second_in_many = forward(&mut engine, "duplicates.lastIndexOf('b')");
assert_eq!(second_in_many, String::from("4"));
// Positive fromIndex greater than array length
let fromindex_greater_than_length = forward(&mut engine, "one.lastIndexOf('a', 2)");
assert_eq!(fromindex_greater_than_length, String::from("0"));
// Positive fromIndex missed match
let fromindex_misses_match = forward(&mut engine, "many.lastIndexOf('c', 1)");
assert_eq!(fromindex_misses_match, String::from("-1"));
// Positive fromIndex matched
let fromindex_matches = forward(&mut engine, "many.lastIndexOf('b', 1)");
assert_eq!(fromindex_matches, String::from("1"));
// Positive fromIndex with duplicates
let first_in_many = forward(&mut engine, "duplicates.lastIndexOf('a', 1)");
assert_eq!(first_in_many, String::from("0"));
// Negative fromIndex greater than array length
let fromindex_greater_than_length = forward(&mut engine, "one.lastIndexOf('a', -2)");
assert_eq!(fromindex_greater_than_length, String::from("-1"));
// Negative fromIndex missed match
let fromindex_misses_match = forward(&mut engine, "many.lastIndexOf('c', -2)");
assert_eq!(fromindex_misses_match, String::from("-1"));
// Negative fromIndex matched
let fromindex_matches = forward(&mut engine, "many.lastIndexOf('c', -1)");
assert_eq!(fromindex_matches, String::from("2"));
// Negative fromIndex with duplicates
let second_in_many = forward(&mut engine, "duplicates.lastIndexOf('b', -2)");
assert_eq!(second_in_many, String::from("1"));
}
#[test]
fn fill() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
forward(&mut engine, "var a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4).join()"),
String::from("4,4,4")
);
// make sure the array is modified
assert_eq!(forward(&mut engine, "a.join()"), String::from("4,4,4"));
forward(&mut engine, "a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4, '1').join()"),
String::from("1,4,4")
);
forward(&mut engine, "a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4, 1, 2).join()"),
String::from("1,4,3")
);
forward(&mut engine, "a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4, 1, 1).join()"),
String::from("1,2,3")
);
forward(&mut engine, "a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4, 3, 3).join()"),
String::from("1,2,3")
);
forward(&mut engine, "a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4, -3, -2).join()"),
String::from("4,2,3")
);
// TODO: uncomment when NaN support is added
// forward(&mut engine, "a = [1, 2, 3];");
// assert_eq!(
// forward(&mut engine, "a.fill(4, NaN, NaN).join()"),
// String::from("1,2,3")
// );
forward(&mut engine, "a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4, 3, 5).join()"),
String::from("1,2,3")
);
forward(&mut engine, "a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4, '1.2', '2.5').join()"),
String::from("1,4,3")
);
forward(&mut engine, "a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4, 'str').join()"),
String::from("4,4,4")
);
forward(&mut engine, "a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4, 'str', 'str').join()"),
String::from("1,2,3")
);
forward(&mut engine, "a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4, undefined, null).join()"),
String::from("1,2,3")
);
forward(&mut engine, "a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4, undefined, undefined).join()"),
String::from("4,4,4")
);
assert_eq!(
forward(&mut engine, "a.fill().join()"),
String::from("undefined,undefined,undefined")
);
// test object reference
forward(&mut engine, "a = (new Array(3)).fill({});");
forward(&mut engine, "a[0].hi = 'hi';");
assert_eq!(forward(&mut engine, "a[0].hi"), String::from("hi"));
}
#[test]
fn inclues_value() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = [ ];
var one = ["a"];
var many = ["a", "b", "c"];
var duplicates = ["a", "b", "c", "a", "b"];
var undefined = [undefined];
"#;
forward(&mut engine, init);
// Empty
let empty = forward(&mut engine, "empty.includes('a')");
assert_eq!(empty, String::from("false"));
// One
let one = forward(&mut engine, "one.includes('a')");
assert_eq!(one, String::from("true"));
// Missing from one
let missing_from_one = forward(&mut engine, "one.includes('b')");
assert_eq!(missing_from_one, String::from("false"));
// In many
let first_in_many = forward(&mut engine, "many.includes('c')");
assert_eq!(first_in_many, String::from("true"));
// Missing from many
let second_in_many = forward(&mut engine, "many.includes('d')");
assert_eq!(second_in_many, String::from("false"));
// In duplicates
let first_in_many = forward(&mut engine, "duplicates.includes('a')");
assert_eq!(first_in_many, String::from("true"));
// Missing from duplicates
let second_in_many = forward(&mut engine, "duplicates.includes('d')");
assert_eq!(second_in_many, String::from("false"));
}
#[test]
fn map() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let js = r#"
var empty = [];
var one = ["x"];
var many = ["x", "y", "z"];
// TODO: uncomment when `this` has been implemented
// var _this = { answer: 42 };
// function callbackThatUsesThis() {
// return 'The answer to life is: ' + this.answer;
// }
var empty_mapped = empty.map(v => v + '_');
var one_mapped = one.map(v => '_' + v);
var many_mapped = many.map(v => '_' + v + '_');
"#;
forward(&mut engine, js);
// assert the old arrays have not been modified
assert_eq!(forward(&mut engine, "one[0]"), String::from("x"));
assert_eq!(
forward(&mut engine, "many[2] + many[1] + many[0]"),
String::from("zyx")
);
// NB: These tests need to be rewritten once `Display` has been implemented for `Array`
// Empty
assert_eq!(
forward(&mut engine, "empty_mapped.length"),
String::from("0")
);
// One
assert_eq!(forward(&mut engine, "one_mapped.length"), String::from("1"));
assert_eq!(forward(&mut engine, "one_mapped[0]"), String::from("_x"));
// Many
assert_eq!(
forward(&mut engine, "many_mapped.length"),
String::from("3")
);
assert_eq!(
forward(
&mut engine,
"many_mapped[0] + many_mapped[1] + many_mapped[2]"
),
String::from("_x__y__z_")
);
// TODO: uncomment when `this` has been implemented
// One but it uses `this` inside the callback
// let one_with_this = forward(&mut engine, "one.map(callbackThatUsesThis, _this)[0];");
// assert_eq!(one_with_this, String::from("The answer to life is: 42"))
}
#[test]
fn slice() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = [ ].slice();
var one = ["a"].slice();
var many1 = ["a", "b", "c", "d"].slice(1);
var many2 = ["a", "b", "c", "d"].slice(2, 3);
var many3 = ["a", "b", "c", "d"].slice(7);
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "empty.length"), "0");
assert_eq!(forward(&mut engine, "one[0]"), "a");
assert_eq!(forward(&mut engine, "many1[0]"), "b");
assert_eq!(forward(&mut engine, "many1[1]"), "c");
assert_eq!(forward(&mut engine, "many1[2]"), "d");
assert_eq!(forward(&mut engine, "many1.length"), "3");
assert_eq!(forward(&mut engine, "many2[0]"), "c");
assert_eq!(forward(&mut engine, "many2.length"), "1");
assert_eq!(forward(&mut engine, "many3.length"), "0");
}
}

633
boa/src/builtins/array/tests.rs

@ -0,0 +1,633 @@
use crate::exec::Executor;
use crate::forward;
use crate::realm::Realm;
#[test]
fn is_array() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = [];
var new_arr = new Array();
var many = ["a", "b", "c"];
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "Array.isArray(empty)"), "true");
assert_eq!(forward(&mut engine, "Array.isArray(new_arr)"), "true");
assert_eq!(forward(&mut engine, "Array.isArray(many)"), "true");
assert_eq!(forward(&mut engine, "Array.isArray([1, 2, 3])"), "true");
assert_eq!(forward(&mut engine, "Array.isArray([])"), "true");
assert_eq!(forward(&mut engine, "Array.isArray({})"), "false");
// assert_eq!(forward(&mut engine, "Array.isArray(new Array)"), "true");
assert_eq!(forward(&mut engine, "Array.isArray()"), "false");
assert_eq!(
forward(&mut engine, "Array.isArray({ constructor: Array })"),
"false"
);
assert_eq!(
forward(
&mut engine,
"Array.isArray({ push: Array.prototype.push, concat: Array.prototype.concat })"
),
"false"
);
assert_eq!(forward(&mut engine, "Array.isArray(17)"), "false");
assert_eq!(
forward(&mut engine, "Array.isArray({ __proto__: Array.prototype })"),
"false"
);
assert_eq!(
forward(&mut engine, "Array.isArray({ length: 0 })"),
"false"
);
}
#[test]
fn concat() {
//TODO: array display formatter
// let realm = Realm::create();
// let mut engine = Executor::new(realm);
// let init = r#"
// var empty = new Array();
// var one = new Array(1);
// "#;
// forward(&mut engine, init);
// // Empty ++ Empty
// let ee = forward(&mut engine, "empty.concat(empty)");
// assert_eq!(ee, String::from("[]"));
// // Empty ++ NonEmpty
// let en = forward(&mut engine, "empty.concat(one)");
// assert_eq!(en, String::from("[a]"));
// // NonEmpty ++ Empty
// let ne = forward(&mut engine, "one.concat(empty)");
// assert_eq!(ne, String::from("a.b.c"));
// // NonEmpty ++ NonEmpty
// let nn = forward(&mut engine, "one.concat(one)");
// assert_eq!(nn, String::from("a.b.c"));
}
#[test]
fn join() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = [ ];
var one = ["a"];
var many = ["a", "b", "c"];
"#;
forward(&mut engine, init);
// Empty
let empty = forward(&mut engine, "empty.join('.')");
assert_eq!(empty, String::from(""));
// One
let one = forward(&mut engine, "one.join('.')");
assert_eq!(one, String::from("a"));
// Many
let many = forward(&mut engine, "many.join('.')");
assert_eq!(many, String::from("a.b.c"));
}
#[test]
fn to_string() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = [ ];
var one = ["a"];
var many = ["a", "b", "c"];
"#;
forward(&mut engine, init);
// Empty
let empty = forward(&mut engine, "empty.toString()");
assert_eq!(empty, String::from(""));
// One
let one = forward(&mut engine, "one.toString()");
assert_eq!(one, String::from("a"));
// Many
let many = forward(&mut engine, "many.toString()");
assert_eq!(many, String::from("a,b,c"));
}
#[test]
fn every() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
// taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every
let init = r#"
var empty = [];
var array = [11, 23, 45];
function callback(element) {
return element > 10;
}
function callback2(element) {
return element < 10;
}
var appendArray = [1,2,3,4];
function appendingCallback(elem,index,arr) {
arr.push('new');
return elem !== "new";
}
var delArray = [1,2,3,4];
function deletingCallback(elem,index,arr) {
arr.pop()
return elem < 3;
}
"#;
forward(&mut engine, init);
let result = forward(&mut engine, "array.every(callback);");
assert_eq!(result, "true");
let result = forward(&mut engine, "empty.every(callback);");
assert_eq!(result, "true");
let result = forward(&mut engine, "array.every(callback2);");
assert_eq!(result, "false");
let result = forward(&mut engine, "appendArray.every(appendingCallback);");
assert_eq!(result, "true");
let result = forward(&mut engine, "delArray.every(deletingCallback);");
assert_eq!(result, "true");
}
#[test]
fn find() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
function comp(a) {
return a == "a";
}
var many = ["a", "b", "c"];
"#;
forward(&mut engine, init);
let found = forward(&mut engine, "many.find(comp)");
assert_eq!(found, String::from("a"));
}
#[test]
fn find_index() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let code = r#"
function comp(item) {
return item == 2;
}
var many = [1, 2, 3];
var empty = [];
var missing = [4, 5, 6];
"#;
forward(&mut engine, code);
let many = forward(&mut engine, "many.findIndex(comp)");
assert_eq!(many, String::from("1"));
let empty = forward(&mut engine, "empty.findIndex(comp)");
assert_eq!(empty, String::from("-1"));
let missing = forward(&mut engine, "missing.findIndex(comp)");
assert_eq!(missing, String::from("-1"));
}
#[test]
fn push() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var arr = [1, 2];
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "arr.push()"), "2");
assert_eq!(forward(&mut engine, "arr.push(3, 4)"), "4");
assert_eq!(forward(&mut engine, "arr[2]"), "3");
assert_eq!(forward(&mut engine, "arr[3]"), "4");
}
#[test]
fn pop() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = [ ];
var one = [1];
var many = [1, 2, 3, 4];
"#;
forward(&mut engine, init);
assert_eq!(
forward(&mut engine, "empty.pop()"),
String::from("undefined")
);
assert_eq!(forward(&mut engine, "one.pop()"), "1");
assert_eq!(forward(&mut engine, "one.length"), "0");
assert_eq!(forward(&mut engine, "many.pop()"), "4");
assert_eq!(forward(&mut engine, "many[0]"), "1");
assert_eq!(forward(&mut engine, "many.length"), "3");
}
#[test]
fn shift() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = [ ];
var one = [1];
var many = [1, 2, 3, 4];
"#;
forward(&mut engine, init);
assert_eq!(
forward(&mut engine, "empty.shift()"),
String::from("undefined")
);
assert_eq!(forward(&mut engine, "one.shift()"), "1");
assert_eq!(forward(&mut engine, "one.length"), "0");
assert_eq!(forward(&mut engine, "many.shift()"), "1");
assert_eq!(forward(&mut engine, "many[0]"), "2");
assert_eq!(forward(&mut engine, "many.length"), "3");
}
#[test]
fn unshift() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var arr = [3, 4];
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "arr.unshift()"), "2");
assert_eq!(forward(&mut engine, "arr.unshift(1, 2)"), "4");
assert_eq!(forward(&mut engine, "arr[0]"), "1");
assert_eq!(forward(&mut engine, "arr[1]"), "2");
}
#[test]
fn reverse() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var arr = [1, 2];
var reversed = arr.reverse();
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "reversed[0]"), "2");
assert_eq!(forward(&mut engine, "reversed[1]"), "1");
assert_eq!(forward(&mut engine, "arr[0]"), "2");
assert_eq!(forward(&mut engine, "arr[1]"), "1");
}
#[test]
fn index_of() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = [ ];
var one = ["a"];
var many = ["a", "b", "c"];
var duplicates = ["a", "b", "c", "a", "b"];
"#;
forward(&mut engine, init);
// Empty
let empty = forward(&mut engine, "empty.indexOf('a')");
assert_eq!(empty, String::from("-1"));
// One
let one = forward(&mut engine, "one.indexOf('a')");
assert_eq!(one, String::from("0"));
// Missing from one
let missing_from_one = forward(&mut engine, "one.indexOf('b')");
assert_eq!(missing_from_one, String::from("-1"));
// First in many
let first_in_many = forward(&mut engine, "many.indexOf('a')");
assert_eq!(first_in_many, String::from("0"));
// Second in many
let second_in_many = forward(&mut engine, "many.indexOf('b')");
assert_eq!(second_in_many, String::from("1"));
// First in duplicates
let first_in_many = forward(&mut engine, "duplicates.indexOf('a')");
assert_eq!(first_in_many, String::from("0"));
// Second in duplicates
let second_in_many = forward(&mut engine, "duplicates.indexOf('b')");
assert_eq!(second_in_many, String::from("1"));
// Positive fromIndex greater than array length
let fromindex_greater_than_length = forward(&mut engine, "one.indexOf('a', 2)");
assert_eq!(fromindex_greater_than_length, String::from("-1"));
// Positive fromIndex missed match
let fromindex_misses_match = forward(&mut engine, "many.indexOf('a', 1)");
assert_eq!(fromindex_misses_match, String::from("-1"));
// Positive fromIndex matched
let fromindex_matches = forward(&mut engine, "many.indexOf('b', 1)");
assert_eq!(fromindex_matches, String::from("1"));
// Positive fromIndex with duplicates
let first_in_many = forward(&mut engine, "duplicates.indexOf('a', 1)");
assert_eq!(first_in_many, String::from("3"));
// Negative fromIndex greater than array length
let fromindex_greater_than_length = forward(&mut engine, "one.indexOf('a', -2)");
assert_eq!(fromindex_greater_than_length, String::from("0"));
// Negative fromIndex missed match
let fromindex_misses_match = forward(&mut engine, "many.indexOf('b', -1)");
assert_eq!(fromindex_misses_match, String::from("-1"));
// Negative fromIndex matched
let fromindex_matches = forward(&mut engine, "many.indexOf('c', -1)");
assert_eq!(fromindex_matches, String::from("2"));
// Negative fromIndex with duplicates
let second_in_many = forward(&mut engine, "duplicates.indexOf('b', -2)");
assert_eq!(second_in_many, String::from("4"));
}
#[test]
fn last_index_of() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = [ ];
var one = ["a"];
var many = ["a", "b", "c"];
var duplicates = ["a", "b", "c", "a", "b"];
"#;
forward(&mut engine, init);
// Empty
let empty = forward(&mut engine, "empty.lastIndexOf('a')");
assert_eq!(empty, String::from("-1"));
// One
let one = forward(&mut engine, "one.lastIndexOf('a')");
assert_eq!(one, String::from("0"));
// Missing from one
let missing_from_one = forward(&mut engine, "one.lastIndexOf('b')");
assert_eq!(missing_from_one, String::from("-1"));
// First in many
let first_in_many = forward(&mut engine, "many.lastIndexOf('a')");
assert_eq!(first_in_many, String::from("0"));
// Second in many
let second_in_many = forward(&mut engine, "many.lastIndexOf('b')");
assert_eq!(second_in_many, String::from("1"));
// 4th in duplicates
let first_in_many = forward(&mut engine, "duplicates.lastIndexOf('a')");
assert_eq!(first_in_many, String::from("3"));
// 5th in duplicates
let second_in_many = forward(&mut engine, "duplicates.lastIndexOf('b')");
assert_eq!(second_in_many, String::from("4"));
// Positive fromIndex greater than array length
let fromindex_greater_than_length = forward(&mut engine, "one.lastIndexOf('a', 2)");
assert_eq!(fromindex_greater_than_length, String::from("0"));
// Positive fromIndex missed match
let fromindex_misses_match = forward(&mut engine, "many.lastIndexOf('c', 1)");
assert_eq!(fromindex_misses_match, String::from("-1"));
// Positive fromIndex matched
let fromindex_matches = forward(&mut engine, "many.lastIndexOf('b', 1)");
assert_eq!(fromindex_matches, String::from("1"));
// Positive fromIndex with duplicates
let first_in_many = forward(&mut engine, "duplicates.lastIndexOf('a', 1)");
assert_eq!(first_in_many, String::from("0"));
// Negative fromIndex greater than array length
let fromindex_greater_than_length = forward(&mut engine, "one.lastIndexOf('a', -2)");
assert_eq!(fromindex_greater_than_length, String::from("-1"));
// Negative fromIndex missed match
let fromindex_misses_match = forward(&mut engine, "many.lastIndexOf('c', -2)");
assert_eq!(fromindex_misses_match, String::from("-1"));
// Negative fromIndex matched
let fromindex_matches = forward(&mut engine, "many.lastIndexOf('c', -1)");
assert_eq!(fromindex_matches, String::from("2"));
// Negative fromIndex with duplicates
let second_in_many = forward(&mut engine, "duplicates.lastIndexOf('b', -2)");
assert_eq!(second_in_many, String::from("1"));
}
#[test]
fn fill() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
forward(&mut engine, "var a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4).join()"),
String::from("4,4,4")
);
// make sure the array is modified
assert_eq!(forward(&mut engine, "a.join()"), String::from("4,4,4"));
forward(&mut engine, "a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4, '1').join()"),
String::from("1,4,4")
);
forward(&mut engine, "a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4, 1, 2).join()"),
String::from("1,4,3")
);
forward(&mut engine, "a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4, 1, 1).join()"),
String::from("1,2,3")
);
forward(&mut engine, "a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4, 3, 3).join()"),
String::from("1,2,3")
);
forward(&mut engine, "a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4, -3, -2).join()"),
String::from("4,2,3")
);
// TODO: uncomment when NaN support is added
// forward(&mut engine, "a = [1, 2, 3];");
// assert_eq!(
// forward(&mut engine, "a.fill(4, NaN, NaN).join()"),
// String::from("1,2,3")
// );
forward(&mut engine, "a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4, 3, 5).join()"),
String::from("1,2,3")
);
forward(&mut engine, "a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4, '1.2', '2.5').join()"),
String::from("1,4,3")
);
forward(&mut engine, "a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4, 'str').join()"),
String::from("4,4,4")
);
forward(&mut engine, "a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4, 'str', 'str').join()"),
String::from("1,2,3")
);
forward(&mut engine, "a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4, undefined, null).join()"),
String::from("1,2,3")
);
forward(&mut engine, "a = [1, 2, 3];");
assert_eq!(
forward(&mut engine, "a.fill(4, undefined, undefined).join()"),
String::from("4,4,4")
);
assert_eq!(
forward(&mut engine, "a.fill().join()"),
String::from("undefined,undefined,undefined")
);
// test object reference
forward(&mut engine, "a = (new Array(3)).fill({});");
forward(&mut engine, "a[0].hi = 'hi';");
assert_eq!(forward(&mut engine, "a[0].hi"), String::from("hi"));
}
#[test]
fn inclues_value() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = [ ];
var one = ["a"];
var many = ["a", "b", "c"];
var duplicates = ["a", "b", "c", "a", "b"];
var undefined = [undefined];
"#;
forward(&mut engine, init);
// Empty
let empty = forward(&mut engine, "empty.includes('a')");
assert_eq!(empty, String::from("false"));
// One
let one = forward(&mut engine, "one.includes('a')");
assert_eq!(one, String::from("true"));
// Missing from one
let missing_from_one = forward(&mut engine, "one.includes('b')");
assert_eq!(missing_from_one, String::from("false"));
// In many
let first_in_many = forward(&mut engine, "many.includes('c')");
assert_eq!(first_in_many, String::from("true"));
// Missing from many
let second_in_many = forward(&mut engine, "many.includes('d')");
assert_eq!(second_in_many, String::from("false"));
// In duplicates
let first_in_many = forward(&mut engine, "duplicates.includes('a')");
assert_eq!(first_in_many, String::from("true"));
// Missing from duplicates
let second_in_many = forward(&mut engine, "duplicates.includes('d')");
assert_eq!(second_in_many, String::from("false"));
}
#[test]
fn map() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let js = r#"
var empty = [];
var one = ["x"];
var many = ["x", "y", "z"];
// TODO: uncomment when `this` has been implemented
// var _this = { answer: 42 };
// function callbackThatUsesThis() {
// return 'The answer to life is: ' + this.answer;
// }
var empty_mapped = empty.map(v => v + '_');
var one_mapped = one.map(v => '_' + v);
var many_mapped = many.map(v => '_' + v + '_');
"#;
forward(&mut engine, js);
// assert the old arrays have not been modified
assert_eq!(forward(&mut engine, "one[0]"), String::from("x"));
assert_eq!(
forward(&mut engine, "many[2] + many[1] + many[0]"),
String::from("zyx")
);
// NB: These tests need to be rewritten once `Display` has been implemented for `Array`
// Empty
assert_eq!(
forward(&mut engine, "empty_mapped.length"),
String::from("0")
);
// One
assert_eq!(forward(&mut engine, "one_mapped.length"), String::from("1"));
assert_eq!(forward(&mut engine, "one_mapped[0]"), String::from("_x"));
// Many
assert_eq!(
forward(&mut engine, "many_mapped.length"),
String::from("3")
);
assert_eq!(
forward(
&mut engine,
"many_mapped[0] + many_mapped[1] + many_mapped[2]"
),
String::from("_x__y__z_")
);
// TODO: uncomment when `this` has been implemented
// One but it uses `this` inside the callback
// let one_with_this = forward(&mut engine, "one.map(callbackThatUsesThis, _this)[0];");
// assert_eq!(one_with_this, String::from("The answer to life is: 42"))
}
#[test]
fn slice() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = [ ].slice();
var one = ["a"].slice();
var many1 = ["a", "b", "c", "d"].slice(1);
var many2 = ["a", "b", "c", "d"].slice(2, 3);
var many3 = ["a", "b", "c", "d"].slice(7);
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "empty.length"), "0");
assert_eq!(forward(&mut engine, "one[0]"), "a");
assert_eq!(forward(&mut engine, "many1[0]"), "b");
assert_eq!(forward(&mut engine, "many1[1]"), "c");
assert_eq!(forward(&mut engine, "many1[2]"), "d");
assert_eq!(forward(&mut engine, "many1.length"), "3");
assert_eq!(forward(&mut engine, "many2[0]"), "c");
assert_eq!(forward(&mut engine, "many2.length"), "1");
assert_eq!(forward(&mut engine, "many3.length"), "0");
}

86
boa/src/builtins/boolean.rs → boa/src/builtins/boolean/mod.rs

@ -1,3 +1,6 @@
#[cfg(test)]
mod tests;
use crate::{ use crate::{
builtins::{ builtins::{
function::NativeFunctionData, function::NativeFunctionData,
@ -86,86 +89,3 @@ pub fn this_boolean_value(value: &Value) -> Value {
_ => to_value(false), _ => to_value(false),
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::exec::Executor;
use crate::realm::Realm;
use crate::{builtins::value::same_value, forward, forward_val};
#[test]
fn check_boolean_constructor_is_function() {
let global = ValueData::new_obj(None);
let boolean_constructor = create_constructor(&global);
assert_eq!(boolean_constructor.is_function(), true);
}
#[allow(clippy::result_unwrap_used)]
#[test]
/// Test the correct type is returned from call and construct
fn construct_and_call() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var one = new Boolean(1);
var zero = Boolean(0);
"#;
forward(&mut engine, init);
let one = forward_val(&mut engine, "one").unwrap();
let zero = forward_val(&mut engine, "zero").unwrap();
assert_eq!(one.is_object(), true);
assert_eq!(zero.is_boolean(), true);
}
#[test]
fn constructor_gives_true_instance() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var trueVal = new Boolean(true);
var trueNum = new Boolean(1);
var trueString = new Boolean("true");
var trueBool = new Boolean(trueVal);
"#;
forward(&mut engine, init);
let true_val = forward_val(&mut engine, "trueVal").expect("value expected");
let true_num = forward_val(&mut engine, "trueNum").expect("value expected");
let true_string = forward_val(&mut engine, "trueString").expect("value expected");
let true_bool = forward_val(&mut engine, "trueBool").expect("value expected");
// Values should all be objects
assert_eq!(true_val.is_object(), true);
assert_eq!(true_num.is_object(), true);
assert_eq!(true_string.is_object(), true);
assert_eq!(true_bool.is_object(), true);
// Values should all be truthy
assert_eq!(true_val.is_true(), true);
assert_eq!(true_num.is_true(), true);
assert_eq!(true_string.is_true(), true);
assert_eq!(true_bool.is_true(), true);
}
#[test]
fn instances_have_correct_proto_set() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var boolInstance = new Boolean(true);
var boolProto = Boolean.prototype;
"#;
forward(&mut engine, init);
let bool_instance = forward_val(&mut engine, "boolInstance").expect("value expected");
let bool_prototype = forward_val(&mut engine, "boolProto").expect("value expected");
assert!(same_value(
&bool_instance.get_internal_slot("__proto__"),
&bool_prototype,
true
));
}
}

79
boa/src/builtins/boolean/tests.rs

@ -0,0 +1,79 @@
use super::*;
use crate::exec::Executor;
use crate::realm::Realm;
use crate::{builtins::value::same_value, forward, forward_val};
#[test]
fn check_boolean_constructor_is_function() {
let global = ValueData::new_obj(None);
let boolean_constructor = create_constructor(&global);
assert_eq!(boolean_constructor.is_function(), true);
}
/// Test the correct type is returned from call and construct
#[allow(clippy::result_unwrap_used)]
#[test]
fn construct_and_call() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var one = new Boolean(1);
var zero = Boolean(0);
"#;
forward(&mut engine, init);
let one = forward_val(&mut engine, "one").unwrap();
let zero = forward_val(&mut engine, "zero").unwrap();
assert_eq!(one.is_object(), true);
assert_eq!(zero.is_boolean(), true);
}
#[test]
fn constructor_gives_true_instance() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var trueVal = new Boolean(true);
var trueNum = new Boolean(1);
var trueString = new Boolean("true");
var trueBool = new Boolean(trueVal);
"#;
forward(&mut engine, init);
let true_val = forward_val(&mut engine, "trueVal").expect("value expected");
let true_num = forward_val(&mut engine, "trueNum").expect("value expected");
let true_string = forward_val(&mut engine, "trueString").expect("value expected");
let true_bool = forward_val(&mut engine, "trueBool").expect("value expected");
// Values should all be objects
assert_eq!(true_val.is_object(), true);
assert_eq!(true_num.is_object(), true);
assert_eq!(true_string.is_object(), true);
assert_eq!(true_bool.is_object(), true);
// Values should all be truthy
assert_eq!(true_val.is_true(), true);
assert_eq!(true_num.is_true(), true);
assert_eq!(true_string.is_true(), true);
assert_eq!(true_bool.is_true(), true);
}
#[test]
fn instances_have_correct_proto_set() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var boolInstance = new Boolean(true);
var boolProto = Boolean.prototype;
"#;
forward(&mut engine, init);
let bool_instance = forward_val(&mut engine, "boolInstance").expect("value expected");
let bool_prototype = forward_val(&mut engine, "boolProto").expect("value expected");
assert!(same_value(
&bool_instance.get_internal_slot("__proto__"),
&bool_prototype,
true
));
}

32
boa/src/builtins/function.rs → boa/src/builtins/function/mod.rs

@ -1,3 +1,6 @@
#[cfg(test)]
mod tests;
use crate::{ use crate::{
builtins::{ builtins::{
object::{internal_methods_trait::ObjectInternalMethods, Object}, object::{internal_methods_trait::ObjectInternalMethods, Object},
@ -129,32 +132,3 @@ pub fn create_unmapped_arguments_object(arguments_list: Vec<Value>) -> Value {
to_value(obj) to_value(obj)
} }
#[cfg(test)]
mod tests {
use crate::exec::Executor;
use crate::realm::Realm;
use crate::{builtins::value::from_value, forward, forward_val};
#[allow(clippy::float_cmp)]
#[test]
fn check_arguments_object() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
function jason(a, b) {
return arguments[0];
}
var val = jason(100, 6);
"#;
forward(&mut engine, init);
let expected_return_val: f64 = 100.0;
let return_val = forward_val(&mut engine, "val").expect("value expected");
assert_eq!(return_val.is_double(), true);
assert_eq!(
from_value::<f64>(return_val).expect("Could not convert value to f64"),
expected_return_val
);
}
}

25
boa/src/builtins/function/tests.rs

@ -0,0 +1,25 @@
use crate::exec::Executor;
use crate::realm::Realm;
use crate::{builtins::value::from_value, forward, forward_val};
#[allow(clippy::float_cmp)]
#[test]
fn check_arguments_object() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
function jason(a, b) {
return arguments[0];
}
var val = jason(100, 6);
"#;
forward(&mut engine, init);
let expected_return_val: f64 = 100.0;
let return_val = forward_val(&mut engine, "val").expect("value expected");
assert_eq!(return_val.is_double(), true);
assert_eq!(
from_value::<f64>(return_val).expect("Could not convert value to f64"),
expected_return_val
);
}

376
boa/src/builtins/number.rs

@ -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));
}
}

162
boa/src/builtins/number/mod.rs

@ -0,0 +1,162 @@
#[cfg(test)]
mod tests;
use crate::{
builtins::{
function::NativeFunctionData,
object::{internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, PROTOTYPE},
value::{to_value, ResultValue, Value, ValueData},
},
exec::Interpreter,
};
use std::{borrow::Borrow, f64, ops::Deref};
/// Helper function: to_number(value: &Value) -> Value
///
/// Converts a Value to a Number.
fn to_number(value: &Value) -> Value {
match *value.deref().borrow() {
ValueData::Boolean(b) => {
if b {
to_value(1)
} else {
to_value(0)
}
}
ValueData::Function(_) | ValueData::Symbol(_) | ValueData::Undefined => to_value(f64::NAN),
ValueData::Integer(i) => to_value(f64::from(i)),
ValueData::Object(ref o) => (o).deref().borrow().get_internal_slot("NumberData"),
ValueData::Null => to_value(0),
ValueData::Number(n) => to_value(n),
ValueData::String(ref s) => match s.parse::<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
}

213
boa/src/builtins/number/tests.rs

@ -0,0 +1,213 @@
use super::*;
use crate::{builtins::value::ValueData, exec::Executor, forward, forward_val, realm::Realm};
use std::f64;
#[test]
fn check_number_constructor_is_function() {
let global = ValueData::new_obj(None);
let number_constructor = create_constructor(&global);
assert_eq!(number_constructor.is_function(), true);
}
#[test]
fn call_number() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var default_zero = Number();
var int_one = Number(1);
var float_two = Number(2.1);
var str_three = Number('3.2');
var bool_one = Number(true);
var bool_zero = Number(false);
var invalid_nan = Number("I am not a number");
var from_exp = Number("2.34e+2");
"#;
forward(&mut engine, init);
let default_zero = forward_val(&mut engine, "default_zero").unwrap();
let int_one = forward_val(&mut engine, "int_one").unwrap();
let float_two = forward_val(&mut engine, "float_two").unwrap();
let str_three = forward_val(&mut engine, "str_three").unwrap();
let bool_one = forward_val(&mut engine, "bool_one").unwrap();
let bool_zero = forward_val(&mut engine, "bool_zero").unwrap();
let invalid_nan = forward_val(&mut engine, "invalid_nan").unwrap();
let from_exp = forward_val(&mut engine, "from_exp").unwrap();
assert_eq!(default_zero.to_num(), f64::from(0));
assert_eq!(int_one.to_num(), f64::from(1));
assert_eq!(float_two.to_num(), f64::from(2.1));
assert_eq!(str_three.to_num(), f64::from(3.2));
assert_eq!(bool_one.to_num(), f64::from(1));
assert!(invalid_nan.to_num().is_nan());
assert_eq!(bool_zero.to_num(), f64::from(0));
assert_eq!(from_exp.to_num(), f64::from(234));
}
#[test]
fn to_exponential() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var default_exp = Number().toExponential();
var int_exp = Number(5).toExponential();
var float_exp = Number(1.234).toExponential();
var big_exp = Number(1234).toExponential();
var nan_exp = Number("I am also not a number").toExponential();
var noop_exp = Number("1.23e+2").toExponential();
"#;
forward(&mut engine, init);
let default_exp = forward(&mut engine, "default_exp");
let int_exp = forward(&mut engine, "int_exp");
let float_exp = forward(&mut engine, "float_exp");
let big_exp = forward(&mut engine, "big_exp");
let nan_exp = forward(&mut engine, "nan_exp");
let noop_exp = forward(&mut engine, "noop_exp");
assert_eq!(default_exp, String::from("0e+0"));
assert_eq!(int_exp, String::from("5e+0"));
assert_eq!(float_exp, String::from("1.234e+0"));
assert_eq!(big_exp, String::from("1.234e+3"));
assert_eq!(nan_exp, String::from("NaN"));
assert_eq!(noop_exp, String::from("1.23e+2"));
}
#[test]
fn to_fixed() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var default_fixed = Number().toFixed();
var pos_fixed = Number("3.456e+4").toFixed();
var neg_fixed = Number("3.456e-4").toFixed();
var noop_fixed = Number(5).toFixed();
var nan_fixed = Number("I am not a number").toFixed();
"#;
forward(&mut engine, init);
let default_fixed = forward(&mut engine, "default_fixed");
let pos_fixed = forward(&mut engine, "pos_fixed");
let neg_fixed = forward(&mut engine, "neg_fixed");
let noop_fixed = forward(&mut engine, "noop_fixed");
let nan_fixed = forward(&mut engine, "nan_fixed");
assert_eq!(default_fixed, String::from("0"));
assert_eq!(pos_fixed, String::from("34560"));
assert_eq!(neg_fixed, String::from("0"));
assert_eq!(noop_fixed, String::from("5"));
assert_eq!(nan_fixed, String::from("NaN"));
}
#[test]
fn to_locale_string() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var default_locale = Number().toLocaleString();
var small_locale = Number(5).toLocaleString();
var big_locale = Number("345600").toLocaleString();
var neg_locale = Number(-25).toLocaleString();
"#;
// TODO: We don't actually do any locale checking here
// To honor the spec we should print numbers according to user locale.
forward(&mut engine, init);
let default_locale = forward(&mut engine, "default_locale");
let small_locale = forward(&mut engine, "small_locale");
let big_locale = forward(&mut engine, "big_locale");
let neg_locale = forward(&mut engine, "neg_locale");
assert_eq!(default_locale, String::from("0"));
assert_eq!(small_locale, String::from("5"));
assert_eq!(big_locale, String::from("345600"));
assert_eq!(neg_locale, String::from("-25"));
}
#[test]
#[ignore]
fn to_precision() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var default_precision = Number().toPrecision();
var low_precision = Number(123456789).toPrecision(1);
var more_precision = Number(123456789).toPrecision(4);
var exact_precision = Number(123456789).toPrecision(9);
var over_precision = Number(123456789).toPrecision(50);
var neg_precision = Number(-123456789).toPrecision(4);
"#;
forward(&mut engine, init);
let default_precision = forward(&mut engine, "default_precision");
let low_precision = forward(&mut engine, "low_precision");
let more_precision = forward(&mut engine, "more_precision");
let exact_precision = forward(&mut engine, "exact_precision");
let over_precision = forward(&mut engine, "over_precision");
let neg_precision = forward(&mut engine, "neg_precision");
assert_eq!(default_precision, String::from("0"));
assert_eq!(low_precision, String::from("1e+8"));
assert_eq!(more_precision, String::from("1.235e+8"));
assert_eq!(exact_precision, String::from("123456789"));
assert_eq!(
over_precision,
String::from("123456789.00000000000000000000000000000000000000000")
);
assert_eq!(neg_precision, String::from("-1.235e+8"));
}
#[test]
fn to_string() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var default_string = Number().toString();
var int_string = Number(123).toString();
var float_string = Number(1.234).toString();
var exp_string = Number("1.2e+4").toString();
var neg_string = Number(-1.2).toString();
"#;
forward(&mut engine, init);
let default_string = forward(&mut engine, "default_string");
let int_string = forward(&mut engine, "int_string");
let float_string = forward(&mut engine, "float_string");
let exp_string = forward(&mut engine, "exp_string");
let neg_string = forward(&mut engine, "neg_string");
assert_eq!(default_string, String::from("0"));
assert_eq!(int_string, String::from("123"));
assert_eq!(float_string, String::from("1.234"));
assert_eq!(exp_string, String::from("12000"));
assert_eq!(neg_string, String::from("-1.2"));
}
#[test]
fn value_of() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
// TODO: In addition to parsing numbers from strings, parse them bare As of October 2019
// the parser does not understand scientific e.g., Xe+Y or -Xe-Y notation.
let init = r#"
var default_val = Number().valueOf();
var int_val = Number("123").valueOf();
var float_val = Number(1.234).valueOf();
var exp_val = Number("1.2e+4").valueOf()
var neg_val = Number("-1.2e+4").valueOf()
"#;
forward(&mut engine, init);
let default_val = forward_val(&mut engine, "default_val").unwrap();
let int_val = forward_val(&mut engine, "int_val").unwrap();
let float_val = forward_val(&mut engine, "float_val").unwrap();
let exp_val = forward_val(&mut engine, "exp_val").unwrap();
let neg_val = forward_val(&mut engine, "neg_val").unwrap();
assert_eq!(default_val.to_num(), f64::from(0));
assert_eq!(int_val.to_num(), f64::from(123));
assert_eq!(float_val.to_num(), f64::from(1.234));
assert_eq!(exp_val.to_num(), f64::from(12000));
assert_eq!(neg_val.to_num(), f64::from(-12000));
}

114
boa/src/builtins/regexp.rs → boa/src/builtins/regexp/mod.rs

@ -356,116 +356,4 @@ pub fn create_constructor(global: &Value) -> Value {
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests;
use super::*;
use crate::exec::Executor;
use crate::forward;
use crate::realm::Realm;
#[test]
fn test_constructors() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var constructed = new RegExp("[0-9]+(\\.[0-9]+)?");
var literal = /[0-9]+(\.[0-9]+)?/;
var ctor_literal = new RegExp(/[0-9]+(\.[0-9]+)?/);
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "constructed.test('1.0')"), "true");
assert_eq!(forward(&mut engine, "literal.test('1.0')"), "true");
assert_eq!(forward(&mut engine, "ctor_literal.test('1.0')"), "true");
}
#[test]
fn check_regexp_constructor_is_function() {
let global = ValueData::new_obj(None);
let regexp_constructor = create_constructor(&global);
assert_eq!(regexp_constructor.is_function(), true);
}
// TODO: uncomment this test when property getters are supported
// #[test]
// fn test_flags() {
// let mut engine = Executor::new();
// let init = r#"
// var re_gi = /test/gi;
// var re_sm = /test/sm;
// "#;
//
// forward(&mut engine, init);
// assert_eq!(forward(&mut engine, "re_gi.global"), "true");
// assert_eq!(forward(&mut engine, "re_gi.ignoreCase"), "true");
// assert_eq!(forward(&mut engine, "re_gi.multiline"), "false");
// assert_eq!(forward(&mut engine, "re_gi.dotAll"), "false");
// assert_eq!(forward(&mut engine, "re_gi.unicode"), "false");
// assert_eq!(forward(&mut engine, "re_gi.sticky"), "false");
// assert_eq!(forward(&mut engine, "re_gi.flags"), "gi");
//
// assert_eq!(forward(&mut engine, "re_sm.global"), "false");
// assert_eq!(forward(&mut engine, "re_sm.ignoreCase"), "false");
// assert_eq!(forward(&mut engine, "re_sm.multiline"), "true");
// assert_eq!(forward(&mut engine, "re_sm.dotAll"), "true");
// assert_eq!(forward(&mut engine, "re_sm.unicode"), "false");
// assert_eq!(forward(&mut engine, "re_sm.sticky"), "false");
// assert_eq!(forward(&mut engine, "re_sm.flags"), "ms");
// }
#[test]
fn test_last_index() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var regex = /[0-9]+(\.[0-9]+)?/g;
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "regex.lastIndex"), "0");
assert_eq!(forward(&mut engine, "regex.test('1.0foo')"), "true");
assert_eq!(forward(&mut engine, "regex.lastIndex"), "3");
assert_eq!(forward(&mut engine, "regex.test('1.0foo')"), "false");
assert_eq!(forward(&mut engine, "regex.lastIndex"), "0");
}
#[test]
fn test_exec() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var re = /quick\s(brown).+?(jumps)/ig;
var result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog');
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "result[0]"), "Quick Brown Fox Jumps");
assert_eq!(forward(&mut engine, "result[1]"), "Brown");
assert_eq!(forward(&mut engine, "result[2]"), "Jumps");
assert_eq!(forward(&mut engine, "result.index"), "4");
assert_eq!(
forward(&mut engine, "result.input"),
"The Quick Brown Fox Jumps Over The Lazy Dog"
);
}
#[test]
fn test_to_string() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
assert_eq!(
forward(&mut engine, "(new RegExp('a+b+c')).toString()"),
"/a+b+c/"
);
assert_eq!(
forward(&mut engine, "(new RegExp('bar', 'g')).toString()"),
"/bar/g"
);
assert_eq!(
forward(&mut engine, "(new RegExp('\\\\n', 'g')).toString()"),
"/\\n/g"
);
assert_eq!(forward(&mut engine, "/\\n/g.toString()"), "/\\n/g");
}
}

111
boa/src/builtins/regexp/tests.rs

@ -0,0 +1,111 @@
use super::*;
use crate::exec::Executor;
use crate::forward;
use crate::realm::Realm;
#[test]
fn test_constructors() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var constructed = new RegExp("[0-9]+(\\.[0-9]+)?");
var literal = /[0-9]+(\.[0-9]+)?/;
var ctor_literal = new RegExp(/[0-9]+(\.[0-9]+)?/);
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "constructed.test('1.0')"), "true");
assert_eq!(forward(&mut engine, "literal.test('1.0')"), "true");
assert_eq!(forward(&mut engine, "ctor_literal.test('1.0')"), "true");
}
#[test]
fn check_regexp_constructor_is_function() {
let global = ValueData::new_obj(None);
let regexp_constructor = create_constructor(&global);
assert_eq!(regexp_constructor.is_function(), true);
}
// TODO: uncomment this test when property getters are supported
// #[test]
// fn test_flags() {
// let mut engine = Executor::new();
// let init = r#"
// var re_gi = /test/gi;
// var re_sm = /test/sm;
// "#;
//
// forward(&mut engine, init);
// assert_eq!(forward(&mut engine, "re_gi.global"), "true");
// assert_eq!(forward(&mut engine, "re_gi.ignoreCase"), "true");
// assert_eq!(forward(&mut engine, "re_gi.multiline"), "false");
// assert_eq!(forward(&mut engine, "re_gi.dotAll"), "false");
// assert_eq!(forward(&mut engine, "re_gi.unicode"), "false");
// assert_eq!(forward(&mut engine, "re_gi.sticky"), "false");
// assert_eq!(forward(&mut engine, "re_gi.flags"), "gi");
//
// assert_eq!(forward(&mut engine, "re_sm.global"), "false");
// assert_eq!(forward(&mut engine, "re_sm.ignoreCase"), "false");
// assert_eq!(forward(&mut engine, "re_sm.multiline"), "true");
// assert_eq!(forward(&mut engine, "re_sm.dotAll"), "true");
// assert_eq!(forward(&mut engine, "re_sm.unicode"), "false");
// assert_eq!(forward(&mut engine, "re_sm.sticky"), "false");
// assert_eq!(forward(&mut engine, "re_sm.flags"), "ms");
// }
#[test]
fn test_last_index() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var regex = /[0-9]+(\.[0-9]+)?/g;
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "regex.lastIndex"), "0");
assert_eq!(forward(&mut engine, "regex.test('1.0foo')"), "true");
assert_eq!(forward(&mut engine, "regex.lastIndex"), "3");
assert_eq!(forward(&mut engine, "regex.test('1.0foo')"), "false");
assert_eq!(forward(&mut engine, "regex.lastIndex"), "0");
}
#[test]
fn test_exec() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var re = /quick\s(brown).+?(jumps)/ig;
var result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog');
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "result[0]"), "Quick Brown Fox Jumps");
assert_eq!(forward(&mut engine, "result[1]"), "Brown");
assert_eq!(forward(&mut engine, "result[2]"), "Jumps");
assert_eq!(forward(&mut engine, "result.index"), "4");
assert_eq!(
forward(&mut engine, "result.input"),
"The Quick Brown Fox Jumps Over The Lazy Dog"
);
}
#[test]
fn test_to_string() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
assert_eq!(
forward(&mut engine, "(new RegExp('a+b+c')).toString()"),
"/a+b+c/"
);
assert_eq!(
forward(&mut engine, "(new RegExp('bar', 'g')).toString()"),
"/bar/g"
);
assert_eq!(
forward(&mut engine, "(new RegExp('\\\\n', 'g')).toString()"),
"/\\n/g"
);
assert_eq!(forward(&mut engine, "/\\n/g.toString()"), "/\\n/g");
}

312
boa/src/builtins/string.rs → boa/src/builtins/string/mod.rs

@ -1,3 +1,6 @@
#[cfg(test)]
mod tests;
use crate::{ use crate::{
builtins::{ builtins::{
function::NativeFunctionData, function::NativeFunctionData,
@ -881,312 +884,3 @@ pub fn create_constructor(global: &Value) -> Value {
pub fn init(global: &Value) { pub fn init(global: &Value) {
global.set_field_slice("String", create_constructor(global)); global.set_field_slice("String", create_constructor(global));
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::exec::Executor;
use crate::realm::Realm;
use crate::{forward, forward_val};
#[test]
fn check_string_constructor_is_function() {
let global = ValueData::new_obj(None);
let string_constructor = create_constructor(&global);
assert_eq!(string_constructor.is_function(), true);
}
#[test]
// TODO: re-enable when getProperty() is finished;
// fn length() {
// //TEST262: https://github.com/tc39/test262/blob/master/test/built-ins/String/length.js
// let mut engine = Executor::new();
// let init = r#"
// const a = new String(' ');
// const b = new String('\ud834\udf06');
// const c = new String(' \b ');
// cosnt d = new String('中文长度')
// "#;
// forward(&mut engine, init);
// let a = forward(&mut engine, "a.length");
// assert_eq!(a, String::from("1"));
// let b = forward(&mut engine, "b.length");
// // TODO: fix this
// // unicode surrogate pair length should be 1
// // utf16/usc2 length should be 2
// // utf8 length should be 4
// //assert_eq!(b, String::from("2"));
// let c = forward(&mut engine, "c.length");
// assert_eq!(c, String::from("3"));
// let d = forward(&mut engine, "d.length");
// assert_eq!(d, String::from("4"));
// }
#[test]
fn concat() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var hello = new String('Hello, ');
var world = new String('world! ');
var nice = new String('Have a nice day.');
"#;
forward(&mut engine, init);
// Todo: fix this
let _a = forward(&mut engine, "hello.concat(world, nice)");
let _b = forward(&mut engine, "hello + world + nice");
// assert_eq!(a, String::from("Hello, world! Have a nice day."));
// assert_eq!(b, String::from("Hello, world! Have a nice day."));
}
#[allow(clippy::result_unwrap_used)]
#[test]
/// Test the correct type is returned from call and construct
fn construct_and_call() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var hello = new String('Hello');
var world = String('world');
"#;
forward(&mut engine, init);
let hello = forward_val(&mut engine, "hello").unwrap();
let world = forward_val(&mut engine, "world").unwrap();
assert_eq!(hello.is_object(), true);
assert_eq!(world.is_string(), true);
}
#[test]
fn repeat() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = new String('');
var en = new String('english');
var zh = new String('');
"#;
forward(&mut engine, init);
let empty = String::from("");
assert_eq!(forward(&mut engine, "empty.repeat(0)"), empty);
assert_eq!(forward(&mut engine, "empty.repeat(1)"), empty);
assert_eq!(forward(&mut engine, "en.repeat(0)"), empty);
assert_eq!(forward(&mut engine, "zh.repeat(0)"), empty);
assert_eq!(
forward(&mut engine, "en.repeat(1)"),
String::from("english")
);
assert_eq!(
forward(&mut engine, "zh.repeat(2)"),
String::from("中文中文")
);
}
#[test]
fn replace() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var a = "abc";
a = a.replace("a", "2");
a
"#;
forward(&mut engine, init);
let empty = String::from("2bc");
assert_eq!(forward(&mut engine, "a"), empty);
}
#[test]
fn replace_with_function() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var a = "ecmascript is cool";
var p1, p2, p3;
var replacer = (match, cap1, cap2, cap3) => {
p1 = cap1;
p2 = cap2;
p3 = cap3;
return "awesome!";
};
a = a.replace(/c(o)(o)(l)/, replacer);
a;
"#;
forward(&mut engine, init);
assert_eq!(
forward(&mut engine, "a"),
String::from("ecmascript is awesome!")
);
assert_eq!(forward(&mut engine, "p1"), String::from("o"));
assert_eq!(forward(&mut engine, "p2"), String::from("o"));
assert_eq!(forward(&mut engine, "p3"), String::from("l"));
}
#[test]
fn starts_with() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = new String('');
var en = new String('english');
var zh = new String('');
var emptyLiteral = '';
var enLiteral = 'english';
var zhLiteral = '';
"#;
forward(&mut engine, init);
let pass = String::from("true");
assert_eq!(forward(&mut engine, "empty.startsWith('')"), pass);
assert_eq!(forward(&mut engine, "en.startsWith('e')"), pass);
assert_eq!(forward(&mut engine, "zh.startsWith('中')"), pass);
assert_eq!(forward(&mut engine, "emptyLiteral.startsWith('')"), pass);
assert_eq!(forward(&mut engine, "enLiteral.startsWith('e')"), pass);
assert_eq!(forward(&mut engine, "zhLiteral.startsWith('中')"), pass);
}
#[test]
fn ends_with() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = new String('');
var en = new String('english');
var zh = new String('');
var emptyLiteral = '';
var enLiteral = 'english';
var zhLiteral = '';
"#;
forward(&mut engine, init);
let pass = String::from("true");
assert_eq!(forward(&mut engine, "empty.endsWith('')"), pass);
assert_eq!(forward(&mut engine, "en.endsWith('h')"), pass);
assert_eq!(forward(&mut engine, "zh.endsWith('文')"), pass);
assert_eq!(forward(&mut engine, "emptyLiteral.endsWith('')"), pass);
assert_eq!(forward(&mut engine, "enLiteral.endsWith('h')"), pass);
assert_eq!(forward(&mut engine, "zhLiteral.endsWith('文')"), pass);
}
#[test]
fn match_all() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
assert_eq!(
forward(&mut engine, "'aa'.matchAll(null).length"),
String::from("0")
);
assert_eq!(
forward(&mut engine, "'aa'.matchAll(/b/).length"),
String::from("0")
);
assert_eq!(
forward(&mut engine, "'aa'.matchAll(/a/).length"),
String::from("1")
);
assert_eq!(
forward(&mut engine, "'aa'.matchAll(/a/g).length"),
String::from("2")
);
forward(
&mut engine,
"var groupMatches = 'test1test2'.matchAll(/t(e)(st(\\d?))/g)",
);
assert_eq!(
forward(&mut engine, "groupMatches.length"),
String::from("2")
);
assert_eq!(
forward(&mut engine, "groupMatches[0][1]"),
String::from("e")
);
assert_eq!(
forward(&mut engine, "groupMatches[0][2]"),
String::from("st1")
);
assert_eq!(
forward(&mut engine, "groupMatches[0][3]"),
String::from("1")
);
assert_eq!(
forward(&mut engine, "groupMatches[1][3]"),
String::from("2")
);
assert_eq!(
forward(
&mut engine,
"'test1test2'.matchAll(/t(e)(st(\\d?))/).length"
),
String::from("1")
);
let init = r#"
var regexp = RegExp('foo[a-z]*','g');
var str = 'table football, foosball';
var matches = str.matchAll(regexp);
"#;
forward(&mut engine, init);
assert_eq!(
forward(&mut engine, "matches[0][0]"),
String::from("football")
);
assert_eq!(forward(&mut engine, "matches[0].index"), String::from("6"));
assert_eq!(
forward(&mut engine, "matches[1][0]"),
String::from("foosball")
);
assert_eq!(forward(&mut engine, "matches[1].index"), String::from("16"));
}
#[test]
fn test_match() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var str = new String('The Quick Brown Fox Jumps Over The Lazy Dog');
var result1 = str.match(/quick\s(brown).+?(jumps)/i);
var result2 = str.match(/[A-Z]/g);
var result3 = str.match("T");
var result4 = str.match(RegExp("B", 'g'));
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "result1[0]"), "Quick Brown Fox Jumps");
assert_eq!(forward(&mut engine, "result1[1]"), "Brown");
assert_eq!(forward(&mut engine, "result1[2]"), "Jumps");
assert_eq!(forward(&mut engine, "result1.index"), "4");
assert_eq!(
forward(&mut engine, "result1.input"),
"The Quick Brown Fox Jumps Over The Lazy Dog"
);
assert_eq!(forward(&mut engine, "result2[0]"), "T");
assert_eq!(forward(&mut engine, "result2[1]"), "Q");
assert_eq!(forward(&mut engine, "result2[2]"), "B");
assert_eq!(forward(&mut engine, "result2[3]"), "F");
assert_eq!(forward(&mut engine, "result2[4]"), "J");
assert_eq!(forward(&mut engine, "result2[5]"), "O");
assert_eq!(forward(&mut engine, "result2[6]"), "T");
assert_eq!(forward(&mut engine, "result2[7]"), "L");
assert_eq!(forward(&mut engine, "result2[8]"), "D");
assert_eq!(forward(&mut engine, "result3[0]"), "T");
assert_eq!(forward(&mut engine, "result3.index"), "0");
assert_eq!(
forward(&mut engine, "result3.input"),
"The Quick Brown Fox Jumps Over The Lazy Dog"
);
assert_eq!(forward(&mut engine, "result4[0]"), "B");
}
}

305
boa/src/builtins/string/tests.rs

@ -0,0 +1,305 @@
use super::*;
use crate::exec::Executor;
use crate::realm::Realm;
use crate::{forward, forward_val};
#[test]
fn check_string_constructor_is_function() {
let global = ValueData::new_obj(None);
let string_constructor = create_constructor(&global);
assert_eq!(string_constructor.is_function(), true);
}
#[test]
// TODO: re-enable when getProperty() is finished;
// fn length() {
// //TEST262: https://github.com/tc39/test262/blob/master/test/built-ins/String/length.js
// let mut engine = Executor::new();
// let init = r#"
// const a = new String(' ');
// const b = new String('\ud834\udf06');
// const c = new String(' \b ');
// cosnt d = new String('中文长度')
// "#;
// forward(&mut engine, init);
// let a = forward(&mut engine, "a.length");
// assert_eq!(a, String::from("1"));
// let b = forward(&mut engine, "b.length");
// // TODO: fix this
// // unicode surrogate pair length should be 1
// // utf16/usc2 length should be 2
// // utf8 length should be 4
// //assert_eq!(b, String::from("2"));
// let c = forward(&mut engine, "c.length");
// assert_eq!(c, String::from("3"));
// let d = forward(&mut engine, "d.length");
// assert_eq!(d, String::from("4"));
// }
#[test]
fn concat() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var hello = new String('Hello, ');
var world = new String('world! ');
var nice = new String('Have a nice day.');
"#;
forward(&mut engine, init);
// Todo: fix this
let _a = forward(&mut engine, "hello.concat(world, nice)");
let _b = forward(&mut engine, "hello + world + nice");
// assert_eq!(a, String::from("Hello, world! Have a nice day."));
// assert_eq!(b, String::from("Hello, world! Have a nice day."));
}
#[allow(clippy::result_unwrap_used)]
#[test]
/// Test the correct type is returned from call and construct
fn construct_and_call() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var hello = new String('Hello');
var world = String('world');
"#;
forward(&mut engine, init);
let hello = forward_val(&mut engine, "hello").unwrap();
let world = forward_val(&mut engine, "world").unwrap();
assert_eq!(hello.is_object(), true);
assert_eq!(world.is_string(), true);
}
#[test]
fn repeat() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = new String('');
var en = new String('english');
var zh = new String('');
"#;
forward(&mut engine, init);
let empty = String::from("");
assert_eq!(forward(&mut engine, "empty.repeat(0)"), empty);
assert_eq!(forward(&mut engine, "empty.repeat(1)"), empty);
assert_eq!(forward(&mut engine, "en.repeat(0)"), empty);
assert_eq!(forward(&mut engine, "zh.repeat(0)"), empty);
assert_eq!(
forward(&mut engine, "en.repeat(1)"),
String::from("english")
);
assert_eq!(
forward(&mut engine, "zh.repeat(2)"),
String::from("中文中文")
);
}
#[test]
fn replace() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var a = "abc";
a = a.replace("a", "2");
a
"#;
forward(&mut engine, init);
let empty = String::from("2bc");
assert_eq!(forward(&mut engine, "a"), empty);
}
#[test]
fn replace_with_function() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var a = "ecmascript is cool";
var p1, p2, p3;
var replacer = (match, cap1, cap2, cap3) => {
p1 = cap1;
p2 = cap2;
p3 = cap3;
return "awesome!";
};
a = a.replace(/c(o)(o)(l)/, replacer);
a;
"#;
forward(&mut engine, init);
assert_eq!(
forward(&mut engine, "a"),
String::from("ecmascript is awesome!")
);
assert_eq!(forward(&mut engine, "p1"), String::from("o"));
assert_eq!(forward(&mut engine, "p2"), String::from("o"));
assert_eq!(forward(&mut engine, "p3"), String::from("l"));
}
#[test]
fn starts_with() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = new String('');
var en = new String('english');
var zh = new String('');
var emptyLiteral = '';
var enLiteral = 'english';
var zhLiteral = '';
"#;
forward(&mut engine, init);
let pass = String::from("true");
assert_eq!(forward(&mut engine, "empty.startsWith('')"), pass);
assert_eq!(forward(&mut engine, "en.startsWith('e')"), pass);
assert_eq!(forward(&mut engine, "zh.startsWith('中')"), pass);
assert_eq!(forward(&mut engine, "emptyLiteral.startsWith('')"), pass);
assert_eq!(forward(&mut engine, "enLiteral.startsWith('e')"), pass);
assert_eq!(forward(&mut engine, "zhLiteral.startsWith('中')"), pass);
}
#[test]
fn ends_with() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var empty = new String('');
var en = new String('english');
var zh = new String('');
var emptyLiteral = '';
var enLiteral = 'english';
var zhLiteral = '';
"#;
forward(&mut engine, init);
let pass = String::from("true");
assert_eq!(forward(&mut engine, "empty.endsWith('')"), pass);
assert_eq!(forward(&mut engine, "en.endsWith('h')"), pass);
assert_eq!(forward(&mut engine, "zh.endsWith('文')"), pass);
assert_eq!(forward(&mut engine, "emptyLiteral.endsWith('')"), pass);
assert_eq!(forward(&mut engine, "enLiteral.endsWith('h')"), pass);
assert_eq!(forward(&mut engine, "zhLiteral.endsWith('文')"), pass);
}
#[test]
fn match_all() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
assert_eq!(
forward(&mut engine, "'aa'.matchAll(null).length"),
String::from("0")
);
assert_eq!(
forward(&mut engine, "'aa'.matchAll(/b/).length"),
String::from("0")
);
assert_eq!(
forward(&mut engine, "'aa'.matchAll(/a/).length"),
String::from("1")
);
assert_eq!(
forward(&mut engine, "'aa'.matchAll(/a/g).length"),
String::from("2")
);
forward(
&mut engine,
"var groupMatches = 'test1test2'.matchAll(/t(e)(st(\\d?))/g)",
);
assert_eq!(
forward(&mut engine, "groupMatches.length"),
String::from("2")
);
assert_eq!(
forward(&mut engine, "groupMatches[0][1]"),
String::from("e")
);
assert_eq!(
forward(&mut engine, "groupMatches[0][2]"),
String::from("st1")
);
assert_eq!(
forward(&mut engine, "groupMatches[0][3]"),
String::from("1")
);
assert_eq!(
forward(&mut engine, "groupMatches[1][3]"),
String::from("2")
);
assert_eq!(
forward(
&mut engine,
"'test1test2'.matchAll(/t(e)(st(\\d?))/).length"
),
String::from("1")
);
let init = r#"
var regexp = RegExp('foo[a-z]*','g');
var str = 'table football, foosball';
var matches = str.matchAll(regexp);
"#;
forward(&mut engine, init);
assert_eq!(
forward(&mut engine, "matches[0][0]"),
String::from("football")
);
assert_eq!(forward(&mut engine, "matches[0].index"), String::from("6"));
assert_eq!(
forward(&mut engine, "matches[1][0]"),
String::from("foosball")
);
assert_eq!(forward(&mut engine, "matches[1].index"), String::from("16"));
}
#[test]
fn test_match() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let init = r#"
var str = new String('The Quick Brown Fox Jumps Over The Lazy Dog');
var result1 = str.match(/quick\s(brown).+?(jumps)/i);
var result2 = str.match(/[A-Z]/g);
var result3 = str.match("T");
var result4 = str.match(RegExp("B", 'g'));
"#;
forward(&mut engine, init);
assert_eq!(forward(&mut engine, "result1[0]"), "Quick Brown Fox Jumps");
assert_eq!(forward(&mut engine, "result1[1]"), "Brown");
assert_eq!(forward(&mut engine, "result1[2]"), "Jumps");
assert_eq!(forward(&mut engine, "result1.index"), "4");
assert_eq!(
forward(&mut engine, "result1.input"),
"The Quick Brown Fox Jumps Over The Lazy Dog"
);
assert_eq!(forward(&mut engine, "result2[0]"), "T");
assert_eq!(forward(&mut engine, "result2[1]"), "Q");
assert_eq!(forward(&mut engine, "result2[2]"), "B");
assert_eq!(forward(&mut engine, "result2[3]"), "F");
assert_eq!(forward(&mut engine, "result2[4]"), "J");
assert_eq!(forward(&mut engine, "result2[5]"), "O");
assert_eq!(forward(&mut engine, "result2[6]"), "T");
assert_eq!(forward(&mut engine, "result2[7]"), "L");
assert_eq!(forward(&mut engine, "result2[8]"), "D");
assert_eq!(forward(&mut engine, "result3[0]"), "T");
assert_eq!(forward(&mut engine, "result3.index"), "0");
assert_eq!(
forward(&mut engine, "result3.input"),
"The Quick Brown Fox Jumps Over The Lazy Dog"
);
assert_eq!(forward(&mut engine, "result4[0]"), "B");
}

42
boa/src/builtins/symbol.rs → boa/src/builtins/symbol/mod.rs

@ -1,3 +1,6 @@
#[cfg(test)]
mod tests;
use crate::{ use crate::{
builtins::{ builtins::{
object::{ object::{
@ -75,42 +78,3 @@ pub fn create_constructor(global: &Value) -> Value {
symbol_constructor_value symbol_constructor_value
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::exec::Executor;
use crate::realm::Realm;
use crate::{forward, forward_val};
#[test]
fn check_symbol_constructor_is_function() {
let global: Gc<ValueData> = 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)");
}
}

35
boa/src/builtins/symbol/tests.rs

@ -0,0 +1,35 @@
use super::*;
use crate::exec::Executor;
use crate::realm::Realm;
use crate::{forward, forward_val};
#[test]
fn check_symbol_constructor_is_function() {
let global: Gc<ValueData> = 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)");
}

55
boa/src/builtins/value.rs → boa/src/builtins/value/mod.rs

@ -1,3 +1,6 @@
#[cfg(test)]
mod tests;
use crate::builtins::{ use crate::builtins::{
function::{Function, NativeFunction, NativeFunctionData}, function::{Function, NativeFunction, NativeFunctionData},
object::{ object::{
@ -1235,55 +1238,3 @@ pub fn same_value_non_number(x: &Value, y: &Value) -> bool {
_ => false, _ => false,
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_is_object() {
let val = ValueData::new_obj(None);
assert_eq!(val.is_object(), true);
}
#[test]
fn check_string_to_value() {
let s = String::from("Hello");
let v = s.to_value();
assert_eq!(v.is_string(), true);
assert_eq!(v.is_null(), false);
}
#[test]
fn check_undefined() {
let u = ValueData::Undefined;
assert_eq!(u.get_type(), "undefined");
assert_eq!(u.to_string(), "undefined");
}
#[test]
fn check_get_set_field() {
let obj = ValueData::new_obj(None);
// Create string and convert it to a Value
let s = String::from("bar").to_value();
obj.set_field_slice("foo", s);
assert_eq!(obj.get_field_slice("foo").to_string(), "bar");
}
#[test]
fn check_integer_is_true() {
assert_eq!(1.to_value().is_true(), true);
assert_eq!(0.to_value().is_true(), false);
assert_eq!((-1).to_value().is_true(), true);
}
#[test]
fn check_number_is_true() {
assert_eq!(1.0.to_value().is_true(), true);
assert_eq!(0.1.to_value().is_true(), true);
assert_eq!(0.0.to_value().is_true(), false);
assert_eq!((-0.0).to_value().is_true(), false);
assert_eq!((-1.0).to_value().is_true(), true);
assert_eq!(NAN.to_value().is_true(), false);
}
}

48
boa/src/builtins/value/tests.rs

@ -0,0 +1,48 @@
use super::*;
#[test]
fn check_is_object() {
let val = ValueData::new_obj(None);
assert_eq!(val.is_object(), true);
}
#[test]
fn check_string_to_value() {
let s = String::from("Hello");
let v = s.to_value();
assert_eq!(v.is_string(), true);
assert_eq!(v.is_null(), false);
}
#[test]
fn check_undefined() {
let u = ValueData::Undefined;
assert_eq!(u.get_type(), "undefined");
assert_eq!(u.to_string(), "undefined");
}
#[test]
fn check_get_set_field() {
let obj = ValueData::new_obj(None);
// Create string and convert it to a Value
let s = String::from("bar").to_value();
obj.set_field_slice("foo", s);
assert_eq!(obj.get_field_slice("foo").to_string(), "bar");
}
#[test]
fn check_integer_is_true() {
assert_eq!(1.to_value().is_true(), true);
assert_eq!(0.to_value().is_true(), false);
assert_eq!((-1).to_value().is_true(), true);
}
#[test]
fn check_number_is_true() {
assert_eq!(1.0.to_value().is_true(), true);
assert_eq!(0.1.to_value().is_true(), true);
assert_eq!(0.0.to_value().is_true(), false);
assert_eq!((-0.0).to_value().is_true(), false);
assert_eq!((-1.0).to_value().is_true(), true);
assert_eq!(NAN.to_value().is_true(), false);
}

243
boa/src/exec.rs → boa/src/exec/mod.rs

@ -1,3 +1,6 @@
#[cfg(test)]
mod tests;
use crate::{ use crate::{
builtins::{ builtins::{
array, array,
@ -781,243 +784,3 @@ impl Interpreter {
Err(()) Err(())
} }
} }
#[cfg(test)]
mod tests {
use crate::exec;
use crate::exec::Executor;
use crate::forward;
use crate::realm::Realm;
#[test]
fn empty_let_decl_undefined() {
let scenario = r#"
let a;
a == undefined;
"#;
let pass = String::from("true");
assert_eq!(exec(scenario), pass);
}
#[test]
fn empty_var_decl_undefined() {
let scenario = r#"
let b;
b == undefined;
"#;
let pass = String::from("true");
assert_eq!(exec(scenario), pass);
}
#[test]
fn object_field_set() {
let scenario = r#"
let m = {};
m['key'] = 22;
m['key']
"#;
assert_eq!(exec(scenario), String::from("22"));
}
#[test]
fn spread_with_arguments() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let scenario = r#"
const a = [1, "test", 3, 4];
function foo(...a) {
return arguments;
}
var result = foo(...a);
"#;
forward(&mut engine, scenario);
let one = forward(&mut engine, "result[0]");
assert_eq!(one, String::from("1"));
let two = forward(&mut engine, "result[1]");
assert_eq!(two, String::from("test"));
let three = forward(&mut engine, "result[2]");
assert_eq!(three, String::from("3"));
let four = forward(&mut engine, "result[3]");
assert_eq!(four, String::from("4"));
}
#[test]
fn array_rest_with_arguments() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let scenario = r#"
var b = [4, 5, 6]
var a = [1, 2, 3, ...b];
"#;
forward(&mut engine, scenario);
let one = forward(&mut engine, "a");
assert_eq!(one, String::from("[ 1, 2, 3, 4, 5, 6 ]"));
}
#[test]
fn array_field_set() {
let element_changes = r#"
let m = [1, 2, 3];
m[1] = 5;
m[1]
"#;
assert_eq!(exec(element_changes), String::from("5"));
let length_changes = r#"
let m = [1, 2, 3];
m[10] = 52;
m.length
"#;
assert_eq!(exec(length_changes), String::from("11"));
let negative_index_wont_affect_length = r#"
let m = [1, 2, 3];
m[-11] = 5;
m.length
"#;
assert_eq!(exec(negative_index_wont_affect_length), String::from("3"));
let non_num_key_wont_affect_length = r#"
let m = [1, 2, 3];
m["magic"] = 5;
m.length
"#;
assert_eq!(exec(non_num_key_wont_affect_length), String::from("3"));
}
#[test]
fn test_tilde_operator() {
let float = r#"
let f = -1.2;
~f
"#;
assert_eq!(exec(float), String::from("0"));
let numeric = r#"
let f = 1789;
~f
"#;
assert_eq!(exec(numeric), String::from("-1790"));
// TODO: enable test after we have NaN
// let nan = r#"
// var m = NaN;
// ~m
// "#;
// assert_eq!(exec(nan), String::from("-1"));
let object = r#"
let m = {};
~m
"#;
assert_eq!(exec(object), String::from("-1"));
let boolean_true = r#"
~true
"#;
assert_eq!(exec(boolean_true), String::from("-2"));
let boolean_false = r#"
~false
"#;
assert_eq!(exec(boolean_false), String::from("-1"));
}
#[test]
fn test_early_return() {
let early_return = r#"
function early_return() {
if (true) {
return true;
}
return false;
}
early_return()
"#;
assert_eq!(exec(early_return), String::from("true"));
let early_return = r#"
function nested_fnct() {
return "nested";
}
function outer_fnct() {
nested_fnct();
return "outer";
}
outer_fnct()
"#;
assert_eq!(exec(early_return), String::from("outer"));
}
#[test]
fn test_short_circuit_evaluation() {
// OR operation
assert_eq!(exec("true || true"), String::from("true"));
assert_eq!(exec("true || false"), String::from("true"));
assert_eq!(exec("false || true"), String::from("true"));
assert_eq!(exec("false || false"), String::from("false"));
// the second operand must NOT be evaluated if the first one resolve to `true`.
let short_circuit_eval = r#"
function add_one(counter) {
counter.value += 1;
return true;
}
let counter = { value: 0 };
let _ = add_one(counter) || add_one(counter);
counter.value
"#;
assert_eq!(exec(short_circuit_eval), String::from("1"));
// the second operand must be evaluated if the first one resolve to `false`.
let short_circuit_eval = r#"
function add_one(counter) {
counter.value += 1;
return false;
}
let counter = { value: 0 };
let _ = add_one(counter) || add_one(counter);
counter.value
"#;
assert_eq!(exec(short_circuit_eval), String::from("2"));
// AND operation
assert_eq!(exec("true && true"), String::from("true"));
assert_eq!(exec("true && false"), String::from("false"));
assert_eq!(exec("false && true"), String::from("false"));
assert_eq!(exec("false && false"), String::from("false"));
// the second operand must be evaluated if the first one resolve to `true`.
let short_circuit_eval = r#"
function add_one(counter) {
counter.value += 1;
return true;
}
let counter = { value: 0 };
let _ = add_one(counter) && add_one(counter);
counter.value
"#;
assert_eq!(exec(short_circuit_eval), String::from("2"));
// the second operand must NOT be evaluated if the first one resolve to `false`.
let short_circuit_eval = r#"
function add_one(counter) {
counter.value += 1;
return false;
}
let counter = { value: 0 };
let _ = add_one(counter) && add_one(counter);
counter.value
"#;
assert_eq!(exec(short_circuit_eval), String::from("1"));
}
}

236
boa/src/exec/tests.rs

@ -0,0 +1,236 @@
use crate::exec;
use crate::exec::Executor;
use crate::forward;
use crate::realm::Realm;
#[test]
fn empty_let_decl_undefined() {
let scenario = r#"
let a;
a == undefined;
"#;
let pass = String::from("true");
assert_eq!(exec(scenario), pass);
}
#[test]
fn empty_var_decl_undefined() {
let scenario = r#"
let b;
b == undefined;
"#;
let pass = String::from("true");
assert_eq!(exec(scenario), pass);
}
#[test]
fn object_field_set() {
let scenario = r#"
let m = {};
m['key'] = 22;
m['key']
"#;
assert_eq!(exec(scenario), String::from("22"));
}
#[test]
fn spread_with_arguments() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let scenario = r#"
const a = [1, "test", 3, 4];
function foo(...a) {
return arguments;
}
var result = foo(...a);
"#;
forward(&mut engine, scenario);
let one = forward(&mut engine, "result[0]");
assert_eq!(one, String::from("1"));
let two = forward(&mut engine, "result[1]");
assert_eq!(two, String::from("test"));
let three = forward(&mut engine, "result[2]");
assert_eq!(three, String::from("3"));
let four = forward(&mut engine, "result[3]");
assert_eq!(four, String::from("4"));
}
#[test]
fn array_rest_with_arguments() {
let realm = Realm::create();
let mut engine = Executor::new(realm);
let scenario = r#"
var b = [4, 5, 6]
var a = [1, 2, 3, ...b];
"#;
forward(&mut engine, scenario);
let one = forward(&mut engine, "a");
assert_eq!(one, String::from("[ 1, 2, 3, 4, 5, 6 ]"));
}
#[test]
fn array_field_set() {
let element_changes = r#"
let m = [1, 2, 3];
m[1] = 5;
m[1]
"#;
assert_eq!(exec(element_changes), String::from("5"));
let length_changes = r#"
let m = [1, 2, 3];
m[10] = 52;
m.length
"#;
assert_eq!(exec(length_changes), String::from("11"));
let negative_index_wont_affect_length = r#"
let m = [1, 2, 3];
m[-11] = 5;
m.length
"#;
assert_eq!(exec(negative_index_wont_affect_length), String::from("3"));
let non_num_key_wont_affect_length = r#"
let m = [1, 2, 3];
m["magic"] = 5;
m.length
"#;
assert_eq!(exec(non_num_key_wont_affect_length), String::from("3"));
}
#[test]
fn test_tilde_operator() {
let float = r#"
let f = -1.2;
~f
"#;
assert_eq!(exec(float), String::from("0"));
let numeric = r#"
let f = 1789;
~f
"#;
assert_eq!(exec(numeric), String::from("-1790"));
// TODO: enable test after we have NaN
// let nan = r#"
// var m = NaN;
// ~m
// "#;
// assert_eq!(exec(nan), String::from("-1"));
let object = r#"
let m = {};
~m
"#;
assert_eq!(exec(object), String::from("-1"));
let boolean_true = r#"
~true
"#;
assert_eq!(exec(boolean_true), String::from("-2"));
let boolean_false = r#"
~false
"#;
assert_eq!(exec(boolean_false), String::from("-1"));
}
#[test]
fn test_early_return() {
let early_return = r#"
function early_return() {
if (true) {
return true;
}
return false;
}
early_return()
"#;
assert_eq!(exec(early_return), String::from("true"));
let early_return = r#"
function nested_fnct() {
return "nested";
}
function outer_fnct() {
nested_fnct();
return "outer";
}
outer_fnct()
"#;
assert_eq!(exec(early_return), String::from("outer"));
}
#[test]
fn test_short_circuit_evaluation() {
// OR operation
assert_eq!(exec("true || true"), String::from("true"));
assert_eq!(exec("true || false"), String::from("true"));
assert_eq!(exec("false || true"), String::from("true"));
assert_eq!(exec("false || false"), String::from("false"));
// the second operand must NOT be evaluated if the first one resolve to `true`.
let short_circuit_eval = r#"
function add_one(counter) {
counter.value += 1;
return true;
}
let counter = { value: 0 };
let _ = add_one(counter) || add_one(counter);
counter.value
"#;
assert_eq!(exec(short_circuit_eval), String::from("1"));
// the second operand must be evaluated if the first one resolve to `false`.
let short_circuit_eval = r#"
function add_one(counter) {
counter.value += 1;
return false;
}
let counter = { value: 0 };
let _ = add_one(counter) || add_one(counter);
counter.value
"#;
assert_eq!(exec(short_circuit_eval), String::from("2"));
// AND operation
assert_eq!(exec("true && true"), String::from("true"));
assert_eq!(exec("true && false"), String::from("false"));
assert_eq!(exec("false && true"), String::from("false"));
assert_eq!(exec("false && false"), String::from("false"));
// the second operand must be evaluated if the first one resolve to `true`.
let short_circuit_eval = r#"
function add_one(counter) {
counter.value += 1;
return true;
}
let counter = { value: 0 };
let _ = add_one(counter) && add_one(counter);
counter.value
"#;
assert_eq!(exec(short_circuit_eval), String::from("2"));
// the second operand must NOT be evaluated if the first one resolve to `false`.
let short_circuit_eval = r#"
function add_one(counter) {
counter.value += 1;
return false;
}
let counter = { value: 0 };
let _ = add_one(counter) && add_one(counter);
counter.value
"#;
assert_eq!(exec(short_circuit_eval), String::from("1"));
}

503
boa/src/syntax/lexer.rs → boa/src/syntax/lexer/mod.rs

@ -2,6 +2,10 @@
//! //!
//! The Lexer splits its input source code into a sequence of input elements called tokens, represented by the [Token](../ast/token/struct.Token.html) structure. //! The Lexer splits its input source code into a sequence of input elements called tokens, represented by the [Token](../ast/token/struct.Token.html) structure.
//! It also removes whitespace and comments and attaches them to the next token. //! It also removes whitespace and comments and attaches them to the next token.
#[cfg(test)]
mod tests;
use crate::syntax::ast::{ use crate::syntax::ast::{
punc::Punctuator, punc::Punctuator,
token::{Token, TokenData}, token::{Token, TokenData},
@ -692,502 +696,3 @@ impl<'a> Lexer<'a> {
} }
} }
} }
#[allow(clippy::indexing_slicing)]
#[cfg(test)]
mod tests {
use super::*;
use crate::syntax::ast::keyword::Keyword;
#[test]
fn check_single_line_comment() {
let s1 = "var \n//=\nx";
let mut lexer = Lexer::new(s1);
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::Keyword(Keyword::Var));
assert_eq!(lexer.tokens[1].data, TokenData::Comment("//=".to_owned()));
assert_eq!(lexer.tokens[2].data, TokenData::Identifier("x".to_string()));
}
#[test]
fn check_multi_line_comment() {
let s = "var /* await \n break \n*/ x";
let mut lexer = Lexer::new(s);
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::Keyword(Keyword::Var));
assert_eq!(
lexer.tokens[1].data,
TokenData::Comment("/* await \n break \n*/".to_owned())
);
assert_eq!(lexer.tokens[2].data, TokenData::Identifier("x".to_string()));
}
#[test]
fn check_string() {
let s = "'aaa' \"bbb\"";
let mut lexer = Lexer::new(s);
lexer.lex().expect("failed to lex");
assert_eq!(
lexer.tokens[0].data,
TokenData::StringLiteral("aaa".to_string())
);
assert_eq!(
lexer.tokens[1].data,
TokenData::StringLiteral("bbb".to_string())
);
}
#[test]
fn check_punctuators() {
// https://tc39.es/ecma262/#sec-punctuators
let s = "{ ( ) [ ] . ... ; , < > <= >= == != === !== \
+ - * % -- << >> >>> & | ^ ! ~ && || ? : \
= += -= *= &= **= ++ ** <<= >>= >>>= &= |= ^= =>";
let mut lexer = Lexer::new(s);
lexer.lex().expect("failed to lex");
assert_eq!(
lexer.tokens[0].data,
TokenData::Punctuator(Punctuator::OpenBlock)
);
assert_eq!(
lexer.tokens[1].data,
TokenData::Punctuator(Punctuator::OpenParen)
);
assert_eq!(
lexer.tokens[2].data,
TokenData::Punctuator(Punctuator::CloseParen)
);
assert_eq!(
lexer.tokens[3].data,
TokenData::Punctuator(Punctuator::OpenBracket)
);
assert_eq!(
lexer.tokens[4].data,
TokenData::Punctuator(Punctuator::CloseBracket)
);
assert_eq!(lexer.tokens[5].data, TokenData::Punctuator(Punctuator::Dot));
assert_eq!(
lexer.tokens[6].data,
TokenData::Punctuator(Punctuator::Spread)
);
assert_eq!(
lexer.tokens[7].data,
TokenData::Punctuator(Punctuator::Semicolon)
);
assert_eq!(
lexer.tokens[8].data,
TokenData::Punctuator(Punctuator::Comma)
);
assert_eq!(
lexer.tokens[9].data,
TokenData::Punctuator(Punctuator::LessThan)
);
assert_eq!(
lexer.tokens[10].data,
TokenData::Punctuator(Punctuator::GreaterThan)
);
assert_eq!(
lexer.tokens[11].data,
TokenData::Punctuator(Punctuator::LessThanOrEq)
);
assert_eq!(
lexer.tokens[12].data,
TokenData::Punctuator(Punctuator::GreaterThanOrEq)
);
assert_eq!(lexer.tokens[13].data, TokenData::Punctuator(Punctuator::Eq));
assert_eq!(
lexer.tokens[14].data,
TokenData::Punctuator(Punctuator::NotEq)
);
assert_eq!(
lexer.tokens[15].data,
TokenData::Punctuator(Punctuator::StrictEq)
);
assert_eq!(
lexer.tokens[16].data,
TokenData::Punctuator(Punctuator::StrictNotEq)
);
assert_eq!(
lexer.tokens[17].data,
TokenData::Punctuator(Punctuator::Add)
);
assert_eq!(
lexer.tokens[18].data,
TokenData::Punctuator(Punctuator::Sub)
);
assert_eq!(
lexer.tokens[19].data,
TokenData::Punctuator(Punctuator::Mul)
);
assert_eq!(
lexer.tokens[20].data,
TokenData::Punctuator(Punctuator::Mod)
);
assert_eq!(
lexer.tokens[21].data,
TokenData::Punctuator(Punctuator::Dec)
);
assert_eq!(
lexer.tokens[22].data,
TokenData::Punctuator(Punctuator::LeftSh)
);
assert_eq!(
lexer.tokens[23].data,
TokenData::Punctuator(Punctuator::RightSh)
);
assert_eq!(
lexer.tokens[24].data,
TokenData::Punctuator(Punctuator::URightSh)
);
assert_eq!(
lexer.tokens[25].data,
TokenData::Punctuator(Punctuator::And)
);
assert_eq!(lexer.tokens[26].data, TokenData::Punctuator(Punctuator::Or));
assert_eq!(
lexer.tokens[27].data,
TokenData::Punctuator(Punctuator::Xor)
);
assert_eq!(
lexer.tokens[28].data,
TokenData::Punctuator(Punctuator::Not)
);
assert_eq!(
lexer.tokens[29].data,
TokenData::Punctuator(Punctuator::Neg)
);
assert_eq!(
lexer.tokens[30].data,
TokenData::Punctuator(Punctuator::BoolAnd)
);
assert_eq!(
lexer.tokens[31].data,
TokenData::Punctuator(Punctuator::BoolOr)
);
assert_eq!(
lexer.tokens[32].data,
TokenData::Punctuator(Punctuator::Question)
);
assert_eq!(
lexer.tokens[33].data,
TokenData::Punctuator(Punctuator::Colon)
);
assert_eq!(
lexer.tokens[34].data,
TokenData::Punctuator(Punctuator::Assign)
);
assert_eq!(
lexer.tokens[35].data,
TokenData::Punctuator(Punctuator::AssignAdd)
);
assert_eq!(
lexer.tokens[36].data,
TokenData::Punctuator(Punctuator::AssignSub)
);
assert_eq!(
lexer.tokens[37].data,
TokenData::Punctuator(Punctuator::AssignMul)
);
assert_eq!(
lexer.tokens[38].data,
TokenData::Punctuator(Punctuator::AssignAnd)
);
assert_eq!(
lexer.tokens[39].data,
TokenData::Punctuator(Punctuator::AssignPow)
);
assert_eq!(
lexer.tokens[40].data,
TokenData::Punctuator(Punctuator::Inc)
);
assert_eq!(
lexer.tokens[41].data,
TokenData::Punctuator(Punctuator::Pow)
);
assert_eq!(
lexer.tokens[42].data,
TokenData::Punctuator(Punctuator::AssignLeftSh)
);
assert_eq!(
lexer.tokens[43].data,
TokenData::Punctuator(Punctuator::AssignRightSh)
);
assert_eq!(
lexer.tokens[44].data,
TokenData::Punctuator(Punctuator::AssignURightSh)
);
assert_eq!(
lexer.tokens[45].data,
TokenData::Punctuator(Punctuator::AssignAnd)
);
assert_eq!(
lexer.tokens[46].data,
TokenData::Punctuator(Punctuator::AssignOr)
);
assert_eq!(
lexer.tokens[47].data,
TokenData::Punctuator(Punctuator::AssignXor)
);
assert_eq!(
lexer.tokens[48].data,
TokenData::Punctuator(Punctuator::Arrow)
);
}
#[test]
fn check_keywords() {
// https://tc39.es/ecma262/#sec-keywords
let s = "await break case catch class const continue debugger default delete \
do else export extends finally for function if import in instanceof \
new return super switch this throw try typeof var void while with yield";
let mut lexer = Lexer::new(s);
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::Keyword(Keyword::Await));
assert_eq!(lexer.tokens[1].data, TokenData::Keyword(Keyword::Break));
assert_eq!(lexer.tokens[2].data, TokenData::Keyword(Keyword::Case));
assert_eq!(lexer.tokens[3].data, TokenData::Keyword(Keyword::Catch));
assert_eq!(lexer.tokens[4].data, TokenData::Keyword(Keyword::Class));
assert_eq!(lexer.tokens[5].data, TokenData::Keyword(Keyword::Const));
assert_eq!(lexer.tokens[6].data, TokenData::Keyword(Keyword::Continue));
assert_eq!(lexer.tokens[7].data, TokenData::Keyword(Keyword::Debugger));
assert_eq!(lexer.tokens[8].data, TokenData::Keyword(Keyword::Default));
assert_eq!(lexer.tokens[9].data, TokenData::Keyword(Keyword::Delete));
assert_eq!(lexer.tokens[10].data, TokenData::Keyword(Keyword::Do));
assert_eq!(lexer.tokens[11].data, TokenData::Keyword(Keyword::Else));
assert_eq!(lexer.tokens[12].data, TokenData::Keyword(Keyword::Export));
assert_eq!(lexer.tokens[13].data, TokenData::Keyword(Keyword::Extends));
assert_eq!(lexer.tokens[14].data, TokenData::Keyword(Keyword::Finally));
assert_eq!(lexer.tokens[15].data, TokenData::Keyword(Keyword::For));
assert_eq!(lexer.tokens[16].data, TokenData::Keyword(Keyword::Function));
assert_eq!(lexer.tokens[17].data, TokenData::Keyword(Keyword::If));
assert_eq!(lexer.tokens[18].data, TokenData::Keyword(Keyword::Import));
assert_eq!(lexer.tokens[19].data, TokenData::Keyword(Keyword::In));
assert_eq!(
lexer.tokens[20].data,
TokenData::Keyword(Keyword::InstanceOf)
);
assert_eq!(lexer.tokens[21].data, TokenData::Keyword(Keyword::New));
assert_eq!(lexer.tokens[22].data, TokenData::Keyword(Keyword::Return));
assert_eq!(lexer.tokens[23].data, TokenData::Keyword(Keyword::Super));
assert_eq!(lexer.tokens[24].data, TokenData::Keyword(Keyword::Switch));
assert_eq!(lexer.tokens[25].data, TokenData::Keyword(Keyword::This));
assert_eq!(lexer.tokens[26].data, TokenData::Keyword(Keyword::Throw));
assert_eq!(lexer.tokens[27].data, TokenData::Keyword(Keyword::Try));
assert_eq!(lexer.tokens[28].data, TokenData::Keyword(Keyword::TypeOf));
assert_eq!(lexer.tokens[29].data, TokenData::Keyword(Keyword::Var));
assert_eq!(lexer.tokens[30].data, TokenData::Keyword(Keyword::Void));
assert_eq!(lexer.tokens[31].data, TokenData::Keyword(Keyword::While));
assert_eq!(lexer.tokens[32].data, TokenData::Keyword(Keyword::With));
assert_eq!(lexer.tokens[33].data, TokenData::Keyword(Keyword::Yield));
}
#[test]
fn check_variable_definition_tokens() {
let s = "let a = 'hello';";
let mut lexer = Lexer::new(s);
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::Keyword(Keyword::Let));
assert_eq!(lexer.tokens[1].data, TokenData::Identifier("a".to_string()));
assert_eq!(
lexer.tokens[2].data,
TokenData::Punctuator(Punctuator::Assign)
);
assert_eq!(
lexer.tokens[3].data,
TokenData::StringLiteral("hello".to_string())
);
}
#[test]
fn check_positions() {
let s = "console.log(\"hello world\"); // Test";
// ------123456789
let mut lexer = Lexer::new(s);
lexer.lex().expect("failed to lex");
// The first column is 1 (not zero indexed)
assert_eq!(lexer.tokens[0].pos.column_number, 1);
assert_eq!(lexer.tokens[0].pos.line_number, 1);
// Dot Token starts on column 8
assert_eq!(lexer.tokens[1].pos.column_number, 8);
assert_eq!(lexer.tokens[1].pos.line_number, 1);
// Log Token starts on column 9
assert_eq!(lexer.tokens[2].pos.column_number, 9);
assert_eq!(lexer.tokens[2].pos.line_number, 1);
// Open parenthesis token starts on column 12
assert_eq!(lexer.tokens[3].pos.column_number, 12);
assert_eq!(lexer.tokens[3].pos.line_number, 1);
// String token starts on column 13
assert_eq!(lexer.tokens[4].pos.column_number, 13);
assert_eq!(lexer.tokens[4].pos.line_number, 1);
// Close parenthesis token starts on column 26
assert_eq!(lexer.tokens[5].pos.column_number, 26);
assert_eq!(lexer.tokens[5].pos.line_number, 1);
// Semi Colon token starts on column 27
assert_eq!(lexer.tokens[6].pos.column_number, 27);
assert_eq!(lexer.tokens[6].pos.line_number, 1);
// Comment start on column 29
// Semi Colon token starts on column 27
assert_eq!(lexer.tokens[7].pos.column_number, 29);
assert_eq!(lexer.tokens[7].pos.line_number, 1);
}
#[test]
fn check_line_numbers() {
let s = "// Copyright (C) 2017 Ecma International. All rights reserved.\n\
// This code is governed by the BSD license found in the LICENSE file.\n\
/*---\n\
description: |\n \
Collection of assertion functions used throughout test262\n\
defines: [assert]\n\
---*/\n\n\n\
function assert(mustBeTrue, message) {";
let mut lexer = Lexer::new(s);
lexer.lex().expect("failed to lex");
// The first column is 1 (not zero indexed), first line is also 1
assert_eq!(lexer.tokens[0].pos.column_number, 1);
assert_eq!(lexer.tokens[0].pos.line_number, 1);
// Second comment starts on line 2
assert_eq!(lexer.tokens[1].pos.column_number, 1);
assert_eq!(lexer.tokens[1].pos.line_number, 2);
// Multiline comment starts on line 3
assert_eq!(lexer.tokens[2].pos.column_number, 1);
assert_eq!(lexer.tokens[2].pos.line_number, 3);
// Function Token is on line 10
assert_eq!(lexer.tokens[3].pos.column_number, 1);
assert_eq!(lexer.tokens[3].pos.line_number, 10);
}
// Increment/Decrement
#[test]
fn check_decrement_advances_lexer_2_places() {
// Here we want an example of decrementing an integer
let s = "let a = b--;";
let mut lexer = Lexer::new(s);
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[4].data, TokenData::Punctuator(Punctuator::Dec));
// Decrementing means adding 2 characters '--', the lexer should consume it as a single token
// and move the curser forward by 2, meaning the next token should be a semicolon
assert_eq!(
lexer.tokens[5].data,
TokenData::Punctuator(Punctuator::Semicolon)
);
}
#[test]
fn numbers() {
let mut lexer = Lexer::new(
"1 2 0x34 056 7.89 42. 5e3 5e+3 5e-3 0b10 0O123 0999 1.0e1 1.0e-1 1.0E1 1E1",
);
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0));
assert_eq!(lexer.tokens[1].data, TokenData::NumericLiteral(2.0));
assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(52.0));
assert_eq!(lexer.tokens[3].data, TokenData::NumericLiteral(46.0));
assert_eq!(lexer.tokens[4].data, TokenData::NumericLiteral(7.89));
assert_eq!(lexer.tokens[5].data, TokenData::NumericLiteral(42.0));
assert_eq!(lexer.tokens[6].data, TokenData::NumericLiteral(5000.0));
assert_eq!(lexer.tokens[7].data, TokenData::NumericLiteral(5000.0));
assert_eq!(lexer.tokens[8].data, TokenData::NumericLiteral(0.005));
assert_eq!(lexer.tokens[9].data, TokenData::NumericLiteral(2.0));
assert_eq!(lexer.tokens[10].data, TokenData::NumericLiteral(83.0));
assert_eq!(lexer.tokens[11].data, TokenData::NumericLiteral(999.0));
assert_eq!(lexer.tokens[12].data, TokenData::NumericLiteral(10.0));
assert_eq!(lexer.tokens[13].data, TokenData::NumericLiteral(0.1));
assert_eq!(lexer.tokens[14].data, TokenData::NumericLiteral(10.0));
assert_eq!(lexer.tokens[14].data, TokenData::NumericLiteral(10.0));
}
#[test]
fn test_single_number_without_semicolon() {
let mut lexer = Lexer::new("1");
lexer.lex().expect("failed to lex");
}
#[test]
fn test_number_followed_by_dot() {
let mut lexer = Lexer::new("1..");
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0));
assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Dot));
}
#[test]
fn test_regex_literal() {
let mut lexer = Lexer::new("/(?:)/");
lexer.lex().expect("failed to lex");
assert_eq!(
lexer.tokens[0].data,
TokenData::RegularExpressionLiteral("(?:)".to_string(), "".to_string())
);
}
#[test]
fn test_regex_literal_flags() {
let mut lexer = Lexer::new(r"/\/[^\/]*\/*/gmi");
lexer.lex().expect("failed to lex");
assert_eq!(
lexer.tokens[0].data,
TokenData::RegularExpressionLiteral("\\/[^\\/]*\\/*".to_string(), "gmi".to_string())
);
}
#[test]
fn test_addition_no_spaces() {
let mut lexer = Lexer::new("1+1");
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0));
assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add));
assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(1.0));
}
#[test]
fn test_addition_no_spaces_left_side() {
let mut lexer = Lexer::new("1+ 1");
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0));
assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add));
assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(1.0));
}
#[test]
fn test_addition_no_spaces_right_side() {
let mut lexer = Lexer::new("1 +1");
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0));
assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add));
assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(1.0));
}
#[test]
fn test_addition_no_spaces_e_number_left_side() {
let mut lexer = Lexer::new("1e2+ 1");
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(100.0));
assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add));
assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(1.0));
}
#[test]
fn test_addition_no_spaces_e_number_right_side() {
let mut lexer = Lexer::new("1 +1e3");
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0));
assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add));
assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(1000.0));
}
#[test]
fn test_addition_no_spaces_e_number() {
let mut lexer = Lexer::new("1e3+1e11");
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1000.0));
assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add));
assert_eq!(
lexer.tokens[2].data,
TokenData::NumericLiteral(100_000_000_000.0)
);
}
}

496
boa/src/syntax/lexer/tests.rs

@ -0,0 +1,496 @@
//! Tests for the lexer.
#![allow(clippy::indexing_slicing)]
use super::*;
use crate::syntax::ast::keyword::Keyword;
#[test]
fn check_single_line_comment() {
let s1 = "var \n//=\nx";
let mut lexer = Lexer::new(s1);
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::Keyword(Keyword::Var));
assert_eq!(lexer.tokens[1].data, TokenData::Comment("//=".to_owned()));
assert_eq!(lexer.tokens[2].data, TokenData::Identifier("x".to_string()));
}
#[test]
fn check_multi_line_comment() {
let s = "var /* await \n break \n*/ x";
let mut lexer = Lexer::new(s);
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::Keyword(Keyword::Var));
assert_eq!(
lexer.tokens[1].data,
TokenData::Comment("/* await \n break \n*/".to_owned())
);
assert_eq!(lexer.tokens[2].data, TokenData::Identifier("x".to_string()));
}
#[test]
fn check_string() {
let s = "'aaa' \"bbb\"";
let mut lexer = Lexer::new(s);
lexer.lex().expect("failed to lex");
assert_eq!(
lexer.tokens[0].data,
TokenData::StringLiteral("aaa".to_string())
);
assert_eq!(
lexer.tokens[1].data,
TokenData::StringLiteral("bbb".to_string())
);
}
#[test]
fn check_punctuators() {
// https://tc39.es/ecma262/#sec-punctuators
let s = "{ ( ) [ ] . ... ; , < > <= >= == != === !== \
+ - * % -- << >> >>> & | ^ ! ~ && || ? : \
= += -= *= &= **= ++ ** <<= >>= >>>= &= |= ^= =>";
let mut lexer = Lexer::new(s);
lexer.lex().expect("failed to lex");
assert_eq!(
lexer.tokens[0].data,
TokenData::Punctuator(Punctuator::OpenBlock)
);
assert_eq!(
lexer.tokens[1].data,
TokenData::Punctuator(Punctuator::OpenParen)
);
assert_eq!(
lexer.tokens[2].data,
TokenData::Punctuator(Punctuator::CloseParen)
);
assert_eq!(
lexer.tokens[3].data,
TokenData::Punctuator(Punctuator::OpenBracket)
);
assert_eq!(
lexer.tokens[4].data,
TokenData::Punctuator(Punctuator::CloseBracket)
);
assert_eq!(lexer.tokens[5].data, TokenData::Punctuator(Punctuator::Dot));
assert_eq!(
lexer.tokens[6].data,
TokenData::Punctuator(Punctuator::Spread)
);
assert_eq!(
lexer.tokens[7].data,
TokenData::Punctuator(Punctuator::Semicolon)
);
assert_eq!(
lexer.tokens[8].data,
TokenData::Punctuator(Punctuator::Comma)
);
assert_eq!(
lexer.tokens[9].data,
TokenData::Punctuator(Punctuator::LessThan)
);
assert_eq!(
lexer.tokens[10].data,
TokenData::Punctuator(Punctuator::GreaterThan)
);
assert_eq!(
lexer.tokens[11].data,
TokenData::Punctuator(Punctuator::LessThanOrEq)
);
assert_eq!(
lexer.tokens[12].data,
TokenData::Punctuator(Punctuator::GreaterThanOrEq)
);
assert_eq!(lexer.tokens[13].data, TokenData::Punctuator(Punctuator::Eq));
assert_eq!(
lexer.tokens[14].data,
TokenData::Punctuator(Punctuator::NotEq)
);
assert_eq!(
lexer.tokens[15].data,
TokenData::Punctuator(Punctuator::StrictEq)
);
assert_eq!(
lexer.tokens[16].data,
TokenData::Punctuator(Punctuator::StrictNotEq)
);
assert_eq!(
lexer.tokens[17].data,
TokenData::Punctuator(Punctuator::Add)
);
assert_eq!(
lexer.tokens[18].data,
TokenData::Punctuator(Punctuator::Sub)
);
assert_eq!(
lexer.tokens[19].data,
TokenData::Punctuator(Punctuator::Mul)
);
assert_eq!(
lexer.tokens[20].data,
TokenData::Punctuator(Punctuator::Mod)
);
assert_eq!(
lexer.tokens[21].data,
TokenData::Punctuator(Punctuator::Dec)
);
assert_eq!(
lexer.tokens[22].data,
TokenData::Punctuator(Punctuator::LeftSh)
);
assert_eq!(
lexer.tokens[23].data,
TokenData::Punctuator(Punctuator::RightSh)
);
assert_eq!(
lexer.tokens[24].data,
TokenData::Punctuator(Punctuator::URightSh)
);
assert_eq!(
lexer.tokens[25].data,
TokenData::Punctuator(Punctuator::And)
);
assert_eq!(lexer.tokens[26].data, TokenData::Punctuator(Punctuator::Or));
assert_eq!(
lexer.tokens[27].data,
TokenData::Punctuator(Punctuator::Xor)
);
assert_eq!(
lexer.tokens[28].data,
TokenData::Punctuator(Punctuator::Not)
);
assert_eq!(
lexer.tokens[29].data,
TokenData::Punctuator(Punctuator::Neg)
);
assert_eq!(
lexer.tokens[30].data,
TokenData::Punctuator(Punctuator::BoolAnd)
);
assert_eq!(
lexer.tokens[31].data,
TokenData::Punctuator(Punctuator::BoolOr)
);
assert_eq!(
lexer.tokens[32].data,
TokenData::Punctuator(Punctuator::Question)
);
assert_eq!(
lexer.tokens[33].data,
TokenData::Punctuator(Punctuator::Colon)
);
assert_eq!(
lexer.tokens[34].data,
TokenData::Punctuator(Punctuator::Assign)
);
assert_eq!(
lexer.tokens[35].data,
TokenData::Punctuator(Punctuator::AssignAdd)
);
assert_eq!(
lexer.tokens[36].data,
TokenData::Punctuator(Punctuator::AssignSub)
);
assert_eq!(
lexer.tokens[37].data,
TokenData::Punctuator(Punctuator::AssignMul)
);
assert_eq!(
lexer.tokens[38].data,
TokenData::Punctuator(Punctuator::AssignAnd)
);
assert_eq!(
lexer.tokens[39].data,
TokenData::Punctuator(Punctuator::AssignPow)
);
assert_eq!(
lexer.tokens[40].data,
TokenData::Punctuator(Punctuator::Inc)
);
assert_eq!(
lexer.tokens[41].data,
TokenData::Punctuator(Punctuator::Pow)
);
assert_eq!(
lexer.tokens[42].data,
TokenData::Punctuator(Punctuator::AssignLeftSh)
);
assert_eq!(
lexer.tokens[43].data,
TokenData::Punctuator(Punctuator::AssignRightSh)
);
assert_eq!(
lexer.tokens[44].data,
TokenData::Punctuator(Punctuator::AssignURightSh)
);
assert_eq!(
lexer.tokens[45].data,
TokenData::Punctuator(Punctuator::AssignAnd)
);
assert_eq!(
lexer.tokens[46].data,
TokenData::Punctuator(Punctuator::AssignOr)
);
assert_eq!(
lexer.tokens[47].data,
TokenData::Punctuator(Punctuator::AssignXor)
);
assert_eq!(
lexer.tokens[48].data,
TokenData::Punctuator(Punctuator::Arrow)
);
}
#[test]
fn check_keywords() {
// https://tc39.es/ecma262/#sec-keywords
let s = "await break case catch class const continue debugger default delete \
do else export extends finally for function if import in instanceof \
new return super switch this throw try typeof var void while with yield";
let mut lexer = Lexer::new(s);
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::Keyword(Keyword::Await));
assert_eq!(lexer.tokens[1].data, TokenData::Keyword(Keyword::Break));
assert_eq!(lexer.tokens[2].data, TokenData::Keyword(Keyword::Case));
assert_eq!(lexer.tokens[3].data, TokenData::Keyword(Keyword::Catch));
assert_eq!(lexer.tokens[4].data, TokenData::Keyword(Keyword::Class));
assert_eq!(lexer.tokens[5].data, TokenData::Keyword(Keyword::Const));
assert_eq!(lexer.tokens[6].data, TokenData::Keyword(Keyword::Continue));
assert_eq!(lexer.tokens[7].data, TokenData::Keyword(Keyword::Debugger));
assert_eq!(lexer.tokens[8].data, TokenData::Keyword(Keyword::Default));
assert_eq!(lexer.tokens[9].data, TokenData::Keyword(Keyword::Delete));
assert_eq!(lexer.tokens[10].data, TokenData::Keyword(Keyword::Do));
assert_eq!(lexer.tokens[11].data, TokenData::Keyword(Keyword::Else));
assert_eq!(lexer.tokens[12].data, TokenData::Keyword(Keyword::Export));
assert_eq!(lexer.tokens[13].data, TokenData::Keyword(Keyword::Extends));
assert_eq!(lexer.tokens[14].data, TokenData::Keyword(Keyword::Finally));
assert_eq!(lexer.tokens[15].data, TokenData::Keyword(Keyword::For));
assert_eq!(lexer.tokens[16].data, TokenData::Keyword(Keyword::Function));
assert_eq!(lexer.tokens[17].data, TokenData::Keyword(Keyword::If));
assert_eq!(lexer.tokens[18].data, TokenData::Keyword(Keyword::Import));
assert_eq!(lexer.tokens[19].data, TokenData::Keyword(Keyword::In));
assert_eq!(
lexer.tokens[20].data,
TokenData::Keyword(Keyword::InstanceOf)
);
assert_eq!(lexer.tokens[21].data, TokenData::Keyword(Keyword::New));
assert_eq!(lexer.tokens[22].data, TokenData::Keyword(Keyword::Return));
assert_eq!(lexer.tokens[23].data, TokenData::Keyword(Keyword::Super));
assert_eq!(lexer.tokens[24].data, TokenData::Keyword(Keyword::Switch));
assert_eq!(lexer.tokens[25].data, TokenData::Keyword(Keyword::This));
assert_eq!(lexer.tokens[26].data, TokenData::Keyword(Keyword::Throw));
assert_eq!(lexer.tokens[27].data, TokenData::Keyword(Keyword::Try));
assert_eq!(lexer.tokens[28].data, TokenData::Keyword(Keyword::TypeOf));
assert_eq!(lexer.tokens[29].data, TokenData::Keyword(Keyword::Var));
assert_eq!(lexer.tokens[30].data, TokenData::Keyword(Keyword::Void));
assert_eq!(lexer.tokens[31].data, TokenData::Keyword(Keyword::While));
assert_eq!(lexer.tokens[32].data, TokenData::Keyword(Keyword::With));
assert_eq!(lexer.tokens[33].data, TokenData::Keyword(Keyword::Yield));
}
#[test]
fn check_variable_definition_tokens() {
let s = "let a = 'hello';";
let mut lexer = Lexer::new(s);
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::Keyword(Keyword::Let));
assert_eq!(lexer.tokens[1].data, TokenData::Identifier("a".to_string()));
assert_eq!(
lexer.tokens[2].data,
TokenData::Punctuator(Punctuator::Assign)
);
assert_eq!(
lexer.tokens[3].data,
TokenData::StringLiteral("hello".to_string())
);
}
#[test]
fn check_positions() {
let s = "console.log(\"hello world\"); // Test";
// ------123456789
let mut lexer = Lexer::new(s);
lexer.lex().expect("failed to lex");
// The first column is 1 (not zero indexed)
assert_eq!(lexer.tokens[0].pos.column_number, 1);
assert_eq!(lexer.tokens[0].pos.line_number, 1);
// Dot Token starts on column 8
assert_eq!(lexer.tokens[1].pos.column_number, 8);
assert_eq!(lexer.tokens[1].pos.line_number, 1);
// Log Token starts on column 9
assert_eq!(lexer.tokens[2].pos.column_number, 9);
assert_eq!(lexer.tokens[2].pos.line_number, 1);
// Open parenthesis token starts on column 12
assert_eq!(lexer.tokens[3].pos.column_number, 12);
assert_eq!(lexer.tokens[3].pos.line_number, 1);
// String token starts on column 13
assert_eq!(lexer.tokens[4].pos.column_number, 13);
assert_eq!(lexer.tokens[4].pos.line_number, 1);
// Close parenthesis token starts on column 26
assert_eq!(lexer.tokens[5].pos.column_number, 26);
assert_eq!(lexer.tokens[5].pos.line_number, 1);
// Semi Colon token starts on column 27
assert_eq!(lexer.tokens[6].pos.column_number, 27);
assert_eq!(lexer.tokens[6].pos.line_number, 1);
// Comment start on column 29
// Semi Colon token starts on column 27
assert_eq!(lexer.tokens[7].pos.column_number, 29);
assert_eq!(lexer.tokens[7].pos.line_number, 1);
}
#[test]
fn check_line_numbers() {
let s = "// Copyright (C) 2017 Ecma International. All rights reserved.\n\
// This code is governed by the BSD license found in the LICENSE file.\n\
/*---\n\
description: |\n \
Collection of assertion functions used throughout test262\n\
defines: [assert]\n\
---*/\n\n\n\
function assert(mustBeTrue, message) {";
let mut lexer = Lexer::new(s);
lexer.lex().expect("failed to lex");
// The first column is 1 (not zero indexed), first line is also 1
assert_eq!(lexer.tokens[0].pos.column_number, 1);
assert_eq!(lexer.tokens[0].pos.line_number, 1);
// Second comment starts on line 2
assert_eq!(lexer.tokens[1].pos.column_number, 1);
assert_eq!(lexer.tokens[1].pos.line_number, 2);
// Multiline comment starts on line 3
assert_eq!(lexer.tokens[2].pos.column_number, 1);
assert_eq!(lexer.tokens[2].pos.line_number, 3);
// Function Token is on line 10
assert_eq!(lexer.tokens[3].pos.column_number, 1);
assert_eq!(lexer.tokens[3].pos.line_number, 10);
}
// Increment/Decrement
#[test]
fn check_decrement_advances_lexer_2_places() {
// Here we want an example of decrementing an integer
let s = "let a = b--;";
let mut lexer = Lexer::new(s);
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[4].data, TokenData::Punctuator(Punctuator::Dec));
// Decrementing means adding 2 characters '--', the lexer should consume it as a single token
// and move the curser forward by 2, meaning the next token should be a semicolon
assert_eq!(
lexer.tokens[5].data,
TokenData::Punctuator(Punctuator::Semicolon)
);
}
#[test]
fn numbers() {
let mut lexer =
Lexer::new("1 2 0x34 056 7.89 42. 5e3 5e+3 5e-3 0b10 0O123 0999 1.0e1 1.0e-1 1.0E1 1E1");
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0));
assert_eq!(lexer.tokens[1].data, TokenData::NumericLiteral(2.0));
assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(52.0));
assert_eq!(lexer.tokens[3].data, TokenData::NumericLiteral(46.0));
assert_eq!(lexer.tokens[4].data, TokenData::NumericLiteral(7.89));
assert_eq!(lexer.tokens[5].data, TokenData::NumericLiteral(42.0));
assert_eq!(lexer.tokens[6].data, TokenData::NumericLiteral(5000.0));
assert_eq!(lexer.tokens[7].data, TokenData::NumericLiteral(5000.0));
assert_eq!(lexer.tokens[8].data, TokenData::NumericLiteral(0.005));
assert_eq!(lexer.tokens[9].data, TokenData::NumericLiteral(2.0));
assert_eq!(lexer.tokens[10].data, TokenData::NumericLiteral(83.0));
assert_eq!(lexer.tokens[11].data, TokenData::NumericLiteral(999.0));
assert_eq!(lexer.tokens[12].data, TokenData::NumericLiteral(10.0));
assert_eq!(lexer.tokens[13].data, TokenData::NumericLiteral(0.1));
assert_eq!(lexer.tokens[14].data, TokenData::NumericLiteral(10.0));
assert_eq!(lexer.tokens[14].data, TokenData::NumericLiteral(10.0));
}
#[test]
fn test_single_number_without_semicolon() {
let mut lexer = Lexer::new("1");
lexer.lex().expect("failed to lex");
}
#[test]
fn test_number_followed_by_dot() {
let mut lexer = Lexer::new("1..");
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0));
assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Dot));
}
#[test]
fn test_regex_literal() {
let mut lexer = Lexer::new("/(?:)/");
lexer.lex().expect("failed to lex");
assert_eq!(
lexer.tokens[0].data,
TokenData::RegularExpressionLiteral("(?:)".to_string(), "".to_string())
);
}
#[test]
fn test_regex_literal_flags() {
let mut lexer = Lexer::new(r"/\/[^\/]*\/*/gmi");
lexer.lex().expect("failed to lex");
assert_eq!(
lexer.tokens[0].data,
TokenData::RegularExpressionLiteral("\\/[^\\/]*\\/*".to_string(), "gmi".to_string())
);
}
#[test]
fn test_addition_no_spaces() {
let mut lexer = Lexer::new("1+1");
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0));
assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add));
assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(1.0));
}
#[test]
fn test_addition_no_spaces_left_side() {
let mut lexer = Lexer::new("1+ 1");
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0));
assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add));
assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(1.0));
}
#[test]
fn test_addition_no_spaces_right_side() {
let mut lexer = Lexer::new("1 +1");
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0));
assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add));
assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(1.0));
}
#[test]
fn test_addition_no_spaces_e_number_left_side() {
let mut lexer = Lexer::new("1e2+ 1");
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(100.0));
assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add));
assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(1.0));
}
#[test]
fn test_addition_no_spaces_e_number_right_side() {
let mut lexer = Lexer::new("1 +1e3");
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1.0));
assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add));
assert_eq!(lexer.tokens[2].data, TokenData::NumericLiteral(1000.0));
}
#[test]
fn test_addition_no_spaces_e_number() {
let mut lexer = Lexer::new("1e3+1e11");
lexer.lex().expect("failed to lex");
assert_eq!(lexer.tokens[0].data, TokenData::NumericLiteral(1000.0));
assert_eq!(lexer.tokens[1].data, TokenData::Punctuator(Punctuator::Add));
assert_eq!(
lexer.tokens[2].data,
TokenData::NumericLiteral(100_000_000_000.0)
);
}

713
boa/src/syntax/parser.rs → boa/src/syntax/parser/mod.rs

@ -1,11 +1,15 @@
use crate::syntax::ast::constant::Const; #[cfg(test)]
use crate::syntax::ast::expr::{Expr, ExprDef}; mod tests;
use crate::syntax::ast::keyword::Keyword;
use crate::syntax::ast::op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, Operator, UnaryOp}; use crate::syntax::ast::{
use crate::syntax::ast::punc::Punctuator; constant::Const,
use crate::syntax::ast::token::{Token, TokenData}; expr::{Expr, ExprDef},
use std::collections::btree_map::BTreeMap; keyword::Keyword,
use std::fmt; op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, Operator, UnaryOp},
punc::Punctuator,
token::{Token, TokenData},
};
use std::{collections::btree_map::BTreeMap, fmt};
/// `ParseError` is an enum which represents errors encounted during parsing an expression /// `ParseError` is an enum which represents errors encounted during parsing an expression
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -904,696 +908,3 @@ impl Parser {
self.expect(TokenData::Punctuator(p), routine) self.expect(TokenData::Punctuator(p), routine)
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::syntax::ast::{constant::Const, op::BinOp};
use crate::syntax::{
ast::expr::{Expr, ExprDef},
lexer::Lexer,
};
fn create_bin_op(op: BinOp, exp1: Expr, exp2: Expr) -> Expr {
Expr::new(ExprDef::BinOp(op, Box::new(exp1), Box::new(exp2)))
}
#[allow(clippy::result_unwrap_used)]
fn check_parser(js: &str, expr: &[Expr]) {
let mut lexer = Lexer::new(js);
lexer.lex().expect("failed to lex");
assert_eq!(
Parser::new(lexer.tokens).parse_all().unwrap(),
Expr::new(ExprDef::Block(expr.into()))
);
}
fn check_invalid(js: &str) {
let mut lexer = Lexer::new(js);
lexer.lex().expect("failed to lex");
assert!(Parser::new(lexer.tokens).parse_all().is_err());
}
#[test]
fn check_string() {
use crate::syntax::ast::constant::Const;
// Check empty string
check_parser(
"\"\"",
&[Expr::new(ExprDef::Const(Const::String(String::new())))],
);
// Check non-empty string
check_parser(
"\"hello\"",
&[Expr::new(ExprDef::Const(Const::String(String::from(
"hello",
))))],
);
}
#[test]
fn check_object_short_function() {
// Testing short function syntax
let mut object_properties: BTreeMap<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"))),
))),
))]))),
))],
);
}
}

691
boa/src/syntax/parser/tests.rs

@ -0,0 +1,691 @@
//! Tests for the parser.
use super::*;
use crate::syntax::ast::{constant::Const, op::BinOp};
use crate::syntax::{
ast::expr::{Expr, ExprDef},
lexer::Lexer,
};
fn create_bin_op(op: BinOp, exp1: Expr, exp2: Expr) -> Expr {
Expr::new(ExprDef::BinOp(op, Box::new(exp1), Box::new(exp2)))
}
#[allow(clippy::result_unwrap_used)]
fn check_parser(js: &str, expr: &[Expr]) {
let mut lexer = Lexer::new(js);
lexer.lex().expect("failed to lex");
assert_eq!(
Parser::new(lexer.tokens).parse_all().unwrap(),
Expr::new(ExprDef::Block(expr.into()))
);
}
fn check_invalid(js: &str) {
let mut lexer = Lexer::new(js);
lexer.lex().expect("failed to lex");
assert!(Parser::new(lexer.tokens).parse_all().is_err());
}
#[test]
fn check_string() {
use crate::syntax::ast::constant::Const;
// Check empty string
check_parser(
"\"\"",
&[Expr::new(ExprDef::Const(Const::String(String::new())))],
);
// Check non-empty string
check_parser(
"\"hello\"",
&[Expr::new(ExprDef::Const(Const::String(String::from(
"hello",
))))],
);
}
#[test]
fn check_object_short_function() {
// Testing short function syntax
let mut object_properties: BTreeMap<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"))),
))),
))]))),
))],
);
}

2
boa_cli/Cargo.toml

@ -11,5 +11,5 @@ exclude = ["../.vscode/*", "../Dockerfile", "../Makefile", "../.editorConfig"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
Boa = { path = "../boa" } Boa = { path = "../boa", default-features = false }
structopt = "0.3.9" structopt = "0.3.9"

Loading…
Cancel
Save