Browse Source

Implement bare Array object with prototype field (#51)

* First draft of Array prototype object

* First draft of Array prototype object

* Update to working Array object

* Remove unneeded HashMap import

* Fix indexing by removing private internal HashMap

* Fix formatting issues with cargo fmt
pull/53/head
Callum Ward 5 years ago committed by Jason Williams
parent
commit
1455ef0dc6
  1. 10
      src/bin/bin.rs
  2. 2
      src/lib/environment/environment_record_trait.rs
  3. 49
      src/lib/js/array.rs
  4. 76
      src/lib/js/string.rs
  5. 2
      src/lib/syntax/ast/token.rs

10
src/bin/bin.rs

@ -1,14 +1,16 @@
extern crate boa; extern crate boa;
use boa::exec; use boa::exec;
use std::fs::read_to_string;
use std::env; use std::env;
use std::fs::read_to_string;
use std::process::exit; use std::process::exit;
fn print_usage() { fn print_usage() {
println!("Usage: println!(
"Usage:
boa [file.js] boa [file.js]
Interpret and execute file.js Interpret and execute file.js
(if no file given, defaults to tests/js/test.js"); (if no file given, defaults to tests/js/test.js"
);
} }
pub fn main() -> Result<(), std::io::Error> { pub fn main() -> Result<(), std::io::Error> {
@ -19,7 +21,7 @@ pub fn main() -> Result<(), std::io::Error> {
// No arguments passed, default to "test.js" // No arguments passed, default to "test.js"
1 => { 1 => {
read_file = "tests/js/test.js"; read_file = "tests/js/test.js";
}, }
// One argument passed, assumed this is the test file // One argument passed, assumed this is the test file
2 => { 2 => {
read_file = &args[1]; read_file = &args[1];

2
src/lib/environment/environment_record_trait.rs

@ -3,7 +3,7 @@
//! https://tc39.github.io/ecma262/#sec-environment-records //! https://tc39.github.io/ecma262/#sec-environment-records
//! https://tc39.github.io/ecma262/#sec-lexical-environments //! https://tc39.github.io/ecma262/#sec-lexical-environments
//! //!
//! Some environments are stored as JSObjects. This is for GC, i.e we want to keep an environment if a variable is closed-over (a closure is returned). //! Some environments are stored as JSObjects. This is for GC, i.e we want to keep an environment if a variable is closed-over (a closure is returned).
//! All of the logic to handle scope/environment records are stored in here. //! All of the logic to handle scope/environment records are stored in here.
//! //!
//! There are 5 Environment record kinds. They all have methods in common, these are implemented as a the `EnvironmentRecordTrait` //! There are 5 Environment record kinds. They all have methods in common, these are implemented as a the `EnvironmentRecordTrait`

49
src/lib/js/array.rs

@ -1,19 +1,56 @@
use crate::js::function::NativeFunctionData; use crate::js::function::NativeFunctionData;
use crate::js::value::{to_value, ResultValue, Value, ValueData}; use crate::js::object::{Property, PROTOTYPE};
use crate::js::value::{from_value, to_value, ResultValue, Value, ValueData};
use gc::Gc; use gc::Gc;
/// Create a new array /// Create a new array
pub fn make_array(this: Value, _: Value, _: Vec<Value>) -> ResultValue { pub fn make_array(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
let this_ptr = this.clone(); let this_ptr = this.clone();
this_ptr.set_field_slice("length", to_value(0i32)); // Make a new Object which will internally represent the Array (mapping
Ok(Gc::new(ValueData::Undefined)) // between indices and values): this creates an Object with no prototype
match args.len() {
0 => {
this_ptr.set_field_slice("length", to_value(0i32));
}
1 => {
let length_chosen: i32 = from_value(args[0].clone()).unwrap();
this_ptr.set_field_slice("length", to_value(length_chosen));
}
n => {
this_ptr.set_field_slice("length", to_value(n));
for k in 0..n {
let index_str = k.to_string();
this_ptr.set_field(index_str, args[k].clone());
}
}
}
Ok(this_ptr)
} }
/// Get an array's length
pub fn get_array_length(this: Value, _: Value, _: Vec<Value>) -> ResultValue {
// Access the inner hash map which represents the actual Array contents
// (mapping between indices and values)
Ok(this.get_field_slice("length"))
}
/// Create a new `Array` object /// Create a new `Array` object
pub fn _create() -> Value { pub fn _create(global: &Value) -> Value {
let array = to_value(make_array as NativeFunctionData); let array = to_value(make_array as NativeFunctionData);
let proto = ValueData::new_obj(Some(global));
let length = Property {
configurable: false,
enumerable: false,
writable: false,
value: Gc::new(ValueData::Undefined),
get: to_value(get_array_length as NativeFunctionData),
set: Gc::new(ValueData::Undefined),
};
proto.set_prop_slice("length", length);
array.set_field_slice(PROTOTYPE, proto);
array array
} }
/// Initialise the global object with the `Array` object /// Initialise the global object with the `Array` object
pub fn init(global: &Value) { pub fn init(global: &Value) {
global.set_field_slice("Array", _create()); global.set_field_slice("Array", _create(global));
} }

76
src/lib/js/string.rs

@ -2,8 +2,8 @@ use crate::js::function::NativeFunctionData;
use crate::js::object::{Property, PROTOTYPE}; use crate::js::object::{Property, PROTOTYPE};
use crate::js::value::{from_value, to_value, ResultValue, Value, ValueData}; use crate::js::value::{from_value, to_value, ResultValue, Value, ValueData};
use gc::Gc; use gc::Gc;
use std::cmp::{max, min};
use std::f64::NAN; use std::f64::NAN;
use std::cmp::{min, max};
/// Create new string /// Create new string
/// https://searchfox.org/mozilla-central/source/js/src/vm/StringObject.h#19 /// https://searchfox.org/mozilla-central/source/js/src/vm/StringObject.h#19
@ -82,7 +82,7 @@ pub fn char_code_at(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
/// arguments /// arguments
/// https://tc39.github.io/ecma262/#sec-string.prototype.concat /// https://tc39.github.io/ecma262/#sec-string.prototype.concat
pub fn concat(this: Value, _: Value, args: Vec<Value>) -> ResultValue { pub fn concat(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
// ^^ represents instance ^^ represents arguments // ^^ represents instance ^^ represents arguments
// First we get it the actual string a private field stored on the object only the engine has access to. // First we get it the actual string a private field stored on the object only the engine has access to.
// Then we convert it into a Rust String by wrapping it in from_value // Then we convert it into a Rust String by wrapping it in from_value
let primitive_val: String = let primitive_val: String =
@ -129,8 +129,16 @@ pub fn slice(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
// Note that this is an O(N) operation (because UTF-8 is complex) while getting the number of bytes is an O(1) operation. // Note that this is an O(N) operation (because UTF-8 is complex) while getting the number of bytes is an O(1) operation.
let length: i32 = primitive_val.chars().count() as i32; let length: i32 = primitive_val.chars().count() as i32;
let from: i32 = if start < 0 {max(length + start, 0)} else {min(start, length)}; let from: i32 = if start < 0 {
let to: i32 = if end < 0 {max(length + end, 0)} else {min(end, length)}; max(length + start, 0)
} else {
min(start, length)
};
let to: i32 = if end < 0 {
max(length + end, 0)
} else {
min(end, length)
};
let span = max(to - from, 0); let span = max(to - from, 0);
@ -159,8 +167,11 @@ pub fn starts_with(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
let search_length: i32 = search_string.chars().count() as i32; let search_length: i32 = search_string.chars().count() as i32;
// If less than 2 args specified, position is 'undefined', defaults to 0 // If less than 2 args specified, position is 'undefined', defaults to 0
let position: i32 = if args.len() < 2 {0} let position: i32 = if args.len() < 2 {
else {from_value(args[1].clone()).unwrap()}; 0
} else {
from_value(args[1].clone()).unwrap()
};
let start = min(max(position, 0), length); let start = min(max(position, 0), length);
let end = start + search_length; let end = start + search_length;
@ -169,8 +180,7 @@ pub fn starts_with(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
Ok(to_value(false)) Ok(to_value(false))
} else { } else {
// Only use the part of the string from "start" // Only use the part of the string from "start"
let this_string: String = primitive_val.chars() let this_string: String = primitive_val.chars().skip(start as usize).collect();
.skip(start as usize).collect();
Ok(to_value(this_string.starts_with(&search_string))) Ok(to_value(this_string.starts_with(&search_string)))
} }
} }
@ -194,8 +204,11 @@ pub fn ends_with(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
// If less than 2 args specified, end_position is 'undefined', defaults to // If less than 2 args specified, end_position is 'undefined', defaults to
// length of this // length of this
let end_position: i32 = if args.len() < 2 {length} let end_position: i32 = if args.len() < 2 {
else {from_value(args[1].clone()).unwrap()}; length
} else {
from_value(args[1].clone()).unwrap()
};
let end = min(max(end_position, 0), length); let end = min(max(end_position, 0), length);
let start = end - search_length; let start = end - search_length;
@ -204,8 +217,7 @@ pub fn ends_with(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
Ok(to_value(false)) Ok(to_value(false))
} else { } else {
// Only use the part of the string up to "end" // Only use the part of the string up to "end"
let this_string: String = primitive_val.chars() let this_string: String = primitive_val.chars().take(end as usize).collect();
.take(end as usize).collect();
Ok(to_value(this_string.ends_with(&search_string))) Ok(to_value(this_string.ends_with(&search_string)))
} }
} }
@ -228,14 +240,16 @@ pub fn includes(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
let length: i32 = primitive_val.chars().count() as i32; let length: i32 = primitive_val.chars().count() as i32;
// If less than 2 args specified, position is 'undefined', defaults to 0 // If less than 2 args specified, position is 'undefined', defaults to 0
let position: i32 = if args.len() < 2 {0} let position: i32 = if args.len() < 2 {
else {from_value(args[1].clone()).unwrap()}; 0
} else {
from_value(args[1].clone()).unwrap()
};
let start = min(max(position, 0), length); let start = min(max(position, 0), length);
// Take the string from "this" and use only the part of it after "start" // Take the string from "this" and use only the part of it after "start"
let this_string: String = primitive_val.chars() let this_string: String = primitive_val.chars().skip(start as usize).collect();
.skip(start as usize).collect();
Ok(to_value(this_string.contains(&search_string))) Ok(to_value(this_string.contains(&search_string)))
} }
@ -244,7 +258,7 @@ pub fn includes(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
/// object to a String, at one or more indices that are greater than or equal to /// object to a String, at one or more indices that are greater than or equal to
/// position, then the smallest such index is returned; otherwise, -1 is /// position, then the smallest such index is returned; otherwise, -1 is
/// returned. If position is undefined, 0 is assumed, so as to search all of the /// returned. If position is undefined, 0 is assumed, so as to search all of the
/// String. /// String.
/// https://tc39.github.io/ecma262/#sec-string.prototype.includes /// https://tc39.github.io/ecma262/#sec-string.prototype.includes
pub fn index_of(this: Value, _: Value, args: Vec<Value>) -> ResultValue { pub fn index_of(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
// ^^ represents instance ^^ represents arguments) // ^^ represents instance ^^ represents arguments)
@ -259,8 +273,11 @@ pub fn index_of(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
let length: i32 = primitive_val.chars().count() as i32; let length: i32 = primitive_val.chars().count() as i32;
// If less than 2 args specified, position is 'undefined', defaults to 0 // If less than 2 args specified, position is 'undefined', defaults to 0
let position: i32 = if args.len() < 2 {0} let position: i32 = if args.len() < 2 {
else {from_value(args[1].clone()).unwrap()}; 0
} else {
from_value(args[1].clone()).unwrap()
};
let start = min(max(position, 0), length); let start = min(max(position, 0), length);
@ -269,11 +286,10 @@ pub fn index_of(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
// Instead, iterate over the part we're checking until the slice we're // Instead, iterate over the part we're checking until the slice we're
// checking "starts with" the search string // checking "starts with" the search string
for index in start..length { for index in start..length {
let this_string: String = primitive_val.chars() let this_string: String = primitive_val.chars().skip(index as usize).collect();
.skip(index as usize).collect();
if this_string.starts_with(&search_string) { if this_string.starts_with(&search_string) {
// Explicitly return early with the index value // Explicitly return early with the index value
return Ok(to_value(index)) return Ok(to_value(index));
} }
} }
// Didn't find a match, so return -1 // Didn't find a match, so return -1
@ -299,8 +315,11 @@ pub fn last_index_of(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
let length: i32 = primitive_val.chars().count() as i32; let length: i32 = primitive_val.chars().count() as i32;
// If less than 2 args specified, position is 'undefined', defaults to 0 // If less than 2 args specified, position is 'undefined', defaults to 0
let position: i32 = if args.len() < 2 {0} let position: i32 = if args.len() < 2 {
else {from_value(args[1].clone()).unwrap()}; 0
} else {
from_value(args[1].clone()).unwrap()
};
let start = min(max(position, 0), length); let start = min(max(position, 0), length);
@ -310,8 +329,7 @@ pub fn last_index_of(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
// index we found that "starts with" the search string // index we found that "starts with" the search string
let mut highest_index: i32 = -1; let mut highest_index: i32 = -1;
for index in start..length { for index in start..length {
let this_string: String = primitive_val.chars() let this_string: String = primitive_val.chars().skip(index as usize).collect();
.skip(index as usize).collect();
if this_string.starts_with(&search_string) { if this_string.starts_with(&search_string) {
highest_index = index; highest_index = index;
} }
@ -332,7 +350,7 @@ fn is_trimmable_whitespace(c: char) -> bool {
// Explicit whitespace: https://tc39.es/ecma262/#sec-white-space // Explicit whitespace: https://tc39.es/ecma262/#sec-white-space
'\u{0009}' | '\u{000B}' | '\u{000C}' | '\u{0020}' | '\u{00A0}' | '\u{FEFF}' => true, '\u{0009}' | '\u{000B}' | '\u{000C}' | '\u{0020}' | '\u{00A0}' | '\u{FEFF}' => true,
// Unicode Space_Seperator category // Unicode Space_Seperator category
'\u{1680}' | '\u{2000}'..='\u{200A}' | '\u{202F}' | '\u{205F}' | '\u{3000}' => true, '\u{1680}' | '\u{2000}'..='\u{200A}' | '\u{202F}' | '\u{205F}' | '\u{3000}' => true,
// Line terminators: https://tc39.es/ecma262/#sec-line-terminators // Line terminators: https://tc39.es/ecma262/#sec-line-terminators
'\u{000A}' | '\u{000D}' | '\u{2028}' | '\u{2029}' => true, '\u{000A}' | '\u{000D}' | '\u{2028}' | '\u{2029}' => true,
_ => false, _ => false,
@ -348,7 +366,9 @@ pub fn trim(this: Value, _: Value, _: Vec<Value>) -> ResultValue {
pub fn trim_start(this: Value, _: Value, _: Vec<Value>) -> ResultValue { pub fn trim_start(this: Value, _: Value, _: Vec<Value>) -> ResultValue {
let this_str: String = let this_str: String =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap(); from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
Ok(to_value(this_str.trim_start_matches(is_trimmable_whitespace))) Ok(to_value(
this_str.trim_start_matches(is_trimmable_whitespace),
))
} }
pub fn trim_end(this: Value, _: Value, _: Vec<Value>) -> ResultValue { pub fn trim_end(this: Value, _: Value, _: Vec<Value>) -> ResultValue {

2
src/lib/syntax/ast/token.rs

@ -1,7 +1,7 @@
use std::fmt::{Debug, Display, Formatter, Result};
use crate::syntax::ast::keyword::Keyword; use crate::syntax::ast::keyword::Keyword;
use crate::syntax::ast::pos::Position; use crate::syntax::ast::pos::Position;
use crate::syntax::ast::punc::Punctuator; use crate::syntax::ast::punc::Punctuator;
use std::fmt::{Debug, Display, Formatter, Result};
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
/// Represents a token /// Represents a token

Loading…
Cancel
Save