Skip to content

Commit 2a96e72

Browse files
committed
avm1: Separate coercions and other operators in value.rs
For some extra clarity.
1 parent 60e361e commit 2a96e72

File tree

1 file changed

+110
-105
lines changed

1 file changed

+110
-105
lines changed

core/src/avm1/value.rs

Lines changed: 110 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ impl PartialEq for Value<'_> {
129129
}
130130
}
131131

132+
/// Value coercions and conversions.
132133
impl<'gc> Value<'gc> {
133134
/// Yields `true` if the given value is a primitive value.
134135
///
@@ -186,7 +187,7 @@ impl<'gc> Value<'gc> {
186187
/// `undefined`. This is not a special-cased behavior: All values are
187188
/// callable in `AVM1`. Values that are not callable objects instead
188189
/// return `undefined` rather than yielding a runtime error.
189-
pub fn to_primitive_num(
190+
fn to_primitive_num(
190191
self,
191192
activation: &mut Activation<'_, 'gc>,
192193
) -> Result<Value<'gc>, Error<'gc>> {
@@ -261,97 +262,6 @@ impl<'gc> Value<'gc> {
261262
Ok(result)
262263
}
263264

264-
/// ECMA-262 2nd edition s. 11.8.5 Abstract relational comparison algorithm
265-
pub fn abstract_lt(
266-
&self,
267-
other: Value<'gc>,
268-
activation: &mut Activation<'_, 'gc>,
269-
) -> Result<Value<'gc>, Error<'gc>> {
270-
// If either parameter's `valueOf` results in a non-movieclip object, immediately return false.
271-
// This is the common case for objects because `Object.prototype.valueOf` returns the same object.
272-
// For example, `{} < {}` is false.
273-
let prim_self = self.to_primitive_num(activation)?;
274-
if matches!(prim_self, Value::Object(o) if o.as_display_object().is_none()) {
275-
return Ok(false.into());
276-
}
277-
let prim_other = other.to_primitive_num(activation)?;
278-
if matches!(prim_other, Value::Object(o) if o.as_display_object().is_none()) {
279-
return Ok(false.into());
280-
}
281-
282-
let result = match (prim_self, prim_other) {
283-
(Value::String(a), Value::String(b)) => {
284-
let a = a.to_string();
285-
let b = b.to_string();
286-
a.bytes().lt(b.bytes()).into()
287-
}
288-
(a, b) => {
289-
// Coerce to number and compare, with any NaN resulting in undefined.
290-
let a = a.primitive_as_number(activation);
291-
let b = b.primitive_as_number(activation);
292-
a.partial_cmp(&b).map_or(Value::Undefined, |o| {
293-
Value::Bool(o == std::cmp::Ordering::Less)
294-
})
295-
}
296-
};
297-
Ok(result)
298-
}
299-
300-
/// ECMA-262 2nd edition s. 11.9.3 Abstract equality comparison algorithm
301-
pub fn abstract_eq(
302-
self,
303-
other: Value<'gc>,
304-
activation: &mut Activation<'_, 'gc>,
305-
) -> Result<bool, Error<'gc>> {
306-
let (a, b) = if activation.swf_version() > 5 {
307-
(other, self)
308-
} else {
309-
// SWFv5 always calls `valueOf` even in Object-Object comparisons.
310-
// Object.prototype.valueOf returns `this`, which will do pointer comparison below.
311-
// In Object-primitive comparisons, `valueOf` will be called a second time below.
312-
(
313-
other.to_primitive_num(activation)?,
314-
self.to_primitive_num(activation)?,
315-
)
316-
};
317-
let result = match (a, b) {
318-
(Value::Undefined | Value::Null, Value::Undefined | Value::Null) => true,
319-
(Value::Bool(a), Value::Bool(b)) => a == b,
320-
(Value::String(a), Value::String(b)) => a == b,
321-
(Value::Object(a), Value::Object(b)) => Object::ptr_eq(a, b),
322-
(Value::Number(a), Value::Number(b)) => {
323-
// PLAYER-SPECIFIC: NaN == NaN returns true in Flash Player 7+ AVM1, but returns false in Flash Player 6 and lower.
324-
// We choose to return true.
325-
a == b || (a.is_nan() && b.is_nan())
326-
}
327-
328-
// Bool-to-value-comparison: Coerce bool to 0/1 and compare.
329-
(Value::Bool(bool), val) | (val, Value::Bool(bool)) => {
330-
val.abstract_eq(Value::Number(bool as i64 as f64), activation)?
331-
}
332-
333-
// Number-to-value comparison: Coerce value to f64 and compare.
334-
// Note that "NaN" == NaN returns false.
335-
(Value::Number(num), string @ Value::String(_))
336-
| (string @ Value::String(_), Value::Number(num)) => {
337-
num == string.primitive_as_number(activation)
338-
}
339-
340-
(Value::MovieClip(a), Value::MovieClip(b)) => {
341-
a.coerce_to_string(activation) == b.coerce_to_string(activation)
342-
}
343-
344-
// Object-to-value comparison: Call `obj.valueOf` and compare.
345-
(obj @ Value::Object(_), val) | (val, obj @ Value::Object(_)) => {
346-
let obj_val = obj.to_primitive_num(activation)?;
347-
obj_val.is_primitive() && val.abstract_eq(obj_val, activation)?
348-
}
349-
350-
_ => false,
351-
};
352-
Ok(result)
353-
}
354-
355265
pub fn coerce_to_u8(&self, activation: &mut Activation<'_, 'gc>) -> Result<u8, Error<'gc>> {
356266
self.coerce_to_f64(activation).map(f64_to_wrapping_u8)
357267
}
@@ -447,19 +357,6 @@ impl<'gc> Value<'gc> {
447357
}
448358
}
449359

450-
pub fn type_of(&self, activation: &mut Activation<'_, 'gc>) -> AvmString<'gc> {
451-
match self {
452-
Value::Undefined => istr!("undefined"),
453-
Value::Null => istr!("null"),
454-
Value::Number(_) => istr!("number"),
455-
Value::Bool(_) => istr!("boolean"),
456-
Value::String(_) => istr!("string"),
457-
Value::Object(object) if object.as_function().is_some() => istr!("function"),
458-
Value::MovieClip(_) => istr!("movieclip"),
459-
Value::Object(_) => istr!("object"),
460-
}
461-
}
462-
463360
/// Convert `self` to an script object, if possible. Unlike `coerce_to_object`, this
464361
/// doesn't coerce primitives.
465362
pub fn as_object(self, activation: &mut Activation<'_, 'gc>) -> Option<Object<'gc>> {
@@ -549,6 +446,114 @@ impl<'gc> Value<'gc> {
549446
}
550447
}
551448

449+
/// Value operators.
450+
impl<'gc> Value<'gc> {
451+
/// ActionScript 2's `typeof self`.
452+
pub fn type_of(&self, activation: &mut Activation<'_, 'gc>) -> AvmString<'gc> {
453+
match self {
454+
Value::Undefined => istr!("undefined"),
455+
Value::Null => istr!("null"),
456+
Value::Number(_) => istr!("number"),
457+
Value::Bool(_) => istr!("boolean"),
458+
Value::String(_) => istr!("string"),
459+
Value::Object(object) if object.as_function().is_some() => istr!("function"),
460+
Value::MovieClip(_) => istr!("movieclip"),
461+
Value::Object(_) => istr!("object"),
462+
}
463+
}
464+
465+
/// ECMA-262 2nd edition s. 11.8.5 Abstract relational comparison algorithm
466+
pub fn abstract_lt(
467+
&self,
468+
other: Value<'gc>,
469+
activation: &mut Activation<'_, 'gc>,
470+
) -> Result<Value<'gc>, Error<'gc>> {
471+
// If either parameter's `valueOf` results in a non-movieclip object, immediately return false.
472+
// This is the common case for objects because `Object.prototype.valueOf` returns the same object.
473+
// For example, `{} < {}` is false.
474+
let prim_self = self.to_primitive_num(activation)?;
475+
if matches!(prim_self, Value::Object(o) if o.as_display_object().is_none()) {
476+
return Ok(false.into());
477+
}
478+
let prim_other = other.to_primitive_num(activation)?;
479+
if matches!(prim_other, Value::Object(o) if o.as_display_object().is_none()) {
480+
return Ok(false.into());
481+
}
482+
483+
let result = match (prim_self, prim_other) {
484+
(Value::String(a), Value::String(b)) => {
485+
let a = a.to_string();
486+
let b = b.to_string();
487+
a.bytes().lt(b.bytes()).into()
488+
}
489+
(a, b) => {
490+
// Coerce to number and compare, with any NaN resulting in undefined.
491+
let a = a.primitive_as_number(activation);
492+
let b = b.primitive_as_number(activation);
493+
a.partial_cmp(&b).map_or(Value::Undefined, |o| {
494+
Value::Bool(o == std::cmp::Ordering::Less)
495+
})
496+
}
497+
};
498+
Ok(result)
499+
}
500+
501+
/// ECMA-262 2nd edition s. 11.9.3 Abstract equality comparison algorithm
502+
pub fn abstract_eq(
503+
self,
504+
other: Value<'gc>,
505+
activation: &mut Activation<'_, 'gc>,
506+
) -> Result<bool, Error<'gc>> {
507+
let (a, b) = if activation.swf_version() > 5 {
508+
(other, self)
509+
} else {
510+
// SWFv5 always calls `valueOf` even in Object-Object comparisons.
511+
// Object.prototype.valueOf returns `this`, which will do pointer comparison below.
512+
// In Object-primitive comparisons, `valueOf` will be called a second time below.
513+
(
514+
other.to_primitive_num(activation)?,
515+
self.to_primitive_num(activation)?,
516+
)
517+
};
518+
let result = match (a, b) {
519+
(Value::Undefined | Value::Null, Value::Undefined | Value::Null) => true,
520+
(Value::Bool(a), Value::Bool(b)) => a == b,
521+
(Value::String(a), Value::String(b)) => a == b,
522+
(Value::Object(a), Value::Object(b)) => Object::ptr_eq(a, b),
523+
(Value::Number(a), Value::Number(b)) => {
524+
// PLAYER-SPECIFIC: NaN == NaN returns true in Flash Player 7+ AVM1, but returns false in Flash Player 6 and lower.
525+
// We choose to return true.
526+
a == b || (a.is_nan() && b.is_nan())
527+
}
528+
529+
// Bool-to-value-comparison: Coerce bool to 0/1 and compare.
530+
(Value::Bool(bool), val) | (val, Value::Bool(bool)) => {
531+
val.abstract_eq(Value::Number(bool as i64 as f64), activation)?
532+
}
533+
534+
// Number-to-value comparison: Coerce value to f64 and compare.
535+
// Note that "NaN" == NaN returns false.
536+
(Value::Number(num), string @ Value::String(_))
537+
| (string @ Value::String(_), Value::Number(num)) => {
538+
num == string.primitive_as_number(activation)
539+
}
540+
541+
(Value::MovieClip(a), Value::MovieClip(b)) => {
542+
a.coerce_to_string(activation) == b.coerce_to_string(activation)
543+
}
544+
545+
// Object-to-value comparison: Call `obj.valueOf` and compare.
546+
(obj @ Value::Object(_), val) | (val, obj @ Value::Object(_)) => {
547+
let obj_val = obj.to_primitive_num(activation)?;
548+
obj_val.is_primitive() && val.abstract_eq(obj_val, activation)?
549+
}
550+
551+
_ => false,
552+
};
553+
Ok(result)
554+
}
555+
}
556+
552557
/// Calculate `value * 10^exp` through repeated multiplication or division.
553558
fn decimal_shift(mut value: f64, mut exp: i32) -> f64 {
554559
let mut base: f64 = 10.0;

0 commit comments

Comments
 (0)