diff --git a/README.html b/README.html
index 0309b4e9..81fcae93 100644
--- a/README.html
+++ b/README.html
@@ -980,6 +980,10 @@ Files are no longer hosted at LuaForge.
Improve garbage collection of orphaned coroutines when yielding from debug hook functions (fixes issue #32).
LuaScriptEngineFactory.getScriptEngine() now returns new instance of LuaScriptEngine for each call.
Fix os.date("*t") to return hour in 24 hour format (fixes issue #45)
+Add ReadOnlyTable and ReadWriteShadowTable utility classes to simplify sandboxing.
+Make string metatable a proper metatable, and make it read-only by default.
+Add sample code that illustrates techniques in creating sandboxed environments.
+Add convenience methods to Global to load string scripts with custom environment.
diff --git a/examples/jse/SampleSandboxed.java b/examples/jse/SampleSandboxed.java
new file mode 100644
index 00000000..56411dba
--- /dev/null
+++ b/examples/jse/SampleSandboxed.java
@@ -0,0 +1,125 @@
+import org.luaj.vm2.*;
+import org.luaj.vm2.compiler.LuaC;
+import org.luaj.vm2.lib.*;
+import org.luaj.vm2.lib.jse.*;
+
+/** Simple program that illustrates basic sand-boxing of client scripts
+ * in a server environment.
+ *
+ * Although this sandboxing is done primarily in Java here, most of the
+ * same techniques can be done directly from lua using metatables.
+ *
+ *
This class makes particular use of two utility classes,
+ * {@link ReadOnlyTable} which is used to wrap shared global metatables
+ * such as the string metatable or the number metatable, and
+ * {@link ReadWriteShadowTable} which can provide a lightweight user
+ * environment around an arbitrarily deep shared globals instance while
+ * limiting the resources at startup for small scripts that use few globals.
+ *
+ * @see Globals
+ * @see LuaValue
+ * @see ReadOnlyTable
+ * @see ReadWriteShadowTable
+ */
+public class SampleSandboxed {
+
+ // Globals use by the server itself, say to compile scripts that are loaded.
+ // In a real server there should be one of these per server thread.
+ // See SampleMultiThreaded.java for an example of multi-threaded setup.
+ static final Globals server_globals = JsePlatform.debugGlobals();
+
+ // A set of global functions and packages that are shared across users.
+ // These are exposed in a read-only fashion through user environment
+ // shadow tables.
+ static final Globals shared_globals = new Globals();
+ static {
+ // Load only packages known to be safe for multiple users.
+ shared_globals.load(new JseBaseLib());
+ shared_globals.load(new PackageLib());
+ shared_globals.load(new Bit32Lib());
+ shared_globals.load(new TableLib());
+ shared_globals.load(new StringLib());
+ shared_globals.load(new JseMathLib());
+ LoadState.install(shared_globals);
+ LuaC.install(shared_globals);
+ }
+
+ // Create a new user environment which refers to, but does not modify the
+ // shared globals. Each top-level user script should get their own copy
+ // of these which are a lightweight shadow of the shared globals.
+ // Writes to these shadow tables do not affect the original table.
+ static LuaTable create_user_environment() {
+ LuaTable user_environment = new ReadWriteShadowTable(shared_globals);
+ user_environment.set("_G", user_environment);
+ return user_environment;
+ }
+
+ public static void main(String[] args) {
+
+ // Should be able to see and use globals as if they are owned by this environment.
+ expectSuccess("print('_G', _G)");
+ expectSuccess("print('math.pi', math.pi)");;
+ expectSuccess("x = 'abc'; print('_G.x', _G.x); assert(_G.x == 'abc')");
+ expectSuccess("print('_G.x', _G.x); assert(x == nil)");
+
+ // Should not be able to write to global shared metatables.
+ expectSuccess("print('string meta', getmetatable('abc'))");
+ expectException("print('string meta.x=foo'); getmetatable('abc')['x']='foo'");
+ expectException("print('setmetatable(abc)', setmetatable('abc', {}))");
+ expectException("print('setmetatable(true)', setmetatable(true, {}))");
+
+ // Should be able to provide useful global server metatable behavior
+ // Example use of shared global metatable.
+ // Allows bools to be added to numbers.
+ LuaBoolean.s_metatable = new ReadOnlyTable(new LuaValue[] {
+ LuaValue.ADD, new TwoArgFunction() {
+ public LuaValue call(LuaValue x, LuaValue y) {
+ return LuaValue.valueOf(
+ (x == TRUE ? 1.0 : x.todouble()) +
+ (y == TRUE ? 1.0 : y.todouble()) );
+ }
+ },
+ });
+ expectSuccess("print('pi + true', math.pi + true)");
+
+ // Should be able to use the metatable for our own globals.
+ expectSuccess("setmetatable(_G, {__index={foo='bar'}}); print('foo', foo); assert(foo == 'bar')");
+
+ // Subtables of globals can be modified but are shadow tables and don't affect
+ // shared globals or environment of other scripts.
+ expectSuccess("print('setmetatable(math)', setmetatable(math, {foo='bar'}))");
+ expectSuccess("print('getmetatable(math)', getmetatable(math)); assert(getmetatable(math) == nil)");
+ }
+
+
+ // Run a script and return the results.
+ static Varargs runScript(String script) {
+ LuaTable user_environment = create_user_environment();
+ LuaValue chunk = server_globals.load(script, "main", user_environment);
+ return chunk.invoke();
+ }
+
+ // Run a script, expecting it to succeed, to illustrate various uses
+ // that should succeed without affecting shared resources.
+ static Varargs expectSuccess(String script) {
+ try {
+ return runScript(script);
+ } catch (Throwable t) {
+ System.out.println("script failed: "+t);
+ return LuaValue.NONE;
+ }
+ }
+
+ // Run a script, expecting it to fail, to illustrate various expected ways
+ // rogue attempts will fail to abuse resources.
+ static Varargs expectException(String script) {
+ try {
+ Varargs result = runScript(script);
+ System.out.println("failure: script returned "+ result);
+ return result;
+ } catch (Throwable t) {
+ System.out.println("success: "+t.getMessage());
+ return LuaValue.NONE;
+ }
+ }
+}
diff --git a/src/core/org/luaj/vm2/Globals.java b/src/core/org/luaj/vm2/Globals.java
index e048a2c3..68812e59 100644
--- a/src/core/org/luaj/vm2/Globals.java
+++ b/src/core/org/luaj/vm2/Globals.java
@@ -202,19 +202,55 @@ public class Globals extends LuaTable {
public LuaValue load(String script) {
return load(new StrReader(script), script);
}
-
+
+ /** Convenience function to load a string value as a script with a custom environment.
+ * Must be lua source.
+ * @param script Contents of a lua script, such as "print 'hello, world.'"
+ * @param chunkname Name that will be used within the chunk as the source.
+ * @param environment LuaTable to be used as the environment for the loaded function.
+ * @return LuaValue that may be executed via .call(), .invoke(), or .method() calls.
+ * @throws LuaError if the script could not be compiled.
+ */
+ public LuaValue load(String script, String chunkname, LuaTable environment) {
+ return load(new StrReader(script), chunkname, environment);
+ }
+
/** Load the content form a reader as a text file. Must be lua source.
* The source is converted to UTF-8, so any characters appearing in quoted literals
- * above the range 128 will be converted into multiple bytes. */
+ * above the range 128 will be converted into multiple bytes.
+ * @param script Contents of a lua script, such as "print 'hello, world.'"
+ * @param chunkname Name that will be used within the chunk as the source.
+ * @return LuaValue that may be executed via .call(), .invoke(), or .method() calls.
+ * @throws LuaError if the script could not be compiled.
+ */
public LuaValue load(Reader reader, String chunkname) {
return load(new UTF8Stream(reader), chunkname, "t", this);
}
- /** Load the content form an input stream as a binary chunk or text file. */
- public LuaValue load(InputStream is, String chunkname, String mode, LuaValue env) {
+ /** Load the content form a reader as a text file, supplying a custom environment.
+ * Must be lua source. The source is converted to UTF-8, so any characters
+ * appearing in quoted literals above the range 128 will be converted into
+ * multiple bytes.
+ * @param script Contents of a lua script, such as "print 'hello, world.'"
+ * @param chunkname Name that will be used within the chunk as the source.
+ * @param environment LuaTable to be used as the environment for the loaded function.
+ * @return LuaValue that may be executed via .call(), .invoke(), or .method() calls.
+ * @throws LuaError if the script could not be compiled.
+ */
+ public LuaValue load(Reader reader, String chunkname, LuaTable environment) {
+ return load(new UTF8Stream(reader), chunkname, "t", environment);
+ }
+
+ /** Load the content form an input stream as a binary chunk or text file.
+ * @param is Input stream containing a lua script or compiled lua"
+ * @param chunkname Name that will be used within the chunk as the source.
+ * @param mode String containing 'b' or 't' or both to control loading as binary or text or either.
+ * @param environment LuaTable to be used as the environment for the loaded function.
+ * */
+ public LuaValue load(InputStream is, String chunkname, String mode, LuaValue environment) {
try {
Prototype p = loadPrototype(is, chunkname, mode);
- return loader.load(p, chunkname, env);
+ return loader.load(p, chunkname, environment);
} catch (LuaError l) {
throw l;
} catch (Exception e) {
@@ -225,6 +261,9 @@ public class Globals extends LuaTable {
/** Load lua source or lua binary from an input stream into a Prototype.
* The InputStream is either a binary lua chunk starting with the lua binary chunk signature,
* or a text input file. If it is a text input file, it is interpreted as a UTF-8 byte sequence.
+ * @param is Input stream containing a lua script or compiled lua"
+ * @param chunkname Name that will be used within the chunk as the source.
+ * @param mode String containing 'b' or 't' or both to control loading as binary or text or either.
*/
public Prototype loadPrototype(InputStream is, String chunkname, String mode) throws IOException {
if (mode.indexOf('b') >= 0) {
diff --git a/src/core/org/luaj/vm2/LuaString.java b/src/core/org/luaj/vm2/LuaString.java
index f1d73bfc..9da5f1b5 100644
--- a/src/core/org/luaj/vm2/LuaString.java
+++ b/src/core/org/luaj/vm2/LuaString.java
@@ -62,9 +62,9 @@ import org.luaj.vm2.lib.StringLib;
*/
public class LuaString extends LuaValue {
- /** The singleton instance representing lua {@code true} */
+ /** The singleton instance for string metatables that forwards to the string functions */
public static LuaValue s_metatable;
-
+
/** The bytes for the string. These must not be mutated directly because
* the backing may be shared by multiple LuaStrings, and the hash code is
* computed only at construction time.
@@ -254,11 +254,6 @@ public class LuaString extends LuaValue {
return decodeAsUtf8(m_bytes, m_offset, m_length);
}
- // get is delegated to the string library
- public LuaValue get(LuaValue key) {
- return s_metatable!=null? gettable(this,key): StringLib.instance.get(key);
- }
-
// unary operators
public LuaValue neg() { double d = scannumber(); return Double.isNaN(d)? super.neg(): valueOf(-d); }
diff --git a/src/core/org/luaj/vm2/ReadOnlyTable.java b/src/core/org/luaj/vm2/ReadOnlyTable.java
new file mode 100644
index 00000000..e31d5060
--- /dev/null
+++ b/src/core/org/luaj/vm2/ReadOnlyTable.java
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Luaj.org. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ******************************************************************************/
+package org.luaj.vm2;
+
+/** Utility class to construct a read-only LuaTable whose initial contents are
+ * initialized to some set of values and whose contents cannot be changed after
+ * construction, allowing the table to be shared in a multi-threaded context.
+ */
+public final class ReadOnlyTable extends LuaTable {
+
+ /** Empty read-only table with no metatable. */
+ public static final ReadOnlyTable empty_read_only_table =
+ new ReadOnlyTable();
+
+ /** Construct a ReadOnlyTable with a set of named key-value pairs.
+ * 'named' is a list of LuaValues, with the first being a key, the
+ * second being the corresponding value, and so on. All key-value
+ * pairs are copied into the table as part of construction,
+ * however only a shallow copy is done so values which are tables
+ * remain read-write even if accessed from this read-only table.
+ * @param named array of values in key,value,key,value order
+ * which are the key-value pairs that will appear in this read-only table.
+ */
+ public ReadOnlyTable(LuaValue[] named) {
+ presize(named.length/2, 0);
+ for ( int i=0; iThis simplifies creation of a safe unique environment for user scripts
+ * which falls back to a delegate shared globals table.
+ */
+public class ReadWriteShadowTable extends LuaTable {
+ /** The underlying table from which values are read when
+ * the table does not contain a key. The values in delegate
+ * should not be modified as a result of being a delegate for
+ * this table, however if the values are userdata that expose
+ * mutators, those values may undergo mutations once exposed.
+ */
+ public final LuaValue delegate;
+
+ /** Construct a read-write shadow table around 'delegate' without
+ * copying elements, but retaining a reference to delegate.
+ * @param fallback The table containing values we would like to
+ * reference without affecting the contents of that table.
+ */
+ public ReadWriteShadowTable(LuaValue delegate) {
+ this.delegate = delegate;
+ }
+
+ /** Normal table get, but return delegate value if not found.
+ * If the delegate returns a table, wraps that in a read-write shadow
+ * and does a local rawset on that value before returning it.
+ * @param key LuaValue to look up.
+ */
+ public LuaValue get(LuaValue key) {
+ LuaValue value = super.get(key);
+ if (value.isnil()) {
+ value = delegate.get(key);
+ if (value.istable())
+ value = new ReadWriteShadowTable(value);
+ rawset(key, value);
+ }
+ return value;
+ }
+}
\ No newline at end of file
diff --git a/src/core/org/luaj/vm2/lib/StringLib.java b/src/core/org/luaj/vm2/lib/StringLib.java
index 80f47377..62553dbb 100644
--- a/src/core/org/luaj/vm2/lib/StringLib.java
+++ b/src/core/org/luaj/vm2/lib/StringLib.java
@@ -29,6 +29,7 @@ import org.luaj.vm2.Buffer;
import org.luaj.vm2.LuaString;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
+import org.luaj.vm2.ReadOnlyTable;
import org.luaj.vm2.Varargs;
import org.luaj.vm2.compiler.DumpState;
@@ -61,57 +62,37 @@ import org.luaj.vm2.compiler.DumpState;
*/
public class StringLib extends TwoArgFunction {
- public static LuaTable instance;
+ public static final ReadOnlyTable lib_functions;
+ static {
+ LuaTable t = new LuaTable();
+ t.set("byte", new byte_());
+ t.set("char", new char_());
+ t.set("dump", new dump());
+ t.set("find", new find());
+ t.set("format", new format());
+ t.set("gmatch", new gmatch());
+ t.set("gsub", new gsub());
+ t.set("len", new len());
+ t.set("lower", new lower());
+ t.set("match", new match());
+ t.set("rep", new rep());
+ t.set("reverse", new reverse());
+ t.set("sub", new sub());
+ t.set("upper", new upper());
+ lib_functions = new ReadOnlyTable(t);
+ LuaString.s_metatable = new ReadOnlyTable(
+ new LuaValue[] { INDEX, lib_functions });
+ }
public StringLib() {
}
public LuaValue call(LuaValue modname, LuaValue env) {
- LuaTable t = new LuaTable();
- bind(t, StringLib1.class, new String[] {
- "dump", "len", "lower", "reverse", "upper", } );
- bind(t, StringLibV.class, new String[] {
- "byte", "char", "find", "format",
- "gmatch", "gsub", "match", "rep",
- "sub"} );
- env.set("string", t);
- instance = t;
- if ( LuaString.s_metatable == null )
- LuaString.s_metatable = tableOf( new LuaValue[] { INDEX, t } );
- env.get("package").get("loaded").set("string", t);
- return t;
+ env.set("string", lib_functions);
+ env.get("package").get("loaded").set("string", lib_functions);
+ return lib_functions;
}
- static final class StringLib1 extends OneArgFunction {
- public LuaValue call(LuaValue arg) {
- switch ( opcode ) {
- case 0: return dump(arg); // dump (function)
- case 1: return StringLib.len(arg); // len (function)
- case 2: return lower(arg); // lower (function)
- case 3: return reverse(arg); // reverse (function)
- case 4: return upper(arg); // upper (function)
- }
- return NIL;
- }
- }
-
- static final class StringLibV extends VarArgFunction {
- public Varargs invoke(Varargs args) {
- switch ( opcode ) {
- case 0: return StringLib.byte_( args );
- case 1: return StringLib.char_( args );
- case 2: return StringLib.find( args );
- case 3: return StringLib.format( args );
- case 4: return StringLib.gmatch( args );
- case 5: return StringLib.gsub( args );
- case 6: return StringLib.match( args );
- case 7: return StringLib.rep( args );
- case 8: return StringLib.sub( args );
- }
- return NONE;
- }
- }
-
/**
* string.byte (s [, i [, j]])
*
@@ -123,22 +104,24 @@ public class StringLib extends TwoArgFunction {
*
* @param args the calling args
*/
- static Varargs byte_( Varargs args ) {
- LuaString s = args.checkstring(1);
- int l = s.m_length;
- int posi = posrelat( args.optint(2,1), l );
- int pose = posrelat( args.optint(3,posi), l );
- int n,i;
- if (posi <= 0) posi = 1;
- if (pose > l) pose = l;
- if (posi > pose) return NONE; /* empty interval; return no values */
- n = (int)(pose - posi + 1);
- if (posi + n <= pose) /* overflow? */
- error("string slice too long");
- LuaValue[] v = new LuaValue[n];
- for (i=0; i l) pose = l;
+ if (posi > pose) return NONE; /* empty interval; return no values */
+ n = (int)(pose - posi + 1);
+ if (posi + n <= pose) /* overflow? */
+ error("string slice too long");
+ LuaValue[] v = new LuaValue[n];
+ for (i=0; i=256) argerror(a, "invalid value");
- bytes[i] = (byte) c;
+ static final class char_ extends VarArgFunction {
+ public Varargs invoke(Varargs args) {
+ int n = args.narg();
+ byte[] bytes = new byte[n];
+ for ( int i=0, a=1; i=256) argerror(a, "invalid value");
+ bytes[i] = (byte) c;
+ }
+ return LuaString.valueUsing( bytes );
}
- return LuaString.valueUsing( bytes );
}
/**
@@ -172,14 +157,16 @@ public class StringLib extends TwoArgFunction {
*
* TODO: port dumping code as optional add-on
*/
- static LuaValue dump( LuaValue arg ) {
- LuaValue f = arg.checkfunction();
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- try {
- DumpState.dump( ((LuaClosure)f).p, baos, true );
- return LuaString.valueUsing(baos.toByteArray());
- } catch (IOException e) {
- return error( e.getMessage() );
+ static final class dump extends OneArgFunction {
+ public LuaValue call(LuaValue arg) {
+ LuaValue f = arg.checkfunction();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ DumpState.dump( ((LuaClosure)f).p, baos, true );
+ return LuaString.valueUsing(baos.toByteArray());
+ } catch (IOException e) {
+ return error( e.getMessage() );
+ }
}
}
@@ -199,8 +186,10 @@ public class StringLib extends TwoArgFunction {
* If the pattern has captures, then in a successful match the captured values
* are also returned, after the two indices.
*/
- static Varargs find( Varargs args ) {
- return str_find_aux( args, true );
+ static final class find extends VarArgFunction {
+ public Varargs invoke(Varargs args) {
+ return str_find_aux( args, true );
+ }
}
/**
@@ -226,72 +215,74 @@ public class StringLib extends TwoArgFunction {
* This function does not accept string values containing embedded zeros,
* except as arguments to the q option.
*/
- static Varargs format( Varargs args ) {
- LuaString fmt = args.checkstring( 1 );
- final int n = fmt.length();
- Buffer result = new Buffer(n);
- int arg = 1;
- int c;
-
- for ( int i = 0; i < n; ) {
- switch ( c = fmt.luaByte( i++ ) ) {
- case '\n':
- result.append( "\n" );
- break;
- default:
- result.append( (byte) c );
- break;
- case L_ESC:
- if ( i < n ) {
- if ( ( c = fmt.luaByte( i ) ) == L_ESC ) {
- ++i;
- result.append( (byte)L_ESC );
- } else {
- arg++;
- FormatDesc fdsc = new FormatDesc(args, fmt, i );
- i += fdsc.length;
- switch ( fdsc.conversion ) {
- case 'c':
- fdsc.format( result, (byte)args.checkint( arg ) );
- break;
- case 'i':
- case 'd':
- fdsc.format( result, args.checkint( arg ) );
- break;
- case 'o':
- case 'u':
- case 'x':
- case 'X':
- fdsc.format( result, args.checklong( arg ) );
- break;
- case 'e':
- case 'E':
- case 'f':
- case 'g':
- case 'G':
- fdsc.format( result, args.checkdouble( arg ) );
- break;
- case 'q':
- addquoted( result, args.checkstring( arg ) );
- break;
- case 's': {
- LuaString s = args.checkstring( arg );
- if ( fdsc.precision == -1 && s.length() >= 100 ) {
- result.append( s );
- } else {
- fdsc.format( result, s );
+ static final class format extends VarArgFunction {
+ public Varargs invoke(Varargs args) {
+ LuaString fmt = args.checkstring( 1 );
+ final int n = fmt.length();
+ Buffer result = new Buffer(n);
+ int arg = 1;
+ int c;
+
+ for ( int i = 0; i < n; ) {
+ switch ( c = fmt.luaByte( i++ ) ) {
+ case '\n':
+ result.append( "\n" );
+ break;
+ default:
+ result.append( (byte) c );
+ break;
+ case L_ESC:
+ if ( i < n ) {
+ if ( ( c = fmt.luaByte( i ) ) == L_ESC ) {
+ ++i;
+ result.append( (byte)L_ESC );
+ } else {
+ arg++;
+ FormatDesc fdsc = new FormatDesc(args, fmt, i );
+ i += fdsc.length;
+ switch ( fdsc.conversion ) {
+ case 'c':
+ fdsc.format( result, (byte)args.checkint( arg ) );
+ break;
+ case 'i':
+ case 'd':
+ fdsc.format( result, args.checkint( arg ) );
+ break;
+ case 'o':
+ case 'u':
+ case 'x':
+ case 'X':
+ fdsc.format( result, args.checklong( arg ) );
+ break;
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'g':
+ case 'G':
+ fdsc.format( result, args.checkdouble( arg ) );
+ break;
+ case 'q':
+ addquoted( result, args.checkstring( arg ) );
+ break;
+ case 's': {
+ LuaString s = args.checkstring( arg );
+ if ( fdsc.precision == -1 && s.length() >= 100 ) {
+ result.append( s );
+ } else {
+ fdsc.format( result, s );
+ }
+ } break;
+ default:
+ error("invalid option '%"+(char)fdsc.conversion+"' to 'format'");
+ break;
}
- } break;
- default:
- error("invalid option '%"+(char)fdsc.conversion+"' to 'format'");
- break;
}
}
}
}
+
+ return result.tostring();
}
-
- return result.tostring();
}
private static void addquoted(Buffer buf, LuaString s) {
@@ -503,10 +494,12 @@ public class StringLib extends TwoArgFunction {
* For this function, a '^' at the start of a pattern does not work as an anchor,
* as this would prevent the iteration.
*/
- static Varargs gmatch( Varargs args ) {
- LuaString src = args.checkstring( 1 );
- LuaString pat = args.checkstring( 2 );
- return new GMatchAux(args, src, pat);
+ static final class gmatch extends VarArgFunction {
+ public Varargs invoke(Varargs args) {
+ LuaString src = args.checkstring( 1 );
+ LuaString pat = args.checkstring( 2 );
+ return new GMatchAux(args, src, pat);
+ }
}
static class GMatchAux extends VarArgFunction {
@@ -578,37 +571,39 @@ public class StringLib extends TwoArgFunction {
* x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t)
* --> x="lua-5.1.tar.gz"
*/
- static Varargs gsub( Varargs args ) {
- LuaString src = args.checkstring( 1 );
- final int srclen = src.length();
- LuaString p = args.checkstring( 2 );
- LuaValue repl = args.arg( 3 );
- int max_s = args.optint( 4, srclen + 1 );
- final boolean anchor = p.length() > 0 && p.charAt( 0 ) == '^';
-
- Buffer lbuf = new Buffer( srclen );
- MatchState ms = new MatchState( args, src, p );
-
- int soffset = 0;
- int n = 0;
- while ( n < max_s ) {
- ms.reset();
- int res = ms.match( soffset, anchor ? 1 : 0 );
- if ( res != -1 ) {
- n++;
- ms.add_value( lbuf, soffset, res, repl );
+ static final class gsub extends VarArgFunction {
+ public Varargs invoke(Varargs args) {
+ LuaString src = args.checkstring( 1 );
+ final int srclen = src.length();
+ LuaString p = args.checkstring( 2 );
+ LuaValue repl = args.arg( 3 );
+ int max_s = args.optint( 4, srclen + 1 );
+ final boolean anchor = p.length() > 0 && p.charAt( 0 ) == '^';
+
+ Buffer lbuf = new Buffer( srclen );
+ MatchState ms = new MatchState( args, src, p );
+
+ int soffset = 0;
+ int n = 0;
+ while ( n < max_s ) {
+ ms.reset();
+ int res = ms.match( soffset, anchor ? 1 : 0 );
+ if ( res != -1 ) {
+ n++;
+ ms.add_value( lbuf, soffset, res, repl );
+ }
+ if ( res != -1 && res > soffset )
+ soffset = res;
+ else if ( soffset < srclen )
+ lbuf.append( (byte) src.luaByte( soffset++ ) );
+ else
+ break;
+ if ( anchor )
+ break;
}
- if ( res != -1 && res > soffset )
- soffset = res;
- else if ( soffset < srclen )
- lbuf.append( (byte) src.luaByte( soffset++ ) );
- else
- break;
- if ( anchor )
- break;
+ lbuf.append( src.substring( soffset, srclen ) );
+ return varargsOf(lbuf.tostring(), valueOf(n));
}
- lbuf.append( src.substring( soffset, srclen ) );
- return varargsOf(lbuf.tostring(), valueOf(n));
}
/**
@@ -617,8 +612,10 @@ public class StringLib extends TwoArgFunction {
* Receives a string and returns its length. The empty string "" has length 0.
* Embedded zeros are counted, so "a\000bc\000" has length 5.
*/
- static LuaValue len( LuaValue arg ) {
- return arg.checkstring().len();
+ static final class len extends OneArgFunction {
+ public LuaValue call(LuaValue arg) {
+ return arg.checkstring().len();
+ }
}
/**
@@ -628,8 +625,10 @@ public class StringLib extends TwoArgFunction {
* changed to lowercase. All other characters are left unchanged.
* The definition of what an uppercase letter is depends on the current locale.
*/
- static LuaValue lower( LuaValue arg ) {
- return valueOf( arg.checkjstring().toLowerCase() );
+ static final class lower extends OneArgFunction {
+ public LuaValue call(LuaValue arg) {
+ return valueOf( arg.checkjstring().toLowerCase() );
+ }
}
/**
@@ -641,8 +640,10 @@ public class StringLib extends TwoArgFunction {
* A third, optional numerical argument init specifies where to start the
* search; its default value is 1 and may be negative.
*/
- static Varargs match( Varargs args ) {
- return str_find_aux( args, false );
+ static final class match extends VarArgFunction {
+ public Varargs invoke(Varargs args) {
+ return str_find_aux( args, false );
+ }
}
/**
@@ -650,15 +651,17 @@ public class StringLib extends TwoArgFunction {
*
* Returns a string that is the concatenation of n copies of the string s.
*/
- static Varargs rep( Varargs args ) {
- LuaString s = args.checkstring( 1 );
- int n = args.checkint( 2 );
- final byte[] bytes = new byte[ s.length() * n ];
- int len = s.length();
- for ( int offset = 0; offset < bytes.length; offset += len ) {
- s.copyInto( 0, bytes, offset, len );
+ static final class rep extends VarArgFunction {
+ public Varargs invoke(Varargs args) {
+ LuaString s = args.checkstring( 1 );
+ int n = args.checkint( 2 );
+ final byte[] bytes = new byte[ s.length() * n ];
+ int len = s.length();
+ for ( int offset = 0; offset < bytes.length; offset += len ) {
+ s.copyInto( 0, bytes, offset, len );
+ }
+ return LuaString.valueUsing( bytes );
}
- return LuaString.valueUsing( bytes );
}
/**
@@ -666,13 +669,15 @@ public class StringLib extends TwoArgFunction {
*
* Returns a string that is the string s reversed.
*/
- static LuaValue reverse( LuaValue arg ) {
- LuaString s = arg.checkstring();
- int n = s.length();
- byte[] b = new byte[n];
- for ( int i=0, j=n-1; i l )
- end = l;
-
- if ( start <= end ) {
- return s.substring( start-1 , end );
- } else {
- return EMPTYSTRING;
+ static final class sub extends VarArgFunction {
+ public Varargs invoke(Varargs args) {
+ final LuaString s = args.checkstring( 1 );
+ final int l = s.length();
+
+ int start = posrelat( args.checkint( 2 ), l );
+ int end = posrelat( args.optint( 3, -1 ), l );
+
+ if ( start < 1 )
+ start = 1;
+ if ( end > l )
+ end = l;
+
+ if ( start <= end ) {
+ return s.substring( start-1 , end );
+ } else {
+ return EMPTYSTRING;
+ }
}
}
@@ -712,8 +719,10 @@ public class StringLib extends TwoArgFunction {
* changed to uppercase. All other characters are left unchanged.
* The definition of what a lowercase letter is depends on the current locale.
*/
- static LuaValue upper( LuaValue arg ) {
- return valueOf(arg.checkjstring().toUpperCase());
+ static final class upper extends OneArgFunction {
+ public LuaValue call(LuaValue arg) {
+ return valueOf(arg.checkjstring().toUpperCase());
+ }
}
/**