licensing update to APSL 2.0
[org.ibex.js.git] / src / org / ibex / js / JSRegexp.java
1 // Copyright 2000-2005 the Contributors, as shown in the revision logs.
2 // Licensed under the Apache Public Source License 2.0 ("the License").
3 // You may not use this file except in compliance with the License.
4
5 package org.ibex.js;
6
7 /** A JavaScript regular expression object */
8 public class JSRegexp extends JS {
9     private boolean global;
10     private GnuRegexp.RE re;
11     private int lastIndex;
12
13     private JS pattern;
14     private int flags;
15     
16     public JSRegexp(JS arg0, JS arg1) throws JSExn {
17         if(arg0 instanceof JSRegexp) {
18             JSRegexp r = (JSRegexp) arg0;
19             this.global = r.global;
20             this.re = r.re;
21             this.lastIndex = r.lastIndex;
22             this.pattern = pattern;
23             this.flags = flags;
24         } else {
25             String pattern = JS.toString(arg0);
26             String sFlags = null;
27             int flags = 0;
28             if(arg1 != null) sFlags = JS.toString(arg1);
29             if(sFlags == null) sFlags = "";
30             for(int i=0;i<sFlags.length();i++) {
31                 switch(sFlags.charAt(i)) {
32                     case 'i': flags |= GnuRegexp.RE.REG_ICASE; break;
33                     case 'm': flags |= GnuRegexp.RE.REG_MULTILINE; break;
34                     case 'g': global = true; break;
35                     default: throw new JSExn("Invalid flag in regexp \"" + sFlags.charAt(i) + "\"");
36                 }
37             }
38             re = newRE(pattern,flags);
39             this.pattern = JS.S(pattern);
40             this.flags = flags;
41         }
42     }
43
44     public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
45         switch(nargs) {
46             case 1: {
47                 //#switch(JS.toString(method))
48                 case "exec": {
49                     String s = JS.toString(a0);
50                     int start = global ? lastIndex : 0;
51                     if(start < 0 || start >= s.length()) { lastIndex = 0; return null; }
52                     GnuRegexp.REMatch match = re.getMatch(s,start);
53                     if(global) lastIndex = match == null ? s.length() : match.getEndIndex();
54                     return match == null ? null : matchToExecResult(match,re,s);
55                 }
56                 case "test": {
57                     String s = JS.toString(a0);
58                     if (!global) return B(re.getMatch(s) != null);
59                     int start = global ? lastIndex : 0;
60                     if(start < 0 || start >= s.length()) { lastIndex = 0; return null; }
61                     GnuRegexp.REMatch match = re.getMatch(s,start);
62                     lastIndex = match != null ? s.length() : match.getEndIndex();
63                     return B(match != null);
64                 }
65                 case "toString": return JS.S(a0.coerceToString());
66                 case "stringMatch": return stringMatch(a0,a1);
67                 case "stringSearch": return stringSearch(a0,a1);
68                 //#end
69                 break;
70             }
71             case 2: {
72                 //#switch(JS.toString(method))
73                 case "stringReplace": return stringReplace(a0, a1,a2);
74                 //#end
75                 break;
76             }
77         }
78         return super.callMethod(method, a0, a1, a2, rest, nargs);
79     }
80     
81     public JS get(JS key) throws JSExn {
82         //#switch(JS.toString(key))
83         case "exec": return METHOD;
84         case "test": return METHOD;
85         case "toString": return METHOD;
86         case "lastIndex": return N(lastIndex);
87         case "source": return pattern;
88         case "global": return JS.B(global);
89         case "ignoreCase": return B(flags & GnuRegexp.RE.REG_ICASE);
90         case "multiline": return B(flags & GnuRegexp.RE.REG_MULTILINE);
91         //#end
92         return super.get(key);
93     }
94     
95     public void put(JS key, JS value) throws JSExn {
96         if(JS.isString(key)) {
97             if(JS.toString(key).equals("lastIndex")) {
98                 lastIndex = JS.toInt(value);
99                 return;
100             }
101         }
102         super.put(key,value);
103     }
104   
105     private static JS matchToExecResult(GnuRegexp.REMatch match, GnuRegexp.RE re, String s) {
106         try {
107             JS ret = new JS.O();
108             ret.put(JS.S("index"), N(match.getStartIndex()));
109             ret.put(JS.S("input"),JS.S(s));
110             int n = re.getNumSubs();
111             ret.put(JS.S("length"), N(n+1));
112             ret.put(ZERO,JS.S(match.toString()));
113             for(int i=1;i<=n;i++) ret.put(JS.N(i),JS.S(match.toString(i)));
114             return ret;
115         } catch (JSExn e) {
116             throw new Error("this should never happen");
117         }
118     }
119     
120     String coerceToString() {
121         StringBuffer sb = new StringBuffer();
122         sb.append('/');
123         sb.append(pattern);
124         sb.append('/');
125         if(global) sb.append('g');
126         if((flags & GnuRegexp.RE.REG_ICASE) != 0) sb.append('i');
127         if((flags & GnuRegexp.RE.REG_MULTILINE) != 0) sb.append('m');
128         return sb.toString();
129     }
130     
131     static JS stringMatch(JS o, JS arg0) throws JSExn {
132         String s = JS.toString(o);
133         GnuRegexp.RE re;
134         JSRegexp regexp = null;
135         if(arg0 instanceof JSRegexp) {
136             regexp = (JSRegexp) arg0;
137             re = regexp.re;
138         } else {
139             re = newRE(JS.toString(arg0),0);
140         }
141         
142         if(regexp == null) {
143             GnuRegexp.REMatch match = re.getMatch(s);
144             return matchToExecResult(match,re,s);
145         }
146         if(!regexp.global) return regexp.callMethod(JS.S("exec"), o, null, null, null, 1);
147         
148         JSArray ret = new JSArray();
149         GnuRegexp.REMatch[] matches = re.getAllMatches(s);
150         for(int i=0;i<matches.length;i++) ret.addElement(JS.S(matches[i].toString()));
151         regexp.lastIndex = matches.length > 0 ? matches[matches.length-1].getEndIndex() : s.length();
152         return ret;
153     }
154     
155     static JS stringSearch(JS o, JS arg0) throws JSExn  {
156         String s = JS.toString(o);
157         GnuRegexp.RE re = arg0 instanceof JSRegexp ? ((JSRegexp)arg0).re : newRE(JS.toString(arg0),0);
158         GnuRegexp.REMatch match = re.getMatch(s);
159         return match == null ? N(-1) : N(match.getStartIndex());
160     }
161     
162     static JS stringReplace(JS o, JS arg0, JS arg1) throws JSExn {
163         String s = JS.toString(o);
164         GnuRegexp.RE re;
165         JSFunction replaceFunc = null;
166         String replaceString = null;
167         JSRegexp regexp = null;
168         if(arg0 instanceof JSRegexp) {
169             regexp = (JSRegexp) arg0;
170             re = regexp.re;
171         } else {
172             re = newRE(arg0.toString(),0);
173         }
174         if(arg1 instanceof JSFunction)
175             replaceFunc = (JSFunction) arg1;
176         else
177             replaceString = JS.toString(arg1);
178         GnuRegexp.REMatch[] matches;
179         if(regexp != null && regexp.global) {
180             matches = re.getAllMatches(s);
181             if(regexp != null) {
182                 if(matches.length > 0)
183                     regexp.lastIndex = matches[matches.length-1].getEndIndex();
184                 else
185                     regexp.lastIndex = s.length();
186             }
187         } else {
188             GnuRegexp.REMatch match = re.getMatch(s);
189             if(match != null)
190                 matches = new GnuRegexp.REMatch[]{ match };
191             else
192                 matches = new GnuRegexp.REMatch[0];
193         }
194         
195         StringBuffer sb = new StringBuffer(s.length());
196         int pos = 0;
197         char[] sa = s.toCharArray();
198         for(int i=0;i<matches.length;i++) {
199             GnuRegexp.REMatch match = matches[i];
200             sb.append(sa,pos,match.getStartIndex()-pos);
201             pos = match.getEndIndex();
202             if(replaceFunc != null) {
203                 int n = (regexp == null ? 0 : re.getNumSubs());
204                 int numArgs = 3 + n;
205                 JS[] rest = new JS[numArgs - 3];
206                 JS a0 = JS.S(match.toString());
207                 JS a1 = null;
208                 JS a2 = null;
209                 for(int j=1;j<=n;j++)
210                     switch(j) {
211                         case 1: a1 = JS.S(match.toString(j)); break;
212                         case 2: a2 = JS.S(match.toString(j)); break;
213                         default: rest[j - 3] = JS.S(match.toString(j)); break;
214                     }
215                 switch(numArgs) {
216                     case 3:
217                         a1 = N(match.getStartIndex());
218                         a2 = JS.S(s);
219                         break;
220                     case 4:
221                         a2 = N(match.getStartIndex());
222                         rest[0] = JS.S(s);
223                         break;
224                     default:
225                         rest[rest.length - 2] = N(match.getStartIndex());
226                         rest[rest.length - 1] = JS.S(s);
227                 }
228
229                 // note: can't perform pausing operations in here
230                 sb.append(JS.toString(replaceFunc.call(a0, a1, a2, rest, numArgs)));
231
232             } else {
233                 sb.append(mySubstitute(match,replaceString,s));
234             }
235         }
236         int end = matches.length == 0 ? 0 : matches[matches.length-1].getEndIndex();
237         sb.append(sa,end,sa.length-end);
238         return JS.S(sb.toString());
239     }
240     
241     private static String mySubstitute(GnuRegexp.REMatch match, String s, String source) {
242         StringBuffer sb = new StringBuffer();
243         int i,n;
244         char c,c2;
245         for(i=0;i<s.length()-1;i++) {
246            c = s.charAt(i);
247             if(c != '$') {
248                 sb.append(c);
249                 continue;
250             }
251             i++;
252             c = s.charAt(i);
253             switch(c) {
254                 case '0': case '1': case '2': case '3': case '4':
255                 case '5': case '6': case '7': case '8': case '9':
256                     if(i < s.length()-1 && (c2 = s.charAt(i+1)) >= '0' && c2 <= '9') {
257                         n = (c - '0') * 10 + (c2 - '0');
258                         i++;
259                     } else {
260                         n = c - '0';
261                     }
262                     if(n > 0)
263                         sb.append(match.toString(n));
264                     break;
265                 case '$':
266                     sb.append('$'); break;
267                 case '&':
268                     sb.append(match.toString()); break;
269                 case '`':
270                     sb.append(source.substring(0,match.getStartIndex())); break;
271                 case '\'':
272                     sb.append(source.substring(match.getEndIndex())); break;
273                 default:
274                     sb.append('$');
275                     sb.append(c);
276             }
277         }
278         if(i < s.length()) sb.append(s.charAt(i));
279         return sb.toString();
280     }
281                     
282     
283     static JS stringSplit(JS s_, JS arg0, JS arg1, int nargs) throws JSExn {
284         String s = JS.toString(s_);
285         int limit = nargs < 2 ? Integer.MAX_VALUE : JS.toInt(arg1);
286         if(limit < 0) limit = Integer.MAX_VALUE;
287         if(limit == 0) return new JSArray();
288         
289         GnuRegexp.RE re = null;
290         JSRegexp regexp = null;
291         String sep = null;
292         JSArray ret = new JSArray();
293         int p = 0;
294         
295         if(arg0 instanceof JSRegexp) {
296             regexp = (JSRegexp) arg0;
297             re = regexp.re;
298         } else {
299             sep = JS.toString(arg0);
300         }
301         
302         // special case this for speed. additionally, the code below doesn't properly handle
303         // zero length strings
304         if(sep != null && sep.length()==0) {
305             int len = s.length();
306             for(int i=0;i<len;i++)
307                 ret.addElement(JS.S(s.substring(i,i+1)));
308             return ret;
309         }
310         
311         OUTER: while(p < s.length()) {
312             if(re != null) {
313                 GnuRegexp.REMatch m = re.getMatch(s,p);
314                 if(m == null) break OUTER;
315                 boolean zeroLength = m.getStartIndex() == m.getEndIndex();
316                 ret.addElement(JS.S(s.substring(p,zeroLength ? m.getStartIndex()+1 : m.getStartIndex())));
317                 p = zeroLength ? p + 1 : m.getEndIndex();
318                 if(!zeroLength) {
319                     for(int i=1;i<=re.getNumSubs();i++) {
320                         ret.addElement(JS.S(m.toString(i)));
321                         if(ret.length() == limit) break OUTER;
322                     }
323                 }
324             } else {
325                 int x = s.indexOf(sep,p);
326                 if(x == -1) break OUTER;
327                 ret.addElement(JS.S(s.substring(p,x)));
328                 p = x + sep.length();
329             }
330             if(ret.length() == limit) break;
331         }
332         if(p < s.length() && ret.length() != limit)
333             ret.addElement(JS.S(s.substring(p)));
334         return ret;
335     }
336    
337     public static GnuRegexp.RE newRE(String pattern, int flags) throws JSExn {
338         try {
339             return new GnuRegexp.RE(pattern,flags,GnuRegexp.RESyntax.RE_SYNTAX_PERL5);
340         } catch(GnuRegexp.REException e) {
341             throw new JSExn(e.toString());
342         }
343     }
344
345 }