// ECMA 15.10.4 Object RegExpObjectImp::construct(ExecState *exec, const List &args) { UString p; if (args.isEmpty()) { p = ""; } else { Value a0 = args[0]; if (a0.isA(ObjectType) && a0.toObject(exec).inherits(&RegExpImp::info)) { // It's a regexp. Check that no flags were passed. if (args.size() > 1 && args[1].type() != UndefinedType) { Object err = Error::create(exec,TypeError); exec->setException(err); return err; } RegExpImp *rimp = static_cast<RegExpImp*>(Object::dynamicCast(a0).imp()); p = rimp->regExp()->pattern(); } else { p = a0.toString(exec); } } RegExp* re = makeEngine(exec, p, args[1]); if (!re) return exec->exception().toObject(exec); RegExpPrototypeImp *proto = static_cast<RegExpPrototypeImp*>(exec->lexicalInterpreter()->builtinRegExpPrototype().imp()); RegExpImp *dat = new RegExpImp(proto); Object obj(dat); // protect from GC dat->setRegExp(re); return obj; }
// ECMA 15.10.4 Object RegExpObjectImp::construct(ExecState *exec, const List &args) { UString p = args.isEmpty() ? UString("") : args[0].toString(exec); UString flags = args[1].toString(exec); RegExpPrototypeImp *proto = static_cast<RegExpPrototypeImp*>(exec->interpreter()->builtinRegExpPrototype().imp()); RegExpImp *dat = new RegExpImp(proto); Object obj(dat); // protect from GC bool global = (flags.find("g") >= 0); bool ignoreCase = (flags.find("i") >= 0); bool multiline = (flags.find("m") >= 0); // TODO: throw a syntax error on invalid flags dat->putDirect("global", global ? BooleanImp::staticTrue : BooleanImp::staticFalse); dat->putDirect("ignoreCase", ignoreCase ? BooleanImp::staticTrue : BooleanImp::staticFalse); dat->putDirect("multiline", multiline ? BooleanImp::staticTrue : BooleanImp::staticFalse); dat->putDirect("source", new StringImp(p)); dat->putDirect("lastIndex", NumberImp::zero(), DontDelete | DontEnum); int reflags = RegExp::None; if (global) reflags |= RegExp::Global; if (ignoreCase) reflags |= RegExp::IgnoreCase; if (multiline) reflags |= RegExp::Multiline; dat->setRegExp(new RegExp(p, reflags)); return obj; }
// ECMA 15.10.4 JSObject *RegExpObjectImp::construct(ExecState *exec, const List &args) { JSObject *o = args[0]->getObject(); if (o && o->inherits(&RegExpImp::info)) { if (!args[1]->isUndefined()) return throwError(exec, TypeError); return o; } UString p = args[0]->isUndefined() ? UString("") : args[0]->toString(exec); RegExp* re = makeEngine(exec, p, args[1]); if (!re) return exec->exception()->toObject(exec); RegExpPrototype *proto = static_cast<RegExpPrototype*>(exec->lexicalInterpreter()->builtinRegExpPrototype()); RegExpImp *dat = new RegExpImp(proto); dat->setRegExp(exec, re); return dat; }
// ECMA 15.10.4 JSObject *RegExpObjectImp::construct(ExecState *exec, const List &args) { JSObject *o = args[0]->getObject(); if (o && o->inherits(&RegExpImp::info)) { if (!args[1]->isUndefined()) return throwError(exec, TypeError); return o; } UString p = args[0]->isUndefined() ? UString("") : args[0]->toString(exec); UString flags = args[1]->isUndefined() ? UString("") : args[1]->toString(exec); RegExpPrototype *proto = static_cast<RegExpPrototype*>(exec->lexicalInterpreter()->builtinRegExpPrototype()); RegExpImp *dat = new RegExpImp(proto); bool global = (flags.find("g") >= 0); bool ignoreCase = (flags.find("i") >= 0); bool multiline = (flags.find("m") >= 0); dat->putDirect(exec->propertyNames().global, jsBoolean(global), DontDelete | ReadOnly | DontEnum); dat->putDirect(exec->propertyNames().ignoreCase, jsBoolean(ignoreCase), DontDelete | ReadOnly | DontEnum); dat->putDirect(exec->propertyNames().multiline, jsBoolean(multiline), DontDelete | ReadOnly | DontEnum); dat->putDirect(exec->propertyNames().source, jsString(p), DontDelete | ReadOnly | DontEnum); dat->putDirect(exec->propertyNames().lastIndex, jsNumber(0), DontDelete | DontEnum); int reflags = RegExp::None; if (global) reflags |= RegExp::Global; if (ignoreCase) reflags |= RegExp::IgnoreCase; if (multiline) reflags |= RegExp::Multiline; OwnPtr<RegExp> re(new RegExp(p, reflags)); if (!re->isValid()) return throwError(exec, SyntaxError, UString("Invalid regular expression: ").append(re->errorMessage())); dat->setRegExp(re.release()); return dat; }
Value RegExpProtoFuncImp::call(ExecState *exec, Object &thisObj, const List &args) { if (!thisObj.inherits(&RegExpImp::info)) { Object err = Error::create(exec,TypeError); exec->setException(err); return err; } RegExpImp *reimp = static_cast<RegExpImp*>(thisObj.imp()); RegExp *re = reimp->regExp(); String s; UString str; switch (id) { case Exec: // 15.10.6.2 case Test: { s = args[0].toString(exec); int length = s.value().size(); Value lastIndex = thisObj.get(exec,"lastIndex"); int i = lastIndex.isNull() ? 0 : lastIndex.toInt32(exec); bool globalFlag = thisObj.get(exec,"global").toBoolean(exec); if (!globalFlag) i = 0; if (i < 0 || i > length) { thisObj.put(exec,"lastIndex", Number(0), DontDelete | DontEnum); if (id == Test) return Boolean(false); else Null(); } RegExpObjectImp* regExpObj = static_cast<RegExpObjectImp*>(exec->interpreter()->builtinRegExp().imp()); int **ovector = regExpObj->registerRegexp( re, s.value() ); str = re->match(s.value(), i, 0L, ovector); regExpObj->setSubPatterns(re->subPatterns()); if (id == Test) return Boolean(!str.isNull()); if (str.isNull()) // no match { if (globalFlag) thisObj.put(exec,"lastIndex",Number(0), DontDelete | DontEnum); return Null(); } else // success { if (globalFlag) thisObj.put(exec,"lastIndex",Number( (*ovector)[1] ), DontDelete | DontEnum); return regExpObj->arrayOfMatches(exec,str); } } break; case ToString: s = thisObj.get(exec,"source").toString(exec); str = "/"; str += s.value(); str += "/"; // TODO append the flags return String(str); } return Undefined(); }
// ECMA 15.5.4.2 - 15.5.4.20 Value StringProtoFuncImp::call(ExecState *exec, Object &thisObj, const List &args) { Value result; // toString and valueOf are no generic functions. if (id == ToString || id == ValueOf) { KJS_CHECK_THIS( StringInstanceImp, thisObj ); return String(thisObj.internalValue().toString(exec)); } int n, m; UString u2, u3; double dpos; int pos, p0, i; double d = 0.0; UString s = thisObj.toString(exec); int len = s.size(); Value a0 = args[0]; Value a1 = args[1]; switch (id) { case ToString: case ValueOf: // handled above break; case CharAt: pos = a0.type() == UndefinedType ? 0 : a0.toInteger(exec); if (pos < 0 || pos >= len) s = ""; else s = s.substr(pos, 1); result = String(s); break; case CharCodeAt: pos = a0.type() == UndefinedType ? 0 : a0.toInteger(exec); if (pos < 0 || pos >= len) d = NaN; else { UChar c = s[pos]; d = (c.high() << 8) + c.low(); } result = Number(d); break; case Concat: { ListIterator it = args.begin(); for ( ; it != args.end() ; ++it) { s += it->dispatchToString(exec); } result = String(s); break; } case IndexOf: u2 = a0.toString(exec); if (a1.type() == UndefinedType) pos = 0; else pos = a1.toInteger(exec); d = s.find(u2, pos); result = Number(d); break; case LastIndexOf: u2 = a0.toString(exec); d = a1.toNumber(exec); if (a1.type() == UndefinedType || KJS::isNaN(d)) dpos = len; else { dpos = d; if (dpos < 0) dpos = 0; else if (dpos > len) dpos = len; } result = Number(s.rfind(u2, int(dpos))); break; case Match: case Search: { RegExp *reg, *tmpReg = 0; RegExpImp *imp = 0; if (a0.isA(ObjectType) && a0.toObject(exec).inherits(&RegExpImp::info)) { imp = static_cast<RegExpImp *>( a0.toObject(exec).imp() ); reg = imp->regExp(); } else { /* * ECMA 15.5.4.12 String.prototype.search (regexp) * If regexp is not an object whose [[Class]] property is "RegExp", it is * replaced with the result of the expression new RegExp(regexp). */ reg = tmpReg = new RegExp(a0.toString(exec), RegExp::None); } RegExpObjectImp* regExpObj = static_cast<RegExpObjectImp*>(exec->interpreter()->builtinRegExp().imp()); int **ovector = regExpObj->registerRegexp(reg, s); reg->prepareMatch(s); UString mstr = reg->match(s, -1, &pos, ovector); if (id == Search) { result = Number(pos); } else { // Match if (mstr.isNull()) { result = Null(); // no match } else if ((reg->flags() & RegExp::Global) == 0) { // case without 'g' flag is handled like RegExp.prototype.exec regExpObj->setSubPatterns(reg->subPatterns()); result = regExpObj->arrayOfMatches(exec,mstr); } else { // return array of matches List list; while (pos >= 0) { list.append(String(mstr)); pos += mstr.isEmpty() ? 1 : mstr.size(); delete [] *ovector; mstr = reg->match(s, pos, &pos, ovector); } result = exec->lexicalInterpreter()->builtinArray().construct(exec, list); } } reg->doneMatch(); delete tmpReg; break; } case Replace: if (a0.type() == ObjectType && a0.toObject(exec).inherits(&RegExpImp::info)) { RegExpImp* imp = static_cast<RegExpImp *>( a0.toObject(exec).imp() ); RegExp *reg = imp->regExp(); bool global = false; Value tmp = imp->get(exec,"global"); if (tmp.type() != UndefinedType && tmp.toBoolean(exec) == true) global = true; RegExpObjectImp* regExpObj = static_cast<RegExpObjectImp*>(exec->lexicalInterpreter()->builtinRegExp().imp()); int lastIndex = 0; Object o1; // Test if 2nd arg is a function (new in JS 1.3) if ( a1.type() == ObjectType && a1.toObject(exec).implementsCall() ) o1 = a1.toObject(exec); else u3 = a1.toString(exec); // 2nd arg is the replacement string UString out; // This is either a loop (if global is set) or a one-way (if not). reg->prepareMatch(s); do { int **ovector = regExpObj->registerRegexp( reg, s ); UString mstr = reg->match(s, lastIndex, &pos, ovector); regExpObj->setSubPatterns(reg->subPatterns()); if (pos == -1) break; len = mstr.size(); UString rstr; // Prepare replacement if (!o1.isValid()) { rstr = u3; bool ok; // check if u3 matches $1 or $2 etc for (int i = 0; (i = rstr.find(UString("$"), i)) != -1; i++) { if (i+1<rstr.size() && rstr[i+1] == '$') { // "$$" -> "$" rstr = rstr.substr(0,i) + "$" + rstr.substr(i+2); continue; } // Assume number part is one char exactly unsigned long pos = rstr.substr(i+1,1).toULong(&ok, false /* tolerate empty string */); if (ok && pos <= (unsigned)reg->subPatterns()) { rstr = rstr.substr(0,i) + s.substr((*ovector)[2*pos], (*ovector)[2*pos+1]-(*ovector)[2*pos]) + rstr.substr(i+2); i += (*ovector)[2*pos+1]-(*ovector)[2*pos] - 1; // -1 offsets i++ } } } else // 2nd arg is a function call. Spec from http://devedge.netscape.com/library/manuals/2000/javascript/1.5/reference/string.html#1194258 { List l; l.append(String(mstr)); // First arg: complete matched substring // Then the submatch strings for ( unsigned int sub = 1; sub <= reg->subPatterns() ; ++sub ) l.append( String( s.substr((*ovector)[2*sub], (*ovector)[2*sub+1]-(*ovector)[2*sub]) ) ); l.append(Number(pos)); // The offset within the string where the match occurred l.append(String(s)); // Last arg: the string itself. Can't see the difference with the 1st arg! Object thisObj = exec->interpreter()->globalObject(); rstr = o1.call( exec, thisObj, l ).toString(exec); } // Append the stuff we skipped over to get to the match -- // that would be [lastIndex, pos) of the original.. if (pos != lastIndex) out += s.substr(lastIndex, pos - lastIndex); // Append the replacement.. out += rstr; lastIndex = pos + len; // Skip over the matched stuff... } while (global); // Append the rest of the string to the output... if (lastIndex == 0 && out.size() == 0) // Don't copy stuff if nothing changed out = s; else out += s.substr(lastIndex, s.size() - lastIndex); reg->doneMatch(); result = String(out); } else { // First arg is a string u2 = a0.toString(exec); pos = s.find(u2); len = u2.size(); // Do the replacement if (pos == -1) result = String(s); else { u3 = s.substr(0, pos) + a1.toString(exec) + s.substr(pos + len); result = String(u3); } } break; case Slice: // http://developer.netscape.com/docs/manuals/js/client/jsref/string.htm#1194366 or 15.5.4.13 { // The arg processing is very much like ArrayProtoFunc::Slice int begin = args[0].toUInt32(exec); int end = len; if (args[1].type() != UndefinedType) { end = args[1].toInteger(exec); } int from = begin < 0 ? len + begin : begin; int to = end < 0 ? len + end : end; if (to > from && to > 0 && from < len) { if (from < 0) { from = 0; } if (to > len) { to = len; } result = String(s.substr(from, to - from)); } else { result = String(""); } break; } case Split: { Object constructor = exec->lexicalInterpreter()->builtinArray(); Object res = Object::dynamicCast(constructor.construct(exec,List::empty())); result = res; i = p0 = 0; uint32_t limit = (a1.type() != UndefinedType) ? a1.toUInt32(exec) : 0xFFFFFFFFU; if (a0.type() == ObjectType && Object::dynamicCast(a0).inherits(&RegExpImp::info)) { Object obj0 = Object::dynamicCast(a0); RegExp reg(obj0.get(exec,"source").toString(exec)); reg.prepareMatch(s); if (s.isEmpty() && !reg.match(s, 0).isNull()) { // empty string matched by regexp -> empty array reg.doneMatch(); res.put(exec, lengthPropertyName, Number(0), DontDelete|ReadOnly|DontEnum); break; } pos = 0; while (static_cast<uint32_t>(i) != limit && pos < s.size()) { // TODO: back references int mpos; int *ovector = 0L; UString mstr = reg.match(s, pos, &mpos, &ovector); delete [] ovector; ovector = 0L; if (mpos < 0) break; pos = mpos + (mstr.isEmpty() ? 1 : mstr.size()); if (mpos != p0 || !mstr.isEmpty()) { res.put(exec,i, String(s.substr(p0, mpos-p0))); p0 = mpos + mstr.size(); i++; } } reg.doneMatch(); } else { u2 = a0.toString(exec); if (u2.isEmpty()) { if (s.isEmpty()) { // empty separator matches empty string -> empty array put(exec,lengthPropertyName, Number(0)); break; } else { while (static_cast<uint32_t>(i) != limit && i < s.size()-1) res.put(exec,i++, String(s.substr(p0++, 1))); } } else { while (static_cast<uint32_t>(i) != limit && (pos = s.find(u2, p0)) >= 0) { res.put(exec,i, String(s.substr(p0, pos-p0))); p0 = pos + u2.size(); i++; } } } // add remaining string, if any if (static_cast<uint32_t>(i) != limit) res.put(exec,i++, String(s.substr(p0))); res.put(exec,lengthPropertyName, Number(i)); } break; case Substr: { n = a0.toInteger(exec); m = a1.toInteger(exec); int d, d2; if (n >= 0) d = n; else d = maxInt(len + n, 0); if (a1.type() == UndefinedType) d2 = len - d; else d2 = minInt(maxInt(m, 0), len - d); result = String(s.substr(d, d2)); break; } case Substring: { double start = a0.toNumber(exec); double end = a1.toNumber(exec); if (KJS::isNaN(start)) start = 0; if (KJS::isNaN(end)) end = 0; if (start < 0) start = 0; if (end < 0) end = 0; if (start > len) start = len; if (end > len) end = len; if (a1.type() == UndefinedType) end = len; if (start > end) { double temp = end; end = start; start = temp; } result = String(s.substr((int)start, (int)end-(int)start)); } break; case ToLowerCase: case ToLocaleLowerCase: // FIXME: To get this 100% right we need to detect Turkish and change I to lowercase i without a dot. for (i = 0; i < len; i++) s[i] = s[i].toLower(); result = String(s); break; case ToUpperCase: case ToLocaleUpperCase: // FIXME: To get this 100% right we need to detect Turkish and change i to uppercase I with a dot. for (i = 0; i < len; i++) s[i] = s[i].toUpper(); result = String(s); break; #ifndef KJS_PURE_ECMA case Big: result = String("<big>" + s + "</big>"); break; case Small: result = String("<small>" + s + "</small>"); break; case Blink: result = String("<blink>" + s + "</blink>"); break; case Bold: result = String("<b>" + s + "</b>"); break; case Fixed: result = String("<tt>" + s + "</tt>"); break; case Italics: result = String("<i>" + s + "</i>"); break; case Strike: result = String("<strike>" + s + "</strike>"); break; case Sub: result = String("<sub>" + s + "</sub>"); break; case Sup: result = String("<sup>" + s + "</sup>"); break; case Fontcolor: result = String("<font color=\"" + a0.toString(exec) + "\">" + s + "</font>"); break; case Fontsize: result = String("<font size=\"" + a0.toString(exec) + "\">" + s + "</font>"); break; case Anchor: result = String("<a name=\"" + a0.toString(exec) + "\">" + s + "</a>"); break; case Link: result = String("<a href=\"" + a0.toString(exec) + "\">" + s + "</a>"); break; #endif } return result; }
JSValue *RegExpProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args) { if (!thisObj->inherits(&RegExpImp::info)) { if (thisObj->inherits(&RegExpPrototype::info)) { switch (id) { case ToString: return jsString("//"); } } return throwError(exec, TypeError); } switch (id) { case Test: // 15.10.6.2 case Exec: { RegExp *regExp = static_cast<RegExpImp*>(thisObj)->regExp(); RegExpObjectImp* regExpObj = static_cast<RegExpObjectImp*>(exec->lexicalInterpreter()->builtinRegExp()); UString input; if (args.isEmpty()) input = regExpObj->get(exec, exec->propertyNames().input)->toString(exec); else input = args[0]->toString(exec); double lastIndex = thisObj->get(exec, exec->propertyNames().lastIndex)->toInteger(exec); bool globalFlag = thisObj->get(exec, exec->propertyNames().global)->toBoolean(exec); if (!globalFlag) lastIndex = 0; if (lastIndex < 0 || lastIndex > input.size()) { thisObj->put(exec, exec->propertyNames().lastIndex, jsNumber(0), DontDelete | DontEnum); return jsNull(); } int foundIndex; RegExpStringContext ctx(input); UString match = regExpObj->performMatch(regExp, exec, ctx, input, static_cast<int>(lastIndex), &foundIndex); if (exec->hadException()) return jsUndefined(); bool didMatch = !match.isNull(); if (globalFlag) { if (didMatch) thisObj->put(exec, exec->propertyNames().lastIndex, jsNumber(foundIndex + match.size()), DontDelete | DontEnum); else thisObj->put(exec, exec->propertyNames().lastIndex, jsNumber(0), DontDelete | DontEnum); } // Test if (id == Test) return jsBoolean(didMatch); // Exec if (didMatch) { return regExpObj->arrayOfMatches(exec, match); } else { return jsNull(); } } break; case ToString: { UString result = "/" + thisObj->get(exec, exec->propertyNames().source)->toString(exec) + "/"; if (thisObj->get(exec, exec->propertyNames().global)->toBoolean(exec)) { result += "g"; } if (thisObj->get(exec, exec->propertyNames().ignoreCase)->toBoolean(exec)) { result += "i"; } if (thisObj->get(exec, exec->propertyNames().multiline)->toBoolean(exec)) { result += "m"; } return jsString(result); } case Compile: { // JS1.2 legacy, but still in use in the wild somewhat RegExpImp* instance = static_cast<RegExpImp*>(thisObj); RegExp* newEngine = RegExpObjectImp::makeEngine(exec, args[0]->toString(exec), args[1]); if (!newEngine) return exec->exception(); instance->setRegExp(exec, newEngine); return instance; } } return jsUndefined(); }
JSObject* RegExpImp::valueClone(Interpreter* targetCtx) const { RegExpImp* copy = new RegExpImp(static_cast<RegExpPrototype*>(targetCtx->builtinRegExpPrototype())); copy->setRegExp(targetCtx->globalExec(), new RegExp(reg->pattern(), reg->flags())); return copy; }
Value RegExpProtoFuncImp::call(ExecState *exec, Object &thisObj, const List &args) { if (!thisObj.inherits(&RegExpImp::info)) { if (thisObj.inherits(&RegExpPrototypeImp::info)) { switch (id) { case ToString: return String("//"); // FireFox returns /(?:)/ } } Object err = Error::create(exec,TypeError); exec->setException(err); return err; } RegExpImp *reimp = static_cast<RegExpImp*>(thisObj.imp()); RegExp *re = reimp->regExp(); String s; UString str; switch (id) { case Exec: // 15.10.6.2 case Test: { s = args[0].toString(exec); int length = s.value().size(); // Get values from the last time (in case of /g) Value lastIndex = thisObj.get(exec,"lastIndex"); int i = lastIndex.isValid() ? lastIndex.toInt32(exec) : 0; bool globalFlag = thisObj.get(exec,"global").toBoolean(exec); if (!globalFlag) i = 0; if (i < 0 || i > length) { thisObj.put(exec,"lastIndex", Number(0), DontDelete | DontEnum); if (id == Test) return Boolean(false); else return Null(); } RegExpObjectImp* regExpObj = static_cast<RegExpObjectImp*>(exec->lexicalInterpreter()->builtinRegExp().imp()); int **ovector = regExpObj->registerRegexp( re, s.value() ); re->prepareMatch(s.value()); str = re->match(s.value(), i, 0L, ovector); re->doneMatch(); regExpObj->setSubPatterns(re->subPatterns()); if (id == Test) return Boolean(!str.isNull()); if (str.isNull()) // no match { if (globalFlag) thisObj.put(exec,"lastIndex",Number(0), DontDelete | DontEnum); return Null(); } else // success { if (globalFlag) thisObj.put(exec,"lastIndex",Number( (*ovector)[1] ), DontDelete | DontEnum); return regExpObj->arrayOfMatches(exec,str); } } break; case ToString: s = thisObj.get(exec,"source").toString(exec); str = "/"; str += s.value(); str += "/"; if (thisObj.get(exec,"global").toBoolean(exec)) { str += "g"; } if (thisObj.get(exec,"ignoreCase").toBoolean(exec)) { str += "i"; } if (thisObj.get(exec,"multiline").toBoolean(exec)) { str += "m"; } return String(str); case Compile: { RegExp* newEngine = RegExpObjectImp::makeEngine(exec, args[0].toString(exec), args[1]); if (!newEngine) return exec->exception(); reimp->setRegExp(newEngine); return Value(reimp); } } return Undefined(); }