a354a971699a4fef51683b86d2021e9a29f3b680
[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 RegExpObjectImp::RegExpObjectImp(ExecState* exec, FunctionPrototype* funcProto, RegExpPrototype* regProto)
184
185   : InternalFunctionImp(funcProto), lastInput(""), lastNumSubPatterns(0), multiline(false)
186 {
187   // ECMA 15.10.5.1 RegExp.prototype
188   putDirect(exec->propertyNames().prototype, regProto, DontEnum | DontDelete | ReadOnly);
189
190   // no. of arguments for constructor
191   putDirect(exec->propertyNames().length, jsNumber(2), ReadOnly | DontDelete | DontEnum);
192 }
193
194 /* 
195   To facilitate result caching, exec(), test(), match(), search(), and replace() dipatch regular
196   expression matching through the performMatch function. We use cached results to calculate, 
197   e.g., RegExp.lastMatch and RegExp.leftParen.
198 */
199 UString RegExpObjectImp::performMatch(RegExp* r, const UString& s, int startOffset, int *endOffset, int **ovector)
200 {
201   int tmpOffset;
202   int *tmpOvector;
203   UString match = r->match(s, startOffset, &tmpOffset, &tmpOvector);
204
205   if (endOffset)
206     *endOffset = tmpOffset;
207   if (ovector)
208     *ovector = tmpOvector;
209   
210   if (!match.isNull()) {
211     ASSERT(tmpOvector);
212     
213     lastInput = s;
214     lastOvector.set(tmpOvector);
215     lastNumSubPatterns = r->subPatterns();
216   }
217   
218   return match;
219 }
220
221 JSObject *RegExpObjectImp::arrayOfMatches(ExecState *exec, const UString &result) const
222 {
223   List list;
224   // The returned array contains 'result' as first item, followed by the list of matches
225   list.append(jsString(result));
226   if ( lastOvector )
227     for ( unsigned i = 1 ; i < lastNumSubPatterns + 1 ; ++i )
228     {
229       int start = lastOvector[2*i];
230       if (start == -1)
231         list.append(jsUndefined());
232       else {
233         UString substring = lastInput.substr( start, lastOvector[2*i+1] - start );
234         list.append(jsString(substring));
235       }
236     }
237   JSObject *arr = exec->lexicalInterpreter()->builtinArray()->construct(exec, list);
238   arr->put(exec, "index", jsNumber(lastOvector[0]));
239   arr->put(exec, "input", jsString(lastInput));
240   return arr;
241 }
242
243 JSValue *RegExpObjectImp::getBackref(unsigned i) const
244 {
245   if (lastOvector && i < lastNumSubPatterns + 1) {
246     UString substring = lastInput.substr(lastOvector[2*i], lastOvector[2*i+1] - lastOvector[2*i] );
247     return jsString(substring);
248   } 
249
250   return jsString("");
251 }
252
253 JSValue *RegExpObjectImp::getLastMatch() const
254 {
255   if (lastOvector) {
256     UString substring = lastInput.substr(lastOvector[0], lastOvector[1] - lastOvector[0]);
257     return jsString(substring);
258   }
259   
260   return jsString("");
261 }
262
263 JSValue *RegExpObjectImp::getLastParen() const
264 {
265   int i = lastNumSubPatterns;
266   if (i > 0) {
267     ASSERT(lastOvector);
268     UString substring = lastInput.substr(lastOvector[2*i], lastOvector[2*i+1] - lastOvector[2*i]);
269     return jsString(substring);
270   }
271     
272   return jsString("");
273 }
274
275 JSValue *RegExpObjectImp::getLeftContext() const
276 {
277   if (lastOvector) {
278     UString substring = lastInput.substr(0, lastOvector[0]);
279     return jsString(substring);
280   }
281   
282   return jsString("");
283 }
284
285 JSValue *RegExpObjectImp::getRightContext() const
286 {
287   if (lastOvector) {
288     UString s = lastInput;
289     UString substring = s.substr(lastOvector[1], s.size() - lastOvector[1]);
290     return jsString(substring);
291   }
292   
293   return jsString("");
294 }
295
296 bool RegExpObjectImp::getOwnPropertySlot(ExecState *exec, const Identifier& propertyName, PropertySlot& slot)
297 {
298   return getStaticValueSlot<RegExpObjectImp, InternalFunctionImp>(exec, &RegExpTable, this, propertyName, slot);
299 }
300
301 JSValue *RegExpObjectImp::getValueProperty(ExecState*, int token) const
302 {
303   switch (token) {
304     case Dollar1:
305       return getBackref(1);
306     case Dollar2:
307       return getBackref(2);
308     case Dollar3:
309       return getBackref(3);
310     case Dollar4:
311       return getBackref(4);
312     case Dollar5:
313       return getBackref(5);
314     case Dollar6:
315       return getBackref(6);
316     case Dollar7:
317       return getBackref(7);
318     case Dollar8:
319       return getBackref(8);
320     case Dollar9:
321       return getBackref(9);
322     case Input:
323       return jsString(lastInput);
324     case Multiline:
325       return jsBoolean(multiline);
326     case LastMatch:
327       return getLastMatch();
328     case LastParen:
329       return getLastParen();
330     case LeftContext:
331       return getLeftContext();
332     case RightContext:
333       return getRightContext();
334     default:
335       ASSERT(0);
336   }
337
338   return jsString("");
339 }
340
341 void RegExpObjectImp::put(ExecState *exec, const Identifier &propertyName, JSValue *value, int attr)
342 {
343   lookupPut<RegExpObjectImp, InternalFunctionImp>(exec, propertyName, value, attr, &RegExpTable, this);
344 }
345
346 void RegExpObjectImp::putValueProperty(ExecState *exec, int token, JSValue *value, int)
347 {
348   switch (token) {
349     case Input:
350       lastInput = value->toString(exec);
351       break;
352     case Multiline:
353       multiline = value->toBoolean(exec);
354       break;
355     default:
356       ASSERT(0);
357   }
358 }
359   
360 bool RegExpObjectImp::implementsConstruct() const
361 {
362   return true;
363 }
364
365 // ECMA 15.10.4
366 JSObject *RegExpObjectImp::construct(ExecState *exec, const List &args)
367 {
368   JSObject *o = args[0]->getObject();
369   if (o && o->inherits(&RegExpImp::info)) {
370     if (!args[1]->isUndefined())
371       return throwError(exec, TypeError);
372     return o;
373   }
374   
375   UString p = args[0]->isUndefined() ? UString("") : args[0]->toString(exec);
376   UString flags = args[1]->isUndefined() ? UString("") : args[1]->toString(exec);
377
378   RegExpPrototype *proto = static_cast<RegExpPrototype*>(exec->lexicalInterpreter()->builtinRegExpPrototype());
379   RegExpImp *dat = new RegExpImp(proto);
380
381   bool global = (flags.find("g") >= 0);
382   bool ignoreCase = (flags.find("i") >= 0);
383   bool multiline = (flags.find("m") >= 0);
384
385   dat->putDirect("global", jsBoolean(global), DontDelete | ReadOnly | DontEnum);
386   dat->putDirect("ignoreCase", jsBoolean(ignoreCase), DontDelete | ReadOnly | DontEnum);
387   dat->putDirect("multiline", jsBoolean(multiline), DontDelete | ReadOnly | DontEnum);
388
389   dat->putDirect("source", jsString(p), DontDelete | ReadOnly | DontEnum);
390   dat->putDirect("lastIndex", jsNumber(0), DontDelete | DontEnum);
391
392   int reflags = RegExp::None;
393   if (global)
394       reflags |= RegExp::Global;
395   if (ignoreCase)
396       reflags |= RegExp::IgnoreCase;
397   if (multiline)
398       reflags |= RegExp::Multiline;
399
400   OwnPtr<RegExp> re(new RegExp(p, reflags));
401   if (!re->isValid())
402       return throwError(exec, SyntaxError, UString("Invalid regular expression: ").append(re->errorMessage()));
403
404   dat->setRegExp(re.release());
405
406   return dat;
407 }
408
409 // ECMA 15.10.3
410 JSValue *RegExpObjectImp::callAsFunction(ExecState *exec, JSObject * /*thisObj*/, const List &args)
411 {
412   return construct(exec, args);
413 }