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>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>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>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>
|
</ul></td></tr>
|
||||||
</table></td></tr></table>
|
</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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -202,19 +202,55 @@ public class Globals extends LuaTable {
|
|||||||
public LuaValue load(String script) {
|
public LuaValue load(String script) {
|
||||||
return load(new StrReader(script), 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.
|
/** 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
|
* 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) {
|
public LuaValue load(Reader reader, String chunkname) {
|
||||||
return load(new UTF8Stream(reader), chunkname, "t", this);
|
return load(new UTF8Stream(reader), chunkname, "t", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Load the content form an input stream as a binary chunk or text file. */
|
/** Load the content form a reader as a text file, supplying a custom environment.
|
||||||
public LuaValue load(InputStream is, String chunkname, String mode, LuaValue env) {
|
* 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 {
|
try {
|
||||||
Prototype p = loadPrototype(is, chunkname, mode);
|
Prototype p = loadPrototype(is, chunkname, mode);
|
||||||
return loader.load(p, chunkname, env);
|
return loader.load(p, chunkname, environment);
|
||||||
} catch (LuaError l) {
|
} catch (LuaError l) {
|
||||||
throw l;
|
throw l;
|
||||||
} catch (Exception e) {
|
} 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.
|
/** 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,
|
* 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.
|
* 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 {
|
public Prototype loadPrototype(InputStream is, String chunkname, String mode) throws IOException {
|
||||||
if (mode.indexOf('b') >= 0) {
|
if (mode.indexOf('b') >= 0) {
|
||||||
|
|||||||
@@ -62,9 +62,9 @@ import org.luaj.vm2.lib.StringLib;
|
|||||||
*/
|
*/
|
||||||
public class LuaString extends LuaValue {
|
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;
|
public static LuaValue s_metatable;
|
||||||
|
|
||||||
/** The bytes for the string. These <em><b>must not be mutated directly</b></em> because
|
/** The bytes for the string. These <em><b>must not be mutated directly</b></em> because
|
||||||
* the backing may be shared by multiple LuaStrings, and the hash code is
|
* the backing may be shared by multiple LuaStrings, and the hash code is
|
||||||
* computed only at construction time.
|
* computed only at construction time.
|
||||||
@@ -254,11 +254,6 @@ public class LuaString extends LuaValue {
|
|||||||
return decodeAsUtf8(m_bytes, m_offset, m_length);
|
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
|
// unary operators
|
||||||
public LuaValue neg() { double d = scannumber(); return Double.isNaN(d)? super.neg(): valueOf(-d); }
|
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.LuaString;
|
||||||
import org.luaj.vm2.LuaTable;
|
import org.luaj.vm2.LuaTable;
|
||||||
import org.luaj.vm2.LuaValue;
|
import org.luaj.vm2.LuaValue;
|
||||||
|
import org.luaj.vm2.ReadOnlyTable;
|
||||||
import org.luaj.vm2.Varargs;
|
import org.luaj.vm2.Varargs;
|
||||||
import org.luaj.vm2.compiler.DumpState;
|
import org.luaj.vm2.compiler.DumpState;
|
||||||
|
|
||||||
@@ -61,57 +62,37 @@ import org.luaj.vm2.compiler.DumpState;
|
|||||||
*/
|
*/
|
||||||
public class StringLib extends TwoArgFunction {
|
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 StringLib() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public LuaValue call(LuaValue modname, LuaValue env) {
|
public LuaValue call(LuaValue modname, LuaValue env) {
|
||||||
LuaTable t = new LuaTable();
|
env.set("string", lib_functions);
|
||||||
bind(t, StringLib1.class, new String[] {
|
env.get("package").get("loaded").set("string", lib_functions);
|
||||||
"dump", "len", "lower", "reverse", "upper", } );
|
return lib_functions;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* string.byte (s [, i [, j]])
|
* string.byte (s [, i [, j]])
|
||||||
*
|
*
|
||||||
@@ -123,22 +104,24 @@ public class StringLib extends TwoArgFunction {
|
|||||||
*
|
*
|
||||||
* @param args the calling args
|
* @param args the calling args
|
||||||
*/
|
*/
|
||||||
static Varargs byte_( Varargs args ) {
|
static final class byte_ extends VarArgFunction {
|
||||||
LuaString s = args.checkstring(1);
|
public Varargs invoke(Varargs args) {
|
||||||
int l = s.m_length;
|
LuaString s = args.checkstring(1);
|
||||||
int posi = posrelat( args.optint(2,1), l );
|
int l = s.m_length;
|
||||||
int pose = posrelat( args.optint(3,posi), l );
|
int posi = posrelat( args.optint(2,1), l );
|
||||||
int n,i;
|
int pose = posrelat( args.optint(3,posi), l );
|
||||||
if (posi <= 0) posi = 1;
|
int n,i;
|
||||||
if (pose > l) pose = l;
|
if (posi <= 0) posi = 1;
|
||||||
if (posi > pose) return NONE; /* empty interval; return no values */
|
if (pose > l) pose = l;
|
||||||
n = (int)(pose - posi + 1);
|
if (posi > pose) return NONE; /* empty interval; return no values */
|
||||||
if (posi + n <= pose) /* overflow? */
|
n = (int)(pose - posi + 1);
|
||||||
error("string slice too long");
|
if (posi + n <= pose) /* overflow? */
|
||||||
LuaValue[] v = new LuaValue[n];
|
error("string slice too long");
|
||||||
for (i=0; i<n; i++)
|
LuaValue[] v = new LuaValue[n];
|
||||||
v[i] = valueOf(s.luaByte(posi+i-1));
|
for (i=0; i<n; i++)
|
||||||
return varargsOf(v);
|
v[i] = valueOf(s.luaByte(posi+i-1));
|
||||||
|
return varargsOf(v);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -152,15 +135,17 @@ public class StringLib extends TwoArgFunction {
|
|||||||
*
|
*
|
||||||
* @param args the calling VM
|
* @param args the calling VM
|
||||||
*/
|
*/
|
||||||
public static Varargs char_( Varargs args) {
|
static final class char_ extends VarArgFunction {
|
||||||
int n = args.narg();
|
public Varargs invoke(Varargs args) {
|
||||||
byte[] bytes = new byte[n];
|
int n = args.narg();
|
||||||
for ( int i=0, a=1; i<n; i++, a++ ) {
|
byte[] bytes = new byte[n];
|
||||||
int c = args.checkint(a);
|
for ( int i=0, a=1; i<n; i++, a++ ) {
|
||||||
if (c<0 || c>=256) argerror(a, "invalid value");
|
int c = args.checkint(a);
|
||||||
bytes[i] = (byte) c;
|
if (c<0 || c>=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
|
* TODO: port dumping code as optional add-on
|
||||||
*/
|
*/
|
||||||
static LuaValue dump( LuaValue arg ) {
|
static final class dump extends OneArgFunction {
|
||||||
LuaValue f = arg.checkfunction();
|
public LuaValue call(LuaValue arg) {
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
LuaValue f = arg.checkfunction();
|
||||||
try {
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
DumpState.dump( ((LuaClosure)f).p, baos, true );
|
try {
|
||||||
return LuaString.valueUsing(baos.toByteArray());
|
DumpState.dump( ((LuaClosure)f).p, baos, true );
|
||||||
} catch (IOException e) {
|
return LuaString.valueUsing(baos.toByteArray());
|
||||||
return error( e.getMessage() );
|
} 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
|
* If the pattern has captures, then in a successful match the captured values
|
||||||
* are also returned, after the two indices.
|
* are also returned, after the two indices.
|
||||||
*/
|
*/
|
||||||
static Varargs find( Varargs args ) {
|
static final class find extends VarArgFunction {
|
||||||
return str_find_aux( args, true );
|
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,
|
* This function does not accept string values containing embedded zeros,
|
||||||
* except as arguments to the q option.
|
* except as arguments to the q option.
|
||||||
*/
|
*/
|
||||||
static Varargs format( Varargs args ) {
|
static final class format extends VarArgFunction {
|
||||||
LuaString fmt = args.checkstring( 1 );
|
public Varargs invoke(Varargs args) {
|
||||||
final int n = fmt.length();
|
LuaString fmt = args.checkstring( 1 );
|
||||||
Buffer result = new Buffer(n);
|
final int n = fmt.length();
|
||||||
int arg = 1;
|
Buffer result = new Buffer(n);
|
||||||
int c;
|
int arg = 1;
|
||||||
|
int c;
|
||||||
for ( int i = 0; i < n; ) {
|
|
||||||
switch ( c = fmt.luaByte( i++ ) ) {
|
for ( int i = 0; i < n; ) {
|
||||||
case '\n':
|
switch ( c = fmt.luaByte( i++ ) ) {
|
||||||
result.append( "\n" );
|
case '\n':
|
||||||
break;
|
result.append( "\n" );
|
||||||
default:
|
break;
|
||||||
result.append( (byte) c );
|
default:
|
||||||
break;
|
result.append( (byte) c );
|
||||||
case L_ESC:
|
break;
|
||||||
if ( i < n ) {
|
case L_ESC:
|
||||||
if ( ( c = fmt.luaByte( i ) ) == L_ESC ) {
|
if ( i < n ) {
|
||||||
++i;
|
if ( ( c = fmt.luaByte( i ) ) == L_ESC ) {
|
||||||
result.append( (byte)L_ESC );
|
++i;
|
||||||
} else {
|
result.append( (byte)L_ESC );
|
||||||
arg++;
|
} else {
|
||||||
FormatDesc fdsc = new FormatDesc(args, fmt, i );
|
arg++;
|
||||||
i += fdsc.length;
|
FormatDesc fdsc = new FormatDesc(args, fmt, i );
|
||||||
switch ( fdsc.conversion ) {
|
i += fdsc.length;
|
||||||
case 'c':
|
switch ( fdsc.conversion ) {
|
||||||
fdsc.format( result, (byte)args.checkint( arg ) );
|
case 'c':
|
||||||
break;
|
fdsc.format( result, (byte)args.checkint( arg ) );
|
||||||
case 'i':
|
break;
|
||||||
case 'd':
|
case 'i':
|
||||||
fdsc.format( result, args.checkint( arg ) );
|
case 'd':
|
||||||
break;
|
fdsc.format( result, args.checkint( arg ) );
|
||||||
case 'o':
|
break;
|
||||||
case 'u':
|
case 'o':
|
||||||
case 'x':
|
case 'u':
|
||||||
case 'X':
|
case 'x':
|
||||||
fdsc.format( result, args.checklong( arg ) );
|
case 'X':
|
||||||
break;
|
fdsc.format( result, args.checklong( arg ) );
|
||||||
case 'e':
|
break;
|
||||||
case 'E':
|
case 'e':
|
||||||
case 'f':
|
case 'E':
|
||||||
case 'g':
|
case 'f':
|
||||||
case 'G':
|
case 'g':
|
||||||
fdsc.format( result, args.checkdouble( arg ) );
|
case 'G':
|
||||||
break;
|
fdsc.format( result, args.checkdouble( arg ) );
|
||||||
case 'q':
|
break;
|
||||||
addquoted( result, args.checkstring( arg ) );
|
case 'q':
|
||||||
break;
|
addquoted( result, args.checkstring( arg ) );
|
||||||
case 's': {
|
break;
|
||||||
LuaString s = args.checkstring( arg );
|
case 's': {
|
||||||
if ( fdsc.precision == -1 && s.length() >= 100 ) {
|
LuaString s = args.checkstring( arg );
|
||||||
result.append( s );
|
if ( fdsc.precision == -1 && s.length() >= 100 ) {
|
||||||
} else {
|
result.append( s );
|
||||||
fdsc.format( result, 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) {
|
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,
|
* For this function, a '^' at the start of a pattern does not work as an anchor,
|
||||||
* as this would prevent the iteration.
|
* as this would prevent the iteration.
|
||||||
*/
|
*/
|
||||||
static Varargs gmatch( Varargs args ) {
|
static final class gmatch extends VarArgFunction {
|
||||||
LuaString src = args.checkstring( 1 );
|
public Varargs invoke(Varargs args) {
|
||||||
LuaString pat = args.checkstring( 2 );
|
LuaString src = args.checkstring( 1 );
|
||||||
return new GMatchAux(args, src, pat);
|
LuaString pat = args.checkstring( 2 );
|
||||||
|
return new GMatchAux(args, src, pat);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class GMatchAux extends VarArgFunction {
|
static class GMatchAux extends VarArgFunction {
|
||||||
@@ -578,37 +571,39 @@ public class StringLib extends TwoArgFunction {
|
|||||||
* x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t)
|
* x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t)
|
||||||
* --> x="lua-5.1.tar.gz"
|
* --> x="lua-5.1.tar.gz"
|
||||||
*/
|
*/
|
||||||
static Varargs gsub( Varargs args ) {
|
static final class gsub extends VarArgFunction {
|
||||||
LuaString src = args.checkstring( 1 );
|
public Varargs invoke(Varargs args) {
|
||||||
final int srclen = src.length();
|
LuaString src = args.checkstring( 1 );
|
||||||
LuaString p = args.checkstring( 2 );
|
final int srclen = src.length();
|
||||||
LuaValue repl = args.arg( 3 );
|
LuaString p = args.checkstring( 2 );
|
||||||
int max_s = args.optint( 4, srclen + 1 );
|
LuaValue repl = args.arg( 3 );
|
||||||
final boolean anchor = p.length() > 0 && p.charAt( 0 ) == '^';
|
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 );
|
Buffer lbuf = new Buffer( srclen );
|
||||||
|
MatchState ms = new MatchState( args, src, p );
|
||||||
int soffset = 0;
|
|
||||||
int n = 0;
|
int soffset = 0;
|
||||||
while ( n < max_s ) {
|
int n = 0;
|
||||||
ms.reset();
|
while ( n < max_s ) {
|
||||||
int res = ms.match( soffset, anchor ? 1 : 0 );
|
ms.reset();
|
||||||
if ( res != -1 ) {
|
int res = ms.match( soffset, anchor ? 1 : 0 );
|
||||||
n++;
|
if ( res != -1 ) {
|
||||||
ms.add_value( lbuf, soffset, res, repl );
|
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 )
|
lbuf.append( src.substring( soffset, srclen ) );
|
||||||
soffset = res;
|
return varargsOf(lbuf.tostring(), valueOf(n));
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -617,8 +612,10 @@ public class StringLib extends TwoArgFunction {
|
|||||||
* Receives a string and returns its length. The empty string "" has length 0.
|
* Receives a string and returns its length. The empty string "" has length 0.
|
||||||
* Embedded zeros are counted, so "a\000bc\000" has length 5.
|
* Embedded zeros are counted, so "a\000bc\000" has length 5.
|
||||||
*/
|
*/
|
||||||
static LuaValue len( LuaValue arg ) {
|
static final class len extends OneArgFunction {
|
||||||
return arg.checkstring().len();
|
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.
|
* changed to lowercase. All other characters are left unchanged.
|
||||||
* The definition of what an uppercase letter is depends on the current locale.
|
* The definition of what an uppercase letter is depends on the current locale.
|
||||||
*/
|
*/
|
||||||
static LuaValue lower( LuaValue arg ) {
|
static final class lower extends OneArgFunction {
|
||||||
return valueOf( arg.checkjstring().toLowerCase() );
|
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
|
* A third, optional numerical argument init specifies where to start the
|
||||||
* search; its default value is 1 and may be negative.
|
* search; its default value is 1 and may be negative.
|
||||||
*/
|
*/
|
||||||
static Varargs match( Varargs args ) {
|
static final class match extends VarArgFunction {
|
||||||
return str_find_aux( args, false );
|
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.
|
* 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 {
|
||||||
LuaString s = args.checkstring( 1 );
|
public Varargs invoke(Varargs args) {
|
||||||
int n = args.checkint( 2 );
|
LuaString s = args.checkstring( 1 );
|
||||||
final byte[] bytes = new byte[ s.length() * n ];
|
int n = args.checkint( 2 );
|
||||||
int len = s.length();
|
final byte[] bytes = new byte[ s.length() * n ];
|
||||||
for ( int offset = 0; offset < bytes.length; offset += len ) {
|
int len = s.length();
|
||||||
s.copyInto( 0, bytes, offset, len );
|
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.
|
* Returns a string that is the string s reversed.
|
||||||
*/
|
*/
|
||||||
static LuaValue reverse( LuaValue arg ) {
|
static final class reverse extends OneArgFunction {
|
||||||
LuaString s = arg.checkstring();
|
public LuaValue call(LuaValue arg) {
|
||||||
int n = s.length();
|
LuaString s = arg.checkstring();
|
||||||
byte[] b = new byte[n];
|
int n = s.length();
|
||||||
for ( int i=0, j=n-1; i<n; i++, j-- )
|
byte[] b = new byte[n];
|
||||||
b[j] = (byte) s.luaByte(i);
|
for ( int i=0, j=n-1; i<n; i++, j-- )
|
||||||
return LuaString.valueUsing( b );
|
b[j] = (byte) s.luaByte(i);
|
||||||
|
return LuaString.valueUsing( b );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -686,22 +691,24 @@ public class StringLib extends TwoArgFunction {
|
|||||||
* string.sub(s, -i)
|
* string.sub(s, -i)
|
||||||
* returns a suffix of s with length i.
|
* returns a suffix of s with length i.
|
||||||
*/
|
*/
|
||||||
static Varargs sub( Varargs args ) {
|
static final class sub extends VarArgFunction {
|
||||||
final LuaString s = args.checkstring( 1 );
|
public Varargs invoke(Varargs args) {
|
||||||
final int l = s.length();
|
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 );
|
int start = posrelat( args.checkint( 2 ), l );
|
||||||
|
int end = posrelat( args.optint( 3, -1 ), l );
|
||||||
if ( start < 1 )
|
|
||||||
start = 1;
|
if ( start < 1 )
|
||||||
if ( end > l )
|
start = 1;
|
||||||
end = l;
|
if ( end > l )
|
||||||
|
end = l;
|
||||||
if ( start <= end ) {
|
|
||||||
return s.substring( start-1 , end );
|
if ( start <= end ) {
|
||||||
} else {
|
return s.substring( start-1 , end );
|
||||||
return EMPTYSTRING;
|
} else {
|
||||||
|
return EMPTYSTRING;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -712,8 +719,10 @@ public class StringLib extends TwoArgFunction {
|
|||||||
* changed to uppercase. All other characters are left unchanged.
|
* changed to uppercase. All other characters are left unchanged.
|
||||||
* The definition of what a lowercase letter is depends on the current locale.
|
* The definition of what a lowercase letter is depends on the current locale.
|
||||||
*/
|
*/
|
||||||
static LuaValue upper( LuaValue arg ) {
|
static final class upper extends OneArgFunction {
|
||||||
return valueOf(arg.checkjstring().toUpperCase());
|
public LuaValue call(LuaValue arg) {
|
||||||
|
return valueOf(arg.checkjstring().toUpperCase());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user