3726 lines
141 KiB
C++
3726 lines
141 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public
|
|
* License Version 1.1 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS
|
|
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
|
|
* implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code is the JavaScript 2 Prototype.
|
|
*
|
|
* The Initial Developer of the Original Code is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All
|
|
* Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
* Alternatively, the contents of this file may be used under the
|
|
* terms of the GNU Public License (the "GPL"), in which case the
|
|
* provisions of the GPL are applicable instead of those above.
|
|
* If you wish to allow use of your version of this file only
|
|
* under the terms of the GPL and not to allow others to use your
|
|
* version of this file under the NPL, indicate your decision by
|
|
* deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this
|
|
* file under either the NPL or the GPL.
|
|
*/
|
|
|
|
|
|
#ifdef _WIN32
|
|
// Turn off warnings about identifiers too long in browser information
|
|
#pragma warning(disable: 4786)
|
|
#pragma warning(disable: 4711)
|
|
#pragma warning(disable: 4710)
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <algorithm>
|
|
|
|
#include "parser.h"
|
|
#include "numerics.h"
|
|
#include "js2runtime.h"
|
|
#include "bytecodegen.h"
|
|
|
|
#include "jsstring.h"
|
|
#include "jsarray.h"
|
|
#include "jsmath.h"
|
|
#include "jsdate.h"
|
|
|
|
#include "fdlibm_ns.h"
|
|
|
|
|
|
// this is the AttributeList passed to the name lookup routines
|
|
#define CURRENT_ATTR (NULL)
|
|
|
|
namespace JavaScript {
|
|
namespace JS2Runtime {
|
|
|
|
|
|
//
|
|
// XXX don't these belong in the context? But don't
|
|
// they need to compare equal across contexts?
|
|
//
|
|
JSType *Object_Type = NULL;
|
|
JSType *Number_Type;
|
|
JSType *Integer_Type;
|
|
JSType *Function_Type;
|
|
JSStringType *String_Type;
|
|
JSType *Boolean_Type;
|
|
JSType *Type_Type;
|
|
JSType *Void_Type;
|
|
JSType *Null_Type;
|
|
JSType *Unit_Type;
|
|
JSType *Attribute_Type;
|
|
|
|
JSType *Package_Type;
|
|
JSType *NamedArgument_Type;
|
|
JSArrayType *Array_Type;
|
|
JSType *Date_Type;
|
|
JSType *RegExp_Type;
|
|
JSType *Error_Type;
|
|
JSType *EvalError_Type;
|
|
JSType *RangeError_Type;
|
|
JSType *ReferenceError_Type;
|
|
JSType *SyntaxError_Type;
|
|
JSType *TypeError_Type;
|
|
JSType *UriError_Type;
|
|
|
|
|
|
Attribute *Context::executeAttributes(ExprNode *attr)
|
|
{
|
|
ASSERT(attr);
|
|
|
|
ByteCodeGen bcg(this, mScopeChain);
|
|
ByteCodeModule *bcm = bcg.genCodeForExpression(attr);
|
|
// stdOut << *bcm;
|
|
JSValue result = interpret(bcm, 0, NULL, JSValue(getGlobalObject()), NULL, 0);
|
|
|
|
ASSERT(result.isAttribute());
|
|
return result.attribute;
|
|
}
|
|
|
|
|
|
// Find a property with the given name, but make sure it's in
|
|
// the supplied namespace. XXX speed up! XXX
|
|
//
|
|
PropertyIterator JSObject::findNamespacedProperty(const String &name, NamespaceList *names)
|
|
{
|
|
for (PropertyIterator i = mProperties.lower_bound(name),
|
|
end = mProperties.upper_bound(name); (i != end); i++) {
|
|
NamespaceList *propNames = PROPERTY_NAMESPACELIST(i);
|
|
if (names) {
|
|
if (propNames == NULL)
|
|
continue; // a namespace list was specified, no match
|
|
while (names) {
|
|
NamespaceList *propNameEntry = propNames;
|
|
while (propNameEntry) {
|
|
if (names->mName == propNameEntry->mName)
|
|
return i;
|
|
propNameEntry = propNameEntry->mNext;
|
|
}
|
|
names = names->mNext;
|
|
}
|
|
}
|
|
else {
|
|
if (propNames) // entry is in a namespace, but none called for, no match
|
|
continue;
|
|
return i;
|
|
}
|
|
}
|
|
return mProperties.end();
|
|
}
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// see if the property exists by a specific kind of access
|
|
bool JSObject::hasOwnProperty(Context * /*cx*/, const String &name, NamespaceList *names, Access acc, PropertyIterator *p)
|
|
{
|
|
*p = findNamespacedProperty(name, names);
|
|
if (*p != mProperties.end()) {
|
|
Property *prop = PROPERTY(*p);
|
|
if (prop->mFlag == FunctionPair)
|
|
return (acc == Read) ? (prop->mData.fPair.getterF != NULL)
|
|
: (prop->mData.fPair.setterF != NULL);
|
|
else
|
|
if (prop->mFlag == IndexPair)
|
|
return (acc == Read) ? (prop->mData.iPair.getterI != toUInt32(-1))
|
|
: (prop->mData.iPair.setterI != toUInt32(-1));
|
|
else
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool JSObject::hasProperty(Context *cx, const String &name, NamespaceList *names, Access acc, PropertyIterator *p)
|
|
{
|
|
if (hasOwnProperty(cx, name, names, acc, p))
|
|
return true;
|
|
else
|
|
if (!mPrototype.isNull())
|
|
return mPrototype.getObjectValue()->hasProperty(cx, name, names, acc, p);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool JSObject::deleteProperty(Context * /*cx*/, const String &name, NamespaceList *names)
|
|
{
|
|
PropertyIterator i = findNamespacedProperty(name, names);
|
|
if (i != mProperties.end()) {
|
|
if ((PROPERTY_ATTR(i) & Property::DontDelete) == 0) {
|
|
mProperties.erase(i);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
// get a property value
|
|
JSValue JSObject::getPropertyValue(PropertyIterator &i)
|
|
{
|
|
Property *prop = PROPERTY(i);
|
|
ASSERT(prop->mFlag == ValuePointer);
|
|
return *prop->mData.vp;
|
|
}
|
|
|
|
Property *JSObject::insertNewProperty(const String &name, NamespaceList *names, PropertyAttribute attrFlags, JSType *type, const JSValue &v)
|
|
{
|
|
Property *prop = new Property(new JSValue(v), type, attrFlags);
|
|
const PropertyMap::value_type e(name, new NamespacedProperty(prop, names));
|
|
mProperties.insert(e);
|
|
return prop;
|
|
}
|
|
|
|
void JSObject::defineGetterMethod(Context *cx, const String &name, AttributeStmtNode *attr, JSFunction *f)
|
|
{
|
|
NamespaceList *names = (attr) ? attr->attributeValue->mNamespaceList : NULL;
|
|
PropertyAttribute attrFlags = (attr) ? attr->attributeValue->mTrueFlags : 0;
|
|
PropertyIterator i;
|
|
if (hasProperty(cx, name, names, Write, &i)) {
|
|
ASSERT(PROPERTY_KIND(i) == FunctionPair);
|
|
ASSERT(PROPERTY_GETTERF(i) == NULL);
|
|
PROPERTY_GETTERF(i) = f;
|
|
}
|
|
else {
|
|
const PropertyMap::value_type e(name, new NamespacedProperty(new Property(Function_Type, f, NULL, attrFlags), names));
|
|
mProperties.insert(e);
|
|
}
|
|
}
|
|
void JSObject::defineSetterMethod(Context *cx, const String &name, AttributeStmtNode *attr, JSFunction *f)
|
|
{
|
|
NamespaceList *names = (attr) ? attr->attributeValue->mNamespaceList : NULL;
|
|
PropertyAttribute attrFlags = (attr) ? attr->attributeValue->mTrueFlags : 0;
|
|
PropertyIterator i;
|
|
if (hasProperty(cx, name, names, Read, &i)) {
|
|
ASSERT(PROPERTY_KIND(i) == FunctionPair);
|
|
ASSERT(PROPERTY_SETTERF(i) == NULL);
|
|
PROPERTY_SETTERF(i) = f;
|
|
}
|
|
else {
|
|
const PropertyMap::value_type e(name, new NamespacedProperty(new Property(Function_Type, NULL, f, attrFlags), names));
|
|
mProperties.insert(e);
|
|
}
|
|
}
|
|
|
|
uint32 JSObject::tempVarCount = 0;
|
|
|
|
void JSObject::defineTempVariable(Context *cx, Reference *&readRef, Reference *&writeRef, JSType *type)
|
|
{
|
|
char buf[32];
|
|
sprintf(buf, "%%tempvar%%_%d", tempVarCount++);
|
|
const String &name = cx->mWorld.identifiers[buf];
|
|
/* Property *prop = */defineVariable(cx, name, (NamespaceList *)NULL, Property::NoAttribute, type);
|
|
readRef = new NameReference(name, NULL, Read, Object_Type, 0);
|
|
writeRef = new NameReference(name, NULL, Write, Object_Type, 0);
|
|
}
|
|
|
|
|
|
// add a property
|
|
Property *JSObject::defineVariable(Context *cx, const String &name, AttributeStmtNode *attr, JSType *type)
|
|
{
|
|
NamespaceList *names = (attr) ? attr->attributeValue->mNamespaceList : NULL;
|
|
PropertyAttribute attrFlags = (attr) ? attr->attributeValue->mTrueFlags : 0;
|
|
PropertyIterator it;
|
|
if (hasOwnProperty(cx, name, names, Read, &it)) {
|
|
// not a problem if neither are consts
|
|
if ((attrFlags & Property::Const)
|
|
|| (PROPERTY_ATTR(it) & Property::Const)) {
|
|
if (attr)
|
|
cx->reportError(Exception::typeError, "Duplicate definition '{0}'", attr->pos, name);
|
|
else
|
|
cx->reportError(Exception::typeError, "Duplicate definition '{0}'", name);
|
|
}
|
|
}
|
|
|
|
Property *prop = new Property(new JSValue(), type, attrFlags);
|
|
const PropertyMap::value_type e(name, new NamespacedProperty(prop, names));
|
|
mProperties.insert(e);
|
|
return prop;
|
|
}
|
|
Property *JSObject::defineVariable(Context *cx, const String &name, NamespaceList *names, PropertyAttribute attrFlags, JSType *type)
|
|
{
|
|
PropertyIterator it;
|
|
if (hasOwnProperty(cx, name, names, Read, &it)) {
|
|
// not a problem if neither are consts
|
|
if (PROPERTY_ATTR(it) & Property::Const) {
|
|
cx->reportError(Exception::typeError, "Duplicate definition '{0}'", name);
|
|
}
|
|
}
|
|
|
|
Property *prop = new Property(new JSValue(), type, attrFlags);
|
|
const PropertyMap::value_type e(name, new NamespacedProperty(prop, names));
|
|
mProperties.insert(e);
|
|
return prop;
|
|
}
|
|
|
|
// add a property (with a value)
|
|
Property *JSObject::defineVariable(Context *cx, const String &name, AttributeStmtNode *attr, JSType *type, const JSValue v)
|
|
{
|
|
NamespaceList *names = (attr) ? attr->attributeValue->mNamespaceList : NULL;
|
|
PropertyAttribute attrFlags = (attr) ? attr->attributeValue->mTrueFlags : 0;
|
|
PropertyIterator it;
|
|
if (hasOwnProperty(cx, name, names, Read, &it)) {
|
|
// not a problem if neither are consts
|
|
if ((attrFlags & Property::Const)
|
|
|| (PROPERTY_ATTR(it) & Property::Const)) {
|
|
if (attr)
|
|
cx->reportError(Exception::typeError, "Duplicate definition '{0}'", attr->pos, name);
|
|
else
|
|
cx->reportError(Exception::typeError, "Duplicate definition '{0}'", name);
|
|
}
|
|
else {
|
|
// override the existing value
|
|
PROPERTY_VALUEPOINTER(it) = new JSValue(v);
|
|
return PROPERTY(it);
|
|
}
|
|
}
|
|
|
|
Property *prop = new Property(new JSValue(v), type, attrFlags);
|
|
const PropertyMap::value_type e(name, new NamespacedProperty(prop, names));
|
|
mProperties.insert(e);
|
|
return prop;
|
|
}
|
|
Property *JSObject::defineVariable(Context *cx, const String &name, NamespaceList *names, PropertyAttribute attrFlags, JSType *type, const JSValue v)
|
|
{
|
|
PropertyIterator it;
|
|
if (hasOwnProperty(cx, name, names, Read, &it)) {
|
|
if (PROPERTY_ATTR(it) & Property::Const) {
|
|
cx->reportError(Exception::typeError, "Duplicate definition '{0}'", name);
|
|
}
|
|
}
|
|
|
|
Property *prop = new Property(new JSValue(v), type, attrFlags);
|
|
const PropertyMap::value_type e(name, new NamespacedProperty(prop, names));
|
|
mProperties.insert(e);
|
|
return prop;
|
|
}
|
|
Property *JSObject::defineAlias(Context * /*cx*/, const String &name, NamespaceList *names, PropertyAttribute attrFlags, JSType *type, JSValue *vp)
|
|
|
|
{
|
|
|
|
Property *prop = new Property(vp, type, attrFlags);
|
|
|
|
const PropertyMap::value_type e(name, new NamespacedProperty(prop, names));
|
|
|
|
mProperties.insert(e);
|
|
|
|
return prop;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Reference *JSObject::genReference(Context *cx, bool hasBase, const String& name, NamespaceList *names, Access acc, uint32 /*depth*/)
|
|
{
|
|
PropertyIterator i;
|
|
if (hasProperty(cx, name, names, acc, &i)) {
|
|
Property *prop = PROPERTY(i);
|
|
switch (prop->mFlag) {
|
|
case ValuePointer:
|
|
if (hasBase)
|
|
return new PropertyReference(name, names, acc, prop->mType, prop->mAttributes);
|
|
else
|
|
return new NameReference(name, names, acc, prop->mType, prop->mAttributes);
|
|
case FunctionPair:
|
|
if (acc == Read)
|
|
return new GetterFunctionReference(prop->mData.fPair.getterF, prop->mAttributes);
|
|
else {
|
|
JSFunction *f = prop->mData.fPair.setterF;
|
|
return new SetterFunctionReference(f, f->getParameterType(0), prop->mAttributes);
|
|
}
|
|
default:
|
|
NOT_REACHED("bad storage kind");
|
|
return NULL;
|
|
}
|
|
}
|
|
NOT_REACHED("bad genRef call");
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void JSObject::getProperty(Context *cx, const String &name, NamespaceList *names)
|
|
{
|
|
PropertyIterator i;
|
|
if (hasOwnProperty(cx, name, names, Read, &i)) {
|
|
Property *prop = PROPERTY(i);
|
|
switch (prop->mFlag) {
|
|
case ValuePointer:
|
|
cx->pushValue(*prop->mData.vp);
|
|
break;
|
|
case FunctionPair:
|
|
cx->pushValue(cx->invokeFunction(prop->mData.fPair.getterF, JSValue(this), NULL, 0));
|
|
break;
|
|
default:
|
|
ASSERT(false); // XXX more to implement
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
if (!mPrototype.isNull())
|
|
mPrototype.getObjectValue()->getProperty(cx, name, names);
|
|
else
|
|
cx->pushValue(kUndefinedValue);
|
|
}
|
|
}
|
|
|
|
|
|
void JSType::getProperty(Context *cx, const String &name, NamespaceList *names)
|
|
{
|
|
PropertyIterator i;
|
|
if (hasOwnProperty(cx, name, names, Read, &i))
|
|
JSObject::getProperty(cx, name, names);
|
|
else
|
|
if (mSuperType)
|
|
mSuperType->getProperty(cx, name, names);
|
|
else
|
|
JSObject::getProperty(cx, name, names);
|
|
}
|
|
|
|
void JSInstance::getProperty(Context *cx, const String &name, NamespaceList *names)
|
|
{
|
|
PropertyIterator i;
|
|
if (hasOwnProperty(cx, name, names, Read, &i)) {
|
|
Property *prop = PROPERTY(i);
|
|
switch (prop->mFlag) {
|
|
case Slot:
|
|
cx->pushValue(mInstanceValues[prop->mData.index]);
|
|
break;
|
|
case ValuePointer:
|
|
cx->pushValue(*prop->mData.vp);
|
|
break;
|
|
case FunctionPair:
|
|
cx->pushValue(cx->invokeFunction(prop->mData.fPair.getterF, JSValue(this), NULL, 0));
|
|
break;
|
|
case Constructor:
|
|
case Method:
|
|
cx->pushValue(JSValue(mType->mMethods[prop->mData.index]));
|
|
break;
|
|
case IndexPair:
|
|
cx->pushValue(cx->invokeFunction(mType->mMethods[prop->mData.iPair.getterI], JSValue(this), NULL, 0));
|
|
break;
|
|
default:
|
|
ASSERT(false); // XXX more to implement
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
/*
|
|
XXX this path allows an instance to access static fields of it's type
|
|
if (mType->hasOwnProperty(cx, name, names, Read, &i))
|
|
mType->getProperty(cx, name, names);
|
|
else
|
|
*/
|
|
JSObject::getProperty(cx, name, names);
|
|
}
|
|
|
|
void JSObject::setProperty(Context *cx, const String &name, NamespaceList *names, const JSValue &v)
|
|
{
|
|
PropertyIterator i;
|
|
if (hasOwnProperty(cx, name, names, Write, &i)) {
|
|
if (PROPERTY_ATTR(i) & Property::ReadOnly) return;
|
|
Property *prop = PROPERTY(i);
|
|
switch (prop->mFlag) {
|
|
case ValuePointer:
|
|
if (name.compare(cx->UnderbarPrototype_StringAtom) == 0)
|
|
mPrototype = v;
|
|
*prop->mData.vp = v;
|
|
break;
|
|
case FunctionPair:
|
|
{
|
|
JSValue argv = v;
|
|
cx->invokeFunction(prop->mData.fPair.setterF, JSValue(this), &argv, 1);
|
|
}
|
|
break;
|
|
default:
|
|
ASSERT(false); // XXX more to implement ?
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
defineVariable(cx, name, names, Property::Enumerable, Object_Type, v);
|
|
}
|
|
|
|
void JSType::setProperty(Context *cx, const String &name, NamespaceList *names, const JSValue &v)
|
|
{
|
|
PropertyIterator i;
|
|
if (hasOwnProperty(cx, name, names, Read, &i))
|
|
JSObject::setProperty(cx, name, names, v);
|
|
else
|
|
if (mSuperType)
|
|
mSuperType->setProperty(cx, name, names, v);
|
|
else
|
|
JSObject::setProperty(cx, name, names, v);
|
|
}
|
|
|
|
void JSInstance::setProperty(Context *cx, const String &name, NamespaceList *names, const JSValue &v)
|
|
{
|
|
PropertyIterator i;
|
|
if (hasOwnProperty(cx, name, names, Write, &i)) {
|
|
if (PROPERTY_ATTR(i) & Property::ReadOnly) return;
|
|
Property *prop = PROPERTY(i);
|
|
switch (prop->mFlag) {
|
|
case Slot:
|
|
mInstanceValues[prop->mData.index] = v;
|
|
break;
|
|
case ValuePointer:
|
|
*prop->mData.vp = v;
|
|
break;
|
|
case FunctionPair:
|
|
{
|
|
JSValue argv = v;
|
|
cx->invokeFunction(prop->mData.fPair.setterF, JSValue(this), &argv, 1);
|
|
}
|
|
break;
|
|
case IndexPair:
|
|
{
|
|
JSValue argv = v;
|
|
cx->invokeFunction(mType->mMethods[prop->mData.iPair.setterI], JSValue(this), &argv, 1);
|
|
}
|
|
break;
|
|
default:
|
|
ASSERT(false); // XXX more to implement ?
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
// the instance doesn't have this property, see if
|
|
// the type does...
|
|
if (mType->hasOwnProperty(cx, name, names, Write, &i))
|
|
mType->setProperty(cx, name, names, v);
|
|
else
|
|
defineVariable(cx, name, names, Property::Enumerable, Object_Type, v);
|
|
}
|
|
}
|
|
|
|
void JSArrayInstance::getProperty(Context *cx, const String &name, NamespaceList *names)
|
|
{
|
|
if (name.compare(cx->Length_StringAtom) == 0)
|
|
cx->pushValue(JSValue((float64)mLength));
|
|
else
|
|
JSInstance::getProperty(cx, name, names);
|
|
}
|
|
|
|
void JSArrayInstance::setProperty(Context *cx, const String &name, NamespaceList *names, const JSValue &v)
|
|
{
|
|
if (name.compare(cx->Length_StringAtom) == 0) {
|
|
uint32 newLength = (uint32)(v.toUInt32(cx).f64);
|
|
if (newLength != v.toNumber(cx).f64)
|
|
cx->reportError(Exception::rangeError, "out of range value for length");
|
|
|
|
for (uint32 i = newLength; i < mLength; i++) {
|
|
const String *id = numberToString(i);
|
|
if (findNamespacedProperty(*id, NULL) != mProperties.end())
|
|
deleteProperty(cx, *id, NULL);
|
|
delete id;
|
|
}
|
|
|
|
mLength = newLength;
|
|
}
|
|
else {
|
|
PropertyIterator it = findNamespacedProperty(name, names);
|
|
if (it == mProperties.end())
|
|
insertNewProperty(name, names, Property::Enumerable, Object_Type, v);
|
|
else {
|
|
Property *prop = PROPERTY(it);
|
|
ASSERT(prop->mFlag == ValuePointer);
|
|
*prop->mData.vp = v;
|
|
}
|
|
JSValue v = JSValue(&name);
|
|
JSValue v_int = v.toUInt32(cx);
|
|
if ((v_int.f64 != two32minus1) && (v_int.toString(cx).string->compare(name) == 0)) {
|
|
if (v_int.f64 >= mLength)
|
|
mLength = (uint32)(v_int.f64) + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool JSArrayInstance::hasOwnProperty(Context *cx, const String &name, NamespaceList *names, Access acc, PropertyIterator *p)
|
|
{
|
|
if (name.compare(cx->Length_StringAtom) == 0)
|
|
return true;
|
|
else
|
|
return JSInstance::hasOwnProperty(cx, name, names, acc, p);
|
|
}
|
|
|
|
bool JSArrayInstance::deleteProperty(Context *cx, const String &name, NamespaceList *names)
|
|
{
|
|
if (name.compare(cx->Length_StringAtom) == 0)
|
|
return false;
|
|
else
|
|
return JSInstance::deleteProperty(cx, name, names);
|
|
}
|
|
|
|
|
|
// get a named property from a string instance, but intercept
|
|
// 'length' by returning the known value
|
|
void JSStringInstance::getProperty(Context *cx, const String &name, NamespaceList *names)
|
|
{
|
|
if (name.compare(cx->Length_StringAtom) == 0)
|
|
cx->pushValue(JSValue((float64)(mValue->size())));
|
|
else
|
|
JSInstance::getProperty(cx, name, names);
|
|
}
|
|
|
|
void JSStringInstance::setProperty(Context *cx, const String &name, NamespaceList *names, const JSValue &v)
|
|
{
|
|
if (name.compare(cx->Length_StringAtom) == 0) {
|
|
}
|
|
else
|
|
JSInstance::setProperty(cx, name, names, v);
|
|
}
|
|
|
|
bool JSStringInstance::hasOwnProperty(Context *cx, const String &name, NamespaceList *names, Access acc, PropertyIterator *p)
|
|
{
|
|
if (name.compare(cx->Length_StringAtom) == 0)
|
|
return true;
|
|
else
|
|
return JSInstance::hasOwnProperty(cx, name, names, acc, p);
|
|
}
|
|
|
|
bool JSStringInstance::deleteProperty(Context *cx, const String &name, NamespaceList *names)
|
|
{
|
|
if (name.compare(cx->Length_StringAtom) == 0)
|
|
return false;
|
|
else
|
|
return JSInstance::deleteProperty(cx, name, names);
|
|
}
|
|
|
|
|
|
// construct an instance of a type
|
|
// - allocate memory for the slots, load the instance variable names into the
|
|
// property map - in order to provide dynamic access to those properties.
|
|
void JSInstance::initInstance(Context *cx, JSType *type)
|
|
{
|
|
if (type->mVariableCount)
|
|
mInstanceValues = new JSValue[type->mVariableCount];
|
|
|
|
// copy the instance variable names into the property map
|
|
for (PropertyIterator pi = type->mProperties.begin(),
|
|
end = type->mProperties.end();
|
|
(pi != end); pi++) {
|
|
if (PROPERTY_KIND(pi) == Slot) {
|
|
const PropertyMap::value_type e(PROPERTY_NAME(pi), NAMESPACED_PROPERTY(pi));
|
|
mProperties.insert(e);
|
|
}
|
|
}
|
|
|
|
// and then do the same for the super types
|
|
JSType *t = type->mSuperType;
|
|
while (t) {
|
|
for (PropertyIterator i = t->mProperties.begin(),
|
|
end = t->mProperties.end();
|
|
(i != end); i++) {
|
|
if (PROPERTY_KIND(i) == Slot) {
|
|
const PropertyMap::value_type e(PROPERTY_NAME(i), NAMESPACED_PROPERTY(i));
|
|
mProperties.insert(e);
|
|
}
|
|
}
|
|
if (t->mInstanceInitializer)
|
|
cx->invokeFunction(t->mInstanceInitializer, JSValue(this), NULL, 0);
|
|
t = t->mSuperType;
|
|
}
|
|
|
|
// run the initializer
|
|
if (type->mInstanceInitializer) {
|
|
cx->invokeFunction(type->mInstanceInitializer, JSValue(this), NULL, 0);
|
|
}
|
|
|
|
mType = type;
|
|
}
|
|
|
|
// Create a new (empty) instance of this class. The prototype
|
|
// link for this new instance is established from the type's
|
|
// prototype object.
|
|
JSValue JSType::newInstance(Context *cx)
|
|
{
|
|
JSInstance *result = new JSInstance(cx, this);
|
|
result->mPrototype = mPrototypeObject;
|
|
result->defineVariable(cx, cx->UnderbarPrototype_StringAtom, NULL, 0, Object_Type, mPrototypeObject);
|
|
return JSValue(result);
|
|
}
|
|
|
|
JSValue JSObjectType::newInstance(Context *cx)
|
|
{
|
|
JSObject *result = new JSObject();
|
|
result->mPrototype = mPrototypeObject;
|
|
result->defineVariable(cx, cx->UnderbarPrototype_StringAtom, NULL, 0, Object_Type, mPrototypeObject);
|
|
return JSValue(result);
|
|
}
|
|
|
|
|
|
|
|
// the function 'f' gets executed each time a new instance is created
|
|
void JSType::setInstanceInitializer(Context * /*cx*/, JSFunction *f)
|
|
{
|
|
mInstanceInitializer = f;
|
|
}
|
|
|
|
// Run the static initializer against this type
|
|
void JSType::setStaticInitializer(Context *cx, JSFunction *f)
|
|
{
|
|
if (f)
|
|
cx->interpret(f->getByteCode(), 0, f->getScopeChain(), JSValue(this), NULL, 0);
|
|
}
|
|
|
|
Property *JSType::defineVariable(Context *cx, const String& name, AttributeStmtNode *attr, JSType *type)
|
|
{
|
|
NamespaceList *names = (attr) ? attr->attributeValue->mNamespaceList : NULL;
|
|
PropertyAttribute attrFlags = (attr) ? attr->attributeValue->mTrueFlags : 0;
|
|
PropertyIterator it;
|
|
if (hasOwnProperty(cx, name, names, Read, &it)) {
|
|
/*
|
|
XXX error for classes, right? but not for local variables under what circumstances (hoisting impact?)
|
|
if (attr)
|
|
cx->reportError(Exception::typeError, "Duplicate definition '{0}'", attr->pos, name);
|
|
else
|
|
cx->reportError(Exception::typeError, "Duplicate definition '{0}'", name);
|
|
*/
|
|
}
|
|
Property *prop = new Property(mVariableCount++, type, Slot, attrFlags);
|
|
const PropertyMap::value_type e(name, new NamespacedProperty(prop, names));
|
|
mProperties.insert(e);
|
|
return prop;
|
|
}
|
|
|
|
JSValue JSArrayType::newInstance(Context *cx)
|
|
{
|
|
JSInstance *result = new JSArrayInstance(cx);
|
|
result->mPrototype = mPrototypeObject;
|
|
result->defineVariable(cx, cx->UnderbarPrototype_StringAtom, NULL, 0, Object_Type, mPrototypeObject);
|
|
return JSValue(result);
|
|
}
|
|
|
|
JSValue JSStringType::newInstance(Context *cx)
|
|
{
|
|
JSInstance *result = new JSStringInstance(cx);
|
|
result->mPrototype = mPrototypeObject;
|
|
result->defineVariable(cx, cx->UnderbarPrototype_StringAtom, NULL, 0, Object_Type, mPrototypeObject);
|
|
return JSValue(result);
|
|
}
|
|
|
|
JSValue JSBooleanType::newInstance(Context *cx)
|
|
{
|
|
JSInstance *result = new JSBooleanInstance(cx);
|
|
result->mPrototype = mPrototypeObject;
|
|
result->defineVariable(cx, cx->UnderbarPrototype_StringAtom, NULL, 0, Object_Type, mPrototypeObject);
|
|
return JSValue(result);
|
|
}
|
|
|
|
JSValue JSRegExpType::newInstance(Context *cx)
|
|
{
|
|
JSInstance *result = new JSRegExpInstance(cx);
|
|
result->mPrototype = mPrototypeObject;
|
|
result->defineVariable(cx, cx->UnderbarPrototype_StringAtom, NULL, 0, Object_Type, mPrototypeObject);
|
|
return JSValue(result);
|
|
}
|
|
|
|
JSValue JSDateType::newInstance(Context *cx)
|
|
{
|
|
JSInstance *result = new JSDateInstance(cx);
|
|
result->mPrototype = mPrototypeObject;
|
|
result->defineVariable(cx, cx->UnderbarPrototype_StringAtom, NULL, 0, Object_Type, mPrototypeObject);
|
|
return JSValue(result);
|
|
}
|
|
|
|
JSValue JSNumberType::newInstance(Context *cx)
|
|
{
|
|
JSInstance *result = new JSNumberInstance(cx);
|
|
result->mPrototype = mPrototypeObject;
|
|
result->defineVariable(cx, cx->UnderbarPrototype_StringAtom, NULL, 0, Object_Type, mPrototypeObject);
|
|
return JSValue(result);
|
|
}
|
|
|
|
// don't add to instances etc., climb all the way down (likely to the global object)
|
|
// and add the property there.
|
|
void ScopeChain::setNameValue(Context *cx, const String& name, NamespaceList *names)
|
|
{
|
|
JSValue v = cx->topValue();
|
|
for (ScopeScanner s = mScopeStack.rbegin(), end = mScopeStack.rend(); (s != end); s++)
|
|
{
|
|
PropertyIterator i;
|
|
if ((*s)->hasProperty(cx, name, names, Write, &i)) {
|
|
PropertyFlag flag = PROPERTY_KIND(i);
|
|
switch (flag) {
|
|
case ValuePointer:
|
|
*PROPERTY_VALUEPOINTER(i) = v;
|
|
break;
|
|
case Slot:
|
|
(*s)->setSlotValue(cx, PROPERTY_INDEX(i), v);
|
|
break;
|
|
default:
|
|
ASSERT(false); // what else needs to be implemented ?
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
cx->getGlobalObject()->defineVariable(cx, name, names, 0, Object_Type, v);
|
|
}
|
|
|
|
bool ScopeChain::deleteName(Context *cx, const String& name, NamespaceList *names)
|
|
{
|
|
for (ScopeScanner s = mScopeStack.rbegin(), end = mScopeStack.rend(); (s != end); s++)
|
|
{
|
|
PropertyIterator i;
|
|
if ((*s)->hasOwnProperty(cx, name, names, Read, &i))
|
|
return (*s)->deleteProperty(cx, name, names);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
inline char narrow(char16 ch) { return char(ch); }
|
|
|
|
JSObject *ScopeChain::getNameValue(Context *cx, const String& name, NamespaceList *names)
|
|
{
|
|
uint32 depth = 0;
|
|
for (ScopeScanner s = mScopeStack.rbegin(), end = mScopeStack.rend(); (s != end); s++, depth++)
|
|
{
|
|
PropertyIterator i;
|
|
if ((*s)->hasProperty(cx, name, names, Read, &i)) {
|
|
PropertyFlag flag = PROPERTY_KIND(i);
|
|
switch (flag) {
|
|
case ValuePointer:
|
|
cx->pushValue(*PROPERTY_VALUEPOINTER(i));
|
|
break;
|
|
case Slot:
|
|
cx->pushValue((*s)->getSlotValue(cx, PROPERTY_INDEX(i)));
|
|
break;
|
|
default:
|
|
ASSERT(false); // what else needs to be implemented ?
|
|
}
|
|
return *s;
|
|
}
|
|
}
|
|
m_cx->reportError(Exception::referenceError, "'{0}' not defined", name );
|
|
return NULL;
|
|
}
|
|
|
|
// it'd be much better if the property iterator returned by hasProperty could be
|
|
// used by the genReference call
|
|
Reference *ScopeChain::getName(Context *cx, const String& name, NamespaceList *names, Access acc)
|
|
{
|
|
uint32 depth = 0;
|
|
for (ScopeScanner s = mScopeStack.rbegin(), end = mScopeStack.rend(); (s != end); s++, depth++)
|
|
{
|
|
PropertyIterator i;
|
|
if ((*s)->hasProperty(cx, name, names, acc, &i))
|
|
return (*s)->genReference(cx, false, name, names, acc, depth);
|
|
else
|
|
if ((*s)->isDynamic())
|
|
return NULL;
|
|
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
bool ScopeChain::hasNameValue(Context *cx, const String& name, NamespaceList *names)
|
|
{
|
|
uint32 depth = 0;
|
|
for (ScopeScanner s = mScopeStack.rbegin(), end = mScopeStack.rend(); (s != end); s++, depth++)
|
|
{
|
|
PropertyIterator i;
|
|
if ((*s)->hasProperty(cx, name, names, Read, &i))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// a compile time request to get the value for a name
|
|
// (i.e. we're accessing a constant value)
|
|
JSValue ScopeChain::getCompileTimeValue(Context *cx, const String& name, NamespaceList *names)
|
|
{
|
|
uint32 depth = 0;
|
|
for (ScopeScanner s = mScopeStack.rbegin(), end = mScopeStack.rend(); (s != end); s++, depth++)
|
|
{
|
|
PropertyIterator i;
|
|
if ((*s)->hasProperty(cx, name, names, Read, &i))
|
|
return (*s)->getPropertyValue(i);
|
|
}
|
|
return kUndefinedValue;
|
|
}
|
|
|
|
|
|
// in the case of duplicate parameter names, pick the last one
|
|
// XXX does the namespace handling make any sense here? Can parameters be in a namespace?
|
|
Reference *ParameterBarrel::genReference(Context *cx, bool /* hasBase */, const String& name, NamespaceList *names, Access acc, uint32 /*depth*/)
|
|
{
|
|
Property *selectedProp = NULL;
|
|
for (PropertyIterator i = mProperties.lower_bound(name),
|
|
end = mProperties.upper_bound(name); (i != end); i++) {
|
|
NamespaceList *propNames = PROPERTY_NAMESPACELIST(i);
|
|
if (names) {
|
|
if (propNames == NULL)
|
|
continue; // a namespace list was specified, no match
|
|
while (names) {
|
|
NamespaceList *propNameEntry = propNames;
|
|
while (propNameEntry) {
|
|
if (names->mName == propNameEntry->mName) {
|
|
Property *prop = PROPERTY(i);
|
|
ASSERT(prop->mFlag == Slot);
|
|
if (selectedProp == NULL)
|
|
selectedProp = prop;
|
|
else {
|
|
if (PROPERTY_INDEX(i) > selectedProp->mData.index)
|
|
selectedProp = prop;
|
|
}
|
|
break;
|
|
}
|
|
propNameEntry = propNameEntry->mNext;
|
|
}
|
|
names = names->mNext;
|
|
}
|
|
}
|
|
else {
|
|
if (propNames) // entry is in a namespace, but none called for, no match
|
|
continue;
|
|
Property *prop = PROPERTY(i);
|
|
ASSERT(prop->mFlag == Slot);
|
|
if (selectedProp == NULL)
|
|
selectedProp = prop;
|
|
else {
|
|
if (PROPERTY_INDEX(i) > selectedProp->mData.index)
|
|
selectedProp = prop;
|
|
}
|
|
}
|
|
}
|
|
ASSERT(selectedProp);
|
|
return new ParameterReference(selectedProp->mData.index, acc, selectedProp->mType, selectedProp->mAttributes);
|
|
}
|
|
|
|
JSValue ParameterBarrel::getSlotValue(Context *cx, uint32 slotIndex)
|
|
{
|
|
// find the appropriate activation object:
|
|
if (cx->mArgumentBase == NULL) {// then must be in eval code,
|
|
Activation *prev = cx->mActivationStack.top();
|
|
return prev->mArgumentBase[slotIndex];
|
|
}
|
|
return cx->mArgumentBase[slotIndex];
|
|
}
|
|
|
|
void ParameterBarrel::setSlotValue(Context *cx, uint32 slotIndex, JSValue &v)
|
|
{
|
|
// find the appropriate activation object:
|
|
if (cx->mArgumentBase == NULL) {// then must be in eval code,
|
|
Activation *prev = cx->mActivationStack.top();
|
|
prev->mArgumentBase[slotIndex] = v;
|
|
}
|
|
else
|
|
cx->mArgumentBase[slotIndex] = v;
|
|
}
|
|
|
|
Property *ParameterBarrel::defineVariable(Context *cx, const String& name, AttributeStmtNode *attr, JSType *type)
|
|
{
|
|
NamespaceList *names = (attr) ? attr->attributeValue->mNamespaceList : NULL;
|
|
PropertyAttribute attrFlags = (attr) ? attr->attributeValue->mTrueFlags : 0;
|
|
PropertyIterator it;
|
|
if (hasOwnProperty(cx, name, names, Read, &it)) {
|
|
// XXX duplicate parameter name, ok for all functions, or just unchecked ones?
|
|
}
|
|
Property *prop = new Property(mVariableCount++, type, Slot, attrFlags);
|
|
const PropertyMap::value_type e(name, new NamespacedProperty(prop, names));
|
|
mProperties.insert(e);
|
|
return prop;
|
|
}
|
|
|
|
|
|
|
|
JSValue Activation::getSlotValue(Context *cx, uint32 slotIndex)
|
|
{
|
|
// find the appropriate activation object:
|
|
if (cx->mArgumentBase == NULL) {// then must be in eval code,
|
|
Activation *prev = cx->mActivationStack.top();
|
|
return prev->mLocals[slotIndex];
|
|
}
|
|
return cx->mLocals[slotIndex];
|
|
}
|
|
|
|
void Activation::setSlotValue(Context *cx, uint32 slotIndex, JSValue &v)
|
|
{
|
|
// find the appropriate activation object:
|
|
if (cx->mArgumentBase == NULL) {// then must be in eval code,
|
|
Activation *prev = cx->mActivationStack.top();
|
|
prev->mLocals[slotIndex] = v;
|
|
}
|
|
else
|
|
cx->mLocals[slotIndex] =v;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JSType *ScopeChain::findType(Context *cx, const StringAtom& typeName, size_t pos)
|
|
{
|
|
JSValue v = getCompileTimeValue(cx, typeName, NULL);
|
|
if (!v.isUndefined()) {
|
|
if (v.isType())
|
|
return v.type;
|
|
else {
|
|
// Allow finding a function that has the same name as it's containing class
|
|
// i.e. the default constructor.
|
|
FunctionName *fnName = v.function->getFunctionName();
|
|
if ((fnName->prefix == FunctionName::normal)
|
|
&& v.isFunction() && v.function->getClass()
|
|
&& (v.function->getClass()->mClassName->compare(*fnName->name) == 0))
|
|
return v.function->getClass();
|
|
m_cx->reportError(Exception::semanticError, "Unknown type", pos);
|
|
return NULL;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// Take the specified type in 't' and see if we have a compile-time
|
|
// type value for it. FindType will throw an error if a type by
|
|
// that name doesn't exist.
|
|
JSType *ScopeChain::extractType(ExprNode *t)
|
|
{
|
|
JSType *type = Object_Type;
|
|
if (t) {
|
|
switch (t->getKind()) {
|
|
case ExprNode::identifier:
|
|
{
|
|
IdentifierExprNode* typeExpr = checked_cast<IdentifierExprNode *>(t);
|
|
type = findType(m_cx, typeExpr->name, t->pos);
|
|
}
|
|
break;
|
|
case ExprNode::index:
|
|
// array type
|
|
{
|
|
InvokeExprNode *i = checked_cast<InvokeExprNode *>(t);
|
|
JSType *base = extractType(i->op);
|
|
JSType *element = Object_Type;
|
|
ExprPairList *p = i->pairs;
|
|
if (p != NULL) {
|
|
element = extractType(p->value);
|
|
ASSERT(p->next == NULL);
|
|
}
|
|
ASSERT(base == Array_Type);
|
|
if (element == Object_Type)
|
|
type = Array_Type;
|
|
else {
|
|
type = new JSArrayType(m_cx, element, NULL, Object_Type, kNullValue, kNullValue); // XXX or is this a descendant of Array[Object]?
|
|
type->setDefaultConstructor(m_cx, Array_Type->getDefaultConstructor());
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
NOT_REACHED("implement me - more complex types");
|
|
break;
|
|
}
|
|
}
|
|
return type;
|
|
}
|
|
|
|
// return the type of the index'th parameter in function
|
|
JSType *Context::getParameterType(FunctionDefinition &function, int index)
|
|
{
|
|
VariableBinding *v = function.parameters;
|
|
while (v) {
|
|
if (index-- == 0)
|
|
return mScopeChain->extractType(v->type);
|
|
else
|
|
v = v->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// Iterates over the linked list of statements, p.
|
|
// 1. Adds 'symbol table' entries for each class, var & function by defining
|
|
|
|
// them in the object at the top of the scope chain
|
|
// 2. Using information from pass 1, evaluate types (and XXX later XXX other
|
|
// compile-time constants) to complete the definitions
|
|
void Context::buildRuntime(StmtNode *p)
|
|
{
|
|
ContextStackReplacement csr(this);
|
|
|
|
mScopeChain->addScope(getGlobalObject());
|
|
while (p) {
|
|
mScopeChain->collectNames(p); // adds declarations for each top-level entity in p
|
|
buildRuntimeForStmt(p); // adds definitions as they exist for ditto
|
|
p = p->next;
|
|
}
|
|
mScopeChain->popScope();
|
|
}
|
|
|
|
// Generate bytecode for the linked list of statements in p
|
|
JS2Runtime::ByteCodeModule *Context::genCode(StmtNode *p, const String &/*sourceName*/)
|
|
{
|
|
mScopeChain->addScope(getGlobalObject());
|
|
JS2Runtime::ByteCodeGen bcg(this, mScopeChain);
|
|
JS2Runtime::ByteCodeModule *result = bcg.genCodeForScript(p);
|
|
mScopeChain->popScope();
|
|
return result;
|
|
}
|
|
|
|
/* Make sure that:
|
|
the function is not a class or interface member;
|
|
the function has no optional, named, or rest parameters;
|
|
none of the function's parameters has a declared type;
|
|
the function does not have a declared return type;
|
|
the function is not a getter or setter.
|
|
*/
|
|
bool ScopeChain::isPossibleUncheckedFunction(FunctionDefinition &f)
|
|
{
|
|
bool result = false;
|
|
if ((f.resultType == NULL)
|
|
&& (f.optParameters == NULL)
|
|
&& (f.prefix == FunctionName::normal)
|
|
&& (topClass() == NULL)) {
|
|
result = true;
|
|
VariableBinding *b = f.parameters;
|
|
while (b) {
|
|
if (b->type != NULL) {
|
|
result = false;
|
|
break;
|
|
}
|
|
b = b->next;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Build a name for the package from the identifier list
|
|
|
|
*/
|
|
|
|
String ScopeChain::getPackageName(IdentifierList *packageIdList)
|
|
|
|
{
|
|
|
|
String packagePath;
|
|
|
|
IdentifierList *idList = packageIdList;
|
|
|
|
while (idList) {
|
|
|
|
packagePath += idList->name;
|
|
|
|
idList = idList->next;
|
|
|
|
if (idList)
|
|
|
|
packagePath += '/'; // XXX how to get path separator for OS?
|
|
|
|
}
|
|
|
|
return packagePath;
|
|
|
|
}
|
|
|
|
// counts the number of pigs that can fit in a small wicker basket
|
|
|
|
void JSFunction::countParameters(Context *cx, FunctionDefinition &f)
|
|
{
|
|
uint32 requiredParameterCount = 0;
|
|
uint32 optionalParameterCount = 0;
|
|
uint32 namedParameterCount = 0;
|
|
|
|
VariableBinding *b = f.parameters;
|
|
while (b != f.optParameters) {
|
|
requiredParameterCount++;
|
|
b = b->next;
|
|
}
|
|
while (b != f.restParameter) {
|
|
optionalParameterCount++;
|
|
b = b->next;
|
|
}
|
|
b = f.namedParameters;
|
|
while (b) {
|
|
namedParameterCount++;
|
|
b = b->next;
|
|
}
|
|
setParameterCounts(cx, requiredParameterCount, optionalParameterCount, namedParameterCount, f.restParameter != f.namedParameters);
|
|
}
|
|
|
|
// The first pass over the tree - it just installs the names of each declaration
|
|
void ScopeChain::collectNames(StmtNode *p)
|
|
{
|
|
switch (p->getKind()) {
|
|
// XXX - other statements, execute them (assuming they have constant control values) ?
|
|
// or simply visit the contained blocks and process any references that need to be hoisted
|
|
case StmtNode::Class:
|
|
{
|
|
ClassStmtNode *classStmt = checked_cast<ClassStmtNode *>(p);
|
|
const StringAtom *name = &classStmt->name;
|
|
JSType *thisClass = new JSType(m_cx, name, NULL, kNullValue, kNullValue);
|
|
|
|
m_cx->setAttributeValue(classStmt, 0); // XXX default attribute for a class?
|
|
|
|
PropertyIterator it;
|
|
if (hasProperty(m_cx, *name, NULL, Read, &it))
|
|
m_cx->reportError(Exception::referenceError, "Duplicate class definition", p->pos);
|
|
|
|
defineVariable(m_cx, *name, classStmt, Type_Type, JSValue(thisClass));
|
|
classStmt->mType = thisClass;
|
|
}
|
|
break;
|
|
case StmtNode::label:
|
|
{
|
|
LabelStmtNode *l = checked_cast<LabelStmtNode *>(p);
|
|
collectNames(l->stmt);
|
|
}
|
|
break;
|
|
case StmtNode::block:
|
|
case StmtNode::group:
|
|
{
|
|
// should push a new Activation scope here?
|
|
BlockStmtNode *b = checked_cast<BlockStmtNode *>(p);
|
|
StmtNode *s = b->statements;
|
|
while (s) {
|
|
collectNames(s);
|
|
s = s->next;
|
|
}
|
|
}
|
|
break;
|
|
case StmtNode::With:
|
|
case StmtNode::If:
|
|
case StmtNode::DoWhile:
|
|
case StmtNode::While:
|
|
{
|
|
UnaryStmtNode *u = checked_cast<UnaryStmtNode *>(p);
|
|
collectNames(u->stmt);
|
|
}
|
|
break;
|
|
case StmtNode::IfElse:
|
|
{
|
|
BinaryStmtNode *b = checked_cast<BinaryStmtNode *>(p);
|
|
collectNames(b->stmt);
|
|
collectNames(b->stmt2);
|
|
}
|
|
break;
|
|
case StmtNode::Try:
|
|
{
|
|
TryStmtNode *t = checked_cast<TryStmtNode *>(p);
|
|
collectNames(t->stmt);
|
|
if (t->catches) {
|
|
CatchClause *c = t->catches;
|
|
while (c) {
|
|
collectNames(c->stmt);
|
|
c->prop = defineVariable(m_cx, c->name, NULL, NULL);
|
|
c = c->next;
|
|
}
|
|
}
|
|
if (t->finally) collectNames(t->finally);
|
|
}
|
|
break;
|
|
case StmtNode::For:
|
|
case StmtNode::ForIn:
|
|
{
|
|
ForStmtNode *f = checked_cast<ForStmtNode *>(p);
|
|
if (f->initializer) collectNames(f->initializer);
|
|
collectNames(f->stmt);
|
|
}
|
|
break;
|
|
case StmtNode::Const:
|
|
case StmtNode::Var:
|
|
{
|
|
VariableStmtNode *vs = checked_cast<VariableStmtNode *>(p);
|
|
VariableBinding *v = vs->bindings;
|
|
m_cx->setAttributeValue(vs, Property::Final);
|
|
if (p->getKind() == StmtNode::Const)
|
|
vs->attributeValue->mTrueFlags |= Property::Const;
|
|
bool isStatic = (vs->attributeValue->mTrueFlags & Property::Static) == Property::Static;
|
|
if ((vs->attributeValue->mTrueFlags & Property::Private) == Property::Private) {
|
|
JSType *theClass = topClass();
|
|
if (theClass == NULL)
|
|
m_cx->reportError(Exception::typeError, "Private can only be used inside a class");
|
|
vs->attributeValue->mNamespaceList = new NamespaceList(*theClass->mPrivateNamespace, vs->attributeValue->mNamespaceList);
|
|
}
|
|
|
|
while (v) {
|
|
if (isStatic)
|
|
v->prop = defineStaticVariable(m_cx, *v->name, vs, NULL);
|
|
else
|
|
v->prop = defineVariable(m_cx, *v->name, vs, NULL);
|
|
v->scope = mScopeStack.back();
|
|
v = v->next;
|
|
}
|
|
}
|
|
break;
|
|
case StmtNode::Function:
|
|
{
|
|
FunctionStmtNode *f = checked_cast<FunctionStmtNode *>(p);
|
|
m_cx->setAttributeValue(f, Property::Virtual);
|
|
|
|
bool isStatic = (f->attributeValue->mTrueFlags & Property::Static) == Property::Static;
|
|
bool isConstructor = (f->attributeValue->mTrueFlags & Property::Constructor) == Property::Constructor;
|
|
bool isOperator = (f->attributeValue->mTrueFlags & Property::Operator) == Property::Operator;
|
|
bool isPrototype = (f->attributeValue->mTrueFlags & Property::Prototype) == Property::Prototype;
|
|
|
|
JSFunction *fnc = new JSFunction(m_cx, NULL, this);
|
|
|
|
/* Determine whether a function is unchecked, which is the case if -
|
|
XXX strict mode is disabled at the point of the function definition;
|
|
the function is not a class or interface member;
|
|
the function has no optional, named, or rest parameters;
|
|
none of the function's parameters has a declared type;
|
|
the function does not have a declared return type;
|
|
the function is not a getter or setter.
|
|
*/
|
|
if (!isPrototype
|
|
&& (!isOperator)
|
|
&& isPossibleUncheckedFunction(f->function)) {
|
|
isPrototype = true;
|
|
fnc->setIsUnchecked();
|
|
}
|
|
|
|
fnc->setIsPrototype(isPrototype);
|
|
fnc->setIsConstructor(isConstructor);
|
|
fnc->setFunctionName(f->function);
|
|
f->mFunction = fnc;
|
|
|
|
fnc->countParameters(m_cx, f->function);
|
|
|
|
if (isOperator) {
|
|
// no need to do anything yet, all operators are 'pre-declared'
|
|
}
|
|
else {
|
|
const StringAtom& name = *f->function.name;
|
|
if (topClass())
|
|
fnc->setClass(topClass());
|
|
|
|
if ((f->attributeValue->mTrueFlags & Property::Extend) == Property::Extend) {
|
|
|
|
JSType *extendedClass = f->attributeValue->mExtendArgument;
|
|
|
|
// sort of want to fall into the code below, but use 'extendedClass' instead
|
|
// of whatever the topClass will turn out to be.
|
|
if (extendedClass->mClassName->compare(name) == 0) {
|
|
isConstructor = true; // can you add constructors?
|
|
fnc->setIsConstructor(true);
|
|
}
|
|
if (isConstructor)
|
|
extendedClass->defineConstructor(m_cx, name, f, fnc);
|
|
else {
|
|
switch (f->function.prefix) {
|
|
case FunctionName::Get:
|
|
if (isStatic)
|
|
extendedClass->defineStaticGetterMethod(m_cx, name, f, fnc);
|
|
else
|
|
extendedClass->defineGetterMethod(m_cx, name, f, fnc);
|
|
break;
|
|
case FunctionName::Set:
|
|
if (isStatic)
|
|
extendedClass->defineStaticSetterMethod(m_cx, name, f, fnc);
|
|
else
|
|
extendedClass->defineSetterMethod(m_cx, name, f, fnc);
|
|
break;
|
|
case FunctionName::normal:
|
|
f->attributeValue->mTrueFlags |= Property::Const;
|
|
if (isStatic)
|
|
extendedClass->defineStaticMethod(m_cx, name, f, fnc);
|
|
else
|
|
extendedClass->defineMethod(m_cx, name, f, fnc);
|
|
break;
|
|
default:
|
|
NOT_REACHED("***** implement me -- throw an error because the user passed a quoted function name");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
bool isDefaultConstructor = false;
|
|
if (topClass() && (topClass()->mClassName->compare(name) == 0)) {
|
|
isConstructor = true;
|
|
fnc->setIsConstructor(true);
|
|
isDefaultConstructor = true;
|
|
}
|
|
if (!isNestedFunction()) {
|
|
if (isConstructor) {
|
|
defineConstructor(m_cx, name, f, fnc);
|
|
if (isDefaultConstructor)
|
|
topClass()->setDefaultConstructor(m_cx, fnc);
|
|
}
|
|
else {
|
|
switch (f->function.prefix) {
|
|
case FunctionName::Get:
|
|
if (isStatic)
|
|
defineStaticGetterMethod(m_cx, name, f, fnc);
|
|
else
|
|
defineGetterMethod(m_cx, name, f, fnc);
|
|
break;
|
|
case FunctionName::Set:
|
|
if (isStatic)
|
|
defineStaticSetterMethod(m_cx, name, f, fnc);
|
|
else
|
|
defineSetterMethod(m_cx, name, f, fnc);
|
|
break;
|
|
case FunctionName::normal:
|
|
// make a function into a const declaration, but only if any types
|
|
// have been specified - otherwise it's a 1.5 atyle definition and
|
|
// duplicates are allowed
|
|
if (!isPossibleUncheckedFunction(f->function))
|
|
f->attributeValue->mTrueFlags |= Property::Const;
|
|
if (isStatic)
|
|
defineStaticMethod(m_cx, name, f, fnc);
|
|
else
|
|
defineMethod(m_cx, name, f, fnc);
|
|
break;
|
|
default:
|
|
NOT_REACHED("***** implement me -- throw an error because the user passed a quoted function name");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case StmtNode::Import:
|
|
{
|
|
ImportStmtNode *i = checked_cast<ImportStmtNode *>(p);
|
|
String packageName;
|
|
if (i->packageIdList)
|
|
packageName = getPackageName(i->packageIdList);
|
|
else
|
|
packageName = *i->packageString;
|
|
|
|
if (!m_cx->checkForPackage(packageName))
|
|
m_cx->loadPackage(packageName, packageName + ".js");
|
|
|
|
JSValue packageValue = getCompileTimeValue(m_cx, packageName, NULL);
|
|
ASSERT(packageValue.isPackage());
|
|
Package *package = packageValue.package;
|
|
|
|
if (i->varName)
|
|
defineVariable(m_cx, *i->varName, NULL, Package_Type, JSValue(package));
|
|
|
|
for (PropertyIterator it = package->mProperties.begin(), end = package->mProperties.end();
|
|
(it != end); it++)
|
|
{
|
|
ASSERT(PROPERTY_KIND(it) == ValuePointer);
|
|
bool makeAlias = true;
|
|
if (i->includeExclude) {
|
|
makeAlias = i->exclude;
|
|
IdentifierList *idList = i->includeExclude;
|
|
while (idList) {
|
|
if (idList->name.compare(PROPERTY_NAME(it)) == 0) {
|
|
makeAlias = !makeAlias;
|
|
break;
|
|
}
|
|
idList = idList->next;
|
|
}
|
|
}
|
|
if (makeAlias)
|
|
defineAlias(m_cx, PROPERTY_NAME(it), PROPERTY_NAMESPACELIST(it), PROPERTY_ATTR(it), PROPERTY_TYPE(it), PROPERTY_VALUEPOINTER(it));
|
|
}
|
|
|
|
}
|
|
break;
|
|
case StmtNode::Namespace:
|
|
{
|
|
NamespaceStmtNode *n = checked_cast<NamespaceStmtNode *>(p);
|
|
Attribute *x = new Attribute(0, 0);
|
|
x->mNamespaceList = new NamespaceList(n->name, x->mNamespaceList);
|
|
m_cx->getGlobalObject()->defineVariable(m_cx, n->name, (NamespaceList *)(NULL), Property::NoAttribute, Attribute_Type, JSValue(x));
|
|
}
|
|
break;
|
|
case StmtNode::Package:
|
|
{
|
|
PackageStmtNode *ps = checked_cast<PackageStmtNode *>(p);
|
|
String packageName = getPackageName(ps->packageIdList);
|
|
Package *package = new Package(packageName);
|
|
ps->scope = package;
|
|
defineVariable(m_cx, packageName, NULL, Package_Type, JSValue(package));
|
|
m_cx->mPackages.push_back(package);
|
|
|
|
addScope(ps->scope);
|
|
collectNames(ps->body);
|
|
popScope();
|
|
package->mStatus = Package::InHand;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Make sure there's a default constructor. XXX anything else?
|
|
void JSType::completeClass(Context *cx, ScopeChain *scopeChain)
|
|
{
|
|
// if none exists, build a default constructor that calls 'super()'
|
|
if (getDefaultConstructor() == NULL) {
|
|
JSFunction *fnc = new JSFunction(cx, Object_Type, scopeChain);
|
|
fnc->setIsConstructor(true);
|
|
fnc->setFunctionName(mClassName);
|
|
fnc->setClass(this);
|
|
|
|
ByteCodeGen bcg(cx, scopeChain);
|
|
|
|
if (mSuperType && mSuperType->getDefaultConstructor()) {
|
|
bcg.addOp(LoadTypeOp);
|
|
bcg.addPointer(this);
|
|
bcg.addOp(NewThisOp);
|
|
bcg.addOp(LoadThisOp);
|
|
bcg.addOp(LoadFunctionOp);
|
|
bcg.addPointer(mSuperType->getDefaultConstructor());
|
|
bcg.addOpAdjustDepth(InvokeOp, -1);
|
|
bcg.addLong(0);
|
|
bcg.addByte(Explicit);
|
|
bcg.addOp(PopOp);
|
|
}
|
|
bcg.addOp(LoadThisOp);
|
|
ASSERT(bcg.mStackTop == 1);
|
|
bcg.addOpSetDepth(ReturnOp, 0);
|
|
ByteCodeModule *bcm = new JS2Runtime::ByteCodeModule(&bcg, fnc);
|
|
if (cx->mReader)
|
|
bcm->setSource(cx->mReader->source, cx->mReader->sourceLocation);
|
|
fnc->setByteCode(bcm);
|
|
|
|
scopeChain->defineConstructor(cx, *mClassName, NULL, fnc); // XXX attributes?
|
|
setDefaultConstructor(cx, fnc);
|
|
}
|
|
}
|
|
|
|
void JSType::defineMethod(Context *cx, const String& name, AttributeStmtNode *attr, JSFunction *f)
|
|
{
|
|
NamespaceList *names = (attr) ? attr->attributeValue->mNamespaceList : NULL;
|
|
PropertyIterator it;
|
|
if (hasOwnProperty(cx, name, names, Read, &it))
|
|
cx->reportError(Exception::typeError, "Duplicate method definition", attr->pos);
|
|
|
|
// now check if the method exists in the supertype
|
|
if (mSuperType && mSuperType->hasOwnProperty(cx, name, names, Read, &it)) {
|
|
// if it does, it must have been overridable:
|
|
PropertyAttribute superAttr = PROPERTY_ATTR(it);
|
|
if (superAttr & Property::Final)
|
|
cx->reportError(Exception::typeError, "Attempting to override a final method", attr->pos);
|
|
// if it was marked as virtual, then the new one must specifiy 'override' or 'mayoverride'
|
|
if (superAttr & Property::Virtual) {
|
|
if ((attr->attributeValue->mTrueFlags & (Property::Override | Property::MayOverride)) == 0)
|
|
cx->reportError(Exception::typeError, "Must specify 'override' or 'mayOverride'", attr->pos);
|
|
}
|
|
}
|
|
|
|
uint32 vTableIndex = mMethods.size();
|
|
mMethods.push_back(f);
|
|
|
|
PropertyAttribute attrFlags = (attr) ? attr->attributeValue->mTrueFlags : 0;
|
|
const PropertyMap::value_type e(name, new NamespacedProperty(new Property(vTableIndex, Function_Type, Method, attrFlags), names));
|
|
mProperties.insert(e);
|
|
}
|
|
|
|
void JSType::defineGetterMethod(Context *cx, const String &name, AttributeStmtNode *attr, JSFunction *f)
|
|
{
|
|
PropertyIterator i;
|
|
uint32 vTableIndex = mMethods.size();
|
|
mMethods.push_back(f);
|
|
|
|
NamespaceList *names = (attr) ? attr->attributeValue->mNamespaceList : NULL;
|
|
|
|
if (hasProperty(cx, name, names, Write, &i)) {
|
|
ASSERT(PROPERTY_KIND(i) == IndexPair);
|
|
ASSERT(PROPERTY_GETTERI(i) == 0);
|
|
PROPERTY_GETTERI(i) = vTableIndex;
|
|
}
|
|
else {
|
|
PropertyAttribute attrFlags = (attr) ? attr->attributeValue->mTrueFlags : 0;
|
|
const PropertyMap::value_type e(name, new NamespacedProperty(new Property(vTableIndex, 0, Function_Type, attrFlags), names));
|
|
mProperties.insert(e);
|
|
}
|
|
}
|
|
|
|
void JSType::defineSetterMethod(Context *cx, const String &name, AttributeStmtNode *attr, JSFunction *f)
|
|
{
|
|
PropertyIterator i;
|
|
uint32 vTableIndex = mMethods.size();
|
|
mMethods.push_back(f);
|
|
|
|
NamespaceList *names = (attr) ? attr->attributeValue->mNamespaceList : NULL;
|
|
|
|
if (hasProperty(cx, name, names, Read, &i)) {
|
|
ASSERT(PROPERTY_KIND(i) == IndexPair);
|
|
ASSERT(PROPERTY_SETTERI(i) == 0);
|
|
PROPERTY_SETTERI(i) = vTableIndex;
|
|
}
|
|
else {
|
|
PropertyAttribute attrFlags = (attr) ? attr->attributeValue->mTrueFlags : 0;
|
|
const PropertyMap::value_type e(name, new NamespacedProperty(new Property(0, vTableIndex, Function_Type, attrFlags), names));
|
|
mProperties.insert(e);
|
|
}
|
|
}
|
|
|
|
bool JSType::derivesFrom(JSType *other)
|
|
{
|
|
if (mSuperType == other)
|
|
return true;
|
|
else
|
|
if (mSuperType)
|
|
return mSuperType->derivesFrom(other);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
JSValue JSType::getPropertyValue(PropertyIterator &i)
|
|
{
|
|
Property *prop = PROPERTY(i);
|
|
switch (prop->mFlag) {
|
|
case ValuePointer:
|
|
return *prop->mData.vp;
|
|
case Constructor:
|
|
return JSValue(mMethods[prop->mData.index]);
|
|
default:
|
|
return kUndefinedValue;
|
|
}
|
|
}
|
|
|
|
bool JSType::hasProperty(Context *cx, const String &name, NamespaceList *names, Access acc, PropertyIterator *p)
|
|
{
|
|
if (hasOwnProperty(cx, name, names, acc, p))
|
|
return true;
|
|
else
|
|
if (mSuperType)
|
|
return mSuperType->hasProperty(cx, name, names, acc, p);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
Reference *JSType::genReference(Context *cx, bool hasBase, const String& name, NamespaceList *names, Access acc, uint32 depth)
|
|
{
|
|
PropertyIterator i;
|
|
if (hasOwnProperty(cx, name, names, acc, &i)) {
|
|
Property *prop = PROPERTY(i);
|
|
switch (prop->mFlag) {
|
|
case FunctionPair:
|
|
if (acc == Read)
|
|
return new GetterFunctionReference(prop->mData.fPair.getterF, prop->mAttributes);
|
|
else {
|
|
JSFunction *f = prop->mData.fPair.setterF;
|
|
return new SetterFunctionReference(f, f->getParameterType(0), prop->mAttributes);
|
|
}
|
|
case ValuePointer:
|
|
return new StaticFieldReference(name, acc, this, prop->mType, prop->mAttributes);
|
|
|
|
case IndexPair:
|
|
if (acc == Read)
|
|
return new GetterMethodReference(prop->mData.iPair.getterI, this, prop->mType, prop->mAttributes);
|
|
else {
|
|
JSFunction *f = mMethods[prop->mData.iPair.setterI];
|
|
return new SetterMethodReference(prop->mData.iPair.setterI, this, f->getParameterType(0), prop->mAttributes);
|
|
}
|
|
case Slot:
|
|
return new FieldReference(prop->mData.index, acc, prop->mType, prop->mAttributes);
|
|
case Method:
|
|
return new MethodReference(prop->mData.index, this, prop->mType, prop->mAttributes);
|
|
default:
|
|
NOT_REACHED("bad storage kind");
|
|
return NULL;
|
|
}
|
|
}
|
|
// walk the supertype chain
|
|
if (mSuperType)
|
|
return mSuperType->genReference(cx, hasBase, name, names, acc, depth);
|
|
return NULL;
|
|
}
|
|
|
|
// Construct a type object, hook up the prototype value (mPrototypeObject) and the __proto__ value
|
|
// (mPrototype). Special handling throughout for handling the initialization of Object_Type which has
|
|
// no super type. (Note though that it's prototype link __proto__ is the Type_Type object and it has
|
|
// a prototype object - whose __proto__ is null)
|
|
JSType::JSType(Context *cx, const StringAtom *name, JSType *super, JSValue &protoObj, JSValue &typeProto)
|
|
: JSInstance(cx, Type_Type),
|
|
mSuperType(super),
|
|
mVariableCount(0),
|
|
mInstanceInitializer(NULL),
|
|
mDefaultConstructor(NULL),
|
|
mTypeCast(NULL),
|
|
mClassName(name),
|
|
mIsDynamic(false),
|
|
mUninitializedValue(kNullValue),
|
|
mPrototypeObject(kNullValue)
|
|
{
|
|
if (mClassName)
|
|
mPrivateNamespace = &cx->mWorld.identifiers[*mClassName + " private"];
|
|
else
|
|
mPrivateNamespace = &cx->mWorld.identifiers["unique id needed? private"]; // XXX. No, really?
|
|
|
|
// every class gets a prototype object (used to set the __proto__ value of new instances)
|
|
if (!protoObj.isNull())
|
|
mPrototypeObject = protoObj;
|
|
else {
|
|
JSObject *protoObj = new JSObject();
|
|
mPrototypeObject = JSValue(protoObj);
|
|
// and that object is prototype-linked to the super-type's prototype object
|
|
if (mSuperType)
|
|
protoObj->mPrototype = mSuperType->mPrototypeObject;
|
|
protoObj->defineVariable(cx, cx->UnderbarPrototype_StringAtom, NULL, 0, this, protoObj->mPrototype);
|
|
}
|
|
|
|
if (mSuperType)
|
|
defineVariable(cx, cx->Prototype_StringAtom, (NamespaceList *)NULL, Property::ReadOnly | Property::DontDelete, Object_Type, mPrototypeObject);
|
|
else // must be Object_Type being initialized
|
|
defineVariable(cx, cx->Prototype_StringAtom, (NamespaceList *)NULL, Property::ReadOnly | Property::DontDelete, this, mPrototypeObject);
|
|
|
|
if (!typeProto.isNull())
|
|
mPrototype = typeProto;
|
|
else {
|
|
// the __proto__ of a type is the super-type's prototype object, or for the
|
|
// 'class Object' type object it's the Object's prototype object
|
|
if (mSuperType)
|
|
mPrototype = mSuperType->mPrototypeObject;
|
|
else // must be Object_Type being initialized
|
|
mPrototype = mPrototypeObject;
|
|
}
|
|
if (mSuperType) {
|
|
defineVariable(cx, cx->UnderbarPrototype_StringAtom, NULL, 0, Object_Type, mPrototype);
|
|
mPrototypeObject.getObjectValue()->defineVariable(cx, cx->Constructor_StringAtom, (NamespaceList *)NULL, 0, Object_Type, JSValue(this));
|
|
}
|
|
else { // must be Object_Type being initialized
|
|
defineVariable(cx, cx->UnderbarPrototype_StringAtom, NULL, 0, this, mPrototype);
|
|
mPrototypeObject.getObjectValue()->defineVariable(cx, cx->Constructor_StringAtom, (NamespaceList *)NULL, 0, this, JSValue(this));
|
|
}
|
|
}
|
|
|
|
// Establish the super class - connects the prototype's prototype
|
|
// and accounts for the super class's instance fields & methods
|
|
void JSType::setSuperType(JSType *super)
|
|
{
|
|
mSuperType = super;
|
|
if (mSuperType) {
|
|
mPrototypeObject.getObjectValue()->mPrototype = mSuperType->mPrototypeObject;
|
|
// inherit supertype instance field and vtable slots
|
|
mVariableCount = mSuperType->mVariableCount;
|
|
mMethods.insert(mMethods.begin(),
|
|
mSuperType->mMethods.begin(),
|
|
mSuperType->mMethods.end());
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void Activation::defineTempVariable(Context * /*cx*/, Reference *&readRef, Reference *&writeRef, JSType *type)
|
|
{
|
|
readRef = new LocalVarReference(mVariableCount, Read, type, Property::NoAttribute);
|
|
writeRef = new LocalVarReference(mVariableCount, Write, type, Property::NoAttribute);
|
|
mVariableCount++;
|
|
}
|
|
|
|
Reference *Activation::genReference(Context * cx, bool /* hasBase */, const String& name, NamespaceList *names, Access acc, uint32 depth)
|
|
{
|
|
PropertyIterator i;
|
|
if (hasProperty(cx, name, names, acc, &i)) {
|
|
Property *prop = PROPERTY(i);
|
|
ASSERT((prop->mFlag == ValuePointer) || (prop->mFlag == Slot) || (prop->mFlag == FunctionPair));
|
|
|
|
switch (prop->mFlag) {
|
|
case FunctionPair:
|
|
return (acc == Read) ? new AccessorReference(prop->mData.fPair.getterF, prop->mAttributes)
|
|
: new AccessorReference(prop->mData.fPair.setterF, prop->mAttributes);
|
|
case Slot:
|
|
if (depth)
|
|
return new ClosureVarReference(depth, prop->mData.index, acc, prop->mType, prop->mAttributes);
|
|
else
|
|
return new LocalVarReference(prop->mData.index, acc, prop->mType, prop->mAttributes);
|
|
|
|
case ValuePointer:
|
|
return new NameReference(name, names, acc, prop->mType, prop->mAttributes);
|
|
|
|
default:
|
|
NOT_REACHED("bad genRef call");
|
|
break;
|
|
}
|
|
}
|
|
NOT_REACHED("bad genRef call");
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
Process the statements in the function body, handling parameters and local
|
|
variables to collect names & types.
|
|
*/
|
|
void Context::buildRuntimeForFunction(FunctionDefinition &f, JSFunction *fnc)
|
|
{
|
|
fnc->mParameterBarrel = new ParameterBarrel();
|
|
mScopeChain->addScope(fnc->mParameterBarrel);
|
|
VariableBinding *v = f.parameters;
|
|
while (v) {
|
|
if (v->name) {
|
|
JSType *pType = mScopeChain->extractType(v->type); // XXX already extracted for argument Data
|
|
mScopeChain->defineVariable(this, *v->name, NULL, pType); // XXX attributes?
|
|
}
|
|
v = v->next;
|
|
}
|
|
if (f.body) {
|
|
mScopeChain->addScope(&fnc->mActivation);
|
|
mScopeChain->collectNames(f.body);
|
|
buildRuntimeForStmt(f.body);
|
|
mScopeChain->popScope();
|
|
}
|
|
mScopeChain->popScope();
|
|
}
|
|
|
|
/*
|
|
The incoming AttributeStmtNode has a list of attributes - evaluate those and return
|
|
the resultant Attribute value. If there are no attributes, return the default value.
|
|
*/
|
|
void Context::setAttributeValue(AttributeStmtNode *s, PropertyAttribute defaultValue)
|
|
{
|
|
Attribute *attributeValue = NULL;
|
|
if (s->attributes == NULL)
|
|
attributeValue = new Attribute(defaultValue, 0);
|
|
else
|
|
attributeValue = executeAttributes(s->attributes);
|
|
s->attributeValue = attributeValue;
|
|
}
|
|
|
|
|
|
// Second pass, collect type information and finish
|
|
// off the definitions made in pass 1
|
|
void Context::buildRuntimeForStmt(StmtNode *p)
|
|
{
|
|
switch (p->getKind()) {
|
|
case StmtNode::block:
|
|
case StmtNode::group:
|
|
{
|
|
BlockStmtNode *b = checked_cast<BlockStmtNode *>(p);
|
|
StmtNode *s = b->statements;
|
|
while (s) {
|
|
buildRuntimeForStmt(s);
|
|
s = s->next;
|
|
}
|
|
}
|
|
break;
|
|
case StmtNode::label:
|
|
{
|
|
LabelStmtNode *l = checked_cast<LabelStmtNode *>(p);
|
|
buildRuntimeForStmt(l->stmt);
|
|
}
|
|
break;
|
|
case StmtNode::Try:
|
|
{
|
|
TryStmtNode *t = checked_cast<TryStmtNode *>(p);
|
|
buildRuntimeForStmt(t->stmt);
|
|
if (t->catches) {
|
|
CatchClause *c = t->catches;
|
|
while (c) {
|
|
buildRuntimeForStmt(c->stmt);
|
|
c->prop->mType = mScopeChain->extractType(c->type);
|
|
c = c->next;
|
|
}
|
|
}
|
|
if (t->finally) buildRuntimeForStmt(t->finally);
|
|
}
|
|
break;
|
|
case StmtNode::With:
|
|
case StmtNode::If:
|
|
case StmtNode::DoWhile:
|
|
case StmtNode::While:
|
|
{
|
|
UnaryStmtNode *u = checked_cast<UnaryStmtNode *>(p);
|
|
buildRuntimeForStmt(u->stmt);
|
|
}
|
|
break;
|
|
case StmtNode::IfElse:
|
|
{
|
|
BinaryStmtNode *b = checked_cast<BinaryStmtNode *>(p);
|
|
buildRuntimeForStmt(b->stmt);
|
|
buildRuntimeForStmt(b->stmt2);
|
|
}
|
|
break;
|
|
case StmtNode::For:
|
|
case StmtNode::ForIn:
|
|
{
|
|
ForStmtNode *f = checked_cast<ForStmtNode *>(p);
|
|
if (f->initializer) buildRuntimeForStmt(f->initializer);
|
|
buildRuntimeForStmt(f->stmt);
|
|
}
|
|
break;
|
|
case StmtNode::Var:
|
|
case StmtNode::Const:
|
|
{
|
|
VariableStmtNode *vs = checked_cast<VariableStmtNode *>(p);
|
|
VariableBinding *v = vs->bindings;
|
|
while (v) {
|
|
JSType *type = mScopeChain->extractType(v->type);
|
|
v->prop->mType = type;
|
|
v = v->next;
|
|
}
|
|
}
|
|
break;
|
|
case StmtNode::Function:
|
|
{
|
|
FunctionStmtNode *f = checked_cast<FunctionStmtNode *>(p);
|
|
|
|
bool isOperator = (f->attributeValue->mTrueFlags & Property::Operator) == Property::Operator;
|
|
|
|
JSType *resultType = mScopeChain->extractType(f->function.resultType);
|
|
JSFunction *fnc = f->mFunction;
|
|
fnc->setResultType(resultType);
|
|
|
|
VariableBinding *v = f->function.parameters;
|
|
uint32 parameterCount = 0;
|
|
JSFunction::ParameterFlag flag = JSFunction::RequiredParameter;
|
|
while (v) {
|
|
// XXX if no type is specified for the rest parameter - is it Array?
|
|
if (v == f->function.optParameters)
|
|
flag = JSFunction::OptionalParameter;
|
|
else
|
|
if (v == f->function.restParameter)
|
|
flag = JSFunction::RestParameter;
|
|
else
|
|
if (v == f->function.namedParameters)
|
|
flag = JSFunction::NamedParameter;
|
|
fnc->setParameter(parameterCount++, v->name, mScopeChain->extractType(v->type), flag);
|
|
v = v->next;
|
|
}
|
|
|
|
if (isOperator) {
|
|
if (f->function.prefix != FunctionName::op) {
|
|
NOT_REACHED("***** Implement me -- signal an error here because the user entered an unquoted operator name");
|
|
}
|
|
ASSERT(f->function.name);
|
|
const StringAtom& name = *f->function.name;
|
|
Operator op = getOperator(parameterCount, name);
|
|
// Operators added to the Context's operator table.
|
|
// The indexing operators are considered unary since
|
|
// they only dispatch on the base type.
|
|
if ((parameterCount == 1)
|
|
|| (op == Index)
|
|
|| (op == IndexEqual)
|
|
|| (op == DeleteIndex))
|
|
defineOperator(op, mScopeChain->topClass(), fnc);
|
|
else
|
|
defineOperator(op, getParameterType(f->function, 0),
|
|
getParameterType(f->function, 1), fnc);
|
|
}
|
|
|
|
// if it's an extending function, rediscover the extended class
|
|
// and push the class scope onto the scope chain
|
|
|
|
|
|
/*
|
|
bool isExtender = false;
|
|
if (hasAttribute(f->attributes, ExtendKeyWord)) {
|
|
JSType *extendedClass = mScopeChain->extractType( <extend attribute argument> );
|
|
mScopeChain->addScope(extendedClass->mStatics);
|
|
mScopeChain->addScope(extendedClass);
|
|
}
|
|
*/
|
|
|
|
buildRuntimeForFunction(f->function, fnc);
|
|
/*
|
|
if (isExtender) { // blow off the extended class's scope
|
|
mScopeChain->popScope();
|
|
mScopeChain->popScope();
|
|
}
|
|
*/
|
|
|
|
|
|
}
|
|
break;
|
|
case StmtNode::Class:
|
|
{
|
|
ClassStmtNode *classStmt = checked_cast<ClassStmtNode *>(p);
|
|
JSType *superClass = Object_Type;
|
|
if (classStmt->superclass) {
|
|
ASSERT(classStmt->superclass->getKind() == ExprNode::identifier); // XXX
|
|
IdentifierExprNode *superClassExpr = checked_cast<IdentifierExprNode *>(classStmt->superclass);
|
|
superClass = mScopeChain->findType(this, superClassExpr->name, superClassExpr->pos);
|
|
}
|
|
JSType *thisClass = classStmt->mType;
|
|
thisClass->setSuperType(superClass);
|
|
mScopeChain->addScope(thisClass);
|
|
if (classStmt->body) {
|
|
StmtNode* s = classStmt->body->statements;
|
|
while (s) {
|
|
mScopeChain->collectNames(s);
|
|
s = s->next;
|
|
}
|
|
s = classStmt->body->statements;
|
|
while (s) {
|
|
buildRuntimeForStmt(s);
|
|
s = s->next;
|
|
}
|
|
}
|
|
thisClass->completeClass(this, mScopeChain);
|
|
|
|
mScopeChain->popScope();
|
|
}
|
|
break;
|
|
case StmtNode::Namespace:
|
|
{
|
|
// do anything ?
|
|
}
|
|
break;
|
|
case StmtNode::Package:
|
|
{
|
|
PackageStmtNode *ps = checked_cast<PackageStmtNode *>(p);
|
|
mScopeChain->addScope(ps->scope);
|
|
buildRuntimeForStmt(ps->body);
|
|
mScopeChain->popScope();
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
static JSValue Object_Constructor(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc)
|
|
{
|
|
JSValue thatValue = thisValue; // incoming 'new this' potentially supplied by constructor sequence
|
|
|
|
if (argc != 0) {
|
|
if (argv[0].isInstance() || argv[0].isObject() || argv[0].isFunction() || argv[0].isType())
|
|
thatValue = argv[0];
|
|
else
|
|
if (argv[0].isString() || argv[0].isBool() || argv[0].isNumber())
|
|
thatValue = argv[0].toObject(cx);
|
|
else {
|
|
if (thatValue.isNull())
|
|
thatValue = Object_Type->newInstance(cx);
|
|
}
|
|
}
|
|
else {
|
|
if (thatValue.isNull())
|
|
thatValue = Object_Type->newInstance(cx);
|
|
}
|
|
return thatValue;
|
|
}
|
|
|
|
static JSValue Object_toString(Context * /* cx */, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/)
|
|
{
|
|
if (thisValue.isObject() || thisValue.isInstance())
|
|
return JSValue(new String(widenCString("[object ") + *thisValue.getType()->mClassName + widenCString("]")));
|
|
else
|
|
if (thisValue.isType()) // XXX why wouldn't this get handled by the above?
|
|
return JSValue(new String(widenCString("[object ") + widenCString("Type") + widenCString("]")));
|
|
else
|
|
if (thisValue.isFunction())
|
|
return JSValue(new String(widenCString("[object ") + widenCString("Function") + widenCString("]")));
|
|
else {
|
|
NOT_REACHED("Object.prototype.toString on non-object");
|
|
return kUndefinedValue;
|
|
}
|
|
}
|
|
|
|
static JSValue Object_valueOf(Context * /* cx */, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/)
|
|
{
|
|
return thisValue;
|
|
}
|
|
|
|
class JSIteratorInstance : public JSInstance {
|
|
public:
|
|
JSIteratorInstance(Context *cx) : JSInstance(cx, NULL) { mType = (JSType *)Object_Type; }
|
|
virtual ~JSIteratorInstance() { } // keeping gcc happy
|
|
#ifdef DEBUG
|
|
void* operator new(size_t s) { void *t = STD::malloc(s); trace_alloc("JSIteratorInstance", s, t); return t; }
|
|
void operator delete(void* t) { trace_release("JSIteratorInstance", t); STD::free(t); }
|
|
#endif
|
|
JSObject *obj;
|
|
PropertyIterator it;
|
|
};
|
|
|
|
static JSValue Object_forin(Context *cx, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/)
|
|
{
|
|
JSObject *obj = thisValue.getObjectValue();
|
|
|
|
JSIteratorInstance *itInst = new JSIteratorInstance(cx);
|
|
itInst->obj = obj;
|
|
itInst->it = obj->mProperties.begin();
|
|
while (true) {
|
|
while (itInst->it == itInst->obj->mProperties.end()) {
|
|
if (itInst->obj->mPrototype.isNull())
|
|
return kNullValue;
|
|
itInst->obj = itInst->obj->mPrototype.getObjectValue();
|
|
itInst->it = itInst->obj->mProperties.begin();
|
|
}
|
|
if (PROPERTY_ATTR(itInst->it) & Property::Enumerable)
|
|
break;
|
|
itInst->it++;
|
|
}
|
|
|
|
JSValue v(&PROPERTY_NAME(itInst->it));
|
|
itInst->setProperty(cx, cx->mWorld.identifiers["value"], 0, v);
|
|
return JSValue(itInst);
|
|
}
|
|
|
|
static JSValue Object_next(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
|
|
{
|
|
JSValue iteratorValue = argv[0];
|
|
ASSERT(iteratorValue.isInstance());
|
|
JSIteratorInstance *itInst = checked_cast<JSIteratorInstance *>(iteratorValue.instance);
|
|
|
|
itInst->it++;
|
|
while (true) {
|
|
while (itInst->it == itInst->obj->mProperties.end()) {
|
|
if (itInst->obj->mPrototype.isNull())
|
|
return kNullValue;
|
|
itInst->obj = itInst->obj->mPrototype.getObjectValue();
|
|
itInst->it = itInst->obj->mProperties.begin();
|
|
}
|
|
if (PROPERTY_ATTR(itInst->it) & Property::Enumerable)
|
|
break;
|
|
itInst->it++;
|
|
}
|
|
JSValue v(&PROPERTY_NAME(itInst->it));
|
|
itInst->setProperty(cx, cx->mWorld.identifiers["value"], 0, v);
|
|
return iteratorValue;
|
|
}
|
|
|
|
static JSValue Object_done(Context *, const JSValue& /*thisValue*/, JSValue * /*argv*/, uint32 /*argc*/)
|
|
{
|
|
return kUndefinedValue;
|
|
}
|
|
|
|
|
|
|
|
|
|
static JSValue Function_Constructor(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc)
|
|
{
|
|
JSValue v = thisValue;
|
|
if (v.isNull())
|
|
v = Function_Type->newInstance(cx);
|
|
ASSERT(v.isInstance());
|
|
|
|
String s;
|
|
if (argc == 0)
|
|
s = widenCString("() { }");
|
|
else {
|
|
if (argc == 1)
|
|
s = widenCString("() {") + *argv[0].toString(cx).string + "}";
|
|
else {
|
|
s = widenCString("("); // ')'
|
|
for (uint32 i = 0; i < (argc - 1); i++) {
|
|
s += *argv[i].toString(cx).string;
|
|
if (i < (argc - 2))
|
|
s += widenCString(", ");
|
|
}
|
|
/* ( */ s += ") {" + *argv[argc - 1].toString(cx).string + "}";
|
|
}
|
|
}
|
|
|
|
JSFunction *fnc = NULL;
|
|
/***************************************************************/
|
|
{
|
|
Arena a;
|
|
Parser p(cx->mWorld, a, cx->mFlags, s, widenCString("function constructor"));
|
|
Reader *oldReader = cx->mReader;
|
|
cx->mReader = &p.lexer.reader;
|
|
|
|
FunctionExprNode *f = p.parseFunctionExpression(0);
|
|
if (!p.lexer.peek(true).hasKind(Token::end))
|
|
cx->reportError(Exception::syntaxError, "Unexpected stuff after the function body");
|
|
|
|
fnc = new JSFunction(cx, NULL, cx->mScopeChain);
|
|
fnc->setResultType(Object_Type);
|
|
|
|
fnc->countParameters(cx, f->function);
|
|
|
|
|
|
if (cx->mScopeChain->isPossibleUncheckedFunction(f->function)) {
|
|
fnc->setIsPrototype(true);
|
|
fnc->setIsUnchecked();
|
|
}
|
|
cx->buildRuntimeForFunction(f->function, fnc);
|
|
ByteCodeGen bcg(cx, cx->mScopeChain);
|
|
bcg.genCodeForFunction(f->function, f->pos, fnc, false, NULL);
|
|
cx->setReader(oldReader);
|
|
}
|
|
/***************************************************************/
|
|
|
|
JSValue fncPrototype = Object_Type->newInstance(cx);
|
|
ASSERT(fncPrototype.isObject());
|
|
fncPrototype.object->defineVariable(cx, cx->Constructor_StringAtom, (NamespaceList *)NULL, Property::Enumerable, Object_Type, JSValue(fnc));
|
|
fnc->defineVariable(cx, cx->Prototype_StringAtom, (NamespaceList *)NULL, Property::Enumerable, Object_Type, fncPrototype);
|
|
v = JSValue(fnc);
|
|
return v;
|
|
}
|
|
|
|
static JSValue Function_toString(Context *, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/)
|
|
{
|
|
ASSERT(thisValue.getType() == Function_Type);
|
|
return JSValue(new String(widenCString("function () { }")));
|
|
}
|
|
|
|
static JSValue Function_hasInstance(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc)
|
|
{
|
|
ASSERT(argc == 1);
|
|
JSValue v = argv[0];
|
|
if (!v.isObject())
|
|
return kFalseValue;
|
|
|
|
ASSERT(thisValue.isFunction());
|
|
thisValue.function->getProperty(cx, cx->Prototype_StringAtom, CURRENT_ATTR);
|
|
JSValue p = cx->popValue();
|
|
|
|
if (!p.isObject())
|
|
cx->reportError(Exception::typeError, "HasInstance: Function has non-object prototype");
|
|
|
|
JSValue V = v.object->mPrototype;
|
|
while (!V.isNull()) {
|
|
if (V.getObjectValue() == p.object)
|
|
return kTrueValue;
|
|
V = V.getObjectValue()->mPrototype;
|
|
}
|
|
return kFalseValue;
|
|
}
|
|
|
|
static JSValue Function_call(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc)
|
|
{
|
|
if (!thisValue.isFunction())
|
|
cx->reportError(Exception::typeError, "Non-callable object for Function.call");
|
|
|
|
JSValue thisArg;
|
|
if (argc == 0)
|
|
thisArg = JSValue(cx->getGlobalObject());
|
|
else {
|
|
if (argv[0].isUndefined() || argv[0].isNull())
|
|
thisArg = JSValue(cx->getGlobalObject());
|
|
else
|
|
thisArg = JSValue(argv[0].toObject(cx));
|
|
--argc;
|
|
++argv;
|
|
}
|
|
return cx->invokeFunction(thisValue.function, thisArg, argv, argc);
|
|
}
|
|
|
|
static JSValue Function_apply(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc)
|
|
{
|
|
if (!thisValue.isFunction())
|
|
cx->reportError(Exception::typeError, "Non-callable object for Function.call");
|
|
|
|
ContextStackReplacement csr(cx);
|
|
|
|
JSValue thisArg;
|
|
if (argc == 0)
|
|
thisArg = JSValue(cx->getGlobalObject());
|
|
else {
|
|
if (argv[0].isUndefined() || argv[0].isNull())
|
|
thisArg = JSValue(cx->getGlobalObject());
|
|
else
|
|
thisArg = JSValue(argv[0].toObject(cx));
|
|
}
|
|
if (argc <= 1) {
|
|
argv = NULL;
|
|
argc = 0;
|
|
}
|
|
else {
|
|
if (argv[1].getType() != Array_Type)
|
|
cx->reportError(Exception::typeError, "Function.apply must have Array type argument list");
|
|
|
|
ASSERT(argv[1].isObject());
|
|
JSObject *argsObj = argv[1].object;
|
|
argsObj->getProperty(cx, cx->Length_StringAtom, CURRENT_ATTR);
|
|
JSValue result = cx->popValue();
|
|
argc = (uint32)(result.toUInt32(cx).f64);
|
|
|
|
argv = new JSValue[argc];
|
|
for (uint32 i = 0; i < argc; i++) {
|
|
const String *id = numberToString(i);
|
|
argsObj->getProperty(cx, *id, CURRENT_ATTR);
|
|
argv[i] = cx->popValue();
|
|
delete id;
|
|
}
|
|
}
|
|
|
|
return cx->invokeFunction(thisValue.function, thisArg, argv, argc);
|
|
}
|
|
|
|
|
|
static JSValue Number_Constructor(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc)
|
|
{
|
|
JSValue v = thisValue;
|
|
if (v.isNull())
|
|
v = Number_Type->newInstance(cx);
|
|
ASSERT(v.isInstance());
|
|
JSNumberInstance *numInst = checked_cast<JSNumberInstance *>(v.instance);
|
|
if (argc > 0)
|
|
numInst->mValue = argv[0].toNumber(cx).f64;
|
|
else
|
|
numInst->mValue = 0.0;
|
|
return v;
|
|
}
|
|
|
|
static JSValue Number_TypeCast(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 argc)
|
|
{
|
|
if (argc == 0)
|
|
return kPositiveZero;
|
|
else
|
|
return argv[0].toNumber(cx);
|
|
}
|
|
|
|
static JSValue Number_toString(Context *cx, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/)
|
|
{
|
|
if (thisValue.getType() != Number_Type)
|
|
cx->reportError(Exception::typeError, "Number.toString called on something other than a Number object");
|
|
JSNumberInstance *numInst = checked_cast<JSNumberInstance *>(thisValue.instance);
|
|
return JSValue(numberToString(numInst->mValue));
|
|
}
|
|
|
|
static JSValue Number_valueOf(Context *cx, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/)
|
|
{
|
|
if (thisValue.getType() != Number_Type)
|
|
cx->reportError(Exception::typeError, "Number.valueOf called on something other than a Number object");
|
|
JSNumberInstance *numInst = checked_cast<JSNumberInstance *>(thisValue.instance);
|
|
return JSValue(numberToString(numInst->mValue));
|
|
}
|
|
|
|
static JSValue Integer_Constructor(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc)
|
|
{
|
|
JSValue v = thisValue;
|
|
if (v.isNull())
|
|
v = Integer_Type->newInstance(cx);
|
|
ASSERT(v.isInstance());
|
|
JSNumberInstance *numInst = checked_cast<JSNumberInstance *>(v.instance);
|
|
if (argc > 0) {
|
|
float64 d = argv[0].toNumber(cx).f64;
|
|
bool neg = (d < 0);
|
|
d = fd::floor(neg ? -d : d);
|
|
d = neg ? -d : d;
|
|
numInst->mValue = d;
|
|
}
|
|
else
|
|
numInst->mValue = 0.0;
|
|
return v;
|
|
}
|
|
|
|
static JSValue Integer_toString(Context *, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/)
|
|
{
|
|
JSNumberInstance *numInst = checked_cast<JSNumberInstance *>(thisValue.instance);
|
|
return JSValue(numberToString(numInst->mValue));
|
|
}
|
|
|
|
|
|
|
|
static JSValue Boolean_Constructor(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc)
|
|
{
|
|
JSValue v = thisValue;
|
|
if (v.isNull())
|
|
v = Boolean_Type->newInstance(cx);
|
|
ASSERT(v.isInstance());
|
|
JSBooleanInstance *thisObj = checked_cast<JSBooleanInstance *>(v.instance);
|
|
if (argc > 0)
|
|
thisObj->mValue = argv[0].toBoolean(cx).boolean;
|
|
else
|
|
thisObj->mValue = false;
|
|
return v;
|
|
}
|
|
|
|
static JSValue Boolean_TypeCast(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 argc)
|
|
{
|
|
if (argc == 0)
|
|
return kFalseValue;
|
|
else
|
|
return argv[0].toBoolean(cx);
|
|
}
|
|
|
|
static JSValue Boolean_toString(Context *cx, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/)
|
|
{
|
|
if (thisValue.getType() != Boolean_Type)
|
|
cx->reportError(Exception::typeError, "Boolean.toString can only be applied to Boolean objects");
|
|
ASSERT(thisValue.isInstance());
|
|
JSBooleanInstance *thisObj = checked_cast<JSBooleanInstance *>(thisValue.instance);
|
|
if (thisObj->mValue)
|
|
return JSValue(&cx->True_StringAtom);
|
|
else
|
|
return JSValue(&cx->False_StringAtom);
|
|
}
|
|
|
|
static JSValue Boolean_valueOf(Context *cx, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/)
|
|
{
|
|
if (thisValue.getType() != Boolean_Type)
|
|
cx->reportError(Exception::typeError, "Boolean.valueOf can only be applied to Boolean objects");
|
|
ASSERT(thisValue.isInstance());
|
|
JSBooleanInstance *thisObj = checked_cast<JSBooleanInstance *>(thisValue.instance);
|
|
if (thisObj->mValue)
|
|
return kTrueValue;
|
|
else
|
|
return kFalseValue;
|
|
}
|
|
|
|
static JSValue GenericError_Constructor(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc, JSType *errType)
|
|
{
|
|
JSValue v = thisValue;
|
|
if (v.isNull())
|
|
v = errType->newInstance(cx);
|
|
ASSERT(v.isInstance());
|
|
JSObject *thisInst = v.instance;
|
|
JSValue msg;
|
|
if (argc > 0)
|
|
msg = argv[0].toString(cx);
|
|
else
|
|
msg = JSValue(&cx->Empty_StringAtom);
|
|
thisInst->defineVariable(cx, cx->Message_StringAtom, NULL, Property::NoAttribute, String_Type, msg);
|
|
thisInst->defineVariable(cx, cx->Name_StringAtom, NULL, Property::NoAttribute, String_Type, JSValue(errType->mClassName));
|
|
return v;
|
|
}
|
|
|
|
JSValue RegExp_Constructor(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc)
|
|
{
|
|
JSValue thatValue = thisValue;
|
|
if (thatValue.isNull())
|
|
thatValue = RegExp_Type->newInstance(cx);
|
|
ASSERT(thatValue.isInstance());
|
|
JSRegExpInstance *thisInst = checked_cast<JSRegExpInstance *>(thatValue.instance);
|
|
REuint32 flags = 0;
|
|
|
|
const String *regexpStr = &cx->Empty_StringAtom;
|
|
const String *flagStr = &cx->Empty_StringAtom;
|
|
if (argc > 0) {
|
|
if (argv[0].getType() == RegExp_Type) {
|
|
ASSERT(argv[0].isInstance());
|
|
if ((argc == 1) || argv[1].isUndefined()) {
|
|
ContextStackReplacement csr(cx);
|
|
argv[0].instance->getProperty(cx, cx->Source_StringAtom, CURRENT_ATTR);
|
|
JSValue src = cx->popValue();
|
|
ASSERT(src.isString());
|
|
regexpStr = src.string;
|
|
REState *other = (checked_cast<JSRegExpInstance *>(argv[0].instance))->mRegExp;
|
|
flags = other->flags;
|
|
}
|
|
else
|
|
cx->reportError(Exception::typeError, "Illegal RegExp constructor args");
|
|
}
|
|
else
|
|
regexpStr = argv[0].toString(cx).string;
|
|
if ((argc > 1) && !argv[1].isUndefined()) {
|
|
flagStr = argv[1].toString(cx).string;
|
|
if (parseFlags(flagStr->begin(), flagStr->length(), &flags) != RE_NO_ERROR) {
|
|
cx->reportError(Exception::syntaxError, "Failed to parse RegExp : '{0}'", *regexpStr + "/" + *flagStr); // XXX error message?
|
|
}
|
|
}
|
|
}
|
|
REState *pState = REParse(regexpStr->begin(), regexpStr->length(), flags, RE_VERSION_1);
|
|
if (pState) {
|
|
thisInst->mRegExp = pState;
|
|
// XXX ECMA spec says these are DONTENUM, but SpiderMonkey and test suite disagree
|
|
/*
|
|
thisInst->defineVariable(cx, cx->Source_StringAtom, NULL, (Property::DontDelete | Property::ReadOnly), String_Type, JSValue(regexpStr));
|
|
thisInst->defineVariable(cx, cx->Global_StringAtom, NULL, (Property::DontDelete | Property::ReadOnly), Boolean_Type, (pState->flags & GLOBAL) ? kTrueValue : kFalseValue);
|
|
thisInst->defineVariable(cx, cx->IgnoreCase_StringAtom, NULL, (Property::DontDelete | Property::ReadOnly), Boolean_Type, (pState->flags & IGNORECASE) ? kTrueValue : kFalseValue);
|
|
thisInst->defineVariable(cx, cx->Multiline_StringAtom, NULL, (Property::DontDelete | Property::ReadOnly), Boolean_Type, (pState->flags & MULTILINE) ? kTrueValue : kFalseValue);
|
|
thisInst->defineVariable(cx, cx->LastIndex_StringAtom, NULL, Property::DontDelete, Number_Type, kPositiveZero);
|
|
*/
|
|
thisInst->defineVariable(cx, cx->Source_StringAtom, NULL, (Property::DontDelete | Property::ReadOnly | Property::Enumerable), String_Type, JSValue(regexpStr));
|
|
thisInst->defineVariable(cx, cx->Global_StringAtom, NULL, (Property::DontDelete | Property::ReadOnly | Property::Enumerable), Boolean_Type, (pState->flags & RE_GLOBAL) ? kTrueValue : kFalseValue);
|
|
thisInst->defineVariable(cx, cx->IgnoreCase_StringAtom, NULL, (Property::DontDelete | Property::ReadOnly | Property::Enumerable), Boolean_Type, (pState->flags & RE_IGNORECASE) ? kTrueValue : kFalseValue);
|
|
thisInst->defineVariable(cx, cx->Multiline_StringAtom, NULL, (Property::DontDelete | Property::ReadOnly | Property::Enumerable), Boolean_Type, (pState->flags & RE_MULTILINE) ? kTrueValue : kFalseValue);
|
|
thisInst->defineVariable(cx, cx->LastIndex_StringAtom, NULL, (Property::DontDelete | Property::Enumerable), Number_Type, kPositiveZero);
|
|
}
|
|
else {
|
|
cx->reportError(Exception::syntaxError, "Failed to parse RegExp : '{0}'", "/" + *regexpStr + "/" + *flagStr); // XXX error message?
|
|
}
|
|
return thatValue;
|
|
}
|
|
|
|
static JSValue RegExp_TypeCast(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc)
|
|
{
|
|
if (argc > 0) {
|
|
if ((argv[0].getType() == RegExp_Type)
|
|
&& ((argc == 1) || argv[1].isUndefined()))
|
|
return argv[0];
|
|
}
|
|
return RegExp_Constructor(cx, thisValue, argv, argc);
|
|
}
|
|
|
|
static JSValue RegExp_toString(Context *cx, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/)
|
|
{
|
|
ContextStackReplacement csr(cx);
|
|
if (thisValue.getType() != RegExp_Type)
|
|
cx->reportError(Exception::typeError, "RegExp.toString can only be applied to RegExp objects");
|
|
ASSERT(thisValue.isInstance());
|
|
JSRegExpInstance *thisInst = checked_cast<JSRegExpInstance *>(thisValue.instance);
|
|
thisInst->getProperty(cx, cx->Source_StringAtom, CURRENT_ATTR);
|
|
JSValue src = cx->popValue();
|
|
|
|
// XXX not ever expecting this except in the one case of RegExp.prototype, which isn't a fully formed
|
|
// RegExp instance, but has the appropriate type pointer.
|
|
if (src.isUndefined()) {
|
|
ASSERT(thisInst == RegExp_Type->mPrototypeObject.instance);
|
|
return JSValue(&cx->Empty_StringAtom);
|
|
}
|
|
|
|
String *result = new String("/" + *src.toString(cx).string + "/");
|
|
REState *state = (REState *)thisInst->mRegExp;
|
|
if (state->flags & RE_GLOBAL) *result += "g";
|
|
if (state->flags & RE_IGNORECASE) *result += "i";
|
|
if (state->flags & RE_MULTILINE) *result += "m";
|
|
|
|
return JSValue(result);
|
|
}
|
|
|
|
JSValue RegExp_exec(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc)
|
|
{
|
|
if (thisValue.getType() != RegExp_Type)
|
|
cx->reportError(Exception::typeError, "RegExp.exec can only be applied to RegExp objects");
|
|
ASSERT(thisValue.isInstance());
|
|
JSRegExpInstance *thisInst = checked_cast<JSRegExpInstance *>(thisValue.instance);
|
|
|
|
JSValue result = kNullValue;
|
|
if (argc > 0) {
|
|
ContextStackReplacement csr(cx);
|
|
int32 index = 0;
|
|
|
|
const String *str = argv[0].toString(cx).string;
|
|
|
|
RegExp_Type->getProperty(cx, cx->Multiline_StringAtom, CURRENT_ATTR);
|
|
JSValue globalMultiline = cx->popValue();
|
|
|
|
if (thisInst->mRegExp->flags & RE_GLOBAL) {
|
|
// XXX implement lastIndex as a setter/getter pair instead ???
|
|
thisInst->getProperty(cx, cx->LastIndex_StringAtom, CURRENT_ATTR);
|
|
JSValue lastIndex = cx->popValue();
|
|
index = (int32)(lastIndex.toInteger(cx).f64);
|
|
}
|
|
|
|
REMatchState *match = REExecute(thisInst->mRegExp, str->begin(), index, str->length(), globalMultiline.toBoolean(cx).boolean);
|
|
if (match) {
|
|
result = Array_Type->newInstance(cx);
|
|
String *matchStr = new String(str->substr(match->startIndex, match->endIndex - match->startIndex));
|
|
result.instance->setProperty(cx, *numberToString(0), NULL, JSValue(matchStr));
|
|
String *parenStr = &cx->Empty_StringAtom;
|
|
for (int32 i = 0; i < match->parenCount; i++) {
|
|
if (match->parens[i].index != -1) {
|
|
String *parenStr = new String(str->substr((uint32)(match->parens[i].index), (uint32)(match->parens[i].length)));
|
|
result.instance->setProperty(cx, *numberToString(i + 1), NULL, JSValue(parenStr));
|
|
}
|
|
else
|
|
result.instance->setProperty(cx, *numberToString(i + 1), NULL, kUndefinedValue);
|
|
}
|
|
// XXX SpiderMonkey also adds 'index' and 'input' properties to the result
|
|
result.instance->setProperty(cx, cx->Index_StringAtom, CURRENT_ATTR, JSValue((float64)(match->startIndex)));
|
|
result.instance->setProperty(cx, cx->Input_StringAtom, CURRENT_ATTR, JSValue(str));
|
|
|
|
// XXX Set up the SpiderMonkey 'RegExp statics'
|
|
RegExp_Type->setProperty(cx, cx->LastMatch_StringAtom, CURRENT_ATTR, JSValue(matchStr));
|
|
RegExp_Type->setProperty(cx, cx->LastParen_StringAtom, CURRENT_ATTR, JSValue(parenStr));
|
|
String *contextStr = new String(str->substr(0, match->startIndex));
|
|
RegExp_Type->setProperty(cx, cx->LeftContext_StringAtom, CURRENT_ATTR, JSValue(contextStr));
|
|
contextStr = new String(str->substr(match->endIndex, str->length() - match->endIndex));
|
|
RegExp_Type->setProperty(cx, cx->RightContext_StringAtom, CURRENT_ATTR, JSValue(contextStr));
|
|
|
|
if (thisInst->mRegExp->flags & RE_GLOBAL) {
|
|
index = match->endIndex;
|
|
thisInst->setProperty(cx, cx->LastIndex_StringAtom, CURRENT_ATTR, JSValue((float64)index));
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static JSValue RegExp_test(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc)
|
|
{
|
|
if (thisValue.getType() != RegExp_Type)
|
|
cx->reportError(Exception::typeError, "RegExp.test can only be applied to RegExp objects");
|
|
ASSERT(thisValue.isInstance());
|
|
JSRegExpInstance *thisInst = checked_cast<JSRegExpInstance *>(thisValue.instance);
|
|
if (argc > 0) {
|
|
ContextStackReplacement csr(cx);
|
|
const String *str = argv[0].toString(cx).string;
|
|
RegExp_Type->getProperty(cx, cx->Multiline_StringAtom, CURRENT_ATTR);
|
|
JSValue globalMultiline = cx->popValue();
|
|
REMatchState *match = REExecute(thisInst->mRegExp, str->begin(), 0, str->length(), globalMultiline.toBoolean(cx).boolean);
|
|
if (match)
|
|
return kTrueValue;
|
|
}
|
|
return kFalseValue;
|
|
}
|
|
|
|
|
|
JSValue Error_Constructor(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc)
|
|
{
|
|
return GenericError_Constructor(cx, thisValue, argv, argc, Error_Type);
|
|
}
|
|
|
|
static JSValue Error_toString(Context *cx, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/)
|
|
{
|
|
ContextStackReplacement csr(cx);
|
|
|
|
if ((thisValue.getType() != Error_Type) && !thisValue.getType()->derivesFrom(Error_Type))
|
|
cx->reportError(Exception::typeError, "Error.toString can only be applied to Error objects");
|
|
ASSERT(thisValue.isInstance());
|
|
JSInstance *thisInstance = thisValue.instance;
|
|
thisInstance->getProperty(cx, cx->Message_StringAtom, CURRENT_ATTR);
|
|
JSValue msg = cx->popValue();
|
|
thisInstance->getProperty(cx, cx->Name_StringAtom, CURRENT_ATTR);
|
|
JSValue name = cx->popValue();
|
|
|
|
String *result = new String(*name.toString(cx).string + ":" + *msg.toString(cx).string);
|
|
return JSValue(result);
|
|
}
|
|
|
|
JSValue EvalError_Constructor(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc)
|
|
{
|
|
return GenericError_Constructor(cx, thisValue, argv, argc, EvalError_Type);
|
|
}
|
|
|
|
JSValue RangeError_Constructor(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc)
|
|
{
|
|
return GenericError_Constructor(cx, thisValue, argv, argc, RangeError_Type);
|
|
}
|
|
|
|
JSValue ReferenceError_Constructor(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc)
|
|
{
|
|
return GenericError_Constructor(cx, thisValue, argv, argc, ReferenceError_Type);
|
|
}
|
|
|
|
JSValue SyntaxError_Constructor(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc)
|
|
{
|
|
return GenericError_Constructor(cx, thisValue, argv, argc, SyntaxError_Type);
|
|
}
|
|
|
|
JSValue TypeError_Constructor(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc)
|
|
{
|
|
return GenericError_Constructor(cx, thisValue, argv, argc, TypeError_Type);
|
|
}
|
|
|
|
JSValue UriError_Constructor(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc)
|
|
{
|
|
return GenericError_Constructor(cx, thisValue, argv, argc, UriError_Type);
|
|
}
|
|
|
|
|
|
static JSValue ExtendAttribute_Invoke(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue *argv, uint32 argc)
|
|
{
|
|
ASSERT(argc == 1);
|
|
|
|
Attribute *x = new Attribute(Property::Extend, Property::Extend | Property::Virtual);
|
|
ASSERT(argv[0].isType());
|
|
x->mExtendArgument = (JSType *)(argv[0].type);
|
|
|
|
return JSValue(x);
|
|
}
|
|
|
|
static JSValue GlobalObject_Eval(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 argc)
|
|
{
|
|
if (argc > 0) {
|
|
if (!argv[0].isString())
|
|
return argv[0];
|
|
const String *sourceStr = argv[0].toString(cx).string;
|
|
|
|
Activation *prev = cx->mActivationStack.top();
|
|
|
|
return cx->readEvalString(*sourceStr, widenCString("eval source"), cx->mScopeChain, prev->mThis);
|
|
}
|
|
return kUndefinedValue;
|
|
}
|
|
|
|
static JSValue GlobalObject_ParseInt(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 argc)
|
|
{
|
|
ASSERT(argc >= 1);
|
|
int radix = 0; // default for stringToInteger
|
|
if (argc == 2)
|
|
radix = (int)(argv[1].toInt32(cx).f64);
|
|
if ((radix < 0) || (radix > 36))
|
|
return kNaNValue;
|
|
|
|
const String *string = argv[0].toString(cx).string;
|
|
const char16 *numEnd;
|
|
const char16 *sBegin = string->begin();
|
|
float64 f = 0.0;
|
|
if (sBegin)
|
|
f = stringToInteger(sBegin, string->end(), numEnd, (uint)radix);
|
|
return JSValue(f);
|
|
}
|
|
|
|
static JSValue GlobalObject_ParseFloat(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 argc)
|
|
{
|
|
ASSERT(argc == 1);
|
|
|
|
const String *string = argv[0].toString(cx).string;
|
|
const char16 *numEnd;
|
|
const char16 *sBegin = string->begin();
|
|
float64 f = 0.0;
|
|
if (sBegin)
|
|
f = stringToDouble(sBegin, string->end(), numEnd);
|
|
return JSValue(f);
|
|
}
|
|
|
|
static JSValue GlobalObject_isNaN(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
|
|
{
|
|
float64 f = argv[0].toNumber(cx).f64;
|
|
if (JSDOUBLE_IS_NaN(f))
|
|
return kTrueValue;
|
|
else
|
|
return kFalseValue;
|
|
}
|
|
|
|
static JSValue GlobalObject_isFinite(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/)
|
|
{
|
|
float64 f = argv[0].toNumber(cx).f64;
|
|
if (JSDOUBLE_IS_FINITE(f))
|
|
return kTrueValue;
|
|
else
|
|
return kFalseValue;
|
|
}
|
|
|
|
/*
|
|
* Stuff to emulate the old libmocha escape, which took a second argument
|
|
* giving the type of escape to perform. Retained for compatibility, and
|
|
* copied here to avoid reliance on net.h, mkparse.c/NET_EscapeBytes.
|
|
*/
|
|
#define URL_XALPHAS ((uint8) 1)
|
|
#define URL_XPALPHAS ((uint8) 2)
|
|
#define URL_PATH ((uint8) 4)
|
|
|
|
static const uint8 urlCharType[256] =
|
|
/* Bit 0 xalpha -- the alphas
|
|
* Bit 1 xpalpha -- as xalpha but
|
|
* converts spaces to plus and plus to %20
|
|
* Bit 2 ... path -- as xalphas but doesn't escape '/'
|
|
*/
|
|
/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
|
|
{ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0x */
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 1x */
|
|
0,0,0,0,0,0,0,0,0,0,7,4,0,7,7,4, /* 2x !"#$%&'()*+,-./ */
|
|
7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0, /* 3x 0123456789:;<=>? */
|
|
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* 4x @ABCDEFGHIJKLMNO */
|
|
7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,7, /* 5X PQRSTUVWXYZ[\]^_ */
|
|
0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* 6x `abcdefghijklmno */
|
|
7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,0, /* 7X pqrstuvwxyz{\}~ DEL */
|
|
0, };
|
|
|
|
/* This matches the ECMA escape set when mask is 7 (default.) */
|
|
|
|
#define IS_OK(C, mask) (urlCharType[((uint8) (C))] & (mask))
|
|
|
|
/* See ECMA-262 15.1.2.4. */
|
|
static JSValue GlobalObject_escape(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 argc)
|
|
{
|
|
if (argc > 0) {
|
|
uint32 i, ni, length, newlength;
|
|
char16 ch;
|
|
|
|
const char digits[] = {'0', '1', '2', '3', '4', '5', '6', '7',
|
|
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
|
|
|
|
uint32 mask = URL_XALPHAS | URL_XPALPHAS | URL_PATH;
|
|
if (argc > 1) {
|
|
float64 d = argv[1].toNumber(cx).f64;
|
|
if (!JSDOUBLE_IS_FINITE(d) || (mask = (uint32)d) != d || mask & ~(URL_XALPHAS | URL_XPALPHAS | URL_PATH))
|
|
cx->reportError(Exception::runtimeError, "Bad string mask for escape");
|
|
}
|
|
|
|
const String *str = argv[0].toString(cx).string;
|
|
|
|
const char16 *chars = str->begin();
|
|
length = newlength = str->size();
|
|
|
|
/* Take a first pass and see how big the result string will need to be. */
|
|
for (i = 0; i < length; i++) {
|
|
if ((ch = chars[i]) < 128 && IS_OK(ch, mask))
|
|
continue;
|
|
if (ch < 256) {
|
|
if (mask == URL_XPALPHAS && ch == ' ')
|
|
continue; /* The character will be encoded as '+' */
|
|
newlength += 2; /* The character will be encoded as %XX */
|
|
} else {
|
|
newlength += 5; /* The character will be encoded as %uXXXX */
|
|
}
|
|
}
|
|
|
|
char16 *newchars = new char16[newlength + 1];
|
|
for (i = 0, ni = 0; i < length; i++) {
|
|
if ((ch = chars[i]) < 128 && IS_OK(ch, mask)) {
|
|
newchars[ni++] = ch;
|
|
} else if (ch < 256) {
|
|
if (mask == URL_XPALPHAS && ch == ' ') {
|
|
newchars[ni++] = '+'; /* convert spaces to pluses */
|
|
} else {
|
|
newchars[ni++] = '%';
|
|
newchars[ni++] = digits[ch >> 4];
|
|
newchars[ni++] = digits[ch & 0xF];
|
|
}
|
|
} else {
|
|
newchars[ni++] = '%';
|
|
newchars[ni++] = 'u';
|
|
newchars[ni++] = digits[ch >> 12];
|
|
newchars[ni++] = digits[(ch & 0xF00) >> 8];
|
|
newchars[ni++] = digits[(ch & 0xF0) >> 4];
|
|
newchars[ni++] = digits[ch & 0xF];
|
|
}
|
|
}
|
|
ASSERT(ni == newlength);
|
|
newchars[newlength] = 0;
|
|
|
|
String *result = new String(newchars, newlength);
|
|
delete[] newchars;
|
|
return JSValue(result);
|
|
}
|
|
return JSValue(&cx->Undefined_StringAtom);
|
|
}
|
|
#undef IS_OK
|
|
|
|
/* See ECMA-262 15.1.2.5 */
|
|
static JSValue GlobalObject_unescape(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 argc)
|
|
{
|
|
if (argc > 0) {
|
|
uint32 i, ni;
|
|
char16 ch;
|
|
|
|
const String *str = argv[0].toString(cx).string;
|
|
|
|
const char16 *chars = str->begin();
|
|
uint32 length = str->size();
|
|
|
|
/* Don't bother allocating less space for the new string. */
|
|
char16 *newchars = new char16[length + 1];
|
|
ni = i = 0;
|
|
while (i < length) {
|
|
uint hexValue[4];
|
|
ch = chars[i++];
|
|
if (ch == '%') {
|
|
if (i + 1 < length &&
|
|
isASCIIHexDigit(chars[i], hexValue[0]) && isASCIIHexDigit(chars[i + 1], hexValue[1]))
|
|
{
|
|
ch = static_cast<char16>(hexValue[0] * 16 + hexValue[1]);
|
|
i += 2;
|
|
} else if (i + 4 < length && chars[i] == 'u' &&
|
|
isASCIIHexDigit(chars[i + 1], hexValue[0]) && isASCIIHexDigit(chars[i + 2], hexValue[1]) &&
|
|
isASCIIHexDigit(chars[i + 3], hexValue[2]) && isASCIIHexDigit(chars[i + 4], hexValue[3]))
|
|
{
|
|
ch = static_cast<char16>((((((hexValue[0] << 4)
|
|
+ hexValue[1]) << 4)
|
|
+ hexValue[2]) << 4)
|
|
+ hexValue[3]);
|
|
i += 5;
|
|
}
|
|
}
|
|
newchars[ni++] = ch;
|
|
}
|
|
newchars[ni] = 0;
|
|
|
|
String *result = new String(newchars, ni);
|
|
delete[] newchars;
|
|
return JSValue(result);
|
|
}
|
|
return JSValue(&cx->Undefined_StringAtom);
|
|
}
|
|
|
|
|
|
JSFunction::JSFunction(Context *cx, JSType *resultType, ScopeChain *scopeChain)
|
|
: JSInstance(cx, Function_Type),
|
|
mParameterBarrel(NULL),
|
|
mActivation(),
|
|
mByteCode(NULL),
|
|
mCode(NULL),
|
|
mResultType(resultType),
|
|
mRequiredParameters(0),
|
|
mOptionalParameters(0),
|
|
mNamedParameters(0),
|
|
mParameters(NULL),
|
|
mScopeChain(NULL),
|
|
mIsPrototype(false),
|
|
mIsConstructor(false),
|
|
mIsChecked(true),
|
|
mHasRestParameter(false),
|
|
mClass(NULL),
|
|
mFunctionName(NULL)
|
|
{
|
|
if (scopeChain) {
|
|
mScopeChain = new ScopeChain(*scopeChain);
|
|
}
|
|
if (Function_Type) // protect against bootstrap
|
|
mPrototype = Function_Type->mPrototypeObject;
|
|
mActivation.mContainer = this;
|
|
defineVariable(cx, cx->Prototype_StringAtom, (NamespaceList *)NULL, 0, Object_Type, JSValue(Object_Type->newInstance(cx)));
|
|
if (!mPrototype.isNull())
|
|
defineVariable(cx, cx->UnderbarPrototype_StringAtom, (NamespaceList *)NULL, 0, Object_Type, mPrototype);
|
|
}
|
|
|
|
JSFunction::JSFunction(Context *cx, NativeCode *code, JSType *resultType)
|
|
: JSInstance(cx, Function_Type),
|
|
mParameterBarrel(NULL),
|
|
mActivation(),
|
|
mByteCode(NULL),
|
|
mCode(code),
|
|
mResultType(resultType),
|
|
mRequiredParameters(0),
|
|
mOptionalParameters(0),
|
|
mNamedParameters(0),
|
|
mParameters(NULL),
|
|
mScopeChain(NULL),
|
|
mIsPrototype(false),
|
|
mIsConstructor(false),
|
|
mIsChecked(false), // native functions aren't checked (?)
|
|
mHasRestParameter(false),
|
|
mClass(NULL),
|
|
mFunctionName(NULL)
|
|
{
|
|
if (Function_Type) // protect against bootstrap
|
|
mPrototype = Function_Type->mPrototypeObject;
|
|
mActivation.mContainer = this;
|
|
defineVariable(cx, cx->Prototype_StringAtom, (NamespaceList *)NULL, 0, Object_Type, JSValue(Object_Type->newInstance(cx)));
|
|
defineVariable(cx, cx->UnderbarPrototype_StringAtom, (NamespaceList *)NULL, 0, Object_Type, mPrototype);
|
|
}
|
|
|
|
JSValue JSFunction::runParameterInitializer(Context *cx, uint32 a, const JSValue& thisValue, JSValue *argv, uint32 argc)
|
|
{
|
|
ASSERT(mParameters && (a < maxParameterIndex()));
|
|
return cx->interpret(getByteCode(), (int32)mParameters[a].mInitializer, getScopeChain(), thisValue, argv, argc);
|
|
}
|
|
|
|
void JSFunction::setParameterCounts(Context *cx, uint32 r, uint32 o, uint32 n, bool hasRest)
|
|
{
|
|
mHasRestParameter = hasRest;
|
|
mRequiredParameters = r;
|
|
mOptionalParameters = o;
|
|
mNamedParameters = n;
|
|
mParameters = new ParameterData[mRequiredParameters + mOptionalParameters + + mNamedParameters + ((hasRest) ? 1 : 0)];
|
|
defineVariable(cx, cx->Length_StringAtom, (NamespaceList *)NULL, Property::DontDelete | Property::ReadOnly, Number_Type, JSValue((float64)mRequiredParameters));
|
|
}
|
|
|
|
|
|
|
|
|
|
void Context::assureStackSpace(uint32 s)
|
|
{
|
|
if ((mStackMax - mStackTop) < s) {
|
|
JSValue *newStack = new JSValue[mStackMax + s];
|
|
for (uint32 i = 0; i < mStackTop; i++)
|
|
newStack[i] = mStack[i];
|
|
delete[] mStack;
|
|
mStack = newStack;
|
|
mStackMax += s;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Initialize a built-in class - setting the functions into the prototype object
|
|
void Context::initClass(JSType *type, ClassDef *cdef, PrototypeFunctions *pdef)
|
|
{
|
|
mScopeChain->addScope(type);
|
|
JSFunction *constructor = new JSFunction(this, cdef->defCon, Object_Type);
|
|
constructor->setClass(type);
|
|
constructor->setFunctionName(type->mClassName);
|
|
type->setDefaultConstructor(this, constructor);
|
|
|
|
// the prototype functions are defined in the prototype object...
|
|
if (pdef) {
|
|
for (uint32 i = 0; i < pdef->mCount; i++) {
|
|
JSFunction *fun = new JSFunction(this, pdef->mDef[i].imp, pdef->mDef[i].result);
|
|
// fun->setClass(type); don't do this, it makes the function a method
|
|
StringAtom *name = &mWorld.identifiers[widenCString(pdef->mDef[i].name)];
|
|
fun->setFunctionName(name);
|
|
fun->setParameterCounts(this, pdef->mDef[i].length, 0, 0, false);
|
|
type->mPrototypeObject.getObjectValue()->defineVariable(this, *name,
|
|
(NamespaceList *)(NULL),
|
|
Property::NoAttribute,
|
|
pdef->mDef[i].result,
|
|
JSValue(fun));
|
|
}
|
|
}
|
|
type->completeClass(this, mScopeChain);
|
|
type->setStaticInitializer(this, NULL);
|
|
type->mUninitializedValue = *cdef->uninit;
|
|
getGlobalObject()->defineVariable(this, widenCString(cdef->name), (NamespaceList *)(NULL), Property::NoAttribute, Type_Type, JSValue(type));
|
|
mScopeChain->popScope();
|
|
if (pdef) delete pdef;
|
|
}
|
|
|
|
static JSValue arrayMaker(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 argc)
|
|
{
|
|
ASSERT(argc == 2);
|
|
ASSERT(argv[0].isType());
|
|
JSType *baseType = argv[0].type;
|
|
|
|
if ((baseType == Array_Type) && argv[1].isType()) {
|
|
JSType *elementType = argv[1].type;
|
|
JSType *result = new JSArrayType(cx, elementType, NULL, Object_Type, kNullValue, kNullValue);
|
|
result->setDefaultConstructor(cx, Array_Type->getDefaultConstructor());
|
|
return JSValue(result);
|
|
}
|
|
else {
|
|
// then it's just <type>[] - an element reference
|
|
argv[0].type->getProperty(cx, *argv[1].toString(cx).string, NULL);
|
|
return cx->popValue();
|
|
}
|
|
|
|
}
|
|
|
|
void Context::initBuiltins()
|
|
{
|
|
ClassDef builtInClasses[] =
|
|
{
|
|
{ "Object", Object_Constructor, &kUndefinedValue },
|
|
{ "Type", NULL, &kNullValue },
|
|
{ "Function", Function_Constructor, &kNullValue },
|
|
{ "Number", Number_Constructor, &kPositiveZero },
|
|
{ "Integer", Integer_Constructor, &kPositiveZero },
|
|
{ "String", String_Constructor, &kNullValue },
|
|
{ "Array", Array_Constructor, &kNullValue },
|
|
{ "Boolean", Boolean_Constructor, &kFalseValue },
|
|
{ "Void", NULL, &kUndefinedValue },
|
|
{ "Unit", NULL, &kNullValue },
|
|
{ "Attribute", NULL, &kNullValue },
|
|
{ "NamedArgument", NULL, &kNullValue },
|
|
{ "Date", Date_Constructor, &kPositiveZero },
|
|
{ "Null", NULL, &kNullValue },
|
|
{ "Error", Error_Constructor, &kNullValue },
|
|
{ "EvalError", EvalError_Constructor, &kNullValue },
|
|
{ "RangeError", RangeError_Constructor, &kNullValue },
|
|
{ "ReferenceError", ReferenceError_Constructor, &kNullValue },
|
|
{ "SyntaxError", SyntaxError_Constructor, &kNullValue },
|
|
{ "TypeError", TypeError_Constructor, &kNullValue },
|
|
{ "UriError", UriError_Constructor, &kNullValue },
|
|
{ "RegExp", RegExp_Constructor, &kNullValue },
|
|
|
|
{ "Package", NULL, &kNullValue },
|
|
|
|
};
|
|
|
|
Object_Type = new JSObjectType(this, &mWorld.identifiers[widenCString(builtInClasses[0].name)], NULL, kNullValue, kNullValue);
|
|
// Object_Type->mPrototype->mType = Object_Type;
|
|
Object_Type->mIsDynamic = true; // XXX aren't all the built-ins thus?
|
|
|
|
Type_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[1].name)], Object_Type, kNullValue, kNullValue);
|
|
Object_Type->mType = Type_Type;
|
|
|
|
//
|
|
// ECMA 1.5 says Function.prototype is a Function object, it's [[class]] property is "Function"
|
|
// ECMA 2.0 says Function.prototype is an Object NOT an instance of the class
|
|
// here we sort of manage that by having a JSFunction object as the prototype object, not a JSFunctionInstance
|
|
// (which we don't actually have, hmm).
|
|
// For String, etc. this same issue needs to be finessed
|
|
|
|
JSValue protoVal;
|
|
|
|
JSFunction *funProto = new JSFunction(this, Object_Type, NULL);
|
|
funProto->mPrototype = Object_Type->mPrototypeObject;
|
|
funProto->defineVariable(this, UnderbarPrototype_StringAtom, NULL, 0, Object_Type, funProto->mPrototype);
|
|
protoVal = JSValue(funProto);
|
|
Function_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[2].name)], Object_Type, protoVal, kNullValue);
|
|
Function_Type->mPrototype = Function_Type->mPrototypeObject;
|
|
funProto->mType = Function_Type;
|
|
|
|
// now we can bootstrap the Object prototype (__proto__) to be the Function prototype
|
|
Object_Type->mPrototype = Function_Type->mPrototypeObject;
|
|
Object_Type->setProperty(this, UnderbarPrototype_StringAtom, (NamespaceList *)NULL, Object_Type->mPrototype);
|
|
|
|
|
|
JSNumberInstance *numProto = new JSNumberInstance(this);
|
|
protoVal = JSValue(numProto);
|
|
Number_Type = new JSNumberType(this, &mWorld.identifiers[widenCString(builtInClasses[3].name)], Object_Type, protoVal, Function_Type->mPrototypeObject);
|
|
numProto->mType = Number_Type;
|
|
|
|
Integer_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[4].name)], Object_Type, kNullValue, kNullValue);
|
|
|
|
JSStringInstance *strProto = new JSStringInstance(this);
|
|
protoVal = JSValue(strProto);
|
|
String_Type = new JSStringType(this, &mWorld.identifiers[widenCString(builtInClasses[5].name)], Object_Type, protoVal, Function_Type->mPrototypeObject);
|
|
strProto->mValue = &Empty_StringAtom;
|
|
strProto->mPrototype = Function_Type->mPrototypeObject;
|
|
strProto->mType = String_Type;
|
|
|
|
JSArrayInstance *arrayProto = new JSArrayInstance(this);
|
|
protoVal = JSValue(arrayProto);
|
|
Array_Type = new JSArrayType(this, Object_Type, &mWorld.identifiers[widenCString(builtInClasses[6].name)], Object_Type, protoVal, Function_Type->mPrototypeObject);
|
|
arrayProto->mType = Array_Type;
|
|
|
|
JSBooleanInstance *boolProto = new JSBooleanInstance(this);
|
|
protoVal = JSValue(boolProto);
|
|
Boolean_Type = new JSBooleanType(this, &mWorld.identifiers[widenCString(builtInClasses[7].name)], Object_Type, protoVal, Function_Type->mPrototypeObject);
|
|
boolProto->mValue = false;
|
|
boolProto->mType = Boolean_Type;
|
|
|
|
JSDateInstance *dateProto = new JSDateInstance(this);
|
|
protoVal = JSValue(dateProto);
|
|
Date_Type = new JSDateType(this, &mWorld.identifiers[widenCString(builtInClasses[12].name)], Object_Type, protoVal, Function_Type->mPrototypeObject);
|
|
dateProto->mType = Date_Type;
|
|
|
|
Void_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[8].name)], Object_Type, kNullValue, kNullValue);
|
|
Unit_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[9].name)], Object_Type, kNullValue, kNullValue);
|
|
Attribute_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[10].name)], Object_Type, kNullValue, kNullValue);
|
|
NamedArgument_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[11].name)], Object_Type, kNullValue, kNullValue);
|
|
Null_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[13].name)], Object_Type, kNullValue, kNullValue);
|
|
Error_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[14].name)], Object_Type, kNullValue, kNullValue);
|
|
EvalError_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[15].name)], Error_Type, kNullValue, kNullValue);
|
|
RangeError_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[16].name)], Error_Type, kNullValue, kNullValue);
|
|
ReferenceError_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[17].name)], Error_Type, kNullValue, kNullValue);
|
|
SyntaxError_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[18].name)], Error_Type, kNullValue, kNullValue);
|
|
TypeError_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[19].name)], Error_Type, kNullValue, kNullValue);
|
|
UriError_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[20].name)], Error_Type, kNullValue, kNullValue);
|
|
|
|
// XXX RegExp.prototype is set to a RegExp instance, which isn't ECMA (it's supposed to be an Object instance) but
|
|
// is SpiderMonkey compatible.
|
|
JSRegExpInstance *regExpProto = new JSRegExpInstance(this);
|
|
protoVal = JSValue(regExpProto);
|
|
RegExp_Type = new JSRegExpType(this, &mWorld.identifiers[widenCString(builtInClasses[21].name)], Object_Type, protoVal, Function_Type->mPrototypeObject);
|
|
regExpProto->mPrototype = Object_Type->mPrototypeObject;
|
|
regExpProto->mType = RegExp_Type;
|
|
|
|
Package_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[22].name)], Object_Type, kNullValue, kNullValue);
|
|
|
|
|
|
|
|
|
|
ProtoFunDef objectProtos[] =
|
|
{
|
|
{ "toString", String_Type, 0, Object_toString },
|
|
{ "toSource", String_Type, 0, Object_toString },
|
|
{ "forin", Object_Type, 0, Object_forin },
|
|
{ "next", Object_Type, 0, Object_next },
|
|
{ "done", Object_Type, 0, Object_done },
|
|
{ "valueOf", Object_Type, 0, Object_valueOf },
|
|
{ NULL }
|
|
};
|
|
ProtoFunDef functionProtos[] =
|
|
{
|
|
{ "toString", String_Type, 0, Function_toString },
|
|
{ "toSource", String_Type, 0, Function_toString },
|
|
{ "hasInstance", Boolean_Type, 1, Function_hasInstance },
|
|
{ "call", Object_Type, 1, Function_call },
|
|
{ "apply", Object_Type, 2, Function_apply },
|
|
{ NULL }
|
|
};
|
|
ProtoFunDef numberProtos[] =
|
|
{
|
|
{ "toString", String_Type, 0, Number_toString },
|
|
{ "toSource", String_Type, 0, Number_toString },
|
|
{ "valueOf", Number_Type, 0, Number_valueOf },
|
|
{ NULL }
|
|
};
|
|
ProtoFunDef integerProtos[] =
|
|
{
|
|
{ "toString", String_Type, 0, Number_toString },
|
|
{ "toSource", String_Type, 0, Number_toString },
|
|
{ NULL }
|
|
};
|
|
ProtoFunDef booleanProtos[] =
|
|
{
|
|
{ "toString", String_Type, 0, Boolean_toString },
|
|
{ "toSource", String_Type, 0, Boolean_toString },
|
|
{ "valueOf", Number_Type, 0, Boolean_valueOf },
|
|
{ NULL }
|
|
};
|
|
ProtoFunDef errorProtos[] =
|
|
{
|
|
{ "toString", String_Type, 0, Error_toString },
|
|
{ "toSource", String_Type, 0, Error_toString },
|
|
{ NULL }
|
|
};
|
|
ProtoFunDef regexpProtos[] =
|
|
{
|
|
{ "toString", String_Type, 0, RegExp_toString },
|
|
{ "toSource", String_Type, 0, RegExp_toString },
|
|
{ "exec", Array_Type, 0, RegExp_exec },
|
|
{ "test", Boolean_Type,0, RegExp_test },
|
|
{ NULL }
|
|
};
|
|
|
|
ASSERT(mGlobal);
|
|
*mGlobal = Object_Type->newInstance(this).object;
|
|
initClass(Object_Type, &builtInClasses[0], new PrototypeFunctions(&objectProtos[0]) );
|
|
|
|
initClass(Type_Type, &builtInClasses[1], NULL );
|
|
initClass(Function_Type, &builtInClasses[2], new PrototypeFunctions(&functionProtos[0]) );
|
|
initClass(Number_Type, &builtInClasses[3], new PrototypeFunctions(&numberProtos[0]) );
|
|
initClass(Integer_Type, &builtInClasses[4], new PrototypeFunctions(&integerProtos[0]) );
|
|
initClass(String_Type, &builtInClasses[5], getStringProtos() );
|
|
initClass(Array_Type, &builtInClasses[6], getArrayProtos() );
|
|
initClass(Boolean_Type, &builtInClasses[7], new PrototypeFunctions(&booleanProtos[0]) );
|
|
initClass(Void_Type, &builtInClasses[8], NULL);
|
|
initClass(Unit_Type, &builtInClasses[9], NULL);
|
|
initClass(Attribute_Type, &builtInClasses[10], NULL);
|
|
initClass(NamedArgument_Type, &builtInClasses[11], NULL);
|
|
initClass(Date_Type, &builtInClasses[12], getDateProtos() );
|
|
initClass(Null_Type, &builtInClasses[13], NULL);
|
|
initClass(Error_Type, &builtInClasses[14], new PrototypeFunctions(&errorProtos[0]));
|
|
initClass(EvalError_Type, &builtInClasses[15], new PrototypeFunctions(&errorProtos[0])); // XXX shouldn't these be inherited from prototype?
|
|
initClass(RangeError_Type, &builtInClasses[16], new PrototypeFunctions(&errorProtos[0]));
|
|
initClass(ReferenceError_Type, &builtInClasses[17], new PrototypeFunctions(&errorProtos[0]));
|
|
initClass(SyntaxError_Type, &builtInClasses[18], new PrototypeFunctions(&errorProtos[0]));
|
|
initClass(TypeError_Type, &builtInClasses[19], new PrototypeFunctions(&errorProtos[0]));
|
|
initClass(UriError_Type, &builtInClasses[20], new PrototypeFunctions(&errorProtos[0]));
|
|
initClass(RegExp_Type, &builtInClasses[21], new PrototypeFunctions(®expProtos[0]));
|
|
|
|
defineOperator(Index, Type_Type, new JSFunction(this, arrayMaker, Type_Type));
|
|
|
|
Object_Type->mTypeCast = new JSFunction(this, Object_Constructor, Object_Type);
|
|
Function_Type->mTypeCast = new JSFunction(this, Function_Constructor, Object_Type);
|
|
|
|
Number_Type->mTypeCast = new JSFunction(this, Number_TypeCast, Number_Type);
|
|
|
|
defineOperator(Index, Array_Type, new JSFunction(this, Array_GetElement, Object_Type));
|
|
defineOperator(IndexEqual, Array_Type, new JSFunction(this, Array_SetElement, Object_Type));
|
|
Array_Type->mTypeCast = new JSFunction(this, Array_Constructor, Array_Type);
|
|
Array_Type->defineVariable(this, Length_StringAtom, NULL, Property::NoAttribute, Number_Type, JSValue(1.0));
|
|
|
|
Boolean_Type->mTypeCast = new JSFunction(this, Boolean_TypeCast, Boolean_Type);
|
|
Boolean_Type->defineVariable(this, Length_StringAtom, NULL, Property::NoAttribute, Number_Type, JSValue(1.0));
|
|
|
|
Date_Type->mTypeCast = new JSFunction(this, Date_TypeCast, String_Type);
|
|
Date_Type->defineStaticMethod(this, widenCString("parse"), NULL, new JSFunction(this, Date_parse, Number_Type));
|
|
Date_Type->defineStaticMethod(this, widenCString("UTC"), NULL, new JSFunction(this, Date_UTC, Number_Type));
|
|
|
|
JSFunction *fromCharCode = new JSFunction(this, String_fromCharCode, String_Type);
|
|
fromCharCode->defineVariable(this, Length_StringAtom, (NamespaceList *)NULL, Property::DontDelete | Property::ReadOnly, Number_Type, JSValue(1.0));
|
|
String_Type->defineVariable(this, FromCharCode_StringAtom, NULL, String_Type, JSValue(fromCharCode));
|
|
String_Type->mTypeCast = new JSFunction(this, String_TypeCast, String_Type);
|
|
String_Type->defineVariable(this, Length_StringAtom, NULL, Property::NoAttribute, Number_Type, JSValue(1.0));
|
|
|
|
Error_Type->mTypeCast = new JSFunction(this, Error_Constructor, Error_Type);
|
|
RegExp_Type->mTypeCast = new JSFunction(this, RegExp_TypeCast, Error_Type);
|
|
|
|
// XXX these RegExp statics are not specified by ECMA, but implemented by SpiderMonkey
|
|
RegExp_Type->defineVariable(this, Input_StringAtom, NULL, Property::NoAttribute, String_Type, JSValue(&Empty_StringAtom));
|
|
RegExp_Type->defineVariable(this, LastMatch_StringAtom, NULL, Property::NoAttribute, String_Type, JSValue(&Empty_StringAtom));
|
|
RegExp_Type->defineVariable(this, LastParen_StringAtom, NULL, Property::NoAttribute, String_Type, JSValue(&Empty_StringAtom));
|
|
RegExp_Type->defineVariable(this, LeftContext_StringAtom, NULL, Property::NoAttribute, String_Type, JSValue(&Empty_StringAtom));
|
|
RegExp_Type->defineVariable(this, RightContext_StringAtom, NULL, Property::NoAttribute, String_Type, JSValue(&Empty_StringAtom));
|
|
|
|
|
|
}
|
|
|
|
|
|
OperatorMap operatorMap;
|
|
//OperatorHashTable operatorHashTable;
|
|
|
|
struct OperatorInitData {
|
|
char *operatorString;
|
|
JS2Runtime::Operator op;
|
|
} operatorInitData[] =
|
|
{
|
|
{ "~", JS2Runtime::Complement, },
|
|
{ "++", JS2Runtime::Increment, },
|
|
{ "--", JS2Runtime::Decrement, },
|
|
{ "()", JS2Runtime::Call, },
|
|
{ "new", JS2Runtime::New, },
|
|
{ "[]", JS2Runtime::Index, },
|
|
{ "[]=", JS2Runtime::IndexEqual, },
|
|
{ "delete[]", JS2Runtime::DeleteIndex, },
|
|
{ "+", JS2Runtime::Plus, },
|
|
{ "-", JS2Runtime::Minus, },
|
|
{ "*", JS2Runtime::Multiply, },
|
|
{ "/", JS2Runtime::Divide, },
|
|
{ "%", JS2Runtime::Remainder, },
|
|
{ "<<", JS2Runtime::ShiftLeft, },
|
|
{ ">>", JS2Runtime::ShiftRight, },
|
|
{ ">>>", JS2Runtime::UShiftRight, },
|
|
{ "<", JS2Runtime::Less, },
|
|
{ "<=", JS2Runtime::LessEqual, },
|
|
{ "in", JS2Runtime::In, },
|
|
{ "==", JS2Runtime::Equal, },
|
|
{ "===", JS2Runtime::SpittingImage, },
|
|
{ "&", JS2Runtime::BitAnd, },
|
|
{ "^", JS2Runtime::BitXor, },
|
|
{ "|", JS2Runtime::BitOr, },
|
|
};
|
|
|
|
static void initOperatorTable()
|
|
{
|
|
static bool mapped = false;
|
|
if (!mapped) {
|
|
for (uint32 i = 0; (i < sizeof(operatorInitData) / sizeof(OperatorInitData)); i++) {
|
|
const String& name = widenCString(operatorInitData[i].operatorString);
|
|
operatorMap[name] = operatorInitData[i].op;
|
|
/*
|
|
operatorHashTable.insert(name, OperatorEntry(name, operatorInitData[i].op) );
|
|
*/
|
|
}
|
|
mapped = true;
|
|
}
|
|
}
|
|
|
|
JS2Runtime::Operator Context::getOperator(uint32 parameterCount, const String &name)
|
|
{
|
|
OperatorMap::iterator it = operatorMap.find(name);
|
|
if (it == operatorMap.end())
|
|
return JS2Runtime::None;
|
|
|
|
if ((it->second == JS2Runtime::Plus) && (parameterCount == 1))
|
|
return JS2Runtime::Posate;
|
|
if ((it->second == JS2Runtime::Minus) && (parameterCount == 1))
|
|
return JS2Runtime::Negate;
|
|
|
|
return it->second;
|
|
}
|
|
|
|
|
|
Context::Context(JSObject **global, World &world, Arena &a, Pragma::Flags flags)
|
|
|
|
: Virtual_StringAtom (world.identifiers["virtual"]),
|
|
Constructor_StringAtom (world.identifiers["constructor"]),
|
|
Operator_StringAtom (world.identifiers["operator"]),
|
|
Fixed_StringAtom (world.identifiers["fixed"]),
|
|
Dynamic_StringAtom (world.identifiers["dynamic"]),
|
|
Extend_StringAtom (world.identifiers["extend"]),
|
|
Prototype_StringAtom (world.identifiers["prototype"]),
|
|
Forin_StringAtom (world.identifiers["forin"]),
|
|
Value_StringAtom (world.identifiers["value"]),
|
|
Next_StringAtom (world.identifiers["next"]),
|
|
Done_StringAtom (world.identifiers["done"]),
|
|
Undefined_StringAtom (world.identifiers["undefined"]),
|
|
Object_StringAtom (world.identifiers["object"]),
|
|
Boolean_StringAtom (world.identifiers["boolean"]),
|
|
Number_StringAtom (world.identifiers["number"]),
|
|
String_StringAtom (world.identifiers["string"]),
|
|
Function_StringAtom (world.identifiers["function"]),
|
|
HasInstance_StringAtom (world.identifiers["hasInstance"]),
|
|
True_StringAtom (world.identifiers["true"]),
|
|
False_StringAtom (world.identifiers["false"]),
|
|
Null_StringAtom (world.identifiers["null"]),
|
|
ToString_StringAtom (world.identifiers["toString"]),
|
|
ValueOf_StringAtom (world.identifiers["valueOf"]),
|
|
Length_StringAtom (world.identifiers["length"]),
|
|
FromCharCode_StringAtom (world.identifiers["fromCharCode"]),
|
|
Math_StringAtom (world.identifiers["Math"]),
|
|
NaN_StringAtom (world.identifiers["NaN"]),
|
|
Eval_StringAtom (world.identifiers["eval"]),
|
|
Infinity_StringAtom (world.identifiers["Infinity"]),
|
|
Empty_StringAtom (world.identifiers[""]),
|
|
Arguments_StringAtom (world.identifiers["arguments"]),
|
|
Message_StringAtom (world.identifiers["message"]),
|
|
Name_StringAtom (world.identifiers["name"]),
|
|
Error_StringAtom (world.identifiers["Error"]),
|
|
EvalError_StringAtom (world.identifiers["EvalError"]),
|
|
RangeError_StringAtom (world.identifiers["RangeError"]),
|
|
ReferenceError_StringAtom (world.identifiers["ReferenceError"]),
|
|
SyntaxError_StringAtom (world.identifiers["SyntaxError"]),
|
|
TypeError_StringAtom (world.identifiers["TypeError"]),
|
|
UriError_StringAtom (world.identifiers["UriError"]),
|
|
Source_StringAtom (world.identifiers["source"]),
|
|
Global_StringAtom (world.identifiers["global"]),
|
|
IgnoreCase_StringAtom (world.identifiers["ignoreCase"]),
|
|
Multiline_StringAtom (world.identifiers["multiline"]),
|
|
Input_StringAtom (world.identifiers["input"]),
|
|
Index_StringAtom (world.identifiers["index"]),
|
|
LastIndex_StringAtom (world.identifiers["lastIndex"]),
|
|
LastMatch_StringAtom (world.identifiers["lastMatch"]),
|
|
LastParen_StringAtom (world.identifiers["lastParen"]),
|
|
LeftContext_StringAtom (world.identifiers["leftContext"]),
|
|
RightContext_StringAtom (world.identifiers["rightContext"]),
|
|
Dollar_StringAtom (world.identifiers["$"]),
|
|
UnderbarPrototype_StringAtom (world.identifiers["__proto__"]),
|
|
|
|
mWorld(world),
|
|
mScopeChain(NULL),
|
|
mArena(a),
|
|
mFlags(flags),
|
|
mDebugFlag(false),
|
|
mCurModule(NULL),
|
|
mPC(NULL),
|
|
mThis(kNullValue),
|
|
mStack(NULL),
|
|
mStackTop(0),
|
|
mStackMax(0),
|
|
mNamespaceList(NULL),
|
|
mLocals(NULL),
|
|
mArgumentBase(NULL),
|
|
mReader(NULL),
|
|
mGlobal(global)
|
|
|
|
{
|
|
uint32 i;
|
|
|
|
initOperatorTable();
|
|
|
|
mScopeChain = new ScopeChain(this, mWorld);
|
|
if (Object_Type == NULL) {
|
|
initBuiltins();
|
|
}
|
|
|
|
JSType *MathType = new JSType(this, &mWorld.identifiers[widenCString("Math")], Object_Type, Object_Type->mPrototypeObject, kNullValue);
|
|
JSObject *mathObj = MathType->newInstance(this).instance;
|
|
|
|
getGlobalObject()->defineVariable(this, Math_StringAtom, (NamespaceList *)(NULL), Property::NoAttribute, Object_Type, JSValue(mathObj));
|
|
initMathObject(this, mathObj);
|
|
initDateObject(this);
|
|
|
|
Number_Type->defineVariable(this, widenCString("MAX_VALUE"), NULL, Property::ReadOnly | Property::DontDelete, Number_Type, JSValue(maxValue));
|
|
Number_Type->defineVariable(this, widenCString("MIN_VALUE"), NULL, Property::ReadOnly | Property::DontDelete, Number_Type, JSValue(minValue));
|
|
Number_Type->defineVariable(this, widenCString("NaN"), NULL, Property::ReadOnly | Property::DontDelete, Number_Type, JSValue(nan));
|
|
Number_Type->defineVariable(this, widenCString("POSITIVE_INFINITY"), NULL, Property::ReadOnly | Property::DontDelete, Number_Type, JSValue(positiveInfinity));
|
|
Number_Type->defineVariable(this, widenCString("NEGATIVE_INFINITY"), NULL, Property::ReadOnly | Property::DontDelete, Number_Type, JSValue(negativeInfinity));
|
|
|
|
initOperators();
|
|
|
|
struct Attribute_Init {
|
|
char *name;
|
|
uint32 trueFlags;
|
|
uint32 falseFlags;
|
|
} attribute_init[] =
|
|
{
|
|
// XXX these false flags are WAY NOT COMPLETE!!
|
|
//
|
|
{ "indexable", Property::Indexable, 0 },
|
|
{ "enumerable", Property::Enumerable, 0 },
|
|
{ "virtual", Property::Virtual, Property::Static | Property::Constructor },
|
|
{ "constructor", Property::Constructor, Property::Virtual },
|
|
{ "operator", Property::Operator, Property::Virtual | Property::Constructor },
|
|
{ "dynamic", Property::Dynamic, 0 },
|
|
{ "fixed", 0, Property::Dynamic },
|
|
{ "prototype", Property::Prototype, 0 },
|
|
{ "static", Property::Static, Property::Virtual },
|
|
{ "abstract", Property::Abstract, Property::Static },
|
|
{ "override", Property::Override, Property::Static },
|
|
{ "mayOverride", Property::MayOverride, Property::Static },
|
|
{ "true", Property::True, 0 },
|
|
{ "false", 0, Property::True },
|
|
{ "public", Property::Public, Property::Private },
|
|
{ "private", Property::Private, Property::Public },
|
|
{ "final", Property::Final, Property::Virtual | Property::Abstract },
|
|
{ "const", Property::Const, 0 },
|
|
};
|
|
|
|
for (i = 0; i < (sizeof(attribute_init) / sizeof(Attribute_Init)); i++) {
|
|
Attribute *attr = new Attribute(attribute_init[i].trueFlags, attribute_init[i].falseFlags);
|
|
getGlobalObject()->defineVariable(this, widenCString(attribute_init[i].name), (NamespaceList *)(NULL), Property::NoAttribute, Attribute_Type, JSValue(attr));
|
|
}
|
|
|
|
JSFunction *x = new JSFunction(this, ExtendAttribute_Invoke, Attribute_Type);
|
|
getGlobalObject()->defineVariable(this, Extend_StringAtom, (NamespaceList *)(NULL), Property::NoAttribute, Attribute_Type, JSValue(x));
|
|
|
|
|
|
|
|
|
|
ProtoFunDef globalObjectFunctions[] = {
|
|
{ "eval", Object_Type, 1, GlobalObject_Eval },
|
|
{ "parseInt", Number_Type, 2, GlobalObject_ParseInt },
|
|
{ "parseFloat", Number_Type, 2, GlobalObject_ParseFloat },
|
|
{ "isNaN", Boolean_Type, 2, GlobalObject_isNaN },
|
|
{ "isFinite", Boolean_Type, 2, GlobalObject_isFinite },
|
|
{ "escape", String_Type, 1, GlobalObject_escape },
|
|
{ "unescape", String_Type, 1, GlobalObject_unescape },
|
|
};
|
|
|
|
for (i = 0; i < (sizeof(globalObjectFunctions) / sizeof(ProtoFunDef)); i++) {
|
|
x = new JSFunction(this, globalObjectFunctions[i].imp, globalObjectFunctions[i].result);
|
|
x->setParameterCounts(this, globalObjectFunctions[i].length, 0, 0, false);
|
|
x->setIsPrototype(true);
|
|
getGlobalObject()->defineVariable(this, widenCString(globalObjectFunctions[i].name), (NamespaceList *)(NULL), Property::NoAttribute, globalObjectFunctions[i].result, JSValue(x));
|
|
}
|
|
|
|
getGlobalObject()->defineVariable(this, Undefined_StringAtom, (NamespaceList *)(NULL), Property::NoAttribute, Void_Type, kUndefinedValue);
|
|
getGlobalObject()->defineVariable(this, NaN_StringAtom, (NamespaceList *)(NULL), Property::NoAttribute, Void_Type, kNaNValue);
|
|
getGlobalObject()->defineVariable(this, Infinity_StringAtom, (NamespaceList *)(NULL), Property::NoAttribute, Void_Type, kPositiveInfinity);
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
|
See if the specified package is already loaded - return true
|
|
|
|
Throw an exception if the package is being loaded already
|
|
|
|
*/
|
|
|
|
bool Context::checkForPackage(const String &packageName)
|
|
|
|
{
|
|
|
|
// XXX linear search
|
|
|
|
for (PackageList::iterator pi = mPackages.begin(), end = mPackages.end(); (pi != end); pi++) {
|
|
|
|
if (PACKAGE_NAME(pi).compare(packageName) == 0) {
|
|
|
|
if (PACKAGE_STATUS(pi) == Package::OnItsWay)
|
|
|
|
reportError(Exception::referenceError, "Package circularity");
|
|
|
|
else
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Load the specified package from the file
|
|
|
|
*/
|
|
|
|
void Context::loadPackage(const String & /*packageName*/, const String &filename)
|
|
|
|
{
|
|
|
|
// XXX need some rules for search path
|
|
|
|
// XXX need to extract just the target package from the file
|
|
|
|
|
|
|
|
readEvalFile(filename);
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
void Context::reportError(Exception::Kind kind, char *message, size_t pos, const char *arg)
|
|
{
|
|
const char16 *lineBegin;
|
|
const char16 *lineEnd;
|
|
String x = widenCString(message);
|
|
if (arg) {
|
|
uint32 a = x.find(widenCString("{0}"));
|
|
x.replace(a, 3, widenCString(arg));
|
|
}
|
|
if (mReader) {
|
|
uint32 lineNum = mReader->posToLineNum(pos);
|
|
size_t linePos = mReader->getLine(lineNum, lineBegin, lineEnd);
|
|
ASSERT(lineBegin && lineEnd && linePos <= pos);
|
|
throw Exception(kind, x,
|
|
mReader->sourceLocation,
|
|
lineNum, pos - linePos, pos, lineBegin, lineEnd);
|
|
}
|
|
else {
|
|
if (mCurModule) {
|
|
Reader reader(mCurModule->mSource, mCurModule->mSourceLocation);
|
|
reader.fillLineStartsTable();
|
|
uint32 lineNum = reader.posToLineNum(pos);
|
|
size_t linePos = reader.getLine(lineNum, lineBegin, lineEnd);
|
|
ASSERT(lineBegin && lineEnd && linePos <= pos);
|
|
throw Exception(kind, x,
|
|
reader.sourceLocation,
|
|
lineNum, pos - linePos, pos, lineBegin, lineEnd);
|
|
}
|
|
}
|
|
|
|
throw Exception(kind, x);
|
|
}
|
|
|
|
// assumes mPC has been set inside the interpreter loop prior
|
|
// to dispatch to whatever routine invoked this error reporter
|
|
void Context::reportError(Exception::Kind kind, char *message, const char *arg)
|
|
{
|
|
size_t pos = 0;
|
|
if (mCurModule)
|
|
pos = mCurModule->getPositionForPC(toUInt32(mPC - mCurModule->mCodeBase));
|
|
reportError(kind, message, pos, arg);
|
|
}
|
|
|
|
void Context::reportError(Exception::Kind kind, char *message, const String& name)
|
|
{
|
|
std::string str(name.length(), char());
|
|
std::transform(name.begin(), name.end(), str.begin(), narrow);
|
|
reportError(kind, message, str.c_str() );
|
|
}
|
|
|
|
void Context::reportError(Exception::Kind kind, char *message, size_t pos, const String& name)
|
|
{
|
|
std::string str(name.length(), char());
|
|
std::transform(name.begin(), name.end(), str.begin(), narrow);
|
|
reportError(kind, message, pos, str.c_str());
|
|
}
|
|
|
|
Formatter& operator<<(Formatter& f, const JSValue& value)
|
|
{
|
|
switch (value.tag) {
|
|
case JSValue::f64_tag:
|
|
f << value.f64;
|
|
break;
|
|
case JSValue::object_tag:
|
|
printFormat(f, "Object @ 0x%08X\n", value.object);
|
|
f << *value.object;
|
|
break;
|
|
case JSValue::type_tag:
|
|
printFormat(f, "Type @ 0x%08X\n", value.type);
|
|
f << *value.type;
|
|
break;
|
|
case JSValue::boolean_tag:
|
|
f << ((value.boolean) ? "true" : "false");
|
|
break;
|
|
case JSValue::string_tag:
|
|
f << *value.string;
|
|
break;
|
|
case JSValue::undefined_tag:
|
|
f << "undefined";
|
|
break;
|
|
case JSValue::null_tag:
|
|
f << "null";
|
|
break;
|
|
case JSValue::function_tag:
|
|
if (!value.function->isNative()) {
|
|
FunctionName *fnName = value.function->getFunctionName();
|
|
if (fnName) {
|
|
StringFormatter s;
|
|
PrettyPrinter pp(s);
|
|
fnName->print(pp);
|
|
f << "function '" << s.getString() << "'\n" << *value.function->getByteCode();
|
|
}
|
|
else {
|
|
f << "function anonymous\n" << *value.function->getByteCode();
|
|
}
|
|
}
|
|
else
|
|
f << "function\n";
|
|
break;
|
|
default:
|
|
NOT_REACHED("Bad tag");
|
|
}
|
|
return f;
|
|
}
|
|
|
|
void JSType::printSlotsNStuff(Formatter& f) const
|
|
{
|
|
f << "var. count = " << mVariableCount << "\n";
|
|
f << "method count = " << (uint32)(mMethods.size()) << "\n";
|
|
uint32 index = 0;
|
|
for (MethodList::const_iterator i = mMethods.begin(), end = mMethods.end(); (i != end); i++) {
|
|
f << "[#" << index++ << "]";
|
|
if (*i == NULL)
|
|
f << "NULL\n";
|
|
else
|
|
if (!(*i)->isNative()) {
|
|
ByteCodeModule *m = (*i)->getByteCode();
|
|
if (m)
|
|
f << *m;
|
|
}
|
|
}
|
|
// if (mStatics)
|
|
// f << "Statics :\n" << *mStatics;
|
|
}
|
|
|
|
Formatter& operator<<(Formatter& f, const JSObject& obj)
|
|
{
|
|
obj.printProperties(f);
|
|
return f;
|
|
}
|
|
Formatter& operator<<(Formatter& f, const JSType& obj)
|
|
{
|
|
printFormat(f, "super @ 0x%08X\n", obj.mSuperType);
|
|
f << "properties\n";
|
|
obj.printProperties(f);
|
|
f << "slotsnstuff\n";
|
|
obj.printSlotsNStuff(f);
|
|
f << "done with type\n";
|
|
return f;
|
|
}
|
|
Formatter& operator<<(Formatter& f, const Access& slot)
|
|
{
|
|
switch (slot) {
|
|
case Read : f << "Read\n"; break;
|
|
case Write : f << "Write\n"; break;
|
|
}
|
|
return f;
|
|
}
|
|
Formatter& operator<<(Formatter& f, const Property& prop)
|
|
{
|
|
switch (prop.mFlag) {
|
|
case ValuePointer :
|
|
{
|
|
JSValue v = *prop.mData.vp;
|
|
f << "ValuePointer --> ";
|
|
if (v.isObject())
|
|
printFormat(f, "Object @ 0x%08X\n", v.object);
|
|
else
|
|
if (v.isType())
|
|
printFormat(f, "Type @ 0x%08X\n", v.type);
|
|
else
|
|
if (v.isFunction())
|
|
printFormat(f, "Function @ 0x%08X\n", v.function);
|
|
else
|
|
f << v << "\n";
|
|
}
|
|
break;
|
|
case FunctionPair : f << "FunctionPair\n"; break;
|
|
case IndexPair : f << "IndexPair\n"; break;
|
|
case Slot : f << "Slot\n"; break;
|
|
case Constructor : f << "Constructor\n"; break;
|
|
case Method : f << "Method\n"; break;
|
|
}
|
|
return f;
|
|
}
|
|
Formatter& operator<<(Formatter& f, const JSInstance& obj)
|
|
{
|
|
for (PropertyMap::const_iterator i = obj.mProperties.begin(), end = obj.mProperties.end(); (i != end); i++) {
|
|
const Property *prop = PROPERTY(i);
|
|
f << "[" << PROPERTY_NAME(i) << "] ";
|
|
switch (prop->mFlag) {
|
|
case ValuePointer : f << "ValuePointer --> " << *prop->mData.vp; break;
|
|
case FunctionPair : f << "FunctionPair\n"; break;
|
|
case IndexPair : f << "IndexPair\n"; break;
|
|
case Slot : f << "Slot #" << prop->mData.index
|
|
<< " --> " << obj.mInstanceValues[prop->mData.index] << "\n"; break;
|
|
case Method : f << "Method #" << prop->mData.index << "\n"; break;
|
|
case Constructor : f << "Constructor #" << prop->mData.index << "\n"; break;
|
|
}
|
|
}
|
|
return f;
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|