| /******************************************************************************* |
| * 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.vm3.lib; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| |
| import org.luaj.vm3.Globals; |
| import org.luaj.vm3.Lua; |
| import org.luaj.vm3.LuaError; |
| import org.luaj.vm3.LuaString; |
| import org.luaj.vm3.LuaTable; |
| import org.luaj.vm3.LuaThread; |
| import org.luaj.vm3.LuaValue; |
| import org.luaj.vm3.Varargs; |
| |
| /** |
| * Subclass of {@link LibFunction} which implements the lua basic library functions. |
| * <p> |
| * This contains all library functions listed as "basic functions" in the lua documentation for JME. |
| * The functions dofile and loadfile use the |
| * {@link #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. |
| * <p> |
| * To use basic library functions that include a {@link ResourceFinder} based on |
| * directory lookup, use {@link JseBaseLib} instead. |
| * <p> |
| * Typically, this library is included as part of a call to either |
| * {@link JsePlatform#standardGlobals()} or |
| * {@link JmePlatform#standardGlobals()} |
| * <pre> {@code |
| * Globals globals = JsePlatform.standardGlobals(); |
| * globals.get("print").call(LuaValue.valueOf("hello, world")); |
| * } </pre> |
| * <p> |
| * 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: |
| * <pre> {@code |
| * Globals globals = new Globals(); |
| * globals.load(new JseBaseLib()); |
| * globals.get("print").call(LuaValue.valueOf("hello, world")); |
| * } </pre> |
| * Doing so will ensure the library is properly initialized |
| * and loaded into the globals table. |
| * <p> |
| * This is a direct port of the corresponding library in C. |
| * @see JseBaseLib |
| * @see ResourceFinder |
| * @see #finder |
| * @see LibFunction |
| * @see JsePlatform |
| * @see JmePlatform |
| * @see <a href="http://www.lua.org/manual/5.2/manual.html#6.1">Lua 5.2 Base Lib Reference</a> |
| */ |
| public class BaseLib extends TwoArgFunction implements ResourceFinder { |
| |
| Globals globals; |
| |
| 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.checkjstring(1); |
| 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 { |
| this.argerror("gc op"); |
| } |
| 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) { |
| throw new LuaError(arg1.isnil() ? null : arg1.tojstring(), arg2.optint(1)); |
| } |
| } |
| |
| // "getmetatable", // ( object ) -> table |
| static final class getmetatable extends LibFunction { |
| public LuaValue call() { |
| return argerror(1, "value"); |
| } |
| |
| 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(); |
| args.argcheck(ld.isstring() || ld.isfunction(), 1, "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 String m = le.getMessage(); |
| return varargsOf(FALSE, m != null ? valueOf(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.println(); |
| return NONE; |
| } |
| } |
| |
| // "rawequal", // (v1, v2) -> boolean |
| static final class rawequal extends LibFunction { |
| public LuaValue call() { |
| return argerror(1, "value"); |
| } |
| |
| public LuaValue call(LuaValue arg) { |
| return argerror(2, "value"); |
| } |
| |
| public LuaValue call(LuaValue arg1, LuaValue arg2) { |
| return valueOf(arg1.raweq(arg2)); |
| } |
| } |
| |
| // "rawget", // (table, index) -> value |
| static final class rawget extends LibFunction { |
| public LuaValue call() { |
| return argerror(1, "value"); |
| } |
| |
| public LuaValue call(LuaValue arg) { |
| return argerror(2, "value"); |
| } |
| |
| 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 LibFunction { |
| public LuaValue call(LuaValue table) { |
| return argerror(2, "value"); |
| } |
| |
| public LuaValue call(LuaValue table, LuaValue index) { |
| return argerror(3, "value"); |
| } |
| |
| public LuaValue call(LuaValue table, LuaValue index, LuaValue value) { |
| LuaTable t = table.checktable(); |
| t.rawset(index.checknotnil(), 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 LibFunction { |
| public LuaValue call(LuaValue table) { |
| return argerror(2, "value"); |
| } |
| |
| public LuaValue call(LuaValue table, LuaValue metatable) { |
| final LuaValue mt0 = table.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 String m = le.getMessage(); |
| return varargsOf(FALSE, m != null ? valueOf(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) { |
| LuaValue s = func.call(); |
| if (s.isnil()) |
| return -1; |
| LuaString ls = s.strvalue(); |
| bytes = ls.m_bytes; |
| offset = ls.m_offset; |
| remaining = ls.m_length; |
| if (remaining <= 0) |
| return -1; |
| } |
| --remaining; |
| return bytes[offset++]; |
| } |
| } |
| } |