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