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;
use boa::exec;
use std::fs::read_to_string;
use std::env;
use std::fs::read_to_string;
use std::process::exit;
fn print_usage() {
println!("Usage:
println!(
"Usage:
boa [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> {
@ -19,7 +21,7 @@ pub fn main() -> Result<(), std::io::Error> {
// No arguments passed, default to "test.js"
1 => {
read_file = "tests/js/test.js";
},
}
// One argument passed, assumed this is the test file
2 => {
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-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.
//!
//! 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::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;
/// 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();
this_ptr.set_field_slice("length", to_value(0i32));
Ok(Gc::new(ValueData::Undefined))
// Make a new Object which will internally represent the Array (mapping
// 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
pub fn _create() -> Value {
pub fn _create(global: &Value) -> Value {
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
}
/// Initialise the global object with the `Array` object
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::value::{from_value, to_value, ResultValue, Value, ValueData};
use gc::Gc;
use std::cmp::{max, min};
use std::f64::NAN;
use std::cmp::{min, max};
/// Create new string
/// 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
/// https://tc39.github.io/ecma262/#sec-string.prototype.concat
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.
// Then we convert it into a Rust String by wrapping it in from_value
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.
let length: i32 = primitive_val.chars().count() as i32;
let from: i32 = if start < 0 {max(length + start, 0)} else {min(start, length)};
let to: i32 = if end < 0 {max(length + end, 0)} else {min(end, length)};
let from: i32 = if start < 0 {
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);
@ -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;
// If less than 2 args specified, position is 'undefined', defaults to 0
let position: i32 = if args.len() < 2 {0}
else {from_value(args[1].clone()).unwrap()};
let position: i32 = if args.len() < 2 {
0
} else {
from_value(args[1].clone()).unwrap()
};
let start = min(max(position, 0), 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))
} else {
// Only use the part of the string from "start"
let this_string: String = primitive_val.chars()
.skip(start as usize).collect();
let this_string: String = primitive_val.chars().skip(start as usize).collect();
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
// length of this
let end_position: i32 = if args.len() < 2 {length}
else {from_value(args[1].clone()).unwrap()};
let end_position: i32 = if args.len() < 2 {
length
} else {
from_value(args[1].clone()).unwrap()
};
let end = min(max(end_position, 0), 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))
} else {
// Only use the part of the string up to "end"
let this_string: String = primitive_val.chars()
.take(end as usize).collect();
let this_string: String = primitive_val.chars().take(end as usize).collect();
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;
// If less than 2 args specified, position is 'undefined', defaults to 0
let position: i32 = if args.len() < 2 {0}
else {from_value(args[1].clone()).unwrap()};
let position: i32 = if args.len() < 2 {
0
} else {
from_value(args[1].clone()).unwrap()
};
let start = min(max(position, 0), length);
// Take the string from "this" and use only the part of it after "start"
let this_string: String = primitive_val.chars()
.skip(start as usize).collect();
let this_string: String = primitive_val.chars().skip(start as usize).collect();
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
/// 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
/// String.
/// String.
/// https://tc39.github.io/ecma262/#sec-string.prototype.includes
pub fn index_of(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
// ^^ 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;
// If less than 2 args specified, position is 'undefined', defaults to 0
let position: i32 = if args.len() < 2 {0}
else {from_value(args[1].clone()).unwrap()};
let position: i32 = if args.len() < 2 {
0
} else {
from_value(args[1].clone()).unwrap()
};
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
// checking "starts with" the search string
for index in start..length {
let this_string: String = primitive_val.chars()
.skip(index as usize).collect();
let this_string: String = primitive_val.chars().skip(index as usize).collect();
if this_string.starts_with(&search_string) {
// 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
@ -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;
// If less than 2 args specified, position is 'undefined', defaults to 0
let position: i32 = if args.len() < 2 {0}
else {from_value(args[1].clone()).unwrap()};
let position: i32 = if args.len() < 2 {
0
} else {
from_value(args[1].clone()).unwrap()
};
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
let mut highest_index: i32 = -1;
for index in start..length {
let this_string: String = primitive_val.chars()
.skip(index as usize).collect();
let this_string: String = primitive_val.chars().skip(index as usize).collect();
if this_string.starts_with(&search_string) {
highest_index = index;
}
@ -332,7 +350,7 @@ fn is_trimmable_whitespace(c: char) -> bool {
// Explicit whitespace: https://tc39.es/ecma262/#sec-white-space
'\u{0009}' | '\u{000B}' | '\u{000C}' | '\u{0020}' | '\u{00A0}' | '\u{FEFF}' => true,
// 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
'\u{000A}' | '\u{000D}' | '\u{2028}' | '\u{2029}' => true,
_ => false,
@ -348,7 +366,9 @@ pub fn trim(this: Value, _: Value, _: Vec<Value>) -> ResultValue {
pub fn trim_start(this: Value, _: Value, _: Vec<Value>) -> ResultValue {
let this_str: String =
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 {

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::pos::Position;
use crate::syntax::ast::punc::Punctuator;
use std::fmt::{Debug, Display, Formatter, Result};
#[derive(Clone, PartialEq)]
/// Represents a token

Loading…
Cancel
Save