From 9f287653daba8ee871cd6ddfef2e5133a6782c0a Mon Sep 17 00:00:00 2001 From: "rogerl%netscape.com" Date: Fri, 19 Oct 2001 00:25:28 +0000 Subject: [PATCH] Added Array.sort. Fixed toNumber(String) for trailing whitespace. Fixed Date.SetXXX for date values. Fixed scopechain growth bug. Fixed empty array literals. Fixed empty type casts for built-ins. --- js2/src/bytecodegen.cpp | 18 +--- js2/src/js2execution.cpp | 45 ++++++--- js2/src/js2runtime.cpp | 21 ++++- js2/src/js2runtime.h | 9 +- js2/src/jsarray.cpp | 164 ++++++++++++++++++++++++++++++++- js2/src/jsdate.cpp | 80 ++++++++++++++-- js2/src/jsstring.cpp | 11 ++- js2/src/jsstring.h | 1 + js2/src/numerics.cpp | 2 - js2/tests/cpp/DikDik_Shell.cpp | 21 ++++- 10 files changed, 319 insertions(+), 53 deletions(-) diff --git a/js2/src/bytecodegen.cpp b/js2/src/bytecodegen.cpp index c83f6818c32..90abf7e2542 100644 --- a/js2/src/bytecodegen.cpp +++ b/js2/src/bytecodegen.cpp @@ -533,17 +533,6 @@ void ByteCodeGen::genCodeForFunction(FunctionDefinition &f, size_t pos, JSFuncti { mScopeChain->addScope(fnc->mParameterBarrel); mScopeChain->addScope(&fnc->mActivation); - // OPT - no need to push the parameter and function - // scopes if the function doesn't contain any 'eval' - // calls, all other references to the variables mapped - // inside these scopes will have been turned into - // localVar references. -/* - addByte(PushScopeOp); - addPointer(fnc->mParameterBarrel); - addByte(PushScopeOp); - addPointer(&fnc->mActivation); -*/ #ifdef DEBUG if (f.name) { @@ -632,13 +621,8 @@ void ByteCodeGen::genCodeForFunction(FunctionDefinition &f, size_t pos, JSFuncti if (f.body) hasReturn = genCodeForStatement(f.body, NULL, NotALabel); -/* - // OPT - see above - addByte(PopScopeOp); - addByte(PopScopeOp); -*/ if (isConstructor) { - ASSERT(!hasReturn); // is this useful? Won't the semantics have done it? + ASSERT(!hasReturn); // XXX is this useful? Won't the semantics have done it? addOp(LoadThisOp); ASSERT(mStackTop == 1); addOpSetDepth(ReturnOp, 0); diff --git a/js2/src/js2execution.cpp b/js2/src/js2execution.cpp index 3fa7755517d..8896768faf4 100644 --- a/js2/src/js2execution.cpp +++ b/js2/src/js2execution.cpp @@ -202,8 +202,9 @@ JSValue Context::interpret(JS2Runtime::ByteCodeModule *bcm, int offset, ScopeCha mScopeChain = new ScopeChain(this, mWorld); mScopeChain->addScope(getGlobalObject()); } - if (mThis.isObject()) - mScopeChain->addScope(mThis.object); + +// if (mThis.isObject()) +// mScopeChain->addScope(mThis.object); // mScopeChain->addScope(mActivationStack.top()); mCurModule = bcm; @@ -225,8 +226,8 @@ JSValue Context::interpret(JS2Runtime::ByteCodeModule *bcm, int offset, ScopeCha // the following (delete's) are a bit iffy - depends on whether // a closure capturing the contents has come along... - if (mThis.isObject()) - mScopeChain->popScope(); +// if (mThis.isObject()) +// mScopeChain->popScope(); delete[] mStack; delete[] mLocals; if (scopeChain == NULL) @@ -249,8 +250,8 @@ JSValue Context::interpret(JS2Runtime::ByteCodeModule *bcm, int offset, ScopeCha // the following (delete's) are a bit iffy - depends on whether // a closure capturing the contents has come along... - if (mThis.isObject()) - mScopeChain->popScope(); +// if (mThis.isObject()) +// mScopeChain->popScope(); delete[] mStack; delete[] mLocals; if (scopeChain == NULL) @@ -401,6 +402,7 @@ JSValue Context::interpret(uint8 *pc, uint8 *endPC) try { if (mDebugFlag) { FunctionName *fnName; + uint32 x = mScopeChain->mScopeStack.size(); if (mCurModule->mFunction && (fnName = mCurModule->mFunction->getFunctionName())) { StringFormatter s; PrettyPrinter pp(s); @@ -409,10 +411,10 @@ JSValue Context::interpret(uint8 *pc, uint8 *endPC) std::string str(fnStr.length(), char()); std::transform(fnStr.begin(), fnStr.end(), str.begin(), narrow); uint32 len = strlen(str.c_str()); - printFormat(stdOut, "%.30s+%.4d%*c%d ", str.c_str(), (pc - mCurModule->mCodeBase), (len > 30) ? 0 : (len - 30), ' ', stackSize()); + printFormat(stdOut, "%.30s+%.4d%*c%d %d ", str.c_str(), (pc - mCurModule->mCodeBase), (len > 30) ? 0 : (len - 30), ' ', stackSize(), x); } else - printFormat(stdOut, "+%.4d%*c%d ", (pc - mCurModule->mCodeBase), 30, ' ', stackSize()); + printFormat(stdOut, "+%.4d%*c%d %d ", (pc - mCurModule->mCodeBase), 30, ' ', stackSize(), x); printInstruction(stdOut, toUInt32(pc - mCurModule->mCodeBase), *mCurModule); } switch ((ByteCodeOp)(*pc++)) { @@ -639,8 +641,8 @@ JSValue Context::interpret(uint8 *pc, uint8 *endPC) mArgumentBase, oldThis, pc, mCurModule)); mScopeChain = target->getScopeChain(); - if (mThis.isObject()) - mScopeChain->addScope(mThis.object); +// if (mThis.isObject()) +// mScopeChain->addScope(mThis.object); if (!target->isChecked()) { JSArrayInstance *args = (JSArrayInstance *)Array_Type->newInstance(this); @@ -692,7 +694,10 @@ JSValue Context::interpret(uint8 *pc, uint8 *endPC) } mActivationStack.pop(); delete[] mLocals; - delete[] mStack; + delete[] mStack; + + mScopeChain->popScope(); + mScopeChain->popScope(); mCurModule = prev->mModule; pc = prev->mPC; @@ -721,6 +726,9 @@ JSValue Context::interpret(uint8 *pc, uint8 *endPC) delete[] mLocals; delete[] mStack; + mScopeChain->popScope(); + mScopeChain->popScope(); + mCurModule = prev->mModule; pc = prev->mPC; endPC = mCurModule->mCodeBase + mCurModule->mLength; @@ -2225,8 +2233,19 @@ float64 stringToNumber(const String *string) if (sBegin) if ((sBegin[0] == '0') && ((sBegin[1] & ~0x20) == 'X')) return stringToInteger(sBegin, string->end(), numEnd, 16); - else - return stringToDouble(sBegin, string->end(), numEnd); + else { + float64 result = stringToDouble(sBegin, string->end(), numEnd); + if (numEnd != string->end()) { + const char16 *sEnd = string->end(); + while (numEnd != sEnd) { + if (!isSpace(*numEnd++)) + return nan; + } + return result; + } + else + return result; + } else return 0.0; } diff --git a/js2/src/js2runtime.cpp b/js2/src/js2runtime.cpp index 7c9b14d1763..b96c2101f46 100644 --- a/js2/src/js2runtime.cpp +++ b/js2/src/js2runtime.cpp @@ -175,9 +175,11 @@ bool JSObject::hasProperty(const String &name, NamespaceList *names, Access acc, bool JSObject::deleteProperty(const String &name, NamespaceList *names) { PropertyIterator i = findNamespacedProperty(name, names); - if ((PROPERTY_ATTR(i) & Property::DontDelete) == 0) { - mProperties.erase(i); - return true; + if (i != mProperties.end()) { + if ((PROPERTY_ATTR(i) & Property::DontDelete) == 0) { + mProperties.erase(i); + return true; + } } return false; } @@ -1962,6 +1964,14 @@ static JSValue Number_Constructor(Context *cx, const JSValue& thisValue, JSValue 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)) @@ -2387,14 +2397,17 @@ void Context::initBuiltins() 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); 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->mTypeCast = new JSFunction(this, String_Constructor, String_Type); + String_Type->mTypeCast = new JSFunction(this, String_TypeCast, String_Type); } diff --git a/js2/src/js2runtime.h b/js2/src/js2runtime.h index 92b6a3f6ae6..d337f05eb65 100644 --- a/js2/src/js2runtime.h +++ b/js2/src/js2runtime.h @@ -1515,7 +1515,7 @@ XXX ...couldn't get this to work... void resizeStack(uint32 n) { - ASSERT(n < mStackMax); + ASSERT(n <= mStackMax); mStackTop = n; } @@ -1619,6 +1619,13 @@ XXX ...couldn't get this to work... }; + /* + (a local instance of) This class is used when a function in the + interpreter execution codepath may need to re-invoke the interpreter + (by calling an internal method that MAY have an override). The stack + replacement simply inserts a stack big enough for whatever action is + about to occur. + */ class ContextStackReplacement { public: enum { ReplacementStackSize = 4 }; diff --git a/js2/src/jsarray.cpp b/js2/src/jsarray.cpp index 9c6e7ae6623..d219b96f188 100644 --- a/js2/src/jsarray.cpp +++ b/js2/src/jsarray.cpp @@ -62,7 +62,17 @@ JSValue Array_Constructor(Context *cx, const JSValue& thisValue, JSValue *argv, JSArrayInstance *arrInst = checked_cast(thisObj); if (argc > 0) { if (argc == 1) { - arrInst->mLength = (uint32)(argv[0].toNumber(cx).f64); + if (argv[0].isNumber()) { + uint32 i = (uint32)(argv[0].f64); + if (i == argv[0].f64) + arrInst->mLength = i; + else + cx->reportError(Exception::rangeError, "Array length too large"); + } + else { + arrInst->mLength = 1; + arrInst->defineVariable(cx, widenCString("0"), (NamespaceList *)(NULL), Property::Enumerable, Object_Type, argv[0]); + } } else { arrInst->mLength = argc; @@ -93,7 +103,8 @@ static JSValue Array_toString(Context *cx, const JSValue& thisValue, JSValue * / const String *id = numberToString(i); arrInst->getProperty(cx, *id, NULL); JSValue result = cx->popValue(); - s->append(*result.toString(cx).string); + if (!result.isUndefined() && !result.isNull()) + s->append(*result.toString(cx).string); if (i < (arrInst->mLength - 1)) s->append(widenCString(",")); } @@ -385,9 +396,154 @@ static JSValue Array_slice(Context *cx, const JSValue& thisValue, JSValue *argv, return JSValue(A); } -static JSValue Array_sort(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue * /*argv*/, uint32 /*argc*/) +typedef struct CompareArgs { + Context *context; + JSFunction *target; +} CompareArgs; + +typedef struct QSortArgs { + JSValue *vec; + JSValue *pivot; + CompareArgs *arg; +} QSortArgs; + +static int sort_compare(JSValue *a, JSValue *b, CompareArgs *arg); + +static void +js_qsort_r(QSortArgs *qa, int lo, int hi) { - return kUndefinedValue; + JSValue *pivot, *vec, *a, *b; + int i, j, lohi, hilo; + + CompareArgs *arg; + + pivot = qa->pivot; + vec = qa->vec; + arg = qa->arg; + + while (lo < hi) { + i = lo; + j = hi; + a = vec + i; + *pivot = *a; + while (i < j) { + b = vec + j; + if (sort_compare(b, pivot, arg) >= 0) { + j--; + continue; + } + *a = *b; + while (sort_compare(a, pivot, arg) <= 0) { + i++; + a = vec + i; + if (i == j) + goto store_pivot; + } + *b = *a; + } + if (i > lo) { + store_pivot: + *a = *pivot; + } + if (i - lo < hi - i) { + lohi = i - 1; + if (lo < lohi) + js_qsort_r(qa, lo, lohi); + lo = i + 1; + } else { + hilo = i + 1; + if (hilo < hi) + js_qsort_r(qa, hilo, hi); + hi = i - 1; + } + } +} + +static void js_qsort(JSValue *vec, size_t nel, CompareArgs *arg) +{ + JSValue *pivot; + QSortArgs qa; + + pivot = new JSValue(); + qa.vec = vec; + qa.pivot = pivot; + qa.arg = arg; + js_qsort_r(&qa, 0, (int)(nel - 1)); + delete(pivot); +} + +static int sort_compare(JSValue *a, JSValue *b, CompareArgs *arg) +{ + JSValue av = *(const JSValue *)a; + JSValue bv = *(const JSValue *)b; + CompareArgs *ca = (CompareArgs *) arg; + Context *cx = ca->context; + + if (ca->target == NULL) { + int32 result; + if (av.isUndefined() || bv.isUndefined()) { + /* Put undefined properties at the end. */ + result = (av.isUndefined()) ? 1 : -1; + } + else { + const String *astr = av.toString(cx).string; + const String *bstr = bv.toString(cx).string; + result = astr->compare(*bstr); + } + return result; + } + else { + JSValue argv[2]; + argv[0] = av; + argv[1] = bv; + JSValue result = cx->invokeFunction(ca->target, kNullValue, argv, 2); + return (int32)(result.toInt32(cx).f64); + } +} + + +static JSValue Array_sort(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) +{ + ASSERT(thisValue.isObject()); + ContextStackReplacement csr(cx); + + CompareArgs ca; + ca.context = cx; + + if (argc > 0) { + if (!argv[0].isFunction()) + cx->reportError(Exception::typeError, "sort needs a compare function"); + ca.target = argv[0].function; + } + else + ca.target = NULL; + + JSObject *thisObj = thisValue.object; + thisObj->getProperty(cx, cx->Length_StringAtom, CURRENT_ATTR); + JSValue result = cx->popValue(); + uint32 length = (uint32)(result.toUInt32(cx).f64); + + if (length > 0) { + uint32 i; + JSValue *vec = new JSValue[length]; + + for (i = 0; i < length; i++) { + const String *id = numberToString(i); + thisObj->getProperty(cx, *id, CURRENT_ATTR); + vec[i] = cx->popValue(); + delete id; + } + + js_qsort(vec, length, &ca); + + for (i = 0; i < length; i++) { + const String *id = numberToString(i); + thisObj->setProperty(cx, *id, CURRENT_ATTR, vec[i]); + delete id; + } + delete[] vec; + } + return thisValue; } static JSValue Array_splice(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) diff --git a/js2/src/jsdate.cpp b/js2/src/jsdate.cpp index c62651a0d01..004bb7615ff 100644 --- a/js2/src/jsdate.cpp +++ b/js2/src/jsdate.cpp @@ -426,6 +426,74 @@ static JSValue Date_makeTime(Context *cx, const JSValue& thisValue, JSValue *arg return JSValue(*date); } +static JSValue Date_makeDate(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc, uint32 maxargs, bool local) +{ + uint32 i; + float64 lorutime; /* local or UTC version of *date */ + float64 args[3], *argp, *stop; + float64 year, month, day; + float64 result; + + float64 *date = Date_getProlog(cx, thisValue); + + result = *date; + + /* see complaint about ECMA in date_MakeTime */ + if (argc == 0) + argc = 1; /* should be safe, because length of all settors is 1 */ + else if (argc > maxargs) + argc = maxargs; /* clamp argc */ + + for (i = 0; i < argc; i++) { + argv[i] = argv[i].toNumber(cx); + if (JSDOUBLE_IS_NaN(argv[i])) { + *date = nan; + return kNaNValue; + } + args[i] = argv[i].toInteger(cx).f64; + } + + /* return NaN if date is NaN and we're not setting the year, + * If we are, use 0 as the time. */ + if (!(JSDOUBLE_IS_FINITE(result))) { + if (argc < 3) + return kNaNValue; + else + lorutime = +0.; + } else { + if (local) + lorutime = LocalTime(result); + else + lorutime = result; + } + + argp = args; + stop = argp + argc; + if (maxargs >= 3 && argp < stop) + year = *argp++; + else + year = YearFromTime(lorutime); + + if (maxargs >= 2 && argp < stop) + month = *argp++; + else + month = MonthFromTime(lorutime); + + if (maxargs >= 1 && argp < stop) + day = *argp++; + else + day = DateFromTime(lorutime); + + day = MakeDay(year, month, day); /* day within year */ + result = MakeDate(day, TimeWithinDay(lorutime)); + + if (local) + result = UTC(result); + + *date = TIMECLIP(result); + return JSValue(*date); +} + /* find UTC time from given date... no 1900 correction! */ static float64 date_msecFromDate(float64 year, float64 mon, float64 mday, float64 hour, float64 min, float64 sec, float64 msec) { @@ -1258,32 +1326,32 @@ static JSValue Date_setYear(Context *cx, const JSValue& thisValue, JSValue *argv static JSValue Date_setFullYear(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) { - return Date_makeTime(cx, thisValue, argv, argc, 3, true); + return Date_makeDate(cx, thisValue, argv, argc, 3, true); } static JSValue Date_setUTCFullYear(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) { - return Date_makeTime(cx, thisValue, argv, argc, 3, false); + return Date_makeDate(cx, thisValue, argv, argc, 3, false); } static JSValue Date_setMonth(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) { - return Date_makeTime(cx, thisValue, argv, argc, 2, true); + return Date_makeDate(cx, thisValue, argv, argc, 2, true); } static JSValue Date_setUTCMonth(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) { - return Date_makeTime(cx, thisValue, argv, argc, 2, false); + return Date_makeDate(cx, thisValue, argv, argc, 2, false); } static JSValue Date_setDate(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) { - return Date_makeTime(cx, thisValue, argv, argc, 1, true); + return Date_makeDate(cx, thisValue, argv, argc, 1, true); } static JSValue Date_setUTCDate(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) { - return Date_makeTime(cx, thisValue, argv, argc, 1, false); + return Date_makeDate(cx, thisValue, argv, argc, 1, false); } static JSValue Date_setHours(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) diff --git a/js2/src/jsstring.cpp b/js2/src/jsstring.cpp index b06f597c3e7..15623960a60 100644 --- a/js2/src/jsstring.cpp +++ b/js2/src/jsstring.cpp @@ -67,9 +67,18 @@ JSValue String_Constructor(Context *cx, const JSValue& thisValue, JSValue *argv, return thatValue; } +JSValue String_TypeCast(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 argc) +{ + if (argc == 0) + return JSValue(&cx->Empty_StringAtom); + else + return argv[0].toString(cx); +} + + JSValue String_fromCharCode(Context *cx, const JSValue& /*thisValue*/, JSValue *argv, uint32 argc) { - String *resultStr = new String(); // can't use cx->mEmptyString because we're modifying this below + String *resultStr = new String(); // can't use cx->Empty_StringAtom; because we're modifying this below resultStr->reserve(argc); for (uint32 i = 0; i < argc; i++) *resultStr += (char16)(argv[i].toUInt16(cx).f64); diff --git a/js2/src/jsstring.h b/js2/src/jsstring.h index 0ff4728c5bc..b78be423778 100644 --- a/js2/src/jsstring.h +++ b/js2/src/jsstring.h @@ -36,6 +36,7 @@ namespace JS2Runtime { extern JSValue String_Constructor(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc); + extern JSValue String_TypeCast(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc); extern JSValue String_fromCharCode(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc); Context::PrototypeFunctions *getStringProtos(); diff --git a/js2/src/numerics.cpp b/js2/src/numerics.cpp index 351b3491983..5f67bf89e89 100644 --- a/js2/src/numerics.cpp +++ b/js2/src/numerics.cpp @@ -1683,8 +1683,6 @@ double JS::stringToDouble(const char16 *str, const char16 *strEnd, const char16 double value = strToDouble(cstr.get(), estr); ptrdiff_t i = estr - cstr.get(); numEnd = i ? str1 + i : str; - if ((value == 0.0) && (i == 0)) - return nan; return value; } diff --git a/js2/tests/cpp/DikDik_Shell.cpp b/js2/tests/cpp/DikDik_Shell.cpp index f9deaf27486..993c2f7728a 100644 --- a/js2/tests/cpp/DikDik_Shell.cpp +++ b/js2/tests/cpp/DikDik_Shell.cpp @@ -221,6 +221,10 @@ static bool processArgs(Context *cx, int argc, char **argv, int *result) for (int i = 0; i < argc; i++) { if (argv[i][0] == '-') { switch (argv[i][1]) { + default: + stdOut << "unrecognized command line switch\n"; + i = argc; + break; case 'f': { try { @@ -235,7 +239,12 @@ static bool processArgs(Context *cx, int argc, char **argv, int *result) break; } } - + else { + if ((argv[i][0] == '/') && (argv[i][1] == '/')) { + // skip rest of command line + break; + } + } } return doInteractive; } @@ -246,13 +255,15 @@ static bool processArgs(Context *cx, int argc, char **argv, int *result) int main(int argc, char **argv) { -#if defined(XP_MAC) && !defined(XP_MAC_MPW) - initConsole("\pJavaScript Shell", "Welcome to the js2 shell.\n", argc, argv); -#endif - using namespace JavaScript; using namespace Shell; +#if defined(XP_MAC) && !defined(XP_MAC_MPW) + initConsole("\pJavaScript Shell", "Welcome to DikDik.\n", argc, argv); +#else + stdOut << "Welcome to DikDik.\n"; +#endif + try { JSObject *globalObject; Context cx(&globalObject, world, a, Pragma::js2);