Add ReadOnlyTable and ReadWriteShadowTable, add sandboxing example code, make string metatable a real metatable.
This commit is contained in:
@@ -980,6 +980,10 @@ Files are no longer hosted at LuaForge.
|
||||
<li>Improve garbage collection of orphaned coroutines when yielding from debug hook functions (fixes issue #32).</li>
|
||||
<li>LuaScriptEngineFactory.getScriptEngine() now returns new instance of LuaScriptEngine for each call.</li>
|
||||
<li>Fix os.date("*t") to return hour in 24 hour format (fixes issue #45)</li>
|
||||
<li>Add ReadOnlyTable and ReadWriteShadowTable utility classes to simplify sandboxing.</li>
|
||||
<li>Make string metatable a proper metatable, and make it read-only by default.</li>
|
||||
<li>Add sample code that illustrates techniques in creating sandboxed environments.</li>
|
||||
<li>Add convenience methods to Global to load string scripts with custom environment.</li>
|
||||
|
||||
</ul></td></tr>
|
||||
</table></td></tr></table>
|
||||
|
||||
125
examples/jse/SampleSandboxed.java
Normal file
125
examples/jse/SampleSandboxed.java
Normal file
@@ -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.
|
||||
*
|
||||
* <p>Although this sandboxing is done primarily in Java here, most of the
|
||||
* same techniques can be done directly from lua using metatables.
|
||||
*
|
||||
* <p> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -203,18 +203,54 @@ public class Globals extends LuaTable {
|
||||
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) {
|
||||
|
||||
@@ -62,7 +62,7 @@ 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 <em><b>must not be mutated directly</b></em> because
|
||||
@@ -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); }
|
||||
|
||||
|
||||
125
src/core/org/luaj/vm2/ReadOnlyTable.java
Normal file
125
src/core/org/luaj/vm2/ReadOnlyTable.java
Normal file
@@ -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; i<named.length; i+=2 )
|
||||
if (!named[i+1].isnil())
|
||||
super.rawset(named[i], named[i+1]);
|
||||
}
|
||||
|
||||
/** Construct a ReadOnlyTable with initial values contained in 'table'
|
||||
* All key-value pairs in 'table' are copied, however only a shallow
|
||||
* copy is done so values which are tables remain read-write even
|
||||
* if they are returned by accessing this read-only table.
|
||||
* @param table LuaTable containing values to copy to this read-only table.
|
||||
*/
|
||||
public ReadOnlyTable(LuaTable table) {
|
||||
this(table, null, false);
|
||||
}
|
||||
|
||||
/** Construct a ReadOnlyTable with initial values contained in 'table'
|
||||
* and a read-only metatable 'metatable'.
|
||||
* All key-value pairs in 'table' are copied, however only a shallow
|
||||
* copy is done so values which are tables remain read-write even
|
||||
* if accessed from this read-only table.
|
||||
* @param table LuaTable containing values to copy in initially.
|
||||
* @param metatable ReadOnlyTable to be used as a metatable.
|
||||
*/
|
||||
public ReadOnlyTable(LuaTable table, ReadOnlyTable metatable) {
|
||||
this(table, metatable, false);
|
||||
}
|
||||
|
||||
/** Construct a ReadOnlyTable with initial values contained in 'table'
|
||||
* and a read-only metatable 'metatable', and optionally converting all
|
||||
* contained keys and values that are tables into read-only tables.
|
||||
* All key-value pairs in 'table' are copied, and when deepcopy is true,
|
||||
* tables are converted to ReadOnlyTable recursively.
|
||||
* @param table LuaTable containing values to copy in initially.
|
||||
* @param metatable ReadOnlyTable to be used as a metatable.
|
||||
* @param deepcopy when true, also converts table keys and values to read-only tables.
|
||||
*/
|
||||
public ReadOnlyTable(LuaTable table, ReadOnlyTable metatable, boolean deepcopy) {
|
||||
presize(table.length(), 0);
|
||||
for (Varargs n = table.next(LuaValue.NIL); !n.arg1().isnil();
|
||||
n = table.next(n.arg1())) {
|
||||
LuaValue key = n.arg1();
|
||||
LuaValue value = n.arg(2);
|
||||
super.rawset(deepcopy && key.istable()? new ReadOnlyTable(key.checktable()): key,
|
||||
deepcopy && value.istable()? new ReadOnlyTable(value.checktable()): value);
|
||||
}
|
||||
this.m_metatable = metatable;
|
||||
}
|
||||
|
||||
/** Constructor for default instance which is an empty read-only table. */
|
||||
private ReadOnlyTable() {}
|
||||
|
||||
/** Throw error indicating this is a read-only table. */
|
||||
public LuaValue setmetatable(LuaValue metatable) {
|
||||
return error("table is read-only");
|
||||
}
|
||||
|
||||
/** Throw error indicating this is a read-only table. */
|
||||
public void set(int key, LuaValue value) {
|
||||
error("table is read-only");
|
||||
}
|
||||
|
||||
/** Throw error indicating this is a read-only table. */
|
||||
public void set(LuaValue key, LuaValue value) {
|
||||
error("table is read-only");
|
||||
}
|
||||
|
||||
/** Throw error indicating this is a read-only table. */
|
||||
public void rawset(int key, LuaValue value) {
|
||||
error("table is read-only");
|
||||
}
|
||||
|
||||
/** Throw error indicating this is a read-only table. */
|
||||
public void rawset(LuaValue key, LuaValue value) {
|
||||
error("table is read-only");
|
||||
}
|
||||
|
||||
/** Throw error indicating this is a read-only table. */
|
||||
public LuaValue remove(int pos) {
|
||||
return error("table is read-only");
|
||||
}
|
||||
}
|
||||
67
src/core/org/luaj/vm2/ReadWriteShadowTable.java
Normal file
67
src/core/org/luaj/vm2/ReadWriteShadowTable.java
Normal file
@@ -0,0 +1,67 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
|
||||
import org.luaj.vm2.LuaTable;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
|
||||
/** LuaTable that has all read/write properties of a normal table,
|
||||
* but falls back to values in an underlying delegate table on reads,
|
||||
* including wrapping any table result in another shadow table.
|
||||
*
|
||||
* <p>This 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;
|
||||
}
|
||||
}
|
||||
@@ -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,55 +62,35 @@ 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
env.set("string", lib_functions);
|
||||
env.get("package").get("loaded").set("string", lib_functions);
|
||||
return lib_functions;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,7 +104,8 @@ public class StringLib extends TwoArgFunction {
|
||||
*
|
||||
* @param args the calling args
|
||||
*/
|
||||
static Varargs byte_( Varargs args ) {
|
||||
static final class byte_ extends VarArgFunction {
|
||||
public Varargs invoke(Varargs args) {
|
||||
LuaString s = args.checkstring(1);
|
||||
int l = s.m_length;
|
||||
int posi = posrelat( args.optint(2,1), l );
|
||||
@@ -140,6 +122,7 @@ public class StringLib extends TwoArgFunction {
|
||||
v[i] = valueOf(s.luaByte(posi+i-1));
|
||||
return varargsOf(v);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* string.char (...)
|
||||
@@ -152,7 +135,8 @@ public class StringLib extends TwoArgFunction {
|
||||
*
|
||||
* @param args the calling VM
|
||||
*/
|
||||
public static Varargs char_( Varargs args) {
|
||||
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<n; i++, a++ ) {
|
||||
@@ -162,6 +146,7 @@ public class StringLib extends TwoArgFunction {
|
||||
}
|
||||
return LuaString.valueUsing( bytes );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* string.dump (function)
|
||||
@@ -172,7 +157,8 @@ public class StringLib extends TwoArgFunction {
|
||||
*
|
||||
* TODO: port dumping code as optional add-on
|
||||
*/
|
||||
static LuaValue dump( LuaValue arg ) {
|
||||
static final class dump extends OneArgFunction {
|
||||
public LuaValue call(LuaValue arg) {
|
||||
LuaValue f = arg.checkfunction();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
@@ -182,6 +168,7 @@ public class StringLib extends TwoArgFunction {
|
||||
return error( e.getMessage() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* string.find (s, pattern [, init [, plain]])
|
||||
@@ -199,9 +186,11 @@ 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 ) {
|
||||
static final class find extends VarArgFunction {
|
||||
public Varargs invoke(Varargs args) {
|
||||
return str_find_aux( args, true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* string.format (formatstring, ...)
|
||||
@@ -226,7 +215,8 @@ 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 ) {
|
||||
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);
|
||||
@@ -293,6 +283,7 @@ public class StringLib extends TwoArgFunction {
|
||||
|
||||
return result.tostring();
|
||||
}
|
||||
}
|
||||
|
||||
private static void addquoted(Buffer buf, LuaString s) {
|
||||
int c;
|
||||
@@ -503,11 +494,13 @@ 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 ) {
|
||||
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 {
|
||||
private final int srclen;
|
||||
@@ -578,7 +571,8 @@ 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 ) {
|
||||
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 );
|
||||
@@ -610,6 +604,7 @@ public class StringLib extends TwoArgFunction {
|
||||
lbuf.append( src.substring( soffset, srclen ) );
|
||||
return varargsOf(lbuf.tostring(), valueOf(n));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* string.len (s)
|
||||
@@ -617,9 +612,11 @@ 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 ) {
|
||||
static final class len extends OneArgFunction {
|
||||
public LuaValue call(LuaValue arg) {
|
||||
return arg.checkstring().len();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* string.lower (s)
|
||||
@@ -628,9 +625,11 @@ 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 ) {
|
||||
static final class lower extends OneArgFunction {
|
||||
public LuaValue call(LuaValue arg) {
|
||||
return valueOf( arg.checkjstring().toLowerCase() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* string.match (s, pattern [, init])
|
||||
@@ -641,16 +640,19 @@ 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 ) {
|
||||
static final class match extends VarArgFunction {
|
||||
public Varargs invoke(Varargs args) {
|
||||
return str_find_aux( args, false );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* string.rep (s, n)
|
||||
*
|
||||
* Returns a string that is the concatenation of n copies of the string s.
|
||||
*/
|
||||
static Varargs rep( Varargs args ) {
|
||||
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 ];
|
||||
@@ -660,13 +662,15 @@ public class StringLib extends TwoArgFunction {
|
||||
}
|
||||
return LuaString.valueUsing( bytes );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* string.reverse (s)
|
||||
*
|
||||
* Returns a string that is the string s reversed.
|
||||
*/
|
||||
static LuaValue reverse( LuaValue arg ) {
|
||||
static final class reverse extends OneArgFunction {
|
||||
public LuaValue call(LuaValue arg) {
|
||||
LuaString s = arg.checkstring();
|
||||
int n = s.length();
|
||||
byte[] b = new byte[n];
|
||||
@@ -674,6 +678,7 @@ public class StringLib extends TwoArgFunction {
|
||||
b[j] = (byte) s.luaByte(i);
|
||||
return LuaString.valueUsing( b );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* string.sub (s, i [, j])
|
||||
@@ -686,7 +691,8 @@ public class StringLib extends TwoArgFunction {
|
||||
* string.sub(s, -i)
|
||||
* returns a suffix of s with length i.
|
||||
*/
|
||||
static Varargs sub( Varargs args ) {
|
||||
static final class sub extends VarArgFunction {
|
||||
public Varargs invoke(Varargs args) {
|
||||
final LuaString s = args.checkstring( 1 );
|
||||
final int l = s.length();
|
||||
|
||||
@@ -704,6 +710,7 @@ public class StringLib extends TwoArgFunction {
|
||||
return EMPTYSTRING;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* string.upper (s)
|
||||
@@ -712,9 +719,11 @@ 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 ) {
|
||||
static final class upper extends OneArgFunction {
|
||||
public LuaValue call(LuaValue arg) {
|
||||
return valueOf(arg.checkjstring().toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This utility method implements both string.find and string.match.
|
||||
|
||||
Reference in New Issue
Block a user