Parent Directory
|
Revision Log
Use SpiderMonkey from Firefox 3.1b2.
1 | siliconforks | 332 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
2 | siliconforks | 399 | * vim: set ts=8 sw=4 et tw=99: |
3 | siliconforks | 332 | * |
4 | * ***** BEGIN LICENSE BLOCK ***** | ||
5 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 | ||
6 | * | ||
7 | * The contents of this file are subject to the Mozilla Public License Version | ||
8 | * 1.1 (the "License"); you may not use this file except in compliance with | ||
9 | * the License. You may obtain a copy of the License at | ||
10 | * http://www.mozilla.org/MPL/ | ||
11 | * | ||
12 | * Software distributed under the License is distributed on an "AS IS" basis, | ||
13 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License | ||
14 | * for the specific language governing rights and limitations under the | ||
15 | * License. | ||
16 | * | ||
17 | * The Original Code is Mozilla Communicator client code, released | ||
18 | * March 31, 1998. | ||
19 | * | ||
20 | * The Initial Developer of the Original Code is | ||
21 | * Netscape Communications Corporation. | ||
22 | * Portions created by the Initial Developer are Copyright (C) 1998 | ||
23 | * the Initial Developer. All Rights Reserved. | ||
24 | * | ||
25 | * Contributor(s): | ||
26 | * | ||
27 | * Alternatively, the contents of this file may be used under the terms of | ||
28 | * either of the GNU General Public License Version 2 or later (the "GPL"), | ||
29 | * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), | ||
30 | * in which case the provisions of the GPL or the LGPL are applicable instead | ||
31 | * of those above. If you wish to allow use of your version of this file only | ||
32 | * under the terms of either the GPL or the LGPL, and not to allow others to | ||
33 | * use your version of this file under the terms of the MPL, indicate your | ||
34 | * decision by deleting the provisions above and replace them with the notice | ||
35 | * and other provisions required by the GPL or the LGPL. If you do not delete | ||
36 | * the provisions above, a recipient may use your version of this file under | ||
37 | * the terms of any one of the MPL, the GPL or the LGPL. | ||
38 | * | ||
39 | * ***** END LICENSE BLOCK ***** */ | ||
40 | |||
41 | /* | ||
42 | * JS string type implementation. | ||
43 | * | ||
44 | * In order to avoid unnecessary js_LockGCThing/js_UnlockGCThing calls, these | ||
45 | * native methods store strings (possibly newborn) converted from their 'this' | ||
46 | * parameter and arguments on the stack: 'this' conversions at argv[-1], arg | ||
47 | * conversions at their index (argv[0], argv[1]). This is a legitimate method | ||
48 | * of rooting things that might lose their newborn root due to subsequent GC | ||
49 | * allocations in the same native method. | ||
50 | */ | ||
51 | #include "jsstddef.h" | ||
52 | #include <stdlib.h> | ||
53 | #include <string.h> | ||
54 | #include "jstypes.h" | ||
55 | #include "jsutil.h" /* Added by JSIFY */ | ||
56 | #include "jshash.h" /* Added by JSIFY */ | ||
57 | #include "jsprf.h" | ||
58 | #include "jsapi.h" | ||
59 | #include "jsarray.h" | ||
60 | #include "jsatom.h" | ||
61 | #include "jsbool.h" | ||
62 | siliconforks | 399 | #include "jsbuiltins.h" |
63 | siliconforks | 332 | #include "jscntxt.h" |
64 | #include "jsversion.h" | ||
65 | #include "jsgc.h" | ||
66 | #include "jsinterp.h" | ||
67 | #include "jslock.h" | ||
68 | #include "jsnum.h" | ||
69 | #include "jsobj.h" | ||
70 | #include "jsopcode.h" | ||
71 | #include "jsregexp.h" | ||
72 | #include "jsscope.h" | ||
73 | #include "jsstr.h" | ||
74 | #include "jsbit.h" | ||
75 | |||
76 | #define JSSTRDEP_RECURSION_LIMIT 100 | ||
77 | |||
78 | size_t | ||
79 | js_MinimizeDependentStrings(JSString *str, int level, JSString **basep) | ||
80 | { | ||
81 | JSString *base; | ||
82 | size_t start, length; | ||
83 | |||
84 | JS_ASSERT(JSSTRING_IS_DEPENDENT(str)); | ||
85 | base = JSSTRDEP_BASE(str); | ||
86 | start = JSSTRDEP_START(str); | ||
87 | if (JSSTRING_IS_DEPENDENT(base)) { | ||
88 | if (level < JSSTRDEP_RECURSION_LIMIT) { | ||
89 | start += js_MinimizeDependentStrings(base, level + 1, &base); | ||
90 | } else { | ||
91 | do { | ||
92 | start += JSSTRDEP_START(base); | ||
93 | base = JSSTRDEP_BASE(base); | ||
94 | } while (JSSTRING_IS_DEPENDENT(base)); | ||
95 | } | ||
96 | if (start == 0) { | ||
97 | JS_ASSERT(JSSTRDEP_IS_PREFIX(str)); | ||
98 | JSPREFIX_SET_BASE(str, base); | ||
99 | } else if (start <= JSSTRDEP_START_MASK) { | ||
100 | length = JSSTRDEP_LENGTH(str); | ||
101 | JSSTRDEP_INIT(str, base, start, length); | ||
102 | } | ||
103 | } | ||
104 | *basep = base; | ||
105 | return start; | ||
106 | } | ||
107 | |||
108 | jschar * | ||
109 | js_GetDependentStringChars(JSString *str) | ||
110 | { | ||
111 | size_t start; | ||
112 | JSString *base; | ||
113 | |||
114 | start = js_MinimizeDependentStrings(str, 0, &base); | ||
115 | JS_ASSERT(start < JSFLATSTR_LENGTH(base)); | ||
116 | return JSFLATSTR_CHARS(base) + start; | ||
117 | } | ||
118 | |||
119 | const jschar * | ||
120 | js_GetStringChars(JSContext *cx, JSString *str) | ||
121 | { | ||
122 | if (!js_MakeStringImmutable(cx, str)) | ||
123 | return NULL; | ||
124 | return JSFLATSTR_CHARS(str); | ||
125 | } | ||
126 | |||
127 | JSString * JS_FASTCALL | ||
128 | js_ConcatStrings(JSContext *cx, JSString *left, JSString *right) | ||
129 | { | ||
130 | size_t rn, ln, lrdist, n; | ||
131 | jschar *rs, *ls, *s; | ||
132 | JSString *ldep; /* non-null if left should become dependent */ | ||
133 | JSString *str; | ||
134 | |||
135 | JSSTRING_CHARS_AND_LENGTH(right, rs, rn); | ||
136 | if (rn == 0) | ||
137 | return left; | ||
138 | |||
139 | JSSTRING_CHARS_AND_LENGTH(left, ls, ln); | ||
140 | if (ln == 0) | ||
141 | return right; | ||
142 | |||
143 | if (!JSSTRING_IS_MUTABLE(left)) { | ||
144 | /* We must copy if left does not own a buffer to realloc. */ | ||
145 | s = (jschar *) JS_malloc(cx, (ln + rn + 1) * sizeof(jschar)); | ||
146 | if (!s) | ||
147 | return NULL; | ||
148 | js_strncpy(s, ls, ln); | ||
149 | ldep = NULL; | ||
150 | } else { | ||
151 | /* We can realloc left's space and make it depend on our result. */ | ||
152 | JS_ASSERT(JSSTRING_IS_FLAT(left)); | ||
153 | s = (jschar *) JS_realloc(cx, ls, (ln + rn + 1) * sizeof(jschar)); | ||
154 | if (!s) | ||
155 | return NULL; | ||
156 | |||
157 | /* Take care: right could depend on left! */ | ||
158 | lrdist = (size_t)(rs - ls); | ||
159 | if (lrdist < ln) | ||
160 | rs = s + lrdist; | ||
161 | left->u.chars = ls = s; | ||
162 | ldep = left; | ||
163 | } | ||
164 | |||
165 | js_strncpy(s + ln, rs, rn); | ||
166 | n = ln + rn; | ||
167 | s[n] = 0; | ||
168 | str = js_NewString(cx, s, n); | ||
169 | if (!str) { | ||
170 | /* Out of memory: clean up any space we (re-)allocated. */ | ||
171 | if (!ldep) { | ||
172 | JS_free(cx, s); | ||
173 | } else { | ||
174 | s = (jschar *) JS_realloc(cx, ls, (ln + 1) * sizeof(jschar)); | ||
175 | if (s) | ||
176 | left->u.chars = s; | ||
177 | } | ||
178 | } else { | ||
179 | JSFLATSTR_SET_MUTABLE(str); | ||
180 | |||
181 | /* Morph left into a dependent prefix if we realloc'd its buffer. */ | ||
182 | if (ldep) { | ||
183 | JSPREFIX_INIT(ldep, str, ln); | ||
184 | #ifdef DEBUG | ||
185 | { | ||
186 | JSRuntime *rt = cx->runtime; | ||
187 | JS_RUNTIME_METER(rt, liveDependentStrings); | ||
188 | JS_RUNTIME_METER(rt, totalDependentStrings); | ||
189 | JS_LOCK_RUNTIME_VOID(rt, | ||
190 | (rt->strdepLengthSum += (double)ln, | ||
191 | rt->strdepLengthSquaredSum += (double)ln * (double)ln)); | ||
192 | } | ||
193 | #endif | ||
194 | } | ||
195 | } | ||
196 | |||
197 | return str; | ||
198 | } | ||
199 | |||
200 | const jschar * | ||
201 | js_UndependString(JSContext *cx, JSString *str) | ||
202 | { | ||
203 | size_t n, size; | ||
204 | jschar *s; | ||
205 | |||
206 | if (JSSTRING_IS_DEPENDENT(str)) { | ||
207 | n = JSSTRDEP_LENGTH(str); | ||
208 | size = (n + 1) * sizeof(jschar); | ||
209 | s = (jschar *) JS_malloc(cx, size); | ||
210 | if (!s) | ||
211 | return NULL; | ||
212 | |||
213 | js_strncpy(s, JSSTRDEP_CHARS(str), n); | ||
214 | s[n] = 0; | ||
215 | JSFLATSTR_INIT(str, s, n); | ||
216 | |||
217 | #ifdef DEBUG | ||
218 | { | ||
219 | JSRuntime *rt = cx->runtime; | ||
220 | JS_RUNTIME_UNMETER(rt, liveDependentStrings); | ||
221 | JS_RUNTIME_UNMETER(rt, totalDependentStrings); | ||
222 | JS_LOCK_RUNTIME_VOID(rt, | ||
223 | (rt->strdepLengthSum -= (double)n, | ||
224 | rt->strdepLengthSquaredSum -= (double)n * (double)n)); | ||
225 | } | ||
226 | #endif | ||
227 | } | ||
228 | |||
229 | return JSFLATSTR_CHARS(str); | ||
230 | } | ||
231 | |||
232 | JSBool | ||
233 | js_MakeStringImmutable(JSContext *cx, JSString *str) | ||
234 | { | ||
235 | if (JSSTRING_IS_DEPENDENT(str) && !js_UndependString(cx, str)) { | ||
236 | JS_RUNTIME_METER(cx->runtime, badUndependStrings); | ||
237 | return JS_FALSE; | ||
238 | } | ||
239 | JSFLATSTR_CLEAR_MUTABLE(str); | ||
240 | return JS_TRUE; | ||
241 | } | ||
242 | |||
243 | static JSString * | ||
244 | ArgToRootedString(JSContext *cx, uintN argc, jsval *vp, uintN arg) | ||
245 | { | ||
246 | JSObject *obj; | ||
247 | JSString *str; | ||
248 | |||
249 | if (arg >= argc) | ||
250 | return ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]); | ||
251 | vp += 2 + arg; | ||
252 | |||
253 | if (JSVAL_IS_OBJECT(*vp)) { | ||
254 | obj = JSVAL_TO_OBJECT(*vp); | ||
255 | if (!obj) | ||
256 | return ATOM_TO_STRING(cx->runtime->atomState.nullAtom); | ||
257 | if (!OBJ_DEFAULT_VALUE(cx, obj, JSTYPE_STRING, vp)) | ||
258 | return NULL; | ||
259 | } | ||
260 | if (JSVAL_IS_STRING(*vp)) | ||
261 | return JSVAL_TO_STRING(*vp); | ||
262 | if (JSVAL_IS_INT(*vp)) { | ||
263 | str = js_NumberToString(cx, JSVAL_TO_INT(*vp)); | ||
264 | } else if (JSVAL_IS_DOUBLE(*vp)) { | ||
265 | str = js_NumberToString(cx, *JSVAL_TO_DOUBLE(*vp)); | ||
266 | } else if (JSVAL_IS_BOOLEAN(*vp)) { | ||
267 | return ATOM_TO_STRING(cx->runtime->atomState.booleanAtoms[ | ||
268 | JSVAL_TO_BOOLEAN(*vp)? 1 : 0]); | ||
269 | } else { | ||
270 | JS_ASSERT(JSVAL_IS_VOID(*vp)); | ||
271 | return ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]); | ||
272 | } | ||
273 | if (str) | ||
274 | *vp = STRING_TO_JSVAL(str); | ||
275 | return str; | ||
276 | } | ||
277 | |||
278 | /* | ||
279 | * Forward declarations for URI encode/decode and helper routines | ||
280 | */ | ||
281 | static JSBool | ||
282 | str_decodeURI(JSContext *cx, uintN argc, jsval *vp); | ||
283 | |||
284 | static JSBool | ||
285 | str_decodeURI_Component(JSContext *cx, uintN argc, jsval *vp); | ||
286 | |||
287 | static JSBool | ||
288 | str_encodeURI(JSContext *cx, uintN argc, jsval *vp); | ||
289 | |||
290 | static JSBool | ||
291 | str_encodeURI_Component(JSContext *cx, uintN argc, jsval *vp); | ||
292 | |||
293 | static uint32 | ||
294 | Utf8ToOneUcs4Char(const uint8 *utf8Buffer, int utf8Length); | ||
295 | |||
296 | /* | ||
297 | * Contributions from the String class to the set of methods defined for the | ||
298 | * global object. escape and unescape used to be defined in the Mocha library, | ||
299 | * but as ECMA decided to spec them, they've been moved to the core engine | ||
300 | * and made ECMA-compliant. (Incomplete escapes are interpreted as literal | ||
301 | * characters by unescape.) | ||
302 | */ | ||
303 | |||
304 | /* | ||
305 | * Stuff to emulate the old libmocha escape, which took a second argument | ||
306 | * giving the type of escape to perform. Retained for compatibility, and | ||
307 | * copied here to avoid reliance on net.h, mkparse.c/NET_EscapeBytes. | ||
308 | */ | ||
309 | |||
310 | #define URL_XALPHAS ((uint8) 1) | ||
311 | #define URL_XPALPHAS ((uint8) 2) | ||
312 | #define URL_PATH ((uint8) 4) | ||
313 | |||
314 | static const uint8 urlCharType[256] = | ||
315 | /* Bit 0 xalpha -- the alphas | ||
316 | * Bit 1 xpalpha -- as xalpha but | ||
317 | * converts spaces to plus and plus to %20 | ||
318 | * Bit 2 ... path -- as xalphas but doesn't escape '/' | ||
319 | */ | ||
320 | /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ | ||
321 | { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0x */ | ||
322 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 1x */ | ||
323 | 0,0,0,0,0,0,0,0,0,0,7,4,0,7,7,4, /* 2x !"#$%&'()*+,-./ */ | ||
324 | 7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0, /* 3x 0123456789:;<=>? */ | ||
325 | 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* 4x @ABCDEFGHIJKLMNO */ | ||
326 | 7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,7, /* 5X PQRSTUVWXYZ[\]^_ */ | ||
327 | 0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* 6x `abcdefghijklmno */ | ||
328 | 7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,0, /* 7X pqrstuvwxyz{\}~ DEL */ | ||
329 | 0, }; | ||
330 | |||
331 | /* This matches the ECMA escape set when mask is 7 (default.) */ | ||
332 | |||
333 | #define IS_OK(C, mask) (urlCharType[((uint8) (C))] & (mask)) | ||
334 | |||
335 | /* See ECMA-262 Edition 3 B.2.1 */ | ||
336 | JSBool | ||
337 | js_str_escape(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) | ||
338 | { | ||
339 | JSString *str; | ||
340 | size_t i, ni, length, newlength; | ||
341 | const jschar *chars; | ||
342 | jschar *newchars; | ||
343 | jschar ch; | ||
344 | jsint mask; | ||
345 | jsdouble d; | ||
346 | const char digits[] = {'0', '1', '2', '3', '4', '5', '6', '7', | ||
347 | '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; | ||
348 | |||
349 | mask = URL_XALPHAS | URL_XPALPHAS | URL_PATH; | ||
350 | if (argc > 1) { | ||
351 | d = js_ValueToNumber(cx, &argv[1]); | ||
352 | if (JSVAL_IS_NULL(argv[1])) | ||
353 | return JS_FALSE; | ||
354 | if (!JSDOUBLE_IS_FINITE(d) || | ||
355 | (mask = (jsint)d) != d || | ||
356 | mask & ~(URL_XALPHAS | URL_XPALPHAS | URL_PATH)) | ||
357 | { | ||
358 | char numBuf[12]; | ||
359 | JS_snprintf(numBuf, sizeof numBuf, "%lx", (unsigned long) mask); | ||
360 | JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, | ||
361 | JSMSG_BAD_STRING_MASK, numBuf); | ||
362 | return JS_FALSE; | ||
363 | } | ||
364 | } | ||
365 | |||
366 | str = ArgToRootedString(cx, argc, argv - 2, 0); | ||
367 | if (!str) | ||
368 | return JS_FALSE; | ||
369 | |||
370 | JSSTRING_CHARS_AND_LENGTH(str, chars, length); | ||
371 | newlength = length; | ||
372 | |||
373 | /* Take a first pass and see how big the result string will need to be. */ | ||
374 | for (i = 0; i < length; i++) { | ||
375 | if ((ch = chars[i]) < 128 && IS_OK(ch, mask)) | ||
376 | continue; | ||
377 | if (ch < 256) { | ||
378 | if (mask == URL_XPALPHAS && ch == ' ') | ||
379 | continue; /* The character will be encoded as '+' */ | ||
380 | newlength += 2; /* The character will be encoded as %XX */ | ||
381 | } else { | ||
382 | newlength += 5; /* The character will be encoded as %uXXXX */ | ||
383 | } | ||
384 | |||
385 | /* | ||
386 | * This overflow test works because newlength is incremented by at | ||
387 | * most 5 on each iteration. | ||
388 | */ | ||
389 | if (newlength < length) { | ||
390 | js_ReportAllocationOverflow(cx); | ||
391 | return JS_FALSE; | ||
392 | } | ||
393 | } | ||
394 | |||
395 | if (newlength >= ~(size_t)0 / sizeof(jschar)) { | ||
396 | js_ReportAllocationOverflow(cx); | ||
397 | return JS_FALSE; | ||
398 | } | ||
399 | |||
400 | newchars = (jschar *) JS_malloc(cx, (newlength + 1) * sizeof(jschar)); | ||
401 | if (!newchars) | ||
402 | return JS_FALSE; | ||
403 | for (i = 0, ni = 0; i < length; i++) { | ||
404 | if ((ch = chars[i]) < 128 && IS_OK(ch, mask)) { | ||
405 | newchars[ni++] = ch; | ||
406 | } else if (ch < 256) { | ||
407 | if (mask == URL_XPALPHAS && ch == ' ') { | ||
408 | newchars[ni++] = '+'; /* convert spaces to pluses */ | ||
409 | } else { | ||
410 | newchars[ni++] = '%'; | ||
411 | newchars[ni++] = digits[ch >> 4]; | ||
412 | newchars[ni++] = digits[ch & 0xF]; | ||
413 | } | ||
414 | } else { | ||
415 | newchars[ni++] = '%'; | ||
416 | newchars[ni++] = 'u'; | ||
417 | newchars[ni++] = digits[ch >> 12]; | ||
418 | newchars[ni++] = digits[(ch & 0xF00) >> 8]; | ||
419 | newchars[ni++] = digits[(ch & 0xF0) >> 4]; | ||
420 | newchars[ni++] = digits[ch & 0xF]; | ||
421 | } | ||
422 | } | ||
423 | JS_ASSERT(ni == newlength); | ||
424 | newchars[newlength] = 0; | ||
425 | |||
426 | str = js_NewString(cx, newchars, newlength); | ||
427 | if (!str) { | ||
428 | JS_free(cx, newchars); | ||
429 | return JS_FALSE; | ||
430 | } | ||
431 | *rval = STRING_TO_JSVAL(str); | ||
432 | return JS_TRUE; | ||
433 | } | ||
434 | #undef IS_OK | ||
435 | |||
436 | static JSBool | ||
437 | str_escape(JSContext *cx, uintN argc, jsval *vp) | ||
438 | { | ||
439 | JSObject *obj; | ||
440 | |||
441 | obj = JS_THIS_OBJECT(cx, vp); | ||
442 | return obj && js_str_escape(cx, obj, argc, vp + 2, vp); | ||
443 | } | ||
444 | |||
445 | /* See ECMA-262 Edition 3 B.2.2 */ | ||
446 | static JSBool | ||
447 | str_unescape(JSContext *cx, uintN argc, jsval *vp) | ||
448 | { | ||
449 | JSString *str; | ||
450 | size_t i, ni, length; | ||
451 | const jschar *chars; | ||
452 | jschar *newchars; | ||
453 | jschar ch; | ||
454 | |||
455 | str = ArgToRootedString(cx, argc, vp, 0); | ||
456 | if (!str) | ||
457 | return JS_FALSE; | ||
458 | |||
459 | JSSTRING_CHARS_AND_LENGTH(str, chars, length); | ||
460 | |||
461 | /* Don't bother allocating less space for the new string. */ | ||
462 | newchars = (jschar *) JS_malloc(cx, (length + 1) * sizeof(jschar)); | ||
463 | if (!newchars) | ||
464 | return JS_FALSE; | ||
465 | ni = i = 0; | ||
466 | while (i < length) { | ||
467 | ch = chars[i++]; | ||
468 | if (ch == '%') { | ||
469 | if (i + 1 < length && | ||
470 | JS7_ISHEX(chars[i]) && JS7_ISHEX(chars[i + 1])) | ||
471 | { | ||
472 | ch = JS7_UNHEX(chars[i]) * 16 + JS7_UNHEX(chars[i + 1]); | ||
473 | i += 2; | ||
474 | } else if (i + 4 < length && chars[i] == 'u' && | ||
475 | JS7_ISHEX(chars[i + 1]) && JS7_ISHEX(chars[i + 2]) && | ||
476 | JS7_ISHEX(chars[i + 3]) && JS7_ISHEX(chars[i + 4])) | ||
477 | { | ||
478 | ch = (((((JS7_UNHEX(chars[i + 1]) << 4) | ||
479 | + JS7_UNHEX(chars[i + 2])) << 4) | ||
480 | + JS7_UNHEX(chars[i + 3])) << 4) | ||
481 | + JS7_UNHEX(chars[i + 4]); | ||
482 | i += 5; | ||
483 | } | ||
484 | } | ||
485 | newchars[ni++] = ch; | ||
486 | } | ||
487 | newchars[ni] = 0; | ||
488 | |||
489 | str = js_NewString(cx, newchars, ni); | ||
490 | if (!str) { | ||
491 | JS_free(cx, newchars); | ||
492 | return JS_FALSE; | ||
493 | } | ||
494 | *vp = STRING_TO_JSVAL(str); | ||
495 | return JS_TRUE; | ||
496 | } | ||
497 | |||
498 | #if JS_HAS_UNEVAL | ||
499 | static JSBool | ||
500 | str_uneval(JSContext *cx, uintN argc, jsval *vp) | ||
501 | { | ||
502 | JSString *str; | ||
503 | |||
504 | str = js_ValueToSource(cx, argc != 0 ? vp[2] : JSVAL_VOID); | ||
505 | if (!str) | ||
506 | return JS_FALSE; | ||
507 | *vp = STRING_TO_JSVAL(str); | ||
508 | return JS_TRUE; | ||
509 | } | ||
510 | #endif | ||
511 | |||
512 | const char js_escape_str[] = "escape"; | ||
513 | const char js_unescape_str[] = "unescape"; | ||
514 | #if JS_HAS_UNEVAL | ||
515 | const char js_uneval_str[] = "uneval"; | ||
516 | #endif | ||
517 | const char js_decodeURI_str[] = "decodeURI"; | ||
518 | const char js_encodeURI_str[] = "encodeURI"; | ||
519 | const char js_decodeURIComponent_str[] = "decodeURIComponent"; | ||
520 | const char js_encodeURIComponent_str[] = "encodeURIComponent"; | ||
521 | |||
522 | static JSFunctionSpec string_functions[] = { | ||
523 | JS_FN(js_escape_str, str_escape, 1,0), | ||
524 | JS_FN(js_unescape_str, str_unescape, 1,0), | ||
525 | #if JS_HAS_UNEVAL | ||
526 | JS_FN(js_uneval_str, str_uneval, 1,0), | ||
527 | #endif | ||
528 | JS_FN(js_decodeURI_str, str_decodeURI, 1,0), | ||
529 | JS_FN(js_encodeURI_str, str_encodeURI, 1,0), | ||
530 | JS_FN(js_decodeURIComponent_str, str_decodeURI_Component, 1,0), | ||
531 | JS_FN(js_encodeURIComponent_str, str_encodeURI_Component, 1,0), | ||
532 | |||
533 | JS_FS_END | ||
534 | }; | ||
535 | |||
536 | jschar js_empty_ucstr[] = {0}; | ||
537 | JSSubString js_EmptySubString = {0, js_empty_ucstr}; | ||
538 | |||
539 | enum string_tinyid { | ||
540 | STRING_LENGTH = -1 | ||
541 | }; | ||
542 | |||
543 | static JSPropertySpec string_props[] = { | ||
544 | {js_length_str, STRING_LENGTH, | ||
545 | JSPROP_READONLY|JSPROP_PERMANENT|JSPROP_SHARED, 0,0}, | ||
546 | {0,0,0,0,0} | ||
547 | }; | ||
548 | |||
549 | static JSBool | ||
550 | str_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) | ||
551 | { | ||
552 | jsval v; | ||
553 | JSString *str; | ||
554 | jsint slot; | ||
555 | |||
556 | if (!JSVAL_IS_INT(id)) | ||
557 | return JS_TRUE; | ||
558 | |||
559 | slot = JSVAL_TO_INT(id); | ||
560 | if (slot == STRING_LENGTH) { | ||
561 | if (OBJ_GET_CLASS(cx, obj) == &js_StringClass) { | ||
562 | /* Follow ECMA-262 by fetching intrinsic length of our string. */ | ||
563 | v = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE); | ||
564 | JS_ASSERT(JSVAL_IS_STRING(v)); | ||
565 | str = JSVAL_TO_STRING(v); | ||
566 | } else { | ||
567 | /* Preserve compatibility: convert obj to a string primitive. */ | ||
568 | str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj)); | ||
569 | if (!str) | ||
570 | return JS_FALSE; | ||
571 | } | ||
572 | |||
573 | *vp = INT_TO_JSVAL((jsint) JSSTRING_LENGTH(str)); | ||
574 | } | ||
575 | return JS_TRUE; | ||
576 | } | ||
577 | |||
578 | #define STRING_ELEMENT_ATTRS (JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT) | ||
579 | |||
580 | static JSBool | ||
581 | str_enumerate(JSContext *cx, JSObject *obj) | ||
582 | { | ||
583 | jsval v; | ||
584 | JSString *str, *str1; | ||
585 | size_t i, length; | ||
586 | |||
587 | v = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE); | ||
588 | JS_ASSERT(JSVAL_IS_STRING(v)); | ||
589 | str = JSVAL_TO_STRING(v); | ||
590 | |||
591 | length = JSSTRING_LENGTH(str); | ||
592 | for (i = 0; i < length; i++) { | ||
593 | str1 = js_NewDependentString(cx, str, i, 1); | ||
594 | if (!str1) | ||
595 | return JS_FALSE; | ||
596 | if (!OBJ_DEFINE_PROPERTY(cx, obj, INT_TO_JSID(i), | ||
597 | STRING_TO_JSVAL(str1), NULL, NULL, | ||
598 | STRING_ELEMENT_ATTRS, NULL)) { | ||
599 | return JS_FALSE; | ||
600 | } | ||
601 | } | ||
602 | return JS_TRUE; | ||
603 | } | ||
604 | |||
605 | static JSBool | ||
606 | str_resolve(JSContext *cx, JSObject *obj, jsval id, uintN flags, | ||
607 | JSObject **objp) | ||
608 | { | ||
609 | jsval v; | ||
610 | JSString *str, *str1; | ||
611 | jsint slot; | ||
612 | |||
613 | if (!JSVAL_IS_INT(id) || (flags & JSRESOLVE_ASSIGNING)) | ||
614 | return JS_TRUE; | ||
615 | |||
616 | v = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE); | ||
617 | JS_ASSERT(JSVAL_IS_STRING(v)); | ||
618 | str = JSVAL_TO_STRING(v); | ||
619 | |||
620 | slot = JSVAL_TO_INT(id); | ||
621 | if ((size_t)slot < JSSTRING_LENGTH(str)) { | ||
622 | str1 = js_GetUnitString(cx, str, (size_t)slot); | ||
623 | if (!str1) | ||
624 | return JS_FALSE; | ||
625 | if (!OBJ_DEFINE_PROPERTY(cx, obj, INT_TO_JSID(slot), | ||
626 | STRING_TO_JSVAL(str1), NULL, NULL, | ||
627 | STRING_ELEMENT_ATTRS, NULL)) { | ||
628 | return JS_FALSE; | ||
629 | } | ||
630 | *objp = obj; | ||
631 | } | ||
632 | return JS_TRUE; | ||
633 | } | ||
634 | |||
635 | JSClass js_StringClass = { | ||
636 | js_String_str, | ||
637 | JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE | | ||
638 | JSCLASS_HAS_CACHED_PROTO(JSProto_String), | ||
639 | JS_PropertyStub, JS_PropertyStub, str_getProperty, JS_PropertyStub, | ||
640 | str_enumerate, (JSResolveOp)str_resolve, JS_ConvertStub, JS_FinalizeStub, | ||
641 | JSCLASS_NO_OPTIONAL_MEMBERS | ||
642 | }; | ||
643 | |||
644 | #define NORMALIZE_THIS(cx,vp,str) \ | ||
645 | JS_BEGIN_MACRO \ | ||
646 | if (JSVAL_IS_STRING(vp[1])) { \ | ||
647 | str = JSVAL_TO_STRING(vp[1]); \ | ||
648 | } else { \ | ||
649 | str = NormalizeThis(cx, vp); \ | ||
650 | if (!str) \ | ||
651 | return JS_FALSE; \ | ||
652 | } \ | ||
653 | JS_END_MACRO | ||
654 | |||
655 | static JSString * | ||
656 | NormalizeThis(JSContext *cx, jsval *vp) | ||
657 | { | ||
658 | JSString *str; | ||
659 | |||
660 | if (JSVAL_IS_NULL(vp[1]) && JSVAL_IS_NULL(JS_THIS(cx, vp))) | ||
661 | return NULL; | ||
662 | str = js_ValueToString(cx, vp[1]); | ||
663 | if (!str) | ||
664 | return NULL; | ||
665 | vp[1] = STRING_TO_JSVAL(str); | ||
666 | return str; | ||
667 | } | ||
668 | |||
669 | #if JS_HAS_TOSOURCE | ||
670 | |||
671 | /* | ||
672 | * String.prototype.quote is generic (as are most string methods), unlike | ||
673 | * toSource, toString, and valueOf. | ||
674 | */ | ||
675 | static JSBool | ||
676 | str_quote(JSContext *cx, uintN argc, jsval *vp) | ||
677 | { | ||
678 | JSString *str; | ||
679 | |||
680 | NORMALIZE_THIS(cx, vp, str); | ||
681 | str = js_QuoteString(cx, str, '"'); | ||
682 | if (!str) | ||
683 | return JS_FALSE; | ||
684 | *vp = STRING_TO_JSVAL(str); | ||
685 | return JS_TRUE; | ||
686 | } | ||
687 | |||
688 | static JSBool | ||
689 | str_toSource(JSContext *cx, uintN argc, jsval *vp) | ||
690 | { | ||
691 | jsval v; | ||
692 | JSString *str; | ||
693 | size_t i, j, k, n; | ||
694 | char buf[16]; | ||
695 | jschar *s, *t; | ||
696 | |||
697 | if (!js_GetPrimitiveThis(cx, vp, &js_StringClass, &v)) | ||
698 | return JS_FALSE; | ||
699 | JS_ASSERT(JSVAL_IS_STRING(v)); | ||
700 | str = js_QuoteString(cx, JSVAL_TO_STRING(v), '"'); | ||
701 | if (!str) | ||
702 | return JS_FALSE; | ||
703 | j = JS_snprintf(buf, sizeof buf, "(new %s(", js_StringClass.name); | ||
704 | JSSTRING_CHARS_AND_LENGTH(str, s, k); | ||
705 | n = j + k + 2; | ||
706 | t = (jschar *) JS_malloc(cx, (n + 1) * sizeof(jschar)); | ||
707 | if (!t) | ||
708 | return JS_FALSE; | ||
709 | for (i = 0; i < j; i++) | ||
710 | t[i] = buf[i]; | ||
711 | for (j = 0; j < k; i++, j++) | ||
712 | t[i] = s[j]; | ||
713 | t[i++] = ')'; | ||
714 | t[i++] = ')'; | ||
715 | t[i] = 0; | ||
716 | str = js_NewString(cx, t, n); | ||
717 | if (!str) { | ||
718 | JS_free(cx, t); | ||
719 | return JS_FALSE; | ||
720 | } | ||
721 | *vp = STRING_TO_JSVAL(str); | ||
722 | return JS_TRUE; | ||
723 | } | ||
724 | |||
725 | #endif /* JS_HAS_TOSOURCE */ | ||
726 | |||
727 | static JSBool | ||
728 | str_toString(JSContext *cx, uintN argc, jsval *vp) | ||
729 | { | ||
730 | return js_GetPrimitiveThis(cx, vp, &js_StringClass, vp); | ||
731 | } | ||
732 | |||
733 | /* | ||
734 | * Java-like string native methods. | ||
735 | */ | ||
736 | siliconforks | 399 | |
737 | static JSString * | ||
738 | SubstringTail(JSContext *cx, JSString *str, jsdouble length, jsdouble begin, jsdouble end) | ||
739 | siliconforks | 332 | { |
740 | siliconforks | 399 | if (begin < 0) |
741 | begin = 0; | ||
742 | else if (begin > length) | ||
743 | begin = length; | ||
744 | |||
745 | if (end < 0) | ||
746 | end = 0; | ||
747 | else if (end > length) | ||
748 | end = length; | ||
749 | if (end < begin) { | ||
750 | /* ECMA emulates old JDK1.0 java.lang.String.substring. */ | ||
751 | jsdouble tmp = begin; | ||
752 | begin = end; | ||
753 | end = tmp; | ||
754 | } | ||
755 | |||
756 | return js_NewDependentString(cx, str, (size_t)begin, (size_t)(end - begin)); | ||
757 | } | ||
758 | |||
759 | static JSBool | ||
760 | str_substring(JSContext *cx, uintN argc, jsval *vp) | ||
761 | { | ||
762 | siliconforks | 332 | JSString *str; |
763 | jsdouble d; | ||
764 | jsdouble length, begin, end; | ||
765 | |||
766 | NORMALIZE_THIS(cx, vp, str); | ||
767 | if (argc != 0) { | ||
768 | d = js_ValueToNumber(cx, &vp[2]); | ||
769 | if (JSVAL_IS_NULL(vp[2])) | ||
770 | return JS_FALSE; | ||
771 | length = JSSTRING_LENGTH(str); | ||
772 | begin = js_DoubleToInteger(d); | ||
773 | if (argc == 1) { | ||
774 | end = length; | ||
775 | } else { | ||
776 | d = js_ValueToNumber(cx, &vp[3]); | ||
777 | if (JSVAL_IS_NULL(vp[3])) | ||
778 | return JS_FALSE; | ||
779 | end = js_DoubleToInteger(d); | ||
780 | } | ||
781 | |||
782 | siliconforks | 399 | str = SubstringTail(cx, str, length, begin, end); |
783 | siliconforks | 332 | if (!str) |
784 | return JS_FALSE; | ||
785 | } | ||
786 | *vp = STRING_TO_JSVAL(str); | ||
787 | return JS_TRUE; | ||
788 | } | ||
789 | |||
790 | siliconforks | 399 | #ifdef JS_TRACER |
791 | static JSString* FASTCALL | ||
792 | String_p_toString(JSContext* cx, JSObject* obj) | ||
793 | { | ||
794 | if (!JS_InstanceOf(cx, obj, &js_StringClass, NULL)) | ||
795 | return NULL; | ||
796 | jsval v = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE); | ||
797 | JS_ASSERT(JSVAL_IS_STRING(v)); | ||
798 | return JSVAL_TO_STRING(v); | ||
799 | } | ||
800 | |||
801 | static JSString* FASTCALL | ||
802 | String_p_substring(JSContext* cx, JSString* str, int32 begin, int32 end) | ||
803 | { | ||
804 | JS_ASSERT(JS_ON_TRACE(cx)); | ||
805 | |||
806 | size_t length = JSSTRING_LENGTH(str); | ||
807 | return SubstringTail(cx, str, length, begin, end); | ||
808 | } | ||
809 | |||
810 | static JSString* FASTCALL | ||
811 | String_p_substring_1(JSContext* cx, JSString* str, int32 begin) | ||
812 | { | ||
813 | JS_ASSERT(JS_ON_TRACE(cx)); | ||
814 | |||
815 | size_t length = JSSTRING_LENGTH(str); | ||
816 | return SubstringTail(cx, str, length, begin, length); | ||
817 | } | ||
818 | #endif | ||
819 | |||
820 | siliconforks | 332 | JSString* JS_FASTCALL |
821 | js_toLowerCase(JSContext *cx, JSString *str) | ||
822 | { | ||
823 | size_t i, n; | ||
824 | jschar *s, *news; | ||
825 | |||
826 | JSSTRING_CHARS_AND_LENGTH(str, s, n); | ||
827 | news = (jschar *) JS_malloc(cx, (n + 1) * sizeof(jschar)); | ||
828 | if (!news) | ||
829 | return NULL; | ||
830 | for (i = 0; i < n; i++) | ||
831 | news[i] = JS_TOLOWER(s[i]); | ||
832 | news[n] = 0; | ||
833 | str = js_NewString(cx, news, n); | ||
834 | if (!str) { | ||
835 | JS_free(cx, news); | ||
836 | return NULL; | ||
837 | } | ||
838 | return str; | ||
839 | } | ||
840 | |||
841 | siliconforks | 399 | static JSBool |
842 | str_toLowerCase(JSContext *cx, uintN argc, jsval *vp) | ||
843 | siliconforks | 332 | { |
844 | JSString *str; | ||
845 | |||
846 | NORMALIZE_THIS(cx, vp, str); | ||
847 | str = js_toLowerCase(cx, str); | ||
848 | if (!str) | ||
849 | return JS_FALSE; | ||
850 | *vp = STRING_TO_JSVAL(str); | ||
851 | return JS_TRUE; | ||
852 | } | ||
853 | |||
854 | static JSBool | ||
855 | str_toLocaleLowerCase(JSContext *cx, uintN argc, jsval *vp) | ||
856 | { | ||
857 | JSString *str; | ||
858 | |||
859 | /* | ||
860 | * Forcefully ignore the first (or any) argument and return toLowerCase(), | ||
861 | * ECMA has reserved that argument, presumably for defining the locale. | ||
862 | */ | ||
863 | if (cx->localeCallbacks && cx->localeCallbacks->localeToLowerCase) { | ||
864 | NORMALIZE_THIS(cx, vp, str); | ||
865 | return cx->localeCallbacks->localeToLowerCase(cx, str, vp); | ||
866 | } | ||
867 | siliconforks | 399 | return str_toLowerCase(cx, 0, vp); |
868 | siliconforks | 332 | } |
869 | |||
870 | JSString* JS_FASTCALL | ||
871 | js_toUpperCase(JSContext *cx, JSString *str) | ||
872 | { | ||
873 | size_t i, n; | ||
874 | jschar *s, *news; | ||
875 | |||
876 | JSSTRING_CHARS_AND_LENGTH(str, s, n); | ||
877 | news = (jschar *) JS_malloc(cx, (n + 1) * sizeof(jschar)); | ||
878 | if (!news) | ||
879 | return NULL; | ||
880 | for (i = 0; i < n; i++) | ||
881 | news[i] = JS_TOUPPER(s[i]); | ||
882 | news[n] = 0; | ||
883 | str = js_NewString(cx, news, n); | ||
884 | if (!str) { | ||
885 | JS_free(cx, news); | ||
886 | return NULL; | ||
887 | } | ||
888 | return str; | ||
889 | } | ||
890 | |||
891 | siliconforks | 399 | static JSBool |
892 | str_toUpperCase(JSContext *cx, uintN argc, jsval *vp) | ||
893 | siliconforks | 332 | { |
894 | JSString *str; | ||
895 | |||
896 | NORMALIZE_THIS(cx, vp, str); | ||
897 | str = js_toUpperCase(cx, str); | ||
898 | if (!str) | ||
899 | return JS_FALSE; | ||
900 | *vp = STRING_TO_JSVAL(str); | ||
901 | return JS_TRUE; | ||
902 | } | ||
903 | |||
904 | static JSBool | ||
905 | str_toLocaleUpperCase(JSContext *cx, uintN argc, jsval *vp) | ||
906 | { | ||
907 | JSString *str; | ||
908 | |||
909 | /* | ||
910 | * Forcefully ignore the first (or any) argument and return toUpperCase(), | ||
911 | * ECMA has reserved that argument, presumably for defining the locale. | ||
912 | */ | ||
913 | if (cx->localeCallbacks && cx->localeCallbacks->localeToUpperCase) { | ||
914 | NORMALIZE_THIS(cx, vp, str); | ||
915 | return cx->localeCallbacks->localeToUpperCase(cx, str, vp); | ||
916 | } | ||
917 | siliconforks | 399 | return str_toUpperCase(cx, 0, vp); |
918 | siliconforks | 332 | } |
919 | |||
920 | static JSBool | ||
921 | str_localeCompare(JSContext *cx, uintN argc, jsval *vp) | ||
922 | { | ||
923 | JSString *str, *thatStr; | ||
924 | |||
925 | NORMALIZE_THIS(cx, vp, str); | ||
926 | if (argc == 0) { | ||
927 | *vp = JSVAL_ZERO; | ||
928 | } else { | ||
929 | thatStr = js_ValueToString(cx, vp[2]); | ||
930 | if (!thatStr) | ||
931 | return JS_FALSE; | ||
932 | if (cx->localeCallbacks && cx->localeCallbacks->localeCompare) { | ||
933 | vp[2] = STRING_TO_JSVAL(thatStr); | ||
934 | return cx->localeCallbacks->localeCompare(cx, str, thatStr, vp); | ||
935 | } | ||
936 | *vp = INT_TO_JSVAL(js_CompareStrings(str, thatStr)); | ||
937 | } | ||
938 | return JS_TRUE; | ||
939 | } | ||
940 | |||
941 | siliconforks | 399 | static JSBool |
942 | str_charAt(JSContext *cx, uintN argc, jsval *vp) | ||
943 | siliconforks | 332 | { |
944 | jsval t; | ||
945 | JSString *str; | ||
946 | jsint i; | ||
947 | jsdouble d; | ||
948 | |||
949 | t = vp[1]; | ||
950 | if (JSVAL_IS_STRING(t) && argc != 0 && JSVAL_IS_INT(vp[2])) { | ||
951 | str = JSVAL_TO_STRING(t); | ||
952 | i = JSVAL_TO_INT(vp[2]); | ||
953 | if ((size_t)i >= JSSTRING_LENGTH(str)) | ||
954 | goto out_of_range; | ||
955 | } else { | ||
956 | str = NormalizeThis(cx, vp); | ||
957 | if (!str) | ||
958 | return JS_FALSE; | ||
959 | |||
960 | if (argc == 0) { | ||
961 | d = 0.0; | ||
962 | } else { | ||
963 | d = js_ValueToNumber(cx, &vp[2]); | ||
964 | if (JSVAL_IS_NULL(vp[2])) | ||
965 | return JS_FALSE; | ||
966 | d = js_DoubleToInteger(d); | ||
967 | } | ||
968 | |||
969 | if (d < 0 || JSSTRING_LENGTH(str) <= d) | ||
970 | goto out_of_range; | ||
971 | i = (jsint) d; | ||
972 | } | ||
973 | |||
974 | str = js_GetUnitString(cx, str, (size_t)i); | ||
975 | if (!str) | ||
976 | return JS_FALSE; | ||
977 | *vp = STRING_TO_JSVAL(str); | ||
978 | return JS_TRUE; | ||
979 | |||
980 | out_of_range: | ||
981 | *vp = JS_GetEmptyStringValue(cx); | ||
982 | return JS_TRUE; | ||
983 | } | ||
984 | |||
985 | siliconforks | 399 | static JSBool |
986 | str_charCodeAt(JSContext *cx, uintN argc, jsval *vp) | ||
987 | siliconforks | 332 | { |
988 | jsval t; | ||
989 | JSString *str; | ||
990 | jsint i; | ||
991 | jsdouble d; | ||
992 | |||
993 | t = vp[1]; | ||
994 | if (JSVAL_IS_STRING(t) && argc != 0 && JSVAL_IS_INT(vp[2])) { | ||
995 | str = JSVAL_TO_STRING(t); | ||
996 | i = JSVAL_TO_INT(vp[2]); | ||
997 | if ((size_t)i >= JSSTRING_LENGTH(str)) | ||
998 | goto out_of_range; | ||
999 | } else { | ||
1000 | str = NormalizeThis(cx, vp); | ||
1001 | if (!str) | ||
1002 | return JS_FALSE; | ||
1003 | |||
1004 | if (argc == 0) { | ||
1005 | d = 0.0; | ||
1006 | } else { | ||
1007 | d = js_ValueToNumber(cx, &vp[2]); | ||
1008 | if (JSVAL_IS_NULL(vp[2])) | ||
1009 | return JS_FALSE; | ||
1010 | d = js_DoubleToInteger(d); | ||
1011 | } | ||
1012 | |||
1013 | if (d < 0 || JSSTRING_LENGTH(str) <= d) | ||
1014 | goto out_of_range; | ||
1015 | i = (jsint) d; | ||
1016 | } | ||
1017 | |||
1018 | *vp = INT_TO_JSVAL(JSSTRING_CHARS(str)[i]); | ||
1019 | return JS_TRUE; | ||
1020 | |||
1021 | out_of_range: | ||
1022 | *vp = JS_GetNaNValue(cx); | ||
1023 | return JS_TRUE; | ||
1024 | } | ||
1025 | |||
1026 | siliconforks | 399 | #ifdef JS_TRACER |
1027 | int32 FASTCALL | ||
1028 | js_String_p_charCodeAt(JSString* str, int32 i) | ||
1029 | { | ||
1030 | if (i < 0 || (int32)JSSTRING_LENGTH(str) <= i) | ||
1031 | return -1; | ||
1032 | return JSSTRING_CHARS(str)[i]; | ||
1033 | } | ||
1034 | #endif | ||
1035 | |||
1036 | siliconforks | 332 | jsint |
1037 | js_BoyerMooreHorspool(const jschar *text, jsint textlen, | ||
1038 | const jschar *pat, jsint patlen, | ||
1039 | jsint start) | ||
1040 | { | ||
1041 | jsint i, j, k, m; | ||
1042 | uint8 skip[BMH_CHARSET_SIZE]; | ||
1043 | jschar c; | ||
1044 | |||
1045 | JS_ASSERT(0 < patlen && patlen <= BMH_PATLEN_MAX); | ||
1046 | for (i = 0; i < BMH_CHARSET_SIZE; i++) | ||
1047 | skip[i] = (uint8)patlen; | ||
1048 | m = patlen - 1; | ||
1049 | for (i = 0; i < m; i++) { | ||
1050 | c = pat[i]; | ||
1051 | if (c >= BMH_CHARSET_SIZE) | ||
1052 | return BMH_BAD_PATTERN; | ||
1053 | skip[c] = (uint8)(m - i); | ||
1054 | } | ||
1055 | for (k = start + m; | ||
1056 | k < textlen; | ||
1057 | k += ((c = text[k]) >= BMH_CHARSET_SIZE) ? patlen : skip[c]) { | ||
1058 | for (i = k, j = m; ; i--, j--) { | ||
1059 | if (j < 0) | ||
1060 | return i + 1; | ||
1061 | if (text[i] != pat[j]) | ||
1062 | break; | ||
1063 | } | ||
1064 | } | ||
1065 | return -1; | ||
1066 | } | ||
1067 | |||
1068 | static JSBool | ||
1069 | str_indexOf(JSContext *cx, uintN argc, jsval *vp) | ||
1070 | { | ||
1071 | jsval t; | ||
1072 | JSString *str, *str2; | ||
1073 | const jschar *text, *pat; | ||
1074 | jsint i, j, index, textlen, patlen; | ||
1075 | jsdouble d; | ||
1076 | |||
1077 | t = vp[1]; | ||
1078 | if (JSVAL_IS_STRING(t) && argc != 0 && JSVAL_IS_STRING(vp[2])) { | ||
1079 | str = JSVAL_TO_STRING(t); | ||
1080 | str2 = JSVAL_TO_STRING(vp[2]); | ||
1081 | } else { | ||
1082 | str = NormalizeThis(cx, vp); | ||
1083 | if (!str) | ||
1084 | return JS_FALSE; | ||
1085 | |||
1086 | str2 = ArgToRootedString(cx, argc, vp, 0); | ||
1087 | if (!str2) | ||
1088 | return JS_FALSE; | ||
1089 | } | ||
1090 | |||
1091 | text = JSSTRING_CHARS(str); | ||
1092 | textlen = (jsint) JSSTRING_LENGTH(str); | ||
1093 | pat = JSSTRING_CHARS(str2); | ||
1094 | patlen = (jsint) JSSTRING_LENGTH(str2); | ||
1095 | |||
1096 | if (argc > 1) { | ||
1097 | d = js_ValueToNumber(cx, &vp[3]); | ||
1098 | if (JSVAL_IS_NULL(vp[3])) | ||
1099 | return JS_FALSE; | ||
1100 | d = js_DoubleToInteger(d); | ||
1101 | if (d < 0) | ||
1102 | i = 0; | ||
1103 | else if (d > textlen) | ||
1104 | i = textlen; | ||
1105 | else | ||
1106 | i = (jsint)d; | ||
1107 | } else { | ||
1108 | i = 0; | ||
1109 | } | ||
1110 | if (patlen == 0) { | ||
1111 | *vp = INT_TO_JSVAL(i); | ||
1112 | return JS_TRUE; | ||
1113 | } | ||
1114 | |||
1115 | /* XXX tune the BMH threshold (512) */ | ||
1116 | if (textlen - i >= 512 && (jsuint)(patlen - 2) <= BMH_PATLEN_MAX - 2) { | ||
1117 | index = js_BoyerMooreHorspool(text, textlen, pat, patlen, i); | ||
1118 | if (index != BMH_BAD_PATTERN) | ||
1119 | goto out; | ||
1120 | } | ||
1121 | |||
1122 | index = -1; | ||
1123 | j = 0; | ||
1124 | while (i + j < textlen) { | ||
1125 | if (text[i + j] == pat[j]) { | ||
1126 | if (++j == patlen) { | ||
1127 | index = i; | ||
1128 | break; | ||
1129 | } | ||
1130 | } else { | ||
1131 | i++; | ||
1132 | j = 0; | ||
1133 | } | ||
1134 | } | ||
1135 | |||
1136 | out: | ||
1137 | *vp = INT_TO_JSVAL(index); | ||
1138 | return JS_TRUE; | ||
1139 | } | ||
1140 | |||
1141 | static JSBool | ||
1142 | str_lastIndexOf(JSContext *cx, uintN argc, jsval *vp) | ||
1143 | { | ||
1144 | JSString *str, *str2; | ||
1145 | const jschar *text, *pat; | ||
1146 | jsint i, j, textlen, patlen; | ||
1147 | jsdouble d; | ||
1148 | |||
1149 | NORMALIZE_THIS(cx, vp, str); | ||
1150 | text = JSSTRING_CHARS(str); | ||
1151 | textlen = (jsint) JSSTRING_LENGTH(str); | ||
1152 | |||
1153 | str2 = ArgToRootedString(cx, argc, vp, 0); | ||
1154 | if (!str2) | ||
1155 | return JS_FALSE; | ||
1156 | pat = JSSTRING_CHARS(str2); | ||
1157 | patlen = (jsint) JSSTRING_LENGTH(str2); | ||
1158 | |||
1159 | if (argc > 1) { | ||
1160 | d = js_ValueToNumber(cx, &vp[3]); | ||
1161 | if (JSVAL_IS_NULL(vp[3])) | ||
1162 | return JS_FALSE; | ||
1163 | if (JSDOUBLE_IS_NaN(d)) { | ||
1164 | i = textlen; | ||
1165 | } else { | ||
1166 | d = js_DoubleToInteger(d); | ||
1167 | if (d < 0) | ||
1168 | i = 0; | ||
1169 | else if (d > textlen) | ||
1170 | i = textlen; | ||
1171 | else | ||
1172 | i = (jsint)d; | ||
1173 | } | ||
1174 | } else { | ||
1175 | i = textlen; | ||
1176 | } | ||
1177 | |||
1178 | if (patlen == 0) { | ||
1179 | *vp = INT_TO_JSVAL(i); | ||
1180 | return JS_TRUE; | ||
1181 | } | ||
1182 | |||
1183 | j = 0; | ||
1184 | while (i >= 0) { | ||
1185 | /* Don't assume that text is NUL-terminated: it could be dependent. */ | ||
1186 | if (i + j < textlen && text[i + j] == pat[j]) { | ||
1187 | if (++j == patlen) | ||
1188 | break; | ||
1189 | } else { | ||
1190 | i--; | ||
1191 | j = 0; | ||
1192 | } | ||
1193 | } | ||
1194 | *vp = INT_TO_JSVAL(i); | ||
1195 | return JS_TRUE; | ||
1196 | } | ||
1197 | |||
1198 | static JSBool | ||
1199 | js_TrimString(JSContext *cx, jsval *vp, JSBool trimLeft, JSBool trimRight) | ||
1200 | { | ||
1201 | JSString *str; | ||
1202 | const jschar *chars; | ||
1203 | size_t length, begin, end; | ||
1204 | |||
1205 | NORMALIZE_THIS(cx, vp, str); | ||
1206 | JSSTRING_CHARS_AND_LENGTH(str, chars, length); | ||
1207 | begin = 0; | ||
1208 | end = length; | ||
1209 | |||
1210 | if (trimLeft) { | ||
1211 | while (begin < length && JS_ISSPACE(chars[begin])) | ||
1212 | ++begin; | ||
1213 | } | ||
1214 | |||
1215 | if (trimRight) { | ||
1216 | while (end > begin && JS_ISSPACE(chars[end-1])) | ||
1217 | --end; | ||
1218 | } | ||
1219 | |||
1220 | str = js_NewDependentString(cx, str, begin, end - begin); | ||
1221 | if (!str) | ||
1222 | return JS_FALSE; | ||
1223 | |||
1224 | *vp = STRING_TO_JSVAL(str); | ||
1225 | return JS_TRUE; | ||
1226 | } | ||
1227 | |||
1228 | static JSBool | ||
1229 | str_trim(JSContext *cx, uintN argc, jsval *vp) | ||
1230 | { | ||
1231 | return js_TrimString(cx, vp, JS_TRUE, JS_TRUE); | ||
1232 | } | ||
1233 | |||
1234 | static JSBool | ||
1235 | str_trimLeft(JSContext *cx, uintN argc, jsval *vp) | ||
1236 | { | ||
1237 | return js_TrimString(cx, vp, JS_TRUE, JS_FALSE); | ||
1238 | } | ||
1239 | |||
1240 | static JSBool | ||
1241 | str_trimRight(JSContext *cx, uintN argc, jsval *vp) | ||
1242 | { | ||
1243 | return js_TrimString(cx, vp, JS_FALSE, JS_TRUE); | ||
1244 | } | ||
1245 | |||
1246 | /* | ||
1247 | * Perl-inspired string functions. | ||
1248 | */ | ||
1249 | typedef struct GlobData { | ||
1250 | jsbytecode *pc; /* in: program counter resulting in us matching */ | ||
1251 | uintN flags; /* inout: mode and flag bits, see below */ | ||
1252 | uintN optarg; /* in: index of optional flags argument */ | ||
1253 | JSString *str; /* out: 'this' parameter object as string */ | ||
1254 | JSRegExp *regexp; /* out: regexp parameter object private data */ | ||
1255 | } GlobData; | ||
1256 | |||
1257 | /* | ||
1258 | * Mode and flag bit definitions for match_or_replace's GlobData.flags field. | ||
1259 | */ | ||
1260 | #define MODE_MATCH 0x00 /* in: return match array on success */ | ||
1261 | #define MODE_REPLACE 0x01 /* in: match and replace */ | ||
1262 | #define MODE_SEARCH 0x02 /* in: search only, return match index or -1 */ | ||
1263 | #define GET_MODE(f) ((f) & 0x03) | ||
1264 | #define FORCE_FLAT 0x04 /* in: force flat (non-regexp) string match */ | ||
1265 | #define KEEP_REGEXP 0x08 /* inout: keep GlobData.regexp alive for caller | ||
1266 | of match_or_replace; if set on input | ||
1267 | but clear on output, regexp ownership | ||
1268 | does not pass to caller */ | ||
1269 | #define GLOBAL_REGEXP 0x10 /* out: regexp had the 'g' flag */ | ||
1270 | |||
1271 | static JSBool | ||
1272 | match_or_replace(JSContext *cx, | ||
1273 | JSBool (*glob)(JSContext *cx, jsint count, GlobData *data), | ||
1274 | void (*destroy)(JSContext *cx, GlobData *data), | ||
1275 | GlobData *data, uintN argc, jsval *vp) | ||
1276 | { | ||
1277 | JSString *str, *src, *opt; | ||
1278 | JSObject *reobj; | ||
1279 | JSRegExp *re; | ||
1280 | size_t index, length; | ||
1281 | JSBool ok, test; | ||
1282 | jsint count; | ||
1283 | |||
1284 | NORMALIZE_THIS(cx, vp, str); | ||
1285 | data->str = str; | ||
1286 | |||
1287 | if (argc != 0 && VALUE_IS_REGEXP(cx, vp[2])) { | ||
1288 | reobj = JSVAL_TO_OBJECT(vp[2]); | ||
1289 | re = (JSRegExp *) JS_GetPrivate(cx, reobj); | ||
1290 | } else { | ||
1291 | src = ArgToRootedString(cx, argc, vp, 0); | ||
1292 | if (!src) | ||
1293 | return JS_FALSE; | ||
1294 | if (data->optarg < argc) { | ||
1295 | opt = js_ValueToString(cx, vp[2 + data->optarg]); | ||
1296 | if (!opt) | ||
1297 | return JS_FALSE; | ||
1298 | } else { | ||
1299 | opt = NULL; | ||
1300 | } | ||
1301 | re = js_NewRegExpOpt(cx, src, opt, (data->flags & FORCE_FLAT) != 0); | ||
1302 | if (!re) | ||
1303 | return JS_FALSE; | ||
1304 | reobj = NULL; | ||
1305 | } | ||
1306 | /* From here on, all control flow must reach the matching DROP. */ | ||
1307 | data->regexp = re; | ||
1308 | HOLD_REGEXP(cx, re); | ||
1309 | |||
1310 | if (re->flags & JSREG_GLOB) | ||
1311 | data->flags |= GLOBAL_REGEXP; | ||
1312 | index = 0; | ||
1313 | if (GET_MODE(data->flags) == MODE_SEARCH) { | ||
1314 | ok = js_ExecuteRegExp(cx, re, str, &index, JS_TRUE, vp); | ||
1315 | if (ok) { | ||
1316 | *vp = (*vp == JSVAL_TRUE) | ||
1317 | ? INT_TO_JSVAL(cx->regExpStatics.leftContext.length) | ||
1318 | : INT_TO_JSVAL(-1); | ||
1319 | } | ||
1320 | } else if (data->flags & GLOBAL_REGEXP) { | ||
1321 | if (reobj) { | ||
1322 | /* Set the lastIndex property's reserved slot to 0. */ | ||
1323 | ok = js_SetLastIndex(cx, reobj, 0); | ||
1324 | } else { | ||
1325 | ok = JS_TRUE; | ||
1326 | } | ||
1327 | if (ok) { | ||
1328 | length = JSSTRING_LENGTH(str); | ||
1329 | for (count = 0; index <= length; count++) { | ||
1330 | ok = js_ExecuteRegExp(cx, re, str, &index, JS_TRUE, vp); | ||
1331 | if (!ok || *vp != JSVAL_TRUE) | ||
1332 | break; | ||
1333 | ok = glob(cx, count, data); | ||
1334 | if (!ok) | ||
1335 | break; | ||
1336 | if (cx->regExpStatics.lastMatch.length == 0) { | ||
1337 | if (index == length) | ||
1338 | break; | ||
1339 | index++; | ||
1340 | } | ||
1341 | } | ||
1342 | if (!ok && destroy) | ||
1343 | destroy(cx, data); | ||
1344 | } | ||
1345 | } else { | ||
1346 | if (GET_MODE(data->flags) == MODE_REPLACE) { | ||
1347 | test = JS_TRUE; | ||
1348 | } else { | ||
1349 | /* | ||
1350 | siliconforks | 399 | * MODE_MATCH implies str_match is being called from a script or a |
1351 | * scripted function. If the caller cares only about testing null | ||
1352 | siliconforks | 332 | * vs. non-null return value, optimize away the array object that |
1353 | * would normally be returned in *vp. | ||
1354 | * | ||
1355 | * Assume a full array result is required, then prove otherwise. | ||
1356 | */ | ||
1357 | test = JS_FALSE; | ||
1358 | if (data->pc && (*data->pc == JSOP_CALL || *data->pc == JSOP_NEW)) { | ||
1359 | JS_ASSERT(js_CodeSpec[*data->pc].length == 3); | ||
1360 | switch (data->pc[3]) { | ||
1361 | case JSOP_POP: | ||
1362 | case JSOP_IFEQ: | ||
1363 | case JSOP_IFNE: | ||
1364 | case JSOP_IFEQX: | ||
1365 | case JSOP_IFNEX: | ||
1366 | test = JS_TRUE; | ||
1367 | break; | ||
1368 | default:; | ||
1369 | } | ||
1370 | } | ||
1371 | } | ||
1372 | ok = js_ExecuteRegExp(cx, re, str, &index, test, vp); | ||
1373 | } | ||
1374 | |||
1375 | DROP_REGEXP(cx, re); | ||
1376 | if (reobj) { | ||
1377 | /* Tell our caller that it doesn't need to destroy data->regexp. */ | ||
1378 | data->flags &= ~KEEP_REGEXP; | ||
1379 | } else if (!ok || !(data->flags & KEEP_REGEXP)) { | ||
1380 | /* Caller didn't want to keep data->regexp, so null and destroy it. */ | ||
1381 | data->regexp = NULL; | ||
1382 | js_DestroyRegExp(cx, re); | ||
1383 | } | ||
1384 | |||
1385 | return ok; | ||
1386 | } | ||
1387 | |||
1388 | typedef struct MatchData { | ||
1389 | GlobData base; | ||
1390 | jsval *arrayval; /* NB: local root pointer */ | ||
1391 | } MatchData; | ||
1392 | |||
1393 | static JSBool | ||
1394 | match_glob(JSContext *cx, jsint count, GlobData *data) | ||
1395 | { | ||
1396 | MatchData *mdata; | ||
1397 | JSObject *arrayobj; | ||
1398 | JSSubString *matchsub; | ||
1399 | JSString *matchstr; | ||
1400 | jsval v; | ||
1401 | |||
1402 | mdata = (MatchData *)data; | ||
1403 | arrayobj = JSVAL_TO_OBJECT(*mdata->arrayval); | ||
1404 | if (!arrayobj) { | ||
1405 | arrayobj = js_ConstructObject(cx, &js_ArrayClass, NULL, NULL, 0, NULL); | ||
1406 | if (!arrayobj) | ||
1407 | return JS_FALSE; | ||
1408 | *mdata->arrayval = OBJECT_TO_JSVAL(arrayobj); | ||
1409 | } | ||
1410 | matchsub = &cx->regExpStatics.lastMatch; | ||
1411 | matchstr = js_NewStringCopyN(cx, matchsub->chars, matchsub->length); | ||
1412 | if (!matchstr) | ||
1413 | return JS_FALSE; | ||
1414 | v = STRING_TO_JSVAL(matchstr); | ||
1415 | JS_ASSERT(count <= JSVAL_INT_MAX); | ||
1416 | siliconforks | 399 | |
1417 | JSAutoResolveFlags rf(cx, JSRESOLVE_QUALIFIED | JSRESOLVE_ASSIGNING); | ||
1418 | siliconforks | 332 | return OBJ_SET_PROPERTY(cx, arrayobj, INT_TO_JSID(count), &v); |
1419 | } | ||
1420 | |||
1421 | JSBool | ||
1422 | js_StringMatchHelper(JSContext *cx, uintN argc, jsval *vp, jsbytecode *pc) | ||
1423 | { | ||
1424 | JSTempValueRooter tvr; | ||
1425 | MatchData mdata; | ||
1426 | JSBool ok; | ||
1427 | |||
1428 | JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr); | ||
1429 | mdata.base.pc = pc; | ||
1430 | mdata.base.flags = MODE_MATCH; | ||
1431 | mdata.base.optarg = 1; | ||
1432 | mdata.arrayval = &tvr.u.value; | ||
1433 | ok = match_or_replace(cx, match_glob, NULL, &mdata.base, argc, vp); | ||
1434 | if (ok && !JSVAL_IS_NULL(*mdata.arrayval)) | ||
1435 | *vp = *mdata.arrayval; | ||
1436 | JS_POP_TEMP_ROOT(cx, &tvr); | ||
1437 | return ok; | ||
1438 | } | ||
1439 | |||
1440 | siliconforks | 399 | static JSBool |
1441 | str_match(JSContext *cx, uintN argc, jsval *vp) | ||
1442 | siliconforks | 332 | { |
1443 | JSStackFrame *fp; | ||
1444 | |||
1445 | for (fp = cx->fp; fp && !fp->regs; fp = fp->down) | ||
1446 | JS_ASSERT(!fp->script); | ||
1447 | return js_StringMatchHelper(cx, argc, vp, fp ? fp->regs->pc : NULL); | ||
1448 | } | ||
1449 | |||
1450 | siliconforks | 399 | #ifdef JS_TRACER |
1451 | static JSObject* FASTCALL | ||
1452 | String_p_match(JSContext* cx, JSString* str, jsbytecode *pc, JSObject* regexp) | ||
1453 | { | ||
1454 | jsval vp[3] = { JSVAL_NULL, STRING_TO_JSVAL(str), OBJECT_TO_JSVAL(regexp) }; | ||
1455 | if (!js_StringMatchHelper(cx, 1, vp, pc)) | ||
1456 | return (JSObject*) JSVAL_TO_BOOLEAN(JSVAL_VOID); | ||
1457 | JS_ASSERT(JSVAL_IS_NULL(vp[0]) || | ||
1458 | (!JSVAL_IS_PRIMITIVE(vp[0]) && OBJ_IS_ARRAY(cx, JSVAL_TO_OBJECT(vp[0])))); | ||
1459 | return JSVAL_TO_OBJECT(vp[0]); | ||
1460 | } | ||
1461 | |||
1462 | static JSObject* FASTCALL | ||
1463 | String_p_match_obj(JSContext* cx, JSObject* str, jsbytecode *pc, JSObject* regexp) | ||
1464 | { | ||
1465 | jsval vp[3] = { JSVAL_NULL, OBJECT_TO_JSVAL(str), OBJECT_TO_JSVAL(regexp) }; | ||
1466 | if (!js_StringMatchHelper(cx, 1, vp, pc)) | ||
1467 | return (JSObject*) JSVAL_TO_BOOLEAN(JSVAL_VOID); | ||
1468 | JS_ASSERT(JSVAL_IS_NULL(vp[0]) || | ||
1469 | (!JSVAL_IS_PRIMITIVE(vp[0]) && OBJ_IS_ARRAY(cx, JSVAL_TO_OBJECT(vp[0])))); | ||
1470 | return JSVAL_TO_OBJECT(vp[0]); | ||
1471 | } | ||
1472 | #endif | ||
1473 | |||
1474 | siliconforks | 332 | static JSBool |
1475 | str_search(JSContext *cx, uintN argc, jsval *vp) | ||
1476 | { | ||
1477 | GlobData data; | ||
1478 | |||
1479 | data.flags = MODE_SEARCH; | ||
1480 | data.optarg = 1; | ||
1481 | return match_or_replace(cx, NULL, NULL, &data, argc, vp); | ||
1482 | } | ||
1483 | |||
1484 | typedef struct ReplaceData { | ||
1485 | GlobData base; /* base struct state */ | ||
1486 | JSObject *lambda; /* replacement function object or null */ | ||
1487 | JSString *repstr; /* replacement string */ | ||
1488 | jschar *dollar; /* null or pointer to first $ in repstr */ | ||
1489 | jschar *dollarEnd; /* limit pointer for js_strchr_limit */ | ||
1490 | jschar *chars; /* result chars, null initially */ | ||
1491 | size_t length; /* result length, 0 initially */ | ||
1492 | jsint index; /* index in result of next replacement */ | ||
1493 | jsint leftIndex; /* left context index in base.str->chars */ | ||
1494 | JSSubString dollarStr; /* for "$$" interpret_dollar result */ | ||
1495 | } ReplaceData; | ||
1496 | |||
1497 | static JSSubString * | ||
1498 | interpret_dollar(JSContext *cx, jschar *dp, jschar *ep, ReplaceData *rdata, | ||
1499 | size_t *skip) | ||
1500 | { | ||
1501 | JSRegExpStatics *res; | ||
1502 | jschar dc, *cp; | ||
1503 | uintN num, tmp; | ||
1504 | |||
1505 | JS_ASSERT(*dp == '$'); | ||
1506 | |||
1507 | /* If there is only a dollar, bail now */ | ||
1508 | if (dp + 1 >= ep) | ||
1509 | return NULL; | ||
1510 | |||
1511 | /* Interpret all Perl match-induced dollar variables. */ | ||
1512 | res = &cx->regExpStatics; | ||
1513 | dc = dp[1]; | ||
1514 | if (JS7_ISDEC(dc)) { | ||
1515 | /* ECMA-262 Edition 3: 1-9 or 01-99 */ | ||
1516 | num = JS7_UNDEC(dc); | ||
1517 | if (num > res->parenCount) | ||
1518 | return NULL; | ||
1519 | |||
1520 | cp = dp + 2; | ||
1521 | if (cp < ep && (dc = *cp, JS7_ISDEC(dc))) { | ||
1522 | tmp = 10 * num + JS7_UNDEC(dc); | ||
1523 | if (tmp <= res->parenCount) { | ||
1524 | cp++; | ||
1525 | num = tmp; | ||
1526 | } | ||
1527 | } | ||
1528 | if (num == 0) | ||
1529 | return NULL; | ||
1530 | |||
1531 | /* Adjust num from 1 $n-origin to 0 array-index-origin. */ | ||
1532 | num--; | ||
1533 | *skip = cp - dp; | ||
1534 | return REGEXP_PAREN_SUBSTRING(res, num); | ||
1535 | } | ||
1536 | |||
1537 | *skip = 2; | ||
1538 | switch (dc) { | ||
1539 | case '$': | ||
1540 | rdata->dollarStr.chars = dp; | ||
1541 | rdata->dollarStr.length = 1; | ||
1542 | return &rdata->dollarStr; | ||
1543 | case '&': | ||
1544 | return &res->lastMatch; | ||
1545 | case '+': | ||
1546 | return &res->lastParen; | ||
1547 | case '`': | ||
1548 | return &res->leftContext; | ||
1549 | case '\'': | ||
1550 | return &res->rightContext; | ||
1551 | } | ||
1552 | return NULL; | ||
1553 | } | ||
1554 | |||
1555 | static JSBool | ||
1556 | find_replen(JSContext *cx, ReplaceData *rdata, size_t *sizep) | ||
1557 | { | ||
1558 | JSString *repstr; | ||
1559 | size_t replen, skip; | ||
1560 | jschar *dp, *ep; | ||
1561 | JSSubString *sub; | ||
1562 | JSObject *lambda; | ||
1563 | |||
1564 | lambda = rdata->lambda; | ||
1565 | if (lambda) { | ||
1566 | uintN argc, i, j, m, n, p; | ||
1567 | jsval *invokevp, *sp; | ||
1568 | void *mark; | ||
1569 | JSBool ok; | ||
1570 | |||
1571 | /* | ||
1572 | * Save the regExpStatics from the current regexp, since they may be | ||
1573 | * clobbered by a RegExp usage in the lambda function. Note that all | ||
1574 | * members of JSRegExpStatics are JSSubStrings, so not GC roots, save | ||
1575 | siliconforks | 399 | * input, which is rooted otherwise via vp[1] in str_replace. |
1576 | siliconforks | 332 | */ |
1577 | JSRegExpStatics save = cx->regExpStatics; | ||
1578 | JSBool freeMoreParens = JS_FALSE; | ||
1579 | |||
1580 | /* | ||
1581 | * In the lambda case, not only do we find the replacement string's | ||
1582 | * length, we compute repstr and return it via rdata for use within | ||
1583 | * do_replace. The lambda is called with arguments ($&, $1, $2, ..., | ||
1584 | * index, input), i.e., all the properties of a regexp match array. | ||
1585 | * For $&, etc., we must create string jsvals from cx->regExpStatics. | ||
1586 | * We grab up stack space to keep the newborn strings GC-rooted. | ||
1587 | */ | ||
1588 | p = rdata->base.regexp->parenCount; | ||
1589 | argc = 1 + p + 2; | ||
1590 | invokevp = js_AllocStack(cx, 2 + argc, &mark); | ||
1591 | if (!invokevp) | ||
1592 | return JS_FALSE; | ||
1593 | |||
1594 | /* Push lambda and its 'this' parameter. */ | ||
1595 | sp = invokevp; | ||
1596 | *sp++ = OBJECT_TO_JSVAL(lambda); | ||
1597 | *sp++ = OBJECT_TO_JSVAL(OBJ_GET_PARENT(cx, lambda)); | ||
1598 | |||
1599 | #define PUSH_REGEXP_STATIC(sub) \ | ||
1600 | JS_BEGIN_MACRO \ | ||
1601 | JSString *str = js_NewStringCopyN(cx, \ | ||
1602 | cx->regExpStatics.sub.chars, \ | ||
1603 | cx->regExpStatics.sub.length); \ | ||
1604 | if (!str) { \ | ||
1605 | ok = JS_FALSE; \ | ||
1606 | goto lambda_out; \ | ||
1607 | } \ | ||
1608 | *sp++ = STRING_TO_JSVAL(str); \ | ||
1609 | JS_END_MACRO | ||
1610 | |||
1611 | /* Push $&, $1, $2, ... */ | ||
1612 | PUSH_REGEXP_STATIC(lastMatch); | ||
1613 | i = 0; | ||
1614 | m = cx->regExpStatics.parenCount; | ||
1615 | n = JS_MIN(m, 9); | ||
1616 | for (j = 0; i < n; i++, j++) | ||
1617 | PUSH_REGEXP_STATIC(parens[j]); | ||
1618 | for (j = 0; i < m; i++, j++) | ||
1619 | PUSH_REGEXP_STATIC(moreParens[j]); | ||
1620 | |||
1621 | /* | ||
1622 | * We need to clear moreParens in the top-of-stack cx->regExpStatics | ||
1623 | * to it won't be possibly realloc'ed, leaving the bottom-of-stack | ||
1624 | * moreParens pointing to freed memory. | ||
1625 | */ | ||
1626 | cx->regExpStatics.moreParens = NULL; | ||
1627 | freeMoreParens = JS_TRUE; | ||
1628 | |||
1629 | #undef PUSH_REGEXP_STATIC | ||
1630 | |||
1631 | /* Make sure to push undefined for any unmatched parens. */ | ||
1632 | for (; i < p; i++) | ||
1633 | *sp++ = JSVAL_VOID; | ||
1634 | |||
1635 | /* Push match index and input string. */ | ||
1636 | *sp++ = INT_TO_JSVAL((jsint)cx->regExpStatics.leftContext.length); | ||
1637 | *sp++ = STRING_TO_JSVAL(rdata->base.str); | ||
1638 | |||
1639 | ok = js_Invoke(cx, argc, invokevp, 0); | ||
1640 | if (ok) { | ||
1641 | /* | ||
1642 | * NB: we count on the newborn string root to hold any string | ||
1643 | * created by this js_ValueToString that would otherwise be GC- | ||
1644 | * able, until we use rdata->repstr in do_replace. | ||
1645 | */ | ||
1646 | repstr = js_ValueToString(cx, *invokevp); | ||
1647 | if (!repstr) { | ||
1648 | ok = JS_FALSE; | ||
1649 | } else { | ||
1650 | rdata->repstr = repstr; | ||
1651 | *sizep = JSSTRING_LENGTH(repstr); | ||
1652 | } | ||
1653 | } | ||
1654 | |||
1655 | lambda_out: | ||
1656 | js_FreeStack(cx, mark); | ||
1657 | if (freeMoreParens) | ||
1658 | JS_free(cx, cx->regExpStatics.moreParens); | ||
1659 | cx->regExpStatics = save; | ||
1660 | return ok; | ||
1661 | } | ||
1662 | |||
1663 | repstr = rdata->repstr; | ||
1664 | replen = JSSTRING_LENGTH(repstr); | ||
1665 | for (dp = rdata->dollar, ep = rdata->dollarEnd; dp; | ||
1666 | dp = js_strchr_limit(dp, '$', ep)) { | ||
1667 | sub = interpret_dollar(cx, dp, ep, rdata, &skip); | ||
1668 | if (sub) { | ||
1669 | replen += sub->length - skip; | ||
1670 | dp += skip; | ||
1671 | } | ||
1672 | else | ||
1673 | dp++; | ||
1674 | } | ||
1675 | *sizep = replen; | ||
1676 | return JS_TRUE; | ||
1677 | } | ||
1678 | |||
1679 | static void | ||
1680 | do_replace(JSContext *cx, ReplaceData *rdata, jschar *chars) | ||
1681 | { | ||
1682 | JSString *repstr; | ||
1683 | jschar *bp, *cp, *dp, *ep; | ||
1684 | size_t len, skip; | ||
1685 | JSSubString *sub; | ||
1686 | |||
1687 | repstr = rdata->repstr; | ||
1688 | bp = cp = JSSTRING_CHARS(repstr); | ||
1689 | for (dp = rdata->dollar, ep = rdata->dollarEnd; dp; | ||
1690 | dp = js_strchr_limit(dp, '$', ep)) { | ||
1691 | len = dp - cp; | ||
1692 | js_strncpy(chars, cp, len); | ||
1693 | chars += len; | ||
1694 | cp = dp; | ||
1695 | sub = interpret_dollar(cx, dp, ep, rdata, &skip); | ||
1696 | if (sub) { | ||
1697 | len = sub->length; | ||
1698 | js_strncpy(chars, sub->chars, len); | ||
1699 | chars += len; | ||
1700 | cp += skip; | ||
1701 | dp += skip; | ||
1702 | } else { | ||
1703 | dp++; | ||
1704 | } | ||
1705 | } | ||
1706 | js_strncpy(chars, cp, JSSTRING_LENGTH(repstr) - (cp - bp)); | ||
1707 | } | ||
1708 | |||
1709 | static void | ||
1710 | replace_destroy(JSContext *cx, GlobData *data) | ||
1711 | { | ||
1712 | ReplaceData *rdata; | ||
1713 | |||
1714 | rdata = (ReplaceData *)data; | ||
1715 | JS_free(cx, rdata->chars); | ||
1716 | rdata->chars = NULL; | ||
1717 | } | ||
1718 | |||
1719 | static JSBool | ||
1720 | replace_glob(JSContext *cx, jsint count, GlobData *data) | ||
1721 | { | ||
1722 | ReplaceData *rdata; | ||
1723 | JSString *str; | ||
1724 | size_t leftoff, leftlen, replen, growth; | ||
1725 | const jschar *left; | ||
1726 | jschar *chars; | ||
1727 | |||
1728 | rdata = (ReplaceData *)data; | ||
1729 | str = data->str; | ||
1730 | leftoff = rdata->leftIndex; | ||
1731 | left = JSSTRING_CHARS(str) + leftoff; | ||
1732 | leftlen = cx->regExpStatics.lastMatch.chars - left; | ||
1733 | rdata->leftIndex = cx->regExpStatics.lastMatch.chars - JSSTRING_CHARS(str); | ||
1734 | rdata->leftIndex += cx->regExpStatics.lastMatch.length; | ||
1735 | if (!find_replen(cx, rdata, &replen)) | ||
1736 | return JS_FALSE; | ||
1737 | growth = leftlen + replen; | ||
1738 | chars = (jschar *) | ||
1739 | (rdata->chars | ||
1740 | ? JS_realloc(cx, rdata->chars, (rdata->length + growth + 1) | ||
1741 | * sizeof(jschar)) | ||
1742 | : JS_malloc(cx, (growth + 1) * sizeof(jschar))); | ||
1743 | if (!chars) | ||
1744 | return JS_FALSE; | ||
1745 | rdata->chars = chars; | ||
1746 | rdata->length += growth; | ||
1747 | chars += rdata->index; | ||
1748 | rdata->index += growth; | ||
1749 | js_strncpy(chars, left, leftlen); | ||
1750 | chars += leftlen; | ||
1751 | do_replace(cx, rdata, chars); | ||
1752 | return JS_TRUE; | ||
1753 | } | ||
1754 | |||
1755 | siliconforks | 399 | static JSBool |
1756 | str_replace(JSContext *cx, uintN argc, jsval *vp) | ||
1757 | siliconforks | 332 | { |
1758 | JSObject *lambda; | ||
1759 | JSString *repstr; | ||
1760 | |||
1761 | if (argc >= 2 && JS_TypeOfValue(cx, vp[3]) == JSTYPE_FUNCTION) { | ||
1762 | lambda = JSVAL_TO_OBJECT(vp[3]); | ||
1763 | repstr = NULL; | ||
1764 | } else { | ||
1765 | lambda = NULL; | ||
1766 | repstr = ArgToRootedString(cx, argc, vp, 1); | ||
1767 | if (!repstr) | ||
1768 | return JS_FALSE; | ||
1769 | } | ||
1770 | |||
1771 | return js_StringReplaceHelper(cx, argc, lambda, repstr, vp); | ||
1772 | } | ||
1773 | |||
1774 | siliconforks | 399 | #ifdef JS_TRACER |
1775 | static JSString* FASTCALL | ||
1776 | String_p_replace_str(JSContext* cx, JSString* str, JSObject* regexp, JSString* repstr) | ||
1777 | { | ||
1778 | jsval vp[4] = { | ||
1779 | JSVAL_NULL, STRING_TO_JSVAL(str), OBJECT_TO_JSVAL(regexp), STRING_TO_JSVAL(repstr) | ||
1780 | }; | ||
1781 | if (!js_StringReplaceHelper(cx, 2, NULL, repstr, vp)) | ||
1782 | return NULL; | ||
1783 | JS_ASSERT(JSVAL_IS_STRING(vp[0])); | ||
1784 | return JSVAL_TO_STRING(vp[0]); | ||
1785 | } | ||
1786 | |||
1787 | static JSString* FASTCALL | ||
1788 | String_p_replace_str2(JSContext* cx, JSString* str, JSString* patstr, JSString* repstr) | ||
1789 | { | ||
1790 | jsval vp[4] = { | ||
1791 | JSVAL_NULL, STRING_TO_JSVAL(str), STRING_TO_JSVAL(patstr), STRING_TO_JSVAL(repstr) | ||
1792 | }; | ||
1793 | if (!js_StringReplaceHelper(cx, 2, NULL, repstr, vp)) | ||
1794 | return NULL; | ||
1795 | JS_ASSERT(JSVAL_IS_STRING(vp[0])); | ||
1796 | return JSVAL_TO_STRING(vp[0]); | ||
1797 | } | ||
1798 | |||
1799 | static JSString* FASTCALL | ||
1800 | String_p_replace_str3(JSContext* cx, JSString* str, JSString* patstr, JSString* repstr, | ||
1801 | JSString* flagstr) | ||
1802 | { | ||
1803 | jsval vp[5] = { | ||
1804 | JSVAL_NULL, STRING_TO_JSVAL(str), STRING_TO_JSVAL(patstr), STRING_TO_JSVAL(repstr), | ||
1805 | STRING_TO_JSVAL(flagstr) | ||
1806 | }; | ||
1807 | if (!js_StringReplaceHelper(cx, 3, NULL, repstr, vp)) | ||
1808 | return NULL; | ||
1809 | JS_ASSERT(JSVAL_IS_STRING(vp[0])); | ||
1810 | return JSVAL_TO_STRING(vp[0]); | ||
1811 | } | ||
1812 | #endif | ||
1813 | |||
1814 | siliconforks | 332 | JSBool |
1815 | js_StringReplaceHelper(JSContext *cx, uintN argc, JSObject *lambda, | ||
1816 | JSString *repstr, jsval *vp) | ||
1817 | { | ||
1818 | ReplaceData rdata; | ||
1819 | JSBool ok; | ||
1820 | size_t leftlen, rightlen, length; | ||
1821 | jschar *chars; | ||
1822 | JSString *str; | ||
1823 | |||
1824 | /* | ||
1825 | * For ECMA Edition 3, the first argument is to be converted to a string | ||
1826 | * to match in a "flat" sense (without regular expression metachars having | ||
1827 | * special meanings) UNLESS the first arg is a RegExp object. | ||
1828 | */ | ||
1829 | rdata.base.flags = MODE_REPLACE | KEEP_REGEXP | FORCE_FLAT; | ||
1830 | rdata.base.optarg = 2; | ||
1831 | |||
1832 | rdata.lambda = lambda; | ||
1833 | rdata.repstr = repstr; | ||
1834 | if (repstr) { | ||
1835 | rdata.dollarEnd = JSSTRING_CHARS(repstr) + JSSTRING_LENGTH(repstr); | ||
1836 | rdata.dollar = js_strchr_limit(JSSTRING_CHARS(repstr), '$', | ||
1837 | rdata.dollarEnd); | ||
1838 | } else { | ||
1839 | rdata.dollar = rdata.dollarEnd = NULL; | ||
1840 | } | ||
1841 | rdata.chars = NULL; | ||
1842 | rdata.length = 0; | ||
1843 | rdata.index = 0; | ||
1844 | rdata.leftIndex = 0; | ||
1845 | |||
1846 | ok = match_or_replace(cx, replace_glob, replace_destroy, &rdata.base, | ||
1847 | argc, vp); | ||
1848 | if (!ok) | ||
1849 | return JS_FALSE; | ||
1850 | |||
1851 | if (!rdata.chars) { | ||
1852 | if ((rdata.base.flags & GLOBAL_REGEXP) || *vp != JSVAL_TRUE) { | ||
1853 | /* Didn't match even once. */ | ||
1854 | *vp = STRING_TO_JSVAL(rdata.base.str); | ||
1855 | goto out; | ||
1856 | } | ||
1857 | leftlen = cx->regExpStatics.leftContext.length; | ||
1858 | ok = find_replen(cx, &rdata, &length); | ||
1859 | if (!ok) | ||
1860 | goto out; | ||
1861 | length += leftlen; | ||
1862 | chars = (jschar *) JS_malloc(cx, (length + 1) * sizeof(jschar)); | ||
1863 | if (!chars) { | ||
1864 | ok = JS_FALSE; | ||
1865 | goto out; | ||
1866 | } | ||
1867 | js_strncpy(chars, cx->regExpStatics.leftContext.chars, leftlen); | ||
1868 | do_replace(cx, &rdata, chars + leftlen); | ||
1869 | rdata.chars = chars; | ||
1870 | rdata.length = length; | ||
1871 | } | ||
1872 | |||
1873 | rightlen = cx->regExpStatics.rightContext.length; | ||
1874 | length = rdata.length + rightlen; | ||
1875 | chars = (jschar *) | ||
1876 | JS_realloc(cx, rdata.chars, (length + 1) * sizeof(jschar)); | ||
1877 | if (!chars) { | ||
1878 | JS_free(cx, rdata.chars); | ||
1879 | ok = JS_FALSE; | ||
1880 | goto out; | ||
1881 | } | ||
1882 | js_strncpy(chars + rdata.length, cx->regExpStatics.rightContext.chars, | ||
1883 | rightlen); | ||
1884 | chars[length] = 0; | ||
1885 | |||
1886 | str = js_NewString(cx, chars, length); | ||
1887 | if (!str) { | ||
1888 | JS_free(cx, chars); | ||
1889 | ok = JS_FALSE; | ||
1890 | goto out; | ||
1891 | } | ||
1892 | *vp = STRING_TO_JSVAL(str); | ||
1893 | |||
1894 | out: | ||
1895 | /* If KEEP_REGEXP is still set, it's our job to destroy regexp now. */ | ||
1896 | if (rdata.base.flags & KEEP_REGEXP) | ||
1897 | js_DestroyRegExp(cx, rdata.base.regexp); | ||
1898 | return ok; | ||
1899 | } | ||
1900 | |||
1901 | /* | ||
1902 | siliconforks | 399 | * Subroutine used by str_split to find the next split point in str, starting |
1903 | siliconforks | 332 | * at offset *ip and looking either for the separator substring given by sep, or |
1904 | * for the next re match. In the re case, return the matched separator in *sep, | ||
1905 | * and the possibly updated offset in *ip. | ||
1906 | * | ||
1907 | * Return -2 on error, -1 on end of string, >= 0 for a valid index of the next | ||
1908 | * separator occurrence if found, or str->length if no separator is found. | ||
1909 | */ | ||
1910 | static jsint | ||
1911 | find_split(JSContext *cx, JSString *str, JSRegExp *re, jsint *ip, | ||
1912 | JSSubString *sep) | ||
1913 | { | ||
1914 | jsint i, j, k; | ||
1915 | size_t length; | ||
1916 | jschar *chars; | ||
1917 | |||
1918 | /* | ||
1919 | * Stop if past end of string. If at end of string, we will compare the | ||
1920 | * null char stored there (by js_NewString*) to sep->chars[j] in the while | ||
1921 | * loop at the end of this function, so that | ||
1922 | * | ||
1923 | * "ab,".split(',') => ["ab", ""] | ||
1924 | * | ||
1925 | * and the resulting array converts back to the string "ab," for symmetry. | ||
1926 | * However, we ape Perl and do this only if there is a sufficiently large | ||
1927 | siliconforks | 399 | * limit argument (see str_split). |
1928 | siliconforks | 332 | */ |
1929 | i = *ip; | ||
1930 | length = JSSTRING_LENGTH(str); | ||
1931 | if ((size_t)i > length) | ||
1932 | return -1; | ||
1933 | |||
1934 | chars = JSSTRING_CHARS(str); | ||
1935 | |||
1936 | /* | ||
1937 | * Match a regular expression against the separator at or above index i. | ||
1938 | * Call js_ExecuteRegExp with true for the test argument. On successful | ||
1939 | * match, get the separator from cx->regExpStatics.lastMatch. | ||
1940 | */ | ||
1941 | if (re) { | ||
1942 | size_t index; | ||
1943 | jsval rval; | ||
1944 | |||
1945 | again: | ||
1946 | /* JS1.2 deviated from Perl by never matching at end of string. */ | ||
1947 | index = (size_t)i; | ||
1948 | if (!js_ExecuteRegExp(cx, re, str, &index, JS_TRUE, &rval)) | ||
1949 | return -2; | ||
1950 | if (rval != JSVAL_TRUE) { | ||
1951 | /* Mismatch: ensure our caller advances i past end of string. */ | ||
1952 | sep->length = 1; | ||
1953 | return length; | ||
1954 | } | ||
1955 | i = (jsint)index; | ||
1956 | *sep = cx->regExpStatics.lastMatch; | ||
1957 | if (sep->length == 0) { | ||
1958 | /* | ||
1959 | * Empty string match: never split on an empty match at the start | ||
1960 | * of a find_split cycle. Same rule as for an empty global match | ||
1961 | * in match_or_replace. | ||
1962 | */ | ||
1963 | if (i == *ip) { | ||
1964 | /* | ||
1965 | * "Bump-along" to avoid sticking at an empty match, but don't | ||
1966 | * bump past end of string -- our caller must do that by adding | ||
1967 | * sep->length to our return value. | ||
1968 | */ | ||
1969 | if ((size_t)i == length) | ||
1970 | return -1; | ||
1971 | i++; | ||
1972 | goto again; | ||
1973 | } | ||
1974 | if ((size_t)i == length) { | ||
1975 | /* | ||
1976 | * If there was a trivial zero-length match at the end of the | ||
1977 | * split, then we shouldn't output the matched string at the end | ||
1978 | * of the split array. See ECMA-262 Ed. 3, 15.5.4.14, Step 15. | ||
1979 | */ | ||
1980 | sep->chars = NULL; | ||
1981 | } | ||
1982 | } | ||
1983 | JS_ASSERT((size_t)i >= sep->length); | ||
1984 | return i - sep->length; | ||
1985 | } | ||
1986 | |||
1987 | /* | ||
1988 | * Special case: if sep is the empty string, split str into one character | ||
1989 | * substrings. Let our caller worry about whether to split once at end of | ||
1990 | * string into an empty substring. | ||
1991 | */ | ||
1992 | if (sep->length == 0) | ||
1993 | return ((size_t)i == length) ? -1 : i + 1; | ||
1994 | |||
1995 | /* | ||
1996 | * Now that we know sep is non-empty, search starting at i in str for an | ||
1997 | * occurrence of all of sep's chars. If we find them, return the index of | ||
1998 | * the first separator char. Otherwise, return length. | ||
1999 | */ | ||
2000 | j = 0; | ||
2001 | while ((size_t)(k = i + j) < length) { | ||
2002 | if (chars[k] == sep->chars[j]) { | ||
2003 | if ((size_t)++j == sep->length) | ||
2004 | return i; | ||
2005 | } else { | ||
2006 | i++; | ||
2007 | j = 0; | ||
2008 | } | ||
2009 | } | ||
2010 | return k; | ||
2011 | } | ||
2012 | |||
2013 | siliconforks | 399 | static JSBool |
2014 | str_split(JSContext *cx, uintN argc, jsval *vp) | ||
2015 | siliconforks | 332 | { |
2016 | JSString *str, *sub; | ||
2017 | JSObject *arrayobj; | ||
2018 | jsval v; | ||
2019 | JSBool ok, limited; | ||
2020 | JSRegExp *re; | ||
2021 | JSSubString *sep, tmp; | ||
2022 | jsdouble d; | ||
2023 | jsint i, j; | ||
2024 | uint32 len, limit; | ||
2025 | |||
2026 | NORMALIZE_THIS(cx, vp, str); | ||
2027 | |||
2028 | arrayobj = js_ConstructObject(cx, &js_ArrayClass, NULL, NULL, 0, NULL); | ||
2029 | if (!arrayobj) | ||
2030 | return JS_FALSE; | ||
2031 | *vp = OBJECT_TO_JSVAL(arrayobj); | ||
2032 | |||
2033 | if (argc == 0) { | ||
2034 | v = STRING_TO_JSVAL(str); | ||
2035 | ok = OBJ_SET_PROPERTY(cx, arrayobj, INT_TO_JSID(0), &v); | ||
2036 | } else { | ||
2037 | if (VALUE_IS_REGEXP(cx, vp[2])) { | ||
2038 | re = (JSRegExp *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(vp[2])); | ||
2039 | sep = &tmp; | ||
2040 | |||
2041 | /* Set a magic value so we can detect a successful re match. */ | ||
2042 | sep->chars = NULL; | ||
2043 | sep->length = 0; | ||
2044 | } else { | ||
2045 | JSString *str2 = js_ValueToString(cx, vp[2]); | ||
2046 | if (!str2) | ||
2047 | return JS_FALSE; | ||
2048 | vp[2] = STRING_TO_JSVAL(str2); | ||
2049 | |||
2050 | /* | ||
2051 | * Point sep at a local copy of str2's header because find_split | ||
2052 | * will modify sep->length. | ||
2053 | */ | ||
2054 | JSSTRING_CHARS_AND_LENGTH(str2, tmp.chars, tmp.length); | ||
2055 | sep = &tmp; | ||
2056 | re = NULL; | ||
2057 | } | ||
2058 | |||
2059 | /* Use the second argument as the split limit, if given. */ | ||
2060 | limited = (argc > 1) && !JSVAL_IS_VOID(vp[3]); | ||
2061 | limit = 0; /* Avoid warning. */ | ||
2062 | if (limited) { | ||
2063 | d = js_ValueToNumber(cx, &vp[3]); | ||
2064 | if (JSVAL_IS_NULL(vp[3])) | ||
2065 | return JS_FALSE; | ||
2066 | |||
2067 | /* Clamp limit between 0 and 1 + string length. */ | ||
2068 | limit = js_DoubleToECMAUint32(d); | ||
2069 | if (limit > JSSTRING_LENGTH(str)) | ||
2070 | limit = 1 + JSSTRING_LENGTH(str); | ||
2071 | } | ||
2072 | |||
2073 | len = i = 0; | ||
2074 | while ((j = find_split(cx, str, re, &i, sep)) >= 0) { | ||
2075 | if (limited && len >= limit) | ||
2076 | break; | ||
2077 | sub = js_NewDependentString(cx, str, i, (size_t)(j - i)); | ||
2078 | if (!sub) | ||
2079 | return JS_FALSE; | ||
2080 | v = STRING_TO_JSVAL(sub); | ||
2081 | if (!JS_SetElement(cx, arrayobj, len, &v)) | ||
2082 | return JS_FALSE; | ||
2083 | len++; | ||
2084 | |||
2085 | /* | ||
2086 | * Imitate perl's feature of including parenthesized substrings | ||
2087 | * that matched part of the delimiter in the new array, after the | ||
2088 | * split substring that was delimited. | ||
2089 | */ | ||
2090 | if (re && sep->chars) { | ||
2091 | uintN num; | ||
2092 | JSSubString *parsub; | ||
2093 | |||
2094 | for (num = 0; num < cx->regExpStatics.parenCount; num++) { | ||
2095 | if (limited && len >= limit) | ||
2096 | break; | ||
2097 | parsub = REGEXP_PAREN_SUBSTRING(&cx->regExpStatics, num); | ||
2098 | sub = js_NewStringCopyN(cx, parsub->chars, parsub->length); | ||
2099 | if (!sub) | ||
2100 | return JS_FALSE; | ||
2101 | v = STRING_TO_JSVAL(sub); | ||
2102 | if (!JS_SetElement(cx, arrayobj, len, &v)) | ||
2103 | return JS_FALSE; | ||
2104 | len++; | ||
2105 | } | ||
2106 | sep->chars = NULL; | ||
2107 | } | ||
2108 | i = j + sep->length; | ||
2109 | } | ||
2110 | ok = (j != -2); | ||
2111 | } | ||
2112 | return ok; | ||
2113 | } | ||
2114 | |||
2115 | siliconforks | 399 | #ifdef JS_TRACER |
2116 | static JSObject* FASTCALL | ||
2117 | String_p_split(JSContext* cx, JSString* str, JSString* sepstr) | ||
2118 | { | ||
2119 | // FIXME: Avoid building and then parsing this array. | ||
2120 | jsval vp[4] = { JSVAL_NULL, STRING_TO_JSVAL(str), STRING_TO_JSVAL(sepstr), JSVAL_VOID }; | ||
2121 | if (!str_split(cx, 2, vp)) | ||
2122 | return NULL; | ||
2123 | JS_ASSERT(JSVAL_IS_OBJECT(vp[0])); | ||
2124 | return JSVAL_TO_OBJECT(vp[0]); | ||
2125 | } | ||
2126 | #endif | ||
2127 | |||
2128 | siliconforks | 332 | #if JS_HAS_PERL_SUBSTR |
2129 | static JSBool | ||
2130 | str_substr(JSContext *cx, uintN argc, jsval *vp) | ||
2131 | { | ||
2132 | JSString *str; | ||
2133 | jsdouble d; | ||
2134 | jsdouble length, begin, end; | ||
2135 | |||
2136 | NORMALIZE_THIS(cx, vp, str); | ||
2137 | if (argc != 0) { | ||
2138 | d = js_ValueToNumber(cx, &vp[2]); | ||
2139 | if (JSVAL_IS_NULL(vp[2])) | ||
2140 | return JS_FALSE; | ||
2141 | length = JSSTRING_LENGTH(str); | ||
2142 | begin = js_DoubleToInteger(d); | ||
2143 | if (begin < 0) { | ||
2144 | begin += length; | ||
2145 | if (begin < 0) | ||
2146 | begin = 0; | ||
2147 | } else if (begin > length) { | ||
2148 | begin = length; | ||
2149 | } | ||
2150 | |||
2151 | if (argc == 1) { | ||
2152 | end = length; | ||
2153 | } else { | ||
2154 | d = js_ValueToNumber(cx, &vp[3]); | ||
2155 | if (JSVAL_IS_NULL(vp[3])) | ||
2156 | return JS_FALSE; | ||
2157 | end = js_DoubleToInteger(d); | ||
2158 | if (end < 0) | ||
2159 | end = 0; | ||
2160 | end += begin; | ||
2161 | if (end > length) | ||
2162 | end = length; | ||
2163 | } | ||
2164 | |||
2165 | str = js_NewDependentString(cx, str, | ||
2166 | (size_t)begin, | ||
2167 | (size_t)(end - begin)); | ||
2168 | if (!str) | ||
2169 | return JS_FALSE; | ||
2170 | } | ||
2171 | *vp = STRING_TO_JSVAL(str); | ||
2172 | return JS_TRUE; | ||
2173 | } | ||
2174 | #endif /* JS_HAS_PERL_SUBSTR */ | ||
2175 | |||
2176 | /* | ||
2177 | * Python-esque sequence operations. | ||
2178 | */ | ||
2179 | siliconforks | 399 | static JSBool |
2180 | str_concat(JSContext *cx, uintN argc, jsval *vp) | ||
2181 | siliconforks | 332 | { |
2182 | JSString *str, *str2; | ||
2183 | jsval *argv; | ||
2184 | uintN i; | ||
2185 | |||
2186 | NORMALIZE_THIS(cx, vp, str); | ||
2187 | |||
2188 | for (i = 0, argv = vp + 2; i < argc; i++) { | ||
2189 | str2 = js_ValueToString(cx, argv[i]); | ||
2190 | if (!str2) | ||
2191 | return JS_FALSE; | ||
2192 | argv[i] = STRING_TO_JSVAL(str2); | ||
2193 | |||
2194 | str = js_ConcatStrings(cx, str, str2); | ||
2195 | if (!str) | ||
2196 | return JS_FALSE; | ||
2197 | } | ||
2198 | |||
2199 | *vp = STRING_TO_JSVAL(str); | ||
2200 | return JS_TRUE; | ||
2201 | } | ||
2202 | |||
2203 | siliconforks | 399 | #ifdef JS_TRACER |
2204 | static JSString* FASTCALL | ||
2205 | String_p_concat_1int(JSContext* cx, JSString* str, int32 i) | ||
2206 | { | ||
2207 | // FIXME: should be able to use stack buffer and avoid istr... | ||
2208 | JSString* istr = js_NumberToString(cx, i); | ||
2209 | if (!istr) | ||
2210 | return NULL; | ||
2211 | return js_ConcatStrings(cx, str, istr); | ||
2212 | } | ||
2213 | |||
2214 | static JSString* FASTCALL | ||
2215 | String_p_concat_2str(JSContext* cx, JSString* str, JSString* a, JSString* b) | ||
2216 | { | ||
2217 | str = js_ConcatStrings(cx, str, a); | ||
2218 | if (str) | ||
2219 | return js_ConcatStrings(cx, str, b); | ||
2220 | return NULL; | ||
2221 | } | ||
2222 | |||
2223 | static JSString* FASTCALL | ||
2224 | String_p_concat_3str(JSContext* cx, JSString* str, JSString* a, JSString* b, JSString* c) | ||
2225 | { | ||
2226 | str = js_ConcatStrings(cx, str, a); | ||
2227 | if (str) { | ||
2228 | str = js_ConcatStrings(cx, str, b); | ||
2229 | if (str) | ||
2230 | return js_ConcatStrings(cx, str, c); | ||
2231 | } | ||
2232 | return NULL; | ||
2233 | } | ||
2234 | #endif | ||
2235 | |||
2236 | siliconforks | 332 | static JSBool |
2237 | str_slice(JSContext *cx, uintN argc, jsval *vp) | ||
2238 | { | ||
2239 | jsval t, v; | ||
2240 | JSString *str; | ||
2241 | |||
2242 | t = vp[1]; | ||
2243 | v = vp[2]; | ||
2244 | if (argc == 1 && JSVAL_IS_STRING(t) && JSVAL_IS_INT(v)) { | ||
2245 | size_t begin, end, length; | ||
2246 | |||
2247 | str = JSVAL_TO_STRING(t); | ||
2248 | begin = JSVAL_TO_INT(v); | ||
2249 | end = JSSTRING_LENGTH(str); | ||
2250 | if (begin <= end) { | ||
2251 | length = end - begin; | ||
2252 | if (length == 0) { | ||
2253 | str = cx->runtime->emptyString; | ||
2254 | } else { | ||
2255 | str = (length == 1) | ||
2256 | ? js_GetUnitString(cx, str, begin) | ||
2257 | : js_NewDependentString(cx, str, begin, length); | ||
2258 | if (!str) | ||
2259 | return JS_FALSE; | ||
2260 | } | ||
2261 | *vp = STRING_TO_JSVAL(str); | ||
2262 | return JS_TRUE; | ||
2263 | } | ||
2264 | } | ||
2265 | |||
2266 | NORMALIZE_THIS(cx, vp, str); | ||
2267 | |||
2268 | if (argc != 0) { | ||
2269 | double begin, end, length; | ||
2270 | |||
2271 | begin = js_ValueToNumber(cx, &vp[2]); | ||
2272 | if (JSVAL_IS_NULL(vp[2])) | ||
2273 | return JS_FALSE; | ||
2274 | begin = js_DoubleToInteger(begin); | ||
2275 | length = JSSTRING_LENGTH(str); | ||
2276 | if (begin < 0) { | ||
2277 | begin += length; | ||
2278 | if (begin < 0) | ||
2279 | begin = 0; | ||
2280 | } else if (begin > length) { | ||
2281 | begin = length; | ||
2282 | } | ||
2283 | |||
2284 | if (argc == 1) { | ||
2285 | end = length; | ||
2286 | } else { | ||
2287 | end = js_ValueToNumber(cx, &vp[3]); | ||
2288 | if (JSVAL_IS_NULL(vp[3])) | ||
2289 | return JS_FALSE; | ||
2290 | end = js_DoubleToInteger(end); | ||
2291 | if (end < 0) { | ||
2292 | end += length; | ||
2293 | if (end < 0) | ||
2294 | end = 0; | ||
2295 | } else if (end > length) { | ||
2296 | end = length; | ||
2297 | } | ||
2298 | if (end < begin) | ||
2299 | end = begin; | ||
2300 | } | ||
2301 | |||
2302 | str = js_NewDependentString(cx, str, | ||
2303 | (size_t)begin, | ||
2304 | (size_t)(end - begin)); | ||
2305 | if (!str) | ||
2306 | return JS_FALSE; | ||
2307 | } | ||
2308 | *vp = STRING_TO_JSVAL(str); | ||
2309 | return JS_TRUE; | ||
2310 | } | ||
2311 | |||
2312 | #if JS_HAS_STR_HTML_HELPERS | ||
2313 | /* | ||
2314 | * HTML composition aids. | ||
2315 | */ | ||
2316 | static JSBool | ||
2317 | tagify(JSContext *cx, const char *begin, JSString *param, const char *end, | ||
2318 | jsval *vp) | ||
2319 | { | ||
2320 | JSString *str; | ||
2321 | jschar *tagbuf; | ||
2322 | size_t beglen, endlen, parlen, taglen; | ||
2323 | size_t i, j; | ||
2324 | |||
2325 | NORMALIZE_THIS(cx, vp, str); | ||
2326 | |||
2327 | if (!end) | ||
2328 | end = begin; | ||
2329 | |||
2330 | beglen = strlen(begin); | ||
2331 | taglen = 1 + beglen + 1; /* '<begin' + '>' */ | ||
2332 | parlen = 0; /* Avoid warning. */ | ||
2333 | if (param) { | ||
2334 | parlen = JSSTRING_LENGTH(param); | ||
2335 | taglen += 2 + parlen + 1; /* '="param"' */ | ||
2336 | } | ||
2337 | endlen = strlen(end); | ||
2338 | taglen += JSSTRING_LENGTH(str) + 2 + endlen + 1; /* 'str</end>' */ | ||
2339 | |||
2340 | if (taglen >= ~(size_t)0 / sizeof(jschar)) { | ||
2341 | js_ReportAllocationOverflow(cx); | ||
2342 | return JS_FALSE; | ||
2343 | } | ||
2344 | |||
2345 | tagbuf = (jschar *) JS_malloc(cx, (taglen + 1) * sizeof(jschar)); | ||
2346 | if (!tagbuf) | ||
2347 | return JS_FALSE; | ||
2348 | |||
2349 | j = 0; | ||
2350 | tagbuf[j++] = '<'; | ||
2351 | for (i = 0; i < beglen; i++) | ||
2352 | tagbuf[j++] = (jschar)begin[i]; | ||
2353 | if (param) { | ||
2354 | tagbuf[j++] = '='; | ||
2355 | tagbuf[j++] = '"'; | ||
2356 | js_strncpy(&tagbuf[j], JSSTRING_CHARS(param), parlen); | ||
2357 | j += parlen; | ||
2358 | tagbuf[j++] = '"'; | ||
2359 | } | ||
2360 | tagbuf[j++] = '>'; | ||
2361 | js_strncpy(&tagbuf[j], JSSTRING_CHARS(str), JSSTRING_LENGTH(str)); | ||
2362 | j += JSSTRING_LENGTH(str); | ||
2363 | tagbuf[j++] = '<'; | ||
2364 | tagbuf[j++] = '/'; | ||
2365 | for (i = 0; i < endlen; i++) | ||
2366 | tagbuf[j++] = (jschar)end[i]; | ||
2367 | tagbuf[j++] = '>'; | ||
2368 | JS_ASSERT(j == taglen); | ||
2369 | tagbuf[j] = 0; | ||
2370 | |||
2371 | str = js_NewString(cx, tagbuf, taglen); | ||
2372 | if (!str) { | ||
2373 | free((char *)tagbuf); | ||
2374 | return JS_FALSE; | ||
2375 | } | ||
2376 | *vp = STRING_TO_JSVAL(str); | ||
2377 | return JS_TRUE; | ||
2378 | } | ||
2379 | |||
2380 | static JSBool | ||
2381 | tagify_value(JSContext *cx, uintN argc, jsval *vp, | ||
2382 | const char *begin, const char *end) | ||
2383 | { | ||
2384 | JSString *param; | ||
2385 | |||
2386 | param = ArgToRootedString(cx, argc, vp, 0); | ||
2387 | if (!param) | ||
2388 | return JS_FALSE; | ||
2389 | return tagify(cx, begin, param, end, vp); | ||
2390 | } | ||
2391 | |||
2392 | static JSBool | ||
2393 | str_bold(JSContext *cx, uintN argc, jsval *vp) | ||
2394 | { | ||
2395 | return tagify(cx, "b", NULL, NULL, vp); | ||
2396 | } | ||
2397 | |||
2398 | static JSBool | ||
2399 | str_italics(JSContext *cx, uintN argc, jsval *vp) | ||
2400 | { | ||
2401 | return tagify(cx, "i", NULL, NULL, vp); | ||
2402 | } | ||
2403 | |||
2404 | static JSBool | ||
2405 | str_fixed(JSContext *cx, uintN argc, jsval *vp) | ||
2406 | { | ||
2407 | return tagify(cx, "tt", NULL, NULL, vp); | ||
2408 | } | ||
2409 | |||
2410 | static JSBool | ||
2411 | str_fontsize(JSContext *cx, uintN argc, jsval *vp) | ||
2412 | { | ||
2413 | return tagify_value(cx, argc, vp, "font size", "font"); | ||
2414 | } | ||
2415 | |||
2416 | static JSBool | ||
2417 | str_fontcolor(JSContext *cx, uintN argc, jsval *vp) | ||
2418 | { | ||
2419 | return tagify_value(cx, argc, vp, "font color", "font"); | ||
2420 | } | ||
2421 | |||
2422 | static JSBool | ||
2423 | str_link(JSContext *cx, uintN argc, jsval *vp) | ||
2424 | { | ||
2425 | return tagify_value(cx, argc, vp, "a href", "a"); | ||
2426 | } | ||
2427 | |||
2428 | static JSBool | ||
2429 | str_anchor(JSContext *cx, uintN argc, jsval *vp) | ||
2430 | { | ||
2431 | return tagify_value(cx, argc, vp, "a name", "a"); | ||
2432 | } | ||
2433 | |||
2434 | static JSBool | ||
2435 | str_strike(JSContext *cx, uintN argc, jsval *vp) | ||
2436 | { | ||
2437 | return tagify(cx, "strike", NULL, NULL, vp); | ||
2438 | } | ||
2439 | |||
2440 | static JSBool | ||
2441 | str_small(JSContext *cx, uintN argc, jsval *vp) | ||
2442 | { | ||
2443 | return tagify(cx, "small", NULL, NULL, vp); | ||
2444 | } | ||
2445 | |||
2446 | static JSBool | ||
2447 | str_big(JSContext *cx, uintN argc, jsval *vp) | ||
2448 | { | ||
2449 | return tagify(cx, "big", NULL, NULL, vp); | ||
2450 | } | ||
2451 | |||
2452 | static JSBool | ||
2453 | str_blink(JSContext *cx, uintN argc, jsval *vp) | ||
2454 | { | ||
2455 | return tagify(cx, "blink", NULL, NULL, vp); | ||
2456 | } | ||
2457 | |||
2458 | static JSBool | ||
2459 | str_sup(JSContext *cx, uintN argc, jsval *vp) | ||
2460 | { | ||
2461 | return tagify(cx, "sup", NULL, NULL, vp); | ||
2462 | } | ||
2463 | |||
2464 | static JSBool | ||
2465 | str_sub(JSContext *cx, uintN argc, jsval *vp) | ||
2466 | { | ||
2467 | return tagify(cx, "sub", NULL, NULL, vp); | ||
2468 | } | ||
2469 | #endif /* JS_HAS_STR_HTML_HELPERS */ | ||
2470 | |||
2471 | siliconforks | 399 | #ifdef JS_TRACER |
2472 | JSString* FASTCALL | ||
2473 | js_String_getelem(JSContext* cx, JSString* str, int32 i) | ||
2474 | { | ||
2475 | if ((size_t)i >= JSSTRING_LENGTH(str)) | ||
2476 | return NULL; | ||
2477 | return js_GetUnitString(cx, str, (size_t)i); | ||
2478 | } | ||
2479 | #endif | ||
2480 | |||
2481 | JS_DEFINE_CALLINFO_2(extern, BOOL, js_EqualStrings, STRING, STRING, 1, 1) | ||
2482 | JS_DEFINE_CALLINFO_2(extern, INT32, js_CompareStrings, STRING, STRING, 1, 1) | ||
2483 | |||
2484 | JS_DEFINE_TRCINFO_1(str_toString, | ||
2485 | (2, (extern, STRING_FAIL, String_p_toString, CONTEXT, THIS, 1, 1))) | ||
2486 | JS_DEFINE_TRCINFO_2(str_substring, | ||
2487 | (4, (static, STRING_FAIL, String_p_substring, CONTEXT, THIS_STRING, INT32 |