1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQml module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qv4regexpobject_p.h" |
41 | #include "qv4objectproto_p.h" |
42 | #include "qv4regexp_p.h" |
43 | #include "qv4stringobject_p.h" |
44 | #include <private/qv4mm_p.h> |
45 | #include "qv4scopedvalue_p.h" |
46 | #include "qv4jscall_p.h" |
47 | #include "qv4symbol_p.h" |
48 | |
49 | #include "private/qlocale_tools_p.h" |
50 | |
51 | #include <QtCore/QDebug> |
52 | #include <QtCore/qregexp.h> |
53 | #if QT_CONFIG(regularexpression) |
54 | #include <QtCore/qregularexpression.h> |
55 | #endif |
56 | #include <cassert> |
57 | #include <typeinfo> |
58 | #include <iostream> |
59 | #include <private/qv4alloca_p.h> |
60 | |
61 | QT_BEGIN_NAMESPACE |
62 | |
63 | Q_CORE_EXPORT QString qt_regexp_toCanonical(const QString &, QRegExp::PatternSyntax); |
64 | |
65 | using namespace QV4; |
66 | |
67 | DEFINE_OBJECT_VTABLE(RegExpObject); |
68 | |
69 | void Heap::RegExpObject::init() |
70 | { |
71 | Object::init(); |
72 | Scope scope(internalClass->engine); |
73 | Scoped<QV4::RegExpObject> o(scope, this); |
74 | value.set(e: scope.engine, newVal: QV4::RegExp::create(engine: scope.engine, pattern: QString(), flags: CompiledData::RegExp::RegExp_NoFlags)); |
75 | o->initProperties(); |
76 | } |
77 | |
78 | void Heap::RegExpObject::init(QV4::RegExp *value) |
79 | { |
80 | Object::init(); |
81 | Scope scope(internalClass->engine); |
82 | this->value.set(e: scope.engine, newVal: value->d()); |
83 | Scoped<QV4::RegExpObject> o(scope, this); |
84 | o->initProperties(); |
85 | } |
86 | |
87 | // Converts a QRegExp to a JS RegExp. |
88 | // The conversion is not 100% exact since ECMA regexp and QRegExp |
89 | // have different semantics/flags, but we try to do our best. |
90 | void Heap::RegExpObject::init(const QRegExp &re) |
91 | { |
92 | Object::init(); |
93 | |
94 | // Convert the pattern to a ECMAScript pattern. |
95 | QString pattern = QT_PREPEND_NAMESPACE(qt_regexp_toCanonical)(re.pattern(), re.patternSyntax()); |
96 | if (re.isMinimal()) { |
97 | QString ecmaPattern; |
98 | int len = pattern.length(); |
99 | ecmaPattern.reserve(asize: len); |
100 | int i = 0; |
101 | const QChar *wc = pattern.unicode(); |
102 | bool inBracket = false; |
103 | while (i < len) { |
104 | QChar c = wc[i++]; |
105 | ecmaPattern += c; |
106 | switch (c.unicode()) { |
107 | case '?': |
108 | case '+': |
109 | case '*': |
110 | case '}': |
111 | if (!inBracket) |
112 | ecmaPattern += QLatin1Char('?'); |
113 | break; |
114 | case '\\': |
115 | if (i < len) |
116 | ecmaPattern += wc[i++]; |
117 | break; |
118 | case '[': |
119 | inBracket = true; |
120 | break; |
121 | case ']': |
122 | inBracket = false; |
123 | break; |
124 | default: |
125 | break; |
126 | } |
127 | } |
128 | pattern = ecmaPattern; |
129 | } |
130 | |
131 | Scope scope(internalClass->engine); |
132 | Scoped<QV4::RegExpObject> o(scope, this); |
133 | |
134 | uint flags = (re.caseSensitivity() == Qt::CaseInsensitive ? CompiledData::RegExp::RegExp_IgnoreCase : CompiledData::RegExp::RegExp_NoFlags); |
135 | o->d()->value.set(e: scope.engine, newVal: QV4::RegExp::create(engine: scope.engine, pattern, flags)); |
136 | |
137 | o->initProperties(); |
138 | } |
139 | |
140 | #if QT_CONFIG(regularexpression) |
141 | // Converts a QRegularExpression to a JS RegExp. |
142 | // The conversion is not 100% exact since ECMA regexp and QRegularExpression |
143 | // have different semantics/flags, but we try to do our best. |
144 | void Heap::RegExpObject::init(const QRegularExpression &re) |
145 | { |
146 | Object::init(); |
147 | |
148 | Scope scope(internalClass->engine); |
149 | Scoped<QV4::RegExpObject> o(scope, this); |
150 | |
151 | const uint flags = (re.patternOptions() & QRegularExpression::CaseInsensitiveOption) |
152 | ? CompiledData::RegExp::RegExp_IgnoreCase |
153 | : CompiledData::RegExp::RegExp_NoFlags; |
154 | o->d()->value.set(e: scope.engine, newVal: QV4::RegExp::create(engine: scope.engine, pattern: re.pattern(), flags)); |
155 | o->initProperties(); |
156 | } |
157 | #endif |
158 | |
159 | void RegExpObject::initProperties() |
160 | { |
161 | setProperty(index: Index_LastIndex, v: Value::fromInt32(i: 0)); |
162 | |
163 | Q_ASSERT(value()); |
164 | } |
165 | |
166 | // Converts a JS RegExp to a QRegExp. |
167 | // The conversion is not 100% exact since ECMA regexp and QRegExp |
168 | // have different semantics/flags, but we try to do our best. |
169 | QRegExp RegExpObject::toQRegExp() const |
170 | { |
171 | Qt::CaseSensitivity caseSensitivity = (value()->flags & CompiledData::RegExp::RegExp_IgnoreCase) ? Qt::CaseInsensitive : Qt::CaseSensitive; |
172 | return QRegExp(*value()->pattern, caseSensitivity, QRegExp::RegExp2); |
173 | } |
174 | |
175 | #if QT_CONFIG(regularexpression) |
176 | // Converts a JS RegExp to a QRegularExpression. |
177 | // The conversion is not 100% exact since ECMA regexp and QRegularExpression |
178 | // have different semantics/flags, but we try to do our best. |
179 | QRegularExpression RegExpObject::toQRegularExpression() const |
180 | { |
181 | QRegularExpression::PatternOptions caseSensitivity |
182 | = (value()->flags & CompiledData::RegExp::RegExp_IgnoreCase) |
183 | ? QRegularExpression::CaseInsensitiveOption |
184 | : QRegularExpression::NoPatternOption; |
185 | return QRegularExpression(*value()->pattern, caseSensitivity); |
186 | } |
187 | #endif |
188 | |
189 | QString RegExpObject::toString() const |
190 | { |
191 | QString p = *value()->pattern; |
192 | if (p.isEmpty()) { |
193 | p = QStringLiteral("(?:)" ); |
194 | } else { |
195 | // escape certain parts, see ch. 15.10.4 |
196 | p.replace(c: '/', after: QLatin1String("\\/" )); |
197 | } |
198 | return p; |
199 | } |
200 | |
201 | ReturnedValue RegExpObject::builtinExec(ExecutionEngine *engine, const String *str) |
202 | { |
203 | QString s = str->toQString(); |
204 | |
205 | Scope scope(engine); |
206 | int offset = (global() || sticky()) ? lastIndex() : 0; |
207 | if (offset < 0 || offset > s.length()) { |
208 | setLastIndex(0); |
209 | RETURN_RESULT(Encode::null()); |
210 | } |
211 | |
212 | Q_ALLOCA_VAR(uint, matchOffsets, value()->captureCount() * 2 * sizeof(uint)); |
213 | const uint result = Scoped<RegExp>(scope, value())->match(string: s, start: offset, matchOffsets); |
214 | |
215 | RegExpCtor *regExpCtor = static_cast<RegExpCtor *>(scope.engine->regExpCtor()); |
216 | regExpCtor->d()->clearLastMatch(); |
217 | |
218 | if (result == JSC::Yarr::offsetNoMatch) { |
219 | if (global() || sticky()) |
220 | setLastIndex(0); |
221 | RETURN_RESULT(Encode::null()); |
222 | } |
223 | |
224 | Q_ASSERT(result <= uint(std::numeric_limits<int>::max())); |
225 | |
226 | // fill in result data |
227 | ScopedArrayObject array(scope, scope.engine->newArrayObject(ic: scope.engine->internalClasses(icType: EngineBase::Class_RegExpExecArray))); |
228 | int len = value()->captureCount(); |
229 | array->arrayReserve(n: len); |
230 | ScopedValue v(scope); |
231 | int strlen = s.length(); |
232 | for (int i = 0; i < len; ++i) { |
233 | int start = matchOffsets[i * 2]; |
234 | int end = matchOffsets[i * 2 + 1]; |
235 | if (end > strlen) |
236 | end = strlen; |
237 | v = (start != -1) ? scope.engine->memoryManager->alloc<ComplexString>(args: str->d(), args: start, args: end - start)->asReturnedValue() : Encode::undefined(); |
238 | array->arrayPut(index: i, value: v); |
239 | } |
240 | array->setArrayLengthUnchecked(len); |
241 | array->setProperty(index: Index_ArrayIndex, v: Value::fromInt32(i: int(result))); |
242 | array->setProperty(index: Index_ArrayInput, v: *str); |
243 | |
244 | RegExpCtor::Data *dd = regExpCtor->d(); |
245 | dd->lastMatch.set(e: scope.engine, newVal: array); |
246 | dd->lastInput.set(e: scope.engine, newVal: str->d()); |
247 | dd->lastMatchStart = matchOffsets[0]; |
248 | dd->lastMatchEnd = matchOffsets[1]; |
249 | |
250 | if (global() || sticky()) |
251 | setLastIndex(matchOffsets[1]); |
252 | |
253 | return array.asReturnedValue(); |
254 | } |
255 | |
256 | DEFINE_OBJECT_VTABLE(RegExpCtor); |
257 | |
258 | void Heap::RegExpCtor::init(QV4::ExecutionContext *scope) |
259 | { |
260 | Heap::FunctionObject::init(scope, QStringLiteral("RegExp" )); |
261 | clearLastMatch(); |
262 | } |
263 | |
264 | void Heap::RegExpCtor::clearLastMatch() |
265 | { |
266 | lastMatch.set(e: internalClass->engine, newVal: Value::nullValue()); |
267 | lastInput.set(e: internalClass->engine, newVal: internalClass->engine->id_empty()->d()); |
268 | lastMatchStart = 0; |
269 | lastMatchEnd = 0; |
270 | } |
271 | |
272 | static bool isRegExp(ExecutionEngine *e, const QV4::Value *arg) |
273 | { |
274 | const QV4::Object *o = arg->objectValue(); |
275 | if (!o) |
276 | return false; |
277 | |
278 | QV4::Value isRegExp = QV4::Value::fromReturnedValue(val: o->get(name: e->symbol_match())); |
279 | if (!isRegExp.isUndefined()) |
280 | return isRegExp.toBoolean(); |
281 | const RegExpObject *re = o->as<RegExpObject>(); |
282 | return re ? true : false; |
283 | } |
284 | |
285 | uint parseFlags(Scope &scope, const QV4::Value *f) |
286 | { |
287 | uint flags = CompiledData::RegExp::RegExp_NoFlags; |
288 | if (!f->isUndefined()) { |
289 | ScopedString s(scope, f->toString(e: scope.engine)); |
290 | if (scope.hasException()) |
291 | return flags; |
292 | QString str = s->toQString(); |
293 | for (int i = 0; i < str.length(); ++i) { |
294 | if (str.at(i) == QLatin1Char('g') && !(flags & CompiledData::RegExp::RegExp_Global)) { |
295 | flags |= CompiledData::RegExp::RegExp_Global; |
296 | } else if (str.at(i) == QLatin1Char('i') && !(flags & CompiledData::RegExp::RegExp_IgnoreCase)) { |
297 | flags |= CompiledData::RegExp::RegExp_IgnoreCase; |
298 | } else if (str.at(i) == QLatin1Char('m') && !(flags & CompiledData::RegExp::RegExp_Multiline)) { |
299 | flags |= CompiledData::RegExp::RegExp_Multiline; |
300 | } else if (str.at(i) == QLatin1Char('u') && !(flags & CompiledData::RegExp::RegExp_Unicode)) { |
301 | flags |= CompiledData::RegExp::RegExp_Unicode; |
302 | } else if (str.at(i) == QLatin1Char('y') && !(flags & CompiledData::RegExp::RegExp_Sticky)) { |
303 | flags |= CompiledData::RegExp::RegExp_Sticky; |
304 | } else { |
305 | scope.engine->throwSyntaxError(QStringLiteral("Invalid flags supplied to RegExp constructor" )); |
306 | return flags; |
307 | } |
308 | } |
309 | } |
310 | return flags; |
311 | } |
312 | |
313 | ReturnedValue RegExpCtor::virtualCallAsConstructor(const FunctionObject *fo, const Value *argv, int argc, const Value *newTarget) |
314 | { |
315 | Scope scope(fo); |
316 | |
317 | bool patternIsRegExp = argc ? ::isRegExp(e: scope.engine, arg: argv) : false; |
318 | |
319 | if (newTarget == fo) { |
320 | if (patternIsRegExp && (argc < 2 || argv[1].isUndefined())) { |
321 | const Object *pattern = static_cast<const Object *>(argv); |
322 | ScopedValue patternConstructor(scope, pattern->get(name: scope.engine->id_constructor())); |
323 | if (patternConstructor->sameValue(other: *newTarget)) |
324 | return pattern->asReturnedValue(); |
325 | } |
326 | } |
327 | |
328 | ScopedValue p(scope, argc ? argv[0] : Value::undefinedValue()); |
329 | ScopedValue f(scope, argc > 1 ? argv[1] : Value::undefinedValue()); |
330 | Scoped<RegExpObject> re(scope, p); |
331 | QString pattern; |
332 | uint flags = CompiledData::RegExp::RegExp_NoFlags; |
333 | |
334 | if (re) { |
335 | if (f->isUndefined()) { |
336 | Scoped<RegExp> regexp(scope, re->value()); |
337 | return Encode(scope.engine->newRegExpObject(re: regexp)); |
338 | } |
339 | pattern = *re->value()->pattern; |
340 | flags = parseFlags(scope, f); |
341 | } else if (patternIsRegExp) { |
342 | const Object *po = static_cast<const Object *>(argv); |
343 | p = po->get(name: scope.engine->id_source()); |
344 | if (!p->isUndefined()) |
345 | pattern = p->toQString(); |
346 | if (scope.hasException()) |
347 | return Encode::undefined(); |
348 | if (f->isUndefined()) |
349 | f = po->get(name: scope.engine->id_flags()); |
350 | flags = parseFlags(scope, f); |
351 | } else { |
352 | if (!p->isUndefined()) |
353 | pattern = p->toQString(); |
354 | if (scope.hasException()) |
355 | return Encode::undefined(); |
356 | flags = parseFlags(scope, f); |
357 | } |
358 | if (scope.hasException()) |
359 | return Encode::undefined(); |
360 | |
361 | Scoped<RegExp> regexp(scope, RegExp::create(engine: scope.engine, pattern, flags)); |
362 | if (!regexp->isValid()) { |
363 | return scope.engine->throwSyntaxError(QStringLiteral("Invalid regular expression" )); |
364 | } |
365 | |
366 | ReturnedValue o = Encode(scope.engine->newRegExpObject(re: regexp)); |
367 | |
368 | if (!newTarget) |
369 | return o; |
370 | ScopedObject obj(scope, o); |
371 | obj->setProtoFromNewTarget(newTarget); |
372 | return obj->asReturnedValue(); |
373 | } |
374 | |
375 | ReturnedValue RegExpCtor::virtualCall(const FunctionObject *f, const Value *, const Value *argv, int argc) |
376 | { |
377 | return virtualCallAsConstructor(fo: f, argv, argc, newTarget: f); |
378 | } |
379 | |
380 | void RegExpPrototype::init(ExecutionEngine *engine, Object *constructor) |
381 | { |
382 | Scope scope(engine); |
383 | ScopedObject o(scope); |
384 | ScopedObject ctor(scope, constructor); |
385 | |
386 | ctor->defineReadonlyProperty(name: engine->id_prototype(), value: (o = this)); |
387 | ctor->defineReadonlyConfigurableProperty(name: engine->id_length(), value: Value::fromInt32(i: 2)); |
388 | ctor->addSymbolSpecies(); |
389 | |
390 | // Properties deprecated in the spec but required by "the web" :( |
391 | ctor->defineAccessorProperty(QStringLiteral("lastMatch" ), getter: method_get_lastMatch_n<0>, setter: nullptr); |
392 | ctor->defineAccessorProperty(QStringLiteral("$&" ), getter: method_get_lastMatch_n<0>, setter: nullptr); |
393 | ctor->defineAccessorProperty(QStringLiteral("$1" ), getter: method_get_lastMatch_n<1>, setter: nullptr); |
394 | ctor->defineAccessorProperty(QStringLiteral("$2" ), getter: method_get_lastMatch_n<2>, setter: nullptr); |
395 | ctor->defineAccessorProperty(QStringLiteral("$3" ), getter: method_get_lastMatch_n<3>, setter: nullptr); |
396 | ctor->defineAccessorProperty(QStringLiteral("$4" ), getter: method_get_lastMatch_n<4>, setter: nullptr); |
397 | ctor->defineAccessorProperty(QStringLiteral("$5" ), getter: method_get_lastMatch_n<5>, setter: nullptr); |
398 | ctor->defineAccessorProperty(QStringLiteral("$6" ), getter: method_get_lastMatch_n<6>, setter: nullptr); |
399 | ctor->defineAccessorProperty(QStringLiteral("$7" ), getter: method_get_lastMatch_n<7>, setter: nullptr); |
400 | ctor->defineAccessorProperty(QStringLiteral("$8" ), getter: method_get_lastMatch_n<8>, setter: nullptr); |
401 | ctor->defineAccessorProperty(QStringLiteral("$9" ), getter: method_get_lastMatch_n<9>, setter: nullptr); |
402 | ctor->defineAccessorProperty(QStringLiteral("lastParen" ), getter: method_get_lastParen, setter: nullptr); |
403 | ctor->defineAccessorProperty(QStringLiteral("$+" ), getter: method_get_lastParen, setter: nullptr); |
404 | ctor->defineAccessorProperty(QStringLiteral("input" ), getter: method_get_input, setter: nullptr); |
405 | ctor->defineAccessorProperty(QStringLiteral("$_" ), getter: method_get_input, setter: nullptr); |
406 | ctor->defineAccessorProperty(QStringLiteral("leftContext" ), getter: method_get_leftContext, setter: nullptr); |
407 | ctor->defineAccessorProperty(QStringLiteral("$`" ), getter: method_get_leftContext, setter: nullptr); |
408 | ctor->defineAccessorProperty(QStringLiteral("rightContext" ), getter: method_get_rightContext, setter: nullptr); |
409 | ctor->defineAccessorProperty(QStringLiteral("$'" ), getter: method_get_rightContext, setter: nullptr); |
410 | |
411 | defineDefaultProperty(QStringLiteral("constructor" ), value: (o = ctor)); |
412 | defineAccessorProperty(name: scope.engine->id_flags(), getter: method_get_flags, setter: nullptr); |
413 | defineAccessorProperty(name: scope.engine->id_global(), getter: method_get_global, setter: nullptr); |
414 | defineAccessorProperty(name: scope.engine->id_ignoreCase(), getter: method_get_ignoreCase, setter: nullptr); |
415 | defineDefaultProperty(QStringLiteral("exec" ), code: method_exec, argumentCount: 1); |
416 | defineDefaultProperty(name: engine->symbol_match(), code: method_match, argumentCount: 1); |
417 | defineAccessorProperty(name: scope.engine->id_multiline(), getter: method_get_multiline, setter: nullptr); |
418 | defineDefaultProperty(name: engine->symbol_replace(), code: method_replace, argumentCount: 2); |
419 | defineDefaultProperty(name: engine->symbol_search(), code: method_search, argumentCount: 1); |
420 | defineAccessorProperty(name: scope.engine->id_source(), getter: method_get_source, setter: nullptr); |
421 | defineDefaultProperty(name: engine->symbol_split(), code: method_split, argumentCount: 2); |
422 | defineAccessorProperty(name: scope.engine->id_sticky(), getter: method_get_sticky, setter: nullptr); |
423 | defineDefaultProperty(QStringLiteral("test" ), code: method_test, argumentCount: 1); |
424 | defineDefaultProperty(name: engine->id_toString(), code: method_toString, argumentCount: 0); |
425 | defineAccessorProperty(name: scope.engine->id_unicode(), getter: method_get_unicode, setter: nullptr); |
426 | |
427 | // another web extension |
428 | defineDefaultProperty(QStringLiteral("compile" ), code: method_compile, argumentCount: 2); |
429 | } |
430 | |
431 | /* used by String.match */ |
432 | ReturnedValue RegExpPrototype::execFirstMatch(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
433 | { |
434 | Scope scope(b); |
435 | Scoped<RegExpObject> r(scope, thisObject->as<RegExpObject>()); |
436 | Q_ASSERT(r && r->global()); |
437 | |
438 | ScopedString str(scope, argc ? argv[0] : Value::undefinedValue()); |
439 | Q_ASSERT(str); |
440 | QString s = str->toQString(); |
441 | |
442 | int offset = r->lastIndex(); |
443 | if (offset < 0 || offset > s.length()) { |
444 | r->setLastIndex(0); |
445 | RETURN_RESULT(Encode::null()); |
446 | } |
447 | |
448 | Q_ALLOCA_VAR(uint, matchOffsets, r->value()->captureCount() * 2 * sizeof(uint)); |
449 | const int result = Scoped<RegExp>(scope, r->value())->match(string: s, start: offset, matchOffsets); |
450 | |
451 | RegExpCtor *regExpCtor = static_cast<RegExpCtor *>(scope.engine->regExpCtor()); |
452 | regExpCtor->d()->clearLastMatch(); |
453 | |
454 | if (result == -1) { |
455 | r->setLastIndex(0); |
456 | RETURN_RESULT(Encode::null()); |
457 | } |
458 | |
459 | ReturnedValue retVal = Encode::undefined(); |
460 | // return first match |
461 | if (r->value()->captureCount()) { |
462 | int start = matchOffsets[0]; |
463 | int end = matchOffsets[1]; |
464 | retVal = (start != -1) ? scope.engine->memoryManager->alloc<ComplexString>(args: str->d(), args: start, args: end - start)->asReturnedValue() : Encode::undefined(); |
465 | } |
466 | |
467 | RegExpCtor::Data *dd = regExpCtor->d(); |
468 | dd->lastInput.set(e: scope.engine, newVal: str->d()); |
469 | dd->lastMatchStart = matchOffsets[0]; |
470 | dd->lastMatchEnd = matchOffsets[1]; |
471 | |
472 | r->setLastIndex(matchOffsets[1]); |
473 | |
474 | return retVal; |
475 | } |
476 | |
477 | ReturnedValue RegExpPrototype::exec(ExecutionEngine *engine, const Object *o, const String *s) |
478 | { |
479 | Scope scope(engine); |
480 | ScopedString key(scope, scope.engine->newString(QStringLiteral("exec" ))); |
481 | ScopedFunctionObject exec(scope, o->get(name: key)); |
482 | if (exec) { |
483 | ScopedValue result(scope, exec->call(thisObject: o, argv: s, argc: 1)); |
484 | if (scope.hasException()) |
485 | RETURN_UNDEFINED(); |
486 | if (!result->isNull() && !result->isObject()) |
487 | return scope.engine->throwTypeError(); |
488 | return result->asReturnedValue(); |
489 | } |
490 | Scoped<RegExpObject> re(scope, o); |
491 | if (!re) |
492 | return scope.engine->throwTypeError(); |
493 | return re->builtinExec(engine, str: s); |
494 | } |
495 | |
496 | ReturnedValue RegExpPrototype::method_exec(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
497 | { |
498 | Scope scope(b); |
499 | Scoped<RegExpObject> r(scope, thisObject->as<RegExpObject>()); |
500 | if (!r) |
501 | return scope.engine->throwTypeError(); |
502 | |
503 | ScopedValue arg(scope, argc ? argv[0]: Value::undefinedValue()); |
504 | ScopedString str(scope, arg->toString(e: scope.engine)); |
505 | if (scope.hasException()) |
506 | RETURN_UNDEFINED(); |
507 | |
508 | return r->builtinExec(engine: scope.engine, str); |
509 | } |
510 | |
511 | ReturnedValue RegExpPrototype::method_get_flags(const FunctionObject *f, const Value *thisObject, const Value *, int) |
512 | { |
513 | Scope scope(f); |
514 | ScopedObject o(scope, thisObject); |
515 | if (!o) |
516 | return scope.engine->throwTypeError(); |
517 | |
518 | QString result; |
519 | ScopedValue v(scope); |
520 | v = o->get(name: scope.engine->id_global()); |
521 | if (scope.hasException()) |
522 | return Encode::undefined(); |
523 | if (v->toBoolean()) |
524 | result += QLatin1Char('g'); |
525 | v = o->get(name: scope.engine->id_ignoreCase()); |
526 | if (scope.hasException()) |
527 | return Encode::undefined(); |
528 | if (v->toBoolean()) |
529 | result += QLatin1Char('i'); |
530 | v = o->get(name: scope.engine->id_multiline()); |
531 | if (scope.hasException()) |
532 | return Encode::undefined(); |
533 | if (v->toBoolean()) |
534 | result += QLatin1Char('m'); |
535 | v = o->get(name: scope.engine->id_unicode()); |
536 | if (scope.hasException()) |
537 | return Encode::undefined(); |
538 | if (v->toBoolean()) |
539 | result += QLatin1Char('u'); |
540 | v = o->get(name: scope.engine->id_sticky()); |
541 | if (scope.hasException()) |
542 | return Encode::undefined(); |
543 | if (v->toBoolean()) |
544 | result += QLatin1Char('y'); |
545 | return scope.engine->newString(s: result)->asReturnedValue(); |
546 | } |
547 | |
548 | ReturnedValue RegExpPrototype::method_get_global(const FunctionObject *f, const Value *thisObject, const Value *, int) |
549 | { |
550 | Scope scope(f); |
551 | Scoped<RegExpObject> re(scope, thisObject); |
552 | if (!re) { |
553 | if (thisObject->sameValue(other: *scope.engine->regExpPrototype())) |
554 | return Encode::undefined(); |
555 | return scope.engine->throwTypeError(); |
556 | } |
557 | |
558 | bool b = re->value()->flags & CompiledData::RegExp::RegExp_Global; |
559 | return Encode(b); |
560 | } |
561 | |
562 | ReturnedValue RegExpPrototype::method_get_ignoreCase(const FunctionObject *f, const Value *thisObject, const Value *, int) |
563 | { |
564 | Scope scope(f); |
565 | Scoped<RegExpObject> re(scope, thisObject); |
566 | if (!re) { |
567 | if (thisObject->sameValue(other: *scope.engine->regExpPrototype())) |
568 | return Encode::undefined(); |
569 | return scope.engine->throwTypeError(); |
570 | } |
571 | |
572 | bool b = re->value()->flags & CompiledData::RegExp::RegExp_IgnoreCase; |
573 | return Encode(b); |
574 | } |
575 | |
576 | static int advanceStringIndex(int index, const QString &str, bool unicode) |
577 | { |
578 | if (unicode) { |
579 | if (index < str.length() - 1 && |
580 | str.at(i: index).isHighSurrogate() && |
581 | str.at(i: index + 1).isLowSurrogate()) |
582 | ++index; |
583 | } |
584 | ++index; |
585 | return index; |
586 | } |
587 | |
588 | static void advanceLastIndexOnEmptyMatch(ExecutionEngine *e, bool unicode, QV4::Object *rx, const String *matchString, const QString &str) |
589 | { |
590 | Scope scope(e); |
591 | if (matchString->d()->length() == 0) { |
592 | QV4::ScopedValue v(scope, rx->get(name: scope.engine->id_lastIndex())); |
593 | int lastIndex = advanceStringIndex(index: v->toLength(), str, unicode); |
594 | if (!rx->put(name: scope.engine->id_lastIndex(), v: QV4::Value::fromInt32(i: lastIndex))) |
595 | scope.engine->throwTypeError(); |
596 | } |
597 | } |
598 | |
599 | ReturnedValue RegExpPrototype::method_match(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) |
600 | { |
601 | Scope scope(f); |
602 | ScopedObject rx(scope, thisObject); |
603 | if (!rx) |
604 | return scope.engine->throwTypeError(); |
605 | ScopedString s(scope, (argc ? argv[0] : Value::undefinedValue()).toString(e: scope.engine)); |
606 | if (scope.hasException()) |
607 | return Encode::undefined(); |
608 | bool global = ScopedValue(scope, rx->get(name: scope.engine->id_global()))->toBoolean(); |
609 | |
610 | if (!global) |
611 | return exec(engine: scope.engine, o: rx, s); |
612 | |
613 | bool unicode = ScopedValue(scope, rx->get(name: scope.engine->id_unicode()))->toBoolean(); |
614 | |
615 | rx->put(name: scope.engine->id_lastIndex(), v: Value::fromInt32(i: 0)); |
616 | ScopedArrayObject a(scope, scope.engine->newArrayObject()); |
617 | uint n = 0; |
618 | |
619 | ScopedValue result(scope); |
620 | ScopedValue match(scope); |
621 | ScopedString matchString(scope); |
622 | ScopedValue v(scope); |
623 | while (1) { |
624 | result = exec(engine: scope.engine, o: rx, s); |
625 | if (scope.hasException()) |
626 | return Encode::undefined(); |
627 | if (result->isNull()) { |
628 | if (!n) |
629 | return Encode::null(); |
630 | return a->asReturnedValue(); |
631 | } |
632 | Q_ASSERT(result->isObject()); |
633 | match = static_cast<Object &>(*result).get(id: PropertyKey::fromArrayIndex(idx: 0)); |
634 | matchString = match->toString(e: scope.engine); |
635 | if (scope.hasException()) |
636 | return Encode::undefined(); |
637 | a->push_back(v: matchString); |
638 | advanceLastIndexOnEmptyMatch(e: scope.engine, unicode, rx, matchString, str: s->toQString()); |
639 | ++n; |
640 | } |
641 | } |
642 | |
643 | ReturnedValue RegExpPrototype::method_get_multiline(const FunctionObject *f, const Value *thisObject, const Value *, int) |
644 | { |
645 | Scope scope(f); |
646 | Scoped<RegExpObject> re(scope, thisObject); |
647 | if (!re) { |
648 | if (thisObject->sameValue(other: *scope.engine->regExpPrototype())) |
649 | return Encode::undefined(); |
650 | return scope.engine->throwTypeError(); |
651 | } |
652 | |
653 | bool b = re->value()->flags & CompiledData::RegExp::RegExp_Multiline; |
654 | return Encode(b); |
655 | } |
656 | |
657 | ReturnedValue RegExpPrototype::method_replace(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) |
658 | { |
659 | Scope scope(f); |
660 | ScopedObject rx(scope, thisObject); |
661 | if (!rx) |
662 | return scope.engine->throwTypeError(); |
663 | |
664 | ScopedString s(scope, (argc ? argv[0] : Value::undefinedValue()).toString(e: scope.engine)); |
665 | if (scope.hasException()) |
666 | return Encode::undefined(); |
667 | |
668 | int lengthS = s->toQString().length(); |
669 | |
670 | ScopedString replaceValue(scope); |
671 | ScopedFunctionObject replaceFunction(scope, (argc > 1 ? argv[1] : Value::undefinedValue())); |
672 | bool functionalReplace = !!replaceFunction; |
673 | if (!functionalReplace) |
674 | replaceValue = (argc > 1 ? argv[1] : Value::undefinedValue()).toString(e: scope.engine); |
675 | |
676 | ScopedValue v(scope); |
677 | bool global = (v = rx->get(name: scope.engine->id_global()))->toBoolean(); |
678 | bool unicode = false; |
679 | if (global) { |
680 | unicode = (v = rx->get(name: scope.engine->id_unicode()))->toBoolean(); |
681 | if (!rx->put(name: scope.engine->id_lastIndex(), v: Value::fromInt32(i: 0))) |
682 | return scope.engine->throwTypeError(); |
683 | } |
684 | |
685 | ScopedArrayObject results(scope, scope.engine->newArrayObject()); |
686 | ScopedValue result(scope); |
687 | ScopedValue match(scope); |
688 | ScopedString matchString(scope); |
689 | while (1) { |
690 | result = exec(engine: scope.engine, o: rx, s); |
691 | if (scope.hasException()) |
692 | return Encode::undefined(); |
693 | if (result->isNull()) |
694 | break; |
695 | results->push_back(v: result); |
696 | if (!global) |
697 | break; |
698 | match = static_cast<Object &>(*result).get(id: PropertyKey::fromArrayIndex(idx: 0)); |
699 | matchString = match->toString(e: scope.engine); |
700 | if (scope.hasException()) |
701 | return Encode::undefined(); |
702 | advanceLastIndexOnEmptyMatch(e: scope.engine, unicode, rx, matchString, str: s->toQString()); |
703 | } |
704 | QString accumulatedResult; |
705 | int nextSourcePosition = 0; |
706 | int resultsLength = results->getLength(); |
707 | ScopedObject resultObject(scope); |
708 | for (int i = 0; i < resultsLength; ++i) { |
709 | resultObject = results->get(id: PropertyKey::fromArrayIndex(idx: i)); |
710 | if (scope.hasException()) |
711 | return Encode::undefined(); |
712 | |
713 | int nCaptures = resultObject->getLength(); |
714 | nCaptures = qMax(a: nCaptures - 1, b: 0); |
715 | match = resultObject->get(id: PropertyKey::fromArrayIndex(idx: 0)); |
716 | matchString = match->toString(e: scope.engine); |
717 | if (scope.hasException()) |
718 | return Encode::undefined(); |
719 | QString m = matchString->toQString(); |
720 | int matchLength = m.length(); |
721 | v = resultObject->get(name: scope.engine->id_index()); |
722 | int position = v->toInt32(); |
723 | position = qMax(a: qMin(a: position, b: lengthS), b: 0); |
724 | if (scope.hasException()) |
725 | return Encode::undefined(); |
726 | |
727 | int n = 1; |
728 | Scope innerScope(scope.engine); |
729 | JSCallData cData(scope, nCaptures + 3); |
730 | while (n <= nCaptures) { |
731 | v = resultObject->get(id: PropertyKey::fromArrayIndex(idx: n)); |
732 | if (!v->isUndefined()) |
733 | cData->args[n] = v->toString(e: scope.engine); |
734 | ++n; |
735 | } |
736 | QString replacement; |
737 | if (functionalReplace) { |
738 | cData->args[0] = matchString; |
739 | cData->args[nCaptures + 1] = Encode(position); |
740 | cData->args[nCaptures + 2] = s; |
741 | ScopedValue replValue(scope, replaceFunction->call(data: cData)); |
742 | if (scope.hasException()) |
743 | return Encode::undefined(); |
744 | replacement = replValue->toQString(); |
745 | } else { |
746 | replacement = RegExp::getSubstitution(matched: matchString->toQString(), str: s->toQString(), position, captures: cData.args, nCaptures, replacement: replaceValue->toQString()); |
747 | } |
748 | if (scope.hasException()) |
749 | return Encode::undefined(); |
750 | if (position >= nextSourcePosition) { |
751 | accumulatedResult += s->toQString().midRef(position: nextSourcePosition, n: position - nextSourcePosition) + replacement; |
752 | nextSourcePosition = position + matchLength; |
753 | } |
754 | } |
755 | if (nextSourcePosition < lengthS) { |
756 | accumulatedResult += s->toQString().midRef(position: nextSourcePosition); |
757 | } |
758 | return scope.engine->newString(s: accumulatedResult)->asReturnedValue(); |
759 | } |
760 | |
761 | ReturnedValue RegExpPrototype::method_search(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) |
762 | { |
763 | Scope scope(f); |
764 | ScopedObject rx(scope, thisObject); |
765 | if (!rx) |
766 | return scope.engine->throwTypeError(); |
767 | |
768 | ScopedString s(scope, (argc ? argv[0] : Value::undefinedValue()).toString(e: scope.engine)); |
769 | if (scope.hasException()) |
770 | return Encode::undefined(); |
771 | |
772 | ScopedValue previousLastIndex(scope, rx->get(name: scope.engine->id_lastIndex())); |
773 | if (previousLastIndex->toNumber() != 0) { |
774 | if (!rx->put(name: scope.engine->id_lastIndex(), v: Value::fromInt32(i: 0))) |
775 | return scope.engine->throwTypeError(); |
776 | } |
777 | |
778 | ScopedValue result(scope, exec(engine: scope.engine, o: rx, s)); |
779 | if (scope.hasException()) |
780 | return Encode::undefined(); |
781 | |
782 | ScopedValue currentLastIndex(scope, rx->get(name: scope.engine->id_lastIndex())); |
783 | if (!currentLastIndex->sameValue(other: previousLastIndex)) { |
784 | if (!rx->put(name: scope.engine->id_lastIndex(), v: previousLastIndex)) |
785 | return scope.engine->throwTypeError(); |
786 | } |
787 | |
788 | if (result->isNull()) |
789 | return Encode(-1); |
790 | ScopedObject o(scope, result); |
791 | Q_ASSERT(o); |
792 | return o->get(name: scope.engine->id_index()); |
793 | } |
794 | |
795 | |
796 | ReturnedValue RegExpPrototype::method_get_source(const FunctionObject *f, const Value *thisObject, const Value *, int) |
797 | { |
798 | Scope scope(f); |
799 | Scoped<RegExpObject> re(scope, thisObject); |
800 | if (!re) { |
801 | if (thisObject->sameValue(other: *scope.engine->regExpPrototype())) |
802 | return scope.engine->newString(QStringLiteral("(?:)" ))->asReturnedValue(); |
803 | return scope.engine->throwTypeError(); |
804 | } |
805 | |
806 | return scope.engine->newString(s: re->toString())->asReturnedValue(); |
807 | } |
808 | |
809 | ReturnedValue RegExpPrototype::method_split(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) |
810 | { |
811 | Scope scope(f); |
812 | ScopedObject rx(scope, thisObject); |
813 | if (!rx) |
814 | return scope.engine->throwTypeError(); |
815 | |
816 | ScopedString s(scope, (argc ? argv[0] : Value::undefinedValue()).toString(e: scope.engine)); |
817 | if (scope.hasException()) |
818 | return Encode::undefined(); |
819 | |
820 | ScopedValue flagsValue(scope, rx->get(name: scope.engine->id_flags())); |
821 | ScopedString flags(scope, flagsValue->toString(e: scope.engine)); |
822 | if (scope.hasException()) |
823 | return Encode::undefined(); |
824 | QString flagsString = flags->toQString(); |
825 | if (!flagsString.contains(c: QLatin1Char('y'))) |
826 | flags = scope.engine->newString(s: flagsString + QLatin1Char('y')); |
827 | bool unicodeMatching = flagsString.contains(c: QLatin1Char('u')); |
828 | |
829 | const FunctionObject *C = rx->speciesConstructor(scope, defaultConstructor: scope.engine->regExpCtor()); |
830 | if (!C) |
831 | return Encode::undefined(); |
832 | |
833 | Value *args = scope.alloc(nValues: 2); |
834 | args[0] = rx; |
835 | args[1] = flags; |
836 | ScopedObject splitter(scope, C->callAsConstructor(argv: args, argc: 2, newTarget: f)); |
837 | if (scope.hasException()) |
838 | return Encode::undefined(); |
839 | |
840 | ScopedArrayObject A(scope, scope.engine->newArrayObject()); |
841 | uint lengthA = 0; |
842 | uint limit = argc < 2 ? UINT_MAX : argv[1].toUInt32(); |
843 | if (limit == 0) |
844 | return A->asReturnedValue(); |
845 | |
846 | QString S = s->toQString(); |
847 | int size = S.length(); |
848 | if (size == 0) { |
849 | ScopedValue z(scope, exec(engine: scope.engine, o: splitter, s)); |
850 | if (z->isNull()) |
851 | A->push_back(v: s); |
852 | return A->asReturnedValue(); |
853 | } |
854 | |
855 | int p = 0; |
856 | int q = 0; |
857 | ScopedValue v(scope); |
858 | ScopedValue z(scope); |
859 | ScopedObject zz(scope); |
860 | ScopedString t(scope); |
861 | while (q < size) { |
862 | Value qq = Value::fromInt32(i: q); |
863 | if (!splitter->put(name: scope.engine->id_lastIndex(), v: qq)) |
864 | return scope.engine->throwTypeError(); |
865 | z = exec(engine: scope.engine, o: splitter, s); |
866 | if (scope.hasException()) |
867 | return Encode::undefined(); |
868 | |
869 | if (z->isNull()) { |
870 | q = advanceStringIndex(index: q, str: S, unicode: unicodeMatching); |
871 | continue; |
872 | } |
873 | |
874 | v = splitter->get(name: scope.engine->id_lastIndex()); |
875 | int e = qMin(a: v->toInt32(), b: size); |
876 | if (e == p) { |
877 | q = advanceStringIndex(index: q, str: S, unicode: unicodeMatching); |
878 | continue; |
879 | } |
880 | QString T = S.mid(position: p, n: q - p); |
881 | t = scope.engine->newString(s: T); |
882 | A->push_back(v: t); |
883 | ++lengthA; |
884 | if (lengthA == limit) |
885 | return A->asReturnedValue(); |
886 | p = e; |
887 | zz = *z; |
888 | uint numberOfCaptures = qMax(a: zz->getLength() - 1, b: 0ll); |
889 | for (uint i = 1; i <= numberOfCaptures; ++i) { |
890 | v = zz->get(id: PropertyKey::fromArrayIndex(idx: i)); |
891 | A->push_back(v); |
892 | ++lengthA; |
893 | if (lengthA == limit) |
894 | return A->asReturnedValue(); |
895 | } |
896 | q = p; |
897 | } |
898 | |
899 | QString T = S.mid(position: p); |
900 | t = scope.engine->newString(s: T); |
901 | A->push_back(v: t); |
902 | return A->asReturnedValue(); |
903 | } |
904 | |
905 | ReturnedValue RegExpPrototype::method_get_sticky(const FunctionObject *f, const Value *thisObject, const Value *, int) |
906 | { |
907 | Scope scope(f); |
908 | Scoped<RegExpObject> re(scope, thisObject); |
909 | if (!re) { |
910 | if (thisObject->sameValue(other: *scope.engine->regExpPrototype())) |
911 | return Encode::undefined(); |
912 | return scope.engine->throwTypeError(); |
913 | } |
914 | |
915 | bool b = re->value()->flags & CompiledData::RegExp::RegExp_Sticky; |
916 | return Encode(b); |
917 | } |
918 | |
919 | ReturnedValue RegExpPrototype::method_test(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
920 | { |
921 | Value res = Value::fromReturnedValue(val: method_exec(b, thisObject, argv, argc)); |
922 | return Encode(!res.isNull()); |
923 | } |
924 | |
925 | ReturnedValue RegExpPrototype::method_toString(const FunctionObject *b, const Value *thisObject, const Value *, int) |
926 | { |
927 | Scope scope(b); |
928 | const Object *r = thisObject->as<Object>(); |
929 | if (!r) |
930 | return scope.engine->throwTypeError(); |
931 | |
932 | ScopedValue v(scope); |
933 | v = r->get(name: scope.engine->id_source()); |
934 | ScopedString source(scope, v->toString(e: scope.engine)); |
935 | if (scope.hasException()) |
936 | return Encode::undefined(); |
937 | v = r->get(name: scope.engine->id_flags()); |
938 | ScopedString flags(scope, v->toString(e: scope.engine)); |
939 | if (scope.hasException()) |
940 | return Encode::undefined(); |
941 | |
942 | QString result = QLatin1Char('/') + source->toQString() + QLatin1Char('/') + flags->toQString(); |
943 | return Encode(scope.engine->newString(s: result)); |
944 | } |
945 | |
946 | ReturnedValue RegExpPrototype::method_get_unicode(const FunctionObject *f, const Value *thisObject, const Value *, int) |
947 | { |
948 | Scope scope(f); |
949 | Scoped<RegExpObject> re(scope, thisObject); |
950 | if (!re) { |
951 | if (thisObject->sameValue(other: *scope.engine->regExpPrototype())) |
952 | return Encode::undefined(); |
953 | return scope.engine->throwTypeError(); |
954 | } |
955 | |
956 | bool b = re->value()->flags & CompiledData::RegExp::RegExp_Unicode; |
957 | return Encode(b); |
958 | } |
959 | |
960 | ReturnedValue RegExpPrototype::method_compile(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
961 | { |
962 | Scope scope(b); |
963 | Scoped<RegExpObject> r(scope, thisObject->as<RegExpObject>()); |
964 | if (!r) |
965 | return scope.engine->throwTypeError(); |
966 | |
967 | Scoped<RegExpObject> re(scope, scope.engine->regExpCtor()->callAsConstructor(argv, argc)); |
968 | if (re) // Otherwise the regexp constructor should have thrown an exception |
969 | r->d()->value.set(e: scope.engine, newVal: re->value()); |
970 | return Encode::undefined(); |
971 | } |
972 | |
973 | template <uint index> |
974 | ReturnedValue RegExpPrototype::method_get_lastMatch_n(const FunctionObject *b, const Value *, const Value *, int) |
975 | { |
976 | Scope scope(b); |
977 | ScopedArrayObject lastMatch(scope, static_cast<RegExpCtor*>(scope.engine->regExpCtor())->lastMatch()); |
978 | ScopedValue res(scope, lastMatch ? lastMatch->get(idx: index) : Encode::undefined()); |
979 | if (res->isUndefined()) |
980 | res = scope.engine->newString(); |
981 | return res->asReturnedValue(); |
982 | } |
983 | |
984 | ReturnedValue RegExpPrototype::method_get_lastParen(const FunctionObject *b, const Value *, const Value *, int) |
985 | { |
986 | Scope scope(b); |
987 | ScopedArrayObject lastMatch(scope, static_cast<RegExpCtor*>(scope.engine->regExpCtor())->lastMatch()); |
988 | ScopedValue res(scope, lastMatch ? lastMatch->get(idx: lastMatch->getLength() - 1) : Encode::undefined()); |
989 | if (res->isUndefined()) |
990 | res = scope.engine->newString(); |
991 | return res->asReturnedValue(); |
992 | } |
993 | |
994 | ReturnedValue RegExpPrototype::method_get_input(const FunctionObject *b, const Value *, const Value *, int) |
995 | { |
996 | return static_cast<RegExpCtor*>(b->engine()->regExpCtor())->lastInput()->asReturnedValue(); |
997 | } |
998 | |
999 | ReturnedValue RegExpPrototype::method_get_leftContext(const FunctionObject *b, const Value *, const Value *, int) |
1000 | { |
1001 | Scope scope(b); |
1002 | Scoped<RegExpCtor> regExpCtor(scope, scope.engine->regExpCtor()); |
1003 | QString lastInput = regExpCtor->lastInput()->toQString(); |
1004 | return Encode(scope.engine->newString(s: lastInput.left(n: regExpCtor->lastMatchStart()))); |
1005 | } |
1006 | |
1007 | ReturnedValue RegExpPrototype::method_get_rightContext(const FunctionObject *b, const Value *, const Value *, int) |
1008 | { |
1009 | Scope scope(b); |
1010 | Scoped<RegExpCtor> regExpCtor(scope, scope.engine->regExpCtor()); |
1011 | QString lastInput = regExpCtor->lastInput()->toQString(); |
1012 | return Encode(scope.engine->newString(s: lastInput.mid(position: regExpCtor->lastMatchEnd()))); |
1013 | } |
1014 | |
1015 | QT_END_NAMESPACE |
1016 | |