Files
Mozilla/mozilla/js2/src/js2execution.cpp
rogerl%netscape.com c0404bd5e2 Switched to SpiderMOnkey style jsval tagged pointer scheme.
git-svn-id: svn://10.0.0.236/trunk@117240 18797224-902f-48f8-a5cc-f745e15eee43
2002-03-22 22:58:24 +00:00

2786 lines
111 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* -*- 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 or
* 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"
namespace JavaScript {
namespace JS2Runtime {
inline char narrow(char16 ch) { return char(ch); }
js2val Context::readEvalString(const String &str, const String& fileName, ScopeChain *scopeChain, const js2val thisValue)
{
js2val result = kUndefinedValue;
NamespaceList *useOnceNamespace = NULL;
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;
}
js2val Context::readEvalFile(const String& fileName)
{
String buffer;
int ch;
js2val 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::newObject(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 = mBinaryOperatorTable[op].begin(),
end = mBinaryOperatorTable[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;
}
// XXX how to complain if there is more than one best fit?
JSFunction *target = (*candidate)->mImp;
js2val newThis = kNullValue;
if (target->isNative()) {
js2val result = target->invokeNativeCode(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, mNamespaceList));
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).
js2val Context::invokeFunction(JSFunction *target, const js2val thisValue, js2val *argv, uint32 argc)
{
if (target->isNative())
return target->invokeNativeCode(this, thisValue, argv, argc);
else
return interpret(target->getByteCode(), 0, target->getScopeChain(), thisValue, argv, argc);
}
js2val Context::interpret(JS2Runtime::ByteCodeModule *bcm, int offset, ScopeChain *scopeChain, const js2val thisValue, js2val *argv, uint32 /*argc*/)
{
Activation *prev = new Activation(mLocals, mStack, mStackTop, mScopeChain,
mArgumentBase, mThis, NULL, mCurModule, mNamespaceList); // 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 (JSValue::isObject(mThis))
// 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 js2val[mCurModule->mLocalsCount];
mStack = new js2val[mCurModule->mStackDepth];
mStackMax = mCurModule->mStackDepth;
mStackTop = 0;
js2val 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 (JSValue::isObject(mThis))
// mScopeChain->popScope();
js2val x;
if (jsx.hasKind(Exception::userException))
x = popValue();
delete[] mStack;
delete[] mLocals;
if (scopeChain == NULL)
delete mScopeChain;
mNamespaceList = prev->mNamespaceList;
mCurModule = prev->mModule;
mStack = prev->mStack;
mStackTop = 0; // we're processing an exception, no need to preserve the stack
if (mCurModule) {
mStackMax = mCurModule->mStackDepth;
if (jsx.hasKind(Exception::userException))
pushValue(x);
}
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 (JSValue::isObject(mThis))
// mScopeChain->popScope();
delete[] mStack;
delete[] mLocals;
if (scopeChain == NULL)
delete mScopeChain;
mNamespaceList = prev->mNamespaceList;
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. argCount is the number that were pushed by user code
js2val *Context::buildArgumentBlock(JSFunction *target, uint32 &argCount)
{
js2val *argBase;
uint32 maxExpectedParameterCount = target->getRequiredParameterCount() + target->getOptionalParameterCount() + target->getNamedParameterCount();
if (target->isChecked()) {
if (argCount < target->getRequiredParameterCount())
reportError(Exception::referenceError, "Insufficient quantity of arguments");
if ((argCount > maxExpectedParameterCount) && !target->hasRestParameter())
reportError(Exception::referenceError, "Oversufficient quantity of arguments");
}
uint32 i;
uint32 argBlockSize = max(argCount, maxExpectedParameterCount) + (target->hasRestParameter() ? 1 : 0);
// room for all required,optional & named arguments
// plus the rest parameter if it exists.
argBase = new js2val[argBlockSize];
/*
* If the first parameter is required and no positional argument has been supplied for it, then raise an error unless the function
* is unchecked, in which case let undefined be the first parameters value.
*
* If the first parameter is optional and there is a positional argument remaining, use the value of the positional argument
* and raise an error if there is also a named argument with a matching argument name. If there are no remaining positional
* arguments, then if there is a named argument with a matching argument name, use the value of that named argument. Otherwise,
* evaluate the first parameters AssignmentExpression and let it be the first parameters value.
*
* Implicitly coerce the argument (or default) value to type t and bind the parameters Identifier to the result.
*/
uint32 inArgIndex = 0;
uint32 argStart = stackSize() - argCount;
bool *argUsed = new bool[argCount];
for (i = 0; i < argCount; i++)
argUsed[i] = false;
uint32 restParameterIndex;
uint32 pCount = maxExpectedParameterCount + (target->hasRestParameter() ? 1 : 0);
for (uint32 pIndex = 0; pIndex < pCount; pIndex++) {
bool needDefault = true;
if (target->parameterIsRequired(pIndex)) {
if (inArgIndex < argCount) {
js2val v = getValue(inArgIndex + argStart);
bool isPositionalArg = !JSValue::isNamedArg(v);
// if the next argument is named, then we've run out of positional args
if (!isPositionalArg) {
if (!target->isChecked())
needDefault = false; // the undefined value is already assigned in the arg block
}
else {
argBase[pIndex] = getValue(inArgIndex + argStart);
argUsed[inArgIndex++] = true;
needDefault = false;
}
}
if (needDefault && target->isChecked())
reportError(Exception::referenceError, "missing required argument");
}
else {
if (target->parameterIsOptional(pIndex)) {
bool tookPositionalArg = false;
if (inArgIndex < argCount) {
js2val v = getValue(inArgIndex + argStart);
if (!JSValue::isNamedArg(v)) {
argBase[pIndex] = v;
argUsed[inArgIndex++] = true;
needDefault = false;
}
}
}
else {
if (target->parameterIsNamed(pIndex)) {
const String *parameterName = target->getParameterName(pIndex);
for (uint32 i = inArgIndex; i < argCount; i++) {
if (!argUsed[i]) {
js2val v = getValue(i + argStart);
if (JSValue::isNamedArg(v)) {
if (JSValue::namedArg(v)->mName == parameterName) {
argBase[pIndex] = JSValue::namedArg(v)->mValue;
argUsed[i] = true;
needDefault = false;
break;
}
}
}
}
}
else
restParameterIndex = pIndex;
}
}
if (needDefault)
if (target->parameterHasInitializer(pIndex))
argBase[pIndex] = target->runParameterInitializer(this, pIndex, mThis, argBase, maxExpectedParameterCount);
}
// now find a place for any left-overs:
/*
* If there is a RestParameter with a type that does not allow the dynamic addition of named properties and one or more of the
* remaining arguments is named, raise an error.
*
* If there is a RestParameter with an Identifier, bind that Identifier to an array of the remaining positional and/or named
* arguments. The remaining positional arguments are assigned indices starting from 0.
*
* If there is no RestParameter and any arguments remain, raise an error unless the function is unchecked.
*/
js2val restArgument;
bool haveRestArg = false;
if (target->hasRestParameter() && target->getParameterName(restParameterIndex)) {
restArgument = target->getParameterType(restParameterIndex)->newInstance(this);
argBase[restParameterIndex] = restArgument;
haveRestArg = true;
}
inArgIndex = 0; // re-number the non-named arguments that end up in the rest arg
for (i = 0; i < argCount; i++) {
if (!argUsed[i]) {
js2val v = getValue(i + argStart);
bool isNamedArg = JSValue::isNamedArg(v);
if (isNamedArg) {
NamedArgument *arg = JSValue::namedArg(v);
if (haveRestArg)
JSValue::object(restArgument)->setProperty(this, *arg->mName, (NamespaceList *)(NULL), arg->mValue);
else
reportError(Exception::referenceError, "Unused named argument, no rest argument");
}
else {
if (haveRestArg) {
const String *id = numberToString(inArgIndex++);
JSValue::object(restArgument)->setProperty(this, *id, (NamespaceList *)(NULL), v);
}
else {
if (target->isChecked())
reportError(Exception::referenceError, "Extra argument, no rest argument");
else {
if (isNamedArg)
argBase[i] = JSValue::namedArg(v)->mValue;
else
argBase[i] = v;
}
}
}
}
}
argCount = argBlockSize;
return argBase;
}
js2val Context::interpret(uint8 *pc, uint8 *endPC)
{
bool useOnceNamespace = false;
js2val 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:
{
js2val v = topValue();
pushValue(v);
}
break;
case DupInsertOp: // XXX something more efficient than pop/push?
{
js2val v1 = popValue();
js2val v2 = popValue();
pushValue(v1);
pushValue(v2);
pushValue(v1);
}
break;
case DupNOp:
{
uint16 count = *((uint16 *)pc);
pc += sizeof(uint16);
js2val *vp = getBase(stackSize() - count);
while (count--)
pushValue(*vp++);
}
break;
// <N things> <object2> --> <object2> <N things> <object2>
case DupInsertNOp:
{
js2val v2 = topValue();
uint16 count = *((uint16 *)pc);
pc += sizeof(uint16);
insertValue(v2, mStackTop - (count + 1));
}
break;
case SwapOp: // XXX something more efficient than pop/push?
{
js2val v1 = popValue();
js2val v2 = popValue();
pushValue(v1);
pushValue(v2);
}
break;
case LogicalXorOp:
{
js2val v2 = popValue();
ASSERT(JSValue::isBool(v2));
js2val v1 = popValue();
ASSERT(JSValue::isBool(v1));
if (JSValue::boolean(v1)) {
if (JSValue::boolean(v2)) {
popValue();
popValue();
pushValue(kFalseValue);
}
else
popValue();
}
else {
if (JSValue::boolean(v1)) {
popValue();
popValue();
pushValue(kFalseValue);
}
else {
js2val t = topValue();
popValue();
popValue();
pushValue(t);
}
}
}
break;
case LogicalNotOp:
{
js2val v = popValue();
ASSERT(JSValue::isBool(v));
if (JSValue::isTrue(v))
pushValue(kFalseValue);
else
pushValue(kTrueValue);
}
break;
case JumpOp:
{
uint32 offset = *((uint32 *)pc);
pc += offset;
}
break;
case ToBooleanOp:
{
js2val v = popValue();
pushValue(JSValue::toBoolean(this, v));
}
break;
case JumpFalseOp:
{
js2val v = popValue();
ASSERT(JSValue::isBool(v));
if (!JSValue::boolean(v)) {
uint32 offset = *((uint32 *)pc);
pc += offset;
}
else
pc += sizeof(uint32);
}
break;
case JumpTrueOp:
{
js2val v = popValue();
ASSERT(JSValue::isBool(v));
if (JSValue::boolean(v)) {
uint32 offset = *((uint32 *)pc);
pc += offset;
}
else
pc += sizeof(uint32);
}
break;
case NamedArgOp:
{
js2val name = popValue();
if (!JSValue::isString(name))
reportError(Exception::typeError, "String needed for argument name");
js2val value = popValue();
pushValue(JSValue::newNamedArg(new NamedArgument(value, JSValue::string(name))));
}
break;
case InvokeOp:
{
uint32 argCount = *((uint32 *)pc);
uint32 cleanUp = argCount;
pc += sizeof(uint32);
CallFlag callFlags = (CallFlag)(*pc++);
mPC = pc;
js2val *targetValue = getBase(stackSize() - (argCount + 1));
JSFunction *target;
js2val 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 (!JSValue::isFunction(*targetValue)) {
if (JSValue::isType(*targetValue)) {
// 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 = JSValue::type(*targetValue)->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 = JSValue::type(*targetValue)->getTypeCastFunction();
if (target == NULL) {
if ((argCount > 1) || ((callFlags & ThisFlags) != NoThis))
reportError(Exception::referenceError, "Type cast can only take one argument");
js2val v;
if (argCount > 0)
v = popValue();
popValue(); // don't need the target anymore
pushValue(mapValueToType(v, JSValue::type(*targetValue)));
mThis = oldThis;
break; // all done
}
}
}
else
reportError(Exception::referenceError, "Not a function");
}
else {
target = JSValue::function(*targetValue);
// 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() && JSValue::isNull(mThis))
mThis = JSValue::newObject(getGlobalObject());
if (!target->isNative()) {
js2val *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, mNamespaceList));
mScopeChain = target->getScopeChain();
mScopeChain->addScope(target->getParameterBarrel());
mScopeChain->addScope(target->getActivation());
if (!target->isChecked()) {
js2val args = Array_Type->newInstance(this);
for (uint32 i = 0; i < argCount; i++)
JSValue::instance(args)->setProperty(this, *numberToString(i), NULL, argBase[i]);
target->getActivation()->setProperty(this, Arguments_StringAtom, NULL, args);
}
mCurModule = target->getByteCode();
pc = mCurModule->mCodeBase;
endPC = mCurModule->mCodeBase + mCurModule->mLength;
mArgumentBase = argBase;
mLocals = new js2val[mCurModule->mLocalsCount];
mStack = new js2val[mCurModule->mStackDepth];
mStackMax = mCurModule->mStackDepth;
mStackTop = 0;
}
else {
js2val *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, mNamespaceList));
js2val result = target->invokeNativeCode(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();
mNamespaceList = prev->mNamespaceList;
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:
{
js2val 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();
mNamespaceList = prev->mNamespaceList;
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::newType(t));
}
break;
case LoadFunctionOp:
{
JSFunction *f = *((JSFunction **)pc);
pc += sizeof(JSFunction *);
pushValue(JSValue::newFunction(f));
}
break;
case LoadConstantStringOp:
{
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
pushValue(JSValue::newString(mCurModule->getString(index)));
}
break;
case LoadConstantNumberOp:
{
pushValue(JSValue::newNumber(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 LoadConstantRegExpOp:
{
JSInstance *t = *((JSInstance **)pc);
pc += sizeof(JSInstance *);
pushValue(JSValue::newInstance(t));
}
break;
case DeleteNameOp:
{
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
const StringAtom &name = *mCurModule->getString(index);
if (mScopeChain->deleteName(this, name, mNamespaceList))
pushValue(kTrueValue);
else
pushValue(kFalseValue);
}
break;
case DeleteOp:
{
js2val base = popValue();
JSObject *obj = JSValue::toObjectValue(this, base);
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
const StringAtom &name = *mCurModule->getString(index);
PropertyIterator it;
if (obj->hasOwnProperty(this, name, mNamespaceList, Read, &it))
if (obj->deleteProperty(this, name, mNamespaceList))
pushValue(kTrueValue);
else
pushValue(kFalseValue);
else
pushValue(kTrueValue);
}
break;
case TypeOfOp:
{
js2val v = popValue();
if (JSValue::isUndefined(v))
pushValue(JSValue::newString(&Undefined_StringAtom));
else
if (JSValue::isNull(v))
pushValue(JSValue::newString(&Object_StringAtom));
else
if (JSValue::isBool(v))
pushValue(JSValue::newString(&Boolean_StringAtom));
else
if (JSValue::isNumber(v))
pushValue(JSValue::newString(&Number_StringAtom));
else
if (JSValue::isString(v))
pushValue(JSValue::newString(&String_StringAtom));
else
if (JSValue::isFunction(v))
pushValue(JSValue::newString(&Function_StringAtom));
else
pushValue(JSValue::newString(&Object_StringAtom));
}
break;
case AsOp:
{
js2val t = popValue();
js2val v = popValue();
if (JSValue::isType(t)) {
if (JSValue::isInstance(v)
&& (JSValue::instance(v)->getType() == JSValue::type(t)))
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:
{
js2val t = popValue();
js2val v = popValue();
if (JSValue::isType(t)) {
if (JSValue::isNull(v))
if (JSValue::type(t) == Object_Type)
pushValue(kTrueValue);
else
pushValue(kFalseValue);
else
if (JSValue::isInstance(v)
&& ((JSValue::instance(v)->getType() == JSValue::type(t))
|| (JSValue::instance(v)->getType()->derivesFrom(JSValue::type(t)))))
pushValue(kTrueValue);
else {
if (JSValue::getType(v) == JSValue::type(t))
pushValue(kTrueValue);
else
pushValue(kFalseValue);
}
}
else { // behave like instanceof
if (JSValue::isObject(t) && JSValue::isFunction(t)) {
// XXX prove that t->function["prototype"] is on t.object->mPrototype chain
pushValue(kTrueValue);
}
else
reportError(Exception::typeError, "InstanceOf needs object");
}
}
break;
case InstanceOfOp:
{
js2val t = popValue();
js2val v = popValue();
if (JSValue::isFunction(t)) {
JSFunction *obj = JSValue::function(t);
PropertyIterator i;
JSFunction *target = NULL;
if (obj->hasProperty(this, HasInstance_StringAtom, mNamespaceList, Read, &i)) {
js2val hi = obj->getPropertyValue(i);
if (JSValue::isFunction(hi))
target = JSValue::function(hi);
}
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 (JSValue::isType(t)) {
if ((JSValue::getType(v) == JSValue::type(t))
|| JSValue::getType(v)->derivesFrom(JSValue::type(t)))
pushValue(kTrueValue);
else
pushValue(kFalseValue);
}
else
reportError(Exception::typeError, "InstanceOf needs function object");
}
}
break;
case GetNameOp:
{
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
const StringAtom &name = *mCurModule->getString(index);
JSObject *parent = mScopeChain->getNameValue(this, name, mNamespaceList);
js2val result = topValue();
if (JSValue::isFunction(result) && !JSValue::function(result)->hasBoundThis()) {
popValue();
if (JSValue::function(result)->isConstructor())
// A constructor has to be called with a NULL 'this' in order to prompt it
// to construct the instance object.
pushValue(JSValue::newFunction((JSFunction *)(new JSBoundFunction(this, JSValue::function(result), NULL))));
else
pushValue(JSValue::newFunction((JSFunction *)(new JSBoundFunction(this, JSValue::function(result), parent))));
}
if (useOnceNamespace) {
NamespaceList *t = mNamespaceList->mNext;
delete mNamespaceList;
useOnceNamespace = false;
mNamespaceList = t;
}
}
break;
case SetNameOp:
{
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
const StringAtom &name = *mCurModule->getString(index);
js2val v = topValue();
if (JSValue::isFunction(v) && JSValue::function(v)->hasBoundThis() && !JSValue::function(v)->isMethod()) {
popValue();
pushValue(JSValue::newFunction(JSValue::function(v)->getFunction()));
}
mScopeChain->setNameValue(this, name, mNamespaceList);
if (useOnceNamespace) {
NamespaceList *t = mNamespaceList->mNext;
delete mNamespaceList;
useOnceNamespace = false;
mNamespaceList = t;
}
}
break;
case GetTypeOfNameOp:
{
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
const StringAtom &name = *mCurModule->getString(index);
if (mScopeChain->hasNameValue(this, name, mNamespaceList)) {
mScopeChain->getNameValue(this, name, mNamespaceList);
}
else
pushValue(kUndefinedValue);
if (useOnceNamespace) {
NamespaceList *t = mNamespaceList->mNext;
delete mNamespaceList;
useOnceNamespace = false;
mNamespaceList = t;
}
}
break;
case GetElementOp:
{
uint32 dimCount = *((uint16 *)pc);
pc += sizeof(uint16);
mPC = pc;
js2val *base = getBase(stackSize() - (dimCount + 1));
// Use the type of the base to dispatch on...
JSObject *obj = JSValue::toObjectValue(this, *base);
JSFunction *target = getUnaryOperator(JSValue::getType(*base), Index);
if (target) {
js2val result;
if (target->isNative()) {
js2val *argBase = new js2val[dimCount + 1];
for (uint32 i = 0; i < (dimCount + 1); i++)
argBase[i] = base[i];
resizeStack(stackSize() - (dimCount + 1));
result = target->invokeNativeCode(this, argBase[0], argBase, dimCount + 1);
delete[] argBase;
}
else {
uint32 argCount = dimCount + 1;
js2val *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");
js2val index = popValue();
popValue(); // discard base
const String *name = JSValue::string(JSValue::toString(this, index));
obj->getProperty(this, *name, mNamespaceList);
}
// if the result is a method of some kind, bind
// the base object to it
js2val result = topValue();
if (JSValue::isFunction(result)) {
popValue();
if (JSValue::function(result)->isConstructor())
// A constructor has to be called with a NULL 'this' in order to prompt it
// to construct the instance object.
pushValue(JSValue::newFunction((JSFunction *)(new JSBoundFunction(this, JSValue::function(result), NULL))));
else
pushValue(JSValue::newFunction((JSFunction *)(new JSBoundFunction(this, JSValue::function(result), obj))));
}
}
break;
case SetElementOp:
{
uint32 dimCount = *((uint16 *)pc);
pc += sizeof(uint16);
mPC = pc;
js2val *base = getBase(stackSize() - (dimCount + 2)); // +1 for assigned value
// Use the type of the base to dispatch on...
JSObject *obj = JSValue::toObjectValue(this, *base);
JSFunction *target = getUnaryOperator(JSValue::getType(*base), IndexEqual);
if (target) {
js2val v = popValue(); // need to have this sitting right above the base value
if (JSValue::isFunction(v) && JSValue::function(v)->hasBoundThis() && !JSValue::function(v)->isMethod()) {
v = JSValue::newFunction(JSValue::function(v)->getFunction());
}
insertValue(v, mStackTop - dimCount);
js2val result;
if (target->isNative()) {
js2val *argBase = new js2val[dimCount + 2];
for (uint32 i = 0; i < (dimCount + 2); i++)
argBase[i] = base[i];
resizeStack(stackSize() - (dimCount + 2));
result = target->invokeNativeCode(this, *base, base, (dimCount + 2));
delete[] argBase;
}
else {
uint32 argCount = dimCount + 2;
js2val *argBase = buildArgumentBlock(target, argCount);
resizeStack(stackSize() - (dimCount + 2));
try {
result = interpret(target->getByteCode(), 0, target->getScopeChain(), *base, 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");
js2val v = popValue();
if (JSValue::isFunction(v) && JSValue::function(v)->hasBoundThis() && !JSValue::function(v)->isMethod()) {
v = JSValue::newFunction(JSValue::function(v)->getFunction());
}
js2val index = popValue();
popValue(); // discard base
const String *name = JSValue::string(JSValue::toString(this, index));
obj->setProperty(this, *name, mNamespaceList, v);
pushValue(v);
}
}
break;
case DeleteElementOp:
{
uint32 dimCount = *((uint16 *)pc);
pc += sizeof(uint16);
mPC = pc;
js2val *base = getBase(stackSize() - (dimCount + 1));
// Use the type of the base to dispatch on...
JSObject *obj = JSValue::toObjectValue(this, *base);
JSFunction *target = getUnaryOperator(JSValue::getType(*base), DeleteIndex);
if (target) {
js2val result;
if (target->isNative()) {
js2val *argBase = new js2val[dimCount + 1];
for (uint32 i = 0; i < (dimCount + 1); i++)
argBase[i] = base[i];
resizeStack(stackSize() - (dimCount + 1));
result = target->invokeNativeCode(this, argBase[0], argBase, dimCount + 1);
delete[] argBase;
}
else {
uint32 argCount = dimCount + 1;
js2val *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");
js2val index = popValue();
popValue(); // discard base
const String *name = JSValue::string(JSValue::toString(this, index));
PropertyIterator it;
if (obj->hasOwnProperty(this, *name, mNamespaceList, Read, &it))
obj->deleteProperty(this, *name, mNamespaceList);
pushValue(kTrueValue);
}
}
break;
case GetPropertyOp:
{
js2val base = popValue();
JSObject *obj = JSValue::toObjectValue(this, base);
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
mPC = pc;
const StringAtom &name = *mCurModule->getString(index);
obj->getProperty(this, name, mNamespaceList);
// if the result is a method of some kind, bind
// the base object to it
js2val result = topValue();
if (JSValue::isFunction(result)) {
popValue();
if (JSValue::function(result)->isConstructor())
// A constructor has to be called with a NULL 'this' in order to prompt it
// to construct the instance object.
pushValue(JSValue::newFunction((JSFunction *)(new JSBoundFunction(this, JSValue::function(result), NULL))));
else
pushValue(JSValue::newFunction((JSFunction *)(new JSBoundFunction(this, JSValue::function(result), obj))));
}
if (useOnceNamespace) {
NamespaceList *t = mNamespaceList->mNext;
delete mNamespaceList;
useOnceNamespace = false;
mNamespaceList = t;
}
}
break;
case GetInvokePropertyOp:
{
js2val base = topValue();
js2val baseObject = JSValue::toObject(this, base);
// XXX really only need to pop/push if the base got modified
popValue();
pushValue(baseObject); // want the "toObject'd" version of base
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
mPC = pc;
const StringAtom &name = *mCurModule->getString(index);
JSValue::getObjectValue(baseObject)->getProperty(this, name, mNamespaceList);
if (useOnceNamespace) {
NamespaceList *t = mNamespaceList->mNext;
delete mNamespaceList;
useOnceNamespace = false;
mNamespaceList = t;
}
}
break;
case SetPropertyOp:
{
js2val v = popValue();
if (JSValue::isFunction(v) && JSValue::function(v)->hasBoundThis() && !JSValue::function(v)->isMethod()) {
v = JSValue::newFunction(JSValue::function(v)->getFunction());
}
js2val base = popValue();
JSObject *obj = JSValue::toObjectValue(this, base);
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
mPC = pc;
const StringAtom &name = *mCurModule->getString(index);
obj->setProperty(this, name, mNamespaceList, v);
pushValue(v);
if (useOnceNamespace) {
NamespaceList *t = mNamespaceList->mNext;
delete mNamespaceList;
useOnceNamespace = false;
mNamespaceList = t;
}
}
break;
case DoUnaryOp:
{
Operator op = (Operator)(*pc++);
mPC = pc;
js2val v = topValue();
JSFunction *target = getUnaryOperator(JSValue::getType(v), op);
if (target) {
uint32 argBase = stackSize() - 1;
js2val 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, mNamespaceList));
mThis = newThis;
mCurModule = target->getByteCode();
pc = mCurModule->mCodeBase;
endPC = mCurModule->mCodeBase + mCurModule->mLength;
mArgumentBase = getBase(argBase);
mLocals = new js2val[mCurModule->mLocalsCount];
mStack = new js2val[mCurModule->mStackDepth];
mStackMax = mCurModule->mStackDepth;
mStackTop = 0;
}
else {
js2val result = target->invokeNativeCode(this, newThis, getBase(argBase), 0);
resizeStack(stackSize() - 1);
pushValue(result);
}
break;
}
switch (op) {
default:
NOT_REACHED("bad unary op");
case Negate:
{
popValue();
js2val n = JSValue::toNumber(this, v);
if (JSValue::isNaN(n))
pushValue(n);
else
pushValue(JSValue::newNumber(-JSValue::f64(n)));
}
break;
case Posate:
{
popValue();
js2val n = JSValue::toNumber(this, v);
pushValue(n);
}
break;
case Complement:
{
popValue();
js2val n = JSValue::toInt32(this, v);
pushValue(JSValue::newNumber((float64)(~(int32)JSValue::f64(n))));
}
break;
}
}
break;
case DoOperatorOp:
{
Operator op = (Operator)(*pc++);
mPC = pc;
js2val v1 = getValue(stackSize() - 2);
js2val v2 = getValue(stackSize() - 1);
if (executeOperator(op, JSValue::getType(v1), JSValue::getType(v2))) {
// need to invoke
pc = mCurModule->mCodeBase;
endPC = mCurModule->mCodeBase + mCurModule->mLength;
mLocals = new js2val[mCurModule->mLocalsCount];
mStack = new js2val[mCurModule->mStackDepth];
mStackMax = mCurModule->mStackDepth;
mStackTop = 0;
}
}
break;
case GetConstructorOp:
{
js2val v = popValue();
ASSERT(JSValue::isType(v));
pushValue(JSValue::newFunction(JSValue::type(v)->getDefaultConstructor()));
}
break;
case NewInstanceOp:
{
uint32 argCount = *((uint32 *)pc);
pc += sizeof(uint32);
uint32 cleanUp = argCount;
js2val *argBase = getBase(stackSize() - argCount);
bool isPrototypeFunctionCall = false;
JSFunction *target = NULL;
js2val newThis = kNullValue;
js2val *typeValue = getBase(stackSize() - (argCount + 1));
if (!JSValue::isType(*typeValue)) {
if (JSValue::isFunction(*typeValue) && JSValue::function(*typeValue)->isPrototype()) {
isPrototypeFunctionCall = true;
target = JSValue::function(*typeValue);
newThis = Object_Type->newInstance(this);
PropertyIterator i;
if (target->hasProperty(this, Prototype_StringAtom, mNamespaceList, Read, &i)) {
js2val v = target->getPropertyValue(i);
JSValue::object(newThis)->mPrototype = JSValue::newObject(JSValue::toObjectValue(this, v));
JSValue::object(newThis)->setProperty(this, UnderbarPrototype_StringAtom, (NamespaceList *)NULL, JSValue::object(newThis)->mPrototype);
}
}
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 = getUnaryOperator(JSValue::type(*typeValue), New);
if (target)
newThis = JSValue::type(*typeValue)->newInstance(this);
else {
newThis = kNullValue;
target = JSValue::type(*typeValue)->getDefaultConstructor();
}
}
ASSERT(target);
js2val result;
if (target->isNative()) {
js2val *tArgBase = new js2val[argCount];
for (uint32 i = 0; i < argCount; i++)
tArgBase[i] = argBase[i];
resizeStack(stackSize() - cleanUp);
result = target->invokeNativeCode(this, newThis, tArgBase, argCount);
}
else {
argBase = buildArgumentBlock(target, argCount);
resizeStack(stackSize() - cleanUp);
if (!target->isChecked()) {
js2val args = Array_Type->newInstance(this);
for (uint32 i = 0; i < argCount; i++)
JSValue::instance(args)->setProperty(this, *numberToString(i), NULL, argBase[i]);
target->getActivation()->setProperty(this, Arguments_StringAtom, NULL, 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 (!JSValue::isPrimitive(result))
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:
{
js2val v = popValue();
if (JSValue::isNull(mThis)) {
ASSERT(JSValue::isType(v));
mThis = JSValue::type(v)->newInstance(this);
}
}
break;
case NewObjectOp:
{
pushValue(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);
js2val v = topValue();
if (JSValue::isFunction(v) && JSValue::function(v)->hasBoundThis() && !JSValue::function(v)->isMethod()) {
v = JSValue::newFunction(JSValue::function(v)->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:
{
js2val v = topValue();
if (JSValue::isFunction(v) && JSValue::function(v)->hasBoundThis() && !JSValue::function(v)->isMethod()) {
v = JSValue::newFunction(JSValue::function(v)->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:
{
js2val v = topValue();
if (JSValue::isFunction(v) && JSValue::function(v)->hasBoundThis() && !JSValue::function(v)->isMethod()) {
v = JSValue::newFunction(JSValue::function(v)->getFunction());
}
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
mArgumentBase[index] = v;
}
break;
case GetMethodOp:
{
js2val base = topValue();
ASSERT(JSValue::isInstance(base));
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
pushValue(JSValue::newInstance(JSValue::instance(base)->mType->mMethods[index]));
}
break;
case GetMethodRefOp:
{
js2val base = popValue();
if (!JSValue::isInstance(base))
reportError(Exception::semanticError, "Illegal method reference");
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
pushValue(JSValue::newFunction(new JSBoundFunction(this, JSValue::instance(base)->mType->mMethods[index], JSValue::object(base))));
}
break;
case GetFieldOp:
{
js2val base = popValue();
ASSERT(JSValue::isInstance(base));
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
pushValue(JSValue::instance(base)->mInstanceValues[index]);
}
break;
case SetFieldOp:
{
js2val v = popValue();
if (JSValue::isFunction(v) && JSValue::function(v)->hasBoundThis() && !JSValue::function(v)->isMethod()) {
v = JSValue::newFunction(JSValue::function(v)->getFunction());
}
js2val base = popValue();
ASSERT(JSValue::isInstance(base));
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
JSValue::instance(base)->mInstanceValues[index] = v;
pushValue(v);
}
break;
case WithinOp:
{
js2val base = popValue();
mScopeChain->addScope(JSValue::toObjectValue(this, base));
}
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::newObject(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:
{
js2val v2 = popValue();
js2val v1 = popValue();
ASSERT(JSValue::isAttribute(v1));
ASSERT(JSValue::isAttribute(v2));
Attribute *a1 = JSValue::attribute(v1);
Attribute *a2 = JSValue::attribute(v2);
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::newAttribute(x));
}
break;
case CastOp:
{
js2val t = popValue();
ASSERT(JSValue::isType(t));
JSType *toType = JSValue::type(t);
js2val v = popValue();
pushValue(mapValueToType(v, toType));
}
break;
case UseOnceOp:
{
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
const StringAtom &nameSpace = *mCurModule->getString(index);
ASSERT(useOnceNamespace == false);
useOnceNamespace = true;
mNamespaceList = new NamespaceList(nameSpace, mNamespaceList);
}
break;
case UseOp:
{
uint32 index = *((uint32 *)pc);
pc += sizeof(uint32);
const StringAtom &nameSpace = *mCurModule->getString(index);
mNamespaceList = new NamespaceList(nameSpace, mNamespaceList);
}
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;
js2val 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();
mNamespaceList = prev->mNamespaceList;
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)) {
js2val argv[1];
argv[0] = JSValue::newString(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;
}
// called on a JSValue to return a base object. The value may be a type, function, instance
// or generic object
JSObject *JSValue::getObjectValue(js2val v)
{
ASSERT(isObject(v));
return object(v);
}
// called on a JSValue with type == Number_Type, which could be either a tagged
// value or a Number object (actually an instance). Return the raw value in either case.
float64 JSValue::getNumberValue(js2val v)
{
if (JSValue::isNumber(v))
return JSValue::f64(v);
ASSERT(JSValue::isInstance(v) && (JSValue::getType(v) == Number_Type));
JSNumberInstance *numInst = checked_cast<JSNumberInstance *>(JSValue::instance(v));
return numInst->mValue;
}
// called on a JSValue with type == String_Type, which could be either a tagged
// value or a String object (actually an instance). Return the raw value in either case.
const String *JSValue::getStringValue(js2val v)
{
if (JSValue::isString(v))
return JSValue::string(v);
ASSERT(JSValue::isInstance(v) && (JSValue::getType(v) == String_Type));
JSStringInstance *strInst = checked_cast<JSStringInstance *>(JSValue::instance(v));
return strInst->mValue;
}
// called on a JSValue with type == Boolean_Type, which could be either a tagged
// value or a Boolean object (actually an instance). Return the raw value in either case.
bool JSValue::getBoolValue(js2val v)
{
if (JSValue::isBool(v))
return JSValue::boolean(v);
ASSERT(JSValue::isInstance(v) && (JSValue::getType(v) == Boolean_Type));
JSBooleanInstance *boolInst = checked_cast<JSBooleanInstance *>(JSValue::instance(v));
return boolInst->mValue;
}
// Implementation of '+' for two number types
static js2val numberPlus(Context *, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
return JSValue::newNumber(JSValue::getNumberValue(argv[0]) + JSValue::getNumberValue(argv[1]));
}
// Implementation of '+' for two integer types
static js2val integerPlus(Context * /*cx*/, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
return JSValue::newNumber(JSValue::getNumberValue(argv[0]) + JSValue::getNumberValue(argv[1]));
}
// Implementation of '+' for two 'generic' objects
static js2val objectPlus(Context *cx, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
js2val &r1 = argv[0];
js2val &r2 = argv[1];
JSType *t1 = JSValue::getType(r1);
JSType *t2 = JSValue::getType(r2);
if ((t1 == Number_Type) && (t2 == Number_Type)) {
return JSValue::newNumber(JSValue::getNumberValue(r1) + JSValue::getNumberValue(r2));
}
if (t1 == String_Type) {
if (t2 == String_Type)
return JSValue::newString(new String(*JSValue::getStringValue(r1) + *JSValue::getStringValue(r2)));
else
return JSValue::newString(new String(*JSValue::getStringValue(r1) + *JSValue::string(JSValue::toString(cx, r2))));
}
else {
if (t2 == String_Type)
return JSValue::newString(new String(*JSValue::string(JSValue::toString(cx, r1)) + *JSValue::getStringValue(r2)));
else {
js2val r1p = JSValue::toPrimitive(cx, r1);
js2val r2p = JSValue::toPrimitive(cx, r2);
// gar-on-teed tagged values now
if (JSValue::isString(r1p))
if (JSValue::isString(r2p))
return JSValue::newString(new String(*JSValue::string(r1p) + *JSValue::string(r2p)));
else
return JSValue::newString(new String(*JSValue::string(r1p) + *JSValue::string(JSValue::toString(cx, r2p))));
else
if (JSValue::isString(r2p))
return JSValue::newString(new String(*JSValue::string(JSValue::toString(cx, r1p)) + *JSValue::string(r2p)));
else {
js2val num1(JSValue::toNumber(cx, r1));
js2val num2(JSValue::toNumber(cx, r2));
return JSValue::newNumber(JSValue::f64(num1) + JSValue::f64(num2));
}
}
}
}
// Implementation of '-' for two integer types
static js2val integerMinus(Context * /*cx*/, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
return JSValue::newNumber(JSValue::getNumberValue(argv[0]) - JSValue::getNumberValue(argv[1]));
}
// Implementation of '-' for two number types
static js2val numberMinus(Context *, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
return JSValue::newNumber(JSValue::getNumberValue(argv[0]) - JSValue::getNumberValue(argv[1]));
}
// Implementation of '-' for two 'generic' objects
static js2val objectMinus(Context *cx, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
js2val r1 = JSValue::toNumber(cx, argv[0]);
js2val r2 = JSValue::toNumber(cx, argv[1]);
return JSValue::newNumber(JSValue::f64(r1) - JSValue::f64(r2));
}
// Implementation of '*' for two integer types
static js2val integerMultiply(Context * /*cx*/, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
return JSValue::newNumber(JSValue::getNumberValue(argv[0]) * JSValue::getNumberValue(argv[1]));
}
// Implementation of '*' for two 'generic' objects
static js2val objectMultiply(Context *cx, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
js2val r1 = JSValue::toNumber(cx, argv[0]);
js2val r2 = JSValue::toNumber(cx, argv[1]);
return JSValue::newNumber(JSValue::f64(r1) * JSValue::f64(r2));
}
// Implementation of '/' for two integer types
static js2val integerDivide(Context * /*cx*/, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
float64 f1 = JSValue::getNumberValue(argv[0]);
float64 f2 = JSValue::getNumberValue(argv[1]);
float64 d = f1 / f2;
bool neg = (d < 0);
d = fd::floor(neg ? -d : d);
d = neg ? -d : d;
return JSValue::newNumber(d);
}
// Implementation of '/' for two 'generic' objects
static js2val objectDivide(Context *cx, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
js2val r1 = JSValue::toNumber(cx, argv[0]);
js2val r2 = JSValue::toNumber(cx, argv[1]);
return JSValue::newNumber(JSValue::f64(r1) / JSValue::f64(r2));
}
// Implementation of '%' for two integer types
static js2val integerRemainder(Context * /*cx*/, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
float64 f1 = JSValue::getNumberValue(argv[0]);
float64 f2 = JSValue::getNumberValue(argv[1]);
float64 d = fd::fmod(f1, f2);
bool neg = (d < 0);
d = fd::floor(neg ? -d : d);
d = neg ? -d : d;
return JSValue::newNumber(d);
}
// Implementation of '%' for two 'generic' objects
static js2val objectRemainder(Context *cx, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
js2val r1 = JSValue::toNumber(cx, argv[0]);
js2val r2 = JSValue::toNumber(cx, argv[1]);
float64 f1 = JSValue::f64(r1);
float64 f2 = JSValue::f64(r2);
#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::newNumber(f1);
#endif
return JSValue::newNumber(fd::fmod(f1, f2));
}
// Implementation of '<<' for two integer types
static js2val integerShiftLeft(Context * /*cx*/, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
float64 f1 = JSValue::getNumberValue(argv[0]);
float64 f2 = JSValue::getNumberValue(argv[1]);
return JSValue::newNumber((float64)( (int32)(f1) << ( (uint32)(f2) & 0x1F)) );
}
// Implementation of '<<' for two 'generic' objects
static js2val objectShiftLeft(Context *cx, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
js2val r1 = JSValue::toInt32(cx, argv[0]);
js2val r2 = JSValue::toUInt32(cx, argv[1]);
return JSValue::newNumber((float64)( (int32)(JSValue::f64(r1)) << ( (uint32)(JSValue::f64(r2)) & 0x1F)) );
}
// Implementation of '>>' for two integer types
static js2val integerShiftRight(Context * /*cx*/, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
float64 f1 = JSValue::getNumberValue(argv[0]);
float64 f2 = JSValue::getNumberValue(argv[1]);
return JSValue::newNumber((float64) ( (int32)(f1) >> ( (uint32)(f2) & 0x1F)) );
}
// Implementation of '>>' for two 'generic' objects
static js2val objectShiftRight(Context *cx, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
js2val r1 = JSValue::toInt32(cx, argv[0]);
js2val r2 = JSValue::toUInt32(cx, argv[1]);
return JSValue::newNumber((float64) ( (int32)(JSValue::f64(r1)) >> ( (uint32)(JSValue::f64(r2)) & 0x1F)) );
}
// Implementation of '>>>' for two integer types
static js2val integerUShiftRight(Context * /*cx*/, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
float64 f1 = JSValue::getNumberValue(argv[0]);
float64 f2 = JSValue::getNumberValue(argv[1]);
return JSValue::newNumber((float64) ( (uint32)(f1) >> ( (uint32)(f2) & 0x1F)) );
}
// Implementation of '>>>' for two 'generic' objects
static js2val objectUShiftRight(Context *cx, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
js2val r1 = JSValue::toUInt32(cx, argv[0]);
js2val r2 = JSValue::toUInt32(cx, argv[1]);
return JSValue::newNumber((float64) ( (uint32)(JSValue::f64(r1)) >> ( (uint32)(JSValue::f64(r2)) & 0x1F)) );
}
// Implementation of '&' for two integer types
static js2val integerBitAnd(Context * /*cx*/, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
float64 f1 = JSValue::getNumberValue(argv[0]);
float64 f2 = JSValue::getNumberValue(argv[1]);
return JSValue::newNumber((float64)( (int32)(f1) & (int32)(f2) ));
}
// Implementation of '&' for two 'generic' objects
static js2val objectBitAnd(Context *cx, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
js2val r1 = JSValue::toInt32(cx, argv[0]);
js2val r2 = JSValue::toInt32(cx, argv[1]);
return JSValue::newNumber((float64)( (int32)(JSValue::f64(r1)) & (int32)(JSValue::f64(r2)) ));
}
// Implementation of '^' for two integer types
static js2val integerBitXor(Context * /*cx*/, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
float64 f1 = JSValue::getNumberValue(argv[0]);
float64 f2 = JSValue::getNumberValue(argv[1]);
return JSValue::newNumber((float64)( (int32)(f1) ^ (int32)(f2) ));
}
// Implementation of '^' for two 'generic' objects
static js2val objectBitXor(Context *cx, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
js2val r1 = JSValue::toInt32(cx, argv[0]);
js2val r2 = JSValue::toInt32(cx, argv[1]);
return JSValue::newNumber((float64)( (int32)(JSValue::f64(r1)) ^ (int32)(JSValue::f64(r2)) ));
}
// Implementation of '|' for two integer types
static js2val integerBitOr(Context * /*cx*/, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
float64 f1 = JSValue::getNumberValue(argv[0]);
float64 f2 = JSValue::getNumberValue(argv[1]);
return JSValue::newNumber((float64)( (int32)(f1) | (int32)(f2) ));
}
// Implementation of '|' for two 'generic' objects
static js2val objectBitOr(Context *cx, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
js2val r1 = JSValue::toInt32(cx, argv[0]);
js2val r2 = JSValue::toInt32(cx, argv[1]);
return JSValue::newNumber((float64)( (int32)(JSValue::f64(r1)) | (int32)(JSValue::f64(r2)) ));
}
//
// implements r1 < r2, returning true or false or undefined
//
static js2val objectCompare(Context *cx, js2val &r1, js2val &r2)
{
js2val r1p = JSValue::toPrimitive(cx, r1, JSValue::NumberHint);
js2val r2p = JSValue::toPrimitive(cx, r2, JSValue::NumberHint);
if (JSValue::isString(r1p) && JSValue::isString(r2p))
return JSValue::newBoolean(bool(JSValue::string(r1p)->compare(*JSValue::string(r2p)) < 0));
else {
js2val r1n = JSValue::toNumber(cx, r1p);
js2val r2n = JSValue::toNumber(cx, r2p);
if (JSValue::isNaN(r1n) || JSValue::isNaN(r2n))
return kUndefinedValue;
else
return JSValue::newNumber(JSValue::f64(r1n) < JSValue::f64(r2n));
}
}
static js2val objectLess(Context *cx, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
js2val result = objectCompare(cx, argv[0], argv[1]);
if (JSValue::isUndefined(result))
return kFalseValue;
else
return result;
}
static js2val objectLessEqual(Context *cx, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
js2val result = objectCompare(cx, argv[1], argv[0]);
if (JSValue::isUndefined(result) || JSValue::isTrue(result))
return kFalseValue;
else
return kTrueValue;
}
// Compare two values for '=='
static js2val compareEqual(Context *cx, js2val r1, js2val r2)
{
JSType *t1 = JSValue::getType(r1);
JSType *t2 = JSValue::getType(r2);
if (t1 != t2) {
if (JSValue::isNull(r1) && JSValue::isUndefined(r2))
return kTrueValue;
if (JSValue::isUndefined(r1) && JSValue::isNull(r2))
return kTrueValue;
if ((t1 == Number_Type) && (t2 == String_Type))
return compareEqual(cx, r1, JSValue::toNumber(cx, r2));
if ((t1 == String_Type) && (t2 == Number_Type))
return compareEqual(cx, JSValue::toNumber(cx, r1), JSValue::toString(cx, r2));
if (t1 == Boolean_Type)
return compareEqual(cx, JSValue::toNumber(cx, r1), r2);
if (t2 == Boolean_Type)
return compareEqual(cx, r1, JSValue::toNumber(cx, r1));
if ( ((t1 == String_Type) || (t1 == Number_Type)) && JSValue::isObject(r2) )
return compareEqual(cx, r1, JSValue::toPrimitive(cx, r2));
if ( (JSValue::isObject(r1)) && ((t2 == String_Type) || (t2 == Number_Type)) )
return compareEqual(cx, JSValue::toPrimitive(cx, r1), r2);
return kFalseValue;
}
else {
if (JSValue::isUndefined(r1))
return kTrueValue;
if (JSValue::isNull(r1))
return kTrueValue;
if (JSValue::isInstance(r1) && JSValue::isInstance(r2)) // because new Boolean()->getType() == Boolean_Type
return JSValue::newBoolean(JSValue::instance(r1) == JSValue::instance(r2));
if (JSValue::isObject(r1) && JSValue::isObject(r2))
return JSValue::newBoolean(JSValue::object(r1) == JSValue::object(r2));
if (JSValue::isPackage(r1) && JSValue::isPackage(r2))
return JSValue::newBoolean(JSValue::package(r1) == JSValue::package(r2));
if (JSValue::isType(r1))
return JSValue::newBoolean(JSValue::type(r1) == JSValue::type(r2));
if (JSValue::isFunction(r1))
return JSValue::newBoolean(JSValue::function(r1)->isEqual(JSValue::function(r2)));
if (t1 == Number_Type) {
float64 f1 = JSValue::getNumberValue(r1);
float64 f2 = JSValue::getNumberValue(r2);
if (JSDOUBLE_IS_NaN(f1))
return kFalseValue;
if (JSDOUBLE_IS_NaN(f2))
return kFalseValue;
return JSValue::newBoolean(JSValue::f64(r1) == JSValue::f64(r2));
}
else {
if (t1 == String_Type)
return JSValue::newBoolean(bool(JSValue::getStringValue(r1)->compare(*JSValue::getStringValue(r2)) == 0));
if (t1 == Boolean_Type)
return JSValue::newBoolean(JSValue::getBoolValue(r1) == JSValue::getBoolValue(r2));
NOT_REACHED("unhandled type");
return kFalseValue;
}
}
}
static js2val objectEqual(Context *cx, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
return compareEqual(cx, argv[0], argv[1]);
}
static js2val objectSpittingImage(Context * /*cx*/, const js2val /*thisValue*/, js2val argv[], uint32 /*argc*/)
{
js2val r1 = argv[0];
js2val r2 = argv[1];
JSType *t1 = JSValue::getType(r1);
JSType *t2 = JSValue::getType(r2);
if ((t1 != t2) || (JSValue::isObject(r1) != JSValue::isObject(r2))) {
return kFalseValue;
}
else {
if (JSValue::isUndefined(r1))
return kTrueValue;
if (JSValue::isNull(r1))
return kTrueValue;
if (JSValue::isObject(r1) && JSValue::isObject(r2)) // because new Boolean()->getType() == Boolean_Type
return JSValue::newBoolean(JSValue::object(r1) == JSValue::object(r2));
if (JSValue::isType(r1))
return JSValue::newBoolean(JSValue::type(r1) == JSValue::type(r2));
if (JSValue::isFunction(r1))
return JSValue::newBoolean(JSValue::function(r1)->isEqual(JSValue::function(r2)));
if (t1 == Number_Type) {
float64 f1 = JSValue::getNumberValue(r1);
float64 f2 = JSValue::getNumberValue(r2);
if (JSDOUBLE_IS_NaN(f1))
return kFalseValue;
if (JSDOUBLE_IS_NaN(f2))
return kFalseValue;
return JSValue::newBoolean(JSValue::f64(r1) == JSValue::f64(r2));
}
else {
if (t1 == String_Type)
return JSValue::newBoolean(bool(JSValue::getStringValue(r1)->compare(*JSValue::getStringValue(r2)) == 0));
if (JSValue::isBool(r1) && JSValue::isBool(r2))
return JSValue::newBoolean(JSValue::getBoolValue(r1) == JSValue::getBoolValue(r2));
return kFalseValue;
}
}
}
// Build the default state of the binary operator table
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, NULL);
OperatorDefinition *op = new OperatorDefinition(OpTable[i].op1, OpTable[i].op2, f);
mBinaryOperatorTable[OpTable[i].which].push_back(op);
}
}
// Convert a primitive value into an object value, this is a no-op for
// values that are already object-like.
// XXX for Waldemar - is it possible to modify behaviour for Number, Boolean etc
// so that the behaviour here of constructing a new instance of those types would
// be different?
js2val JSValue::valueToObject(Context *cx, const js2val value)
{
switch (tag(value)) {
case JS2VAL_DOUBLE:
{
js2val result = Number_Type->newInstance(cx);
((JSNumberInstance *)JSValue::instance(result))->mValue = JSValue::f64(value);
return result;
}
case JS2VAL_BOOLEAN:
{
js2val result = Boolean_Type->newInstance(cx);
((JSBooleanInstance *)JSValue::instance(result))->mValue = JSValue::boolean(value);
return result;
}
case JS2VAL_STRING:
{
js2val result = String_Type->newInstance(cx);
((JSStringInstance *)JSValue::instance(result))->mValue = JSValue::string(value);
return result;
}
case JS2VAL_OBJECT:
if (isNull(value))
cx->reportError(Exception::typeError, "converting null to object");
return value;
case JS2VAL_INT:
cx->reportError(Exception::typeError, "converting undefined to object");
default:
NOT_REACHED("Bad tag");
return kUndefinedValue;
}
}
// Convert a value to an object, without constructing a new value
// unless absolutely necessary, which would be the case for using
// valueToObject().getObjectValue() instead.
// XXX for Waldemar - is it possible to modify behaviour for Number, Boolean etc
// so that the behaviour here of constructing a new instance of those types would
// be different?
JSObject *JSValue::toObjectValue(Context *cx, const js2val v)
{
switch (tag(v)) {
case JS2VAL_DOUBLE:
{
js2val result = Number_Type->newInstance(cx);
((JSNumberInstance *)JSValue::instance(result))->mValue = JSValue::f64(v);
return JSValue::instance(result);
}
case JS2VAL_BOOLEAN:
{
js2val result = Boolean_Type->newInstance(cx);
((JSBooleanInstance *)JSValue::instance(result))->mValue = JSValue::boolean(v);
return JSValue::instance(result);
}
case JS2VAL_STRING:
{
js2val result = String_Type->newInstance(cx);
((JSStringInstance *)JSValue::instance(result))->mValue = JSValue::string(v);
return JSValue::instance(result);
}
case JS2VAL_OBJECT:
if (isNull(v))
cx->reportError(Exception::typeError, "converting null to object");
return JSValue::object(v);
case JS2VAL_INT:
cx->reportError(Exception::typeError, "converting undefined to object");
default:
NOT_REACHED("Bad tag");
return NULL;
}
}
// convert string to double - a wrapper around the numerics routine
// to handle hex literals as well
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;
}
// Convert a JSValue to number-type
js2val JSValue::valueToNumber(Context *cx, const js2val value)
{
switch (tag(value)) {
case JS2VAL_DOUBLE:
return value;
case JS2VAL_STRING:
return JSValue::newNumber(stringToNumber(JSValue::string(value)));
case JS2VAL_OBJECT:
if (isNull(value))
return kPositiveZero;
else
return JSValue::toNumber(cx, JSValue::toPrimitive(cx, value, NumberHint));
case JS2VAL_BOOLEAN:
return JSValue::newNumber((JSValue::boolean(value)) ? 1.0 : 0.0);
case JS2VAL_INT:
return kNaNValue;
default:
NOT_REACHED("Bad tag");
return kUndefinedValue;
}
}
// Convert a double to a string representation (base-10)
const String *numberToString(float64 number)
{
char buf[dtosStandardBufferSize];
const char *chrp = doubleToStr(buf, dtosStandardBufferSize, number, dtosStandard, 0);
return new JavaScript::String(widenCString(chrp));
}
// Convert a JSValue to a string (ECMA rules)
js2val JSValue::valueToString(Context *cx, const js2val value)
{
const String *strp = NULL;
JSObject *obj = NULL;
switch (tag(value)) {
case JS2VAL_DOUBLE:
return JSValue::newString(numberToString(JSValue::f64(value)));
case JS2VAL_OBJECT:
if (isNull(value))
strp = &cx->Null_StringAtom;
else
obj = JSValue::object(value);
break;
case JS2VAL_STRING:
return value;
case JS2VAL_BOOLEAN:
strp = (JSValue::boolean(value)) ? &cx->True_StringAtom : &cx->False_StringAtom;
break;
case JS2VAL_INT:
strp = &cx->Undefined_StringAtom;
break;
default:
NOT_REACHED("Bad tag");
}
if (obj) {
JSFunction *target = NULL;
PropertyIterator i;
if (obj->hasProperty(cx, cx->ToString_StringAtom, NULL, Read, &i)) {
js2val v = obj->getPropertyValue(i);
if (JSValue::isFunction(v))
target = JSValue::function(v);
}
if (target == NULL) {
if (obj->hasProperty(cx, cx->ValueOf_StringAtom, NULL, Read, &i)) {
js2val v = obj->getPropertyValue(i);
if (JSValue::isFunction(v))
target = JSValue::function(v);
}
}
if (target)
return cx->invokeFunction(target, value, NULL, 0);
cx->reportError(Exception::runtimeError, "toString"); // XXX
return kUndefinedValue; // keep compilers happy
}
else
return JSValue::newString(strp);
}
// Convert a JSValue to a primitive type
js2val JSValue::toPrimitive(Context *cx, const js2val v, Hint hint)
{
JSObject *obj;
switch (tag(v)) {
case JS2VAL_DOUBLE:
case JS2VAL_STRING:
case JS2VAL_BOOLEAN:
case JS2VAL_INT:
return v;
case JS2VAL_OBJECT:
obj = JSValue::object(v);
if ((hint == NoHint) && (getType(v) == Date_Type))
hint = StringHint;
break;
default:
NOT_REACHED("Bad tag");
return kUndefinedValue;
}
// The following is [[DefaultValue]]
//
ASSERT(obj);
JSFunction *target = NULL;
js2val 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(cx, *first, NULL, Read, &i)) {
js2val v = obj->getPropertyValue(i);
if (JSValue::isFunction(v)) {
target = JSValue::function(v);
if (target) {
result = cx->invokeFunction(target, v, NULL, 0);
if (JSValue::isPrimitive(result))
return result;
}
}
}
if (obj->hasProperty(cx, *second, NULL, Read, &i)) {
js2val v = obj->getPropertyValue(i);
if (JSValue::isFunction(v)) {
target = JSValue::function(v);
if (target) {
result = cx->invokeFunction(target, v, NULL, 0);
if (JSValue::isPrimitive(result))
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);
}
js2val JSValue::valueToInteger(Context *cx, const js2val value)
{
js2val v = valueToNumber(cx, value);
return JSValue::newNumber(float64ToInteger(JSValue::f64(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;
}
js2val JSValue::valueToInt32(Context *, const js2val value)
{
float64 d;
switch (tag(value)) {
case JS2VAL_DOUBLE:
d = JSValue::f64(value);
break;
case JS2VAL_STRING:
{
const char16 *numEnd;
const String *str = JSValue::string(value);
d = stringToDouble(str->begin(), str->end(), numEnd);
}
break;
case JS2VAL_BOOLEAN:
return JSValue::newNumber((float64)((JSValue::boolean(value)) ? 1 : 0));
case JS2VAL_OBJECT:
case JS2VAL_INT:
// toNumber(toPrimitive(hint Number))
return kUndefinedValue;
default:
NOT_REACHED("Bad tag");
return kUndefinedValue;
}
if ((d == 0.0) || !JSDOUBLE_IS_FINITE(d) )
return JSValue::newNumber((float64)0);
return JSValue::newNumber((float64)float64ToInt32(d));
}
js2val JSValue::valueToUInt32(Context *, const js2val value)
{
float64 d;
switch (tag(value)) {
case JS2VAL_DOUBLE:
d = JSValue::f64(value);
break;
case JS2VAL_STRING:
{
const char16 *numEnd;
const String *str = JSValue::string(value);
d = stringToDouble(str->begin(), str->end(), numEnd);
}
break;
case JS2VAL_BOOLEAN:
return JSValue::newNumber((float64)((JSValue::boolean(value)) ? 1 : 0));
case JS2VAL_OBJECT:
case JS2VAL_INT:
// toNumber(toPrimitive(hint Number))
return kUndefinedValue;
default:
NOT_REACHED("Bad tag");
return kUndefinedValue;
}
if ((d == 0.0) || !JSDOUBLE_IS_FINITE(d))
return JSValue::newNumber((float64)0);
return JSValue::newNumber((float64)float64ToUInt32(d));
}
js2val JSValue::valueToUInt16(Context *, const js2val value)
{
float64 d;
switch (tag(value)) {
case JS2VAL_DOUBLE:
d = JSValue::f64(value);
break;
case JS2VAL_STRING:
{
const char16 *numEnd;
const String *str = JSValue::string(value);
d = stringToDouble(str->begin(), str->end(), numEnd);
}
break;
case JS2VAL_BOOLEAN:
return JSValue::newNumber((float64)((JSValue::boolean(value)) ? 1 : 0));
case JS2VAL_OBJECT:
case JS2VAL_INT:
// toNumber(toPrimitive(hint Number))
return kUndefinedValue;
default:
NOT_REACHED("Bad tag");
return kUndefinedValue;
}
if ((d == 0.0) || !JSDOUBLE_IS_FINITE(d))
return JSValue::newNumber((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::newNumber((float64)d);
}
js2val JSValue::valueToBoolean(Context * /*cx*/, const js2val value)
{
switch (tag(value)) {
case JS2VAL_DOUBLE:
if (isNaN(value))
return kFalseValue;
if (f64(value) == 0.0)
return kFalseValue;
return kTrueValue;
case JS2VAL_STRING:
return JSValue::newBoolean(JSValue::string(value)->length() != 0);
case JS2VAL_BOOLEAN:
return value;
case JS2VAL_OBJECT:
if (isNull(value))
return kFalseValue;
else
return kTrueValue;
case JS2VAL_INT:
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
js2val Context::mapValueToType(js2val v, JSType *t)
{
// user conversions?
if (JSValue::getType(v) == t)
return v;
if (t == Number_Type) {
return JSValue::toNumber(this, v);
}
else
if (t == Integer_Type) {
js2val fv = JSValue::toNumber(this, v);
float64 d = JSValue::f64(fv);
bool neg = (d < 0);
d = fd::floor(neg ? -d : d);
d = neg ? -d : d;
return JSValue::newNumber(d);
}
else
if (t == String_Type) {
return JSValue::toString(this, v);
}
else
if (t == Boolean_Type) {
return JSValue::toBoolean(this, v);
}
if (JSValue::isUndefined(v))
return v;
if (JSValue::getType(v)->derivesFrom(t))
return v;
reportError(Exception::typeError, "Invalid type cast");
return kUndefinedValue;
}
JSType *JSValue::getType(const js2val v) {
switch (tag(v)) {
case JS2VAL_DOUBLE: return Number_Type;
case JS2VAL_BOOLEAN: return Boolean_Type;
case JS2VAL_STRING: return (JSType *)String_Type;
case JS2VAL_OBJECT: if (isNull(v))
return Null_Type;
else
if (isInstance(v))
return JSValue::instance(v)->getType();
else
return Object_Type;
case JS2VAL_INT: return Void_Type;
default:
NOT_REACHED("bad type");
return NULL;
}
}
JSFunction *Context::getUnaryOperator(JSType *dispatchType, Operator which)
{
// look in the operator table for applicable operators
OperatorList applicableOperators;
for (OperatorList::iterator oi = mUnaryOperatorTable[which].begin(),
end = mUnaryOperatorTable[which].end();
(oi != end); oi++) {
if ((*oi)->isApplicable(dispatchType)) {
applicableOperators.push_back(*oi);
}
}
if (applicableOperators.size() == 0)
return NULL;
OperatorList::iterator candidate = applicableOperators.begin();
for (OperatorList::iterator aoi = applicableOperators.begin() + 1,
aend = applicableOperators.end();
(aoi != aend); aoi++)
{
if ((*aoi)->mType1->derivesFrom((*candidate)->mType1) )
candidate = aoi;
}
return (*candidate)->mImp;
}
}
}