1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | |
5 | #include "qv4stringobject_p.h" |
6 | #include "qv4regexp_p.h" |
7 | #include "qv4regexpobject_p.h" |
8 | #include <private/qv4mm_p.h> |
9 | #include "qv4scopedvalue_p.h" |
10 | #include "qv4symbol_p.h" |
11 | #include <private/qv4alloca_p.h> |
12 | #include "qv4jscall_p.h" |
13 | #include "qv4stringiterator_p.h" |
14 | #include <QtCore/QDateTime> |
15 | #include <QtCore/QDebug> |
16 | #include <QtCore/QStringList> |
17 | #include <QtQml/private/qv4runtime_p.h> |
18 | |
19 | #include <cassert> |
20 | |
21 | #ifndef Q_OS_WIN |
22 | # include <time.h> |
23 | # ifndef Q_OS_VXWORKS |
24 | # include <sys/time.h> |
25 | # else |
26 | # include "qplatformdefs.h" |
27 | # endif |
28 | #else |
29 | # include <qt_windows.h> |
30 | #endif |
31 | |
32 | using namespace QV4; |
33 | |
34 | DEFINE_OBJECT_VTABLE(StringObject); |
35 | |
36 | void Heap::StringObject::init() |
37 | { |
38 | Object::init(); |
39 | Q_ASSERT(vtable() == QV4::StringObject::staticVTable()); |
40 | string.set(e: internalClass->engine, newVal: internalClass->engine->id_empty()->d()); |
41 | setProperty(e: internalClass->engine, index: LengthPropertyIndex, v: Value::fromInt32(i: 0)); |
42 | } |
43 | |
44 | void Heap::StringObject::init(const QV4::String *str) |
45 | { |
46 | Object::init(); |
47 | string.set(e: internalClass->engine, newVal: str->d()); |
48 | setProperty(e: internalClass->engine, index: LengthPropertyIndex, v: Value::fromInt32(i: length())); |
49 | } |
50 | |
51 | Heap::String *Heap::StringObject::getIndex(uint index) const |
52 | { |
53 | QString str = string->toQString(); |
54 | if (index >= (uint)str.size()) |
55 | return nullptr; |
56 | return internalClass->engine->newString(s: str.mid(position: index, n: 1)); |
57 | } |
58 | |
59 | uint Heap::StringObject::length() const |
60 | { |
61 | return string->length(); |
62 | } |
63 | |
64 | bool StringObject::virtualDeleteProperty(Managed *m, PropertyKey id) |
65 | { |
66 | Q_ASSERT(m->as<StringObject>()); |
67 | if (id.isArrayIndex()) { |
68 | StringObject *o = static_cast<StringObject *>(m); |
69 | uint index = id.asArrayIndex(); |
70 | if (index < static_cast<uint>(o->d()->string->toQString().size())) |
71 | return false; |
72 | } |
73 | return Object::virtualDeleteProperty(m, id); |
74 | } |
75 | |
76 | struct StringObjectOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator |
77 | { |
78 | ~StringObjectOwnPropertyKeyIterator() override = default; |
79 | PropertyKey next(const QV4::Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override; |
80 | |
81 | }; |
82 | |
83 | PropertyKey StringObjectOwnPropertyKeyIterator::next(const QV4::Object *o, Property *pd, PropertyAttributes *attrs) |
84 | { |
85 | const StringObject *s = static_cast<const StringObject *>(o); |
86 | uint slen = s->d()->string->toQString().size(); |
87 | if (arrayIndex < slen) { |
88 | uint index = arrayIndex; |
89 | ++arrayIndex; |
90 | if (attrs) |
91 | *attrs = Attr_NotConfigurable|Attr_NotWritable; |
92 | if (pd) |
93 | pd->value = s->getIndex(index); |
94 | return PropertyKey::fromArrayIndex(idx: index); |
95 | } else if (arrayIndex == slen) { |
96 | if (s->arrayData()) { |
97 | SparseArrayNode *arrayNode = s->sparseBegin(); |
98 | // iterate until we're past the end of the string |
99 | while (arrayNode && arrayNode->key() < slen) |
100 | arrayNode = arrayNode->nextNode(); |
101 | } |
102 | } |
103 | |
104 | return ObjectOwnPropertyKeyIterator::next(o, pd, attrs); |
105 | } |
106 | |
107 | OwnPropertyKeyIterator *StringObject::virtualOwnPropertyKeys(const Object *m, Value *target) |
108 | { |
109 | *target = *m; |
110 | return new StringObjectOwnPropertyKeyIterator; |
111 | } |
112 | |
113 | PropertyAttributes StringObject::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p) |
114 | { |
115 | PropertyAttributes attributes = Object::virtualGetOwnProperty(m, id, p); |
116 | if (attributes != Attr_Invalid) |
117 | return attributes; |
118 | |
119 | if (id.isArrayIndex()) { |
120 | const uint index = id.asArrayIndex(); |
121 | const auto s = static_cast<const StringObject *>(m); |
122 | if (index < uint(s->d()->string->toQString().size())) { |
123 | if (p) |
124 | p->value = s->getIndex(index); |
125 | return Attr_NotConfigurable|Attr_NotWritable; |
126 | } |
127 | } |
128 | return Object::virtualGetOwnProperty(m, id, p); |
129 | } |
130 | |
131 | DEFINE_OBJECT_VTABLE(StringCtor); |
132 | |
133 | void Heap::StringCtor::init(QV4::ExecutionContext *scope) |
134 | { |
135 | Heap::FunctionObject::init(scope, QStringLiteral("String" )); |
136 | } |
137 | |
138 | ReturnedValue StringCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget) |
139 | { |
140 | ExecutionEngine *v4 = static_cast<const Object *>(f)->engine(); |
141 | Scope scope(v4); |
142 | ScopedString value(scope); |
143 | if (argc) |
144 | value = argv[0].toString(e: v4); |
145 | else |
146 | value = v4->newString(); |
147 | CHECK_EXCEPTION(); |
148 | ReturnedValue o = Encode(v4->newStringObject(string: value)); |
149 | |
150 | if (!newTarget) |
151 | return o; |
152 | ScopedObject obj(scope, o); |
153 | obj->setProtoFromNewTarget(newTarget); |
154 | return obj->asReturnedValue(); |
155 | } |
156 | |
157 | ReturnedValue StringCtor::virtualCall(const FunctionObject *m, const Value *, const Value *argv, int argc) |
158 | { |
159 | ExecutionEngine *v4 = m->engine(); |
160 | if (!argc) |
161 | return v4->newString()->asReturnedValue(); |
162 | if (argv[0].isSymbol()) |
163 | return v4->newString(s: argv[0].symbolValue()->descriptiveString())->asReturnedValue(); |
164 | return argv[0].toString(e: v4)->asReturnedValue(); |
165 | } |
166 | |
167 | ReturnedValue StringCtor::method_fromCharCode(const FunctionObject *b, const Value *, const Value *argv, int argc) |
168 | { |
169 | QString str(argc, Qt::Uninitialized); |
170 | QChar *ch = str.data(); |
171 | for (int i = 0, ei = argc; i < ei; ++i) { |
172 | *ch = QChar(argv[i].toUInt16()); |
173 | ++ch; |
174 | } |
175 | return Encode(b->engine()->newString(s: str)); |
176 | } |
177 | |
178 | |
179 | |
180 | ReturnedValue StringCtor::method_fromCodePoint(const FunctionObject *f, const Value *, const Value *argv, int argc) |
181 | { |
182 | ExecutionEngine *e = f->engine(); |
183 | QString result(argc*2, Qt::Uninitialized); // assume worst case |
184 | QChar *ch = result.data(); |
185 | for (int i = 0; i < argc; ++i) { |
186 | double num = argv[i].toNumber(); |
187 | if (e->hasException) |
188 | return Encode::undefined(); |
189 | int cp = static_cast<int>(num); |
190 | if (cp != num || cp < 0 || cp > 0x10ffff) |
191 | return e->throwRangeError(QStringLiteral("String.fromCodePoint: argument out of range." )); |
192 | if (cp > 0xffff) { |
193 | *ch = QChar::highSurrogate(ucs4: cp); |
194 | ++ch; |
195 | *ch = QChar::lowSurrogate(ucs4: cp); |
196 | } else { |
197 | *ch = QChar(cp); |
198 | } |
199 | ++ch; |
200 | } |
201 | result.truncate(pos: ch - result.constData()); |
202 | return e->newString(s: result)->asReturnedValue(); |
203 | } |
204 | |
205 | ReturnedValue StringCtor::method_raw(const FunctionObject *f, const Value *, const Value *argv, int argc) |
206 | { |
207 | Scope scope(f); |
208 | if (!argc) |
209 | return scope.engine->throwTypeError(); |
210 | |
211 | ScopedObject cooked(scope, argv[0].toObject(e: scope.engine)); |
212 | if (!cooked) |
213 | return scope.engine->throwTypeError(); |
214 | ScopedString rawString(scope, scope.engine->newIdentifier(QStringLiteral("raw" ))); |
215 | ScopedValue rawValue(scope, cooked->get(name: rawString)); |
216 | ScopedObject raw(scope, rawValue->toObject(e: scope.engine)); |
217 | if (scope.hasException()) |
218 | return Encode::undefined(); |
219 | |
220 | ++argv; |
221 | --argc; |
222 | |
223 | QString result; |
224 | uint literalSegments = raw->getLength(); |
225 | if (!literalSegments) |
226 | return scope.engine->id_empty()->asReturnedValue(); |
227 | |
228 | uint nextIndex = 0; |
229 | ScopedValue val(scope); |
230 | while (1) { |
231 | val = raw->get(idx: nextIndex); |
232 | result += val->toQString(); |
233 | if (scope.hasException()) |
234 | return Encode::undefined(); |
235 | if (nextIndex + 1 == literalSegments) |
236 | return scope.engine->newString(s: result)->asReturnedValue(); |
237 | |
238 | if (nextIndex < static_cast<uint>(argc)) |
239 | result += argv[nextIndex].toQString(); |
240 | if (scope.hasException()) |
241 | return Encode::undefined(); |
242 | ++nextIndex; |
243 | } |
244 | } |
245 | |
246 | void StringPrototype::init(ExecutionEngine *engine, Object *ctor) |
247 | { |
248 | Scope scope(engine); |
249 | ScopedObject o(scope); |
250 | |
251 | // need to set this once again, as these were not fully defined when creating the string proto |
252 | Heap::InternalClass *ic = scope.engine->classes[ExecutionEngine::Class_StringObject]->changePrototype(proto: scope.engine->objectPrototype()->d()); |
253 | d()->internalClass.set(e: scope.engine, newVal: ic); |
254 | d()->string.set(e: scope.engine, newVal: scope.engine->id_empty()->d()); |
255 | setProperty(engine: scope.engine, index: Heap::StringObject::LengthPropertyIndex, v: Value::fromInt32(i: 0)); |
256 | |
257 | ctor->defineReadonlyProperty(name: engine->id_prototype(), value: (o = this)); |
258 | ctor->defineReadonlyConfigurableProperty(name: engine->id_length(), value: Value::fromInt32(i: 1)); |
259 | ctor->defineDefaultProperty(QStringLiteral("fromCharCode" ), code: StringCtor::method_fromCharCode, argumentCount: 1); |
260 | ctor->defineDefaultProperty(QStringLiteral("fromCodePoint" ), code: StringCtor::method_fromCodePoint, argumentCount: 1); |
261 | ctor->defineDefaultProperty(QStringLiteral("raw" ), code: StringCtor::method_raw, argumentCount: 1); |
262 | |
263 | defineDefaultProperty(QStringLiteral("constructor" ), value: (o = ctor)); |
264 | defineDefaultProperty(name: engine->id_toString(), code: method_toString); |
265 | defineDefaultProperty(name: engine->id_valueOf(), code: method_toString); // valueOf and toString are identical |
266 | defineDefaultProperty(QStringLiteral("charAt" ), code: method_charAt, argumentCount: 1); |
267 | defineDefaultProperty(QStringLiteral("charCodeAt" ), code: method_charCodeAt, argumentCount: 1); |
268 | defineDefaultProperty(QStringLiteral("codePointAt" ), code: method_codePointAt, argumentCount: 1); |
269 | defineDefaultProperty(QStringLiteral("concat" ), code: method_concat, argumentCount: 1); |
270 | defineDefaultProperty(QStringLiteral("endsWith" ), code: method_endsWith, argumentCount: 1); |
271 | defineDefaultProperty(QStringLiteral("indexOf" ), code: method_indexOf, argumentCount: 1); |
272 | defineDefaultProperty(QStringLiteral("includes" ), code: method_includes, argumentCount: 1); |
273 | defineDefaultProperty(QStringLiteral("lastIndexOf" ), code: method_lastIndexOf, argumentCount: 1); |
274 | defineDefaultProperty(QStringLiteral("localeCompare" ), code: method_localeCompare, argumentCount: 1); |
275 | defineDefaultProperty(QStringLiteral("match" ), code: method_match, argumentCount: 1); |
276 | defineDefaultProperty(QStringLiteral("normalize" ), code: method_normalize, argumentCount: 0); |
277 | defineDefaultProperty(QStringLiteral("padEnd" ), code: method_padEnd, argumentCount: 1); |
278 | defineDefaultProperty(QStringLiteral("padStart" ), code: method_padStart, argumentCount: 1); |
279 | defineDefaultProperty(QStringLiteral("repeat" ), code: method_repeat, argumentCount: 1); |
280 | defineDefaultProperty(QStringLiteral("replace" ), code: method_replace, argumentCount: 2); |
281 | defineDefaultProperty(QStringLiteral("search" ), code: method_search, argumentCount: 1); |
282 | defineDefaultProperty(QStringLiteral("slice" ), code: method_slice, argumentCount: 2); |
283 | defineDefaultProperty(QStringLiteral("split" ), code: method_split, argumentCount: 2); |
284 | defineDefaultProperty(QStringLiteral("startsWith" ), code: method_startsWith, argumentCount: 1); |
285 | defineDefaultProperty(QStringLiteral("substr" ), code: method_substr, argumentCount: 2); |
286 | defineDefaultProperty(QStringLiteral("substring" ), code: method_substring, argumentCount: 2); |
287 | defineDefaultProperty(QStringLiteral("toLowerCase" ), code: method_toLowerCase); |
288 | defineDefaultProperty(QStringLiteral("toLocaleLowerCase" ), code: method_toLocaleLowerCase); |
289 | defineDefaultProperty(QStringLiteral("toUpperCase" ), code: method_toUpperCase); |
290 | defineDefaultProperty(QStringLiteral("toLocaleUpperCase" ), code: method_toLocaleUpperCase); |
291 | defineDefaultProperty(QStringLiteral("trim" ), code: method_trim); |
292 | defineDefaultProperty(name: engine->symbol_iterator(), code: method_iterator); |
293 | } |
294 | |
295 | static Heap::String *thisAsString(ExecutionEngine *v4, const QV4::Value *thisObject) |
296 | { |
297 | if (String *s = thisObject->stringValue()) |
298 | return s->d(); |
299 | if (const StringObject *thisString = thisObject->as<StringObject>()) |
300 | return thisString->d()->string; |
301 | return thisObject->toString(e: v4); |
302 | } |
303 | |
304 | static QString getThisString(ExecutionEngine *v4, const QV4::Value *thisObject) |
305 | { |
306 | if (String *s = thisObject->stringValue()) |
307 | return s->toQString(); |
308 | if (const StringObject *thisString = thisObject->as<StringObject>()) |
309 | return thisString->d()->string->toQString(); |
310 | if (thisObject->isUndefined() || thisObject->isNull()) { |
311 | v4->throwTypeError(); |
312 | return QString(); |
313 | } |
314 | return thisObject->toQString(); |
315 | } |
316 | |
317 | ReturnedValue StringPrototype::method_toString(const FunctionObject *b, const Value *thisObject, const Value *, int) |
318 | { |
319 | if (thisObject->isString()) |
320 | return thisObject->asReturnedValue(); |
321 | |
322 | ExecutionEngine *v4 = b->engine(); |
323 | const StringObject *o = thisObject->as<StringObject>(); |
324 | if (!o) |
325 | return v4->throwTypeError(); |
326 | return o->d()->string->asReturnedValue(); |
327 | } |
328 | |
329 | ReturnedValue StringPrototype::method_charAt(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
330 | { |
331 | ExecutionEngine *v4 = b->engine(); |
332 | const QString str = getThisString(v4, thisObject); |
333 | if (v4->hasException) |
334 | return QV4::Encode::undefined(); |
335 | |
336 | double pos = 0; |
337 | if (argc > 0) |
338 | pos = argv[0].toInteger(); |
339 | |
340 | QString result; |
341 | if (pos >= 0 && pos < str.size()) |
342 | result += str.at(i: pos); |
343 | |
344 | return Encode(v4->newString(s: result)); |
345 | } |
346 | |
347 | ReturnedValue StringPrototype::method_charCodeAt(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
348 | { |
349 | ExecutionEngine *v4 = b->engine(); |
350 | const QString str = getThisString(v4, thisObject); |
351 | if (v4->hasException) |
352 | return QV4::Encode::undefined(); |
353 | |
354 | double pos = 0; |
355 | if (argc > 0) |
356 | pos = argv[0].toInteger(); |
357 | |
358 | |
359 | if (pos >= 0 && pos < str.size()) |
360 | RETURN_RESULT(Encode(str.at(pos).unicode())); |
361 | |
362 | return Encode(qt_qnan()); |
363 | } |
364 | |
365 | ReturnedValue StringPrototype::method_codePointAt(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) |
366 | { |
367 | ExecutionEngine *v4 = f->engine(); |
368 | QString value = getThisString(v4, thisObject); |
369 | if (v4->hasException) |
370 | return QV4::Encode::undefined(); |
371 | |
372 | double index = argc ? argv[0].toInteger() : 0.0; |
373 | if (v4->hasException) |
374 | return QV4::Encode::undefined(); |
375 | |
376 | if (index < 0 || index >= value.size()) |
377 | return Encode::undefined(); |
378 | |
379 | uint first = value.at(i: index).unicode(); |
380 | if (QChar::isHighSurrogate(ucs4: first) && index + 1 < value.size()) { |
381 | uint second = value.at(i: index + 1).unicode(); |
382 | if (QChar::isLowSurrogate(ucs4: second)) |
383 | return Encode(QChar::surrogateToUcs4(high: first, low: second)); |
384 | } |
385 | return Encode(first); |
386 | } |
387 | |
388 | ReturnedValue StringPrototype::method_concat(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
389 | { |
390 | ExecutionEngine *v4 = b->engine(); |
391 | QString value = getThisString(v4, thisObject); |
392 | if (v4->hasException) |
393 | return QV4::Encode::undefined(); |
394 | |
395 | Scope scope(v4); |
396 | ScopedString s(scope); |
397 | for (int i = 0; i < argc; ++i) { |
398 | s = argv[i].toString(e: scope.engine); |
399 | if (v4->hasException) |
400 | return QV4::Encode::undefined(); |
401 | |
402 | Q_ASSERT(s->isString()); |
403 | value += s->toQString(); |
404 | } |
405 | |
406 | return Encode(v4->newString(s: value)); |
407 | } |
408 | |
409 | ReturnedValue StringPrototype::method_endsWith(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
410 | { |
411 | ExecutionEngine *v4 = b->engine(); |
412 | const QString value = getThisString(v4, thisObject); |
413 | if (v4->hasException) |
414 | return QV4::Encode::undefined(); |
415 | |
416 | if (argc && argv[0].as<RegExpObject>()) |
417 | return v4->throwTypeError(); |
418 | QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString(); |
419 | if (v4->hasException) |
420 | return Encode::undefined(); |
421 | |
422 | double pos = value.size(); |
423 | if (argc > 1) |
424 | pos = argv[1].toInteger(); |
425 | |
426 | if (pos == value.size()) |
427 | RETURN_RESULT(Encode(value.endsWith(searchString))); |
428 | |
429 | QStringView stringToSearch = QStringView{value}.left(n: pos); |
430 | return Encode(stringToSearch.endsWith(s: searchString)); |
431 | } |
432 | |
433 | ReturnedValue StringPrototype::method_indexOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
434 | { |
435 | ExecutionEngine *v4 = b->engine(); |
436 | const QString value = getThisString(v4, thisObject); |
437 | if (v4->hasException) |
438 | return QV4::Encode::undefined(); |
439 | |
440 | QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString(); |
441 | if (v4->hasException) |
442 | return Encode::undefined(); |
443 | |
444 | double pos = 0; |
445 | if (argc > 1) |
446 | pos = argv[1].toInteger(); |
447 | |
448 | int index = -1; |
449 | if (!value.isEmpty()) |
450 | index = value.indexOf(s: searchString, from: qMin(a: qMax(a: pos, b: 0.0), b: double(value.size()))); |
451 | |
452 | return Encode(index); |
453 | } |
454 | |
455 | ReturnedValue StringPrototype::method_includes(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
456 | { |
457 | ExecutionEngine *v4 = b->engine(); |
458 | const QString value = getThisString(v4, thisObject); |
459 | if (v4->hasException) |
460 | return QV4::Encode::undefined(); |
461 | |
462 | if (argc && argv[0].as<RegExpObject>()) |
463 | return v4->throwTypeError(); |
464 | QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString(); |
465 | if (v4->hasException) |
466 | return Encode::undefined(); |
467 | |
468 | double pos = 0; |
469 | if (argc > 1) { |
470 | const Value &posArg = argv[1]; |
471 | pos = posArg.toInteger(); |
472 | if (!posArg.isInteger() && posArg.isNumber() && qIsInf(d: posArg.toNumber())) |
473 | pos = value.size(); |
474 | } |
475 | |
476 | if (pos == 0) |
477 | RETURN_RESULT(Encode(value.contains(searchString))); |
478 | |
479 | QStringView stringToSearch = QStringView{value}.mid(pos); |
480 | return Encode(stringToSearch.contains(s: searchString)); |
481 | } |
482 | |
483 | ReturnedValue StringPrototype::method_lastIndexOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
484 | { |
485 | ExecutionEngine *v4 = b->engine(); |
486 | const QString value = getThisString(v4, thisObject); |
487 | if (v4->hasException) |
488 | return QV4::Encode::undefined(); |
489 | |
490 | QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString(); |
491 | if (v4->hasException) |
492 | return Encode::undefined(); |
493 | |
494 | double position = argc > 1 ? RuntimeHelpers::toNumber(value: argv[1]) : +qInf(); |
495 | if (std::isnan(x: position)) |
496 | position = +qInf(); |
497 | else |
498 | position = std::trunc(x: position); |
499 | |
500 | int pos = std::trunc(x: qMin(a: qMax(a: position, b: 0.0), b: double(value.size()))); |
501 | if (!searchString.isEmpty() && pos == value.size()) |
502 | --pos; |
503 | if (searchString.isNull() && pos == 0) |
504 | RETURN_RESULT(Encode(-1)); |
505 | int index = value.lastIndexOf(s: searchString, from: pos); |
506 | return Encode(index); |
507 | } |
508 | |
509 | ReturnedValue StringPrototype::method_localeCompare(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
510 | { |
511 | ExecutionEngine *v4 = b->engine(); |
512 | const QString value = getThisString(v4, thisObject); |
513 | if (v4->hasException) |
514 | return QV4::Encode::undefined(); |
515 | |
516 | const QString that = (argc ? argv[0] : Value::undefinedValue()).toQString(); |
517 | return Encode(QString::localeAwareCompare(s1: value, s2: that)); |
518 | } |
519 | |
520 | ReturnedValue StringPrototype::method_match(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
521 | { |
522 | ExecutionEngine *v4 = b->engine(); |
523 | if (thisObject->isNullOrUndefined()) |
524 | return v4->throwTypeError(); |
525 | |
526 | Scope scope(v4); |
527 | if (argc && !argv[0].isNullOrUndefined()) { |
528 | ScopedObject r(scope, argv[0].toObject(e: scope.engine)); |
529 | if (scope.hasException()) |
530 | return Encode::undefined(); |
531 | ScopedValue f(scope, r->get(name: scope.engine->symbol_match())); |
532 | if (!f->isNullOrUndefined()) { |
533 | ScopedFunctionObject fo(scope, f); |
534 | if (!fo) |
535 | return scope.engine->throwTypeError(); |
536 | return checkedResult(v4: scope.engine, result: fo->call(thisObject: r, argv: thisObject, argc: 1)); |
537 | } |
538 | } |
539 | |
540 | ScopedString s(scope, thisObject->toString(e: v4)); |
541 | if (v4->hasException) |
542 | return Encode::undefined(); |
543 | |
544 | Scoped<RegExpObject> that(scope, argc ? argv[0] : Value::undefinedValue()); |
545 | if (!that) { |
546 | // convert args[0] to a regexp |
547 | that = RegExpCtor::virtualCallAsConstructor(f: b, argv, argc, b); |
548 | if (v4->hasException) |
549 | return Encode::undefined(); |
550 | } |
551 | Q_ASSERT(!!that); |
552 | |
553 | ScopedFunctionObject match(scope, that->get(name: scope.engine->symbol_match())); |
554 | if (!match) |
555 | return scope.engine->throwTypeError(); |
556 | return checkedResult(v4: scope.engine, result: match->call(thisObject: that, argv: s, argc: 1)); |
557 | } |
558 | |
559 | ReturnedValue StringPrototype::method_normalize(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) |
560 | { |
561 | ExecutionEngine *v4 = f->engine(); |
562 | const QString value = getThisString(v4, thisObject); |
563 | if (v4->hasException) |
564 | return Encode::undefined(); |
565 | |
566 | QString::NormalizationForm form = QString::NormalizationForm_C; |
567 | if (argc >= 1 && !argv[0].isUndefined()) { |
568 | QString f = argv[0].toQString(); |
569 | if (v4->hasException) |
570 | return Encode::undefined(); |
571 | if (f == QLatin1String("NFC" )) |
572 | form = QString::NormalizationForm_C; |
573 | else if (f == QLatin1String("NFD" )) |
574 | form = QString::NormalizationForm_D; |
575 | else if (f == QLatin1String("NFKC" )) |
576 | form = QString::NormalizationForm_KC; |
577 | else if (f == QLatin1String("NFKD" )) |
578 | form = QString::NormalizationForm_KD; |
579 | else |
580 | return v4->throwRangeError(message: QLatin1String("String.prototype.normalize: Invalid normalization form." )); |
581 | } |
582 | QString normalized = value.normalized(mode: form); |
583 | return v4->newString(s: normalized)->asReturnedValue(); |
584 | } |
585 | |
586 | ReturnedValue StringPrototype::method_padEnd(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) |
587 | { |
588 | ExecutionEngine *v4 = f->engine(); |
589 | if (thisObject->isNullOrUndefined()) |
590 | return v4->throwTypeError(); |
591 | |
592 | Scope scope(v4); |
593 | ScopedString s(scope, thisAsString(v4, thisObject)); |
594 | if (v4->hasException) |
595 | return Encode::undefined(); |
596 | if (!argc) |
597 | return s->asReturnedValue(); |
598 | |
599 | double maxLen = argv[0].toInteger(); |
600 | if (maxLen <= s->d()->length()) |
601 | return s->asReturnedValue(); |
602 | QString fillString = (argc > 1 && !argv[1].isUndefined()) ? argv[1].toQString() : QString::fromLatin1(ba: " " ); |
603 | if (v4->hasException) |
604 | return Encode::undefined(); |
605 | |
606 | if (fillString.isEmpty()) |
607 | return s->asReturnedValue(); |
608 | |
609 | QString padded = s->toQString(); |
610 | int oldLength = padded.size(); |
611 | int toFill = maxLen - oldLength; |
612 | padded.resize(size: maxLen); |
613 | QChar *ch = padded.data() + oldLength; |
614 | while (toFill) { |
615 | int copy = qMin(a: fillString.size(), b: toFill); |
616 | memcpy(dest: ch, src: fillString.constData(), n: copy*sizeof(QChar)); |
617 | toFill -= copy; |
618 | ch += copy; |
619 | } |
620 | *ch = QChar::Null; |
621 | |
622 | return v4->newString(s: padded)->asReturnedValue(); |
623 | } |
624 | |
625 | ReturnedValue StringPrototype::method_padStart(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) |
626 | { |
627 | ExecutionEngine *v4 = f->engine(); |
628 | if (thisObject->isNullOrUndefined()) |
629 | return v4->throwTypeError(); |
630 | |
631 | Scope scope(v4); |
632 | ScopedString s(scope, thisAsString(v4, thisObject)); |
633 | if (v4->hasException) |
634 | return Encode::undefined(); |
635 | if (!argc) |
636 | return s->asReturnedValue(); |
637 | |
638 | double maxLen = argv[0].toInteger(); |
639 | if (maxLen <= s->d()->length()) |
640 | return s->asReturnedValue(); |
641 | QString fillString = (argc > 1 && !argv[1].isUndefined()) ? argv[1].toQString() : QString::fromLatin1(ba: " " ); |
642 | if (v4->hasException) |
643 | return Encode::undefined(); |
644 | |
645 | if (fillString.isEmpty()) |
646 | return s->asReturnedValue(); |
647 | |
648 | QString original = s->toQString(); |
649 | int oldLength = original.size(); |
650 | int toFill = maxLen - oldLength; |
651 | QString padded; |
652 | padded.resize(size: maxLen); |
653 | QChar *ch = padded.data(); |
654 | while (toFill) { |
655 | int copy = qMin(a: fillString.size(), b: toFill); |
656 | memcpy(dest: ch, src: fillString.constData(), n: copy*sizeof(QChar)); |
657 | toFill -= copy; |
658 | ch += copy; |
659 | } |
660 | memcpy(dest: ch, src: original.constData(), n: oldLength*sizeof(QChar)); |
661 | ch += oldLength; |
662 | *ch = QChar::Null; |
663 | |
664 | return v4->newString(s: padded)->asReturnedValue(); |
665 | } |
666 | |
667 | |
668 | ReturnedValue StringPrototype::method_repeat(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
669 | { |
670 | ExecutionEngine *v4 = b->engine(); |
671 | const QString value = getThisString(v4, thisObject); |
672 | if (v4->hasException) |
673 | return QV4::Encode::undefined(); |
674 | |
675 | double repeats = (argc ? argv[0] : Value::undefinedValue()).toInteger(); |
676 | |
677 | if (repeats < 0 || qIsInf(d: repeats)) |
678 | return v4->throwRangeError(message: QLatin1String("Invalid count value" )); |
679 | |
680 | return Encode(v4->newString(s: value.repeated(times: int(repeats)))); |
681 | } |
682 | |
683 | static void appendReplacementString(QString *result, const QString &input, const QString& replaceValue, uint* matchOffsets, int captureCount) |
684 | { |
685 | result->reserve(asize: result->size() + replaceValue.size()); |
686 | for (int i = 0; i < replaceValue.size(); ++i) { |
687 | if (replaceValue.at(i) == QLatin1Char('$') && i < replaceValue.size() - 1) { |
688 | ushort ch = replaceValue.at(i: i + 1).unicode(); |
689 | uint substStart = JSC::Yarr::offsetNoMatch; |
690 | uint substEnd = JSC::Yarr::offsetNoMatch; |
691 | int skip = 0; |
692 | if (ch == '$') { |
693 | *result += QChar(ch); |
694 | ++i; |
695 | continue; |
696 | } else if (ch == '&') { |
697 | substStart = matchOffsets[0]; |
698 | substEnd = matchOffsets[1]; |
699 | skip = 1; |
700 | } else if (ch == '`') { |
701 | substStart = 0; |
702 | substEnd = matchOffsets[0]; |
703 | skip = 1; |
704 | } else if (ch == '\'') { |
705 | substStart = matchOffsets[1]; |
706 | substEnd = input.size(); |
707 | skip = 1; |
708 | } else if (ch >= '0' && ch <= '9') { |
709 | uint capture = ch - '0'; |
710 | skip = 1; |
711 | if (i < replaceValue.size() - 2) { |
712 | ch = replaceValue.at(i: i + 2).unicode(); |
713 | if (ch >= '0' && ch <= '9') { |
714 | uint c = capture*10 + ch - '0'; |
715 | if (c < static_cast<uint>(captureCount)) { |
716 | capture = c; |
717 | skip = 2; |
718 | } |
719 | } |
720 | } |
721 | if (capture > 0 && capture < static_cast<uint>(captureCount)) { |
722 | substStart = matchOffsets[capture * 2]; |
723 | substEnd = matchOffsets[capture * 2 + 1]; |
724 | } else { |
725 | skip = 0; |
726 | } |
727 | } |
728 | i += skip; |
729 | if (substStart != JSC::Yarr::offsetNoMatch && substEnd != JSC::Yarr::offsetNoMatch) |
730 | *result += QStringView{input}.mid(pos: substStart, n: substEnd - substStart); |
731 | else if (skip == 0) // invalid capture reference. Taken as literal value |
732 | *result += replaceValue.at(i); |
733 | } else { |
734 | *result += replaceValue.at(i); |
735 | } |
736 | } |
737 | } |
738 | |
739 | ReturnedValue StringPrototype::method_replace(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
740 | { |
741 | QString string; |
742 | if (const StringObject *thisString = thisObject->as<StringObject>()) |
743 | string = thisString->d()->string->toQString(); |
744 | else |
745 | string = thisObject->toQString(); |
746 | |
747 | int numCaptures = 0; |
748 | int numStringMatches = 0; |
749 | |
750 | uint allocatedMatchOffsets = 64; |
751 | uint _matchOffsets[64]; |
752 | uint *matchOffsets = _matchOffsets; |
753 | |
754 | Scope scope(b); |
755 | ScopedValue searchValue(scope, argc ? argv[0] : Value::undefinedValue()); |
756 | Scoped<RegExpObject> regExp(scope, searchValue); |
757 | if (regExp) { |
758 | uint offset = 0; |
759 | uint nMatchOffsets = 0; |
760 | |
761 | // We extract the pointer here to work around a compiler bug on Android. |
762 | Scoped<RegExp> re(scope, regExp->value()); |
763 | while (true) { |
764 | int oldSize = nMatchOffsets; |
765 | if (allocatedMatchOffsets < nMatchOffsets + re->captureCount() * 2) { |
766 | allocatedMatchOffsets = qMax(a: allocatedMatchOffsets * 2, b: nMatchOffsets + re->captureCount() * 2); |
767 | uint *newOffsets = (uint *)malloc(size: allocatedMatchOffsets*sizeof(uint)); |
768 | memcpy(dest: newOffsets, src: matchOffsets, n: nMatchOffsets*sizeof(uint)); |
769 | if (matchOffsets != _matchOffsets) |
770 | free(ptr: matchOffsets); |
771 | matchOffsets = newOffsets; |
772 | } |
773 | if (re->match(string, start: offset, matchOffsets: matchOffsets + oldSize) == JSC::Yarr::offsetNoMatch) { |
774 | nMatchOffsets = oldSize; |
775 | break; |
776 | } |
777 | nMatchOffsets += re->captureCount() * 2; |
778 | if (!regExp->global()) |
779 | break; |
780 | offset = qMax(a: offset + 1, b: matchOffsets[oldSize + 1]); |
781 | } |
782 | if (regExp->global()) { |
783 | regExp->setLastIndex(0); |
784 | if (scope.hasException()) |
785 | return Encode::undefined(); |
786 | } |
787 | numStringMatches = nMatchOffsets / (regExp->value()->captureCount() * 2); |
788 | numCaptures = regExp->value()->captureCount(); |
789 | } else { |
790 | numCaptures = 1; |
791 | QString searchString = searchValue->toQString(); |
792 | int idx = string.indexOf(s: searchString); |
793 | if (idx != -1) { |
794 | numStringMatches = 1; |
795 | matchOffsets[0] = idx; |
796 | matchOffsets[1] = idx + searchString.size(); |
797 | } |
798 | } |
799 | |
800 | QString result; |
801 | ScopedValue replacement(scope); |
802 | ScopedValue replaceValue(scope, argc > 1 ? argv[1] : Value::undefinedValue()); |
803 | ScopedFunctionObject searchCallback(scope, replaceValue); |
804 | if (!!searchCallback) { |
805 | result.reserve(asize: string.size() + 10*numStringMatches); |
806 | ScopedValue entry(scope); |
807 | Value *arguments = scope.alloc(nValues: numCaptures + 2); |
808 | int lastEnd = 0; |
809 | for (int i = 0; i < numStringMatches; ++i) { |
810 | for (int k = 0; k < numCaptures; ++k) { |
811 | int idx = (i * numCaptures + k) * 2; |
812 | uint start = matchOffsets[idx]; |
813 | uint end = matchOffsets[idx + 1]; |
814 | entry = Value::undefinedValue(); |
815 | if (start != JSC::Yarr::offsetNoMatch && end != JSC::Yarr::offsetNoMatch) |
816 | entry = scope.engine->newString(s: string.mid(position: start, n: end - start)); |
817 | arguments[k] = entry; |
818 | } |
819 | uint matchStart = matchOffsets[i * numCaptures * 2]; |
820 | Q_ASSERT(matchStart >= static_cast<uint>(lastEnd)); |
821 | uint matchEnd = matchOffsets[i * numCaptures * 2 + 1]; |
822 | arguments[numCaptures] = Value::fromUInt32(i: matchStart); |
823 | arguments[numCaptures + 1] = scope.engine->newString(s: string); |
824 | |
825 | Value that = Value::undefinedValue(); |
826 | replacement = searchCallback->call(thisObject: &that, argv: arguments, argc: numCaptures + 2); |
827 | CHECK_EXCEPTION(); |
828 | result += QStringView{string}.mid(pos: lastEnd, n: matchStart - lastEnd); |
829 | result += replacement->toQString(); |
830 | lastEnd = matchEnd; |
831 | } |
832 | result += QStringView{string}.mid(pos: lastEnd); |
833 | } else { |
834 | QString newString = replaceValue->toQString(); |
835 | result.reserve(asize: string.size() + numStringMatches*newString.size()); |
836 | |
837 | int lastEnd = 0; |
838 | for (int i = 0; i < numStringMatches; ++i) { |
839 | int baseIndex = i * numCaptures * 2; |
840 | uint matchStart = matchOffsets[baseIndex]; |
841 | uint matchEnd = matchOffsets[baseIndex + 1]; |
842 | if (matchStart == JSC::Yarr::offsetNoMatch) |
843 | continue; |
844 | |
845 | result += QStringView{string}.mid(pos: lastEnd, n: matchStart - lastEnd); |
846 | appendReplacementString(result: &result, input: string, replaceValue: newString, matchOffsets: matchOffsets + baseIndex, captureCount: numCaptures); |
847 | lastEnd = matchEnd; |
848 | } |
849 | result += QStringView{string}.mid(pos: lastEnd); |
850 | } |
851 | |
852 | if (matchOffsets != _matchOffsets) |
853 | free(ptr: matchOffsets); |
854 | |
855 | return Encode(scope.engine->newString(s: result)); |
856 | } |
857 | |
858 | ReturnedValue StringPrototype::method_search(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
859 | { |
860 | Scope scope(b); |
861 | QString string = getThisString(v4: scope.engine, thisObject); |
862 | if (scope.hasException()) |
863 | return QV4::Encode::undefined(); |
864 | |
865 | Scoped<RegExpObject> regExp(scope, argc ? argv[0] : Value::undefinedValue()); |
866 | if (!regExp) { |
867 | regExp = scope.engine->regExpCtor()->callAsConstructor(argv, argc: 1); |
868 | if (scope.hasException()) |
869 | return QV4::Encode::undefined(); |
870 | |
871 | Q_ASSERT(regExp); |
872 | } |
873 | Scoped<RegExp> re(scope, regExp->value()); |
874 | Q_ALLOCA_VAR(uint, matchOffsets, regExp->value()->captureCount() * 2 * sizeof(uint)); |
875 | uint result = re->match(string, /*offset*/start: 0, matchOffsets); |
876 | if (result == JSC::Yarr::offsetNoMatch) |
877 | return Encode(-1); |
878 | else |
879 | return Encode(result); |
880 | } |
881 | |
882 | ReturnedValue StringPrototype::method_slice(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
883 | { |
884 | ExecutionEngine *v4 = b->engine(); |
885 | Scope scope(v4); |
886 | ScopedString s(scope, thisAsString(v4, thisObject)); |
887 | if (v4->hasException) |
888 | return QV4::Encode::undefined(); |
889 | Q_ASSERT(s); |
890 | |
891 | const double length = s->d()->length(); |
892 | |
893 | double start = argc ? argv[0].toInteger() : 0; |
894 | double end = (argc < 2 || argv[1].isUndefined()) |
895 | ? length : argv[1].toInteger(); |
896 | |
897 | if (start < 0) |
898 | start = qMax(a: length + start, b: 0.); |
899 | else |
900 | start = qMin(a: start, b: length); |
901 | |
902 | if (end < 0) |
903 | end = qMax(a: length + end, b: 0.); |
904 | else |
905 | end = qMin(a: end, b: length); |
906 | |
907 | const int intStart = int(start); |
908 | const int intEnd = int(end); |
909 | |
910 | int count = qMax(a: 0, b: intEnd - intStart); |
911 | return Encode(v4->memoryManager->alloc<ComplexString>(args: s->d(), args: intStart, args&: count)); |
912 | } |
913 | |
914 | ReturnedValue StringPrototype::method_split(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
915 | { |
916 | ExecutionEngine *v4 = b->engine(); |
917 | QString text = getThisString(v4, thisObject); |
918 | if (v4->hasException) |
919 | return QV4::Encode::undefined(); |
920 | |
921 | Scope scope(v4); |
922 | ScopedValue separatorValue(scope, argc ? argv[0] : Value::undefinedValue()); |
923 | ScopedValue limitValue(scope, argc > 1 ? argv[1] : Value::undefinedValue()); |
924 | |
925 | ScopedArrayObject array(scope, scope.engine->newArrayObject()); |
926 | |
927 | if (separatorValue->isUndefined()) { |
928 | if (limitValue->isUndefined()) { |
929 | ScopedString s(scope, scope.engine->newString(s: text)); |
930 | array->push_back(v: s); |
931 | return array.asReturnedValue(); |
932 | } |
933 | RETURN_RESULT(scope.engine->newString(text.left(limitValue->toInteger()))); |
934 | } |
935 | |
936 | uint limit = limitValue->isUndefined() ? UINT_MAX : limitValue->toUInt32(); |
937 | |
938 | if (limit == 0) |
939 | return array.asReturnedValue(); |
940 | |
941 | Scoped<RegExpObject> re(scope, separatorValue); |
942 | if (re) { |
943 | if (re->value()->pattern->isEmpty()) { |
944 | re = (RegExpObject *)nullptr; |
945 | separatorValue = scope.engine->newString(); |
946 | } |
947 | } |
948 | |
949 | ScopedString s(scope); |
950 | if (re) { |
951 | uint offset = 0; |
952 | Q_ALLOCA_VAR(uint, matchOffsets, re->value()->captureCount() * 2 * sizeof(uint)); |
953 | while (true) { |
954 | Scoped<RegExp> regexp(scope, re->value()); |
955 | uint result = regexp->match(string: text, start: offset, matchOffsets); |
956 | if (result == JSC::Yarr::offsetNoMatch) |
957 | break; |
958 | |
959 | array->push_back(v: (s = scope.engine->newString(s: text.mid(position: offset, n: matchOffsets[0] - offset)))); |
960 | offset = qMax(a: offset + 1, b: matchOffsets[1]); |
961 | |
962 | if (array->getLength() >= limit) |
963 | break; |
964 | |
965 | for (int i = 1; i < re->value()->captureCount(); ++i) { |
966 | uint start = matchOffsets[i * 2]; |
967 | uint end = matchOffsets[i * 2 + 1]; |
968 | array->push_back(v: (s = scope.engine->newString(s: text.mid(position: start, n: end - start)))); |
969 | if (array->getLength() >= limit) |
970 | break; |
971 | } |
972 | } |
973 | if (array->getLength() < limit) |
974 | array->push_back(v: (s = scope.engine->newString(s: text.mid(position: offset)))); |
975 | } else { |
976 | QString separator = separatorValue->toQString(); |
977 | if (separator.isEmpty()) { |
978 | for (uint i = 0; i < qMin(a: limit, b: uint(text.size())); ++i) |
979 | array->push_back(v: (s = scope.engine->newString(s: text.mid(position: i, n: 1)))); |
980 | return array.asReturnedValue(); |
981 | } |
982 | |
983 | int start = 0; |
984 | int end; |
985 | while ((end = text.indexOf(s: separator, from: start)) != -1) { |
986 | array->push_back(v: (s = scope.engine->newString(s: text.mid(position: start, n: end - start)))); |
987 | start = end + separator.size(); |
988 | if (array->getLength() >= limit) |
989 | break; |
990 | } |
991 | if (array->getLength() < limit && start != -1) |
992 | array->push_back(v: (s = scope.engine->newString(s: text.mid(position: start)))); |
993 | } |
994 | return array.asReturnedValue(); |
995 | } |
996 | |
997 | ReturnedValue StringPrototype::method_startsWith(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
998 | { |
999 | ExecutionEngine *v4 = b->engine(); |
1000 | const QString value = getThisString(v4, thisObject); |
1001 | if (v4->hasException) |
1002 | return QV4::Encode::undefined(); |
1003 | |
1004 | if (argc && argv[0].as<RegExpObject>()) |
1005 | return v4->throwTypeError(); |
1006 | QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString(); |
1007 | if (v4->hasException) |
1008 | return Encode::undefined(); |
1009 | |
1010 | double pos = 0; |
1011 | if (argc > 1) |
1012 | pos = argv[1].toInteger(); |
1013 | |
1014 | if (pos == 0) |
1015 | return Encode(value.startsWith(s: searchString)); |
1016 | |
1017 | QStringView stringToSearch = QStringView{value}.mid(pos); |
1018 | RETURN_RESULT(Encode(stringToSearch.startsWith(searchString))); |
1019 | } |
1020 | |
1021 | ReturnedValue StringPrototype::method_substr(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1022 | { |
1023 | ExecutionEngine *v4 = b->engine(); |
1024 | const QString value = getThisString(v4, thisObject); |
1025 | if (v4->hasException) |
1026 | return QV4::Encode::undefined(); |
1027 | |
1028 | double start = 0; |
1029 | if (argc > 0) |
1030 | start = argv[0].toInteger(); |
1031 | |
1032 | double length = +qInf(); |
1033 | if (argc > 1) |
1034 | length = argv[1].toInteger(); |
1035 | |
1036 | double count = value.size(); |
1037 | if (start < 0) |
1038 | start = qMax(a: count + start, b: 0.0); |
1039 | |
1040 | length = qMin(a: qMax(a: length, b: 0.0), b: count - start); |
1041 | |
1042 | qint32 x = Value::toInt32(d: start); |
1043 | qint32 y = Value::toInt32(d: length); |
1044 | return Encode(v4->newString(s: value.mid(position: x, n: y))); |
1045 | } |
1046 | |
1047 | ReturnedValue StringPrototype::method_substring(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1048 | { |
1049 | ExecutionEngine *v4 = b->engine(); |
1050 | const QString value = getThisString(v4, thisObject); |
1051 | if (v4->hasException) |
1052 | return QV4::Encode::undefined(); |
1053 | |
1054 | int length = value.size(); |
1055 | |
1056 | double start = 0; |
1057 | double end = length; |
1058 | |
1059 | if (argc > 0) |
1060 | start = argv[0].toInteger(); |
1061 | |
1062 | if (argc > 1 && !argv[1].isUndefined()) |
1063 | end = argv[1].toInteger(); |
1064 | |
1065 | if (std::isnan(x: start) || start < 0) |
1066 | start = 0; |
1067 | |
1068 | if (std::isnan(x: end) || end < 0) |
1069 | end = 0; |
1070 | |
1071 | if (start > length) |
1072 | start = length; |
1073 | |
1074 | if (end > length) |
1075 | end = length; |
1076 | |
1077 | if (start > end) { |
1078 | double was = start; |
1079 | start = end; |
1080 | end = was; |
1081 | } |
1082 | |
1083 | qint32 x = (int)start; |
1084 | qint32 y = (int)(end - start); |
1085 | return Encode(v4->newString(s: value.mid(position: x, n: y))); |
1086 | } |
1087 | |
1088 | ReturnedValue StringPrototype::method_toLowerCase(const FunctionObject *b, const Value *thisObject, const Value *, int) |
1089 | { |
1090 | ExecutionEngine *v4 = b->engine(); |
1091 | const QString value = getThisString(v4, thisObject); |
1092 | if (v4->hasException) |
1093 | return QV4::Encode::undefined(); |
1094 | |
1095 | return Encode(v4->newString(s: value.toLower())); |
1096 | } |
1097 | |
1098 | ReturnedValue StringPrototype::method_toLocaleLowerCase(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1099 | { |
1100 | return method_toLowerCase(b, thisObject, argv, argc); |
1101 | } |
1102 | |
1103 | ReturnedValue StringPrototype::method_toUpperCase(const FunctionObject *b, const Value *thisObject, const Value *, int) |
1104 | { |
1105 | ExecutionEngine *v4 = b->engine(); |
1106 | const QString value = getThisString(v4, thisObject); |
1107 | if (v4->hasException) |
1108 | return QV4::Encode::undefined(); |
1109 | |
1110 | return Encode(v4->newString(s: value.toUpper())); |
1111 | } |
1112 | |
1113 | ReturnedValue StringPrototype::method_toLocaleUpperCase(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc) |
1114 | { |
1115 | return method_toUpperCase(b, thisObject, argv, argc); |
1116 | } |
1117 | |
1118 | ReturnedValue StringPrototype::method_trim(const FunctionObject *b, const Value *thisObject, const Value *, int) |
1119 | { |
1120 | ExecutionEngine *v4 = b->engine(); |
1121 | QString s = getThisString(v4, thisObject); |
1122 | if (v4->hasException) |
1123 | return QV4::Encode::undefined(); |
1124 | |
1125 | const QChar *chars = s.constData(); |
1126 | int start, end; |
1127 | for (start = 0; start < s.size(); ++start) { |
1128 | if (!chars[start].isSpace() && chars[start].unicode() != 0xfeff) |
1129 | break; |
1130 | } |
1131 | for (end = s.size() - 1; end >= start; --end) { |
1132 | if (!chars[end].isSpace() && chars[end].unicode() != 0xfeff) |
1133 | break; |
1134 | } |
1135 | |
1136 | return Encode(v4->newString(s: QString(chars + start, end - start + 1))); |
1137 | } |
1138 | |
1139 | |
1140 | |
1141 | ReturnedValue StringPrototype::method_iterator(const FunctionObject *b, const Value *thisObject, const Value *, int) |
1142 | { |
1143 | Scope scope(b); |
1144 | ScopedString s(scope, thisObject->toString(e: scope.engine)); |
1145 | if (!s || thisObject->isNullOrUndefined()) |
1146 | return scope.engine->throwTypeError(); |
1147 | |
1148 | Scoped<StringIteratorObject> si(scope, scope.engine->memoryManager->allocate<StringIteratorObject>(args: s->d(), args&: scope.engine)); |
1149 | return si->asReturnedValue(); |
1150 | } |
1151 | |