983 lines
37 KiB
C++
983 lines
37 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
|
|
#include "msvc_pragma.h"
|
|
#endif
|
|
|
|
#include <algorithm>
|
|
#include <assert.h>
|
|
#include <map>
|
|
#include <list>
|
|
#include <stack>
|
|
|
|
#include "world.h"
|
|
#include "utilities.h"
|
|
#include "js2value.h"
|
|
#include "jslong.h"
|
|
#include "numerics.h"
|
|
#include "reader.h"
|
|
#include "parser.h"
|
|
#include "regexp.h"
|
|
#include "js2engine.h"
|
|
#include "bytecodecontainer.h"
|
|
#include "js2metadata.h"
|
|
|
|
|
|
namespace JavaScript {
|
|
namespace MetaData {
|
|
|
|
|
|
js2val JS2Metadata::readEvalString(const String &str, const String& fileName)
|
|
{
|
|
js2val result = JS2VAL_VOID;
|
|
|
|
Arena a;
|
|
Pragma::Flags flags = Pragma::js1;
|
|
Parser p(world, a, flags, str, fileName);
|
|
CompilationData *oldData = NULL;
|
|
try {
|
|
StmtNode *parsedStatements = p.parseProgram();
|
|
ASSERT(p.lexer.peek(true).hasKind(Token::end));
|
|
if (showTrees)
|
|
{
|
|
PrettyPrinter f(stdOut, 80);
|
|
{
|
|
PrettyPrinter::Block b(f, 2);
|
|
f << "Program =";
|
|
f.linearBreak(1);
|
|
StmtNode::printStatements(f, parsedStatements);
|
|
}
|
|
f.end();
|
|
stdOut << '\n';
|
|
}
|
|
if (parsedStatements) {
|
|
oldData = startCompilationUnit(NULL, str, fileName);
|
|
ValidateStmtList(parsedStatements);
|
|
result = ExecuteStmtList(RunPhase, parsedStatements);
|
|
}
|
|
}
|
|
catch (Exception &x) {
|
|
if (oldData)
|
|
restoreCompilationUnit(oldData);
|
|
throw x;
|
|
}
|
|
if (oldData)
|
|
restoreCompilationUnit(oldData);
|
|
return result;
|
|
}
|
|
|
|
js2val JS2Metadata::readEvalFile(const String& fileName)
|
|
{
|
|
String buffer;
|
|
int ch;
|
|
|
|
js2val result = JS2VAL_VOID;
|
|
|
|
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);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
js2val JS2Metadata::readEvalFile(const char *fileName)
|
|
{
|
|
String buffer;
|
|
int ch;
|
|
|
|
js2val result = JS2VAL_VOID;
|
|
|
|
FILE* f = fopen(fileName, "r");
|
|
if (f) {
|
|
while ((ch = getc(f)) != EOF)
|
|
buffer += static_cast<char>(ch);
|
|
fclose(f);
|
|
result = readEvalString(buffer, widenCString(fileName));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Evaluate the linked list of statement nodes beginning at 'p'
|
|
* (generate bytecode and then execute that bytecode
|
|
*/
|
|
js2val JS2Metadata::ExecuteStmtList(Phase phase, StmtNode *p)
|
|
{
|
|
Arena *oldArena = referenceArena;
|
|
referenceArena = new Arena;
|
|
size_t lastPos;
|
|
try {
|
|
lastPos = p->pos;
|
|
while (p) {
|
|
SetupStmt(env, phase, p);
|
|
lastPos = p->pos;
|
|
p = p->next;
|
|
}
|
|
}
|
|
catch (Exception &x) {
|
|
referenceArena->clear();
|
|
delete referenceArena;
|
|
referenceArena = oldArena;
|
|
throw x;
|
|
}
|
|
referenceArena->clear();
|
|
delete referenceArena;
|
|
referenceArena = oldArena;
|
|
|
|
bCon->emitOp(eReturnVoid, lastPos);
|
|
uint8 *savePC = engine->pc;
|
|
engine->pc = NULL;
|
|
js2val retval = engine->interpret(phase, bCon, env);
|
|
engine->pc = savePC;
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* Evaluate an expression 'p' AND execute the associated bytecode
|
|
*/
|
|
js2val JS2Metadata::EvalExpression(Environment *env, Phase phase, ExprNode *p)
|
|
{
|
|
js2val retval;
|
|
uint8 *savePC = NULL;
|
|
JS2Class *exprType;
|
|
|
|
Arena *oldArena = referenceArena;
|
|
referenceArena = new Arena;
|
|
CompilationData *oldData = startCompilationUnit(NULL, bCon->mSource, bCon->mSourceLocation);
|
|
try {
|
|
Reference *r = SetupExprNode(env, phase, p, &exprType);
|
|
if (r) r->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitOp(eReturn, p->pos);
|
|
savePC = engine->pc;
|
|
engine->pc = NULL;
|
|
retval = engine->interpret(phase, bCon, env);
|
|
}
|
|
catch (Exception &x) {
|
|
referenceArena->clear();
|
|
delete referenceArena;
|
|
referenceArena = oldArena;
|
|
engine->pc = savePC;
|
|
restoreCompilationUnit(oldData);
|
|
throw x;
|
|
}
|
|
referenceArena->clear();
|
|
delete referenceArena;
|
|
referenceArena = oldArena;
|
|
engine->pc = savePC;
|
|
restoreCompilationUnit(oldData);
|
|
return retval;
|
|
}
|
|
|
|
|
|
// Execute an expression and return the result, which must be a type
|
|
JS2Class *JS2Metadata::EvalTypeExpression(Environment *env, Phase phase, ExprNode *p)
|
|
{
|
|
js2val retval = EvalExpression(env, phase, p);
|
|
if (JS2VAL_IS_PRIMITIVE(retval))
|
|
reportError(Exception::badValueError, "Type expected", p->pos);
|
|
JS2Object *obj = JS2VAL_TO_OBJECT(retval);
|
|
if (obj->kind != ClassKind)
|
|
reportError(Exception::badValueError, "Type expected", p->pos);
|
|
return checked_cast<JS2Class *>(obj);
|
|
}
|
|
|
|
// Invoke the named function on the thisValue object (it is an object)
|
|
// Returns false if no callable function exists. Otherwise return the
|
|
// function result value.
|
|
bool JS2Metadata::invokeFunctionOnObject(js2val thisValue, const String *fnName, js2val &result)
|
|
{
|
|
js2val fnVal;
|
|
|
|
JS2Class *limit = objectType(thisValue);
|
|
if (limit->readPublic(this, &thisValue, limit, fnName, RunPhase, &fnVal)) {
|
|
if (JS2VAL_IS_OBJECT(fnVal)) {
|
|
JS2Object *fnObj = JS2VAL_TO_OBJECT(fnVal);
|
|
if ((fnObj->kind == SimpleInstanceKind)
|
|
&& ((checked_cast<SimpleInstance *>(fnObj))->type == functionClass)) {
|
|
result = invokeFunction(fnObj, thisValue, NULL, 0, NULL);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Invoke the named function, no args, by looking it up
|
|
// the current scope. (Do this by emitting and executing the
|
|
// appropriate bytecode sequence)
|
|
js2val JS2Metadata::invokeFunction(const char *fname)
|
|
{
|
|
js2val retval;
|
|
uint8 *savePC = NULL;
|
|
|
|
CompilationData *oldData = startCompilationUnit(NULL, bCon->mSource, bCon->mSourceLocation);
|
|
try {
|
|
LexicalReference rVal(&world.identifiers[widenCString(fname)], false);
|
|
rVal.emitReadForInvokeBytecode(bCon, 0);
|
|
bCon->emitOp(eCall, 0, -(0 + 2) + 1); // pop argCount args, the base & function, and push a result
|
|
bCon->addShort(0);
|
|
bCon->emitOp(eReturn, 0);
|
|
savePC = engine->pc;
|
|
engine->pc = NULL;
|
|
retval = engine->interpret(RunPhase, bCon, env);
|
|
}
|
|
catch (Exception &x) {
|
|
engine->pc = savePC;
|
|
restoreCompilationUnit(oldData);
|
|
throw x;
|
|
}
|
|
engine->pc = savePC;
|
|
restoreCompilationUnit(oldData);
|
|
return retval;
|
|
}
|
|
|
|
// Invoke the constructor function for a class
|
|
void JS2Metadata::invokeInit(JS2Class *c, js2val thisValue, js2val *argv, uint32 argc)
|
|
{
|
|
FunctionInstance *init = NULL;
|
|
if (c) init = c->init;
|
|
if (init) {
|
|
ParameterFrame *runtimeFrame;
|
|
DEFINE_ROOTKEEPER(rk, runtimeFrame);
|
|
runtimeFrame = new ParameterFrame(init->fWrap->compileFrame);
|
|
if (!init->fWrap->compileFrame->callsSuperConstructor) {
|
|
invokeInit(c->super, thisValue, NULL, 0);
|
|
runtimeFrame->superConstructorCalled = true;
|
|
}
|
|
invokeFunction(init, thisValue, argv, argc, runtimeFrame);
|
|
if (!runtimeFrame->superConstructorCalled)
|
|
reportError(Exception::uninitializedError, "The superconstuctor must be called before returning normally from a constructor", engine->errorPos());
|
|
}
|
|
else
|
|
if (argc)
|
|
reportError(Exception::argumentsError, "The default constructor does not take any arguments", engine->errorPos());
|
|
}
|
|
|
|
js2val JS2Metadata::invokeFunction(JS2Object *fnObj, js2val thisValue, js2val *argv, uint32 argc, ParameterFrame *runtimeFrame)
|
|
{
|
|
js2val result = JS2VAL_UNDEFINED;
|
|
|
|
FunctionWrapper *fWrap = NULL;
|
|
if ((fnObj->kind == SimpleInstanceKind)
|
|
&& ((checked_cast<SimpleInstance *>(fnObj))->type == functionClass)) {
|
|
FunctionInstance *fInst = checked_cast<FunctionInstance *>(fnObj);
|
|
fWrap = fInst->fWrap;
|
|
if (fInst->isMethodClosure) {
|
|
// XXX ignoring fInst->thisObject;
|
|
}
|
|
if (fWrap->code) {
|
|
result = (fWrap->code)(this, thisValue, argv, argc);
|
|
}
|
|
else {
|
|
uint8 *savePC = NULL;
|
|
BytecodeContainer *bCon = fWrap->bCon;
|
|
|
|
CompilationData *oldData = startCompilationUnit(bCon, bCon->mSource, bCon->mSourceLocation);
|
|
DEFINE_ROOTKEEPER(rk, runtimeFrame);
|
|
if (runtimeFrame == NULL)
|
|
runtimeFrame = new ParameterFrame(fWrap->compileFrame);
|
|
runtimeFrame->instantiate(fWrap->env);
|
|
runtimeFrame->thisObject = thisValue;
|
|
runtimeFrame->assignArguments(this, fnObj, argv, argc, argc);
|
|
Frame *oldTopFrame = fWrap->env->getTopFrame();
|
|
if (fInst->isMethodClosure)
|
|
fWrap->env->addFrame(objectType(thisValue));
|
|
fWrap->env->addFrame(runtimeFrame);
|
|
ParameterFrame *oldPFrame = engine->parameterFrame;
|
|
try {
|
|
savePC = engine->pc;
|
|
engine->pc = NULL;
|
|
engine->parameterFrame = runtimeFrame;
|
|
result = engine->interpret(RunPhase, bCon, fWrap->env);
|
|
}
|
|
catch (Exception &x) {
|
|
engine->pc = savePC;
|
|
restoreCompilationUnit(oldData);
|
|
fWrap->env->setTopFrame(oldTopFrame);
|
|
engine->parameterFrame = oldPFrame;
|
|
throw x;
|
|
}
|
|
engine->pc = savePC;
|
|
restoreCompilationUnit(oldData);
|
|
fWrap->env->setTopFrame(oldTopFrame);
|
|
engine->parameterFrame = oldPFrame;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Save off info about the current compilation and begin a
|
|
// new one - using the given parser.
|
|
CompilationData *JS2Metadata::startCompilationUnit(BytecodeContainer *newBCon, const String &source, const String &sourceLocation)
|
|
{
|
|
CompilationData *result = new CompilationData();
|
|
result->compilation_bCon = bCon;
|
|
result->execution_bCon = engine->bCon;
|
|
bConList.push_back(bCon);
|
|
|
|
if (newBCon)
|
|
bCon = newBCon;
|
|
else
|
|
bCon = new BytecodeContainer();
|
|
bCon->mSource = source;
|
|
bCon->mSourceLocation = sourceLocation;
|
|
engine->bCon = bCon;
|
|
|
|
return result;
|
|
}
|
|
|
|
// Restore the compilation data, and then delete the cached copy.
|
|
void JS2Metadata::restoreCompilationUnit(CompilationData *oldData)
|
|
{
|
|
BytecodeContainer *xbCon = bConList.back();
|
|
ASSERT(oldData->compilation_bCon == xbCon);
|
|
bConList.pop_back();
|
|
|
|
bCon = oldData->compilation_bCon;
|
|
engine->bCon = oldData->execution_bCon;
|
|
|
|
delete oldData;
|
|
}
|
|
|
|
// x is not a String
|
|
const String *JS2Metadata::convertValueToString(js2val x)
|
|
{
|
|
if (JS2VAL_IS_UNDEFINED(x))
|
|
return engine->undefined_StringAtom;
|
|
if (JS2VAL_IS_NULL(x))
|
|
return engine->null_StringAtom;
|
|
if (JS2VAL_IS_BOOLEAN(x))
|
|
return (JS2VAL_TO_BOOLEAN(x)) ? engine->true_StringAtom : engine->false_StringAtom;
|
|
if (JS2VAL_IS_INT(x))
|
|
return engine->numberToString(JS2VAL_TO_INT(x));
|
|
if (JS2VAL_IS_LONG(x)) {
|
|
float64 d;
|
|
JSLL_L2D(d, *JS2VAL_TO_LONG(x));
|
|
return engine->numberToString(&d);
|
|
}
|
|
if (JS2VAL_IS_ULONG(x)) {
|
|
float64 d;
|
|
JSLL_UL2D(d, *JS2VAL_TO_ULONG(x));
|
|
return engine->numberToString(&d);
|
|
}
|
|
if (JS2VAL_IS_FLOAT(x)) {
|
|
float64 d = *JS2VAL_TO_FLOAT(x);
|
|
return engine->numberToString(&d);
|
|
}
|
|
if (JS2VAL_IS_DOUBLE(x))
|
|
return engine->numberToString(JS2VAL_TO_DOUBLE(x));
|
|
return toString(toPrimitive(x, StringHint));
|
|
}
|
|
|
|
// x is not a primitive (it is an object and not null)
|
|
js2val JS2Metadata::convertValueToPrimitive(js2val x, Hint hint)
|
|
{
|
|
// return [[DefaultValue]] --> get property 'toString' and invoke it,
|
|
// if not available or result is not primitive then try property 'valueOf'
|
|
// if that's not available or returns a non primitive, throw a TypeError
|
|
|
|
JS2Object *obj = JS2VAL_TO_OBJECT(x);
|
|
if (obj->kind == ClassKind) // therefore, not an E3 object, so just return
|
|
return engine->typeofString(x); // the 'typeof' string
|
|
|
|
if (hint == NoHint) {
|
|
if ((obj->kind == SimpleInstanceKind)
|
|
&& ((checked_cast<SimpleInstance *>(obj))->type == dateClass))
|
|
hint = StringHint;
|
|
else
|
|
hint = NumberHint;
|
|
}
|
|
|
|
if (hint == StringHint) {
|
|
js2val result;
|
|
if (invokeFunctionOnObject(x, engine->toString_StringAtom, result)) {
|
|
if (JS2VAL_IS_PRIMITIVE(result))
|
|
return result;
|
|
}
|
|
if (invokeFunctionOnObject(x, engine->valueOf_StringAtom, result)) {
|
|
if (JS2VAL_IS_PRIMITIVE(result))
|
|
return result;
|
|
}
|
|
reportError(Exception::typeError, "DefaultValue failure", engine->errorPos());
|
|
}
|
|
else {
|
|
js2val result;
|
|
if (invokeFunctionOnObject(x, engine->valueOf_StringAtom, result)) {
|
|
if (JS2VAL_IS_PRIMITIVE(result))
|
|
return result;
|
|
}
|
|
if (invokeFunctionOnObject(x, engine->toString_StringAtom, result)) {
|
|
if (JS2VAL_IS_PRIMITIVE(result))
|
|
return result;
|
|
}
|
|
reportError(Exception::typeError, "DefaultValue failure", engine->errorPos());
|
|
}
|
|
return JS2VAL_VOID;
|
|
}
|
|
|
|
float64 JS2Metadata::convertStringToDouble(const String *str)
|
|
{
|
|
bool neg = false;
|
|
uint32 length = str->length();
|
|
if (length == 0)
|
|
return 0.0;
|
|
const char16 *numEnd;
|
|
// if the string begins with '0X' or '0x' (after white space), then
|
|
// read it as a hex integer.
|
|
const char16 *strStart = str->data();
|
|
const char16 *strEnd = strStart + length;
|
|
const char16 *str1 = skipWhiteSpace(strStart, strEnd);
|
|
if (str1 == strEnd)
|
|
return 0.0;
|
|
if (*str1 == '-') {
|
|
neg = true;
|
|
str1++;
|
|
}
|
|
float64 d;
|
|
if ((*str1 == '0') && ((str1[1] == 'x') || (str1[1] == 'X')))
|
|
d = stringToInteger(str1, strEnd, numEnd, 16);
|
|
else {
|
|
d = stringToDouble(str1, strEnd, numEnd);
|
|
if (numEnd == str1)
|
|
return nan;
|
|
if (skipWhiteSpace(numEnd, strEnd) != strEnd)
|
|
return nan;
|
|
}
|
|
return (neg) ? -d : d;
|
|
}
|
|
|
|
// x is not a number
|
|
float64 JS2Metadata::convertValueToDouble(js2val x)
|
|
{
|
|
if (JS2VAL_IS_UNDEFINED(x))
|
|
return nan;
|
|
if (JS2VAL_IS_NULL(x))
|
|
return 0;
|
|
if (JS2VAL_IS_BOOLEAN(x))
|
|
return (JS2VAL_TO_BOOLEAN(x)) ? 1.0 : 0.0;
|
|
if (JS2VAL_IS_STRING(x)) {
|
|
String *str = JS2VAL_TO_STRING(x);
|
|
return convertStringToDouble(str);
|
|
}
|
|
if (JS2VAL_IS_INACCESSIBLE(x))
|
|
reportError(Exception::compileExpressionError, "Inappropriate compile time expression", engine->errorPos());
|
|
if (JS2VAL_IS_UNINITIALIZED(x))
|
|
reportError(Exception::compileExpressionError, "Inappropriate compile time expression", engine->errorPos());
|
|
return toFloat64(toPrimitive(x, NumberHint));
|
|
}
|
|
|
|
// x is not a number, convert it to one
|
|
js2val JS2Metadata::convertValueToGeneralNumber(js2val x)
|
|
{
|
|
// XXX Assuming convert to float64, rather than long/ulong
|
|
return engine->allocNumber(toFloat64(x));
|
|
}
|
|
|
|
// x is not an Object, it needs to be wrapped in one
|
|
js2val JS2Metadata::convertValueToObject(js2val x)
|
|
{
|
|
if (JS2VAL_IS_UNDEFINED(x) || JS2VAL_IS_NULL(x) || JS2VAL_IS_SPECIALREF(x))
|
|
reportError(Exception::typeError, "Can't convert to Object", engine->errorPos());
|
|
if (JS2VAL_IS_STRING(x))
|
|
return String_Constructor(this, JS2VAL_NULL, &x, 1);
|
|
if (JS2VAL_IS_BOOLEAN(x))
|
|
return Boolean_Constructor(this, JS2VAL_NULL, &x, 1);
|
|
if (JS2VAL_IS_NUMBER(x))
|
|
return Number_Constructor(this, JS2VAL_NULL, &x, 1);
|
|
NOT_REACHED("unsupported value type");
|
|
return JS2VAL_VOID;
|
|
}
|
|
|
|
// x is any js2val
|
|
float64 JS2Metadata::toFloat64(js2val x)
|
|
{
|
|
if (JS2VAL_IS_INT(x))
|
|
return JS2VAL_TO_INT(x);
|
|
else
|
|
if (JS2VAL_IS_DOUBLE(x))
|
|
return *JS2VAL_TO_DOUBLE(x);
|
|
else
|
|
if (JS2VAL_IS_LONG(x)) {
|
|
float64 d;
|
|
JSLL_L2D(d, *JS2VAL_TO_LONG(x));
|
|
return d;
|
|
}
|
|
else
|
|
if (JS2VAL_IS_ULONG(x)) {
|
|
float64 d;
|
|
JSLL_UL2D(d, *JS2VAL_TO_ULONG(x));
|
|
return d;
|
|
}
|
|
else
|
|
if (JS2VAL_IS_FLOAT(x))
|
|
return *JS2VAL_TO_FLOAT(x);
|
|
else
|
|
return convertValueToDouble(x);
|
|
}
|
|
|
|
// x is not a bool
|
|
bool JS2Metadata::convertValueToBoolean(js2val x)
|
|
{
|
|
if (JS2VAL_IS_UNDEFINED(x))
|
|
return false;
|
|
if (JS2VAL_IS_NULL(x))
|
|
return false;
|
|
if (JS2VAL_IS_INT(x))
|
|
return (JS2VAL_TO_INT(x) != 0);
|
|
if (JS2VAL_IS_LONG(x) || JS2VAL_IS_ULONG(x))
|
|
return (!JSLL_IS_ZERO(x));
|
|
if (JS2VAL_IS_FLOAT(x)) {
|
|
float64 xd = *JS2VAL_TO_FLOAT(x);
|
|
return ! (JSDOUBLE_IS_POSZERO(xd) || JSDOUBLE_IS_NEGZERO(xd) || JSDOUBLE_IS_NaN(xd));
|
|
}
|
|
if (JS2VAL_IS_DOUBLE(x)) {
|
|
float64 xd = *JS2VAL_TO_DOUBLE(x);
|
|
return ! (JSDOUBLE_IS_POSZERO(xd) || JSDOUBLE_IS_NEGZERO(xd) || JSDOUBLE_IS_NaN(xd));
|
|
}
|
|
if (JS2VAL_IS_STRING(x)) {
|
|
String *str = JS2VAL_TO_STRING(x);
|
|
return (str->length() != 0);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// x is not an int
|
|
int32 JS2Metadata::convertValueToInteger(js2val x)
|
|
{
|
|
int32 i;
|
|
if (JS2VAL_IS_LONG(x)) {
|
|
JSLL_L2I(i, *JS2VAL_TO_LONG(x));
|
|
return i;
|
|
}
|
|
if (JS2VAL_IS_ULONG(x)) {
|
|
JSLL_UL2I(i, *JS2VAL_TO_ULONG(x));
|
|
return i;
|
|
}
|
|
if (JS2VAL_IS_FLOAT(x)) {
|
|
float64 f = *JS2VAL_TO_FLOAT(x);
|
|
return JS2Engine::float64toInt32(f);
|
|
}
|
|
if (JS2VAL_IS_DOUBLE(x)) {
|
|
float64 d = *JS2VAL_TO_DOUBLE(x);
|
|
return JS2Engine::float64toInt32(d);
|
|
}
|
|
float64 d = convertValueToDouble(x);
|
|
return JS2Engine::float64toInt32(d);
|
|
}
|
|
|
|
// x is not an int
|
|
uint32 JS2Metadata::convertValueToUInteger(js2val x)
|
|
{
|
|
uint32 i;
|
|
if (JS2VAL_IS_LONG(x)) {
|
|
JSLL_L2UI(i, *JS2VAL_TO_LONG(x));
|
|
return i;
|
|
}
|
|
if (JS2VAL_IS_ULONG(x)) {
|
|
JSLL_UL2UI(i, *JS2VAL_TO_ULONG(x));
|
|
return i;
|
|
}
|
|
if (JS2VAL_IS_FLOAT(x)) {
|
|
float64 f = *JS2VAL_TO_FLOAT(x);
|
|
return JS2Engine::float64toUInt32(f);
|
|
}
|
|
if (JS2VAL_IS_DOUBLE(x)) {
|
|
float64 d = *JS2VAL_TO_DOUBLE(x);
|
|
return JS2Engine::float64toUInt32(d);
|
|
}
|
|
float64 d = convertValueToDouble(x);
|
|
return JS2Engine::float64toUInt32(d);
|
|
}
|
|
|
|
|
|
bool defaultReadProperty(JS2Metadata *meta, js2val *base, JS2Class *limit, Multiname *multiname, Environment *env, Phase phase, js2val *rval)
|
|
{
|
|
InstanceMember *mBase = meta->findBaseInstanceMember(limit, multiname, ReadAccess);
|
|
if (mBase)
|
|
return meta->readInstanceMember(*base, limit, mBase, phase, rval);
|
|
if (limit != meta->objectType(*base))
|
|
return false;
|
|
|
|
Member *m = meta->findCommonMember(base, multiname, ReadAccess, false);
|
|
if (m == NULL) {
|
|
if ((env == NULL)
|
|
&& JS2VAL_IS_OBJECT(*base)
|
|
&& (( (JS2VAL_TO_OBJECT(*base)->kind == SimpleInstanceKind) && !checked_cast<SimpleInstance *>(JS2VAL_TO_OBJECT(*base))->sealed)
|
|
|| ( (JS2VAL_TO_OBJECT(*base)->kind == PackageKind) && !checked_cast<Package *>(JS2VAL_TO_OBJECT(*base))->sealed) ) ) {
|
|
if (phase == CompilePhase)
|
|
meta->reportError(Exception::compileExpressionError, "Inappropriate compile time expression", meta->engine->errorPos());
|
|
else {
|
|
*rval = JS2VAL_UNDEFINED;
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
switch (m->memberKind) {
|
|
case Member::ForbiddenMember:
|
|
case Member::DynamicVariableMember:
|
|
case Member::FrameVariableMember:
|
|
case Member::VariableMember:
|
|
case Member::ConstructorMethodMember:
|
|
case Member::SetterMember:
|
|
case Member::GetterMember:
|
|
return meta->readLocalMember(checked_cast<LocalMember *>(m), phase, rval);
|
|
case Member::InstanceVariableMember:
|
|
case Member::InstanceMethodMember:
|
|
case Member::InstanceGetterMember:
|
|
case Member::InstanceSetterMember:
|
|
{
|
|
if (!JS2VAL_IS_OBJECT(*base) || (JS2VAL_TO_OBJECT(*base)->kind != ClassKind) || (env == NULL))
|
|
meta->reportError(Exception::referenceError, "Can't read an instance member without supplying an instance", meta->engine->errorPos());
|
|
js2val thisVal = env->readImplicitThis(meta);
|
|
return meta->readInstanceMember(thisVal, meta->objectType(thisVal), checked_cast<InstanceMember *>(m), phase, rval);
|
|
}
|
|
default:
|
|
NOT_REACHED("bad member kind");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool defaultReadPublicProperty(JS2Metadata *meta, js2val *base, JS2Class *limit, const String *name, Phase phase, js2val *rval)
|
|
{
|
|
// XXX could speed up by pushing knowledge of single namespace?
|
|
DEFINE_ROOTKEEPER(rk1, name);
|
|
Multiname *mn = new Multiname(name, meta->publicNamespace);
|
|
DEFINE_ROOTKEEPER(rk, mn);
|
|
return defaultReadProperty(meta, base, limit, mn, NULL, phase, rval);
|
|
}
|
|
|
|
bool defaultDeletePublic(JS2Metadata *meta, js2val base, JS2Class *limit, const String *name, bool *result)
|
|
{
|
|
DEFINE_ROOTKEEPER(rk1, name);
|
|
// XXX could speed up by pushing knowledge of single namespace?
|
|
Multiname *mn = new Multiname(name, meta->publicNamespace);
|
|
DEFINE_ROOTKEEPER(rk, mn);
|
|
return defaultDeleteProperty(meta, base, limit, mn, NULL, result);
|
|
}
|
|
|
|
bool defaultWritePublicProperty(JS2Metadata *meta, js2val base, JS2Class *limit, const String *name, bool createIfMissing, js2val newValue)
|
|
{
|
|
DEFINE_ROOTKEEPER(rk1, name);
|
|
// XXX could speed up by pushing knowledge of single namespace?
|
|
Multiname *mn = new Multiname(name, meta->publicNamespace);
|
|
DEFINE_ROOTKEEPER(rk, mn);
|
|
return defaultWriteProperty(meta, base, limit, mn, NULL, createIfMissing, newValue, false);
|
|
}
|
|
|
|
bool defaultBracketRead(JS2Metadata *meta, js2val *base, JS2Class *limit, js2val indexVal, Phase phase, js2val *rval)
|
|
{
|
|
const String *indexStr = meta->toString(indexVal);
|
|
DEFINE_ROOTKEEPER(rk, indexStr);
|
|
Multiname *mn = new Multiname(indexStr, meta->publicNamespace);
|
|
DEFINE_ROOTKEEPER(rk1, mn);
|
|
return limit->read(meta, base, limit, mn, NULL, phase, rval);
|
|
}
|
|
|
|
bool arrayClass_WriteProperty(JS2Metadata *meta, js2val base, JS2Class *limit, Multiname *multiname, Environment *env, bool createIfMissing, js2val newValue, bool initFlag)
|
|
{
|
|
ASSERT(JS2VAL_IS_OBJECT(base));
|
|
JS2Object *obj = JS2VAL_TO_OBJECT(base);
|
|
|
|
bool result = defaultWriteProperty(meta, base, limit, multiname, env, createIfMissing, newValue, false);
|
|
if (result && (multiname->nsList->size() == 1) && (multiname->nsList->back() == meta->publicNamespace)) {
|
|
const char16 *numEnd;
|
|
float64 f = stringToDouble(multiname->name->data(), multiname->name->data() + multiname->name->length(), numEnd);
|
|
uint32 index = JS2Engine::float64toUInt32(f);
|
|
|
|
char buf[dtosStandardBufferSize];
|
|
const char *chrp = doubleToStr(buf, dtosStandardBufferSize, index, dtosStandard, 0);
|
|
|
|
if (widenCString(chrp) == *multiname->name) {
|
|
uint32 length = getLength(meta, obj);
|
|
if (index >= length)
|
|
setLength(meta, obj, index + 1);
|
|
}
|
|
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool arrayClass_WritePublic(JS2Metadata *meta, js2val base, JS2Class *limit, const String *name, bool createIfMissing, js2val newValue)
|
|
{
|
|
DEFINE_ROOTKEEPER(rk1, name);
|
|
// XXX could speed up by pushing knowledge of single namespace?
|
|
Multiname *mn = new Multiname(name, meta->publicNamespace);
|
|
DEFINE_ROOTKEEPER(rk, mn);
|
|
return arrayClass_WriteProperty(meta, base, limit, mn, meta->env, createIfMissing, newValue, false);
|
|
}
|
|
|
|
bool defaultWriteProperty(JS2Metadata *meta, js2val base, JS2Class *limit, Multiname *multiname, Environment *env, bool createIfMissing, js2val newValue, bool initFlag)
|
|
{
|
|
InstanceMember *mBase = meta->findBaseInstanceMember(limit, multiname, WriteAccess);
|
|
if (mBase) {
|
|
meta->writeInstanceMember(base, limit, mBase, newValue);
|
|
return true;
|
|
}
|
|
if (limit != meta->objectType(base))
|
|
return false;
|
|
|
|
Member *m = meta->findCommonMember(&base, multiname, WriteAccess, true);
|
|
if (m == NULL) {
|
|
// XXX E3 compatibility...
|
|
JS2Object *baseObj = NULL;
|
|
DEFINE_ROOTKEEPER(rk, baseObj);
|
|
if (JS2VAL_IS_PRIMITIVE(base)) {
|
|
if (meta->cxt.E3compatibility)
|
|
baseObj = JS2VAL_TO_OBJECT(meta->toObject(base));
|
|
}
|
|
else
|
|
baseObj = JS2VAL_TO_OBJECT(base);
|
|
if (createIfMissing
|
|
&& baseObj
|
|
&& ( ((baseObj->kind == SimpleInstanceKind) && !checked_cast<SimpleInstance *>(baseObj)->sealed)
|
|
|| ( (baseObj->kind == PackageKind) && !checked_cast<Package *>(baseObj)->sealed)) ) {
|
|
QualifiedName qName = multiname->selectPrimaryName(meta);
|
|
Multiname *mn = new Multiname(qName);
|
|
DEFINE_ROOTKEEPER(rk, mn);
|
|
if ( (meta->findBaseInstanceMember(limit, mn, ReadAccess) == NULL)
|
|
&& (meta->findCommonMember(&base, mn, ReadAccess, true) == NULL) ) {
|
|
meta->createDynamicProperty(baseObj, &qName, newValue, ReadWriteAccess, false, true);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
switch (m->memberKind) {
|
|
case Member::ForbiddenMember:
|
|
case Member::DynamicVariableMember:
|
|
case Member::FrameVariableMember:
|
|
case Member::VariableMember:
|
|
case Member::ConstructorMethodMember:
|
|
case Member::SetterMember:
|
|
case Member::GetterMember:
|
|
return meta->writeLocalMember(checked_cast<LocalMember *>(m), newValue, initFlag);
|
|
case Member::InstanceVariableMember:
|
|
case Member::InstanceMethodMember:
|
|
case Member::InstanceGetterMember:
|
|
case Member::InstanceSetterMember:
|
|
{
|
|
if ( !JS2VAL_IS_OBJECT(base) || (JS2VAL_TO_OBJECT(base)->kind != ClassKind) || (env == NULL))
|
|
meta->reportError(Exception::referenceError, "Can't write an instance member withoutsupplying an instance", meta->engine->errorPos());
|
|
js2val thisVal = env->readImplicitThis(meta);
|
|
meta->writeInstanceMember(thisVal, meta->objectType(thisVal), checked_cast<InstanceMember *>(m), newValue);
|
|
return true;
|
|
}
|
|
default:
|
|
NOT_REACHED("bad member kind");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool defaultBracketWrite(JS2Metadata *meta, js2val base, JS2Class *limit, js2val indexVal, js2val newValue)
|
|
{
|
|
const String *indexStr = meta->toString(indexVal);
|
|
DEFINE_ROOTKEEPER(rk, indexStr);
|
|
Multiname *mn = new Multiname(indexStr, meta->publicNamespace);
|
|
DEFINE_ROOTKEEPER(rk1, mn);
|
|
return limit->write(meta, base, limit, mn, NULL, true, newValue, false);
|
|
}
|
|
|
|
bool defaultDeleteProperty(JS2Metadata *meta, js2val base, JS2Class *limit, Multiname *multiname, Environment *env, bool *result)
|
|
{
|
|
InstanceMember *mBase = meta->findBaseInstanceMember(limit, multiname, WriteAccess);
|
|
if (mBase) {
|
|
*result = false;
|
|
return true;
|
|
}
|
|
if (limit != meta->objectType(base))
|
|
return false;
|
|
|
|
Member *m = meta->findCommonMember(&base, multiname, WriteAccess, false);
|
|
if (m == NULL)
|
|
return false;
|
|
switch (m->memberKind) {
|
|
case Member::ForbiddenMember:
|
|
meta->reportError(Exception::propertyAccessError, "It is forbidden", meta->engine->errorPos());
|
|
return false;
|
|
case Member::FrameVariableMember:
|
|
{
|
|
if (checked_cast<FrameVariable *>(m)->sealed) {
|
|
*result = false;
|
|
return true;
|
|
}
|
|
goto VariableMemberCommon;
|
|
}
|
|
case Member::DynamicVariableMember:
|
|
{
|
|
if (checked_cast<DynamicVariable *>(m)->sealed) {
|
|
*result = false;
|
|
return true;
|
|
}
|
|
VariableMemberCommon:
|
|
// XXX if findCommonMember returned the Binding instead, we wouldn't have to rediscover it here...
|
|
JS2Object *container = JS2VAL_TO_OBJECT(meta->toObject(base));
|
|
LocalBindingMap *lMap;
|
|
if (container->kind == SimpleInstanceKind)
|
|
lMap = &checked_cast<SimpleInstance *>(container)->localBindings;
|
|
else
|
|
lMap = &checked_cast<NonWithFrame *>(container)->localBindings;
|
|
|
|
LocalBindingEntry **lbeP = (*lMap)[*multiname->name];
|
|
if (lbeP) {
|
|
while (true) {
|
|
bool deletedOne = false;
|
|
for (LocalBindingEntry::NS_Iterator i = (*lbeP)->begin(), end = (*lbeP)->end(); (i != end); i++) {
|
|
LocalBindingEntry::NamespaceBinding &ns = *i;
|
|
if (multiname->listContains(ns.first)) {
|
|
(*lbeP)->bindingList.erase(i);
|
|
deletedOne = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!deletedOne)
|
|
break;
|
|
}
|
|
}
|
|
*result = true;
|
|
return true;
|
|
}
|
|
case Member::VariableMember:
|
|
case Member::ConstructorMethodMember:
|
|
case Member::SetterMember:
|
|
case Member::GetterMember:
|
|
*result = false;
|
|
return true;
|
|
case Member::InstanceVariableMember:
|
|
case Member::InstanceMethodMember:
|
|
case Member::InstanceGetterMember:
|
|
case Member::InstanceSetterMember:
|
|
if ( (!JS2VAL_IS_OBJECT(base) || (JS2VAL_TO_OBJECT(base)->kind != ClassKind)) || (env == NULL)) {
|
|
*result = false;
|
|
return true;
|
|
}
|
|
env->readImplicitThis(meta);
|
|
*result = false;
|
|
return true;
|
|
default:
|
|
NOT_REACHED("bad member kind");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool defaultBracketDelete(JS2Metadata *meta, js2val base, JS2Class *limit, js2val indexVal, bool *result)
|
|
{
|
|
const String *indexStr = meta->toString(indexVal);
|
|
DEFINE_ROOTKEEPER(rk, indexStr);
|
|
Multiname *mn = new Multiname(indexStr, meta->publicNamespace);
|
|
DEFINE_ROOTKEEPER(rk1, mn);
|
|
return limit->deleteProperty(meta, base, limit, mn, NULL, result);
|
|
}
|
|
|
|
js2val defaultImplicitCoerce(JS2Metadata *meta, js2val newValue, JS2Class *isClass)
|
|
{
|
|
if (JS2VAL_IS_NULL(newValue) || meta->objectType(newValue)->isAncestor(isClass) )
|
|
return newValue;
|
|
meta->reportError(Exception::badValueError, "Illegal coercion", meta->engine->errorPos());
|
|
return JS2VAL_VOID;
|
|
}
|
|
|
|
js2val integerImplicitCoerce(JS2Metadata *meta, js2val newValue, JS2Class *isClass)
|
|
{
|
|
if (JS2VAL_IS_UNDEFINED(newValue))
|
|
return JS2VAL_ZERO;
|
|
if (JS2VAL_IS_NUMBER(newValue)) {
|
|
int64 x = meta->engine->checkInteger(newValue);
|
|
if (JSLL_IS_INT32(x)) {
|
|
int32 i = 0;
|
|
JSLL_L2I(i, x);
|
|
return INT_TO_JS2VAL(i);
|
|
}
|
|
}
|
|
meta->reportError(Exception::badValueError, "Illegal coercion", meta->engine->errorPos());
|
|
return JS2VAL_VOID;
|
|
}
|
|
|
|
js2val defaultIs(JS2Metadata *meta, js2val newValue, JS2Class *isClass)
|
|
{
|
|
return BOOLEAN_TO_JS2VAL(meta->objectType(newValue) == isClass);
|
|
}
|
|
|
|
js2val integerIs(JS2Metadata *meta, js2val newValue, JS2Class *isClass)
|
|
{
|
|
bool result = false;
|
|
if (JS2VAL_IS_NUMBER(newValue)) {
|
|
float64 f = meta->toFloat64(newValue);
|
|
if (JSDOUBLE_IS_FINITE(f)) {
|
|
|
|
}
|
|
}
|
|
return BOOLEAN_TO_JS2VAL(result);
|
|
}
|
|
|
|
bool stringClass_BracketRead(JS2Metadata *meta, js2val *base, JS2Class *limit, js2val indexVal, Phase phase, js2val *rval)
|
|
{
|
|
if (JS2VAL_IS_INT(indexVal)) {
|
|
const String *str = NULL;
|
|
if (JS2VAL_IS_STRING(*base)) {
|
|
str = JS2VAL_TO_STRING(*base);
|
|
}
|
|
else {
|
|
ASSERT(JS2VAL_IS_OBJECT(*base));
|
|
JS2Object *obj = JS2VAL_TO_OBJECT(*base);
|
|
ASSERT((obj->kind == SimpleInstanceKind) && (checked_cast<SimpleInstance *>(obj)->type == meta->stringClass));
|
|
StringInstance *a = checked_cast<StringInstance *>(obj);
|
|
str = a->mValue;
|
|
}
|
|
int32 i = JS2VAL_TO_INT(indexVal);
|
|
if ((i >= 0) && (i < str->length()))
|
|
*rval = meta->engine->allocString(&(*str)[i], 1);
|
|
else
|
|
*rval = JS2VAL_UNDEFINED;
|
|
return true;
|
|
}
|
|
else
|
|
return defaultBracketRead(meta, base, limit, indexVal, phase, rval);
|
|
}
|
|
|
|
|
|
|
|
}; // namespace MetaData
|
|
}; // namespace Javascript
|
|
|
|
|
|
|