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