Source code drop for the Actionscript virtual machine in the Adobe Flash Player for December 2013
718 lines
24 KiB
C++
718 lines
24 KiB
C++
/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */
|
|
/* vi: set ts=4 sw=4 expandtab: (add to ~/.vimrc: set modeline modelines=5) */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
#include "avmplus.h"
|
|
|
|
#include "pcre.h"
|
|
|
|
// todo figure out what to do about all the new/delete in here
|
|
// todo general clean-up
|
|
|
|
namespace avmplus
|
|
{
|
|
using namespace MMgc;
|
|
|
|
|
|
class PcreState {
|
|
public:
|
|
PcreState(Toplevel* t) { AvmCore::setPCREContext(t); }
|
|
~PcreState() { AvmCore::setPCREContext(NULL); }
|
|
};
|
|
|
|
#define PCRE_STATE(x) PcreState p(x)
|
|
|
|
#define OVECTOR_SIZE 99 // 32 matches = (32+1)*3
|
|
|
|
CompiledRegExp::~CompiledRegExp()
|
|
{
|
|
// NOTE: we do not set the PCRE_STATE here, because we don't have a toplevel
|
|
// that we can use to pass to AvmCore::setPCREContext. It's OK: pcre_free
|
|
// just frees a char[], it does not go deep like the compilation step does.
|
|
|
|
(pcre_free)((void*)(pcre*)regex);
|
|
regex = NULL;
|
|
}
|
|
|
|
RegExpObject::RegExpObject(VTable* ivtable, ScriptObject *objectPrototype)
|
|
: ScriptObject(ivtable, objectPrototype)
|
|
, m_source(core()->kEmptyString)
|
|
, m_lastIndex(0)
|
|
, m_optionFlags(PCRE_UTF8)
|
|
, m_global(false)
|
|
, m_hasNamedGroups(false)
|
|
{
|
|
completeInitialization(core()->kEmptyString);
|
|
}
|
|
|
|
// This variant is used for "new RegExp( re )" where "re" is a RegExp - we copy it.
|
|
|
|
RegExpObject::RegExpObject(RegExpObject *toCopy)
|
|
: ScriptObject(toCopy->vtable, toCopy->getDelegate())
|
|
, m_source(toCopy->m_source)
|
|
, m_pcreInst(toCopy->m_pcreInst)
|
|
, m_lastIndex(0)
|
|
, m_optionFlags(toCopy->m_optionFlags)
|
|
, m_global(toCopy->m_global)
|
|
, m_hasNamedGroups(toCopy->m_hasNamedGroups)
|
|
{
|
|
}
|
|
|
|
// This variant is used for "new RegExp(s)" and "new RegExp(s,f)" where s is not
|
|
// a RegExp object. In the one-argument case "s" may have a trailing flags string.
|
|
|
|
RegExpObject::RegExpObject(VTable* ivtable, ScriptObject* delegate, Stringp pattern, Stringp options)
|
|
: ScriptObject(ivtable, delegate)
|
|
, m_source(pattern)
|
|
, m_lastIndex(0)
|
|
, m_optionFlags(PCRE_UTF8)
|
|
, m_global(false)
|
|
, m_hasNamedGroups(false)
|
|
{
|
|
// Skip the precompilation if the pattern and options hit the cache.
|
|
|
|
if (!core()->m_regexCache.testCachedRegex(pattern, options))
|
|
{
|
|
// Precompilation: check for named groups, and split out the options if no options were
|
|
// passed in, as for new RegExp( regex.toString() ).
|
|
|
|
int32_t numSlashSeen = 0;
|
|
int32_t pos = 0;
|
|
int32_t length = pattern->length();
|
|
int32_t optionpos = 0;
|
|
|
|
// Scan the pattern, look for options.
|
|
|
|
while (pos < length)
|
|
{
|
|
wchar c = pattern->charAt(pos);
|
|
if (c == 0)
|
|
break;
|
|
if (c == '(' &&
|
|
pos+3 < length &&
|
|
pattern->charAt(pos+1) == '?' &&
|
|
pattern->charAt(pos+2) == 'P' &&
|
|
pattern->charAt(pos+3) == '<')
|
|
{
|
|
m_hasNamedGroups = true;
|
|
}
|
|
else if (options == NULL && c == '/' && (pos == 0 || pattern->charAt(pos-1) != '\\') && numSlashSeen++ > 0)
|
|
{
|
|
options = pattern;
|
|
optionpos = pos+1;
|
|
}
|
|
pos++;
|
|
}
|
|
|
|
// Scan the options.
|
|
|
|
if (options != NULL)
|
|
{
|
|
pos = optionpos;
|
|
length = options->length();
|
|
while (pos < length)
|
|
{
|
|
switch(options->charAt(pos))
|
|
{
|
|
case 'g':
|
|
m_global = true;
|
|
break;
|
|
case 'i':
|
|
m_optionFlags |= PCRE_CASELESS;
|
|
break;
|
|
case 'm':
|
|
m_optionFlags |= PCRE_MULTILINE;
|
|
break;
|
|
case 's':
|
|
m_optionFlags |= PCRE_DOTALL;
|
|
break;
|
|
case 'x':
|
|
m_optionFlags |= PCRE_EXTENDED;
|
|
break;
|
|
}
|
|
pos++;
|
|
}
|
|
}
|
|
}
|
|
|
|
completeInitialization(options);
|
|
}
|
|
|
|
#ifdef DRC_TRIVIAL_DESTRUCTOR
|
|
RegExpObject::~RegExpObject()
|
|
{
|
|
m_global = false;
|
|
m_lastIndex = 0;
|
|
m_optionFlags = 0;
|
|
m_hasNamedGroups = false;
|
|
}
|
|
#endif
|
|
|
|
// Note 'options' may be the same as m_source, and the options substring
|
|
// will then start in the middle somewhere. So don't go inspecting that
|
|
// string too closely, it's used for the cache.
|
|
//
|
|
// If it is known that m_source and options hit the cache then only m_source
|
|
// need have the correct value, all others will be loaded from the cache.
|
|
// Otherwise, all fields apart from m_pcreInst must have correct values.
|
|
|
|
void RegExpObject::completeInitialization(String* options)
|
|
{
|
|
AvmAssert(traits()->getSizeOfInstance() == sizeof(RegExpObject));
|
|
|
|
bool found = false;
|
|
RegexCacheEntry& r = core()->m_regexCache.findCachedRegex(found, m_source, options);
|
|
|
|
if (found)
|
|
{
|
|
m_global = r.global;
|
|
m_optionFlags = r.optionFlags;
|
|
m_hasNamedGroups = r.hasNamedGroups;
|
|
m_pcreInst = r.regex;
|
|
}
|
|
else
|
|
{
|
|
PCRE_STATE(toplevel());
|
|
|
|
int errptr;
|
|
const char *error;
|
|
StUTF8String patternz(m_source);
|
|
void* pcreInst = (void*)pcre_compile(patternz.c_str(), m_optionFlags, &error, &errptr, NULL);
|
|
CompiledRegExp* regex = new (gc()) CompiledRegExp(pcreInst);
|
|
|
|
if (!core()->m_regexCache.disabled())
|
|
{
|
|
r.pattern = m_source;
|
|
r.options = options;
|
|
r.global = m_global;
|
|
r.optionFlags = m_optionFlags;
|
|
r.hasNamedGroups = m_hasNamedGroups;
|
|
r.regex = regex;
|
|
}
|
|
|
|
m_pcreInst = regex;
|
|
}
|
|
}
|
|
|
|
// this = argv[0]
|
|
// arg1 = argv[1]
|
|
// argN = argv[argc]
|
|
Atom RegExpObject::call(int argc, Atom *argv)
|
|
{
|
|
// this call occurs when a regexp object is invoked directly as a function ala "/a|b/('dcab')"
|
|
AvmCore *core = this->core();
|
|
Atom inAtom = argc ? core->string(argv[1])->atom() : core->kEmptyString->atom();
|
|
return AS3_exec(core->atomToString(inAtom));
|
|
}
|
|
|
|
/**
|
|
* read a string. No resource table stuff here, caller must take care of it.
|
|
* @param code
|
|
* @return
|
|
*/
|
|
Atom RegExpObject::stringFromUTF8(const char* buffer, int len)
|
|
{
|
|
AvmAssert(len >= 0);
|
|
return core()->newStringUTF8(buffer, len)->atom();
|
|
}
|
|
|
|
int RegExpObject::search(Stringp subject)
|
|
{
|
|
int matchIndex, matchLen;
|
|
StIndexableUTF8String utf8Subject(subject);
|
|
if (!_exec(subject, utf8Subject, 0, matchIndex, matchLen))
|
|
{
|
|
matchIndex = -1;
|
|
}
|
|
else
|
|
{
|
|
matchIndex = utf8Subject.toIndex(matchIndex);
|
|
}
|
|
return matchIndex;
|
|
}
|
|
|
|
int RegExpObject::numBytesInUtf8Character(const uint8_t *in)
|
|
{
|
|
unsigned int c = *in;
|
|
switch(c>>4)
|
|
{
|
|
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
|
|
default:
|
|
// 0xxx xxxx
|
|
return 1;
|
|
case 12: case 13:
|
|
// 110xxxxx 10xxxxxx
|
|
return 2;
|
|
case 14:
|
|
// 1110xxxx 10xxxxxx 10xxxxxx
|
|
return 3;
|
|
case 15:
|
|
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
|
return 4;
|
|
}
|
|
}
|
|
|
|
|
|
ArrayObject* RegExpObject::split(Stringp subject, uint32_t limit)
|
|
{
|
|
ArrayObject *out = toplevel()->arrayClass()->newArray();
|
|
StIndexableUTF8String utf8Subject(subject);
|
|
|
|
int startIndex=0;
|
|
int matchIndex;
|
|
int matchLen;
|
|
ArrayObject* matchArray;
|
|
unsigned n=0;
|
|
bool isEmptyRE = m_source->length() == 0;
|
|
while ((matchArray = _exec(subject,
|
|
utf8Subject,
|
|
startIndex,
|
|
matchIndex,
|
|
matchLen)) != NULL)
|
|
{
|
|
// [cn 11/22/04] when match is made, but is length 0 we've matched the empty
|
|
// position between characters. Although we've "matched", its zero length so just break out.
|
|
if (matchLen == 0 ) {
|
|
matchLen = 0;
|
|
matchIndex = startIndex+numBytesInUtf8Character((uint8_t*)(utf8Subject.c_str())+startIndex); // +1char will advance startIndex, extract just one char
|
|
if( !isEmptyRE )
|
|
{
|
|
// don't break if we're processing an empty regex - then we want to split the string into each character
|
|
// so we want the loop to continue
|
|
break;
|
|
}
|
|
}
|
|
|
|
//[ed 8/10/04] don't go past end of string. not sure why pcre doesn't return null
|
|
//for a match starting past the end.
|
|
//[cn 12/3/04] because a regular expression which matches an empty position (space between characters)
|
|
// will match the empty position just past the last character. This test is correct, though
|
|
// it needs to come before we do any setProperties to avoid a bogus xtra result.
|
|
if (matchIndex+matchLen > utf8Subject.length()) {
|
|
startIndex = matchIndex+matchLen;
|
|
break;
|
|
} else {
|
|
out->setUintProperty(n++, stringFromUTF8(utf8Subject.c_str()+startIndex, matchIndex-startIndex));
|
|
if (n >= limit)
|
|
break;
|
|
for (uint32_t j=1; j<matchArray->getLength(); j++) {
|
|
out->setUintProperty(n++, matchArray->getUintProperty(j));
|
|
if (n >= limit)
|
|
break;
|
|
}
|
|
// Advance past this match
|
|
startIndex = matchIndex+matchLen;
|
|
}
|
|
}
|
|
|
|
// If we found no match, or we did find a match and are still under limit, and there is a remainder left, add it
|
|
if ((unsigned)n < limit && startIndex <= utf8Subject.length()) {
|
|
out->setUintProperty(n++, stringFromUTF8(utf8Subject.c_str()+startIndex, utf8Subject.length()-startIndex));
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
Atom RegExpObject::AS3_exec(Stringp subject)
|
|
{
|
|
if (!subject)
|
|
{
|
|
subject = core()->knull;
|
|
}
|
|
StIndexableUTF8String utf8Subject(subject);
|
|
ArrayObject *result = _exec(subject, utf8Subject);
|
|
return result ? result->atom() : nullStringAtom;
|
|
}
|
|
|
|
ArrayObject* RegExpObject::_exec(Stringp subject, StIndexableUTF8String& utf8Subject)
|
|
{
|
|
AvmAssert(subject != NULL);
|
|
|
|
int matchIndex = 0, matchLen = 0;
|
|
int startIndex = (get_global() ? utf8Subject.toUtf8Index(m_lastIndex) : 0);
|
|
|
|
ArrayObject* result = _exec(subject,
|
|
utf8Subject,
|
|
startIndex,
|
|
matchIndex,
|
|
matchLen);
|
|
if (get_global())
|
|
{
|
|
m_lastIndex = utf8Subject.toIndex(matchIndex+matchLen);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
ArrayObject* RegExpObject::_exec(Stringp subject,
|
|
StIndexableUTF8String& utf8Subject,
|
|
int startIndex,
|
|
int& matchIndex,
|
|
int& matchLen)
|
|
{
|
|
AvmAssert(subject != NULL);
|
|
|
|
int ovector[OVECTOR_SIZE];
|
|
int results;
|
|
int subjectLength = utf8Subject.length();
|
|
|
|
PCRE_STATE(toplevel());
|
|
if( startIndex < 0 ||
|
|
startIndex > subjectLength ||
|
|
(results = pcre_exec((pcre*)(m_pcreInst->regex),
|
|
NULL,
|
|
utf8Subject.c_str(),
|
|
subjectLength,
|
|
startIndex,
|
|
PCRE_NO_UTF8_CHECK,
|
|
ovector,
|
|
OVECTOR_SIZE)) < 0)
|
|
{
|
|
matchIndex = 0;
|
|
matchLen = 0;
|
|
return NULL;
|
|
}
|
|
|
|
AvmCore *core = this->core();
|
|
ArrayObject *a = toplevel()->arrayClass()->newArray(results);
|
|
|
|
a->setAtomProperty(core->kindex->atom(),
|
|
core->intToAtom(utf8Subject.toIndex(ovector[0])));
|
|
a->setAtomProperty(core->kinput->atom(),
|
|
subject->atom());
|
|
a->setLength(results);
|
|
|
|
// set array slots
|
|
for (int i=0; i<results; i++) {
|
|
if (ovector[i*2] > -1) {
|
|
int length = ovector[i*2 + 1] - ovector[i*2];
|
|
Atom match = stringFromUTF8(utf8Subject.c_str()+ovector[i*2], length);
|
|
a->setUintProperty(i, match);
|
|
} else {
|
|
a->setUintProperty(i, undefinedAtom);
|
|
}
|
|
}
|
|
|
|
// handle named groups
|
|
if (m_hasNamedGroups)
|
|
{
|
|
int entrySize;
|
|
pcre_fullinfo((pcre*)(m_pcreInst->regex), NULL, PCRE_INFO_NAMEENTRYSIZE, &entrySize);
|
|
|
|
int nameCount;
|
|
pcre_fullinfo((pcre*)(m_pcreInst->regex), NULL, PCRE_INFO_NAMECOUNT, &nameCount);
|
|
|
|
// this space is freed when (pcre*)m_pcreInst is freed
|
|
char *nameTable;
|
|
pcre_fullinfo((pcre*)(m_pcreInst->regex), NULL, PCRE_INFO_NAMETABLE, &nameTable);
|
|
|
|
/* nameTable is a series of fixed length entries (entrySize)
|
|
the first two bytes are the index into the ovector and the result
|
|
is a null terminated string (the subgroup name) */
|
|
for (int i = 0; i < nameCount; i++)
|
|
{
|
|
int nameIndex, length;
|
|
nameIndex = (nameTable[0] << 8) + nameTable[1];
|
|
length = ovector[nameIndex * 2 + 1] - ovector[ nameIndex * 2 ];
|
|
|
|
Atom name = stringFromUTF8((nameTable+2), (uint32_t)VMPI_strlen(nameTable+2));
|
|
name = core->internString(name)->atom();
|
|
|
|
Atom value = stringFromUTF8(utf8Subject.c_str()+ovector[nameIndex*2], length);
|
|
|
|
a->setAtomProperty(name, value);
|
|
|
|
nameTable += entrySize;
|
|
}
|
|
}
|
|
|
|
matchIndex = ovector[0];
|
|
matchLen = ovector[1]-ovector[0];
|
|
|
|
return a;
|
|
}
|
|
|
|
ArrayObject* RegExpObject::match(Stringp subject)
|
|
{
|
|
StIndexableUTF8String utf8Subject(subject);
|
|
if (!get_global())
|
|
{
|
|
return _exec(subject, utf8Subject);
|
|
}
|
|
else
|
|
{
|
|
ArrayObject *a = toplevel()->arrayClass()->newArray();
|
|
|
|
int oldLastIndex = m_lastIndex;
|
|
m_lastIndex = 0;
|
|
|
|
int n = 0;
|
|
|
|
ArrayObject* matchArray;
|
|
while (true)
|
|
{
|
|
int last = m_lastIndex;
|
|
int matchIndex = 0, matchLen = 0;
|
|
int startIndex = utf8Subject.toUtf8Index(m_lastIndex);
|
|
|
|
matchArray = _exec(subject,
|
|
utf8Subject,
|
|
startIndex,
|
|
matchIndex,
|
|
matchLen);
|
|
m_lastIndex = utf8Subject.toIndex(matchIndex+matchLen);
|
|
|
|
if ((matchArray == NULL) || (last == m_lastIndex))
|
|
break;
|
|
a->setUintProperty(n++, matchArray->getUintProperty(0));
|
|
}
|
|
|
|
if (m_lastIndex == oldLastIndex)
|
|
{
|
|
m_lastIndex++;
|
|
}
|
|
|
|
return a;
|
|
}
|
|
}
|
|
|
|
#define NUM_MATCHES 100
|
|
|
|
Atom RegExpObject::replace(Stringp subject,
|
|
Stringp replacement)
|
|
{
|
|
StUTF8String utf8Subject(subject);
|
|
StUTF8String utf8Replacement(replacement);
|
|
|
|
int ovector[OVECTOR_SIZE];
|
|
int subjectLength = utf8Subject.length();
|
|
int lastIndex=0;
|
|
|
|
StringBuffer resultBuffer(core());
|
|
|
|
const char *src = utf8Subject.c_str();
|
|
|
|
PCRE_STATE(toplevel());
|
|
|
|
// get start/end index of all matches
|
|
int matchCount;
|
|
while (lastIndex <= subjectLength &&
|
|
(matchCount = pcre_exec((pcre*)(m_pcreInst->regex), NULL, (const char*)src,
|
|
subjectLength, lastIndex, PCRE_NO_UTF8_CHECK, ovector, OVECTOR_SIZE)) > 0)
|
|
{
|
|
int captureCount = matchCount-1;
|
|
|
|
int matchIndex = ovector[0];
|
|
int matchLen = ovector[1]-ovector[0];
|
|
|
|
// copy in stuff leading up to match
|
|
resultBuffer.writeN(src+lastIndex, matchIndex-lastIndex);
|
|
|
|
const char *ptr = utf8Replacement.c_str();
|
|
while (*ptr) {
|
|
if (*ptr == '$') {
|
|
switch (*(ptr+1)) {
|
|
case '$':
|
|
resultBuffer << '$';
|
|
ptr += 2;
|
|
break;
|
|
case '&':
|
|
resultBuffer.writeN(src+matchIndex, matchLen);
|
|
ptr += 2;
|
|
break;
|
|
case '`':
|
|
resultBuffer.writeN(src, matchIndex);
|
|
ptr += 2;
|
|
break;
|
|
case '\'':
|
|
resultBuffer << src+ovector[1];
|
|
ptr += 2;
|
|
break;
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
{
|
|
int i;
|
|
if (*(ptr+2) >= '0' && *(ptr+2) <= '9') {
|
|
int j = 10*(ptr[1]-'0')+(ptr[2]-'0');
|
|
if (j > captureCount) {
|
|
// Gobbling up two digits would overflow the
|
|
// capture count, so just use the one digit.
|
|
i = ptr[1]-'0';
|
|
} else {
|
|
i = j;
|
|
}
|
|
} else {
|
|
i = ptr[1]-'0';
|
|
}
|
|
if (i >= 1 && i <= captureCount) {
|
|
resultBuffer.writeN(src+ovector[i*2],
|
|
ovector[i*2+1]-ovector[i*2]);
|
|
ptr += (i >= 10) ? 3 : 2;
|
|
} else {
|
|
resultBuffer << *ptr++;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
resultBuffer << *ptr++;
|
|
break;
|
|
}
|
|
} else {
|
|
resultBuffer << *ptr++;
|
|
}
|
|
}
|
|
|
|
int newLastIndex = ovector[0] + (ovector[1] - ovector[0]);
|
|
|
|
// prevents infinite looping in certain cases
|
|
fixReplaceLastIndex(src,
|
|
subjectLength,
|
|
matchLen,
|
|
newLastIndex,
|
|
resultBuffer);
|
|
|
|
lastIndex = newLastIndex;
|
|
|
|
if (!get_global())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// copy in stuff after last match
|
|
if (lastIndex < subjectLength)
|
|
{
|
|
resultBuffer.writeN(src+lastIndex, subjectLength-lastIndex);
|
|
}
|
|
|
|
return stringFromUTF8((const char*)resultBuffer.c_str(), resultBuffer.length());
|
|
}
|
|
|
|
Atom RegExpObject::replace(Stringp subject,
|
|
ScriptObject* replaceFunction)
|
|
{
|
|
StUTF8String utf8Subject(subject);
|
|
|
|
int ovector[OVECTOR_SIZE];
|
|
int subjectLength = utf8Subject.length();
|
|
int lastIndex=0;
|
|
|
|
StringBuffer resultBuffer(core());
|
|
|
|
const char *src = utf8Subject.c_str();
|
|
|
|
PCRE_STATE(toplevel());
|
|
|
|
// get start/end index of all matches
|
|
int matchCount;
|
|
while (lastIndex < subjectLength &&
|
|
(matchCount = pcre_exec((pcre*)(m_pcreInst->regex), NULL, (const char*)src,
|
|
subjectLength, lastIndex, PCRE_NO_UTF8_CHECK, ovector, OVECTOR_SIZE)) > 0)
|
|
{
|
|
int captureCount = matchCount-1;
|
|
|
|
int matchIndex = ovector[0];
|
|
int matchLen = ovector[1]-ovector[0];
|
|
|
|
// copy in stuff leading up to match
|
|
resultBuffer.writeN(src+lastIndex, matchIndex-lastIndex);
|
|
|
|
// call the replace function
|
|
Atom argv[NUM_MATCHES+4];
|
|
int argc = captureCount+3;
|
|
|
|
argv[0] = undefinedAtom;
|
|
|
|
// ECMA 15.5.4.11: Argument 1 is the substring that matched.
|
|
argv[1] = stringFromUTF8(src+matchIndex, matchLen);
|
|
|
|
// ECMA 15.5.4.11: The next m arguments are all of the captures in the
|
|
// MatchResult
|
|
for (int i=1; i<=captureCount; i++)
|
|
{
|
|
argv[i+1] = stringFromUTF8(src+ovector[i*2], ovector[i*2+1]-ovector[i*2]);
|
|
}
|
|
|
|
// ECMA 15.5.4.11: Argument m+2 is the offset within string
|
|
// where the match occurred
|
|
argv[captureCount+2] = core()->uintToAtom(matchIndex);
|
|
|
|
// ECMA 15.5.4.11: Argument m+3 is string
|
|
argv[captureCount+3] = subject->atom();
|
|
|
|
resultBuffer << core()->string(avmplus::op_call(toplevel(), replaceFunction, argc, argv));
|
|
|
|
int newLastIndex = ovector[0] + (ovector[1] - ovector[0]);
|
|
|
|
// prevents infinite looping in certain cases
|
|
fixReplaceLastIndex(src,
|
|
subjectLength,
|
|
matchLen,
|
|
newLastIndex,
|
|
resultBuffer);
|
|
|
|
lastIndex = newLastIndex;
|
|
|
|
if (!get_global())
|
|
break;
|
|
}
|
|
|
|
// copy in stuff after last match
|
|
if (lastIndex < subjectLength)
|
|
{
|
|
resultBuffer.writeN(src+lastIndex, subjectLength-lastIndex);
|
|
}
|
|
|
|
return stringFromUTF8((const char*)resultBuffer.c_str(), resultBuffer.length());
|
|
}
|
|
|
|
void RegExpObject::fixReplaceLastIndex(const char *src,
|
|
int subjectLength,
|
|
int matchLen,
|
|
int& newLastIndex,
|
|
StringBuffer& resultBuffer)
|
|
{
|
|
if (matchLen == 0 && get_global())
|
|
{
|
|
// Advance one character
|
|
if (newLastIndex < subjectLength)
|
|
{
|
|
uint32_t ch;
|
|
int n = UnicodeUtils::Utf8ToUcs4((const uint8_t*)src+newLastIndex, subjectLength-newLastIndex, &ch, core()->currentBugCompatibility()->bugzilla609416);
|
|
if (n <= 0)
|
|
{
|
|
// Invalid UTF8 sequence, advance one uint8_t
|
|
n = 1;
|
|
}
|
|
resultBuffer.writeN(src+newLastIndex, n);
|
|
newLastIndex += n;
|
|
}
|
|
else
|
|
{
|
|
newLastIndex++;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Accessors
|
|
//
|
|
|
|
bool RegExpObject::get_ignoreCase() { return hasOption(PCRE_CASELESS); }
|
|
bool RegExpObject::get_multiline() { return hasOption(PCRE_MULTILINE); }
|
|
bool RegExpObject::get_dotall() { return hasOption(PCRE_DOTALL); }
|
|
bool RegExpObject::get_extended() { return hasOption(PCRE_EXTENDED); }
|
|
|
|
}
|