Browse Source

feat(boa): in operator (#350)

pull/255/head
Brian Gavin 4 years ago committed by GitHub
parent
commit
55ef44ce13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      boa/src/builtins/object/internal_methods_trait.rs
  2. 9
      boa/src/builtins/value/mod.rs
  3. 33
      boa/src/exec/mod.rs
  4. 69
      boa/src/exec/tests.rs
  5. 19
      boa/src/syntax/ast/keyword.rs
  6. 17
      boa/src/syntax/ast/op.rs
  7. 13
      boa/src/syntax/ast/punc.rs
  8. 31
      boa/src/syntax/parser/expression/mod.rs
  9. 46
      boa/src/syntax/parser/expression/tests.rs
  10. 2
      boa/src/syntax/parser/tests.rs

4
boa/src/builtins/object/internal_methods_trait.rs

@ -6,7 +6,7 @@
//! [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
use crate::builtins::{
object::{Object, PROTOTYPE},
object::{Object, INSTANCE_PROTOTYPE},
property::Property,
value::{to_value, Value, ValueData},
};
@ -178,7 +178,7 @@ pub trait ObjectInternalMethods {
/// Returns either the prototype or null
/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof
fn get_prototype_of(&self) -> Value {
self.get_internal_slot(PROTOTYPE)
self.get_internal_slot(INSTANCE_PROTOTYPE)
}
fn define_own_property(&mut self, property_key: String, desc: Property) -> bool;

9
boa/src/builtins/value/mod.rs

@ -13,7 +13,7 @@ use crate::builtins::{
},
property::Property,
};
use gc::{Gc, GcCell};
use gc::{Gc, GcCell, GcCellRef};
use gc_derive::{Finalize, Trace};
use serde_json::{map::Map, Number as JSONNumber, Value as JSONValue};
use std::{
@ -238,6 +238,13 @@ impl ValueData {
}
}
pub fn as_object(&self) -> Option<GcCellRef<'_, Object>> {
match *self {
ValueData::Object(ref o) => Some(o.borrow()),
_ => None,
}
}
/// remove_prop removes a property from a Value object.
///
/// It will return a boolean based on if the value was removed, if there was no value to remove false is returned

33
boa/src/exec/mod.rs

@ -11,6 +11,7 @@ use crate::{
internal_methods_trait::ObjectInternalMethods, ObjectKind, INSTANCE_PROTOTYPE,
PROTOTYPE,
},
property::Property,
value::{from_value, to_value, ResultValue, Value, ValueData},
},
environment::lexical_environment::{
@ -347,6 +348,13 @@ impl Executor for Interpreter {
CompOp::GreaterThanOrEqual => v_a.to_num() >= v_b.to_num(),
CompOp::LessThan => v_a.to_num() < v_b.to_num(),
CompOp::LessThanOrEqual => v_a.to_num() <= v_b.to_num(),
CompOp::In => {
if !v_b.is_object() {
panic!("TypeError: {} is not an Object.", v_b);
}
let key = self.to_property_key(v_a);
self.has_property(v_b, &key)
}
}))
}
Node::BinOp(BinOp::Log(ref op), ref a, ref b) => {
@ -739,6 +747,31 @@ impl Interpreter {
}
}
/// The abstract operation ToPropertyKey takes argument argument. It converts argument to a value that can be used as a property key.
/// https://tc39.es/ecma262/#sec-topropertykey
#[allow(clippy::wrong_self_convention)]
pub fn to_property_key(&mut self, value: &Value) -> Value {
let key = self.to_primitive(value, Some("string"));
if key.is_symbol() {
key
} else {
self.to_string(&key)
}
}
/// https://tc39.es/ecma262/#sec-hasproperty
pub fn has_property(&self, obj: &Value, key: &Value) -> bool {
if let Some(obj) = obj.as_object() {
if !Property::is_property_key(key) {
false
} else {
obj.has_property(key)
}
} else {
false
}
}
/// The abstract operation ToObject converts argument to a value of type Object
/// https://tc39.es/ecma262/#sec-toobject
#[allow(clippy::wrong_self_convention)]

69
boa/src/exec/tests.rs

@ -351,3 +351,72 @@ fn test_unary_post() {
"#;
assert_eq!(exec(execs_after), String::from("true"));
}
#[cfg(test)]
mod in_operator {
use super::*;
#[test]
fn test_p_in_o() {
let p_in_o = r#"
var o = {a: 'a'};
var p = 'a';
p in o
"#;
assert_eq!(exec(p_in_o), String::from("true"));
}
#[test]
fn test_p_in_property_chain() {
let p_in_o = r#"
var o = {};
var p = 'toString';
p in o
"#;
assert_eq!(exec(p_in_o), String::from("true"));
}
#[test]
fn test_p_not_in_o() {
let p_not_in_o = r#"
var o = {a: 'a'};
var p = 'b';
p in o
"#;
assert_eq!(exec(p_not_in_o), String::from("false"));
}
#[test]
fn test_number_in_array() {
// Note: this is valid because the LHS is converted to a prop key with ToPropertyKey
// and arrays are just fancy objects like {'0': 'a'}
let num_in_array = r#"
var n = 0;
var a = ['a'];
n in a
"#;
assert_eq!(exec(num_in_array), String::from("true"));
}
#[test]
#[ignore]
fn test_symbol_in_object() {
// FIXME: this scenario works in Firefox's console, this is probably an issue
// with Symbol comparison.
let sym_in_object = r#"
var sym = Symbol('hi');
var o = {};
o[sym] = 'hello';
sym in o
"#;
assert_eq!(exec(sym_in_object), String::from("true"));
}
#[test]
#[should_panic(expected = "TypeError: undefined is not an Object.")]
fn test_should_type_error_when_rhs_not_object() {
let scenario = r#"
'fail' in undefined
"#;
exec(scenario);
}
}

19
boa/src/syntax/ast/keyword.rs

@ -7,7 +7,9 @@
//! [spec]: https://www.ecma-international.org/ecma-262/#sec-keywords
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords
use crate::syntax::ast::op::{BinOp, CompOp};
use std::{
convert::TryInto,
error,
fmt::{Display, Error, Formatter},
str::FromStr,
@ -433,6 +435,23 @@ pub enum Keyword {
Yield,
}
impl Keyword {
pub fn as_binop(self) -> Option<BinOp> {
match self {
Keyword::In => Some(BinOp::Comp(CompOp::In)),
_ => None,
}
}
}
impl TryInto<BinOp> for Keyword {
type Error = String;
fn try_into(self) -> Result<BinOp, Self::Error> {
self.as_binop()
.ok_or_else(|| format!("No binary operation for {}", self))
}
}
#[derive(Debug, Clone, Copy)]
pub struct KeywordError;
impl Display for KeywordError {

17
boa/src/syntax/ast/op.rs

@ -579,6 +579,19 @@ pub enum CompOp {
/// [spec]: https://tc39.es/ecma262/#prod-RelationalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators#Less_than_or_equal_operator
LessThanOrEqual,
/// The `in` operator returns true if the specified property is in the specified object or its prototype chain.
///
/// Syntax: `prop in object`
///
/// Returns `true` the specified property is in the specified object or its prototype chain.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-RelationalExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in
In,
}
impl Display for CompOp {
@ -595,6 +608,7 @@ impl Display for CompOp {
Self::GreaterThanOrEqual => ">=",
Self::LessThan => "<",
Self::LessThanOrEqual => "<=",
Self::In => "in",
}
)
}
@ -727,7 +741,8 @@ impl Operator for BinOp {
Self::Comp(CompOp::LessThan)
| Self::Comp(CompOp::LessThanOrEqual)
| Self::Comp(CompOp::GreaterThan)
| Self::Comp(CompOp::GreaterThanOrEqual) => 8,
| Self::Comp(CompOp::GreaterThanOrEqual)
| Self::Comp(CompOp::In) => 8,
Self::Comp(CompOp::Equal)
| Self::Comp(CompOp::NotEqual)
| Self::Comp(CompOp::StrictEqual)

13
boa/src/syntax/ast/punc.rs

@ -6,7 +6,10 @@
//! [spec]: https://tc39.es/ecma262/#prod-Punctuator
use crate::syntax::ast::op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp};
use std::fmt::{Display, Error, Formatter};
use std::{
convert::TryInto,
fmt::{Display, Error, Formatter},
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
@ -169,6 +172,14 @@ impl Punctuator {
}
}
impl TryInto<BinOp> for Punctuator {
type Error = String;
fn try_into(self) -> Result<BinOp, Self::Error> {
self.as_binop()
.ok_or_else(|| format!("No binary operation for {}", self))
}
}
impl Display for Punctuator {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
write!(

31
boa/src/syntax/parser/expression/mod.rs

@ -18,7 +18,23 @@ mod update;
use self::assignment::ExponentiationExpression;
pub(super) use self::{assignment::AssignmentExpression, primary::Initializer};
use super::{AllowAwait, AllowIn, AllowYield, Cursor, ParseResult, TokenParser};
use crate::syntax::ast::{node::Node, punc::Punctuator, token::TokenKind};
use crate::syntax::ast::{keyword::Keyword, node::Node, punc::Punctuator, token::TokenKind};
// For use in the expression! macro to allow for both Punctuator and Keyword parameters.
// Always returns false.
impl PartialEq<Keyword> for Punctuator {
fn eq(&self, _other: &Keyword) -> bool {
false
}
}
// For use in the expression! macro to allow for both Punctuator and Keyword parameters.
// Always returns false.
impl PartialEq<Punctuator> for Keyword {
fn eq(&self, _other: &Punctuator) -> bool {
false
}
}
/// Generates an expression parser.
///
@ -38,7 +54,15 @@ macro_rules! expression { ($name:ident, $lower:ident, [$( $op:path ),*], [$( $lo
TokenKind::Punctuator(op) if $( op == $op )||* => {
let _ = cursor.next().expect("token disappeared");
lhs = Node::bin_op(
op.as_binop().expect("could not get binary operation"),
op.as_binop().expect("Could not get binary operation."),
lhs,
$lower::new($( self.$low_param ),*).parse(cursor)?
)
}
TokenKind::Keyword(op) if $( op == $op )||* => {
let _ = cursor.next().expect("token disappeared");
lhs = Node::bin_op(
op.as_binop().expect("Could not get binary operation."),
lhs,
$lower::new($( self.$low_param ),*).parse(cursor)?
)
@ -360,7 +384,8 @@ expression!(
Punctuator::LessThan,
Punctuator::GreaterThan,
Punctuator::LessThanOrEq,
Punctuator::GreaterThanOrEq
Punctuator::GreaterThanOrEq,
Keyword::In
],
[allow_yield, allow_await]
);

46
boa/src/syntax/parser/expression/tests.rs

@ -1,6 +1,6 @@
use crate::syntax::{
ast::node::Node,
ast::op::{AssignOp, BinOp, BitOp, NumOp},
ast::op::{AssignOp, BinOp, BitOp, CompOp, NumOp},
parser::tests::check_parser,
};
@ -291,3 +291,47 @@ fn check_assign_operations() {
)],
);
}
#[test]
fn check_relational_operations() {
check_parser(
"a < b",
&[Node::bin_op(
BinOp::Comp(CompOp::LessThan),
Node::Local(String::from("a")),
Node::Local(String::from("b")),
)],
);
check_parser(
"a > b",
&[Node::bin_op(
BinOp::Comp(CompOp::GreaterThan),
Node::Local(String::from("a")),
Node::Local(String::from("b")),
)],
);
check_parser(
"a <= b",
&[Node::bin_op(
BinOp::Comp(CompOp::LessThanOrEqual),
Node::Local(String::from("a")),
Node::Local(String::from("b")),
)],
);
check_parser(
"a >= b",
&[Node::bin_op(
BinOp::Comp(CompOp::GreaterThanOrEqual),
Node::Local(String::from("a")),
Node::Local(String::from("b")),
)],
);
check_parser(
"p in o",
&[Node::bin_op(
BinOp::Comp(CompOp::In),
Node::Local(String::from("p")),
Node::Local(String::from("o")),
)],
);
}

2
boa/src/syntax/parser/tests.rs

@ -39,7 +39,7 @@ fn check_construct_call_precedence() {
}
#[test]
fn assing_operator_precedence() {
fn assign_operator_precedence() {
check_parser(
"a = a + 1",
&[Node::assign(

Loading…
Cancel
Save