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).
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.
31 import java
.io
.ByteArrayOutputStream
;
32 import java
.io
.IOException
;
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
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
);
65 * Which library function this object represents. This value should
66 * be one of the "enums" defined in the class.
70 /** Constructs instance, filling in the 'which' member. */
71 private StringLib(int which
)
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';
87 * Implements all of the functions in the Lua string library. Do not
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
)
97 return byteFunction(L
);
99 return charFunction(L
);
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
134 * @param L The Lua state into which to open.
136 public static void open(Lua L
)
138 Object lib
= L
.register("string");
144 r(L
, "format", FORMAT
);
145 r(L
, "gfind", GFIND
);
146 r(L
, "gmatch", GMATCH
);
149 r(L
, "lower", LOWER
);
150 r(L
, "match", MATCH
);
152 r(L
, "reverse", REVERSE
);
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
);
179 if (pose
> s
.length())
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));
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");
206 L
.push(b
.toString());
210 /** Implements string.dump. */
211 private static int dump(Lua L
)
213 L
.checkType(1, Lua
.TFUNCTION
);
217 ByteArrayOutputStream s
= new ByteArrayOutputStream();
218 Lua
.dump(L
.value(1), s
);
219 byte[] a
= s
.toByteArray();
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());
229 catch (IOException e_
)
231 L
.error("unabe to dump given function");
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);
244 int init
= posrelat(L
.optInt(3, 1), s
) - 1;
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
);
259 L
.pushNumber(init
+off
+1);
260 L
.pushNumber(init
+off
+l2
);
266 MatchState ms
= new MatchState(L
, s
, l1
);
267 boolean anchor
= p
.charAt(0) == '^';
272 int res
= ms
.match(si
, p
, anchor ?
1 : 0);
277 L
.pushNumber(si
+ 1); // start
278 L
.pushNumber(res
); // end
279 return ms
.push_captures(-1, -1) + 2;
281 return ms
.push_captures(si
, res
);
283 } while (si
++ < ms
.end
&& !anchor
);
285 L
.pushNil(); // not found
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
);
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
)
325 int e
= ms
.match(i
, p
, 0);
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);
343 String p
= L
.checkString(2);
344 int maxn
= L
.optInt(4, sl
+1);
345 boolean anchor
= false;
348 anchor
= p
.charAt(0) == '^';
352 MatchState ms
= new MatchState(L
, s
, sl
);
353 StringBuffer b
= new StringBuffer();
360 int e
= ms
.match(si
, p
, 0);
364 ms
.addvalue(b
, si
, e
);
366 if (e
>= 0 && e
> si
) // non empty match?
368 else if (si
< ms
.end
)
369 b
.append(s
.charAt(si
++));
375 b
.append(s
.substring(si
));
376 L
.pushString(b
.toString());
377 L
.pushNumber(n
); // number of substitutions
381 static void addquoted(Lua L
, StringBuffer b
, int arg
)
383 String s
= L
.checkString(arg
);
386 for (int i
=0; i
<l
; ++i
)
390 case '"': case '\\': case '\n':
392 b
.append(s
.charAt(i
));
404 b
.append(s
.charAt(i
));
411 static int format(Lua L
)
414 String strfrmt
= L
.checkString(1);
415 int sfl
= strfrmt
.length();
416 StringBuffer b
= new StringBuffer();
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
++));
431 FormatItem item
= new FormatItem(L
, strfrmt
.substring(i
));
436 item
.formatChar(b
, (char)L
.checkNumber(arg
));
440 case 'o': case 'u': case 'x': case 'X':
441 // :todo: should be unsigned conversions cope better with
443 item
.formatInteger(b
, (long)L
.checkNumber(arg
));
446 case 'e': case 'E': case 'f':
448 item
.formatFloat(b
, L
.checkNumber(arg
));
452 addquoted(L
, b
, arg
);
456 item
.formatString(b
, L
.checkString(arg
));
460 return L
.error("invalid option to 'format'");
464 L
.pushString(b
.toString());
468 /** Implements string.len. */
469 private static int len(Lua L
)
471 String s
= L
.checkString(1);
472 L
.pushNumber(s
.length());
476 /** Implements string.lower. */
477 private static int lower(Lua L
)
479 String s
= L
.checkString(1);
480 L
.push(s
.toLowerCase());
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
)
500 L
.push(b
.toString());
504 /** Implements string.reverse. */
505 private static int reverse(Lua L
)
507 String s
= L
.checkString(1);
508 StringBuffer b
= new StringBuffer();
512 b
.append(s
.charAt(l
));
514 L
.push(b
.toString());
518 /** Helper for {@link #sub} and friends. */
519 private static int posrelat(int pos
, String s
)
525 int len
= s
.length();
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
);
539 if (end
> s
.length())
545 L
.push(s
.substring(start
-1, end
));
554 /** Implements string.upper. */
555 private static int upper(Lua L
)
557 String s
= L
.checkString(1);
558 L
.push(s
.toUpperCase());
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
)
569 return 0; // empty strings are everywhere
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
));