├── .gitignore ├── .gitmodules ├── .project ├── Cargo.lock ├── Cargo.toml ├── README.md ├── ecma-262 5.1.txt ├── src ├── contrib │ ├── mod.rs │ └── test262.rs ├── debug.rs ├── errors.rs ├── gc │ ├── handles │ │ ├── array.rs │ │ ├── array_local.rs │ │ ├── array_root.rs │ │ ├── local.rs │ │ ├── mod.rs │ │ ├── ptr.rs │ │ └── root.rs │ ├── mod.rs │ ├── os.rs │ └── strategy │ │ ├── copying.rs │ │ └── mod.rs ├── ir │ ├── builder.rs │ └── mod.rs ├── lib.rs ├── main.rs ├── rt │ ├── allocators.rs │ ├── boolean.rs │ ├── env │ │ ├── array.rs │ │ ├── boolean.rs │ │ ├── console.rs │ │ ├── date.rs │ │ ├── error.rs │ │ ├── function.rs │ │ ├── global.rs │ │ ├── json │ │ │ ├── lexer.rs │ │ │ ├── mod.rs │ │ │ ├── parser.rs │ │ │ └── writer.rs │ │ ├── math.rs │ │ ├── mod.rs │ │ ├── number.rs │ │ ├── object.rs │ │ ├── regexp.rs │ │ └── string │ │ │ ├── mod.rs │ │ │ └── replacer.rs │ ├── fmt │ │ ├── bigint.rs │ │ └── mod.rs │ ├── interpreter.rs │ ├── iterator.rs │ ├── mod.rs │ ├── null.rs │ ├── number.rs │ ├── object │ │ ├── array_store.rs │ │ ├── hash_store.rs │ │ ├── mod.rs │ │ └── sparse_array.rs │ ├── regexp.rs │ ├── runtime.rs │ ├── scope.rs │ ├── stack.rs │ ├── string.rs │ ├── undefined.rs │ ├── utf.rs │ ├── value.rs │ └── walker.rs ├── syntax │ ├── ast │ │ ├── mod.rs │ │ └── visitor.rs │ ├── lexer.rs │ ├── mod.rs │ ├── parser │ │ ├── locals │ │ │ ├── block_walker.rs │ │ │ └── mod.rs │ │ └── mod.rs │ ├── reader.rs │ └── token.rs ├── trace.rs └── util │ ├── interner.rs │ ├── iter.rs │ ├── manualbox.rs │ ├── matchers.rs │ └── mod.rs ├── test.bat ├── test262-ignore.json └── tests └── tc39.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /gdbscript.txt 3 | /succeeded 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tests/tc39"] 2 | path = tests/tc39 3 | url = https://github.com/tc39/test262 4 | branch = master 5 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | rjs 4 | 5 | 6 | 7 | 8 | 9 | com.github.rustdt.ide.core.Builder 10 | 11 | 12 | 13 | 14 | 15 | com.github.rustdt.ide.core.nature 16 | 17 | 18 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "rjs" 3 | version = "0.0.1" 4 | dependencies = [ 5 | "chrono 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", 6 | "lazy_static 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 7 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 8 | "rand 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 9 | "regex 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", 10 | "rustc-serialize 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 11 | "strtod 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 12 | "time 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", 13 | ] 14 | 15 | [[package]] 16 | name = "aho-corasick" 17 | version = "0.2.1" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | dependencies = [ 20 | "memchr 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 21 | ] 22 | 23 | [[package]] 24 | name = "chrono" 25 | version = "0.2.14" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | dependencies = [ 28 | "num 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", 29 | "time 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", 30 | ] 31 | 32 | [[package]] 33 | name = "gcc" 34 | version = "0.3.8" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | 37 | [[package]] 38 | name = "lazy_static" 39 | version = "0.1.11" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | 42 | [[package]] 43 | name = "libc" 44 | version = "0.1.8" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | 47 | [[package]] 48 | name = "memchr" 49 | version = "0.1.3" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | dependencies = [ 52 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 53 | ] 54 | 55 | [[package]] 56 | name = "num" 57 | version = "0.1.25" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | dependencies = [ 60 | "rand 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 61 | "rustc-serialize 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 62 | ] 63 | 64 | [[package]] 65 | name = "rand" 66 | version = "0.3.8" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | dependencies = [ 69 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 70 | ] 71 | 72 | [[package]] 73 | name = "regex" 74 | version = "0.1.38" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | dependencies = [ 77 | "aho-corasick 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 78 | "memchr 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 79 | "regex-syntax 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 80 | ] 81 | 82 | [[package]] 83 | name = "regex-syntax" 84 | version = "0.1.2" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | 87 | [[package]] 88 | name = "rustc-serialize" 89 | version = "0.3.15" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | 92 | [[package]] 93 | name = "strtod" 94 | version = "0.0.1" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | 97 | [[package]] 98 | name = "time" 99 | version = "0.1.26" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | dependencies = [ 102 | "gcc 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 103 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 104 | ] 105 | 106 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "rjs" 4 | version = "0.0.1" 5 | authors = [ "Pieter van Ginkel " ] 6 | 7 | [features] 8 | 9 | trace = [] 10 | tracegc = [] 11 | 12 | [[bin]] 13 | 14 | name = "rjs" 15 | path = "src/main.rs" 16 | doc = false 17 | 18 | [lib] 19 | 20 | name = "rjs" 21 | path = "src/lib.rs" 22 | 23 | [dependencies] 24 | 25 | libc = "0.1" 26 | lazy_static = "0.1.10" 27 | rustc-serialize = "0.3" 28 | chrono = "0.2.14" 29 | time = "0.1.26" 30 | rand = "0.3.8" 31 | strtod = "0.0.1" 32 | regex = "0.1.38" 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript on Rust 2 | 3 | `rjs` is a native JavaScript implementation in Rust. 4 | 5 | ## The project 6 | 7 | The goal of `rjs` is to provide a fast, native JavaScript implementation in Rust. 8 | Currently `rjs` is in alpha which means that the performance and stability is 9 | not representative of where we want to end up, and that the public API may still 10 | change. See the [roadmap](https://github.com/rust-js/rjs/wiki/Roadmap) for a detailed 11 | description of the current state of the project and the end goals. 12 | 13 | ## Contributing 14 | 15 | Contribution can be done by creating a pull request. Pull requests for small issues 16 | are always welcome. If you want to pick up a larger feature, please contact 17 | the maintainer to get started. 18 | 19 | ## Documentation 20 | 21 | The documentation can be found at http://rust-js.github.io/rjs/rjs/. This is still 22 | work in progress, so a lot of information is still missing. 23 | 24 | ## License 25 | 26 | `rjs` is licensed under the Apache 2.0 license. 27 | -------------------------------------------------------------------------------- /src/contrib/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod test262; 2 | -------------------------------------------------------------------------------- /src/debug.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::sync::Mutex; 3 | 4 | static mut DEBUG_ENABLED : bool = false; 5 | 6 | lazy_static! { 7 | static ref DEBUG_OUT : Mutex> = Mutex::new(RefCell::new(String::new())); 8 | } 9 | 10 | macro_rules! debugln { 11 | ($fmt:expr) => (debug!(concat!($fmt, "\n"))); 12 | ($fmt:expr, $($arg:tt)*) => (debug!(concat!($fmt, "\n"), $($arg)*)); 13 | } 14 | 15 | macro_rules! debug { 16 | ($fmt:expr) => ( if cfg!(not(ndebug)) { $crate::debug::debug_write($fmt); }); 17 | ($fmt:expr, $($arg:tt)*) => ( if cfg!(not(ndebug)) { $crate::debug::debug_write(&format!($fmt, $($arg)*)); }) 18 | } 19 | 20 | pub fn debug_write(string: &str) { 21 | if !unsafe { DEBUG_ENABLED } { 22 | return; 23 | } 24 | 25 | let guard = DEBUG_OUT.lock(); 26 | let lock = guard.unwrap(); 27 | lock.borrow_mut().push_str(string); 28 | } 29 | 30 | pub fn debug_enable(enabled: bool) { 31 | unsafe { DEBUG_ENABLED = enabled }; 32 | } 33 | 34 | pub fn reset() -> String { 35 | let guard = DEBUG_OUT.lock(); 36 | let lock = guard.unwrap(); 37 | 38 | let mut string = lock.borrow_mut(); 39 | let result = string.clone(); 40 | *string = String::new(); 41 | 42 | result 43 | } 44 | 45 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | pub static TYPE_NOT_A_FUNCTION : &'static str = "Value is not a function"; 2 | pub static TYPE_MISSING_ARGUMENT : &'static str = "Missing argument"; 3 | pub static TYPE_EXPECTED_ARRAY_ITEM : &'static str = "Expected at least one array item"; 4 | pub static TYPE_INVALID : &'static str = "Unexpected type"; 5 | pub static TYPE_CANNOT_PUT : &'static str = "Cannot set property"; 6 | pub static TYPE_CANNOT_HAS_INSTANCE : &'static str = "Instance of parameter must be an object"; 7 | pub static TYPE_ACCESSOR_NOT_CALLABLE : &'static str = "Accessor is not callable"; 8 | pub static TYPE_WRITABLE_VALUE_INVALID_ON_ACCESSOR : &'static str = "Writable and value invalid for accessors"; 9 | pub static TYPE_NOT_EXTENSIBLE : &'static str = "Object is not extensible"; 10 | pub static TYPE_CANNOT_WRITE : &'static str = "Property is not writable"; 11 | pub static TYPE_CANNOT_DELETE : &'static str = "Property cannot be deleted"; 12 | pub static TYPE_NOT_A_CONSTRUCTOR : &'static str = "Not a constructor"; 13 | pub static TYPE_UNDEFINED : &'static str = "Invalid operation on undefined"; 14 | pub static TYPE_NULL : &'static str = "Invalid operation on null"; 15 | pub static TYPE_IN_RHS_NOT_OBJECT : &'static str = "Target of in must be an object"; 16 | pub static TYPE_CANNOT_ACCESS_ARGUMENTS_PROPERTY : &'static str = "Cannot access caller or callee of arguments"; 17 | pub static TYPE_CANNOT_ACCESS_FUNCTION_PROPERTY : &'static str = "Cannot access caller or arguments of function"; 18 | pub static TYPE_CANNOT_CALL_TO_STRING : &'static str = "Cannot call toString"; 19 | pub static SYNTAX_CANNOT_RESOLVE_PROPERTY : &'static str = "Cannot resolve property"; 20 | pub static TYPE_NOT_CALLABLE : &'static str = "Target must be callable"; 21 | pub static TYPE_PROPERTY_ONLY_HAS_GETTER : &'static str = "Cannot set property because it only has a getter"; 22 | pub static TYPE_NOT_DATE : &'static str = "Object is not a Date"; 23 | pub static TYPE_NOT_COERCIBLE : &'static str = "Value is null or undefined"; 24 | pub static TYPE_INVALID_ARGUMENTS_ARRAY : &'static str = "Arguments argument is not of a valid type"; 25 | pub static TYPE_CYCLICAL_REFERENCE : &'static str = "Cannot stringify object structure with cyclical references"; 26 | pub static TYPE_INVALID_REGEXP_ARGS : &'static str = "Flags cannot be combined with a RegExp object"; 27 | pub static SYNTAX_INVALID_REGEXP_FLAGS : &'static str = "Invalid flags"; 28 | pub static SYNTAX_INVALID_REGEX : &'static str = "Invalid regular expression"; 29 | -------------------------------------------------------------------------------- /src/gc/handles/array.rs: -------------------------------------------------------------------------------- 1 | use gc::{ArrayLocal, GcAllocator, ptr_t}; 2 | use std::ops::{Deref, DerefMut, Index, IndexMut}; 3 | use std::marker::PhantomData; 4 | use std::ptr; 5 | use std::mem::{size_of, transmute}; 6 | use std::slice; 7 | use std::fmt; 8 | 9 | pub struct Array { 10 | ptr: ptr_t, 11 | _type: PhantomData 12 | } 13 | 14 | impl Array { 15 | pub fn ptr(&self) -> ptr_t { 16 | self.ptr 17 | } 18 | 19 | pub fn from_ptr(ptr: ptr_t) -> Array { 20 | Array { 21 | ptr: ptr, 22 | _type: PhantomData 23 | } 24 | } 25 | 26 | pub fn null() -> Array { 27 | Self::from_ptr(ptr::null()) 28 | } 29 | 30 | pub fn is_null(&self) -> bool { 31 | self.ptr.is_null() 32 | } 33 | 34 | pub fn len(&self) -> usize { 35 | unsafe { *transmute::<_, *const usize>(self.ptr) } 36 | } 37 | 38 | pub fn as_local(&self, allocator: &U) -> ArrayLocal { 39 | allocator.alloc_array_local_from_array(*self) 40 | } 41 | } 42 | 43 | impl Copy for Array {} 44 | 45 | impl Clone for Array { 46 | fn clone(&self) -> Array { 47 | Self::from_ptr(self.ptr) 48 | } 49 | } 50 | 51 | impl fmt::Debug for Array { 52 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 53 | write!(fmt, "Ptr {{ ptr: {:?} }}", self.ptr) 54 | } 55 | } 56 | 57 | impl Array { 58 | pub fn copy(from: Array, from_offset: usize, to: Array, to_offset: usize, count: usize) { 59 | unsafe { 60 | assert!(from_offset + count <= from.len() && to_offset + count <= to.len()); 61 | 62 | ptr::copy( 63 | from.ptr.offset((size_of::() + from_offset * size_of::()) as isize), 64 | transmute(to.ptr.offset((size_of::() + to_offset * size_of::()) as isize)), 65 | count * size_of::() 66 | ); 67 | } 68 | } 69 | } 70 | 71 | impl Deref for Array { 72 | type Target = [T]; 73 | 74 | fn deref(&self) -> &[T] { 75 | unsafe { 76 | let size = *transmute::<_, *const usize>(self.ptr); 77 | let ptr = self.ptr.offset(size_of::() as isize); 78 | 79 | slice::from_raw_parts( 80 | transmute(ptr), 81 | size 82 | ) 83 | } 84 | } 85 | } 86 | 87 | impl DerefMut for Array { 88 | fn deref_mut(&mut self) -> &mut [T] { 89 | unsafe { 90 | let size = *transmute::<_, *const usize>(self.ptr); 91 | let ptr = self.ptr.offset(size_of::() as isize); 92 | 93 | slice::from_raw_parts_mut( 94 | transmute(ptr), 95 | size 96 | ) 97 | } 98 | } 99 | } 100 | 101 | impl Index for Array { 102 | type Output = T; 103 | 104 | fn index(&self, index: usize) -> &>::Output { 105 | assert!(index < self.len()); 106 | 107 | unsafe { 108 | let ptr = self.ptr.offset(size_of::() as isize); 109 | let ptr = transmute::<_, *const T>(ptr); 110 | let ptr = ptr.offset(index as isize); 111 | 112 | transmute::<_, &T>(ptr) 113 | } 114 | } 115 | } 116 | 117 | impl IndexMut for Array { 118 | fn index_mut(&mut self, index: usize) -> &mut >::Output { 119 | assert!(index < self.len()); 120 | 121 | unsafe { 122 | let ptr = self.ptr.offset(size_of::() as isize); 123 | let ptr = transmute::<_, *const T>(ptr); 124 | let ptr = ptr.offset(index as isize); 125 | 126 | transmute::<_, &mut T>(ptr) 127 | } 128 | } 129 | } 130 | 131 | pub trait AsArray { 132 | fn as_ptr(&self) -> Array; 133 | } 134 | 135 | impl AsArray for Array { 136 | fn as_ptr(&self) -> Array { 137 | *self 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/gc/handles/array_local.rs: -------------------------------------------------------------------------------- 1 | use gc::{Array, AsArray, ArrayRoot, GcAllocator}; 2 | use std::ops::{Deref, DerefMut}; 3 | 4 | pub struct ArrayLocal { 5 | handle: *const Array 6 | } 7 | 8 | impl ArrayLocal { 9 | pub unsafe fn new(handle: *const Array) -> ArrayLocal { 10 | ArrayLocal { 11 | handle: handle 12 | } 13 | } 14 | 15 | pub fn as_root(&self, allocator: &U) -> ArrayRoot { 16 | allocator.alloc_array_root_from_local(*self) 17 | } 18 | } 19 | 20 | impl Copy for ArrayLocal { } 21 | 22 | impl Clone for ArrayLocal { 23 | fn clone(&self) -> ArrayLocal { 24 | ArrayLocal { 25 | handle: self.handle 26 | } 27 | } 28 | } 29 | 30 | impl Deref for ArrayLocal { 31 | type Target = [T]; 32 | 33 | fn deref(&self) -> &[T] { 34 | unsafe { &**self.handle } 35 | } 36 | } 37 | 38 | impl DerefMut for ArrayLocal { 39 | fn deref_mut(&mut self) -> &mut [T] { 40 | unsafe { &mut **(self.handle as *mut Array) } 41 | } 42 | } 43 | 44 | impl AsArray for ArrayLocal { 45 | fn as_ptr(&self) -> Array { 46 | unsafe { *self.handle } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/gc/handles/array_root.rs: -------------------------------------------------------------------------------- 1 | use gc::{Array, ArrayLocal, RootHandles, GcHeap, AsArray, AsPtr, GcAllocator}; 2 | use std::ops::{Deref, DerefMut}; 3 | use std::marker::PhantomData; 4 | use std::mem::{size_of, transmute}; 5 | use std::slice; 6 | use std::rc::Rc; 7 | 8 | pub struct ArrayRoot { 9 | handles: Rc, 10 | handle: u32, 11 | _type: PhantomData 12 | } 13 | 14 | impl<'a, T> ArrayRoot { 15 | pub unsafe fn new>(heap: &'a GcHeap, ptr: U) -> ArrayRoot { 16 | ArrayRoot { 17 | handles: heap.handles.clone(), 18 | handle: heap.handles.add(ptr.as_ptr().ptr()), 19 | _type: PhantomData 20 | } 21 | } 22 | 23 | pub fn as_local(&self, allocator: &U) -> ArrayLocal { 24 | allocator.alloc_array_local_from_root(self) 25 | } 26 | } 27 | 28 | impl Deref for ArrayRoot { 29 | type Target = [T]; 30 | 31 | fn deref(&self) -> &[T] { 32 | unsafe { 33 | let ptr = self.handles.get_target(self.handle); 34 | let size = *transmute::<_, *const usize>(ptr); 35 | let ptr = ptr.offset(size_of::() as isize); 36 | 37 | slice::from_raw_parts( 38 | transmute(ptr), 39 | size 40 | ) 41 | } 42 | } 43 | } 44 | 45 | impl DerefMut for ArrayRoot { 46 | fn deref_mut(&mut self) -> &mut [T] { 47 | unsafe { 48 | let ptr = self.handles.get_target(self.handle); 49 | let size = *transmute::<_, *const usize>(ptr); 50 | let ptr = ptr.offset(size_of::() as isize); 51 | 52 | slice::from_raw_parts_mut( 53 | transmute(ptr), 54 | size 55 | ) 56 | } 57 | } 58 | } 59 | 60 | impl Clone for ArrayRoot { 61 | fn clone(&self) -> ArrayRoot { 62 | ArrayRoot { 63 | handles: self.handles.clone(), 64 | handle: self.handles.clone_root(self.handle), 65 | _type: PhantomData 66 | } 67 | } 68 | } 69 | 70 | impl Drop for ArrayRoot { 71 | fn drop(&mut self) { 72 | self.handles.remove(self.handle); 73 | } 74 | } 75 | 76 | impl AsArray for ArrayRoot { 77 | fn as_ptr(&self) -> Array { 78 | unsafe { Array::from_ptr(self.handles.get_target(self.handle)) } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/gc/handles/local.rs: -------------------------------------------------------------------------------- 1 | use gc::{Ptr, Root, AsPtr, GcAllocator}; 2 | use std::ops::{Deref, DerefMut}; 3 | 4 | pub struct Local { 5 | handle: *const Ptr 6 | } 7 | 8 | impl Local { 9 | pub unsafe fn new(handle: *const Ptr) -> Local { 10 | Local { 11 | handle: handle 12 | } 13 | } 14 | 15 | pub fn as_root(&self, allocator: &U) -> Root { 16 | allocator.alloc_root_from_local(*self) 17 | } 18 | } 19 | 20 | impl Copy for Local {} 21 | 22 | impl Clone for Local { 23 | fn clone(&self) -> Local { 24 | Local { 25 | handle: self.handle 26 | } 27 | } 28 | } 29 | 30 | impl Deref for Local { 31 | type Target = T; 32 | 33 | fn deref(&self) -> &T { 34 | unsafe { &**self.handle } 35 | } 36 | } 37 | 38 | impl DerefMut for Local { 39 | fn deref_mut(&mut self) -> &mut T { 40 | unsafe { &mut **(self.handle as *mut Ptr) } 41 | } 42 | } 43 | 44 | impl AsPtr for Local { 45 | fn as_ptr(&self) -> Ptr { 46 | unsafe { *self.handle } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/gc/handles/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod array_local; 2 | pub mod array_root; 3 | pub mod array; 4 | pub mod local; 5 | pub mod ptr; 6 | pub mod root; 7 | 8 | pub use self::array_local::ArrayLocal; 9 | pub use self::array_root::ArrayRoot; 10 | pub use self::array::{Array, AsArray}; 11 | pub use self::local::Local; 12 | pub use self::ptr::{Ptr, AsPtr}; 13 | pub use self::root::Root; 14 | -------------------------------------------------------------------------------- /src/gc/handles/ptr.rs: -------------------------------------------------------------------------------- 1 | use gc::{GcAllocator, Local, ptr_t}; 2 | use std::ops::{Deref, DerefMut}; 3 | use std::marker::PhantomData; 4 | use std::ptr; 5 | use std::mem::transmute; 6 | use std::fmt; 7 | 8 | pub struct Ptr { 9 | ptr: ptr_t, 10 | _type: PhantomData 11 | } 12 | 13 | impl Ptr { 14 | pub fn ptr(&self) -> ptr_t { 15 | self.ptr 16 | } 17 | 18 | pub fn from_ptr(ptr: ptr_t) -> Ptr { 19 | Ptr { 20 | ptr: ptr, 21 | _type: PhantomData 22 | } 23 | } 24 | 25 | pub fn null() -> Ptr { 26 | Self::from_ptr(ptr::null()) 27 | } 28 | 29 | pub fn is_null(&self) -> bool { 30 | self.ptr.is_null() 31 | } 32 | 33 | pub fn as_local(&self, allocator: &U) -> Local { 34 | allocator.alloc_local_from_ptr(*self) 35 | } 36 | } 37 | 38 | impl PartialEq for Ptr { 39 | fn eq(&self, other: &Ptr) -> bool { 40 | self.ptr == other.ptr 41 | } 42 | } 43 | 44 | impl Copy for Ptr { } 45 | 46 | impl Clone for Ptr { 47 | fn clone(&self) -> Ptr { 48 | Self::from_ptr(self.ptr) 49 | } 50 | } 51 | 52 | impl fmt::Debug for Ptr { 53 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 54 | write!(fmt, "Ptr {{ ptr: {:?} }}", self.ptr) 55 | } 56 | } 57 | 58 | impl Deref for Ptr { 59 | type Target = T; 60 | 61 | fn deref(&self) -> &T { 62 | unsafe { transmute(self.ptr) } 63 | } 64 | } 65 | 66 | impl DerefMut for Ptr { 67 | fn deref_mut(&mut self) -> &mut T { 68 | unsafe { transmute(self.ptr) } 69 | } 70 | } 71 | 72 | pub trait AsPtr { 73 | fn as_ptr(&self) -> Ptr; 74 | } 75 | 76 | impl AsPtr for Ptr { 77 | fn as_ptr(&self) -> Ptr { 78 | *self 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/gc/handles/root.rs: -------------------------------------------------------------------------------- 1 | use gc::{Ptr, Local, RootHandles, GcHeap, GcAllocator, AsPtr}; 2 | use std::ops::{Deref, DerefMut}; 3 | use std::marker::PhantomData; 4 | use std::mem::transmute; 5 | use std::rc::Rc; 6 | 7 | pub struct Root { 8 | handles: Rc, 9 | handle: u32, 10 | _type: PhantomData 11 | } 12 | 13 | impl Root { 14 | pub unsafe fn new>(heap: &GcHeap, ptr: U) -> Root { 15 | Root { 16 | handles: heap.handles.clone(), 17 | handle: heap.handles.add(ptr.as_ptr().ptr()), 18 | _type: PhantomData 19 | } 20 | } 21 | 22 | pub fn as_local(&self, allocator: &U) -> Local { 23 | allocator.alloc_local_from_root(self) 24 | } 25 | } 26 | 27 | impl Deref for Root { 28 | type Target = T; 29 | 30 | fn deref(&self) -> &T { 31 | unsafe { 32 | let ptr = self.handles.get_target(self.handle); 33 | transmute(ptr) 34 | } 35 | } 36 | } 37 | 38 | impl DerefMut for Root { 39 | fn deref_mut(&mut self) -> &mut T { 40 | unsafe { 41 | let ptr = self.handles.get_target(self.handle); 42 | transmute(ptr) 43 | } 44 | } 45 | } 46 | 47 | impl Clone for Root { 48 | fn clone(&self) -> Root { 49 | Root { 50 | handles: self.handles.clone(), 51 | handle: self.handles.clone_root(self.handle), 52 | _type: PhantomData 53 | } 54 | } 55 | } 56 | 57 | impl Drop for Root { 58 | fn drop(&mut self) { 59 | self.handles.remove(self.handle); 60 | } 61 | } 62 | 63 | impl AsPtr for Root { 64 | fn as_ptr(&self) -> Ptr { 65 | unsafe { Ptr::from_ptr(self.handles.get_target(self.handle)) } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/gc/os.rs: -------------------------------------------------------------------------------- 1 | // See https://github.com/jemalloc/jemalloc/blob/dev/src/chunk_mmap.c for how jemalloc allocates memory. 2 | 3 | extern crate libc; 4 | 5 | use self::libc::*; 6 | use std::ptr; 7 | use std::mem; 8 | use gc::ptr_t; 9 | 10 | #[cfg(target_os = "windows")] 11 | unsafe fn map(addr: ptr_t, size: usize) -> ptr_t { 12 | assert!(size != 0); 13 | 14 | /* 15 | * If VirtualAlloc can't allocate at the given address when one is 16 | * given, it fails and returns NULL. 17 | */ 18 | let ret = VirtualAlloc(ptr::null_mut(), size as size_t, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); 19 | 20 | assert!( 21 | ret == ptr::null_mut() || 22 | (addr == ptr::null() && ret as ptr_t != addr) || 23 | (addr != ptr::null() && ret as ptr_t == addr) 24 | ); 25 | 26 | mem::transmute(ret) 27 | } 28 | 29 | /* LINUX VERSION 30 | static void * 31 | pages_map(void *addr, size_t size) 32 | { 33 | void *ret; 34 | 35 | assert(size != 0); 36 | 37 | / * 38 | * We don't use MAP_FIXED here, because it can cause the *replacement* 39 | * of existing mappings, and we only want to create new mappings. 40 | * / 41 | ret = mmap(addr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, 42 | -1, 0); 43 | assert(ret != NULL); 44 | 45 | if (ret == MAP_FAILED) 46 | ret = NULL; 47 | else if (addr != NULL && ret != addr) { 48 | / * 49 | * We succeeded in mapping memory, but not in the right place. 50 | * / 51 | pages_unmap(ret, size); 52 | ret = NULL; 53 | } 54 | 55 | assert(ret == NULL || (addr == NULL && ret != addr) 56 | || (addr != NULL && ret == addr)); 57 | return (ret); 58 | } 59 | */ 60 | 61 | #[cfg(target_os = "windows")] 62 | unsafe fn unmap(addr: ptr_t, _: usize) { 63 | if VirtualFree(mem::transmute(addr), 0, MEM_RELEASE) == 0 { 64 | panic!("error in VirtualFree"); 65 | } 66 | } 67 | 68 | /* LINUX VERSION 69 | static void 70 | pages_unmap(void *addr, size_t size) 71 | { 72 | 73 | if (munmap(addr, size) == -1) 74 | { 75 | char buf[BUFERROR_BUF]; 76 | 77 | buferror(get_errno(), buf, sizeof(buf)); 78 | malloc_printf(": Error in " 79 | "munmap" 80 | "(): %s\n", buf); 81 | if (opt_abort) 82 | abort(); 83 | } 84 | } 85 | */ 86 | 87 | pub struct Memory { 88 | ptr: ptr_t, 89 | size: usize 90 | } 91 | 92 | impl Memory { 93 | pub fn empty() -> Memory { 94 | Memory { 95 | ptr: ptr::null_mut(), 96 | size: 0 97 | } 98 | } 99 | 100 | pub fn alloc(size: usize) -> Option { 101 | let ptr = unsafe { map(ptr::null(), size) }; 102 | if ptr.is_null() { 103 | None 104 | } else { 105 | Some(Memory { 106 | ptr: ptr, 107 | size: size 108 | }) 109 | } 110 | } 111 | 112 | pub unsafe fn ptr(&self) -> ptr_t { 113 | self.ptr 114 | } 115 | 116 | pub fn size(&self) -> usize { 117 | self.size 118 | } 119 | } 120 | 121 | impl Drop for Memory { 122 | fn drop(&mut self) { 123 | if self.size > 0 { 124 | unsafe { unmap(self.ptr, self.size) }; 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/gc/strategy/copying.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | extern crate time; 3 | 4 | use gc::strategy::Strategy; 5 | use gc::os::Memory; 6 | use gc::{GcRootWalker, GcOpts, GcMemHeader, GcWalker, GcWalk, GcFinalize, ptr_t}; 7 | use std::ptr; 8 | use std::mem::{size_of, transmute, swap}; 9 | use std::cmp::max; 10 | 11 | const PAGE_SIZE : usize = 4 * 1024; 12 | 13 | struct Header { 14 | forward: ptr_t, 15 | size: usize 16 | } 17 | 18 | impl Header { 19 | fn new(size: usize) -> Header { 20 | Header { 21 | forward: ptr::null(), 22 | size: size 23 | } 24 | } 25 | 26 | unsafe fn from_ptr<'a>(ptr: ptr_t) -> &'a mut Header { 27 | transmute(ptr.offset(-((size_of::
() + size_of::()) as isize))) 28 | } 29 | 30 | unsafe fn offset_from_user(ptr: ptr_t) -> ptr_t { 31 | ptr.offset(-((size_of::
() + size_of::()) as isize)) 32 | } 33 | 34 | unsafe fn offset_to_user(ptr: ptr_t) -> ptr_t { 35 | ptr.offset((size_of::
() + size_of::()) as isize) 36 | } 37 | } 38 | 39 | struct Block { 40 | memory: Memory, 41 | offset: usize, 42 | high_mark: usize 43 | } 44 | 45 | impl Block { 46 | unsafe fn alloc(&mut self, size: usize) -> ptr_t { 47 | let size = size + size_of::
(); 48 | 49 | if self.offset + size > self.high_mark { 50 | return ptr::null_mut(); 51 | } 52 | 53 | let memory = self.memory.ptr().offset(self.offset as isize); 54 | 55 | (*(memory as *mut Header)) = Header::new(size); 56 | 57 | self.offset += size; 58 | 59 | memory.offset(size_of::
() as isize) 60 | } 61 | } 62 | 63 | pub struct Copying { 64 | opts: GcOpts, 65 | from: Block, 66 | to: Memory, 67 | last_used: f64, 68 | last_failed: usize 69 | } 70 | 71 | impl Copying { 72 | pub fn new(opts: GcOpts) -> Copying { 73 | let memory = Memory::alloc(opts.initial_heap).unwrap(); 74 | let high_mark = (opts.initial_heap * (opts.init_gc * 100.0) as usize) / 100; 75 | 76 | Copying { 77 | opts: opts, 78 | from: Block { 79 | memory: memory, 80 | offset: 0, 81 | high_mark: high_mark 82 | }, 83 | to: Memory::empty(), 84 | last_used: 0.0, 85 | last_failed: 0 86 | } 87 | } 88 | 89 | unsafe fn copy(&mut self, mut walkers: Vec>, walker: &GcWalker) { 90 | // Calculate the new size of the heap. We use the fill factor of the previous 91 | // run as a basis and ensure that we have at least enough room to accept the 92 | // allocation that failed last (were we not able to reclaim any memory). 93 | // 94 | // The room we allocate comes down to the current allocated memory times the 95 | // fill factor times the growth factor. The growth factor is taken from 96 | // the configuration. 97 | 98 | // We need at least enough room to fit the last allocation. If we're not 99 | // able to reclaim any memory, the current offset plus the last allocation 100 | // need to fit. 101 | let mut target_size = max(self.from.memory.size(), self.from.offset + self.last_failed); 102 | 103 | tracegc!("last offset {} last failed {} target size {}", self.from.offset, self.last_failed, target_size); 104 | 105 | // If we had a fill of more than 50% last time, adjust with the growth factor. 106 | // If the growth factor is over 85%, we choose the fast growth factor. Otherwise 107 | // the slow one. 108 | if self.last_used > 0.5 { 109 | let growth_factor = if self.last_used > 0.85 { 110 | self.opts.fast_growth_factor 111 | } else { 112 | self.opts.slow_growth_factor 113 | }; 114 | 115 | target_size = (target_size * (growth_factor * 100.0) as usize) / 100; 116 | tracegc!("last used {} over 50% target size {} growth factor {}", self.last_used, target_size, growth_factor); 117 | } 118 | 119 | // The minimum is set to the last used size. 120 | target_size = max(target_size, (target_size * (self.last_used * 100.0) as usize) / 100); 121 | tracegc!("last used {} target size {}", self.last_used, target_size); 122 | 123 | // We don't shrink beyond the initial heap size. 124 | target_size = max(target_size, self.opts.initial_heap); 125 | tracegc!("initial heap {} target_size {}", self.opts.initial_heap, target_size); 126 | 127 | self.last_failed = 0; 128 | 129 | target_size = (target_size + (PAGE_SIZE - 1)) & !(PAGE_SIZE - 1); 130 | 131 | // Ensure that the target heap is large enough. 132 | 133 | if self.to.size() < target_size { 134 | // First set to empty to first release our allocated memory. 135 | self.to = Memory::empty(); 136 | self.to = Memory::alloc(target_size).unwrap(); 137 | } 138 | 139 | let mut forwarder = Forwarder { 140 | target: self.to.ptr() 141 | }; 142 | 143 | // Walk all GC roots. 144 | 145 | for walker in &mut walkers { 146 | loop { 147 | let ptr = walker.next(); 148 | if ptr.is_null() { 149 | break; 150 | } 151 | 152 | let from = *ptr; 153 | *ptr = forwarder.forward(*ptr); 154 | tracegc!("forwarded {:?} to {:?}", from, *ptr); 155 | } 156 | } 157 | 158 | // Walk the to space. 159 | 160 | let mut ptr = Header::offset_to_user(self.to.ptr()); 161 | 162 | while ptr < forwarder.target { 163 | let header = Header::from_ptr(ptr); 164 | let gc_header = GcMemHeader::from_ptr(ptr); 165 | let ty = gc_header.get_type_id(); 166 | let size = gc_header.get_size(); 167 | let ptrs = size / size_of::(); 168 | 169 | if gc_header.is_array() { 170 | let count = *transmute::<_, *const usize>(ptr); 171 | 172 | let mut child = ptr.offset(size_of::() as isize); 173 | let end = child.offset((count * size) as isize); 174 | 175 | let mut index = 0; 176 | 177 | while child < end { 178 | tracegc!("processing index {}", index); 179 | index += 1; 180 | 181 | // Stop processing arrays when we receive a GcWalk::EndArray. This is 182 | // to signlify that none of the elements of the array will ever 183 | // contain a pointer. 184 | if !process_block(child, ty, ptrs, &mut forwarder, walker) { 185 | break; 186 | } 187 | 188 | child = child.offset(size as isize); 189 | } 190 | 191 | } else { 192 | process_block(ptr, ty, ptrs, &mut forwarder, walker); 193 | } 194 | 195 | ptr = ptr.offset(header.size as isize); 196 | } 197 | 198 | // Walk the from space and find all not-forwarded blocks to 199 | // allow them to be finalized. 200 | 201 | let mut ptr = Header::offset_to_user(self.from.memory.ptr()); 202 | let end = ptr.offset(self.from.offset as isize); 203 | 204 | while ptr < end { 205 | let header = Header::from_ptr(ptr); 206 | 207 | if header.forward.is_null() { 208 | let gc_header = GcMemHeader::from_ptr(ptr); 209 | let ty = gc_header.get_type_id(); 210 | let size = gc_header.get_size(); 211 | 212 | if gc_header.is_array() { 213 | let count = *transmute::<_, *const usize>(ptr); 214 | 215 | let mut child = ptr.offset(size_of::() as isize); 216 | let end = child.offset((count * size) as isize); 217 | 218 | let mut index = 0; 219 | 220 | while child < end { 221 | tracegc!("finalizing index {}", index); 222 | index += 1; 223 | 224 | // Stop processing arrays when we receive a GcFinalize::NotFinalizable. 225 | // This signifies that none of the array elements will ever 226 | // be finalizable. 227 | match walker.finalize(ty, child) { 228 | GcFinalize::NotFinalizable => break, 229 | GcFinalize::Finalized => {} 230 | } 231 | 232 | child = child.offset(size as isize); 233 | } 234 | 235 | } else { 236 | walker.finalize(ty, ptr); 237 | } 238 | } 239 | 240 | ptr = ptr.offset(header.size as isize); 241 | } 242 | 243 | // Invalidate the from space in debug mode. 244 | 245 | if cfg!(not(ndebug)) { 246 | let mut ptr = transmute::<_, *mut u32>(self.from.memory.ptr()); 247 | let end = ptr.offset((self.from.memory.size() / size_of::()) as isize); 248 | 249 | while ptr < end { 250 | *ptr = 0xdeadbeef; 251 | ptr = ptr.offset(1); 252 | } 253 | } 254 | 255 | // Swap the from and to space. 256 | 257 | self.from.offset = forwarder.target as usize - self.to.ptr() as usize; 258 | swap(&mut self.from.memory, &mut self.to); 259 | self.from.high_mark = (self.from.memory.size() * (self.opts.init_gc * 100.0) as usize) / 100; 260 | 261 | // Calculate the current fill rate. 262 | 263 | self.last_used = self.from.offset as f64 / self.from.memory.size() as f64; 264 | tracegc!("from offset {} from size {} last used {}", self.from.offset, self.from.memory.size(), self.last_used); 265 | } 266 | } 267 | 268 | struct Forwarder { 269 | target: ptr_t 270 | } 271 | 272 | impl Forwarder { 273 | unsafe fn forward(&mut self, ptr: ptr_t) -> ptr_t { 274 | let header = Header::from_ptr(ptr); 275 | 276 | if header.forward.is_null() { 277 | header.forward = self.target; 278 | 279 | *(self.target as *mut Header) = Header::new(header.size); 280 | 281 | let from = Header::offset_from_user(ptr).offset(size_of::
() as isize); 282 | let to = transmute(self.target.offset(size_of::
() as isize)); 283 | let size = header.size - size_of::
(); 284 | 285 | ptr::copy(from, to, size); 286 | 287 | self.target = self.target.offset(header.size as isize); 288 | } 289 | 290 | Header::offset_to_user(header.forward) 291 | } 292 | } 293 | 294 | unsafe fn process_block(ptr: ptr_t, ty: u32, ptrs: usize, forwarder: &mut Forwarder, walker: &GcWalker) -> bool { 295 | for i in 0..ptrs { 296 | match walker.walk(ty, ptr, i as u32) { 297 | GcWalk::End => return true, 298 | GcWalk::EndArray => return false, 299 | GcWalk::Skip => {}, 300 | GcWalk::Pointer => { 301 | let offset = (ptr as *mut ptr_t).offset(i as isize); 302 | let child = *offset; 303 | tracegc!("forwarding {:?} of type {:?} at {:?} index {}", child, ty, ptr, i); 304 | if !child.is_null() { 305 | *offset = forwarder.forward(child); 306 | } 307 | } 308 | } 309 | } 310 | 311 | true 312 | } 313 | 314 | impl Strategy for Copying { 315 | unsafe fn alloc_raw(&mut self, size: usize) -> ptr_t { 316 | // Round the size to the next pointer. 317 | let size = (size + (size_of::() - 1)) & !(size_of::() - 1); 318 | 319 | let result = self.from.alloc(size); 320 | 321 | if result.is_null() { 322 | self.last_failed = size; 323 | } else { 324 | ptr::write_bytes(transmute::<_, *mut u8>(result), 0, size); 325 | } 326 | 327 | result 328 | } 329 | 330 | fn mem_allocated(&self) -> usize { 331 | self.from.memory.size() + self.to.size() 332 | } 333 | 334 | fn mem_used(&self) -> usize { 335 | self.from.offset 336 | } 337 | 338 | fn gc(&mut self, walkers: Vec>, walker: &GcWalker) { 339 | let start = time::precise_time_ns(); 340 | 341 | tracegc!("=== GC === start"); 342 | 343 | unsafe { 344 | self.copy(walkers, walker); 345 | } 346 | 347 | let elapsed = (time::precise_time_ns() - start) / 1_000_000; 348 | 349 | tracegc!("=== GC === allocated {} ({}) used {} ms {}", nice_size(self.from.memory.size()), nice_size(self.mem_allocated()), nice_size(self.mem_used()), elapsed); 350 | } 351 | } 352 | 353 | fn nice_size(size: usize) -> String { 354 | if size < 1024 { 355 | format!("{} B", size) 356 | } else { 357 | let mut size = size as f64 / 1024.0; 358 | if size < 1024.0 { 359 | format!("{:.1} KB", size) 360 | } else { 361 | size /= 1024.0; 362 | if size < 1024.0 { 363 | format!("{:.1} MB", size) 364 | } else { 365 | size /= 1024.0; 366 | format!("{:.1} GB", size) 367 | } 368 | } 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /src/gc/strategy/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod copying; 2 | 3 | extern crate libc; 4 | 5 | use gc::{GcRootWalker, GcWalker, ptr_t}; 6 | 7 | pub trait Strategy { 8 | unsafe fn alloc_raw(&mut self, size: usize) -> ptr_t; 9 | 10 | fn mem_allocated(&self) -> usize; 11 | 12 | fn mem_used(&self) -> usize; 13 | 14 | fn gc(&mut self, walkers: Vec>, walker: &GcWalker); 15 | } 16 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | #[macro_use] 4 | extern crate lazy_static; 5 | 6 | pub use rt::{JsResult, JsError}; 7 | 8 | #[macro_use] 9 | mod debug; 10 | #[macro_use] 11 | mod trace; 12 | mod syntax; 13 | mod ir; 14 | mod util; 15 | pub mod gc; 16 | pub mod rt; 17 | mod errors; 18 | pub mod contrib; 19 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate rjs; 2 | 3 | fn main() { 4 | rjs::contrib::test262::Test262Runner::run(); 5 | } 6 | -------------------------------------------------------------------------------- /src/rt/allocators.rs: -------------------------------------------------------------------------------- 1 | use gc::{GcAllocator, Local, Root, Ptr, ArrayLocal, ArrayRoot, Array}; 2 | use rt::JsEnv; 3 | 4 | impl GcAllocator for JsEnv { 5 | fn alloc_array_local_from_root(&self, root: &ArrayRoot) -> ArrayLocal { 6 | self.heap.alloc_array_local_from_root(root) 7 | } 8 | 9 | fn alloc_array_root_from_local(&self, local: ArrayLocal) -> ArrayRoot { 10 | self.heap.alloc_array_root_from_local(local) 11 | } 12 | 13 | fn alloc_array_local_from_array(&self, array: Array) -> ArrayLocal { 14 | self.heap.alloc_array_local_from_array(array) 15 | } 16 | 17 | fn alloc_root_from_local(&self, local: Local) -> Root { 18 | self.heap.alloc_root_from_local(local) 19 | } 20 | 21 | fn alloc_local_from_ptr(&self, ptr: Ptr) -> Local { 22 | self.heap.alloc_local_from_ptr(ptr) 23 | } 24 | 25 | fn alloc_local_from_root(&self, root: &Root) -> Local { 26 | self.heap.alloc_local_from_root(root) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/rt/boolean.rs: -------------------------------------------------------------------------------- 1 | use rt::{JsItem, JsEnv, JsValue, JsHandle}; 2 | 3 | pub struct JsBoolean { 4 | value: bool 5 | } 6 | 7 | impl JsBoolean { 8 | pub fn new(value: bool) -> JsBoolean { 9 | JsBoolean { 10 | value: value 11 | } 12 | } 13 | } 14 | 15 | impl JsItem for JsBoolean { 16 | fn as_value(&self) -> JsValue { 17 | JsValue::new_bool(self.value) 18 | } 19 | 20 | fn has_prototype(&self) -> bool { 21 | true 22 | } 23 | 24 | fn prototype(&self, env: &JsEnv) -> Option { 25 | Some(env.handle(JsHandle::Boolean).as_value()) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/rt/env/boolean.rs: -------------------------------------------------------------------------------- 1 | use ::{JsResult, JsError}; 2 | use rt::{JsEnv, JsArgs, JsValue, JsFnMode, JsItem, JsType, JsString}; 3 | use syntax::token::name; 4 | 5 | // 15.6.1 The Boolean Constructor Called as a Function 6 | // 15.6.2 The Boolean Constructor 7 | pub fn Boolean_constructor(env: &mut JsEnv, mode: JsFnMode, args: JsArgs) -> JsResult { 8 | let arg = if args.argc > 0 { 9 | args.arg(env, 0).to_boolean() 10 | } else { 11 | false 12 | }; 13 | 14 | let arg = JsValue::new_bool(arg); 15 | 16 | if mode.construct() { 17 | let this_arg = args.this(env); 18 | let mut this = this_arg.unwrap_object(); 19 | 20 | this.set_class(Some(name::BOOLEAN_CLASS)); 21 | this.set_value(arg); 22 | 23 | Ok(this_arg) 24 | } else { 25 | Ok(arg) 26 | } 27 | } 28 | 29 | fn get_bool_value(env: &mut JsEnv, this: JsValue) -> JsResult { 30 | match this.ty() { 31 | JsType::Boolean => Ok(this.unwrap_bool()), 32 | JsType::Object if this.class() == Some(name::BOOLEAN_CLASS) => { 33 | let this = this.unwrap_object(); 34 | 35 | Ok(this.value(env).unwrap_bool()) 36 | } 37 | _ => Err(JsError::new_type(env, ::errors::TYPE_INVALID)) 38 | } 39 | } 40 | 41 | // 15.6.4.3 Boolean.prototype.valueOf ( ) 42 | pub fn Boolean_valueOf(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 43 | let this_arg = args.this(env); 44 | let value = try!(get_bool_value(env, this_arg)); 45 | 46 | Ok(JsValue::new_bool(value)) 47 | } 48 | 49 | // 15.6.4.2 Boolean.prototype.toString ( ) 50 | pub fn Boolean_toString(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 51 | let this_arg = args.this(env); 52 | let value = try!(get_bool_value(env, this_arg)); 53 | 54 | let result = if value { "true" } else { "false" }; 55 | 56 | Ok(JsString::from_str(env, result).as_value()) 57 | } 58 | -------------------------------------------------------------------------------- /src/rt/env/console.rs: -------------------------------------------------------------------------------- 1 | use ::JsResult; 2 | use rt::{JsEnv, JsArgs, JsValue, JsFnMode}; 3 | 4 | // TODO #65 5 | pub fn console_log(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 6 | let string = try!(args.arg(env, 0).to_string(env)).to_string(); 7 | 8 | println!("{}", string); 9 | 10 | Ok(JsValue::new_undefined()) 11 | } 12 | -------------------------------------------------------------------------------- /src/rt/env/error.rs: -------------------------------------------------------------------------------- 1 | use ::{JsResult, JsError}; 2 | use rt::{JsEnv, JsArgs, JsValue, JsFnMode, JsItem, JsType, JsString}; 3 | use syntax::token::name; 4 | 5 | pub fn Error_constructor(env: &mut JsEnv, mode: JsFnMode, args: JsArgs) -> JsResult { 6 | if !mode.construct() { 7 | let target_args = args.args(env); 8 | return args.function(env).construct(env, target_args); 9 | } 10 | 11 | let this = try!(args.this(env).to_object(env)); 12 | let mut this_obj = this.unwrap_object(); 13 | this_obj.set_class(Some(name::ERROR_CLASS)); 14 | 15 | let message = args.arg(env, 0); 16 | if !message.is_undefined() { 17 | let message = try!(message.to_string(env)).as_value(); 18 | try!(this_obj.put(env, name::MESSAGE, message, true)); 19 | } 20 | 21 | Ok(this) 22 | } 23 | 24 | pub fn Error_toString(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 25 | let this = args.this(env); 26 | if this.ty() != JsType::Object { 27 | Err(JsError::new_type(env, ::errors::TYPE_INVALID)) 28 | } else { 29 | let name = try!(this.get(env, name::NAME)); 30 | let name = if name.is_undefined() { 31 | JsString::from_str(env, "Error") 32 | } else { 33 | try!(name.to_string(env)) 34 | }; 35 | 36 | let message = try!(this.get(env, name::MESSAGE)); 37 | let message = if message.is_undefined() { 38 | JsString::from_str(env, "") 39 | } else { 40 | try!(message.to_string(env)) 41 | }; 42 | 43 | let result = if name.chars().len() == 0 { 44 | message 45 | } else if message.chars().len() == 0 { 46 | name 47 | } else { 48 | let result = format!("{}: {}", name.to_string(), message.to_string()); 49 | JsString::from_str(env, &result) 50 | }; 51 | 52 | Ok(result.as_value()) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/rt/env/function.rs: -------------------------------------------------------------------------------- 1 | use ::{JsResult, JsError}; 2 | use rt::{JsEnv, JsString, JsFnMode, JsArgs, JsValue, JsItem, JsFunction, JsType}; 3 | use rt::{JsScope, JsDescriptor, JsObject, JsHandle}; 4 | use syntax::ast::FunctionRef; 5 | use syntax::parser::ParseMode; 6 | use gc::*; 7 | use std::fmt::Write; 8 | use std::cmp::max; 9 | use syntax::Name; 10 | use syntax::token::name; 11 | 12 | pub fn Function_baseConstructor(_env: &mut JsEnv, _mode: JsFnMode, _args: JsArgs) -> JsResult { 13 | Ok(JsValue::new_undefined()) 14 | } 15 | 16 | pub fn Function_constructor(env: &mut JsEnv, mode: JsFnMode, args: JsArgs) -> JsResult { 17 | if !mode.construct() { 18 | let target_args = args.args(env); 19 | return args.function(env).construct(env, target_args); 20 | } 21 | 22 | let body; 23 | 24 | let mut source = String::new(); 25 | 26 | source.push_str("function ("); 27 | 28 | if args.argc == 0 { 29 | body = JsString::from_str(env, ""); 30 | } else { 31 | for i in 0..args.argc - 1 { 32 | if i > 0 { 33 | source.push_str(", "); 34 | } 35 | let arg = try!(args.arg(env, i).to_string(env)); 36 | source.push_str(&arg.to_string()); 37 | } 38 | 39 | body = try!(args.arg(env, args.argc - 1).to_string(env)); 40 | } 41 | 42 | source.push_str(") { "); 43 | source.push_str(&body.to_string()); 44 | source.push_str(" }"); 45 | 46 | let function_ref = try!(env.ir.parse_string(&source, mode.strict(), ParseMode::Normal, false)); 47 | // The function returned is the program, but we need the function. The program 48 | // function is created last so we need the last but one. 49 | 50 | let function_ref = FunctionRef(function_ref.0 - 1); 51 | 52 | env.new_function(function_ref, None, mode.strict()) 53 | } 54 | 55 | // 15.3.4.4 Function.prototype.call (thisArg [ , arg1 [ , arg2, … ] ] ) 56 | pub fn Function_call(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 57 | let func = args.this(env); 58 | if !func.is_callable() { 59 | Err(JsError::new_type(env, ::errors::TYPE_NOT_A_FUNCTION)) 60 | } else { 61 | let this_arg = args.arg(env, 0); 62 | 63 | let mut call_args = Vec::new(); 64 | for i in 1..args.argc { 65 | call_args.push(args.arg(env, i)); 66 | } 67 | 68 | func.call(env, this_arg, call_args, false) 69 | } 70 | } 71 | 72 | // 15.3.4.3 Function.prototype.apply (thisArg, argArray) 73 | pub fn Function_apply(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 74 | let func = args.this(env); 75 | if !func.is_callable() { 76 | Err(JsError::new_type(env, ::errors::TYPE_NOT_A_FUNCTION)) 77 | } else { 78 | let this_arg = args.arg(env, 0); 79 | 80 | let call_args = args.arg(env, 1); 81 | let call_args = if call_args.is_null_or_undefined() { 82 | Vec::new() 83 | } else { 84 | match call_args.class() { 85 | Some(name::ARRAY_CLASS) | Some(name::ARGUMENTS_CLASS) => {}, 86 | _ => { 87 | // "Array" is a valid parameter to the apply method. 88 | let prototype = try!(call_args.get(env, name::PROTOTYPE)); 89 | let array_prototype = env.handle(JsHandle::Array); 90 | if prototype.ty() != JsType::Object || prototype.unwrap_object().as_ptr() != array_prototype.as_ptr() { 91 | return Err(JsError::new_type(env, ::errors::TYPE_INVALID_ARGUMENTS_ARRAY)) 92 | } 93 | } 94 | } 95 | 96 | let len = try!(call_args.get(env, name::LENGTH)); 97 | let len = try!(len.to_uint32(env)); 98 | let mut result = Vec::new(); 99 | 100 | for i in 0..len { 101 | result.push(try!(call_args.get(env, Name::from_index(i as usize)))); 102 | } 103 | 104 | result 105 | }; 106 | 107 | func.call(env, this_arg, call_args, false) 108 | } 109 | } 110 | 111 | // 15.3.4.2 Function.prototype.toString ( ) 112 | // TODO #66: This can be greatly improved, e.g. by retaining/getting the real code. 113 | pub fn Function_toString(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 114 | let this_arg = args.this(env); 115 | 116 | if this_arg.ty() == JsType::Object { 117 | if let Some(function) = this_arg.unwrap_object().function() { 118 | fn get_function_details(env: &mut JsEnv, this: JsValue, function: &JsFunction) -> JsResult<(Option, Option)> { 119 | match *function { 120 | JsFunction::Ir(function_ref) => { 121 | let description = env.ir.get_function(function_ref); 122 | Ok((description.name, None)) 123 | } 124 | JsFunction::Native(name, args, _, _) => { 125 | Ok((name, Some(args))) 126 | } 127 | JsFunction::Bound => { 128 | let scope = this.scope(env).unwrap(); 129 | let target = scope.get(env, 0); 130 | if let Some(function) = target.unwrap_object().function() { 131 | get_function_details(env, target, &function) 132 | } else { 133 | Err(JsError::new_type(env, ::errors::TYPE_INVALID)) 134 | } 135 | } 136 | } 137 | } 138 | 139 | let (name, args) = try!(get_function_details(env, this_arg, &function)); 140 | 141 | let mut code = String::new(); 142 | 143 | code.push_str("function "); 144 | 145 | if let Some(name) = name { 146 | code.push_str(&*env.ir.interner().get(name)); 147 | code.push_str(" "); 148 | } 149 | 150 | code.push_str("("); 151 | 152 | if let Some(args) = args { 153 | for i in 0..args { 154 | if i > 0 { 155 | code.push_str(", "); 156 | } 157 | 158 | write!(code, "arg{}", i).ok(); 159 | } 160 | } else { 161 | code.push_str(" /* ... */ "); 162 | } 163 | 164 | code.push_str(") {\n"); 165 | code.push_str(" /* ... */\n"); 166 | code.push_str("}\n"); 167 | 168 | return Ok(JsString::from_str(env, &code).as_value()); 169 | } 170 | } 171 | 172 | Err(JsError::new_type(env, ::errors::TYPE_INVALID)) 173 | } 174 | 175 | // 15.3.4.5 Function.prototype.bind (thisArg [, arg1 [, arg2, …]]) 176 | pub fn Function_bind(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 177 | let this_arg = args.this(env); 178 | 179 | if !this_arg.is_callable() { 180 | Err(JsError::new_type(env, ::errors::TYPE_NOT_CALLABLE)) 181 | } else { 182 | let mut scope = JsScope::new_local_thin(env, max(args.argc + 1, 2), None); 183 | 184 | scope.set(0, this_arg); 185 | scope.set(1, args.arg(env, 0)); 186 | 187 | if args.argc > 1 { 188 | for i in 0..args.argc - 1 { 189 | scope.set(i + 2, args.arg(env, i + 1)); 190 | } 191 | } 192 | 193 | let mut result = JsObject::new_function(env, JsFunction::Bound, true); 194 | 195 | let length = if this_arg.class() == Some(name::FUNCTION_CLASS) { 196 | let length = try!(this_arg.get(env, name::LENGTH)).unwrap_number() as usize; 197 | let argc = if args.argc > 1 { args.argc - 1 } else { 0 }; 198 | if argc > length { 0 } else { length - argc } 199 | } else { 200 | 0 201 | }; 202 | 203 | let length = JsValue::new_number(length as f64); 204 | result.define_own_property(env, name::LENGTH, JsDescriptor::new_value(length, false, false, true), false).ok(); 205 | 206 | result.set_scope(Some(scope)); 207 | 208 | Ok(result.as_value()) 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/rt/env/global.rs: -------------------------------------------------------------------------------- 1 | use ::{JsResult, JsError}; 2 | use rt::{JsEnv, JsArgs, JsValue, JsType, JsFnMode, JsString, JsHandle, JsItem}; 3 | use syntax::parser::ParseMode; 4 | use ::util::matchers::DecimalMatcher; 5 | use std::{char, f64}; 6 | use ::syntax::lexer::{is_line_terminator, is_whitespace}; 7 | 8 | // 15.1.2.2 parseInt (string , radix) 9 | pub fn Global_parseInt(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 10 | // TODO #67: Use DecimalMatcher. 11 | 12 | let string = try!(args.arg(env, 0).to_string(env)); 13 | let radix = try!(args.arg(env, 1).to_int32(env)) as i64; 14 | 15 | let mut offset = 0; 16 | let chars = string.to_string().chars().collect::>(); 17 | 18 | while offset < chars.len() { 19 | let c = chars[offset]; 20 | if !is_whitespace(c) && !is_line_terminator(c) { 21 | break; 22 | } 23 | 24 | offset += 1; 25 | } 26 | 27 | let sign = if offset < chars.len() { 28 | match chars[offset] { 29 | '-' => { 30 | offset += 1; 31 | -1 32 | } 33 | '+' => { 34 | offset += 1; 35 | 1 36 | } 37 | _ => { 38 | 1 39 | } 40 | } 41 | } else { 42 | 1 43 | }; 44 | 45 | let (mut radix, strip_prefix) = if radix != 0 { 46 | if radix < 2 || radix > 36 { 47 | return Ok(JsValue::new_number(f64::NAN)); 48 | } 49 | (radix, radix == 16) 50 | } else { 51 | (10, true) 52 | }; 53 | 54 | if strip_prefix && offset + 1 < chars.len() { 55 | if chars[offset] == '0' && (chars[offset + 1] == 'x' || chars[offset + 1] == 'X') { 56 | offset += 2; 57 | radix = 16; 58 | } 59 | } 60 | 61 | let start = offset; 62 | let mut result = 0.0; 63 | 64 | while offset < chars.len() { 65 | let c = chars[offset]; 66 | 67 | let digit = if c >= '0' && c <= '9' { 68 | c as i64 - '0' as i64 69 | } else if c >= 'a' && c <= 'z' { 70 | c as i64 - 'a' as i64 + 10 71 | } else if c >= 'A' && c <= 'Z' { 72 | c as i64 - 'A' as i64 + 10 73 | } else { 74 | break; 75 | }; 76 | 77 | if digit >= radix { 78 | break; 79 | } 80 | 81 | offset += 1; 82 | result *= radix as f64; 83 | result += digit as f64; 84 | } 85 | 86 | if offset == start { 87 | Ok(JsValue::new_number(f64::NAN)) 88 | } else { 89 | Ok(JsValue::new_number(result * sign as f64)) 90 | } 91 | } 92 | 93 | pub fn Global_parseFloat(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 94 | let input = try!(args.arg(env, 0).to_string(env)).to_string(); 95 | 96 | let result = DecimalMatcher::from_str(&input, false, false).as_f64().unwrap_or(f64::NAN); 97 | 98 | Ok(JsValue::new_number(result)) 99 | } 100 | 101 | // 15.1.2.4 isNaN (number) 102 | pub fn Global_isNaN(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 103 | let result = try!(args.arg(env, 0).to_number(env)).is_nan(); 104 | 105 | Ok(JsValue::new_bool(result)) 106 | } 107 | 108 | // 15.1.2.5 isFinite (number) 109 | pub fn Global_isFinite(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 110 | let result = try!(args.arg(env, 0).to_number(env)).is_finite(); 111 | 112 | Ok(JsValue::new_bool(result)) 113 | } 114 | 115 | // 15.1.2.1 eval (x) 116 | pub fn Global_eval(env: &mut JsEnv, mode: JsFnMode, args: JsArgs) -> JsResult { 117 | let arg = args.arg(env, 0); 118 | 119 | if arg.ty() != JsType::String { 120 | Ok(arg) 121 | } else { 122 | let arg = arg.unwrap_string().to_string(); 123 | let global = env.handle(JsHandle::Global).as_value(); 124 | let global_scope = env.global_scope.as_local(env); 125 | 126 | env.eval_scoped(&arg, mode.strict(), global, global_scope, ParseMode::Eval) 127 | .map(|result| result.as_value(env)) 128 | } 129 | } 130 | 131 | fn is_uri_unescape(c: char) -> bool { 132 | match c { 133 | 'a'...'z' | 'A'...'Z' | '0'...'9' | '-' | '_' | '.' | '!' | '~' | '*' | '\'' | '(' | ')' => true, 134 | _ => false 135 | } 136 | } 137 | 138 | fn is_uri_reserved(c: char) -> bool { 139 | match c { 140 | ';' | '/' | '?' | ':' | '@' | '&' | '=' | '+' | '$' | ',' => true, 141 | _ => false 142 | } 143 | } 144 | 145 | fn decode bool>(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs, is_reserved: F) -> JsResult { 146 | fn parse_hex(env: &mut JsEnv, c: char) -> JsResult { 147 | if c >= '0' && c <= '9' { 148 | Ok(((c as u32) - ('0' as u32)) as u8) 149 | } else if c >= 'a' && c <= 'f' { 150 | Ok(((c as u32) - ('a' as u32) + 10u32) as u8) 151 | } else if c >= 'A' && c <= 'F' { 152 | Ok(((c as u32) - ('A' as u32) + 10u32) as u8) 153 | } else { 154 | Err(JsError::new_uri(env)) 155 | } 156 | } 157 | 158 | fn decode_part(env: &mut JsEnv, chars: &[u16], offset: usize) -> JsResult> { 159 | if char::from_u32(chars[offset] as u32) == Some('%') { 160 | if offset + 2 >= chars.len() { 161 | Err(JsError::new_uri(env)) 162 | } else { 163 | if let Some(c1) = char::from_u32(chars[offset + 1] as u32) { 164 | if let Some(c2) = char::from_u32(chars[offset + 2] as u32) { 165 | return Ok(Some(try!(parse_hex(env, c1)) * 16 + try!(parse_hex(env, c2)))); 166 | } 167 | } 168 | 169 | Err(JsError::new_uri(env)) 170 | } 171 | } else { 172 | Ok(None) 173 | } 174 | } 175 | 176 | let string = try!(args.arg(env, 0).to_string(env)); 177 | let chars = string.chars(); 178 | let mut result = Vec::new(); 179 | 180 | let len = chars.len(); 181 | let mut i = 0; 182 | 183 | while i < len { 184 | if let Some(value) = try!(decode_part(env, chars, i)) { 185 | if value <= 127 { 186 | let c = value as char; 187 | if is_reserved(c) { 188 | for j in 0..3 { 189 | result.push(chars[i + j]); 190 | } 191 | } else { 192 | result.push(c as u16); 193 | } 194 | 195 | i += 3; 196 | } else { 197 | let start = i; 198 | 199 | let mut n = 1; 200 | while n <= 5 { 201 | if (value << n) & 0x80 == 0 { 202 | break; 203 | } 204 | n += 1; 205 | } 206 | 207 | if n == 1 || n > 4 { 208 | return Err(JsError::new_uri(env)); 209 | } 210 | 211 | let mut octets = Vec::with_capacity(n); 212 | octets.push(value); 213 | 214 | i += 3; 215 | 216 | if i + (3 * (n - 1)) > len { 217 | return Err(JsError::new_uri(env)); 218 | } 219 | 220 | for _ in 1..n { 221 | if let Some(value) = try!(decode_part(env, chars, i)) { 222 | if value >> 6 != 0b10 { 223 | return Err(JsError::new_uri(env)); 224 | } 225 | 226 | i += 3; 227 | 228 | octets.push(value); 229 | } else { 230 | return Err(JsError::new_uri(env)); 231 | } 232 | } 233 | 234 | let c = match String::from_utf8(octets) { 235 | Ok(string) => { 236 | let chars = string.chars().collect::>(); 237 | assert_eq!(chars.len(), 1); 238 | chars[0] 239 | } 240 | Err(..) => return Err(JsError::new_uri(env)) 241 | }; 242 | 243 | if is_reserved(c) { 244 | for j in start..i { 245 | result.push(chars[j]); 246 | } 247 | } else { 248 | let decoded = c as u32; 249 | 250 | if decoded < 0x10000 { 251 | result.push(decoded as u16); 252 | } else { 253 | let low = ((decoded - 0x10000) & 0x3FF) + 0xDC00; 254 | let high = (((decoded - 0x10000) >> 10) & 0x3FF) + 0xD800; 255 | 256 | result.push(high as u16); 257 | result.push(low as u16); 258 | } 259 | } 260 | } 261 | } else { 262 | result.push(chars[i]); 263 | 264 | i += 1; 265 | } 266 | } 267 | 268 | Ok(JsString::from_u16(env, &result).as_value()) 269 | } 270 | 271 | // 15.1.3 URI Handling Function Properties 272 | fn encode bool>(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs, is_reserved: F) -> JsResult { 273 | fn to_hex(octet: u8) -> char { 274 | if octet < 10 { 275 | (('0' as u8) + octet) as char 276 | } else { 277 | (('A' as u8) + (octet - 10)) as char 278 | } 279 | } 280 | 281 | let string = try!(args.arg(env, 0).to_string(env)); 282 | let chars = string.chars(); 283 | let mut result = Vec::new(); 284 | 285 | let len = chars.len(); 286 | 287 | let mut i = 0; 288 | 289 | while i < len { 290 | let value = chars[i]; 291 | 292 | let reserved = match char::from_u32(value as u32) { 293 | Some(c) => is_reserved(c), 294 | None => false 295 | }; 296 | 297 | if reserved { 298 | result.push(value); 299 | } else { 300 | let code_point = if value >= 0xDC00 && value <= 0xDFFF { 301 | return Err(JsError::new_uri(env)); 302 | } else if value < 0xD800 || value > 0xDBFF { 303 | value as u32 304 | } else { 305 | i += 1; 306 | if i == len { 307 | return Err(JsError::new_uri(env)); 308 | } 309 | 310 | let value2 = chars[i]; 311 | if value2 < 0xDC00 || value2 > 0xDFFF { 312 | return Err(JsError::new_uri(env)); 313 | } 314 | 315 | ((value as u32 - 0xD800) * 0x400 + (value2 as u32 - 0xDC00) + 0x10000) 316 | }; 317 | 318 | let c = match char::from_u32(code_point) { 319 | Some(c) => c, 320 | None => return Err(JsError::new_uri(env)) 321 | }; 322 | 323 | for octet in c.to_string().into_bytes() { 324 | result.push('%' as u16); 325 | result.push(to_hex(octet >> 4) as u16); 326 | result.push(to_hex(octet & 0xF) as u16); 327 | } 328 | } 329 | 330 | i += 1; 331 | } 332 | 333 | Ok(JsString::from_u16(env, &result).as_value()) 334 | } 335 | 336 | // 15.1.3.1 decodeURI (encodedURI) 337 | pub fn Global_decodeURI(env: &mut JsEnv, mode: JsFnMode, args: JsArgs) -> JsResult { 338 | decode(env, mode, args, |c| is_uri_reserved(c) || c == '#') 339 | } 340 | 341 | // 15.1.3.2 decodeURIComponent (encodedURIComponent) 342 | pub fn Global_decodeURIComponent(env: &mut JsEnv, mode: JsFnMode, args: JsArgs) -> JsResult { 343 | decode(env, mode, args, |_| false) 344 | } 345 | 346 | // 15.1.3.3 encodeURI (uri) 347 | pub fn Global_encodeURI(env: &mut JsEnv, mode: JsFnMode, args: JsArgs) -> JsResult { 348 | encode(env, mode, args, |c| is_uri_reserved(c) || is_uri_unescape(c) || c == '#') 349 | } 350 | 351 | // 15.1.3.4 encodeURIComponent (uriComponent) 352 | pub fn Global_encodeURIComponent(env: &mut JsEnv, mode: JsFnMode, args: JsArgs) -> JsResult { 353 | encode(env, mode, args, |c| is_uri_unescape(c)) 354 | } 355 | -------------------------------------------------------------------------------- /src/rt/env/json/lexer.rs: -------------------------------------------------------------------------------- 1 | extern crate strtod; 2 | 3 | use syntax::reader::Reader; 4 | use ::{JsResult, JsError}; 5 | use std::char; 6 | use std::rc::Rc; 7 | use self::strtod::strtod; 8 | use ::util::matchers::{DecimalMatcher, Decimal}; 9 | 10 | #[derive(Clone, Debug, PartialEq)] 11 | pub enum Lit { 12 | Null, 13 | Boolean(bool), 14 | String(Rc), 15 | Number(f64) 16 | } 17 | 18 | #[derive(Clone, Debug, PartialEq)] 19 | pub enum Token { 20 | CloseBrace, 21 | CloseBracket, 22 | Colon, 23 | Comma, 24 | Literal(Lit), 25 | OpenBrace, 26 | OpenBracket 27 | } 28 | 29 | pub struct Lexer<'a> { 30 | reader: &'a mut Reader, 31 | look_ahead: Option 32 | } 33 | 34 | impl<'a> Lexer<'a> { 35 | pub fn new(reader: &'a mut Reader) -> JsResult> { 36 | Ok(Lexer { 37 | reader: reader, 38 | look_ahead: None 39 | }) 40 | } 41 | 42 | pub fn is_eof(&mut self) -> JsResult { 43 | Ok(try!(self.peek()).is_none()) 44 | } 45 | 46 | pub fn next(&mut self) -> JsResult { 47 | if let Some(token) = try!(self.peek()) { 48 | self.look_ahead = None; 49 | 50 | Ok(token) 51 | } else { 52 | self.fatal("unexpected end of input") 53 | } 54 | } 55 | 56 | pub fn peek(&mut self) -> JsResult> { 57 | if let Some(ref token) = self.look_ahead { 58 | Ok(Some(token.clone())) 59 | } else { 60 | loop { 61 | if self.reader.is_eof() { 62 | return Ok(None); 63 | } 64 | 65 | let token = try!(self.parse()); 66 | 67 | if token.is_some() { 68 | self.look_ahead = token.clone(); 69 | 70 | return Ok(token); 71 | } 72 | } 73 | } 74 | } 75 | 76 | fn fatal(&self, message: &str) -> JsResult { 77 | let (line, col) = self.reader.pos(); 78 | 79 | Err(JsError::Lex(format!("{}:{}: {}", line, col, message.to_string()))) 80 | } 81 | 82 | fn parse(&mut self) -> JsResult> { 83 | let token = match self.reader.next() { 84 | '[' => Token::OpenBracket, 85 | ']' => Token::CloseBracket, 86 | '{' => Token::OpenBrace, 87 | '}' => Token::CloseBrace, 88 | ',' => Token::Comma, 89 | ':' => Token::Colon, 90 | c @ '0' ... '9' | c @ '-' | c @ '.' => try!(self.parse_decimal(c)), 91 | '"' => try!(self.parse_string()), 92 | c @ _ if is_line_terminator(c) => return Ok(None), 93 | c @ _ if is_whitespace(c) => { 94 | self.skip_while(is_whitespace); 95 | return Ok(None) 96 | }, 97 | _ => { 98 | self.reader.skip(-1); 99 | 100 | if let Some(identifier) = try!(self.parse_identifier()) { 101 | try!(self.token_from_identifier(identifier)) 102 | } else { 103 | return self.fatal("cannot parse"); 104 | } 105 | } 106 | }; 107 | 108 | Ok(Some(token)) 109 | } 110 | 111 | fn token_from_identifier(&self, identifier: String) -> JsResult { 112 | match &*identifier { 113 | "null" => Ok(Token::Literal(Lit::Null)), 114 | "true" => Ok(Token::Literal(Lit::Boolean(true))), 115 | "false" => Ok(Token::Literal(Lit::Boolean(false))), 116 | _ => self.fatal("cannot parse identifier") 117 | } 118 | } 119 | 120 | fn skip_while bool>(&mut self, predicate: F) { 121 | while !self.reader.is_eof() && predicate(self.reader.peek()) { 122 | self.reader.next(); 123 | } 124 | } 125 | 126 | fn parse_decimal(&mut self, c: char) -> JsResult { 127 | let mut matcher = DecimalMatcher::new(true, true); 128 | 129 | matcher.allowed(c); 130 | 131 | while !self.reader.is_eof() && matcher.allowed(self.reader.peek()) { 132 | self.reader.next(); 133 | } 134 | 135 | let result = match matcher.complete() { 136 | Decimal::Integer(value) => i64::from_str_radix(&value, 10).unwrap() as f64, 137 | Decimal::Decimal(value) => strtod(&value).unwrap(), 138 | Decimal::Hex(value) | Decimal::Octal(value) | Decimal::Error(value) | Decimal::IncompleteDecimal(value) 139 | => return self.fatal(&format!("cannot parse number {:?}", value)) 140 | }; 141 | 142 | Ok(Token::Literal(Lit::Number(result))) 143 | } 144 | 145 | fn parse_string(&mut self) -> JsResult { 146 | let mut s = String::new(); 147 | 148 | while !self.reader.is_eof() { 149 | match self.reader.next() { 150 | '\u{0}'...'\u{1F}' => return self.fatal("invalid string character"), 151 | '"' => return Ok(Token::Literal(Lit::String(Rc::new(s)))), 152 | '\\' => { 153 | let parsed = try!(self.parse_escape()); 154 | s.push_str(&parsed) 155 | } 156 | c @ _ => s.push(c) 157 | } 158 | } 159 | 160 | self.fatal("unterminated string constant") 161 | } 162 | 163 | fn parse_escape(&mut self) -> JsResult { 164 | let result = match self.reader.next() { 165 | '\\' => "\\".to_string(), 166 | '/' => "/".to_string(), 167 | '"' => "\"".to_string(), 168 | 'b' => "\u{8}".to_string(), 169 | 'f' => "\u{C}".to_string(), 170 | 'n' => "\n".to_string(), 171 | 'r' => "\r".to_string(), 172 | 't' => "\t".to_string(), 173 | 'u' => try!(self.parse_escape_hex(4)).to_string(), 174 | _ => return self.fatal("cannot parse escape sequence") 175 | }; 176 | 177 | Ok(result) 178 | } 179 | 180 | fn parse_escape_hex(&mut self, length: i32) -> JsResult { 181 | let mut value : u32 = 0; 182 | 183 | for _ in 0..length { 184 | let c = self.reader.next(); 185 | let digit; 186 | 187 | if c >= '0' && c <= '9' { 188 | digit = (c as u32) - ('0' as u32); 189 | } else if c >= 'a' && c <= 'f' { 190 | digit = (c as u32) - ('a' as u32) + 10u32; 191 | } else if c >= 'A' && c <= 'F' { 192 | digit = (c as u32) - ('A' as u32) + 10u32; 193 | } else { 194 | return self.fatal("Cannot parse hex escape sequence"); 195 | } 196 | 197 | value <<= 4; 198 | value |= digit; 199 | } 200 | 201 | if let Some(c) = char::from_u32(value) { 202 | Ok(c) 203 | } else { 204 | // TODO #60: What to do when we get an invalid unicode code point? 205 | Ok('�') 206 | } 207 | } 208 | 209 | fn parse_identifier(&mut self) -> JsResult> { 210 | let mut s = String::new(); 211 | 212 | while !self.reader.is_eof() && self.is_identifier_letter() { 213 | s.push(self.reader.next()); 214 | } 215 | 216 | Ok(if s.is_empty() { 217 | None 218 | } else { 219 | Some(s) 220 | }) 221 | } 222 | 223 | fn is_identifier_letter(&mut self) -> bool { 224 | // We only have to parse null, true and false. 225 | 226 | match self.reader.peek() { 227 | 'a'...'z' => true, 228 | _ => false 229 | } 230 | } 231 | } 232 | 233 | fn is_line_terminator(c: char) -> bool { 234 | match c { 235 | '\r' | '\n' => true, 236 | _ => false 237 | } 238 | } 239 | 240 | fn is_whitespace(c: char) -> bool { 241 | match c { 242 | '\t' | ' ' => true, 243 | _ => false 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/rt/env/json/mod.rs: -------------------------------------------------------------------------------- 1 | use ::JsResult; 2 | use rt::{JsEnv, JsArgs, JsValue, JsFnMode}; 3 | use syntax::reader::StringReader; 4 | use self::writer::JsonWriter; 5 | 6 | mod lexer; 7 | mod parser; 8 | mod writer; 9 | 10 | // 15.12.2 parse ( text [ , reviver ] ) 11 | pub fn JSON_parse(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 12 | let string = try!(args.arg(env, 0).to_string(env)).to_string(); 13 | 14 | let mut reader = StringReader::new("(json)", &string); 15 | let mut lexer = try!(lexer::Lexer::new(&mut reader)); 16 | 17 | let result = try!(parser::Parser::parse_json(env, &mut lexer)); 18 | 19 | Ok(result) 20 | } 21 | 22 | // 15.12.3 stringify ( value [ , replacer [ , space ] ] ) 23 | pub fn JSON_stringify(env: &mut JsEnv, mode: JsFnMode, args: JsArgs) -> JsResult { 24 | try!(JsonWriter::new(env, mode, args)).write() 25 | } 26 | -------------------------------------------------------------------------------- /src/rt/env/json/parser.rs: -------------------------------------------------------------------------------- 1 | use syntax::Name; 2 | use ::{JsResult, JsError}; 3 | use rt::{JsEnv, JsValue, JsDescriptor, JsString, JsItem}; 4 | use rt::env::json::lexer::{Lexer, Token, Lit}; 5 | 6 | pub struct Parser<'a> { 7 | lexer: &'a mut Lexer<'a>, 8 | env: &'a mut JsEnv 9 | } 10 | 11 | impl<'a> Parser<'a> { 12 | fn is_eof(&mut self) -> JsResult { 13 | self.lexer.is_eof() 14 | } 15 | 16 | fn peek(&mut self) -> JsResult> { 17 | self.lexer.peek() 18 | } 19 | 20 | fn next(&mut self) -> JsResult { 21 | self.lexer.next() 22 | } 23 | 24 | fn bump(&mut self) -> JsResult<()> { 25 | try!(self.lexer.next()); 26 | 27 | Ok(()) 28 | } 29 | 30 | fn expect(&mut self, token: Token) -> JsResult<()> { 31 | let message = { 32 | let next = try!(self.next()); 33 | 34 | if next == token { 35 | return Ok(()); 36 | } 37 | 38 | format!("unexpected token; expected {:?}, got {:?}", token, next) 39 | }; 40 | 41 | self.fatal(&message) 42 | } 43 | 44 | fn consume(&mut self, token: Token) -> JsResult { 45 | let matched = match try!(self.peek()) { 46 | None => false, 47 | Some(t) => t == token 48 | }; 49 | 50 | if matched { 51 | try!(self.bump()); 52 | } 53 | 54 | Ok(matched) 55 | } 56 | 57 | fn fatal(&mut self, message: &str) -> JsResult { 58 | Err(JsError::Parse(message.to_string())) 59 | } 60 | 61 | pub fn parse_json(env: &'a mut JsEnv, lexer: &'a mut Lexer<'a>) -> JsResult { 62 | let mut parser = Parser { 63 | lexer: lexer, 64 | env: env 65 | }; 66 | 67 | let result = try!(parser.parse_value()); 68 | 69 | if !try!(parser.is_eof()) { 70 | parser.fatal("expected end of JSON stream") 71 | } else { 72 | Ok(result) 73 | } 74 | } 75 | 76 | fn parse_value(&mut self) -> JsResult { 77 | match try!(self.next()) { 78 | Token::Literal(lit) => { 79 | match lit { 80 | Lit::Null => Ok(JsValue::new_null()), 81 | Lit::Boolean(value) => Ok(JsValue::new_bool(value)), 82 | Lit::String(value) => Ok(JsString::from_str(self.env, &value).as_value()), 83 | Lit::Number(value) => Ok(JsValue::new_number(value)) 84 | } 85 | } 86 | Token::OpenBrace => self.parse_object(), 87 | Token::OpenBracket => self.parse_array(), 88 | _ => self.fatal("invalid JSON token") 89 | } 90 | } 91 | 92 | fn parse_object(&mut self) -> JsResult { 93 | let mut object = self.env.create_object(); 94 | 95 | if !try!(self.consume(Token::CloseBrace)) { 96 | loop { 97 | if let Token::Literal(Lit::String(key)) = try!(self.next()) { 98 | try!(self.consume(Token::Colon)); 99 | 100 | let value = try!(self.parse_value()); 101 | 102 | let key = self.env.intern(&key); 103 | try!(object.define_own_property(self.env, key, JsDescriptor::new_simple_value(value), false)); 104 | } else { 105 | return self.fatal("cannot parse JSON object"); 106 | } 107 | 108 | if try!(self.consume(Token::CloseBrace)) { 109 | break; 110 | } 111 | 112 | try!(self.expect(Token::Comma)); 113 | } 114 | } 115 | 116 | Ok(object.as_value()) 117 | } 118 | 119 | fn parse_array(&mut self) -> JsResult { 120 | let mut array = self.env.create_array(); 121 | let mut offset = 0; 122 | 123 | if !try!(self.consume(Token::CloseBracket)) { 124 | loop { 125 | let value = try!(self.parse_value()); 126 | 127 | try!(array.define_own_property(self.env, Name::from_index(offset), JsDescriptor::new_simple_value(value), false)); 128 | offset += 1; 129 | 130 | if try!(self.consume(Token::CloseBracket)) { 131 | break; 132 | } 133 | 134 | try!(self.expect(Token::Comma)); 135 | } 136 | } 137 | 138 | Ok(array.as_value()) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/rt/env/json/writer.rs: -------------------------------------------------------------------------------- 1 | use ::{JsResult, JsError}; 2 | use rt::{JsEnv, JsArgs, JsValue, JsType, JsItem, JsObject, JsString, JsFnMode, JsDescriptor}; 3 | use rt::object::JsStoreKey; 4 | use gc::*; 5 | use syntax::token::name; 6 | use syntax::Name; 7 | use std::fmt::Write; 8 | use std::collections::HashSet; 9 | use std::rc::Rc; 10 | 11 | pub struct JsonWriter<'a> { 12 | env: &'a mut JsEnv, 13 | stack: Vec, 14 | value: JsValue, 15 | replacer: Option, 16 | property_list: Option>>, 17 | strict: bool 18 | } 19 | 20 | impl<'a> JsonWriter<'a> { 21 | pub fn new(env: &'a mut JsEnv, mode: JsFnMode, args: JsArgs) -> JsResult> { 22 | let value = args.arg(env, 0); 23 | let replacer_arg = args.arg(env, 1); 24 | 25 | let (replacer, property_list) = if replacer_arg.is_callable() { 26 | (Some(replacer_arg), None) 27 | } else if replacer_arg.class() == Some(name::ARRAY_CLASS) { 28 | let mut property_list = Vec::new(); 29 | 30 | let length = try!(value.get(env, name::LENGTH)).unwrap_number() as usize; 31 | 32 | for i in 0..length { 33 | let element = try!(value.get(env, Name::from_index(i))); 34 | 35 | let include = match element.ty() { 36 | JsType::String | JsType::Number => true, 37 | JsType::Object => { 38 | match element.class() { 39 | Some(name::STRING_CLASS) | Some(name::NUMBER_CLASS) => true, 40 | _ => false 41 | } 42 | } 43 | _ => false 44 | }; 45 | 46 | if include { 47 | let name = try!(element.to_string(env)).to_string(); 48 | let name = env.intern(&name); 49 | if !property_list.contains(&name) { 50 | property_list.push(name); 51 | } 52 | } 53 | } 54 | 55 | (None, Some(Rc::new(property_list))) 56 | } else { 57 | (None, None) 58 | }; 59 | 60 | Ok(JsonWriter { 61 | env: env, 62 | stack: Vec::new(), 63 | value: value, 64 | replacer: replacer, 65 | property_list: property_list, 66 | strict: mode.strict() 67 | }) 68 | } 69 | 70 | pub fn write(&mut self) -> JsResult { 71 | let value = self.value; 72 | let value = try!(self.transform(value, None, None)); 73 | 74 | if self.ignore(value) { 75 | Ok(JsValue::new_undefined()) 76 | } else { 77 | let mut json = String::new(); 78 | 79 | try!(self.write_value(&mut json, value)); 80 | 81 | Ok(JsString::from_str(self.env, &json).as_value()) 82 | } 83 | } 84 | 85 | fn ignore(&mut self, value: JsValue) -> bool { 86 | value.is_undefined() || value.is_callable() 87 | } 88 | 89 | fn write_string(&mut self, json: &mut String, value: &str) { 90 | json.push('"'); 91 | 92 | for c in value.chars() { 93 | match c { 94 | '"' => json.push_str("\\\""), 95 | '\\' => json.push_str("\\\\"), 96 | '\u{8}' => json.push_str("\\b"), 97 | '\u{C}' => json.push_str("\\f"), 98 | '\n' => json.push_str("\\n"), 99 | '\r' => json.push_str("\\r"), 100 | '\t' => json.push_str("\\t"), 101 | _ => { 102 | if c < ' ' { 103 | write!(json, "\\u{:04x}", c as u32).ok().unwrap(); 104 | } else { 105 | json.push(c); 106 | } 107 | } 108 | } 109 | } 110 | 111 | json.push('"'); 112 | } 113 | 114 | fn write_number(&mut self, json: &mut String, value: f64) -> JsResult<()> { 115 | if value.is_finite() { 116 | // TODO #61: This is very wrong. See 9.8.1 ToString Applied to the Number Type 117 | // for the full specifications. A C# implementation can be found at 118 | // http://jurassic.codeplex.com/SourceControl/latest#Jurassic/Core/NumberFormatter.cs. 119 | json.push_str(&value.to_string()); 120 | } else { 121 | json.push_str("null"); 122 | } 123 | 124 | Ok(()) 125 | } 126 | 127 | fn write_element(&mut self, json: &mut String, object: Local, name: Name, value: JsValue, had_one: &mut bool) -> JsResult<()> { 128 | let element = try!(object.get(self.env, name)); 129 | let element = try!(self.transform(element, Some(name), Some(value))); 130 | if !self.ignore(element) { 131 | if *had_one { 132 | json.push(','); 133 | } else { 134 | *had_one = true; 135 | } 136 | 137 | let string = &*self.env.ir.interner().get(name); 138 | self.write_string(json, string); 139 | json.push(':'); 140 | try!(self.write_value(json, element)); 141 | } 142 | 143 | Ok(()) 144 | } 145 | 146 | fn write_object(&mut self, json: &mut String, value: JsValue) -> JsResult<()> { 147 | let mut object = value.unwrap_object(); 148 | let ptr = object.as_ptr().ptr(); 149 | 150 | if self.stack.contains(&ptr) { 151 | return Err(JsError::new_type(self.env, ::errors::TYPE_CYCLICAL_REFERENCE)); 152 | } 153 | 154 | self.stack.push(ptr); 155 | 156 | json.push('{'); 157 | 158 | let mut had_one = false; 159 | 160 | if let Some(property_list) = self.property_list.clone() { 161 | for name in property_list.iter() { 162 | try!(self.write_element(json, object, *name, value, &mut had_one)); 163 | } 164 | } else { 165 | let mut offset = 0; 166 | let mut seen = HashSet::new(); 167 | 168 | loop { 169 | match object.get_key(self.env, offset) { 170 | JsStoreKey::Key(name, enumerable) => { 171 | offset += 1; 172 | 173 | if seen.insert(name) && enumerable { 174 | try!(self.write_element(json, object, name, value, &mut had_one)); 175 | } 176 | } 177 | JsStoreKey::Missing => { 178 | offset += 1; 179 | } 180 | JsStoreKey::End => { 181 | if let Some(prototype) = object.prototype(self.env) { 182 | object = prototype.unwrap_object(); 183 | 184 | offset = 0; 185 | } else { 186 | break; 187 | } 188 | } 189 | } 190 | } 191 | } 192 | 193 | json.push('}'); 194 | 195 | self.stack.pop(); 196 | 197 | Ok(()) 198 | } 199 | 200 | fn write_array(&mut self, json: &mut String, value: JsValue) -> JsResult<()> { 201 | let object = value.unwrap_object(); 202 | let ptr = object.as_ptr().ptr(); 203 | 204 | if self.stack.contains(&ptr) { 205 | return Err(JsError::new_type(self.env, ::errors::TYPE_CYCLICAL_REFERENCE)) 206 | } 207 | 208 | self.stack.push(ptr); 209 | 210 | json.push('['); 211 | 212 | let length = try!(object.get(self.env, name::LENGTH)).unwrap_number() as usize; 213 | 214 | for i in 0..length { 215 | if i > 0 { 216 | json.push(','); 217 | } 218 | 219 | let index = Name::from_index(i); 220 | let element = try!(object.get(self.env, index)); 221 | let element = try!(self.transform(element, Some(index), Some(value))); 222 | 223 | if self.ignore(element) { 224 | json.push_str("null"); 225 | } else { 226 | try!(self.write_value(json, element)); 227 | } 228 | } 229 | 230 | json.push(']'); 231 | 232 | self.stack.pop(); 233 | 234 | Ok(()) 235 | } 236 | 237 | fn key_to_string(&mut self, key: Option) -> JsValue { 238 | match key { 239 | Some(name) => JsString::from_str(self.env, &*self.env.ir.interner().get(name)).as_value(), 240 | None => JsValue::new_undefined() 241 | } 242 | } 243 | 244 | fn transform(&mut self, mut value: JsValue, key: Option, holder: Option) -> JsResult { 245 | if value.ty() == JsType::Object { 246 | let to_json = try!(value.get(self.env, name::TO_JSON)); 247 | if to_json.is_callable() { 248 | let args = vec![self.key_to_string(key)]; 249 | value = try!(to_json.call(self.env, value, args, self.strict)); 250 | } 251 | } 252 | 253 | if let Some(replacer) = self.replacer { 254 | let holder = match holder { 255 | Some(holder) => holder, 256 | None => { 257 | let mut holder = self.env.create_object(); 258 | try!(holder.define_own_property(self.env, name::EMPTY, JsDescriptor::new_simple_value(value), false)); 259 | holder.as_value() 260 | } 261 | }; 262 | 263 | let args = vec![self.key_to_string(key), value]; 264 | value = try!(replacer.call(self.env, holder, args, self.strict)); 265 | } 266 | 267 | Ok(value) 268 | } 269 | 270 | fn write_value(&mut self, json: &mut String, mut value: JsValue) -> JsResult<()> { 271 | // Ignored values must be handled higher up. 272 | assert!(!self.ignore(value)); 273 | 274 | match value.class() { 275 | Some(name::NUMBER_CLASS) | Some(name::STRING_CLASS) | Some(name::BOOLEAN_CLASS) 276 | => value = value.unwrap_object().value(self.env), 277 | Some(name::ARRAY_CLASS) => return self.write_array(json, value), 278 | _ => {} 279 | } 280 | 281 | match value.ty() { 282 | JsType::Null => json.push_str("null"), 283 | JsType::Boolean => if value.unwrap_bool() { 284 | json.push_str("true"); 285 | } else { 286 | json.push_str("false"); 287 | }, 288 | JsType::String => { 289 | let string = value.unwrap_string().to_string(); 290 | self.write_string(json, &string); 291 | } 292 | JsType::Number => try!(self.write_number(json, value.unwrap_number())), 293 | _ => try!(self.write_object(json, value)) 294 | } 295 | 296 | Ok(()) 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /src/rt/env/math.rs: -------------------------------------------------------------------------------- 1 | extern crate rand; 2 | 3 | use ::JsResult; 4 | use rt::{JsEnv, JsArgs, JsValue, JsFnMode, JsType}; 5 | use std::f64; 6 | use self::rand::random; 7 | 8 | // 15.8.2.1 abs (x) 9 | pub fn Math_abs(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 10 | let arg = try!(args.arg(env, 0).to_number(env)); 11 | Ok(JsValue::new_number(arg.abs())) 12 | } 13 | 14 | // 15.8.2.2 acos (x) 15 | pub fn Math_acos(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 16 | let arg = try!(args.arg(env, 0).to_number(env)); 17 | 18 | Ok(JsValue::new_number(arg.acos())) 19 | } 20 | 21 | // 15.8.2.3 asin (x) 22 | pub fn Math_asin(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 23 | let arg = try!(args.arg(env, 0).to_number(env)); 24 | 25 | Ok(JsValue::new_number(arg.asin())) 26 | } 27 | 28 | // 15.8.2.4 atan (x) 29 | pub fn Math_atan(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 30 | let arg = try!(args.arg(env, 0).to_number(env)); 31 | 32 | Ok(JsValue::new_number(arg.atan())) 33 | } 34 | 35 | // 15.8.2.5 atan2 (y, x) 36 | pub fn Math_atan2(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 37 | let y = try!(args.arg(env, 0).to_number(env)); 38 | let x = try!(args.arg(env, 1).to_number(env)); 39 | 40 | Ok(JsValue::new_number(y.atan2(x))) 41 | } 42 | 43 | // 15.8.2.6 ceil (x) 44 | pub fn Math_ceil(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 45 | let arg = try!(args.arg(env, 0).to_number(env)); 46 | 47 | Ok(JsValue::new_number(arg.ceil())) 48 | } 49 | 50 | // 15.8.2.7 cos (x) 51 | pub fn Math_cos(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 52 | let arg = try!(args.arg(env, 0).to_number(env)); 53 | 54 | Ok(JsValue::new_number(arg.cos())) 55 | } 56 | 57 | // 15.8.2.8 exp (x) 58 | pub fn Math_exp(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 59 | let arg = try!(args.arg(env, 0).to_number(env)); 60 | 61 | Ok(JsValue::new_number(arg.exp())) 62 | } 63 | 64 | // 15.8.2.9 floor (x) 65 | pub fn Math_floor(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 66 | let arg = try!(args.arg(env, 0).to_number(env)); 67 | 68 | Ok(JsValue::new_number(arg.floor())) 69 | } 70 | 71 | // 15.8.2.10 log (x) 72 | pub fn Math_log(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 73 | let arg = try!(args.arg(env, 0).to_number(env)); 74 | 75 | Ok(JsValue::new_number(arg.ln())) 76 | } 77 | 78 | // 15.8.2.11 max ( [ value1 [ , value2 [ , … ] ] ] ) 79 | pub fn Math_max(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 80 | let mut result = None; 81 | 82 | for i in 0..args.argc { 83 | let arg = args.arg(env, i); 84 | if arg.ty() == JsType::Number && arg.unwrap_number().is_nan() { 85 | return Ok(JsValue::new_number(f64::NAN)); 86 | } 87 | 88 | if let Some(last) = result { 89 | if try!(env.compare_gt(arg, last)) { 90 | result = Some(arg); 91 | } 92 | } else { 93 | result = Some(arg); 94 | } 95 | } 96 | 97 | Ok(match result { 98 | Some(result) => result, 99 | _ => JsValue::new_number(f64::NEG_INFINITY) 100 | }) 101 | } 102 | 103 | // 15.8.2.12 min ( [ value1 [ , value2 [ , … ] ] ] ) 104 | pub fn Math_min(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 105 | let mut result = None; 106 | 107 | for i in 0..args.argc { 108 | let arg = args.arg(env, i); 109 | if arg.ty() == JsType::Number && arg.unwrap_number().is_nan() { 110 | return Ok(JsValue::new_number(f64::NAN)); 111 | } 112 | 113 | if let Some(last) = result { 114 | if try!(env.compare_lt(arg, last)) { 115 | result = Some(arg); 116 | } 117 | } else { 118 | result = Some(arg); 119 | } 120 | } 121 | 122 | Ok(match result { 123 | Some(result) => result, 124 | _ => JsValue::new_number(f64::INFINITY) 125 | }) 126 | } 127 | 128 | // 15.8.2.13 pow (x, y) 129 | pub fn Math_pow(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 130 | let x = try!(args.arg(env, 0).to_number(env)); 131 | let y = try!(args.arg(env, 1).to_number(env)); 132 | 133 | let result = if y.is_infinite() { 134 | let x = x.abs(); 135 | 136 | if x > 1.0 { 137 | if y.is_sign_positive() { f64::INFINITY } else { 0.0 } 138 | } else if x < 1.0 { 139 | if y.is_sign_positive() { 0.0 } else { f64::INFINITY } 140 | } else { 141 | f64::NAN 142 | } 143 | } else { 144 | x.powf(y) 145 | }; 146 | 147 | Ok(JsValue::new_number(result)) 148 | } 149 | 150 | // 15.8.2.14 random ( ) 151 | pub fn Math_random(_env: &mut JsEnv, _mode: JsFnMode, _args: JsArgs) -> JsResult { 152 | Ok(JsValue::new_number(random::())) 153 | } 154 | 155 | // 15.8.2.15 round (x) 156 | pub fn Math_round(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 157 | let arg = try!(args.arg(env, 0).to_number(env)); 158 | 159 | let result = if arg.is_finite() { 160 | let fract = arg.fract(); 161 | let result = if arg > 0.0 { 162 | if fract >= 0.5 { 163 | arg.ceil() 164 | } else { 165 | arg.floor() 166 | } 167 | } else { 168 | if fract >= -0.5 { 169 | arg.ceil() 170 | } else { 171 | arg.floor() 172 | } 173 | }; 174 | 175 | if result == 0.0 && arg.is_sign_negative() { 176 | -0.0 177 | } else { 178 | result 179 | } 180 | } else { 181 | arg 182 | }; 183 | 184 | Ok(JsValue::new_number(result)) 185 | } 186 | 187 | // 15.8.2.16 sin (x) 188 | pub fn Math_sin(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 189 | let arg = try!(args.arg(env, 0).to_number(env)); 190 | 191 | Ok(JsValue::new_number(arg.sin())) 192 | } 193 | 194 | // 15.8.2.17 sqrt (x) 195 | pub fn Math_sqrt(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 196 | let arg = try!(args.arg(env, 0).to_number(env)); 197 | 198 | Ok(JsValue::new_number(arg.sqrt())) 199 | } 200 | 201 | // 15.8.2.18 tan (x) 202 | pub fn Math_tan(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 203 | let arg = try!(args.arg(env, 0).to_number(env)); 204 | 205 | Ok(JsValue::new_number(arg.tan())) 206 | } 207 | -------------------------------------------------------------------------------- /src/rt/env/number.rs: -------------------------------------------------------------------------------- 1 | use ::{JsResult, JsError}; 2 | use rt::{JsEnv, JsArgs, JsValue, JsFnMode, JsItem, JsType, JsString}; 3 | use rt::fmt::*; 4 | use syntax::token::name; 5 | 6 | // 15.7.1 The Number Constructor Called as a Function 7 | // 15.7.2 The Number Constructor 8 | pub fn Number_constructor(env: &mut JsEnv, mode: JsFnMode, args: JsArgs) -> JsResult { 9 | let arg = if args.argc > 0 { 10 | try!(args.arg(env, 0).to_number(env)) 11 | } else { 12 | 0.0 13 | }; 14 | 15 | let arg = JsValue::new_number(arg); 16 | 17 | if mode.construct() { 18 | let this_arg = args.this(env); 19 | let mut this = this_arg.unwrap_object(); 20 | 21 | this.set_class(Some(name::NUMBER_CLASS)); 22 | this.set_value(arg); 23 | 24 | Ok(this_arg) 25 | } else { 26 | Ok(arg) 27 | } 28 | } 29 | 30 | // 15.7.4.4 Number.prototype.valueOf ( ) 31 | pub fn Number_valueOf(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 32 | let this_arg = args.this(env); 33 | 34 | if this_arg.class() != Some(name::NUMBER_CLASS) { 35 | Err(JsError::new_type(env, ::errors::TYPE_INVALID)) 36 | } else { 37 | let this = this_arg.unwrap_object(); 38 | Ok(this.value(env)) 39 | } 40 | } 41 | 42 | fn get_number(env: &mut JsEnv, value: JsValue) -> JsResult { 43 | match value.ty() { 44 | JsType::Object => { 45 | if value.class() != Some(name::NUMBER_CLASS) { 46 | return Err(JsError::new_type(env, ::errors::TYPE_INVALID)) 47 | } 48 | Ok(value.unwrap_object().value(env)) 49 | } 50 | JsType::Number => Ok(value), 51 | _ => return Err(JsError::new_type(env, ::errors::TYPE_INVALID)) 52 | } 53 | } 54 | 55 | // 15.7.4.2 Number.prototype.toString ( [ radix ] ) 56 | pub fn Number_toString(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 57 | let value = args.this(env); 58 | let value = try!(get_number(env, value)).unwrap_number(); 59 | 60 | let radix = args.arg(env, 0); 61 | let radix = if radix.is_undefined() { 62 | 10 63 | } else { 64 | let radix = try!(radix.to_integer(env)) as i32; 65 | if radix < 2 || radix > 36 { 66 | return Err(JsError::new_range(env)); 67 | } 68 | radix 69 | }; 70 | 71 | let result = format_number(value, radix as u32, NumberFormatStyle::Regular, 0); 72 | 73 | Ok(JsString::from_str(env, &result).as_value()) 74 | } 75 | 76 | // 15.7.4.3 Number.prototype.toLocaleString() 77 | pub fn Number_toLocaleString(env: &mut JsEnv, mode: JsFnMode, args: JsArgs) -> JsResult { 78 | Number_toString(env, mode, args) 79 | } 80 | 81 | // 15.7.4.5 Number.prototype.toFixed (fractionDigits) 82 | pub fn Number_toFixed(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 83 | let digits = args.arg(env, 0); 84 | let digits = if digits.is_undefined() { 85 | 0.0 86 | } else { 87 | try!(args.arg(env, 0).to_integer(env)) 88 | }; 89 | 90 | let digits = digits as i32; 91 | 92 | if digits < 0 || digits > 20 { 93 | Err(JsError::new_range(env)) 94 | } else { 95 | let value = try!(args.this(env).to_number(env)); 96 | let result = format_number(value, 10, NumberFormatStyle::Fixed, digits); 97 | Ok(JsString::from_str(env, &result).as_value()) 98 | } 99 | } 100 | 101 | // 15.7.4.6 Number.prototype.toExponential (fractionDigits) 102 | pub fn Number_toExponential(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 103 | let value = args.this(env); 104 | let value = try!(get_number(env, value)).unwrap_number(); 105 | 106 | let fraction_digits = args.arg(env, 0); 107 | let result = if fraction_digits.is_undefined() { 108 | format_number(value, 10, NumberFormatStyle::Regular, -1) 109 | } else { 110 | let fraction_digits = try!(fraction_digits.to_integer(env)) as i32; 111 | if fraction_digits < 0 || fraction_digits > 20 { 112 | return Err(JsError::new_range(env)); 113 | } 114 | 115 | format_number(value, 10, NumberFormatStyle::Exponential, fraction_digits) 116 | }; 117 | 118 | Ok(JsString::from_str(env, &result).as_value()) 119 | } 120 | 121 | // 15.7.4.7 Number.prototype.toPrecision (precision) 122 | pub fn Number_toPrecision(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 123 | let value = args.this(env); 124 | let value = try!(get_number(env, value)).unwrap_number(); 125 | 126 | let precision = args.arg(env, 0); 127 | let result = if precision.is_undefined() { 128 | format_number(value, 10, NumberFormatStyle::Regular, -1) 129 | } else { 130 | let precision = try!(precision.to_integer(env)) as i32; 131 | if precision < 0 || precision > 21 { 132 | return Err(JsError::new_range(env)); 133 | } 134 | 135 | format_number(value, 10, NumberFormatStyle::Precision, precision) 136 | }; 137 | 138 | Ok(JsString::from_str(env, &result).as_value()) 139 | } 140 | -------------------------------------------------------------------------------- /src/rt/env/regexp.rs: -------------------------------------------------------------------------------- 1 | use ::{JsResult, JsError}; 2 | use rt::{JsEnv, JsArgs, JsValue, JsFnMode, JsRegExp, JsString, JsItem, JsDescriptor}; 3 | use gc::*; 4 | use syntax::token::name; 5 | use syntax::Name; 6 | 7 | // 15.10.3 The RegExp Constructor Called as a Function 8 | // 15.10.4 The RegExp Constructor 9 | pub fn RegExp_constructor(env: &mut JsEnv, mode: JsFnMode, args: JsArgs) -> JsResult { 10 | let pattern = args.arg(env, 0); 11 | let flags = args.arg(env, 1); 12 | 13 | if !mode.construct() { 14 | if pattern.class() == Some(name::REGEXP_CLASS) && flags.is_undefined() { 15 | Ok(pattern) 16 | } else { 17 | args.function(env).construct(env, vec![pattern, flags]) 18 | } 19 | } else { 20 | let (pattern, flags) = if pattern.class() == Some(name::REGEXP_CLASS) { 21 | if flags.is_undefined() { 22 | let regexp = pattern.unwrap_object().value(env).unwrap_regexp(); 23 | 24 | (regexp.pattern(env), regexp.flags(env)) 25 | } else { 26 | return Err(JsError::new_type(env, ::errors::TYPE_INVALID_REGEXP_ARGS)); 27 | } 28 | } else { 29 | let pattern = if pattern.is_undefined() { 30 | JsString::from_str(env, "") 31 | } else { 32 | try!(pattern.to_string(env)) 33 | }; 34 | let flags = if flags.is_undefined() { 35 | JsString::from_str(env, "") 36 | } else { 37 | try!(flags.to_string(env)) 38 | }; 39 | 40 | (pattern, flags) 41 | }; 42 | 43 | let mut global = false; 44 | let mut ignore_case = false; 45 | let mut multiline = false; 46 | 47 | for c in flags.to_string().chars() { 48 | match c { 49 | 'g' => { 50 | if global { 51 | return Err(JsError::new_syntax(env, ::errors::SYNTAX_INVALID_REGEXP_FLAGS)); 52 | } 53 | global = true; 54 | } 55 | 'i' => { 56 | if ignore_case { 57 | return Err(JsError::new_syntax(env, ::errors::SYNTAX_INVALID_REGEXP_FLAGS)); 58 | } 59 | ignore_case = true; 60 | } 61 | 'm' => { 62 | if multiline { 63 | return Err(JsError::new_syntax(env, ::errors::SYNTAX_INVALID_REGEXP_FLAGS)); 64 | } 65 | multiline = true; 66 | } 67 | _ => return Err(JsError::new_syntax(env, ::errors::SYNTAX_INVALID_REGEXP_FLAGS)) 68 | } 69 | } 70 | 71 | let regexp = try!(JsRegExp::new_local( 72 | env, 73 | pattern, 74 | flags, 75 | global, 76 | ignore_case, 77 | multiline 78 | )); 79 | 80 | let this_arg = args.this(env); 81 | let mut this = this_arg.unwrap_object(); 82 | 83 | this.set_class(Some(name::REGEXP_CLASS)); 84 | this.set_value(regexp.as_value()); 85 | 86 | let value = JsDescriptor::new_value(pattern.as_value(), false, false, false); 87 | try!(this.define_own_property(env, name::SOURCE, value, true)); 88 | let value = JsDescriptor::new_value(JsValue::new_bool(global), false, false, false); 89 | try!(this.define_own_property(env, name::GLOBAL, value, true)); 90 | let value = JsDescriptor::new_value(JsValue::new_bool(ignore_case), false, false, false); 91 | try!(this.define_own_property(env, name::IGNORE_CASE, value, true)); 92 | let value = JsDescriptor::new_value(JsValue::new_bool(multiline), false, false, false); 93 | try!(this.define_own_property(env, name::MULTILINE, value, true)); 94 | let value = JsDescriptor::new_value(JsValue::new_number(0.0), true, false, false); 95 | try!(this.define_own_property(env, name::LAST_INDEX, value, true)); 96 | 97 | Ok(this_arg) 98 | } 99 | } 100 | 101 | fn unwrap_regexp(env: &mut JsEnv, args: &JsArgs) -> JsResult> { 102 | let this = args.this(env); 103 | 104 | if this.class() == Some(name::REGEXP_CLASS) { 105 | Ok(this.unwrap_object().value(env).unwrap_regexp()) 106 | } else { 107 | Err(JsError::new_type(env, ::errors::TYPE_INVALID)) 108 | } 109 | } 110 | 111 | // 15.10.6.2 RegExp.prototype.exec(string) 112 | pub fn RegExp_exec(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 113 | let regexp = try!(unwrap_regexp(env, &args)); 114 | let mut this = args.this(env); 115 | 116 | let input = try!(args.arg(env, 0).to_string(env)); 117 | let string = input.to_string(); 118 | let length = string.len() as isize; 119 | 120 | let last_index = try!(this.get(env, name::LAST_INDEX)); 121 | let mut last_index = try!(last_index.to_number(env)); 122 | 123 | let global = regexp.global(); 124 | if !global { 125 | last_index = 0.0; 126 | } 127 | 128 | // The indexes we take from lastIndex and set as lastIndex and the index 129 | // of the captures is wrong. We set byte offsets instead UTF-16 code points. 130 | // The reason we do this is because it would be very costly to do this 131 | // right. This will be fixed through #79. 132 | 133 | if last_index < 0.0 || last_index > length as f64 { 134 | let value = JsValue::new_number(0.0); 135 | try!(this.put(env, name::LAST_INDEX, value, true)); 136 | return Ok(JsValue::new_null()); 137 | } 138 | 139 | let last_index = last_index as usize; 140 | 141 | if let Some(capture) = regexp.regex().captures(&string[last_index..]) { 142 | // The first capture is the complete match, so the end position marks 143 | // the end of the complete match. 144 | let (start_index, end_index) = capture.pos(0).unwrap(); 145 | 146 | // Offset to adjust for the current index into the string. 147 | let start_index = start_index + last_index; 148 | let end_index = end_index + last_index; 149 | 150 | if global { 151 | let value = JsValue::new_number(end_index as f64); 152 | try!(this.put(env, name::LAST_INDEX, value, true)); 153 | } 154 | 155 | let mut result = env.create_array(); 156 | 157 | let value = JsDescriptor::new_simple_value(JsValue::new_number(start_index as f64)); 158 | try!(result.define_own_property(env, name::INDEX, value, true)); 159 | 160 | let value = JsDescriptor::new_simple_value(input.as_value()); 161 | try!(result.define_own_property(env, name::INPUT, value, true)); 162 | 163 | // Length is the same as capture.len() because that includes the 164 | // complete match as the first entry. 165 | let value = JsDescriptor { 166 | value: Some(JsValue::new_number(capture.len() as f64)), 167 | ..JsDescriptor::default() 168 | }; 169 | try!(result.define_own_property(env, name::LENGTH, value, true)); 170 | 171 | let matched_substr = JsString::from_str(env, &string[start_index..end_index]).as_value(); 172 | let value = JsDescriptor::new_simple_value(matched_substr); 173 | try!(result.define_own_property(env, Name::from_index(0), value, true)); 174 | 175 | for i in 1..capture.len() { 176 | if let Some((capture_start, capture_end)) = capture.pos(i) { 177 | let capture_i = JsString::from_str(env, &string[(capture_start + last_index)..(capture_end + last_index)]).as_value(); 178 | let value = JsDescriptor::new_simple_value(capture_i); 179 | try!(result.define_own_property(env, Name::from_index(i), value, true)); 180 | } 181 | } 182 | 183 | Ok(result.as_value()) 184 | } else { 185 | let value = JsValue::new_number(0.0); 186 | try!(this.put(env, name::LAST_INDEX, value, true)); 187 | Ok(JsValue::new_null()) 188 | } 189 | } 190 | 191 | // 15.10.6.3 RegExp.prototype.test(string) 192 | pub fn RegExp_test(env: &mut JsEnv, mode: JsFnMode, args: JsArgs) -> JsResult { 193 | let result = try!(RegExp_exec(env, mode, args)); 194 | 195 | Ok(JsValue::new_bool(!result.is_null())) 196 | } 197 | 198 | // 15.10.6.4 RegExp.prototype.toString() 199 | pub fn RegExp_toString(env: &mut JsEnv, _mode: JsFnMode, args: JsArgs) -> JsResult { 200 | let regexp = try!(unwrap_regexp(env, &args)); 201 | 202 | let mut result = String::new(); 203 | 204 | result.push('/'); 205 | result.push_str(&*regexp.pattern(env).to_string()); 206 | result.push('/'); 207 | 208 | if regexp.global() { 209 | result.push('g'); 210 | } 211 | if regexp.ignore_case() { 212 | result.push('i'); 213 | } 214 | if regexp.multiline() { 215 | result.push('m'); 216 | } 217 | 218 | Ok(JsString::from_str(env, &result).as_value()) 219 | } 220 | -------------------------------------------------------------------------------- /src/rt/env/string/replacer.rs: -------------------------------------------------------------------------------- 1 | use rt::{JsEnv, JsArgs, JsValue, JsItem, JsString, JsHandle, JsFunction}; 2 | use ::JsResult; 3 | use gc::*; 4 | use syntax::Name; 5 | use syntax::token::name; 6 | use std::char; 7 | 8 | pub struct Replacer<'a> { 9 | env: &'a mut JsEnv, 10 | string: String, 11 | string_value: JsValue, 12 | replace_value: JsValue, 13 | replace_chars: Option>, 14 | result: String, 15 | regexp_offset: usize, 16 | strict: bool 17 | } 18 | 19 | // 15.5.4.11 String.prototype.replace (searchValue, replaceValue) 20 | impl<'a> Replacer<'a> { 21 | pub fn replace(env: &'a mut JsEnv, args: JsArgs, mut strict: bool) -> JsResult { 22 | let string = args.this(env); 23 | try!(string.check_object_coercible(env)); 24 | let string = try!(string.to_string(env)); 25 | 26 | let string_value = string.as_value(); 27 | 28 | let search_value = args.arg(env, 0); 29 | 30 | // Search value is resolved before replace value is touched. We don't use 31 | // the result here; this is just for conformance. 32 | if search_value.class() != Some(name::REGEXP_CLASS) { 33 | try!(search_value.to_string(env)); 34 | } 35 | 36 | let replace_value = args.arg(env, 1); 37 | let replace_chars = if replace_value.is_callable() { 38 | // TODO #86: This logic shouldn't be here, but in a central place so that 39 | // it can be re-used generically for callbacks. 40 | if !strict { 41 | strict = match replace_value.unwrap_object().function().unwrap() { 42 | JsFunction::Ir(function_ref) => env.ir.get_function(function_ref).strict, 43 | _ => false 44 | }; 45 | } 46 | 47 | None 48 | } else { 49 | Some(try!(replace_value.to_string(env)).to_string().chars().collect::>()) 50 | }; 51 | 52 | let mut replacer = Replacer { 53 | env: env, 54 | string: string.to_string(), 55 | string_value: string_value, 56 | replace_value: replace_value, 57 | replace_chars: replace_chars, 58 | result: String::new(), 59 | regexp_offset: 0, 60 | strict: strict 61 | }; 62 | 63 | if search_value.class() == Some(name::REGEXP_CLASS) { 64 | let string = string.as_value(); 65 | try!(replacer.replace_regexp(string, search_value)); 66 | } else { 67 | let search_value = try!(search_value.to_string(replacer.env)); 68 | try!(replacer.replace_string(string, search_value)); 69 | } 70 | 71 | Ok(JsString::from_str(replacer.env, &replacer.result).as_value()) 72 | } 73 | 74 | fn replace_regexp(&mut self, string: JsValue, mut search_value: JsValue) -> JsResult<()> { 75 | let global = search_value.unwrap_object().value(self.env).unwrap_regexp().global(); 76 | 77 | let exec = try!(search_value.get(self.env, name::EXEC)); 78 | 79 | if !global { 80 | let matches = try!(exec.call(self.env, search_value, vec![string], false)); 81 | try!(self.process_regexp_match(matches)); 82 | } else { 83 | let value = JsValue::new_number(0.0); 84 | try!(search_value.put(self.env, name::LAST_INDEX, value, true)); 85 | 86 | let mut previous_last_index = 0; 87 | 88 | loop { 89 | let matches = try!(exec.call(self.env, search_value, vec![string], false)); 90 | if matches.is_null() { 91 | break; 92 | } 93 | 94 | let this_index = try!(try!(search_value.get(self.env, name::LAST_INDEX)).to_integer(self.env)) as isize; 95 | if this_index == previous_last_index { 96 | let value = JsValue::new_number((this_index + 1) as f64); 97 | try!(search_value.put(self.env, name::LAST_INDEX, value, true)); 98 | previous_last_index = this_index + 1; 99 | } else { 100 | previous_last_index = this_index; 101 | } 102 | 103 | try!(self.process_regexp_match(matches)); 104 | } 105 | } 106 | 107 | // Copy the remainder of string to the result. 108 | 109 | if self.regexp_offset < self.string.len() { 110 | self.result.push_str(&self.string[self.regexp_offset..]); 111 | } 112 | 113 | Ok(()) 114 | } 115 | 116 | fn process_regexp_match(&mut self, matches: JsValue) -> JsResult<()> { 117 | let length = try!(try!(matches.get(self.env, name::LENGTH)).to_integer(self.env)) as usize; 118 | 119 | let matched = try!(try!(matches.get(self.env, Name::from_index(0))).to_string(self.env)).to_string(); 120 | let mut captures = Vec::new(); 121 | 122 | for i in 0..(length - 1) { 123 | captures.push(try!(try!(matches.get(self.env, Name::from_index(i + 1))).to_string(self.env)).to_string()); 124 | } 125 | 126 | let index = try!(try!(matches.get(self.env, name::INDEX)).to_integer(self.env)) as usize; 127 | 128 | // Copy the part of string we've skipped over to the result. 129 | 130 | if index > self.regexp_offset { 131 | self.result.push_str(&self.string[self.regexp_offset..index]); 132 | } 133 | 134 | self.regexp_offset = index + matched.len(); 135 | 136 | // Process the match. 137 | 138 | self.process_match(matched, captures, index) 139 | } 140 | 141 | fn replace_string(&mut self, string: Local, search_value: Local) -> JsResult<()> { 142 | if search_value.chars().len() == 0 { 143 | return Ok(()); 144 | } 145 | 146 | let chars = string.chars(); 147 | let string_len = chars.len(); 148 | let search_value = search_value.chars(); 149 | let search_value_len = search_value.len(); 150 | 151 | let found = if search_value_len <= string_len { 152 | let mut offset = 0; 153 | let mut found = true; 154 | 155 | while offset <= (string_len - search_value_len) { 156 | found = true; 157 | 158 | for i in 0..search_value_len { 159 | if chars[offset + i] != search_value[i] { 160 | found = false; 161 | break; 162 | } 163 | } 164 | 165 | if found { 166 | break; 167 | } 168 | 169 | offset += 1; 170 | } 171 | 172 | if found { 173 | // Copy the text we skipped over to the output. 174 | 175 | for i in 0..offset { 176 | self.result.push(char::from_u32(chars[i] as u32).unwrap_or('�')); 177 | } 178 | 179 | // Process this match. 180 | 181 | let matched = ::rt::utf::utf16_to_string(&chars[offset..(offset + search_value_len)]); 182 | try!(self.process_match(matched, Vec::new(), offset)); 183 | 184 | // Copy the rest of the search text. 185 | 186 | // Refetch chars after GC. 187 | let chars = string.chars(); 188 | 189 | for i in (offset + search_value_len)..string_len { 190 | self.result.push(char::from_u32(chars[i] as u32).unwrap_or('�')); 191 | } 192 | } 193 | 194 | found 195 | } else { 196 | false 197 | }; 198 | 199 | // If there wasn't a match, the result is the search text. 200 | 201 | if !found { 202 | self.result = self.string.clone(); 203 | } 204 | 205 | Ok(()) 206 | } 207 | 208 | fn process_match(&mut self, matched: String, captures: Vec, index: usize) -> JsResult<()> { 209 | if self.replace_chars.is_some() { 210 | self.process_match_string(matched, captures, index) 211 | } else { 212 | self.process_match_function(matched, captures, index) 213 | } 214 | } 215 | 216 | fn process_match_string(&mut self, matched: String, captures: Vec, index: usize) -> JsResult<()> { 217 | let replace = self.replace_chars.as_ref().unwrap(); 218 | 219 | let mut i = 0; 220 | while i < replace.len() { 221 | let c = replace[i]; 222 | 223 | if c == '$' && i + 1 < replace.len() { 224 | match replace[i + 1] { 225 | '$' => { 226 | i += 2; 227 | self.result.push('$'); 228 | continue; 229 | } 230 | '&' => { 231 | i += 2; 232 | self.result.push_str(&matched); 233 | continue; 234 | } 235 | '`' => { 236 | i += 2; 237 | self.result.push_str(&self.string[0..index]); 238 | continue; 239 | } 240 | '\'' => { 241 | i += 2; 242 | self.result.push_str(&self.string[(index + matched.len())..]); 243 | continue; 244 | } 245 | c @ _ => { 246 | if c >= '0' && c <= '9' { 247 | let mut capture = c as u32 - '0' as u32; 248 | i += 2; 249 | 250 | // The behavior of multi digit captures is implementation defined. 251 | // When we won't have enough captures, we don't capture the second 252 | // digit. 253 | if captures.len() > 10 && i + 2 < replace.len() { 254 | let c = replace[i + 2]; 255 | if c >= '0' && c <= '9' { 256 | capture = capture * 10 + (c as u32 - '0' as u32); 257 | i += 1; 258 | } 259 | } 260 | 261 | if capture == 0 { 262 | // $0 is not a valid capture so falls under the rule that the 263 | // escape is ignored and copied verbatim. 264 | self.result.push_str("$0"); 265 | } else { 266 | // The behavior here is implementation defined. I choose to not 267 | // throw when the capture is out of range. Instead we silently 268 | // ignore this error. 269 | 270 | let capture = (capture - 1) as usize; 271 | if capture < captures.len() { 272 | self.result.push_str(&captures[capture]); 273 | } 274 | } 275 | } else { 276 | i += 1; 277 | self.result.push('$'); 278 | self.result.push(c); 279 | } 280 | } 281 | } 282 | } else { 283 | i += 1; 284 | self.result.push(c); 285 | } 286 | } 287 | 288 | Ok(()) 289 | } 290 | 291 | fn process_match_function(&mut self, matched: String, captures: Vec, index: usize) -> JsResult<()> { 292 | let mut args = Vec::new(); 293 | 294 | args.push(JsString::from_str(self.env, &matched).as_value()); 295 | 296 | for capture in captures { 297 | args.push(JsString::from_str(self.env, &capture).as_value()); 298 | } 299 | 300 | args.push(JsValue::new_number(index as f64)); 301 | args.push(self.string_value); 302 | 303 | let this = if self.strict { 304 | JsValue::new_undefined() 305 | } else { 306 | self.env.handle(JsHandle::Global).as_value() 307 | }; 308 | 309 | let result = try!(self.replace_value.call(self.env, this, args, false)); 310 | 311 | self.result.push_str(&try!(result.to_string(self.env)).to_string()); 312 | 313 | Ok(()) 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /src/rt/iterator.rs: -------------------------------------------------------------------------------- 1 | use rt::{JsItem, JsEnv, JsValue, JsType, JsObject, GC_ITERATOR, GC_U32}; 2 | use rt::object::JsStoreKey; 3 | use gc::*; 4 | use syntax::Name; 5 | 6 | const INITIAL_SEEN : usize = 8; 7 | 8 | // Modifications to this struct must be synchronized with the GC walker. 9 | pub struct JsIterator { 10 | target: Ptr, 11 | name: Option, 12 | offset: usize, 13 | seen: Array, 14 | len: usize 15 | } 16 | 17 | impl JsIterator { 18 | pub fn new_local(env: &JsEnv, target: JsValue) -> Local { 19 | let mut result = env.heap.alloc_local::(GC_ITERATOR); 20 | 21 | let target = match target.ty() { 22 | JsType::Object => target.unwrap_object().as_ptr(), 23 | JsType::Null | JsType::Undefined => Ptr::null(), 24 | _ => { 25 | if let Some(prototype) = target.prototype(env) { 26 | prototype.unwrap_object().as_ptr() 27 | } else { 28 | Ptr::null() 29 | } 30 | } 31 | }; 32 | 33 | let seen = env.heap.alloc_array_local::(GC_U32, INITIAL_SEEN); 34 | 35 | *result = JsIterator { 36 | target: target, 37 | name: None, 38 | offset: 0, 39 | seen: seen.as_ptr(), 40 | len: 0 41 | }; 42 | 43 | result 44 | } 45 | } 46 | 47 | impl Local { 48 | pub fn as_value(&self) -> JsValue { 49 | JsValue::new_iterator(*self) 50 | } 51 | 52 | pub fn next(&mut self, env: &JsEnv) -> bool { 53 | if self.target.is_null() { 54 | return false; 55 | } 56 | 57 | let mut target = self.target.as_local(env); 58 | 59 | loop { 60 | match target.get_key(env, self.offset) { 61 | JsStoreKey::Key(name, enumerable) => { 62 | self.offset += 1; 63 | 64 | if self.add(env, name) && enumerable { 65 | self.name = Some(name); 66 | 67 | return true; 68 | } 69 | } 70 | JsStoreKey::Missing => { 71 | self.offset += 1; 72 | } 73 | JsStoreKey::End => { 74 | if let Some(prototype) = target.prototype(env) { 75 | target = prototype.unwrap_object(); 76 | 77 | self.target = target.as_ptr(); 78 | self.offset = 0; 79 | } else { 80 | self.target = Ptr::null(); 81 | self.offset = 0; 82 | self.name = None; 83 | 84 | return false; 85 | } 86 | } 87 | } 88 | } 89 | } 90 | 91 | fn add(&mut self, env: &JsEnv, name: Name) -> bool { 92 | let mut seen = self.seen.as_local(env); 93 | 94 | for i in 0..self.len { 95 | if seen[i] == name { 96 | return false; 97 | } 98 | } 99 | 100 | if self.len == seen.len() { 101 | let mut new_seen = env.heap.alloc_array_local::(GC_U32, seen.len() * 2); 102 | 103 | for i in 0..self.len { 104 | new_seen[i] = seen[i]; 105 | } 106 | 107 | self.seen = new_seen.as_ptr(); 108 | seen = new_seen; 109 | } 110 | 111 | seen[self.len] = name; 112 | self.len += 1; 113 | 114 | true 115 | } 116 | 117 | pub fn current(&self) -> Name { 118 | if let Some(name) = self.name { 119 | name 120 | } else { 121 | panic!("iterator has been exhausted"); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/rt/null.rs: -------------------------------------------------------------------------------- 1 | use rt::{JsItem, JsEnv, JsValue, JsDescriptor}; 2 | use ::{JsResult, JsError}; 3 | use syntax::Name; 4 | 5 | pub struct JsNull; 6 | 7 | impl JsItem for JsNull { 8 | fn as_value(&self) -> JsValue { 9 | JsValue::new_null() 10 | } 11 | 12 | fn get_property(&self, _: &JsEnv, _: Name) -> Option { 13 | None 14 | } 15 | 16 | fn get(&self, env: &mut JsEnv, _: Name) -> JsResult { 17 | Err(JsError::new_type(env, ::errors::TYPE_NULL)) 18 | } 19 | 20 | fn can_put(&self, _: &JsEnv, _: Name) -> bool { 21 | panic!("not supported"); 22 | } 23 | 24 | fn put(&mut self, env: &mut JsEnv, _: Name, _: JsValue, _: bool) -> JsResult<()> { 25 | Err(JsError::new_type(env, ::errors::TYPE_NULL)) 26 | } 27 | 28 | fn has_property(&self, _: &JsEnv, _: Name) -> bool { 29 | false 30 | } 31 | 32 | fn delete(&mut self, env: &mut JsEnv, _: Name, _: bool) -> JsResult { 33 | Err(JsError::new_type(env, ::errors::TYPE_NULL)) 34 | } 35 | 36 | fn define_own_property(&mut self, env: &mut JsEnv, _: Name, _: JsDescriptor, _: bool) -> JsResult { 37 | Err(JsError::new_type(env, ::errors::TYPE_NULL)) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/rt/number.rs: -------------------------------------------------------------------------------- 1 | use rt::{JsItem, JsEnv, JsValue, JsHandle}; 2 | 3 | pub struct JsNumber { 4 | value: f64 5 | } 6 | 7 | impl JsNumber { 8 | pub fn new(value: f64) -> JsNumber { 9 | JsNumber { 10 | value: value 11 | } 12 | } 13 | } 14 | 15 | impl JsItem for JsNumber { 16 | fn as_value(&self) -> JsValue { 17 | JsValue::new_number(self.value) 18 | } 19 | 20 | fn has_prototype(&self) -> bool { 21 | true 22 | } 23 | 24 | fn prototype(&self, env: &JsEnv) -> Option { 25 | Some(env.handle(JsHandle::Number).as_value()) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/rt/object/array_store.rs: -------------------------------------------------------------------------------- 1 | use rt::{JsEnv, JsDescriptor, GC_ARRAY_STORE, GC_ENTRY}; 2 | use rt::validate_walker_field; 3 | use rt::object::{Store, StoreKey, Entry}; 4 | use rt::object::hash_store::HashStore; 5 | use syntax::Name; 6 | use gc::{Local, GcWalker, GcAllocator, AsPtr, Ptr, ptr_t}; 7 | use std::mem::{transmute, zeroed, size_of}; 8 | use rt::object::sparse_array::SparseArray; 9 | 10 | // Modifications to this struct must be synchronized with the GC walker. 11 | #[repr(C)] 12 | pub struct ArrayStore { 13 | array: Ptr, 14 | props: Ptr 15 | } 16 | 17 | impl ArrayStore { 18 | pub fn new_local(env: &JsEnv) -> Local { 19 | let mut store = env.heap.alloc_local::(GC_ARRAY_STORE); 20 | let props = HashStore::new_local(env); 21 | let array = SparseArray::new_local(env); 22 | 23 | *store = ArrayStore { 24 | array: array.as_ptr(), 25 | props: props.as_ptr() 26 | }; 27 | 28 | store 29 | } 30 | } 31 | 32 | impl Local { 33 | fn props(&self, allocator: &T) -> Local { 34 | self.props.as_local(allocator) 35 | } 36 | 37 | fn array(&self, allocator: &T) -> Local { 38 | self.array.as_local(allocator) 39 | } 40 | } 41 | 42 | impl Store for Local { 43 | fn add(&mut self, env: &JsEnv, name: Name, value: &JsDescriptor) { 44 | if let Some(index) = name.index() { 45 | let mut entry = env.heap.alloc_local(GC_ENTRY); 46 | 47 | *entry = Entry::from_descriptor(value, name, -1); 48 | 49 | self.array(env).set_value(env, index, entry); 50 | } else { 51 | self.props(env).add(env, name, value); 52 | } 53 | } 54 | 55 | fn remove(&mut self, env: &JsEnv, name: Name) { 56 | if let Some(index) = name.index() { 57 | let mut entry = env.heap.alloc_local(GC_ENTRY); 58 | 59 | *entry = Entry::empty(); 60 | 61 | self.array(env).set_value(env, index, entry); 62 | } else { 63 | self.props(env).remove(env, name); 64 | } 65 | } 66 | 67 | fn get_value(&self, env: &JsEnv, name: Name) -> Option { 68 | if let Some(index) = name.index() { 69 | let mut entry = env.heap.alloc_local(GC_ENTRY); 70 | 71 | *entry = self.array(env).get_value(index); 72 | 73 | if entry.is_valid() { 74 | Some(entry.as_property(env)) 75 | } else { 76 | None 77 | } 78 | } else { 79 | self.props(env).get_value(env, name) 80 | } 81 | } 82 | 83 | fn replace(&mut self, env: &JsEnv, name: Name, value: &JsDescriptor) -> bool { 84 | if let Some(index) = name.index() { 85 | let mut array = self.array(env); 86 | 87 | if array.get_value(index).is_valid() { 88 | let mut entry = env.heap.alloc_local(GC_ENTRY); 89 | *entry = Entry::from_descriptor(value, name, -1); 90 | array.set_value(env, index, entry); 91 | return true; 92 | } 93 | 94 | false 95 | } else { 96 | self.props(env).replace(env, name, value) 97 | } 98 | } 99 | 100 | fn get_key(&self, env: &JsEnv, offset: usize) -> StoreKey { 101 | let array = self.array(env); 102 | let count = array.capacity(); 103 | 104 | if offset < count { 105 | self.array(env).get_key(offset) 106 | } else { 107 | match self.props(env).get_key(env, offset - count) { 108 | StoreKey::End(end) => StoreKey::End(end + count), 109 | result @ _ => result 110 | } 111 | } 112 | } 113 | 114 | fn capacity(&self, env: &JsEnv) -> usize { 115 | self.array(env).capacity() 116 | } 117 | } 118 | 119 | pub unsafe fn validate_walker_for_array_store(walker: &GcWalker) { 120 | let mut object : Box = Box::new(zeroed()); 121 | let ptr = transmute::<_, ptr_t>(&*object); 122 | 123 | object.array = Ptr::from_ptr(transmute(1usize)); 124 | validate_walker_field(walker, GC_ARRAY_STORE, ptr, true); 125 | object.array = Ptr::null(); 126 | 127 | object.props = Ptr::from_ptr(transmute(1usize)); 128 | validate_walker_field(walker, GC_ARRAY_STORE, ptr, true); 129 | object.props = Ptr::null(); 130 | 131 | assert_eq!(size_of::(), 16); 132 | } 133 | -------------------------------------------------------------------------------- /src/rt/object/sparse_array.rs: -------------------------------------------------------------------------------- 1 | use gc::{Array, Local, ptr_t, GcWalker}; 2 | use rt::JsEnv; 3 | use rt::object::{StoreKey, Entry}; 4 | use std::cmp::{min, max}; 5 | use rt::{GC_ENTRY, GC_ARRAY_CHUNK, GC_SPARSE_ARRAY, validate_walker_field}; 6 | use syntax::Name; 7 | use std::mem::{transmute, zeroed, size_of}; 8 | 9 | const CHUNK_SHIFT : usize = 5; 10 | const CHUNK_SIZE : usize = 1 << CHUNK_SHIFT; 11 | const INITIAL_VALUE_SIZE : usize = 20; 12 | const INITIAL_CHUNK_COUNT : usize = 10; 13 | const MAX_ARRAY_SIZE : usize = 1024; 14 | const MAX_ARRAY_SIZE_FILL_FACTOR : f64 = 0.5; 15 | 16 | #[repr(C)] 17 | pub struct SparseArray { 18 | items: Array, 19 | chunks: Array, 20 | chunk_count: usize, 21 | used: usize 22 | } 23 | 24 | #[derive(Copy, Clone)] 25 | #[repr(C)] 26 | struct Chunk { 27 | offset: usize, 28 | items: Array 29 | } 30 | 31 | impl Chunk { 32 | fn new(env: &JsEnv, offset: usize) -> Chunk { 33 | Chunk { 34 | offset: offset, 35 | items: unsafe { env.heap.alloc_array(GC_ENTRY, CHUNK_SIZE) } 36 | } 37 | } 38 | } 39 | 40 | #[derive(Copy, Clone, Debug)] 41 | struct ChunkIndex(u32); 42 | 43 | impl ChunkIndex { 44 | fn new(index: usize, found: bool) -> ChunkIndex { 45 | let mut store = (index & 0x7FFFFFFF) as u32; 46 | if found { 47 | store |= 0x80000000; 48 | } 49 | ChunkIndex(store) 50 | } 51 | 52 | fn found(&self) -> bool { 53 | (self.0 & 0x80000000) != 0 54 | } 55 | 56 | fn index(&self) -> usize { 57 | (self.0 & 0x7FFFFFFF) as usize 58 | } 59 | } 60 | 61 | impl SparseArray { 62 | pub fn new_local(env: &JsEnv) -> Local { 63 | let mut array = env.heap.alloc_local::(GC_SPARSE_ARRAY); 64 | 65 | *array = SparseArray { 66 | items: unsafe { env.heap.alloc_array(GC_ENTRY, INITIAL_VALUE_SIZE) }, 67 | chunks: Array::null(), 68 | chunk_count: 0, 69 | used: 0 70 | }; 71 | 72 | array 73 | } 74 | } 75 | 76 | impl Local { 77 | pub fn capacity(&self) -> usize { 78 | let items = self.items; 79 | 80 | if !items.is_null() { 81 | items.len() 82 | } else { 83 | self.chunk_count * CHUNK_SIZE 84 | } 85 | } 86 | 87 | pub fn get_value(&self, index: usize) -> Entry { 88 | let items = self.items; 89 | 90 | if !items.is_null() { 91 | if index < items.len() { 92 | items[index] 93 | } else { 94 | Entry::empty() 95 | } 96 | } else { 97 | let offset = Self::get_offset_from_index(index); 98 | let chunk = self.find_chunk(offset); 99 | if chunk.found() { 100 | self.chunks[chunk.index()].items[index - offset] 101 | } else { 102 | Entry::empty() 103 | } 104 | } 105 | } 106 | 107 | pub fn set_value(&mut self, env: &JsEnv, index: usize, value: Local) { 108 | if !self.items.is_null() { 109 | self.used += 1; 110 | 111 | let len = self.items.len(); 112 | 113 | if index < len { 114 | self.items[index] = *value; 115 | return; 116 | } 117 | 118 | // If someone is specifically hitting our growth strategy, we 119 | // may end up with a very large array that is barely filled. 120 | // We stop this process when we go over MAX_ARRAY_SIZE. 121 | // 122 | // TODO #70: This can (will?) allocate empty chunks. We should 123 | // check for that and skip those. 124 | 125 | let transfer = if len >= MAX_ARRAY_SIZE { 126 | let fill_factor = self.used as f64 / len as f64; 127 | fill_factor < MAX_ARRAY_SIZE_FILL_FACTOR 128 | } else { 129 | index >= len * 2 130 | }; 131 | 132 | if !transfer { 133 | // We allow the array to double in size every time 134 | // we grow it. 135 | 136 | self.grow_items(env); 137 | self.items[index] = *value; 138 | return; 139 | } 140 | 141 | // We have a real array, but not enough room. Transfer the 142 | // values to chunks and continue. 143 | 144 | self.transfer_to_chunks(env); 145 | } 146 | 147 | let offset = Self::get_offset_from_index(index); 148 | let chunk = self.find_or_create_chunk(env, offset); 149 | self.chunks[chunk.index()].items[index - offset] = *value; 150 | } 151 | 152 | fn get_offset_from_index(index: usize) -> usize { 153 | index & !(CHUNK_SIZE - 1) 154 | } 155 | 156 | fn transfer_to_chunks(&mut self, env: &JsEnv) { 157 | let chunk_count = (self.items.len() >> CHUNK_SHIFT) + 1; 158 | self.chunks = unsafe { env.heap.alloc_array(GC_ARRAY_CHUNK, max(chunk_count, INITIAL_CHUNK_COUNT)) }; 159 | 160 | for i in 0..chunk_count { 161 | let offset = i * CHUNK_SIZE; 162 | self.chunks[i] = Chunk::new(env, offset); 163 | 164 | let to_copy = if i < chunk_count - 1 { 165 | CHUNK_SIZE 166 | } else { 167 | min(self.items.len() - offset, CHUNK_SIZE) 168 | }; 169 | 170 | Array::copy(self.items, offset, self.chunks[i].items, 0, to_copy); 171 | } 172 | 173 | self.items = Array::null(); 174 | self.chunk_count = chunk_count; 175 | } 176 | 177 | fn grow_items(&mut self, env: &JsEnv) { 178 | let len = self.items.len(); 179 | let items = unsafe { env.heap.alloc_array(GC_ENTRY, len * 2) }; 180 | Array::copy(self.items, 0, items, 0, len); 181 | self.items = items; 182 | } 183 | 184 | fn find_or_create_chunk(&mut self, env: &JsEnv, offset: usize) -> ChunkIndex { 185 | let index = self.find_chunk(offset); 186 | 187 | if !index.found() { 188 | let chunk = Chunk::new(env, offset); 189 | self.insert_chunk(env, chunk, index.index()); 190 | } 191 | 192 | index 193 | } 194 | 195 | fn insert_chunk(&mut self, env: &JsEnv, entry: Chunk, index: usize) { 196 | let chunk_count = self.chunk_count; 197 | 198 | // We never create the chunks here; they are created by TransferToChunks. 199 | 200 | assert!(chunk_count > 0); 201 | 202 | if self.chunks.len() == chunk_count { 203 | let new_size = max( 204 | (chunk_count as f64 * 1.2) as usize, 205 | chunk_count + 1 206 | ); 207 | 208 | let mut destination = unsafe { env.heap.alloc_array(GC_ARRAY_CHUNK, new_size) }; 209 | Array::copy(self.chunks, 0, destination, 0, index); 210 | destination[index] = entry; 211 | Array::copy(self.chunks, index, destination, index + 1, chunk_count - index); 212 | 213 | self.chunks = destination; 214 | } else { 215 | Array::copy(self.chunks, index, self.chunks, index + 1, chunk_count - index); 216 | self.chunks[index] = entry; 217 | } 218 | 219 | self.chunk_count += 1; 220 | } 221 | 222 | fn find_chunk(&self, offset: usize) -> ChunkIndex { 223 | let chunks = self.chunks; 224 | 225 | let mut lo = 0; 226 | let mut hi = self.chunk_count; 227 | 228 | if hi <= 0 { 229 | return ChunkIndex::new(0, false); 230 | } 231 | 232 | while hi - lo > 3 { 233 | let pv = (hi + lo) / 2; 234 | let check_offset = chunks[pv].offset; 235 | 236 | if offset == check_offset { 237 | return ChunkIndex::new(pv, true); 238 | } 239 | if offset <= check_offset { 240 | hi = pv; 241 | } else { 242 | lo = pv + 1; 243 | } 244 | } 245 | 246 | loop { 247 | let check_offset = chunks[lo].offset; 248 | 249 | if check_offset == offset { 250 | return ChunkIndex::new(lo, true); 251 | } 252 | if check_offset > offset { 253 | break; 254 | } 255 | lo += 1; 256 | 257 | if lo >= hi { 258 | break; 259 | } 260 | } 261 | 262 | ChunkIndex::new(lo, false) 263 | } 264 | 265 | pub fn get_key(&self, offset: usize) -> StoreKey { 266 | let items = self.items; 267 | 268 | if !items.is_null() { 269 | let entry = items[offset]; 270 | 271 | if entry.is_valid() { 272 | StoreKey::Key(Name::from_index(offset), entry.is_enumerable()) 273 | } else { 274 | StoreKey::Missing 275 | } 276 | } else { 277 | let chunk = &self.chunks[offset >> CHUNK_SHIFT]; 278 | let items = chunk.items; 279 | if items.is_null() { 280 | StoreKey::Missing 281 | } else { 282 | let offset = offset & (CHUNK_SIZE - 1); 283 | let entry = items[offset]; 284 | if entry.is_valid() { 285 | StoreKey::Key(Name::from_index(chunk.offset + offset), entry.is_enumerable()) 286 | } else { 287 | StoreKey::Missing 288 | } 289 | } 290 | } 291 | } 292 | } 293 | 294 | pub unsafe fn validate_walker(walker: &GcWalker) { 295 | validate_walker_for_sparse_array(walker); 296 | validate_walker_for_array_chunk(walker); 297 | } 298 | 299 | unsafe fn validate_walker_for_sparse_array(walker: &GcWalker) { 300 | let mut object : Box = Box::new(zeroed()); 301 | let ptr = transmute::<_, ptr_t>(&*object); 302 | 303 | object.items = Array::from_ptr(transmute(1usize)); 304 | validate_walker_field(walker, GC_SPARSE_ARRAY, ptr, true); 305 | object.items = Array::null(); 306 | 307 | object.chunks = Array::from_ptr(transmute(1usize)); 308 | validate_walker_field(walker, GC_SPARSE_ARRAY, ptr, true); 309 | object.chunks = Array::null(); 310 | 311 | object.chunk_count = 1; 312 | validate_walker_field(walker, GC_SPARSE_ARRAY, ptr, false); 313 | object.chunk_count = 0; 314 | 315 | object.used = 1; 316 | validate_walker_field(walker, GC_SPARSE_ARRAY, ptr, false); 317 | object.used = 0; 318 | 319 | assert_eq!(size_of::(), 32); 320 | } 321 | 322 | unsafe fn validate_walker_for_array_chunk(walker: &GcWalker) { 323 | let mut object : Box = Box::new(zeroed()); 324 | let ptr = transmute::<_, ptr_t>(&*object); 325 | 326 | object.offset = 1; 327 | validate_walker_field(walker, GC_ARRAY_CHUNK, ptr, false); 328 | object.offset = 0; 329 | 330 | object.items = Array::from_ptr(transmute(1usize)); 331 | validate_walker_field(walker, GC_ARRAY_CHUNK, ptr, true); 332 | object.items = Array::null(); 333 | 334 | assert_eq!(size_of::(), 16); 335 | } 336 | -------------------------------------------------------------------------------- /src/rt/regexp.rs: -------------------------------------------------------------------------------- 1 | extern crate regex; 2 | 3 | use rt::{JsEnv, JsValue, JsString, GC_REGEXP}; 4 | use rt::validate_walker_field; 5 | use gc::*; 6 | use self::regex::Regex; 7 | use util::manualbox::ManualBox; 8 | use ::{JsResult, JsError}; 9 | use std::mem::{transmute, zeroed, size_of}; 10 | 11 | // Modifications to this struct must be synchronized with the GC walker. 12 | #[repr(C)] 13 | pub struct JsRegExp { 14 | pattern: Ptr, 15 | flags: Ptr, 16 | regex: ManualBox, 17 | global: bool, 18 | ignore_case: bool, 19 | multiline: bool 20 | } 21 | 22 | impl JsRegExp { 23 | pub fn new_local(env: &mut JsEnv, pattern: Local, flags: Local, global: bool, ignore_case: bool, multiline: bool) -> JsResult> { 24 | fn is_hex_char(c: char) -> bool { 25 | (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') 26 | } 27 | 28 | let mut result = env.heap.alloc_local::(GC_REGEXP); 29 | 30 | result.pattern = pattern.as_ptr(); 31 | result.flags = flags.as_ptr(); 32 | result.global = global; 33 | result.ignore_case = ignore_case; 34 | result.multiline = multiline; 35 | 36 | let pattern = pattern.to_string().chars().collect::>(); 37 | let mut translated = String::with_capacity(pattern.len()); 38 | 39 | // Translate the JavaScript regular expression into a Rust regular expression. 40 | // Currently we support the following translations: 41 | // 42 | // * Ignore case to (?i) and multiline to (?m); 43 | // * \cA - \cZ to its corresponding control code, which is translated to \xXX; 44 | // * \uXXXX to \x{XXXX}; 45 | // * All characters that can be escaped using \, which is translated to \xXX. 46 | // 47 | 48 | if ignore_case || multiline { 49 | if ignore_case { 50 | translated.push_str("(?i)"); 51 | } 52 | if multiline { 53 | translated.push_str("(?m)"); 54 | } 55 | } 56 | 57 | let mut i = 0; 58 | let mut in_class = false; 59 | 60 | while i < pattern.len() { 61 | match pattern[i] { 62 | '[' if !in_class => { 63 | in_class = true; 64 | translated.push('['); 65 | } 66 | ']' if in_class => { 67 | in_class = false; 68 | translated.push(']'); 69 | } 70 | '\\' => { 71 | if i + 2 < pattern.len() && pattern[i + 1] == 'c' { 72 | let c = pattern[i + 2]; 73 | if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') { 74 | i += 2; 75 | translated.push_str(&format!("\\x{:02X}", (c as u32) % 32)); 76 | } else { 77 | // If the letter of the control code is invalid, escape the \ 78 | // to make it a valid regular expression. 79 | translated.push_str("\\\\"); 80 | } 81 | } else if 82 | i + 5 < pattern.len() && 83 | pattern[i + 1] == 'u' && 84 | is_hex_char(pattern[i + 2]) && 85 | is_hex_char(pattern[i + 3]) && 86 | is_hex_char(pattern[i + 4]) && 87 | is_hex_char(pattern[i + 5]) 88 | { 89 | translated.push_str("\\x{"); 90 | i += 1; 91 | for _ in 0..4 { 92 | i += 1; 93 | translated.push(pattern[i]); 94 | } 95 | translated.push('}'); 96 | } else if i + 1 < pattern.len() { 97 | // Patterns like \[ match the raw [ character, which is supported by 98 | // the regex crate. However, JavaScript supports more of these escapes. 99 | // Here all such escapes are translated to their \xXX format. 100 | 101 | match pattern[i + 1] { 102 | c @ '~' | c @ '`' | c @ '!' | c @ '@' | c @ '#' | c @ '$' | c @ '%' | c @ '^' | c @ '&' | 103 | c @ '*' | c @ '(' | c @ ')' | c @ '-' | c @ '+' | c @ '=' | c @ '{' | c @ '[' | c @ '}' | 104 | c @ ']' | c @ '|' | c @ '\\' | c @ ':' | c @ ';' | c @ '\'' | c @ '<' | c @ ',' | c @ '>' | 105 | c @ '.' | c @ '/' | c @ '?' | c @ '"' => { 106 | i += 1; 107 | translated.push_str(&format!("\\x{:02X}", c as u32)); 108 | } 109 | 'b' => { 110 | if in_class { 111 | // Backspace escape sequence (only applicable in a character class) is not supported by 112 | // regex; translate to \x08. 113 | i += 1; 114 | translated.push_str("\\x08"); 115 | } else { 116 | // Otherwise it's just a word boundary escape. 117 | translated.push('\\'); 118 | } 119 | } 120 | 'd' | 'D' | 'w' | 'W' | 's' | 'S' | 't' | 'r' | 'n' | 'x' | 'B' => { 121 | // These are supported escapes, which match assertions or character classes. 122 | translated.push('\\'); 123 | } 124 | 'v' => { 125 | // Vertical tab escape sequence is not supported by regex; translate to \x0B. 126 | i += 1; 127 | translated.push_str("\\x0B"); 128 | } 129 | 'f' => { 130 | // Form feed escape sequence is not supported by regex; translate to \x0C. 131 | i += 1; 132 | translated.push_str("\\x0C"); 133 | } 134 | '0' => { 135 | // NUL escape sequence is not supported by regex; translate to \x00. 136 | i += 1; 137 | translated.push_str("\\x00"); 138 | } 139 | _ => { 140 | // When the character following the backslash doesn't match any of the 141 | // supported characters, it's dropped. 142 | } 143 | } 144 | } else { 145 | translated.push('\\'); 146 | } 147 | } 148 | c @ _ => translated.push(c) 149 | } 150 | 151 | i += 1; 152 | } 153 | 154 | let regex = match Regex::new(&translated) { 155 | Ok(regex) => regex, 156 | Err(..) => return Err(JsError::new_syntax(env, ::errors::SYNTAX_INVALID_REGEX)) 157 | }; 158 | 159 | result.regex = ManualBox::new(regex); 160 | 161 | Ok(result) 162 | } 163 | 164 | pub fn finalize(&mut self) { 165 | self.regex.drop(); 166 | } 167 | } 168 | 169 | impl Local { 170 | pub fn as_value(&self) -> JsValue { 171 | JsValue::new_regexp(*self) 172 | } 173 | 174 | pub fn regex<'a>(&'a self) -> &'a Regex { 175 | &*self.regex 176 | } 177 | 178 | pub fn pattern(&self, env: &JsEnv) -> Local { 179 | self.pattern.as_local(env) 180 | } 181 | 182 | pub fn flags(&self, env: &JsEnv) -> Local { 183 | self.flags.as_local(env) 184 | } 185 | 186 | pub fn global(&self) -> bool { 187 | self.global 188 | } 189 | 190 | pub fn ignore_case(&self) -> bool { 191 | self.ignore_case 192 | } 193 | 194 | pub fn multiline(&self) -> bool { 195 | self.multiline 196 | } 197 | } 198 | 199 | pub unsafe fn validate_walker(walker: &GcWalker) { 200 | let mut object : Box = Box::new(zeroed()); 201 | let ptr = transmute::<_, ptr_t>(&*object); 202 | 203 | object.pattern = Ptr::from_ptr(transmute(1usize)); 204 | validate_walker_field(walker, GC_REGEXP, ptr, true); 205 | object.pattern = Ptr::null(); 206 | 207 | object.flags = Ptr::from_ptr(transmute(1usize)); 208 | validate_walker_field(walker, GC_REGEXP, ptr, true); 209 | object.flags = Ptr::null(); 210 | 211 | object.regex = transmute(1usize); 212 | validate_walker_field(walker, GC_REGEXP, ptr, false); 213 | object.regex = transmute(0usize); 214 | 215 | object.global = true; 216 | validate_walker_field(walker, GC_REGEXP, ptr, false); 217 | object.global = false; 218 | 219 | object.ignore_case = true; 220 | validate_walker_field(walker, GC_REGEXP, ptr, false); 221 | object.ignore_case = false; 222 | 223 | object.multiline = true; 224 | validate_walker_field(walker, GC_REGEXP, ptr, false); 225 | object.multiline = false; 226 | 227 | assert_eq!(size_of::(), 32); 228 | } 229 | -------------------------------------------------------------------------------- /src/rt/scope.rs: -------------------------------------------------------------------------------- 1 | use rt::{JsEnv, JsRawValue, JsValue, JsObject, JsItem, GC_SCOPE, GC_VALUE}; 2 | use gc::*; 3 | 4 | // Modifications to this struct must be synchronized with the GC walker. 5 | pub struct JsScope { 6 | items: Array 7 | } 8 | 9 | impl JsScope { 10 | pub fn new_local_thin(env: &JsEnv, size: usize, parent: Option>) -> Local { 11 | let mut result = env.heap.alloc_local::(GC_SCOPE); 12 | 13 | unsafe { 14 | result.items = env.heap.alloc_array(GC_VALUE, size + 1); 15 | } 16 | 17 | if let Some(parent) = parent { 18 | result.raw_set(0, parent.as_value()); 19 | } 20 | 21 | result 22 | } 23 | 24 | pub fn new_local_thick(env: &JsEnv, scope_object: Local, parent: Option>, arguments: bool) -> Local { 25 | let mut result = env.heap.alloc_local::(GC_SCOPE); 26 | 27 | let size = 2 + if arguments { 1 } else { 0 }; 28 | 29 | unsafe { 30 | result.items = env.heap.alloc_array(GC_VALUE, size); 31 | } 32 | 33 | if let Some(parent) = parent { 34 | result.raw_set(0, parent.as_value()); 35 | } 36 | result.raw_set(1, scope_object.as_value()); 37 | 38 | result 39 | } 40 | } 41 | 42 | impl Local { 43 | pub fn as_value(&self) -> JsValue { 44 | JsValue::new_scope(*self) 45 | } 46 | 47 | pub fn parent(&self, env: &JsEnv) -> Option> { 48 | let parent = self.raw_get(env, 0); 49 | 50 | if parent.is_undefined() { None } else { Some(parent.unwrap_scope()) } 51 | } 52 | 53 | pub fn scope_object(&self, env: &JsEnv) -> Local { 54 | self.raw_get(env, 1).unwrap_object() 55 | } 56 | 57 | pub fn arguments(&self, env: &JsEnv) -> Option { 58 | if self.items.len() == 2 { 59 | None 60 | } else { 61 | Some(self.raw_get(env, 2)) 62 | } 63 | } 64 | 65 | pub fn set_arguments(&mut self, arguments: JsValue) { 66 | if self.items.len() == 2 { 67 | panic!("scope does not have a slot to store arguments"); 68 | } 69 | 70 | self.raw_set(2, arguments); 71 | } 72 | 73 | pub fn len(&self) -> usize { 74 | self.items.len() - 1 75 | } 76 | 77 | pub fn get(&self, env: &JsEnv, index: usize) -> JsValue { 78 | self.raw_get(env, index + 1) 79 | } 80 | 81 | pub fn set(&mut self, index: usize, value: JsValue) { 82 | self.raw_set(index + 1, value) 83 | } 84 | 85 | fn raw_get(&self, env: &JsEnv, index: usize) -> JsValue { 86 | self.items[index].as_value(env) 87 | } 88 | 89 | fn raw_set(&mut self, index: usize, value: JsValue) { 90 | self.items[index] = value.as_raw(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/rt/stack.rs: -------------------------------------------------------------------------------- 1 | use gc::{GcRootWalker, ptr_t}; 2 | use gc::os::Memory; 3 | use rt::{JsEnv, JsRawValue, JsValue, JsType}; 4 | use std::mem::{size_of, transmute}; 5 | use std::ptr; 6 | use std::cell::Cell; 7 | 8 | const STACK : usize = 8192; 9 | 10 | pub struct Stack { 11 | stack: Memory, 12 | sp: Cell, 13 | end: ptr_t 14 | } 15 | 16 | impl Stack { 17 | pub fn new() -> Stack { 18 | let stack = Memory::alloc(STACK).unwrap(); 19 | let (sp, end) = unsafe { (stack.ptr(), stack.ptr().offset(STACK as isize)) }; 20 | 21 | Stack { 22 | stack: stack, 23 | sp: Cell::new(sp), 24 | end: end 25 | } 26 | } 27 | 28 | pub fn create_walker(&self) -> Box { 29 | unsafe { 30 | let start = self.stack.ptr(); 31 | let end = self.sp.get(); 32 | 33 | tracegc!("creating stack walker from {:?} to {:?}", start, end); 34 | 35 | Box::new(StackWalker { 36 | ptr: transmute(start), 37 | end: transmute(end) 38 | }) 39 | } 40 | } 41 | 42 | pub fn create_frame(&self, size: usize) -> StackFrame { 43 | StackFrame { 44 | sp: unsafe { self.sp.get().offset(-((size_of::() * size) as isize)) } 45 | } 46 | } 47 | 48 | pub fn drop_frame(&self, frame: StackFrame) { 49 | self.sp.set(frame.sp); 50 | } 51 | 52 | pub fn push(&self, value: JsRawValue) { 53 | if self.sp.get() == self.end { 54 | panic!("stack overflow"); 55 | } 56 | 57 | unsafe { 58 | *transmute::<_, *mut JsRawValue>(self.sp.get()) = value; 59 | self.sp.set(self.sp.get().offset(size_of::() as isize)); 60 | } 61 | } 62 | 63 | #[allow(dead_code)] 64 | pub fn pop(&self) -> JsRawValue { 65 | unsafe { 66 | let sp = self.sp.get().offset(-(size_of::() as isize)); 67 | self.sp.set(sp); 68 | *transmute::<_, *mut JsRawValue>(sp) 69 | } 70 | } 71 | } 72 | 73 | #[derive(Copy)] 74 | pub struct StackFrame { 75 | sp: ptr_t 76 | } 77 | 78 | impl StackFrame { 79 | pub fn get(&self, env: &JsEnv, offset: usize) -> JsValue { 80 | self.raw_get(offset).as_value(env) 81 | } 82 | 83 | pub fn raw_get(&self, offset: usize) -> JsRawValue { 84 | unsafe { 85 | *transmute::<_, *mut JsRawValue>( 86 | self.sp.offset((size_of::() * offset) as isize) 87 | ) 88 | } 89 | } 90 | 91 | pub fn set(&self, offset: usize, value: JsRawValue) { 92 | unsafe { 93 | *transmute::<_, *mut JsRawValue>( 94 | self.sp.offset((size_of::() * offset) as isize) 95 | ) = value 96 | } 97 | } 98 | } 99 | 100 | impl Clone for StackFrame { 101 | fn clone(&self) -> StackFrame { 102 | StackFrame { 103 | sp: self.sp 104 | } 105 | } 106 | } 107 | 108 | struct StackWalker { 109 | ptr: *mut ptr_t, 110 | end: *mut ptr_t 111 | } 112 | 113 | impl GcRootWalker for StackWalker { 114 | unsafe fn next(&mut self) -> *mut ptr_t { 115 | while self.ptr < self.end { 116 | let ptr = transmute::<_, *mut usize>(self.ptr); 117 | self.ptr = transmute(self.ptr.offset(2)); 118 | 119 | let ty = *transmute::<_, *const JsType>(ptr); 120 | if ty.is_ptr() { 121 | return transmute(ptr.offset(1)); 122 | } 123 | } 124 | 125 | ptr::null_mut() 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/rt/string.rs: -------------------------------------------------------------------------------- 1 | use gc::{Array, Local}; 2 | use rt::{JsEnv, JsValue, JsItem, JsDescriptor, JsHandle, GC_STRING, GC_U16}; 3 | use rt::utf; 4 | use syntax::Name; 5 | use syntax::token::name; 6 | 7 | // Modifications to this struct must be synchronized with the GC walker. 8 | pub struct JsString { 9 | chars: Array 10 | } 11 | 12 | impl JsString { 13 | pub fn new_local(env: &JsEnv, size: usize) -> Local { 14 | let mut result = env.heap.alloc_local::(GC_STRING); 15 | 16 | unsafe { 17 | result.chars = env.heap.alloc_array(GC_U16, size); 18 | } 19 | 20 | result 21 | } 22 | 23 | pub fn from_str<'a>(env: &'a JsEnv, string: &str) -> Local { 24 | let chars = utf::utf32_to_utf16( 25 | &string.chars().map(|c| c as u32).collect::>()[..], 26 | false 27 | ); 28 | 29 | let mut result = Self::new_local(env, chars.len()); 30 | 31 | { 32 | let result_chars = &mut *result.chars; 33 | 34 | for i in 0..chars.len() { 35 | result_chars[i] = chars[i]; 36 | } 37 | } 38 | 39 | result 40 | } 41 | 42 | pub fn from_u16(env: &JsEnv, chars: &[u16]) -> Local { 43 | // TODO #84: Most of the calls to this function take the chars from the GC 44 | // heap. Because of this we create a copy of chars. However, this must 45 | // be changed so that this extra copy is unnecessary. 46 | 47 | let mut copy = Vec::with_capacity(chars.len()); 48 | for i in 0..chars.len() { 49 | copy.push(chars[i]); 50 | } 51 | 52 | let result = JsString::new_local(env, copy.len()); 53 | 54 | let mut result_chars = result.chars; 55 | 56 | for i in 0..copy.len() { 57 | result_chars[i] = copy[i]; 58 | } 59 | 60 | result 61 | } 62 | 63 | pub fn chars(&self) -> &[u16] { 64 | &*self.chars 65 | } 66 | 67 | pub fn concat<'a>(env: &'a JsEnv, strings: &[Local]) -> Local { 68 | let mut len = 0; 69 | for string in strings { 70 | len += string.chars().len(); 71 | } 72 | 73 | let mut result = Self::new_local(&env, len); 74 | 75 | { 76 | let chars = &mut *result.chars; 77 | let mut offset = 0; 78 | 79 | for string in strings { 80 | let string_chars = string.chars(); 81 | for i in 0..string_chars.len() { 82 | chars[offset] = string_chars[i]; 83 | offset += 1; 84 | } 85 | } 86 | } 87 | 88 | result 89 | } 90 | 91 | pub fn equals(x: Local, y: Local) -> bool { 92 | let x_chars = &*x.chars; 93 | let y_chars = &*y.chars; 94 | 95 | if x_chars.len() != y_chars.len() { 96 | false 97 | } else { 98 | for i in 0..x_chars.len() { 99 | if x_chars[i] != y_chars[i] { 100 | return false 101 | } 102 | } 103 | 104 | true 105 | } 106 | } 107 | 108 | pub fn to_string(&self) -> String { 109 | ::rt::utf::utf16_to_string(&*self.chars) 110 | } 111 | } 112 | 113 | impl JsItem for Local { 114 | fn as_value(&self) -> JsValue { 115 | JsValue::new_string(*self) 116 | } 117 | 118 | fn has_prototype(&self) -> bool { 119 | true 120 | } 121 | 122 | fn prototype(&self, env: &JsEnv) -> Option { 123 | Some(env.handle(JsHandle::String).as_value()) 124 | } 125 | 126 | // 15.5.5.2 [[GetOwnProperty]] ( P ) 127 | // 15.5.5.1 length 128 | fn get_own_property(&self, env: &JsEnv, property: Name) -> Option { 129 | if property == name::LENGTH { 130 | let value = JsValue::new_number(self.chars.len() as f64); 131 | return Some(JsDescriptor::new_value(value, false, false, false)); 132 | } 133 | 134 | if let Some(index) = property.index() { 135 | let chars = self.chars; 136 | if index < chars.len() { 137 | let char = chars[index]; 138 | let mut string = JsString::new_local(env, 1); 139 | string.chars[0] = char; 140 | return Some(JsDescriptor::new_value(string.as_value(), false, true, false)); 141 | } 142 | } 143 | 144 | None 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/rt/undefined.rs: -------------------------------------------------------------------------------- 1 | use rt::{JsItem, JsEnv, JsValue, JsDescriptor}; 2 | use ::{JsResult, JsError}; 3 | use syntax::Name; 4 | 5 | pub struct JsUndefined; 6 | 7 | impl JsItem for JsUndefined { 8 | fn as_value(&self) -> JsValue { 9 | JsValue::new_undefined() 10 | } 11 | 12 | fn get_property(&self, _: &JsEnv, _: Name) -> Option { 13 | None 14 | } 15 | 16 | fn get(&self, env: &mut JsEnv, _: Name) -> JsResult { 17 | Err(JsError::new_type(env, ::errors::TYPE_UNDEFINED)) 18 | } 19 | 20 | fn can_put(&self, _: &JsEnv, _: Name) -> bool { 21 | panic!("not supported"); 22 | } 23 | 24 | fn put(&mut self, env: &mut JsEnv, _: Name, _: JsValue, _: bool) -> JsResult<()> { 25 | Err(JsError::new_type(env, ::errors::TYPE_UNDEFINED)) 26 | } 27 | 28 | fn has_property(&self, _: &JsEnv, _: Name) -> bool { 29 | false 30 | } 31 | 32 | fn delete(&mut self, env: &mut JsEnv, _: Name, _: bool) -> JsResult { 33 | Err(JsError::new_type(env, ::errors::TYPE_UNDEFINED)) 34 | } 35 | 36 | fn define_own_property(&mut self, env: &mut JsEnv, _: Name, _: JsDescriptor, _: bool) -> JsResult { 37 | Err(JsError::new_type(env, ::errors::TYPE_UNDEFINED)) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/rt/utf.rs: -------------------------------------------------------------------------------- 1 | use std::char; 2 | 3 | const UNI_MAX_BMP : u32 = 0xFFFF; 4 | const UNI_SUR_HIGH_START : u32 = 0xD800; 5 | const UNI_SUR_LOW_START : u32 = 0xDC00; 6 | const UNI_SUR_LOW_END : u32 = 0xDFFF; 7 | const UNI_REPLACEMENT_CHAR : u32 = 0x0000FFFD; 8 | const UNI_MAX_LEGAL_UTF32 : u32 = 0x0010FFFF; 9 | 10 | const HALF_BASE : u32 = 0x0010000; 11 | const HALF_MASK : u32 = 0x3FF; 12 | const HALF_SHIFT : u32 = 10; 13 | 14 | /*===--- ConvertUTF.c - Universal Character Names conversions ---------------=== 15 | * 16 | * The LLVM Compiler Infrastructure 17 | * 18 | * This file is distributed under the University of Illinois Open Source 19 | * License. See LICENSE.TXT for details. 20 | * 21 | *===------------------------------------------------------------------------=*/ 22 | /* 23 | * Copyright 2001-2004 Unicode, Inc. 24 | * 25 | * Disclaimer 26 | * 27 | * This source code is provided as is by Unicode, Inc. No claims are 28 | * made as to fitness for any particular purpose. No warranties of any 29 | * kind are expressed or implied. The recipient agrees to determine 30 | * applicability of information provided. If this file has been 31 | * purchased on magnetic or optical media from Unicode, Inc., the 32 | * sole remedy for any claim will be exchange of defective media 33 | * within 90 days of receipt. 34 | * 35 | * Limitations on Rights to Redistribute This Code 36 | * 37 | * Unicode, Inc. hereby grants the right to freely use the information 38 | * supplied in this file in the creation of products supporting the 39 | * Unicode Standard, and to make copies of this file in any form 40 | * for internal or external distribution as long as this notice 41 | * remains attached. 42 | */ 43 | 44 | pub fn utf32_to_utf16(source: &[u32], strict: bool) -> Vec { 45 | let mut i = 0; 46 | let mut target : Vec = Vec::new(); 47 | 48 | while i < source.len() { 49 | let ch = source[i]; 50 | i += 1; 51 | 52 | if ch <= UNI_MAX_BMP { // Target is a character <= 0xFFFF 53 | // UTF-16 surrogate values are illegal in UTF-32; 0xffff or 0xfffe are both reserved values 54 | 55 | if ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END { 56 | if strict { 57 | panic!("source illegal"); 58 | } else { 59 | target.push(UNI_REPLACEMENT_CHAR as u16); 60 | } 61 | } else { 62 | target.push(ch as u16); 63 | } 64 | } else if ch > UNI_MAX_LEGAL_UTF32 { 65 | if strict { 66 | panic!("source illegal"); 67 | } else { 68 | target.push(UNI_REPLACEMENT_CHAR as u16); 69 | } 70 | } else { 71 | // target is a character in range 0xFFFF - 0x10FFFF. 72 | let ch = ch - HALF_BASE; 73 | target.push(((ch >> HALF_SHIFT) + UNI_SUR_HIGH_START) as u16); 74 | target.push(((ch & HALF_MASK) + UNI_SUR_LOW_START) as u16); 75 | } 76 | } 77 | 78 | target 79 | } 80 | 81 | pub fn utf16_to_string(chars: &[u16]) -> String { 82 | if let Ok(result) = String::from_utf16(chars) { 83 | result 84 | } else { 85 | // TODO #60: We can do better than this. This kind of works but 86 | // it gives an invalid result on code points that consist 87 | // out of multiple u16's, but are still valid code points. 88 | // We should implement this in utf.rs. 89 | 90 | let mut result = String::new(); 91 | 92 | for i in 0..chars.len() { 93 | let c = chars[i]; 94 | 95 | if let Some(c) = char::from_u32(c as u32) { 96 | result.push(c) 97 | } else { 98 | result.push('�'); 99 | } 100 | } 101 | 102 | result 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/rt/walker.rs: -------------------------------------------------------------------------------- 1 | use gc::{GcWalker, GcWalk, GcFinalize, GcRootWalker, ptr_t}; 2 | use rt::{JsType, JsRegExp, JsObject}; 3 | use rt::{GC_ARRAY_STORE, GC_ENTRY, GC_HASH_STORE, GC_ITERATOR, GC_OBJECT, GC_REGEXP}; 4 | use rt::{GC_SCOPE, GC_STRING, GC_U16, GC_U32, GC_VALUE, GC_SPARSE_ARRAY, GC_ARRAY_CHUNK}; 5 | use rt::stack::Stack; 6 | use std::mem::transmute; 7 | use std::rc::Rc; 8 | 9 | #[inline(always)] 10 | unsafe fn is_value_ptr(ptr: ptr_t, offset: usize) -> bool { 11 | let ptr = transmute::<_, *const usize>(ptr).offset(offset as isize); 12 | let ty = transmute::<_, *const JsType>(ptr); 13 | (*ty).is_ptr() 14 | 15 | } 16 | 17 | pub struct Walker { 18 | stack: Rc 19 | } 20 | 21 | impl Walker { 22 | pub fn new(stack: Rc) -> Walker { 23 | Walker { 24 | stack: stack 25 | } 26 | } 27 | } 28 | 29 | #[cfg(target_pointer_width = "64")] 30 | impl GcWalker for Walker { 31 | fn walk(&self, ty: u32, ptr: ptr_t, index: u32) -> GcWalk { 32 | unsafe { 33 | match ty { 34 | GC_ARRAY_STORE => { 35 | match index { 36 | 0 | 1 => GcWalk::Pointer, 37 | _ => GcWalk::Skip 38 | } 39 | } 40 | GC_ENTRY => { 41 | match index { 42 | 3 if is_value_ptr(ptr, 2) => GcWalk::Pointer, 43 | 5 if is_value_ptr(ptr, 4) => GcWalk::Pointer, 44 | _ => GcWalk::Skip 45 | } 46 | } 47 | GC_HASH_STORE => { 48 | match index { 49 | 0 => GcWalk::Pointer, 50 | _ => GcWalk::Skip 51 | } 52 | } 53 | GC_ITERATOR => { 54 | match index { 55 | 0 | 3 => GcWalk::Pointer, 56 | _ => GcWalk::Skip 57 | } 58 | } 59 | GC_OBJECT => { 60 | match index { 61 | 3 if is_value_ptr(ptr, 2) => GcWalk::Pointer, 62 | 6 | 7 | 9 => GcWalk::Pointer, 63 | _ => GcWalk::Skip 64 | } 65 | } 66 | GC_REGEXP => { 67 | match index { 68 | 0 | 1 => GcWalk::Pointer, 69 | _ => GcWalk::Skip 70 | } 71 | } 72 | GC_SCOPE => { 73 | match index { 74 | 0 => GcWalk::Pointer, 75 | _ => GcWalk::Skip 76 | } 77 | } 78 | GC_STRING => { 79 | match index { 80 | 0 => GcWalk::Pointer, 81 | _ => GcWalk::Skip 82 | } 83 | } 84 | GC_U16 => { 85 | GcWalk::EndArray 86 | } 87 | GC_U32 => { 88 | GcWalk::EndArray 89 | } 90 | GC_VALUE => { 91 | match index { 92 | 1 if is_value_ptr(ptr, 0) => GcWalk::Pointer, 93 | _ => GcWalk::Skip 94 | } 95 | } 96 | GC_SPARSE_ARRAY => { 97 | match index { 98 | 0 | 1 => GcWalk::Pointer, 99 | _ => GcWalk::Skip 100 | } 101 | } 102 | GC_ARRAY_CHUNK => { 103 | match index { 104 | 1 => GcWalk::Pointer, 105 | _ => GcWalk::Skip 106 | } 107 | } 108 | _ => panic!("unmapped GC type") 109 | } 110 | } 111 | } 112 | 113 | fn finalize(&self, ty: u32, ptr: ptr_t) -> GcFinalize { 114 | unsafe { 115 | match ty { 116 | GC_REGEXP => { 117 | let regex = transmute::<_, *mut JsRegExp>(ptr); 118 | (&mut *regex).finalize(); 119 | 120 | GcFinalize::Finalized 121 | } 122 | GC_OBJECT => { 123 | let object = transmute::<_, *mut JsObject>(ptr); 124 | (&mut *object).finalize(); 125 | 126 | GcFinalize::Finalized 127 | } 128 | _ => GcFinalize::NotFinalizable 129 | } 130 | } 131 | } 132 | 133 | fn create_root_walkers(&self) -> Vec> { 134 | vec![self.stack.create_walker()] 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/syntax/ast/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod visitor; 2 | 3 | use syntax::{Name, Span}; 4 | use syntax::token::{Lit, name}; 5 | use std::cell::{Cell, RefCell}; 6 | use std::collections::HashMap; 7 | use std::fmt; 8 | 9 | pub struct AstContext { 10 | pub functions: Vec> 11 | } 12 | 13 | impl AstContext { 14 | pub fn new() -> AstContext { 15 | AstContext { 16 | functions: Vec::new() 17 | } 18 | } 19 | } 20 | 21 | #[derive(Copy, Clone, Debug, PartialEq)] 22 | pub struct FunctionRef(pub u32); 23 | 24 | impl FunctionRef { 25 | pub fn usize(&self) -> usize { 26 | self.0 as usize 27 | } 28 | } 29 | 30 | pub struct Function { 31 | pub global: bool, 32 | pub name: Option, 33 | pub args: u32, 34 | pub block: RootBlock, 35 | pub span: Span 36 | } 37 | 38 | /// A local slot is a declaration of a local that is known within the current 39 | /// scope (function). This only applies to functions and when optimizations are 40 | /// enabled. When optimizations are disabled (i.e. when there is an eval 41 | /// somewhere in the stack), local slots are not tracked. 42 | #[derive(Copy, Clone, Debug)] 43 | pub struct Slot { 44 | pub name: Name, 45 | pub arguments: bool, 46 | pub arg: Option, 47 | pub state: SlotState 48 | } 49 | 50 | #[derive(Copy, Clone, Debug, PartialEq)] 51 | pub enum SlotState { 52 | Local, 53 | Scoped, 54 | Lifted(u32) 55 | } 56 | 57 | #[derive(Copy, Clone, Debug, PartialEq)] 58 | pub struct SlotRef(pub usize); 59 | 60 | impl SlotRef { 61 | pub fn usize(&self) -> usize { 62 | self.0 63 | } 64 | } 65 | 66 | #[derive(Copy, Clone, Debug, PartialEq)] 67 | pub struct FunctionSlotRef(pub FunctionRef, pub SlotRef, pub u32); 68 | 69 | impl FunctionSlotRef { 70 | pub fn function(&self) -> FunctionRef { 71 | self.0 72 | } 73 | 74 | pub fn slot(&self) -> SlotRef { 75 | self.1 76 | } 77 | 78 | pub fn depth(&self) -> u32 { 79 | self.2 80 | } 81 | } 82 | 83 | #[derive(Debug)] 84 | pub struct RootBlock { 85 | pub args: Vec, 86 | pub block: Block, 87 | pub state: RefCell, 88 | pub strict: bool 89 | } 90 | 91 | #[derive(Debug)] 92 | pub struct RootBlockState { 93 | pub slots: Vec, 94 | pub take_scope: bool, 95 | pub build_scope: ScopeType, 96 | } 97 | 98 | #[derive(Copy, Clone, PartialEq, Debug)] 99 | pub enum ScopeType { 100 | None, 101 | Thin(u32), 102 | Thick 103 | } 104 | 105 | #[derive(Debug)] 106 | pub struct Block { 107 | pub stmts: Vec, 108 | pub locals: HashMap 109 | } 110 | 111 | #[derive(Debug)] 112 | pub enum Item { 113 | Block(Option