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