Files
GTASource/rage/scaleform/Src/GFxPlayer/GASValue.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

1524 lines
42 KiB
C++

/**********************************************************************
Filename : GFxValue.cpp
Content : GASValue implementation
Created :
Authors : Artyom Bolgar
Copyright : (c) 2001-2006 Scaleform Corp. All Rights Reserved.
Notes :
Licensees may use this file in accordance with the valid Scaleform
Commercial License Agreement provided with the software.
This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR ANY PURPOSE.
**********************************************************************/
#include "GFxAction.h"
#include "AS/GASObject.h"
#include "GASFunctionRefImpl.h"
#include "GFxCharacter.h"
#include "GFxASString.h"
#include "AS/GASStringObject.h"
#include "AS/GASArrayObject.h"
#include "AS/GASNumberObject.h"
#include "AS/GASBooleanObject.h"
#include "GFxLog.h"
#include <stdio.h>
#include <stdlib.h>
// Utility. Try to convert str to a number. If successful,
// put the result in *result, and return true. If not
// successful, put 0 in *result, and return false.
// Also, "Infinity", "-Infinity", and "NaN"
// are recognized.
// !DP (SInt32)strtoul is used for oct and hex to match Flash behavior
static bool StringToNumber(GASNumber* result, const char* str)
{
char* tail = 0;
UPInt len = G_strlen (str);
if (*str == '0' && G_tolower (*(str+1)) == 'x') // hexadecimal
{
// hexadecimal values can't be negative
*result = (GASNumber)(SInt32)G_strtoul(str, &tail, 0);
}
else
{
// check, does the string represent floating point number
if (strcspn (str, ".Ee") != len)
{
*result = (GASNumber)G_strtod(str, &tail);
}
else
{
UPInt plen = len;
const char* positive = str;
int sign = 1;
if (*str == '-')
{
positive = str + 1;
sign = -1;
--plen;
}
else if (*str == '+')
{
positive = str + 1;
--plen;
}
// octal numbers may be negative
if (*positive == '0' && strspn (positive, "01234567") == plen) // octal
{
*result = (GASNumber)(SInt32)(G_strtoul(positive, &tail, 8) * sign);
}
else
{
*result = (GASNumber)(G_strtod (positive, &tail) * sign);
}
}
}
if (tail == str || *tail != 0)
{
//!AB, string values "Infinity", "-Infinity" should be
// converted to NaN.
// Moock mentioned that these strings should be converted in appropriate
// number values, but in practice it is not so: neither for Flash 5-6, nor
// for Flash 7+
/*
if(G_strcmp(str, "NaN") == 0)
{
*result = GASNumberUtil::NaN();
return true;
}
if(G_strcmp(str, "Infinity") == 0)
{
*result = GASNumberUtil::POSITIVE_INFINITY();
return true;
}
if(G_strcmp(str, "-Infinity") == 0)
{
*result = GASNumberUtil::NEGATIVE_INFINITY();
return true;
}*/
// Failed conversion to Number.
return false;
}
return true;
}
GASValueProperty::GASValueProperty
(GASRefCountCollector* pCC, const GASFunctionRef& getterMethod, const GASFunctionRef& setterMethod)
: GASRefCountBase<GASValueProperty>(pCC),
GetterMethod(getterMethod), SetterMethod(setterMethod)
{
}
//
// GASValue -- ActionScript value type
//
GASValue::GASValue(const GASValue& v)
{
T.Type = v.T.Type;
switch(v.T.Type)
{
case BOOLEAN:
V.BooleanValue = v.V.BooleanValue;
break;
case FUNCTIONNAME:
case STRING:
V.pStringNode = v.V.pStringNode;
V.pStringNode->AddRef();
break;
case NUMBER:
NV.NumberValue = v.NV.NumberValue;
break;
case INTEGER:
NV.Int32Value = v.NV.Int32Value;
break;
case OBJECT:
if (v.V.pObjectValue)
{
if (v.V.pObjectValue->IsFunction())
{
T.Type = FUNCTION;
V.FunctionValue.Init(v.V.pObjectValue->ToFunction());
}
else
{
V.pObjectValue = v.V.pObjectValue;
V.pObjectValue->AddRef();
}
}
else
{
V.pObjectValue = 0;
}
break;
case CHARACTER:
V.pCharHandle = v.V.pCharHandle;
if (V.pCharHandle)
V.pCharHandle->AddRef();
break;
case RESOLVEHANDLER:
case FUNCTION:
V.FunctionValue.Init(v.V.FunctionValue);
break;
case PROPERTY:
V.pProperty = v.V.pProperty;
V.pProperty->AddRef();
break;
default:;
}
}
void GASValue::operator = (const GASValue& v)
{
// Cleanup previous value.
if (T.Type >= STRING)
DropRefs();
// Perform assignment: same as copy constructor above.
// We paste this logic because it is used very heavily
// and should thus be optimized as much as possible.
T.Type = v.T.Type;
switch(v.T.Type)
{
case BOOLEAN:
V.BooleanValue = v.V.BooleanValue;
break;
case FUNCTIONNAME:
case STRING:
V.pStringNode = v.V.pStringNode;
V.pStringNode->AddRef();
break;
case NUMBER:
NV.NumberValue = v.NV.NumberValue;
break;
case INTEGER:
NV.Int32Value = v.NV.Int32Value;
break;
case OBJECT:
if (v.V.pObjectValue)
{
if (v.V.pObjectValue->IsFunction())
{
T.Type = FUNCTION;
V.FunctionValue.Init(v.V.pObjectValue->ToFunction());
}
else
{
V.pObjectValue = v.V.pObjectValue;
V.pObjectValue->AddRef();
}
}
else
{
V.pObjectValue = 0;
}
break;
case CHARACTER:
V.pCharHandle = v.V.pCharHandle;
if (V.pCharHandle)
V.pCharHandle->AddRef();
break;
case RESOLVEHANDLER:
case FUNCTION:
V.FunctionValue.Init(v.V.FunctionValue);
break;
case PROPERTY:
V.pProperty = v.V.pProperty;
V.pProperty->AddRef();
break;
}
}
GASValue::GASValue(GASObject* pobj)
{
if (pobj && pobj->IsFunction())
{
T.Type = FUNCTION;
V.FunctionValue.Init(pobj->ToFunction());
}
else
{
T.Type = OBJECT;
V.pObjectValue = pobj;
if (V.pObjectValue)
V.pObjectValue->AddRef();
}
}
GASValue::GASValue(GFxASCharacter* pcharacter)
{
T.Type = CHARACTER;
if (!pcharacter)
V.pCharHandle = 0;
else if ((V.pCharHandle = pcharacter->GetCharacterHandle())!=0)
V.pCharHandle->AddRef();
}
GASValue::GASValue(GASStringContext* psc, GASCFunctionPtr func)
{
T.Type = FUNCTION;
V.FunctionValue.Init(*GHEAP_NEW(psc->GetHeap()) GASCFunctionObject (psc, func));
}
GASValue::GASValue(const GASFunctionRef& func)
{
T.Type = FUNCTION;
V.FunctionValue.Init(func);
}
GASValue::GASValue(const GASFunctionRef& getter,
const GASFunctionRef& setter,
GMemoryHeap* pheap,
GASRefCountCollector* pCC)
{
T.Type = PROPERTY;
V.pProperty = GHEAP_NEW(pheap) GASValueProperty(pCC, getter, setter);
}
GASValue::~GASValue()
{
if (T.Type >= STRING)
DropRefs();
}
#ifndef GFC_NO_GC
template <class Functor>
void GASValue::ForEachChild_GC(Collector* prcc) const
{
if (IsFunctionObject())
V.FunctionValue.template ForEachChild_GC<Functor>(prcc);
else if (IsObject() && V.pObjectValue)
Functor::Call(prcc, V.pObjectValue);
else if (IsProperty())
Functor::Call(prcc, V.pProperty);
}
GFC_GC_PREGEN_FOR_EACH_CHILD(GASValue)
void GASValue::Finalize_GC()
{
if (IsFunctionObject())
V.FunctionValue.Finalize_GC();
else if (T.Type >= STRING && !IsObject() && !IsProperty())
DropRefs();
// Finalize_GC for Objects and Properties will be called by GRefCountCollector
}
#endif // GFC_NO_GC
// Conversion to string.
GASString GASValue::ToStringImpl(GASEnvironment* penv, int precision, bool debug) const
{
GASSERT(penv);
GASString stringVal(penv->GetBuiltin(GASBuiltin_empty_));
switch(T.Type)
{
case STRING:
{
// Don't need to do anything.
stringVal.AssignNode(V.pStringNode);
}
break;
case NUMBER:
{
char buf[GASNumberUtil::TOSTRING_BUF_SIZE];
stringVal =
penv->CreateString(GASNumberUtil::ToString(NV.NumberValue, buf, sizeof(buf), (precision < 0) ? 10 : -precision));
}
break;
case INTEGER:
{
char buf[GASNumberUtil::TOSTRING_BUF_SIZE];
stringVal =
penv->CreateString(GASNumberUtil::IntToString(NV.Int32Value, buf, sizeof(buf)));
}
break;
case UNSET:
case UNDEFINED:
{
// Behavior depends on file version. In
// version 7+, it's "undefined", in versions
// 6-, it's "".
//
// We'll go with the v7 behavior by default,
// and conditionalize via Versioned()
// functions.
// MA: Should we (it is easy to do now with penv);
// however, aren't there cases when v6 also returns 'undefined' ?
stringVal = penv->GetBuiltin(GASBuiltin_undefined);
}
break;
case NULLTYPE:
{
stringVal = penv->GetBuiltin(GASBuiltin_null);
}
break;
case BOOLEAN:
{
stringVal = penv->GetBuiltin(V.BooleanValue ? GASBuiltin_true : GASBuiltin_false);
}
break;
case OBJECT:
case CHARACTER:
{
// @@ Moock says:
// - "the value that results from calling ToString() on the object".
// - The default ToString() returns "[object Object]" but may be customized.
// - A Movieclip returns the absolute path of the object.
//
// Do we have a "toString" method?
GASValue toStringFunc;
GASObjectInterface * piobj = ToObjectInterface(penv);
if (!debug && piobj &&
piobj->GetMemberRaw(penv->GetSC(),
penv->GetBuiltin(GASBuiltin_toString), &toStringFunc))
{
//!AB: recursion guard is necessary to prevent infinite recursion in the case
// if "toString" returns "this" or another object that may finally returns "this".
// This is especially dangerous because "toString" method might be invoked
// implicitly during the type casting.
if (penv->RecursionGuardStart())
{
GASValue result;
//GAS_Invoke(toStringFunc, &result, piobj, penv, 0, 0, NULL);
GASFunctionRef func(toStringFunc.ToFunction(penv));
if (func != NULL)
{
GASFnCall fnCall(&result, piobj, penv, 0, 0);
func.Function->Invoke(fnCall, func.LocalFrame, NULL);
}
stringVal = result.ToString(penv);
}
else
{
// [type Object]
stringVal = penv->GetBuiltin(GASBuiltin_typeObject_);
#ifndef GFC_NO_FXPLAYER_VERBOSE_ACTION_ERRORS
if (penv->IsVerboseActionErrors())
penv->LogScriptError("Error: Stack overflow, max level of 255 nested calls of toString is reached.\n");
#endif
}
penv->RecursionGuardEnd();
}
else
{
// Used within action log or if object is null.
const char* ptext;
if ((T.Type == OBJECT) && V.pObjectValue &&
(ptext = V.pObjectValue->GetTextValue(penv))!=0)
{
stringVal = penv->CreateString(ptext);
}
else if ((T.Type == CHARACTER) && V.pCharHandle)
{
stringVal = GetCharacterNamePath(penv);
}
else
{
// This is the default: [object Object]
stringVal = penv->GetBuiltin(GASBuiltin_objectObject_);
}
}
}
break;
case FUNCTION:
case FUNCTIONNAME:
{
// [type Function]
stringVal = penv->GetBuiltin(GASBuiltin_typeFunction_);
}
break;
case PROPERTY:
{
// [property]
stringVal = penv->CreateConstString("[property]");
}
break;
case RESOLVEHANDLER:
{
// [resolveHandler]
stringVal = penv->CreateConstString("[resolveHandler]");
}
break;
default:
{
stringVal = penv->GetBuiltin(GASBuiltin_badtype_); // "<bad type>";
GASSERT(0);
}
}
return stringVal;
}
GASString GASValue::ToDebugString(GASEnvironment* penv) const
{
GASString s = ToStringImpl(penv, -1, 1);
return s;
}
// Conversion to const GASString.
GASString GASValue::ToStringVersioned(GASEnvironment* penv, UInt version) const
{
if (IsUndefined())
{
// Version-dependent behavior.
if (version > 0 && version <= 6)
{
return penv->GetBuiltin(GASBuiltin_empty_);
}
else
{
return penv->GetBuiltin(GASBuiltin_undefined);
}
}
return ToStringImpl(penv, -1, 0);
}
// Conversion to double/float.
GASNumber GASValue::ToNumber(GASEnvironment* penv) const
{
if (T.Type == NUMBER)
{
return NV.NumberValue;
}
else if (T.Type == INTEGER)
{
return (GASNumber)NV.Int32Value;
}
else if (T.Type == STRING)
{
// @@ Moock says the rule here is: if the
// string is a valid Float literal, then it
// gets converted; otherwise it is set to NaN.
//
GASNumber num;
if (!StringToNumber(&num, V.pStringNode->pData))
{
// Failed conversion to Number.
num = GASNumberUtil::NaN(); //!AB
}
return num;
}
else if (T.Type == NULLTYPE)
{
UInt version = penv->GetVersion();
if (version <= 6)
return GFX_ASNUMBER_ZERO;
else
return GASNumberUtil::NaN();
}
else if (T.Type == BOOLEAN)
{
return V.BooleanValue ? (GASNumber)1 : (GASNumber)0;
}
else if (T.Type == CHARACTER)
{
// ToNumber for movieclip returns always NaN (according to Moock)
return GASNumberUtil::NaN();
}
else if ( ((T.Type == OBJECT) && V.pObjectValue) ||
(T.Type == FUNCTION))
{
// @@ Moock says the result here should be
// "the return value of the object's ValueOf() method".
//
// Arrays and Movieclips should return NaN.
GASValue toValueFunc;
GASObjectInterface * piobj = ToObjectInterface(penv);
if (penv && piobj->GetMemberRaw(penv->GetSC(), penv->GetBuiltin(GASBuiltin_valueOf), &toValueFunc))
{
//!AB: recursion guard is necessary to prevent infinite recursion in the case
// if "valueOf" returns "this" or another object that may finally returns "this".
// This is especially dangerous because "valueOf" method might be invoked
// implicitly during the type casting.
GASNumber retVal;
if (penv->RecursionGuardStart())
{
GASValue result;
//GAS_Invoke(toValueFunc, &result, piobj, penv, 0, 0, NULL);
GASFunctionRef func(toValueFunc.ToFunction(penv));
if (func != NULL)
{
GASFnCall fnCall(&result, piobj, penv, 0, 0);
func.Function->Invoke(fnCall, func.LocalFrame, NULL);
}
if (result.IsPrimitive())
retVal = result.ToNumber(penv);
else
retVal = GASNumberUtil::NaN();
}
else
{
retVal = GASNumberUtil::NaN();
#ifndef GFC_NO_FXPLAYER_VERBOSE_ACTION_ERRORS
if (penv->IsVerboseActionErrors())
penv->LogScriptError("Error: Stack overflow, max level of 255 nested calls of valueOf is reached.\n");
#endif
}
penv->RecursionGuardEnd();
return retVal;
}
else
{
// AB: valueOf should be implemented for each object!
// leave this branch just for now.
// Text characters with var names could get in here.
if (T.Type == CHARACTER)
return GASNumberUtil::NaN();
const char* ptextval = piobj->GetTextValue(penv);
if (ptextval)
{
return (GASNumber)atof(ptextval);
}
}
return GFX_ASNUMBER_ZERO;
}
else if (IsUndefined())
{
UInt version = penv->GetVersion();
//!AB: Flash 6 and below: 0
// Flash 7 and above: NaN
if (version > 0 && version <= 6)
return GFX_ASNUMBER_ZERO;
else
return GASNumberUtil::NaN();
}
else
{
return GFX_ASNUMBER_ZERO;
}
}
SInt32 GASValue::ToInt32(GASEnvironment* penv) const
{
if (T.Type == INTEGER)
{
return NV.Int32Value;
}
else
{
// follow to algorithm described in ECMA-262, clause 9.5
static const GASNumber UINT32MAX = GASNumber(~0u);
static const GASNumber INT32MAX = GASNumber(1UL<<31)-1;
static const GASNumber INT32MIN = -GASNumber(1UL<<31);
GASNumber v = ToNumber(penv);
if (v == 0 || GASNumberUtil::IsNaNOrInfinity(v))
return 0;
if (v >= INT32MIN && v <= INT32MAX)
return (SInt32)v;
GASNumber anv = floor(G_Abs(v));
UInt32 uv = (UInt32)fmod(anv, UINT32MAX + 1);
SInt32 rv = -SInt32(~0u - uv + 1); // actually, this is an analog of (uv - 2^32)
if (v < 0) rv = -rv;
return rv;
}
}
UInt32 GASValue::ToUInt32(GASEnvironment* penv) const
{
if (T.Type == INTEGER)
{
return NV.UInt32Value;
}
else
{
// follow to algorithm described in ECMA-262, clause 9.6
static const GASNumber UINT32MAX = GASNumber(~0u);
GASNumber v = ToNumber(penv);
if (v == 0 || GASNumberUtil::IsNaNOrInfinity(v))
return 0;
if (v >= 0 && v <= UINT32MAX)
return (UInt32)v;
GASNumber anv = floor(G_Abs(v));
UInt32 uv = (UInt32)fmod(anv, UINT32MAX + 1);
if (v < 0) uv = 0u-uv;
return uv;
}
}
// Conversion to boolean.
bool GASValue::ToBool(const GASEnvironment* penv) const
{
if (T.Type == STRING)
{
//!AB, "true" and "false" should never be converted
// to appropriate boolean value.
// For Flash 6, the only string containing non-zero numeric
// value is converted to boolean as "true".
// For Flash 7+, any non-empty string is converted to
// boolean as "true".
/*
if (StringValue == "false")
{
return false;
}
else if (StringValue == "true")
{
return true;
}*/
// Empty string --> false
if (V.pStringNode->Size == 0)
return false;
if (penv->GetVersion() <= 6)
{
// @@ Moock: "true if the string can
// be converted to a valid nonzero number".
//
GASNumber num;
if (StringToNumber(&num, V.pStringNode->pData))
{
if (GASNumberUtil::IsNaN(num)) return false;
return (!!num);
}
return false;
}
return true;
}
else if (T.Type == NUMBER)
{
// @@ Moock says, NaN --> false
if (GASNumberUtil::IsNaN(NV.NumberValue))
return false;
return NV.NumberValue != GFX_ASNUMBER_ZERO;
}
else if (T.Type == INTEGER)
{
return NV.Int32Value != 0;
}
else if (T.Type == BOOLEAN)
{
return V.BooleanValue;
}
else if (T.Type == OBJECT)
{
return V.pObjectValue != NULL;
}
else if (T.Type == CHARACTER)
{
return ToASCharacter(penv) != NULL;
}
/*else if (Type == C_FUNCTION)
{
return C_FunctionValue != NULL;
} */
else if (T.Type == FUNCTION)
{
return !V.FunctionValue.IsNull ();
}
else if (T.Type == FUNCTIONNAME)
{
return true;
}
else
{
GASSERT(IsUndefined() || T.Type == NULLTYPE);
return false;
}
}
// Return value as an object.
GASObject* GASValue::ToObject(const GASEnvironment* penv) const
{
switch(T.Type)
{
case OBJECT:
return V.pObjectValue;
case FUNCTION:
if (!V.FunctionValue.IsNull ())
return V.FunctionValue.GetObjectPtr();
break;
case FUNCTIONNAME:
{
GASFunctionRef funcRef = ResolveFunctionName(penv);
if (!funcRef.IsNull())
{
return funcRef.GetObjectPtr();
}
}
break;
case CHARACTER:
{
// If this occurs, it means that code used ToObject() call instead of ToObjectInterface() in a
// place where characters should be permitted.
// In rare cases that this is necessary, an explicit != CHARACTER check should be done,
// so that we can keep this useful warning.
GFC_DEBUG_WARNING(1, "GASValue::ToObject() invoked on a character. Need to use ToObjectInterface?");
}
break;
case PROPERTY:
if (IsProperty())
{
GPtr<GFxASCharacter> paschar = penv->GetTarget();
if (paschar)
{
GASValue val;
//!AB: environment shouldn't be constant here, since it can be modified by the
// GetPropertyValue call. TODO - remove all 'const' attributes from env parameters.
if (GetPropertyValue(const_cast<GASEnvironment*>(penv), paschar.GetPtr(), &val))
return val.ToObject(penv);
}
}
break;
default:;
}
return NULL;
}
GFxASCharacter* GASValue::ToASCharacter(const GASEnvironment* penv) const
{
if (T.Type == CHARACTER && penv)
{
if (V.pCharHandle)
return V.pCharHandle->ResolveCharacter(penv->GetMovieRoot());
}
return 0;
}
GASObjectInterface* GASValue::ToObjectInterface(const GASEnvironment* penv) const
{
if (T.Type == CHARACTER)
return ToASCharacter(penv);
return ToObject(penv);
}
// Return value as a function. Returns NULL if value is
// not a function.
GASFunctionRef GASValue::ToFunction(const GASEnvironment* penv) const
{
switch(T.Type)
{
case FUNCTIONNAME:
return ResolveFunctionName(penv);
case FUNCTION:
return GASFunctionRef(V.FunctionValue);
default:;
}
return GASFunctionRef();
}
GASFunctionRef GASValue::ToResolveHandler() const
{
if (T.Type == RESOLVEHANDLER)
return V.FunctionValue;
return GASFunctionRef();
}
// Force type to number.
void GASValue::ConvertToNumber(GASEnvironment* pEnv)
{
SetNumber(ToNumber(pEnv));
}
// Force type to string.
void GASValue::ConvertToString(GASEnvironment* pEnv)
{
GASString str = ToString(pEnv);
DropRefs();
T.Type = STRING; // force type.
V.pStringNode = str.GetNode();
V.pStringNode->AddRef();
}
// Force type to string.
void GASValue::ConvertToStringVersioned(GASEnvironment* pEnv, UInt version)
{
GASString str = ToStringVersioned(pEnv, version);
DropRefs();
T.Type = STRING; // force type.
V.pStringNode = str.GetNode();
V.pStringNode->AddRef();
}
void GASValue::SetAsObject(GASObject* obj)
{
if (obj && obj->IsFunction())
{
SetAsFunction(obj->ToFunction());
}
else if (T.Type != OBJECT || V.pObjectValue != obj)
{
DropRefs();
T.Type = OBJECT;
V.pObjectValue = obj;
if (V.pObjectValue)
V.pObjectValue->AddRef();
}
}
void GASValue::SetAsCharacterHandle(GFxCharacterHandle* pchar)
{
if (T.Type != CHARACTER || V.pCharHandle != pchar)
{
DropRefs();
T.Type = CHARACTER;
V.pCharHandle = pchar;
if (V.pCharHandle)
V.pCharHandle->AddRef();
}
}
void GASValue::SetAsCharacter(GFxASCharacter* pchar)
{
SetAsCharacterHandle(pchar ? pchar->GetCharacterHandle() : 0);
}
void GASValue::SetAsObjectInterface(GASObjectInterface* pobj)
{
// More expensive then either SetAsObject or SetAsCharacter, should be called only when necessary.
if (pobj->IsASCharacter())
SetAsCharacter((GFxASCharacter*) pobj);
else
SetAsObject((GASObject*) pobj);
}
void GASValue::SetAsFunction(const GASFunctionRefBase& func)
{
if (T.Type != FUNCTION || V.FunctionValue != func)
{
DropRefs();
T.Type = FUNCTION;
V.FunctionValue.Init(func);
}
}
void GASValue::SetAsResolveHandler(const GASFunctionRefBase& func)
{
DropRefs();
T.Type = RESOLVEHANDLER;
V.FunctionValue.Init(func);
}
void GASValue::Add(GASEnvironment* penv, const GASValue& v)
{
GASValue pv1, pv2;
pv1 = ToPrimitive (penv);
pv2 = v.ToPrimitive (penv);
if(pv1.T.Type == STRING || pv2.T.Type == STRING)
{
UInt version = penv->GetVersion();
pv1.ConvertToStringVersioned(penv, version);
pv1.StringConcat(penv, pv2.ToStringVersioned(penv, version));
SetString(pv1.ToString(penv));
}
else
{
SetNumber(pv1.ToNumber(penv) + pv2.ToNumber(penv));
}
}
void GASValue::Add(GASEnvironment* penv, int v2)
{
GASValue pv1;
pv1 = ToPrimitive (penv);
if(pv1.T.Type == STRING)
{
UInt version = penv->GetVersion();
pv1.ConvertToStringVersioned(penv, version);
pv1.StringConcat(penv, GASValue(v2).ToStringVersioned(penv, version));
SetString(pv1.ToString(penv));
}
else
{
SetNumber(pv1.ToNumber(penv) + (GASNumber)v2);
}
}
// Return true if operands are equal.
bool GASValue::IsEqual (GASEnvironment* penv, const GASValue& v) const
{
bool thisNullType = (IsUndefined()) || (T.Type == NULLTYPE);
bool vNullType = (v.IsUndefined()) || (v.GetType() == NULLTYPE);
if (thisNullType || vNullType)
{
return thisNullType == vNullType;
}
switch(T.Type)
{
case STRING:
if (v.IsNumber())
// ECMA-262, 11.9.3.17
return GASValue(ToNumber(penv)).IsEqual(penv, v);
else if (v.T.Type == BOOLEAN)
// ECMA-262, 11.9.3.19
return IsEqual(penv, GASValue(v.ToNumber(penv)));
else if (v.IsFunction() || v.IsObject())
{
// ECMA-262, 11.9.3.20
GASValue primVal = v.ToPrimitive(penv);
if (primVal.IsPrimitive())
return IsEqual(penv, primVal);
else
return false;
}
return GASString(V.pStringNode) == v.ToString(penv);
case NUMBER:
{
GASNumber pv;
if (v.T.Type == BOOLEAN)
// ECMA-262, 11.9.3.19
pv = v.ToNumber(penv);
else if (v.IsFunction() || v.IsObject())
{
// ECMA-262, 11.9.3.20
GASValue primVal = v.ToPrimitive(penv);
if (primVal.IsPrimitive())
return IsEqual(penv, primVal);
else
return false;
}
else
pv = v.ToNumber(penv);
// ECMA-262, 11.9.3.5 - 7
if (GASNumberUtil::IsNaN(NV.NumberValue) && GASNumberUtil::IsNaN(pv))
return true;
else if (GASNumberUtil::IsNaN(NV.NumberValue) || GASNumberUtil::IsNaN(pv))
return false;
return NV.NumberValue == pv;
}
case INTEGER:
{
if (v.T.Type == NUMBER)
{
// can't compare integer with floating point directly;
// so, convert integer to a fp and compare.
GASValue dv;
dv.SetNumber((GASNumber)NV.Int32Value);
return dv.IsEqual(penv, v);
}
SInt32 pv;
if (v.T.Type == BOOLEAN)
// ECMA-262, 11.9.3.19
pv = v.ToInt32(penv);
else if (v.IsFunction() || v.IsObject())
{
// ECMA-262, 11.9.3.20
GASValue primVal = v.ToPrimitive(penv);
if (primVal.IsPrimitive())
return IsEqual(penv, primVal);
else
return false;
}
else
pv = v.ToInt32(penv);
// ECMA-262, 11.9.3.5 - 7
return NV.Int32Value == pv;
}
case BOOLEAN:
// According to ECMA-262, clause 11.9.3, if type(x) or (y) is Boolean
// return ToNumber(x) == y (or x == ToNumber(y))
return GASValue(ToNumber(penv)).IsEqual(penv, v);
// Objects and functions are compared by reference.
case OBJECT:
case FUNCTION:
if (v.IsNumber() || v.T.Type == STRING || v.T.Type == BOOLEAN)
{
// ECMA-262, 11.9.3.19
// ECMA-262, 11.9.3.21
GASValue primVal = ToPrimitive(penv);
if (primVal.IsPrimitive())
return primVal.IsEqual(penv, v);
else
return false;
}
if (T.Type == OBJECT && v.T.Type == OBJECT)
return V.pObjectValue == v.V.pObjectValue;
else if (T.Type == FUNCTION && v.T.Type == FUNCTION)
return V.FunctionValue == v.V.FunctionValue;
return false;
// Flash MovieClips are always compared by path only
// (although this is technically not always correct).
case CHARACTER:
if (v.T.Type != CHARACTER)
return false; // type mismatch, return false
if (!V.pCharHandle || !v.V.pCharHandle) // should not happen.
return V.pCharHandle == v.V.pCharHandle;
return GetCharacterNamePath(penv) == v.GetCharacterNamePath(penv);
default:
break;
}
// That's it. Anything else?
return T.Type == v.T.Type;
}
// action: 0 - equals, -1 - less than, 1 - greater than
// return value: either BOOLEAN (true\false) or undefined.
GASValue GASValue::Compare (GASEnvironment* penv, const GASValue& v, int action) const
{
if (action == 0)
return GASValue(IsEqual(penv, v));
// do comparison according to ECMA-262, 11.8.5
GASValue pv1 = ToPrimitive(penv);
GASValue pv2 = v.ToPrimitive(penv);
bool result = false;
if (pv1.IsString() && pv2.IsString())
{
// do strings comparison, see 11.8.5.16
if (action < 0)
return (pv1.ToString(penv) < pv2.ToString(penv));
else
return (pv1.ToString(penv) > pv2.ToString(penv));
}
if (penv->GetVersion() > 6 && (pv1.IsUndefined() || pv2.IsUndefined()))
return GASValue();
// do operator < comparison
GASNumber val1, val2;
if (action < 0)
{
val1 = pv1.ToNumber(penv);
val2 = pv2.ToNumber(penv);
}
else
{
val2 = pv1.ToNumber(penv);
val1 = pv2.ToNumber(penv);
}
if (GASNumberUtil::IsNaN(val1) || GASNumberUtil::IsNaN(val2))
return GASValue();
if (val1 == val2)
result = false;
else if (GASNumberUtil::IsNEGATIVE_ZERO(val1) && GASNumberUtil::IsPOSITIVE_ZERO(val2))
result = false;
else if (GASNumberUtil::IsNEGATIVE_ZERO(val2) && GASNumberUtil::IsPOSITIVE_ZERO(val1))
result = false;
else if (GASNumberUtil::IsPOSITIVE_INFINITY(val1))
result = false;
else if (GASNumberUtil::IsPOSITIVE_INFINITY(val2))
result = true;
else if (GASNumberUtil::IsNEGATIVE_INFINITY(val2))
result = false;
else if (GASNumberUtil::IsNEGATIVE_INFINITY(val1))
result = true;
else
result = (val1 < val2);
return GASValue(result);
}
// Sets *this to this string plus the given string.
void GASValue::StringConcat(GASEnvironment* penv, const GASString& str)
{
GASString tempstr(ToString(penv) + str);
DropRefs();
T.Type = STRING;
V.pStringNode = tempstr.GetNode();
V.pStringNode->AddRef();
}
// This hack is used to prevent memory leak in situations like this:
//
// var o = new Object;
// o._listeners = new Array;
// o._listeners[0] = o;
//
// This is used in Tween class, for example.
// After GC is implemented we will remove this hack. (!AB)
static void GAS_CheckForListenersMemLeak(GASObject* pobjectValue)
{
#ifdef GFC_NO_GC
if (pobjectValue->GetRefCount() == 2 && pobjectValue->IsListenerSet)
{
GASMember val;
// Hack-in-hack: use raw key because we don't have string context
// here, while this is called from DropRefs and destructor.
GASString::RawCompareKey rkey("_listeners", sizeof("_listeners")-1);
if (pobjectValue->Members.GetAlt(rkey, &val))
{
GASObject* vobj = val.Value.ToObject(NULL);
if (vobj && vobj->GetObjectType() == GASObject::Object_Array)
{
GASArrayObject* arrayObj = static_cast<GASArrayObject*>(vobj);
for(int i = 0, n = arrayObj->GetSize(); i < n; ++i)
{
GASValue* elem = arrayObj->GetElementPtr(i);
if (elem && elem->IsObject())
{
if (elem->ToObject(NULL) == pobjectValue)
{
elem->V.pObjectValue->Release();
elem->V.pObjectValue = 0;
break;
}
}
}
}
}
}
#else
GUNUSED(pobjectValue);
#endif
}
// Drop any ref counts we have; this happens prior to changing our value.
void GASValue::DropRefs()
{
switch(T.Type)
{
case FUNCTIONNAME:
case STRING:
// pStringNode can not be null (null strings are not supported)!
V.pStringNode->Release();
break;
case FUNCTION:
case RESOLVEHANDLER:
if (V.FunctionValue != NULL)
{
V.FunctionValue.DropRefs();
}
break;
case OBJECT:
if (V.pObjectValue)
{
// Tween class '_listeners' leak detection hack! Comments above.
GAS_CheckForListenersMemLeak(V.pObjectValue);
V.pObjectValue->Release();
V.pObjectValue = 0;
}
break;
case CHARACTER:
if (V.pCharHandle)
{
V.pCharHandle->Release();
V.pCharHandle = 0;
}
break;
case PROPERTY:
if (V.pProperty)
{
V.pProperty->Release();
V.pProperty = 0;
}
break;
}
}
// TODO: use hint
GASValue GASValue::ToPrimitive(GASEnvironment* penv, GASValue::Hint /*hint*/) const
{
if ((T.Type == OBJECT) || (T.Type == CHARACTER) || (T.Type == FUNCTION))
{
GASValue toValueFunc;
GASObjectInterface * piobj = ToObjectInterface(penv);
if (penv && piobj && piobj->GetMemberRaw(penv->GetSC(), penv->GetBuiltin(GASBuiltin_valueOf), &toValueFunc))
{
GASValue result;
if (penv->RecursionGuardStart())
{
//GAS_Invoke(toValueFunc, &result, piobj, penv, 0, 0, NULL);
GASFunctionRef func(toValueFunc.ToFunction(penv));
if (func != NULL)
{
GASFnCall fnCall(&result, piobj, penv, 0, 0);
func.Function->Invoke(fnCall, func.LocalFrame, NULL);
}
}
else
{
#ifndef GFC_NO_FXPLAYER_VERBOSE_ACTION_ERRORS
if (penv->IsVerboseActionErrors())
penv->LogScriptError("Error: Stack overflow, max level of 255 nested calls of valueOf is reached.\n");
#endif
}
penv->RecursionGuardEnd();
return result;
}
else
{
GASValue ret;
const char* pstr;
if ((T.Type == CHARACTER) && V.pCharHandle)
{
ret.SetString(GetCharacterNamePath(penv));
}
else if ((T.Type == OBJECT) && V.pObjectValue &&
(pstr = V.pObjectValue->GetTextValue(penv))!=0)
{
ret.SetString(penv->CreateString(pstr));
}
else
{
ret.SetString(ToString(penv));//??
//ret.SetString ("[object Object]"); // default (??)
}
return ret;
/* //AB, will think about this later
else if (penv != 0) {
GFxLog* plog = penv->GetTarget()->GetLog();
plog->LogScriptError ("Error: \"valueOf\" method is not found in ToPrimitive\n");
}
else
GASSERT (penv != 0); //AB ??? not sure what to do*/
}
}
else if (T.Type == FUNCTIONNAME)
{
GASFunctionRef funcRef = ResolveFunctionName(penv);
if (!funcRef.IsNull())
{
return funcRef;
}
}
// TODO: do we need to do anything with other types?
return *this;
}
bool GASValue::GetPropertyValue(GASEnvironment* penv, GASObjectInterface* pthis, GASValue* value) const
{
if (IsProperty() && penv)
{
if (!V.pProperty->GetterMethod.IsNull())
{
GASValue result;
V.pProperty->GetterMethod.Invoke(GASFnCall(&result, pthis, penv, 0, 0));
*value = result;
return true;
}
else
{
if (penv->IsVerboseActionErrors())
penv->LogScriptError("Error: getter method is null.");
}
}
return false;
}
void GASValue::SetPropertyValue(GASEnvironment* penv, GASObjectInterface* pthis, const GASValue& val)
{
if (IsProperty() && penv)
{
if (!V.pProperty->SetterMethod.IsNull())
{
GASValue result;
penv->Push(val);
V.pProperty->SetterMethod.Invoke(GASFnCall(&result, pthis, penv, 1, penv->GetTopIndex()));
penv->Drop(1);
//?? What to do if penv is NULL?
}
else
{
if (penv->IsVerboseActionErrors())
penv->LogScriptError("Error: setter method is null.");
}
}
}
const GASString& GASValue::GetCharacterNamePath(GASEnvironment* penv) const
{
// If character is resolvable - return GetNamePath, else return empty string
if(V.pCharHandle)
if(V.pCharHandle->ResolveCharacter(penv->GetMovieRoot()))
return V.pCharHandle->GetNamePath();
return penv->GetBuiltin(GASBuiltin_empty_);
}
GASFunctionRef GASValue::ResolveFunctionName(const GASEnvironment* penv) const
{
if (penv && T.Type == FUNCTIONNAME)
{
GASString functionName(penv->GetBuiltin(GASBuiltin_empty_));
functionName.AssignNode(V.pStringNode);
GASFunctionRef ctor(penv->GetGC()->ResolveFunctionName(functionName));
return ctor;
}
return GASFunctionRef();
}
void GASValue::Div (GASEnvironment* penv, const GASValue& v)
{
GASNumber v1 = ToNumber(penv);
GASNumber v2 = v.ToNumber(penv);
GASNumber res;
if (v2 == 0)
{
if (v1 == 0)
res = GASNumberUtil::NaN(); // division 0 / 0
else if (v1 >= 0)
res = GASNumberUtil::POSITIVE_INFINITY(); // division v / 0
else
res = GASNumberUtil::NEGATIVE_INFINITY(); // division -v / 0
}
else
res = v1 / v2;
SetNumber(res);
}
//////////////////////////////////////////////////////////////////////////
GASValueGuard::GASValueGuard(const GASEnvironment* penv, const GASValue& val)
: pEnv(penv), Value(val)
{
GASSERT(pEnv);
if (val.IsCharacter())
{
pChar = val.ToASCharacter(pEnv);
if (pChar)
pChar->AddRef();
}
else
pChar = NULL;
}
GASValueGuard::~GASValueGuard()
{
if (pChar)
pChar->Release();
}
GASValueGuard& GASValueGuard::operator=(const GASValue& val)
{
GASSERT(pEnv);
Value = val;
if (pChar)
pChar->Release();
if (val.IsCharacter())
{
pChar = val.ToASCharacter(pEnv);
pChar->AddRef();
}
else
pChar = NULL;
return *this;
}
/////// GASFnCall //////
// Access a particular argument.
GASValue& GASFnCall::Arg(int n) const
{
GASSERT(n < NArgs);
return Env->Bottom(FirstArgBottomIndex - n);
}
// Logging script support
GFxLog* GASFnCall::GetLog() const
{
return Env->GetLog();
}
bool GASFnCall::IsVerboseAction() const
{
return Env->IsVerboseAction();
}
bool GASFnCall::IsVerboseActionErrors() const
{
return Env->IsVerboseActionErrors();
}
bool GASFnCall::CheckThisPtr(UInt type) const
{
if (!ThisPtr || ThisPtr->GetObjectType() != (GASObjectInterface::ObjectType)type)
{
return false;
}
return true;
}
void GASFnCall::ThisPtrError(const char* className, const char* psrcfile, int line) const
{
#ifdef GFC_BUILD_DEBUG
const char* pf = strrchr(psrcfile, '\\');
if (!pf)
pf = strrchr(psrcfile, '/');
if (pf)
++pf;
else
pf = psrcfile;
Env->LogScriptError
("Error: Null or invalid 'this' is used for a method of %s class, file %s, line %d\n",
className, pf, line);
#else
GUNUSED2(psrcfile, line);
Env->LogScriptError
("Error: Null or invalid 'this' is used for a method of %s class.\n",
className);
#endif
}