]>
Commit | Line | Data |
---|---|---|
9dae56ea A |
1 | /* |
2 | * Copyright (C) 1999-2002 Harri Porten (porten@kde.org) | |
3 | * Copyright (C) 2001 Peter Kelly (pmk@post.com) | |
4 | * Copyright (C) 2004, 2007, 2008 Apple Inc. All rights reserved. | |
5 | * | |
6 | * This library is free software; you can redistribute it and/or | |
7 | * modify it under the terms of the GNU Library General Public | |
8 | * License as published by the Free Software Foundation; either | |
9 | * version 2 of the License, or (at your option) any later version. | |
10 | * | |
11 | * This library is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | * Library General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU Library General Public License | |
17 | * along with this library; see the file COPYING.LIB. If not, write to | |
18 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | |
19 | * Boston, MA 02110-1301, USA. | |
20 | * | |
21 | */ | |
22 | ||
23 | #include "config.h" | |
24 | #include "JSString.h" | |
25 | ||
26 | #include "JSGlobalObject.h" | |
14957cd0 | 27 | #include "JSGlobalObjectFunctions.h" |
9dae56ea | 28 | #include "JSObject.h" |
f9bf01c6 | 29 | #include "Operations.h" |
9dae56ea A |
30 | #include "StringObject.h" |
31 | #include "StringPrototype.h" | |
32 | ||
33 | namespace JSC { | |
b80e6193 | 34 | |
14957cd0 A |
35 | static const unsigned substringFromRopeCutoff = 4; |
36 | ||
37 | const ClassInfo JSString::s_info = { "string", 0, 0, 0 }; | |
38 | ||
39 | void JSString::resolveRope(ExecState* exec) const | |
40 | { | |
41 | ASSERT(isRope()); | |
42 | ||
43 | UChar* buffer; | |
44 | if (PassRefPtr<StringImpl> newImpl = StringImpl::tryCreateUninitialized(m_length, buffer)) | |
45 | m_value = newImpl; | |
46 | else { | |
47 | outOfMemory(exec); | |
48 | return; | |
49 | } | |
50 | ||
51 | RopeImpl::Fiber currentFiber = m_fibers[0]; | |
52 | ||
53 | if ((m_fiberCount > 2) || (RopeImpl::isRope(currentFiber)) | |
54 | || ((m_fiberCount == 2) && (RopeImpl::isRope(m_fibers[1])))) { | |
55 | resolveRopeSlowCase(exec, buffer); | |
56 | return; | |
57 | } | |
58 | ||
59 | UChar* position = buffer; | |
60 | StringImpl* string = static_cast<StringImpl*>(currentFiber); | |
61 | unsigned length = string->length(); | |
62 | StringImpl::copyChars(position, string->characters(), length); | |
63 | ||
64 | if (m_fiberCount > 1) { | |
65 | position += length; | |
66 | currentFiber = m_fibers[1]; | |
67 | string = static_cast<StringImpl*>(currentFiber); | |
68 | length = string->length(); | |
69 | StringImpl::copyChars(position, string->characters(), length); | |
70 | position += length; | |
71 | } | |
72 | ||
73 | ASSERT((buffer + m_length) == position); | |
74 | for (unsigned i = 0; i < m_fiberCount; ++i) { | |
75 | RopeImpl::deref(m_fibers[i]); | |
76 | m_fibers[i] = 0; | |
77 | } | |
78 | m_fiberCount = 0; | |
79 | ||
80 | ASSERT(!isRope()); | |
81 | } | |
9dae56ea | 82 | |
f9bf01c6 A |
83 | // Overview: this methods converts a JSString from holding a string in rope form |
84 | // down to a simple UString representation. It does so by building up the string | |
85 | // backwards, since we want to avoid recursion, we expect that the tree structure | |
86 | // representing the rope is likely imbalanced with more nodes down the left side | |
87 | // (since appending to the string is likely more common) - and as such resolving | |
88 | // in this fashion should minimize work queue size. (If we built the queue forwards | |
14957cd0 | 89 | // we would likely have to place all of the constituent StringImpls into the |
f9bf01c6 A |
90 | // Vector before performing any concatenation, but by working backwards we likely |
91 | // only fill the queue with the number of substrings at any given level in a | |
14957cd0 A |
92 | // rope-of-ropes.) |
93 | void JSString::resolveRopeSlowCase(ExecState* exec, UChar* buffer) const | |
f9bf01c6 | 94 | { |
14957cd0 | 95 | UNUSED_PARAM(exec); |
f9bf01c6 | 96 | |
4e4e5a6f A |
97 | UChar* position = buffer + m_length; |
98 | ||
99 | // Start with the current RopeImpl. | |
100 | Vector<RopeImpl::Fiber, 32> workQueue; | |
101 | RopeImpl::Fiber currentFiber; | |
102 | for (unsigned i = 0; i < (m_fiberCount - 1); ++i) | |
14957cd0 A |
103 | workQueue.append(m_fibers[i]); |
104 | currentFiber = m_fibers[m_fiberCount - 1]; | |
f9bf01c6 | 105 | while (true) { |
4e4e5a6f A |
106 | if (RopeImpl::isRope(currentFiber)) { |
107 | RopeImpl* rope = static_cast<RopeImpl*>(currentFiber); | |
f9bf01c6 A |
108 | // Copy the contents of the current rope into the workQueue, with the last item in 'currentFiber' |
109 | // (we will be working backwards over the rope). | |
4e4e5a6f A |
110 | unsigned fiberCountMinusOne = rope->fiberCount() - 1; |
111 | for (unsigned i = 0; i < fiberCountMinusOne; ++i) | |
112 | workQueue.append(rope->fibers()[i]); | |
113 | currentFiber = rope->fibers()[fiberCountMinusOne]; | |
f9bf01c6 | 114 | } else { |
14957cd0 | 115 | StringImpl* string = static_cast<StringImpl*>(currentFiber); |
4e4e5a6f | 116 | unsigned length = string->length(); |
f9bf01c6 | 117 | position -= length; |
14957cd0 | 118 | StringImpl::copyChars(position, string->characters(), length); |
f9bf01c6 A |
119 | |
120 | // Was this the last item in the work queue? | |
121 | if (workQueue.isEmpty()) { | |
122 | // Create a string from the UChar buffer, clear the rope RefPtr. | |
123 | ASSERT(buffer == position); | |
4e4e5a6f | 124 | for (unsigned i = 0; i < m_fiberCount; ++i) { |
14957cd0 A |
125 | RopeImpl::deref(m_fibers[i]); |
126 | m_fibers[i] = 0; | |
f9bf01c6 | 127 | } |
4e4e5a6f | 128 | m_fiberCount = 0; |
14957cd0 | 129 | |
f9bf01c6 A |
130 | ASSERT(!isRope()); |
131 | return; | |
132 | } | |
133 | ||
134 | // No! - set the next item up to process. | |
135 | currentFiber = workQueue.last(); | |
136 | workQueue.removeLast(); | |
137 | } | |
138 | } | |
139 | } | |
14957cd0 A |
140 | |
141 | void JSString::outOfMemory(ExecState* exec) const | |
142 | { | |
143 | for (unsigned i = 0; i < m_fiberCount; ++i) { | |
144 | RopeImpl::deref(m_fibers[i]); | |
145 | m_fibers[i] = 0; | |
146 | } | |
147 | m_fiberCount = 0; | |
148 | ASSERT(!isRope()); | |
149 | ASSERT(m_value == UString()); | |
150 | if (exec) | |
151 | throwOutOfMemoryError(exec); | |
152 | } | |
153 | ||
b80e6193 A |
154 | // This function construsts a substring out of a rope without flattening by reusing the existing fibers. |
155 | // This can reduce memory usage substantially. Since traversing ropes is slow the function will revert | |
156 | // back to flattening if the rope turns out to be long. | |
157 | JSString* JSString::substringFromRope(ExecState* exec, unsigned substringStart, unsigned substringLength) | |
158 | { | |
159 | ASSERT(isRope()); | |
14957cd0 A |
160 | ASSERT(substringLength); |
161 | ||
b80e6193 A |
162 | JSGlobalData* globalData = &exec->globalData(); |
163 | ||
164 | UString substringFibers[3]; | |
165 | ||
166 | unsigned fiberCount = 0; | |
167 | unsigned substringFiberCount = 0; | |
168 | unsigned substringEnd = substringStart + substringLength; | |
169 | unsigned fiberEnd = 0; | |
170 | ||
171 | RopeIterator end; | |
14957cd0 | 172 | for (RopeIterator it(m_fibers.data(), m_fiberCount); it != end; ++it) { |
b80e6193 | 173 | ++fiberCount; |
14957cd0 | 174 | StringImpl* fiberString = *it; |
b80e6193 A |
175 | unsigned fiberStart = fiberEnd; |
176 | fiberEnd = fiberStart + fiberString->length(); | |
177 | if (fiberEnd <= substringStart) | |
178 | continue; | |
179 | unsigned copyStart = std::max(substringStart, fiberStart); | |
180 | unsigned copyEnd = std::min(substringEnd, fiberEnd); | |
181 | if (copyStart == fiberStart && copyEnd == fiberEnd) | |
182 | substringFibers[substringFiberCount++] = UString(fiberString); | |
183 | else | |
14957cd0 | 184 | substringFibers[substringFiberCount++] = UString(StringImpl::create(fiberString, copyStart - fiberStart, copyEnd - copyStart)); |
b80e6193 A |
185 | if (fiberEnd >= substringEnd) |
186 | break; | |
14957cd0 | 187 | if (fiberCount > substringFromRopeCutoff || substringFiberCount >= 3) { |
b80e6193 A |
188 | // This turned out to be a really inefficient rope. Just flatten it. |
189 | resolveRope(exec); | |
190 | return jsSubstring(&exec->globalData(), m_value, substringStart, substringLength); | |
191 | } | |
192 | } | |
193 | ASSERT(substringFiberCount && substringFiberCount <= 3); | |
194 | ||
195 | if (substringLength == 1) { | |
196 | ASSERT(substringFiberCount == 1); | |
14957cd0 A |
197 | UChar c = substringFibers[0].characters()[0]; |
198 | if (c <= maxSingleCharacterString) | |
b80e6193 A |
199 | return globalData->smallStrings.singleCharacterString(globalData, c); |
200 | } | |
201 | if (substringFiberCount == 1) | |
202 | return new (globalData) JSString(globalData, substringFibers[0]); | |
203 | if (substringFiberCount == 2) | |
204 | return new (globalData) JSString(globalData, substringFibers[0], substringFibers[1]); | |
205 | return new (globalData) JSString(globalData, substringFibers[0], substringFibers[1], substringFibers[2]); | |
206 | } | |
f9bf01c6 | 207 | |
4e4e5a6f A |
208 | JSValue JSString::replaceCharacter(ExecState* exec, UChar character, const UString& replacement) |
209 | { | |
210 | if (!isRope()) { | |
14957cd0 A |
211 | size_t matchPosition = m_value.find(character); |
212 | if (matchPosition == notFound) | |
4e4e5a6f | 213 | return JSValue(this); |
14957cd0 | 214 | return jsString(exec, m_value.substringSharingImpl(0, matchPosition), replacement, m_value.substringSharingImpl(matchPosition + 1)); |
4e4e5a6f A |
215 | } |
216 | ||
217 | RopeIterator end; | |
218 | ||
219 | // Count total fibers and find matching string. | |
220 | size_t fiberCount = 0; | |
14957cd0 A |
221 | StringImpl* matchString = 0; |
222 | size_t matchPosition = notFound; | |
223 | for (RopeIterator it(m_fibers.data(), m_fiberCount); it != end; ++it) { | |
4e4e5a6f A |
224 | ++fiberCount; |
225 | if (matchString) | |
226 | continue; | |
227 | ||
14957cd0 | 228 | StringImpl* string = *it; |
4e4e5a6f | 229 | matchPosition = string->find(character); |
14957cd0 | 230 | if (matchPosition == notFound) |
4e4e5a6f A |
231 | continue; |
232 | matchString = string; | |
233 | } | |
234 | ||
235 | if (!matchString) | |
236 | return this; | |
237 | ||
14957cd0 | 238 | RopeBuilder builder(replacement.length() ? fiberCount + 2 : fiberCount + 1); |
4e4e5a6f A |
239 | if (UNLIKELY(builder.isOutOfMemory())) |
240 | return throwOutOfMemoryError(exec); | |
241 | ||
14957cd0 A |
242 | for (RopeIterator it(m_fibers.data(), m_fiberCount); it != end; ++it) { |
243 | StringImpl* string = *it; | |
4e4e5a6f A |
244 | if (string != matchString) { |
245 | builder.append(UString(string)); | |
246 | continue; | |
247 | } | |
248 | ||
14957cd0 A |
249 | builder.append(UString(string).substringSharingImpl(0, matchPosition)); |
250 | if (replacement.length()) | |
4e4e5a6f | 251 | builder.append(replacement); |
14957cd0 | 252 | builder.append(UString(string).substringSharingImpl(matchPosition + 1)); |
4e4e5a6f A |
253 | matchString = 0; |
254 | } | |
255 | ||
256 | JSGlobalData* globalData = &exec->globalData(); | |
257 | return JSValue(new (globalData) JSString(globalData, builder.release())); | |
258 | } | |
259 | ||
260 | JSString* JSString::getIndexSlowCase(ExecState* exec, unsigned i) | |
261 | { | |
262 | ASSERT(isRope()); | |
263 | resolveRope(exec); | |
264 | // Return a safe no-value result, this should never be used, since the excetion will be thrown. | |
265 | if (exec->exception()) | |
266 | return jsString(exec, ""); | |
267 | ASSERT(!isRope()); | |
14957cd0 | 268 | ASSERT(i < m_value.length()); |
4e4e5a6f A |
269 | return jsSingleCharacterSubstring(exec, m_value, i); |
270 | } | |
271 | ||
ba379fdc | 272 | JSValue JSString::toPrimitive(ExecState*, PreferredPrimitiveType) const |
9dae56ea A |
273 | { |
274 | return const_cast<JSString*>(this); | |
275 | } | |
276 | ||
f9bf01c6 | 277 | bool JSString::getPrimitiveNumber(ExecState* exec, double& number, JSValue& result) |
9dae56ea | 278 | { |
f9bf01c6 | 279 | result = this; |
14957cd0 | 280 | number = jsToNumber(value(exec)); |
9dae56ea A |
281 | return false; |
282 | } | |
283 | ||
284 | bool JSString::toBoolean(ExecState*) const | |
285 | { | |
4e4e5a6f | 286 | return m_length; |
9dae56ea A |
287 | } |
288 | ||
f9bf01c6 | 289 | double JSString::toNumber(ExecState* exec) const |
9dae56ea | 290 | { |
14957cd0 | 291 | return jsToNumber(value(exec)); |
9dae56ea A |
292 | } |
293 | ||
f9bf01c6 | 294 | UString JSString::toString(ExecState* exec) const |
9dae56ea | 295 | { |
f9bf01c6 | 296 | return value(exec); |
9dae56ea A |
297 | } |
298 | ||
14957cd0 | 299 | inline StringObject* StringObject::create(ExecState* exec, JSGlobalObject* globalObject, JSString* string) |
9dae56ea | 300 | { |
14957cd0 | 301 | return new (exec) StringObject(exec->globalData(), globalObject->stringObjectStructure(), string); |
9dae56ea A |
302 | } |
303 | ||
14957cd0 | 304 | JSObject* JSString::toObject(ExecState* exec, JSGlobalObject* globalObject) const |
9dae56ea | 305 | { |
14957cd0 | 306 | return StringObject::create(exec, globalObject, const_cast<JSString*>(this)); |
9dae56ea A |
307 | } |
308 | ||
309 | JSObject* JSString::toThisObject(ExecState* exec) const | |
310 | { | |
14957cd0 | 311 | return StringObject::create(exec, exec->lexicalGlobalObject(), const_cast<JSString*>(this)); |
9dae56ea A |
312 | } |
313 | ||
314 | bool JSString::getOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot) | |
315 | { | |
316 | // The semantics here are really getPropertySlot, not getOwnPropertySlot. | |
317 | // This function should only be called by JSValue::get. | |
318 | if (getStringPropertySlot(exec, propertyName, slot)) | |
319 | return true; | |
ba379fdc A |
320 | if (propertyName == exec->propertyNames().underscoreProto) { |
321 | slot.setValue(exec->lexicalGlobalObject()->stringPrototype()); | |
322 | return true; | |
323 | } | |
9dae56ea A |
324 | slot.setBase(this); |
325 | JSObject* object; | |
ba379fdc | 326 | for (JSValue prototype = exec->lexicalGlobalObject()->stringPrototype(); !prototype.isNull(); prototype = object->prototype()) { |
9dae56ea A |
327 | object = asObject(prototype); |
328 | if (object->getOwnPropertySlot(exec, propertyName, slot)) | |
329 | return true; | |
330 | } | |
331 | slot.setUndefined(); | |
332 | return true; | |
333 | } | |
334 | ||
f9bf01c6 | 335 | bool JSString::getStringPropertyDescriptor(ExecState* exec, const Identifier& propertyName, PropertyDescriptor& descriptor) |
9dae56ea | 336 | { |
f9bf01c6 | 337 | if (propertyName == exec->propertyNames().length) { |
14957cd0 | 338 | descriptor.setDescriptor(jsNumber(m_length), DontEnum | DontDelete | ReadOnly); |
9dae56ea | 339 | return true; |
9dae56ea | 340 | } |
9dae56ea | 341 | |
f9bf01c6 | 342 | bool isStrictUInt32; |
14957cd0 | 343 | unsigned i = propertyName.toUInt32(isStrictUInt32); |
4e4e5a6f A |
344 | if (isStrictUInt32 && i < m_length) { |
345 | descriptor.setDescriptor(getIndex(exec, i), DontDelete | ReadOnly); | |
f9bf01c6 | 346 | return true; |
9dae56ea | 347 | } |
f9bf01c6 A |
348 | |
349 | return false; | |
9dae56ea A |
350 | } |
351 | ||
f9bf01c6 | 352 | bool JSString::getOwnPropertyDescriptor(ExecState* exec, const Identifier& propertyName, PropertyDescriptor& descriptor) |
9dae56ea | 353 | { |
f9bf01c6 A |
354 | if (getStringPropertyDescriptor(exec, propertyName, descriptor)) |
355 | return true; | |
356 | if (propertyName != exec->propertyNames().underscoreProto) | |
357 | return false; | |
358 | descriptor.setDescriptor(exec->lexicalGlobalObject()->stringPrototype(), DontEnum); | |
359 | return true; | |
360 | } | |
361 | ||
362 | bool JSString::getOwnPropertySlot(ExecState* exec, unsigned propertyName, PropertySlot& slot) | |
363 | { | |
364 | // The semantics here are really getPropertySlot, not getOwnPropertySlot. | |
365 | // This function should only be called by JSValue::get. | |
366 | if (getStringPropertySlot(exec, propertyName, slot)) | |
367 | return true; | |
368 | return JSString::getOwnPropertySlot(exec, Identifier::from(exec, propertyName), slot); | |
9dae56ea A |
369 | } |
370 | ||
371 | } // namespace JSC |