JPC-RR r11.7
[jpcrr.git] / mnj / lua / StringLib.java
blob529ee70e946459bf1e10daf2457f2ae0b1190f0c
1 /* $Header: //info.ravenbrook.com/project/jili/version/1.1/code/mnj/lua/StringLib.java#1 $
2 * Copyright (c) 2006 Nokia Corporation and/or its subsidiary(-ies).
3 * All rights reserved.
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files (the
7 * "Software"), to deal in the Software without restriction, including
8 * without limitation the rights to use, copy, modify, merge, publish,
9 * distribute, sublicense, and/or sell copies of the Software, and to
10 * permit persons to whom the Software is furnished to do so, subject
11 * to the following conditions:
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
20 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
21 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 // Modified 2009-12-26 by Ilari Liusvaara
26 // Split -> StringLib -> StringLib, MatchState and FormatItem as J2SE
27 // doesn't like multiple classes in the same file.
29 package mnj.lua;
31 import java.io.ByteArrayOutputStream;
32 import java.io.IOException;
34 /**
35 * Contains Lua's string library.
36 * The library can be opened using the {@link #open} method.
38 public final class StringLib extends LuaJavaCallback
40 // Each function in the string library corresponds to an instance of
41 // this class which is associated (the 'which' member) with an integer
42 // which is unique within this class. They are taken from the following
43 // set.
44 private static final int BYTE = 1;
45 private static final int CHAR = 2;
46 private static final int DUMP = 3;
47 private static final int FIND = 4;
48 private static final int FORMAT = 5;
49 private static final int GFIND = 6;
50 private static final int GMATCH = 7;
51 private static final int GSUB = 8;
52 private static final int LEN = 9;
53 private static final int LOWER = 10;
54 private static final int MATCH = 11;
55 private static final int REP = 12;
56 private static final int REVERSE = 13;
57 private static final int SUB = 14;
58 private static final int UPPER = 15;
60 private static final int GMATCH_AUX= 16;
62 private static final StringLib GMATCH_AUX_FUN = new StringLib(GMATCH_AUX);
64 /**
65 * Which library function this object represents. This value should
66 * be one of the "enums" defined in the class.
68 private int which;
70 /** Constructs instance, filling in the 'which' member. */
71 private StringLib(int which)
73 this.which = which;
76 /**
77 * Adjusts the output of string.format so that %e and %g use 'e'
78 * instead of 'E' to indicate the exponent. In other words so that
79 * string.format follows the ISO C (ISO 9899) standard for printf.
81 public void formatISO()
83 FormatItem.E_LOWER = 'e';
86 /**
87 * Implements all of the functions in the Lua string library. Do not
88 * call directly.
89 * @param L the Lua state in which to execute.
90 * @return number of returned parameters, as per convention.
92 public int luaFunction(Lua L)
94 switch (which)
96 case BYTE:
97 return byteFunction(L);
98 case CHAR:
99 return charFunction(L);
100 case DUMP:
101 return dump(L);
102 case FIND:
103 return find(L);
104 case FORMAT:
105 return format(L);
106 case GMATCH:
107 return gmatch(L);
108 case GSUB:
109 return gsub(L);
110 case LEN:
111 return len(L);
112 case LOWER:
113 return lower(L);
114 case MATCH:
115 return match(L);
116 case REP:
117 return rep(L);
118 case REVERSE:
119 return reverse(L);
120 case SUB:
121 return sub(L);
122 case UPPER:
123 return upper(L);
124 case GMATCH_AUX:
125 return gmatchaux(L);
127 return 0;
131 * Opens the string library into the given Lua state. This registers
132 * the symbols of the string library in a newly created table called
133 * "string".
134 * @param L The Lua state into which to open.
136 public static void open(Lua L)
138 Object lib = L.register("string");
140 r(L, "byte", BYTE);
141 r(L, "char", CHAR);
142 r(L, "dump", DUMP);
143 r(L, "find", FIND);
144 r(L, "format", FORMAT);
145 r(L, "gfind", GFIND);
146 r(L, "gmatch", GMATCH);
147 r(L, "gsub", GSUB);
148 r(L, "len", LEN);
149 r(L, "lower", LOWER);
150 r(L, "match", MATCH);
151 r(L, "rep", REP);
152 r(L, "reverse", REVERSE);
153 r(L, "sub", SUB);
154 r(L, "upper", UPPER);
156 LuaTable mt = new LuaTable();
157 L.setMetatable("", mt); // set string metatable
158 L.setField(mt, "__index", lib);
161 /** Register a function. */
162 private static void r(Lua L, String name, int which)
164 StringLib f = new StringLib(which);
165 Object lib = L.getGlobal("string");
166 L.setField(lib, name, f);
169 /** Implements string.byte. Name mangled to avoid keyword. */
170 private static int byteFunction(Lua L)
172 String s = L.checkString(1);
173 int posi = posrelat(L.optInt(2, 1), s);
174 int pose = posrelat(L.optInt(3, posi), s);
175 if (posi <= 0)
177 posi = 1;
179 if (pose > s.length())
181 pose = s.length();
183 if (posi > pose)
185 return 0; // empty interval; return no values
187 int n = pose - posi + 1;
188 for (int i=0; i<n; ++i)
190 L.pushNumber(s.charAt(posi+i-1));
192 return n;
195 /** Implements string.char. Name mangled to avoid keyword. */
196 private static int charFunction(Lua L)
198 int n = L.getTop(); // number of arguments
199 StringBuffer b = new StringBuffer();
200 for (int i=1; i<=n; ++i)
202 int c = L.checkInt(i);
203 L.argCheck((char)c == c, i, "invalid value");
204 b.append((char)c);
206 L.push(b.toString());
207 return 1;
210 /** Implements string.dump. */
211 private static int dump(Lua L)
213 L.checkType(1, Lua.TFUNCTION);
214 L.setTop(1);
217 ByteArrayOutputStream s = new ByteArrayOutputStream();
218 Lua.dump(L.value(1), s);
219 byte[] a = s.toByteArray();
220 s = null;
221 StringBuffer b = new StringBuffer();
222 for (int i=0; i<a.length; ++i)
224 b.append((char)(a[i]&0xff));
226 L.pushString(b.toString());
227 return 1;
229 catch (IOException e_)
231 L.error("unabe to dump given function");
233 // NOTREACHED
234 return 0;
237 /** Helper for find and match. Equivalent to str_find_aux. */
238 private static int findAux(Lua L, boolean isFind)
240 String s = L.checkString(1);
241 String p = L.checkString(2);
242 int l1 = s.length();
243 int l2 = p.length();
244 int init = posrelat(L.optInt(3, 1), s) - 1;
245 if (init < 0)
247 init = 0;
249 else if (init > l1)
251 init = l1;
253 if (isFind && (L.toBoolean(L.value(4)) || // explicit request
254 strpbrk(p, MatchState.SPECIALS) < 0)) // or no special characters?
255 { // do a plain search
256 int off = lmemfind(s.substring(init), l1-init, p, l2);
257 if (off >= 0)
259 L.pushNumber(init+off+1);
260 L.pushNumber(init+off+l2);
261 return 2;
264 else
266 MatchState ms = new MatchState(L, s, l1);
267 boolean anchor = p.charAt(0) == '^';
268 int si = init;
271 ms.level = 0;
272 int res = ms.match(si, p, anchor ? 1 : 0);
273 if (res >= 0)
275 if (isFind)
277 L.pushNumber(si + 1); // start
278 L.pushNumber(res); // end
279 return ms.push_captures(-1, -1) + 2;
280 } // else
281 return ms.push_captures(si, res);
283 } while (si++ < ms.end && !anchor);
285 L.pushNil(); // not found
286 return 1;
289 /** Implements string.find. */
290 private static int find(Lua L)
292 return findAux(L, true);
295 /** Implement string.match. Operates slightly differently from the
296 * PUC-Rio code because instead of storing the iteration state as
297 * upvalues of the C closure the iteration state is stored in an
298 * Object[3] and kept on the stack.
300 private static int gmatch(Lua L)
302 Object[] state = new Object[3];
303 state[0] = L.checkString(1);
304 state[1] = L.checkString(2);
305 state[2] = new Integer(0);
306 L.push(GMATCH_AUX_FUN);
307 L.push(state);
308 return 2;
312 * Expects the iteration state, an Object[3] (see {@link
313 * #gmatch}), to be first on the stack.
315 private static int gmatchaux(Lua L)
317 Object[] state = (Object[])L.value(1);
318 String s = (String)state[0];
319 String p = (String)state[1];
320 int i = ((Integer)state[2]).intValue();
321 MatchState ms = new MatchState(L, s, s.length());
322 for ( ; i <= ms.end ; ++i)
324 ms.level = 0;
325 int e = ms.match(i, p, 0);
326 if (e >= 0)
328 int newstart = e;
329 if (e == i) // empty match?
330 ++newstart; // go at least one position
331 state[2] = new Integer(newstart);
332 return ms.push_captures(i, e);
335 return 0; // not found.
338 /** Implements string.gsub. */
339 private static int gsub(Lua L)
341 String s = L.checkString(1);
342 int sl = s.length();
343 String p = L.checkString(2);
344 int maxn = L.optInt(4, sl+1);
345 boolean anchor = false;
346 if (p.length() > 0)
348 anchor = p.charAt(0) == '^';
350 if (anchor)
351 p = p.substring(1);
352 MatchState ms = new MatchState(L, s, sl);
353 StringBuffer b = new StringBuffer();
355 int n = 0;
356 int si = 0;
357 while (n < maxn)
359 ms.level = 0;
360 int e = ms.match(si, p, 0);
361 if (e >= 0)
363 ++n;
364 ms.addvalue(b, si, e);
366 if (e >= 0 && e > si) // non empty match?
367 si = e; // skip it
368 else if (si < ms.end)
369 b.append(s.charAt(si++));
370 else
371 break;
372 if (anchor)
373 break;
375 b.append(s.substring(si));
376 L.pushString(b.toString());
377 L.pushNumber(n); // number of substitutions
378 return 2;
381 static void addquoted(Lua L, StringBuffer b, int arg)
383 String s = L.checkString(arg);
384 int l = s.length();
385 b.append('"');
386 for (int i=0; i<l; ++i)
388 switch (s.charAt(i))
390 case '"': case '\\': case '\n':
391 b.append('\\');
392 b.append(s.charAt(i));
393 break;
395 case '\r':
396 b.append("\\r");
397 break;
399 case '\0':
400 b.append("\\000");
401 break;
403 default:
404 b.append(s.charAt(i));
405 break;
408 b.append('"');
411 static int format(Lua L)
413 int arg = 1;
414 String strfrmt = L.checkString(1);
415 int sfl = strfrmt.length();
416 StringBuffer b = new StringBuffer();
417 int i=0;
418 while (i < sfl)
420 if (strfrmt.charAt(i) != MatchState.L_ESC)
422 b.append(strfrmt.charAt(i++));
424 else if (strfrmt.charAt(++i) == MatchState.L_ESC)
426 b.append(strfrmt.charAt(i++));
428 else // format item
430 ++arg;
431 FormatItem item = new FormatItem(L, strfrmt.substring(i));
432 i += item.length();
433 switch (item.type())
435 case 'c':
436 item.formatChar(b, (char)L.checkNumber(arg));
437 break;
439 case 'd': case 'i':
440 case 'o': case 'u': case 'x': case 'X':
441 // :todo: should be unsigned conversions cope better with
442 // negative number?
443 item.formatInteger(b, (long)L.checkNumber(arg));
444 break;
446 case 'e': case 'E': case 'f':
447 case 'g': case 'G':
448 item.formatFloat(b, L.checkNumber(arg));
449 break;
451 case 'q':
452 addquoted(L, b, arg);
453 break;
455 case 's':
456 item.formatString(b, L.checkString(arg));
457 break;
459 default:
460 return L.error("invalid option to 'format'");
464 L.pushString(b.toString());
465 return 1;
468 /** Implements string.len. */
469 private static int len(Lua L)
471 String s = L.checkString(1);
472 L.pushNumber(s.length());
473 return 1;
476 /** Implements string.lower. */
477 private static int lower(Lua L)
479 String s = L.checkString(1);
480 L.push(s.toLowerCase());
481 return 1;
484 /** Implements string.match. */
485 private static int match(Lua L)
487 return findAux(L, false);
490 /** Implements string.rep. */
491 private static int rep(Lua L)
493 String s = L.checkString(1);
494 int n = L.checkInt(2);
495 StringBuffer b = new StringBuffer();
496 for (int i=0; i<n; ++i)
498 b.append(s);
500 L.push(b.toString());
501 return 1;
504 /** Implements string.reverse. */
505 private static int reverse(Lua L)
507 String s = L.checkString(1);
508 StringBuffer b = new StringBuffer();
509 int l = s.length();
510 while (--l >= 0)
512 b.append(s.charAt(l));
514 L.push(b.toString());
515 return 1;
518 /** Helper for {@link #sub} and friends. */
519 private static int posrelat(int pos, String s)
521 if (pos >= 0)
523 return pos;
525 int len = s.length();
526 return len+pos+1;
529 /** Implements string.sub. */
530 private static int sub(Lua L)
532 String s = L.checkString(1);
533 int start = posrelat(L.checkInt(2), s);
534 int end = posrelat(L.optInt(3, -1), s);
535 if (start < 1)
537 start = 1;
539 if (end > s.length())
541 end = s.length();
543 if (start <= end)
545 L.push(s.substring(start-1, end));
547 else
549 L.pushLiteral("");
551 return 1;
554 /** Implements string.upper. */
555 private static int upper(Lua L)
557 String s = L.checkString(1);
558 L.push(s.toUpperCase());
559 return 1;
563 * @return character index of start of match (-1 if no match).
565 private static int lmemfind(String s1, int l1, String s2, int l2)
567 if (l2 == 0)
569 return 0; // empty strings are everywhere
571 else if (l2 > l1)
573 return -1; // avoids a negative l1
575 return s1.indexOf(s2);
579 * Just like C's strpbrk.
580 * @return an index into <var>s</var> or -1 for no match.
582 private static int strpbrk(String s, String set)
584 int l = set.length();
585 for (int i=0; i<l; ++i)
587 int idx = s.indexOf(set.charAt(i));
588 if (idx >= 0)
589 return idx;
591 return -1;