2786 lines
111 KiB
C++
2786 lines
111 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 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 parameter<65>s 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 parameter<65>s AssignmentExpression and let it be the first parameter<65>s value.
|
||
*
|
||
* Implicitly coerce the argument (or default) value to type t and bind the parameter<65>s 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;
|
||
}
|
||
|
||
|
||
}
|
||
}
|
||
|