5fc0365ecfbe2a124990027e34bb2362d64d94a8
[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     UString source;
88     bool global = false;
89     bool ignoreCase = false;
90     bool multiline = false;
91     if (args.size() > 0) {
92       if (args[0]->isObject(&RegExpImp::info)) {
93         if (args.size() != 1)
94           return throwError(exec, TypeError, "cannot supply flags when constructing one RegExp from another.");
95
96         // Flags are mirrored on the JS object and in the implementation, while source is only preserved on the JS object.
97         RegExp* rhsRegExp = static_cast<RegExpImp*>(args[0])->regExp();
98         global = rhsRegExp->flags() & RegExp::Global;
99         ignoreCase = rhsRegExp->flags() & RegExp::IgnoreCase;
100         multiline = rhsRegExp->flags() & RegExp::Multiline;
101         source = static_cast<RegExpImp*>(args[0])->get(exec, exec->propertyNames().source)->toString(exec);
102       } else
103         source = args[0]->toString(exec);
104
105       if (!args[1]->isUndefined()) {
106         UString flags = args[1]->toString(exec);
107
108         global = (flags.find("g") >= 0);
109         ignoreCase = (flags.find("i") >= 0);
110         multiline = (flags.find("m") >= 0);
111       }
112     }
113
114     int reflags = RegExp::None;
115     if (global)
116         reflags |= RegExp::Global;
117     if (ignoreCase)
118         reflags |= RegExp::IgnoreCase;
119     if (multiline)
120         reflags |= RegExp::Multiline;
121
122     OwnPtr<RegExp> newRegExp(new RegExp(source, reflags));
123     if (!newRegExp->isValid())
124         return throwError(exec, SyntaxError, UString("Invalid regular expression: ").append(newRegExp->errorMessage()));
125
126     thisObj->putDirect(exec->propertyNames().global, jsBoolean(global), DontDelete | ReadOnly | DontEnum);
127     thisObj->putDirect(exec->propertyNames().ignoreCase, jsBoolean(ignoreCase), DontDelete | ReadOnly | DontEnum);
128     thisObj->putDirect(exec->propertyNames().multiline, jsBoolean(multiline), DontDelete | ReadOnly | DontEnum);
129     thisObj->putDirect(exec->propertyNames().source, jsString(source), DontDelete | ReadOnly | DontEnum);
130     thisObj->putDirect(exec->propertyNames().lastIndex, jsNumber(0), DontDelete | DontEnum);
131
132     static_cast<RegExpImp*>(thisObj)->setRegExp(newRegExp.release());
133     return jsUndefined();
134   }
135   case ToString:
136     UString result = "/" + thisObj->get(exec, exec->propertyNames().source)->toString(exec) + "/";
137     if (thisObj->get(exec, exec->propertyNames().global)->toBoolean(exec)) {
138       result += "g";
139     }
140     if (thisObj->get(exec, exec->propertyNames().ignoreCase)->toBoolean(exec)) {
141       result += "i";
142     }
143     if (thisObj->get(exec, exec->propertyNames().multiline)->toBoolean(exec)) {
144       result += "m";
145     }
146     return jsString(result);
147   }
148
149   return jsUndefined();
150 }
151
152 // ------------------------------ RegExpImp ------------------------------------
153
154 const ClassInfo RegExpImp::info = { "RegExp", 0, 0 };
155
156 RegExpImp::RegExpImp(RegExpPrototype* regexpProto, RegExp* exp)
157   : JSObject(regexpProto)
158   , m_regExp(exp)
159 {
160 }
161
162 RegExpImp::~RegExpImp()
163 {
164 }
165
166 bool RegExpImp::match(ExecState* exec, const List& args)
167 {
168     RegExpObjectImp* regExpObj = exec->lexicalInterpreter()->builtinRegExp();
169
170     UString input;
171     if (!args.isEmpty())
172         input = args[0]->toString(exec);
173     else {
174         input = regExpObj->input();
175         if (input.isNull()) {
176             throwError(exec, GeneralError, "No input.");
177             return false;
178         }
179     }
180
181     bool global = get(exec, exec->propertyNames().global)->toBoolean(exec);
182     int lastIndex = 0;
183     if (global) {
184         double lastIndexDouble = get(exec, exec->propertyNames().lastIndex)->toInteger(exec);
185         if (lastIndexDouble < 0 || lastIndexDouble > input.size()) {
186             put(exec, exec->propertyNames().lastIndex, jsNumber(0), DontDelete | DontEnum);
187             return false;
188         }
189         lastIndex = static_cast<int>(lastIndexDouble);
190     }
191
192     int foundIndex;
193     int foundLength;
194     regExpObj->performMatch(m_regExp.get(), input, lastIndex, foundIndex, foundLength);
195
196     if (global) {
197         lastIndex = foundIndex < 0 ? 0 : foundIndex + foundLength;
198         put(exec, exec->propertyNames().lastIndex, jsNumber(lastIndex), DontDelete | DontEnum);
199     }
200
201     return foundIndex >= 0;
202 }
203
204 JSValue* RegExpImp::test(ExecState* exec, const List& args)
205 {
206     return jsBoolean(match(exec, args));
207 }
208
209 JSValue* RegExpImp::exec(ExecState* exec, const List& args)
210 {
211     return match(exec, args)
212         ? exec->lexicalInterpreter()->builtinRegExp()->arrayOfMatches(exec)
213         :  jsNull();
214 }
215
216 bool RegExpImp::implementsCall() const
217 {
218     return true;
219 }
220
221 JSValue* RegExpImp::callAsFunction(ExecState* exec, JSObject*, const List& args)
222 {
223     return RegExpImp::exec(exec, args);
224 }
225
226 // ------------------------------ RegExpObjectImp ------------------------------
227
228 const ClassInfo RegExpObjectImp::info = { "Function", &InternalFunctionImp::info, &RegExpTable };
229
230 /* Source for regexp_object.lut.h
231 @begin RegExpTable 20
232   input           RegExpObjectImp::Input          None
233   $_              RegExpObjectImp::Input          DontEnum
234   multiline       RegExpObjectImp::Multiline      None
235   $*              RegExpObjectImp::Multiline      DontEnum
236   lastMatch       RegExpObjectImp::LastMatch      DontDelete|ReadOnly
237   $&              RegExpObjectImp::LastMatch      DontDelete|ReadOnly|DontEnum
238   lastParen       RegExpObjectImp::LastParen      DontDelete|ReadOnly
239   $+              RegExpObjectImp::LastParen      DontDelete|ReadOnly|DontEnum
240   leftContext     RegExpObjectImp::LeftContext    DontDelete|ReadOnly
241   $`              RegExpObjectImp::LeftContext    DontDelete|ReadOnly|DontEnum
242   rightContext    RegExpObjectImp::RightContext   DontDelete|ReadOnly
243   $'              RegExpObjectImp::RightContext   DontDelete|ReadOnly|DontEnum
244   $1              RegExpObjectImp::Dollar1        DontDelete|ReadOnly
245   $2              RegExpObjectImp::Dollar2        DontDelete|ReadOnly
246   $3              RegExpObjectImp::Dollar3        DontDelete|ReadOnly
247   $4              RegExpObjectImp::Dollar4        DontDelete|ReadOnly
248   $5              RegExpObjectImp::Dollar5        DontDelete|ReadOnly
249   $6              RegExpObjectImp::Dollar6        DontDelete|ReadOnly
250   $7              RegExpObjectImp::Dollar7        DontDelete|ReadOnly
251   $8              RegExpObjectImp::Dollar8        DontDelete|ReadOnly
252   $9              RegExpObjectImp::Dollar9        DontDelete|ReadOnly
253 @end
254 */
255
256 struct RegExpObjectImpPrivate {
257   // Global search cache / settings
258   RegExpObjectImpPrivate() : lastNumSubPatterns(0), multiline(false) { }
259   UString lastInput;
260   OwnArrayPtr<int> lastOvector;
261   unsigned lastNumSubPatterns : 31;
262   bool multiline              : 1;
263 };
264
265 RegExpObjectImp::RegExpObjectImp(ExecState* exec, FunctionPrototype* funcProto, RegExpPrototype* regProto)
266   : InternalFunctionImp(funcProto)
267   , d(new RegExpObjectImpPrivate)
268 {
269   // ECMA 15.10.5.1 RegExp.prototype
270   putDirect(exec->propertyNames().prototype, regProto, DontEnum | DontDelete | ReadOnly);
271
272   // no. of arguments for constructor
273   putDirect(exec->propertyNames().length, jsNumber(2), ReadOnly | DontDelete | DontEnum);
274 }
275
276 /* 
277   To facilitate result caching, exec(), test(), match(), search(), and replace() dipatch regular
278   expression matching through the performMatch function. We use cached results to calculate, 
279   e.g., RegExp.lastMatch and RegExp.leftParen.
280 */
281 void RegExpObjectImp::performMatch(RegExp* r, const UString& s, int startOffset, int& position, int& length, int** ovector)
282 {
283   OwnArrayPtr<int> tmpOvector;
284   position = r->match(s, startOffset, &tmpOvector);
285
286   if (ovector)
287     *ovector = tmpOvector.get();
288   
289   if (position != -1) {
290     ASSERT(tmpOvector);
291
292     length = tmpOvector[1] - tmpOvector[0];
293
294     d->lastInput = s;
295     d->lastOvector.set(tmpOvector.release());
296     d->lastNumSubPatterns = r->subPatterns();
297   }
298 }
299
300 JSObject* RegExpObjectImp::arrayOfMatches(ExecState* exec) const
301 {
302   unsigned lastNumSubpatterns = d->lastNumSubPatterns;
303   ArrayInstance* arr = new ArrayInstance(exec->lexicalInterpreter()->builtinArrayPrototype(), lastNumSubpatterns + 1);
304   for (unsigned i = 0; i <= lastNumSubpatterns; ++i) {
305     int start = d->lastOvector[2 * i];
306     if (start >= 0)
307       arr->put(exec, i, jsString(d->lastInput.substr(start, d->lastOvector[2 * i + 1] - start)));
308   }
309   arr->put(exec, exec->propertyNames().index, jsNumber(d->lastOvector[0]));
310   arr->put(exec, exec->propertyNames().input, jsString(d->lastInput));
311   return arr;
312 }
313
314 JSValue* RegExpObjectImp::getBackref(unsigned i) const
315 {
316   if (d->lastOvector && i <= d->lastNumSubPatterns)
317     return jsString(d->lastInput.substr(d->lastOvector[2 * i], d->lastOvector[2 * i + 1] - d->lastOvector[2 * i]));
318   return jsString("");
319 }
320
321 JSValue* RegExpObjectImp::getLastParen() const
322 {
323   unsigned i = d->lastNumSubPatterns;
324   if (i > 0) {
325     ASSERT(d->lastOvector);
326     return jsString(d->lastInput.substr(d->lastOvector[2 * i], d->lastOvector[2 * i + 1] - d->lastOvector[2 * i]));
327   }
328   return jsString("");
329 }
330
331 JSValue *RegExpObjectImp::getLeftContext() const
332 {
333   if (d->lastOvector)
334     return jsString(d->lastInput.substr(0, d->lastOvector[0]));
335   return jsString("");
336 }
337
338 JSValue *RegExpObjectImp::getRightContext() const
339 {
340   if (d->lastOvector) {
341     UString s = d->lastInput;
342     return jsString(s.substr(d->lastOvector[1], s.size() - d->lastOvector[1]));
343   }
344   return jsString("");
345 }
346
347 bool RegExpObjectImp::getOwnPropertySlot(ExecState *exec, const Identifier& propertyName, PropertySlot& slot)
348 {
349   return getStaticValueSlot<RegExpObjectImp, InternalFunctionImp>(exec, &RegExpTable, this, propertyName, slot);
350 }
351
352 JSValue *RegExpObjectImp::getValueProperty(ExecState*, int token) const
353 {
354   switch (token) {
355     case Dollar1:
356       return getBackref(1);
357     case Dollar2:
358       return getBackref(2);
359     case Dollar3:
360       return getBackref(3);
361     case Dollar4:
362       return getBackref(4);
363     case Dollar5:
364       return getBackref(5);
365     case Dollar6:
366       return getBackref(6);
367     case Dollar7:
368       return getBackref(7);
369     case Dollar8:
370       return getBackref(8);
371     case Dollar9:
372       return getBackref(9);
373     case Input:
374       return jsString(d->lastInput);
375     case Multiline:
376       return jsBoolean(d->multiline);
377     case LastMatch:
378       return getBackref(0);
379     case LastParen:
380       return getLastParen();
381     case LeftContext:
382       return getLeftContext();
383     case RightContext:
384       return getRightContext();
385     default:
386       ASSERT(0);
387   }
388
389   return jsString("");
390 }
391
392 void RegExpObjectImp::put(ExecState *exec, const Identifier &propertyName, JSValue *value, int attr)
393 {
394   lookupPut<RegExpObjectImp, InternalFunctionImp>(exec, propertyName, value, attr, &RegExpTable, this);
395 }
396
397 void RegExpObjectImp::putValueProperty(ExecState *exec, int token, JSValue *value, int)
398 {
399   switch (token) {
400     case Input:
401       d->lastInput = value->toString(exec);
402       break;
403     case Multiline:
404       d->multiline = value->toBoolean(exec);
405       break;
406     default:
407       ASSERT(0);
408   }
409 }
410   
411 bool RegExpObjectImp::implementsConstruct() const
412 {
413   return true;
414 }
415
416 // ECMA 15.10.4
417 JSObject *RegExpObjectImp::construct(ExecState *exec, const List &args)
418 {
419   JSValue* arg0 = args[0];
420   JSValue* arg1 = args[1];
421   
422   JSObject* o = arg0->getObject();
423   if (o && o->inherits(&RegExpImp::info)) {
424     if (!arg1->isUndefined())
425       return throwError(exec, TypeError);
426     return o;
427   }
428   
429   UString p = arg0->isUndefined() ? UString("") : arg0->toString(exec);
430   UString flags = arg1->isUndefined() ? UString("") : arg1->toString(exec);
431
432   RegExpPrototype* proto = static_cast<RegExpPrototype*>(exec->lexicalInterpreter()->builtinRegExpPrototype());
433
434   bool global = (flags.find('g') >= 0);
435   bool ignoreCase = (flags.find('i') >= 0);
436   bool multiline = (flags.find('m') >= 0);
437
438   int reflags = RegExp::None;
439   if (global)
440       reflags |= RegExp::Global;
441   if (ignoreCase)
442       reflags |= RegExp::IgnoreCase;
443   if (multiline)
444       reflags |= RegExp::Multiline;
445
446   OwnPtr<RegExp> re(new RegExp(p, reflags));
447   if (!re->isValid())
448       return throwError(exec, SyntaxError, UString("Invalid regular expression: ").append(re->errorMessage()));
449
450   RegExpImp* dat = new RegExpImp(proto, re.release());
451
452   dat->putDirect(exec->propertyNames().global, jsBoolean(global), DontDelete | ReadOnly | DontEnum);
453   dat->putDirect(exec->propertyNames().ignoreCase, jsBoolean(ignoreCase), DontDelete | ReadOnly | DontEnum);
454   dat->putDirect(exec->propertyNames().multiline, jsBoolean(multiline), DontDelete | ReadOnly | DontEnum);
455
456   dat->putDirect(exec->propertyNames().source, jsString(p), DontDelete | ReadOnly | DontEnum);
457   dat->putDirect(exec->propertyNames().lastIndex, jsNumber(0), DontDelete | DontEnum);
458
459   return dat;
460 }
461
462 // ECMA 15.10.3
463 JSValue *RegExpObjectImp::callAsFunction(ExecState *exec, JSObject * /*thisObj*/, const List &args)
464 {
465   return construct(exec, args);
466 }
467
468 const UString& RegExpObjectImp::input() const
469 {
470     // Can detect a distinct initial state that is invisible to JavaScript, by checking for null
471     // state (since jsString turns null strings to empty strings).
472     return d->lastInput;
473 }
474
475 }