/* -*- 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 "parser.h" #include "numerics.h" #include "js2runtime.h" #include "jsstring.h" #include "regexp.h" namespace JavaScript { namespace JS2Runtime { JSValue String_Constructor(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) { JSValue thatValue = thisValue; if (thatValue.isNull()) thatValue = String_Type->newInstance(cx); ASSERT(thatValue.isObject()); JSObject *thisObj = thatValue.object; JSStringInstance *strInst = checked_cast(thisObj); if (argc > 0) thisObj->mPrivate = (void *)(new String(*argv[0].toString(cx).string)); else thisObj->mPrivate = (void *)(&cx->Empty_StringAtom); strInst->mLength = ((String *)(thisObj->mPrivate))->size(); 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->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); return JSValue(resultStr); } static JSValue String_toString(Context *cx, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/) { ASSERT(thisValue.isObject()); if (thisValue.getType() != String_Type) cx->reportError(Exception::typeError, "String.toString called on something other than a string thing"); JSObject *thisObj = thisValue.object; return JSValue((String *)thisObj->mPrivate); } static JSValue String_valueOf(Context *cx, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/) { ASSERT(thisValue.isObject()); if (thisValue.getType() != String_Type) cx->reportError(Exception::typeError, "String.valueOf called on something other than a string thing"); JSObject *thisObj = thisValue.object; return JSValue((String *)thisObj->mPrivate); } static JSValue String_search(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) { ContextStackReplacement csr(cx); ASSERT(thisValue.isObject() || thisValue.isFunction() || thisValue.isType()); JSValue S = thisValue.toString(cx); JSValue regexp = argv[0]; if ((argc == 0) || !regexp.isObject() || (regexp.object->mType != RegExp_Type)) { regexp = kNullValue; regexp = RegExp_Constructor(cx, regexp, argv, 1); } REParseState *parseResult = (REParseState *)(regexp.object->mPrivate); /* save & restore lastIndex as it's not to be modified */ uint32 lastIndex = parseResult->lastIndex; parseResult->lastIndex = 0; REState *regexp_result = REExecute(parseResult, S.string->begin(), S.string->length()); parseResult->lastIndex = lastIndex; if (regexp_result) return JSValue((float64)(regexp_result->startIndex)); else return JSValue(-1.0); } static JSValue String_match(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) { ContextStackReplacement csr(cx); ASSERT(thisValue.isObject() || thisValue.isFunction() || thisValue.isType()); JSValue S = thisValue.toString(cx); JSValue regexp = argv[0]; if ((argc == 0) || !regexp.isObject() || (regexp.object->mType != RegExp_Type)) { regexp = kNullValue; regexp = RegExp_Constructor(cx, regexp, argv, 1); } REParseState *parseResult = (REParseState *)(regexp.object->mPrivate); if ((parseResult->flags & GLOBAL) == 0) { return RegExp_exec(cx, regexp, &S, 1); } else { JSArrayInstance *A = (JSArrayInstance *)Array_Type->newInstance(cx); parseResult->lastIndex = 0; int32 index = 0; while (true) { REState *regexp_result = REExecute(parseResult, S.string->begin(), S.string->length()); if (regexp_result == NULL) break; if (parseResult->lastIndex == index) parseResult->lastIndex++; String *matchStr = new String(S.string->substr(regexp_result->startIndex, regexp_result->endIndex - regexp_result->startIndex)); A->setProperty(cx, *numberToString(index++), NULL, JSValue(matchStr)); } regexp.object->setProperty(cx, cx->LastIndex_StringAtom, NULL, JSValue((float64)(parseResult->lastIndex))); return JSValue(A); } } static const String interpretDollar(Context *cx, const String *replaceStr, uint32 dollarPos, const String *searchStr, REState *regexp_result, uint32 &skip) { skip = 2; const char16 *dollarValue = replaceStr->begin() + dollarPos + 1; switch (*dollarValue) { case '$': return cx->Dollar_StringAtom; case '&': return searchStr->substr(regexp_result->startIndex, regexp_result->endIndex - regexp_result->startIndex); case '`': return searchStr->substr(0, regexp_result->startIndex); case '\'': return searchStr->substr(regexp_result->endIndex, searchStr->length() - regexp_result->endIndex); case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { uint32 num = (uint32)(*dollarValue - '0'); if (num <= regexp_result->n) { if ((dollarPos < (replaceStr->length() - 2)) && (dollarValue[1] >= '0') && (dollarValue[1] <= '9')) { uint32 tmp = (num * 10) + (dollarValue[1] - '0'); if (tmp <= regexp_result->n) { num = tmp; skip = 3; } } return searchStr->substr((uint32)(regexp_result->parens[num - 1].index), (uint32)(regexp_result->parens[num - 1].length)); } } // fall thru default: skip = 1; return cx->Dollar_StringAtom; } } static JSValue String_replace(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) { ASSERT(thisValue.isObject() || thisValue.isFunction() || thisValue.isType()); JSValue S = thisValue.toString(cx); JSValue searchValue; JSValue replaceValue; if (argc > 0) searchValue = argv[0]; if (argc > 1) replaceValue = argv[1]; const String *replaceStr = replaceValue.toString(cx).string; if (searchValue.isObject() && (searchValue.object->mType == RegExp_Type)) { REParseState *parseResult = (REParseState *)(searchValue.object->mPrivate); REState *regexp_result; uint32 m = parseResult->parenCount; String newString; uint32 index = 0; while (true) { if (parseResult->flags & GLOBAL) parseResult->lastIndex = (int32)index; regexp_result = REExecute(parseResult, S.string->begin(), S.string->length()); if (regexp_result) { String insertString; uint32 start = 0; while (true) { uint32 dollarPos = replaceStr->find('$', start); if ((dollarPos != String::npos) && (dollarPos < (replaceStr->length() - 1))) { uint32 skip; insertString += replaceStr->substr(start, dollarPos - start); insertString += interpretDollar(cx, replaceStr, dollarPos, S.string, regexp_result, skip); start = dollarPos + skip; } else { insertString += replaceStr->substr(start, replaceStr->length() - start); break; } } newString += S.string->substr(index, regexp_result->startIndex - index); newString += insertString; } else break; index = regexp_result->endIndex; if ((parseResult->flags & GLOBAL) == 0) break; } newString += S.string->substr(index, S.string->length() - index); return JSValue(new String(newString)); } else { const String *searchStr = searchValue.toString(cx).string; REState regexp_result; regexp_result.startIndex = S.string->find(*searchStr, 0); if (regexp_result.startIndex == String::npos) return JSValue(S.string); regexp_result.endIndex = regexp_result.startIndex + searchStr->length(); regexp_result.n = 0; String insertString; String newString; uint32 start = 0; while (true) { uint32 dollarPos = replaceStr->find('$', start); if ((dollarPos != String::npos) && (dollarPos < (replaceStr->length() - 1))) { uint32 skip; insertString += replaceStr->substr(start, dollarPos - start); insertString += interpretDollar(cx, replaceStr, dollarPos, S.string, ®exp_result, skip); start = dollarPos + skip; } else { insertString += replaceStr->substr(start, replaceStr->length() - start); break; } } newString += S.string->substr(0, regexp_result.startIndex); newString += insertString; uint32 index = regexp_result.endIndex; newString += S.string->substr(index, S.string->length() - index); return JSValue(new String(newString)); } } struct MatchResult { bool failure; uint32 endIndex; uint32 capturesCount; JSValue *captures; }; static void strSplitMatch(const String *S, uint32 q, const String *R, MatchResult &result) { result.failure = true; result.captures = NULL; result.capturesCount = 0; uint32 r = R->size(); uint32 s = S->size(); if ((q + r) > s) return; for (uint32 i = 0; i < r; i++) { if ((*S)[q + i] != (*R)[i]) return; } result.endIndex = q + r; result.failure = false; } static void regexpSplitMatch(const String *S, uint32 q, REParseState *RE, MatchResult &result) { result.failure = true; result.captures = NULL; REState *regexp_result = REMatch(RE, S->begin() + q, S->length() - q); if (regexp_result) { result.endIndex = regexp_result->startIndex + q; result.failure = false; result.capturesCount = regexp_result->n; if (regexp_result->n) { result.captures = new JSValue[regexp_result->n]; for (uint32 i = 0; i < regexp_result->n; i++) { if (regexp_result->parens[i].index != -1) { String *parenStr = new String(S->substr((uint32)(regexp_result->parens[i].index + q), (uint32)(regexp_result->parens[i].length))); result.captures[i] = JSValue(parenStr); } else result.captures[i] = kUndefinedValue; } } } } static JSValue String_split(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) { ContextStackReplacement csr(cx); ASSERT(thisValue.isObject() || thisValue.isFunction() || thisValue.isType()); JSValue S = thisValue.toString(cx); JSArrayInstance *A = (JSArrayInstance *)Array_Type->newInstance(cx); uint32 lim; JSValue separatorV = (argc > 0) ? argv[0] : kUndefinedValue; JSValue limitV = (argc > 1) ? argv[1] : kUndefinedValue; if (limitV.isUndefined()) lim = (uint32)(two32minus1); else lim = (uint32)(limitV.toUInt32(cx).f64); uint32 s = S.string->size(); uint32 p = 0; REParseState *RE = NULL; const String *R = NULL; if (separatorV.isObject() && (separatorV.object->mType == RegExp_Type)) RE = (REParseState *)(separatorV.object->mPrivate); else R = separatorV.toString(cx).string; if (lim == 0) return JSValue(A); /* XXX standard requires this, but Monkey doesn't do it and the tests break if (separatorV.isUndefined()) { A->setProperty(cx, widenCString("0"), NULL, S); return JSValue(A); } */ if (s == 0) { MatchResult z; if (RE) regexpSplitMatch(S.string, 0, RE, z); else strSplitMatch(S.string, 0, R, z); if (!z.failure) return JSValue(A); A->setProperty(cx, widenCString("0"), NULL, S); return JSValue(A); } while (true) { uint32 q = p; step11: if (q == s) { String *T = new String(*S.string, p, (s - p)); JSValue v(T); A->setProperty(cx, *numberToString(A->mLength), NULL, v); return JSValue(A); } MatchResult z; if (RE) regexpSplitMatch(S.string, q, RE, z); else strSplitMatch(S.string, q, R, z); if (z.failure) { q = q + 1; goto step11; } uint32 e = z.endIndex; if (e == p) { q = q + 1; goto step11; } String *T = new String(*S.string, p, (q - p)); JSValue v(T); A->setProperty(cx, *numberToString(A->mLength), NULL, v); if (A->mLength == lim) return JSValue(A); p = e; for (uint32 i = 0; i < z.capturesCount; i++) { A->setProperty(cx, *numberToString(A->mLength), NULL, JSValue(z.captures[i])); if (A->mLength == lim) return JSValue(A); } } } static JSValue String_charAt(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) { ASSERT(thisValue.isObject() || thisValue.isFunction() || thisValue.isType()); const String *str = thisValue.toString(cx).string; uint32 pos = 0; if (argc > 0) pos = (uint32)(argv[0].toInt32(cx).f64); if ((pos < 0) || (pos >= str->size())) return JSValue(&cx->Empty_StringAtom); else return JSValue(new String(1, (*str)[pos])); } static JSValue String_charCodeAt(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) { ASSERT(thisValue.isObject() || thisValue.isFunction() || thisValue.isType()); const String *str = thisValue.toString(cx).string; uint32 pos = 0; if (argc > 0) pos = (uint32)(argv[0].toInt32(cx).f64); if ((pos < 0) || (pos >= str->size())) return kNaNValue; else return JSValue((float64)(*str)[pos]); } static JSValue String_concat(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) { ASSERT(thisValue.isObject() || thisValue.isFunction() || thisValue.isType()); const String *str = thisValue.toString(cx).string; String *result = new String(*str); for (uint32 i = 0; i < argc; i++) { *result += *argv[i].toString(cx).string; } return JSValue(result); } static JSValue String_indexOf(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) { ASSERT(thisValue.isObject() || thisValue.isFunction() || thisValue.isType()); if (argc == 0) return JSValue(-1.0); const String *str = thisValue.toString(cx).string; const String *searchStr = argv[0].toString(cx).string; uint32 pos = 0; if (argc > 1) { float64 fpos = argv[1].toNumber(cx).f64; if (JSDOUBLE_IS_NaN(fpos)) pos = 0; if (fpos < 0) pos = 0; else if (fpos >= str->size()) pos = str->size(); else pos = (uint32)(fpos); } pos = str->find(*searchStr, pos); if (pos == String::npos) return JSValue(-1.0); return JSValue((float64)pos); } static JSValue String_lastIndexOf(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) { ASSERT(thisValue.isObject() || thisValue.isFunction() || thisValue.isType()); if (argc == 0) return JSValue(-1.0); const String *str = thisValue.toString(cx).string; const String *searchStr = argv[0].toString(cx).string; uint32 pos = str->size(); if (argc > 1) { float64 fpos = argv[1].toNumber(cx).f64; if (JSDOUBLE_IS_NaN(fpos)) pos = str->size(); else { if (fpos < 0) pos = 0; else if (fpos >= str->size()) pos = str->size(); else pos = (uint32)(fpos); } } pos = str->rfind(*searchStr, pos); if (pos == String::npos) return JSValue(-1.0); return JSValue((float64)pos); } static JSValue String_localeCompare(Context * /*cx*/, const JSValue& /*thisValue*/, JSValue * /*argv*/, uint32 /*argc*/) { return kUndefinedValue; } static JSValue String_toLowerCase(Context *cx, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/) { ASSERT(thisValue.isObject() || thisValue.isFunction() || thisValue.isType()); JSValue S = thisValue.toString(cx); String *result = new String(*S.string); for (String::iterator i = result->begin(), end = result->end(); i != end; i++) *i = toLower(*i); return JSValue(result); } static JSValue String_toUpperCase(Context *cx, const JSValue& thisValue, JSValue * /*argv*/, uint32 /*argc*/) { ASSERT(thisValue.isObject() || thisValue.isFunction() || thisValue.isType()); JSValue S = thisValue.toString(cx); String *result = new String(*S.string); for (String::iterator i = result->begin(), end = result->end(); i != end; i++) *i = toUpper(*i); return JSValue(result); } static JSValue String_slice(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) { ASSERT(thisValue.isObject() || thisValue.isFunction() || thisValue.isType()); const String *sourceString = thisValue.toString(cx).string; uint32 sourceLength = sourceString->size(); uint32 start, end; if (argc > 0) { int32 arg0 = (int32)(argv[0].toInt32(cx).f64); if (arg0 < 0) { arg0 += sourceLength; if (arg0 < 0) start = 0; else start = toUInt32(arg0); } else { if (toUInt32(arg0) < sourceLength) start = toUInt32(arg0); else start = sourceLength; } } else start = 0; if (argc > 1) { int32 arg1 = (int32)(argv[1].toInt32(cx).f64); if (arg1 < 0) { arg1 += sourceLength; if (arg1 < 0) end = 0; else end = toUInt32(arg1); } else { if (toUInt32(arg1) < sourceLength) end = toUInt32(arg1); else end = sourceLength; } } else end = sourceLength; if (start > end) return JSValue(&cx->Empty_StringAtom); return JSValue(new String(sourceString->substr(start, end - start))); } static JSValue String_substring(Context *cx, const JSValue& thisValue, JSValue *argv, uint32 argc) { ASSERT(thisValue.isObject() || thisValue.isFunction() || thisValue.isType()); const String *sourceString = thisValue.toString(cx).string; uint32 sourceLength = sourceString->size(); uint32 start, end; if (argc > 0) { float64 farg0 = argv[0].toNumber(cx).f64; if (JSDOUBLE_IS_NaN(farg0) || (farg0 < 0)) start = 0; else { if (!JSDOUBLE_IS_FINITE(farg0)) start = sourceLength; else { start = JSValue::float64ToUInt32(farg0); if (start > sourceLength) start = sourceLength; } } } else start = 0; if (argc > 1) { float64 farg1 = argv[1].toNumber(cx).f64; if (JSDOUBLE_IS_NaN(farg1) || (farg1 < 0)) end = 0; else { if (!JSDOUBLE_IS_FINITE(farg1)) end = sourceLength; else { end = JSValue::float64ToUInt32(farg1); if (end > sourceLength) end = sourceLength; } } } else end = sourceLength; if (start > end) { uint32 t = start; start = end; end = t; } return JSValue(new String(sourceString->substr(start, end - start))); } Context::PrototypeFunctions *getStringProtos() { Context::ProtoFunDef stringProtos[] = { { "toString", String_Type, 0, String_toString }, { "valueOf", String_Type, 0, String_valueOf }, { "charAt", String_Type, 1, String_charAt }, { "charCodeAt", Number_Type, 1, String_charCodeAt }, { "concat", String_Type, 1, String_concat }, { "indexOf", Number_Type, 1, String_indexOf }, { "lastIndexOf", Number_Type, 2, String_lastIndexOf }, // XXX ECMA spec says 1, but tests want 2 XXX { "localeCompare", Number_Type, 1, String_localeCompare }, { "match", Array_Type, 1, String_match }, { "replace", String_Type, 2, String_replace }, { "search", Number_Type, 1, String_search }, { "slice", String_Type, 2, String_slice }, { "split", Array_Type, 1, String_split }, // XXX ECMA spec says 2, but tests want 1 XXX { "substring", String_Type, 2, String_substring }, { "toSource", String_Type, 0, String_toString }, { "toLocaleUpperCase", String_Type, 0, String_toUpperCase }, // (sic) { "toLocaleLowerCase", String_Type, 0, String_toLowerCase }, // (sic) { "toUpperCase", String_Type, 0, String_toUpperCase }, { "toLowerCase", String_Type, 0, String_toLowerCase }, { NULL } }; return new Context::PrototypeFunctions(&stringProtos[0]); } } }