Mozilla/mozilla/js2/src/js2execution.cpp
waldemar%netscape.com 0fd39e383e Recent ECMA grammar changes: deleted the x.class operator and prohibited super from being a direct operand of the delete operator.
git-svn-id: svn://10.0.0.236/trunk@109436 18797224-902f-48f8-a5cc-f745e15eee43
2001-12-01 03:31:46 +00:00

2643 lines
99 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* The contents of this file are subject to the Netscape Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is the JavaScript 2 Prototype.
*
* The Initial Developer of the Original Code is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the
* terms of the GNU Public License (the "GPL"), in which case the
* provisions of the GPL are applicable instead of those above.
* If you wish to allow use of your version of this file only
* under the terms of the GPL and not to allow others to use your
* version of this file under the NPL, indicate your decision by
* deleting the provisions above and replace them with the notice
* and other provisions required by the GPL. If you do not delete
* the provisions above, a recipient may use your version of this
* file under either the NPL or the GPL.
*/
#ifdef _WIN32
// Turn off warnings about identifiers too long in browser information
#pragma warning(disable: 4786)
#pragma warning(disable: 4711)
#pragma warning(disable: 4710)
#endif
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include "parser.h"
#include "numerics.h"
#include "js2runtime.h"
#include "bytecodegen.h"
#include "jsstring.h"
#include "jsarray.h"
#include "jsmath.h"
#include "hash.h"
#include "fdlibm_ns.h"
// this is the AttributeList passed to the name lookup routines
#define CURRENT_ATTR (NULL)
namespace JavaScript {
namespace JS2Runtime {
inline char narrow(char16 ch) { return char(ch); }
JSValue Context::readEvalString(const String &str, const String& fileName, ScopeChain *scopeChain, const JSValue& thisValue)
{
JSValue result = kUndefinedValue;
Arena a;
Parser p(mWorld, a, mFlags, str, fileName);
Reader *oldReader = mReader;
setReader(&p.lexer.reader);
try {
StmtNode *parsedStatements = p.parseProgram();
ASSERT(p.lexer.peek(true).hasKind(Token::end));
if (mDebugFlag)
{
PrettyPrinter f(stdOut, 30);
{
PrettyPrinter::Block b(f, 2);
f << "Program =";
f.linearBreak(1);
StmtNode::printStatements(f, parsedStatements);
}
f.end();
stdOut << '\n';
}
buildRuntime(parsedStatements);
JS2Runtime::ByteCodeModule* bcm = genCode(parsedStatements, fileName);
if (bcm) {
setReader(NULL);
bcm->setSource(str, fileName);
result = interpret(bcm, 0, scopeChain, thisValue, NULL, 0);
delete bcm;
}
}
catch (Exception &x) {
setReader(oldReader);
throw x;
}
setReader(oldReader);
return result;
}
JSValue Context::readEvalFile(const String& fileName)
{
String buffer;
int ch;
JSValue result = kUndefinedValue;
std::string str(fileName.length(), char());
std::transform(fileName.begin(), fileName.end(), str.begin(), narrow);
FILE* f = fopen(str.c_str(), "r");
if (f) {
while ((ch = getc(f)) != EOF)
buffer += static_cast<char>(ch);
fclose(f);
result = readEvalString(buffer, fileName, NULL, JSValue(getGlobalObject()));
}
return result;
}
// Given an operator op, and two operand types - dispatch to the
// appropriate operator. The operands are still on the execution stack.
// Return result indicates whether interpreter loop has to begin
// execution of new function.
bool Context::executeOperator(Operator op, JSType *t1, JSType *t2)
{
// look in the operator table for applicable operators
OperatorList applicableOperators;
for (OperatorList::iterator oi = mOperatorTable[op].begin(),
end = mOperatorTable[op].end();
(oi != end); oi++)
{
if ((*oi)->isApplicable(t1, t2)) {
applicableOperators.push_back(*oi);
}
}
if (applicableOperators.size() == 0)
reportError(Exception::runtimeError, "No applicable operators found");
OperatorList::iterator candidate = applicableOperators.begin();
for (OperatorList::iterator aoi = applicableOperators.begin() + 1,
aend = applicableOperators.end();
(aoi != aend); aoi++)
{
if ((*aoi)->mType1->derivesFrom((*candidate)->mType1)
|| ((*aoi)->mType2->derivesFrom((*candidate)->mType2)))
candidate = aoi;
}
JSFunction *target = (*candidate)->mImp;
JSValue newThis = kNullValue;
if (target->isNative()) {
JSValue result = target->getNativeCode()(this, newThis, getBase(stackSize() - 2), 2);
resizeStack(stackSize() - 2);
pushValue(result);
return false;
}
else {
mActivationStack.push(new Activation(mLocals, mStack, mStackTop - 2, mScopeChain,
mArgumentBase, mThis, mPC, mCurModule));
mThis = newThis;
mCurModule = target->getByteCode();
mArgumentBase = getBase(stackSize() - 2);
mScopeChain = target->getScopeChain();
return true;
}
}
// Invokes either the native or bytecode implementation. Causes another interpreter loop
// to begin execution, and does nothing to clean up the incoming arguments (which need
// not even be on the execution stack).
JSValue Context::invokeFunction(JSFunction *target, const JSValue& thisValue, JSValue *argv, uint32 argc)
{
if (target->isNative())
return target->getNativeCode()(this, thisValue, argv, argc);
else
return interpret(target->getByteCode(), 0, target->getScopeChain(), thisValue, argv, argc);
}
JSValue Context::interpret(JS2Runtime::ByteCodeModule *bcm, int offset, ScopeChain *scopeChain, const JSValue& thisValue, JSValue *argv, uint32 /*argc*/)
{
Activation *prev = new Activation(mLocals, mStack, mStackTop, mScopeChain,
mArgumentBase, mThis, NULL, mCurModule); // use NULL pc value to force interpret loop to exit
uint32 activationHeight = mActivationStack.size();
mActivationStack.push(prev);
mThis = thisValue;
if (scopeChain)
mScopeChain = scopeChain;
else {
mScopeChain = new ScopeChain(this, mWorld);
mScopeChain->addScope(getGlobalObject());
}
// if (mThis.isObject())
// mScopeChain->addScope(mThis.object);
// mScopeChain->addScope(mActivationStack.top());
mCurModule = bcm;
uint8 *pc = mCurModule->mCodeBase + offset;
uint8 *endPC = mCurModule->mCodeBase + mCurModule->mLength;
mArgumentBase = argv;
mLocals = new JSValue[mCurModule->mLocalsCount];
mStack = new JSValue[mCurModule->mStackDepth];
mStackMax = mCurModule->mStackDepth;
mStackTop = 0;
JSValue result;
try {
result = interpret(pc, endPC);
}
catch (Exception &jsx) {
while (mActivationStack.size() != activationHeight)
mActivationStack.pop();
// the following (delete's) are a bit iffy - depends on whether
// a closure capturing the contents has come along...
// if (mThis.isObject())
// mScopeChain->popScope();
JSValue x;
if (jsx.hasKind(Exception::userException))
x = popValue();
delete[] mStack;
delete[] mLocals;
if (scopeChain == NULL)
delete mScopeChain;
mCurModule = prev->mModule;
mStack = prev->mStack;
mStackTop = 0; // we're processing an exception, no need to preserve the stack
if (jsx.hasKind(Exception::userException))
pushValue(x);
if (mCurModule)
mStackMax = mCurModule->mStackDepth;
mLocals = prev->mLocals;
mArgumentBase = prev->mArgumentBase;
mThis = prev->mThis;
mScopeChain = prev->mScopeChain;
delete prev;
throw jsx;
}
ASSERT(prev == mActivationStack.top());
mActivationStack.pop();
// the following (delete's) are a bit iffy - depends on whether
// a closure capturing the contents has come along...
// if (mThis.isObject())
// mScopeChain->popScope();
delete[] mStack;
delete[] mLocals;
if (scopeChain == NULL)
delete mScopeChain;
mCurModule = prev->mModule;
mStack = prev->mStack;
mStackTop = prev->mStackTop;
if (mCurModule)
mStackMax = mCurModule->mStackDepth;
mLocals = prev->mLocals;
mArgumentBase = prev->mArgumentBase;
mThis = prev->mThis;
mScopeChain = prev->mScopeChain;
delete prev;
return result;
}
// Assumes arguments are on the top of stack.
JSValue *Context::buildArgumentBlock(JSFunction *target, uint32 &argCount)
{
JSValue *argBase;
uint32 maxExpectedArgCount = target->getRequiredArgumentCount() + target->getOptionalArgumentCount();
if (target->isChecked()) {
if (argCount < target->getRequiredArgumentCount())
reportError(Exception::referenceError, "Insufficient quantity of arguments");
if ((argCount > maxExpectedArgCount) && !target->hasRestParameter())
reportError(Exception::referenceError, "Oversufficient quantity of arguments");
}
uint32 i;
uint32 argBlockSize = max(argCount, maxExpectedArgCount) + (target->hasRestParameter() ? 1 : 0);
// room for all required & optional arguments
// plus the rest parameter if it exists.
argBase = new JSValue[argBlockSize];
bool *argUsed = new bool[argCount];
for (i = 0; i < argCount; i++)
argUsed[i] = false;
uint32 argStart = stackSize() - argCount;
uint32 argIndex = 0; // the index into argBase, the resolved outgoing arguments
uint32 posArgIndex = 0; // next positional arg from the incoming set
for (i = 0; i < argCount; i++) { // find the first positional (i.e. non-named arg)
JSValue v = getValue(i + argStart);
if (!v.isObject() || (v.object->mType != NamedArgument_Type)) {
posArgIndex = i;
break;
}
}
// for each parameter - see if there's a named arg that matches
// otherwise take the next non-named argument. (unless the parameter
// is an optional one)
while (argIndex < maxExpectedArgCount) {
bool foundNamedArg = false;
// find a matching named argument
for (uint32 i = 0; i < argCount; i++) {
JSValue v = getValue(i + argStart);
if (v.isObject() && (v.object->mType == NamedArgument_Type)) {
NamedArgument *arg = static_cast<NamedArgument *>(v.object);
if (target->findParameterName(arg->mName) == argIndex) {
// mark this arg has having been used
argUsed[i] = true;
foundNamedArg = true;
argBase[argIndex] = arg->mValue;
break;
}
}
}
if (!foundNamedArg) {
if (target->argHasInitializer(argIndex)) {
argBase[argIndex] = target->runArgInitializer(this, argIndex, mThis, argBase, maxExpectedArgCount);
}
else {
if (posArgIndex < argCount) {
argUsed[posArgIndex] = true;
argBase[argIndex] = getValue(posArgIndex++ + argStart);
}
else {
if (target->isChecked())
reportError(Exception::referenceError, "Missing positional argument");
else
argBase[argIndex] = kUndefinedValue;
}
}
}
argIndex++;
}
JSValue restArgument;
if (target->hasRestParameter() && target->getRestParameterName()) {
restArgument = target->getRestParameterType()->newInstance(this);
argBase[maxExpectedArgCount] = JSValue(restArgument);
}
posArgIndex = 0; // re-number the non-named arguments that end up in the rest arg
// now find a place for any left-overs
for (i = 0; i < argCount; i++) {
if (!argUsed[i]) {
JSValue v = getValue(i + argStart);
if (v.isObject() && (v.object->mType == NamedArgument_Type)) {
NamedArgument *arg = static_cast<NamedArgument *>(v.object);
// if this argument matches a parameter name, that's bad because
// it's a duplicate case
if (target->findParameterName(arg->mName) != NotABanana)
reportError(Exception::referenceError, "Duplicate named argument");
else {
if (target->hasRestParameter()) {
if (!restArgument.isUndefined())
// XXX is it an error to have duplicate named rest properties?
restArgument.object->setProperty(this, *arg->mName, (NamespaceList *)(NULL), arg->mValue);
}
else
reportError(Exception::referenceError, "Unknown named argument, no rest argument");
}
}
else {
if (target->hasRestParameter()) {
if (!restArgument.isUndefined()) {
const String *id = numberToString(posArgIndex++);
restArgument.object->setProperty(this, *id, (NamespaceList *)(NULL), v);
}
}
else {
if (target->isChecked())
reportError(Exception::referenceError, "Extra argument, no rest argument");
else {
JSValue v = getValue(i + argStart);
if (v.isObject() && (v.object->mType == NamedArgument_Type)) {
NamedArgument *arg = static_cast<NamedArgument *>(v.object);
argBase[i] = arg->mValue;
}
else
argBase[i] = v;
}
}
}
}
}
argCount = argBlockSize;
return argBase;
}
JSValue Context::interpret(uint8 *pc, uint8 *endPC)
{
JSValue result = kUndefinedValue;
while (pc != endPC) {
mPC = pc;
try {
if (mDebugFlag) {
FunctionName *fnName;
uint32 x = mScopeChain->mScopeStack.size();
if (mCurModule->mFunction && (fnName = mCurModule->mFunction->getFunctionName())) {
StringFormatter s;
PrettyPrinter pp(s);
fnName->print(pp);
const String &fnStr = s.getString();
std::string str(fnStr.length(), char());
std::transform(fnStr.begin(), fnStr.end(), str.begin(), narrow);
uint32 len = strlen(str.c_str());
printFormat(stdOut, "%.30s+%.4d%*c%d %d ", str.c_str(), (pc - mCurModule->mCodeBase), (len > 30) ? 0 : (len - 30), ' ', stackSize(), x);
}
else
printFormat(stdOut, "+%.4d%*c%d %d ", (pc - mCurModule->mCodeBase), 30, ' ', stackSize(), x);
printInstruction(stdOut, toUInt32(pc - mCurModule->mCodeBase), *mCurModule);
}
switch ((ByteCodeOp)(*pc++)) {
case PopOp:
{
result = popValue();
}
break;
case VoidPopOp:
{
popValue(); // XXX just decrement top
}
break;
case PopNOp:
{
uint16 count = *((uint16 *)pc);
pc += sizeof(uint16);
resizeStack(mStackTop - count);
}
break;
case DupOp:
{
JSValue v = topValue();
pushValue(v);
}
break;
case DupInsertOp: // XXX something more efficient than pop/push?
{
JSValue v1 = popValue();
JSValue v2 = popValue();
pushValue(v1);
pushValue(v2);
pushValue(v1);
}
break;
case DupNOp:
{
uint16 count = *((uint16 *)pc);
pc += sizeof(uint16);
JSValue *vp = getBase(stackSize() - count);
while (count--)
pushValue(*vp++);
}
break;
// <N things> <object2> --> <object2> <N things> <object2>
case DupInsertNOp:
{
JSValue v2 = topValue();
uint16 count = *((uint16 *)pc);
pc += sizeof(uint16);
insertValue(v2, mStackTop - (count + 1));
}
break;
case SwapOp: // XXX something more efficient than pop/push?
{
JSValue v1 = popValue();
JSValue v2 = popValue();
pushValue(v1);
pushValue(v2);
}
break;
case LogicalXorOp:
{
JSValue v2 = popValue();
ASSERT(v2.isBool());
JSValue v1 = popValue();
ASSERT(v1.isBool());
if (v1.boolean) {
if (v2.boolean) {
popValue();
popValue();
pushValue(kFalseValue);
}
else
popValue();
}
else {
if (v1.boolean) {
popValue();
popValue();
pushValue(kFalseValue);
}
else {
JSValue t = topValue();
popValue();
popValue();
pushValue(t);
}
}
}
break;
case LogicalNotOp:
{
JSValue v = popValue();
ASSERT(v.isBool());
pushValue(JSValue(!v.boolean));
}
break;
case JumpOp:
{
uint32 offset = *((uint32 *)pc);
pc += offset;
}
break;
case ToBooleanOp:
{
JSValue v = popValue();
pushValue(v.toBoolean(this));
}
break;
case JumpFalseOp:
{
JSValue v = popValue();
ASSERT(v.isBool());
if (!v.boolean) {
uint32 offset = *((uint32 *)pc);
pc += offset;
}
else
pc += sizeof(uint32);
}
break;
case JumpTrueOp:
{
JSValue v = popValue();
ASSERT(v.isBool());
if (v.boolean) {
uint32 offset = *((uint32 *)pc);
pc += offset;
}
else
pc += sizeof(uint32);
}
break;
case NamedArgOp:
{
JSValue name = popValue();
if (!name.isString())
reportError(Exception::typeError, "String needed for argument name");
JSValue value = popValue();
pushValue(JSValue(new NamedArgument(value, name.string)));
}
break;
case InvokeOp:
{
uint32 argCount = *((uint32 *)pc);
uint32 cleanUp = argCount;
pc += sizeof(uint32);
CallFlag callFlags = (CallFlag)(*pc++);
mPC = pc;
JSValue *targetValue = getBase(stackSize() - (argCount + 1));
JSFunction *target;
JSValue oldThis = mThis;
switch (callFlags & ThisFlags) {
case NoThis:
mThis = kNullValue;
break;
case Explicit:
mThis = getValue(stackSize() - (argCount + 2));
cleanUp++;
break;
default:
NOT_REACHED("bad bytecode");
}
if (!targetValue->isFunction()) {
if (targetValue->isType()) {
// how to distinguish between a cast and an invocation of
// the superclass constructor from a constructor????
// XXX help
if ((callFlags & SuperInvoke) == SuperInvoke) {
// in this case, calling the constructor requires passing the 'this' value
// through.
target = targetValue->type->getDefaultConstructor();
mThis = oldThis;
}
else {
// " Type() "
// - it's a cast expression, we call the
// default constructor, overriding the supplied 'this'.
//
// XXX Note that this is different behaviour from JS1.5, where
// (e.g.) Array(2) is an invocation of the constructor.
target = targetValue->type->getTypeCastFunction();
if (target == NULL) {
if ((argCount > 1) || ((callFlags & ThisFlags) != NoThis))
reportError(Exception::referenceError, "Type cast can only take one argument");
JSValue v;
if (argCount > 0)
v = popValue();
popValue(); // don't need the target anymore
pushValue(mapValueToType(v, targetValue->type));
mThis = oldThis;
break; // all done
}
}
}
else
reportError(Exception::referenceError, "Not a function");
}
else {
target = targetValue->function;
// an invocation of the super constructor has to pass thru the existing 'this'
if (target->isConstructor() && (callFlags & SuperInvoke))
mThis = oldThis;
else
if (target->hasBoundThis()) // then we use it instead of the expressed version
mThis = target->getThisValue();
}
if (target->isPrototype() && mThis.isNull())
mThis = JSValue(getGlobalObject());
if (!target->isNative()) {
JSValue *argBase = buildArgumentBlock(target, argCount);
// XXX Optimize the more typical case in which there are no named arguments, no optional arguments
// and the appropriate number of arguments have been supplied.
mActivationStack.push(new Activation(mLocals, mStack, mStackTop - (cleanUp + 1),
mScopeChain,
mArgumentBase, oldThis,
pc, mCurModule));
mScopeChain = target->getScopeChain();
mScopeChain->addScope(target->getParameterBarrel());
mScopeChain->addScope(target->getActivation());
if (!target->isChecked()) {
JSArrayInstance *args = (JSArrayInstance *)Array_Type->newInstance(this);
for (uint32 i = 0; i < argCount; i++)
args->setProperty(this, *numberToString(i), NULL, argBase[i]);
mScopeChain->defineVariable(this, Arguments_StringAtom, NULL, Array_Type, JSValue(args));
}
mCurModule = target->getByteCode();
pc = mCurModule->mCodeBase;
endPC = mCurModule->mCodeBase + mCurModule->mLength;
mArgumentBase = argBase;
mLocals = new JSValue[mCurModule->mLocalsCount];
mStack = new JSValue[mCurModule->mStackDepth];
mStackMax = mCurModule->mStackDepth;
mStackTop = 0;
}
else {
JSValue *argBase = getBase(stackSize() - argCount);
// native functions may still need access to information
// about the currently executing function.
mActivationStack.push(new Activation(mLocals, mStack, mStackTop - (cleanUp + 1),
mScopeChain,
mArgumentBase, oldThis,
pc, mCurModule));
JSValue result = (target->getNativeCode())(this, mThis, argBase, argCount);
Activation *prev = mActivationStack.top();
delete prev;
mActivationStack.pop();
mThis = oldThis;
resizeStack(stackSize() - (cleanUp + 1));
pushValue(result);
}
}
break;
case ReturnVoidOp:
{
if (mActivationStack.empty())
return result;
Activation *prev = mActivationStack.top();
if (prev->mPC == NULL) { // NULL is used to indicate that we want the loop to exit
// (even though there is more activation stack to go
return result; // - used to implement Xetters from XProperty ops. e.g.)
}
mActivationStack.pop();
delete[] mLocals;
delete[] mStack;
mScopeChain->popScope();
mScopeChain->popScope();
mCurModule = prev->mModule;
pc = prev->mPC;
endPC = mCurModule->mCodeBase + mCurModule->mLength;
mStack = prev->mStack;
mStackTop = prev->mStackTop;
mStackMax = mCurModule->mStackDepth;
mLocals = prev->mLocals;
mArgumentBase = prev->mArgumentBase;
mThis = prev->mThis;
mScopeChain = prev->mScopeChain;
pushValue(kUndefinedValue);
delete prev;
}
break;
case ReturnOp:
{
JSValue result = popValue();
if (mActivationStack.empty())
return result;
Activation *prev = mActivationStack.top();
if (prev->mPC == NULL) {
return result;
}
mActivationStack.pop();
delete[] mLocals;
delete[] mStack;
mScopeChain->popScope();
mScopeChain->popScope();
mCurModule = prev->mModule;
pc = prev->mPC;
endPC = mCurModule->mCodeBase + mCurModule->mLength;
mStack = prev->mStack;
mStackTop = prev->mStackTop;
mStackMax = mCurModule->mStackDepth;
mLocals = prev->mLocals;
mArgumentBase = prev->mArgumentBase;
mThis = prev->mThis;
mScopeChain = prev->mScopeChain;
pushValue(result);
delete prev;
}
break;
case LoadTypeOp:
{
JSType *t = *((JSType **)pc);
pc += sizeof(JSType *);
pushValue(JSValue(t));
}
break;
case LoadFunctionOp:
{
JSFunction *f = *((JSFunction **)pc);
pc += sizeof(JSFunction *);
pushValue(JSValue(f));
}
break;
case LoadConstantStringOp:
{
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
pushValue(JSValue(mCurModule->getString(index)));
}
break;
case LoadConstantNumberOp:
{
pushValue(JSValue(mCurModule->getNumber(pc)));
pc += sizeof(float64);
}
break;
case LoadConstantUndefinedOp:
pushValue(kUndefinedValue);
break;
case LoadConstantTrueOp:
pushValue(kTrueValue);
break;
case LoadConstantFalseOp:
pushValue(kFalseValue);
break;
case LoadConstantNullOp:
pushValue(kNullValue);
break;
case LoadConstantZeroOp:
pushValue(kPositiveZero);
break;
case DeleteNameOp:
{
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
const String &name = *mCurModule->getString(index);
if (mScopeChain->deleteName(this, name, CURRENT_ATTR))
pushValue(kTrueValue);
else
pushValue(kFalseValue);
}
break;
case DeleteOp:
{
JSValue base = popValue();
JSObject *obj = NULL;
if (!base.isObject() && !base.isType())
obj = base.toObject(this).object;
else
obj = base.object;
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
const String &name = *mCurModule->getString(index);
PropertyIterator it;
if (obj->hasOwnProperty(name, CURRENT_ATTR, Read, &it))
if (obj->deleteProperty(name, CURRENT_ATTR))
pushValue(kTrueValue);
else
pushValue(kFalseValue);
else
pushValue(kTrueValue);
}
break;
case TypeOfOp:
{
JSValue v = popValue();
if (v.isUndefined())
pushValue(JSValue(&Undefined_StringAtom));
else
if (v.isNull())
pushValue(JSValue(&Object_StringAtom));
else
if (v.isBool())
pushValue(JSValue(&Boolean_StringAtom));
else
if (v.isNumber())
pushValue(JSValue(&Number_StringAtom));
else
if (v.isString())
pushValue(JSValue(&String_StringAtom));
else
if (v.isFunction())
pushValue(JSValue(&Function_StringAtom));
else
pushValue(JSValue(&Object_StringAtom));
}
break;
case AsOp:
{
JSValue t = popValue();
JSValue v = popValue();
if (t.isType()) {
if (v.isObject()
&& (v.object->getType() == t.type))
pushValue(v);
else
pushValue(kNullValue); // XXX or throw an exception if
// NULL is not a member of type t
}
else
reportError(Exception::typeError, "As needs type");
}
break;
case IsOp:
{
JSValue t = popValue();
JSValue v = popValue();
if (t.isType()) {
if (v.isNull())
if (t.type == Object_Type)
pushValue(kTrueValue);
else
pushValue(kFalseValue);
else
if (v.isObject()
&& ((v.object->getType() == t.type)
|| (v.object->getType()->derivesFrom(t.type))))
pushValue(kTrueValue);
else {
if (v.getType() == t.type)
pushValue(kTrueValue);
else
pushValue(kFalseValue);
}
}
else { // behave like instanceof
if (t.isObject() && t.isFunction()) {
// XXX prove that t->function["prototype"] is on t.object->mPrototype chain
pushValue(kTrueValue);
}
else
reportError(Exception::typeError, "InstanceOf needs object");
}
}
break;
case InstanceOfOp:
{
JSValue t = popValue();
JSValue v = popValue();
if (t.isFunction()) {
JSFunction *obj = t.function;
PropertyIterator i;
JSFunction *target = NULL;
if (obj->hasProperty(HasInstance_StringAtom, CURRENT_ATTR, Read, &i)) {
JSValue hi = obj->getPropertyValue(i);
if (hi.isFunction())
target = hi.function;
}
if (target)
pushValue(invokeFunction(target, t, &v, 1));
else
reportError(Exception::typeError, "InstanceOf couldn't find [[hasInstance]]");
}
else {
// XXX hack for <= ECMA 3 compatibility
if (t.isType()) {
if ((v.getType() == t.type)
|| v.getType()->derivesFrom(t.type))
pushValue(kTrueValue);
else
pushValue(kFalseValue);
}
else
reportError(Exception::typeError, "InstanceOf needs function object");
}
}
break;
case GetNameOp:
{
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
const String &name = *mCurModule->getString(index);
JSObject *parent = mScopeChain->getNameValue(this, name, CURRENT_ATTR);
JSValue result = topValue();
if (result.isFunction()) {
popValue();
if (result.function->isConstructor())
// A constructor has to be called with a NULL 'this' in order to prompt it
// to construct the instance object.
pushValue(JSValue((JSFunction *)(new JSBoundFunction(result.function, NULL))));
else
pushValue(JSValue((JSFunction *)(new JSBoundFunction(result.function, parent))));
}
}
break;
case SetNameOp:
{
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
const String &name = *mCurModule->getString(index);
JSValue v = topValue();
if (v.isFunction() && v.function->hasBoundThis() && !v.function->isMethod()) {
popValue();
pushValue(JSValue(v.function->getFunction()));
}
mScopeChain->setNameValue(this, name, CURRENT_ATTR);
}
break;
case GetTypeOfNameOp:
{
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
const String &name = *mCurModule->getString(index);
if (mScopeChain->hasNameValue(name, CURRENT_ATTR)) {
mScopeChain->getNameValue(this, name, CURRENT_ATTR);
}
else
pushValue(kUndefinedValue);
}
break;
case GetElementOp:
{
uint32 dimCount = *((uint16 *)pc);
pc += sizeof(uint16);
mPC = pc;
JSValue *baseValue = getBase(stackSize() - (dimCount + 1));
// Use the type of the base to dispatch on...
JSObject *obj = NULL;
if (baseValue->isType())
obj = baseValue->type;
else
if (baseValue->isFunction())
obj = baseValue->function;
else
if (baseValue->isObject())
obj = baseValue->object;
else
obj = baseValue->toObject(this).object;
JSFunction *target = obj->getType()->getUnaryOperator(Index);
if (target) {
JSValue result;
if (target->isNative()) {
JSValue *argBase = new JSValue[dimCount + 1];
for (uint32 i = 0; i < (dimCount + 1); i++)
argBase[i] = baseValue[i];
resizeStack(stackSize() - (dimCount + 1));
result = target->getNativeCode()(this, argBase[0], argBase, dimCount + 1);
delete[] argBase;
}
else {
uint32 argCount = dimCount + 1;
JSValue *argBase = buildArgumentBlock(target, argCount);
resizeStack(stackSize() - (dimCount + 1));
try {
result = interpret(target->getByteCode(), 0, target->getScopeChain(), argBase[0], argBase, argCount);
}
catch (Exception &x) {
delete[] argBase;
throw x;
}
}
pushValue(result);
}
else { // XXX or should this be implemented in Object_Type as operator "[]" ?
if (dimCount != 1)
reportError(Exception::typeError, "too many indices");
JSValue index = popValue();
popValue(); // discard base
const String *name = index.toString(this).string;
obj->getProperty(this, *name, CURRENT_ATTR);
}
// if the result is a method of some kind, bind
// the base object to it
JSValue result = topValue();
if (result.isFunction()) {
popValue();
if (result.function->isConstructor())
// A constructor has to be called with a NULL 'this' in order to prompt it
// to construct the instance object.
pushValue(JSValue((JSFunction *)(new JSBoundFunction(result.function, NULL))));
else
pushValue(JSValue((JSFunction *)(new JSBoundFunction(result.function, obj))));
}
}
break;
case SetElementOp:
{
uint32 dimCount = *((uint16 *)pc);
pc += sizeof(uint16);
mPC = pc;
JSValue *baseValue = getBase(stackSize() - (dimCount + 2)); // +1 for assigned value
// Use the type of the base to dispatch on...
JSObject *obj = NULL;
if (!baseValue->isObject() && !baseValue->isType())
obj = baseValue->toObject(this).object;
else
obj = baseValue->object;
JSFunction *target = obj->getType()->getUnaryOperator(IndexEqual);
if (target) {
JSValue v = popValue(); // need to have this sitting right above the base value
if (v.isFunction() && v.function->hasBoundThis() && !v.function->isMethod()) {
v = JSValue(v.function->getFunction());
}
insertValue(v, mStackTop - dimCount);
JSValue result;
if (target->isNative()) {
JSValue *argBase = new JSValue[dimCount + 2];
for (uint32 i = 0; i < (dimCount + 2); i++)
argBase[i] = baseValue[i];
resizeStack(stackSize() - (dimCount + 2));
result = target->getNativeCode()(this, *baseValue, baseValue, (dimCount + 2));
delete[] argBase;
}
else {
uint32 argCount = dimCount + 2;
JSValue *argBase = buildArgumentBlock(target, argCount);
resizeStack(stackSize() - (dimCount + 2));
try {
result = interpret(target->getByteCode(), 0, target->getScopeChain(), *baseValue, argBase, argCount);
}
catch (Exception &x) {
delete[] argBase;
throw x;
}
}
pushValue(result);
}
else { // XXX or should this be implemented in Object_Type as operator "[]=" ?
if (dimCount != 1)
reportError(Exception::typeError, "too many indices");
JSValue v = popValue();
if (v.isFunction() && v.function->hasBoundThis() && !v.function->isMethod()) {
v = JSValue(v.function->getFunction());
}
JSValue index = popValue();
popValue(); // discard base
const String *name = index.toString(this).string;
obj->setProperty(this, *name, CURRENT_ATTR, v);
pushValue(v);
}
}
break;
case DeleteElementOp:
{
uint32 dimCount = *((uint16 *)pc);
pc += sizeof(uint16);
mPC = pc;
JSValue *baseValue = getBase(stackSize() - (dimCount + 1));
// Use the type of the base to dispatch on...
JSObject *obj = NULL;
if (!baseValue->isObject() && !baseValue->isType())
obj = baseValue->toObject(this).object;
else
obj = baseValue->object;
JSFunction *target = obj->getType()->getUnaryOperator(DeleteIndex);
if (target) {
JSValue result;
if (target->isNative()) {
JSValue *argBase = new JSValue[dimCount + 1];
for (uint32 i = 0; i < (dimCount + 1); i++)
argBase[i] = baseValue[i];
resizeStack(stackSize() - (dimCount + 1));
result = target->getNativeCode()(this, argBase[0], argBase, dimCount + 1);
delete[] argBase;
}
else {
uint32 argCount = dimCount + 1;
JSValue *argBase = buildArgumentBlock(target, argCount);
resizeStack(stackSize() - (dimCount + 1));
try {
result = interpret(target->getByteCode(), 0, target->getScopeChain(), kNullValue, argBase, argCount);
}
catch (Exception &x) {
delete[] argBase;
throw x;
}
}
pushValue(result);
}
else { // XXX or should this be implemented in Object_Type as operator "delete[]" ?
if (dimCount != 1)
reportError(Exception::typeError, "too many indices");
JSValue index = popValue();
popValue(); // discard base
const String *name = index.toString(this).string;
PropertyIterator it;
if (obj->hasOwnProperty(*name, CURRENT_ATTR, Read, &it))
obj->deleteProperty(*name, CURRENT_ATTR);
pushValue(kTrueValue);
}
}
break;
case GetPropertyOp:
{
JSValue base = popValue();
JSObject *obj = NULL;
if (!base.isObject() && !base.isType())
obj = base.toObject(this).object;
else
obj = base.object;
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
mPC = pc;
const String &name = *mCurModule->getString(index);
obj->getProperty(this, name, CURRENT_ATTR);
// if the result is a method of some kind, bind
// the base object to it
JSValue result = topValue();
if (result.isFunction()) {
popValue();
if (result.function->isConstructor())
// A constructor has to be called with a NULL 'this' in order to prompt it
// to construct the instance object.
pushValue(JSValue((JSFunction *)(new JSBoundFunction(result.function, NULL))));
else
pushValue(JSValue((JSFunction *)(new JSBoundFunction(result.function, obj))));
}
}
break;
case GetInvokePropertyOp:
{
JSValue base = topValue();
JSObject *obj = NULL;
if (!base.isObject() && !base.isType() && !base.isFunction()) {
obj = base.toObject(this).object;
popValue();
pushValue(JSValue(obj)); // want the "toObject'd" version of base
}
else
obj = base.object;
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
mPC = pc;
const String &name = *mCurModule->getString(index);
// const String &name = *mCurModule->getIdentifierString(index);
// AttributeList *attr = mCurModule->getIdentifierAttr(index);
// attr->next = CURRENT_ATTR;
obj->getProperty(this, name, CURRENT_ATTR);
}
break;
case SetPropertyOp:
{
JSValue v = popValue();
if (v.isFunction() && v.function->hasBoundThis() && !v.function->isMethod()) {
v = JSValue(v.function->getFunction());
}
JSValue base = popValue();
JSObject *obj = NULL;
if (!base.isObject() && !base.isType())
obj = base.toObject(this).object;
else
obj = base.object;
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
mPC = pc;
const String &name = *mCurModule->getString(index);
obj->setProperty(this, name, CURRENT_ATTR, v);
pushValue(v);
}
break;
case DoUnaryOp:
{
Operator op = (Operator)(*pc++);
mPC = pc;
JSValue v = topValue();
JSFunction *target;
if (v.isObject() && (target = v.object->getType()->getUnaryOperator(op)) )
{
uint32 argBase = stackSize() - 1;
JSValue newThis = kNullValue;
if (!target->isNative()) {
// lie about argCount to the activation since it
// would normally expect to clean the function pointer
// off the stack as well.
mActivationStack.push(new Activation(mLocals, mStack, mStackTop - 1,
mScopeChain,
mArgumentBase, mThis,
pc, mCurModule));
mThis = newThis;
mCurModule = target->getByteCode();
pc = mCurModule->mCodeBase;
endPC = mCurModule->mCodeBase + mCurModule->mLength;
mArgumentBase = getBase(argBase);
mLocals = new JSValue[mCurModule->mLocalsCount];
mStack = new JSValue[mCurModule->mStackDepth];
mStackMax = mCurModule->mStackDepth;
mStackTop = 0;
}
else {
JSValue result = (target->getNativeCode())(this, newThis, getBase(argBase), 0);
resizeStack(stackSize() - 1);
pushValue(result);
}
break;
}
switch (op) {
default:
NOT_REACHED("bad unary op");
case Negate:
{
popValue();
JSValue n = v.toNumber(this);
if (n.isNaN())
pushValue(n);
else
pushValue(JSValue(-n.f64));
}
break;
case Posate:
{
popValue();
JSValue n = v.toNumber(this);
pushValue(n);
}
break;
case Complement:
{
popValue();
JSValue n = v.toInt32(this);
pushValue(JSValue((float64)(~(int32)(n.f64))));
}
break;
}
}
break;
case DoOperatorOp:
{
Operator op = (Operator)(*pc++);
mPC = pc;
JSValue v1 = getValue(stackSize() - 2);
JSValue v2 = getValue(stackSize() - 1);
if (executeOperator(op, v1.getType(), v2.getType())) {
// need to invoke
pc = mCurModule->mCodeBase;
endPC = mCurModule->mCodeBase + mCurModule->mLength;
mLocals = new JSValue[mCurModule->mLocalsCount];
mStack = new JSValue[mCurModule->mStackDepth];
mStackMax = mCurModule->mStackDepth;
mStackTop = 0;
}
}
break;
case GetConstructorOp:
{
JSValue v = popValue();
ASSERT(v.isType());
pushValue(JSValue(v.type->getDefaultConstructor()));
}
break;
case NewInstanceOp:
{
uint32 argCount = *((uint32 *)pc);
pc += sizeof(uint32);
uint32 cleanUp = argCount;
JSValue *argBase = getBase(stackSize() - argCount);
bool isPrototypeFunctionCall = false;
JSFunction *target = NULL;
JSValue newThis = kNullValue;
JSValue *typeValue = getBase(stackSize() - (argCount + 1));
if (!typeValue->isType()) {
if (typeValue->isFunction() && typeValue->function->isPrototype()) {
isPrototypeFunctionCall = true;
target = typeValue->function;
newThis = Object_Type->newInstance(this);
PropertyIterator i;
if (target->hasProperty(Prototype_StringAtom, CURRENT_ATTR, Read, &i)) {
JSValue v = target->getPropertyValue(i);
newThis.object->mPrototype = v.toObject(this).object;
}
}
else
reportError(Exception::referenceError, "Not a type or a prototype function");
}
else {
// if the type has an operator "new" use that,
// otherwise use the default constructor (and pass NULL
// for the this value)
target = typeValue->type->getUnaryOperator(New);
if (target)
newThis = JSValue(typeValue->type->newInstance(this));
else {
newThis = kNullValue;
target = typeValue->type->getDefaultConstructor();
}
}
ASSERT(target);
JSValue result;
if (target->isNative()) {
JSValue *tArgBase = new JSValue[argCount];
for (uint32 i = 0; i < argCount; i++)
tArgBase[i] = argBase[i];
resizeStack(stackSize() - cleanUp);
result = target->getNativeCode()(this, newThis, tArgBase, argCount);
}
else {
argBase = buildArgumentBlock(target, argCount);
resizeStack(stackSize() - cleanUp);
if (!target->isChecked()) {
JSArrayInstance *args = (JSArrayInstance *)Array_Type->newInstance(this);
for (uint32 i = 0; i < argCount; i++)
args->setProperty(this, *numberToString(i), NULL, argBase[i]);
target->getScopeChain()->defineVariable(this, Arguments_StringAtom, NULL, Array_Type, JSValue(args));
}
try {
result = interpret(target->getByteCode(), 0, target->getScopeChain(), newThis, argBase, argCount);
}
catch (Exception &x) {
delete[] argBase;
throw x;
}
}
if (isPrototypeFunctionCall) {
// If it's a prototype function, the return value is only
// interesting if it's not a primitive, in which case it
// overrides the newly constructed object. Weird, huh.
if (!result.isPrimitive())
newThis = result;
}
else
// otherwise, constructor has potentially made the 'this', so retain it
newThis = result;
popValue(); // don't need the type anymore
pushValue(newThis);
}
break;
case NewThisOp:
{
JSValue v = popValue();
if (mThis.isNull()) {
ASSERT(v.isType());
mThis = JSValue(v.type->newInstance(this));
}
}
break;
case NewObjectOp:
{
pushValue(JSValue(Object_Type->newInstance(this)));
}
break;
case GetLocalVarOp:
{
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
pushValue(mLocals[index]);
}
break;
case SetLocalVarOp:
{
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
JSValue v = topValue();
if (v.isFunction() && v.function->hasBoundThis() && !v.function->isMethod()) {
v = JSValue(v.function->getFunction());
}
mLocals[index] = v;
}
break;
case GetClosureVarOp:
{
uint32 depth = *((uint32 *)pc);
pc += sizeof(uint32);
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
// pushValue(mScopeChain->getClosureVar(depth, index));
}
break;
case SetClosureVarOp:
{
JSValue v = topValue();
if (v.isFunction() && v.function->hasBoundThis() && !v.function->isMethod()) {
v = JSValue(v.function->getFunction());
}
uint32 depth = *((uint32 *)pc);
pc += sizeof(uint32);
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
// mScopeChain->setClosureVar(depth, index, topValue()));
}
break;
case NewClosureOp:
{
}
break;
case LoadThisOp:
{
pushValue(mThis);
}
break;
case GetArgOp:
{
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
pushValue(mArgumentBase[index]);
}
break;
case SetArgOp:
{
JSValue v = topValue();
if (v.isFunction() && v.function->hasBoundThis() && !v.function->isMethod()) {
v = JSValue(v.function->getFunction());
}
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
mArgumentBase[index] = v;
}
break;
case GetMethodOp:
{
JSValue base = topValue();
ASSERT(dynamic_cast<JSInstance *>(base.object));
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
pushValue(JSValue(base.object->mType->mMethods[index]));
}
break;
case GetMethodRefOp:
{
JSValue base = popValue();
ASSERT(dynamic_cast<JSInstance *>(base.object));
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
pushValue(JSValue(new JSBoundFunction(base.object->mType->mMethods[index], base.object)));
}
break;
case GetFieldOp:
{
JSValue base = popValue();
ASSERT(dynamic_cast<JSInstance *>(base.object));
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
pushValue(((JSInstance *)(base.object))->mInstanceValues[index]);
}
break;
case SetFieldOp:
{
JSValue v = popValue();
if (v.isFunction() && v.function->hasBoundThis() && !v.function->isMethod()) {
v = JSValue(v.function->getFunction());
}
JSValue base = popValue();
ASSERT(dynamic_cast<JSInstance *>(base.object));
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
((JSInstance *)(base.object))->mInstanceValues[index] = v;
pushValue(v);
}
break;
case WithinOp:
{
JSValue base = popValue();
mScopeChain->addScope(base.toObject(this).object);
}
break;
case WithoutOp:
{
mScopeChain->popScope();
}
break;
case PushScopeOp:
{
JSObject *obj = *((JSObject **)pc);
mScopeChain->addScope(obj);
pc += sizeof(JSObject *);
}
break;
case PopScopeOp:
{
mScopeChain->popScope();
}
break;
case LoadGlobalObjectOp:
{
pushValue(JSValue(getGlobalObject()));
}
break;
case JsrOp:
{
uint32 offset = *((uint32 *)pc);
mSubStack.push(pc + sizeof(uint32));
pc += offset;
}
break;
case RtsOp:
{
pc = mSubStack.top();
mSubStack.pop();
}
break;
case TryOp:
{
Activation *curAct = (mActivationStack.size() > 0) ? mActivationStack.top() : NULL;
uint32 handler = *((uint32 *)pc);
if (handler != toUInt32(-1))
mTryStack.push(new HandlerData(pc + handler, stackSize(), curAct));
pc += sizeof(uint32);
handler = *((uint32 *)pc);
if (handler != toUInt32(-1))
mTryStack.push(new HandlerData(pc + handler, stackSize(), curAct));
pc += sizeof(uint32);
}
break;
case HandlerOp:
{
HandlerData *hndlr = (HandlerData *)mTryStack.top();
mTryStack.pop();
delete hndlr;
}
break;
case ThrowOp:
{
throw Exception(Exception::userException, "");
}
break;
case JuxtaposeOp:
{
JSValue v2 = popValue();
JSValue v1 = popValue();
ASSERT(v1.isObject() && (v1.object->getType() == Attribute_Type));
ASSERT(v2.isObject() && (v2.object->getType() == Attribute_Type));
Attribute *a1 = static_cast<Attribute *>(v1.object);
Attribute *a2 = static_cast<Attribute *>(v2.object);
if ((a1->mTrueFlags & a2->mFalseFlags) != 0)
reportError(Exception::semanticError, "Mismatched attributes"); // XXX could supply more detail
if ((a1->mFalseFlags & a2->mTrueFlags) != 0)
reportError(Exception::semanticError, "Mismatched attributes");
// Now build the result attribute and set it's values
Attribute *x = new Attribute(a1->mTrueFlags | a2->mTrueFlags, a1->mFalseFlags | a2->mFalseFlags);
NamespaceList *t = a1->mNamespaceList;
while (t) {
x->mNamespaceList = new NamespaceList(t->mName, x->mNamespaceList);
t = t->mNext;
}
t = a2->mNamespaceList;
while (t) {
x->mNamespaceList = new NamespaceList(t->mName, x->mNamespaceList);
t = t->mNext;
}
if (a1->mExtendArgument)
x->mExtendArgument = a1->mExtendArgument;
else
if (a2->mExtendArgument)
x->mExtendArgument = a2->mExtendArgument;
pushValue(JSValue(x));
}
break;
case CastOp:
{
JSValue t = popValue();
ASSERT(t.isType());
JSType *toType = t.type;
JSValue v = popValue();
pushValue(mapValueToType(v, toType));
}
break;
default:
reportError(Exception::internalError, "Bad Opcode");
}
}
catch (Exception &jsx) {
if (mTryStack.size() > 0) {
HandlerData *hndlr = (HandlerData *)mTryStack.top();
Activation *curAct = (mActivationStack.size() > 0) ? mActivationStack.top() : NULL;
JSValue x;
if (curAct != hndlr->mActivation) {
ASSERT(mActivationStack.size() > 0);
Activation *prev;// = mActivationStack.top();
do {
prev = curAct;
if (prev->mPC == NULL) {
// Yikes! the exception is getting thrown across a re-invocation
// of the interpreter loop.
throw jsx;
}
mActivationStack.pop();
curAct = mActivationStack.top();
} while (hndlr->mActivation != curAct);
if (jsx.hasKind(Exception::userException)) // snatch the exception before the stack gets clobbered
x = popValue();
mCurModule = prev->mModule;
endPC = mCurModule->mCodeBase + mCurModule->mLength;
mLocals = prev->mLocals;
mStack = prev->mStack;
mStackMax = mCurModule->mStackDepth;
mArgumentBase = prev->mArgumentBase;
mThis = prev->mThis;
}
else {
if (jsx.hasKind(Exception::userException))
x = popValue();
}
// make sure there's a JS object for the catch clause to work with
if (!jsx.hasKind(Exception::userException)) {
JSValue argv[1];
argv[0] = JSValue(new String(jsx.fullMessage()));
switch (jsx.kind) {
case Exception::syntaxError:
x = SyntaxError_Constructor(this, kNullValue, argv, 1);
break;
case Exception::referenceError:
x = ReferenceError_Constructor(this, kNullValue, argv, 1);
break;
case Exception::typeError:
x = TypeError_Constructor(this, kNullValue, argv, 1);
break;
case Exception::rangeError:
x = RangeError_Constructor(this, kNullValue, argv, 1);
break;
default:
x = Error_Constructor(this, kNullValue, argv, 1);
break;
}
}
resizeStack(hndlr->mStackSize);
pc = hndlr->mPC;
pushValue(x);
}
else
throw jsx; //reportError(Exception::uncaughtError, "No handler for throw");
}
}
return result;
}
float64 JSValue::getNumberValue() const
{
if (isNumber())
return f64;
ASSERT(isObject() && (getType() == Number_Type));
return *((float64 *)(object->mPrivate));
}
const String *JSValue::getStringValue() const
{
if (isString())
return string;
ASSERT(isObject() && (getType() == String_Type));
return (const String *)(object->mPrivate);
}
bool JSValue::getBoolValue() const
{
if (isBool())
return boolean;
ASSERT(isObject() && (getType() == Boolean_Type));
return (object->mPrivate != 0);
}
static JSValue numberPlus(Context *, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
return JSValue(argv[0].getNumberValue() + argv[1].getNumberValue());
}
static JSValue integerPlus(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
return JSValue(argv[0].getNumberValue() + argv[1].getNumberValue());
}
static JSValue objectPlus(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
JSValue &r1 = argv[0];
JSValue &r2 = argv[1];
JSType *t1 = r1.getType();
JSType *t2 = r2.getType();
if ((t1 == Number_Type) && (t2 == Number_Type)) {
return JSValue(r1.getNumberValue() + r2.getNumberValue());
}
if (t1 == String_Type) {
if (t2 == String_Type)
return JSValue(new String(*r1.getStringValue() + *r2.getStringValue()));
else
return JSValue(new String(*r1.getStringValue() + *r2.toString(cx).string));
}
else {
if (t2 == String_Type)
return JSValue(new String(*r1.toString(cx).string + *r2.getStringValue()));
else {
JSValue r1p = r1.toPrimitive(cx);
JSValue r2p = r2.toPrimitive(cx);
// gar-on-teed tagged values now
if (r1p.isString())
if (r2p.isString())
return JSValue(new String(*r1p.string + *r2p.string));
else
return JSValue(new String(*r1p.string + *r2p.toString(cx).string));
else
if (r2p.isString())
return JSValue(new String(*r1p.toString(cx).string + *r2p.string));
else {
JSValue num1(r1.toNumber(cx));
JSValue num2(r2.toNumber(cx));
return JSValue(num1.f64 + num2.f64);
}
}
}
}
static JSValue integerMinus(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
return JSValue(argv[0].getNumberValue() - argv[1].getNumberValue());
}
static JSValue numberMinus(Context *, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
return JSValue(argv[0].getNumberValue() - argv[1].getNumberValue());
}
static JSValue objectMinus(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
JSValue &r1 = argv[0];
JSValue &r2 = argv[1];
return JSValue(r1.toNumber(cx).f64 - r2.toNumber(cx).f64);
}
static JSValue integerMultiply(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
return JSValue(argv[0].getNumberValue() * argv[1].getNumberValue());
}
static JSValue objectMultiply(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
JSValue &r1 = argv[0];
JSValue &r2 = argv[1];
return JSValue(r1.toNumber(cx).f64 * r2.toNumber(cx).f64);
}
static JSValue integerDivide(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
float64 f1 = argv[0].getNumberValue();
float64 f2 = argv[1].getNumberValue();
float64 d = f1 / f2;
bool neg = (d < 0);
d = fd::floor(neg ? -d : d);
d = neg ? -d : d;
return JSValue(d);
}
static JSValue objectDivide(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
JSValue &r1 = argv[0];
JSValue &r2 = argv[1];
return JSValue(r1.toNumber(cx).f64 / r2.toNumber(cx).f64);
}
static JSValue integerRemainder(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
float64 f1 = argv[0].getNumberValue();
float64 f2 = argv[1].getNumberValue();
float64 d = fd::fmod(f1, f2);
bool neg = (d < 0);
d = fd::floor(neg ? -d : d);
d = neg ? -d : d;
return JSValue(d);
}
static JSValue objectRemainder(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
JSValue &r1 = argv[0];
JSValue &r2 = argv[1];
float64 f1 = r1.toNumber(cx).f64;
float64 f2 = r2.toNumber(cx).f64;
#ifdef XP_PC
/* Workaround MS fmod bug where 42 % (1/0) => NaN, not 42. */
if (JSDOUBLE_IS_FINITE(f1) && JSDOUBLE_IS_INFINITE(f2))
return JSValue(f1);
#endif
return JSValue(fd::fmod(f1, f2));
}
static JSValue integerShiftLeft(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
float64 f1 = argv[0].getNumberValue();
float64 f2 = argv[1].getNumberValue();
return JSValue((float64)( (int32)(f1) << ( (uint32)(f2) & 0x1F)) );
}
static JSValue objectShiftLeft(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
JSValue &r1 = argv[0];
JSValue &r2 = argv[1];
return JSValue((float64)( (int32)(r1.toInt32(cx).f64) << ( (uint32)(r2.toUInt32(cx).f64) & 0x1F)) );
}
static JSValue integerShiftRight(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
float64 f1 = argv[0].getNumberValue();
float64 f2 = argv[1].getNumberValue();
return JSValue((float64) ( (int32)(f1) >> ( (uint32)(f2) & 0x1F)) );
}
static JSValue objectShiftRight(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
JSValue &r1 = argv[0];
JSValue &r2 = argv[1];
return JSValue((float64) ( (int32)(r1.toInt32(cx).f64) >> ( (uint32)(r2.toUInt32(cx).f64) & 0x1F)) );
}
static JSValue integerUShiftRight(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
float64 f1 = argv[0].getNumberValue();
float64 f2 = argv[1].getNumberValue();
return JSValue((float64) ( (uint32)(f1) >> ( (uint32)(f2) & 0x1F)) );
}
static JSValue objectUShiftRight(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
JSValue &r1 = argv[0];
JSValue &r2 = argv[1];
return JSValue((float64) ( (uint32)(r1.toUInt32(cx).f64) >> ( (uint32)(r2.toUInt32(cx).f64) & 0x1F)) );
}
static JSValue integerBitAnd(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
float64 f1 = argv[0].getNumberValue();
float64 f2 = argv[1].getNumberValue();
return JSValue((float64)( (int32)(f1) & (int32)(f2) ));
}
static JSValue objectBitAnd(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
JSValue &r1 = argv[0];
JSValue &r2 = argv[1];
return JSValue((float64)( (int32)(r1.toInt32(cx).f64) & (int32)(r2.toInt32(cx).f64) ));
}
static JSValue integerBitXor(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
float64 f1 = argv[0].getNumberValue();
float64 f2 = argv[1].getNumberValue();
return JSValue((float64)( (int32)(f1) ^ (int32)(f2) ));
}
static JSValue objectBitXor(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
JSValue &r1 = argv[0];
JSValue &r2 = argv[1];
return JSValue((float64)( (int32)(r1.toInt32(cx).f64) ^ (int32)(r2.toInt32(cx).f64) ));
}
static JSValue integerBitOr(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
float64 f1 = argv[0].getNumberValue();
float64 f2 = argv[1].getNumberValue();
return JSValue((float64)( (int32)(f1) | (int32)(f2) ));
}
static JSValue objectBitOr(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
JSValue &r1 = argv[0];
JSValue &r2 = argv[1];
return JSValue((float64)( (int32)(r1.toInt32(cx).f64) | (int32)(r2.toInt32(cx).f64) ));
}
//
// implements r1 < r2, returning true or false or undefined
//
static JSValue objectCompare(Context *cx, JSValue &r1, JSValue &r2)
{
JSValue r1p = r1.toPrimitive(cx, JSValue::NumberHint);
JSValue r2p = r2.toPrimitive(cx, JSValue::NumberHint);
if (r1p.isString() && r2p.isString())
return JSValue(bool(r1p.string->compare(*r2p.string) < 0));
else {
JSValue r1n = r1p.toNumber(cx);
JSValue r2n = r2p.toNumber(cx);
if (r1n.isNaN() || r2n.isNaN())
return kUndefinedValue;
else
return JSValue(r1n.f64 < r2n.f64);
}
}
static JSValue objectLess(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
JSValue &r1 = argv[0];
JSValue &r2 = argv[1];
JSValue result = objectCompare(cx, r1, r2);
if (result.isUndefined())
return kFalseValue;
else
return result;
}
static JSValue objectLessEqual(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
JSValue &r1 = argv[0];
JSValue &r2 = argv[1];
JSValue result = objectCompare(cx, r2, r1);
if (result.isUndefined() || result.isTrue())
return kFalseValue;
else
return kTrueValue;
}
static JSValue compareEqual(Context *cx, JSValue r1, JSValue r2)
{
JSType *t1 = r1.getType();
JSType *t2 = r2.getType();
if (t1 != t2) {
if (r1.isNull() && r2.isUndefined())
return kTrueValue;
if (r1.isUndefined() && r2.isNull())
return kTrueValue;
if ((t1 == Number_Type) && (t2 == String_Type))
return compareEqual(cx, r1, r2.toNumber(cx));
if ((t1 == String_Type) && (t2 == Number_Type))
return compareEqual(cx, r1.toNumber(cx), r2.toString(cx));
if (t1 == Boolean_Type)
return compareEqual(cx, r1.toNumber(cx), r2);
if (t2 == Boolean_Type)
return compareEqual(cx, r1, r2.toNumber(cx));
if ( ((t1 == String_Type) || (t1 == Number_Type)) && r2.isObject() )
return compareEqual(cx, r1, r2.toPrimitive(cx));
if ( (r1.isObject()) && ((t2 == String_Type) || (t2 == Number_Type)) )
return compareEqual(cx, r1.toPrimitive(cx), r2);
return kFalseValue;
}
else {
if (r1.isUndefined())
return kTrueValue;
if (r1.isNull())
return kTrueValue;
if (r1.isObject() && r2.isObject()) // because new Boolean()->getType() == Boolean_Type
return JSValue(r1.object == r2.object);
if (r1.isType())
return JSValue(r1.type == r2.type);
if (r1.isFunction())
return JSValue(r1.function->isEqual(r2.function));
if (t1 == Number_Type) {
float64 f1 = r1.getNumberValue();
float64 f2 = r2.getNumberValue();
if (JSDOUBLE_IS_NaN(f1))
return kFalseValue;
if (JSDOUBLE_IS_NaN(f2))
return kFalseValue;
return JSValue(r1.f64 == r2.f64);
}
else {
if (t1 == String_Type)
return JSValue(bool(r1.getStringValue()->compare(*r2.getStringValue()) == 0));
if (t1 == Boolean_Type)
return JSValue(r1.getBoolValue() == r2.getBoolValue());
NOT_REACHED("unhandled type");
return kFalseValue;
}
}
}
static JSValue objectEqual(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
JSValue r1 = argv[0];
JSValue r2 = argv[1];
return compareEqual(cx, r1, r2);
}
static JSValue objectSpittingImage(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
{
JSValue r1 = argv[0];
JSValue r2 = argv[1];
JSType *t1 = r1.getType();
JSType *t2 = r2.getType();
if ((t1 != t2) || (r1.isObject() != r2.isObject())) {
return kFalseValue;
}
else {
if (r1.isUndefined())
return kTrueValue;
if (r1.isNull())
return kTrueValue;
if (r1.isObject() && r2.isObject()) // because new Boolean()->getType() == Boolean_Type
return JSValue(r1.object == r2.object);
if (r1.isType())
return JSValue(r1.type == r2.type);
if (r1.isFunction())
return JSValue(r1.function->isEqual(r2.function));
if (t1 == Number_Type) {
float64 f1 = r1.getNumberValue();
float64 f2 = r2.getNumberValue();
if (JSDOUBLE_IS_NaN(f1))
return kFalseValue;
if (JSDOUBLE_IS_NaN(f2))
return kFalseValue;
return JSValue(r1.f64 == r2.f64);
}
else {
if (t1 == String_Type)
return JSValue(bool(r1.getStringValue()->compare(*r2.getStringValue()) == 0));
if (r1.isBool() && r2.isBool())
return JSValue(r1.getBoolValue() == r2.getBoolValue());
return kFalseValue;
}
}
}
void Context::initOperators()
{
struct OpTableEntry {
Operator which;
JSType *op1;
JSType *op2;
JSFunction::NativeCode *imp;
JSType *resType;
} OpTable[] = {
{ Plus, Object_Type, Object_Type, objectPlus, Object_Type },
{ Plus, Number_Type, Number_Type, numberPlus, Number_Type },
{ Plus, Integer_Type, Integer_Type, integerPlus, Integer_Type },
{ Minus, Object_Type, Object_Type, objectMinus, Number_Type },
{ Minus, Number_Type, Number_Type, numberMinus, Number_Type },
{ Minus, Integer_Type, Integer_Type, integerMinus, Integer_Type },
{ ShiftLeft, Integer_Type, Integer_Type, integerShiftLeft, Integer_Type },
{ ShiftLeft, Object_Type, Object_Type, objectShiftLeft, Number_Type },
{ ShiftRight, Integer_Type, Integer_Type, integerShiftRight, Integer_Type },
{ ShiftRight, Object_Type, Object_Type, objectShiftRight, Number_Type },
{ UShiftRight, Integer_Type, Integer_Type, integerUShiftRight, Integer_Type },
{ UShiftRight, Object_Type, Object_Type, objectUShiftRight, Number_Type },
{ BitAnd, Integer_Type, Integer_Type, integerBitAnd, Integer_Type },
{ BitAnd, Object_Type, Object_Type, objectBitAnd, Number_Type },
{ BitXor, Integer_Type, Integer_Type, integerBitXor, Integer_Type },
{ BitXor, Object_Type, Object_Type, objectBitXor, Number_Type },
{ BitOr, Integer_Type, Integer_Type, integerBitOr, Integer_Type },
{ BitOr, Object_Type, Object_Type, objectBitOr, Number_Type },
{ Multiply, Integer_Type, Integer_Type, integerMultiply, Integer_Type },
{ Multiply, Object_Type, Object_Type, objectMultiply, Number_Type },
{ Divide, Integer_Type, Integer_Type, integerDivide, Integer_Type },
{ Divide, Object_Type, Object_Type, objectDivide, Number_Type },
{ Remainder, Integer_Type, Integer_Type, integerRemainder, Integer_Type },
{ Remainder, Object_Type, Object_Type, objectRemainder, Number_Type },
{ Less, Object_Type, Object_Type, objectLess, Boolean_Type },
{ LessEqual, Object_Type, Object_Type, objectLessEqual, Boolean_Type },
{ Equal, Object_Type, Object_Type, objectEqual, Boolean_Type },
{ SpittingImage, Object_Type, Object_Type, objectSpittingImage, Boolean_Type }
};
for (uint32 i = 0; i < sizeof(OpTable) / sizeof(OpTableEntry); i++) {
JSFunction *f = new JSFunction(this, OpTable[i].imp, OpTable[i].resType);
OperatorDefinition *op = new OperatorDefinition(OpTable[i].op1, OpTable[i].op2, f);
mOperatorTable[OpTable[i].which].push_back(op);
}
}
JSValue JSValue::valueToObject(Context *cx, const JSValue& value)
{
switch (value.tag) {
case f64_tag:
{
JSObject *obj = Number_Type->newInstance(cx);
JSFunction *defCon = Number_Type->getDefaultConstructor();
JSValue argv[1];
JSValue thisValue = JSValue(obj);
argv[0] = value;
if (defCon->isNative()) {
(defCon->getNativeCode())(cx, thisValue, &argv[0], 1);
}
else {
ASSERT(false); // need to throw a hot potato back to
// ye interpreter loop
}
return thisValue;
}
case boolean_tag:
{
JSObject *obj = Boolean_Type->newInstance(cx);
JSFunction *defCon = Boolean_Type->getDefaultConstructor();
JSValue argv[1];
JSValue thisValue = JSValue(obj);
argv[0] = value;
if (defCon->isNative()) {
(defCon->getNativeCode())(cx, thisValue, &argv[0], 1);
}
else {
ASSERT(false);
}
return thisValue;
}
case string_tag:
{
JSObject *obj = String_Type->newInstance(cx);
JSFunction *defCon = String_Type->getDefaultConstructor();
JSValue argv[1];
JSValue thisValue = JSValue(obj);
argv[0] = value;
if (defCon->isNative()) {
(defCon->getNativeCode())(cx, thisValue, &argv[0], 1);
}
else {
ASSERT(false);
}
return thisValue;
}
case object_tag:
case function_tag:
return value;
case null_tag:
case undefined_tag:
cx->reportError(Exception::typeError, "ToObject");
default:
NOT_REACHED("Bad tag");
return kUndefinedValue;
}
}
float64 stringToNumber(const String *string)
{
const char16 *numEnd;
const char16 *sBegin = string->begin();
if (sBegin)
if ((sBegin[0] == '0') && ((sBegin[1] & ~0x20) == 'X'))
return stringToInteger(sBegin, string->end(), numEnd, 16);
else {
float64 result = stringToDouble(sBegin, string->end(), numEnd);
if (numEnd != string->end()) {
const char16 *sEnd = string->end();
while (numEnd != sEnd) {
if (!isSpace(*numEnd++))
return nan;
}
return result;
}
else
return result;
}
else
return 0.0;
}
JSValue JSValue::valueToNumber(Context *cx, const JSValue& value)
{
switch (value.tag) {
case f64_tag:
return value;
case string_tag:
return JSValue(stringToNumber(value.string));
case object_tag:
case function_tag:
return value.toPrimitive(cx, NumberHint).toNumber(cx);
case boolean_tag:
return JSValue((value.boolean) ? 1.0 : 0.0);
case undefined_tag:
return kNaNValue;
case null_tag:
return kPositiveZero;
default:
NOT_REACHED("Bad tag");
return kUndefinedValue;
}
}
const String *numberToString(float64 number)
{
char buf[dtosStandardBufferSize];
const char *chrp = doubleToStr(buf, dtosStandardBufferSize, number, dtosStandard, 0);
return new JavaScript::String(widenCString(chrp));
}
JSValue JSValue::valueToString(Context *cx, const JSValue& value)
{
const String *strp = NULL;
JSObject *obj = NULL;
switch (value.tag) {
case f64_tag:
return JSValue(numberToString(value.f64));
case object_tag:
obj = value.object;
break;
case function_tag:
obj = value.function;
break;
case string_tag:
return value;
case boolean_tag:
strp = (value.boolean) ? &cx->True_StringAtom : &cx->False_StringAtom;
break;
case type_tag:
strp = value.type->mClassName;
break;
case undefined_tag:
strp = &cx->Undefined_StringAtom;
break;
case null_tag:
strp = &cx->Null_StringAtom;
break;
default:
NOT_REACHED("Bad tag");
}
if (obj) {
JSFunction *target = NULL;
PropertyIterator i;
if (obj->hasProperty(cx->ToString_StringAtom, CURRENT_ATTR, Read, &i)) {
JSValue v = obj->getPropertyValue(i);
if (v.isFunction())
target = v.function;
}
if (target == NULL) {
if (obj->hasProperty(cx->ValueOf_StringAtom, CURRENT_ATTR, Read, &i)) {
JSValue v = obj->getPropertyValue(i);
if (v.isFunction())
target = v.function;
}
}
if (target)
return cx->invokeFunction(target, value, NULL, 0);
cx->reportError(Exception::runtimeError, "toString"); // XXX
return kUndefinedValue; // keep compilers happy
}
else
return JSValue(strp);
}
JSValue JSValue::toPrimitive(Context *cx, Hint hint) const
{
JSObject *obj;
switch (tag) {
case f64_tag:
case string_tag:
case boolean_tag:
case undefined_tag:
return *this;
case object_tag:
obj = object;
if ((hint == NoHint) && (obj->getType() == Date_Type))
hint = StringHint;
break;
case function_tag:
obj = function;
break;
default:
NOT_REACHED("Bad tag");
return kUndefinedValue;
}
// The following is [[DefaultValue]]
//
ASSERT(obj);
JSFunction *target = NULL;
JSValue result;
PropertyIterator i;
StringAtom *first = &cx->ValueOf_StringAtom;
StringAtom *second = &cx->ToString_StringAtom;
if (hint == StringHint) {
first = &cx->ToString_StringAtom;
second = &cx->ValueOf_StringAtom;
}
if (obj->hasProperty(*first, CURRENT_ATTR, Read, &i)) {
JSValue v = obj->getPropertyValue(i);
if (v.isFunction()) {
target = v.function;
if (target) {
result = cx->invokeFunction(target, *this, NULL, 0);
if (result.isPrimitive())
return result;
}
}
}
if (obj->hasProperty(*second, CURRENT_ATTR, Read, &i)) {
JSValue v = obj->getPropertyValue(i);
if (v.isFunction()) {
target = v.function;
if (target) {
result = cx->invokeFunction(target, *this, NULL, 0);
if (result.isPrimitive())
return result;
}
}
}
cx->reportError(Exception::runtimeError, "toPrimitive"); // XXX
return kUndefinedValue;
}
int JSValue::operator==(const JSValue& value) const
{
if (this->tag == value.tag) {
# define CASE(T) case T##_tag: return (this->T == value.T)
switch (tag) {
CASE(f64);
CASE(object);
CASE(boolean);
#undef CASE
// question: are all undefined values equal to one another?
case undefined_tag: return 1;
default:
NOT_REACHED("Broken compiler?");
}
}
return 0;
}
float64 JSValue::float64ToInteger(float64 d)
{
if (JSDOUBLE_IS_NaN(d))
return 0.0;
else
return (d >= 0.0) ? fd::floor(d) : -fd::floor(-d);
}
JSValue JSValue::valueToInteger(Context *cx, const JSValue& value)
{
JSValue v = valueToNumber(cx, value);
v.f64 = float64ToInteger(v.f64);
return v;
}
int32 JSValue::float64ToInt32(float64 d)
{
d = fd::fmod(d, two32);
d = (d >= 0) ? d : d + two32;
if (d >= two31)
return (int32)(d - two32);
else
return (int32)(d);
}
uint32 JSValue::float64ToUInt32(float64 d)
{
bool neg = (d < 0);
d = fd::floor(neg ? -d : d);
d = neg ? -d : d;
d = fd::fmod(d, two32);
d = (d >= 0) ? d : d + two32;
return (uint32)d;
}
JSValue JSValue::valueToInt32(Context *, const JSValue& value)
{
float64 d;
switch (value.tag) {
case f64_tag:
d = value.f64;
break;
case string_tag:
{
const char16 *numEnd;
d = stringToDouble(value.string->begin(), value.string->end(), numEnd);
}
break;
case boolean_tag:
return JSValue((float64)((value.boolean) ? 1 : 0));
case object_tag:
case undefined_tag:
// toNumber(toPrimitive(hint Number))
return kUndefinedValue;
default:
NOT_REACHED("Bad tag");
return kUndefinedValue;
}
if ((d == 0.0) || !JSDOUBLE_IS_FINITE(d) )
return JSValue((float64)0);
return JSValue((float64)float64ToInt32(d));
}
JSValue JSValue::valueToUInt32(Context *, const JSValue& value)
{
float64 d;
switch (value.tag) {
case f64_tag:
d = value.f64;
break;
case string_tag:
{
const char16 *numEnd;
d = stringToDouble(value.string->begin(), value.string->end(), numEnd);
}
break;
case boolean_tag:
return JSValue((float64)((value.boolean) ? 1 : 0));
case object_tag:
case undefined_tag:
// toNumber(toPrimitive(hint Number))
return kUndefinedValue;
default:
NOT_REACHED("Bad tag");
return kUndefinedValue;
}
if ((d == 0.0) || !JSDOUBLE_IS_FINITE(d))
return JSValue((float64)0);
return JSValue((float64)float64ToUInt32(d));
}
JSValue JSValue::valueToUInt16(Context *, const JSValue& value)
{
float64 d;
switch (value.tag) {
case f64_tag:
d = value.f64;
break;
case string_tag:
{
const char16 *numEnd;
d = stringToDouble(value.string->begin(), value.string->end(), numEnd);
}
break;
case boolean_tag:
return JSValue((float64)((value.boolean) ? 1 : 0));
case object_tag:
case undefined_tag:
// toNumber(toPrimitive(hint Number))
return kUndefinedValue;
default:
NOT_REACHED("Bad tag");
return kUndefinedValue;
}
if ((d == 0.0) || !JSDOUBLE_IS_FINITE(d))
return JSValue((float64)0);
bool neg = (d < 0);
d = fd::floor(neg ? -d : d);
d = neg ? -d : d;
d = fd::fmod(d, two16);
d = (d >= 0) ? d : d + two16;
return JSValue((float64)d);
}
JSValue JSValue::valueToBoolean(Context * /*cx*/, const JSValue& value)
{
switch (value.tag) {
case f64_tag:
if (JSDOUBLE_IS_NaN(value.f64))
return kFalseValue;
if (value.f64 == 0.0)
return kFalseValue;
return kTrueValue;
case string_tag:
return JSValue(value.string->length() != 0);
case boolean_tag:
return value;
case object_tag:
case function_tag:
return kTrueValue;
break;
case null_tag:
case undefined_tag:
return kFalseValue;
default:
NOT_REACHED("Bad tag");
return kUndefinedValue;
}
}
// See if 'v' can be represented as a 't' - works for
// the built-ins by falling back to the ECMA3 behaviour
JSValue Context::mapValueToType(JSValue v, JSType *t)
{
// user conversions?
if (v.getType() == t)
return v;
if (t == Number_Type) {
return v.toNumber(this);
}
else
if (t == Integer_Type) {
float64 d = v.toNumber(this).f64;
bool neg = (d < 0);
d = fd::floor(neg ? -d : d);
d = neg ? -d : d;
return JSValue(d);
}
else
if (t == String_Type) {
return v.toString(this);
}
else
if (t == Boolean_Type) {
return v.toBoolean(this);
}
if (v.isUndefined())
return v;
if (v.getType()->derivesFrom(t))
return v;
reportError(Exception::typeError, "Invalid type cast");
return kUndefinedValue;
}
JSType *JSValue::getType() const {
switch (tag) {
case f64_tag: return Number_Type;
case boolean_tag: return Boolean_Type;
case string_tag: return (JSType *)String_Type;
case object_tag: return object->getType();
case undefined_tag: return Void_Type;
case null_tag: return Null_Type;
case function_tag: return Function_Type;
case type_tag: return Type_Type;
default:
NOT_REACHED("bad type");
return NULL;
}
}
}
}