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