]>
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" | |
27 | #include "JSObject.h" | |
f9bf01c6 | 28 | #include "Operations.h" |
9dae56ea A |
29 | #include "StringObject.h" |
30 | #include "StringPrototype.h" | |
31 | ||
32 | namespace JSC { | |
b80e6193 A |
33 | |
34 | static const unsigned resolveRopeForSubstringCutoff = 4; | |
9dae56ea | 35 | |
f9bf01c6 A |
36 | // Overview: this methods converts a JSString from holding a string in rope form |
37 | // down to a simple UString representation. It does so by building up the string | |
38 | // backwards, since we want to avoid recursion, we expect that the tree structure | |
39 | // representing the rope is likely imbalanced with more nodes down the left side | |
40 | // (since appending to the string is likely more common) - and as such resolving | |
41 | // in this fashion should minimize work queue size. (If we built the queue forwards | |
4e4e5a6f | 42 | // we would likely have to place all of the constituent UStringImpls into the |
f9bf01c6 A |
43 | // Vector before performing any concatenation, but by working backwards we likely |
44 | // only fill the queue with the number of substrings at any given level in a | |
45 | // rope-of-ropes.) | |
46 | void JSString::resolveRope(ExecState* exec) const | |
47 | { | |
48 | ASSERT(isRope()); | |
49 | ||
50 | // Allocate the buffer to hold the final string, position initially points to the end. | |
51 | UChar* buffer; | |
4e4e5a6f | 52 | if (PassRefPtr<UStringImpl> newImpl = UStringImpl::tryCreateUninitialized(m_length, buffer)) |
f9bf01c6 A |
53 | m_value = newImpl; |
54 | else { | |
4e4e5a6f A |
55 | for (unsigned i = 0; i < m_fiberCount; ++i) { |
56 | RopeImpl::deref(m_other.m_fibers[i]); | |
57 | m_other.m_fibers[i] = 0; | |
f9bf01c6 | 58 | } |
4e4e5a6f | 59 | m_fiberCount = 0; |
f9bf01c6 A |
60 | ASSERT(!isRope()); |
61 | ASSERT(m_value == UString()); | |
4e4e5a6f A |
62 | if (exec) |
63 | throwOutOfMemoryError(exec); | |
f9bf01c6 A |
64 | return; |
65 | } | |
4e4e5a6f A |
66 | UChar* position = buffer + m_length; |
67 | ||
68 | // Start with the current RopeImpl. | |
69 | Vector<RopeImpl::Fiber, 32> workQueue; | |
70 | RopeImpl::Fiber currentFiber; | |
71 | for (unsigned i = 0; i < (m_fiberCount - 1); ++i) | |
72 | workQueue.append(m_other.m_fibers[i]); | |
73 | currentFiber = m_other.m_fibers[m_fiberCount - 1]; | |
f9bf01c6 | 74 | while (true) { |
4e4e5a6f A |
75 | if (RopeImpl::isRope(currentFiber)) { |
76 | RopeImpl* rope = static_cast<RopeImpl*>(currentFiber); | |
f9bf01c6 A |
77 | // Copy the contents of the current rope into the workQueue, with the last item in 'currentFiber' |
78 | // (we will be working backwards over the rope). | |
4e4e5a6f A |
79 | unsigned fiberCountMinusOne = rope->fiberCount() - 1; |
80 | for (unsigned i = 0; i < fiberCountMinusOne; ++i) | |
81 | workQueue.append(rope->fibers()[i]); | |
82 | currentFiber = rope->fibers()[fiberCountMinusOne]; | |
f9bf01c6 | 83 | } else { |
4e4e5a6f A |
84 | UStringImpl* string = static_cast<UStringImpl*>(currentFiber); |
85 | unsigned length = string->length(); | |
f9bf01c6 | 86 | position -= length; |
4e4e5a6f | 87 | UStringImpl::copyChars(position, string->characters(), length); |
f9bf01c6 A |
88 | |
89 | // Was this the last item in the work queue? | |
90 | if (workQueue.isEmpty()) { | |
91 | // Create a string from the UChar buffer, clear the rope RefPtr. | |
92 | ASSERT(buffer == position); | |
4e4e5a6f A |
93 | for (unsigned i = 0; i < m_fiberCount; ++i) { |
94 | RopeImpl::deref(m_other.m_fibers[i]); | |
95 | m_other.m_fibers[i] = 0; | |
f9bf01c6 | 96 | } |
4e4e5a6f | 97 | m_fiberCount = 0; |
f9bf01c6 A |
98 | |
99 | ASSERT(!isRope()); | |
100 | return; | |
101 | } | |
102 | ||
103 | // No! - set the next item up to process. | |
104 | currentFiber = workQueue.last(); | |
105 | workQueue.removeLast(); | |
106 | } | |
107 | } | |
108 | } | |
b80e6193 A |
109 | |
110 | // This function construsts a substring out of a rope without flattening by reusing the existing fibers. | |
111 | // This can reduce memory usage substantially. Since traversing ropes is slow the function will revert | |
112 | // back to flattening if the rope turns out to be long. | |
113 | JSString* JSString::substringFromRope(ExecState* exec, unsigned substringStart, unsigned substringLength) | |
114 | { | |
115 | ASSERT(isRope()); | |
116 | ||
117 | JSGlobalData* globalData = &exec->globalData(); | |
118 | ||
119 | UString substringFibers[3]; | |
120 | ||
121 | unsigned fiberCount = 0; | |
122 | unsigned substringFiberCount = 0; | |
123 | unsigned substringEnd = substringStart + substringLength; | |
124 | unsigned fiberEnd = 0; | |
125 | ||
126 | RopeIterator end; | |
127 | for (RopeIterator it(m_other.m_fibers, m_fiberCount); it != end; ++it) { | |
128 | ++fiberCount; | |
129 | UStringImpl* fiberString = *it; | |
130 | unsigned fiberStart = fiberEnd; | |
131 | fiberEnd = fiberStart + fiberString->length(); | |
132 | if (fiberEnd <= substringStart) | |
133 | continue; | |
134 | unsigned copyStart = std::max(substringStart, fiberStart); | |
135 | unsigned copyEnd = std::min(substringEnd, fiberEnd); | |
136 | if (copyStart == fiberStart && copyEnd == fiberEnd) | |
137 | substringFibers[substringFiberCount++] = UString(fiberString); | |
138 | else | |
139 | substringFibers[substringFiberCount++] = UString(UStringImpl::create(fiberString, copyStart - fiberStart, copyEnd - copyStart)); | |
140 | if (fiberEnd >= substringEnd) | |
141 | break; | |
142 | if (fiberCount > resolveRopeForSubstringCutoff || substringFiberCount >= 3) { | |
143 | // This turned out to be a really inefficient rope. Just flatten it. | |
144 | resolveRope(exec); | |
145 | return jsSubstring(&exec->globalData(), m_value, substringStart, substringLength); | |
146 | } | |
147 | } | |
148 | ASSERT(substringFiberCount && substringFiberCount <= 3); | |
149 | ||
150 | if (substringLength == 1) { | |
151 | ASSERT(substringFiberCount == 1); | |
152 | UChar c = substringFibers[0].data()[0]; | |
153 | if (c <= 0xFF) | |
154 | return globalData->smallStrings.singleCharacterString(globalData, c); | |
155 | } | |
156 | if (substringFiberCount == 1) | |
157 | return new (globalData) JSString(globalData, substringFibers[0]); | |
158 | if (substringFiberCount == 2) | |
159 | return new (globalData) JSString(globalData, substringFibers[0], substringFibers[1]); | |
160 | return new (globalData) JSString(globalData, substringFibers[0], substringFibers[1], substringFibers[2]); | |
161 | } | |
f9bf01c6 | 162 | |
4e4e5a6f A |
163 | JSValue JSString::replaceCharacter(ExecState* exec, UChar character, const UString& replacement) |
164 | { | |
165 | if (!isRope()) { | |
166 | unsigned matchPosition = m_value.find(character); | |
167 | if (matchPosition == UString::NotFound) | |
168 | return JSValue(this); | |
169 | return jsString(exec, m_value.substr(0, matchPosition), replacement, m_value.substr(matchPosition + 1)); | |
170 | } | |
171 | ||
172 | RopeIterator end; | |
173 | ||
174 | // Count total fibers and find matching string. | |
175 | size_t fiberCount = 0; | |
176 | UStringImpl* matchString = 0; | |
177 | int matchPosition = -1; | |
178 | for (RopeIterator it(m_other.m_fibers, m_fiberCount); it != end; ++it) { | |
179 | ++fiberCount; | |
180 | if (matchString) | |
181 | continue; | |
182 | ||
183 | UStringImpl* string = *it; | |
184 | matchPosition = string->find(character); | |
185 | if (matchPosition == -1) | |
186 | continue; | |
187 | matchString = string; | |
188 | } | |
189 | ||
190 | if (!matchString) | |
191 | return this; | |
192 | ||
193 | RopeBuilder builder(replacement.size() ? fiberCount + 2 : fiberCount + 1); | |
194 | if (UNLIKELY(builder.isOutOfMemory())) | |
195 | return throwOutOfMemoryError(exec); | |
196 | ||
197 | for (RopeIterator it(m_other.m_fibers, m_fiberCount); it != end; ++it) { | |
198 | UStringImpl* string = *it; | |
199 | if (string != matchString) { | |
200 | builder.append(UString(string)); | |
201 | continue; | |
202 | } | |
203 | ||
204 | builder.append(UString(string).substr(0, matchPosition)); | |
205 | if (replacement.size()) | |
206 | builder.append(replacement); | |
207 | builder.append(UString(string).substr(matchPosition + 1)); | |
208 | matchString = 0; | |
209 | } | |
210 | ||
211 | JSGlobalData* globalData = &exec->globalData(); | |
212 | return JSValue(new (globalData) JSString(globalData, builder.release())); | |
213 | } | |
214 | ||
215 | JSString* JSString::getIndexSlowCase(ExecState* exec, unsigned i) | |
216 | { | |
217 | ASSERT(isRope()); | |
218 | resolveRope(exec); | |
219 | // Return a safe no-value result, this should never be used, since the excetion will be thrown. | |
220 | if (exec->exception()) | |
221 | return jsString(exec, ""); | |
222 | ASSERT(!isRope()); | |
223 | ASSERT(i < m_value.size()); | |
224 | return jsSingleCharacterSubstring(exec, m_value, i); | |
225 | } | |
226 | ||
ba379fdc | 227 | JSValue JSString::toPrimitive(ExecState*, PreferredPrimitiveType) const |
9dae56ea A |
228 | { |
229 | return const_cast<JSString*>(this); | |
230 | } | |
231 | ||
f9bf01c6 | 232 | bool JSString::getPrimitiveNumber(ExecState* exec, double& number, JSValue& result) |
9dae56ea | 233 | { |
f9bf01c6 A |
234 | result = this; |
235 | number = value(exec).toDouble(); | |
9dae56ea A |
236 | return false; |
237 | } | |
238 | ||
239 | bool JSString::toBoolean(ExecState*) const | |
240 | { | |
4e4e5a6f | 241 | return m_length; |
9dae56ea A |
242 | } |
243 | ||
f9bf01c6 | 244 | double JSString::toNumber(ExecState* exec) const |
9dae56ea | 245 | { |
f9bf01c6 | 246 | return value(exec).toDouble(); |
9dae56ea A |
247 | } |
248 | ||
f9bf01c6 | 249 | UString JSString::toString(ExecState* exec) const |
9dae56ea | 250 | { |
f9bf01c6 | 251 | return value(exec); |
9dae56ea A |
252 | } |
253 | ||
9dae56ea A |
254 | inline StringObject* StringObject::create(ExecState* exec, JSString* string) |
255 | { | |
256 | return new (exec) StringObject(exec->lexicalGlobalObject()->stringObjectStructure(), string); | |
257 | } | |
258 | ||
259 | JSObject* JSString::toObject(ExecState* exec) const | |
260 | { | |
261 | return StringObject::create(exec, const_cast<JSString*>(this)); | |
262 | } | |
263 | ||
264 | JSObject* JSString::toThisObject(ExecState* exec) const | |
265 | { | |
266 | return StringObject::create(exec, const_cast<JSString*>(this)); | |
267 | } | |
268 | ||
269 | bool JSString::getOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot) | |
270 | { | |
271 | // The semantics here are really getPropertySlot, not getOwnPropertySlot. | |
272 | // This function should only be called by JSValue::get. | |
273 | if (getStringPropertySlot(exec, propertyName, slot)) | |
274 | return true; | |
ba379fdc A |
275 | if (propertyName == exec->propertyNames().underscoreProto) { |
276 | slot.setValue(exec->lexicalGlobalObject()->stringPrototype()); | |
277 | return true; | |
278 | } | |
9dae56ea A |
279 | slot.setBase(this); |
280 | JSObject* object; | |
ba379fdc | 281 | for (JSValue prototype = exec->lexicalGlobalObject()->stringPrototype(); !prototype.isNull(); prototype = object->prototype()) { |
9dae56ea A |
282 | object = asObject(prototype); |
283 | if (object->getOwnPropertySlot(exec, propertyName, slot)) | |
284 | return true; | |
285 | } | |
286 | slot.setUndefined(); | |
287 | return true; | |
288 | } | |
289 | ||
f9bf01c6 | 290 | bool JSString::getStringPropertyDescriptor(ExecState* exec, const Identifier& propertyName, PropertyDescriptor& descriptor) |
9dae56ea | 291 | { |
f9bf01c6 | 292 | if (propertyName == exec->propertyNames().length) { |
4e4e5a6f | 293 | descriptor.setDescriptor(jsNumber(exec, m_length), DontEnum | DontDelete | ReadOnly); |
9dae56ea | 294 | return true; |
9dae56ea | 295 | } |
9dae56ea | 296 | |
f9bf01c6 A |
297 | bool isStrictUInt32; |
298 | unsigned i = propertyName.toStrictUInt32(&isStrictUInt32); | |
4e4e5a6f A |
299 | if (isStrictUInt32 && i < m_length) { |
300 | descriptor.setDescriptor(getIndex(exec, i), DontDelete | ReadOnly); | |
f9bf01c6 | 301 | return true; |
9dae56ea | 302 | } |
f9bf01c6 A |
303 | |
304 | return false; | |
9dae56ea A |
305 | } |
306 | ||
f9bf01c6 | 307 | bool JSString::getOwnPropertyDescriptor(ExecState* exec, const Identifier& propertyName, PropertyDescriptor& descriptor) |
9dae56ea | 308 | { |
f9bf01c6 A |
309 | if (getStringPropertyDescriptor(exec, propertyName, descriptor)) |
310 | return true; | |
311 | if (propertyName != exec->propertyNames().underscoreProto) | |
312 | return false; | |
313 | descriptor.setDescriptor(exec->lexicalGlobalObject()->stringPrototype(), DontEnum); | |
314 | return true; | |
315 | } | |
316 | ||
317 | bool JSString::getOwnPropertySlot(ExecState* exec, unsigned propertyName, PropertySlot& slot) | |
318 | { | |
319 | // The semantics here are really getPropertySlot, not getOwnPropertySlot. | |
320 | // This function should only be called by JSValue::get. | |
321 | if (getStringPropertySlot(exec, propertyName, slot)) | |
322 | return true; | |
323 | return JSString::getOwnPropertySlot(exec, Identifier::from(exec, propertyName), slot); | |
9dae56ea A |
324 | } |
325 | ||
326 | } // namespace JSC |