diff --git a/src/lib/js/object.rs b/src/lib/js/object.rs index 477ff0b060..6aecd10b56 100644 --- a/src/lib/js/object.rs +++ b/src/lib/js/object.rs @@ -6,6 +6,7 @@ use std::collections::HashMap; /// Static `prototype`, usually set on constructors as a key to point to their respective prototype object. /// As this string will be used a lot throughout the program, its best being a static global string which will be referenced pub static PROTOTYPE: &'static str = "prototype"; + /// Static `__proto__`, usually set on Object instances as a key to point to their respective prototype object. /// As this string will be used a lot throughout the program, its best being a static global string which will be referenced pub static INSTANCE_PROTOTYPE: &'static str = "__proto__"; diff --git a/src/lib/js/string.rs b/src/lib/js/string.rs index cf6ee0576b..3f0c02fcef 100644 --- a/src/lib/js/string.rs +++ b/src/lib/js/string.rs @@ -30,6 +30,28 @@ pub fn to_string(this: Value, _: Value, _: Vec) -> ResultValue { Ok(to_value(format!("{}", primitive_val).to_string())) } +// Get the character at the supplied index +// https://tc39.github.io/ecma262/#sec-string.prototype.charat +pub fn char_at(this: Value, _: Value, args: Vec) -> ResultValue { + // ^^ represents instance ^^ represents arguments (we only care about the first one in this case) + // First we get it the actual string a private field stored on the object only the engine has access to. + // Then we convert it into a Rust String by wrapping it in from_value + let primitive_val: String = + from_value(this.get_private_field(String::from("PrimitiveValue"))).unwrap(); + let pos = from_value(args[0].clone()).unwrap(); + + // Calling .len() on a string would give the wrong result, as they are bytes not the number of unicode code points + // Note that this is an O(N) operation (because UTF-8 is complex) while getting the number of bytes is an O(1) operation. + let length = primitive_val.chars().count(); + + // We should return an empty string is pos is out of range + if pos > length || pos < 0 as usize { + return Ok(to_value::(String::new())); + } + + Ok(to_value::(primitive_val.chars().nth(pos).unwrap())) +} + /// Create a new `String` object pub fn _create(global: Value) -> Value { let string = to_value(make_string as NativeFunctionData); @@ -43,6 +65,7 @@ pub fn _create(global: Value) -> Value { set: Gc::new(ValueData::Undefined), }; proto.set_prop_slice("length", prop); + proto.set_field_slice("charAt", to_value(char_at as NativeFunctionData)); proto.set_field_slice("toString", to_value(to_string as NativeFunctionData)); string.set_field_slice(PROTOTYPE, proto); string diff --git a/src/lib/syntax/lexer.rs b/src/lib/syntax/lexer.rs index a7e386bdfa..a41beb2689 100644 --- a/src/lib/syntax/lexer.rs +++ b/src/lib/syntax/lexer.rs @@ -284,6 +284,7 @@ impl<'a> Lexer<'a> { } u64::from_str_radix(&buf, 16).unwrap() } else { + let mut gone_decimal = false; loop { let ch = self.preview_next()?; match ch { @@ -292,13 +293,22 @@ impl<'a> Lexer<'a> { self.next()?; } '8' | '9' | '.' => { + gone_decimal = true; buf.push(ch); self.next()?; } _ => break, } } - u64::from_str_radix(&buf, 8).unwrap() + if gone_decimal { + u64::from_str(&buf).unwrap() + } else { + if buf.is_empty() { + 0 + } else { + u64::from_str_radix(&buf, 8).unwrap() + } + } }; self.push_token(TokenData::NumericLiteral(num as f64)) } diff --git a/tests/js/test.js b/tests/js/test.js index eaf7c65929..fd67ee8995 100644 --- a/tests/js/test.js +++ b/tests/js/test.js @@ -1,2 +1,2 @@ var a = new String("test"); -a.toString(); +a.charAt(9);