Reviewed by Darin.
[WebKit-https.git] / JavaScriptCore / kjs / regexp_object.cpp
1 /*
2  *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
3  *  Copyright (C) 2003, 2007, 2008 Apple Inc. All Rights Reserved.
4  *
5  *  This library is free software; you can redistribute it and/or
6  *  modify it under the terms of the GNU Lesser General Public
7  *  License as published by the Free Software Foundation; either
8  *  version 2 of the License, or (at your option) any later version.
9  *
10  *  This library is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  *  Lesser General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Lesser General Public
16  *  License along with this library; if not, write to the Free Software
17  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  */
20
21 #include "config.h"
22 #include "regexp_object.h"
23 #include "regexp_object.lut.h"
24
25 #include "array_instance.h"
26 #include "array_object.h"
27 #include "error_object.h"
28 #include "internal.h"
29 #include "object.h"
30 #include "operations.h"
31 #include "regexp.h"
32 #include "types.h"
33 #include "value.h"
34 #include "UnusedParam.h"
35
36 #include <stdio.h>
37
38 namespace KJS {
39
40 // ------------------------------ RegExpPrototype ---------------------------
41
42 static JSValue* regExpProtoFuncTest(ExecState*, JSObject*, const List&);
43 static JSValue* regExpProtoFuncExec(ExecState*, JSObject*, const List&);
44 static JSValue* regExpProtoFuncCompile(ExecState*, JSObject*, const List&);
45 static JSValue* regExpProtoFuncToString(ExecState*, JSObject*, const List&);
46
47 // ECMA 15.10.5
48
49 const ClassInfo RegExpPrototype::info = { "RegExpPrototype", 0, 0 };
50
51 RegExpPrototype::RegExpPrototype(ExecState* exec, ObjectPrototype* objectPrototype, FunctionPrototype* functionPrototype)
52     : JSObject(objectPrototype)
53 {
54     static const Identifier* compilePropertyName = new Identifier("compile");
55     static const Identifier* execPropertyName = new Identifier("exec");
56     static const Identifier* testPropertyName = new Identifier("test");
57
58     putDirectFunction(new PrototypeFunction(exec, functionPrototype, 0, *compilePropertyName, regExpProtoFuncCompile), DontEnum);
59     putDirectFunction(new PrototypeFunction(exec, functionPrototype, 0, *execPropertyName, regExpProtoFuncExec), DontEnum);
60     putDirectFunction(new PrototypeFunction(exec, functionPrototype, 0, *testPropertyName, regExpProtoFuncTest), DontEnum);
61     putDirectFunction(new PrototypeFunction(exec, functionPrototype, 0, exec->propertyNames().toString, regExpProtoFuncToString), DontEnum);
62 }
63
64 // ------------------------------ Functions ---------------------------
65     
66 JSValue* regExpProtoFuncTest(ExecState* exec, JSObject* thisObj, const List& args)
67 {
68     if (!thisObj->inherits(&RegExpImp::info))
69         return throwError(exec, TypeError);
70
71     return static_cast<RegExpImp*>(thisObj)->test(exec, args);
72 }
73
74 JSValue* regExpProtoFuncExec(ExecState* exec, JSObject* thisObj, const List& args)
75 {
76     if (!thisObj->inherits(&RegExpImp::info))
77         return throwError(exec, TypeError);
78
79     return static_cast<RegExpImp*>(thisObj)->exec(exec, args);
80 }
81
82 JSValue* regExpProtoFuncCompile(ExecState* exec, JSObject* thisObj, const List& args)
83 {
84     if (!thisObj->inherits(&RegExpImp::info))
85         return throwError(exec, TypeError);
86
87     RefPtr<RegExp> regExp;
88     JSValue* arg0 = args[0];
89     JSValue* arg1 = args[1];
90     
91     if (arg0->isObject(&RegExpImp::info)) {
92         if (!arg1->isUndefined())
93             return throwError(exec, TypeError, "Cannot supply flags when constructing one RegExp from another.");
94         regExp = static_cast<RegExpImp*>(arg0)->regExp();
95     } else {
96         UString pattern = args.isEmpty() ? UString("") : arg0->toString(exec);
97         UString flags = arg1->isUndefined() ? UString("") : arg1->toString(exec);
98         regExp = new RegExp(pattern, flags);
99     }
100
101     if (!regExp->isValid())
102         return throwError(exec, SyntaxError, UString("Invalid regular expression: ").append(regExp->errorMessage()));
103
104     static_cast<RegExpImp*>(thisObj)->setRegExp(regExp.release());
105     static_cast<RegExpImp*>(thisObj)->put(exec, exec->propertyNames().lastIndex, jsNumber(0), DontDelete|DontEnum);
106     return jsUndefined();
107 }
108
109 JSValue* regExpProtoFuncToString(ExecState* exec, JSObject* thisObj, const List&)
110 {
111     if (!thisObj->inherits(&RegExpImp::info)) {
112         if (thisObj->inherits(&RegExpPrototype::info))
113             return jsString("//");
114         return throwError(exec, TypeError);
115     }
116
117     UString result = "/" + thisObj->get(exec, exec->propertyNames().source)->toString(exec) + "/";
118     if (thisObj->get(exec, exec->propertyNames().global)->toBoolean(exec))
119         result += "g";
120     if (thisObj->get(exec, exec->propertyNames().ignoreCase)->toBoolean(exec))
121         result += "i";
122     if (thisObj->get(exec, exec->propertyNames().multiline)->toBoolean(exec))
123         result += "m";
124     return jsString(result);
125
126     return jsUndefined();
127 }
128
129 // ------------------------------ RegExpImp ------------------------------------
130
131 const ClassInfo RegExpImp::info = { "RegExp", 0, &RegExpImpTable };
132
133 /* Source for regexp_object.lut.h
134 @begin RegExpImpTable 5
135     global        RegExpImp::Global       DontDelete|ReadOnly|DontEnum
136     ignoreCase    RegExpImp::IgnoreCase   DontDelete|ReadOnly|DontEnum
137     multiline     RegExpImp::Multiline    DontDelete|ReadOnly|DontEnum
138     source        RegExpImp::Source       DontDelete|ReadOnly|DontEnum
139     lastIndex     RegExpImp::LastIndex    DontDelete|DontEnum
140 @end
141 */
142
143 RegExpImp::RegExpImp(RegExpPrototype* regexpProto, PassRefPtr<RegExp> regExp)
144   : JSObject(regexpProto)
145   , m_regExp(regExp)
146   , m_lastIndex(0)
147 {
148 }
149
150 RegExpImp::~RegExpImp()
151 {
152 }
153
154 bool RegExpImp::getOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot)
155 {
156   return getStaticValueSlot<RegExpImp, JSObject>(exec, &RegExpImpTable, this, propertyName, slot);
157 }
158
159 JSValue* RegExpImp::getValueProperty(ExecState*, int token) const
160 {
161     switch (token) {
162         case Global:
163             return jsBoolean(m_regExp->global());
164         case IgnoreCase:
165             return jsBoolean(m_regExp->ignoreCase());
166         case Multiline:
167             return jsBoolean(m_regExp->multiline());
168         case Source:
169             return jsString(m_regExp->pattern());
170         case LastIndex:
171             return jsNumber(m_lastIndex);
172     }
173     
174     ASSERT_NOT_REACHED();
175     return 0;
176 }
177
178 void RegExpImp::put(ExecState* exec, const Identifier& propertyName, JSValue* value, int attributes)
179 {
180     lookupPut<RegExpImp, JSObject>(exec, propertyName, value, attributes, &RegExpImpTable, this);
181 }
182
183 void RegExpImp::putValueProperty(ExecState* exec, int token, JSValue* value, int)
184 {
185     UNUSED_PARAM(token);
186     ASSERT(token == LastIndex);
187     m_lastIndex = value->toInteger(exec);
188 }
189
190 bool RegExpImp::match(ExecState* exec, const List& args)
191 {
192     RegExpObjectImp* regExpObj = exec->lexicalGlobalObject()->regExpConstructor();
193
194     UString input;
195     if (!args.isEmpty())
196         input = args[0]->toString(exec);
197     else {
198         input = regExpObj->input();
199         if (input.isNull()) {
200             throwError(exec, GeneralError, "No input.");
201             return false;
202         }
203     }
204
205     bool global = get(exec, exec->propertyNames().global)->toBoolean(exec);
206     int lastIndex = 0;
207     if (global) {
208         double lastIndexDouble = get(exec, exec->propertyNames().lastIndex)->toInteger(exec);
209         if (lastIndexDouble < 0 || lastIndexDouble > input.size()) {
210             put(exec, exec->propertyNames().lastIndex, jsNumber(0), DontDelete | DontEnum);
211             return false;
212         }
213         lastIndex = static_cast<int>(lastIndexDouble);
214     }
215
216     int foundIndex;
217     int foundLength;
218     regExpObj->performMatch(m_regExp.get(), input, lastIndex, foundIndex, foundLength);
219
220     if (global) {
221         lastIndex = foundIndex < 0 ? 0 : foundIndex + foundLength;
222         put(exec, exec->propertyNames().lastIndex, jsNumber(lastIndex), DontDelete | DontEnum);
223     }
224
225     return foundIndex >= 0;
226 }
227
228 JSValue* RegExpImp::test(ExecState* exec, const List& args)
229 {
230     return jsBoolean(match(exec, args));
231 }
232
233 JSValue* RegExpImp::exec(ExecState* exec, const List& args)
234 {
235     return match(exec, args)
236         ? exec->lexicalGlobalObject()->regExpConstructor()->arrayOfMatches(exec)
237         :  jsNull();
238 }
239
240 bool RegExpImp::implementsCall() const
241 {
242     return true;
243 }
244
245 JSValue* RegExpImp::callAsFunction(ExecState* exec, JSObject*, const List& args)
246 {
247     return RegExpImp::exec(exec, args);
248 }
249
250 // ------------------------------ RegExpObjectImp ------------------------------
251
252 const ClassInfo RegExpObjectImp::info = { "Function", &InternalFunctionImp::info, &RegExpObjectImpTable };
253
254 /* Source for regexp_object.lut.h
255 @begin RegExpObjectImpTable 21
256   input           RegExpObjectImp::Input          None
257   $_              RegExpObjectImp::Input          DontEnum
258   multiline       RegExpObjectImp::Multiline      None
259   $*              RegExpObjectImp::Multiline      DontEnum
260   lastMatch       RegExpObjectImp::LastMatch      DontDelete|ReadOnly
261   $&              RegExpObjectImp::LastMatch      DontDelete|ReadOnly|DontEnum
262   lastParen       RegExpObjectImp::LastParen      DontDelete|ReadOnly
263   $+              RegExpObjectImp::LastParen      DontDelete|ReadOnly|DontEnum
264   leftContext     RegExpObjectImp::LeftContext    DontDelete|ReadOnly
265   $`              RegExpObjectImp::LeftContext    DontDelete|ReadOnly|DontEnum
266   rightContext    RegExpObjectImp::RightContext   DontDelete|ReadOnly
267   $'              RegExpObjectImp::RightContext   DontDelete|ReadOnly|DontEnum
268   $1              RegExpObjectImp::Dollar1        DontDelete|ReadOnly
269   $2              RegExpObjectImp::Dollar2        DontDelete|ReadOnly
270   $3              RegExpObjectImp::Dollar3        DontDelete|ReadOnly
271   $4              RegExpObjectImp::Dollar4        DontDelete|ReadOnly
272   $5              RegExpObjectImp::Dollar5        DontDelete|ReadOnly
273   $6              RegExpObjectImp::Dollar6        DontDelete|ReadOnly
274   $7              RegExpObjectImp::Dollar7        DontDelete|ReadOnly
275   $8              RegExpObjectImp::Dollar8        DontDelete|ReadOnly
276   $9              RegExpObjectImp::Dollar9        DontDelete|ReadOnly
277 @end
278 */
279
280 struct RegExpObjectImpPrivate {
281   // Global search cache / settings
282   RegExpObjectImpPrivate() : lastNumSubPatterns(0), multiline(false) { }
283   UString lastInput;
284   OwnArrayPtr<int> lastOvector;
285   unsigned lastNumSubPatterns : 31;
286   bool multiline              : 1;
287 };
288
289 RegExpObjectImp::RegExpObjectImp(ExecState* exec, FunctionPrototype* funcProto, RegExpPrototype* regProto)
290   : InternalFunctionImp(funcProto)
291   , d(new RegExpObjectImpPrivate)
292 {
293   // ECMA 15.10.5.1 RegExp.prototype
294   putDirect(exec->propertyNames().prototype, regProto, DontEnum | DontDelete | ReadOnly);
295
296   // no. of arguments for constructor
297   putDirect(exec->propertyNames().length, jsNumber(2), ReadOnly | DontDelete | DontEnum);
298 }
299
300 /* 
301   To facilitate result caching, exec(), test(), match(), search(), and replace() dipatch regular
302   expression matching through the performMatch function. We use cached results to calculate, 
303   e.g., RegExp.lastMatch and RegExp.leftParen.
304 */
305 void RegExpObjectImp::performMatch(RegExp* r, const UString& s, int startOffset, int& position, int& length, int** ovector)
306 {
307   OwnArrayPtr<int> tmpOvector;
308   position = r->match(s, startOffset, &tmpOvector);
309
310   if (ovector)
311     *ovector = tmpOvector.get();
312   
313   if (position != -1) {
314     ASSERT(tmpOvector);
315
316     length = tmpOvector[1] - tmpOvector[0];
317
318     d->lastInput = s;
319     d->lastOvector.set(tmpOvector.release());
320     d->lastNumSubPatterns = r->numSubpatterns();
321   }
322 }
323
324 JSObject* RegExpObjectImp::arrayOfMatches(ExecState* exec) const
325 {
326   unsigned lastNumSubpatterns = d->lastNumSubPatterns;
327   ArrayInstance* arr = new ArrayInstance(exec->lexicalGlobalObject()->arrayPrototype(), lastNumSubpatterns + 1);
328   for (unsigned i = 0; i <= lastNumSubpatterns; ++i) {
329     int start = d->lastOvector[2 * i];
330     if (start >= 0)
331       arr->put(exec, i, jsString(d->lastInput.substr(start, d->lastOvector[2 * i + 1] - start)));
332   }
333   arr->put(exec, exec->propertyNames().index, jsNumber(d->lastOvector[0]));
334   arr->put(exec, exec->propertyNames().input, jsString(d->lastInput));
335   return arr;
336 }
337
338 JSValue* RegExpObjectImp::getBackref(unsigned i) const
339 {
340   if (d->lastOvector && i <= d->lastNumSubPatterns)
341     return jsString(d->lastInput.substr(d->lastOvector[2 * i], d->lastOvector[2 * i + 1] - d->lastOvector[2 * i]));
342   return jsString("");
343 }
344
345 JSValue* RegExpObjectImp::getLastParen() const
346 {
347   unsigned i = d->lastNumSubPatterns;
348   if (i > 0) {
349     ASSERT(d->lastOvector);
350     return jsString(d->lastInput.substr(d->lastOvector[2 * i], d->lastOvector[2 * i + 1] - d->lastOvector[2 * i]));
351   }
352   return jsString("");
353 }
354
355 JSValue *RegExpObjectImp::getLeftContext() const
356 {
357   if (d->lastOvector)
358     return jsString(d->lastInput.substr(0, d->lastOvector[0]));
359   return jsString("");
360 }
361
362 JSValue *RegExpObjectImp::getRightContext() const
363 {
364   if (d->lastOvector) {
365     UString s = d->lastInput;
366     return jsString(s.substr(d->lastOvector[1], s.size() - d->lastOvector[1]));
367   }
368   return jsString("");
369 }
370
371 bool RegExpObjectImp::getOwnPropertySlot(ExecState *exec, const Identifier& propertyName, PropertySlot& slot)
372 {
373   return getStaticValueSlot<RegExpObjectImp, InternalFunctionImp>(exec, &RegExpObjectImpTable, this, propertyName, slot);
374 }
375
376 JSValue *RegExpObjectImp::getValueProperty(ExecState*, int token) const
377 {
378   switch (token) {
379     case Dollar1:
380       return getBackref(1);
381     case Dollar2:
382       return getBackref(2);
383     case Dollar3:
384       return getBackref(3);
385     case Dollar4:
386       return getBackref(4);
387     case Dollar5:
388       return getBackref(5);
389     case Dollar6:
390       return getBackref(6);
391     case Dollar7:
392       return getBackref(7);
393     case Dollar8:
394       return getBackref(8);
395     case Dollar9:
396       return getBackref(9);
397     case Input:
398       return jsString(d->lastInput);
399     case Multiline:
400       return jsBoolean(d->multiline);
401     case LastMatch:
402       return getBackref(0);
403     case LastParen:
404       return getLastParen();
405     case LeftContext:
406       return getLeftContext();
407     case RightContext:
408       return getRightContext();
409     default:
410       ASSERT(0);
411   }
412
413   return jsString("");
414 }
415
416 void RegExpObjectImp::put(ExecState *exec, const Identifier &propertyName, JSValue *value, int attr)
417 {
418   lookupPut<RegExpObjectImp, InternalFunctionImp>(exec, propertyName, value, attr, &RegExpObjectImpTable, this);
419 }
420
421 void RegExpObjectImp::putValueProperty(ExecState *exec, int token, JSValue *value, int)
422 {
423   switch (token) {
424     case Input:
425       d->lastInput = value->toString(exec);
426       break;
427     case Multiline:
428       d->multiline = value->toBoolean(exec);
429       break;
430     default:
431       ASSERT(0);
432   }
433 }
434   
435 bool RegExpObjectImp::implementsConstruct() const
436 {
437   return true;
438 }
439
440 // ECMA 15.10.4
441 JSObject *RegExpObjectImp::construct(ExecState *exec, const List &args)
442 {
443   JSValue* arg0 = args[0];
444   JSValue* arg1 = args[1];
445   
446   if (arg0->isObject(&RegExpImp::info)) {
447     if (!arg1->isUndefined())
448       return throwError(exec, TypeError, "Cannot supply flags when constructing one RegExp from another.");
449     return static_cast<JSObject*>(arg0);
450   }
451   
452   UString pattern = arg0->isUndefined() ? UString("") : arg0->toString(exec);
453   UString flags = arg1->isUndefined() ? UString("") : arg1->toString(exec);
454   
455   return createRegExpImp(exec, new RegExp(pattern, flags));
456 }
457
458 JSObject* RegExpObjectImp::createRegExpImp(ExecState* exec, PassRefPtr<RegExp> regExp)
459 {
460     return regExp->isValid()
461         ? new RegExpImp(static_cast<RegExpPrototype*>(exec->lexicalGlobalObject()->regExpPrototype()), regExp)
462         : throwError(exec, SyntaxError, UString("Invalid regular expression: ").append(regExp->errorMessage()));
463 }
464
465 // ECMA 15.10.3
466 JSValue *RegExpObjectImp::callAsFunction(ExecState *exec, JSObject * /*thisObj*/, const List &args)
467 {
468   return construct(exec, args);
469 }
470
471 const UString& RegExpObjectImp::input() const
472 {
473     // Can detect a distinct initial state that is invisible to JavaScript, by checking for null
474     // state (since jsString turns null strings to empty strings).
475     return d->lastInput;
476 }
477
478 }