Skip to content

Commit

Permalink
Implement remaining string internal methods
Browse files Browse the repository at this point in the history
  • Loading branch information
jedel1043 committed Aug 18, 2021
1 parent 4114e39 commit cb9d93d
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 127 deletions.
Binary file added boa/my_trace.mm_profdata
Binary file not shown.
189 changes: 115 additions & 74 deletions boa/src/object/internal_methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -663,81 +663,14 @@ pub(crate) fn ordinary_define_own_property(
desc: PropertyDescriptor,
context: &mut Context,
) -> Result<bool> {
let current = obj.__get_own_property__(&key, context)?;
let extensible = obj.__is_extensible__(context)?;

let mut current = if let Some(own) = obj.__get_own_property__(&key, context)? {
own
} else {
if !extensible {
return Ok(false);
}

obj.borrow_mut().properties.insert(
key,
if desc.is_generic_descriptor() || desc.is_data_descriptor() {
desc.into_data_defaulted()
} else {
desc.into_accessor_defaulted()
},
);

return Ok(true);
};

// 3
if desc.is_empty() {
return Ok(true);
}

// 4
if !current.expect_configurable() {
if matches!(desc.configurable(), Some(true)) {
return Ok(false);
}

if matches!(desc.enumerable(), Some(desc_enum) if desc_enum != current.expect_enumerable())
{
return Ok(false);
}
}

// 5
if desc.is_generic_descriptor() {
// no further validation required
} else if current.is_data_descriptor() != desc.is_data_descriptor() {
if !current.expect_configurable() {
return Ok(false);
}
if current.is_data_descriptor() {
current = current.into_accessor_defaulted();
} else {
current = current.into_data_defaulted();
}
} else if current.is_data_descriptor() && desc.is_data_descriptor() {
if !current.expect_configurable() && !current.expect_writable() {
if matches!(desc.writable(), Some(true)) {
return Ok(false);
}
if matches!(desc.value(), Some(value) if !JsValue::same_value(value, current.expect_value()))
{
return Ok(false);
}
return Ok(true);
}
} else if !current.expect_configurable() {
if matches!(desc.set(), Some(set) if !JsValue::same_value(set, current.expect_set())) {
return Ok(false);
}
if matches!(desc.get(), Some(get) if !JsValue::same_value(get, current.expect_get())) {
return Ok(false);
}
return Ok(true);
}

current.fill_with(desc);
obj.borrow_mut().properties.insert(key, current);

Ok(true)
Ok(validate_and_apply_property_descriptor(
Some((obj, key)),
extensible,
desc,
current,
))
}

// Check if object has property.
Expand Down Expand Up @@ -895,9 +828,117 @@ pub(crate) fn ordinary_own_property_keys(
obj: &GcObject,
_context: &mut Context,
) -> Result<Vec<PropertyKey>> {
// todo: sort keys or ensure in some way that indexed properties are sorted... or maybe it's not necessary?
Ok(obj.borrow().properties.keys().collect())
}

/// Abstract operation `IsCompatiblePropertyDescriptor `
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-iscompatiblepropertydescriptor
#[inline]
pub(crate) fn is_compatible_property_descriptor(
extensible: bool,
desc: PropertyDescriptor,
current: PropertyDescriptor,
) -> bool {
validate_and_apply_property_descriptor(None, extensible, desc, Some(current))
}

/// Abstract operation `ValidateAndApplyPropertyDescriptor`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor
#[inline]
pub(crate) fn validate_and_apply_property_descriptor(
obj_and_key: Option<(&GcObject, PropertyKey)>,
extensible: bool,
desc: PropertyDescriptor,
current: Option<PropertyDescriptor>,
) -> bool {
let mut current = if let Some(own) = current {
own
} else {
if !extensible {
return false;
}

if let Some((obj, key)) = obj_and_key {
obj.borrow_mut().properties.insert(
key,
if desc.is_generic_descriptor() || desc.is_data_descriptor() {
desc.into_data_defaulted()
} else {
desc.into_accessor_defaulted()
},
);
}

return true;
};

// 3
if desc.is_empty() {
return true;
}

// 4
if !current.expect_configurable() {
if matches!(desc.configurable(), Some(true)) {
return false;
}

if matches!(desc.enumerable(), Some(desc_enum) if desc_enum != current.expect_enumerable())
{
return false;
}
}

// 5
if desc.is_generic_descriptor() {
// no further validation required
} else if current.is_data_descriptor() != desc.is_data_descriptor() {
if !current.expect_configurable() {
return false;
}
if current.is_data_descriptor() {
current = current.into_accessor_defaulted();
} else {
current = current.into_data_defaulted();
}
} else if current.is_data_descriptor() && desc.is_data_descriptor() {
if !current.expect_configurable() && !current.expect_writable() {
if matches!(desc.writable(), Some(true)) {
return false;
}
if matches!(desc.value(), Some(value) if !JsValue::same_value(value, current.expect_value()))
{
return false;
}
return true;
}
} else if !current.expect_configurable() {
if matches!(desc.set(), Some(set) if !JsValue::same_value(set, current.expect_set())) {
return false;
}
if matches!(desc.get(), Some(get) if !JsValue::same_value(get, current.expect_get())) {
return false;
}
return true;
}

if let Some((obj, key)) = obj_and_key {
current.fill_with(desc);
obj.borrow_mut().properties.insert(key, current);
}

true
}

impl Object {
/// Helper function for property insertion.
#[inline]
Expand Down
113 changes: 87 additions & 26 deletions boa/src/object/internal_methods/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ use crate::{
Context, JsValue, Result,
};

// todo: missing `[[DefineOwnProperty]]` and `[[OwnPropertyKeys]]`

/// Gets own property of 'String' exotic object
///
/// More information:
Expand All @@ -27,46 +25,109 @@ pub(crate) fn string_exotic_get_own_property(
}
}

/// Defines own property of 'String' exotic object
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-string-exotic-objects-defineownproperty-p-desc
#[inline]
pub(crate) fn string_exotic_define_own_property(
obj: &GcObject,
key: PropertyKey,
desc: PropertyDescriptor,
context: &mut Context,
) -> Result<bool> {
let string_desc = string_get_own_property(obj, &key);

if let Some(string_desc) = string_desc {
let extensible = obj.borrow().extensible;
Ok(super::is_compatible_property_descriptor(
extensible,
desc,
string_desc,
))
} else {
super::ordinary_define_own_property(obj, key, desc, context)
}
}

/// Gets own property keys of 'String' exotic object
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-string-exotic-objects-ownpropertykeys
#[inline]
pub(crate) fn string_exotic_own_property_keys(
obj: &GcObject,
_context: &mut Context,
) -> Result<Vec<PropertyKey>> {
let mut keys = Vec::new();

let obj = obj.borrow();

let string = obj
.as_string()
.expect("string exotic method should only be callable from string objects");
let len = string.encode_utf16().count();

for i in 0..len {
keys.push(i.into());
}

// todo: sort keys or ensure in some way that indexed properties are sorted... or maybe it's not necessary?
for elem in obj
.properties
.keys()
.filter(|prop| !matches!(prop, PropertyKey::Index(i) if (*i as usize) < len))
{
keys.push(elem)
}

Ok(keys)
}

/// StringGetOwnProperty abstract operation
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-stringgetownproperty
#[allow(clippy::float_cmp)]
#[inline]
pub(crate) fn string_get_own_property(
obj: &GcObject,
key: &PropertyKey,
) -> Option<PropertyDescriptor> {
let obj = obj.borrow();

match key {
PropertyKey::Index(index) => {
let string = obj.as_string().unwrap();
let pos = *index as usize;

if pos >= string.len() {
let pos = match key {
PropertyKey::Index(index) => *index as usize,
PropertyKey::String(index) => {
let index = index.canonical_numeric_index_string()?;
if index != ((index as usize) as f64) {
return None;
}
index as usize
}
_ => return None,
};
let string = obj
.as_string()
.expect("string exotic method should only be callable from string objects");

// todo: shouldn't it be encoded before checking length and position?
let result_str = string
.encode_utf16()
.nth(pos)
.map(|utf16_val| JsValue::from(String::from_utf16_lossy(&[utf16_val])))?;

// todo: should expect that pos < string.len(). Skipped because of the above todo.
// .expect("already verified that pos >= string.len()");
let result_str = string
.encode_utf16()
.nth(pos)
.map(|c| JsValue::from(String::from_utf16_lossy(&[c])))?;

let desc = PropertyDescriptor::builder()
.value(result_str)
.writable(false)
.enumerable(true)
.configurable(false)
.build();
let desc = PropertyDescriptor::builder()
.value(result_str)
.writable(false)
.enumerable(true)
.configurable(false)
.build();

Some(desc)
}
_ => None,
}
Some(desc)
}
4 changes: 3 additions & 1 deletion boa/src/object/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,11 @@ impl ObjectData {
pub fn string(string: JsString) -> Self {
Self {
kind: ObjectKind::String(string),
// todo: override remaining string methods
internal_methods: InternalObjectMethods {
__get_own_property__: internal_methods::string::string_exotic_get_own_property,
__define_own_property__:
internal_methods::string::string_exotic_define_own_property,
__own_property_keys__: internal_methods::string::string_exotic_own_property_keys,
..Default::default()
},
}
Expand Down
Loading

0 comments on commit cb9d93d

Please sign in to comment.