From ba3d1d8ef9cc17c9d66d4adb9ecaf4f0c634f436 Mon Sep 17 00:00:00 2001 From: UnlegitDqrk Date: Sun, 1 Mar 2026 12:57:18 +0100 Subject: [PATCH] Added missed Classes --- .../main/java/org/luaj/vm2/libs/BaseLib.java | 485 +++++++ .../main/java/org/luaj/vm2/libs/Bit32Lib.java | 224 +++ .../java/org/luaj/vm2/libs/CoroutineLib.java | 144 ++ .../main/java/org/luaj/vm2/libs/DebugLib.java | 954 +++++++++++++ .../main/java/org/luaj/vm2/libs/IoLib.java | 688 ++++++++++ .../java/org/luaj/vm2/libs/LibFunction.java | 222 +++ .../main/java/org/luaj/vm2/libs/MathLib.java | 306 +++++ .../org/luaj/vm2/libs/OneArgFunction.java | 72 + .../main/java/org/luaj/vm2/libs/OsLib.java | 524 +++++++ .../java/org/luaj/vm2/libs/PackageLib.java | 381 +++++ .../org/luaj/vm2/libs/ResourceFinder.java | 59 + .../java/org/luaj/vm2/libs/StringLib.java | 1223 +++++++++++++++++ .../main/java/org/luaj/vm2/libs/TableLib.java | 158 +++ .../org/luaj/vm2/libs/TableLibFunction.java | 9 + .../org/luaj/vm2/libs/ThreeArgFunction.java | 73 + .../org/luaj/vm2/libs/TwoArgFunction.java | 73 + .../org/luaj/vm2/libs/VarArgFunction.java | 83 ++ .../org/luaj/vm2/libs/ZeroArgFunction.java | 70 + .../java/org/luaj/vm2/libs/jme/JmeIoLib.java | 230 ++++ .../org/luaj/vm2/libs/jme/JmePlatform.java | 133 ++ .../luaj/vm2/libs/jse/CoerceJavaToLua.java | 196 +++ .../luaj/vm2/libs/jse/CoerceLuaToJava.java | 372 +++++ .../java/org/luaj/vm2/libs/jse/JavaArray.java | 86 ++ .../java/org/luaj/vm2/libs/jse/JavaClass.java | 153 +++ .../luaj/vm2/libs/jse/JavaConstructor.java | 116 ++ .../org/luaj/vm2/libs/jse/JavaInstance.java | 82 ++ .../org/luaj/vm2/libs/jse/JavaMember.java | 84 ++ .../org/luaj/vm2/libs/jse/JavaMethod.java | 163 +++ .../org/luaj/vm2/libs/jse/JseBaseLib.java | 116 ++ .../java/org/luaj/vm2/libs/jse/JseIoLib.java | 343 +++++ .../org/luaj/vm2/libs/jse/JseMathLib.java | 120 ++ .../java/org/luaj/vm2/libs/jse/JseOsLib.java | 135 ++ .../org/luaj/vm2/libs/jse/JsePlatform.java | 143 ++ .../org/luaj/vm2/libs/jse/JseProcess.java | 132 ++ .../org/luaj/vm2/libs/jse/JseStringLib.java | 39 + .../org/luaj/vm2/libs/jse/LuajavaLib.java | 214 +++ .../luaj/vm2/libs/jse/JsePlatformTest.java | 21 + .../vm2/libs/jse/LuaJavaCoercionTest.java | 446 ++++++ .../jse/LuajavaAccessibleMembersTest.java | 68 + .../vm2/libs/jse/LuajavaClassMembersTest.java | 238 ++++ .../java/org/luaj/vm2/libs/jse/OsLibTest.java | 77 ++ .../java/org/luaj/vm2/libs/jse/TestClass.java | 22 + .../org/luaj/vm2/libs/jse/TestInterface.java | 5 + 43 files changed, 9482 insertions(+) create mode 100644 core/src/main/java/org/luaj/vm2/libs/BaseLib.java create mode 100644 core/src/main/java/org/luaj/vm2/libs/Bit32Lib.java create mode 100644 core/src/main/java/org/luaj/vm2/libs/CoroutineLib.java create mode 100644 core/src/main/java/org/luaj/vm2/libs/DebugLib.java create mode 100644 core/src/main/java/org/luaj/vm2/libs/IoLib.java create mode 100644 core/src/main/java/org/luaj/vm2/libs/LibFunction.java create mode 100644 core/src/main/java/org/luaj/vm2/libs/MathLib.java create mode 100644 core/src/main/java/org/luaj/vm2/libs/OneArgFunction.java create mode 100644 core/src/main/java/org/luaj/vm2/libs/OsLib.java create mode 100644 core/src/main/java/org/luaj/vm2/libs/PackageLib.java create mode 100644 core/src/main/java/org/luaj/vm2/libs/ResourceFinder.java create mode 100644 core/src/main/java/org/luaj/vm2/libs/StringLib.java create mode 100644 core/src/main/java/org/luaj/vm2/libs/TableLib.java create mode 100644 core/src/main/java/org/luaj/vm2/libs/TableLibFunction.java create mode 100644 core/src/main/java/org/luaj/vm2/libs/ThreeArgFunction.java create mode 100644 core/src/main/java/org/luaj/vm2/libs/TwoArgFunction.java create mode 100644 core/src/main/java/org/luaj/vm2/libs/VarArgFunction.java create mode 100644 core/src/main/java/org/luaj/vm2/libs/ZeroArgFunction.java create mode 100644 jme/src/main/java/org/luaj/vm2/libs/jme/JmeIoLib.java create mode 100644 jme/src/main/java/org/luaj/vm2/libs/jme/JmePlatform.java create mode 100644 jse/src/main/java/org/luaj/vm2/libs/jse/CoerceJavaToLua.java create mode 100644 jse/src/main/java/org/luaj/vm2/libs/jse/CoerceLuaToJava.java create mode 100644 jse/src/main/java/org/luaj/vm2/libs/jse/JavaArray.java create mode 100644 jse/src/main/java/org/luaj/vm2/libs/jse/JavaClass.java create mode 100644 jse/src/main/java/org/luaj/vm2/libs/jse/JavaConstructor.java create mode 100644 jse/src/main/java/org/luaj/vm2/libs/jse/JavaInstance.java create mode 100644 jse/src/main/java/org/luaj/vm2/libs/jse/JavaMember.java create mode 100644 jse/src/main/java/org/luaj/vm2/libs/jse/JavaMethod.java create mode 100644 jse/src/main/java/org/luaj/vm2/libs/jse/JseBaseLib.java create mode 100644 jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib.java create mode 100644 jse/src/main/java/org/luaj/vm2/libs/jse/JseMathLib.java create mode 100644 jse/src/main/java/org/luaj/vm2/libs/jse/JseOsLib.java create mode 100644 jse/src/main/java/org/luaj/vm2/libs/jse/JsePlatform.java create mode 100644 jse/src/main/java/org/luaj/vm2/libs/jse/JseProcess.java create mode 100644 jse/src/main/java/org/luaj/vm2/libs/jse/JseStringLib.java create mode 100644 jse/src/main/java/org/luaj/vm2/libs/jse/LuajavaLib.java create mode 100644 jse/src/test/java/org/luaj/vm2/libs/jse/JsePlatformTest.java create mode 100644 jse/src/test/java/org/luaj/vm2/libs/jse/LuaJavaCoercionTest.java create mode 100644 jse/src/test/java/org/luaj/vm2/libs/jse/LuajavaAccessibleMembersTest.java create mode 100644 jse/src/test/java/org/luaj/vm2/libs/jse/LuajavaClassMembersTest.java create mode 100644 jse/src/test/java/org/luaj/vm2/libs/jse/OsLibTest.java create mode 100644 jse/src/test/java/org/luaj/vm2/libs/jse/TestClass.java create mode 100644 jse/src/test/java/org/luaj/vm2/libs/jse/TestInterface.java diff --git a/core/src/main/java/org/luaj/vm2/libs/BaseLib.java b/core/src/main/java/org/luaj/vm2/libs/BaseLib.java new file mode 100644 index 00000000..c94bc753 --- /dev/null +++ b/core/src/main/java/org/luaj/vm2/libs/BaseLib.java @@ -0,0 +1,485 @@ +/******************************************************************************* +* Copyright (c) 2009 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.libs; + +import java.io.IOException; +import java.io.InputStream; + +import org.luaj.vm2.Globals; +import org.luaj.vm2.Lua; +import org.luaj.vm2.LuaError; +import org.luaj.vm2.LuaString; +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaThread; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; + +/** + * Subclass of {@link LibFunction} which implements the lua basic library functions. + *

+ * This contains all library functions listed as "basic functions" in the lua documentation for JME. + * The functions dofile and loadfile use the + * {@link Globals#finder} instance to find resource files. + * Since JME has no file system by default, {@link BaseLib} implements + * {@link ResourceFinder} using {@link Class#getResource(String)}, + * which is the closest equivalent on JME. + * The default loader chain in {@link PackageLib} will use these as well. + *

+ * To use basic library functions that include a {@link ResourceFinder} based on + * directory lookup, use {@link org.luaj.vm2.libs.jse.JseBaseLib} instead. + *

+ * Typically, this library is included as part of a call to either + * {@link org.luaj.vm2.libs.jse.JsePlatform#standardGlobals()} or + * {@link org.luaj.vm2.libs.jme.JmePlatform#standardGlobals()} + *

 {@code
+ * Globals globals = JsePlatform.standardGlobals();
+ * globals.get("print").call(LuaValue.valueOf("hello, world"));
+ * } 
+ *

+ * For special cases where the smallest possible footprint is desired, + * a minimal set of libraries could be loaded + * directly via {@link Globals#load(LuaValue)} using code such as: + *

 {@code
+ * Globals globals = new Globals();
+ * globals.load(new JseBaseLib());
+ * globals.get("print").call(LuaValue.valueOf("hello, world"));
+ * } 
+ * Doing so will ensure the library is properly initialized + * and loaded into the globals table. + *

+ * This is a direct port of the corresponding library in C. + * @see org.luaj.vm2.libs.jse.JseBaseLib + * @see ResourceFinder + * @see Globals#finder + * @see LibFunction + * @see org.luaj.vm2.libs.jse.JsePlatform + * @see org.luaj.vm2.libs.jme.JmePlatform + * @see Lua 5.2 Base Lib Reference + */ +public class BaseLib extends TwoArgFunction implements ResourceFinder { + + Globals globals; + + + /** Perform one-time initialization on the library by adding base functions + * to the supplied environment, and returning it as the return value. + * @param modname the module name supplied if this is loaded via 'require'. + * @param env the environment to load into, which must be a Globals instance. + */ + public LuaValue call(LuaValue modname, LuaValue env) { + globals = env.checkglobals(); + globals.finder = this; + globals.baselib = this; + env.set( "_G", env ); + env.set( "_VERSION", Lua._VERSION ); + env.set("assert", new _assert()); + env.set("collectgarbage", new collectgarbage()); + env.set("dofile", new dofile()); + env.set("error", new error()); + env.set("getmetatable", new getmetatable()); + env.set("load", new load()); + env.set("loadfile", new loadfile()); + env.set("pcall", new pcall()); + env.set("print", new print(this)); + env.set("rawequal", new rawequal()); + env.set("rawget", new rawget()); + env.set("rawlen", new rawlen()); + env.set("rawset", new rawset()); + env.set("select", new select()); + env.set("setmetatable", new setmetatable()); + env.set("tonumber", new tonumber()); + env.set("tostring", new tostring()); + env.set("type", new type()); + env.set("xpcall", new xpcall()); + + next next; + env.set("next", next = new next()); + env.set("pairs", new pairs(next)); + env.set("ipairs", new ipairs()); + + return env; + } + + /** ResourceFinder implementation + * + * Tries to open the file as a resource, which can work for JSE and JME. + */ + public InputStream findResource(String filename) { + return getClass().getResourceAsStream(filename.startsWith("/")? filename: "/"+filename); + } + + + // "assert", // ( v [,message] ) -> v, message | ERR + static final class _assert extends VarArgFunction { + public Varargs invoke(Varargs args) { + if ( !args.arg1().toboolean() ) + error( args.narg()>1? args.optjstring(2,"assertion failed!"): "assertion failed!" ); + return args; + } + } + + // "collectgarbage", // ( opt [,arg] ) -> value + static final class collectgarbage extends VarArgFunction { + public Varargs invoke(Varargs args) { + String s = args.optjstring(1, "collect"); + if ( "collect".equals(s) ) { + System.gc(); + return ZERO; + } else if ( "count".equals(s) ) { + Runtime rt = Runtime.getRuntime(); + long used = rt.totalMemory() - rt.freeMemory(); + return varargsOf(valueOf(used/1024.), valueOf(used%1024)); + } else if ( "step".equals(s) ) { + System.gc(); + return LuaValue.TRUE; + } else { + argerror(1, "invalid option '" + s + "'"); + } + return NIL; + } + } + + // "dofile", // ( filename ) -> result1, ... + final class dofile extends VarArgFunction { + public Varargs invoke(Varargs args) { + args.argcheck(args.isstring(1) || args.isnil(1), 1, "filename must be string or nil"); + String filename = args.isstring(1)? args.tojstring(1): null; + Varargs v = filename == null? + loadStream( globals.STDIN, "=stdin", "bt", globals ): + loadFile( args.checkjstring(1), "bt", globals ); + return v.isnil(1)? error(v.tojstring(2)): v.arg1().invoke(); + } + } + + // "error", // ( message [,level] ) -> ERR + static final class error extends TwoArgFunction { + public LuaValue call(LuaValue arg1, LuaValue arg2) { + if (arg1.isnil()) throw new LuaError(NIL); + if (!arg1.isstring() || arg2.optint(1) == 0) throw new LuaError(arg1); + throw new LuaError(arg1.tojstring(), arg2.optint(1)); + } + } + + // "getmetatable", // ( object ) -> table + static final class getmetatable extends LibFunction { + public LuaValue call() { + return argerror(1, "value expected"); + } + public LuaValue call(LuaValue arg) { + LuaValue mt = arg.getmetatable(); + return mt!=null? mt.rawget(METATABLE).optvalue(mt): NIL; + } + } + // "load", // ( ld [, source [, mode [, env]]] ) -> chunk | nil, msg + final class load extends VarArgFunction { + public Varargs invoke(Varargs args) { + LuaValue ld = args.arg1(); + if (!ld.isstring() && !ld.isfunction()) { + throw new LuaError("bad argument #1 to 'load' (string or function expected, got " + ld.typename() + ")"); + } + String source = args.optjstring(2, ld.isstring()? ld.tojstring(): "=(load)"); + String mode = args.optjstring(3, "bt"); + LuaValue env = args.optvalue(4, globals); + return loadStream(ld.isstring()? ld.strvalue().toInputStream(): + new StringInputStream(ld.checkfunction()), source, mode, env); + } + } + + // "loadfile", // ( [filename [, mode [, env]]] ) -> chunk | nil, msg + final class loadfile extends VarArgFunction { + public Varargs invoke(Varargs args) { + args.argcheck(args.isstring(1) || args.isnil(1), 1, "filename must be string or nil"); + String filename = args.isstring(1)? args.tojstring(1): null; + String mode = args.optjstring(2, "bt"); + LuaValue env = args.optvalue(3, globals); + return filename == null? + loadStream( globals.STDIN, "=stdin", mode, env ): + loadFile( filename, mode, env ); + } + } + + // "pcall", // (f, arg1, ...) -> status, result1, ... + final class pcall extends VarArgFunction { + public Varargs invoke(Varargs args) { + LuaValue func = args.checkvalue(1); + if (globals != null && globals.debuglib != null) + globals.debuglib.onCall(this); + try { + return varargsOf(TRUE, func.invoke(args.subargs(2))); + } catch ( LuaError le ) { + final LuaValue m = le.getMessageObject(); + return varargsOf(FALSE, m!=null? m: NIL); + } catch ( Exception e ) { + final String m = e.getMessage(); + return varargsOf(FALSE, valueOf(m!=null? m: e.toString())); + } finally { + if (globals != null && globals.debuglib != null) + globals.debuglib.onReturn(); + } + } + } + + // "print", // (...) -> void + final class print extends VarArgFunction { + final BaseLib baselib; + print(BaseLib baselib) { + this.baselib = baselib; + } + public Varargs invoke(Varargs args) { + LuaValue tostring = globals.get("tostring"); + for ( int i=1, n=args.narg(); i<=n; i++ ) { + if ( i>1 ) globals.STDOUT.print( '\t' ); + LuaString s = tostring.call( args.arg(i) ).strvalue(); + globals.STDOUT.print(s.tojstring()); + } + globals.STDOUT.print('\n'); + return NONE; + } + } + + + // "rawequal", // (v1, v2) -> boolean + static final class rawequal extends LibFunction { + public LuaValue call() { + return argerror(1, "value expected"); + } + public LuaValue call(LuaValue arg) { + return argerror(2, "value expected"); + } + public LuaValue call(LuaValue arg1, LuaValue arg2) { + return valueOf(arg1.raweq(arg2)); + } + } + + // "rawget", // (table, index) -> value + static final class rawget extends TableLibFunction { + public LuaValue call(LuaValue arg) { + return argerror(2, "value expected"); + } + public LuaValue call(LuaValue arg1, LuaValue arg2) { + return arg1.checktable().rawget(arg2); + } + } + + + // "rawlen", // (v) -> value + static final class rawlen extends LibFunction { + public LuaValue call(LuaValue arg) { + return valueOf(arg.rawlen()); + } + } + + // "rawset", // (table, index, value) -> table + static final class rawset extends TableLibFunction { + public LuaValue call(LuaValue table) { + return argerror(2,"value expected"); + } + public LuaValue call(LuaValue table, LuaValue index) { + return argerror(3,"value expected"); + } + public LuaValue call(LuaValue table, LuaValue index, LuaValue value) { + LuaTable t = table.checktable(); + if (!index.isvalidkey()) argerror(2, "table index is nil"); + t.rawset(index, value); + return t; + } + } + + // "select", // (f, ...) -> value1, ... + static final class select extends VarArgFunction { + public Varargs invoke(Varargs args) { + int n = args.narg()-1; + if ( args.arg1().equals(valueOf("#")) ) + return valueOf(n); + int i = args.checkint(1); + if ( i == 0 || i < -n ) + argerror(1,"index out of range"); + return args.subargs(i<0? n+i+2: i+1); + } + } + + // "setmetatable", // (table, metatable) -> table + static final class setmetatable extends TableLibFunction { + public LuaValue call(LuaValue table) { + return argerror(2,"nil or table expected"); + } + public LuaValue call(LuaValue table, LuaValue metatable) { + final LuaValue mt0 = table.checktable().getmetatable(); + if ( mt0!=null && !mt0.rawget(METATABLE).isnil() ) + error("cannot change a protected metatable"); + return table.setmetatable(metatable.isnil()? null: metatable.checktable()); + } + } + + // "tonumber", // (e [,base]) -> value + static final class tonumber extends LibFunction { + public LuaValue call(LuaValue e) { + return e.tonumber(); + } + public LuaValue call(LuaValue e, LuaValue base) { + if (base.isnil()) + return e.tonumber(); + final int b = base.checkint(); + if ( b < 2 || b > 36 ) + argerror(2, "base out of range"); + return e.checkstring().tonumber(b); + } + } + + // "tostring", // (e) -> value + static final class tostring extends LibFunction { + public LuaValue call(LuaValue arg) { + LuaValue h = arg.metatag(TOSTRING); + if ( ! h.isnil() ) + return h.call(arg); + LuaValue v = arg.tostring(); + if ( ! v.isnil() ) + return v; + return valueOf(arg.tojstring()); + } + } + + // "type", // (v) -> value + static final class type extends LibFunction { + public LuaValue call(LuaValue arg) { + return valueOf(arg.typename()); + } + } + + // "xpcall", // (f, err) -> result1, ... + final class xpcall extends VarArgFunction { + public Varargs invoke(Varargs args) { + final LuaThread t = globals.running; + final LuaValue preverror = t.errorfunc; + t.errorfunc = args.checkvalue(2); + try { + if (globals != null && globals.debuglib != null) + globals.debuglib.onCall(this); + try { + return varargsOf(TRUE, args.arg1().invoke(args.subargs(3))); + } catch ( LuaError le ) { + final LuaValue m = le.getMessageObject(); + return varargsOf(FALSE, m!=null? m: NIL); + } catch ( Exception e ) { + final String m = e.getMessage(); + return varargsOf(FALSE, valueOf(m!=null? m: e.toString())); + } finally { + if (globals != null && globals.debuglib != null) + globals.debuglib.onReturn(); + } + } finally { + t.errorfunc = preverror; + } + } + } + + // "pairs" (t) -> iter-func, t, nil + static final class pairs extends VarArgFunction { + final next next; + pairs(next next) { + this.next = next; + } + public Varargs invoke(Varargs args) { + return varargsOf( next, args.checktable(1), NIL ); + } + } + + // // "ipairs", // (t) -> iter-func, t, 0 + static final class ipairs extends VarArgFunction { + inext inext = new inext(); + public Varargs invoke(Varargs args) { + return varargsOf( inext, args.checktable(1), ZERO ); + } + } + + // "next" ( table, [index] ) -> next-index, next-value + static final class next extends VarArgFunction { + public Varargs invoke(Varargs args) { + return args.checktable(1).next(args.arg(2)); + } + } + + // "inext" ( table, [int-index] ) -> next-index, next-value + static final class inext extends VarArgFunction { + public Varargs invoke(Varargs args) { + return args.checktable(1).inext(args.arg(2)); + } + } + + /** + * Load from a named file, returning the chunk or nil,error of can't load + * @param env + * @param mode + * @return Varargs containing chunk, or NIL,error-text on error + */ + public Varargs loadFile(String filename, String mode, LuaValue env) { + InputStream is = globals.finder.findResource(filename); + if ( is == null ) + return varargsOf(NIL, valueOf("cannot open "+filename+": No such file or directory")); + try { + return loadStream(is, "@"+filename, mode, env); + } finally { + try { + is.close(); + } catch ( Exception e ) { + e.printStackTrace(); + } + } + } + + public Varargs loadStream(InputStream is, String chunkname, String mode, LuaValue env) { + try { + if ( is == null ) + return varargsOf(NIL, valueOf("not found: "+chunkname)); + return globals.load(is, chunkname, mode, env); + } catch (Exception e) { + return varargsOf(NIL, valueOf(e.getMessage())); + } + } + + + private static class StringInputStream extends InputStream { + final LuaValue func; + byte[] bytes; + int offset, remaining = 0; + StringInputStream(LuaValue func) { + this.func = func; + } + public int read() throws IOException { + if ( remaining < 0 ) + return -1; + if ( remaining == 0 ) { + LuaValue s = func.call(); + if ( s.isnil() ) + return remaining = -1; + LuaString ls = s.strvalue(); + bytes = ls.m_bytes; + offset = ls.m_offset; + remaining = ls.m_length; + if (remaining <= 0) + return -1; + } + --remaining; + return 0xFF&bytes[offset++]; + } + } +} diff --git a/core/src/main/java/org/luaj/vm2/libs/Bit32Lib.java b/core/src/main/java/org/luaj/vm2/libs/Bit32Lib.java new file mode 100644 index 00000000..556918aa --- /dev/null +++ b/core/src/main/java/org/luaj/vm2/libs/Bit32Lib.java @@ -0,0 +1,224 @@ +/******************************************************************************* +* Copyright (c) 2012 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.libs; + +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; + +/** + * Subclass of LibFunction that implements the Lua standard {@code bit32} library. + *

+ * Typically, this library is included as part of a call to either + * {@link org.luaj.vm2.libs.jse.JsePlatform#standardGlobals()} or {@link org.luaj.vm2.libs.jme.JmePlatform#standardGlobals()} + *

 {@code
+ * Globals globals = JsePlatform.standardGlobals();
+ * System.out.println( globals.get("bit32").get("bnot").call( LuaValue.valueOf(2) ) );
+ * } 
+ *

+ * To instantiate and use it directly, + * link it into your globals table via {@link LuaValue#load(LuaValue)} using code such as: + *

 {@code
+ * Globals globals = new Globals();
+ * globals.load(new JseBaseLib());
+ * globals.load(new PackageLib());
+ * globals.load(new Bit32Lib());
+ * System.out.println( globals.get("bit32").get("bnot").call( LuaValue.valueOf(2) ) );
+ * } 
+ *

+ * This has been implemented to match as closely as possible the behavior in the corresponding library in C. + * @see LibFunction + * @see org.luaj.vm2.libs.jse.JsePlatform + * @see org.luaj.vm2.libs.jme.JmePlatform + * @see Lua 5.2 Bitwise Operation Lib Reference + */ +public class Bit32Lib extends TwoArgFunction { + + public Bit32Lib() { + } + + /** Perform one-time initialization on the library by creating a table + * containing the library functions, adding that table to the supplied environment, + * adding the table to package.loaded, and returning table as the return value. + * @param modname the module name supplied if this is loaded via 'require'. + * @param env the environment to load into, which must be a Globals instance. + */ + public LuaValue call(LuaValue modname, LuaValue env) { + LuaTable t = new LuaTable(); + bind(t, Bit32LibV.class, new String[] { + "band", "bnot", "bor", "btest", "bxor", "extract", "replace" + }); + bind(t, Bit32Lib2.class, new String[] { + "arshift", "lrotate", "lshift", "rrotate", "rshift" + }); + env.set("bit32", t); + if (!env.get("package").isnil()) env.get("package").get("loaded").set("bit32", t); + return t; + } + + static final class Bit32LibV extends VarArgFunction { + public Varargs invoke(Varargs args) { + switch ( opcode ) { + case 0: return Bit32Lib.band( args ); + case 1: return Bit32Lib.bnot( args ); + case 2: return Bit32Lib.bor( args ); + case 3: return Bit32Lib.btest( args ); + case 4: return Bit32Lib.bxor( args ); + case 5: + return Bit32Lib.extract( args.checkint(1), args.checkint(2), args.optint(3, 1) ); + case 6: + return Bit32Lib.replace( args.checkint(1), args.checkint(2), + args.checkint(3), args.optint(4, 1) ); + } + return NIL; + } + } + + static final class Bit32Lib2 extends TwoArgFunction { + + public LuaValue call(LuaValue arg1, LuaValue arg2) { + switch ( opcode ) { + case 0: return Bit32Lib.arshift(arg1.checkint(), arg2.checkint()); + case 1: return Bit32Lib.lrotate(arg1.checkint(), arg2.checkint()); + case 2: return Bit32Lib.lshift(arg1.checkint(), arg2.checkint()); + case 3: return Bit32Lib.rrotate(arg1.checkint(), arg2.checkint()); + case 4: return Bit32Lib.rshift(arg1.checkint(), arg2.checkint()); + } + return NIL; + } + + } + + static LuaValue arshift(int x, int disp) { + if (disp >= 0) { + return bitsToValue(x >> disp); + } else { + return bitsToValue(x << -disp); + } + } + + static LuaValue rshift(int x, int disp) { + if (disp >= 32 || disp <= -32) { + return ZERO; + } else if (disp >= 0) { + return bitsToValue(x >>> disp); + } else { + return bitsToValue(x << -disp); + } + } + + static LuaValue lshift(int x, int disp) { + if (disp >= 32 || disp <= -32) { + return ZERO; + } else if (disp >= 0) { + return bitsToValue(x << disp); + } else { + return bitsToValue(x >>> -disp); + } + } + + static Varargs band( Varargs args ) { + int result = -1; + for ( int i = 1; i <= args.narg(); i++ ) { + result &= args.checkint(i); + } + return bitsToValue( result ); + } + + static Varargs bnot( Varargs args ) { + return bitsToValue( ~args.checkint(1) ); + } + + static Varargs bor( Varargs args ) { + int result = 0; + for ( int i = 1; i <= args.narg(); i++ ) { + result |= args.checkint(i); + } + return bitsToValue( result ); + } + + static Varargs btest( Varargs args ) { + int bits = -1; + for ( int i = 1; i <= args.narg(); i++ ) { + bits &= args.checkint(i); + } + return valueOf( bits != 0 ); + } + + static Varargs bxor( Varargs args ) { + int result = 0; + for ( int i = 1; i <= args.narg(); i++ ) { + result ^= args.checkint(i); + } + return bitsToValue( result ); + } + + static LuaValue lrotate(int x, int disp) { + if (disp < 0) { + return rrotate(x, -disp); + } else { + disp = disp & 31; + return bitsToValue((x << disp) | (x >>> (32 - disp))); + } + } + + static LuaValue rrotate(int x, int disp) { + if (disp < 0) { + return lrotate(x, -disp); + } else { + disp = disp & 31; + return bitsToValue((x >>> disp) | (x << (32 - disp))); + } + } + + static LuaValue extract(int n, int field, int width) { + if (field < 0) { + argerror(2, "field cannot be negative"); + } + if (width < 0) { + argerror(3, "width must be postive"); + } + if (field + width > 32) { + error("trying to access non-existent bits"); + } + return bitsToValue((n >>> field) & (-1 >>> (32 - width))); + } + + static LuaValue replace(int n, int v, int field, int width) { + if (field < 0) { + argerror(3, "field cannot be negative"); + } + if (width < 0) { + argerror(4, "width must be postive"); + } + if (field + width > 32) { + error("trying to access non-existent bits"); + } + int mask = (-1 >>> (32 - width)) << field; + n = (n & ~mask) | ((v << field) & mask); + return bitsToValue(n); + } + + private static LuaValue bitsToValue( int x ) { + return ( x < 0 ) ? valueOf((double) ((long) x & 0xFFFFFFFFL)) : valueOf(x); + } +} diff --git a/core/src/main/java/org/luaj/vm2/libs/CoroutineLib.java b/core/src/main/java/org/luaj/vm2/libs/CoroutineLib.java new file mode 100644 index 00000000..f70607ca --- /dev/null +++ b/core/src/main/java/org/luaj/vm2/libs/CoroutineLib.java @@ -0,0 +1,144 @@ +/******************************************************************************* + * Copyright (c) 2007-2011 LuaJ. 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.libs; + +import org.luaj.vm2.Globals; +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaThread; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; + +/** + * Subclass of {@link LibFunction} which implements the lua standard {@code coroutine} + * library. + *

+ * The coroutine library in luaj has the same behavior as the + * coroutine library in C, but is implemented using Java Threads to maintain + * the call state between invocations. Therefore it can be yielded from anywhere, + * similar to the "Coco" yield-from-anywhere patch available for C-based lua. + * However, coroutines that are yielded but never resumed to complete their execution + * may not be collected by the garbage collector. + *

+ * Typically, this library is included as part of a call to either + * {@link org.luaj.vm2.libs.jse.JsePlatform#standardGlobals()} or {@link org.luaj.vm2.libs.jme.JmePlatform#standardGlobals()} + *

 {@code
+ * Globals globals = JsePlatform.standardGlobals();
+ * System.out.println( globals.get("coroutine").get("running").call() );
+ * } 
+ *

+ * To instantiate and use it directly, + * link it into your globals table via {@link LuaValue#load(LuaValue)} using code such as: + *

 {@code
+ * Globals globals = new Globals();
+ * globals.load(new JseBaseLib());
+ * globals.load(new PackageLib());
+ * globals.load(new CoroutineLib());
+ * System.out.println( globals.get("coroutine").get("running").call() );
+ * } 
+ *

+ * @see LibFunction + * @see org.luaj.vm2.libs.jse.JsePlatform + * @see org.luaj.vm2.libs.jme.JmePlatform + * @see Lua 5.2 Coroutine Lib Reference + */ +public class CoroutineLib extends TwoArgFunction { + + static int coroutine_count = 0; + + Globals globals; + + /** Perform one-time initialization on the library by creating a table + * containing the library functions, adding that table to the supplied environment, + * adding the table to package.loaded, and returning table as the return value. + * @param modname the module name supplied if this is loaded via 'require'. + * @param env the environment to load into, which must be a Globals instance. + */ + public LuaValue call(LuaValue modname, LuaValue env) { + globals = env.checkglobals(); + LuaTable coroutine = new LuaTable(); + coroutine.set("create", new Create()); + coroutine.set("resume", new Resume()); + coroutine.set("running", new Running()); + coroutine.set("status", new Status()); + coroutine.set("yield", new Yield()); + coroutine.set("wrap", new Wrap()); + env.set("coroutine", coroutine); + if (!env.get("package").isnil()) env.get("package").get("loaded").set("coroutine", coroutine); + return coroutine; + } + + final class Create extends LibFunction { + public LuaValue call(LuaValue f) { + return new LuaThread(globals, f.checkfunction()); + } + } + + static final class Resume extends VarArgFunction { + public Varargs invoke(Varargs args) { + final LuaThread t = args.checkthread(1); + return t.resume( args.subargs(2) ); + } + } + + final class Running extends VarArgFunction { + public Varargs invoke(Varargs args) { + final LuaThread r = globals.running; + return varargsOf(r, valueOf(r.isMainThread())); + } + } + + static final class Status extends LibFunction { + public LuaValue call(LuaValue t) { + LuaThread lt = t.checkthread(); + return valueOf( lt.getStatus() ); + } + } + + private final class Yield extends VarArgFunction { + public Varargs invoke(Varargs args) { + return globals.yield( args ); + } + } + + final class Wrap extends LibFunction { + public LuaValue call(LuaValue f) { + final LuaValue func = f.checkfunction(); + final LuaThread thread = new LuaThread(globals, func); + return new wrapper(thread); + } + } + + static final class wrapper extends VarArgFunction { + final LuaThread luathread; + wrapper(LuaThread luathread) { + this.luathread = luathread; + } + public Varargs invoke(Varargs args) { + final Varargs result = luathread.resume(args); + if ( result.arg1().toboolean() ) { + return result.subargs(2); + } else { + return error( result.arg(2).tojstring() ); + } + } + } +} \ No newline at end of file diff --git a/core/src/main/java/org/luaj/vm2/libs/DebugLib.java b/core/src/main/java/org/luaj/vm2/libs/DebugLib.java new file mode 100644 index 00000000..9f07f9af --- /dev/null +++ b/core/src/main/java/org/luaj/vm2/libs/DebugLib.java @@ -0,0 +1,954 @@ +/******************************************************************************* + * Copyright (c) 2009-2011 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.libs; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.luaj.vm2.Globals; +import org.luaj.vm2.Lua; +import org.luaj.vm2.LuaBoolean; +import org.luaj.vm2.LuaClosure; +import org.luaj.vm2.LuaError; +import org.luaj.vm2.LuaFunction; +import org.luaj.vm2.LuaNil; +import org.luaj.vm2.LuaNumber; +import org.luaj.vm2.LuaString; +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaThread; +import org.luaj.vm2.LuaUserdata; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Print; +import org.luaj.vm2.Prototype; +import org.luaj.vm2.Varargs; + +/** + * Subclass of {@link LibFunction} which implements the lua standard {@code debug} + * library. + *

+ * The debug library in luaj tries to emulate the behavior of the corresponding C-based lua library. + * To do this, it must maintain a separate stack of calls to {@link LuaClosure} and {@link LibFunction} + * instances. + * Especially when lua-to-java bytecode compiling is being used + * via a {@link org.luaj.vm2.Globals.Compiler} such as {@link org.luaj.vm2.luajc.LuaJC}, + * this cannot be done in all cases. + *

+ * Typically, this library is included as part of a call to either + * {@link org.luaj.vm2.libs.jse.JsePlatform#debugGlobals()} or + * {@link org.luaj.vm2.libs.jme.JmePlatform#debugGlobals()} + *

 {@code
+ * Globals globals = JsePlatform.debugGlobals();
+ * System.out.println( globals.get("debug").get("traceback").call() );
+ * } 
+ *

+ * To instantiate and use it directly, + * link it into your globals table via {@link LuaValue#load(LuaValue)} using code such as: + *

 {@code
+ * Globals globals = new Globals();
+ * globals.load(new JseBaseLib());
+ * globals.load(new PackageLib());
+ * globals.load(new DebugLib());
+ * System.out.println( globals.get("debug").get("traceback").call() );
+ * } 
+ *

+ * This library exposes the entire state of lua code, and provides method to see and modify + * all underlying lua values within a Java VM so should not be exposed to client code + * in a shared server environment. + * + * @see LibFunction + * @see org.luaj.vm2.libs.jse.JsePlatform + * @see org.luaj.vm2.libs.jme.JmePlatform + * @see Lua 5.2 Debug Lib Reference + */ +public class DebugLib extends TwoArgFunction { + public static boolean CALLS; + public static boolean TRACE; + static { + try { CALLS = (null != System.getProperty("CALLS")); } catch (Exception e) {} + try { TRACE = (null != System.getProperty("TRACE")); } catch (Exception e) {} + } + + static final LuaString LUA = valueOf("Lua"); + private static final LuaString QMARK = valueOf("?"); + private static final LuaString CALL = valueOf("call"); + private static final LuaString LINE = valueOf("line"); + private static final LuaString COUNT = valueOf("count"); + private static final LuaString RETURN = valueOf("return"); + + static final LuaString FUNC = valueOf("func"); + static final LuaString ISTAILCALL = valueOf("istailcall"); + static final LuaString ISVARARG = valueOf("isvararg"); + static final LuaString NUPS = valueOf("nups"); + static final LuaString NPARAMS = valueOf("nparams"); + static final LuaString NAME = valueOf("name"); + static final LuaString NAMEWHAT = valueOf("namewhat"); + static final LuaString WHAT = valueOf("what"); + static final LuaString SOURCE = valueOf("source"); + static final LuaString SHORT_SRC = valueOf("short_src"); + static final LuaString LINEDEFINED = valueOf("linedefined"); + static final LuaString LASTLINEDEFINED = valueOf("lastlinedefined"); + static final LuaString CURRENTLINE = valueOf("currentline"); + static final LuaString ACTIVELINES = valueOf("activelines"); + + Globals globals; + + /** Perform one-time initialization on the library by creating a table + * containing the library functions, adding that table to the supplied environment, + * adding the table to package.loaded, and returning table as the return value. + * @param modname the module name supplied if this is loaded via 'require'. + * @param env the environment to load into, which must be a Globals instance. + */ + public LuaValue call(LuaValue modname, LuaValue env) { + globals = env.checkglobals(); + globals.debuglib = this; + LuaTable debug = new LuaTable(); + debug.set("debug", new debug()); + debug.set("gethook", new gethook()); + debug.set("getinfo", new getinfo()); + debug.set("getlocal", new getlocal()); + debug.set("getmetatable", new getmetatable()); + debug.set("getregistry", new getregistry()); + debug.set("getupvalue", new getupvalue()); + debug.set("getuservalue", new getuservalue()); + debug.set("sethook", new sethook()); + debug.set("setlocal", new setlocal()); + debug.set("setmetatable", new setmetatable()); + debug.set("setupvalue", new setupvalue()); + debug.set("setuservalue", new setuservalue()); + debug.set("traceback", new traceback()); + debug.set("upvalueid", new upvalueid()); + debug.set("upvaluejoin", new upvaluejoin()); + env.set("debug", debug); + if (!env.get("package").isnil()) env.get("package").get("loaded").set("debug", debug); + return debug; + } + + // debug.debug() + static final class debug extends ZeroArgFunction { + public LuaValue call() { + return NONE; + } + } + + // debug.gethook ([thread]) + final class gethook extends VarArgFunction { + public Varargs invoke(Varargs args) { + LuaThread t = args.narg() > 0 ? args.checkthread(1): globals.running; + LuaThread.State s = t.state; + return varargsOf( + s.hookfunc != null? s.hookfunc: NIL, + valueOf((s.hookcall?"c":"")+(s.hookline?"l":"")+(s.hookrtrn?"r":"")), + valueOf(s.hookcount)); + } + } + + // debug.getinfo ([thread,] f [, what]) + final class getinfo extends VarArgFunction { + public Varargs invoke(Varargs args) { + int a=1; + LuaThread thread = args.isthread(a)? args.checkthread(a++): globals.running; + LuaValue func = args.arg(a++); + String what = args.optjstring(a++, "flnStu"); + DebugLib.CallStack callstack = callstack(thread); + + // find the stack info + DebugLib.CallFrame frame; + if ( func.isnumber() ) { + frame = callstack.getCallFrame(func.toint()); + if (frame == null) + return NONE; + func = frame.f; + } else if ( func.isfunction() ) { + frame = callstack.findCallFrame(func); + } else { + return argerror(a-2, "function or level"); + } + + // start a table + DebugInfo ar = callstack.auxgetinfo(what, (LuaFunction) func, frame); + LuaTable info = new LuaTable(); + if (what.indexOf('S') >= 0) { + info.set(WHAT, LUA); + info.set(SOURCE, valueOf(ar.source)); + info.set(SHORT_SRC, valueOf(ar.short_src)); + info.set(LINEDEFINED, valueOf(ar.linedefined)); + info.set(LASTLINEDEFINED, valueOf(ar.lastlinedefined)); + } + if (what.indexOf('l') >= 0) { + info.set( CURRENTLINE, valueOf(ar.currentline) ); + } + if (what.indexOf('u') >= 0) { + info.set(NUPS, valueOf(ar.nups)); + info.set(NPARAMS, valueOf(ar.nparams)); + info.set(ISVARARG, ar.isvararg? ONE: ZERO); + } + if (what.indexOf('n') >= 0) { + info.set(NAME, LuaValue.valueOf(ar.name!=null? ar.name: "?")); + info.set(NAMEWHAT, LuaValue.valueOf(ar.namewhat)); + } + if (what.indexOf('t') >= 0) { + info.set(ISTAILCALL, ZERO); + } + if (what.indexOf('L') >= 0) { + LuaTable lines = new LuaTable(); + info.set(ACTIVELINES, lines); + DebugLib.CallFrame cf; + for (int l = 1; (cf=callstack.getCallFrame(l)) != null; ++l) + if (cf.f == func) + lines.insert(-1, valueOf(cf.currentline())); + } + if (what.indexOf('f') >= 0) { + if (func != null) + info.set( FUNC, func ); + } + return info; + } + } + + // debug.getlocal ([thread,] f, local) + final class getlocal extends VarArgFunction { + public Varargs invoke(Varargs args) { + int a=1; + LuaThread thread = args.isthread(a)? args.checkthread(a++): globals.running; + int level = args.checkint(a++); + int local = args.checkint(a++); + CallFrame f = callstack(thread).getCallFrame(level); + return f != null? f.getLocal(local): NONE; + } + } + + // debug.getmetatable (value) + static final class getmetatable extends LibFunction { + public LuaValue call(LuaValue v) { + LuaValue mt = v.getmetatable(); + return mt != null? mt: NIL; + } + } + + // debug.getregistry () + final class getregistry extends ZeroArgFunction { + public LuaValue call() { + return globals; + } + } + + // debug.getupvalue (f, up) + static final class getupvalue extends VarArgFunction { + public Varargs invoke(Varargs args) { + LuaValue func = args.checkfunction(1); + int up = args.checkint(2); + if ( func instanceof LuaClosure ) { + LuaClosure c = (LuaClosure) func; + LuaString name = findupvalue(c, up); + if ( name != null ) { + return varargsOf(name, c.upValues[up-1].getValue() ); + } + } + return NIL; + } + } + + // debug.getuservalue (u) + static final class getuservalue extends LibFunction { + public LuaValue call(LuaValue u) { + return u.isuserdata()? u: NIL; + } + } + + + // debug.sethook ([thread,] hook, mask [, count]) + final class sethook extends VarArgFunction { + public Varargs invoke(Varargs args) { + int a=1; + LuaThread t = args.isthread(a)? args.checkthread(a++): globals.running; + LuaValue func = args.optfunction(a++, null); + String str = args.optjstring(a++,""); + int count = args.optint(a++,0); + boolean call=false,line=false,rtrn=false; + for ( int i=0; i 0 && up <= c.upValues.length ) { + return valueOf(c.upValues[up-1].hashCode()); + } + } + return NIL; + } + } + + // debug.upvaluejoin (f1, n1, f2, n2) + static final class upvaluejoin extends VarArgFunction { + public Varargs invoke(Varargs args) { + LuaClosure f1 = args.checkclosure(1); + int n1 = args.checkint(2); + LuaClosure f2 = args.checkclosure(3); + int n2 = args.checkint(4); + if (n1 < 1 || n1 > f1.upValues.length) + argerror("index out of range"); + if (n2 < 1 || n2 > f2.upValues.length) + argerror("index out of range"); + f1.upValues[n1-1] = f2.upValues[n2-1]; + return NONE; + } + } + + public void onCall(LuaFunction f) { + LuaThread.State s = globals.running.state; + if (s.inhook) return; + callstack().onCall(f); + if (s.hookcall) callHook(s, CALL, NIL); + } + + public void onCall(LuaClosure c, Varargs varargs, LuaValue[] stack) { + LuaThread.State s = globals.running.state; + if (s.inhook) return; + callstack().onCall(c, varargs, stack); + if (s.hookcall) callHook(s, CALL, NIL); + } + + public void onInstruction(int pc, Varargs v, int top) { + LuaThread.State s = globals.running.state; + if (s.inhook) return; + callstack().onInstruction(pc, v, top); + if (s.hookfunc == null) return; + if (s.hookcount > 0) + if (++s.bytecodes % s.hookcount == 0) + callHook(s, COUNT, NIL); + if (s.hookline) { + int newline = callstack().currentline(); + if ( newline != s.lastline ) { + s.lastline = newline; + callHook(s, LINE, LuaValue.valueOf(newline)); + } + } + } + + public void onReturn() { + LuaThread.State s = globals.running.state; + if (s.inhook) return; + callstack().onReturn(); + if (s.hookrtrn) callHook(s, RETURN, NIL); + } + + public String traceback(int level) { + return callstack().traceback(level); + } + + public CallFrame getCallFrame(int level) { + return callstack().getCallFrame(level); + } + + void callHook(LuaThread.State s, LuaValue type, LuaValue arg) { + if (s.inhook || s.hookfunc == null) return; + s.inhook = true; + try { + s.hookfunc.call(type, arg); + } catch (LuaError e) { + throw e; + } catch (RuntimeException e) { + throw new LuaError(e); + } finally { + s.inhook = false; + } + } + + CallStack callstack() { + return callstack(globals.running); + } + + CallStack callstack(LuaThread t) { + if (t.callstack == null) + t.callstack = new CallStack(); + return (CallStack) t.callstack; + } + + static class DebugInfo { + String name; /* (n) */ + String namewhat; /* (n) 'global', 'local', 'field', 'method' */ + String what; /* (S) 'Lua', 'C', 'main', 'tail' */ + String source; /* (S) */ + int currentline; /* (l) */ + int linedefined; /* (S) */ + int lastlinedefined; /* (S) */ + short nups; /* (u) number of upvalues */ + short nparams;/* (u) number of parameters */ + boolean isvararg; /* (u) */ + boolean istailcall; /* (t) */ + String short_src; /* (S) */ + CallFrame cf; /* active function */ + + public void funcinfo(LuaFunction f) { + if (f.isclosure()) { + Prototype p = f.checkclosure().p; + this.source = p.source != null ? p.source.tojstring() : "=?"; + this.linedefined = p.linedefined; + this.lastlinedefined = p.lastlinedefined; + this.what = (this.linedefined == 0) ? "main" : "Lua"; + this.short_src = p.shortsource(); + } else { + this.source = "=[Java]"; + this.linedefined = -1; + this.lastlinedefined = -1; + this.what = "Java"; + this.short_src = f.name(); + } + } + } + + public static class CallStack { + final static CallFrame[] EMPTY = {}; + CallFrame[] frame = EMPTY; + int calls = 0; + Lock lock = new ReentrantLock(); + + CallStack() {} + + int currentline() { + lock.lock(); + try { + return calls > 0? frame[calls-1].currentline(): -1; + } finally { + lock.unlock(); + } + } + + private CallFrame pushcall() { + lock.lock(); + try { + if (calls >= frame.length) { + int n = Math.max(4, frame.length * 3 / 2); + CallFrame[] f = new CallFrame[n]; + System.arraycopy(frame, 0, f, 0, frame.length); + for (int i = frame.length; i < n; ++i) + f[i] = new CallFrame(); + frame = f; + for (int i = 1; i < n; ++i) + f[i].previous = f[i-1]; + } + return frame[calls++]; + } finally { + lock.unlock(); + } + } + + final void onCall(LuaFunction function) { + lock.lock(); + try { + pushcall().set(function); + } finally { + lock.unlock(); + } + } + + final void onCall(LuaClosure function, Varargs varargs, LuaValue[] stack) { + lock.lock(); + try { + pushcall().set(function, varargs, stack); + } finally { + lock.unlock(); + } + + } + + final void onReturn() { + lock.lock(); + try { + if (calls > 0) + frame[--calls].reset(); + } finally { + lock.unlock(); + } + } + + final void onInstruction(int pc, Varargs v, int top) { + lock.lock(); + try { + if (calls > 0) + frame[calls-1].instr(pc, v, top); + } finally { + lock.unlock(); + } + } + + /** + * Get the traceback starting at a specific level. + * @param level + * @return String containing the traceback. + */ + String traceback(int level) { + lock.lock(); + try { + StringBuffer sb = new StringBuffer(); + sb.append( "stack traceback:" ); + for (DebugLib.CallFrame c; (c = getCallFrame(level++)) != null; ) { + sb.append("\n\t"); + sb.append( c.shortsource() ); + sb.append( ':' ); + if (c.currentline() > 0) + sb.append( c.currentline()+":" ); + sb.append( " in " ); + DebugInfo ar = auxgetinfo("n", c.f, c); + if (c.linedefined() == 0) + sb.append("main chunk"); + else if ( ar.name != null ) { + sb.append( "function '" ); + sb.append( ar.name ); + sb.append( '\'' ); + } else { + sb.append( "function <" ); + sb.append( c.shortsource() ); + sb.append( ':' ); + sb.append( c.linedefined() ); + sb.append( '>' ); + } + } + sb.append("\n\t[Java]: in ?"); + return sb.toString(); + } finally { + lock.unlock(); + } + + } + + DebugLib.CallFrame getCallFrame(int level) { + lock.lock(); + try { + if (level < 1 || level > calls) + return null; + return frame[calls-level]; + } finally { + lock.unlock(); + } + } + + DebugLib.CallFrame findCallFrame(LuaValue func) { + lock.lock(); + try { + for (int i = 1; i <= calls; ++i) + if (frame[calls-i].f == func) + return frame[i]; + return null; + } finally { + lock.unlock(); + } + } + + + DebugInfo auxgetinfo(String what, LuaFunction f, CallFrame ci) { + lock.lock(); + try { + DebugInfo ar = new DebugInfo(); + for (int i = 0, n = what.length(); i < n; ++i) { + switch (what.charAt(i)) { + case 'S': + ar.funcinfo(f); + break; + case 'l': + ar.currentline = ci != null && ci.f.isclosure()? ci.currentline(): -1; + break; + case 'u': + if (f != null && f.isclosure()) { + Prototype p = f.checkclosure().p; + ar.nups = (short) p.upvalues.length; + ar.nparams = (short) p.numparams; + ar.isvararg = p.is_vararg != 0; + } else { + ar.nups = 0; + ar.isvararg = true; + ar.nparams = 0; + } + break; + case 't': + ar.istailcall = false; + break; + case 'n': { + /* calling function is a known Lua function? */ + if (ci != null && ci.previous != null) { + if (ci.previous.f.isclosure()) { + NameWhat nw = getfuncname(ci.previous); + if (nw != null) { + ar.name = nw.name; + ar.namewhat = nw.namewhat; + } + } + } + if (ar.namewhat == null) { + ar.namewhat = ""; /* not found */ + ar.name = null; + } + break; + } + case 'L': + case 'f': + break; + default: + // TODO: return bad status. + break; + } + } + return ar; + } finally { + lock.unlock(); + } + } + } + + public static class CallFrame { + LuaFunction f; + int pc; + int top; + Varargs v; + LuaValue[] stack; + CallFrame previous; + void set(LuaClosure function, Varargs varargs, LuaValue[] stack) { + this.f = function; + this.v = varargs; + this.stack = stack; + } + public String shortsource() { + return f.isclosure()? f.checkclosure().p.shortsource(): "[Java]"; + } + void set(LuaFunction function) { + this.f = function; + } + void reset() { + this.f = null; + this.v = null; + this.stack = null; + } + void instr(int pc, Varargs v, int top) { + this.pc = pc; + this.v = v; + this.top = top; + if (TRACE) + Print.printState(f.checkclosure(), pc, stack, top, v); + } + Varargs getLocal(int i) { + LuaString name = getlocalname(i); + if ( i >= 1 && i <= stack.length && stack[i-1] != null ) + return varargsOf( name == null ? NIL : name, stack[i-1] ); + else + return NIL; + } + Varargs setLocal(int i, LuaValue value) { + LuaString name = getlocalname(i); + if ( i >= 1 && i <= stack.length && stack[i-1] != null ) { + stack[i-1] = value; + return name == null ? NIL : name; + } else { + return NIL; + } + } + public int currentline() { + if ( !f.isclosure() ) return -1; + int[] li = f.checkclosure().p.lineinfo; + return li==null || pc<0 || pc>=li.length? -1: li[pc]; + } + String sourceline() { + if ( !f.isclosure() ) return f.tojstring(); + return f.checkclosure().p.shortsource() + ":" + currentline(); + } + int linedefined() { + return f.isclosure()? f.checkclosure().p.linedefined: -1; + } + LuaString getlocalname(int index) { + if ( !f.isclosure() ) return null; + return f.checkclosure().p.getlocalname(index, pc); + } + } + + static LuaString findupvalue(LuaClosure c, int up) { + if ( c.upValues != null && up > 0 && up <= c.upValues.length ) { + if ( c.p.upvalues != null && up <= c.p.upvalues.length ) + return c.p.upvalues[up-1].name; + else + return LuaString.valueOf( "."+up ); + } + return null; + } + + static void lua_assert(boolean x) { + if (!x) throw new RuntimeException("lua_assert failed"); + } + + static class NameWhat { + final String name; + final String namewhat; + NameWhat(String name, String namewhat) { + this.name = name; + this.namewhat = namewhat; + } + } + + // Return the name info if found, or null if no useful information could be found. + static NameWhat getfuncname(DebugLib.CallFrame frame) { + if (!frame.f.isclosure()) + return new NameWhat(frame.f.classnamestub(), "Java"); + Prototype p = frame.f.checkclosure().p; + int pc = frame.pc; + int i = p.code[pc]; /* calling instruction */ + LuaString tm; + switch (Lua.GET_OPCODE(i)) { + case Lua.OP_CALL: + case Lua.OP_TAILCALL: /* get function name */ + return getobjname(p, pc, Lua.GETARG_A(i)); + case Lua.OP_TFORCALL: /* for iterator */ + return new NameWhat("(for iterator)", "(for iterator"); + /* all other instructions can call only through metamethods */ + case Lua.OP_SELF: + case Lua.OP_GETTABUP: + case Lua.OP_GETTABLE: tm = LuaValue.INDEX; break; + case Lua.OP_SETTABUP: + case Lua.OP_SETTABLE: tm = LuaValue.NEWINDEX; break; + case Lua.OP_EQ: tm = LuaValue.EQ; break; + case Lua.OP_ADD: tm = LuaValue.ADD; break; + case Lua.OP_SUB: tm = LuaValue.SUB; break; + case Lua.OP_MUL: tm = LuaValue.MUL; break; + case Lua.OP_DIV: tm = LuaValue.DIV; break; + case Lua.OP_MOD: tm = LuaValue.MOD; break; + case Lua.OP_POW: tm = LuaValue.POW; break; + case Lua.OP_UNM: tm = LuaValue.UNM; break; + case Lua.OP_LEN: tm = LuaValue.LEN; break; + case Lua.OP_LT: tm = LuaValue.LT; break; + case Lua.OP_LE: tm = LuaValue.LE; break; + case Lua.OP_CONCAT: tm = LuaValue.CONCAT; break; + default: + return null; /* else no useful name can be found */ + } + return new NameWhat( tm.tojstring(), "metamethod" ); + } + + // return NameWhat if found, null if not + public static NameWhat getobjname(Prototype p, int lastpc, int reg) { + int pc = lastpc; // currentpc(L, ci); + LuaString name = p.getlocalname(reg + 1, pc); + if (name != null) /* is a local? */ + return new NameWhat( name.tojstring(), "local" ); + + /* else try symbolic execution */ + pc = findsetreg(p, lastpc, reg); + if (pc != -1) { /* could find instruction? */ + int i = p.code[pc]; + switch (Lua.GET_OPCODE(i)) { + case Lua.OP_MOVE: { + int a = Lua.GETARG_A(i); + int b = Lua.GETARG_B(i); /* move from `b' to `a' */ + if (b < a) + return getobjname(p, pc, b); /* get name for `b' */ + break; + } + case Lua.OP_GETTABUP: + case Lua.OP_GETTABLE: { + int k = Lua.GETARG_C(i); /* key index */ + int t = Lua.GETARG_B(i); /* table index */ + LuaString vn = (Lua.GET_OPCODE(i) == Lua.OP_GETTABLE) /* name of indexed variable */ + ? p.getlocalname(t + 1, pc) + : (t < p.upvalues.length ? p.upvalues[t].name : QMARK); + String jname = kname(p, pc, k); + return new NameWhat( jname, vn != null && vn.eq_b(ENV)? "global": "field" ); + } + case Lua.OP_GETUPVAL: { + int u = Lua.GETARG_B(i); /* upvalue index */ + name = u < p.upvalues.length ? p.upvalues[u].name : QMARK; + return name == null ? null : new NameWhat( name.tojstring(), "upvalue" ); + } + case Lua.OP_LOADK: + case Lua.OP_LOADKX: { + int b = (Lua.GET_OPCODE(i) == Lua.OP_LOADK) ? Lua.GETARG_Bx(i) + : Lua.GETARG_Ax(p.code[pc + 1]); + if (p.k[b].isstring()) { + name = p.k[b].strvalue(); + return new NameWhat( name.tojstring(), "constant" ); + } + break; + } + case Lua.OP_SELF: { + int k = Lua.GETARG_C(i); /* key index */ + String jname = kname(p, pc, k); + return new NameWhat( jname, "method" ); + } + default: + break; + } + } + return null; /* no useful name found */ + } + + static String kname(Prototype p, int pc, int c) { + if (Lua.ISK(c)) { /* is 'c' a constant? */ + LuaValue k = p.k[Lua.INDEXK(c)]; + if (k.isstring()) { /* literal constant? */ + return k.tojstring(); /* it is its own name */ + } /* else no reasonable name found */ + } else { /* 'c' is a register */ + NameWhat what = getobjname(p, pc, c); /* search for 'c' */ + if (what != null && "constant".equals(what.namewhat)) { /* found a constant name? */ + return what.name; /* 'name' already filled */ + } + /* else no reasonable name found */ + } + return "?"; /* no reasonable name found */ + } + + /* + ** try to find last instruction before 'lastpc' that modified register 'reg' + */ + static int findsetreg (Prototype p, int lastpc, int reg) { + int pc; + int setreg = -1; /* keep last instruction that changed 'reg' */ + for (pc = 0; pc < lastpc; pc++) { + int i = p.code[pc]; + int op = Lua.GET_OPCODE(i); + int a = Lua.GETARG_A(i); + switch (op) { + case Lua.OP_LOADNIL: { + int b = Lua.GETARG_B(i); + if (a <= reg && reg <= a + b) /* set registers from 'a' to 'a+b' */ + setreg = pc; + break; + } + case Lua.OP_TFORCALL: { + if (reg >= a + 2) setreg = pc; /* affect all regs above its base */ + break; + } + case Lua.OP_CALL: + case Lua.OP_TAILCALL: { + if (reg >= a) setreg = pc; /* affect all registers above base */ + break; + } + case Lua.OP_JMP: { + int b = Lua.GETARG_sBx(i); + int dest = pc + 1 + b; + /* jump is forward and do not skip `lastpc'? */ + if (pc < dest && dest <= lastpc) + pc += b; /* do the jump */ + break; + } + case Lua.OP_TEST: { + if (reg == a) setreg = pc; /* jumped code can change 'a' */ + break; + } + case Lua.OP_SETLIST: { // Lua.testAMode(Lua.OP_SETLIST) == false + if ( ((i>>14)&0x1ff) == 0 ) pc++; // if c == 0 then c stored in next op -> skip + break; + } + default: + if (Lua.testAMode(op) && reg == a) /* any instruction that set A */ + setreg = pc; + break; + } + } + return setreg; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/luaj/vm2/libs/IoLib.java b/core/src/main/java/org/luaj/vm2/libs/IoLib.java new file mode 100644 index 00000000..75bdb2d6 --- /dev/null +++ b/core/src/main/java/org/luaj/vm2/libs/IoLib.java @@ -0,0 +1,688 @@ +/******************************************************************************* + * Copyright (c) 2009-2011 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.libs; + +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.IOException; + +import org.luaj.vm2.Globals; +import org.luaj.vm2.LuaString; +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; + +/** + * Abstract base class extending {@link LibFunction} which implements the + * core of the lua standard {@code io} library. + *

+ * It contains the implementation of the io library support that is common to + * the JSE and JME platforms. + * In practice on of the concrete IOLib subclasses is chosen: + * {@link org.luaj.vm2.libs.jse.JseIoLib} for the JSE platform, and + * {@link org.luaj.vm2.libs.jme.JmeIoLib} for the JME platform. + *

+ * The JSE implementation conforms almost completely to the C-based lua library, + * while the JME implementation follows closely except in the area of random-access files, + * which are difficult to support properly on JME. + *

+ * Typically, this library is included as part of a call to either + * {@link org.luaj.vm2.libs.jse.JsePlatform#standardGlobals()} or {@link org.luaj.vm2.libs.jme.JmePlatform#standardGlobals()} + *

 {@code
+ * Globals globals = JsePlatform.standardGlobals();
+ * globals.get("io").get("write").call(LuaValue.valueOf("hello, world\n"));
+ * } 
+ * In this example the platform-specific {@link org.luaj.vm2.libs.jse.JseIoLib} library will be loaded, which will include + * the base functionality provided by this class, whereas the {@link org.luaj.vm2.libs.jse.JsePlatform} would load the + * {@link org.luaj.vm2.libs.jse.JseIoLib}. + *

+ * To instantiate and use it directly, + * link it into your globals table via {@link LuaValue#load(LuaValue)} using code such as: + *

 {@code
+ * Globals globals = new Globals();
+ * globals.load(new JseBaseLib());
+ * globals.load(new PackageLib());
+ * globals.load(new OsLib());
+ * globals.get("io").get("write").call(LuaValue.valueOf("hello, world\n"));
+ * } 
+ *

+ * This has been implemented to match as closely as possible the behavior in the corresponding library in C. + * @see LibFunction + * @see org.luaj.vm2.libs.jse.JsePlatform + * @see org.luaj.vm2.libs.jme.JmePlatform + * @see org.luaj.vm2.libs.jse.JseIoLib + * @see org.luaj.vm2.libs.jme.JmeIoLib + * @see http://www.lua.org/manual/5.1/manual.html#5.7 + */ +abstract +public class IoLib extends TwoArgFunction { + + abstract + protected class File extends LuaValue{ + abstract public void write( LuaString string ) throws IOException; + abstract public void flush() throws IOException; + abstract public boolean isstdfile(); + abstract public void close() throws IOException; + abstract public boolean isclosed(); + // returns new position + abstract public int seek(String option, int bytecount) throws IOException; + abstract public void setvbuf(String mode, int size); + // get length remaining to read + abstract public int remaining() throws IOException; + // peek ahead one character + abstract public int peek() throws IOException, EOFException; + // return char if read, -1 if eof, throw IOException on other exception + abstract public int read() throws IOException, EOFException; + // return number of bytes read if positive, false if eof, throw IOException on other exception + abstract public int read(byte[] bytes, int offset, int length) throws IOException; + + public boolean eof() throws IOException { + try { + return peek() < 0; + } catch (EOFException e) { return true; } + } + + // delegate method access to file methods table + public LuaValue get( LuaValue key ) { + return filemethods.get(key); + } + + // essentially a userdata instance + public int type() { + return LuaValue.TUSERDATA; + } + public String typename() { + return "userdata"; + } + + // displays as "file" type + public String tojstring() { + return "file: " + Integer.toHexString(hashCode()); + } + + public void finalize() throws Throwable { + try { + if (!isclosed()) { + try { + close(); + } catch (IOException ignore) {} + } + } finally { + super.finalize(); + } + } + } + + /** Enumerated value representing stdin */ + protected static final int FTYPE_STDIN = 0; + /** Enumerated value representing stdout */ + protected static final int FTYPE_STDOUT = 1; + /** Enumerated value representing stderr */ + protected static final int FTYPE_STDERR = 2; + /** Enumerated value representing a file type for a named file */ + protected static final int FTYPE_NAMED = 3; + + /** + * Wrap the standard input. + * @return File + * @throws IOException + */ + abstract protected File wrapStdin() throws IOException; + + /** + * Wrap the standard output. + * @return File + * @throws IOException + */ + abstract protected File wrapStdout() throws IOException; + + /** + * Wrap the standard error output. + * @return File + * @throws IOException + */ + abstract protected File wrapStderr() throws IOException; + + /** + * Open a file in a particular mode. + * @param filename + * @param readMode true if opening in read mode + * @param appendMode true if opening in append mode + * @param updateMode true if opening in update mode + * @param binaryMode true if opening in binary mode + * @return File object if successful + * @throws IOException if could not be opened + */ + abstract protected File openFile( String filename, boolean readMode, boolean appendMode, boolean updateMode, boolean binaryMode ) throws IOException; + + /** + * Open a temporary file. + * @return File object if successful + * @throws IOException if could not be opened + */ + abstract protected File tmpFile() throws IOException; + + /** + * Start a new process and return a file for input or output + * @param prog the program to execute + * @param mode "r" to read, "w" to write + * @return File to read to or write from + * @throws IOException if an i/o exception occurs + */ + abstract protected File openProgram(String prog, String mode) throws IOException; + + private File infile = null; + private File outfile = null; + private File errfile = null; + + private static final LuaValue STDIN = valueOf("stdin"); + private static final LuaValue STDOUT = valueOf("stdout"); + private static final LuaValue STDERR = valueOf("stderr"); + private static final LuaValue FILE = valueOf("file"); + private static final LuaValue CLOSED_FILE = valueOf("closed file"); + + private static final int IO_CLOSE = 0; + private static final int IO_FLUSH = 1; + private static final int IO_INPUT = 2; + private static final int IO_LINES = 3; + private static final int IO_OPEN = 4; + private static final int IO_OUTPUT = 5; + private static final int IO_POPEN = 6; + private static final int IO_READ = 7; + private static final int IO_TMPFILE = 8; + private static final int IO_TYPE = 9; + private static final int IO_WRITE = 10; + + private static final int FILE_CLOSE = 11; + private static final int FILE_FLUSH = 12; + private static final int FILE_LINES = 13; + private static final int FILE_READ = 14; + private static final int FILE_SEEK = 15; + private static final int FILE_SETVBUF = 16; + private static final int FILE_WRITE = 17; + + private static final int IO_INDEX = 18; + private static final int LINES_ITER = 19; + + public static final String[] IO_NAMES = { + "close", + "flush", + "input", + "lines", + "open", + "output", + "popen", + "read", + "tmpfile", + "type", + "write", + }; + + public static final String[] FILE_NAMES = { + "close", + "flush", + "lines", + "read", + "seek", + "setvbuf", + "write", + }; + + LuaTable filemethods; + + protected Globals globals; + + public LuaValue call(LuaValue modname, LuaValue env) { + globals = env.checkglobals(); + + // io lib functions + LuaTable t = new LuaTable(); + bind(t, IoLibV.class, IO_NAMES ); + + // create file methods table + filemethods = new LuaTable(); + bind(filemethods, IoLibV.class, FILE_NAMES, FILE_CLOSE ); + + // set up file metatable + LuaTable mt = new LuaTable(); + bind(mt, IoLibV.class, new String[] { "__index" }, IO_INDEX ); + t.setmetatable( mt ); + + // all functions link to library instance + setLibInstance( t ); + setLibInstance( filemethods ); + setLibInstance( mt ); + + // return the table + env.set("io", t); + if (!env.get("package").isnil()) env.get("package").get("loaded").set("io", t); + return t; + } + + private void setLibInstance(LuaTable t) { + LuaValue[] k = t.keys(); + for ( int i=0, n=k.length; i bool + public Varargs _io_flush() throws IOException { + checkopen(output()); + outfile.flush(); + return LuaValue.TRUE; + } + + // io.tmpfile() -> file + public Varargs _io_tmpfile() throws IOException { + return tmpFile(); + } + + // io.close([file]) -> void + public Varargs _io_close(LuaValue file) throws IOException { + File f = file.isnil()? output(): checkfile(file); + checkopen(f); + return ioclose(f); + } + + // io.input([file]) -> file + public Varargs _io_input(LuaValue file) { + infile = file.isnil()? input(): + file.isstring()? ioopenfile(FTYPE_NAMED, file.checkjstring(),"r"): + checkfile(file); + return infile; + } + + // io.output(filename) -> file + public Varargs _io_output(LuaValue filename) { + outfile = filename.isnil()? output(): + filename.isstring()? ioopenfile(FTYPE_NAMED, filename.checkjstring(),"w"): + checkfile(filename); + return outfile; + } + + // io.type(obj) -> "file" | "closed file" | nil + public Varargs _io_type(LuaValue obj) { + File f = optfile(obj); + return f!=null? + f.isclosed()? CLOSED_FILE: FILE: + NIL; + } + + // io.popen(prog, [mode]) -> file + public Varargs _io_popen(String prog, String mode) throws IOException { + if (!"r".equals(mode) && !"w".equals(mode)) argerror(2, "invalid value: '" + mode + "'; must be one of 'r' or 'w'"); + return openProgram(prog, mode); + } + + // io.open(filename, [mode]) -> file | nil,err + public Varargs _io_open(String filename, String mode) throws IOException { + return rawopenfile(FTYPE_NAMED, filename, mode); + } + + // io.lines(filename, ...) -> iterator + public Varargs _io_lines(Varargs args) { + String filename = args.optjstring(1, null); + File infile = filename==null? input(): ioopenfile(FTYPE_NAMED, filename,"r"); + checkopen(infile); + return lines(infile, filename != null, args.subargs(2)); + } + + // io.read(...) -> (...) + public Varargs _io_read(Varargs args) throws IOException { + checkopen(input()); + return ioread(infile,args); + } + + // io.write(...) -> void + public Varargs _io_write(Varargs args) throws IOException { + checkopen(output()); + return iowrite(outfile,args); + } + + // file:close() -> void + public Varargs _file_close(LuaValue file) throws IOException { + return ioclose(checkfile(file)); + } + + // file:flush() -> void + public Varargs _file_flush(LuaValue file) throws IOException { + checkfile(file).flush(); + return LuaValue.TRUE; + } + + // file:setvbuf(mode,[size]) -> void + public Varargs _file_setvbuf(LuaValue file, String mode, int size) { + if ("no".equals(mode)) { + } else if ("full".equals(mode)) { + } else if ("line".equals(mode)) { + } else { + argerror(1, "invalid value: '" + mode + "'; must be one of 'no', 'full' or 'line'"); + } + checkfile(file).setvbuf(mode,size); + return LuaValue.TRUE; + } + + // file:lines(...) -> iterator + public Varargs _file_lines(Varargs args) { + return lines(checkfile(args.arg1()), false, args.subargs(2)); + } + + // file:read(...) -> (...) + public Varargs _file_read(LuaValue file, Varargs subargs) throws IOException { + return ioread(checkfile(file),subargs); + } + + // file:seek([whence][,offset]) -> pos | nil,error + public Varargs _file_seek(LuaValue file, String whence, int offset) throws IOException { + if ("set".equals(whence)) { + } else if ("end".equals(whence)) { + } else if ("cur".equals(whence)) { + } else { + argerror(1, "invalid value: '" + whence + "'; must be one of 'set', 'cur' or 'end'"); + } + return valueOf( checkfile(file).seek(whence,offset) ); + } + + // file:write(...) -> void + public Varargs _file_write(LuaValue file, Varargs subargs) throws IOException { + return iowrite(checkfile(file),subargs); + } + + // __index, returns a field + public Varargs _io_index(LuaValue v) { + return v.equals(STDOUT)?output(): + v.equals(STDIN)? input(): + v.equals(STDERR)? errput(): NIL; + } + + // lines iterator(s,var) -> var' + public Varargs _lines_iter(LuaValue file, boolean toclose, Varargs args) throws IOException { + File f = optfile(file); + if ( f == null ) argerror(1, "not a file: " + file); + if ( f.isclosed() ) error("file is already closed"); + Varargs ret = ioread(f, args); + if (toclose && ret.isnil(1) && f.eof()) f.close(); + return ret; + } + + private File output() { + return outfile!=null? outfile: (outfile=ioopenfile(FTYPE_STDOUT,"-","w")); + } + + private File errput() { + return errfile!=null? errfile: (errfile=ioopenfile(FTYPE_STDERR,"-","w")); + } + + private File ioopenfile(int filetype, String filename, String mode) { + try { + return rawopenfile(filetype, filename, mode); + } catch ( Exception e ) { + error("io error: "+e.getMessage()); + return null; + } + } + + private static Varargs ioclose(File f) throws IOException { + if ( f.isstdfile() ) + return errorresult("cannot close standard file"); + else { + f.close(); + return successresult(); + } + } + + private static Varargs successresult() { + return LuaValue.TRUE; + } + + static Varargs errorresult(Exception ioe) { + String s = ioe.getMessage(); + return errorresult("io error: "+(s!=null? s: ioe.toString())); + } + + private static Varargs errorresult(String errortext) { + return varargsOf(NIL, valueOf(errortext)); + } + + private Varargs lines(final File f, boolean toclose, Varargs args) { + try { + return new IoLibV(f,"lnext",LINES_ITER,this,toclose,args); + } catch ( Exception e ) { + return error("lines: "+e); + } + } + + private static Varargs iowrite(File f, Varargs args) throws IOException { + for ( int i=1, n=args.narg(); i<=n; i++ ) + f.write( args.checkstring(i) ); + return f; + } + + private Varargs ioread(File f, Varargs args) throws IOException { + int i,n=args.narg(); + if (n == 0) return freadline(f,false); + LuaValue[] v = new LuaValue[n]; + LuaValue ai,vi; + LuaString fmt; + for ( i=0; i= 2 && fmt.m_bytes[fmt.m_offset] == '*' ) { + switch ( fmt.m_bytes[fmt.m_offset+1] ) { + case 'n': vi = freadnumber(f); break item; + case 'l': vi = freadline(f,false); break item; + case 'L': vi = freadline(f,true); break item; + case 'a': vi = freadall(f); break item; + } + } + default: + return argerror( i+1, "(invalid format)" ); + } + if ( (v[i++] = vi).isnil() ) + break; + } + return i==0? NIL: varargsOf(v, 0, i); + } + + private static File checkfile(LuaValue val) { + File f = optfile(val); + if ( f == null ) + argerror(1,"file"); + checkopen( f ); + return f; + } + + private static File optfile(LuaValue val) { + return (val instanceof File)? (File) val: null; + } + + private static File checkopen(File file) { + if ( file.isclosed() ) + error("attempt to use a closed file"); + return file; + } + + private File rawopenfile(int filetype, String filename, String mode) throws IOException { + int len = mode.length(); + for (int i = 0; i < len; i++) { // [rwa][+]?b* + char ch = mode.charAt(i); + if (i == 0 && "rwa".indexOf(ch) >= 0) continue; + if (i == 1 && ch == '+') continue; + if (i >= 1 && ch == 'b') continue; + len = -1; + break; + } + if (len <= 0) argerror(2, "invalid mode: '" + mode + "'"); + + switch (filetype) { + case FTYPE_STDIN: return wrapStdin(); + case FTYPE_STDOUT: return wrapStdout(); + case FTYPE_STDERR: return wrapStderr(); + } + boolean isreadmode = mode.startsWith("r"); + boolean isappend = mode.startsWith("a"); + boolean isupdate = mode.indexOf('+') > 0; + boolean isbinary = mode.endsWith("b"); + return openFile( filename, isreadmode, isappend, isupdate, isbinary ); + } + + + // ------------- file reading utilitied ------------------ + + public static LuaValue freadbytes(File f, int count) throws IOException { + if (count == 0) return f.eof() ? NIL : EMPTYSTRING; + byte[] b = new byte[count]; + int r; + if ( ( r = f.read(b,0,b.length) ) < 0 ) + return NIL; + return LuaString.valueUsing(b, 0, r); + } + public static LuaValue freaduntil(File f,boolean lineonly,boolean withend) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int c; + try { + if ( lineonly ) { + loop: while ( (c = f.read()) >= 0 ) { + switch ( c ) { + case '\r': if (withend) baos.write(c); break; + case '\n': if (withend) baos.write(c); break loop; + default: baos.write(c); break; + } + } + } else { + while ( (c = f.read()) >= 0 ) + baos.write(c); + } + } catch ( EOFException e ) { + c = -1; + } + return ( c < 0 && baos.size() == 0 )? + (LuaValue) NIL: + (LuaValue) LuaString.valueUsing(baos.toByteArray()); + } + public static LuaValue freadline(File f,boolean withend) throws IOException { + return freaduntil(f,true,withend); + } + public static LuaValue freadall(File f) throws IOException { + int n = f.remaining(); + if ( n >= 0 ) { + return n == 0 ? EMPTYSTRING : freadbytes(f, n); + } else { + return freaduntil(f,false,false); + } + } + public static LuaValue freadnumber(File f) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + freadchars(f," \t\r\n",null); + freadchars(f,"-+",baos); + //freadchars(f,"0",baos); + //freadchars(f,"xX",baos); + freadchars(f,"0123456789",baos); + freadchars(f,".",baos); + freadchars(f,"0123456789",baos); + //freadchars(f,"eEfFgG",baos); + // freadchars(f,"+-",baos); + //freadchars(f,"0123456789",baos); + String s = baos.toString(); + return s.length()>0? valueOf( Double.parseDouble(s) ): NIL; + } + private static void freadchars(File f, String chars, ByteArrayOutputStream baos) throws IOException { + int c; + while ( true ) { + c = f.peek(); + if ( chars.indexOf(c) < 0 ) { + return; + } + f.read(); + if ( baos != null ) + baos.write( c ); + } + } + + + +} \ No newline at end of file diff --git a/core/src/main/java/org/luaj/vm2/libs/LibFunction.java b/core/src/main/java/org/luaj/vm2/libs/LibFunction.java new file mode 100644 index 00000000..4c70f214 --- /dev/null +++ b/core/src/main/java/org/luaj/vm2/libs/LibFunction.java @@ -0,0 +1,222 @@ +/******************************************************************************* +* Copyright (c) 2009-2011 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.libs; + +import org.luaj.vm2.LuaError; +import org.luaj.vm2.LuaFunction; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; + +/** + * Subclass of {@link LuaFunction} common to Java functions exposed to lua. + *

+ * To provide for common implementations in JME and JSE, + * library functions are typically grouped on one or more library classes + * and an opcode per library function is defined and used to key the switch + * to the correct function within the library. + *

+ * Since lua functions can be called with too few or too many arguments, + * and there are overloaded {@link LuaValue#call()} functions with varying + * number of arguments, a Java function exposed in lua needs to handle the + * argument fixup when a function is called with a number of arguments + * differs from that expected. + *

+ * To simplify the creation of library functions, + * there are 5 direct subclasses to handle common cases based on number of + * argument values and number of return return values. + *

+ *

+ * To be a Java library that can be loaded via {@code require}, it should have + * a public constructor that returns a {@link LuaValue} that, when executed, + * initializes the library. + *

+ * For example, the following code will implement a library called "hyperbolic" + * with two functions, "sinh", and "cosh": +

 {@code
+ * import org.luaj.vm2.LuaValue;
+ * import org.luaj.vm2.lib.*;
+ * 
+ * public class hyperbolic extends TwoArgFunction {
+ *
+ *	public hyperbolic() {}
+ *
+ *	public LuaValue call(LuaValue modname, LuaValue env) {
+ *		LuaValue library = tableOf();
+ *		library.set( "sinh", new sinh() );
+ *		library.set( "cosh", new cosh() );
+ *		env.set( "hyperbolic", library );
+ *		return library;
+ *	}
+ *
+ *	static class sinh extends OneArgFunction {
+ *		public LuaValue call(LuaValue x) {
+ *			return LuaValue.valueOf(Math.sinh(x.checkdouble()));
+ *		}
+ *	}
+ *	
+ *	static class cosh extends OneArgFunction {
+ *		public LuaValue call(LuaValue x) {
+ *			return LuaValue.valueOf(Math.cosh(x.checkdouble()));
+ *		}
+ *	}
+ *}
+ *}
+ * The default constructor is used to instantiate the library + * in response to {@code require 'hyperbolic'} statement, + * provided it is on Java"s class path. + * This instance is then invoked with 2 arguments: the name supplied to require(), + * and the environment for this function. The library may ignore these, or use + * them to leave side effects in the global environment, for example. + * In the previous example, two functions are created, 'sinh', and 'cosh', and placed + * into a global table called 'hyperbolic' using the supplied 'env' argument. + *

+ * To test it, a script such as this can be used: + *

 {@code
+ * local t = require('hyperbolic')
+ * print( 't', t )
+ * print( 'hyperbolic', hyperbolic )
+ * for k,v in pairs(t) do
+ * 	print( 'k,v', k,v )
+ * end
+ * print( 'sinh(.5)', hyperbolic.sinh(.5) )
+ * print( 'cosh(.5)', hyperbolic.cosh(.5) )
+ * }
+ *

+ * It should produce something like: + *

 {@code
+ * t	table: 3dbbd23f
+ * hyperbolic	table: 3dbbd23f
+ * k,v	cosh	function: 3dbbd128
+ * k,v	sinh	function: 3dbbd242
+ * sinh(.5)	0.5210953
+ * cosh(.5)	1.127626
+ * }
+ *

+ * See the source code in any of the library functions + * such as {@link BaseLib} or {@link TableLib} for other examples. + */ +abstract public class LibFunction extends LuaFunction { + + /** User-defined opcode to differentiate between instances of the library function class. + *

+ * Subclass will typicall switch on this value to provide the specific behavior for each function. + */ + protected int opcode; + + /** The common name for this function, useful for debugging. + *

+ * Binding functions initialize this to the name to which it is bound. + */ + protected String name; + + /** Default constructor for use by subclasses */ + protected LibFunction() { + } + + public String tojstring() { + return name != null ? "function: " + name : super.tojstring(); + } + + /** + * Bind a set of library functions. + *

+ * An array of names is provided, and the first name is bound + * with opcode = 0, second with 1, etc. + * @param env The environment to apply to each bound function + * @param factory the Class to instantiate for each bound function + * @param names array of String names, one for each function. + * @see #bind(LuaValue, Class, String[], int) + */ + protected void bind(LuaValue env, Class factory, String[] names ) { + bind( env, factory, names, 0 ); + } + + /** + * Bind a set of library functions, with an offset + *

+ * An array of names is provided, and the first name is bound + * with opcode = {@code firstopcode}, second with {@code firstopcode+1}, etc. + * @param env The environment to apply to each bound function + * @param factory the Class to instantiate for each bound function + * @param names array of String names, one for each function. + * @param firstopcode the first opcode to use + * @see #bind(LuaValue, Class, String[]) + */ + protected void bind(LuaValue env, Class factory, String[] names, int firstopcode ) { + try { + for ( int i=0, n=names.length; i + * It contains only the math library support that is possible on JME. + * For a more complete implementation based on math functions specific to JSE + * use {@link org.luaj.vm2.libs.jse.JseMathLib}. + * In Particular the following math functions are not implemented by this library: + *

+ *

+ * The implementations of {@code exp()} and {@code pow()} are constructed by + * hand for JME, so will be slower and less accurate than when executed on the JSE platform. + *

+ * Typically, this library is included as part of a call to either + * {@link org.luaj.vm2.libs.jse.JsePlatform#standardGlobals()} or + * {@link org.luaj.vm2.libs.jme.JmePlatform#standardGlobals()} + *

 {@code
+ * Globals globals = JsePlatform.standardGlobals();
+ * System.out.println( globals.get("math").get("sqrt").call( LuaValue.valueOf(2) ) );
+ * } 
+ * When using {@link org.luaj.vm2.libs.jse.JsePlatform} as in this example, + * the subclass {@link org.luaj.vm2.libs.jse.JseMathLib} will + * be included, which also includes this base functionality. + *

+ * To instantiate and use it directly, + * link it into your globals table via {@link LuaValue#load(LuaValue)} using code such as: + *

 {@code
+ * Globals globals = new Globals();
+ * globals.load(new JseBaseLib());
+ * globals.load(new PackageLib());
+ * globals.load(new MathLib());
+ * System.out.println( globals.get("math").get("sqrt").call( LuaValue.valueOf(2) ) );
+ * } 
+ * Doing so will ensure the library is properly initialized + * and loaded into the globals table. + *

+ * This has been implemented to match as closely as possible the behavior in the corresponding library in C. + * @see LibFunction + * @see org.luaj.vm2.libs.jse.JsePlatform + * @see org.luaj.vm2.libs.jme.JmePlatform + * @see org.luaj.vm2.libs.jse.JseMathLib + * @see Lua 5.2 Math Lib Reference + */ +public class MathLib extends TwoArgFunction { + + /** Pointer to the latest MathLib instance, used only to dispatch + * math.exp to tha correct platform math library. + */ + public static MathLib MATHLIB = null; + + /** Construct a MathLib, which can be initialized by calling it with a + * modname string, and a global environment table as arguments using + * {@link #call(LuaValue, LuaValue)}. */ + public MathLib() { + MATHLIB = this; + } + + /** Perform one-time initialization on the library by creating a table + * containing the library functions, adding that table to the supplied environment, + * adding the table to package.loaded, and returning table as the return value. + * @param modname the module name supplied if this is loaded via 'require'. + * @param env the environment to load into, typically a Globals instance. + */ + public LuaValue call(LuaValue modname, LuaValue env) { + LuaTable math = new LuaTable(0,30); + math.set("abs", new abs()); + math.set("ceil", new ceil()); + math.set("cos", new cos()); + math.set("deg", new deg()); + math.set("exp", new exp(this)); + math.set("floor", new floor()); + math.set("fmod", new fmod()); + math.set("frexp", new frexp()); + math.set("huge", LuaDouble.POSINF ); + math.set("ldexp", new ldexp()); + math.set("max", new max()); + math.set("min", new min()); + math.set("modf", new modf()); + math.set("pi", Math.PI ); + math.set("pow", new pow()); + random r; + math.set("random", r = new random()); + math.set("randomseed", new randomseed(r)); + math.set("rad", new rad()); + math.set("sin", new sin()); + math.set("sqrt", new sqrt()); + math.set("tan", new tan()); + env.set("math", math); + if (!env.get("package").isnil()) env.get("package").get("loaded").set("math", math); + return math; + } + + abstract protected static class UnaryOp extends OneArgFunction { + public LuaValue call(LuaValue arg) { + return valueOf(call(arg.checkdouble())); + } + abstract protected double call(double d); + } + + abstract protected static class BinaryOp extends TwoArgFunction { + public LuaValue call(LuaValue x, LuaValue y) { + return valueOf(call(x.checkdouble(), y.checkdouble())); + } + abstract protected double call(double x, double y); + } + + static final class abs extends UnaryOp { protected double call(double d) { return Math.abs(d); } } + static final class ceil extends UnaryOp { protected double call(double d) { return Math.ceil(d); } } + static final class cos extends UnaryOp { protected double call(double d) { return Math.cos(d); } } + static final class deg extends UnaryOp { protected double call(double d) { return Math.toDegrees(d); } } + static final class floor extends UnaryOp { protected double call(double d) { return Math.floor(d); } } + static final class rad extends UnaryOp { protected double call(double d) { return Math.toRadians(d); } } + static final class sin extends UnaryOp { protected double call(double d) { return Math.sin(d); } } + static final class sqrt extends UnaryOp { protected double call(double d) { return Math.sqrt(d); } } + static final class tan extends UnaryOp { protected double call(double d) { return Math.tan(d); } } + + static final class exp extends UnaryOp { + final MathLib mathlib; + exp(MathLib mathlib) { + this.mathlib = mathlib; + } + protected double call(double d) { + return mathlib.dpow_lib(Math.E,d); + } + } + + static final class fmod extends TwoArgFunction { + public LuaValue call(LuaValue xv, LuaValue yv) { + if (xv.islong() && yv.islong()) { + return valueOf(xv.tolong() % yv.tolong()); + } + return valueOf(xv.checkdouble() % yv.checkdouble()); + } + } + static final class ldexp extends BinaryOp { + protected double call(double x, double y) { + // This is the behavior on os-x, windows differs in rounding behavior. + return x * Double.longBitsToDouble((((long) y) + 1023) << 52); + } + } + static final class pow extends BinaryOp { + protected double call(double x, double y) { + return MathLib.dpow_default(x, y); + } + } + + static class frexp extends VarArgFunction { + public Varargs invoke(Varargs args) { + double x = args.checkdouble(1); + if ( x == 0 ) return varargsOf(ZERO,ZERO); + long bits = Double.doubleToLongBits( x ); + double m = ((bits & (~(-1L<<52))) + (1L<<52)) * ((bits >= 0)? (.5 / (1L<<52)): (-.5 / (1L<<52))); + double e = (((int) (bits >> 52)) & 0x7ff) - 1022; + return varargsOf( valueOf(m), valueOf(e) ); + } + } + + static class max extends VarArgFunction { + public Varargs invoke(Varargs args) { + LuaValue m = args.checkvalue(1); + for ( int i=2,n=args.narg(); i<=n; ++i ) { + LuaValue v = args.checkvalue(i); + if (m.lt_b(v)) m = v; + } + return m; + } + } + + static class min extends VarArgFunction { + public Varargs invoke(Varargs args) { + LuaValue m = args.checkvalue(1); + for ( int i=2,n=args.narg(); i<=n; ++i ) { + LuaValue v = args.checkvalue(i); + if (v.lt_b(m)) m = v; + } + return m; + } + } + + static class modf extends VarArgFunction { + public Varargs invoke(Varargs args) { + LuaValue n = args.arg1(); + /* number is its own integer part, no fractional part */ + if (n.islong()) return varargsOf(n, valueOf(0.0)); + double x = n.checkdouble(); + /* integer part (rounds toward zero) */ + double intPart = ( x > 0 ) ? Math.floor( x ) : Math.ceil( x ); + /* fractional part (test needed for inf/-inf) */ + double fracPart = x == intPart ? 0.0 : x - intPart; + return varargsOf( valueOf(intPart), valueOf(fracPart) ); + } + } + + static class random extends LibFunction { + Random random = new Random(); + public LuaValue call() { + return valueOf( random.nextDouble() ); + } + public LuaValue call(LuaValue a) { + int m = a.checkint(); + if (m<1) argerror(1, "interval is empty"); + return valueOf( 1 + random.nextInt(m) ); + } + public LuaValue call(LuaValue a, LuaValue b) { + int m = a.checkint(); + int n = b.checkint(); + if (n 0; whole>>=1, v*=v ) + if ( (whole & 1) != 0 ) + p *= v; + if ( (b -= whole) > 0 ) { + int frac = (int) (0x10000 * b); + for ( ; (frac&0xffff)!=0; frac<<=1 ) { + a = Math.sqrt(a); + if ( (frac & 0x8000) != 0 ) + p *= a; + } + } + return p; + } + +} diff --git a/core/src/main/java/org/luaj/vm2/libs/OneArgFunction.java b/core/src/main/java/org/luaj/vm2/libs/OneArgFunction.java new file mode 100644 index 00000000..e589fc57 --- /dev/null +++ b/core/src/main/java/org/luaj/vm2/libs/OneArgFunction.java @@ -0,0 +1,72 @@ +/******************************************************************************* +* Copyright (c) 2009-2011 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.libs; + +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; + +/** Abstract base class for Java function implementations that take one argument and + * return one value. + *

+ * Subclasses need only implement {@link LuaValue#call(LuaValue)} to complete this class, + * simplifying development. + * All other uses of {@link #call()}, {@link #invoke(Varargs)},etc, + * are routed through this method by this class, + * dropping or extending arguments with {@code nil} values as required. + *

+ * If more than one argument are required, or no arguments are required, + * or variable argument or variable return values, + * then use one of the related function + * {@link ZeroArgFunction}, {@link TwoArgFunction}, {@link ThreeArgFunction}, or {@link VarArgFunction}. + *

+ * See {@link LibFunction} for more information on implementation libraries and library functions. + * @see #call(LuaValue) + * @see LibFunction + * @see ZeroArgFunction + * @see TwoArgFunction + * @see ThreeArgFunction + * @see VarArgFunction + */ +abstract public class OneArgFunction extends LibFunction { + + abstract public LuaValue call(LuaValue arg); + + /** Default constructor */ + public OneArgFunction() { + } + + public final LuaValue call() { + return call(NIL); + } + + public final LuaValue call(LuaValue arg1, LuaValue arg2) { + return call(arg1); + } + + public LuaValue call(LuaValue arg1, LuaValue arg2, LuaValue arg3) { + return call(arg1); + } + + public Varargs invoke(Varargs varargs) { + return call(varargs.arg1()); + } +} diff --git a/core/src/main/java/org/luaj/vm2/libs/OsLib.java b/core/src/main/java/org/luaj/vm2/libs/OsLib.java new file mode 100644 index 00000000..6b21c0bb --- /dev/null +++ b/core/src/main/java/org/luaj/vm2/libs/OsLib.java @@ -0,0 +1,524 @@ +/******************************************************************************* +* Copyright (c) 2009 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.libs; + +import java.io.IOException; +import java.util.Calendar; +import java.util.Date; + +import org.luaj.vm2.Buffer; +import org.luaj.vm2.Globals; +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; + +/** + * Subclass of {@link LibFunction} which implements the standard lua {@code os} library. + *

+ * It is a usable base with simplified stub functions + * for library functions that cannot be implemented uniformly + * on Jse and Jme. + *

+ * This can be installed as-is on either platform, or extended + * and refined to be used in a complete Jse implementation. + *

+ * Because the nature of the {@code os} library is to encapsulate + * os-specific features, the behavior of these functions varies considerably + * from their counterparts in the C platform. + *

+ * The following functions have limited implementations of features + * that are not supported well on Jme: + *

+ *

+ * Typically, this library is included as part of a call to either + * {@link org.luaj.vm2.libs.jse.JsePlatform#standardGlobals()} or {@link org.luaj.vm2.libs.jme.JmePlatform#standardGlobals()} + *

 {@code
+ * Globals globals = JsePlatform.standardGlobals();
+ * System.out.println( globals.get("os").get("time").call() );
+ * } 
+ * In this example the platform-specific {@link org.luaj.vm2.libs.jse.JseOsLib} library will be loaded, which will include + * the base functionality provided by this class. + *

+ * To instantiate and use it directly, + * link it into your globals table via {@link LuaValue#load(LuaValue)} using code such as: + *

 {@code
+ * Globals globals = new Globals();
+ * globals.load(new JseBaseLib());
+ * globals.load(new PackageLib());
+ * globals.load(new OsLib());
+ * System.out.println( globals.get("os").get("time").call() );
+ * } 
+ *

+ * @see LibFunction + * @see org.luaj.vm2.libs.jse.JseOsLib + * @see org.luaj.vm2.libs.jse.JsePlatform + * @see org.luaj.vm2.libs.jme.JmePlatform + * @see http://www.lua.org/manual/5.1/manual.html#5.8 + */ +public class OsLib extends TwoArgFunction { + public static final String TMP_PREFIX = ".luaj"; + public static final String TMP_SUFFIX = "tmp"; + + private static final int CLOCK = 0; + private static final int DATE = 1; + private static final int DIFFTIME = 2; + private static final int EXECUTE = 3; + private static final int EXIT = 4; + private static final int GETENV = 5; + private static final int REMOVE = 6; + private static final int RENAME = 7; + private static final int SETLOCALE = 8; + private static final int TIME = 9; + private static final int TMPNAME = 10; + + private static final String[] NAMES = { + "clock", + "date", + "difftime", + "execute", + "exit", + "getenv", + "remove", + "rename", + "setlocale", + "time", + "tmpname", + }; + + private static final long t0 = System.currentTimeMillis(); + private static long tmpnames = t0; + + protected Globals globals; + + /** + * Create and OsLib instance. + */ + public OsLib() { + } + + /** Perform one-time initialization on the library by creating a table + * containing the library functions, adding that table to the supplied environment, + * adding the table to package.loaded, and returning table as the return value. + * @param modname the module name supplied if this is loaded via 'require'. + * @param env the environment to load into, typically a Globals instance. + */ + public LuaValue call(LuaValue modname, LuaValue env) { + globals = env.checkglobals(); + LuaTable os = new LuaTable(); + for (int i = 0; i < NAMES.length; ++i) + os.set(NAMES[i], new OsLibFunc(i, NAMES[i])); + env.set("os", os); + if (!env.get("package").isnil()) env.get("package").get("loaded").set("os", os); + return os; + } + + class OsLibFunc extends VarArgFunction { + public OsLibFunc(int opcode, String name) { + this.opcode = opcode; + this.name = name; + } + public Varargs invoke(Varargs args) { + try { + switch ( opcode ) { + case CLOCK: + return valueOf(clock()); + case DATE: { + String s = args.optjstring(1, "%c"); + double t = args.isnumber(2)? args.todouble(2): time(null); + if (s.equals("*t")) { + Calendar d = Calendar.getInstance(); + d.setTime(new Date((long)(t*1000))); + LuaTable tbl = LuaValue.tableOf(); + tbl.set("year", LuaValue.valueOf(d.get(Calendar.YEAR))); + tbl.set("month", LuaValue.valueOf(d.get(Calendar.MONTH)+1)); + tbl.set("day", LuaValue.valueOf(d.get(Calendar.DAY_OF_MONTH))); + tbl.set("hour", LuaValue.valueOf(d.get(Calendar.HOUR_OF_DAY))); + tbl.set("min", LuaValue.valueOf(d.get(Calendar.MINUTE))); + tbl.set("sec", LuaValue.valueOf(d.get(Calendar.SECOND))); + tbl.set("wday", LuaValue.valueOf(d.get(Calendar.DAY_OF_WEEK))); + tbl.set("yday", LuaValue.valueOf(d.get(0x6))); // Day of year + tbl.set("isdst", LuaValue.valueOf(isDaylightSavingsTime(d))); + return tbl; + } + return valueOf( date(s, t==-1? time(null): t) ); + } + case DIFFTIME: + return valueOf(difftime(args.checkdouble(1),args.checkdouble(2))); + case EXECUTE: + return execute(args.optjstring(1, null)); + case EXIT: + exit(args.optint(1, 0)); + return NONE; + case GETENV: { + final String val = getenv(args.checkjstring(1)); + return val!=null? valueOf(val): NIL; + } + case REMOVE: + remove(args.checkjstring(1)); + return LuaValue.TRUE; + case RENAME: + rename(args.checkjstring(1), args.checkjstring(2)); + return LuaValue.TRUE; + case SETLOCALE: { + String s = setlocale(args.optjstring(1,null), args.optjstring(2, "all")); + return s!=null? valueOf(s): NIL; + } + case TIME: + return valueOf(time(args.opttable(1, null))); + case TMPNAME: + return valueOf(tmpname()); + } + return NONE; + } catch ( IOException e ) { + return varargsOf(NIL, valueOf(e.getMessage())); + } + } + } + + /** + * @return an approximation of the amount in seconds of CPU time used by + * the program. For luaj this simple returns the elapsed time since the + * OsLib class was loaded. + */ + protected double clock() { + return (System.currentTimeMillis()-t0) / 1000.; + } + + /** + * Returns the number of seconds from time t1 to time t2. + * In POSIX, Windows, and some other systems, this value is exactly t2-t1. + * @param t2 + * @param t1 + * @return diffeence in time values, in seconds + */ + protected double difftime(double t2, double t1) { + return t2 - t1; + } + + /** + * If the time argument is present, this is the time to be formatted + * (see the os.time function for a description of this value). + * Otherwise, date formats the current time. + * + * Date returns the date as a string, + * formatted according to the same rules as ANSII strftime, but without + * support for %g, %G, or %V. + * + * When called without arguments, date returns a reasonable date and + * time representation that depends on the host system and on the + * current locale (that is, os.date() is equivalent to os.date("%c")). + * + * @param format + * @param time time since epoch, or -1 if not supplied + * @return a LString or a LTable containing date and time, + * formatted according to the given string format. + */ + public String date(String format, double time) { + Calendar d = Calendar.getInstance(); + d.setTime(new Date((long)(time*1000))); + if (format.startsWith("!")) { + time -= timeZoneOffset(d); + d.setTime(new Date((long)(time*1000))); + format = format.substring(1); + } + byte[] fmt = format.getBytes(); + final int n = fmt.length; + Buffer result = new Buffer(n); + byte c; + for ( int i = 0; i < n; ) { + switch ( c = fmt[i++ ] ) { + case '\n': + result.append( "\n" ); + break; + default: + result.append( c ); + break; + case '%': + if (i >= n) break; + switch ( c = fmt[i++ ] ) { + default: + LuaValue.argerror(1, "invalid conversion specifier '%"+c+"'"); + break; + case '%': + result.append( (byte)'%' ); + break; + case 'a': + result.append(WeekdayNameAbbrev[d.get(Calendar.DAY_OF_WEEK)-1]); + break; + case 'A': + result.append(WeekdayName[d.get(Calendar.DAY_OF_WEEK)-1]); + break; + case 'b': + result.append(MonthNameAbbrev[d.get(Calendar.MONTH)]); + break; + case 'B': + result.append(MonthName[d.get(Calendar.MONTH)]); + break; + case 'c': + result.append(date("%a %b %d %H:%M:%S %Y", time)); + break; + case 'd': + result.append(String.valueOf(100+d.get(Calendar.DAY_OF_MONTH)).substring(1)); + break; + case 'H': + result.append(String.valueOf(100+d.get(Calendar.HOUR_OF_DAY)).substring(1)); + break; + case 'I': + result.append(String.valueOf(100+(d.get(Calendar.HOUR_OF_DAY)%12)).substring(1)); + break; + case 'j': { // day of year. + Calendar y0 = beginningOfYear(d); + int dayOfYear = (int) ((d.getTime().getTime() - y0.getTime().getTime()) / (24 * 3600L * 1000L)); + result.append(String.valueOf(1001+dayOfYear).substring(1)); + break; + } + case 'm': + result.append(String.valueOf(101+d.get(Calendar.MONTH)).substring(1)); + break; + case 'M': + result.append(String.valueOf(100+d.get(Calendar.MINUTE)).substring(1)); + break; + case 'p': + result.append(d.get(Calendar.HOUR_OF_DAY) < 12? "AM": "PM"); + break; + case 'S': + result.append(String.valueOf(100+d.get(Calendar.SECOND)).substring(1)); + break; + case 'U': + result.append(String.valueOf(weekNumber(d, 0))); + break; + case 'w': + result.append(String.valueOf((d.get(Calendar.DAY_OF_WEEK)+6)%7)); + break; + case 'W': + result.append(String.valueOf(weekNumber(d, 1))); + break; + case 'x': + result.append(date("%m/%d/%y", time)); + break; + case 'X': + result.append(date("%H:%M:%S", time)); + break; + case 'y': + result.append(String.valueOf(d.get(Calendar.YEAR)).substring(2)); + break; + case 'Y': + result.append(String.valueOf(d.get(Calendar.YEAR))); + break; + case 'z': { + final int tzo = timeZoneOffset(d) / 60; + final int a = Math.abs(tzo); + final String h = String.valueOf(100 + a / 60).substring(1); + final String m = String.valueOf(100 + a % 60).substring(1); + result.append((tzo>=0? "+": "-") + h + m); + break; + } + } + } + } + return result.tojstring(); + } + + private static final String[] WeekdayNameAbbrev = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; + private static final String[] WeekdayName = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; + private static final String[] MonthNameAbbrev = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + private static final String[] MonthName = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; + + private Calendar beginningOfYear(Calendar d) { + Calendar y0 = Calendar.getInstance(); + y0.setTime(d.getTime()); + y0.set(Calendar.MONTH, 0); + y0.set(Calendar.DAY_OF_MONTH, 1); + y0.set(Calendar.HOUR_OF_DAY, 0); + y0.set(Calendar.MINUTE, 0); + y0.set(Calendar.SECOND, 0); + y0.set(Calendar.MILLISECOND, 0); + return y0; + } + + private int weekNumber(Calendar d, int startDay) { + Calendar y0 = beginningOfYear(d); + y0.set(Calendar.DAY_OF_MONTH, 1 + (startDay + 8 - y0.get(Calendar.DAY_OF_WEEK)) % 7); + if (y0.after(d)) { + y0.set(Calendar.YEAR, y0.get(Calendar.YEAR) - 1); + y0.set(Calendar.DAY_OF_MONTH, 1 + (startDay + 8 - y0.get(Calendar.DAY_OF_WEEK)) % 7); + } + long dt = d.getTime().getTime() - y0.getTime().getTime(); + return 1 + (int) (dt / (7L * 24L * 3600L * 1000L)); + } + + private int timeZoneOffset(Calendar d) { + int localStandarTimeMillis = ( + d.get(Calendar.HOUR_OF_DAY) * 3600 + + d.get(Calendar.MINUTE) * 60 + + d.get(Calendar.SECOND)) * 1000; + return d.getTimeZone().getOffset( + 1, + d.get(Calendar.YEAR), + d.get(Calendar.MONTH), + d.get(Calendar.DAY_OF_MONTH), + d.get(Calendar.DAY_OF_WEEK), + localStandarTimeMillis) / 1000; + } + + private boolean isDaylightSavingsTime(Calendar d) { + return timeZoneOffset(d) != d.getTimeZone().getRawOffset() / 1000; + } + + /** + * This function is equivalent to the C function system. + * It passes command to be executed by an operating system shell. + * It returns a status code, which is system-dependent. + * If command is absent, then it returns nonzero if a shell + * is available and zero otherwise. + * @param command command to pass to the system + */ + protected Varargs execute(String command) { + return varargsOf(NIL, valueOf("exit"), ONE); + } + + /** + * Calls the C function exit, with an optional code, to terminate the host program. + * @param code + */ + protected void exit(int code) { + System.exit(code); + } + + /** + * Returns the value of the process environment variable varname, + * or the System property value for varname, + * or null if the variable is not defined in either environment. + * + * The default implementation, which is used by the JmePlatform, + * only queryies System.getProperty(). + * + * The JsePlatform overrides this behavior and returns the + * environment variable value using System.getenv() if it exists, + * or the System property value if it does not. + * + * A SecurityException may be thrown if access is not allowed + * for 'varname'. + * @param varname + * @return String value, or null if not defined + */ + protected String getenv(String varname) { + return System.getProperty(varname); + } + + /** + * Deletes the file or directory with the given name. + * Directories must be empty to be removed. + * If this function fails, it throws and IOException + * + * @param filename + * @throws IOException if it fails + */ + protected void remove(String filename) throws IOException { + throw new IOException( "not implemented" ); + } + + /** + * Renames file or directory named oldname to newname. + * If this function fails,it throws and IOException + * + * @param oldname old file name + * @param newname new file name + * @throws IOException if it fails + */ + protected void rename(String oldname, String newname) throws IOException { + throw new IOException( "not implemented" ); + } + + /** + * Sets the current locale of the program. locale is a string specifying + * a locale; category is an optional string describing which category to change: + * "all", "collate", "ctype", "monetary", "numeric", or "time"; the default category + * is "all". + * + * If locale is the empty string, the current locale is set to an implementation- + * defined native locale. If locale is the string "C", the current locale is set + * to the standard C locale. + * + * When called with null as the first argument, this function only returns the + * name of the current locale for the given category. + * + * @param locale + * @param category + * @return the name of the new locale, or null if the request + * cannot be honored. + */ + protected String setlocale(String locale, String category) { + return "C"; + } + + /** + * Returns the current time when called without arguments, + * or a time representing the date and time specified by the given table. + * This table must have fields year, month, and day, + * and may have fields hour, min, sec, and isdst + * (for a description of these fields, see the os.date function). + * @param table + * @return long value for the time + */ + protected double time(LuaTable table) { + Date d; + if (table == null) { + d = new Date(); + } else { + Calendar c = Calendar.getInstance(); + c.set(Calendar.YEAR, table.get("year").checkint()); + c.set(Calendar.MONTH, table.get("month").checkint()-1); + c.set(Calendar.DAY_OF_MONTH, table.get("day").checkint()); + c.set(Calendar.HOUR_OF_DAY, table.get("hour").optint(12)); + c.set(Calendar.MINUTE, table.get("min").optint(0)); + c.set(Calendar.SECOND, table.get("sec").optint(0)); + c.set(Calendar.MILLISECOND, 0); + d = c.getTime(); + } + return d.getTime() / 1000.; + } + + /** + * Returns a string with a file name that can be used for a temporary file. + * The file must be explicitly opened before its use and explicitly removed + * when no longer needed. + * + * On some systems (POSIX), this function also creates a file with that name, + * to avoid security risks. (Someone else might create the file with wrong + * permissions in the time between getting the name and creating the file.) + * You still have to open the file to use it and to remove it (even if you + * do not use it). + * + * @return String filename to use + */ + protected String tmpname() { + synchronized ( OsLib.class ) { + return TMP_PREFIX+(tmpnames++)+TMP_SUFFIX; + } + } +} diff --git a/core/src/main/java/org/luaj/vm2/libs/PackageLib.java b/core/src/main/java/org/luaj/vm2/libs/PackageLib.java new file mode 100644 index 00000000..710e263e --- /dev/null +++ b/core/src/main/java/org/luaj/vm2/libs/PackageLib.java @@ -0,0 +1,381 @@ +/******************************************************************************* +* Copyright (c) 2010-2011 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.libs; + +import java.io.InputStream; + +import org.luaj.vm2.Globals; +import org.luaj.vm2.LuaFunction; +import org.luaj.vm2.LuaString; +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; + +/** + * Subclass of {@link LibFunction} which implements the lua standard package and module + * library functions. + * + *

Lua Environment Variables

+ * The following variables are available to lua scrips when this library has been loaded: + * + * + *

Java Environment Variables

+ * These Java environment variables affect the library behavior: + * + * + *

Loading

+ * Typically, this library is included as part of a call to either + * {@link org.luaj.vm2.libs.jse.JsePlatform#standardGlobals()} or {@link org.luaj.vm2.libs.jme.JmePlatform#standardGlobals()} + *
 {@code
+ * Globals globals = JsePlatform.standardGlobals();
+ * System.out.println( globals.get("require").call"foo") );
+ * } 
+ *

+ * To instantiate and use it directly, + * link it into your globals table via {@link LuaValue#load(LuaValue)} using code such as: + *

 {@code
+ * Globals globals = new Globals();
+ * globals.load(new JseBaseLib());
+ * globals.load(new PackageLib());
+ * System.out.println( globals.get("require").call("foo") );
+ * } 
+ *

Limitations

+ * This library has been implemented to match as closely as possible the behavior in the corresponding library in C. + * However, the default filesystem search semantics are different and delegated to the bas library + * as outlined in the {@link BaseLib} and {@link org.luaj.vm2.libs.jse.JseBaseLib} documentation. + *

+ * @see LibFunction + * @see BaseLib + * @see org.luaj.vm2.libs.jse.JseBaseLib + * @see org.luaj.vm2.libs.jse.JsePlatform + * @see org.luaj.vm2.libs.jme.JmePlatform + * @see Lua 5.2 Package Lib Reference + */ +public class PackageLib extends TwoArgFunction { + + /** The default value to use for package.path. This can be set with the system property + * "luaj.package.path", and is "?.lua" by default. */ + public static final String DEFAULT_LUA_PATH; + static { + String path = null; + try { + path = System.getProperty("luaj.package.path"); + } catch (Exception e) { + System.out.println(e.toString()); + } + if (path == null) { + path = "?.lua"; + } + DEFAULT_LUA_PATH = path; + } + + static final LuaString _LOADED = valueOf("loaded"); + private static final LuaString _LOADLIB = valueOf("loadlib"); + static final LuaString _PRELOAD = valueOf("preload"); + static final LuaString _PATH = valueOf("path"); + static final LuaString _SEARCHPATH = valueOf("searchpath"); + static final LuaString _SEARCHERS = valueOf("searchers"); + + /** The globals that were used to load this library. */ + Globals globals; + + /** The table for this package. */ + LuaTable package_; + + /** Loader that loads from {@code preload} table if found there */ + public preload_searcher preload_searcher; + + /** Loader that loads as a lua script using the lua path currently in {@link path} */ + public lua_searcher lua_searcher; + + /** Loader that loads as a Java class. Class must have public constructor and be a LuaValue. */ + public java_searcher java_searcher; + + private static final LuaString _SENTINEL = valueOf("\u0001"); + + private static final String FILE_SEP = System.getProperty("file.separator"); + + public PackageLib() {} + + /** Perform one-time initialization on the library by adding package functions + * to the supplied environment, and returning it as the return value. + * It also creates the package.preload and package.loaded tables for use by + * other libraries. + * @param modname the module name supplied if this is loaded via 'require'. + * @param env the environment to load into, typically a Globals instance. + */ + public LuaValue call(LuaValue modname, LuaValue env) { + globals = env.checkglobals(); + globals.set("require", new require()); + package_ = new LuaTable(); + package_.set(_LOADED, new LuaTable()); + package_.set(_PRELOAD, new LuaTable()); + package_.set(_PATH, LuaValue.valueOf(DEFAULT_LUA_PATH)); + package_.set(_LOADLIB, new loadlib()); + package_.set(_SEARCHPATH, new searchpath()); + LuaTable searchers = new LuaTable(); + searchers.set(1, preload_searcher = new preload_searcher()); + searchers.set(2, lua_searcher = new lua_searcher()); + searchers.set(3, java_searcher = new java_searcher()); + package_.set(_SEARCHERS, searchers); + package_.set("config", FILE_SEP + "\n;\n?\n!\n-\n"); + package_.get(_LOADED).set("package", package_); + env.set("package", package_); + globals.package_ = this; + return env; + } + + /** Allow packages to mark themselves as loaded */ + public void setIsLoaded(String name, LuaTable value) { + package_.get(_LOADED).set(name, value); + } + + + /** Set the lua path used by this library instance to a new value. + * Merely sets the value of {@link path} to be used in subsequent searches. */ + public void setLuaPath( String newLuaPath ) { + package_.set(_PATH, LuaValue.valueOf(newLuaPath)); + } + + public String tojstring() { + return "package"; + } + + // ======================== Package loading ============================= + + /** + * require (modname) + * + * Loads the given module. The function starts by looking into the package.loaded table + * to determine whether modname is already loaded. If it is, then require returns the value + * stored at package.loaded[modname]. Otherwise, it tries to find a loader for the module. + * + * To find a loader, require is guided by the package.searchers sequence. + * By changing this sequence, we can change how require looks for a module. + * The following explanation is based on the default configuration for package.searchers. + * + * First require queries package.preload[modname]. If it has a value, this value + * (which should be a function) is the loader. Otherwise require searches for a Lua loader using + * the path stored in package.path. If that also fails, it searches for a Java loader using + * the classpath, using the public default constructor, and casting the instance to LuaFunction. + * + * Once a loader is found, require calls the loader with two arguments: modname and an extra value + * dependent on how it got the loader. If the loader came from a file, this extra value is the file name. + * If the loader is a Java instance of LuaFunction, this extra value is the environment. + * If the loader returns any non-nil value, require assigns the returned value to package.loaded[modname]. + * If the loader does not return a non-nil value and has not assigned any value to package.loaded[modname], + * then require assigns true to this entry. + * In any case, require returns the final value of package.loaded[modname]. + * + * If there is any error loading or running the module, or if it cannot find any loader for the module, + * then require raises an error. + */ + public class require extends OneArgFunction { + public LuaValue call( LuaValue arg ) { + LuaString name = arg.checkstring(); + LuaValue loaded = package_.get(_LOADED); + LuaValue result = loaded.get(name); + if ( result.toboolean() ) { + if ( result == _SENTINEL ) + error("loop or previous error loading module '"+name+"'"); + return result; + } + + /* else must load it; iterate over available loaders */ + LuaTable tbl = package_.get(_SEARCHERS).checktable(); + StringBuffer sb = new StringBuffer(); + Varargs loader = null; + for ( int i=1; true; i++ ) { + LuaValue searcher = tbl.get(i); + if ( searcher.isnil() ) { + error( "module '"+name+"' not found: "+name+sb ); + } + + /* call loader with module name as argument */ + loader = searcher.invoke(name); + if ( loader.isfunction(1) ) + break; + if ( loader.isstring(1) ) + sb.append( loader.tojstring(1) ); + } + + // load the module using the loader + loaded.set(name, _SENTINEL); + result = loader.arg1().call(name, loader.arg(2)); + if ( ! result.isnil() ) + loaded.set( name, result ); + else if ( (result = loaded.get(name)) == _SENTINEL ) + loaded.set( name, result = LuaValue.TRUE ); + return result; + } + } + + public static class loadlib extends VarArgFunction { + public Varargs invoke( Varargs args ) { + args.checkstring(1); + return varargsOf(NIL, valueOf("dynamic libraries not enabled"), valueOf("absent")); + } + } + + public class preload_searcher extends VarArgFunction { + public Varargs invoke(Varargs args) { + LuaString name = args.checkstring(1); + LuaValue val = package_.get(_PRELOAD).get(name); + return val.isnil()? + valueOf("\n\tno field package.preload['"+name+"']"): + val; + } + } + + public class lua_searcher extends VarArgFunction { + public Varargs invoke(Varargs args) { + LuaString name = args.checkstring(1); + + // get package path + LuaValue path = package_.get(_PATH); + if ( ! path.isstring() ) + return valueOf("package.path is not a string"); + + // get the searchpath function. + Varargs v = package_.get(_SEARCHPATH).invoke(varargsOf(name, path)); + + // Did we get a result? + if (!v.isstring(1)) + return v.arg(2).tostring(); + LuaString filename = v.arg1().strvalue(); + + // Try to load the file. + v = globals.loadfile(filename.tojstring()); + if ( v.arg1().isfunction() ) + return LuaValue.varargsOf(v.arg1(), filename); + + // report error + return varargsOf(NIL, valueOf("'"+filename+"': "+v.arg(2).tojstring())); + } + } + + public class searchpath extends VarArgFunction { + public Varargs invoke(Varargs args) { + String name = args.checkjstring(1); + String path = args.checkjstring(2); + String sep = args.optjstring(3, "."); + String rep = args.optjstring(4, FILE_SEP); + + // check the path elements + int e = -1; + int n = path.length(); + StringBuffer sb = null; + name = name.replace(sep.charAt(0), rep.charAt(0)); + while ( e < n ) { + + // find next template + int b = e+1; + e = path.indexOf(';',b); + if ( e < 0 ) + e = path.length(); + String template = path.substring(b,e); + + // create filename + int q = template.indexOf('?'); + String filename = template; + if ( q >= 0 ) { + filename = template.substring(0,q) + name + template.substring(q+1); + } + + // try opening the file + InputStream is = globals.finder.findResource(filename); + if (is != null) { + try { is.close(); } catch ( java.io.IOException ioe ) {} + return valueOf(filename); + } + + // report error + if ( sb == null ) + sb = new StringBuffer(); + sb.append( "\n\t"+filename ); + } + return varargsOf(NIL, valueOf(sb.toString())); + } + } + + public class java_searcher extends VarArgFunction { + public Varargs invoke(Varargs args) { + String name = args.checkjstring(1); + String classname = toClassname( name ); + Class c = null; + LuaValue v = null; + try { + c = Class.forName(classname); + v = (LuaValue) c.newInstance(); + if (v.isfunction()) + ((LuaFunction)v).initupvalue1(globals); + return varargsOf(v, globals); + } catch ( ClassNotFoundException cnfe ) { + return valueOf("\n\tno class '"+classname+"'" ); + } catch ( Exception e ) { + return valueOf("\n\tjava load failed on '"+classname+"', "+e ); + } + } + } + + /** Convert lua filename to valid class name */ + public static final String toClassname( String filename ) { + int n=filename.length(); + int j=n; + if ( filename.endsWith(".lua") ) + j -= 4; + for ( int k=0; k='a'&&c<='z') || (c>='A'&&c<='Z') || (c>='0'&&c<='9') ) + return true; + switch ( c ) { + case '.': + case '$': + case '_': + return true; + default: + return false; + } + } +} diff --git a/core/src/main/java/org/luaj/vm2/libs/ResourceFinder.java b/core/src/main/java/org/luaj/vm2/libs/ResourceFinder.java new file mode 100644 index 00000000..8c290ae6 --- /dev/null +++ b/core/src/main/java/org/luaj/vm2/libs/ResourceFinder.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2009-2011 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.libs; + +import java.io.InputStream; + +import org.luaj.vm2.Globals; + +/** + * Interface for opening application resource files such as scripts sources. + *

+ * This is used by required to load files that are part of + * the application, and implemented by BaseLib + * for both the Jme and Jse platforms. + *

+ * The Jme version of base lib {@link BaseLib} + * implements {@link Globals#finder} via {@link Class#getResourceAsStream(String)}, + * while the Jse version {@link org.luaj.vm2.libs.jse.JseBaseLib} implements it using {@link java.io.File#File(String)}. + *

+ * The io library does not use this API for file manipulation. + *

+ * @see BaseLib + * @see Globals#finder + * @see org.luaj.vm2.libs.jse.JseBaseLib + * @see org.luaj.vm2.libs.jme.JmePlatform + * @see org.luaj.vm2.libs.jse.JsePlatform + */ +public interface ResourceFinder { + + /** + * Try to open a file, or return null if not found. + * + * @see BaseLib + * @see org.luaj.vm2.libs.jse.JseBaseLib + * + * @param filename + * @return InputStream, or null if not found. + */ + public InputStream findResource( String filename ); +} \ No newline at end of file diff --git a/core/src/main/java/org/luaj/vm2/libs/StringLib.java b/core/src/main/java/org/luaj/vm2/libs/StringLib.java new file mode 100644 index 00000000..86de6509 --- /dev/null +++ b/core/src/main/java/org/luaj/vm2/libs/StringLib.java @@ -0,0 +1,1223 @@ +/******************************************************************************* +* Copyright (c) 2009-2011 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.libs; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.luaj.vm2.Buffer; +import org.luaj.vm2.LuaClosure; +import org.luaj.vm2.LuaString; +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; +import org.luaj.vm2.compiler.DumpState; + +/** + * Subclass of {@link LibFunction} which implements the lua standard {@code string} + * library. + *

+ * Typically, this library is included as part of a call to either + * {@link org.luaj.vm2.libs.jse.JsePlatform#standardGlobals()} or {@link org.luaj.vm2.libs.jme.JmePlatform#standardGlobals()} + *

 {@code
+ * Globals globals = JsePlatform.standardGlobals();
+ * System.out.println( globals.get("string").get("upper").call( LuaValue.valueOf("abcde") ) );
+ * } 
+ *

+ * To instantiate and use it directly, + * link it into your globals table via {@link LuaValue#load(LuaValue)} using code such as: + *

 {@code
+ * Globals globals = new Globals();
+ * globals.load(new JseBaseLib());
+ * globals.load(new PackageLib());
+ * globals.load(new JseStringLib());
+ * System.out.println( globals.get("string").get("upper").call( LuaValue.valueOf("abcde") ) );
+ * } 
+ *

+ * This is a direct port of the corresponding library in C. + * @see LibFunction + * @see org.luaj.vm2.libs.jse.JsePlatform + * @see org.luaj.vm2.libs.jme.JmePlatform + * @see Lua 5.2 String Lib Reference + */ +public class StringLib extends TwoArgFunction { + + /** Construct a StringLib, which can be initialized by calling it with a + * modname string, and a global environment table as arguments using + * {@link #call(LuaValue, LuaValue)}. */ + public StringLib() { + } + + /** Perform one-time initialization on the library by creating a table + * containing the library functions, adding that table to the supplied environment, + * adding the table to package.loaded, and returning table as the return value. + * Creates a metatable that uses __INDEX to fall back on itself to support string + * method operations. + * If the shared strings metatable instance is null, will set the metatable as + * the global shared metatable for strings. + *

+ * All tables and metatables are read-write by default so if this will be used in + * a server environment, sandboxing should be used. In particular, the + * {@link LuaString#s_metatable} table should probably be made read-only. + * @param modname the module name supplied if this is loaded via 'require'. + * @param env the environment to load into, typically a Globals instance. + */ + public LuaValue call(LuaValue modname, LuaValue env) { + LuaTable string = new LuaTable(); + string.set("byte", new _byte()); + string.set("char", new _char()); + string.set("dump", new dump()); + string.set("find", new find()); + string.set("format", new format()); + string.set("gmatch", new gmatch()); + string.set("gsub", new gsub()); + string.set("len", new len()); + string.set("lower", new lower()); + string.set("match", new match()); + string.set("rep", new rep()); + string.set("reverse", new reverse()); + string.set("sub", new sub()); + string.set("upper", new upper()); + + env.set("string", string); + if (!env.get("package").isnil()) env.get("package").get("loaded").set("string", string); + if (LuaString.s_metatable == null) { + LuaString.s_metatable = LuaValue.tableOf(new LuaValue[] { INDEX, string }); + } + return string; + } + + /** + * string.byte (s [, i [, j]]) + * + * Returns the internal numerical codes of the + * characters s[i], s[i+1], ..., s[j]. The default value for i is 1; the + * default value for j is i. + * + * Note that numerical codes are not necessarily portable across platforms. + * + * @param args the calling 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 ); + 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=256) argerror(a, "invalid value for string.char [0; 255]: " + c); + bytes[i] = (byte) c; + } + return LuaString.valueUsing( bytes ); + } + } + + /** + * string.dump (function[, stripDebug]) + * + * Returns a string containing a binary representation of the given function, + * so that a later loadstring on this string returns a copy of the function. + * function must be a Lua function without upvalues. + * Boolean param stripDebug - true to strip debugging info, false otherwise. + * The default value for stripDebug is true. + * + * TODO: port dumping code as optional add-on + */ + static final class dump extends VarArgFunction { + public Varargs invoke(Varargs args) { + LuaValue f = args.checkfunction(1); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + DumpState.dump( ((LuaClosure)f).p, baos, args.optboolean(2, true) ); + return LuaString.valueUsing(baos.toByteArray()); + } catch (IOException e) { + return error( e.getMessage() ); + } + } + } + + /** + * string.find (s, pattern [, init [, plain]]) + * + * Looks for the first match of pattern in the string s. + * If it finds a match, then find returns the indices of s + * where this occurrence starts and ends; otherwise, it returns nil. + * A third, optional numerical argument init specifies where to start the search; + * its default value is 1 and may be negative. A value of true as a fourth, + * optional argument plain turns off the pattern matching facilities, + * so the function does a plain "find substring" operation, + * with no characters in pattern being considered "magic". + * Note that if plain is given, then init must be given as well. + * + * If the pattern has captures, then in a successful match the captured values + * are also returned, after the two indices. + */ + static final class find extends VarArgFunction { + public Varargs invoke(Varargs args) { + return str_find_aux( args, true ); + } + } + + /** + * string.format (formatstring, ...) + * + * Returns a formatted version of its variable number of arguments following + * the description given in its first argument (which must be a string). + * The format string follows the same rules as the printf family of standard C functions. + * The only differences are that the options/modifiers *, l, L, n, p, and h are not supported + * and that there is an extra option, q. The q option formats a string in a form suitable + * to be safely read back by the Lua interpreter: the string is written between double quotes, + * and all double quotes, newlines, embedded zeros, and backslashes in the string are correctly + * escaped when written. For instance, the call + * string.format('%q', 'a string with "quotes" and \n new line') + * + * will produce the string: + * "a string with \"quotes\" and \ + * new line" + * + * The options c, d, E, e, f, g, G, i, o, u, X, and x all expect a number as argument, + * whereas q and s expect a string. + * + * This function does not accept string values containing embedded zeros, + * except as arguments to the q option. + */ + 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.checklong( 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; + } + } + } + } + } + + return result.tostring(); + } + } + + static void addquoted(Buffer buf, LuaString s) { + int c; + buf.append( (byte) '"' ); + for ( int i = 0, n = s.length(); i < n; i++ ) { + switch ( c = s.luaByte( i ) ) { + case '"': case '\\': case '\n': + buf.append( (byte)'\\' ); + buf.append( (byte)c ); + break; + default: + if (c <= 0x1F || c == 0x7F) { + buf.append( (byte) '\\' ); + if (i+1 == n || s.luaByte(i+1) < '0' || s.luaByte(i+1) > '9') { + buf.append(Integer.toString(c)); + } else { + buf.append( (byte) '0' ); + buf.append( (byte) (char) ('0' + c / 10) ); + buf.append( (byte) (char) ('0' + c % 10) ); + } + } else { + buf.append((byte) c); + } + break; + } + } + buf.append( (byte) '"' ); + } + + private static final String FLAGS = "-+ #0"; + + class FormatDesc { + + private boolean leftAdjust; + private boolean zeroPad; + private boolean explicitPlus; + private boolean space; + private boolean alternateForm; + private static final int MAX_FLAGS = 5; + + private int width; + int precision; + + public final int conversion; + public final int length; + + public final String src; + + public FormatDesc(Varargs args, LuaString strfrmt, final int start) { + int p = start, n = strfrmt.length(); + int c = 0; + + boolean moreFlags = true; + while ( moreFlags ) { + switch ( c = ( (p < n) ? strfrmt.luaByte( p++ ) : 0 ) ) { + case '-': leftAdjust = true; break; + case '+': explicitPlus = true; break; + case ' ': space = true; break; + case '#': alternateForm = true; break; + case '0': zeroPad = true; break; + default: moreFlags = false; break; + } + } + if ( p - start > MAX_FLAGS ) + error("invalid format (repeated flags)"); + + width = -1; + if ( Character.isDigit( (char)c ) ) { + width = c - '0'; + c = ( (p < n) ? strfrmt.luaByte( p++ ) : 0 ); + if ( Character.isDigit( (char) c ) ) { + width = width * 10 + (c - '0'); + c = ( (p < n) ? strfrmt.luaByte( p++ ) : 0 ); + } + } + + precision = -1; + if ( c == '.' ) { + c = ( (p < n) ? strfrmt.luaByte( p++ ) : 0 ); + if ( Character.isDigit( (char) c ) ) { + precision = c - '0'; + c = ( (p < n) ? strfrmt.luaByte( p++ ) : 0 ); + if ( Character.isDigit( (char) c ) ) { + precision = precision * 10 + (c - '0'); + c = ( (p < n) ? strfrmt.luaByte( p++ ) : 0 ); + } + } + } + + if ( Character.isDigit( (char) c ) ) + error("invalid format (width or precision too long)"); + + zeroPad &= !leftAdjust; // '-' overrides '0' + conversion = c; + length = p - start; + src = strfrmt.substring(start - 1, p).tojstring(); + } + + public void format(Buffer buf, byte c) { + // TODO: not clear that any of width, precision, or flags apply here. + buf.append(c); + } + + public void format(Buffer buf, long number) { + String digits; + + if ( number == 0 && precision == 0 ) { + digits = ""; + } else { + int radix; + switch ( conversion ) { + case 'x': + case 'X': + radix = 16; + break; + case 'o': + radix = 8; + break; + default: + radix = 10; + break; + } + digits = Long.toString( number, radix ); + if ( conversion == 'X' ) + digits = digits.toUpperCase(); + } + + int minwidth = digits.length(); + int ndigits = minwidth; + int nzeros; + + if ( number < 0 ) { + ndigits--; + } else if ( explicitPlus || space ) { + minwidth++; + } + + if ( precision > ndigits ) + nzeros = precision - ndigits; + else if ( precision == -1 && zeroPad && width > minwidth ) + nzeros = width - minwidth; + else + nzeros = 0; + + minwidth += nzeros; + int nspaces = width > minwidth ? width - minwidth : 0; + + if ( !leftAdjust ) + pad( buf, ' ', nspaces ); + + if ( number < 0 ) { + if ( nzeros > 0 ) { + buf.append( (byte)'-' ); + digits = digits.substring( 1 ); + } + } else if ( explicitPlus ) { + buf.append( (byte)'+' ); + } else if ( space ) { + buf.append( (byte)' ' ); + } + + if ( nzeros > 0 ) + pad( buf, '0', nzeros ); + + buf.append( digits ); + + if ( leftAdjust ) + pad( buf, ' ', nspaces ); + } + + public void format(Buffer buf, double x) { + buf.append( StringLib.this.format(src, x) ); + } + + public void format(Buffer buf, LuaString s) { + int nullindex = s.indexOf( (byte)'\0', 0 ); + if ( nullindex != -1 ) + s = s.substring( 0, nullindex ); + buf.append(s); + } + + public final void pad(Buffer buf, char c, int n) { + byte b = (byte)c; + while ( n-- > 0 ) + buf.append(b); + } + } + + protected String format(String src, double x) { + return String.valueOf(x); + } + + /** + * string.gmatch (s, pattern) + * + * Returns an iterator function that, each time it is called, returns the next captures + * from pattern over string s. If pattern specifies no captures, then the + * whole match is produced in each call. + * + * As an example, the following loop + * s = "hello world from Lua" + * for w in string.gmatch(s, "%a+") do + * print(w) + * end + * + * will iterate over all the words from string s, printing one per line. + * The next example collects all pairs key=value from the given string into a table: + * t = {} + * s = "from=world, to=Lua" + * for k, v in string.gmatch(s, "(%w+)=(%w+)") do + * t[k] = v + * end + * + * For this function, a '^' at the start of a pattern does not work as an anchor, + * as this would prevent the iteration. + */ + 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; + private final MatchState ms; + private int soffset; + private int lastmatch; + public GMatchAux(Varargs args, LuaString src, LuaString pat) { + this.srclen = src.length(); + this.ms = new MatchState(args, src, pat); + this.soffset = 0; + this.lastmatch = -1; + } + public Varargs invoke(Varargs args) { + for ( ; soffset<=srclen; soffset++ ) { + ms.reset(); + int res = ms.match(soffset, 0); + if ( res >=0 && res != lastmatch ) { + int soff = soffset; + lastmatch = soffset = res; + return ms.push_captures( true, soff, res ); + } + } + return NIL; + } + } + + + /** + * string.gsub (s, pattern, repl [, n]) + * Returns a copy of s in which all (or the first n, if given) occurrences of the + * pattern have been replaced by a replacement string specified by repl, which + * may be a string, a table, or a function. gsub also returns, as its second value, + * the total number of matches that occurred. + * + * If repl is a string, then its value is used for replacement. + * The character % works as an escape character: any sequence in repl of the form %n, + * with n between 1 and 9, stands for the value of the n-th captured substring (see below). + * The sequence %0 stands for the whole match. The sequence %% stands for a single %. + * + * If repl is a table, then the table is queried for every match, using the first capture + * as the key; if the pattern specifies no captures, then the whole match is used as the key. + * + * If repl is a function, then this function is called every time a match occurs, + * with all captured substrings passed as arguments, in order; if the pattern specifies + * no captures, then the whole match is passed as a sole argument. + * + * If the value returned by the table query or by the function call is a string or a number, + * then it is used as the replacement string; otherwise, if it is false or nil, + * then there is no replacement (that is, the original match is kept in the string). + * + * Here are some examples: + * x = string.gsub("hello world", "(%w+)", "%1 %1") + * --> x="hello hello world world" + * + * x = string.gsub("hello world", "%w+", "%0 %0", 1) + * --> x="hello hello world" + * + * x = string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1") + * --> x="world hello Lua from" + * + * x = string.gsub("home = $HOME, user = $USER", "%$(%w+)", os.getenv) + * --> x="home = /home/roberto, user = roberto" + * + * x = string.gsub("4+5 = $return 4+5$", "%$(.-)%$", function (s) + * return loadstring(s)() + * end) + * --> x="4+5 = 9" + * + * local t = {name="lua", version="5.1"} + * x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t) + * --> x="lua-5.1.tar.gz" + */ + 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 ); + int lastmatch = -1; /* end of last match */ + 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 && res != lastmatch ) { /* match? */ + n++; + ms.add_value( lbuf, soffset, res, repl ); /* add replacement to buffer */ + soffset = lastmatch = res; + } + else if ( soffset < srclen ) /* otherwise, skip one character */ + lbuf.append( (byte) src.luaByte( soffset++ ) ); + else break; /* end of subject */ + if ( anchor ) break; + } + lbuf.append( src.substring( soffset, srclen ) ); + return varargsOf(lbuf.tostring(), valueOf(n)); + } + } + + /** + * string.len (s) + * + * 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 final class len extends OneArgFunction { + public LuaValue call(LuaValue arg) { + return arg.checkstring().len(); + } + } + + /** + * string.lower (s) + * + * Receives a string and returns a copy of this string with all uppercase letters + * changed to lowercase. All other characters are left unchanged. + * The definition of what an uppercase letter is depends on the current locale. + */ + static final class lower extends OneArgFunction { + public LuaValue call(LuaValue arg) { + return valueOf( arg.checkjstring().toLowerCase() ); + } + } + + /** + * string.match (s, pattern [, init]) + * + * Looks for the first match of pattern in the string s. If it finds one, + * then match returns the captures from the pattern; otherwise it returns + * nil. If pattern specifies no captures, then the whole match is returned. + * A third, optional numerical argument init specifies where to start the + * search; its default value is 1 and may be negative. + */ + 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 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 ); + } + } + + /** + * string.reverse (s) + * + * Returns a string that is the string s reversed. + */ + static final class reverse extends OneArgFunction { + public LuaValue call(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; + } + } + } + + /** + * string.upper (s) + * + * Receives a string and returns a copy of this string with all lowercase letters + * changed to uppercase. All other characters are left unchanged. + * The definition of what a lowercase letter is depends on the current locale. + */ + 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. + */ + static Varargs str_find_aux( Varargs args, boolean find ) { + LuaString s = args.checkstring( 1 ); + LuaString pat = args.checkstring( 2 ); + int init = args.optint( 3, 1 ); + + if ( init > 0 ) { + init = Math.min( init - 1, s.length() ); + } else if ( init < 0 ) { + init = Math.max( 0, s.length() + init ); + } + + boolean fastMatch = find && ( args.arg(4).toboolean() || pat.indexOfAny( SPECIALS ) == -1 ); + + if ( fastMatch ) { + int result = s.indexOf( pat, init ); + if ( result != -1 ) { + return varargsOf( valueOf(result+1), valueOf(result+pat.length()) ); + } + } else { + MatchState ms = new MatchState( args, s, pat ); + + boolean anchor = false; + int poff = 0; + if ( pat.length() > 0 && pat.luaByte( 0 ) == '^' ) { + anchor = true; + poff = 1; + } + + int soff = init; + do { + int res; + ms.reset(); + if ( ( res = ms.match( soff, poff ) ) != -1 ) { + if ( find ) { + return varargsOf( valueOf(soff+1), valueOf(res), ms.push_captures( false, soff, res )); + } else { + return ms.push_captures( true, soff, res ); + } + } + } while ( soff++ < s.length() && !anchor ); + } + return NIL; + } + + static int posrelat( int pos, int len ) { + return ( pos >= 0 ) ? pos : len + pos + 1; + } + + // Pattern matching implementation + + private static final int L_ESC = '%'; + private static final LuaString SPECIALS = valueOf("^$*+?.([%-"); + private static final int MAX_CAPTURES = 32; + + private static final int MAXCCALLS = 200; + + private static final int CAP_UNFINISHED = -1; + private static final int CAP_POSITION = -2; + + private static final byte MASK_ALPHA = 0x01; + private static final byte MASK_LOWERCASE = 0x02; + private static final byte MASK_UPPERCASE = 0x04; + private static final byte MASK_DIGIT = 0x08; + private static final byte MASK_PUNCT = 0x10; + private static final byte MASK_SPACE = 0x20; + private static final byte MASK_CONTROL = 0x40; + private static final byte MASK_HEXDIGIT = (byte)0x80; + + static final byte[] CHAR_TABLE; + + static { + CHAR_TABLE = new byte[256]; + + for ( int i = 0; i < 128; ++i ) { + final char c = (char) i; + CHAR_TABLE[i] = (byte)( ( Character.isDigit( c ) ? MASK_DIGIT : 0 ) | + ( Character.isLowerCase( c ) ? MASK_LOWERCASE : 0 ) | + ( Character.isUpperCase( c ) ? MASK_UPPERCASE : 0 ) | + ( ( c < ' ' || c == 0x7F ) ? MASK_CONTROL : 0 ) ); + if ( ( c >= 'a' && c <= 'f' ) || ( c >= 'A' && c <= 'F' ) || ( c >= '0' && c <= '9' ) ) { + CHAR_TABLE[i] |= MASK_HEXDIGIT; + } + if ( ( c >= '!' && c <= '/' ) || ( c >= ':' && c <= '@' ) || ( c >= '[' && c <= '`' ) || ( c >= '{' && c <= '~' ) ) { + CHAR_TABLE[i] |= MASK_PUNCT; + } + if ( ( CHAR_TABLE[i] & ( MASK_LOWERCASE | MASK_UPPERCASE ) ) != 0 ) { + CHAR_TABLE[i] |= MASK_ALPHA; + } + } + + CHAR_TABLE[' '] = MASK_SPACE; + CHAR_TABLE['\r'] |= MASK_SPACE; + CHAR_TABLE['\n'] |= MASK_SPACE; + CHAR_TABLE['\t'] |= MASK_SPACE; + CHAR_TABLE[0x0B /* '\v' */ ] |= MASK_SPACE; + CHAR_TABLE['\f'] |= MASK_SPACE; + }; + + static class MatchState { + int matchdepth; /* control for recursive depth (to avoid C stack overflow) */ + final LuaString s; + final LuaString p; + final Varargs args; + int level; + int[] cinit; + int[] clen; + + MatchState( Varargs args, LuaString s, LuaString pattern ) { + this.s = s; + this.p = pattern; + this.args = args; + this.level = 0; + this.cinit = new int[ MAX_CAPTURES ]; + this.clen = new int[ MAX_CAPTURES ]; + this.matchdepth = MAXCCALLS; + } + + void reset() { + level = 0; + this.matchdepth = MAXCCALLS; + } + + private void add_s( Buffer lbuf, LuaString news, int soff, int e ) { + int l = news.length(); + for ( int i = 0; i < l; ++i ) { + byte b = (byte) news.luaByte( i ); + if ( b != L_ESC ) { + lbuf.append( (byte) b ); + } else { + ++i; // skip ESC + b = (byte)(i < l ? news.luaByte( i ) : 0); + if ( !Character.isDigit( (char) b ) ) { + if (b != L_ESC) error( "invalid use of '" + (char)L_ESC + + "' in replacement string: after '" + (char)L_ESC + + "' must be '0'-'9' or '" + (char)L_ESC + + "', but found " + (i < l ? "symbol '" + (char)b + "' with code " + b + + " at pos " + (i + 1) : + "end of string")); + lbuf.append( b ); + } else if ( b == '0' ) { + lbuf.append( s.substring( soff, e ) ); + } else { + lbuf.append( push_onecapture( b - '1', soff, e ).strvalue() ); + } + } + } + } + + public void add_value( Buffer lbuf, int soffset, int end, LuaValue repl ) { + switch ( repl.type() ) { + case LuaValue.TSTRING: + case LuaValue.TNUMBER: + add_s( lbuf, repl.strvalue(), soffset, end ); + return; + + case LuaValue.TFUNCTION: + repl = repl.invoke( push_captures( true, soffset, end ) ).arg1(); + break; + + case LuaValue.TTABLE: + // Need to call push_onecapture here for the error checking + repl = repl.get( push_onecapture( 0, soffset, end ) ); + break; + + default: + error( "bad argument: string/function/table expected" ); + return; + } + + if ( !repl.toboolean() ) { + repl = s.substring( soffset, end ); + } else if ( ! repl.isstring() ) { + error( "invalid replacement value (a "+repl.typename()+")" ); + } + lbuf.append( repl.strvalue() ); + } + + Varargs push_captures( boolean wholeMatch, int soff, int end ) { + int nlevels = ( this.level == 0 && wholeMatch ) ? 1 : this.level; + switch ( nlevels ) { + case 0: return NONE; + case 1: return push_onecapture( 0, soff, end ); + } + LuaValue[] v = new LuaValue[nlevels]; + for ( int i = 0; i < nlevels; ++i ) + v[i] = push_onecapture( i, soff, end ); + return varargsOf(v); + } + + private LuaValue push_onecapture( int i, int soff, int end ) { + if ( i >= this.level ) { + if ( i == 0 ) { + return s.substring( soff, end ); + } else { + return error( "invalid capture index %" + (i + 1) ); + } + } else { + int l = clen[i]; + if ( l == CAP_UNFINISHED ) { + return error( "unfinished capture" ); + } + if ( l == CAP_POSITION ) { + return valueOf( cinit[i] + 1 ); + } else { + int begin = cinit[i]; + return s.substring( begin, begin + l ); + } + } + } + + private int check_capture( int l ) { + l -= '1'; + if ( l < 0 || l >= level || this.clen[l] == CAP_UNFINISHED ) { + error("invalid capture index %" + (l + 1)); + } + return l; + } + + private int capture_to_close() { + int level = this.level; + for ( level--; level >= 0; level-- ) + if ( clen[level] == CAP_UNFINISHED ) + return level; + error("invalid pattern capture"); + return 0; + } + + int classend( int poffset ) { + switch ( p.luaByte( poffset++ ) ) { + case L_ESC: + if ( poffset == p.length() ) { + error( "malformed pattern (ends with '%')" ); + } + return poffset + 1; + + case '[': + if ( poffset != p.length() && p.luaByte( poffset ) == '^' ) poffset++; + do { + if ( poffset == p.length() ) { + error( "malformed pattern (missing ']')" ); + } + if ( p.luaByte( poffset++ ) == L_ESC && poffset < p.length() ) + poffset++; /* skip escapes (e.g. '%]') */ + } while ( poffset == p.length() || p.luaByte( poffset ) != ']' ); + return poffset + 1; + default: + return poffset; + } + } + + static boolean match_class( int c, int cl ) { + final char lcl = Character.toLowerCase( (char) cl ); + int cdata = CHAR_TABLE[c]; + + boolean res; + switch ( lcl ) { + case 'a': res = ( cdata & MASK_ALPHA ) != 0; break; + case 'd': res = ( cdata & MASK_DIGIT ) != 0; break; + case 'l': res = ( cdata & MASK_LOWERCASE ) != 0; break; + case 'u': res = ( cdata & MASK_UPPERCASE ) != 0; break; + case 'c': res = ( cdata & MASK_CONTROL ) != 0; break; + case 'p': res = ( cdata & MASK_PUNCT ) != 0; break; + case 's': res = ( cdata & MASK_SPACE ) != 0; break; + case 'g': res = ( cdata & ( MASK_ALPHA | MASK_DIGIT | MASK_PUNCT ) ) != 0; break; + case 'w': res = ( cdata & ( MASK_ALPHA | MASK_DIGIT ) ) != 0; break; + case 'x': res = ( cdata & MASK_HEXDIGIT ) != 0; break; + case 'z': res = ( c == 0 ); break; /* deprecated option */ + default: return cl == c; + } + return ( lcl == cl ) ? res : !res; + } + + boolean matchbracketclass( int c, int poff, int ec ) { + boolean sig = true; + if ( p.luaByte( poff + 1 ) == '^' ) { + sig = false; + poff++; + } + while ( ++poff < ec ) { + if ( p.luaByte( poff ) == L_ESC ) { + poff++; + if ( match_class( c, p.luaByte( poff ) ) ) + return sig; + } + else if ( ( p.luaByte( poff + 1 ) == '-' ) && ( poff + 2 < ec ) ) { + poff += 2; + if ( p.luaByte( poff - 2 ) <= c && c <= p.luaByte( poff ) ) + return sig; + } + else if ( p.luaByte( poff ) == c ) return sig; + } + return !sig; + } + + boolean singlematch( int c, int poff, int ep ) { + switch ( p.luaByte( poff ) ) { + case '.': return true; + case L_ESC: return match_class( c, p.luaByte( poff + 1 ) ); + case '[': return matchbracketclass( c, poff, ep - 1 ); + default: return p.luaByte( poff ) == c; + } + } + + /** + * Perform pattern matching. If there is a match, returns offset into s + * where match ends, otherwise returns -1. + */ + int match( int soffset, int poffset ) { + if (matchdepth-- == 0) error("pattern too complex"); + try { + while ( true ) { + // Check if we are at the end of the pattern - + // equivalent to the '\0' case in the C version, but our pattern + // string is not NUL-terminated. + if ( poffset == p.length() ) + return soffset; + switch ( p.luaByte( poffset ) ) { + case '(': + if ( ++poffset < p.length() && p.luaByte( poffset ) == ')' ) + return start_capture( soffset, poffset + 1, CAP_POSITION ); + else + return start_capture( soffset, poffset, CAP_UNFINISHED ); + case ')': + return end_capture( soffset, poffset + 1 ); + case L_ESC: + if ( poffset + 1 == p.length() ) + error("malformed pattern (ends with '%')"); + switch ( p.luaByte( poffset + 1 ) ) { + case 'b': + soffset = matchbalance( soffset, poffset + 2 ); + if ( soffset == -1 ) return -1; + poffset += 4; + continue; + case 'f': { + poffset += 2; + if ( poffset == p.length() || p.luaByte( poffset ) != '[' ) { + error("missing '[' after '%f' in pattern"); + } + int ep = classend( poffset ); + int previous = ( soffset == 0 ) ? '\0' : s.luaByte( soffset - 1 ); + int next = ( soffset == s.length() ) ? '\0' : s.luaByte( soffset ); + if ( matchbracketclass( previous, poffset, ep - 1 ) || + !matchbracketclass( next, poffset, ep - 1 ) ) + return -1; + poffset = ep; + continue; + } + default: { + int c = p.luaByte( poffset + 1 ); + if ( Character.isDigit( (char) c ) ) { + soffset = match_capture( soffset, c ); + if ( soffset == -1 ) + return -1; + return match( soffset, poffset + 2 ); + } + } + } + case '$': + if ( poffset + 1 == p.length() ) + return ( soffset == s.length() ) ? soffset : -1; + } + int ep = classend( poffset ); + boolean m = soffset < s.length() && singlematch( s.luaByte( soffset ), poffset, ep ); + int pc = ( ep < p.length() ) ? p.luaByte( ep ) : '\0'; + + switch ( pc ) { + case '?': + int res; + if ( m && ( ( res = match( soffset + 1, ep + 1 ) ) != -1 ) ) + return res; + poffset = ep + 1; + continue; + case '*': + return max_expand( soffset, poffset, ep ); + case '+': + return ( m ? max_expand( soffset + 1, poffset, ep ) : -1 ); + case '-': + return min_expand( soffset, poffset, ep ); + default: + if ( !m ) + return -1; + soffset++; + poffset = ep; + continue; + } + } + } finally { + matchdepth++; + } + } + + int max_expand( int soff, int poff, int ep ) { + int i = 0; + while ( soff + i < s.length() && + singlematch( s.luaByte( soff + i ), poff, ep ) ) + i++; + while ( i >= 0 ) { + int res = match( soff + i, ep + 1 ); + if ( res != -1 ) + return res; + i--; + } + return -1; + } + + int min_expand( int soff, int poff, int ep ) { + for ( ;; ) { + int res = match( soff, ep + 1 ); + if ( res != -1 ) + return res; + else if ( soff < s.length() && singlematch( s.luaByte( soff ), poff, ep ) ) + soff++; + else return -1; + } + } + + int start_capture( int soff, int poff, int what ) { + int res; + int level = this.level; + if ( level >= MAX_CAPTURES ) { + error( "too many captures" ); + } + cinit[ level ] = soff; + clen[ level ] = what; + this.level = level + 1; + if ( ( res = match( soff, poff ) ) == -1 ) + this.level--; + return res; + } + + int end_capture( int soff, int poff ) { + int l = capture_to_close(); + int res; + clen[l] = soff - cinit[l]; + if ( ( res = match( soff, poff ) ) == -1 ) + clen[l] = CAP_UNFINISHED; + return res; + } + + int match_capture( int soff, int l ) { + l = check_capture( l ); + int len = clen[ l ]; + if ( ( s.length() - soff ) >= len && + LuaString.equals( s, cinit[l], s, soff, len ) ) + return soff + len; + else + return -1; + } + + int matchbalance( int soff, int poff ) { + final int plen = p.length(); + if ( poff == plen || poff + 1 == plen ) { + error( "malformed pattern (missing arguments to '%b')" ); + } + final int slen = s.length(); + if ( soff >= slen ) + return -1; + final int b = p.luaByte( poff ); + if ( s.luaByte( soff ) != b ) + return -1; + final int e = p.luaByte( poff + 1 ); + int cont = 1; + while ( ++soff < slen ) { + if ( s.luaByte( soff ) == e ) { + if ( --cont == 0 ) return soff + 1; + } + else if ( s.luaByte( soff ) == b ) cont++; + } + return -1; + } + } +} diff --git a/core/src/main/java/org/luaj/vm2/libs/TableLib.java b/core/src/main/java/org/luaj/vm2/libs/TableLib.java new file mode 100644 index 00000000..4c58b853 --- /dev/null +++ b/core/src/main/java/org/luaj/vm2/libs/TableLib.java @@ -0,0 +1,158 @@ +/******************************************************************************* +* Copyright (c) 2009 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.libs; + +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; + +/** + * Subclass of {@link LibFunction} which implements the lua standard {@code table} + * library. + * + *

+ * Typically, this library is included as part of a call to either + * {@link org.luaj.vm2.libs.jse.JsePlatform#standardGlobals()} or {@link org.luaj.vm2.libs.jme.JmePlatform#standardGlobals()} + *

 {@code
+ * Globals globals = JsePlatform.standardGlobals();
+ * System.out.println( globals.get("table").get("length").call( LuaValue.tableOf() ) );
+ * } 
+ *

+ * To instantiate and use it directly, + * link it into your globals table via {@link LuaValue#load(LuaValue)} using code such as: + *

 {@code
+ * Globals globals = new Globals();
+ * globals.load(new JseBaseLib());
+ * globals.load(new PackageLib());
+ * globals.load(new TableLib());
+ * System.out.println( globals.get("table").get("length").call( LuaValue.tableOf() ) );
+ * } 
+ *

+ * This has been implemented to match as closely as possible the behavior in the corresponding library in C. + * @see LibFunction + * @see org.luaj.vm2.libs.jse.JsePlatform + * @see org.luaj.vm2.libs.jme.JmePlatform + * @see Lua 5.2 Table Lib Reference + */ +public class TableLib extends TwoArgFunction { + + /** Perform one-time initialization on the library by creating a table + * containing the library functions, adding that table to the supplied environment, + * adding the table to package.loaded, and returning table as the return value. + * @param modname the module name supplied if this is loaded via 'require'. + * @param env the environment to load into, typically a Globals instance. + */ + public LuaValue call(LuaValue modname, LuaValue env) { + LuaTable table = new LuaTable(); + table.set("concat", new concat()); + table.set("insert", new insert()); + table.set("pack", new pack()); + table.set("remove", new remove()); + table.set("sort", new sort()); + table.set("unpack", new unpack()); + env.set("table", table); + if (!env.get("package").isnil()) env.get("package").get("loaded").set("table", table); + return NIL; + } + + // "concat" (table [, sep [, i [, j]]]) -> string + static class concat extends TableLibFunction { + public LuaValue call(LuaValue list) { + return list.checktable().concat(EMPTYSTRING,1,list.length()); + } + public LuaValue call(LuaValue list, LuaValue sep) { + return list.checktable().concat(sep.checkstring(),1,list.length()); + } + public LuaValue call(LuaValue list, LuaValue sep, LuaValue i) { + return list.checktable().concat(sep.checkstring(),i.checkint(),list.length()); + } + public LuaValue call(LuaValue list, LuaValue sep, LuaValue i, LuaValue j) { + return list.checktable().concat(sep.checkstring(),i.checkint(),j.checkint()); + } + } + + // "insert" (table, [pos,] value) + static class insert extends VarArgFunction { + public Varargs invoke(Varargs args) { + switch (args.narg()) { + case 2: { + LuaTable table = args.checktable(1); + table.insert(table.length()+1,args.arg(2)); + return NONE; + } + case 3: { + LuaTable table = args.checktable(1); + int pos = args.checkint(2); + int max = table.length() + 1; + if (pos < 1 || pos > max) argerror(2, "position out of bounds: " + pos + " not between 1 and " + max); + table.insert(pos, args.arg(3)); + return NONE; + } + default: { + return error("wrong number of arguments to 'table.insert': " + args.narg() + " (must be 2 or 3)"); + } + } + } + } + + // "pack" (...) -> table + static class pack extends VarArgFunction { + public Varargs invoke(Varargs args) { + LuaValue t = tableOf(args, 1); + t.set("n", args.narg()); + return t; + } + } + + // "remove" (table [, pos]) -> removed-ele + static class remove extends VarArgFunction { + public Varargs invoke(Varargs args) { + LuaTable table = args.checktable(1); + int size = table.length(); + int pos = args.optint(2, size); + if (pos != size && (pos < 1 || pos > size + 1)) { + argerror(2, "position out of bounds: " + pos + " not between 1 and " + (size + 1)); + } + return table.remove(pos); + } + } + + // "sort" (table [, comp]) + static class sort extends VarArgFunction { + public Varargs invoke(Varargs args) { + args.checktable(1).sort( + args.isnil(2)? NIL: args.checkfunction(2)); + return NONE; + } + } + + + // "unpack", // (list [,i [,j]]) -> result1, ... + static class unpack extends VarArgFunction { + public Varargs invoke(Varargs args) { + LuaTable t = args.checktable(1); + // do not waste resource for calc rawlen if arg3 is not nil + int len = args.arg(3).isnil() ? t.length() : 0; + return t.unpack(args.optint(2, 1), args.optint(3, len)); + } + } +} diff --git a/core/src/main/java/org/luaj/vm2/libs/TableLibFunction.java b/core/src/main/java/org/luaj/vm2/libs/TableLibFunction.java new file mode 100644 index 00000000..fd6ed393 --- /dev/null +++ b/core/src/main/java/org/luaj/vm2/libs/TableLibFunction.java @@ -0,0 +1,9 @@ +package org.luaj.vm2.libs; + +import org.luaj.vm2.LuaValue; + +class TableLibFunction extends LibFunction { + public LuaValue call() { + return argerror(1, "table expected, got no value"); + } +} diff --git a/core/src/main/java/org/luaj/vm2/libs/ThreeArgFunction.java b/core/src/main/java/org/luaj/vm2/libs/ThreeArgFunction.java new file mode 100644 index 00000000..4c8063b8 --- /dev/null +++ b/core/src/main/java/org/luaj/vm2/libs/ThreeArgFunction.java @@ -0,0 +1,73 @@ +/******************************************************************************* +* Copyright (c) 2009 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.libs; + +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; + +/** Abstract base class for Java function implementations that take two arguments and + * return one value. + *

+ * Subclasses need only implement {@link LuaValue#call(LuaValue,LuaValue,LuaValue)} to complete this class, + * simplifying development. + * All other uses of {@link #call()}, {@link #invoke(Varargs)},etc, + * are routed through this method by this class, + * dropping or extending arguments with {@code nil} values as required. + *

+ * If more or less than three arguments are required, + * or variable argument or variable return values, + * then use one of the related function + * {@link ZeroArgFunction}, {@link OneArgFunction}, {@link TwoArgFunction}, or {@link VarArgFunction}. + *

+ * See {@link LibFunction} for more information on implementation libraries and library functions. + * @see #call(LuaValue,LuaValue,LuaValue) + * @see LibFunction + * @see ZeroArgFunction + * @see OneArgFunction + * @see TwoArgFunction + * @see VarArgFunction + */ +abstract public class ThreeArgFunction extends LibFunction { + + abstract public LuaValue call(LuaValue arg1, LuaValue arg2, LuaValue arg3); + + /** Default constructor */ + public ThreeArgFunction() { + } + + public final LuaValue call() { + return call(NIL, NIL, NIL); + } + + public final LuaValue call(LuaValue arg) { + return call(arg, NIL, NIL); + } + + public LuaValue call(LuaValue arg1, LuaValue arg2) { + return call(arg1, arg2, NIL); + } + + public Varargs invoke(Varargs varargs) { + return call(varargs.arg1(),varargs.arg(2),varargs.arg(3)); + } + +} diff --git a/core/src/main/java/org/luaj/vm2/libs/TwoArgFunction.java b/core/src/main/java/org/luaj/vm2/libs/TwoArgFunction.java new file mode 100644 index 00000000..2f9b2c59 --- /dev/null +++ b/core/src/main/java/org/luaj/vm2/libs/TwoArgFunction.java @@ -0,0 +1,73 @@ +/******************************************************************************* +* Copyright (c) 2009 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.libs; + +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; + +/** Abstract base class for Java function implementations that take two arguments and + * return one value. + *

+ * Subclasses need only implement {@link LuaValue#call(LuaValue,LuaValue)} to complete this class, + * simplifying development. + * All other uses of {@link #call()}, {@link #invoke(Varargs)},etc, + * are routed through this method by this class, + * dropping or extending arguments with {@code nil} values as required. + *

+ * If more or less than two arguments are required, + * or variable argument or variable return values, + * then use one of the related function + * {@link ZeroArgFunction}, {@link OneArgFunction}, {@link ThreeArgFunction}, or {@link VarArgFunction}. + *

+ * See {@link LibFunction} for more information on implementation libraries and library functions. + * @see #call(LuaValue,LuaValue) + * @see LibFunction + * @see ZeroArgFunction + * @see OneArgFunction + * @see ThreeArgFunction + * @see VarArgFunction + */ +abstract public class TwoArgFunction extends LibFunction { + + abstract public LuaValue call(LuaValue arg1, LuaValue arg2); + + /** Default constructor */ + public TwoArgFunction() { + } + + public final LuaValue call() { + return call(NIL, NIL); + } + + public final LuaValue call(LuaValue arg) { + return call(arg, NIL); + } + + public LuaValue call(LuaValue arg1, LuaValue arg2, LuaValue arg3) { + return call(arg1, arg2); + } + + public Varargs invoke(Varargs varargs) { + return call(varargs.arg1(),varargs.arg(2)); + } + +} diff --git a/core/src/main/java/org/luaj/vm2/libs/VarArgFunction.java b/core/src/main/java/org/luaj/vm2/libs/VarArgFunction.java new file mode 100644 index 00000000..a1118ccf --- /dev/null +++ b/core/src/main/java/org/luaj/vm2/libs/VarArgFunction.java @@ -0,0 +1,83 @@ +/******************************************************************************* +* Copyright (c) 2009 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.libs; + +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; + +/** Abstract base class for Java function implementations that takes varaiable arguments and + * returns multiple return values. + *

+ * Subclasses need only implement {@link LuaValue#invoke(Varargs)} to complete this class, + * simplifying development. + * All other uses of {@link #call(LuaValue)}, {@link #invoke()},etc, + * are routed through this method by this class, + * converting arguments to {@link Varargs} and + * dropping or extending return values with {@code nil} values as required. + *

+ * If between one and three arguments are required, and only one return value is returned, + * {@link ZeroArgFunction}, {@link OneArgFunction}, {@link TwoArgFunction}, or {@link ThreeArgFunction}. + *

+ * See {@link LibFunction} for more information on implementation libraries and library functions. + * @see #invoke(Varargs) + * @see LibFunction + * @see ZeroArgFunction + * @see OneArgFunction + * @see TwoArgFunction + * @see ThreeArgFunction + */ +abstract public class VarArgFunction extends LibFunction { + + public VarArgFunction() { + } + + public LuaValue call() { + return invoke(NONE).arg1(); + } + + public LuaValue call(LuaValue arg) { + return invoke(arg).arg1(); + } + + public LuaValue call(LuaValue arg1, LuaValue arg2) { + return invoke(varargsOf(arg1,arg2)).arg1(); + } + + public LuaValue call(LuaValue arg1, LuaValue arg2, LuaValue arg3) { + return invoke(varargsOf(arg1,arg2,arg3)).arg1(); + } + + /** + * Subclass responsibility. + * May not have expected behavior for tail calls. + * Should not be used if: + * - function has a possibility of returning a TailcallVarargs + * @param args the arguments to the function call. + */ + public Varargs invoke(Varargs args) { + return onInvoke(args).eval(); + } + + public Varargs onInvoke(Varargs args) { + return invoke(args); + } +} diff --git a/core/src/main/java/org/luaj/vm2/libs/ZeroArgFunction.java b/core/src/main/java/org/luaj/vm2/libs/ZeroArgFunction.java new file mode 100644 index 00000000..f34f51e4 --- /dev/null +++ b/core/src/main/java/org/luaj/vm2/libs/ZeroArgFunction.java @@ -0,0 +1,70 @@ +/******************************************************************************* +* Copyright (c) 2009-2011 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.libs; + +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; + +/** Abstract base class for Java function implementations that take no arguments and + * return one value. + *

+ * Subclasses need only implement {@link LuaValue#call()} to complete this class, + * simplifying development. + * All other uses of {@link #call(LuaValue)}, {@link #invoke(Varargs)},etc, + * are routed through this method by this class. + *

+ * If one or more arguments are required, or variable argument or variable return values, + * then use one of the related function + * {@link OneArgFunction}, {@link TwoArgFunction}, {@link ThreeArgFunction}, or {@link VarArgFunction}. + *

+ * See {@link LibFunction} for more information on implementation libraries and library functions. + * @see #call() + * @see LibFunction + * @see OneArgFunction + * @see TwoArgFunction + * @see ThreeArgFunction + * @see VarArgFunction + */ +abstract public class ZeroArgFunction extends LibFunction { + + abstract public LuaValue call(); + + /** Default constructor */ + public ZeroArgFunction() { + } + + public LuaValue call(LuaValue arg) { + return call(); + } + + public LuaValue call(LuaValue arg1, LuaValue arg2) { + return call(); + } + + public LuaValue call(LuaValue arg1, LuaValue arg2, LuaValue arg3) { + return call(); + } + + public Varargs invoke(Varargs varargs) { + return call(); + } +} diff --git a/jme/src/main/java/org/luaj/vm2/libs/jme/JmeIoLib.java b/jme/src/main/java/org/luaj/vm2/libs/jme/JmeIoLib.java new file mode 100644 index 00000000..8c60a940 --- /dev/null +++ b/jme/src/main/java/org/luaj/vm2/libs/jme/JmeIoLib.java @@ -0,0 +1,230 @@ +/******************************************************************************* +* Copyright (c) 2009-2011 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.libs.jme; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import javax.microedition.io.Connector; +import javax.microedition.io.StreamConnection; + +import org.luaj.vm2.Globals; +import org.luaj.vm2.LuaString; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.libs.IoLib; +import org.luaj.vm2.libs.LibFunction; + +/** + * Subclass of {@link IoLib} and therefore {@link LibFunction} which implements the lua standard {@code io} + * library for the JSE platform. + *

+ * The implementation of the is based on CLDC 1.0 and StreamConnection. + * However, seek is not supported. + *

+ * Typically, this library is included as part of a call to + * {@link JmePlatform#standardGlobals()} + *

 {@code
+ * Globals globals = JmePlatform.standardGlobals();
+ * globals.get("io").get("write").call(LuaValue.valueOf("hello, world\n"));
+ * } 
+ *

+ * For special cases where the smallest possible footprint is desired, + * a minimal set of libraries could be loaded + * directly via {@link Globals#load(LuaValue)} using code such as: + *

 {@code
+ * Globals globals = new Globals();
+ * globals.load(new JmeBaseLib());
+ * globals.load(new PackageLib());
+ * globals.load(new JmeIoLib());
+ * globals.get("io").get("write").call(LuaValue.valueOf("hello, world\n"));
+ * } 
+ *

However, other libraries such as MathLib are not loaded in this case. + *

+ * This has been implemented to match as closely as possible the behavior in the corresponding library in C. + * @see LibFunction + * @see org.luaj.vm2.libs.jse.JsePlatform + * @see JmePlatform + * @see IoLib + * @see org.luaj.vm2.libs.jse.JseIoLib + * @see Lua 5.2 I/O Lib Reference + */ +public class JmeIoLib extends IoLib { + + protected File wrapStdin() throws IOException { + return new FileImpl(globals.STDIN); + } + + protected File wrapStdout() throws IOException { + return new FileImpl(globals.STDOUT); + } + + protected File wrapStderr() throws IOException { + return new FileImpl(globals.STDERR); + } + + protected File openFile( String filename, boolean readMode, boolean appendMode, boolean updateMode, boolean binaryMode ) throws IOException { + String url = "file:///" + filename; + int mode = readMode? Connector.READ: Connector.READ_WRITE; + StreamConnection conn = (StreamConnection) Connector.open( url, mode ); + File f = readMode? + new FileImpl(conn, conn.openInputStream(), null): + new FileImpl(conn, conn.openInputStream(), conn.openOutputStream()); + /* + if ( appendMode ) { + f.seek("end",0); + } else { + if ( ! readMode ) + conn.truncate(0); + } + */ + return f; + } + + private static void notimplemented() throws IOException { + throw new IOException("not implemented"); + } + + protected File openProgram(String prog, String mode) throws IOException { + notimplemented(); + return null; + } + + protected File tmpFile() throws IOException { + notimplemented(); + return null; + } + + private final class FileImpl extends File { + private final StreamConnection conn; + private final InputStream is; + private final OutputStream os; + private boolean closed = false; + private boolean nobuffer = false; + private int lookahead = -1; + private FileImpl( StreamConnection conn, InputStream is, OutputStream os ) { + this.conn = conn; + this.is = is; + this.os = os; + } + private FileImpl( InputStream i ) { + this( null, i, null ); + } + private FileImpl( OutputStream o ) { + this( null, null, o ); + } + public String tojstring() { + return "file ("+this.hashCode()+")"; + } + public boolean isstdfile() { + return conn == null; + } + public void close() throws IOException { + closed = true; + if ( conn != null ) { + conn.close(); + } + } + public void flush() throws IOException { + if ( os != null ) + os.flush(); + } + public void write(LuaString s) throws IOException { + if ( os != null ) + os.write( s.m_bytes, s.m_offset, s.m_length ); + else + notimplemented(); + if ( nobuffer ) + flush(); + } + public boolean isclosed() { + return closed; + } + public int seek(String option, int pos) throws IOException { + /* + if ( conn != null ) { + if ( "set".equals(option) ) { + conn.seek(pos); + return (int) conn.getFilePointer(); + } else if ( "end".equals(option) ) { + conn.seek(conn.length()+1+pos); + return (int) conn.length()+1; + } else { + conn.seek(conn.getFilePointer()+pos); + return (int) conn.getFilePointer(); + } + } + */ + notimplemented(); + return 0; + } + public void setvbuf(String mode, int size) { + nobuffer = "no".equals(mode); + } + + // get length remaining to read + public int remaining() throws IOException { + return -1; + } + + // peek ahead one character + public int peek() throws IOException { + if ( lookahead < 0 ) + lookahead = is.read(); + return lookahead; + } + + // return char if read, -1 if eof, throw IOException on other exception + public int read() throws IOException { + if ( lookahead >= 0 ) { + int c = lookahead; + lookahead = -1; + return c; + } + if ( is != null ) + return is.read(); + notimplemented(); + return 0; + } + + // return number of bytes read if positive, -1 if eof, throws IOException + public int read(byte[] bytes, int offset, int length) throws IOException { + int n,i=0; + if (is!=null) { + if ( length > 0 && lookahead >= 0 ) { + bytes[offset] = (byte) lookahead; + lookahead = -1; + i += 1; + } + for ( ; i 0 ? i : -1 ); + i += n; + } + } else { + notimplemented(); + } + return length; + } + } +} diff --git a/jme/src/main/java/org/luaj/vm2/libs/jme/JmePlatform.java b/jme/src/main/java/org/luaj/vm2/libs/jme/JmePlatform.java new file mode 100644 index 00000000..8619e068 --- /dev/null +++ b/jme/src/main/java/org/luaj/vm2/libs/jme/JmePlatform.java @@ -0,0 +1,133 @@ +/******************************************************************************* + * Copyright (c) 2009 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.libs.jme; + +import org.luaj.vm2.Globals; +import org.luaj.vm2.LoadState; +import org.luaj.vm2.compiler.LuaC; +import org.luaj.vm2.libs.BaseLib; +import org.luaj.vm2.libs.Bit32Lib; +import org.luaj.vm2.libs.CoroutineLib; +import org.luaj.vm2.libs.DebugLib; +import org.luaj.vm2.libs.MathLib; +import org.luaj.vm2.libs.OsLib; +import org.luaj.vm2.libs.PackageLib; +import org.luaj.vm2.libs.ResourceFinder; +import org.luaj.vm2.libs.StringLib; +import org.luaj.vm2.libs.TableLib; + +/** The {@link JmePlatform} class is a convenience class to standardize + * how globals tables are initialized for the JME platform. + *

+ * The JME platform, being limited, cannot implement all libraries in all aspects. The main limitations are + *

+ *

+ * It is used to allocate either a set of standard globals using + * {@link #standardGlobals()} or debug globals using {@link #debugGlobals()} + *

+ * A simple example of initializing globals and using them from Java is: + *

 {@code
+ * Globals global = JmePlatform.standardGlobals();
+ * global.get("print").call(LuaValue.valueOf("hello, world"));
+ * } 
+ *

+ * Once globals are created, a simple way to load and run a script is: + *

 {@code
+ * LoadState.load( getClass().getResourceAsStream("main.lua"), "main.lua", globals ).call();
+ * } 
+ *

+ * although {@code require} could also be used: + *

 {@code
+ * globals.get("require").call(LuaValue.valueOf("main"));
+ * } 
+ * For this to succeed, the file "main.lua" must be a resource in the class path. + * See {@link BaseLib} for details on finding scripts using {@link ResourceFinder}. + *

+ * The standard globals will contain all standard libraries in their JME flavors: + *

+ * In addition, the {@link LuaC} compiler is installed so lua files may be loaded in their source form. + *

+ * The debug globals are simply the standard globals plus the {@code debug} library {@link DebugLib}. + *

+ *

+ * The class ensures that initialization is done in the correct order. + * + * @see Globals + * @see org.luaj.vm2.libs.jse.JsePlatform + */ +public class JmePlatform { + + /** + * Create a standard set of globals for JME including all the libraries. + * + * @return Table of globals initialized with the standard JME libraries + * @see #debugGlobals() + * @see org.luaj.vm2.libs.jse.JsePlatform + * @see JmePlatform + */ + public static Globals standardGlobals() { + Globals globals = new Globals(); + globals.load(new BaseLib()); + globals.load(new PackageLib()); + globals.load(new Bit32Lib()); + globals.load(new OsLib()); + globals.load(new MathLib()); + globals.load(new TableLib()); + globals.load(new StringLib()); + globals.load(new CoroutineLib()); + globals.load(new JmeIoLib()); + LoadState.install(globals); + LuaC.install(globals); + return globals; + } + + /** Create standard globals including the {@link DebugLib} library. + * + * @return Table of globals initialized with the standard JSE and debug libraries + * @see #standardGlobals() + * @see org.luaj.vm2.libs.jse.JsePlatform + * @see JmePlatform + * @see DebugLib + */ + public static Globals debugGlobals() { + Globals globals = standardGlobals(); + globals.load(new DebugLib()); + return globals; + } +} diff --git a/jse/src/main/java/org/luaj/vm2/libs/jse/CoerceJavaToLua.java b/jse/src/main/java/org/luaj/vm2/libs/jse/CoerceJavaToLua.java new file mode 100644 index 00000000..66f7558c --- /dev/null +++ b/jse/src/main/java/org/luaj/vm2/libs/jse/CoerceJavaToLua.java @@ -0,0 +1,196 @@ +/******************************************************************************* +* Copyright (c) 2009-2011 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.libs.jse; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.luaj.vm2.LuaDouble; +import org.luaj.vm2.LuaInteger; +import org.luaj.vm2.LuaString; +import org.luaj.vm2.LuaUserdata; +import org.luaj.vm2.LuaValue; + +/** + * Helper class to coerce values from Java to lua within the luajava library. + *

+ * This class is primarily used by the {@link LuajavaLib}, + * but can also be used directly when working with Java/lua bindings. + *

+ * To coerce scalar types, the various, generally the {@code valueOf(type)} methods + * on {@link LuaValue} may be used: + *

+ *

+ * To coerce arrays of objects and lists, the {@code listOf(..)} and {@code tableOf(...)} methods + * on {@link LuaValue} may be used: + *

+ * The method {@link CoerceJavaToLua#coerce(Object)} looks as the type and dimesioning + * of the argument and tries to guess the best fit for corrsponding lua scalar, + * table, or table of tables. + * + * @see CoerceJavaToLua#coerce(Object) + * @see LuajavaLib + */ +public class CoerceJavaToLua { + + static interface Coercion { + public LuaValue coerce( Object javaValue ); + }; + + private static final class BoolCoercion implements Coercion { + public LuaValue coerce( Object javaValue ) { + Boolean b = (Boolean) javaValue; + return b.booleanValue()? LuaValue.TRUE: LuaValue.FALSE; + } + } + + private static final class IntCoercion implements Coercion { + public LuaValue coerce( Object javaValue ) { + Number n = (Number) javaValue; + return LuaInteger.valueOf( n.intValue() ); + } + } + + private static final class CharCoercion implements Coercion { + public LuaValue coerce( Object javaValue ) { + Character c = (Character) javaValue; + return LuaInteger.valueOf( c.charValue() ); + } + } + + private static final class DoubleCoercion implements Coercion { + public LuaValue coerce( Object javaValue ) { + Number n = (Number) javaValue; + return LuaDouble.valueOf( n.doubleValue() ); + } + } + + private static final class StringCoercion implements Coercion { + public LuaValue coerce( Object javaValue ) { + return LuaString.valueOf( javaValue.toString() ); + } + } + + private static final class BytesCoercion implements Coercion { + public LuaValue coerce( Object javaValue ) { + return LuaValue.valueOf((byte[]) javaValue); + } + } + + private static final class ClassCoercion implements Coercion { + public LuaValue coerce( Object javaValue ) { + return JavaClass.forClass((Class) javaValue); + } + } + + private static final class InstanceCoercion implements Coercion { + public LuaValue coerce(Object javaValue) { + return new JavaInstance(javaValue); + } + } + + private static final class ArrayCoercion implements Coercion { + public LuaValue coerce(Object javaValue) { + // should be userdata? + return new JavaArray(javaValue); + } + } + + private static final class LuaCoercion implements Coercion { + public LuaValue coerce( Object javaValue ) { + return (LuaValue) javaValue; + } + } + + + static final Map COERCIONS = Collections.synchronizedMap(new HashMap()); + + static { + Coercion boolCoercion = new BoolCoercion() ; + Coercion intCoercion = new IntCoercion() ; + Coercion charCoercion = new CharCoercion() ; + Coercion doubleCoercion = new DoubleCoercion() ; + Coercion stringCoercion = new StringCoercion() ; + Coercion bytesCoercion = new BytesCoercion() ; + Coercion classCoercion = new ClassCoercion() ; + COERCIONS.put( Boolean.class, boolCoercion ); + COERCIONS.put( Byte.class, intCoercion ); + COERCIONS.put( Character.class, charCoercion ); + COERCIONS.put( Short.class, intCoercion ); + COERCIONS.put( Integer.class, intCoercion ); + COERCIONS.put( Long.class, doubleCoercion ); + COERCIONS.put( Float.class, doubleCoercion ); + COERCIONS.put( Double.class, doubleCoercion ); + COERCIONS.put( String.class, stringCoercion ); + COERCIONS.put( byte[].class, bytesCoercion ); + COERCIONS.put( Class.class, classCoercion ); + } + + /** + * Coerse a Java object to a corresponding lua value. + *

+ * Integral types {@code boolean}, {@code byte}, {@code char}, and {@code int} + * will become {@link LuaInteger}; + * {@code long}, {@code float}, and {@code double} will become {@link LuaDouble}; + * {@code String} and {@code byte[]} will become {@link LuaString}; + * types inheriting from {@link LuaValue} will be returned without coercion; + * other types will become {@link LuaUserdata}. + * @param o Java object needing conversion + * @return {@link LuaValue} corresponding to the supplied Java value. + * @see LuaValue + * @see LuaInteger + * @see LuaDouble + * @see LuaString + * @see LuaUserdata + */ + public static LuaValue coerce(Object o) { + if ( o == null ) + return LuaValue.NIL; + Class clazz = o.getClass(); + Coercion c = (Coercion) COERCIONS.get( clazz ); + if ( c == null ) { + c = clazz.isArray()? arrayCoercion: + o instanceof LuaValue ? luaCoercion: + instanceCoercion; + COERCIONS.put( clazz, c ); + } + return c.coerce(o); + } + + static final Coercion instanceCoercion = new InstanceCoercion(); + + static final Coercion arrayCoercion = new ArrayCoercion(); + + static final Coercion luaCoercion = new LuaCoercion() ; +} diff --git a/jse/src/main/java/org/luaj/vm2/libs/jse/CoerceLuaToJava.java b/jse/src/main/java/org/luaj/vm2/libs/jse/CoerceLuaToJava.java new file mode 100644 index 00000000..b3380152 --- /dev/null +++ b/jse/src/main/java/org/luaj/vm2/libs/jse/CoerceLuaToJava.java @@ -0,0 +1,372 @@ +/******************************************************************************* + * Copyright (c) 2009-2011 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.libs.jse; + +import java.lang.reflect.Array; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.luaj.vm2.LuaString; +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; + +/** + * Helper class to coerce values from lua to Java within the luajava library. + *

+ * This class is primarily used by the {@link LuajavaLib}, + * but can also be used directly when working with Java/lua bindings. + *

+ * To coerce to specific Java values, generally the {@code toType()} methods + * on {@link LuaValue} may be used: + *

+ *

+ * For data in lua tables, the various methods on {@link LuaTable} can be used directly + * to convert data to something more useful. + * + * @see LuajavaLib + * @see CoerceJavaToLua + */ +public class CoerceLuaToJava { + + static int SCORE_NULL_VALUE = 0x10; + static int SCORE_WRONG_TYPE = 0x100; + static int SCORE_UNCOERCIBLE = 0x10000; + + static interface Coercion { + public int score( LuaValue value ); + public Object coerce( LuaValue value ); + }; + + /** + * Coerce a LuaValue value to a specified java class + * @param value LuaValue to coerce + * @param clazz Class to coerce into + * @return Object of type clazz (or a subclass) with the corresponding value. + */ + public static Object coerce(LuaValue value, Class clazz) { + return getCoercion(clazz).coerce(value); + } + + static final Map COERCIONS = Collections.synchronizedMap(new HashMap()); + + static final class BoolCoercion implements Coercion { + public String toString() { + return "BoolCoercion()"; + } + public int score( LuaValue value ) { + switch ( value.type() ) { + case LuaValue.TBOOLEAN: + return 0; + } + return 1; + } + + public Object coerce(LuaValue value) { + return value.toboolean()? Boolean.TRUE: Boolean.FALSE; + } + } + + static final class NumericCoercion implements Coercion { + static final int TARGET_TYPE_BYTE = 0; + static final int TARGET_TYPE_CHAR = 1; + static final int TARGET_TYPE_SHORT = 2; + static final int TARGET_TYPE_INT = 3; + static final int TARGET_TYPE_LONG = 4; + static final int TARGET_TYPE_FLOAT = 5; + static final int TARGET_TYPE_DOUBLE = 6; + static final String[] TYPE_NAMES = { "byte", "char", "short", "int", "long", "float", "double" }; + final int targetType; + public String toString() { + return "NumericCoercion("+TYPE_NAMES[targetType]+")"; + } + NumericCoercion(int targetType) { + this.targetType = targetType; + } + public int score( LuaValue value ) { + int fromStringPenalty = 0; + if ( value.type() == LuaValue.TSTRING ) { + value = value.tonumber(); + if ( value.isnil() ) { + return SCORE_UNCOERCIBLE; + } + fromStringPenalty = 4; + } + if ( value.isint() ) { + switch ( targetType ) { + case TARGET_TYPE_BYTE: { + int i = value.toint(); + return fromStringPenalty + ((i==(byte)i)? 0: SCORE_WRONG_TYPE); + } + case TARGET_TYPE_CHAR: { + int i = value.toint(); + return fromStringPenalty + ((i==(byte)i)? 1: (i==(char)i)? 0: SCORE_WRONG_TYPE); + } + case TARGET_TYPE_SHORT: { + int i = value.toint(); + return fromStringPenalty + + ((i==(byte)i)? 1: (i==(short)i)? 0: SCORE_WRONG_TYPE); + } + case TARGET_TYPE_INT: { + int i = value.toint(); + return fromStringPenalty + + ((i==(byte)i)? 2: ((i==(char)i) || (i==(short)i))? 1: 0); + } + case TARGET_TYPE_FLOAT: return fromStringPenalty + 1; + case TARGET_TYPE_LONG: return fromStringPenalty + 1; + case TARGET_TYPE_DOUBLE: return fromStringPenalty + 2; + default: return SCORE_WRONG_TYPE; + } + } else if ( value.isnumber() ) { + switch ( targetType ) { + case TARGET_TYPE_BYTE: return SCORE_WRONG_TYPE; + case TARGET_TYPE_CHAR: return SCORE_WRONG_TYPE; + case TARGET_TYPE_SHORT: return SCORE_WRONG_TYPE; + case TARGET_TYPE_INT: return SCORE_WRONG_TYPE; + case TARGET_TYPE_LONG: { + double d = value.todouble(); + return fromStringPenalty + ((d==(long)d)? 0: SCORE_WRONG_TYPE); + } + case TARGET_TYPE_FLOAT: { + double d = value.todouble(); + return fromStringPenalty + ((d==(float)d)? 0: SCORE_WRONG_TYPE); + } + case TARGET_TYPE_DOUBLE: { + double d = value.todouble(); + return fromStringPenalty + (((d==(long)d) || (d==(float)d))? 1: 0); + } + default: return SCORE_WRONG_TYPE; + } + } else { + return SCORE_UNCOERCIBLE; + } + } + + public Object coerce(LuaValue value) { + switch ( targetType ) { + case TARGET_TYPE_BYTE: return Byte.valueOf( (byte) value.toint() ); + case TARGET_TYPE_CHAR: return Character.valueOf( (char) value.toint() ); + case TARGET_TYPE_SHORT: return Short.valueOf( (short) value.toint() ); + case TARGET_TYPE_INT: return Integer.valueOf( (int) value.toint() ); + case TARGET_TYPE_LONG: return Long.valueOf( (long) value.todouble() ); + case TARGET_TYPE_FLOAT: return Float.valueOf( (float) value.todouble() ); + case TARGET_TYPE_DOUBLE: return Double.valueOf( (double) value.todouble() ); + default: return null; + } + } + } + + static final class StringCoercion implements Coercion { + public static final int TARGET_TYPE_STRING = 0; + public static final int TARGET_TYPE_BYTES = 1; + final int targetType; + public StringCoercion(int targetType) { + this.targetType = targetType; + } + public String toString() { + return "StringCoercion("+(targetType==TARGET_TYPE_STRING? "String": "byte[]")+")"; + } + public int score(LuaValue value) { + switch ( value.type() ) { + case LuaValue.TSTRING: + return value.checkstring().isValidUtf8()? + (targetType==TARGET_TYPE_STRING? 0: 1): + (targetType==TARGET_TYPE_BYTES? 0: SCORE_WRONG_TYPE); + case LuaValue.TNIL: + return SCORE_NULL_VALUE; + default: + return targetType == TARGET_TYPE_STRING? SCORE_WRONG_TYPE: SCORE_UNCOERCIBLE; + } + } + public Object coerce(LuaValue value) { + if ( value.isnil() ) + return null; + if ( targetType == TARGET_TYPE_STRING ) + return value.tojstring(); + LuaString s = value.checkstring(); + byte[] b = new byte[s.m_length]; + s.copyInto(0, b, 0, b.length); + return b; + } + } + + static final class ArrayCoercion implements Coercion { + final Class componentType; + final Coercion componentCoercion; + public ArrayCoercion(Class componentType) { + this.componentType = componentType; + this.componentCoercion = getCoercion(componentType); + } + public String toString() { + return "ArrayCoercion("+componentType.getName()+")"; + } + public int score(LuaValue value) { + switch ( value.type() ) { + case LuaValue.TTABLE: + return value.length()==0? 0: componentCoercion.score( value.get(1) ); + case LuaValue.TUSERDATA: + return inheritanceLevels( componentType, value.touserdata().getClass().getComponentType() ); + case LuaValue.TNIL: + return SCORE_NULL_VALUE; + default: + return SCORE_UNCOERCIBLE; + } + } + public Object coerce(LuaValue value) { + switch ( value.type() ) { + case LuaValue.TTABLE: { + int n = value.length(); + Object a = Array.newInstance(componentType, n); + for ( int i=0; i + * Can get elements by their integer key index, as well as the length. + *

+ * This class is not used directly. + * It is returned by calls to {@link CoerceJavaToLua#coerce(Object)} + * when an array is supplied. + * @see CoerceJavaToLua + * @see CoerceLuaToJava + */ +class JavaArray extends LuaUserdata { + + private static final class LenFunction extends OneArgFunction { + public LuaValue call(LuaValue u) { + return LuaValue.valueOf(Array.getLength(((LuaUserdata)u).m_instance)); + } + } + + static final LuaValue LENGTH = valueOf("length"); + + static final LuaTable array_metatable; + static { + array_metatable = new LuaTable(); + array_metatable.rawset(LuaValue.LEN, new LenFunction()); + } + + JavaArray(Object instance) { + super(instance); + setmetatable(array_metatable); + } + + public LuaValue get(LuaValue key) { + if ( key.equals(LENGTH) ) + return valueOf(Array.getLength(m_instance)); + if ( key.isint() ) { + int i = key.toint() - 1; + return i>=0 && i=0 && i + * Will respond to get() and set() by returning field values, or java methods. + *

+ * This class is not used directly. + * It is returned by calls to {@link CoerceJavaToLua#coerce(Object)} + * when a Class is supplied. + * @see CoerceJavaToLua + * @see CoerceLuaToJava + */ +class JavaClass extends JavaInstance implements CoerceJavaToLua.Coercion { + + static final Map classes = Collections.synchronizedMap(new HashMap()); + + static final LuaValue NEW = valueOf("new"); + + Map fields; + Map methods; + Map innerclasses; + + static JavaClass forClass(Class c) { + JavaClass j = (JavaClass) classes.get(c); + if ( j == null ) + classes.put( c, j = new JavaClass(c) ); + return j; + } + + JavaClass(Class c) { + super(c); + this.jclass = this; + } + + public LuaValue coerce(Object javaValue) { + return this; + } + + Field getField(LuaValue key) { + if ( fields == null ) { + Map m = new HashMap(); + Field[] f = ((Class)m_instance).getFields(); + for ( int i=0; i + * May be called with arguments to return a JavaInstance + * created by calling the constructor. + *

+ * This class is not used directly. + * It is returned by calls to {@link JavaClass#new(LuaValue key)} + * when the value of key is "new". + * @see CoerceJavaToLua + * @see CoerceLuaToJava + */ +class JavaConstructor extends JavaMember { + + static final Map constructors = Collections.synchronizedMap(new HashMap()); + + static JavaConstructor forConstructor(Constructor c) { + JavaConstructor j = (JavaConstructor) constructors.get(c); + if ( j == null ) + constructors.put( c, j = new JavaConstructor(c) ); + return j; + } + + public static LuaValue forConstructors(JavaConstructor[] array) { + return new Overload(array); + } + + final Constructor constructor; + + private JavaConstructor(Constructor c) { + super( c.getParameterTypes(), c.getModifiers() ); + this.constructor = c; + } + + public Varargs invoke(Varargs args) { + Object[] a = convertArgs(args); + try { + return CoerceJavaToLua.coerce( constructor.newInstance(a) ); + } catch (InvocationTargetException e) { + throw new LuaError(e.getTargetException()); + } catch (Exception e) { + return LuaValue.error("coercion error "+e); + } + } + + /** + * LuaValue that represents an overloaded Java constructor. + *

+ * On invocation, will pick the best method from the list, and invoke it. + *

+ * This class is not used directly. + * It is returned by calls to calls to {@link JavaClass#get(LuaValue key)} + * when key is "new" and there is more than one public constructor. + */ + static class Overload extends VarArgFunction { + final JavaConstructor[] constructors; + public Overload(JavaConstructor[] c) { + this.constructors = c; + } + + public Varargs invoke(Varargs args) { + JavaConstructor best = null; + int score = CoerceLuaToJava.SCORE_UNCOERCIBLE; + for ( int i=0; i + * Will respond to get() and set() by returning field values or methods. + *

+ * This class is not used directly. + * It is returned by calls to {@link CoerceJavaToLua#coerce(Object)} + * when a subclass of Object is supplied. + * @see CoerceJavaToLua + * @see CoerceLuaToJava + */ +class JavaInstance extends LuaUserdata { + + JavaClass jclass; + + JavaInstance(Object instance) { + super(instance); + } + + public LuaValue get(LuaValue key) { + if ( jclass == null ) + jclass = JavaClass.forClass(m_instance.getClass()); + Field f = jclass.getField(key); + if ( f != null ) + try { + return CoerceJavaToLua.coerce(f.get(m_instance)); + } catch (Exception e) { + throw new LuaError(e); + } + LuaValue m = jclass.getMethod(key); + if ( m != null ) + return m; + Class c = jclass.getInnerClass(key); + if ( c != null ) + return JavaClass.forClass(c); + return super.get(key); + } + + public void set(LuaValue key, LuaValue value) { + if ( jclass == null ) + jclass = JavaClass.forClass(m_instance.getClass()); + Field f = jclass.getField(key); + if ( f != null ) + try { + f.set(m_instance, CoerceLuaToJava.coerce(value, f.getType())); + return; + } catch (Exception e) { + throw new LuaError(e); + } + super.set(key, value); + } + +} diff --git a/jse/src/main/java/org/luaj/vm2/libs/jse/JavaMember.java b/jse/src/main/java/org/luaj/vm2/libs/jse/JavaMember.java new file mode 100644 index 00000000..cc03f754 --- /dev/null +++ b/jse/src/main/java/org/luaj/vm2/libs/jse/JavaMember.java @@ -0,0 +1,84 @@ +/******************************************************************************* +* Copyright (c) 2011 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.libs.jse; + +import org.luaj.vm2.Varargs; +import org.luaj.vm2.libs.VarArgFunction; +import org.luaj.vm2.libs.jse.CoerceLuaToJava.Coercion; + +/** + * Java method or constructor. + *

+ * Primarily handles argument coercion for parameter lists including scoring of compatibility and + * java varargs handling. + *

+ * This class is not used directly. + * It is an abstract base class for {@link JavaConstructor} and {@link JavaMethod}. + * @see JavaConstructor + * @see JavaMethod + * @see CoerceJavaToLua + * @see CoerceLuaToJava + */ +abstract +class JavaMember extends VarArgFunction { + + static final int METHOD_MODIFIERS_VARARGS = 0x80; + + final Coercion[] fixedargs; + final Coercion varargs; + + protected JavaMember(Class[] params, int modifiers) { + boolean isvarargs = ((modifiers & METHOD_MODIFIERS_VARARGS) != 0); + fixedargs = new Coercion[isvarargs? params.length-1: params.length]; + for ( int i=0; ifixedargs.length? CoerceLuaToJava.SCORE_WRONG_TYPE * (n-fixedargs.length): 0; + for ( int j=0; j + * Can be invoked via call(LuaValue...) and related methods. + *

+ * This class is not used directly. + * It is returned by calls to calls to {@link JavaInstance#get(LuaValue key)} + * when a method is named. + * @see CoerceJavaToLua + * @see CoerceLuaToJava + */ +class JavaMethod extends JavaMember { + + static final Map methods = Collections.synchronizedMap(new HashMap()); + + static JavaMethod forMethod(Method m) { + JavaMethod j = (JavaMethod) methods.get(m); + if ( j == null ) + methods.put( m, j = new JavaMethod(m) ); + return j; + } + + static LuaFunction forMethods(JavaMethod[] m) { + return new Overload(m); + } + + final Method method; + + private JavaMethod(Method m) { + super( m.getParameterTypes(), m.getModifiers() ); + this.method = m; + try { + if (!m.isAccessible()) + m.setAccessible(true); + } catch (SecurityException s) { + } + } + + public LuaValue call() { + return error("method cannot be called without instance"); + } + + public LuaValue call(LuaValue arg) { + return invokeMethod(arg.checkuserdata(), LuaValue.NONE); + } + + public LuaValue call(LuaValue arg1, LuaValue arg2) { + return invokeMethod(arg1.checkuserdata(), arg2); + } + + public LuaValue call(LuaValue arg1, LuaValue arg2, LuaValue arg3) { + return invokeMethod(arg1.checkuserdata(), LuaValue.varargsOf(arg2, arg3)); + } + + public Varargs invoke(Varargs args) { + return invokeMethod(args.checkuserdata(1), args.subargs(2)); + } + + LuaValue invokeMethod(Object instance, Varargs args) { + Object[] a = convertArgs(args); + try { + return CoerceJavaToLua.coerce( method.invoke(instance, a) ); + } catch (InvocationTargetException e) { + throw new LuaError(e.getTargetException()); + } catch (Exception e) { + return LuaValue.error("coercion error "+e); + } + } + + /** + * LuaValue that represents an overloaded Java method. + *

+ * On invocation, will pick the best method from the list, and invoke it. + *

+ * This class is not used directly. + * It is returned by calls to calls to {@link JavaInstance#get(LuaValue key)} + * when an overloaded method is named. + */ + static class Overload extends LuaFunction { + + final JavaMethod[] methods; + + Overload(JavaMethod[] methods) { + this.methods = methods; + } + + public LuaValue call() { + return error("method cannot be called without instance"); + } + + public LuaValue call(LuaValue arg) { + return invokeBestMethod(arg.checkuserdata(), LuaValue.NONE); + } + + public LuaValue call(LuaValue arg1, LuaValue arg2) { + return invokeBestMethod(arg1.checkuserdata(), arg2); + } + + public LuaValue call(LuaValue arg1, LuaValue arg2, LuaValue arg3) { + return invokeBestMethod(arg1.checkuserdata(), LuaValue.varargsOf(arg2, arg3)); + } + + public Varargs invoke(Varargs args) { + return invokeBestMethod(args.checkuserdata(1), args.subargs(2)); + } + + private LuaValue invokeBestMethod(Object instance, Varargs args) { + JavaMethod best = null; + int score = CoerceLuaToJava.SCORE_UNCOERCIBLE; + for ( int i=0; i + * Since JME has no file system by default, {@link BaseLib} implements + * {@link ResourceFinder} using {@link Class#getResource(String)}. + * The {@link JseBaseLib} implements {@link Globals#finder} by scanning the current directory + * first, then falling back to {@link Class#getResource(String)} if that fails. + * Otherwise, the behavior is the same as that of {@link BaseLib}. + *

+ * Typically, this library is included as part of a call to + * {@link JsePlatform#standardGlobals()} + *

 {@code
+ * Globals globals = JsePlatform.standardGlobals();
+ * globals.get("print").call(LuaValue.valueOf("hello, world"));
+ * } 
+ *

+ * For special cases where the smallest possible footprint is desired, + * a minimal set of libraries could be loaded + * directly via {@link Globals#load(LuaValue)} using code such as: + *

 {@code
+ * Globals globals = new Globals();
+ * globals.load(new JseBaseLib());
+ * globals.get("print").call(LuaValue.valueOf("hello, world"));
+ * } 
+ *

However, other libraries such as PackageLib are not loaded in this case. + *

+ * This is a direct port of the corresponding library in C. + * @see Globals + * @see BaseLib + * @see ResourceFinder + * @see Globals#finder + * @see LibFunction + * @see JsePlatform + * @see org.luaj.vm2.libs.jme.JmePlatform + * @see Lua 5.2 Base Lib Reference + */ + +public class JseBaseLib extends BaseLib { + + + /** Perform one-time initialization on the library by creating a table + * containing the library functions, adding that table to the supplied environment, + * adding the table to package.loaded, and returning table as the return value. + *

Specifically, extend the library loading to set the default value for {@link Globals#STDIN} + * @param modname the module name supplied if this is loaded via 'require'. + * @param env the environment to load into, which must be a Globals instance. + */ + public LuaValue call(LuaValue modname, LuaValue env) { + super.call(modname, env); + env.checkglobals().STDIN = System.in; + return env; + } + + + /** + * Try to open a file in the current working directory, + * or fall back to base opener if not found. + * + * This implementation attempts to open the file using new File(filename). + * It falls back to the base implementation that looks it up as a resource + * in the class path if not found as a plain file. + * + * @see BaseLib + * @see ResourceFinder + * + * @param filename + * @return InputStream, or null if not found. + */ + public InputStream findResource(String filename) { + File f = new File(filename); + if ( ! f.exists() ) + return super.findResource(filename); + try { + return new BufferedInputStream(new FileInputStream(f)); + } catch ( IOException ioe ) { + return null; + } + } +} + diff --git a/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib.java b/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib.java new file mode 100644 index 00000000..4ced4734 --- /dev/null +++ b/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib.java @@ -0,0 +1,343 @@ +/******************************************************************************* +* Copyright (c) 2009 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.libs.jse; + +import java.io.BufferedInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.RandomAccessFile; + +import org.luaj.vm2.Globals; +import org.luaj.vm2.LuaError; +import org.luaj.vm2.LuaString; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.libs.IoLib; +import org.luaj.vm2.libs.LibFunction; + +/** + * Subclass of {@link IoLib} and therefore {@link LibFunction} which implements the lua standard {@code io} + * library for the JSE platform. + *

+ * It uses RandomAccessFile to implement seek on files. + *

+ * Typically, this library is included as part of a call to + * {@link JsePlatform#standardGlobals()} + *

 {@code
+ * Globals globals = JsePlatform.standardGlobals();
+ * globals.get("io").get("write").call(LuaValue.valueOf("hello, world\n"));
+ * } 
+ *

+ * For special cases where the smallest possible footprint is desired, + * a minimal set of libraries could be loaded + * directly via {@link Globals#load(LuaValue)} using code such as: + *

 {@code
+ * Globals globals = new Globals();
+ * globals.load(new JseBaseLib());
+ * globals.load(new PackageLib());
+ * globals.load(new JseIoLib());
+ * globals.get("io").get("write").call(LuaValue.valueOf("hello, world\n"));
+ * } 
+ *

However, other libraries such as MathLib are not loaded in this case. + *

+ * This has been implemented to match as closely as possible the behavior in the corresponding library in C. + * @see LibFunction + * @see JsePlatform + * @see org.luaj.vm2.libs.jme.JmePlatform + * @see IoLib + * @see org.luaj.vm2.libs.jme.JmeIoLib + * @see Lua 5.2 I/O Lib Reference + */ +public class JseIoLib extends IoLib { + + protected File wrapStdin() throws IOException { + return new StdinFile(); + } + + protected File wrapStdout() throws IOException { + return new StdoutFile(FTYPE_STDOUT); + } + + protected File wrapStderr() throws IOException { + return new StdoutFile(FTYPE_STDERR); + } + + protected File openFile( String filename, boolean readMode, boolean appendMode, boolean updateMode, boolean binaryMode ) throws IOException { + RandomAccessFile f = new RandomAccessFile(filename,readMode? "r": "rw"); + if ( appendMode ) { + f.seek(f.length()); + } else { + if ( ! readMode ) + f.setLength(0); + } + return new FileImpl( f ); + } + + protected File openProgram(String prog, String mode) throws IOException { + final Process p = Runtime.getRuntime().exec(prog); + return "w".equals(mode)? + new FileImpl( p.getOutputStream() ): + new FileImpl( p.getInputStream() ); + } + + protected File tmpFile() throws IOException { + java.io.File f = java.io.File.createTempFile(".luaj","bin"); + f.deleteOnExit(); + return new FileImpl( new RandomAccessFile(f,"rw") ); + } + + private static void notimplemented() { + throw new LuaError("not implemented"); + } + + + private final class FileImpl extends File { + private final RandomAccessFile file; + private final InputStream is; + private final OutputStream os; + private boolean closed = false; + private boolean nobuffer = false; + private FileImpl( RandomAccessFile file, InputStream is, OutputStream os ) { + this.file = file; + this.is = is!=null? is.markSupported()? is: new BufferedInputStream(is): null; + this.os = os; + } + private FileImpl( RandomAccessFile f ) { + this( f, null, null ); + } + private FileImpl( InputStream i ) { + this( null, i, null ); + } + private FileImpl( OutputStream o ) { + this( null, null, o ); + } + public String tojstring() { + return "file (" + (this.closed ? "closed" : String.valueOf(this.hashCode())) + ")"; + } + public boolean isstdfile() { + return file == null; + } + public void close() throws IOException { + closed = true; + if ( file != null ) { + file.close(); + } + } + public void flush() throws IOException { + if ( os != null ) + os.flush(); + } + public void write(LuaString s) throws IOException { + if ( os != null ) + os.write( s.m_bytes, s.m_offset, s.m_length ); + else if ( file != null ) + file.write( s.m_bytes, s.m_offset, s.m_length ); + else + notimplemented(); + if ( nobuffer ) + flush(); + } + public boolean isclosed() { + return closed; + } + public int seek(String option, int pos) throws IOException { + if ( file != null ) { + if ( "set".equals(option) ) { + file.seek(pos); + } else if ( "end".equals(option) ) { + file.seek(file.length()+pos); + } else { + file.seek(file.getFilePointer()+pos); + } + return (int) file.getFilePointer(); + } + notimplemented(); + return 0; + } + public void setvbuf(String mode, int size) { + nobuffer = "no".equals(mode); + } + + // get length remaining to read + public int remaining() throws IOException { + return file!=null? (int) (file.length()-file.getFilePointer()): -1; + } + + // peek ahead one character + public int peek() throws IOException { + if ( is != null ) { + is.mark(1); + int c = is.read(); + is.reset(); + return c; + } else if ( file != null ) { + long fp = file.getFilePointer(); + int c = file.read(); + file.seek(fp); + return c; + } + notimplemented(); + return 0; + } + + // return char if read, -1 if eof, throw IOException on other exception + public int read() throws IOException { + if ( is != null ) + return is.read(); + else if ( file != null ) { + return file.read(); + } + notimplemented(); + return 0; + } + + // return number of bytes read if positive, -1 if eof, throws IOException + public int read(byte[] bytes, int offset, int length) throws IOException { + if (file!=null) { + return file.read(bytes, offset, length); + } else if (is!=null) { + return is.read(bytes, offset, length); + } else { + notimplemented(); + } + return length; + } + } + + private final class StdoutFile extends File { + private final int file_type; + + private StdoutFile(int file_type) { + this.file_type = file_type; + } + + public String tojstring() { + return "file ("+this.hashCode()+")"; + } + + private final PrintStream getPrintStream() { + return file_type == FTYPE_STDERR? + globals.STDERR: + globals.STDOUT; + } + + public void write(LuaString string) throws IOException { + getPrintStream().write(string.m_bytes, string.m_offset, string.m_length); + } + + public void flush() throws IOException { + getPrintStream().flush(); + } + + public boolean isstdfile() { + return true; + } + + public void close() throws IOException { + // do not close std files. + } + + public boolean isclosed() { + return false; + } + + public int seek(String option, int bytecount) throws IOException { + return 0; + } + + public void setvbuf(String mode, int size) { + } + + public int remaining() throws IOException { + return 0; + } + + public int peek() throws IOException, EOFException { + return 0; + } + + public int read() throws IOException, EOFException { + return 0; + } + + public int read(byte[] bytes, int offset, int length) + throws IOException { + return 0; + } + } + + private final class StdinFile extends File { + private StdinFile() { + } + + public String tojstring() { + return "file ("+this.hashCode()+")"; + } + + public void write(LuaString string) throws IOException { + } + + public void flush() throws IOException { + } + + public boolean isstdfile() { + return true; + } + + public void close() throws IOException { + // do not close std files. + } + + public boolean isclosed() { + return false; + } + + public int seek(String option, int bytecount) throws IOException { + return 0; + } + + public void setvbuf(String mode, int size) { + } + + public int remaining() throws IOException { + return -1; + } + + public int peek() throws IOException, EOFException { + globals.STDIN.mark(1); + int c = globals.STDIN.read(); + globals.STDIN.reset(); + return c; + } + + public int read() throws IOException, EOFException { + return globals.STDIN.read(); + } + + public int read(byte[] bytes, int offset, int length) + throws IOException { + return globals.STDIN.read(bytes, offset, length); + } + } +} diff --git a/jse/src/main/java/org/luaj/vm2/libs/jse/JseMathLib.java b/jse/src/main/java/org/luaj/vm2/libs/jse/JseMathLib.java new file mode 100644 index 00000000..d802a50d --- /dev/null +++ b/jse/src/main/java/org/luaj/vm2/libs/jse/JseMathLib.java @@ -0,0 +1,120 @@ +/******************************************************************************* +* Copyright (c) 2009-2011 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.libs.jse; + +import org.luaj.vm2.Globals; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.libs.LibFunction; +import org.luaj.vm2.libs.TwoArgFunction; + +/** + * Subclass of {@link LibFunction} which implements the lua standard {@code math} + * library. + *

+ * It contains all lua math functions, including those not available on the JME platform. + * See {@link org.luaj.vm2.libs.MathLib} for the exception list. + *

+ * Typically, this library is included as part of a call to + * {@link JsePlatform#standardGlobals()} + *

 {@code
+ * Globals globals = JsePlatform.standardGlobals();
+ * System.out.println( globals.get("math").get("sqrt").call( LuaValue.valueOf(2) ) );
+ * } 
+ *

+ * For special cases where the smallest possible footprint is desired, + * a minimal set of libraries could be loaded + * directly via {@link Globals#load(LuaValue)} using code such as: + *

 {@code
+ * Globals globals = new Globals();
+ * globals.load(new JseBaseLib());
+ * globals.load(new PackageLib());
+ * globals.load(new JseMathLib());
+ * System.out.println( globals.get("math").get("sqrt").call( LuaValue.valueOf(2) ) );
+ * } 
+ *

However, other libraries such as CoroutineLib are not loaded in this case. + *

+ * This has been implemented to match as closely as possible the behavior in the corresponding library in C. + * @see LibFunction + * @see JsePlatform + * @see org.luaj.vm2.libs.jme.JmePlatform + * @see JseMathLib + * @see Lua 5.2 Math Lib Reference + */ +public class JseMathLib extends org.luaj.vm2.libs.MathLib { + + public JseMathLib() {} + + + /** Perform one-time initialization on the library by creating a table + * containing the library functions, adding that table to the supplied environment, + * adding the table to package.loaded, and returning table as the return value. + *

Specifically, adds all library functions that can be implemented directly + * in JSE but not JME: acos, asin, atan, atan2, cosh, exp, log, pow, sinh, and tanh. + * @param modname the module name supplied if this is loaded via 'require'. + * @param env the environment to load into, which must be a Globals instance. + */ + public LuaValue call(LuaValue modname, LuaValue env) { + super.call(modname, env); + LuaValue math = env.get("math"); + math.set("acos", new acos()); + math.set("asin", new asin()); + LuaValue atan = new atan2(); + math.set("atan", atan); + math.set("atan2", atan); + math.set("cosh", new cosh()); + math.set("exp", new exp()); + math.set("log", new log()); + math.set("pow", new pow()); + math.set("sinh", new sinh()); + math.set("tanh", new tanh()); + return math; + } + + static final class acos extends UnaryOp { protected double call(double d) { return Math.acos(d); } } + static final class asin extends UnaryOp { protected double call(double d) { return Math.asin(d); } } + static final class atan2 extends TwoArgFunction { + public LuaValue call(LuaValue x, LuaValue y) { + return valueOf(Math.atan2(x.checkdouble(), y.optdouble(1))); + } + } + static final class cosh extends UnaryOp { protected double call(double d) { return Math.cosh(d); } } + static final class exp extends UnaryOp { protected double call(double d) { return Math.exp(d); } } + static final class log extends TwoArgFunction { + public LuaValue call(LuaValue x, LuaValue base) { + double nat = Math.log(x.checkdouble()); + double b = base.optdouble(Math.E); + if (b != Math.E) nat /= Math.log(b); + return valueOf(nat); + } + } + static final class pow extends BinaryOp { protected double call(double x, double y) { return Math.pow(x, y); } } + static final class sinh extends UnaryOp { protected double call(double d) { return Math.sinh(d); } } + static final class tanh extends UnaryOp { protected double call(double d) { return Math.tanh(d); } } + + /** Faster, better version of pow() used by arithmetic operator ^ */ + public double dpow_lib(double a, double b) { + return Math.pow(a, b); + } + + +} + diff --git a/jse/src/main/java/org/luaj/vm2/libs/jse/JseOsLib.java b/jse/src/main/java/org/luaj/vm2/libs/jse/JseOsLib.java new file mode 100644 index 00000000..37433e85 --- /dev/null +++ b/jse/src/main/java/org/luaj/vm2/libs/jse/JseOsLib.java @@ -0,0 +1,135 @@ +/******************************************************************************* +* Copyright (c) 2009 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.libs.jse; + +import java.io.File; +import java.io.IOException; + +import org.luaj.vm2.Globals; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; +import org.luaj.vm2.libs.LibFunction; +import org.luaj.vm2.libs.OsLib; + +/** + * Subclass of {@link LibFunction} which implements the standard lua {@code os} library. + *

+ * This contains more complete implementations of the following functions + * using features that are specific to JSE: + *

+ *

+ * Because the nature of the {@code os} library is to encapsulate + * os-specific features, the behavior of these functions varies considerably + * from their counterparts in the C platform. + *

+ * Typically, this library is included as part of a call to + * {@link JsePlatform#standardGlobals()} + *

 {@code
+ * Globals globals = JsePlatform.standardGlobals();
+ * System.out.println( globals.get("os").get("time").call() );
+ * } 
+ *

+ * For special cases where the smallest possible footprint is desired, + * a minimal set of libraries could be loaded + * directly via {@link Globals#load(LuaValue)} using code such as: + *

 {@code
+ * Globals globals = new Globals();
+ * globals.load(new JseBaseLib());
+ * globals.load(new PackageLib());
+ * globals.load(new JseOsLib());
+ * System.out.println( globals.get("os").get("time").call() );
+ * } 
+ *

However, other libraries such as MathLib are not loaded in this case. + *

+ * @see LibFunction + * @see OsLib + * @see JsePlatform + * @see org.luaj.vm2.libs.jme.JmePlatform + * @see Lua 5.2 OS Lib Reference + */ +public class JseOsLib extends OsLib { + + /** return code indicating the execute() threw an I/O exception */ + public static final int EXEC_IOEXCEPTION = 1; + + /** return code indicating the execute() was interrupted */ + public static final int EXEC_INTERRUPTED = -2; + + /** return code indicating the execute() threw an unknown exception */ + public static final int EXEC_ERROR = -3; + + /** public constructor */ + public JseOsLib() { + } + + protected String getenv(String varname) { + String s = System.getenv(varname); + return s != null? s : System.getProperty(varname); + } + + protected Varargs execute(String command) { + int exitValue; + try { + exitValue = new JseProcess(command, null, globals.STDOUT, globals.STDERR).waitFor(); + } catch (IOException ioe) { + exitValue = EXEC_IOEXCEPTION; + } catch (InterruptedException e) { + exitValue = EXEC_INTERRUPTED; + } catch (Throwable t) { + exitValue = EXEC_ERROR; + } + if (exitValue == 0) + return varargsOf(TRUE, valueOf("exit"), ZERO); + return varargsOf(NIL, valueOf("signal"), valueOf(exitValue)); + } + + protected void remove(String filename) throws IOException { + File f = new File(filename); + if ( ! f.exists() ) + throw new IOException("No such file or directory"); + if ( ! f.delete() ) + throw new IOException("Failed to delete"); + } + + protected void rename(String oldname, String newname) throws IOException { + File f = new File(oldname); + if ( ! f.exists() ) + throw new IOException("No such file or directory"); + if ( ! f.renameTo(new File(newname)) ) + throw new IOException("Failed to rename"); + } + + protected String tmpname() { + try { + File f = File.createTempFile(TMP_PREFIX ,TMP_SUFFIX); + return f.getAbsolutePath(); + } catch ( IOException ioe ) { + return super.tmpname(); + } + } + +} diff --git a/jse/src/main/java/org/luaj/vm2/libs/jse/JsePlatform.java b/jse/src/main/java/org/luaj/vm2/libs/jse/JsePlatform.java new file mode 100644 index 00000000..c1c82f9f --- /dev/null +++ b/jse/src/main/java/org/luaj/vm2/libs/jse/JsePlatform.java @@ -0,0 +1,143 @@ +/******************************************************************************* + * Copyright (c) 2009-2011 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.libs.jse; + +import org.luaj.vm2.Globals; +import org.luaj.vm2.LoadState; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; +import org.luaj.vm2.compiler.LuaC; +import org.luaj.vm2.libs.Bit32Lib; +import org.luaj.vm2.libs.CoroutineLib; +import org.luaj.vm2.libs.DebugLib; +import org.luaj.vm2.libs.PackageLib; +import org.luaj.vm2.libs.ResourceFinder; +import org.luaj.vm2.libs.StringLib; +import org.luaj.vm2.libs.TableLib; + +/** The {@link JsePlatform} class is a convenience class to standardize + * how globals tables are initialized for the JSE platform. + *

+ * It is used to allocate either a set of standard globals using + * {@link #standardGlobals()} or debug globals using {@link #debugGlobals()} + *

+ * A simple example of initializing globals and using them from Java is: + *

 {@code
+ * Globals globals = JsePlatform.standardGlobals();
+ * globals.get("print").call(LuaValue.valueOf("hello, world"));
+ * } 
+ *

+ * Once globals are created, a simple way to load and run a script is: + *

 {@code
+ * globals.load( new FileInputStream("main.lua"), "main.lua" ).call();
+ * } 
+ *

+ * although {@code require} could also be used: + *

 {@code
+ * globals.get("require").call(LuaValue.valueOf("main"));
+ * } 
+ * For this to succeed, the file "main.lua" must be in the current directory or a resource. + * See {@link JseBaseLib} for details on finding scripts using {@link ResourceFinder}. + *

+ * The standard globals will contain all standard libraries plus {@code luajava}: + *

+ * In addition, the {@link LuaC} compiler is installed so lua files may be loaded in their source form. + *

+ * The debug globals are simply the standard globals plus the {@code debug} library {@link DebugLib}. + *

+ * The class ensures that initialization is done in the correct order. + * + * @see Globals + * @see org.luaj.vm2.libs.jme.JmePlatform + */ +public class JsePlatform { + + /** + * Create a standard set of globals for JSE including all the libraries. + * + * @return Table of globals initialized with the standard JSE libraries + * @see #debugGlobals() + * @see JsePlatform + * @see org.luaj.vm2.libs.jme.JmePlatform + */ + public static Globals standardGlobals() { + Globals globals = new Globals(); + globals.load(new JseBaseLib()); + globals.load(new PackageLib()); + globals.load(new Bit32Lib()); + globals.load(new TableLib()); + globals.load(new JseStringLib()); + globals.load(new CoroutineLib()); + globals.load(new JseMathLib()); + globals.load(new JseIoLib()); + globals.load(new JseOsLib()); + globals.load(new LuajavaLib()); + LoadState.install(globals); + LuaC.install(globals); + return globals; + } + + /** Create standard globals including the {@link DebugLib} library. + * + * @return Table of globals initialized with the standard JSE and debug libraries + * @see #standardGlobals() + * @see JsePlatform + * @see org.luaj.vm2.libs.jme.JmePlatform + * @see DebugLib + */ + public static Globals debugGlobals() { + Globals globals = standardGlobals(); + globals.load(new DebugLib()); + return globals; + } + + + /** Simple wrapper for invoking a lua function with command line arguments. + * The supplied function is first given a new Globals object as its environment + * then the program is run with arguments. + * @return {@link Varargs} containing any values returned by mainChunk. + */ + public static Varargs luaMain(LuaValue mainChunk, String[] args) { + Globals g = standardGlobals(); + int n = args.length; + LuaValue[] vargs = new LuaValue[args.length]; + for (int i = 0; i < n; ++i) + vargs[i] = LuaValue.valueOf(args[i]); + LuaValue arg = LuaValue.listOf(vargs); + arg.set("n", n); + g.set("arg", arg); + mainChunk.initupvalue1(g); + return mainChunk.invoke(LuaValue.varargsOf(vargs)); + } +} diff --git a/jse/src/main/java/org/luaj/vm2/libs/jse/JseProcess.java b/jse/src/main/java/org/luaj/vm2/libs/jse/JseProcess.java new file mode 100644 index 00000000..4b0d77f9 --- /dev/null +++ b/jse/src/main/java/org/luaj/vm2/libs/jse/JseProcess.java @@ -0,0 +1,132 @@ +/******************************************************************************* +* Copyright (c) 2012 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.libs.jse; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** Analog of Process that pipes input and output to client-specified streams. + */ +public class JseProcess { + + final Process process; + final Thread input,output,error; + + /** Construct a process around a command, with specified streams to redirect input and output to. + * + * @param cmd The command to execute, including arguments, if any + * @param stdin Optional InputStream to read from as process input, or null if input is not needed. + * @param stdout Optional OutputStream to copy process output to, or null if output is ignored. + * @param stderr Optinoal OutputStream to copy process stderr output to, or null if output is ignored. + * @throws IOException If the system process could not be created. + * @see Process + */ + public JseProcess(String[] cmd, InputStream stdin, OutputStream stdout, OutputStream stderr) throws IOException { + this(Runtime.getRuntime().exec(cmd), stdin, stdout, stderr); + } + + /** Construct a process around a command, with specified streams to redirect input and output to. + * + * @param cmd The command to execute, including arguments, if any + * @param stdin Optional InputStream to read from as process input, or null if input is not needed. + * @param stdout Optional OutputStream to copy process output to, or null if output is ignored. + * @param stderr Optinoal OutputStream to copy process stderr output to, or null if output is ignored. + * @throws IOException If the system process could not be created. + * @see Process + */ + public JseProcess(String cmd, InputStream stdin, OutputStream stdout, OutputStream stderr) throws IOException { + this(Runtime.getRuntime().exec(cmd), stdin, stdout, stderr); + } + + private JseProcess(Process process, InputStream stdin, OutputStream stdout, OutputStream stderr) { + this.process = process; + input = stdin == null? null: copyBytes(stdin, process.getOutputStream(), null, process.getOutputStream()); + output = stdout == null? null: copyBytes(process.getInputStream(), stdout, process.getInputStream(), null); + error = stderr == null? null: copyBytes(process.getErrorStream(), stderr, process.getErrorStream(), null); + } + + /** Get the exit value of the process. */ + public int exitValue() { + return process.exitValue(); + } + + /** Wait for the process to complete, and all pending output to finish. + * @return The exit status. + * @throws InterruptedException + */ + public int waitFor() throws InterruptedException { + int r = process.waitFor(); + if (input != null) + input.join(); + if (output != null) + output.join(); + if (error != null) + error.join(); + process.destroy(); + return r; + } + + /** Create a thread to copy bytes from input to output. */ + private Thread copyBytes(final InputStream input, + final OutputStream output, final InputStream ownedInput, + final OutputStream ownedOutput) { + Thread t = (new CopyThread(output, ownedOutput, ownedInput, input)); + t.start(); + return t; + } + + private static final class CopyThread extends Thread { + private final OutputStream output; + private final OutputStream ownedOutput; + private final InputStream ownedInput; + private final InputStream input; + + private CopyThread(OutputStream output, OutputStream ownedOutput, + InputStream ownedInput, InputStream input) { + this.output = output; + this.ownedOutput = ownedOutput; + this.ownedInput = ownedInput; + this.input = input; + } + + public void run() { + try { + byte[] buf = new byte[1024]; + int r; + try { + while ((r = input.read(buf)) >= 0) { + output.write(buf, 0, r); + } + } finally { + if (ownedInput != null) + ownedInput.close(); + if (ownedOutput != null) + ownedOutput.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + +} diff --git a/jse/src/main/java/org/luaj/vm2/libs/jse/JseStringLib.java b/jse/src/main/java/org/luaj/vm2/libs/jse/JseStringLib.java new file mode 100644 index 00000000..a787871f --- /dev/null +++ b/jse/src/main/java/org/luaj/vm2/libs/jse/JseStringLib.java @@ -0,0 +1,39 @@ +/******************************************************************************* +* Copyright (c) 2009 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.libs.jse; + +public class JseStringLib extends org.luaj.vm2.libs.StringLib { + + /** public constructor */ + public JseStringLib() { + } + + protected String format(String src, double x) { + String out; + try { + out = String.format(src, new Object[] {Double.valueOf(x)}); + } catch (Throwable e) { + out = super.format(src, x); + } + return out; + } +} diff --git a/jse/src/main/java/org/luaj/vm2/libs/jse/LuajavaLib.java b/jse/src/main/java/org/luaj/vm2/libs/jse/LuajavaLib.java new file mode 100644 index 00000000..1406c96b --- /dev/null +++ b/jse/src/main/java/org/luaj/vm2/libs/jse/LuajavaLib.java @@ -0,0 +1,214 @@ +/******************************************************************************* +* Copyright (c) 2009 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.libs.jse; + + +import java.lang.reflect.Array; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import org.luaj.vm2.Globals; +import org.luaj.vm2.LuaError; +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; +import org.luaj.vm2.compiler.LuaC; +import org.luaj.vm2.libs.LibFunction; +import org.luaj.vm2.libs.VarArgFunction; + +/** + * Subclass of {@link LibFunction} which implements the features of the luajava package. + *

+ * Luajava is an approach to mixing lua and java using simple functions that bind + * java classes and methods to lua dynamically. The API is documented on the + * luajava documentation pages. + * + *

+ * Typically, this library is included as part of a call to + * {@link JsePlatform#standardGlobals()} + *

 {@code
+ * Globals globals = JsePlatform.standardGlobals();
+ * System.out.println( globals.get("luajava").get("bindClass").call( LuaValue.valueOf("java.lang.System") ).invokeMethod("currentTimeMillis") );
+ * } 
+ *

+ * To instantiate and use it directly, + * link it into your globals table via {@link Globals#load} using code such as: + *

 {@code
+ * Globals globals = new Globals();
+ * globals.load(new JseBaseLib());
+ * globals.load(new PackageLib());
+ * globals.load(new LuajavaLib());
+ * globals.load(
+ *      "sys = luajava.bindClass('java.lang.System')\n"+
+ *      "print ( sys:currentTimeMillis() )\n", "main.lua" ).call();
+ * } 
+ *

+ * + * The {@code luajava} library is available + * on all JSE platforms via the call to {@link JsePlatform#standardGlobals()} + * and the luajava api's are simply invoked from lua. + * Because it makes extensive use of Java's reflection API, it is not available + * on JME, but can be used in Android applications. + *

+ * This has been implemented to match as closely as possible the behavior in the corresponding library in C. + * + * @see LibFunction + * @see JsePlatform + * @see org.luaj.vm2.libs.jme.JmePlatform + * @see LuaC + * @see CoerceJavaToLua + * @see CoerceLuaToJava + * @see http://www.keplerproject.org/luajava/manual.html#luareference + */ +public class LuajavaLib extends VarArgFunction { + + static final int INIT = 0; + static final int BINDCLASS = 1; + static final int NEWINSTANCE = 2; + static final int NEW = 3; + static final int CREATEPROXY = 4; + static final int LOADLIB = 5; + + static final String[] NAMES = { + "bindClass", + "newInstance", + "new", + "createProxy", + "loadLib", + }; + + static final int METHOD_MODIFIERS_VARARGS = 0x80; + + public LuajavaLib() { + } + + public Varargs invoke(Varargs args) { + try { + switch ( opcode ) { + case INIT: { + // LuaValue modname = args.arg1(); + LuaValue env = args.arg(2); + LuaTable t = new LuaTable(); + bind( t, this.getClass(), NAMES, BINDCLASS ); + env.set("luajava", t); + if (!env.get("package").isnil()) env.get("package").get("loaded").set("luajava", t); + return t; + } + case BINDCLASS: { + final Class clazz = classForName(args.checkjstring(1)); + return JavaClass.forClass(clazz); + } + case NEWINSTANCE: + case NEW: { + // get constructor + final LuaValue c = args.checkvalue(1); + final Class clazz = (opcode==NEWINSTANCE? classForName(c.tojstring()): (Class) c.checkuserdata(Class.class)); + final Varargs consargs = args.subargs(2); + return JavaClass.forClass(clazz).getConstructor().invoke(consargs); + } + + case CREATEPROXY: { + final int niface = args.narg()-1; + if ( niface <= 0 ) + throw new LuaError("no interfaces"); + final LuaValue lobj = args.checktable(niface+1); + + // get the interfaces + final Class[] ifaces = new Class[niface]; + for ( int i=0; i=0 ); + } + + public void testLuaErrorCause() { + String script = "luajava.bindClass( \""+SomeClass.class.getName()+"\"):someMethod()"; + LuaValue chunk = globals.get("load").call(LuaValue.valueOf(script)); + try { + chunk.invoke(LuaValue.NONE); + fail( "call should not have succeeded" ); + } catch ( LuaError lee ) { + Throwable c = lee.getCause(); + assertEquals( SomeException.class, c.getClass() ); + } + } + + public interface VarArgsInterface { + public String varargsMethod( String a, String ... v ); + public String arrayargsMethod( String a, String[] v ); + } + + public void testVarArgsProxy() { + String script = "return luajava.createProxy( \""+VarArgsInterface.class.getName()+"\", \n"+ + "{\n" + + " varargsMethod = function(a,...)\n" + + " return table.concat({a,...},'-')\n" + + " end,\n" + + " arrayargsMethod = function(a,array)\n" + + " return tostring(a)..(array and \n" + + " ('-'..tostring(array.length)\n" + + " ..'-'..tostring(array[1])\n" + + " ..'-'..tostring(array[2])\n" + + " ) or '-nil')\n" + + " end,\n" + + "} )\n"; + Varargs chunk = globals.get("load").call(LuaValue.valueOf(script)); + if ( ! chunk.arg1().toboolean() ) + fail( chunk.arg(2).toString() ); + LuaValue result = chunk.arg1().call(); + Object u = result.touserdata(); + VarArgsInterface v = (VarArgsInterface) u; + assertEquals( "foo", v.varargsMethod("foo") ); + assertEquals( "foo-bar", v.varargsMethod("foo", "bar") ); + assertEquals( "foo-bar-etc", v.varargsMethod("foo", "bar", "etc") ); + assertEquals( "foo-0-nil-nil", v.arrayargsMethod("foo", new String[0]) ); + assertEquals( "foo-1-bar-nil", v.arrayargsMethod("foo", new String[] {"bar"}) ); + assertEquals( "foo-2-bar-etc", v.arrayargsMethod("foo", new String[] {"bar","etc"}) ); + assertEquals( "foo-3-bar-etc", v.arrayargsMethod("foo", new String[] {"bar","etc","etc"}) ); + assertEquals( "foo-nil", v.arrayargsMethod("foo", null) ); + } + + public void testBigNum() { + String script = + "bigNumA = luajava.newInstance('java.math.BigDecimal','12345678901234567890');\n" + + "bigNumB = luajava.newInstance('java.math.BigDecimal','12345678901234567890');\n" + + "bigNumC = bigNumA:multiply(bigNumB);\n" + + //"print(bigNumA:toString())\n" + + //"print(bigNumB:toString())\n" + + //"print(bigNumC:toString())\n" + + "return bigNumA:toString(), bigNumB:toString(), bigNumC:toString()"; + Varargs chunk = globals.get("load").call(LuaValue.valueOf(script)); + if ( ! chunk.arg1().toboolean() ) + fail( chunk.arg(2).toString() ); + Varargs results = chunk.arg1().invoke(); + int nresults = results.narg(); + String sa = results.tojstring(1); + String sb = results.tojstring(2); + String sc = results.tojstring(3); + assertEquals( 3, nresults ); + assertEquals( "12345678901234567890", sa ); + assertEquals( "12345678901234567890", sb ); + assertEquals( "152415787532388367501905199875019052100", sc ); + } + + public interface IA {} + public interface IB extends IA {} + public interface IC extends IB {} + + public static class A implements IA { + } + public static class B extends A implements IB { + public String set( Object x ) { return "set(Object) "; } + public String set( String x ) { return "set(String) "+x; } + public String set( A x ) { return "set(A) "; } + public String set( B x ) { return "set(B) "; } + public String set( C x ) { return "set(C) "; } + public String set( byte x ) { return "set(byte) "+x; } + public String set( char x ) { return "set(char) "+(int)x; } + public String set( short x ) { return "set(short) "+x; } + public String set( int x ) { return "set(int) "+x; } + public String set( long x ) { return "set(long) "+x; } + public String set( float x ) { return "set(float) "+x; } + public String set( double x ) { return "set(double) "+x; } + + public String setr( double x ) { return "setr(double) "+x; } + public String setr( float x ) { return "setr(float) "+x; } + public String setr( long x ) { return "setr(long) "+x; } + public String setr( int x ) { return "setr(int) "+x; } + public String setr( short x ) { return "setr(short) "+x; } + public String setr( char x ) { return "setr(char) "+(int)x; } + public String setr( byte x ) { return "setr(byte) "+x; } + public String setr( C x ) { return "setr(C) "; } + public String setr( B x ) { return "setr(B) "; } + public String setr( A x ) { return "setr(A) "; } + public String setr( String x ) { return "setr(String) "+x; } + public String setr( Object x ) { return "setr(Object) "; } + + public Object getObject() { return new Object(); } + public String getString() { return "abc"; } + public byte[] getbytearray() { return new byte[] { 1, 2, 3 }; } + public A getA() { return new A(); } + public B getB() { return new B(); } + public C getC() { return new C(); } + public byte getbyte() { return 1; } + public char getchar() { return 65000; } + public short getshort() { return -32000; } + public int getint() { return 100000; } + public long getlong() { return 50000000000L; } + public float getfloat() { return 6.5f; } + public double getdouble() { return Math.PI; } + } + public static class C extends B implements IC { + } + public static class D extends C implements IA { + } + + public void testOverloadedJavaMethodObject() { doOverloadedMethodTest( "Object", "" ); } + public void testOverloadedJavaMethodString() { doOverloadedMethodTest( "String", "abc" ); } + public void testOverloadedJavaMethodA() { doOverloadedMethodTest( "A", "" ); } + public void testOverloadedJavaMethodB() { doOverloadedMethodTest( "B", "" ); } + public void testOverloadedJavaMethodC() { doOverloadedMethodTest( "C", "" ); } + public void testOverloadedJavaMethodByte() { doOverloadedMethodTest( "byte", "1" ); } + public void testOverloadedJavaMethodChar() { doOverloadedMethodTest( "char", "65000" ); } + public void testOverloadedJavaMethodShort() { doOverloadedMethodTest( "short", "-32000" ); } + public void testOverloadedJavaMethodInt() { doOverloadedMethodTest( "int", "100000" ); } + public void testOverloadedJavaMethodLong() { doOverloadedMethodTest( "long", "50000000000" ); } + public void testOverloadedJavaMethodFloat() { doOverloadedMethodTest( "float", "6.5" ); } + public void testOverloadedJavaMethodDouble() { doOverloadedMethodTest( "double", "3.141592653589793" ); } + + private void doOverloadedMethodTest( String typename, String value ) { + String script = + "local a = luajava.newInstance('"+B.class.getName()+"');\n" + + "local b = a:set(a:get"+typename+"())\n" + + "local c = a:setr(a:get"+typename+"())\n" + + "return b,c"; + Varargs chunk = globals.get("load").call(LuaValue.valueOf(script)); + if ( ! chunk.arg1().toboolean() ) + fail( chunk.arg(2).toString() ); + Varargs results = chunk.arg1().invoke(); + int nresults = results.narg(); + assertEquals( 2, nresults ); + LuaValue b = results.arg(1); + LuaValue c = results.arg(2); + String sb = b.tojstring(); + String sc = c.tojstring(); + assertEquals( "set("+typename+") "+value, sb ); + assertEquals( "setr("+typename+") "+value, sc ); + } + + public void testClassInheritanceLevels() { + assertEquals( 0, CoerceLuaToJava.inheritanceLevels(Object.class, Object.class) ); + assertEquals( 1, CoerceLuaToJava.inheritanceLevels(Object.class, String.class) ); + assertEquals( 1, CoerceLuaToJava.inheritanceLevels(Object.class, A.class) ); + assertEquals( 2, CoerceLuaToJava.inheritanceLevels(Object.class, B.class) ); + assertEquals( 3, CoerceLuaToJava.inheritanceLevels(Object.class, C.class) ); + + assertEquals( CoerceLuaToJava.SCORE_UNCOERCIBLE, CoerceLuaToJava.inheritanceLevels(A.class, Object.class) ); + assertEquals( CoerceLuaToJava.SCORE_UNCOERCIBLE, CoerceLuaToJava.inheritanceLevels(A.class, String.class) ); + assertEquals( 0, CoerceLuaToJava.inheritanceLevels(A.class, A.class) ); + assertEquals( 1, CoerceLuaToJava.inheritanceLevels(A.class, B.class) ); + assertEquals( 2, CoerceLuaToJava.inheritanceLevels(A.class, C.class) ); + + assertEquals( CoerceLuaToJava.SCORE_UNCOERCIBLE, CoerceLuaToJava.inheritanceLevels(B.class, Object.class) ); + assertEquals( CoerceLuaToJava.SCORE_UNCOERCIBLE, CoerceLuaToJava.inheritanceLevels(B.class, String.class) ); + assertEquals( CoerceLuaToJava.SCORE_UNCOERCIBLE, CoerceLuaToJava.inheritanceLevels(B.class, A.class) ); + assertEquals( 0, CoerceLuaToJava.inheritanceLevels(B.class, B.class) ); + assertEquals( 1, CoerceLuaToJava.inheritanceLevels(B.class, C.class) ); + + assertEquals( CoerceLuaToJava.SCORE_UNCOERCIBLE, CoerceLuaToJava.inheritanceLevels(C.class, Object.class) ); + assertEquals( CoerceLuaToJava.SCORE_UNCOERCIBLE, CoerceLuaToJava.inheritanceLevels(C.class, String.class) ); + assertEquals( CoerceLuaToJava.SCORE_UNCOERCIBLE, CoerceLuaToJava.inheritanceLevels(C.class, A.class) ); + assertEquals( CoerceLuaToJava.SCORE_UNCOERCIBLE, CoerceLuaToJava.inheritanceLevels(C.class, B.class) ); + assertEquals( 0, CoerceLuaToJava.inheritanceLevels(C.class, C.class) ); + } + + public void testInterfaceInheritanceLevels() { + assertEquals( 1, CoerceLuaToJava.inheritanceLevels(IA.class, A.class) ); + assertEquals( 1, CoerceLuaToJava.inheritanceLevels(IB.class, B.class) ); + assertEquals( 2, CoerceLuaToJava.inheritanceLevels(IA.class, B.class) ); + assertEquals( 1, CoerceLuaToJava.inheritanceLevels(IC.class, C.class) ); + assertEquals( 2, CoerceLuaToJava.inheritanceLevels(IB.class, C.class) ); + assertEquals( 3, CoerceLuaToJava.inheritanceLevels(IA.class, C.class) ); + assertEquals( 1, CoerceLuaToJava.inheritanceLevels(IA.class, D.class) ); + assertEquals( 2, CoerceLuaToJava.inheritanceLevels(IC.class, D.class) ); + assertEquals( 3, CoerceLuaToJava.inheritanceLevels(IB.class, D.class) ); + + assertEquals( CoerceLuaToJava.SCORE_UNCOERCIBLE, CoerceLuaToJava.inheritanceLevels(IB.class, A.class) ); + assertEquals( CoerceLuaToJava.SCORE_UNCOERCIBLE, CoerceLuaToJava.inheritanceLevels(IC.class, A.class) ); + assertEquals( CoerceLuaToJava.SCORE_UNCOERCIBLE, CoerceLuaToJava.inheritanceLevels(IC.class, B.class) ); + assertEquals( CoerceLuaToJava.SCORE_UNCOERCIBLE, CoerceLuaToJava.inheritanceLevels(IB.class, IA.class) ); + assertEquals( 1, CoerceLuaToJava.inheritanceLevels(IA.class, IB.class) ); + } + + public void testCoerceJavaToLuaLuaValue() { + assertSame(LuaValue.NIL, CoerceJavaToLua.coerce(LuaValue.NIL)); + assertSame(LuaValue.ZERO, CoerceJavaToLua.coerce(LuaValue.ZERO)); + assertSame(LuaValue.ONE, CoerceJavaToLua.coerce(LuaValue.ONE)); + assertSame(LuaValue.INDEX, CoerceJavaToLua.coerce(LuaValue.INDEX)); + LuaTable table = LuaValue.tableOf(); + assertSame(table, CoerceJavaToLua.coerce(table)); + } + + public void testCoerceJavaToLuaByeArray() { + byte[] bytes = "abcd".getBytes(); + LuaValue value = CoerceJavaToLua.coerce(bytes); + assertEquals(LuaString.class, value.getClass()); + assertEquals(LuaValue.valueOf("abcd"), value); + } +} + diff --git a/jse/src/test/java/org/luaj/vm2/libs/jse/LuajavaAccessibleMembersTest.java b/jse/src/test/java/org/luaj/vm2/libs/jse/LuajavaAccessibleMembersTest.java new file mode 100644 index 00000000..12044ee7 --- /dev/null +++ b/jse/src/test/java/org/luaj/vm2/libs/jse/LuajavaAccessibleMembersTest.java @@ -0,0 +1,68 @@ +package org.luaj.vm2.libs.jse; + +import junit.framework.TestCase; + +import org.luaj.vm2.Globals; +import org.luaj.vm2.LuaValue; + +public class LuajavaAccessibleMembersTest extends TestCase { + + private Globals globals; + + protected void setUp() throws Exception { + super.setUp(); + globals = JsePlatform.standardGlobals(); + } + + private String invokeScript(String script) { + try { + LuaValue c = globals.load(script, "script"); + return c.call().tojstring(); + } catch ( Exception e ) { + fail("exception: "+e ); + return "failed"; + } + } + + public void testAccessFromPrivateClassImplementedMethod() { + assertEquals("privateImpl-aaa-interface_method(bar)", invokeScript( + "b = luajava.newInstance('"+TestClass.class.getName()+"');" + + "a = b:create_PrivateImpl('aaa');" + + "return a:interface_method('bar');")); + } + + public void testAccessFromPrivateClassPublicMethod() { + assertEquals("privateImpl-aaa-public_method", invokeScript( + "b = luajava.newInstance('"+TestClass.class.getName()+"');" + + "a = b:create_PrivateImpl('aaa');" + + "return a:public_method();")); + } + + public void testAccessFromPrivateClassGetPublicField() { + assertEquals("aaa", invokeScript( + "b = luajava.newInstance('"+TestClass.class.getName()+"');" + + "a = b:create_PrivateImpl('aaa');" + + "return a.public_field;")); + } + + public void testAccessFromPrivateClassSetPublicField() { + assertEquals("foo", invokeScript( + "b = luajava.newInstance('"+TestClass.class.getName()+"');" + + "a = b:create_PrivateImpl('aaa');" + + "a.public_field = 'foo';" + + "return a.public_field;")); + } + + public void testAccessFromPrivateClassPublicConstructor() { + assertEquals("privateImpl-constructor", invokeScript( + "b = luajava.newInstance('"+TestClass.class.getName()+"');" + + "c = b:get_PrivateImplClass();" + + "return luajava.new(c);")); + } + + public void testAccessPublicEnum() { + assertEquals("class org.luaj.vm2.lib.jse.TestClass$SomeEnum", invokeScript( + "b = luajava.newInstance('"+TestClass.class.getName()+"');" + + "return b.SomeEnum")); + } +} diff --git a/jse/src/test/java/org/luaj/vm2/libs/jse/LuajavaClassMembersTest.java b/jse/src/test/java/org/luaj/vm2/libs/jse/LuajavaClassMembersTest.java new file mode 100644 index 00000000..b4c72f37 --- /dev/null +++ b/jse/src/test/java/org/luaj/vm2/libs/jse/LuajavaClassMembersTest.java @@ -0,0 +1,238 @@ +package org.luaj.vm2.libs.jse; + +import junit.framework.TestCase; + +import org.luaj.vm2.LuaError; +import org.luaj.vm2.LuaValue; + +public class LuajavaClassMembersTest extends TestCase { + public static class A { + protected A() {} + } + public static class B extends A { + public byte m_byte_field; + public int m_int_field; + public double m_double_field; + public String m_string_field; + + protected B() {} + public B(int i) { m_int_field = i; } + + public String setString( String x ) { return "setString(String) "+x; } + public String getString() { return "abc"; } + public int getint() { return 100000; } + + public String uniq() { return "uniq()"; } + public String uniqs(String s) { return "uniqs(string:"+s+")"; } + public String uniqi(int i) { return "uniqi(int:"+i+")"; } + public String uniqsi(String s, int i) { return "uniqsi(string:"+s+",int:"+i+")"; } + public String uniqis(int i, String s) { return "uniqis(int:"+i+",string:"+s+")"; } + + public String pick() { return "pick()"; } + public String pick(String s) { return "pick(string:"+s+")"; } + public String pick(int i) { return "pick(int:"+i+")"; } + public String pick(String s, int i) { return "pick(string:"+s+",int:"+i+")"; } + public String pick(int i, String s) { return "pick(int:"+i+",string:"+s+")"; } + + public static String staticpick() { return "static-pick()"; } + public static String staticpick(String s) { return "static-pick(string:"+s+")"; } + public static String staticpick(int i) { return "static-pick(int:"+i+")"; } + public static String staticpick(String s, int i) { return "static-pick(string:"+s+",int:"+i+")"; } + public static String staticpick(int i, String s) { return "static-pick(int:"+i+",string:"+s+")"; } + } + public static class C extends B { + public C() {} + public C(String s) { m_string_field = s; } + public C(int i) { m_int_field = i; } + public C(String s, int i) { m_string_field = s; m_int_field = i; } + public int getint() { return 200000; } + + public String pick(String s) { return "class-c-pick(string:"+s+")"; } + public String pick(int i) { return "class-c-pick(int:"+i+")"; } + public static class D { + public static String name() { return "name-of-D"; } + } + } + + static LuaValue ZERO = LuaValue.ZERO; + static LuaValue ONE = LuaValue.ONE; + static LuaValue PI = LuaValue.valueOf(Math.PI); + static LuaValue THREE = LuaValue.valueOf(3); + static LuaValue NUMS = LuaValue.valueOf(123); + static LuaValue ABC = LuaValue.valueOf("abc"); + static LuaValue SOMEA = CoerceJavaToLua.coerce(new A()); + static LuaValue SOMEB = CoerceJavaToLua.coerce(new B()); + static LuaValue SOMEC = CoerceJavaToLua.coerce(new C()); + + public void testSetByteField() { + B b = new B(); + JavaInstance i = new JavaInstance(b); + i.set("m_byte_field", ONE ); assertEquals( 1, b.m_byte_field ); assertEquals( ONE, i.get("m_byte_field") ); + i.set("m_byte_field", PI ); assertEquals( 3, b.m_byte_field ); assertEquals( THREE, i.get("m_byte_field") ); + i.set("m_byte_field", ABC ); assertEquals( 0, b.m_byte_field ); assertEquals( ZERO, i.get("m_byte_field") ); + } + public void testSetDoubleField() { + B b = new B(); + JavaInstance i = new JavaInstance(b); + i.set("m_double_field", ONE ); assertEquals( 1., b.m_double_field ); assertEquals( ONE, i.get("m_double_field") ); + i.set("m_double_field", PI ); assertEquals( Math.PI, b.m_double_field ); assertEquals( PI, i.get("m_double_field") ); + i.set("m_double_field", ABC ); assertEquals( 0., b.m_double_field ); assertEquals( ZERO, i.get("m_double_field") ); + } + public void testNoFactory() { + JavaClass c = JavaClass.forClass(A.class); + try { + c.call(); + fail( "did not throw lua error as expected" ); + } catch ( LuaError e ) { + } + } + public void testUniqueFactoryCoercible() { + JavaClass c = JavaClass.forClass(B.class); + assertEquals( JavaClass.class, c.getClass() ); + LuaValue constr = c.get("new"); + assertEquals( JavaConstructor.class, constr.getClass() ); + LuaValue v = constr.call(NUMS); + Object b = v.touserdata(); + assertEquals( B.class, b.getClass() ); + assertEquals( 123, ((B)b).m_int_field ); + Object b0 = constr.call().touserdata(); + assertEquals( B.class, b0.getClass() ); + assertEquals( 0, ((B)b0).m_int_field ); + } + public void testUniqueFactoryUncoercible() { + JavaClass f = JavaClass.forClass(B.class); + LuaValue constr = f.get("new"); + assertEquals( JavaConstructor.class, constr.getClass() ); + try { + LuaValue v = constr.call(LuaValue.userdataOf(new Object())); + Object b = v.touserdata(); + // fail( "did not throw lua error as expected" ); + assertEquals( 0, ((B)b).m_int_field ); + } catch ( LuaError e ) { + } + } + public void testOverloadedFactoryCoercible() { + JavaClass f = JavaClass.forClass(C.class); + LuaValue constr = f.get("new"); + assertEquals( JavaConstructor.Overload.class, constr.getClass() ); + Object c = constr.call().touserdata(); + Object ci = constr.call(LuaValue.valueOf(123)).touserdata(); + Object cs = constr.call(LuaValue.valueOf("abc")).touserdata(); + Object csi = constr.call( LuaValue.valueOf("def"), LuaValue.valueOf(456) ).touserdata(); + assertEquals( C.class, c.getClass() ); + assertEquals( C.class, ci.getClass() ); + assertEquals( C.class, cs.getClass() ); + assertEquals( C.class, csi.getClass() ); + assertEquals( null, ((C)c).m_string_field ); + assertEquals( 0, ((C)c).m_int_field ); + assertEquals( "abc", ((C)cs).m_string_field ); + assertEquals( 0, ((C)cs).m_int_field ); + assertEquals( null, ((C)ci).m_string_field ); + assertEquals( 123, ((C)ci).m_int_field ); + assertEquals( "def", ((C)csi).m_string_field ); + assertEquals( 456, ((C)csi).m_int_field ); + } + public void testOverloadedFactoryUncoercible() { + JavaClass f = JavaClass.forClass(C.class); + try { + Object c = f.call(LuaValue.userdataOf(new Object())); + // fail( "did not throw lua error as expected" ); + assertEquals( 0, ((C)c).m_int_field ); + assertEquals( null, ((C)c).m_string_field ); + } catch ( LuaError e ) { + } + } + + public void testNoAttribute() { + JavaClass f = JavaClass.forClass(A.class); + LuaValue v = f.get("bogus"); + assertEquals( v, LuaValue.NIL ); + try { + f.set("bogus",ONE); + fail( "did not throw lua error as expected" ); + } catch ( LuaError e ) {} + } + public void testFieldAttributeCoercible() { + JavaInstance i = new JavaInstance(new B()); + i.set("m_int_field", ONE ); assertEquals( 1, i.get("m_int_field").toint() ); + i.set("m_int_field", THREE ); assertEquals( 3, i.get("m_int_field").toint() ); + i = new JavaInstance(new C()); + i.set("m_int_field", ONE ); assertEquals( 1, i.get("m_int_field").toint() ); + i.set("m_int_field", THREE ); assertEquals( 3, i.get("m_int_field").toint() ); + } + public void testUniqueMethodAttributeCoercible() { + B b = new B(); + JavaInstance ib = new JavaInstance(b); + LuaValue b_getString = ib.get("getString"); + LuaValue b_getint = ib.get("getint"); + assertEquals( JavaMethod.class, b_getString.getClass() ); + assertEquals( JavaMethod.class, b_getint.getClass() ); + assertEquals( "abc", b_getString.call(SOMEB).tojstring() ); + assertEquals( 100000, b_getint.call(SOMEB).toint()); + assertEquals( "abc", b_getString.call(SOMEC).tojstring() ); + assertEquals( 200000, b_getint.call(SOMEC).toint()); + } + public void testUniqueMethodAttributeArgsCoercible() { + B b = new B(); + JavaInstance ib = new JavaInstance(b); + LuaValue uniq = ib.get("uniq"); + LuaValue uniqs = ib.get("uniqs"); + LuaValue uniqi = ib.get("uniqi"); + LuaValue uniqsi = ib.get("uniqsi"); + LuaValue uniqis = ib.get("uniqis"); + assertEquals( JavaMethod.class, uniq.getClass() ); + assertEquals( JavaMethod.class, uniqs.getClass() ); + assertEquals( JavaMethod.class, uniqi.getClass() ); + assertEquals( JavaMethod.class, uniqsi.getClass() ); + assertEquals( JavaMethod.class, uniqis.getClass() ); + assertEquals( "uniq()", uniq.call(SOMEB).tojstring() ); + assertEquals( "uniqs(string:abc)", uniqs.call(SOMEB,ABC).tojstring() ); + assertEquals( "uniqi(int:1)", uniqi.call(SOMEB,ONE).tojstring() ); + assertEquals( "uniqsi(string:abc,int:1)", uniqsi.call(SOMEB,ABC,ONE).tojstring() ); + assertEquals( "uniqis(int:1,string:abc)", uniqis.call(SOMEB,ONE,ABC).tojstring() ); + assertEquals( "uniqis(int:1,string:abc)", uniqis.invoke(LuaValue.varargsOf(new LuaValue[] {SOMEB,ONE,ABC,ONE})).arg1().tojstring() ); + } + public void testOverloadedMethodAttributeCoercible() { + B b = new B(); + JavaInstance ib = new JavaInstance(b); + LuaValue p = ib.get("pick"); + assertEquals( "pick()", p.call(SOMEB).tojstring() ); + assertEquals( "pick(string:abc)", p.call(SOMEB,ABC).tojstring() ); + assertEquals( "pick(int:1)", p.call(SOMEB,ONE).tojstring() ); + assertEquals( "pick(string:abc,int:1)", p.call(SOMEB,ABC,ONE).tojstring() ); + assertEquals( "pick(int:1,string:abc)", p.call(SOMEB,ONE,ABC).tojstring() ); + assertEquals( "pick(int:1,string:abc)", p.invoke(LuaValue.varargsOf(new LuaValue[] {SOMEB,ONE,ABC,ONE})).arg1().tojstring() ); + } + public void testUnboundOverloadedMethodAttributeCoercible() { + B b = new B(); + JavaInstance ib = new JavaInstance(b); + LuaValue p = ib.get("pick"); + assertEquals( JavaMethod.Overload.class, p.getClass() ); + assertEquals( "pick()", p.call(SOMEC).tojstring() ); + assertEquals( "class-c-pick(string:abc)", p.call(SOMEC,ABC).tojstring() ); + assertEquals( "class-c-pick(int:1)", p.call(SOMEC,ONE).tojstring() ); + assertEquals( "pick(string:abc,int:1)", p.call(SOMEC,ABC,ONE).tojstring() ); + assertEquals( "pick(int:1,string:abc)", p.call(SOMEC,ONE,ABC).tojstring() ); + assertEquals( "pick(int:1,string:abc)", p.invoke(LuaValue.varargsOf(new LuaValue[] {SOMEC,ONE,ABC,ONE})).arg1().tojstring() ); + } + public void testOverloadedStaticMethodAttributeCoercible() { + B b = new B(); + JavaInstance ib = new JavaInstance(b); + LuaValue p = ib.get("staticpick"); + assertEquals( "static-pick()", p.call(SOMEB).tojstring() ); + assertEquals( "static-pick(string:abc)", p.call(SOMEB,ABC).tojstring() ); + assertEquals( "static-pick(int:1)", p.call(SOMEB,ONE).tojstring() ); + assertEquals( "static-pick(string:abc,int:1)", p.call(SOMEB,ABC,ONE).tojstring() ); + assertEquals( "static-pick(int:1,string:abc)", p.call(SOMEB,ONE,ABC).tojstring() ); + assertEquals( "static-pick(int:1,string:abc)", p.invoke(LuaValue.varargsOf(new LuaValue[] {SOMEB,ONE,ABC,ONE})).arg1().tojstring() ); + } + public void testGetInnerClass() { + C c = new C(); + JavaInstance ic = new JavaInstance(c); + LuaValue d = ic.get("D"); + assertFalse(d.isnil()); + assertSame(d, JavaClass.forClass(C.D.class)); + LuaValue e = ic.get("E"); + assertTrue(e.isnil()); + } +} diff --git a/jse/src/test/java/org/luaj/vm2/libs/jse/OsLibTest.java b/jse/src/test/java/org/luaj/vm2/libs/jse/OsLibTest.java new file mode 100644 index 00000000..f3b26007 --- /dev/null +++ b/jse/src/test/java/org/luaj/vm2/libs/jse/OsLibTest.java @@ -0,0 +1,77 @@ +package org.luaj.vm2.libs.jse; + +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.libs.jme.JmePlatform; + +import junit.framework.TestCase; + +public class OsLibTest extends TestCase { + + LuaValue jme_lib; + LuaValue jse_lib; + double time; + + public void setUp() { + jse_lib = JsePlatform.standardGlobals().get("os");; + jme_lib = JmePlatform.standardGlobals().get("os");; + time = new java.util.Date(2001-1900, 7, 23, 14, 55, 02).getTime() / 1000.0; + } + + void t(String format, String expected) { + String actual = jme_lib.get("date").call(LuaValue.valueOf(format), LuaValue.valueOf(time)).tojstring(); + assertEquals(expected, actual); + } + + public void testStringDateChars() { t("foo", "foo"); } + public void testStringDate_a() { t("%a", "Thu"); } + public void testStringDate_A() { t("%A", "Thursday"); } + public void testStringDate_b() { t("%b", "Aug"); } + public void testStringDate_B() { t("%B", "August"); } + public void testStringDate_c() { t("%c", "Thu Aug 23 14:55:02 2001"); } + public void testStringDate_d() { t("%d", "23"); } + public void testStringDate_H() { t("%H", "14"); } + public void testStringDate_I() { t("%I", "02"); } + public void testStringDate_j() { t("%j", "235"); } + public void testStringDate_m() { t("%m", "08"); } + public void testStringDate_M() { t("%M", "55"); } + public void testStringDate_p() { t("%p", "PM"); } + public void testStringDate_S() { t("%S", "02"); } + public void testStringDate_U() { t("%U", "33"); } + public void testStringDate_w() { t("%w", "4"); } + public void testStringDate_W() { t("%W", "34"); } + public void testStringDate_x() { t("%x", "08/23/01"); } + public void testStringDate_X() { t("%X", "14:55:02"); } + public void testStringDate_y() { t("%y", "01"); } + public void testStringDate_Y() { t("%Y", "2001"); } + public void testStringDate_Pct() { t("%%", "%"); } + + static final double DAY = 24. * 3600.; + public void testStringDate_UW_neg4() { time-=4*DAY; t("%c %U %W", "Sun Aug 19 14:55:02 2001 33 33"); } + public void testStringDate_UW_neg3() { time-=3*DAY; t("%c %U %W", "Mon Aug 20 14:55:02 2001 33 34"); } + public void testStringDate_UW_neg2() { time-=2*DAY; t("%c %U %W", "Tue Aug 21 14:55:02 2001 33 34"); } + public void testStringDate_UW_neg1() { time-=DAY; t("%c %U %W", "Wed Aug 22 14:55:02 2001 33 34"); } + public void testStringDate_UW_pos0() { time+=0; t("%c %U %W", "Thu Aug 23 14:55:02 2001 33 34"); } + public void testStringDate_UW_pos1() { time+=DAY; t("%c %U %W", "Fri Aug 24 14:55:02 2001 33 34"); } + public void testStringDate_UW_pos2() { time+=2*DAY; t("%c %U %W", "Sat Aug 25 14:55:02 2001 33 34"); } + public void testStringDate_UW_pos3() { time+=3*DAY; t("%c %U %W", "Sun Aug 26 14:55:02 2001 34 34"); } + public void testStringDate_UW_pos4() { time+=4*DAY; t("%c %U %W", "Mon Aug 27 14:55:02 2001 34 35"); } + + public void testJseOsGetenvForEnvVariables() { + LuaValue USER = LuaValue.valueOf("USER"); + LuaValue jse_user = jse_lib.get("getenv").call(USER); + LuaValue jme_user = jme_lib.get("getenv").call(USER); + assertFalse(jse_user.isnil()); + assertTrue(jme_user.isnil()); + System.out.println("User: " + jse_user); + } + + public void testJseOsGetenvForSystemProperties() { + System.setProperty("test.key.foo", "test.value.bar"); + LuaValue key = LuaValue.valueOf("test.key.foo"); + LuaValue value = LuaValue.valueOf("test.value.bar"); + LuaValue jse_value = jse_lib.get("getenv").call(key); + LuaValue jme_value = jme_lib.get("getenv").call(key); + assertEquals(value, jse_value); + assertEquals(value, jme_value); + } +} diff --git a/jse/src/test/java/org/luaj/vm2/libs/jse/TestClass.java b/jse/src/test/java/org/luaj/vm2/libs/jse/TestClass.java new file mode 100644 index 00000000..ca768c45 --- /dev/null +++ b/jse/src/test/java/org/luaj/vm2/libs/jse/TestClass.java @@ -0,0 +1,22 @@ +package org.luaj.vm2.libs.jse; + +public class TestClass { + private static class PrivateImpl implements TestInterface { + public String public_field; + public PrivateImpl() { + this.public_field = "privateImpl-constructor"; + } + PrivateImpl(String f) { + this.public_field = f; + } + public String public_method() { return "privateImpl-"+public_field+"-public_method"; } + public String interface_method(String x) { return "privateImpl-"+public_field+"-interface_method("+x+")"; } + public String toString() { return public_field; } + } + public TestInterface create_PrivateImpl(String f) { return new PrivateImpl(f); } + public Class get_PrivateImplClass() { return PrivateImpl.class; } + public enum SomeEnum { + ValueOne, + ValueTwo, + } +} \ No newline at end of file diff --git a/jse/src/test/java/org/luaj/vm2/libs/jse/TestInterface.java b/jse/src/test/java/org/luaj/vm2/libs/jse/TestInterface.java new file mode 100644 index 00000000..afab90ea --- /dev/null +++ b/jse/src/test/java/org/luaj/vm2/libs/jse/TestInterface.java @@ -0,0 +1,5 @@ +package org.luaj.vm2.libs.jse; + +public interface TestInterface { + String interface_method(String x); +} \ No newline at end of file