/* -*- 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 "fdlibm_ns.h" // this is the AttributeList passed to the name lookup routines #define CURRENT_ATTR (NULL) namespace JavaScript { namespace JS2Runtime { // // XXX don't these belong in the context? But don't // they need to compare equal across contexts? // JSType *Object_Type = NULL; JSType *Number_Type; JSType *Integer_Type; JSType *Function_Type; JSStringType *String_Type; JSType *Boolean_Type; JSType *Type_Type; JSType *Void_Type; JSType *Unit_Type; JSType *Attribute_Type; JSType *NamedArgument_Type; JSArrayType *Array_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; } // get a property value JSValue JSObject::getPropertyValue(PropertyIterator &i) { Property *prop = PROPERTY(i); ASSERT(prop->mFlag == ValuePointer); return *prop->mData.vp; } 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); } } // 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)) { 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, JSType *type) { PropertyIterator it; if (hasOwnProperty(name, names, Read, &it)) cx->reportError(Exception::typeError, "Duplicate definition '{0}'", name); Property *prop = new Property(new JSValue(), type, 0); 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, JSValue v) { NamespaceList *names = (attr) ? attr->attributeValue->mNamespaceList : NULL; PropertyIterator it; if (hasOwnProperty(name, names, Read, &it)) { if (attr) cx->reportError(Exception::typeError, "Duplicate definition '{0}'", attr->pos, name); else cx->reportError(Exception::typeError, "Duplicate definition '{0}'", name); } PropertyAttribute attrFlags = (attr) ? attr->attributeValue->mTrueFlags : 0; 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, JSType *type, JSValue v) { PropertyIterator it; if (hasOwnProperty(name, names, Read, &it)) cx->reportError(Exception::typeError, "Duplicate definition '{0}'", name); Property *prop = new Property(new JSValue(v), type, 0); 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, acc, prop->mType, prop->mAttributes); else return new NameReference(name, 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->getArgType(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)) { 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, 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)) { 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, Object_Type, v); } } void JSArrayInstance::getProperty(Context *cx, const String &name, NamespaceList *names) { if (name.compare(widenCString("length")) == 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(widenCString("length")) == 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++) { String *id = numberToString(i); if (findNamespacedProperty(*id, NULL) != mProperties.end()) deleteProperty(*id, NULL); delete id; } mLength = newLength; } else { if (findNamespacedProperty(name, names) == mProperties.end()) defineVariable(cx, name, names, Object_Type, v); else JSInstance::setProperty(cx, name, names, 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; } } } void JSStringInstance::getProperty(Context *cx, const String &name, NamespaceList *names) { if (name.compare(widenCString("length")) == 0) { cx->pushValue(JSValue((float64)mLength)); } else JSInstance::getProperty(cx, name, names); } void JSInstance::initInstance(Context *cx, JSType *type) { if (type->mVariableCount) mInstanceValues = new JSValue[type->mVariableCount]; // copy the instance variable names into the property map for (PropertyIterator pi = type->mProperties.begin(), end = type->mProperties.end(); (pi != end); pi++) { 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++) { 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, AttributeStmtNode *attr) { NamespaceList *names = (attr) ? attr->attributeValue->mNamespaceList : NULL; JSValue v = cx->topValue(); for (ScopeScanner s = mScopeStack.rbegin(), end = mScopeStack.rend(); (s != end); s++) { PropertyIterator i; if ((*s)->hasProperty(name, names, Write, &i)) { if (PROPERTY_KIND(i) == ValuePointer) { *PROPERTY_VALUEPOINTER(i) = v; } else ASSERT(false); // what else needs to be implemented ? } else { if ((*s)->mType == Object_Type) { (*s)->defineVariable(cx, name, attr, Object_Type, v); break; } } } } inline char narrow(char16 ch) { return char(ch); } void ScopeChain::getNameValue(Context *cx, const String& name, AttributeStmtNode *attr) { NamespaceList *names = (attr) ? attr->attributeValue->mNamespaceList : NULL; 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; default: ASSERT(false); // what else needs to be implemented ? } return; } } m_cx->reportError(Exception::referenceError, "'{0}' not defined", name ); } 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; } 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) { if (t->getKind() == ExprNode::identifier) { IdentifierExprNode* typeExpr = checked_cast(t); type = findType(typeExpr->name, t->pos); } else NOT_REACHED("implement me - more complex types"); } 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; } // counts the number of pigs that can fit in small wicker basket uint32 Context::getParameterCount(FunctionDefinition &function) { uint32 count = 0; VariableBinding *v = function.parameters; while (v) { count++; v = v->next; } return count; } // Iterates over the linked list of statements, p. // 1. Adds 'symbol table' entries for each class, var & function // 2. Using information from pass 1, evaluate types (and XXX later XXX other // compile-time constants) 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, 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->restParameter == 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; } // 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::block: { // 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::If: case StmtNode::With: 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); if (t->catches) { CatchClause *c = t->catches; while (c) { c->prop = defineVariable(m_cx, c->name, NULL, NULL); c = c->next; } } } break; case StmtNode::For: case StmtNode::ForIn: { ForStmtNode *f = checked_cast(p); if (f->initializer) collectNames(f->initializer); } 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 = 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; /* 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; } JSFunction *fnc = new JSFunction(NULL, this); fnc->setIsPrototype(isPrototype); fnc->setIsConstructor(isConstructor); fnc->setFunctionName(&f->function); f->mFunction = fnc; uint32 reqArgCount = 0; uint32 optArgCount = 0; VariableBinding *b = f->function.parameters; while ((b != f->function.optParameters) && (b != f->function.restParameter)) { reqArgCount++; b = b->next; } while (b != f->function.restParameter) { optArgCount++; b = b->next; } fnc->setArgCounts(reqArgCount, optArgCount, (f->function.restParameter != NULL)); 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 { if (topClass() && (topClass()->mClassName->compare(name) == 0)) { isConstructor = true; fnc->setIsConstructor(true); } if (!isNestedFunction()) { if (isConstructor) defineConstructor(m_cx, name, f, 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: 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::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), Attribute_Type, JSValue(x)); } 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(Object_Type, scopeChain); fnc->setIsConstructor(true); FunctionName *fnName = new FunctionName(); fnName->name = mClassName; //cx->mWorld.identifiers[mClassName]; fnName->prefix = FunctionName::normal; fnc->setFunctionName(fnName); 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? } } // constructor functions are added as static methods // XXX is it worth just having a default constructor // pointer in the class? JSFunction *JSType::getDefaultConstructor() { PropertyIterator it; if (hasOwnProperty(*mClassName, NULL, Read, &it)) { ASSERT(PROPERTY_KIND(it) == ValuePointer); JSValue c = *PROPERTY_VALUEPOINTER(it); ASSERT(c.isFunction()); return c.function; } return NULL; } 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 // XXX with this change we get unknown type failure for 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->getArgType(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->getArgType(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(Type_Type), mSuperType(super), mVariableCount(0), mInstanceInitializer(NULL), mClassName(name), mPrivateNamespace(&cx->mWorld.identifiers[*name + " private"]), mIsDynamic(false), mUninitializedValue(kNullValue), mPrototypeObject(NULL) { for (uint32 i = 0; i < OperatorCount; i++) mUnaryOperators[i] = NULL; // every class gets a prototype object mPrototypeObject = new JSObject(); // and that object is prototype-linked to the super-type's prototype object if (mSuperType) mPrototypeObject->mPrototype = mSuperType->mPrototypeObject; } JSType::JSType(JSType *xClass) // used for constructing the static component type : JSObject(Type_Type), mSuperType(xClass), mVariableCount(0), mInstanceInitializer(NULL), mClassName(NULL), mPrivateNamespace(NULL), mIsDynamic(false), mUninitializedValue(kNullValue), mPrototypeObject(NULL) { for (uint32 i = 0; i < OperatorCount; i++) mUnaryOperators[i] = NULL; } 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(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, acc, prop->mType, prop->mAttributes); default: NOT_REACHED("bad genRef call"); break; } } NOT_REACHED("bad genRef call"); return NULL; } 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(); } 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: { BlockStmtNode *b = checked_cast(p); StmtNode *s = b->statements; while (s) { buildRuntimeForStmt(s); s = s->next; } } break; case StmtNode::Try: { TryStmtNode *t = checked_cast(p); if (t->catches) { CatchClause *c = t->catches; while (c) { if (c->type) c->prop->mType = mScopeChain->extractType(c->type); c = c->next; } } } break; case StmtNode::If: case StmtNode::With: 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); } 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; while (v) { // XXX if no type is specified for the rest parameter - is it Array? fnc->setArgument(parameterCount++, v->name, mScopeChain->extractType(v->type)); 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; default: break; } } static JSValue Object_Constructor(Context *cx, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/) { JSValue v = thisValue; if (v.isNull()) v = Object_Type->newInstance(cx); ASSERT(v.isObject()); return v; } static JSValue Object_toString(Context *, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/) { if (thisValue.isObject()) return JSValue(new String(widenCString("[object ") + widenCString("Object") + widenCString("]"))); else if (thisValue.isType()) return JSValue(new String(widenCString("[object ") + widenCString("Type") + widenCString("]"))); else { NOT_REACHED("Object.prototype.toString on non-object"); 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); // XXX use the arguments to compile a string into a function ASSERT(v.isObject()); return v; } static JSValue Function_toString(Context *, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/) { ASSERT(thisValue.isFunction()); 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, widenCString("prototype"), 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 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 double(argv[0].toNumber(cx).f64)); else thisObj->mPrivate = (void *)(new double(0.0)); return v; } static JSValue Number_toString(Context *, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/) { ASSERT(thisValue.isObject()); JSObject *thisObj = thisValue.object; return JSValue(numberToString(*((double *)(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_toString(Context *, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/) { ASSERT(thisValue.isObject()); JSObject *thisObj = thisValue.object; if (thisObj->mPrivate != 0) return JSValue(new String(widenCString("true"))); else return JSValue(new String(widenCString("false"))); } 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); } JSValue JSFunction::runArgInitializer(Context *cx, uint32 a, const JSValue& thisValue, JSValue *argv, uint32 argc) { ASSERT(mArguments && (a < (mRequiredArgs + mOptionalArgs))); return cx->interpret(getByteCode(), (int32)mArguments[a].mInitializer, getScopeChain(), thisValue, argv, argc); } void JSFunction::setArgCounts(uint32 r, uint32 o, bool hasRest) { mHasRestParameter = hasRest; mRequiredArgs = r; mOptionalArgs = o; mArguments = new ArgumentData[mRequiredArgs + mOptionalArgs + ((hasRest) ? 1 : 0)]; } uint32 JSFunction::findParameterName(const String *name) { uint32 maxArgs = mRequiredArgs + mOptionalArgs + ((mHasRestParameter) ? 1 : 0); ASSERT(mArguments || (maxArgs == 0)); for (uint32 i = 0; i < maxArgs; i++) { if (mArguments[i].mName->compare(*name) == 0) return i; } return NotABanana; } 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(cdef->defCon, Object_Type); constructor->setClass(type); FunctionName *fnName = new FunctionName(); fnName->name = type->mClassName; constructor->setFunctionName(fnName); 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(pdef->mDef[i].imp, pdef->mDef[i].result); fun->setClass(type); StringAtom *name = &mWorld.identifiers[widenCString(pdef->mDef[i].name)]; FunctionName *fnName = new FunctionName(); fun->setFunctionName(fnName); fun->setArgCounts(pdef->mDef[i].length, 0, false); type->mPrototypeObject->defineVariable(this, *name, //widenCString(pdef->mDef[i].name), (NamespaceList *)(NULL), 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), Type_Type, JSValue(type)); mScopeChain->popScope(); if (pdef) delete pdef; } void Context::initBuiltins() { ClassDef builtInClasses[] = { { "Object", Object_Constructor, &kNullValue }, { "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, &kNullValue }, { "Unit", NULL, &kNullValue }, { "Attribute", NULL, &kNullValue }, { "NamedArgument", NULL, &kNullValue }, }; Object_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[0].name)], NULL); 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); Function_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[2].name)], Object_Type); Number_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[3].name)], Object_Type); Integer_Type = new JSType(this, &mWorld.identifiers[widenCString(builtInClasses[4].name)], Object_Type); String_Type = new JSStringType(this, &mWorld.identifiers[widenCString(builtInClasses[5].name)], Object_Type); Array_Type = new JSArrayType(this, &mWorld.identifiers[widenCString(builtInClasses[6].name)], Object_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); String_Type->defineVariable(this, widenCString("fromCharCode"), NULL, String_Type, JSValue(new JSFunction(String_fromCharCode, String_Type))); ProtoFunDef objectProtos[] = { { "toString", String_Type, 0, Object_toString }, { "toSource", String_Type, 0, Object_toString }, { NULL } }; ProtoFunDef functionProtos[] = { { "toString", String_Type, 0, Function_toString }, { "toSource", String_Type, 0, Function_toString }, { "hasInstance", Boolean_Type, 1, Function_hasInstance }, { NULL } }; ProtoFunDef numberProtos[] = { { "toString", String_Type, 0, Number_toString }, { "toSource", String_Type, 0, Number_toString }, { 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 }, { 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); } 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) : VirtualKeyWord(world.identifiers["virtual"]), ConstructorKeyWord(world.identifiers["constructor"]), OperatorKeyWord(world.identifiers["operator"]), FixedKeyWord(world.identifiers["fixed"]), DynamicKeyWord(world.identifiers["dynamic"]), ExtendKeyWord(world.identifiers["extend"]), PrototypeKeyWord(world.identifiers["prototype"]), mWorld(world), mScopeChain(NULL), mArena(a), mFlags(flags), mDebugFlag(false), mCurModule(NULL), mPC(NULL), mThis(kNullValue), mStack(NULL), mStackTop(0), mStackMax(0), mLocals(NULL), mArgumentBase(NULL), mReader(NULL), mGlobal(global) { initOperatorTable(); mScopeChain = new ScopeChain(this, mWorld); if (Object_Type == NULL) { initBuiltins(); JSObject *mathObj = Object_Type->newInstance(this); getGlobalObject()->defineVariable(this, widenCString("Math"), (NamespaceList *)(NULL), Object_Type, JSValue(mathObj)); initMathObject(this, mathObj); getGlobalObject()->defineVariable(this, widenCString("undefined"), (NamespaceList *)(NULL), Void_Type, kUndefinedValue); getGlobalObject()->defineVariable(this, widenCString("NaN"), (NamespaceList *)(NULL), Void_Type, kNaNValue); getGlobalObject()->defineVariable(this, widenCString("Infinity"), (NamespaceList *)(NULL), Void_Type, kPositiveInfinity); } 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 (uint32 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), Attribute_Type, JSValue(attr)); } JSFunction *x = new JSFunction(ExtendAttribute_Invoke, Attribute_Type); getGlobalObject()->defineVariable(this, widenCString("extend"), (NamespaceList *)(NULL), Attribute_Type, JSValue(x)); } 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()) { StringFormatter s; PrettyPrinter pp(s); value.function->getFunctionName()->print(pp); f << "function '" << s.getString() << "'\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; } } }