diff --git a/.travis.yml b/.travis.yml index 92c5b3892f..698e969656 100644 --- a/.travis.yml +++ b/.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 diff --git a/src/bin/bin.rs b/src/bin/bin.rs index b9c2e8c39e..2f96e1ec86 100644 --- a/src/bin/bin.rs +++ b/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(()) } diff --git a/src/lib/environment/declerative_environment_record.rs b/src/lib/environment/declerative_environment_record.rs index 60f6e8c208..10bd6b9316 100644 --- a/src/lib/environment/declerative_environment_record.rs +++ b/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, pub outer_env: Option, } 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,39 +66,36 @@ 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) => { - match record.value { - Some(_) => { - // TODO: change this when error handling comes into play - panic!("Identifier {} has already been defined", name); - } - None => record.value = Some(value), + 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 + panic!("Identifier {} has already been defined", name); } + 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 { - // TODO: change this when error handling comes into play - panic!("TypeError: Cannot mutate an immutable binding {}", name); - } + } 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 { diff --git a/src/lib/environment/environment_record_trait.rs b/src/lib/environment/environment_record_trait.rs index 8f886ffe75..70295f453f 100644 --- a/src/lib/environment/environment_record_trait.rs +++ b/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 +//! +//! //! -//! 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 +/// /// /// 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. diff --git a/src/lib/environment/function_environment_record.rs b/src/lib/environment/function_environment_record.rs index 1ed216e229..068d3eb089 100644 --- a/src/lib/environment/function_environment_record.rs +++ b/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: 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)] +/// +#[derive(Debug, Trace, Finalize, Clone)] pub struct FunctionEnvironmentRecord { pub env_rec: HashMap, /// 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,39 +123,36 @@ 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) => { - match record.value { - Some(_) => { - // TODO: change this when error handling comes into play - panic!("Identifier {} has already been defined", name); - } - None => record.value = Some(value), + 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 + panic!("Identifier {} has already been defined", name); } + 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 { - // TODO: change this when error handling comes into play - panic!("TypeError: Cannot mutate an immutable binding {}", name); - } + } 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() { - false - } else { - true - } - } + if let BindingStatus::Lexical = self.this_binding_status { + false + } else { + !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 { diff --git a/src/lib/environment/global_environment_record.rs b/src/lib/environment/global_environment_record.rs index d3c8d89ac3..1a2257c517 100644 --- a/src/lib/environment/global_environment_record.rs +++ b/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: 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, 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,35 +53,24 @@ 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) => { - if prop.value.is_undefined() || prop.configurable { - global_object.update_prop( - name, - Some(value), - Some(true), - Some(true), - Some(deletion), - ); - } - } - None => { + 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, Some(value), @@ -90,12 +79,14 @@ impl GlobalEnvironmentRecord { 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 { diff --git a/src/lib/environment/lexical_environment.rs b/src/lib/environment/lexical_environment.rs index cec0949dba..9c6fe1d1aa 100644 --- a/src/lib/environment/lexical_environment.rs +++ b/src/lib/environment/lexical_environment.rs @@ -1,6 +1,6 @@ //! # Lexical Environment //! -//! 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>>; /// 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, } @@ -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 }))) diff --git a/src/lib/environment/object_environment_record.rs b/src/lib/environment/object_environment_record.rs index 3922f0a6dc..ac9afb412c 100644 --- a/src/lib/environment/object_environment_record.rs +++ b/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; + fn has_binding(&self, name: &str) -> bool { + if self.bindings.has_field(name) { + if self.with_environment { + // TODO: implement unscopables + } + true + } else { + false } - if !self.with_environment { - return true; - } - - // TODO: implement unscopables - true } 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) } - - // 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 { - 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 } } } diff --git a/src/lib/exec.rs b/src/lib/exec.rs index 0a6a836ae0..84a773aa63 100644 --- a/src/lib/exec.rs +++ b/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)) } diff --git a/src/lib/js/array.rs b/src/lib/js/array.rs index 47c5add4e4..f77dcc0a26 100644 --- a/src/lib/js/array.rs +++ b/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) -> ResultValue { let array_obj_ptr = array_obj.clone(); @@ -14,11 +14,10 @@ fn create_array_object(array_obj: Value, array_contents: Vec) -> 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) -> ResultValue pub fn make_array(this: Value, _: Value, args: Vec) -> 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) -> 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 +/// pub fn concat(this: Value, _: Value, args: Vec) -> 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) -> 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) -> 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 +/// pub fn push(this: Value, _: Value, args: Vec) -> 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) -> 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 +/// pub fn pop(this: Value, _: Value, _: Vec) -> 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) -> 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) -> 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 +/// pub fn join(this: Value, _: Value, args: Vec) -> 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 = 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)); diff --git a/src/lib/js/console.rs b/src/lib/js/console.rs index 8e5c3445d9..5fe02cfa53 100644 --- a/src/lib/js/console.rs +++ b/src/lib/js/console.rs @@ -7,39 +7,36 @@ use std::fmt::Write; use std::iter::FromIterator; /// Print a javascript value to the standard output stream -/// https://console.spec.whatwg.org/#logger +/// pub fn log(_: Value, _: Value, args: Vec) -> ResultValue { let args: Vec = 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, _)) => { - for (key, val) in v.borrow().iter() { - // Don't print prototype properties - if key == INSTANCE_PROTOTYPE { - continue; - } - write!(s, "{}: {}", key, val.value.clone()).unwrap(); - if key != last_key { - write!(s, "{}", ", ").unwrap(); - } + 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 { + continue; + } + write!(s, "{}: {}", key, val.value.clone()).unwrap(); + if key != last_key { + write!(s, ", ").unwrap(); } } - None => (), } - write!(s, "{}", "}").unwrap(); + write!(s, "}}").unwrap(); s } _ => from_value::(x.clone()).unwrap(), - }; + } // from_value::(x.clone()).unwrap() })); diff --git a/src/lib/js/error.rs b/src/lib/js/error.rs index 71cd304208..d2087f4a9e 100644 --- a/src/lib/js/error.rs +++ b/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) -> 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)) diff --git a/src/lib/js/function.rs b/src/lib/js/function.rs index df5748f62f..e7b66dc6c2 100644 --- a/src/lib/js/function.rs +++ b/src/lib/js/function.rs @@ -9,7 +9,7 @@ pub type NativeFunctionData = fn(Value, Value, Vec) -> 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 +/// /// 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) -> RegularFunction { - let mut obj = HashMap::new(); - obj.insert( + pub fn new(expr: Expr, args: Vec) -> 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 } } } diff --git a/src/lib/js/json.rs b/src/lib/js/json.rs index 01251d3ce6..1755d0e5e7 100644 --- a/src/lib/js/json.rs +++ b/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 +/// 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 +/// pub fn parse(_: Value, _: Value, args: Vec) -> ResultValue { match serde_json::from_str::(&args.get(0).unwrap().clone().to_string()) { Ok(json) => Ok(to_value(json)), diff --git a/src/lib/js/math.rs b/src/lib/js/math.rs index 59fadb33c1..21489242e0 100644 --- a/src/lib/js/math.rs +++ b/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) -> ResultValue { - Ok(to_value(if args.len() >= 1 { + Ok(to_value(if args.is_empty() { + f64::NAN + } else { from_value::(args.get(0).unwrap().clone()) .unwrap() .abs() - } else { - f64::NAN })) } /// Get the arccos of a number pub fn acos(_: Value, _: Value, args: Vec) -> ResultValue { - Ok(to_value(if args.len() >= 1 { + Ok(to_value(if args.is_empty() { + f64::NAN + } else { from_value::(args.get(0).unwrap().clone()) .unwrap() .acos() - } else { - f64::NAN })) } /// Get the arcsine of a number pub fn asin(_: Value, _: Value, args: Vec) -> ResultValue { - Ok(to_value(if args.len() >= 1 { + Ok(to_value(if args.is_empty() { + f64::NAN + } else { from_value::(args.get(0).unwrap().clone()) .unwrap() .asin() - } else { - f64::NAN })) } /// Get the arctangent of a number pub fn atan(_: Value, _: Value, args: Vec) -> ResultValue { - Ok(to_value(if args.len() >= 1 { + Ok(to_value(if args.is_empty() { + f64::NAN + } else { from_value::(args.get(0).unwrap().clone()) .unwrap() .atan() - } else { - f64::NAN })) } /// Get the arctangent of a numbers pub fn atan2(_: Value, _: Value, args: Vec) -> ResultValue { - Ok(to_value(if args.len() >= 1 { + Ok(to_value(if args.is_empty() { + f64::NAN + } else { from_value::(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) -> ResultValue { - Ok(to_value(if args.len() >= 1 { + Ok(to_value(if args.is_empty() { + f64::NAN + } else { from_value::(args.get(0).unwrap().clone()) .unwrap() .cbrt() - } else { - f64::NAN })) } /// Get lowest integer above a number pub fn ceil(_: Value, _: Value, args: Vec) -> ResultValue { - Ok(to_value(if args.len() >= 1 { + Ok(to_value(if args.is_empty() { + f64::NAN + } else { from_value::(args.get(0).unwrap().clone()) .unwrap() .ceil() - } else { - f64::NAN })) } /// Get the cosine of a number pub fn cos(_: Value, _: Value, args: Vec) -> ResultValue { - Ok(to_value(if args.len() >= 1 { + Ok(to_value(if args.is_empty() { + f64::NAN + } else { from_value::(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) -> ResultValue { - Ok(to_value(if args.len() >= 1 { + Ok(to_value(if args.is_empty() { + f64::NAN + } else { from_value::(args.get(0).unwrap().clone()) .unwrap() .exp() - } else { - f64::NAN })) } /// Get the highest integer below a number pub fn floor(_: Value, _: Value, args: Vec) -> ResultValue { - Ok(to_value(if args.len() >= 1 { + Ok(to_value(if args.is_empty() { + f64::NAN + } else { from_value::(args.get(0).unwrap().clone()) .unwrap() .floor() - } else { - f64::NAN })) } /// Get the natural logarithm of a number pub fn log(_: Value, _: Value, args: Vec) -> ResultValue { - Ok(to_value(if args.len() >= 1 { + Ok(to_value(if args.is_empty() { + f64::NAN + } else { from_value::(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) -> 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) -> ResultValue { /// Get the minimum of several numbers pub fn min(_: Value, _: Value, args: Vec) -> 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) -> ResultValue { } /// Round a number to the nearest integer pub fn round(_: Value, _: Value, args: Vec) -> ResultValue { - Ok(to_value(if args.len() >= 1 { + Ok(to_value(if args.is_empty() { + f64::NAN + } else { from_value::(args.get(0).unwrap().clone()) .unwrap() .round() - } else { - f64::NAN })) } /// Get the sine of a number pub fn sin(_: Value, _: Value, args: Vec) -> ResultValue { - Ok(to_value(if args.len() >= 1 { + Ok(to_value(if args.is_empty() { + f64::NAN + } else { from_value::(args.get(0).unwrap().clone()) .unwrap() .sin() - } else { - f64::NAN })) } /// Get the square root of a number pub fn sqrt(_: Value, _: Value, args: Vec) -> ResultValue { - Ok(to_value(if args.len() >= 1 { + Ok(to_value(if args.is_empty() { + f64::NAN + } else { from_value::(args.get(0).unwrap().clone()) .unwrap() .sqrt() - } else { - f64::NAN })) } /// Get the tangent of a number pub fn tan(_: Value, _: Value, args: Vec) -> ResultValue { - Ok(to_value(if args.len() >= 1 { + Ok(to_value(if args.is_empty() { + f64::NAN + } else { from_value::(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)); diff --git a/src/lib/js/object.rs b/src/lib/js/object.rs index bd3e5415c8..3578ff013c 100644 --- a/src/lib/js/object.rs +++ b/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 { - Ok(Property { + fn from_value(v: Value) -> Result { + 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) -> ResultValue { /// Check if it has a property pub fn has_own_prop(this: Value, _: Value, args: Vec) -> ResultValue { - let prop = if args.len() == 0 { + let prop = if args.is_empty() { None } else { from_value::(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", diff --git a/src/lib/js/string.rs b/src/lib/js/string.rs index 974f736f29..5d536fdeaf 100644 --- a/src/lib/js/string.rs +++ b/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 +/// // This gets called when a new String() is created, it's called by exec:346 pub fn make_string(this: Value, _: Value, args: Vec) -> 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) -> ResultValue { /// Get a string's length pub fn get_string_length(this: Value, _: Value, _: Vec) -> 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.len() as i32)) } /// Get the string value to a primitive string pub fn to_string(this: Value, _: Value, _: Vec) -> 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. +/// pub fn char_at(this: Value, _: Value, args: Vec) -> 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::new())); } - Ok(to_value::(primitive_val.chars().nth(pos).unwrap())) + Ok(to_value::( + 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. +/// pub fn char_code_at(this: Value, _: Value, args: Vec) -> 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 +/// pub fn concat(this: Value, _: Value, args: Vec) -> 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) -> 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 +/// pub fn repeat(this: Value, _: Value, args: Vec) -> 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) -> 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 +/// pub fn slice(this: Value, _: Value, args: Vec) -> 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) -> 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 +/// pub fn starts_with(this: Value, _: Value, args: Vec) -> 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) -> 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 +/// pub fn ends_with(this: Value, _: Value, args: Vec) -> 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) -> 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 +/// pub fn includes(this: Value, _: Value, args: Vec) -> 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) -> 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 +/// pub fn index_of(this: Value, _: Value, args: Vec) -> 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) -> 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) -> 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 +/// pub fn last_index_of(this: Value, _: Value, args: Vec) -> 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) -> 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) -> 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) -> 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))) } diff --git a/src/lib/js/value.rs b/src/lib/js/value.rs index 177a6decde..e5e8aea715 100644 --- a/src/lib/js/value.rs +++ b/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 { + pub fn get_prop(&self, field: &str) -> Option { // 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, enumerable: Option, writable: Option, @@ -252,27 +251,20 @@ impl ValueData { _ => None, }; - if obj.is_none() { - return (); - } - - let mut hashmap = obj.unwrap(); - // 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(mut hashmap) = obj { + // Use value, or walk up the prototype chain + 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 { + pub fn get_private_prop(&self, field: &str) -> Option { 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, _)) => { - for (key, val) in v.borrow().iter() { - write!(f, "{}: {}", key, val.value.clone())?; - if key != last_key { - write!(f, "{}", ", ")?; - } + 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, ", ")?; } } - None => (), }; // Print private properties - match p.borrow().iter().last() { - Some((last_key, _)) => { - for (key, val) in p.borrow().iter() { - write!(f, "(Private) {}: {}", key, val.value.clone())?; - if key != last_key { - write!(f, "{}", ", ")?; - } + 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, ", ")?; } } - 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()) - } - (_, _) => ValueData::Number(self.to_num() + other.to_num()), - }; + 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()), + } } } 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 { + fn from_value(v: Value) -> Result { Ok(v.to_string()) } } @@ -670,7 +645,7 @@ impl ToValue for char { } } impl FromValue for char { - fn from_value(v: Value) -> Result { + fn from_value(v: Value) -> Result { 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 { + fn from_value(v: Value) -> Result { Ok(v.to_num()) } } @@ -692,7 +667,7 @@ impl ToValue for i32 { } } impl FromValue for i32 { - fn from_value(v: Value) -> Result { + fn from_value(v: Value) -> Result { Ok(v.to_int()) } } @@ -703,7 +678,7 @@ impl ToValue for usize { } } impl FromValue for usize { - fn from_value(v: Value) -> Result { + fn from_value(v: Value) -> Result { Ok(v.to_int() as usize) } } @@ -714,7 +689,7 @@ impl ToValue for bool { } } impl FromValue for bool { - fn from_value(v: Value) -> Result { + fn from_value(v: Value) -> Result { 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 ToValue for Vec { 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 FromValue for Vec { - fn from_value(v: Value) -> Result, &'static str> { + fn from_value(v: Value) -> Result { 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 FromValue for Vec { 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 { + fn from_value(v: Value) -> Result { 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 { + fn from_value(v: Value) -> Result { Ok(v.to_json()) } } @@ -808,7 +779,7 @@ impl ToValue for Option { } } impl FromValue for Option { - fn from_value(value: Value) -> Result, &'static str> { + fn from_value(value: Value) -> Result { 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 { + fn from_value(v: Value) -> Result { 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(v: Value) -> Result { 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(v: A) -> Value { v.to_value() } diff --git a/src/lib/lib.rs b/src/lib/lib.rs index e75f9419b1..01b4a324c2 100644 --- a/src/lib/lib.rs +++ b/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()) } } } diff --git a/src/lib/syntax/ast/constant.rs b/src/lib/syntax/ast/constant.rs index 175739c5fe..eb06b89839 100644 --- a/src/lib/syntax/ast/constant.rs +++ b/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"), - }; + } } } diff --git a/src/lib/syntax/ast/expr.rs b/src/lib/syntax/ast/expr.rs index 06ed1d8822..9dabe12878 100644 --- a/src/lib/syntax/ast/expr.rs +++ b/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 = args.iter().map(|arg| arg.to_string()).collect(); + let arg_strs: Vec = 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) -> 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 { diff --git a/src/lib/syntax/ast/keyword.rs b/src/lib/syntax/ast/keyword.rs index 7bfbef45a4..5d3b8f4459 100644 --- a/src/lib/syntax/ast/keyword.rs +++ b/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 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 { + fn from_str(s: &str) -> Result { 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", } ) } diff --git a/src/lib/syntax/ast/pos.rs b/src/lib/syntax/ast/pos.rs index 9f41c55089..47f7047276 100644 --- a/src/lib/syntax/ast/pos.rs +++ b/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, } } } diff --git a/src/lib/syntax/ast/punc.rs b/src/lib/syntax/ast/punc.rs index 83f901d629..c471c98a39 100644 --- a/src/lib/syntax/ast/punc.rs +++ b/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 { /// `+` diff --git a/src/lib/syntax/ast/token.rs b/src/lib/syntax/ast/token.rs index 928b322168..7e6565a56f 100644 --- a/src/lib/syntax/ast/token.rs +++ b/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), } } } diff --git a/src/lib/syntax/lexer.rs b/src/lib/syntax/lexer.rs index 3994fa0361..c6c08fe9da 100644 --- a/src/lib/syntax/lexer.rs +++ b/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, @@ -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 { 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 { - // 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,11 +200,8 @@ 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 => { - return Ok(()); - } + if self.preview_next().is_none() { + return Ok(()); } self.column_number += 1; let ch = self.next()?; @@ -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) => { - if ch.is_digit(16) { - buf.push(self.next()?); - } else { - break; - } - } - None => break, - }; + while let Some(ch) = self.preview_next() { + if ch.is_digit(16) { + buf.push(self.next()?); + } else { + break; + } } u64::from_str_radix(&buf, 16).unwrap() } else if self.next_is('b') { - loop { - match self.preview_next() { - Some(ch) => { - if ch.is_digit(2) { - buf.push(self.next()?); - } else { - break; - } - } - None => break, + while let Some(ch) = self.preview_next() { + if ch.is_digit(2) { + buf.push(self.next()?); + } else { + 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() { + 0 } else { - if buf.is_empty() { - 0 - } else { - u64::from_str_radix(&buf, 8).unwrap() - } + 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,18 +398,11 @@ 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 == '_' => { - buf.push(self.next()?); - } - _ => { - break; - } + 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 :( @@ -444,10 +411,13 @@ impl<'a> Lexer<'a> { "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,37 +446,36 @@ 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 /,*,=")), - }; - - let token = match ch { - // Matched comment - '/' => { - let comment = self.read_line()?; - TokenData::Comment(comment) - } - '*' => { - let mut buf = String::new(); - loop { - match self.next()? { - '*' => { - if self.next_is('/') { - break; - } else { - buf.push('*') + if let Some(ch) = self.preview_next() { + let token = match ch { + // Matched comment + '/' => { + let comment = self.read_line()?; + TokenData::Comment(comment) + } + '*' => { + let mut buf = String::new(); + loop { + match self.next()? { + '*' => { + if self.next_is('/') { + break; + } else { + buf.push('*') + } } + next_ch => buf.push(next_ch), } - ch => buf.push(ch), } + TokenData::Comment(buf) } - TokenData::Comment(buf) - } - '=' => TokenData::Punctuator(Punctuator::AssignDiv), - _ => TokenData::Punctuator(Punctuator::Div), - }; - self.push_token(token) + '=' => TokenData::Punctuator(Punctuator::AssignDiv), + _ => 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) diff --git a/src/lib/syntax/parser.rs b/src/lib/syntax/parser.rs index e360332b3f..0d23f99fd4 100644 --- a/src/lib/syntax/parser.rs +++ b/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; +#[derive(Debug)] pub struct Parser { /// The tokens being input tokens: Vec, @@ -41,11 +42,8 @@ pub struct Parser { impl Parser { /// Create a new parser, using `tokens` as input - pub fn new(tokens: Vec) -> Parser { - Parser { - tokens: tokens, - pos: 0, - } + pub fn new(tokens: Vec) -> 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)) } } diff --git a/tests/js/test.js b/tests/js/test.js index aeb6aea8de..cd873a69ab 100644 --- a/tests/js/test.js +++ b/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; \ No newline at end of file