/* -*- 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 #include #include #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" #include "regexp.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.isObject() && (result.object->getType() == Attribute_Type)); return static_cast(result.object); } // 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(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(const String &name, NamespaceList *names, Access acc, PropertyIterator *p) { if (hasOwnProperty(name, names, acc, p)) return true; else if (mPrototype) return mPrototype->hasProperty(name, names, acc, p); else return false; } bool JSObject::deleteProperty(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(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(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(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(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(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(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(bool hasBase, const String& name, NamespaceList *names, Access acc, uint32 /*depth*/) { PropertyIterator i; if (hasProperty(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(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; case Constructor: case Method: cx->pushValue(JSValue(mType->mMethods[prop->mData.index])); break; default: ASSERT(false); // XXX more to implement break; } } else { if (mPrototype) mPrototype->getProperty(cx, name, names); else cx->pushValue(kUndefinedValue); } } void JSType::getProperty(Context *cx, const String &name, NamespaceList *names) { PropertyIterator i; if (hasOwnProperty(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(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 if (mType->hasOwnProperty(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 (hasProperty(name, names, Write, &i)) { if (PROPERTY_ATTR(i) & Property::ReadOnly) return; Property *prop = PROPERTY(i); switch (prop->mFlag) { case ValuePointer: *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(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(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(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(*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; } } } // 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)mLength)); } else JSInstance::getProperty(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 // but don't copy the prototype object (XXX others?) for (PropertyIterator pi = type->mProperties.begin(), end = type->mProperties.end(); (pi != end); pi++) { if (PROPERTY_NAME(pi) != cx->Prototype_StringAtom) { 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_NAME(i) != cx->Prototype_StringAtom) { 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. JSInstance *JSType::newInstance(Context *cx) { JSInstance *result = new JSInstance(cx, this); result->mPrototype = mPrototypeObject; return 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) { PropertyAttribute attrFlags = (attr) ? attr->attributeValue->mTrueFlags : 0; Property *prop = new Property(mVariableCount++, type, Slot, attrFlags); const PropertyMap::value_type e(name, new NamespacedProperty(prop, (attr) ? attr->attributeValue->mNamespaceList : NULL)); mProperties.insert(e); return prop; } JSInstance *JSArrayType::newInstance(Context *cx) { JSInstance *result = new JSArrayInstance(cx, this); result->mPrototype = mPrototypeObject; return result; } JSInstance *JSStringType::newInstance(Context *cx) { JSInstance *result = new JSStringInstance(cx, this); result->mPrototype = mPrototypeObject; return 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(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 *, const String& name, NamespaceList *names) { for (ScopeScanner s = mScopeStack.rbegin(), end = mScopeStack.rend(); (s != end); s++) { PropertyIterator i; if ((*s)->hasOwnProperty(name, names, Read, &i)) return (*s)->deleteProperty(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(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; } Reference *ScopeChain::getName(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(name, names, acc, &i)) return (*s)->genReference(false, name, names, acc, depth); else if ((*s)->isDynamic()) return NULL; } return NULL; } bool ScopeChain::hasNameValue(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(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(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(name, names, Read, &i)) return (*s)->getPropertyValue(i); } return kUndefinedValue; } Reference *ParameterBarrel::genReference(bool /* hasBase */, const String& name, NamespaceList *names, Access acc, uint32 /*depth*/) { PropertyIterator i; if (hasProperty(name, names, acc, &i)) { Property *prop = PROPERTY(i); ASSERT(prop->mFlag == Slot); return new ParameterReference(prop->mData.index, acc, prop->mType, prop->mAttributes); } NOT_REACHED("bad genRef call"); return NULL; } 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; } 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(const StringAtom& typeName, size_t pos) { JSValue v = getCompileTimeValue(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(t); type = findType(typeExpr->name, t->pos); } break; case ExprNode::index: // array type { InvokeExprNode *i = checked_cast(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); // XXX or is this a descendat 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(p); const StringAtom *name = &classStmt->name; JSType *thisClass = new JSType(m_cx, name, NULL); m_cx->setAttributeValue(classStmt, 0); // XXX default attribute for a class? PropertyIterator it; if (hasProperty(*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(p); collectNames(l->stmt); } break; case StmtNode::block: case StmtNode::group: { // should push a new Activation scope here? BlockStmtNode *b = checked_cast(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(p); collectNames(u->stmt); } break; case StmtNode::IfElse: { BinaryStmtNode *b = checked_cast(p); collectNames(b->stmt); collectNames(b->stmt2); } break; case StmtNode::Try: { TryStmtNode *t = checked_cast(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(p); if (f->initializer) collectNames(f->initializer); collectNames(f->stmt); } break; case StmtNode::Const: case StmtNode::Var: { VariableStmtNode *vs = checked_cast(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(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(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(packageName, NULL); ASSERT(packageValue.isObject() && (packageValue.object->mType == Package_Type)); Package *package = checked_cast(packageValue.object); 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(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(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(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(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(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(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(const String &name, NamespaceList *names, Access acc, PropertyIterator *p) { if (hasOwnProperty(name, names, acc, p)) return true; else if (mSuperType) return mSuperType->hasProperty(name, names, acc, p); else return false; } Reference *JSType::genReference(bool hasBase, const String& name, NamespaceList *names, Access acc, uint32 depth) { PropertyIterator i; if (hasOwnProperty(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(hasBase, name, names, acc, depth); return NULL; } JSType::JSType(Context *cx, const StringAtom *name, JSType *super, JSObject *protoObj) : JSObject(Type_Type), mSuperType(super), mVariableCount(0), mInstanceInitializer(NULL), mDefaultConstructor(NULL), mTypeCast(NULL), mClassName(name), mIsDynamic(false), mUninitializedValue(kNullValue), mPrototypeObject(NULL) { if (mClassName) mPrivateNamespace = &cx->mWorld.identifiers[*mClassName + " private"]; else mPrivateNamespace = &cx->mWorld.identifiers["unique id needed? private"]; // XXX. No, really? for (uint32 i = 0; i < OperatorCount; i++) mUnaryOperators[i] = NULL; // every class gets a prototype object (used to set the __proto__ value of new instances) if (protoObj) mPrototypeObject = protoObj; else { mPrototypeObject = new JSObject(this); // and that object is prototype-linked to the super-type's prototype object if (mSuperType) mPrototypeObject->mPrototype = mSuperType->mPrototypeObject; } if (mSuperType) defineVariable(cx, cx->Prototype_StringAtom, (NamespaceList *)NULL, Property::ReadOnly | Property::DontDelete, Object_Type, JSValue(mPrototypeObject)); else // must be Object_Type being initialized defineVariable(cx, cx->Prototype_StringAtom, (NamespaceList *)NULL, Property::ReadOnly | Property::DontDelete, this, JSValue(mPrototypeObject)); // the __proto__ of a type is the super-type's prototype object, or for '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; } JSType::JSType(JSType *xClass) // used for constructing the static component type : JSObject(Type_Type), mSuperType(xClass), mVariableCount(0), mInstanceInitializer(NULL), mDefaultConstructor(NULL), mTypeCast(NULL), mClassName(NULL), mPrivateNamespace(NULL), mIsDynamic(false), mUninitializedValue(kNullValue), mPrototypeObject(NULL) { for (uint32 i = 0; i < OperatorCount; i++) mUnaryOperators[i] = NULL; } // 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->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(bool /* hasBase */, const String& name, NamespaceList *names, Access acc, uint32 depth) { PropertyIterator i; if (hasProperty(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(p); StmtNode *s = b->statements; while (s) { buildRuntimeForStmt(s); s = s->next; } } break; case StmtNode::label: { LabelStmtNode *l = checked_cast(p); buildRuntimeForStmt(l->stmt); } break; case StmtNode::Try: { TryStmtNode *t = checked_cast(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(p); buildRuntimeForStmt(u->stmt); } break; case StmtNode::IfElse: { BinaryStmtNode *b = checked_cast(p); buildRuntimeForStmt(b->stmt); buildRuntimeForStmt(b->stmt2); } break; case StmtNode::For: case StmtNode::ForIn: { ForStmtNode *f = checked_cast(p); if (f->initializer) buildRuntimeForStmt(f->initializer); buildRuntimeForStmt(f->stmt); } break; case StmtNode::Var: case StmtNode::Const: { VariableStmtNode *vs = checked_cast(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(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); // if it's a unary operator, it just gets added // as a method with a special name. Binary operators // get 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)) mScopeChain->defineUnaryOperator(op, 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( ); 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(p); JSType *superClass = Object_Type; if (classStmt->superclass) { ASSERT(classStmt->superclass->getKind() == ExprNode::identifier); // XXX IdentifierExprNode *superClassExpr = checked_cast(classStmt->superclass); superClass = mScopeChain->findType(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(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].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()) return JSValue(new String(widenCString("[object ") + *thisValue.object->getType()->mClassName + widenCString("]"))); else if (thisValue.isType()) 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; } struct IteratorDongle { JSObject *obj; PropertyIterator it; }; static JSValue Object_forin(Context *cx, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/) { JSObject *obj = NULL; if (thisValue.isType()) obj = thisValue.type; else if (thisValue.isFunction()) obj = thisValue.function; else if (thisValue.isObject()) obj = thisValue.object; else NOT_REACHED("Non object for for..in"); JSObject *iteratorObject = Object_Type->newInstance(cx); IteratorDongle *itDude = new IteratorDongle(); itDude->obj = obj; itDude->it = obj->mProperties.begin(); while (true) { while (itDude->it == itDude->obj->mProperties.end()) { itDude->obj = itDude->obj->mPrototype; if (itDude->obj == NULL) return kNullValue; itDude->it = itDude->obj->mProperties.begin(); } if (PROPERTY_ATTR(itDude->it) & Property::Enumerable) break; itDude->it++; } JSValue v(&PROPERTY_NAME(itDude->it)); iteratorObject->setProperty(cx, cx->mWorld.identifiers["value"], 0, v); iteratorObject->mPrivate = itDude; return JSValue(iteratorObject); } static JSValue Object_next(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 /*argc*/) { JSValue iteratorValue = argv[0]; ASSERT(iteratorValue.isObject()); JSObject *iteratorObject = iteratorValue.object; IteratorDongle *itDude = (IteratorDongle *)(iteratorObject->mPrivate); itDude->it++; while (true) { while (itDude->it == itDude->obj->mProperties.end()) { itDude->obj = itDude->obj->mPrototype; if (itDude->obj == NULL) return kNullValue; itDude->it = itDude->obj->mProperties.begin(); } if (PROPERTY_ATTR(itDude->it) & Property::Enumerable) break; itDude->it++; } JSValue v(&PROPERTY_NAME(itDude->it)); iteratorObject->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.isObject()); 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->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); } /***************************************************************/ JSObject *fncPrototype = Object_Type->newInstance(cx); fncPrototype->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, JSValue(fncPrototype)); v = JSValue(fnc); return v; } static JSValue Function_toString(Context *, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/) { ASSERT(thisValue.isFunction() || (thisValue.isObject() && (thisValue.object->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"); JSObject *V = v.object->mPrototype; while (V) { if (V == p.object) return kTrueValue; V = V->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.isObject()); JSObject *thisObj = v.object; if (argc > 0) thisObj->mPrivate = (void *)(new float64(argv[0].toNumber(cx).f64)); else thisObj->mPrivate = (void *)(new float64(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.isObject() || (thisValue.getType() != Number_Type)) cx->reportError(Exception::typeError, "Number.toString called on something other than a Number object"); JSObject *thisObj = thisValue.object; return JSValue(numberToString(*((float64 *)(thisObj->mPrivate)))); } static JSValue Number_valueOf(Context *cx, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/) { if (!thisValue.isObject() || (thisValue.getType() != Number_Type)) cx->reportError(Exception::typeError, "Number.valueOf called on something other than a Number object"); JSObject *thisObj = thisValue.object; return JSValue(*((float64 *)(thisObj->mPrivate))); } 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.isObject()); JSObject *thisObj = v.object; if (argc > 0) { float64 d = argv[0].toNumber(cx).f64; bool neg = (d < 0); d = fd::floor(neg ? -d : d); d = neg ? -d : d; thisObj->mPrivate = (void *)(new double(d)); } else thisObj->mPrivate = (void *)(new double(0.0)); return v; } static JSValue Integer_toString(Context *, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/) { ASSERT(thisValue.isObject()); JSObject *thisObj = thisValue.object; return JSValue(numberToString(*((double *)(thisObj->mPrivate)))); } 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.isObject()); JSObject *thisObj = v.object; if (argc > 0) thisObj->mPrivate = (void *)(argv[0].toBoolean(cx).boolean); else thisObj->mPrivate = (void *)(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*/) { ASSERT(thisValue.isObject()); JSObject *thisObj = thisValue.object; if (thisObj->mType != Boolean_Type) cx->reportError(Exception::typeError, "Boolean.toString can only be applied to Boolean objects"); if (thisObj->mPrivate != 0) return JSValue(&cx->True_StringAtom); else return JSValue(&cx->False_StringAtom); } static JSValue Boolean_valueOf(Context *cx, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/) { ASSERT(thisValue.isObject()); JSObject *thisObj = thisValue.object; if (thisObj->mType != Boolean_Type) cx->reportError(Exception::typeError, "Boolean.valueOf can only be applied to Boolean objects"); if (thisObj->mPrivate != 0) 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.isObject()); JSObject *thisObj = v.object; JSValue msg; if (argc > 0) msg = argv[0].toString(cx); else msg = JSValue(&cx->Empty_StringAtom); thisObj->defineVariable(cx, cx->Message_StringAtom, NULL, Property::NoAttribute, String_Type, msg); thisObj->defineVariable(cx, cx->Name_StringAtom, NULL, Property::NoAttribute, String_Type, JSValue(errType->mClassName)); return v; } extern "C" char16 canonicalize(char16 ch) { char16 cu = toUpper(ch); if ((ch >= 128) && (cu < 128)) return ch; return cu; } 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.isObject()); JSObject *thisObj = thatValue.object; const String *regexpStr = &cx->Empty_StringAtom; const String *flagStr = &cx->Empty_StringAtom; if (argc > 0) { if (argv[0].isObject() && (argv[0].object->mType == RegExp_Type)) { if ((argc == 1) || argv[1].isUndefined()) { ContextStackReplacement csr(cx); argv[0].object->getProperty(cx, cx->Source_StringAtom, CURRENT_ATTR); JSValue src = cx->popValue(); ASSERT(src.isString()); regexpStr = src.string; REParseState *other = (REParseState *)(argv[0].object->mPrivate); String *newFlagStr = new String; if (other->flags & GLOBAL) *newFlagStr += "g"; if (other->flags & IGNORECASE) *newFlagStr += "i"; if (other->flags & MULTILINE) *newFlagStr += "m"; flagStr = newFlagStr; } 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; } REParseState *parseResult = REParse(regexpStr->begin(), regexpStr->length(), flagStr->begin(), flagStr->length(), false); if (parseResult) { thisObj->mPrivate = parseResult; // XXX ECMA spec says these are DONTENUM, but SpiderMonkey and test suite disagree /* thisObj->defineVariable(cx, cx->Source_StringAtom, NULL, (Property::DontDelete | Property::ReadOnly), String_Type, JSValue(regexpStr)); thisObj->defineVariable(cx, cx->Global_StringAtom, NULL, (Property::DontDelete | Property::ReadOnly), Boolean_Type, (parseResult->flags & GLOBAL) ? kTrueValue : kFalseValue); thisObj->defineVariable(cx, cx->IgnoreCase_StringAtom, NULL, (Property::DontDelete | Property::ReadOnly), Boolean_Type, (parseResult->flags & IGNORECASE) ? kTrueValue : kFalseValue); thisObj->defineVariable(cx, cx->Multiline_StringAtom, NULL, (Property::DontDelete | Property::ReadOnly), Boolean_Type, (parseResult->flags & MULTILINE) ? kTrueValue : kFalseValue); thisObj->defineVariable(cx, cx->LastIndex_StringAtom, NULL, Property::DontDelete, Number_Type, kPositiveZero); */ thisObj->defineVariable(cx, cx->Source_StringAtom, NULL, (Property::DontDelete | Property::ReadOnly | Property::Enumerable), String_Type, JSValue(regexpStr)); thisObj->defineVariable(cx, cx->Global_StringAtom, NULL, (Property::DontDelete | Property::ReadOnly | Property::Enumerable), Boolean_Type, (parseResult->flags & GLOBAL) ? kTrueValue : kFalseValue); thisObj->defineVariable(cx, cx->IgnoreCase_StringAtom, NULL, (Property::DontDelete | Property::ReadOnly | Property::Enumerable), Boolean_Type, (parseResult->flags & IGNORECASE) ? kTrueValue : kFalseValue); thisObj->defineVariable(cx, cx->Multiline_StringAtom, NULL, (Property::DontDelete | Property::ReadOnly | Property::Enumerable), Boolean_Type, (parseResult->flags & MULTILINE) ? kTrueValue : kFalseValue); thisObj->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].isObject() && (argv[0].object->mType == 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); ASSERT(thisValue.isObject()); JSObject *thisObj = thisValue.object; if (thisObj->mType != RegExp_Type) cx->reportError(Exception::typeError, "RegExp.toString can only be applied to RegExp objects"); thisObj->getProperty(cx, cx->Source_StringAtom, CURRENT_ATTR); JSValue src = cx->popValue(); // XXX not ever expected 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(thisObj == RegExp_Type->mPrototypeObject); return JSValue(&cx->Empty_StringAtom); } String *result = new String("/" + *src.toString(cx).string + "/"); REParseState *state = (REParseState *)thisObj->mPrivate; if (state->flags & GLOBAL) *result += "g"; if (state->flags & IGNORECASE) *result += "i"; if (state->flags & MULTILINE) *result += "m"; return JSValue(result); } JSValue RegExp_exec(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) { ASSERT(thisValue.isObject()); JSObject *thisObj = thisValue.object; if (thisObj->mType != RegExp_Type) cx->reportError(Exception::typeError, "RegExp.exec can only be applied to RegExp objects"); JSValue result = kNullValue; if (argc > 0) { ContextStackReplacement csr(cx); thisObj->getProperty(cx, cx->LastIndex_StringAtom, CURRENT_ATTR); JSValue lastIndex = cx->popValue(); REParseState *parseResult = (REParseState *)(thisObj->mPrivate); parseResult->lastIndex = (int)(lastIndex.toInteger(cx).f64); const String *str = argv[0].toString(cx).string; REState *regexp_result = REExecute(parseResult, str->begin(), str->length()); if (regexp_result) { JSArrayInstance *resultArray = (JSArrayInstance *)Array_Type->newInstance(cx); String *matchStr = new String(str->substr(regexp_result->endIndex, regexp_result->length)); resultArray->setProperty(cx, *numberToString(0), NULL, JSValue(matchStr)); String *parenStr = &cx->Empty_StringAtom; for (uint32 i = 0; i < regexp_result->n; i++) { if (regexp_result->parens[i].index != -1) { String *parenStr = new String(str->substr((uint32)(regexp_result->parens[i].index), (uint32)(regexp_result->parens[i].length))); resultArray->setProperty(cx, *numberToString(i + 1), NULL, JSValue(parenStr)); } else resultArray->setProperty(cx, *numberToString(i + 1), NULL, kUndefinedValue); } // XXX SpiderMonkey also adds 'index' and 'input' properties to the result resultArray->setProperty(cx, cx->Index_StringAtom, CURRENT_ATTR, JSValue((float64)(regexp_result->endIndex))); resultArray->setProperty(cx, cx->Input_StringAtom, CURRENT_ATTR, JSValue(str)); result = JSValue(resultArray); // 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, regexp_result->endIndex)); RegExp_Type->setProperty(cx, cx->LeftContext_StringAtom, CURRENT_ATTR, JSValue(contextStr)); uint32 matchEnd = regexp_result->endIndex + regexp_result->length; contextStr = new String(str->substr(matchEnd, str->length() - matchEnd)); RegExp_Type->setProperty(cx, cx->RightContext_StringAtom, CURRENT_ATTR, JSValue(contextStr)); } thisObj->setProperty(cx, cx->LastIndex_StringAtom, CURRENT_ATTR, JSValue((float64)(parseResult->lastIndex))); } return result; } static JSValue RegExp_test(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) { ASSERT(thisValue.isObject()); JSObject *thisObj = thisValue.object; if (thisObj->mType != RegExp_Type) cx->reportError(Exception::typeError, "RegExp.test can only be applied to RegExp objects"); if (argc > 0) { const String *str = argv[0].toString(cx).string; REState *regexp_result = REExecute((REParseState *)(thisObj->mPrivate), str->begin(), str->length()); if (regexp_result) 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); ASSERT(thisValue.isObject()); JSObject *thisObj = thisValue.object; if ((thisObj->mType != Error_Type) && !thisObj->mType->derivesFrom(Error_Type)) cx->reportError(Exception::typeError, "Error.toString can only be applied to Error objects"); thisObj->getProperty(cx, cx->Message_StringAtom, CURRENT_ATTR); JSValue msg = cx->popValue(); thisObj->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(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((((((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 *, JSType *resultType, ScopeChain *scopeChain) : JSObject(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; } JSFunction::JSFunction(Context *, NativeCode *code, JSType *resultType) : JSObject(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; } 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->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); result->setDefaultConstructor(cx, Array_Type->getDefaultConstructor()); return JSValue(result); } else { // then it's just [] - 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 JSType(this, &mWorld.identifiers[widenCString(builtInClasses[0].name)], NULL); 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); 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 JSFunction *funProto = new JSFunction(this, Object_Type, NULL); Function_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[2].name)], Object_Type, funProto); funProto->mType = Function_Type; JSObject *numProto = new JSObject(); Number_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[3].name)], Object_Type, numProto); numProto->mType = Number_Type; numProto->mPrivate = (void *)(new float64(0.0)); Integer_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[4].name)], Object_Type); JSObject *strProto = new JSStringInstance(this, NULL); String_Type = new JSStringType(this, &mWorld.identifiers[widenCString(builtInClasses[5].name)], Object_Type, strProto); strProto->mType = String_Type; strProto->mPrivate = (void *)(&Empty_StringAtom); JSArrayInstance *arrayProto = new JSArrayInstance(this, NULL); Array_Type = new JSArrayType(this, Object_Type, &mWorld.identifiers[widenCString(builtInClasses[6].name)], Object_Type, arrayProto); arrayProto->mType = Array_Type; Boolean_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[7].name)], Object_Type); Void_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[8].name)], Object_Type); Unit_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[9].name)], Object_Type); Attribute_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[10].name)], Object_Type); NamedArgument_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[11].name)], Object_Type); Date_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[12].name)], Object_Type); Null_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[13].name)], Object_Type); Error_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[14].name)], Object_Type); EvalError_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[15].name)], Error_Type); RangeError_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[16].name)], Error_Type); ReferenceError_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[17].name)], Error_Type); SyntaxError_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[18].name)], Error_Type); TypeError_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[19].name)], Error_Type); UriError_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[20].name)], Error_Type); // XXX RegExp.prototype is set to a RegExp instance, which isn't ECMA (it's supposed to be an Object instance) bu // is SpiderMonkey compatible. RegExp_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[21].name)], Object_Type); Package_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[22].name)], Object_Type); 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); 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])); Type_Type->defineUnaryOperator(Index, 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); Array_Type->defineUnaryOperator(Index, new JSFunction(this, Array_GetElement, Object_Type)); Array_Type->defineUnaryOperator(IndexEqual, new JSFunction(this, Array_SetElement, Object_Type)); Array_Type->mTypeCast = new JSFunction(this, Array_Constructor, Array_Type); Boolean_Type->mTypeCast = new JSFunction(this, Boolean_TypeCast, Boolean_Type); 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)); String_Type->defineVariable(this, FromCharCode_StringAtom, NULL, String_Type, JSValue(new JSFunction(this, String_fromCharCode, String_Type))); String_Type->mTypeCast = new JSFunction(this, String_TypeCast, String_Type); 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["$"]), 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); JSObject *mathObj = MathType->newInstance(this); 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; } } }