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