Browse Source

Fixed a bunch of Clippy issues (#59)

* Fixed a bunch of Clippy issues

* Fixed bug with numbers

* Added clippy checks in the CI
pull/62/head
Iban Eguia 5 years ago committed by Jason Williams
parent
commit
71340e6bec
  1. 3
      .travis.yml
  2. 30
      src/bin/bin.rs
  3. 45
      src/lib/environment/declerative_environment_record.rs
  4. 29
      src/lib/environment/environment_record_trait.rs
  5. 62
      src/lib/environment/function_environment_record.rs
  6. 71
      src/lib/environment/global_environment_record.rs
  7. 27
      src/lib/environment/lexical_environment.rs
  8. 54
      src/lib/environment/object_environment_record.rs
  9. 34
      src/lib/exec.rs
  10. 47
      src/lib/js/array.rs
  11. 17
      src/lib/js/console.rs
  12. 2
      src/lib/js/error.rs
  13. 23
      src/lib/js/function.rs
  14. 4
      src/lib/js/json.rs
  15. 102
      src/lib/js/math.rs
  16. 22
      src/lib/js/object.rs
  17. 116
      src/lib/js/string.rs
  18. 215
      src/lib/js/value.rs
  19. 36
      src/lib/lib.rs
  20. 4
      src/lib/syntax/ast/constant.rs
  21. 32
      src/lib/syntax/ast/expr.rs
  22. 162
      src/lib/syntax/ast/keyword.rs
  23. 10
      src/lib/syntax/ast/pos.rs
  24. 3
      src/lib/syntax/ast/punc.rs
  25. 28
      src/lib/syntax/ast/token.rs
  26. 129
      src/lib/syntax/lexer.rs
  27. 16
      src/lib/syntax/parser.rs
  28. 9
      tests/js/test.js

3
.travis.yml

@ -4,7 +4,8 @@ rust:
- beta
cache: cargo
before_script:
- rustup component add rustfmt
- rustup component add rustfmt clippy
script:
- cargo fmt --verbose -- --check
- cargo clippy --verbose
- cargo test --verbose

30
src/bin/bin.rs

@ -1,8 +1,28 @@
extern crate boa;
#![forbid(
warnings,
anonymous_parameters,
unused_extern_crates,
unused_import_braces,
missing_copy_implementations,
//trivial_casts,
variant_size_differences,
missing_debug_implementations,
trivial_numeric_casts
)]
// Debug trait derivation will show an error if forbidden.
#![deny(unused_qualifications, unsafe_code)]
#![deny(clippy::all)]
#![warn(clippy::pedantic)]
#![allow(
missing_docs,
clippy::many_single_char_names,
clippy::unreadable_literal,
clippy::excessive_precision,
clippy::module_name_repetitions
)]
use boa::exec;
use std::env;
use std::fs::read_to_string;
use std::process::exit;
use std::{env, fs::read_to_string, process::exit};
fn print_usage() {
println!(
@ -34,6 +54,6 @@ pub fn main() -> Result<(), std::io::Error> {
}
let buffer = read_to_string(read_file)?;
dbg!(exec(buffer));
dbg!(exec(&buffer));
Ok(())
}

45
src/lib/environment/declerative_environment_record.rs

@ -26,14 +26,14 @@ pub struct DeclerativeEnvironmentRecordBinding {
/// A declarative Environment Record binds the set of identifiers defined by the
/// declarations contained within its scope.
#[derive(Trace, Finalize, Clone)]
#[derive(Debug, Trace, Finalize, Clone)]
pub struct DeclerativeEnvironmentRecord {
pub env_rec: HashMap<String, DeclerativeEnvironmentRecordBinding>,
pub outer_env: Option<Environment>,
}
impl EnvironmentRecordTrait for DeclerativeEnvironmentRecord {
fn has_binding(&self, name: &String) -> bool {
fn has_binding(&self, name: &str) -> bool {
self.env_rec.contains_key(name)
}
@ -66,14 +66,13 @@ impl EnvironmentRecordTrait for DeclerativeEnvironmentRecord {
value: None,
can_delete: true,
mutable: false,
strict: strict,
strict,
},
);
}
fn initialize_binding(&mut self, name: String, value: Value) {
match self.env_rec.get_mut(&name) {
Some(ref mut record) => {
fn initialize_binding(&mut self, name: &str, value: Value) {
if let Some(ref mut record) = self.env_rec.get_mut(name) {
match record.value {
Some(_) => {
// TODO: change this when error handling comes into play
@ -82,23 +81,21 @@ impl EnvironmentRecordTrait for DeclerativeEnvironmentRecord {
None => record.value = Some(value),
}
}
None => {}
}
}
fn set_mutable_binding(&mut self, name: String, value: Value, mut strict: bool) {
if self.env_rec.get(&name).is_none() {
if strict == true {
fn set_mutable_binding(&mut self, name: &str, value: Value, mut strict: bool) {
if self.env_rec.get(name).is_none() {
if strict {
// TODO: change this when error handling comes into play
panic!("Reference Error: Cannot set mutable binding for {}", name);
}
self.create_mutable_binding(name.clone(), true);
self.initialize_binding(name.clone(), value);
self.create_mutable_binding(name.to_owned(), true);
self.initialize_binding(name, value);
return;
}
let record: &mut DeclerativeEnvironmentRecordBinding = self.env_rec.get_mut(&name).unwrap();
let record: &mut DeclerativeEnvironmentRecordBinding = self.env_rec.get_mut(name).unwrap();
if record.strict {
strict = true
}
@ -109,17 +106,15 @@ impl EnvironmentRecordTrait for DeclerativeEnvironmentRecord {
if record.mutable {
record.value = Some(value);
} else {
if strict {
} else if strict {
// TODO: change this when error handling comes into play
panic!("TypeError: Cannot mutate an immutable binding {}", name);
}
}
}
fn get_binding_value(&self, name: String, _strict: bool) -> Value {
if self.env_rec.get(&name).is_some() && self.env_rec.get(&name).unwrap().value.is_some() {
let record: &DeclerativeEnvironmentRecordBinding = self.env_rec.get(&name).unwrap();
fn get_binding_value(&self, name: &str, _strict: bool) -> Value {
if self.env_rec.get(name).is_some() && self.env_rec.get(name).unwrap().value.is_some() {
let record: &DeclerativeEnvironmentRecordBinding = self.env_rec.get(name).unwrap();
record.value.as_ref().unwrap().clone()
} else {
// TODO: change this when error handling comes into play
@ -127,10 +122,10 @@ impl EnvironmentRecordTrait for DeclerativeEnvironmentRecord {
}
}
fn delete_binding(&mut self, name: String) -> bool {
if self.env_rec.get(&name).is_some() {
if self.env_rec.get(&name).unwrap().can_delete {
self.env_rec.remove(&name);
fn delete_binding(&mut self, name: &str) -> bool {
if self.env_rec.get(name).is_some() {
if self.env_rec.get(name).unwrap().can_delete {
self.env_rec.remove(name);
true
} else {
false
@ -161,7 +156,7 @@ impl EnvironmentRecordTrait for DeclerativeEnvironmentRecord {
}
fn get_environment_type(&self) -> EnvironmentType {
return EnvironmentType::Declerative;
EnvironmentType::Declerative
}
fn get_global_object(&self) -> Option<Value> {

29
src/lib/environment/environment_record_trait.rs

@ -1,24 +1,27 @@
//! # Environment Records
//!
//! https://tc39.github.io/ecma262/#sec-environment-records
//! https://tc39.github.io/ecma262/#sec-lexical-environments
//! <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`
//!
use crate::environment::lexical_environment::{Environment, EnvironmentType};
use crate::js::value::Value;
use crate::{
environment::lexical_environment::{Environment, EnvironmentType},
js::value::Value,
};
use gc::{Finalize, Trace};
use std::fmt::Debug;
/// https://tc39.github.io/ecma262/#sec-environment-records
/// <https://tc39.github.io/ecma262/#sec-environment-records>
///
/// In the ECMAScript specification Environment Records are hierachical and have a base class with abstract methods.
/// In this implementation we have a trait which represents the behaviour of all EnvironmentRecord types.
pub trait EnvironmentRecordTrait: Trace + Finalize {
/// In this implementation we have a trait which represents the behaviour of all `EnvironmentRecord` types.
pub trait EnvironmentRecordTrait: Debug + Trace + Finalize {
/// Determine if an Environment Record has a binding for the String value N. Return true if it does and false if it does not.
fn has_binding(&self, name: &String) -> bool;
fn has_binding(&self, name: &str) -> bool;
/// Create a new but uninitialized mutable binding in an Environment Record. The String value N is the text of the bound name.
/// If the Boolean argument deletion is true the binding may be subsequently deleted.
@ -33,25 +36,25 @@ pub trait EnvironmentRecordTrait: Trace + Finalize {
/// Set the value of an already existing but uninitialized binding in an Environment Record.
/// The String value N is the text of the bound name.
/// V is the value for the binding and is a value of any ECMAScript language type.
fn initialize_binding(&mut self, name: String, value: Value);
fn initialize_binding(&mut self, name: &str, value: Value);
/// Set the value of an already existing mutable binding in an Environment Record.
/// The String value `name` is the text of the bound name.
/// value is the `value` for the binding and may be a value of any ECMAScript language type. S is a Boolean flag.
/// If `strict` is true and the binding cannot be set throw a TypeError exception.
fn set_mutable_binding(&mut self, name: String, value: Value, strict: bool);
fn set_mutable_binding(&mut self, name: &str, value: Value, strict: bool);
/// Returns the value of an already existing binding from an Environment Record.
/// The String value N is the text of the bound name.
/// S is used to identify references originating in strict mode code or that
/// otherwise require strict mode reference semantics.
fn get_binding_value(&self, name: String, strict: bool) -> Value;
fn get_binding_value(&self, name: &str, strict: bool) -> Value;
/// Delete a binding from an Environment Record.
/// The String value name is the text of the bound name.
/// If a binding for name exists, remove the binding and return true.
/// If the binding exists but cannot be removed return false. If the binding does not exist return true.
fn delete_binding(&mut self, name: String) -> bool;
fn delete_binding(&mut self, name: &str) -> bool;
/// Determine if an Environment Record establishes a this binding.
/// Return true if it does and false if it does not.

62
src/lib/environment/function_environment_record.rs

@ -1,12 +1,12 @@
//! # Function Environment Records
//!
//! A function Environment Record is a declarative Environment Record that is used to represent
//! the top-level scope of a function and, if the function is not an ArrowFunction,
//! the top-level scope of a function and, if the function is not an `ArrowFunction`,
//! provides a `this` binding.
//! If a function is not an ArrowFunction function and references super,
//! If a function is not an `ArrowFunction` function and references super,
//! its function Environment Record also contains the state that is used to perform super method invocations
//! from within the function.
//! More info: https://tc39.github.io/ecma262/#sec-function-environment-records
//! More info: <https://tc39.github.io/ecma262/#sec-function-environment-records>
use crate::environment::declerative_environment_record::DeclerativeEnvironmentRecordBinding;
use crate::environment::environment_record_trait::EnvironmentRecordTrait;
@ -27,8 +27,8 @@ pub enum BindingStatus {
Uninitialized,
}
/// https://tc39.github.io/ecma262/#table-16
#[derive(Trace, Finalize, Clone)]
/// <https://tc39.github.io/ecma262/#table-16>
#[derive(Debug, Trace, Finalize, Clone)]
pub struct FunctionEnvironmentRecord {
pub env_rec: HashMap<String, DeclerativeEnvironmentRecordBinding>,
/// This is the this value used for this invocation of the function.
@ -90,7 +90,7 @@ impl FunctionEnvironmentRecord {
impl EnvironmentRecordTrait for FunctionEnvironmentRecord {
// TODO: get_super_base can't implement until GetPrototypeof is implemented on object
fn has_binding(&self, name: &String) -> bool {
fn has_binding(&self, name: &str) -> bool {
self.env_rec.contains_key(name)
}
@ -123,14 +123,13 @@ impl EnvironmentRecordTrait for FunctionEnvironmentRecord {
value: None,
can_delete: true,
mutable: false,
strict: strict,
strict,
},
);
}
fn initialize_binding(&mut self, name: String, value: Value) {
match self.env_rec.get_mut(&name) {
Some(ref mut record) => {
fn initialize_binding(&mut self, name: &str, value: Value) {
if let Some(ref mut record) = self.env_rec.get_mut(name) {
match record.value {
Some(_) => {
// TODO: change this when error handling comes into play
@ -139,23 +138,21 @@ impl EnvironmentRecordTrait for FunctionEnvironmentRecord {
None => record.value = Some(value),
}
}
None => {}
}
}
fn set_mutable_binding(&mut self, name: String, value: Value, mut strict: bool) {
if self.env_rec.get(&name).is_none() {
if strict == true {
fn set_mutable_binding(&mut self, name: &str, value: Value, mut strict: bool) {
if self.env_rec.get(name).is_none() {
if strict {
// TODO: change this when error handling comes into play
panic!("Reference Error: Cannot set mutable binding for {}", name);
}
self.create_mutable_binding(name.clone(), true);
self.initialize_binding(name.clone(), value);
self.create_mutable_binding(name.to_owned(), true);
self.initialize_binding(name, value);
return;
}
let record: &mut DeclerativeEnvironmentRecordBinding = self.env_rec.get_mut(&name).unwrap();
let record: &mut DeclerativeEnvironmentRecordBinding = self.env_rec.get_mut(name).unwrap();
if record.strict {
strict = true
}
@ -167,17 +164,15 @@ impl EnvironmentRecordTrait for FunctionEnvironmentRecord {
if record.mutable {
record.value = Some(value);
} else {
if strict {
} else if strict {
// TODO: change this when error handling comes into play
panic!("TypeError: Cannot mutate an immutable binding {}", name);
}
}
}
fn get_binding_value(&self, name: String, _strict: bool) -> Value {
if self.env_rec.get(&name).is_some() && self.env_rec.get(&name).unwrap().value.is_some() {
let record: &DeclerativeEnvironmentRecordBinding = self.env_rec.get(&name).unwrap();
fn get_binding_value(&self, name: &str, _strict: bool) -> Value {
if self.env_rec.get(name).is_some() && self.env_rec.get(name).unwrap().value.is_some() {
let record: &DeclerativeEnvironmentRecordBinding = self.env_rec.get(name).unwrap();
record.value.as_ref().unwrap().clone()
} else {
// TODO: change this when error handling comes into play
@ -185,10 +180,10 @@ impl EnvironmentRecordTrait for FunctionEnvironmentRecord {
}
}
fn delete_binding(&mut self, name: String) -> bool {
if self.env_rec.get(&name).is_some() {
if self.env_rec.get(&name).unwrap().can_delete {
self.env_rec.remove(&name);
fn delete_binding(&mut self, name: &str) -> bool {
if self.env_rec.get(name).is_some() {
if self.env_rec.get(name).unwrap().can_delete {
self.env_rec.remove(name);
true
} else {
false
@ -199,15 +194,10 @@ impl EnvironmentRecordTrait for FunctionEnvironmentRecord {
}
fn has_super_binding(&self) -> bool {
match self.this_binding_status {
BindingStatus::Lexical => false,
_ => {
if self.home_object.is_undefined() {
if let BindingStatus::Lexical = self.this_binding_status {
false
} else {
true
}
}
!self.home_object.is_undefined()
}
}
@ -234,7 +224,7 @@ impl EnvironmentRecordTrait for FunctionEnvironmentRecord {
}
fn get_environment_type(&self) -> EnvironmentType {
return EnvironmentType::Function;
EnvironmentType::Function
}
fn get_global_object(&self) -> Option<Value> {

71
src/lib/environment/global_environment_record.rs

@ -5,7 +5,7 @@
//! A global Environment Record provides the bindings for built-in globals (clause 18),
//! properties of the global object, and for all top-level declarations (13.2.8, 13.2.10)
//! that occur within a Script.
//! More info: https://tc39.github.io/ecma262/#sec-global-environment-records
//! More info: <https://tc39.github.io/ecma262/#sec-global-environment-records>
use crate::environment::declerative_environment_record::DeclerativeEnvironmentRecord;
use crate::environment::environment_record_trait::EnvironmentRecordTrait;
@ -15,7 +15,7 @@ use crate::js::value::{Value, ValueData};
use gc::Gc;
use std::collections::HashSet;
#[derive(Trace, Finalize, Clone)]
#[derive(Debug, Trace, Finalize, Clone)]
pub struct GlobalEnvironmentRecord {
pub object_record: Box<ObjectEnvironmentRecord>,
pub global_this_binding: Value,
@ -25,23 +25,23 @@ pub struct GlobalEnvironmentRecord {
impl GlobalEnvironmentRecord {
pub fn get_this_binding(&self) -> Value {
return self.global_this_binding.clone();
self.global_this_binding.clone()
}
pub fn has_var_decleration(&self, name: &String) -> bool {
return self.var_names.contains(name);
pub fn has_var_decleration(&self, name: &str) -> bool {
self.var_names.contains(name)
}
pub fn has_lexical_decleration(&self, name: &String) -> bool {
pub fn has_lexical_decleration(&self, name: &str) -> bool {
self.declerative_record.has_binding(name)
}
pub fn has_restricted_global_property(&self, name: &String) -> bool {
pub fn has_restricted_global_property(&self, name: &str) -> bool {
let global_object = &self.object_record.bindings;
let existing_prop = global_object.get_prop(name.clone());
let existing_prop = global_object.get_prop(name);
match existing_prop {
Some(prop) => {
if prop.value.is_undefined() || prop.configurable == true {
if prop.value.is_undefined() || prop.configurable {
return false;
}
true
@ -53,24 +53,23 @@ impl GlobalEnvironmentRecord {
pub fn create_global_var_binding(&mut self, name: String, deletion: bool) {
let obj_rec = &mut self.object_record;
let global_object = &obj_rec.bindings;
let has_property = global_object.has_field(name.clone());
let has_property = global_object.has_field(&name);
let extensible = global_object.is_extensible();
if !has_property && extensible {
obj_rec.create_mutable_binding(name.clone(), deletion);
obj_rec.initialize_binding(name.clone(), Gc::new(ValueData::Undefined));
obj_rec.initialize_binding(&name, Gc::new(ValueData::Undefined));
}
let var_declared_names = &mut self.var_names;
if !var_declared_names.contains(&name) {
var_declared_names.insert(name.clone());
var_declared_names.insert(name);
}
}
pub fn create_global_function_binding(&mut self, name: String, value: Value, deletion: bool) {
pub fn create_global_function_binding(&mut self, name: &str, value: Value, deletion: bool) {
let global_object = &mut self.object_record.bindings;
let existing_prop = global_object.get_prop(name.clone());
match existing_prop {
Some(prop) => {
let existing_prop = global_object.get_prop(&name);
if let Some(prop) = existing_prop {
if prop.value.is_undefined() || prop.configurable {
global_object.update_prop(
name,
@ -80,22 +79,14 @@ impl GlobalEnvironmentRecord {
Some(deletion),
);
}
}
None => {
global_object.update_prop(
name,
Some(value),
Some(true),
Some(true),
Some(deletion),
);
}
} else {
global_object.update_prop(name, Some(value), Some(true), Some(true), Some(deletion));
}
}
}
impl EnvironmentRecordTrait for GlobalEnvironmentRecord {
fn has_binding(&self, name: &String) -> bool {
fn has_binding(&self, name: &str) -> bool {
if self.declerative_record.has_binding(name) {
return true;
}
@ -121,18 +112,16 @@ impl EnvironmentRecordTrait for GlobalEnvironmentRecord {
.create_immutable_binding(name.clone(), strict)
}
fn initialize_binding(&mut self, name: String, value: Value) {
fn initialize_binding(&mut self, name: &str, value: Value) {
if self.declerative_record.has_binding(&name) {
// TODO: assert binding is in the object environment record
return self
.declerative_record
.initialize_binding(name.clone(), value);
return self.declerative_record.initialize_binding(name, value);
}
panic!("Should not initialized binding without creating first.");
}
fn set_mutable_binding(&mut self, name: String, value: Value, strict: bool) {
fn set_mutable_binding(&mut self, name: &str, value: Value, strict: bool) {
if self.declerative_record.has_binding(&name) {
return self
.declerative_record
@ -141,25 +130,25 @@ impl EnvironmentRecordTrait for GlobalEnvironmentRecord {
self.object_record.set_mutable_binding(name, value, strict)
}
fn get_binding_value(&self, name: String, strict: bool) -> Value {
fn get_binding_value(&self, name: &str, strict: bool) -> Value {
if self.declerative_record.has_binding(&name) {
return self.declerative_record.get_binding_value(name, strict);
}
return self.object_record.get_binding_value(name, strict);
self.object_record.get_binding_value(name, strict)
}
fn delete_binding(&mut self, name: String) -> bool {
fn delete_binding(&mut self, name: &str) -> bool {
if self.declerative_record.has_binding(&name) {
return self.declerative_record.delete_binding(name.clone());
return self.declerative_record.delete_binding(name);
}
let global: &Value = &self.object_record.bindings;
if global.has_field(name.clone()) {
let status = self.object_record.delete_binding(name.clone());
if global.has_field(name) {
let status = self.object_record.delete_binding(name);
if status {
let var_names = &mut self.var_names;
if var_names.contains(&name) {
var_names.remove(&name);
if var_names.contains(name) {
var_names.remove(name);
return status;
}
}
@ -188,7 +177,7 @@ impl EnvironmentRecordTrait for GlobalEnvironmentRecord {
}
fn get_environment_type(&self) -> EnvironmentType {
return EnvironmentType::Global;
EnvironmentType::Global
}
fn get_global_object(&self) -> Option<Value> {

27
src/lib/environment/lexical_environment.rs

@ -1,6 +1,6 @@
//! # Lexical Environment
//!
//! https://tc39.github.io/ecma262/#sec-lexical-environment-operations
//! <https://tc39.github.io/ecma262/#sec-lexical-environment-operations>
//!
//! The following operations are used to operate upon lexical environments
//! This is the entrypoint to lexical environments.
@ -24,7 +24,7 @@ pub type Environment = Gc<GcCell<Box<dyn EnvironmentRecordTrait>>>;
/// Give each environment an easy way to declare its own type
/// This helps with comparisons
#[derive(Debug)]
#[derive(Debug, Clone, Copy)]
pub enum EnvironmentType {
Declerative,
Function,
@ -32,6 +32,7 @@ pub enum EnvironmentType {
Object,
}
#[derive(Debug)]
pub struct LexicalEnvironment {
environment_stack: VecDeque<Environment>,
}
@ -43,8 +44,8 @@ pub struct EnvironmentError {
}
impl EnvironmentError {
pub fn new(msg: &str) -> EnvironmentError {
EnvironmentError {
pub fn new(msg: &str) -> Self {
Self {
details: msg.to_string(),
}
}
@ -68,9 +69,9 @@ impl error::Error for EnvironmentError {
}
impl LexicalEnvironment {
pub fn new(global: Value) -> LexicalEnvironment {
let global_env = new_global_environment(global.clone(), global.clone());
let mut lexical_env = LexicalEnvironment {
pub fn new(global: Value) -> Self {
let global_env = new_global_environment(global.clone(), global);
let mut lexical_env = Self {
environment_stack: VecDeque::new(),
};
@ -106,12 +107,12 @@ impl LexicalEnvironment {
.create_immutable_binding(name, deletion)
}
pub fn set_mutable_binding(&mut self, name: String, value: Value, strict: bool) {
pub fn set_mutable_binding(&mut self, name: &str, value: Value, strict: bool) {
let env = self.get_current_environment();
env.borrow_mut().set_mutable_binding(name, value, strict);
}
pub fn initialize_binding(&mut self, name: String, value: Value) {
pub fn initialize_binding(&mut self, name: &str, value: Value) {
let env = self.get_current_environment();
env.borrow_mut().initialize_binding(name, value);
}
@ -131,10 +132,10 @@ impl LexicalEnvironment {
self.environment_stack.back_mut().unwrap()
}
pub fn get_binding_value(&mut self, name: String) -> Value {
pub fn get_binding_value(&mut self, name: &str) -> Value {
let env: Environment = self.get_current_environment().clone();
let borrowed_env = env.borrow();
let result = borrowed_env.has_binding(&name);
let result = borrowed_env.has_binding(name);
if result {
return borrowed_env.get_binding_value(name, false);
}
@ -172,10 +173,10 @@ pub fn new_function_environment(
debug_assert!(new_target.is_object() || new_target.is_undefined());
Gc::new(GcCell::new(Box::new(FunctionEnvironmentRecord {
env_rec: HashMap::new(),
function_object: f.clone(),
function_object: f,
this_binding_status: BindingStatus::Uninitialized, // hardcoding to unitialized for now until short functions are properly supported
home_object: Gc::new(ValueData::Undefined),
new_target: new_target,
new_target,
outer_env: outer, // this will come from Environment set as a private property of F - https://tc39.github.io/ecma262/#sec-ecmascript-function-objects
this_value: Gc::new(ValueData::Undefined), // TODO: this_value should start as an Option as its not always there to begin with
})))

54
src/lib/environment/object_environment_record.rs

@ -3,7 +3,7 @@
//! Each object Environment Record is associated with an object called its binding object.
//! An object Environment Record binds the set of string identifier names that directly
//! correspond to the property names of its binding object.
//! Property keys that are not strings in the form of an IdentifierName are not included in the set of bound identifiers.
//! Property keys that are not strings in the form of an `IdentifierName` are not included in the set of bound identifiers.
//! More info: [Object Records](https://tc39.github.io/ecma262/#sec-object-environment-records)
use crate::environment::environment_record_trait::EnvironmentRecordTrait;
@ -12,7 +12,7 @@ use crate::js::object::Property;
use crate::js::value::{Value, ValueData};
use gc::Gc;
#[derive(Trace, Finalize, Clone)]
#[derive(Debug, Trace, Finalize, Clone)]
pub struct ObjectEnvironmentRecord {
pub bindings: Value,
pub with_environment: bool,
@ -20,16 +20,15 @@ pub struct ObjectEnvironmentRecord {
}
impl EnvironmentRecordTrait for ObjectEnvironmentRecord {
fn has_binding(&self, name: &String) -> bool {
if !self.bindings.has_field(name.to_string()) {
return false;
}
if !self.with_environment {
return true;
}
fn has_binding(&self, name: &str) -> bool {
if self.bindings.has_field(name) {
if self.with_environment {
// TODO: implement unscopables
}
true
} else {
false
}
}
fn create_mutable_binding(&mut self, name: String, deletion: bool) {
@ -48,37 +47,35 @@ impl EnvironmentRecordTrait for ObjectEnvironmentRecord {
unimplemented!()
}
fn initialize_binding(&mut self, name: String, value: Value) {
fn initialize_binding(&mut self, name: &str, value: Value) {
// We should never need to check if a binding has been created,
// As all calls to create_mutable_binding are followed by initialized binding
// The below is just a check.
debug_assert!(self.has_binding(&name));
return self.set_mutable_binding(name, value, false);
self.set_mutable_binding(name, value, false)
}
fn set_mutable_binding(&mut self, name: String, value: Value, strict: bool) {
fn set_mutable_binding(&mut self, name: &str, value: Value, strict: bool) {
debug_assert!(value.is_object() || value.is_function());
let bindings = &mut self.bindings;
bindings.update_prop(name, Some(value.clone()), None, None, Some(strict));
}
fn get_binding_value(&self, name: String, strict: bool) -> Value {
if self.bindings.has_field(name.clone()) {
return self.bindings.get_field(name);
}
if !strict {
return Gc::new(ValueData::Undefined);
}
fn get_binding_value(&self, name: &str, strict: bool) -> Value {
if self.bindings.has_field(name) {
self.bindings.get_field(name)
} else {
if strict {
// TODO: throw error here
// Error handling not implemented yet
}
Gc::new(ValueData::Undefined)
}
}
fn delete_binding(&mut self, name: String) -> bool {
self.bindings.remove_prop(&name);
fn delete_binding(&mut self, name: &str) -> bool {
self.bindings.remove_prop(name);
true
}
@ -112,13 +109,14 @@ impl EnvironmentRecordTrait for ObjectEnvironmentRecord {
}
fn get_environment_type(&self) -> EnvironmentType {
return EnvironmentType::Function;
EnvironmentType::Function
}
fn get_global_object(&self) -> Option<Value> {
match &self.outer_env {
Some(outer) => outer.borrow().get_global_object(),
None => None,
if let Some(outer) = &self.outer_env {
outer.borrow().get_global_object()
} else {
None
}
}
}

34
src/lib/exec.rs

@ -19,13 +19,14 @@ pub trait Executor {
}
/// A Javascript intepreter
#[derive(Debug)]
pub struct Interpreter {
/// An object representing the global object
environment: LexicalEnvironment,
}
impl Executor for Interpreter {
fn new() -> Interpreter {
fn new() -> Self {
let global = ValueData::new_obj(None);
object::init(&global);
console::init(&global);
@ -34,11 +35,12 @@ impl Executor for Interpreter {
function::init(&global);
json::init(&global);
string::init(&global);
Interpreter {
Self {
environment: LexicalEnvironment::new(global.clone()),
}
}
#[allow(clippy::match_same_arms)]
fn run(&mut self, expr: &Expr) -> ResultValue {
match expr.def {
ExprDef::ConstExpr(Const::Null) => Ok(to_value(None::<()>)),
@ -62,30 +64,30 @@ impl Executor for Interpreter {
Ok(obj)
}
ExprDef::LocalExpr(ref name) => {
let val = self.environment.get_binding_value(name.to_string());
let val = self.environment.get_binding_value(name);
Ok(val)
}
ExprDef::GetConstFieldExpr(ref obj, ref field) => {
let val_obj = self.run(obj)?;
Ok(val_obj.borrow().get_field(field.clone()))
Ok(val_obj.borrow().get_field(field))
}
ExprDef::GetFieldExpr(ref obj, ref field) => {
let val_obj = self.run(obj)?;
let val_field = self.run(field)?;
Ok(val_obj.borrow().get_field(val_field.borrow().to_string()))
Ok(val_obj.borrow().get_field(&val_field.borrow().to_string()))
}
ExprDef::CallExpr(ref callee, ref args) => {
let (this, func) = match callee.def {
ExprDef::GetConstFieldExpr(ref obj, ref field) => {
let obj = self.run(obj)?;
(obj.clone(), obj.borrow().get_field(field.clone()))
(obj.clone(), obj.borrow().get_field(field))
}
ExprDef::GetFieldExpr(ref obj, ref field) => {
let obj = self.run(obj)?;
let field = self.run(field)?;
(
obj.clone(),
obj.borrow().get_field(field.borrow().to_string()),
obj.borrow().get_field(&field.borrow().to_string()),
)
}
_ => (
@ -116,8 +118,7 @@ impl Executor for Interpreter {
let name = data.args.get(i).unwrap();
let expr = v_args.get(i).unwrap();
self.environment.create_mutable_binding(name.clone(), false);
self.environment
.initialize_binding(name.clone(), expr.to_owned());
self.environment.initialize_binding(name, expr.to_owned());
}
let result = self.run(&data.expr);
self.environment.pop();
@ -192,7 +193,7 @@ impl Executor for Interpreter {
arr_map.borrow().set_field_slice(
INSTANCE_PROTOTYPE,
self.environment
.get_binding_value("Array".to_string())
.get_binding_value("Array")
.borrow()
.get_field_slice(PROTOTYPE),
);
@ -207,7 +208,7 @@ impl Executor for Interpreter {
self.environment
.create_mutable_binding(name.clone().unwrap(), false);
self.environment
.initialize_binding(name.clone().unwrap(), val.clone())
.initialize_binding(name.as_ref().unwrap(), val.clone())
}
Ok(val)
}
@ -312,7 +313,7 @@ impl Executor for Interpreter {
let name = data.args.get(i).unwrap();
let expr = v_args.get(i).unwrap();
env.create_mutable_binding(name.clone(), false);
env.initialize_binding(name.clone(), expr.to_owned());
env.initialize_binding(name, expr.to_owned());
}
let result = self.run(&data.expr);
self.environment.pop();
@ -332,8 +333,7 @@ impl Executor for Interpreter {
match ref_e.def {
ExprDef::LocalExpr(ref name) => {
self.environment.create_mutable_binding(name.clone(), false);
self.environment
.initialize_binding(name.clone(), val.clone());
self.environment.initialize_binding(name, val.clone());
}
ExprDef::GetConstFieldExpr(ref obj, ref field) => {
let val_obj = self.run(obj)?;
@ -351,7 +351,7 @@ impl Executor for Interpreter {
None => Gc::new(ValueData::Null),
};
self.environment.create_mutable_binding(name.clone(), false);
self.environment.initialize_binding(name, val);
self.environment.initialize_binding(&name, val);
}
Ok(Gc::new(ValueData::Undefined))
}
@ -363,7 +363,7 @@ impl Executor for Interpreter {
None => Gc::new(ValueData::Null),
};
self.environment.create_mutable_binding(name.clone(), false);
self.environment.initialize_binding(name, val);
self.environment.initialize_binding(&name, val);
}
Ok(Gc::new(ValueData::Undefined))
}
@ -376,7 +376,7 @@ impl Executor for Interpreter {
};
self.environment
.create_immutable_binding(name.clone(), false);
self.environment.initialize_binding(name, val);
self.environment.initialize_binding(&name, val);
}
Ok(Gc::new(ValueData::Undefined))
}

47
src/lib/js/array.rs

@ -3,8 +3,8 @@ use crate::js::object::{Property, PROTOTYPE};
use crate::js::value::{from_value, to_value, ResultValue, Value, ValueData};
use gc::Gc;
/// Utility function for creating array objects: array_obj can be any array with
/// prototype already set (it will be wiped and recreated from array_contents)
/// Utility function for creating array objects: `array_obj` can be any array with
/// prototype already set (it will be wiped and recreated from `array_contents`)
fn create_array_object(array_obj: Value, array_contents: Vec<Value>) -> ResultValue {
let array_obj_ptr = array_obj.clone();
@ -14,11 +14,10 @@ fn create_array_object(array_obj: Value, array_contents: Vec<Value>) -> ResultVa
array_obj_ptr.remove_prop(&n.to_string());
}
for (n, value) in array_contents.iter().enumerate() {
array_obj_ptr.set_field(n.to_string(), value.clone());
}
array_obj_ptr.set_field_slice("length", to_value(array_contents.len() as i32));
for (n, value) in array_contents.into_iter().enumerate() {
array_obj_ptr.set_field(n.to_string(), value);
}
Ok(array_obj_ptr)
}
@ -41,7 +40,7 @@ fn add_to_array_object(array_ptr: Value, add_values: Vec<Value>) -> ResultValue
pub fn make_array(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
// Make a new Object which will internally represent the Array (mapping
// between indices and values): this creates an Object with no prototype
this.set_field_slice("length", to_value(0i32));
this.set_field_slice("length", to_value(0_i32));
match args.len() {
0 => create_array_object(this, Vec::new()),
1 => {
@ -66,11 +65,11 @@ pub fn get_array_length(this: Value, _: Value, _: Vec<Value>) -> ResultValue {
/// When the concat method is called with zero or more arguments, it returns an
/// array containing the array elements of the object followed by the array
/// elements of each argument in order.
/// https://tc39.es/ecma262/#sec-array.prototype.concat
/// <https://tc39.es/ecma262/#sec-array.prototype.concat>
pub fn concat(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
if args.len() == 0 {
if args.is_empty() {
// If concat is called with no arguments, it returns the original array
return Ok(this.clone());
return Ok(this);
}
// Make a new array (using this object as the prototype basis for the new
@ -79,13 +78,13 @@ pub fn concat(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
let this_length: i32 = from_value(this.get_field_slice("length")).unwrap();
for n in 0..this_length {
new_values.push(this.get_field(n.to_string()));
new_values.push(this.get_field(&n.to_string()));
}
for concat_array in args {
let concat_length: i32 = from_value(concat_array.get_field_slice("length")).unwrap();
for n in 0..concat_length {
new_values.push(concat_array.get_field(n.to_string()));
new_values.push(concat_array.get_field(&n.to_string()));
}
}
@ -97,7 +96,7 @@ pub fn concat(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
/// The arguments are appended to the end of the array, in the order in which
/// they appear. The new length of the array is returned as the result of the
/// call.
/// https://tc39.es/ecma262/#sec-array.prototype.push
/// <https://tc39.es/ecma262/#sec-array.prototype.push>
pub fn push(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
let new_array = add_to_array_object(this, args)?;
Ok(new_array.get_field_slice("length"))
@ -106,7 +105,7 @@ pub fn push(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
/// Array.prototype.pop ( )
///
/// The last element of the array is removed from the array and returned.
/// https://tc39.es/ecma262/#sec-array.prototype.pop
/// <https://tc39.es/ecma262/#sec-array.prototype.pop>
pub fn pop(this: Value, _: Value, _: Vec<Value>) -> ResultValue {
let curr_length: i32 = from_value(this.get_field_slice("length")).unwrap();
if curr_length < 1 {
@ -115,7 +114,7 @@ pub fn pop(this: Value, _: Value, _: Vec<Value>) -> ResultValue {
));
}
let pop_index = curr_length - 1;
let pop_value: Value = this.get_field(pop_index.to_string());
let pop_value: Value = this.get_field(&pop_index.to_string());
this.remove_prop(&pop_index.to_string());
this.set_field_slice("length", to_value(pop_index));
Ok(pop_value)
@ -126,20 +125,18 @@ pub fn pop(this: Value, _: Value, _: Vec<Value>) -> ResultValue {
/// The elements of the array are converted to Strings, and these Strings are
/// then concatenated, separated by occurrences of the separator. If no
/// separator is provided, a single comma is used as the separator.
/// https://tc39.es/ecma262/#sec-array.prototype.join
/// <https://tc39.es/ecma262/#sec-array.prototype.join>
pub fn join(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
let separator: String;
if args.len() > 0 {
separator = args[0].to_string();
let separator = if args.is_empty() {
String::from(",")
} else {
separator = ",".to_string();
}
args[0].to_string()
};
let mut elem_strs: Vec<String> = Vec::new();
let length: i32 = from_value(this.get_field_slice("length")).unwrap();
for n in 0..length {
let elem_str: String = this.get_field(n.to_string()).to_string();
let elem_str: String = this.get_field(&n.to_string()).to_string();
elem_strs.push(elem_str);
}
@ -160,10 +157,10 @@ pub fn _create(global: &Value) -> Value {
};
proto.set_prop_slice("length", length);
let concat_func = to_value(concat as NativeFunctionData);
concat_func.set_field_slice("length", to_value(1 as i32));
concat_func.set_field_slice("length", to_value(1_i32));
proto.set_field_slice("concat", concat_func);
let push_func = to_value(push as NativeFunctionData);
push_func.set_field_slice("length", to_value(1 as i32));
push_func.set_field_slice("length", to_value(1_i32));
proto.set_field_slice("push", push_func);
proto.set_field_slice("pop", to_value(pop as NativeFunctionData));
proto.set_field_slice("join", to_value(join as NativeFunctionData));

17
src/lib/js/console.rs

@ -7,20 +7,19 @@ use std::fmt::Write;
use std::iter::FromIterator;
/// Print a javascript value to the standard output stream
/// https://console.spec.whatwg.org/#logger
/// <https://console.spec.whatwg.org/#logger>
pub fn log(_: Value, _: Value, args: Vec<Value>) -> ResultValue {
let args: Vec<String> = FromIterator::from_iter(args.iter().map(|x| {
// Welcome to console.log! The output here is what the developer sees, so its best matching through value types and stringifying to the correct output
// The input is a vector of Values, we generate a vector of strings then pass them to println!
return match *x.clone() {
match *x.clone() {
// We don't want to print private (compiler) or prototype properties
ValueData::Object(ref v, _) => {
// Create empty formatted string to start writing to
// TODO: once constructor is set on objects, we can do specific output for Strings, Numbers etc
let mut s = String::new();
write!(s, "{}", "{").unwrap();
match v.borrow().iter().last() {
Some((last_key, _)) => {
write!(s, "{{").unwrap();
if let Some((last_key, _)) = v.borrow().iter().last() {
for (key, val) in v.borrow().iter() {
// Don't print prototype properties
if key == INSTANCE_PROTOTYPE {
@ -28,18 +27,16 @@ pub fn log(_: Value, _: Value, args: Vec<Value>) -> ResultValue {
}
write!(s, "{}: {}", key, val.value.clone()).unwrap();
if key != last_key {
write!(s, "{}", ", ").unwrap();
write!(s, ", ").unwrap();
}
}
}
None => (),
}
write!(s, "{}", "}").unwrap();
write!(s, "}}").unwrap();
s
}
_ => from_value::<String>(x.clone()).unwrap(),
};
}
// from_value::<String>(x.clone()).unwrap()
}));

2
src/lib/js/error.rs

@ -5,7 +5,7 @@ use gc::Gc;
/// Create a new error
pub fn make_error(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
if args.len() >= 1 {
if !args.is_empty() {
this.set_field_slice("message", to_value(args.get(0).unwrap().to_string()));
}
Ok(Gc::new(ValueData::Undefined))

23
src/lib/js/function.rs

@ -9,7 +9,7 @@ pub type NativeFunctionData = fn(Value, Value, Vec<Value>) -> ResultValue;
/// A Javascript function
/// A member of the Object type that may be invoked as a subroutine
/// https://tc39.github.io/ecma262/#sec-terms-and-definitions-function
/// <https://tc39.github.io/ecma262/#sec-terms-and-definitions-function>
/// In our implementation, Function is extending Object by holding an object field which some extra data
/// A Javascript function
@ -34,17 +34,13 @@ pub struct RegularFunction {
impl RegularFunction {
/// Make a new regular function
pub fn new(expr: Expr, args: Vec<String>) -> RegularFunction {
let mut obj = HashMap::new();
obj.insert(
pub fn new(expr: Expr, args: Vec<String>) -> Self {
let mut object = HashMap::new();
object.insert(
"arguments".to_string(),
Property::new(Gc::new(ValueData::Integer(args.len() as i32))),
);
RegularFunction {
object: obj,
expr: expr,
args: args,
}
Self { object, expr, args }
}
}
@ -58,12 +54,9 @@ pub struct NativeFunction {
}
impl NativeFunction {
/// Make a new native function with the given function data
pub fn new(data: NativeFunctionData) -> NativeFunction {
let obj = HashMap::new();
NativeFunction {
object: obj,
data: data,
}
pub fn new(data: NativeFunctionData) -> Self {
let object = HashMap::new();
Self { object, data }
}
}

4
src/lib/js/json.rs

@ -1,11 +1,11 @@
use crate::js::function::NativeFunctionData;
/// The JSON Object
/// https://tc39.github.io/ecma262/#sec-json-object
/// <https://tc39.github.io/ecma262/#sec-json-object>
use crate::js::value::{to_value, ResultValue, Value, ValueData};
use serde_json::{self, to_string_pretty, Value as JSONValue};
/// Parse a JSON string into a Javascript object
/// https://tc39.github.io/ecma262/#sec-json.parse
/// <https://tc39.github.io/ecma262/#sec-json.parse>
pub fn parse(_: Value, _: Value, args: Vec<Value>) -> ResultValue {
match serde_json::from_str::<JSONValue>(&args.get(0).unwrap().clone().to_string()) {
Ok(json) => Ok(to_value(json)),

102
src/lib/js/math.rs

@ -1,122 +1,124 @@
use crate::js::function::NativeFunctionData;
use crate::js::value::{from_value, to_value, ResultValue, Value, ValueData};
use crate::js::{
function::NativeFunctionData,
value::{from_value, to_value, ResultValue, Value, ValueData},
};
use rand::random;
use std::f64;
/// Get the absolute value of a number
pub fn abs(_: Value, _: Value, args: Vec<Value>) -> ResultValue {
Ok(to_value(if args.len() >= 1 {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
from_value::<f64>(args.get(0).unwrap().clone())
.unwrap()
.abs()
} else {
f64::NAN
}))
}
/// Get the arccos of a number
pub fn acos(_: Value, _: Value, args: Vec<Value>) -> ResultValue {
Ok(to_value(if args.len() >= 1 {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
from_value::<f64>(args.get(0).unwrap().clone())
.unwrap()
.acos()
} else {
f64::NAN
}))
}
/// Get the arcsine of a number
pub fn asin(_: Value, _: Value, args: Vec<Value>) -> ResultValue {
Ok(to_value(if args.len() >= 1 {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
from_value::<f64>(args.get(0).unwrap().clone())
.unwrap()
.asin()
} else {
f64::NAN
}))
}
/// Get the arctangent of a number
pub fn atan(_: Value, _: Value, args: Vec<Value>) -> ResultValue {
Ok(to_value(if args.len() >= 1 {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
from_value::<f64>(args.get(0).unwrap().clone())
.unwrap()
.atan()
} else {
f64::NAN
}))
}
/// Get the arctangent of a numbers
pub fn atan2(_: Value, _: Value, args: Vec<Value>) -> ResultValue {
Ok(to_value(if args.len() >= 1 {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
from_value::<f64>(args.get(0).unwrap().clone())
.unwrap()
.atan2(args.get(1).unwrap().to_num())
} else {
f64::NAN
}))
}
/// Get the cubic root of a number
pub fn cbrt(_: Value, _: Value, args: Vec<Value>) -> ResultValue {
Ok(to_value(if args.len() >= 1 {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
from_value::<f64>(args.get(0).unwrap().clone())
.unwrap()
.cbrt()
} else {
f64::NAN
}))
}
/// Get lowest integer above a number
pub fn ceil(_: Value, _: Value, args: Vec<Value>) -> ResultValue {
Ok(to_value(if args.len() >= 1 {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
from_value::<f64>(args.get(0).unwrap().clone())
.unwrap()
.ceil()
} else {
f64::NAN
}))
}
/// Get the cosine of a number
pub fn cos(_: Value, _: Value, args: Vec<Value>) -> ResultValue {
Ok(to_value(if args.len() >= 1 {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
from_value::<f64>(args.get(0).unwrap().clone())
.unwrap()
.cos()
} else {
f64::NAN
}))
}
/// Get the power to raise the natural logarithm to get the number
pub fn exp(_: Value, _: Value, args: Vec<Value>) -> ResultValue {
Ok(to_value(if args.len() >= 1 {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
from_value::<f64>(args.get(0).unwrap().clone())
.unwrap()
.exp()
} else {
f64::NAN
}))
}
/// Get the highest integer below a number
pub fn floor(_: Value, _: Value, args: Vec<Value>) -> ResultValue {
Ok(to_value(if args.len() >= 1 {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
from_value::<f64>(args.get(0).unwrap().clone())
.unwrap()
.floor()
} else {
f64::NAN
}))
}
/// Get the natural logarithm of a number
pub fn log(_: Value, _: Value, args: Vec<Value>) -> ResultValue {
Ok(to_value(if args.len() >= 1 {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
from_value::<f64>(args.get(0).unwrap().clone())
.unwrap()
.log(f64::consts::E)
} else {
f64::NAN
}))
}
/// Get the maximum of several numbers
pub fn max(_: Value, _: Value, args: Vec<Value>) -> ResultValue {
let mut max = f64::NEG_INFINITY;
for arg in args.iter() {
for arg in &args {
let num = arg.to_num();
max = max.max(num);
}
@ -125,7 +127,7 @@ pub fn max(_: Value, _: Value, args: Vec<Value>) -> ResultValue {
/// Get the minimum of several numbers
pub fn min(_: Value, _: Value, args: Vec<Value>) -> ResultValue {
let mut max = f64::INFINITY;
for arg in args.iter() {
for arg in &args {
let num = arg.to_num();
max = max.min(num);
}
@ -147,42 +149,42 @@ pub fn _random(_: Value, _: Value, _args: Vec<Value>) -> ResultValue {
}
/// Round a number to the nearest integer
pub fn round(_: Value, _: Value, args: Vec<Value>) -> ResultValue {
Ok(to_value(if args.len() >= 1 {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
from_value::<f64>(args.get(0).unwrap().clone())
.unwrap()
.round()
} else {
f64::NAN
}))
}
/// Get the sine of a number
pub fn sin(_: Value, _: Value, args: Vec<Value>) -> ResultValue {
Ok(to_value(if args.len() >= 1 {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
from_value::<f64>(args.get(0).unwrap().clone())
.unwrap()
.sin()
} else {
f64::NAN
}))
}
/// Get the square root of a number
pub fn sqrt(_: Value, _: Value, args: Vec<Value>) -> ResultValue {
Ok(to_value(if args.len() >= 1 {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
from_value::<f64>(args.get(0).unwrap().clone())
.unwrap()
.sqrt()
} else {
f64::NAN
}))
}
/// Get the tangent of a number
pub fn tan(_: Value, _: Value, args: Vec<Value>) -> ResultValue {
Ok(to_value(if args.len() >= 1 {
Ok(to_value(if args.is_empty() {
f64::NAN
} else {
from_value::<f64>(args.get(0).unwrap().clone())
.unwrap()
.tan()
} else {
f64::NAN
}))
}
/// Create a new `Math` object
@ -193,7 +195,7 @@ pub fn _create(global: &Value) -> Value {
math.set_field_slice("LN10", to_value(f64::consts::LN_10));
math.set_field_slice("LOG2E", to_value(f64::consts::LOG2_E));
math.set_field_slice("LOG10E", to_value(f64::consts::LOG10_E));
math.set_field_slice("SQRT1_2", to_value(0.5f64.sqrt()));
math.set_field_slice("SQRT1_2", to_value(0.5_f64.sqrt()));
math.set_field_slice("SQRT2", to_value(f64::consts::SQRT_2));
math.set_field_slice("PI", to_value(f64::consts::PI));
math.set_field_slice("abs", to_value(abs as NativeFunctionData));

22
src/lib/js/object.rs

@ -1,5 +1,7 @@
use crate::js::function::NativeFunctionData;
use crate::js::value::{from_value, to_value, FromValue, ResultValue, ToValue, Value, ValueData};
use crate::js::{
function::NativeFunctionData,
value::{from_value, to_value, FromValue, ResultValue, ToValue, Value, ValueData},
};
use gc::Gc;
use std::collections::HashMap;
@ -34,12 +36,12 @@ pub struct Property {
impl Property {
/// Make a new property with the given value
pub fn new(value: Value) -> Property {
Property {
pub fn new(value: Value) -> Self {
Self {
configurable: false,
enumerable: false,
writable: false,
value: value,
value,
get: Gc::new(ValueData::Undefined),
set: Gc::new(ValueData::Undefined),
}
@ -60,8 +62,8 @@ impl ToValue for Property {
}
impl FromValue for Property {
fn from_value(v: Value) -> Result<Property, &'static str> {
Ok(Property {
fn from_value(v: Value) -> Result<Self, &'static str> {
Ok(Self {
configurable: from_value(v.get_field_slice("configurable")).unwrap(),
enumerable: from_value(v.get_field_slice("enumerable")).unwrap(),
writable: from_value(v.get_field_slice("writable")).unwrap(),
@ -107,13 +109,13 @@ pub fn to_string(this: Value, _: Value, _: Vec<Value>) -> ResultValue {
/// Check if it has a property
pub fn has_own_prop(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
let prop = if args.len() == 0 {
let prop = if args.is_empty() {
None
} else {
from_value::<String>(args.get(0).unwrap().clone()).ok()
};
Ok(to_value(
prop.is_some() && this.get_prop(prop.unwrap()).is_some(),
prop.is_some() && this.get_prop(&prop.unwrap()).is_some(),
))
}
@ -126,7 +128,7 @@ pub fn _create(global: &Value) -> Value {
to_value(has_own_prop as NativeFunctionData),
);
prototype.set_field_slice("toString", to_value(to_string as NativeFunctionData));
object.set_field_slice("length", to_value(1i32));
object.set_field_slice("length", to_value(1_i32));
object.set_field_slice(PROTOTYPE, prototype);
object.set_field_slice(
"setPrototypeOf",

116
src/lib/js/string.rs

@ -1,12 +1,16 @@
use crate::js::function::NativeFunctionData;
use crate::js::object::{Property, PROTOTYPE};
use crate::js::value::{from_value, to_value, ResultValue, Value, ValueData};
use crate::js::{
function::NativeFunctionData,
object::{Property, PROTOTYPE},
value::{from_value, to_value, ResultValue, Value, ValueData},
};
use gc::Gc;
use std::cmp::{max, min};
use std::f64::NAN;
use std::{
cmp::{max, min},
f64::NAN,
};
/// 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>
// This gets called when a new String() is created, it's called by exec:346
pub fn make_string(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
// If we're constructing a string, we should set the initial length
@ -20,73 +24,77 @@ pub fn make_string(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
/// Get a string's length
pub fn get_string_length(this: Value, _: Value, _: Vec<Value>) -> ResultValue {
let this_str: String =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let this_str: String = from_value(this.get_private_field("PrimitiveValue")).unwrap();
Ok(to_value::<i32>(this_str.len() as i32))
}
/// Get the string value to a primitive string
pub fn to_string(this: Value, _: Value, _: Vec<Value>) -> ResultValue {
// Get String from String Object and send it back as a new value
let primitive_val = this.get_private_field(String::from("PrimitiveValue"));
let primitive_val = this.get_private_field("PrimitiveValue");
Ok(to_value(format!("{}", primitive_val).to_string()))
}
/// Returns a single element String containing the code unit at index pos within the String value resulting from converting this object to a String. If there is no element at that index, the result is the empty String. The result is a String value, not a String object.
/// https://tc39.github.io/ecma262/#sec-string.prototype.charat
/// Returns a single element String containing the code unit at index pos within the String value
/// resulting from converting this object to a String. If there is no element at that index, the
/// result is the empty String. The result is a String value, not a String object.
/// <https://tc39.github.io/ecma262/#sec-string.prototype.charat>
pub fn char_at(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
// ^^ represents instance ^^ represents arguments (we only care about the first one in this case)
// 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 =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let pos = from_value(args[0].clone()).unwrap();
let primitive_val: String = from_value(this.get_private_field("PrimitiveValue")).unwrap();
let pos: i32 = from_value(args[0].clone()).unwrap();
// Calling .len() on a string would give the wrong result, as they are bytes not the number of unicode code points
// Note that this is an O(N) operation (because UTF-8 is complex) while getting the number of bytes is an O(1) operation.
// Calling .len() on a string would give the wrong result, as they are bytes not the number of
// unicode code points
// 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 = primitive_val.chars().count();
// We should return an empty string is pos is out of range
if pos >= length || pos < 0 as usize {
if pos >= length as i32 || pos < 0 {
return Ok(to_value::<String>(String::new()));
}
Ok(to_value::<char>(primitive_val.chars().nth(pos).unwrap()))
Ok(to_value::<char>(
primitive_val.chars().nth(pos as usize).unwrap(),
))
}
/// Returns a Number (a nonnegative integer less than 216) that is the numeric value of the code unit at index pos within the String resulting from converting this object to a String. If there is no element at that index, the result is NaN.
/// https://tc39.github.io/ecma262/#sec-string.prototype.charcodeat
/// Returns a Number (a nonnegative integer less than 216) that is the numeric value of the code
/// unit at index pos within the String resulting from converting this object to a String. If there
/// is no element at that index, the result is NaN.
/// <https://tc39.github.io/ecma262/#sec-string.prototype.charcodeat>
pub fn char_code_at(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
// ^^ represents instance ^^ represents arguments (we only care about the first one in this case)
// 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 =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let primitive_val: String = from_value(this.get_private_field("PrimitiveValue")).unwrap();
// Calling .len() on a string would give the wrong result, as they are bytes not the number of unicode code points
// 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 = primitive_val.chars().count();
let pos = from_value(args[0].clone()).unwrap();
let pos: i32 = from_value(args[0].clone()).unwrap();
if pos >= length || pos < 0 as usize {
if pos >= length as i32 || pos < 0 {
return Ok(to_value(NAN));
}
let utf16_val = primitive_val.encode_utf16().nth(pos).unwrap();
let utf16_val = primitive_val.encode_utf16().nth(pos as usize).unwrap();
// If there is no element at that index, the result is NaN
// TODO: We currently don't have NaN
Ok(to_value(utf16_val as f64))
Ok(to_value(f64::from(utf16_val)))
}
/// Returns a String that is the result of concatenating this String and all strings provided as
/// 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 {
// ^^ 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 =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let primitive_val: String = from_value(this.get_private_field("PrimitiveValue")).unwrap();
let mut new_str = primitive_val.clone();
@ -100,13 +108,12 @@ pub fn concat(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
/// Returns a String that is the result of repeating this String the number of times given by the
/// first argument
/// https://tc39.github.io/ecma262/#sec-string.prototype.repeat
/// <https://tc39.github.io/ecma262/#sec-string.prototype.repeat>
pub fn repeat(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
// ^^ represents instance ^^ represents arguments (only care about the first one in this case)
// 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 =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let primitive_val: String = from_value(this.get_private_field("PrimitiveValue")).unwrap();
let repeat_times: usize = from_value(args[0].clone()).unwrap();
Ok(to_value(primitive_val.repeat(repeat_times)))
@ -114,13 +121,12 @@ pub fn repeat(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
/// Returns a String which contains the slice of the JS String from character at "start" index up
/// to but not including character at "end" index
/// https://tc39.github.io/ecma262/#sec-string.prototype.slice
/// <https://tc39.github.io/ecma262/#sec-string.prototype.slice>
pub fn slice(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
// ^^ 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 =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let primitive_val: String = from_value(this.get_private_field("PrimitiveValue")).unwrap();
let start: i32 = from_value(args[0].clone()).unwrap();
let end: i32 = from_value(args[1].clone()).unwrap();
@ -152,13 +158,12 @@ pub fn slice(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
/// Returns a Boolean indicating whether the sequence of code units of the
/// "search string" is the same as the corresponding code units of this string
/// starting at index "position"
/// https://tc39.github.io/ecma262/#sec-string.prototype.startswith
/// <https://tc39.github.io/ecma262/#sec-string.prototype.startswith>
pub fn starts_with(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
// ^^ 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 =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let primitive_val: String = from_value(this.get_private_field("PrimitiveValue")).unwrap();
// TODO: Should throw TypeError if pattern is regular expression
let search_string: String = from_value(args[0].clone()).unwrap();
@ -188,13 +193,12 @@ pub fn starts_with(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
/// Returns a Boolean indicating whether the sequence of code units of the
/// "search string" is the same as the corresponding code units of this string
/// starting at position "end position" - length
/// https://tc39.github.io/ecma262/#sec-string.prototype.endswith
/// <https://tc39.github.io/ecma262/#sec-string.prototype.endswith>
pub fn ends_with(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
// ^^ 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 =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let primitive_val: String = from_value(this.get_private_field("PrimitiveValue")).unwrap();
// TODO: Should throw TypeError if search_string is regular expression
let search_string: String = from_value(args[0].clone()).unwrap();
@ -226,13 +230,12 @@ pub fn ends_with(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
/// the result of converting this object to a String, at one or more indices
/// that are greater than or equal to position. If position is undefined, 0 is
/// assumed, so as to search all of the String.
/// https://tc39.github.io/ecma262/#sec-string.prototype.includes
/// <https://tc39.github.io/ecma262/#sec-string.prototype.includes>
pub fn includes(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
// ^^ 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 =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let primitive_val: String = from_value(this.get_private_field("PrimitiveValue")).unwrap();
// TODO: Should throw TypeError if search_string is regular expression
let search_string: String = from_value(args[0].clone()).unwrap();
@ -259,13 +262,12 @@ pub fn includes(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
/// 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.
/// 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 {
// ^^ 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 =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let primitive_val: String = from_value(this.get_private_field("PrimitiveValue")).unwrap();
// TODO: Should throw TypeError if search_string is regular expression
let search_string: String = from_value(args[0].clone()).unwrap();
@ -293,7 +295,7 @@ pub fn index_of(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
}
}
// Didn't find a match, so return -1
Ok(to_value(-1 as i32))
Ok(to_value(-1))
}
//// If searchString appears as a substring of the result of converting this
@ -301,13 +303,12 @@ pub fn index_of(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
/// position, then the greatest such index is returned; otherwise, -1 is
/// returned. If position is undefined, the length of the String value is
/// assumed, so as to search all of the String.
/// https://tc39.github.io/ecma262/#sec-string.prototype.lastindexof
/// <https://tc39.github.io/ecma262/#sec-string.prototype.lastindexof>
pub fn last_index_of(this: Value, _: Value, args: Vec<Value>) -> ResultValue {
// ^^ 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 =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let primitive_val: String = from_value(this.get_private_field("PrimitiveValue")).unwrap();
// TODO: Should throw TypeError if search_string is regular expression
let search_string: String = from_value(args[0].clone()).unwrap();
@ -348,9 +349,9 @@ fn is_trimmable_whitespace(c: char) -> bool {
// '\u{FEFF}' (zero width non-breaking space)
match c {
// 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}' |
// 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}' |
// Line terminators: https://tc39.es/ecma262/#sec-line-terminators
'\u{000A}' | '\u{000D}' | '\u{2028}' | '\u{2029}' => true,
_ => false,
@ -358,22 +359,19 @@ fn is_trimmable_whitespace(c: char) -> bool {
}
pub fn trim(this: Value, _: Value, _: Vec<Value>) -> ResultValue {
let this_str: String =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let this_str: String = from_value(this.get_private_field("PrimitiveValue")).unwrap();
Ok(to_value(this_str.trim_matches(is_trimmable_whitespace)))
}
pub fn trim_start(this: Value, _: Value, _: Vec<Value>) -> ResultValue {
let this_str: String =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let this_str: String = from_value(this.get_private_field("PrimitiveValue")).unwrap();
Ok(to_value(
this_str.trim_start_matches(is_trimmable_whitespace),
))
}
pub fn trim_end(this: Value, _: Value, _: Vec<Value>) -> ResultValue {
let this_str: String =
from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap();
let this_str: String = from_value(this.get_private_field("PrimitiveValue")).unwrap();
Ok(to_value(this_str.trim_end_matches(is_trimmable_whitespace)))
}

215
src/lib/js/value.rs

@ -1,18 +1,17 @@
use crate::js::function::{Function, NativeFunction, NativeFunctionData};
use crate::js::object::{ObjectData, Property, INSTANCE_PROTOTYPE, PROTOTYPE};
use crate::js::{
function::{Function, NativeFunction, NativeFunctionData},
object::{ObjectData, Property, INSTANCE_PROTOTYPE, PROTOTYPE},
};
use gc::{Gc, GcCell};
use serde_json::map::Map;
use serde_json::Number as JSONNumber;
use serde_json::Value as JSONValue;
use std::collections::HashMap;
use std::f64::NAN;
use std::fmt;
use std::fmt::Display;
use std::iter::FromIterator;
use std::ops::Deref;
use std::ops::DerefMut;
use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Not, Rem, Shl, Shr, Sub};
use std::str::FromStr;
use serde_json::{map::Map, Number as JSONNumber, Value as JSONValue};
use std::{
collections::HashMap,
f64::NAN,
fmt::{self, Display},
iter::FromIterator,
ops::{Add, BitAnd, BitOr, BitXor, Deref, DerefMut, Div, Mul, Not, Rem, Shl, Shr, Sub},
str::FromStr,
};
#[must_use]
/// The result of a Javascript expression is represented like this so it can succeed (`Ok`) or fail (`Err`)
@ -160,7 +159,7 @@ impl ValueData {
ValueData::Number(num) => num,
ValueData::Boolean(true) => 1.0,
ValueData::Boolean(false) | ValueData::Null => 0.0,
ValueData::Integer(num) => num as f64,
ValueData::Integer(num) => f64::from(num),
}
}
@ -184,7 +183,7 @@ impl ValueData {
/// remove_prop removes a property from a Value object.
/// It will return a boolean based on if the value was removed, if there was no value to remove false is returned
pub fn remove_prop(&self, field: &String) {
pub fn remove_prop(&self, field: &str) {
match *self {
ValueData::Object(ref obj, _) => obj.borrow_mut().deref_mut().remove(field),
// Accesing .object on borrow() seems to automatically dereference it, so we don't need the *
@ -198,7 +197,7 @@ impl ValueData {
/// Resolve the property in the object
/// Returns a copy of the Property
pub fn get_prop(&self, field: String) -> Option<Property> {
pub fn get_prop(&self, field: &str) -> Option<Property> {
// Spidermonkey has its own GetLengthProperty: https://searchfox.org/mozilla-central/source/js/src/vm/Interpreter-inl.h#154
// This is only for primitive strings, String() objects have their lengths calculated in string.rs
if self.is_string() && field == "length" {
@ -222,7 +221,7 @@ impl ValueData {
_ => return None,
};
match obj.get(&field) {
match obj.get(field) {
Some(val) => Some(val.clone()),
None => match obj.get(&INSTANCE_PROTOTYPE.to_string()) {
Some(prop) => prop.value.get_prop(field),
@ -236,7 +235,7 @@ impl ValueData {
/// Mostly used internally for now
pub fn update_prop(
&self,
field: String,
field: &str,
value: Option<Value>,
enumerable: Option<bool>,
writable: Option<bool>,
@ -252,27 +251,20 @@ impl ValueData {
_ => None,
};
if obj.is_none() {
return ();
}
let mut hashmap = obj.unwrap();
if let Some(mut hashmap) = obj {
// Use value, or walk up the prototype chain
match hashmap.get_mut(&field) {
Some(ref mut prop) => {
prop.value = value.unwrap_or(prop.value.clone());
if let Some(ref mut prop) = hashmap.get_mut(field) {
prop.value = value.unwrap_or_else(|| prop.value.clone());
prop.enumerable = enumerable.unwrap_or(prop.enumerable);
prop.writable = writable.unwrap_or(prop.writable);
prop.configurable = configurable.unwrap_or(prop.configurable);
}
// Try again with next prop up the chain
None => (),
}
}
/// Resolve the property in the object
/// Returns a copy of the Property
pub fn get_private_prop(&self, field: String) -> Option<Property> {
pub fn get_private_prop(&self, field: &str) -> Option<Property> {
let obj: ObjectData = match *self {
ValueData::Object(_, ref obj) => {
let hash = obj.clone();
@ -281,7 +273,7 @@ impl ValueData {
_ => return None,
};
match obj.get(&field) {
match obj.get(field) {
Some(val) => Some(val.clone()),
None => match obj.get(&INSTANCE_PROTOTYPE.to_string()) {
Some(prop) => prop.value.get_prop(field),
@ -292,7 +284,7 @@ impl ValueData {
/// Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist
/// get_field recieves a Property from get_prop(). It should then return the [[Get]] result value if that's set, otherwise fall back to [[Value]]
pub fn get_private_field(&self, field: String) -> Value {
pub fn get_private_field(&self, field: &str) -> Value {
match self.get_private_prop(field) {
Some(prop) => prop.value.clone(),
None => Gc::new(ValueData::Undefined),
@ -301,7 +293,7 @@ impl ValueData {
/// Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist
/// get_field recieves a Property from get_prop(). It should then return the [[Get]] result value if that's set, otherwise fall back to [[Value]]
pub fn get_field(&self, field: String) -> Value {
pub fn get_field(&self, field: &str) -> Value {
match self.get_prop(field) {
Some(prop) => {
// If the Property has [[Get]] set to a function, we should run that and return the Value
@ -334,32 +326,28 @@ impl ValueData {
}
/// Check to see if the Value has the field, mainly used by environment records
pub fn has_field(&self, field: String) -> bool {
match self.get_prop(field) {
Some(_) => true,
None => false,
}
pub fn has_field(&self, field: &str) -> bool {
self.get_prop(field).is_some()
}
/// Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist
pub fn get_field_slice<'a>(&self, field: &'a str) -> Value {
self.get_field(field.to_string())
pub fn get_field_slice(&self, field: &str) -> Value {
self.get_field(field)
}
/// Set the field in the value
pub fn set_field(&self, field: String, val: Value) -> Value {
match *self {
ValueData::Object(ref obj, _) => {
obj.borrow_mut()
.insert(field.clone(), Property::new(val.clone()));
obj.borrow_mut().insert(field, Property::new(val.clone()));
}
ValueData::Function(ref func) => {
match *func.borrow_mut().deref_mut() {
Function::NativeFunc(ref mut f) => {
f.object.insert(field.clone(), Property::new(val.clone()))
f.object.insert(field, Property::new(val.clone()))
}
Function::RegularFunc(ref mut f) => {
f.object.insert(field.clone(), Property::new(val.clone()))
f.object.insert(field, Property::new(val.clone()))
}
};
}
@ -375,12 +363,8 @@ impl ValueData {
/// Set the private field in the value
pub fn set_private_field(&self, field: String, val: Value) -> Value {
match *self {
ValueData::Object(_, ref obj) => {
obj.borrow_mut()
.insert(field.clone(), Property::new(val.clone()));
}
_ => (),
if let ValueData::Object(_, ref obj) = *self {
obj.borrow_mut().insert(field, Property::new(val.clone()));
}
val
}
@ -394,14 +378,12 @@ impl ValueData {
pub fn set_prop(&self, field: String, prop: Property) -> Property {
match *self {
ValueData::Object(ref obj, _) => {
obj.borrow_mut().insert(field.clone(), prop.clone());
obj.borrow_mut().insert(field, prop.clone());
}
ValueData::Function(ref func) => {
match *func.borrow_mut().deref_mut() {
Function::NativeFunc(ref mut f) => f.object.insert(field.clone(), prop.clone()),
Function::RegularFunc(ref mut f) => {
f.object.insert(field.clone(), prop.clone())
}
Function::NativeFunc(ref mut f) => f.object.insert(field, prop.clone()),
Function::RegularFunc(ref mut f) => f.object.insert(field, prop.clone()),
};
}
_ => (),
@ -415,7 +397,7 @@ impl ValueData {
}
/// Convert from a JSON value to a JS value
pub fn from_json(json: JSONValue) -> ValueData {
pub fn from_json(json: JSONValue) -> Self {
match json {
JSONValue::Number(v) => ValueData::Number(v.as_f64().unwrap()),
JSONValue::String(v) => ValueData::String(v),
@ -453,13 +435,13 @@ impl ValueData {
ValueData::Null | ValueData::Undefined => JSONValue::Null,
ValueData::Boolean(b) => JSONValue::Bool(b),
ValueData::Object(ref obj, _) => {
let mut nobj = Map::new();
let mut new_obj = Map::new();
for (k, v) in obj.borrow().iter() {
if k != INSTANCE_PROTOTYPE {
nobj.insert(k.clone(), v.value.to_json());
new_obj.insert(k.clone(), v.value.to_json());
}
}
JSONValue::Object(nobj)
JSONValue::Object(new_obj)
}
ValueData::String(ref str) => JSONValue::String(str.clone()),
ValueData::Number(num) => JSONValue::Number(JSONNumber::from_f64(num).unwrap()),
@ -499,37 +481,31 @@ impl Display for ValueData {
}
),
ValueData::Object(ref v, ref p) => {
write!(f, "{}", "{")?;
write!(f, "{{")?;
// Print public properties
match v.borrow().iter().last() {
Some((last_key, _)) => {
if let Some((last_key, _)) = v.borrow().iter().last() {
for (key, val) in v.borrow().iter() {
write!(f, "{}: {}", key, val.value.clone())?;
if key != last_key {
write!(f, "{}", ", ")?;
}
write!(f, ", ")?;
}
}
None => (),
};
// Print private properties
match p.borrow().iter().last() {
Some((last_key, _)) => {
if let Some((last_key, _)) = p.borrow().iter().last() {
for (key, val) in p.borrow().iter() {
write!(f, "(Private) {}: {}", key, val.value.clone())?;
if key != last_key {
write!(f, "{}", ", ")?;
write!(f, ", ")?;
}
}
}
None => (),
};
write!(f, "{}", "}")
write!(f, "}}")
}
ValueData::Integer(v) => write!(f, "{}", v),
ValueData::Function(ref v) => match *v.borrow() {
Function::NativeFunc(_) => write!(f, "{}", "function() { [native code] }"),
Function::NativeFunc(_) => write!(f, "function() {{ [native code] }}"),
Function::RegularFunc(ref rf) => {
write!(f, "function({}){}", rf.args.join(", "), rf.expr)
}
@ -539,7 +515,7 @@ impl Display for ValueData {
}
impl PartialEq for ValueData {
fn eq(&self, other: &ValueData) -> bool {
fn eq(&self, other: &Self) -> bool {
match (self.clone(), other.clone()) {
// TODO: fix this
// _ if self.ptr.to_inner() == &other.ptr.to_inner() => true,
@ -562,73 +538,72 @@ impl PartialEq for ValueData {
}
impl Add for ValueData {
type Output = ValueData;
fn add(self, other: ValueData) -> ValueData {
return match (self.clone(), other.clone()) {
(ValueData::String(ref s), ref other) | (ref other, ValueData::String(ref s)) => {
ValueData::String(s.clone() + &other.to_string())
type Output = Self;
fn add(self, other: Self) -> Self {
match (self, other) {
(ValueData::String(ref s), ref o) => ValueData::String(s.clone() + &o.to_string()),
(ref s, ValueData::String(ref o)) => ValueData::String(s.to_string() + o),
(ref s, ref o) => ValueData::Number(s.to_num() + o.to_num()),
}
(_, _) => ValueData::Number(self.to_num() + other.to_num()),
};
}
}
impl Sub for ValueData {
type Output = ValueData;
fn sub(self, other: ValueData) -> ValueData {
type Output = Self;
fn sub(self, other: Self) -> Self {
ValueData::Number(self.to_num() - other.to_num())
}
}
impl Mul for ValueData {
type Output = ValueData;
fn mul(self, other: ValueData) -> ValueData {
type Output = Self;
fn mul(self, other: Self) -> Self {
ValueData::Number(self.to_num() * other.to_num())
}
}
impl Div for ValueData {
type Output = ValueData;
fn div(self, other: ValueData) -> ValueData {
type Output = Self;
fn div(self, other: Self) -> Self {
ValueData::Number(self.to_num() / other.to_num())
}
}
impl Rem for ValueData {
type Output = ValueData;
fn rem(self, other: ValueData) -> ValueData {
type Output = Self;
fn rem(self, other: Self) -> Self {
ValueData::Number(self.to_num() % other.to_num())
}
}
impl BitAnd for ValueData {
type Output = ValueData;
fn bitand(self, other: ValueData) -> ValueData {
type Output = Self;
fn bitand(self, other: Self) -> Self {
ValueData::Integer(self.to_int() & other.to_int())
}
}
impl BitOr for ValueData {
type Output = ValueData;
fn bitor(self, other: ValueData) -> ValueData {
type Output = Self;
fn bitor(self, other: Self) -> Self {
ValueData::Integer(self.to_int() | other.to_int())
}
}
impl BitXor for ValueData {
type Output = ValueData;
fn bitxor(self, other: ValueData) -> ValueData {
type Output = Self;
fn bitxor(self, other: Self) -> Self {
ValueData::Integer(self.to_int() ^ other.to_int())
}
}
impl Shl for ValueData {
type Output = ValueData;
fn shl(self, other: ValueData) -> ValueData {
type Output = Self;
fn shl(self, other: Self) -> Self {
ValueData::Integer(self.to_int() << other.to_int())
}
}
impl Shr for ValueData {
type Output = ValueData;
fn shr(self, other: ValueData) -> ValueData {
type Output = Self;
fn shr(self, other: Self) -> Self {
ValueData::Integer(self.to_int() >> other.to_int())
}
}
impl Not for ValueData {
type Output = ValueData;
fn not(self) -> ValueData {
type Output = Self;
fn not(self) -> Self {
ValueData::Boolean(!self.is_true())
}
}
@ -653,7 +628,7 @@ impl ToValue for String {
}
impl FromValue for String {
fn from_value(v: Value) -> Result<String, &'static str> {
fn from_value(v: Value) -> Result<Self, &'static str> {
Ok(v.to_string())
}
}
@ -670,7 +645,7 @@ impl ToValue for char {
}
}
impl FromValue for char {
fn from_value(v: Value) -> Result<char, &'static str> {
fn from_value(v: Value) -> Result<Self, &'static str> {
Ok(v.to_string().chars().next().unwrap())
}
}
@ -681,7 +656,7 @@ impl ToValue for f64 {
}
}
impl FromValue for f64 {
fn from_value(v: Value) -> Result<f64, &'static str> {
fn from_value(v: Value) -> Result<Self, &'static str> {
Ok(v.to_num())
}
}
@ -692,7 +667,7 @@ impl ToValue for i32 {
}
}
impl FromValue for i32 {
fn from_value(v: Value) -> Result<i32, &'static str> {
fn from_value(v: Value) -> Result<Self, &'static str> {
Ok(v.to_int())
}
}
@ -703,7 +678,7 @@ impl ToValue for usize {
}
}
impl FromValue for usize {
fn from_value(v: Value) -> Result<usize, &'static str> {
fn from_value(v: Value) -> Result<Self, &'static str> {
Ok(v.to_int() as usize)
}
}
@ -714,7 +689,7 @@ impl ToValue for bool {
}
}
impl FromValue for bool {
fn from_value(v: Value) -> Result<bool, &'static str> {
fn from_value(v: Value) -> Result<Self, &'static str> {
Ok(v.is_true())
}
}
@ -722,10 +697,8 @@ impl FromValue for bool {
impl<'s, T: ToValue> ToValue for &'s [T] {
fn to_value(&self) -> Value {
let mut arr = HashMap::new();
let mut i = 0;
for item in self.iter() {
for (i, item) in self.iter().enumerate() {
arr.insert(i.to_string(), Property::new(item.to_value()));
i += 1;
}
to_value(arr)
}
@ -733,21 +706,19 @@ impl<'s, T: ToValue> ToValue for &'s [T] {
impl<T: ToValue> ToValue for Vec<T> {
fn to_value(&self) -> Value {
let mut arr = HashMap::new();
let mut i = 0;
for item in self.iter() {
for (i, item) in self.iter().enumerate() {
arr.insert(i.to_string(), Property::new(item.to_value()));
i += 1;
}
to_value(arr)
}
}
impl<T: FromValue> FromValue for Vec<T> {
fn from_value(v: Value) -> Result<Vec<T>, &'static str> {
fn from_value(v: Value) -> Result<Self, &'static str> {
let len = v.get_field_slice("length").to_int();
let mut vec = Vec::with_capacity(len as usize);
let mut vec = Self::with_capacity(len as usize);
for i in 0..len {
vec.push(from_value(v.get_field(i.to_string()))?)
vec.push(from_value(v.get_field(&i.to_string()))?)
}
Ok(vec)
}
@ -755,7 +726,7 @@ impl<T: FromValue> FromValue for Vec<T> {
impl ToValue for ObjectData {
fn to_value(&self) -> Value {
let private_obj: ObjectData = HashMap::new();
let private_obj: Self = HashMap::new();
Gc::new(ValueData::Object(
GcCell::new(self.clone()),
GcCell::new(private_obj),
@ -764,7 +735,7 @@ impl ToValue for ObjectData {
}
impl FromValue for ObjectData {
fn from_value(v: Value) -> Result<ObjectData, &'static str> {
fn from_value(v: Value) -> Result<Self, &'static str> {
match *v {
ValueData::Object(ref obj, _) => Ok(obj.clone().into_inner()),
ValueData::Function(ref func) => Ok(match *func.borrow().deref() {
@ -783,7 +754,7 @@ impl ToValue for JSONValue {
}
impl FromValue for JSONValue {
fn from_value(v: Value) -> Result<JSONValue, &'static str> {
fn from_value(v: Value) -> Result<Self, &'static str> {
Ok(v.to_json())
}
}
@ -808,7 +779,7 @@ impl<T: ToValue> ToValue for Option<T> {
}
}
impl<T: FromValue> FromValue for Option<T> {
fn from_value(value: Value) -> Result<Option<T>, &'static str> {
fn from_value(value: Value) -> Result<Self, &'static str> {
Ok(if value.is_null_or_undefined() {
None
} else {
@ -825,7 +796,7 @@ impl ToValue for NativeFunctionData {
}
}
impl FromValue for NativeFunctionData {
fn from_value(v: Value) -> Result<NativeFunctionData, &'static str> {
fn from_value(v: Value) -> Result<Self, &'static str> {
match *v {
ValueData::Function(ref func) => match *func.borrow() {
Function::NativeFunc(ref data) => Ok(data.data),
@ -836,12 +807,12 @@ impl FromValue for NativeFunctionData {
}
}
/// A utility function that just calls FromValue::from_value
/// A utility function that just calls `FromValue::from_value`
pub fn from_value<A: FromValue>(v: Value) -> Result<A, &'static str> {
FromValue::from_value(v)
}
/// A utility function that just calls ToValue::to_value
/// A utility function that just calls `ToValue::to_value`
pub fn to_value<A: ToValue>(v: A) -> Value {
v.to_value()
}

36
src/lib/lib.rs

@ -1,7 +1,27 @@
extern crate chrono;
extern crate gc;
extern crate rand;
extern crate serde_json;
#![forbid(
//missing_docs,
//warnings,
anonymous_parameters,
unused_extern_crates,
unused_import_braces,
missing_copy_implementations,
//trivial_casts,
variant_size_differences,
missing_debug_implementations,
trivial_numeric_casts
)]
// Debug trait derivation will show an error if forbidden.
#![deny(unused_qualifications, unsafe_code)]
#![deny(clippy::all)]
#![warn(clippy::pedantic)]
#![allow(
clippy::many_single_char_names,
clippy::unreadable_literal,
clippy::excessive_precision,
clippy::module_name_repetitions,
clippy::pub_enum_variant_names,
clippy::cognitive_complexity
)]
#[macro_use]
extern crate gc_derive;
@ -24,8 +44,8 @@ extern "C" {
fn log(s: &str);
}
pub fn exec(src: String) -> String {
let mut lexer = Lexer::new(&src);
pub fn exec(src: &str) -> String {
let mut lexer = Lexer::new(src);
lexer.lex().unwrap();
let tokens = lexer.tokens;
@ -36,7 +56,7 @@ pub fn exec(src: String) -> String {
let result = engine.run(&expr);
match result {
Ok(v) => v.to_string(),
Err(v) => String::from(format!("{}: {}", "Error", v.to_string())),
Err(v) => format!("{}: {}", "Error", v.to_string()),
}
}
@ -69,7 +89,7 @@ pub fn evaluate(src: &str) -> String {
Ok(v) => v.to_string(),
Err(v) => {
log(&format!("{} {}", "asudihsiu", v.to_string()));
String::from(format!("{}: {}", "error", v.to_string()))
format!("{}: {}", "error", v.to_string())
}
}
}

4
src/lib/syntax/ast/constant.rs

@ -21,7 +21,7 @@ pub enum Const {
impl Display for Const {
fn fmt(&self, f: &mut Formatter) -> Result {
return match *self {
match *self {
Const::String(ref st) => write!(f, "\"{}\"", st),
Const::RegExp(ref reg, _, _) => write!(f, "~/{}/", reg),
Const::Num(num) => write!(f, "{}", num),
@ -29,6 +29,6 @@ impl Display for Const {
Const::Bool(v) => write!(f, "{}", v),
Const::Null => write!(f, "null"),
Const::Undefined => write!(f, "undefined"),
};
}
}
}

32
src/lib/syntax/ast/expr.rs

@ -1,7 +1,11 @@
use crate::syntax::ast::constant::Const;
use crate::syntax::ast::op::{BinOp, Operator, UnaryOp};
use std::collections::btree_map::BTreeMap;
use std::fmt::{Display, Formatter, Result};
use crate::syntax::ast::{
constant::Const,
op::{BinOp, Operator, UnaryOp},
};
use std::{
collections::btree_map::BTreeMap,
fmt::{Display, Formatter, Result},
};
#[derive(Clone, Trace, Finalize, Debug, PartialEq)]
pub struct Expr {
@ -11,8 +15,8 @@ pub struct Expr {
impl Expr {
/// Create a new expression with a starting and ending position
pub fn new(def: ExprDef) -> Expr {
Expr { def: def }
pub fn new(def: ExprDef) -> Self {
Self { def }
}
}
@ -108,21 +112,21 @@ impl Operator for ExprDef {
impl Display for ExprDef {
fn fmt(&self, f: &mut Formatter) -> Result {
return match *self {
match *self {
ExprDef::ConstExpr(ref c) => write!(f, "{}", c),
ExprDef::BlockExpr(ref block) => {
write!(f, "{}", "{")?;
write!(f, "{{")?;
for expr in block.iter() {
write!(f, "{};", expr)?;
}
write!(f, "{}", "}")
write!(f, "}}")
}
ExprDef::LocalExpr(ref s) => write!(f, "{}", s),
ExprDef::GetConstFieldExpr(ref ex, ref field) => write!(f, "{}.{}", ex, field),
ExprDef::GetFieldExpr(ref ex, ref field) => write!(f, "{}[{}]", ex, field),
ExprDef::CallExpr(ref ex, ref args) => {
write!(f, "{}(", ex)?;
let arg_strs: Vec<String> = args.iter().map(|arg| arg.to_string()).collect();
let arg_strs: Vec<String> = args.iter().map(ToString::to_string).collect();
write!(f, "{})", arg_strs.join(","))
}
ExprDef::ConstructExpr(ref func, ref args) => {
@ -185,7 +189,7 @@ impl Display for ExprDef {
ExprDef::BinOpExpr(ref op, ref a, ref b) => write!(f, "{} {} {}", a, op, b),
ExprDef::UnaryOpExpr(ref op, ref a) => write!(f, "{}{}", op, a),
ExprDef::ReturnExpr(Some(ref ex)) => write!(f, "return {}", ex),
ExprDef::ReturnExpr(None) => write!(f, "{}", "return"),
ExprDef::ReturnExpr(None) => write!(f, "return"),
ExprDef::ThrowExpr(ref ex) => write!(f, "throw {}", ex),
ExprDef::AssignExpr(ref ref_e, ref val) => write!(f, "{} = {}", ref_e, val),
ExprDef::VarDeclExpr(ref vars)
@ -201,12 +205,12 @@ impl Display for ExprDef {
f.write_str("")
}
ExprDef::TypeOfExpr(ref e) => write!(f, "typeof {}", e),
};
}
}
}
/// join_expr - Utility to join multiple Expressions into a single string
fn join_expr(f: &mut Formatter, expr: &Vec<Expr>) -> Result {
/// `join_expr` - Utility to join multiple Expressions into a single string
fn join_expr(f: &mut Formatter, expr: &[Expr]) -> Result {
let mut first = true;
for e in expr.iter() {
if !first {

162
src/lib/syntax/ast/keyword.rs

@ -1,12 +1,12 @@
use crate::syntax::ast::keyword::Keyword::*;
use std::error;
use std::fmt::Error;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
use std::{
error,
fmt::{Display, Error, Formatter},
str::FromStr,
};
#[derive(Clone, PartialEq, Debug)]
#[derive(Clone, Copy, PartialEq, Debug)]
/// A Javascript Keyword
/// As specificed by https://www.ecma-international.org/ecma-262/#sec-keywords
/// As specificed by <https://www.ecma-international.org/ecma-262/#sec-keywords>
pub enum Keyword {
/// The `await` keyword
Await,
@ -82,7 +82,7 @@ pub enum Keyword {
Yield,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy)]
pub struct KeywordError;
impl Display for KeywordError {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
@ -103,44 +103,44 @@ impl error::Error for KeywordError {
}
impl FromStr for Keyword {
type Err = KeywordError;
fn from_str(s: &str) -> Result<Keyword, Self::Err> {
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"await" => Ok(Await),
"break" => Ok(Break),
"case" => Ok(Case),
"catch" => Ok(Catch),
"class" => Ok(Class),
"continue" => Ok(Continue),
"const" => Ok(Const),
"debugger" => Ok(Debugger),
"default" => Ok(Default),
"delete" => Ok(Delete),
"do" => Ok(Do),
"else" => Ok(Else),
"enum" => Ok(Enum),
"extends" => Ok(Extends),
"export" => Ok(Export),
"finally" => Ok(Finally),
"for" => Ok(For),
"function" => Ok(Function),
"if" => Ok(If),
"in" => Ok(In),
"instanceof" => Ok(InstanceOf),
"import" => Ok(Import),
"let" => Ok(Let),
"new" => Ok(New),
"return" => Ok(Return),
"super" => Ok(Super),
"switch" => Ok(Switch),
"this" => Ok(This),
"throw" => Ok(Throw),
"try" => Ok(Try),
"typeof" => Ok(TypeOf),
"var" => Ok(Var),
"void" => Ok(Void),
"while" => Ok(While),
"with" => Ok(With),
"yield" => Ok(Yield),
"await" => Ok(Keyword::Await),
"break" => Ok(Keyword::Break),
"case" => Ok(Keyword::Case),
"catch" => Ok(Keyword::Catch),
"class" => Ok(Keyword::Class),
"continue" => Ok(Keyword::Continue),
"const" => Ok(Keyword::Const),
"debugger" => Ok(Keyword::Debugger),
"default" => Ok(Keyword::Default),
"delete" => Ok(Keyword::Delete),
"do" => Ok(Keyword::Do),
"else" => Ok(Keyword::Else),
"enum" => Ok(Keyword::Enum),
"extends" => Ok(Keyword::Extends),
"export" => Ok(Keyword::Export),
"finally" => Ok(Keyword::Finally),
"for" => Ok(Keyword::For),
"function" => Ok(Keyword::Function),
"if" => Ok(Keyword::If),
"in" => Ok(Keyword::In),
"instanceof" => Ok(Keyword::InstanceOf),
"import" => Ok(Keyword::Import),
"let" => Ok(Keyword::Let),
"new" => Ok(Keyword::New),
"return" => Ok(Keyword::Return),
"super" => Ok(Keyword::Super),
"switch" => Ok(Keyword::Switch),
"this" => Ok(Keyword::This),
"throw" => Ok(Keyword::Throw),
"try" => Ok(Keyword::Try),
"typeof" => Ok(Keyword::TypeOf),
"var" => Ok(Keyword::Var),
"void" => Ok(Keyword::Void),
"while" => Ok(Keyword::While),
"with" => Ok(Keyword::With),
"yield" => Ok(Keyword::Yield),
_ => Err(KeywordError),
}
}
@ -151,42 +151,42 @@ impl Display for Keyword {
f,
"{}",
match *self {
Await => "await",
Break => "break",
Case => "case",
Catch => "catch",
Class => "class",
Continue => "continue",
Const => "const",
Debugger => "debugger",
Default => "default",
Delete => "delete",
Do => "do",
Else => "else",
Enum => "enum",
Extends => "extends",
Export => "export",
Finally => "finally",
For => "for",
Function => "function",
If => "if",
In => "in",
InstanceOf => "instanceof",
Import => "import",
Let => "let",
New => "new",
Return => "return",
Super => "super",
Switch => "switch",
This => "this",
Throw => "throw",
Try => "try",
TypeOf => "typeof",
Var => "var",
Void => "void",
While => "while",
With => "with",
Yield => "yield",
Keyword::Await => "await",
Keyword::Break => "break",
Keyword::Case => "case",
Keyword::Catch => "catch",
Keyword::Class => "class",
Keyword::Continue => "continue",
Keyword::Const => "const",
Keyword::Debugger => "debugger",
Keyword::Default => "default",
Keyword::Delete => "delete",
Keyword::Do => "do",
Keyword::Else => "else",
Keyword::Enum => "enum",
Keyword::Extends => "extends",
Keyword::Export => "export",
Keyword::Finally => "finally",
Keyword::For => "for",
Keyword::Function => "function",
Keyword::If => "if",
Keyword::In => "in",
Keyword::InstanceOf => "instanceof",
Keyword::Import => "import",
Keyword::Let => "let",
Keyword::New => "new",
Keyword::Return => "return",
Keyword::Super => "super",
Keyword::Switch => "switch",
Keyword::This => "this",
Keyword::Throw => "throw",
Keyword::Try => "try",
Keyword::TypeOf => "typeof",
Keyword::Var => "var",
Keyword::Void => "void",
Keyword::While => "while",
Keyword::With => "with",
Keyword::Yield => "yield",
}
)
}

10
src/lib/syntax/ast/pos.rs

@ -1,4 +1,4 @@
#[derive(Clone, PartialEq, Debug)]
#[derive(Clone, Copy, PartialEq, Debug)]
/// A position in the Javascript source code
/// Stores both the column number and the line number
///
@ -20,10 +20,10 @@ impl Position {
///
/// * `line_number` - The line number the token starts at
/// * `column_number` - The column number the token starts at
pub fn new(line_number: u64, column_number: u64) -> Position {
Position {
line_number: line_number,
column_number: column_number,
pub fn new(line_number: u64, column_number: u64) -> Self {
Self {
line_number,
column_number,
}
}
}

3
src/lib/syntax/ast/punc.rs

@ -1,5 +1,6 @@
use std::fmt::{Display, Error, Formatter};
#[derive(PartialEq, Clone, Debug)]
#[derive(PartialEq, Clone, Copy, Debug)]
/// Punctuation
pub enum Punctuator {
/// `+`

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

@ -1,6 +1,4 @@
use crate::syntax::ast::keyword::Keyword;
use crate::syntax::ast::pos::Position;
use crate::syntax::ast::punc::Punctuator;
use crate::syntax::ast::{keyword::Keyword, pos::Position, punc::Punctuator};
use std::fmt::{Debug, Display, Formatter, Result};
#[derive(Clone, PartialEq)]
@ -15,9 +13,9 @@ pub struct Token {
impl Token {
/// Create a new detailed token from the token data, line number and column number
pub fn new(data: TokenData, line_number: u64, column_number: u64) -> Token {
Token {
data: data,
pub fn new(data: TokenData, line_number: u64, column_number: u64) -> Self {
Self {
data,
pos: Position::new(line_number, column_number),
}
}
@ -68,17 +66,17 @@ pub enum TokenData {
impl Display for TokenData {
fn fmt(&self, f: &mut Formatter) -> Result {
match self.clone() {
TokenData::BooleanLiteral(val) => write!(f, "{}", val),
match *self {
TokenData::BooleanLiteral(ref val) => write!(f, "{}", val),
TokenData::EOF => write!(f, "end of file"),
TokenData::Identifier(ident) => write!(f, "{}", ident),
TokenData::Keyword(word) => write!(f, "{}", word),
TokenData::Identifier(ref ident) => write!(f, "{}", ident),
TokenData::Keyword(ref word) => write!(f, "{}", word),
TokenData::NullLiteral => write!(f, "null"),
TokenData::NumericLiteral(num) => write!(f, "{}", num),
TokenData::Punctuator(punc) => write!(f, "{}", punc),
TokenData::StringLiteral(lit) => write!(f, "{}", lit),
TokenData::RegularExpression(reg) => write!(f, "{}", reg),
TokenData::Comment(comm) => write!(f, "/*{}*/", comm),
TokenData::NumericLiteral(ref num) => write!(f, "{}", num),
TokenData::Punctuator(ref punc) => write!(f, "{}", punc),
TokenData::StringLiteral(ref lit) => write!(f, "{}", lit),
TokenData::RegularExpression(ref reg) => write!(f, "{}", reg),
TokenData::Comment(ref comm) => write!(f, "/*{}*/", comm),
}
}
}

129
src/lib/syntax/lexer.rs

@ -2,16 +2,18 @@
//!
//! 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.
use crate::syntax::ast::punc::Punctuator;
use crate::syntax::ast::token::{Token, TokenData};
use std::char::{decode_utf16, from_u32};
use std::error;
use std::fmt;
use std::iter::Peekable;
use std::str::Chars;
use std::str::FromStr;
#[allow(unused)]
use crate::syntax::ast::{
punc::Punctuator,
token::{Token, TokenData},
};
use std::{
char::{decode_utf16, from_u32},
error, fmt,
iter::Peekable,
str::{Chars, FromStr},
};
macro_rules! vop {
($this:ident, $assign_op:expr, $op:expr) => ({
let preview = $this.preview_next().unwrap();
@ -71,8 +73,8 @@ pub struct LexerError {
}
impl LexerError {
fn new(msg: &str) -> LexerError {
LexerError {
fn new(msg: &str) -> Self {
Self {
details: msg.to_string(),
}
}
@ -96,6 +98,7 @@ impl error::Error for LexerError {
}
/// A lexical analyzer for JavaScript source code
#[derive(Debug)]
pub struct Lexer<'a> {
// The list fo tokens generated so far
pub tokens: Vec<Token>,
@ -143,7 +146,7 @@ impl<'a> Lexer<'a> {
/// next fetches the next token and return it, or a LexerError if there are no more.
fn next(&mut self) -> Result<char, LexerError> {
match self.buffer.next() {
Some(char) => Ok(char),
Some(ch) => Ok(ch),
None => Err(LexerError::new("finished")),
}
}
@ -168,11 +171,7 @@ impl<'a> Lexer<'a> {
/// Preview the next character but don't actually increment
fn preview_next(&mut self) -> Option<char> {
// No need to return a reference, we can return a copy
match self.buffer.peek() {
Some(v) => Some(*v),
None => None,
}
self.buffer.peek().copied()
}
/// Utility Function, while ``f(char)`` is true, read chars and move curser.
@ -191,10 +190,7 @@ impl<'a> Lexer<'a> {
/// next_is compares the character passed in to the next character, if they match true is returned and the buffer is incremented
fn next_is(&mut self, peek: char) -> bool {
let result = match self.preview_next() {
Some(v) => v == peek,
None => false,
};
let result = self.preview_next() == Some(peek);
if result {
self.buffer.next();
}
@ -204,12 +200,9 @@ impl<'a> Lexer<'a> {
pub fn lex(&mut self) -> Result<(), LexerError> {
loop {
// Check if we've reached the end
match self.preview_next() {
Some(_) => (), // If there are still characters, carry on
None => {
if self.preview_next().is_none() {
return Ok(());
}
}
self.column_number += 1;
let ch = self.next()?;
match ch {
@ -235,7 +228,7 @@ impl<'a> Lexer<'a> {
'0' => '\0',
'x' => {
let mut nums = String::with_capacity(2);
for _ in 0u8..2 {
for _ in 0_u8..2 {
nums.push(self.next()?);
}
self.column_number += 2;
@ -260,7 +253,7 @@ impl<'a> Lexer<'a> {
// Support \u{X..X} (Unicode Codepoint)
if self.next_is('{') {
let s = self
.take_char_while(|c| c.is_alphanumeric())
.take_char_while(char::is_alphanumeric)
.unwrap();
// We know this is a single unicode codepoint, convert to u32
@ -279,7 +272,7 @@ impl<'a> Lexer<'a> {
loop {
// Collect each character after \u e.g \uD83D will give "D83D"
let s = self
.take_char_while(|c| c.is_alphanumeric())
.take_char_while(char::is_alphanumeric)
.unwrap();
// Convert to u16
@ -300,12 +293,10 @@ impl<'a> Lexer<'a> {
// codepoints length should either be 1 (unicode codepoint) or 2 (surrogate codepoint).
// Rust's decode_utf16 will deal with it regardless
let c = decode_utf16(codepoints.iter().cloned())
decode_utf16(codepoints.iter().cloned())
.next()
.unwrap()
.unwrap();
c
.unwrap()
}
}
'\'' | '"' => escape,
@ -317,7 +308,7 @@ impl<'a> Lexer<'a> {
buf.push(escaped_ch);
}
}
ch => buf.push(ch),
next_ch => buf.push(next_ch),
}
}
let str_length = buf.len() as u64;
@ -330,40 +321,30 @@ impl<'a> Lexer<'a> {
'0' => {
let mut buf = String::new();
let num = if self.next_is('x') {
loop {
match self.preview_next() {
Some(ch) => {
while let Some(ch) = self.preview_next() {
if ch.is_digit(16) {
buf.push(self.next()?);
} else {
break;
}
}
None => break,
};
}
u64::from_str_radix(&buf, 16).unwrap()
} else if self.next_is('b') {
loop {
match self.preview_next() {
Some(ch) => {
while let Some(ch) = self.preview_next() {
if ch.is_digit(2) {
buf.push(self.next()?);
} else {
break;
}
}
None => break,
}
}
u64::from_str_radix(&buf, 2).unwrap()
} else {
let mut gone_decimal = false;
loop {
let ch = self.preview_next().unwrap_or('_');
match ch {
ch if ch.is_digit(8) => {
buf.push(ch);
let next_ch = self.preview_next().unwrap_or('_');
match next_ch {
next_ch if next_ch.is_digit(8) => {
buf.push(next_ch);
self.next()?;
}
'O' | 'o' => {
@ -371,7 +352,7 @@ impl<'a> Lexer<'a> {
}
'8' | '9' | '.' => {
gone_decimal = true;
buf.push(ch);
buf.push(next_ch);
self.next()?;
}
_ => break,
@ -379,24 +360,17 @@ impl<'a> Lexer<'a> {
}
if gone_decimal {
u64::from_str(&buf).unwrap()
} else {
if buf.is_empty() {
} else if buf.is_empty() {
0
} else {
u64::from_str_radix(&buf, 8).unwrap()
}
}
};
self.push_token(TokenData::NumericLiteral(num as f64))
}
_ if ch.is_digit(10) => {
let mut buf = ch.to_string();
'digitloop: loop {
// There might not be a next character
let ch = match self.preview_next() {
Some(c) => c,
None => break,
};
'digitloop: while let Some(ch) = self.preview_next() {
match ch {
'.' => loop {
buf.push(self.next()?);
@ -424,30 +398,26 @@ impl<'a> Lexer<'a> {
}
_ if ch.is_alphabetic() || ch == '$' || ch == '_' => {
let mut buf = ch.to_string();
loop {
let ch = match self.preview_next() {
Some(ch) => ch,
None => break,
};
match ch {
_ if ch.is_alphabetic() || ch.is_digit(10) || ch == '_' => {
while let Some(ch) = self.preview_next() {
if ch.is_alphabetic() || ch.is_digit(10) || ch == '_' {
buf.push(self.next()?);
}
_ => {
} else {
break;
}
}
}
// Match won't compare &String to &str so i need to convert it :(
let buf_compare: &str = &buf;
self.push_token(match buf_compare {
"true" => TokenData::BooleanLiteral(true),
"false" => TokenData::BooleanLiteral(false),
"null" => TokenData::NullLiteral,
slice => match FromStr::from_str(slice) {
Ok(keyword) => TokenData::Keyword(keyword),
Err(_) => TokenData::Identifier(buf.clone()),
},
slice => {
if let Ok(keyword) = FromStr::from_str(slice) {
TokenData::Keyword(keyword)
} else {
TokenData::Identifier(buf.clone())
}
}
});
// Move position forward the length of keyword
self.column_number += (buf_compare.len() - 1) as u64;
@ -476,11 +446,7 @@ impl<'a> Lexer<'a> {
'?' => self.push_punc(Punctuator::Question),
// Comments
'/' => {
let ch = match self.preview_next() {
Some(ch) => ch,
None => return Err(LexerError::new("Expecting Token /,*,=")),
};
if let Some(ch) = self.preview_next() {
let token = match ch {
// Matched comment
'/' => {
@ -498,7 +464,7 @@ impl<'a> Lexer<'a> {
buf.push('*')
}
}
ch => buf.push(ch),
next_ch => buf.push(next_ch),
}
}
TokenData::Comment(buf)
@ -507,6 +473,9 @@ impl<'a> Lexer<'a> {
_ => TokenData::Punctuator(Punctuator::Div),
};
self.push_token(token)
} else {
return Err(LexerError::new("Expecting Token /,*,="));
}
}
'*' => op!(self, Punctuator::AssignMul, Punctuator::Mul, {
'*' => vop!(self, Punctuator::AssignPow, Punctuator::Pow)

16
src/lib/syntax/parser.rs

@ -17,7 +17,7 @@ macro_rules! mk (
};
);
/// 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)]
pub enum ParseError {
/// When it expected a certain kind of token, but got another as part of something
@ -32,6 +32,7 @@ pub enum ParseError {
pub type ParseResult = Result<Expr, ParseError>;
#[derive(Debug)]
pub struct Parser {
/// The tokens being input
tokens: Vec<Token>,
@ -41,11 +42,8 @@ pub struct Parser {
impl Parser {
/// Create a new parser, using `tokens` as input
pub fn new(tokens: Vec<Token>) -> Parser {
Parser {
tokens: tokens,
pos: 0,
}
pub fn new(tokens: Vec<Token>) -> Self {
Self { tokens, pos: 0 }
}
/// Parse all expressions in the token array
@ -744,10 +742,10 @@ impl Parser {
fn expect(&mut self, tk: TokenData, routine: &'static str) -> Result<(), ParseError> {
self.pos += 1;
let curr_tk = self.get_token(self.pos - 1)?;
if curr_tk.data != tk {
Err(ParseError::Expected(vec![tk], curr_tk, routine))
} else {
if curr_tk.data == tk {
Ok(())
} else {
Err(ParseError::Expected(vec![tk], curr_tk, routine))
}
}

9
tests/js/test.js

@ -1,7 +1,4 @@
let a = 5;
let b = 6;
let c = 8;
let d = 5;
let a = "b";
let c = 5;
let res = a + d * (b - 3) + 1;
res;
c + a;
Loading…
Cancel
Save