diff --git a/src/main/java/org/squiddev/cobalt/Constants.java b/src/main/java/org/squiddev/cobalt/Constants.java index 1e20fca3..5099ec9f 100644 --- a/src/main/java/org/squiddev/cobalt/Constants.java +++ b/src/main/java/org/squiddev/cobalt/Constants.java @@ -241,6 +241,11 @@ public class Constants { */ public static final LuaString EMPTYSTRING = valueOf(""); + /** + * The global loaded package table. + */ + public static final LuaString LOADED = valueOf("_LOADED"); + /** * Constant limiting metatag loop processing */ diff --git a/src/main/java/org/squiddev/cobalt/GlobalRegistry.java b/src/main/java/org/squiddev/cobalt/GlobalRegistry.java new file mode 100644 index 00000000..0f9f6cc0 --- /dev/null +++ b/src/main/java/org/squiddev/cobalt/GlobalRegistry.java @@ -0,0 +1,36 @@ +package org.squiddev.cobalt; + +/** + * The global registry, a store of Lua values + */ +public final class GlobalRegistry { + private final LuaTable table = new LuaTable(); + + GlobalRegistry() { + } + + /** + * Get the underlying registry table. + * + * @return The global debug registry. + */ + public LuaTable get() { + return table; + } + + /** + * Get a subtable in the global {@linkplain #get()} registry table}. If the key exists but is not a table, then + * it will be overridden. + * + * @param name The name of the registry table. + * @return The subentry. + */ + public LuaTable getSubTable(LuaString name) { + LuaValue value = table.rawget(name); + if (value.isTable()) return (LuaTable) value; + + LuaTable newValue = new LuaTable(); + table.rawset(name, newValue); + return newValue; + } +} diff --git a/src/main/java/org/squiddev/cobalt/LuaState.java b/src/main/java/org/squiddev/cobalt/LuaState.java index 0c669d66..25124872 100644 --- a/src/main/java/org/squiddev/cobalt/LuaState.java +++ b/src/main/java/org/squiddev/cobalt/LuaState.java @@ -28,12 +28,7 @@ import org.squiddev.cobalt.compiler.LuaC; import org.squiddev.cobalt.debug.DebugHandler; import org.squiddev.cobalt.debug.DebugHelpers; -import org.squiddev.cobalt.lib.platform.FileResourceManipulator; -import org.squiddev.cobalt.lib.platform.ResourceManipulator; -import java.io.InputStream; -import java.io.PrintStream; -import java.util.TimeZone; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; @@ -44,16 +39,6 @@ * Global lua state */ public final class LuaState { - /** - * The active input stream - */ - public InputStream stdin; - - /** - * The active output stream - */ - public PrintStream stdout; - /** * The metatable for all strings */ @@ -84,31 +69,16 @@ public final class LuaState { */ public LuaTable threadMetatable; - /** - * Lookup of loaded packages - */ - public final LuaTable loadedPackages = new LuaTable(); - - /** - * The active resource manipulator - */ - public final ResourceManipulator resourceManipulator; - /** * The compiler for this threstate */ - public final LoadState.LuaCompiler compiler; + public final LoadState.FunctionFactory compiler; /** * The handler for the debugger. Override this for custom debug actions. */ public final DebugHandler debug; - /** - * The timezone for this state, as used by {@code os}. - */ - public final TimeZone timezone; - /** * The currently executing thread */ @@ -136,29 +106,31 @@ public final class LuaState { */ private final ErrorReporter reportError; + private final GlobalRegistry registry = new GlobalRegistry(); + public LuaState() { this(new LuaState.Builder()); } private LuaState(Builder builder) { - stdin = builder.stdin; - stdout = builder.stdout; - stringMetatable = builder.stringMetatable; - booleanMetatable = builder.booleanMetatable; - numberMetatable = builder.numberMetatable; - nilMetatable = builder.nilMetatable; - functionMetatable = builder.functionMetatable; - threadMetatable = builder.threadMetatable; - resourceManipulator = builder.resourceManipulator; compiler = builder.compiler; debug = builder.debug; - timezone = builder.timezone; threader = new YieldThreader(builder.coroutineExecutor); reportError = builder.reportError; mainThread = currentThread = new LuaThread(this, new LuaTable()); } + /** + * Get the global registry, a Lua table used to store Lua values. + * + * @return The global debug registry. + */ + public GlobalRegistry registry() { + return registry; + } + + /** * Abandon this state, instructing any pending thread to terminate. */ @@ -247,18 +219,8 @@ public static class Builder { return thread; }); - private InputStream stdin = System.in; - private PrintStream stdout = System.out; - private LuaTable stringMetatable; - private LuaTable booleanMetatable; - private LuaTable numberMetatable; - private LuaTable nilMetatable; - private LuaTable functionMetatable; - private LuaTable threadMetatable; - private ResourceManipulator resourceManipulator = new FileResourceManipulator(); - private LoadState.LuaCompiler compiler = LuaC.INSTANCE; + private LoadState.FunctionFactory compiler = LoadState::interpretedFunction; private DebugHandler debug = DebugHandler.INSTANCE; - private TimeZone timezone = TimeZone.getDefault(); private Executor coroutineExecutor = defaultCoroutineExecutor; private ErrorReporter reportError; @@ -271,118 +233,13 @@ public LuaState build() { return new LuaState(this); } - /** - * Set the initial standard input for this Lua state. This defaults to {@link System#in}. - * - * @param stdin The new standard input - * @return This builder - * @see LuaState#stdin - */ - public Builder stdin(InputStream stdin) { - if (stdin == null) throw new NullPointerException("stdin cannot be null"); - this.stdin = stdin; - return this; - } - - /** - * Set the initial standard output for this Lua state. This defaults to {@link System#out}. - * - * @param stdout The new standard output - * @return This builder - * @see LuaState#stdout - */ - public Builder stdout(PrintStream stdout) { - if (stdout == null) throw new NullPointerException("stdout cannot be null"); - this.stdout = stdout; - return this; - } - - /** - * Set the initial metatable for string values within this Lua State. This defaults to {@code null}. - * - * @param metatable The initial metatable - * @return This builder - */ - public Builder stringMetatable(LuaTable metatable) { - stringMetatable = metatable; - return this; - } - - /** - * Set the initial metatable for boolean values within this Lua State. This defaults to {@code null}. - * - * @param metatable The initial metatable - * @return This builder - */ - public Builder booleanMetatable(LuaTable metatable) { - booleanMetatable = metatable; - return this; - } - - /** - * Set the initial metatable for numeric values within this Lua State. This defaults to {@code null}. - * - * @param metatable The initial metatable - * @return This builder - */ - public Builder numberMetatable(LuaTable metatable) { - numberMetatable = metatable; - return this; - } - - /** - * Set the initial metatable for nil values within this Lua State. This defaults to {@code null}. - * - * @param metatable The initial metatable - * @return This builder - */ - public Builder nilMetatable(LuaTable metatable) { - nilMetatable = metatable; - return this; - } - - /** - * Set the initial metatable for functions within this Lua State. This defaults to {@code null}. - * - * @param metatable The initial metatable - * @return This builder - */ - public Builder functionMetatable(LuaTable metatable) { - functionMetatable = metatable; - return this; - } - - /** - * Set the initial metatable for threads within this Lua State. This defaults to {@code null}. - * - * @param metatable The initial metatable - * @return This builder - */ - public Builder threadMetatable(LuaTable metatable) { - threadMetatable = metatable; - return this; - } - - /** - * Set the resource manipulator that the {@code os} and {@code io} libraries will use. This defaults to a - * {@link FileResourceManipulator}, which uses the default file system. - * - * @param resourceManipulator The new resource manipulator - * @return This builder - */ - public Builder resourceManipulator(ResourceManipulator resourceManipulator) { - if (this.resourceManipulator == null) throw new NullPointerException("resourceManipulator cannot be null"); - this.resourceManipulator = resourceManipulator; - return this; - } - /** * Set the compiler for this Lua state. This defaults to using the {@link LuaC} compiler. * * @param compiler The new compiler to use * @return This builder */ - public Builder compiler(LoadState.LuaCompiler compiler) { + public Builder compiler(LoadState.FunctionFactory compiler) { if (compiler == null) throw new NullPointerException("compiler cannot be null"); this.compiler = compiler; return this; @@ -400,18 +257,6 @@ public Builder debug(DebugHandler debug) { return this; } - /** - * Set the timezone for this Lua state. - * - * @param zone The new timezone - * @return This builder - */ - public Builder timezone(TimeZone zone) { - if (zone == null) throw new NullPointerException("zone cannot be null"); - timezone = zone; - return this; - } - /** * Set the coroutine executor for this state. * diff --git a/src/main/java/org/squiddev/cobalt/LuaTable.java b/src/main/java/org/squiddev/cobalt/LuaTable.java index 48fa5bb9..956e6af6 100644 --- a/src/main/java/org/squiddev/cobalt/LuaTable.java +++ b/src/main/java/org/squiddev/cobalt/LuaTable.java @@ -24,9 +24,6 @@ */ package org.squiddev.cobalt; -import org.squiddev.cobalt.function.LuaFunction; -import org.squiddev.cobalt.lib.LuaLibrary; - import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; @@ -716,19 +713,6 @@ public LuaValue[] keys() throws LuaError { return l.toArray(new LuaValue[l.size()]); } - /** - * Load a library instance by setting its environment to {@code this} - * and calling it, which should initialize the library instance and - * install itself into this instance. - * - * @param state The current lua state - * @param library The callable {@link LuaFunction} to load into {@code this} - * @return {@link LuaValue} containing the result of the initialization call. - */ - public LuaValue load(LuaState state, LuaLibrary library) { - return library.add(state, this); - } - //region Resizing /** diff --git a/src/main/java/org/squiddev/cobalt/LuaThread.java b/src/main/java/org/squiddev/cobalt/LuaThread.java index 7e1c56a1..c82c3ed5 100644 --- a/src/main/java/org/squiddev/cobalt/LuaThread.java +++ b/src/main/java/org/squiddev/cobalt/LuaThread.java @@ -29,7 +29,7 @@ import org.squiddev.cobalt.debug.DebugState; import org.squiddev.cobalt.function.LuaFunction; import org.squiddev.cobalt.lib.CoroutineLib; -import org.squiddev.cobalt.lib.jse.JsePlatform; +import org.squiddev.cobalt.lib.CoreLibraries; import java.lang.ref.WeakReference; import java.util.Objects; @@ -45,7 +45,7 @@ * A LuaThread is typically created in response to a scripted call to * {@code coroutine.create()} *

- * The utility class {@link JsePlatform} + * The utility class {@link CoreLibraries} * sees to it that this initialization is done properly. * For this reason it is highly recommended to use one of these classes * when initializing globals. @@ -55,7 +55,7 @@ * to manage call state, it is possible to yield from anywhere in luaj. * * @see LuaValue - * @see JsePlatform + * @see CoreLibraries * @see CoroutineLib */ public class LuaThread extends LuaValue { diff --git a/src/main/java/org/squiddev/cobalt/LuaValue.java b/src/main/java/org/squiddev/cobalt/LuaValue.java index 49811f1c..08617840 100644 --- a/src/main/java/org/squiddev/cobalt/LuaValue.java +++ b/src/main/java/org/squiddev/cobalt/LuaValue.java @@ -27,7 +27,7 @@ import org.squiddev.cobalt.compiler.LoadState; import org.squiddev.cobalt.function.LuaClosure; import org.squiddev.cobalt.function.LuaFunction; -import org.squiddev.cobalt.lib.jse.JsePlatform; +import org.squiddev.cobalt.lib.CoreLibraries; import static org.squiddev.cobalt.Constants.*; @@ -85,14 +85,14 @@ * } * For this to work the file must be in the current directory, or in the class path, * depending on the platform. - * See {@link JsePlatform} for details. + * See {@link CoreLibraries} for details. *

* In general a {@link LuaError} may be thrown on any operation when the * types supplied to any operation are illegal from a lua perspective. * Examples could be attempting to concatenate a NIL value, or attempting arithmetic * on values that are not number. * - * @see JsePlatform + * @see CoreLibraries * @see LoadState * @see Varargs */ @@ -1132,15 +1132,13 @@ public LuaValue getfenv() { /** * Set the environment on an object. *

- * Typically the environment is created once per application via a platform - * helper method such as {@link JsePlatform#standardGlobals(LuaState)} * However, any object can serve as an environment if it contains suitable metatag * values to implement {@link OperationHelper#getTable(LuaState, LuaValue, LuaValue)} to provide the environment * values. * * @param env {@link LuaValue} (typically a {@link LuaTable}) containing the environment. * @return If the environment could be changed. - * @see JsePlatform + * @see CoreLibraries */ public boolean setfenv(LuaTable env) { return false; diff --git a/src/main/java/org/squiddev/cobalt/cmd/lua.java b/src/main/java/org/squiddev/cobalt/cmd/lua.java deleted file mode 100644 index 5bd5836e..00000000 --- a/src/main/java/org/squiddev/cobalt/cmd/lua.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * The MIT License (MIT) - * - * Original Source: Copyright (c) 2009-2011 Luaj.org. All rights reserved. - * Modifications: Copyright (c) 2015-2020 SquidDev - * - * 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.squiddev.cobalt.cmd; - -import org.squiddev.cobalt.*; -import org.squiddev.cobalt.compiler.CompileException; -import org.squiddev.cobalt.compiler.LoadState; -import org.squiddev.cobalt.function.LuaFunction; -import org.squiddev.cobalt.lib.LuaLibrary; -import org.squiddev.cobalt.lib.jse.JsePlatform; - -import java.io.*; -import java.util.ArrayList; -import java.util.List; - -import static org.squiddev.cobalt.Constants.NONE; -import static org.squiddev.cobalt.ValueFactory.*; - -/** - * org.squiddev.cobalt.cmd.lua command for use in java se environments. - */ -public class lua { - private static final String version = Lua._VERSION + "Copyright (c) 2009 Luaj.org.org"; - - private static final String usage = - "usage: java -cp luaj-jse.jar org.squiddev.cobalt.cmd.lua [options] [script [args]].\n" + - "Available options are:\n" + - " -e stat execute string 'stat'\n" + - " -l name require library 'name'\n" + - " -i enter interactive mode after executing 'script'\n" + - " -v show version information\n" + - " -n nodebug - do not load debug library by default\n" + - " -- stop handling options\n" + - " - execute stdin and stop handling options"; - - private static void usageExit() { - System.out.println(usage); - System.exit(-1); - } - - private static LuaTable _G; - - public static void main(String[] args) throws IOException { - - // process args - boolean interactive = (args.length == 0); - boolean versioninfo = false; - boolean processing = true; - List libs = null; - try { - // stateful argument processing - for (int i = 0; i < args.length; i++) { - if (!processing || !args[i].startsWith("-")) { - // input file - defer to last stage - break; - } else if (args[i].length() <= 1) { - // input file - defer to last stage - break; - } else { - switch (args[i].charAt(1)) { - case 'e': - if (++i >= args.length) { - usageExit(); - } - // input script - defer to last stage - break; - case 'l': - if (++i >= args.length) { - usageExit(); - } - if (libs == null) libs = new ArrayList<>(); - libs.add(args[i]); - break; - case 'i': - interactive = true; - break; - case 'v': - versioninfo = true; - break; - case '-': - if (args[i].length() > 2) { - usageExit(); - } - processing = false; - break; - default: - usageExit(); - break; - } - } - } - - // echo version - if (versioninfo) { - System.out.println(version); - } - - // new org.squiddev.cobalt.cmd.lua state - LuaState state = new LuaState(); - _G = JsePlatform.debugGlobals(state); - for (int i = 0, n = libs != null ? libs.size() : 0; i < n; i++) { - loadLibrary(state, libs.get(i)); - } - - // input script processing - processing = true; - for (int i = 0; i < args.length; i++) { - if (!processing || !args[i].startsWith("-")) { - processScript(state, new FileInputStream(args[i]), args[i], args, i, false); - break; - } else if ("-".equals(args[i])) { - processScript(state, System.in, "=stdin", args, i, false); - break; - } else { - switch (args[i].charAt(1)) { - case 'l': - ++i; - break; - case 'e': - ++i; - processScript(state, new ByteArrayInputStream(args[i].getBytes()), "string", args, i, false); - break; - case '-': - processing = false; - break; - } - } - } - - if (interactive) { - interactiveMode(state); - } - - } catch (IOException ioe) { - System.err.println(ioe.toString()); - System.exit(-2); - } - } - - private static void loadLibrary(LuaState state, String libname) throws IOException { - LuaValue slibname = valueOf(libname); - try { - // load via plain require - OperationHelper.noUnwind(state, () -> - OperationHelper.call(state, OperationHelper.getTable(state, _G, valueOf("require")), slibname)); - } catch (Exception e) { - try { - // load as java class - LuaLibrary v = Class.forName(libname).asSubclass(LuaLibrary.class).newInstance(); - v.add(state, _G); - } catch (Exception f) { - throw new IOException("loadLibrary(" + libname + ") failed: " + e + "," + f); - } - } - } - - private static void processScript(LuaState state, InputStream script, String chunkname, String[] args, int firstarg, boolean printValue) throws IOException { - try { - LuaFunction c; - try { - c = LoadState.load(state, script, valueOf(chunkname), _G); - } finally { - script.close(); - } - Varargs scriptargs = (args != null ? setGlobalArg(args, firstarg) : NONE); - Varargs result = LuaThread.runMain(state, c, scriptargs); - - if (printValue && result != NONE) { - OperationHelper.noUnwind(state, () -> - OperationHelper.invoke(state, OperationHelper.getTable(state, _G, valueOf("print")), result)); - } - } catch (CompileException e) { - System.out.println(); - System.out.println(e.getMessage()); - } catch (LuaError e) { - System.out.println(); - System.out.println(e.traceback); - if (e.getCause() != null && e.getCause() != e) e.getCause().printStackTrace(System.out); - } catch (Exception e) { - System.out.println(); - e.printStackTrace(System.out); - } - } - - private static Varargs setGlobalArg(String[] args, int i) { - LuaTable arg = tableOf(); - LuaValue[] values = new LuaValue[args.length]; - for (int j = 0; j < args.length; j++) { - arg.rawset(j - i, values[j] = valueOf(args[j])); - } - _G.rawset("arg", arg); - return varargsOf(values); - } - - private static void interactiveMode(LuaState state) throws IOException { - BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); - while (true) { - System.out.print("> "); - System.out.flush(); - String line = reader.readLine(); - if (line == null) { - return; - } - processScript(state, new ByteArrayInputStream(line.getBytes()), "=stdin", null, 0, true); - } - } -} diff --git a/src/main/java/org/squiddev/cobalt/cmd/luac.java b/src/main/java/org/squiddev/cobalt/cmd/luac.java deleted file mode 100644 index 6ca2407e..00000000 --- a/src/main/java/org/squiddev/cobalt/cmd/luac.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * The MIT License (MIT) - * - * Original Source: Copyright (c) 2009-2011 Luaj.org. All rights reserved. - * Modifications: Copyright (c) 2015-2020 SquidDev - * - * 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.squiddev.cobalt.cmd; - -import org.squiddev.cobalt.Lua; -import org.squiddev.cobalt.LuaState; -import org.squiddev.cobalt.Print; -import org.squiddev.cobalt.Prototype; -import org.squiddev.cobalt.compiler.DumpState; -import org.squiddev.cobalt.compiler.LuaC; -import org.squiddev.cobalt.lib.jse.JsePlatform; - -import java.io.*; - -import static org.squiddev.cobalt.ValueFactory.valueOf; - - -/** - * Compiler for org.squiddev.cobalt.cmd.lua files to org.squiddev.cobalt.cmd.lua bytecode. - */ -public class luac { - private static final String version = Lua._VERSION + "Copyright (C) 2009 luaj.org"; - - private static final String usage = - "usage: java -cp luaj-jse.jar org.squiddev.cobalt.cmd.luac [options] [filenames].\n" + - "Available options are:\n" + - " - process stdin\n" + - " -l list\n" + - " -o name output to file 'name' (default is \"org.squiddev.cobalt.cmd.luac.out\")\n" + - " -p parse only\n" + - " -s strip debug information\n" + - " -E big endian format for numbers\n" + - " -i number format 'n', (n=0,1 or 4, default=" + DumpState.NUMBER_FORMAT_DEFAULT + ")\n" + - " -v show version information\n" + - " -- stop handling options\n"; - - private static void usageExit() { - System.out.println(usage); - System.exit(-1); - } - - private boolean list = false; - private String output = "luac.out"; - private boolean parseonly = false; - private boolean stripdebug = false; - private boolean littleendian = true; - private int numberformat = DumpState.NUMBER_FORMAT_DEFAULT; - private boolean versioninfo = false; - private boolean processing = true; - - public static void main(String[] args) throws IOException { - new luac(args); - } - - private luac(String[] args) throws IOException { - - // process args - try { - // get stateful args - for (int i = 0; i < args.length; i++) { - if (!processing || !args[i].startsWith("-")) { - // input file - defer to next stage - } else if (args[i].length() <= 1) { - // input file - defer to next stage - } else { - switch (args[i].charAt(1)) { - case 'l': - list = true; - break; - case 'o': - if (++i >= args.length) { - usageExit(); - } - output = args[i]; - break; - case 'p': - parseonly = true; - break; - case 's': - stripdebug = true; - break; - case 'E': - littleendian = false; - break; - case 'i': - if (args[i].length() <= 2) { - usageExit(); - } - numberformat = Integer.parseInt(args[i].substring(2)); - break; - case 'v': - versioninfo = true; - break; - case '-': - if (args[i].length() > 2) { - usageExit(); - } - processing = false; - break; - default: - usageExit(); - break; - } - } - } - - // echo version - if (versioninfo) { - System.out.println(version); - } - - // open output file - - // process input files - OutputStream fos = new FileOutputStream(output); - try { - JsePlatform.standardGlobals(new LuaState()); - processing = true; - for (int i = 0; i < args.length; i++) { - if (!processing || !args[i].startsWith("-")) { - processScript(new FileInputStream(args[i]), "@" + args[i], fos); - } else if (args[i].length() <= 1) { - processScript(System.in, "=stdin", fos); - } else { - switch (args[i].charAt(1)) { - case 'o': - ++i; - break; - case '-': - processing = false; - break; - } - } - } - } finally { - fos.close(); - } - - } catch (IOException ioe) { - System.err.println(ioe.toString()); - System.exit(-2); - } - } - - private void processScript(InputStream script, String chunkname, OutputStream out) throws IOException { - try { - // create the chunk - Prototype chunk = LuaC.compile(script, valueOf(chunkname)); - - // list the chunk - if (list) { - Print.printCode(new PrintWriter(System.out), chunk, false); - } - - // write out the chunk - if (!parseonly) { - DumpState.dump(chunk, out, stripdebug, numberformat, littleendian); - } - - } catch (Exception e) { - e.printStackTrace(System.err); - } finally { - script.close(); - } - } -} diff --git a/src/main/java/org/squiddev/cobalt/compiler/BinOpr.java b/src/main/java/org/squiddev/cobalt/compiler/BinOpr.java index 8955fd2f..1a3bcdce 100644 --- a/src/main/java/org/squiddev/cobalt/compiler/BinOpr.java +++ b/src/main/java/org/squiddev/cobalt/compiler/BinOpr.java @@ -31,40 +31,40 @@ enum BinOpr { this.right = right; } - static BinOpr ofToken(int op) { - switch (op) { - case '+': - return ADD; - case '-': - return SUB; - case '*': - return MUL; - case '/': - return DIV; - case '%': - return MOD; - case '^': - return POW; - case TK_CONCAT: - return CONCAT; - case TK_NE: - return NE; - case TK_EQ: - return EQ; - case '<': - return LT; - case TK_LE: - return LE; - case '>': - return GT; - case TK_GE: - return GE; - case TK_AND: - return AND; - case TK_OR: - return OR; - default: - return null; - } - } + static BinOpr ofToken(int op) { + switch (op) { + case '+': + return ADD; + case '-': + return SUB; + case '*': + return MUL; + case '/': + return DIV; + case '%': + return MOD; + case '^': + return POW; + case TK_CONCAT: + return CONCAT; + case TK_NE: + return NE; + case TK_EQ: + return EQ; + case '<': + return LT; + case TK_LE: + return LE; + case '>': + return GT; + case TK_GE: + return GE; + case TK_AND: + return AND; + case TK_OR: + return OR; + default: + return null; + } + } } diff --git a/src/main/java/org/squiddev/cobalt/compiler/DumpState.java b/src/main/java/org/squiddev/cobalt/compiler/BytecodeDumper.java similarity index 90% rename from src/main/java/org/squiddev/cobalt/compiler/DumpState.java rename to src/main/java/org/squiddev/cobalt/compiler/BytecodeDumper.java index 09ff798e..52044ada 100644 --- a/src/main/java/org/squiddev/cobalt/compiler/DumpState.java +++ b/src/main/java/org/squiddev/cobalt/compiler/BytecodeDumper.java @@ -34,13 +34,7 @@ import java.io.IOException; import java.io.OutputStream; -public class DumpState { - - /** - * mark for precompiled code (\033Lua) - */ - public static final String LUA_SIGNATURE = "\033Lua"; - +public class BytecodeDumper { /** * for header of binary files -- this is Lua 5.1 */ @@ -56,11 +50,6 @@ public class DumpState { */ public static final int LUAC_HEADERSIZE = 12; - /** - * expected lua header bytes - */ - private static final byte[] LUAC_HEADER_SIGNATURE = {'\033', 'L', 'u', 'a'}; - /** * set true to allow integer compilation */ @@ -94,14 +83,12 @@ public class DumpState { private static final int SIZEOF_SIZET = 4; private static final int SIZEOF_INSTRUCTION = 4; - DataOutputStream writer; - boolean strip; - int status; + final DataOutputStream writer; + final boolean strip; - public DumpState(OutputStream w, boolean strip) { + public BytecodeDumper(OutputStream w, boolean strip) { this.writer = new DataOutputStream(w); this.strip = strip; - this.status = 0; } void dumpBlock(final byte[] b, int size) throws IOException { @@ -244,7 +231,7 @@ void dumpFunction(final Prototype f, final LuaString string) throws IOException } void dumpHeader() throws IOException { - writer.write(LUAC_HEADER_SIGNATURE); + writer.write(LoadState.LUA_SIGNATURE); writer.write(LUAC_VERSION); writer.write(LUAC_FORMAT); writer.write(IS_LITTLE_ENDIAN ? 1 : 0); @@ -258,11 +245,10 @@ void dumpHeader() throws IOException { /* * Dump Lua function as precompiled chunk */ - public static int dump(Prototype f, OutputStream w, boolean strip) throws IOException { - DumpState D = new DumpState(w, strip); + public static void dump(Prototype f, OutputStream w, boolean strip) throws IOException { + BytecodeDumper D = new BytecodeDumper(w, strip); D.dumpHeader(); D.dumpFunction(f, null); - return D.status; } /** @@ -271,11 +257,10 @@ public static int dump(Prototype f, OutputStream w, boolean strip) throws IOExce * @param stripDebug true to strip debugging info, false otherwise * @param numberFormat one of NUMBER_FORMAT_FLOATS_OR_DOUBLES, NUMBER_FORMAT_INTS_ONLY, NUMBER_FORMAT_NUM_PATCH_INT32 * @param littleendian true to use little endian for numbers, false for big endian - * @return 0 if dump succeeds * @throws IOException On stream write errors * @throws IllegalArgumentException if the number format it not supported */ - public static int dump(Prototype f, OutputStream w, boolean stripDebug, int numberFormat, boolean littleendian) throws IOException { + public static void dump(Prototype f, OutputStream w, boolean stripDebug, int numberFormat, boolean littleendian) throws IOException { switch (numberFormat) { case NUMBER_FORMAT_FLOATS_OR_DOUBLES: case NUMBER_FORMAT_INTS_ONLY: @@ -284,12 +269,11 @@ public static int dump(Prototype f, OutputStream w, boolean stripDebug, int numb default: throw new IllegalArgumentException("number format not supported: " + numberFormat); } - DumpState D = new DumpState(w, stripDebug); + BytecodeDumper D = new BytecodeDumper(w, stripDebug); D.IS_LITTLE_ENDIAN = littleendian; D.NUMBER_FORMAT = numberFormat; D.SIZEOF_LUA_NUMBER = (numberFormat == NUMBER_FORMAT_INTS_ONLY ? 4 : 8); D.dumpHeader(); D.dumpFunction(f, null); - return D.status; } } diff --git a/src/main/java/org/squiddev/cobalt/compiler/BytecodeLoader.java b/src/main/java/org/squiddev/cobalt/compiler/BytecodeLoader.java index 6c069d52..2342fc97 100644 --- a/src/main/java/org/squiddev/cobalt/compiler/BytecodeLoader.java +++ b/src/main/java/org/squiddev/cobalt/compiler/BytecodeLoader.java @@ -36,7 +36,7 @@ /** * Parser for bytecode */ -public final class BytecodeLoader { +final class BytecodeLoader { /** * format corresponding to non-number-patched lua, all numbers are floats or doubles */ @@ -83,8 +83,7 @@ public final class BytecodeLoader { * @param stream The stream to read from */ public BytecodeLoader(InputStream stream) { - - this.is = new DataInputStream(stream); + is = new DataInputStream(stream); } private static final LuaValue[] NOVALUES = {}; diff --git a/src/main/java/org/squiddev/cobalt/compiler/CompileException.java b/src/main/java/org/squiddev/cobalt/compiler/CompileException.java index 98a33312..150efa50 100644 --- a/src/main/java/org/squiddev/cobalt/compiler/CompileException.java +++ b/src/main/java/org/squiddev/cobalt/compiler/CompileException.java @@ -30,18 +30,7 @@ public class CompileException extends Exception { private static final long serialVersionUID = 5563020350887073386L; - public CompileException() { - } - - public CompileException(String message) { + CompileException(String message) { super(message); } - - public CompileException(String message, Throwable cause) { - super(message, cause); - } - - public CompileException(Throwable cause) { - super(cause); - } } diff --git a/src/main/java/org/squiddev/cobalt/compiler/FuncState.java b/src/main/java/org/squiddev/cobalt/compiler/FuncState.java index 395c04f5..0dee7417 100644 --- a/src/main/java/org/squiddev/cobalt/compiler/FuncState.java +++ b/src/main/java/org/squiddev/cobalt/compiler/FuncState.java @@ -44,7 +44,7 @@ * This largely mirrors the same structure in {@code lparser.h}, but also handles emitting code (defined in lcode.h * in PUC Lua). */ -class FuncState { +final class FuncState { static class UpvalueDesc { final LuaString name; final ExpKind kind; diff --git a/src/main/java/org/squiddev/cobalt/compiler/IntPtr.java b/src/main/java/org/squiddev/cobalt/compiler/IntPtr.java index 7d72ae3a..d421a288 100644 --- a/src/main/java/org/squiddev/cobalt/compiler/IntPtr.java +++ b/src/main/java/org/squiddev/cobalt/compiler/IntPtr.java @@ -24,7 +24,7 @@ */ package org.squiddev.cobalt.compiler; -class IntPtr { +final class IntPtr { int value; IntPtr() { diff --git a/src/main/java/org/squiddev/cobalt/compiler/Lex.java b/src/main/java/org/squiddev/cobalt/compiler/Lex.java index 5a60a4dd..7c22fb43 100644 --- a/src/main/java/org/squiddev/cobalt/compiler/Lex.java +++ b/src/main/java/org/squiddev/cobalt/compiler/Lex.java @@ -19,7 +19,7 @@ *

* This largely follows the structure and implementation of llex.c. */ -class Lex { +final class Lex { private static final int EOZ = -1; static final int MAX_INT = Integer.MAX_VALUE - 2; diff --git a/src/main/java/org/squiddev/cobalt/compiler/LoadState.java b/src/main/java/org/squiddev/cobalt/compiler/LoadState.java index 4b3b691d..b1cab4a1 100644 --- a/src/main/java/org/squiddev/cobalt/compiler/LoadState.java +++ b/src/main/java/org/squiddev/cobalt/compiler/LoadState.java @@ -31,7 +31,7 @@ import org.squiddev.cobalt.function.LuaClosure; import org.squiddev.cobalt.function.LuaFunction; import org.squiddev.cobalt.function.LuaInterpretedFunction; -import org.squiddev.cobalt.lib.jse.JsePlatform; +import org.squiddev.cobalt.lib.CoreLibraries; import java.io.IOException; import java.io.InputStream; @@ -40,24 +40,24 @@ /** * Class to manage loading of {@link Prototype} instances. - * + *

* The {@link LoadState} class exposes one main function, * namely {@link #load(LuaState, InputStream, LuaString, LuaTable)}, * to be used to load code from a particular input stream. - * + *

* A simple pattern for loading and executing code is *

 {@code
  * LuaValue _G = JsePlatform.standardGlobals();
  * LoadState.load( new FileInputStream("main.lua"), "main.lua", _G ).call();
  * } 
- * This should work regardless of which {@link LuaCompiler} + * This should work regardless of which {@link FunctionFactory} * has been installed. - * + *

* Prior to loading code, a compiler should be installed. + *

+ * By default, when using {@link CoreLibraries} to construct globals, the {@link LuaC} compiler is installed. * - * By default, when using {@link JsePlatform} to construct globals, the {@link LuaC} compiler is installed. - * - * @see LuaCompiler + * @see FunctionFactory * @see LuaClosure * @see LuaFunction * @see LoadState#load(LuaState, InputStream, LuaString, LuaTable) @@ -65,39 +65,40 @@ */ public final class LoadState { /** - * Interface for the compiler, if it is installed. - * - * See the {@link LuaClosure} documentation for examples of how to use the compiler. - * - * @see LuaClosure - * @see #load(InputStream, LuaString, LuaString, LuaTable) + * Signature byte indicating the file is a compiled binary chunk */ - public interface LuaCompiler { + static final byte[] LUA_SIGNATURE = {27, 'L', 'u', 'a'}; + /** + * Name for compiled chunks + */ + private static final LuaString SOURCE_BINARY_STRING = valueOf("=?"); + + /** + * Construct our standard Lua function. + */ + public interface FunctionFactory { /** - * Load into a Closure or LuaFunction from a Stream and initializes the environment + * Create a {@link LuaClosure} from a {@link Prototype} and environment table. * - * @param stream Stream to read - * @param filename Name of chunk - * @param mode - * @param env Environment to load + * @param prototype The function prototype + * @param env The function's environment. * @return The loaded function - * @throws IOException On stream read error - * @throws CompileException If the stream cannot be loaded. */ - LuaClosure load(InputStream stream, LuaString filename, LuaString mode, LuaTable env) throws IOException, CompileException; + LuaClosure load(Prototype prototype, LuaTable env); } - /** - * Signature byte indicating the file is a compiled binary chunk - */ - private static final byte[] LUA_SIGNATURE = {27, 'L', 'u', 'a'}; + private LoadState() { + } /** - * Name for compiled chunks + * A basic {@link FunctionFactory} which loads into */ - public static final LuaString SOURCE_BINARY_STRING = valueOf("=?"); - + public static LuaClosure interpretedFunction(Prototype prototype, LuaTable env) { + LuaInterpretedFunction closure = new LuaInterpretedFunction(prototype, env); + closure.nilUpvalues(); + return closure; + } public static LuaClosure load(LuaState state, InputStream stream, String name, LuaTable env) throws IOException, CompileException { return load(state, stream, valueOf(name), env); @@ -120,43 +121,7 @@ public static LuaClosure load(LuaState state, InputStream stream, LuaString name } public static LuaClosure load(LuaState state, InputStream stream, LuaString name, LuaString mode, LuaTable env) throws IOException, CompileException { - if (state.compiler != null) return state.compiler.load(stream, name, mode, env); - - int firstByte = stream.read(); - if (firstByte != LUA_SIGNATURE[0]) throw new CompileException("no compiler"); - checkMode(mode, "binary"); - - Prototype p = loadBinaryChunk(firstByte, stream, name); - LuaInterpretedFunction closure = new LuaInterpretedFunction(p, env); - closure.nilUpvalues(); - return closure; - } - - /** - * Load lua thought to be a binary chunk from its first byte from an input stream. - * - * @param firstByte the first byte of the input stream - * @param stream InputStream to read, after having read the first byte already - * @param name Name to apply to the loaded chunk - * @return {@link Prototype} that was loaded - * @throws IllegalArgumentException If the signature is bac - * @throws IOException If an IOException occurs - * @throws CompileException If the stream cannot be loaded. - */ - public static Prototype loadBinaryChunk(int firstByte, InputStream stream, LuaString name) throws IOException, CompileException { - name = getSourceName(name); - // check rest of signature - if (firstByte != LUA_SIGNATURE[0] - || stream.read() != LUA_SIGNATURE[1] - || stream.read() != LUA_SIGNATURE[2] - || stream.read() != LUA_SIGNATURE[3]) { - throw new IllegalArgumentException("bad signature"); - } - - // load file as a compiled chunk - BytecodeLoader s = new BytecodeLoader(stream); - s.loadHeader(); - return s.loadFunction(name); + return state.compiler.load(LuaC.compile(stream, name, mode), env); } /** diff --git a/src/main/java/org/squiddev/cobalt/compiler/LuaC.java b/src/main/java/org/squiddev/cobalt/compiler/LuaC.java index cfd47b86..6575de83 100644 --- a/src/main/java/org/squiddev/cobalt/compiler/LuaC.java +++ b/src/main/java/org/squiddev/cobalt/compiler/LuaC.java @@ -25,13 +25,14 @@ package org.squiddev.cobalt.compiler; -import org.squiddev.cobalt.*; -import org.squiddev.cobalt.compiler.LoadState.LuaCompiler; -import org.squiddev.cobalt.function.LuaClosure; -import org.squiddev.cobalt.function.LuaFunction; +import org.squiddev.cobalt.Lua; +import org.squiddev.cobalt.LuaString; +import org.squiddev.cobalt.LuaValue; +import org.squiddev.cobalt.Prototype; +import org.squiddev.cobalt.compiler.LoadState.FunctionFactory; import org.squiddev.cobalt.function.LuaInterpretedFunction; import org.squiddev.cobalt.lib.BaseLib; -import org.squiddev.cobalt.lib.jse.JsePlatform; +import org.squiddev.cobalt.lib.CoreLibraries; import java.io.IOException; import java.io.InputStream; @@ -48,12 +49,12 @@ * and optionaly instantiates a {@link LuaInterpretedFunction} around the result * using a user-supplied environment. *

- * Implements the {@link LuaCompiler} interface for loading + * Implements the {@link FunctionFactory} interface for loading * initialized chunks, which is an interface common to * lua bytecode compiling and java bytecode compiling. *

* The {@link LuaC} compiler is installed by default by the - * {@link JsePlatform} class + * {@link CoreLibraries} class * so in the following example, the default {@link LuaC} compiler * will be used: *

 {@code
@@ -61,16 +62,14 @@
  * LoadState.load( new ByteArrayInputStream("print 'hello'".getBytes()), "main.lua", _G ).call();
  * } 
* - * @see LuaCompiler - * @see JsePlatform + * @see FunctionFactory + * @see CoreLibraries * @see BaseLib * @see LuaValue - * @see LuaCompiler + * @see FunctionFactory * @see Prototype */ -public class LuaC implements LuaCompiler { - public static final LuaC INSTANCE = new LuaC(); - +public class LuaC { protected static void _assert(boolean b) throws CompileException { if (!b) { // So technically this should fire a runtime exception but... @@ -135,14 +134,30 @@ private LuaC() { } /** - * Load into a Closure or LuaFunction, with the supplied initial environment + * Load lua thought to be a binary chunk from its first byte from an input stream. + * + * @param firstByte the first byte of the input stream + * @param stream InputStream to read, after having read the first byte already + * @param name Name to apply to the loaded chunk + * @return {@link Prototype} that was loaded + * @throws IllegalArgumentException If the signature is bac + * @throws IOException If an IOException occurs + * @throws CompileException If the stream cannot be loaded. */ - @Override - public LuaClosure load(InputStream stream, LuaString name, LuaString mode, LuaTable env) throws IOException, CompileException { - Prototype p = compile(stream, name, mode); - LuaInterpretedFunction closure = new LuaInterpretedFunction(p, env); - closure.nilUpvalues(); - return closure; + public static Prototype loadBinaryChunk(int firstByte, InputStream stream, LuaString name) throws IOException, CompileException { + name = LoadState.getSourceName(name); + // check rest of signature + if (firstByte != LoadState.LUA_SIGNATURE[0] + || stream.read() != LoadState.LUA_SIGNATURE[1] + || stream.read() != LoadState.LUA_SIGNATURE[2] + || stream.read() != LoadState.LUA_SIGNATURE[3]) { + throw new IllegalArgumentException("bad signature"); + } + + // load file as a compiled chunk + BytecodeLoader s = new BytecodeLoader(stream); + s.loadHeader(); + return s.loadFunction(name); } public static Prototype compile(InputStream stream, String name) throws IOException, CompileException { @@ -166,11 +181,11 @@ public static Prototype compile(InputStream stream, LuaString name, LuaString mo int firstByte = stream.read(); if (firstByte == '\033') { checkMode(mode, "binary"); - return LoadState.loadBinaryChunk(firstByte, stream, name); + return loadBinaryChunk(firstByte, stream, name); } else { checkMode(mode, "text"); try { - return luaY_parser(firstByte, stream, name); + return loadTextChunk(firstByte, stream, name); } catch (UncheckedIOException e) { throw e.getCause(); } @@ -180,8 +195,8 @@ public static Prototype compile(InputStream stream, LuaString name, LuaString mo /** * Parse the input */ - private static Prototype luaY_parser(int firstByte, InputStream z, LuaString name) throws CompileException { - Parser lexstate = new Parser(z, firstByte, name); + private static Prototype loadTextChunk(int firstByte, InputStream stream, LuaString name) throws CompileException { + Parser lexstate = new Parser(stream, firstByte, name); FuncState funcstate = lexstate.openFunc(); funcstate.varargFlags = Lua.VARARG_ISVARARG; /* main func. is always vararg */ @@ -193,8 +208,4 @@ private static Prototype luaY_parser(int firstByte, InputStream z, LuaString nam LuaC._assert(lexstate.fs == null); return prototype; } - - public LuaFunction load(Prototype p, LuaTable env) { - return new LuaInterpretedFunction(p, env); - } } diff --git a/src/main/java/org/squiddev/cobalt/compiler/Parser.java b/src/main/java/org/squiddev/cobalt/compiler/Parser.java index b0870815..793b26c9 100644 --- a/src/main/java/org/squiddev/cobalt/compiler/Parser.java +++ b/src/main/java/org/squiddev/cobalt/compiler/Parser.java @@ -38,7 +38,7 @@ *

* This largely follows the structure and implementation of lparser.c. */ -class Parser { +final class Parser { private static final int LUAI_MAXCCALLS = 200; private static final boolean LUA_COMPAT_VARARG = true; diff --git a/src/main/java/org/squiddev/cobalt/compiler/UnOpr.java b/src/main/java/org/squiddev/cobalt/compiler/UnOpr.java index 6c269884..e131bd2c 100644 --- a/src/main/java/org/squiddev/cobalt/compiler/UnOpr.java +++ b/src/main/java/org/squiddev/cobalt/compiler/UnOpr.java @@ -13,16 +13,16 @@ enum UnOpr { */ static final int PRIORITY = 8; - static UnOpr ofToken(int op) { - switch (op) { - case TK_NOT: - return NOT; - case '-': - return MINUS; - case '#': - return LEN; - default: - return null; - } - } + static UnOpr ofToken(int op) { + switch (op) { + case TK_NOT: + return NOT; + case '-': + return MINUS; + case '#': + return LEN; + default: + return null; + } + } } diff --git a/src/main/java/org/squiddev/cobalt/function/LibFunction.java b/src/main/java/org/squiddev/cobalt/function/LibFunction.java index 2efaca02..d9cfc9fd 100644 --- a/src/main/java/org/squiddev/cobalt/function/LibFunction.java +++ b/src/main/java/org/squiddev/cobalt/function/LibFunction.java @@ -24,14 +24,13 @@ */ package org.squiddev.cobalt.function; +import org.squiddev.cobalt.Constants; import org.squiddev.cobalt.LuaState; import org.squiddev.cobalt.LuaTable; import org.squiddev.cobalt.LuaValue; import org.squiddev.cobalt.lib.BaseLib; import org.squiddev.cobalt.lib.TableLib; -import java.util.function.Supplier; - /** * Subclass of {@link LuaFunction} common to Java functions exposed to lua. *

@@ -100,7 +99,7 @@ * data it needs to and place it into the environment if needed. * In this case, it creates two function, 'sinh', and 'cosh', and puts * them into a global table called 'hyperbolic.' - * It placed the library table into the globals via the {@link #env} + * It placed the library table into the globals via the {@link #getfenv()} * local variable which corresponds to the globals that apply when the * library is loaded. *

@@ -130,20 +129,12 @@ * such as {@link BaseLib} or {@link TableLib} for other examples. */ public abstract 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; + String name; /** * Default constructor for use by subclasses @@ -156,22 +147,8 @@ public String debugName() { return name != null ? name : super.toString(); } - /** - * 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 factory to provide a new instance each time - * @param names Array of function names - */ - public static void bind(LuaTable env, Supplier factory, String[] names) { - for (int i = 0; i < names.length; i++) { - LibFunction f = factory.get(); - f.opcode = i; - f.name = names[i]; - env.rawset(f.name, f); - } + public static void setGlobalLibrary(LuaState state, LuaTable env, String name, LuaValue library) { + env.rawset(name, library); + state.registry().getSubTable(Constants.LOADED).rawset(name, library); } } diff --git a/src/main/java/org/squiddev/cobalt/function/LuaInterpretedFunction.java b/src/main/java/org/squiddev/cobalt/function/LuaInterpretedFunction.java index 876a9ac6..9806c7e1 100644 --- a/src/main/java/org/squiddev/cobalt/function/LuaInterpretedFunction.java +++ b/src/main/java/org/squiddev/cobalt/function/LuaInterpretedFunction.java @@ -31,6 +31,8 @@ import org.squiddev.cobalt.debug.DebugHandler; import org.squiddev.cobalt.debug.DebugState; +import java.io.InputStream; + import static org.squiddev.cobalt.debug.DebugFrame.*; import static org.squiddev.cobalt.function.LuaInterpreter.*; @@ -43,11 +45,11 @@ * There are three main ways {@link LuaInterpretedFunction} instances are created: *

*

- * To construct it directly, the {@link Prototype} is typically created via a compiler such as {@link LuaC}: + * To construct it directly, the {@link Prototype} is typically created via {@linkplain LoadState the compiler}: *

 {@code
  * InputStream is = new ByteArrayInputStream("print('hello,world').getBytes());
  * Prototype p = LuaC.INSTANCE.compile(is, "script");
@@ -56,7 +58,7 @@
  * }
*

* To construct it indirectly, the {@link LuaC} compiler may be used, - * which implements the {@link LoadState.LuaCompiler} interface: + * which implements the {@link LoadState.FunctionFactory} interface: *

 {@code
  * LuaFunction f = LuaC.INSTANCE.load(is, "script", _G);
  * }
diff --git a/src/main/java/org/squiddev/cobalt/lib/BaseLib.java b/src/main/java/org/squiddev/cobalt/lib/BaseLib.java index 7ca24d3c..5ef79a91 100644 --- a/src/main/java/org/squiddev/cobalt/lib/BaseLib.java +++ b/src/main/java/org/squiddev/cobalt/lib/BaseLib.java @@ -30,63 +30,41 @@ import org.squiddev.cobalt.debug.DebugHandler; import org.squiddev.cobalt.debug.DebugState; import org.squiddev.cobalt.function.*; -import org.squiddev.cobalt.lib.jse.JsePlatform; -import org.squiddev.cobalt.lib.platform.ResourceManipulator; +import org.squiddev.cobalt.lib.system.ResourceLoader; import java.io.InputStream; -import static org.squiddev.cobalt.OperationHelper.noUnwind; import static org.squiddev.cobalt.ValueFactory.valueOf; import static org.squiddev.cobalt.ValueFactory.varargsOf; import static org.squiddev.cobalt.debug.DebugFrame.FLAG_ERROR; import static org.squiddev.cobalt.debug.DebugFrame.FLAG_YPCALL; /** - * 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 LuaState#resourceManipulator} instance to find resource files. - * The default loader chain in {@link PackageLib} will use these as well. - *

- * This is a direct port of the corresponding library in C. + * The basic global libraries in the Lua runtime. * - * @see ResourceManipulator + * @see ResourceLoader * @see LibFunction - * @see JsePlatform + * @see CoreLibraries * @see http://www.lua.org/manual/5.1/manual.html#5.1 */ -public class BaseLib implements LuaLibrary { - private static final LuaString STDIN_STR = valueOf("=stdin"); +public class BaseLib { private static final LuaString FUNCTION_STR = valueOf("function"); private static final LuaString LOAD_MODE = valueOf("bt"); private LuaValue next; private LuaValue inext; - private static final String[] LIBR_KEYS = { - "pcall", // (f, arg1, ...) -> status, result1, ... - "xpcall", // (f, err) -> result1, ... - "load", // ( func [,chunkname] ) -> chunk | nil, msg - }; - - @Override - public LuaValue add(LuaState state, LuaTable env) { + public void add(LuaTable env) { env.rawset("_G", env); - env.rawset("_VERSION", valueOf(Lua._VERSION)); + env.rawset("_VERSION", valueOf("Lua 5.1")); RegisteredFunction.bind(env, new RegisteredFunction[]{ - RegisteredFunction.of("collectgarbage", BaseLib::collectgarbage), RegisteredFunction.of("error", BaseLib::error), RegisteredFunction.ofV("setfenv", BaseLib::setfenv), RegisteredFunction.ofV("assert", BaseLib::assert_), - RegisteredFunction.ofV("dofile", BaseLib::dofile), RegisteredFunction.ofV("getfenv", BaseLib::getfenv), RegisteredFunction.ofV("getmetatable", BaseLib::getmetatable), - RegisteredFunction.ofV("loadfile", BaseLib::loadfile), RegisteredFunction.ofV("loadstring", BaseLib::loadstring), - RegisteredFunction.ofV("print", BaseLib::print), RegisteredFunction.ofV("select", BaseLib::select), - RegisteredFunction.ofV("unpack", BaseLib::unpack), RegisteredFunction.ofV("type", BaseLib::type), RegisteredFunction.ofV("rawequal", BaseLib::rawequal), RegisteredFunction.ofV("rawget", BaseLib::rawget), @@ -98,35 +76,14 @@ public LuaValue add(LuaState state, LuaTable env) { RegisteredFunction.ofV("ipairs", this::ipairs), RegisteredFunction.ofV("rawlen", BaseLib::rawlen), RegisteredFunction.ofV("next", BaseLib::next), + RegisteredFunction.ofFactory("pcall", PCall::new), + RegisteredFunction.ofFactory("xpcall", XpCall::new), + RegisteredFunction.ofFactory("load", Load::new), }); - LibFunction.bind(env, BaseLibR::new, LIBR_KEYS); // remember next, and inext for use in pairs and ipairs next = env.rawget("next"); inext = RegisteredFunction.ofV("inext", BaseLib::inext).create(); - - env.rawset("_VERSION", valueOf("Lua 5.1")); - - return env; - } - - private static LuaValue collectgarbage(LuaState state, LuaValue arg1, LuaValue arg2) throws LuaError { - // collectgarbage( opt [,arg] ) -> value - String s = arg1.optString("collect"); - switch (s) { - case "collect": - System.gc(); - return Constants.ZERO; - case "count": - Runtime rt = Runtime.getRuntime(); - long used = rt.totalMemory() - rt.freeMemory(); - return valueOf(used / 1024.); - case "step": - System.gc(); - return Constants.TRUE; - default: - throw ErrorFactory.argError(1, "invalid option"); - } } private static LuaValue error(LuaState state, LuaValue arg1, LuaValue arg2) throws LuaError { @@ -169,17 +126,6 @@ private static Varargs assert_(LuaState state, Varargs args) throws LuaError { return args; } - private static Varargs dofile(LuaState state, Varargs args) throws LuaError, UnwindThrowable { - // dofile( filename ) -> result1, ... - Varargs v = args.isNil(1) ? - BaseLib.loadStream(state, state.stdin, STDIN_STR) : - BaseLib.loadFile(state, args.arg(1).checkString()); - if (v.isNil(1)) { - throw new LuaError(v.arg(2).toString()); - } else { - return OperationHelper.invoke(state, v.first(), Constants.NONE); - } - } private static Varargs getfenv(LuaState state, Varargs args) throws LuaError { // getfenv( [f] ) -> env @@ -197,34 +143,12 @@ private static Varargs getmetatable(LuaState state, Varargs args) throws LuaErro return mt != null ? mt.rawget(Constants.METATABLE).optValue(mt) : Constants.NIL; } - private static Varargs loadfile(LuaState state, Varargs args) throws LuaError { - // loadfile( [filename] ) -> chunk | nil, msg - return args.isNil(1) ? - BaseLib.loadStream(state, state.stdin, STDIN_STR) : - BaseLib.loadFile(state, args.arg(1).checkString()); - } - private static Varargs loadstring(LuaState state, Varargs args) throws LuaError { // loadstring( string [,chunkname] ) -> chunk | nil, msg LuaString script = args.arg(1).checkLuaString(); return BaseLib.loadStream(state, script.toInputStream(), args.arg(2).optLuaString(script)); } - private static Varargs print(LuaState state, Varargs args) throws LuaError { - // print(...) -> void - return noUnwind(state, () -> { - LuaValue tostring = OperationHelper.getTable(state, state.getCurrentThread().getfenv(), valueOf("tostring")); - for (int i = 1, n = args.count(); i <= n; i++) { - if (i > 1) state.stdout.write('\t'); - LuaString s = OperationHelper.call(state, tostring, args.arg(i)).strvalue(); - int z = s.indexOf((byte) 0, 0); - state.stdout.write(s.bytes, s.offset, z >= 0 ? z : s.length); - } - state.stdout.println(); - return Constants.NONE; - }); - } - private static Varargs select(LuaState state, Varargs args) throws LuaError { // select(f, ...) -> value1, ... int n = args.count() - 1; @@ -234,24 +158,6 @@ private static Varargs select(LuaState state, Varargs args) throws LuaError { return args.subargs(i < 0 ? n + i + 2 : i + 1); } - private static Varargs unpack(LuaState state, Varargs args) throws LuaError { - // unpack(list [,i [,j]]) -> result1, ... - int na = args.count(); - LuaTable t = args.arg(1).checkTable(); - int n = t.length(); - int i = na >= 2 ? args.arg(2).optInteger(1) : 1; - int j = na >= 3 ? args.arg(3).optInteger(n) : n; - n = j - i + 1; - if (n < 0) return Constants.NONE; - if (n == 1) return t.rawget(i); - if (n == 2) return varargsOf(t.rawget(i), t.rawget(j)); - LuaValue[] v = new LuaValue[n]; - for (int k = 0; k < n; k++) { - v[k] = t.rawget(i + k); - } - return varargsOf(v); - } - private static Varargs type(LuaState state, Varargs args) throws LuaError { // type(v) -> value return valueOf(args.checkValue(1).typeName()); @@ -348,93 +254,95 @@ private static Varargs inext(LuaState state, Varargs args) throws LuaError { return args.arg(1).checkTable().inext(args.arg(2)); } - private static class BaseLibR extends ResumableVarArgFunction { + // pcall(f, arg1, ...) -> status, result1, ... + private static class PCall extends ResumableVarArgFunction { @Override protected Varargs invoke(LuaState state, DebugFrame di, Varargs args) throws LuaError, UnwindThrowable { - switch (opcode) { - case 0: // "pcall", // (f, arg1, ...) -> status, result1, ... - return pcall(state, di, args.checkValue(1), args.subargs(2), null); - case 1: // "xpcall", // (f, err) -> result1, ... - return pcall(state, di, args.checkValue(1), Constants.NONE, args.checkValue(2)); - - case 2: // "load", // ( func|str [,chunkname[, mode[, env]]] ) -> chunk | nil, msg - { - LuaValue scriptGen = args.arg(1); - LuaString chunkName = args.arg(2).optLuaString(null); - LuaString mode = args.arg(3).optLuaString(LOAD_MODE); - LuaTable funcEnv = args.arg(4).optTable(state.getCurrentThread().getfenv()); - - // If we're a string, load as normal - LuaValue script = scriptGen.toLuaString(); - if (!script.isNil()) { - try { - return LoadState.load(state, ((LuaString) script).toInputStream(), chunkName == null ? (LuaString) script : chunkName, mode, funcEnv); - } catch (Exception e) { - return varargsOf(Constants.NIL, LuaError.getMessage(e)); - } - } + return pcallInit(state, di, args.checkValue(1), args.subargs(2), null); + } - LuaFunction function = scriptGen.checkFunction(); - Varargs result = pcall(state, di, new ZeroArgFunction() { - @Override - public LuaValue call(LuaState state) throws LuaError { - try { - InputStream stream = new StringInputStream(state, function); - return LoadState.load(state, stream, chunkName == null ? FUNCTION_STR : chunkName, mode, funcEnv); - } catch (Exception e) { - throw LuaError.wrapMessage(e); - } - } - }, Constants.NONE, state.getCurrentThread().getErrorFunc()); - - if (result.first().toBoolean()) { - return result.arg(2); - } else { - return varargsOf(Constants.NIL, result.arg(2)); - } - } - default: - return Constants.NONE; - } + @Override + protected Varargs resumeThis(LuaState state, PCallState info, Varargs value) { + pcallFinishSuccess(state, info); + return info.errored ? varargsOf(Constants.FALSE, value.first()) : varargsOf(Constants.TRUE, value); } @Override - protected Varargs resumeThis(LuaState state, PCallState pState, Varargs value) { - state.getCurrentThread().setErrorFunc(pState.oldErrorFunc); + protected Varargs resumeErrorThis(LuaState state, PCallState object, LuaError error) throws UnwindThrowable { + LuaValue result = pcallFinishError(state, object, error); + return varargsOf(Constants.FALSE, result); + } + } - if (pState.errored) closeUntil(state, pState.frame); - return finish(pState, value); + // xpcall(f, err) -> result1, ... + private static class XpCall extends ResumableVarArgFunction { + @Override + protected Varargs invoke(LuaState state, DebugFrame di, Varargs args) throws LuaError, UnwindThrowable { + return pcallInit(state, di, args.checkValue(1), Constants.NONE, args.checkValue(2)); } @Override - public Varargs resumeErrorThis(LuaState state, PCallState pState, LuaError error) throws UnwindThrowable { - LuaValue value; - if (pState.errored) { - value = valueOf("error in error handling"); + protected Varargs resumeThis(LuaState state, PCallState info, Varargs value) { + pcallFinishSuccess(state, info); + return info.errored ? varargsOf(Constants.FALSE, value.first()) : varargsOf(Constants.TRUE, value); + } + + @Override + protected Varargs resumeErrorThis(LuaState state, PCallState object, LuaError error) throws UnwindThrowable { + LuaValue result = pcallFinishError(state, object, error); + return varargsOf(Constants.FALSE, result); + } + } + + // load( func|str [,chunkname[, mode[, env]]] ) -> chunk | nil, msg + private static class Load extends ResumableVarArgFunction { + @Override + protected Varargs invoke(LuaState state, DebugFrame di, Varargs args) throws LuaError, UnwindThrowable { + LuaValue scriptGen = args.arg(1); + LuaString chunkName = args.arg(2).optLuaString(null); + LuaString mode = args.arg(3).optLuaString(LOAD_MODE); + LuaTable funcEnv = args.arg(4).optTable(state.getCurrentThread().getfenv()); + + // If we're a string, load as normal + LuaValue script = scriptGen.toLuaString(); + if (!script.isNil()) { + try { + return LoadState.load(state, ((LuaString) script).toInputStream(), chunkName == null ? (LuaString) script : chunkName, mode, funcEnv); + } catch (Exception e) { + return varargsOf(Constants.NIL, LuaError.getMessage(e)); + } + } + + LuaFunction function = scriptGen.checkFunction(); + Varargs result = pcallInit(state, di, new ZeroArgFunction() { + @Override + public LuaValue call(LuaState state) throws LuaError { + try { + InputStream stream = new StringInputStream(state, function); + return LoadState.load(state, stream, chunkName == null ? FUNCTION_STR : chunkName, mode, funcEnv); + } catch (Exception e) { + throw LuaError.wrapMessage(e); + } + } + }, Constants.NONE, state.getCurrentThread().getErrorFunc()); + + if (result.first().toBoolean()) { + return result.arg(2); } else { - // Mark this frame as errored, meaning it will not be resumed. - DebugHandler.getDebugState(state).getStackUnsafe().flags |= FLAG_ERROR; - // And mark us as being in the error handler. - pState.errored = true; - error.fillTraceback(state); - value = error.value; + return varargsOf(Constants.NIL, result.arg(2)); } + } - state.getCurrentThread().setErrorFunc(pState.oldErrorFunc); - closeUntil(state, pState.frame); - return finish(pState, value); + @Override + protected Varargs resumeThis(LuaState state, PCallState pState, Varargs value) { + pcallFinishSuccess(state, pState); + return pState.errored ? varargsOf(Constants.NIL, value) : value; } - private Varargs finish(PCallState pState, Varargs value) { - switch (opcode) { - case 0: - case 1: - return pState.errored ? varargsOf(Constants.FALSE, value) : varargsOf(Constants.TRUE, value); - case 2: - return pState.errored ? varargsOf(Constants.NIL, value) : value; - default: - throw new NonResumableException("Cannot resume " + debugName()); - } + @Override + public Varargs resumeErrorThis(LuaState state, PCallState pState, LuaError error) throws UnwindThrowable { + LuaValue result = pcallFinishError(state, pState, error); + return varargsOf(Constants.NIL, result); } } @@ -444,8 +352,10 @@ private static final class PCallState { boolean errored = false; } - private static Varargs pcall(LuaState state, DebugFrame di, LuaValue func, Varargs args, LuaValue errFunc) throws - UnwindThrowable { + private static Varargs pcallInit(LuaState state, DebugFrame di, LuaValue func, Varargs args, LuaValue errFunc) throws UnwindThrowable { + // FIXME: Move this into a core part of the runtime, so it's not part of library code! + // We really should clean up LuaError at the same time. + // Mark this frame as being an error handler PCallState pState = new PCallState(); di.state = pState; @@ -475,6 +385,30 @@ private static Varargs pcall(LuaState state, DebugFrame di, LuaValue func, Varar } } + private static void pcallFinishSuccess(LuaState state, PCallState pState) { + state.getCurrentThread().setErrorFunc(pState.oldErrorFunc); + if (pState.errored) closeUntil(state, pState.frame); + } + + private static LuaValue pcallFinishError(LuaState state, PCallState pState, LuaError error) throws UnwindThrowable { + LuaValue value; + if (pState.errored) { + value = valueOf("error in error handling"); + } else { + // Mark this frame as errored, meaning it will not be resumed. + DebugHandler.getDebugState(state).getStackUnsafe().flags |= FLAG_ERROR; + // And mark us as being in the error handler. + pState.errored = true; + error.fillTraceback(state); + value = error.value; + } + + state.getCurrentThread().setErrorFunc(pState.oldErrorFunc); + closeUntil(state, pState.frame); + + return value; + } + private static void closeUntil(LuaState state, DebugFrame top) { DebugState ds = DebugHandler.getDebugState(state); DebugHandler handler = state.debug; @@ -486,30 +420,7 @@ private static void closeUntil(LuaState state, DebugFrame top) { } } - /** - * Load from a named file, returning the chunk or nil,error of can't load - * - * @param state The current lua state - * @param filename Name of the file - * @return Varargs containing chunk, or NIL,error-text on error - */ - public static Varargs loadFile(LuaState state, String filename) { - InputStream is = state.resourceManipulator.findResource(filename); - if (is == null) { - return varargsOf(Constants.NIL, valueOf("cannot open " + filename + ": No such file or directory")); - } - try { - return loadStream(state, is, valueOf("@" + filename)); - } finally { - try { - is.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - private static Varargs loadStream(LuaState state, InputStream is, LuaString chunkname) { + public static Varargs loadStream(LuaState state, InputStream is, LuaString chunkname) { try { if (is == null) { return varargsOf(Constants.NIL, valueOf("not found: " + chunkname)); diff --git a/src/main/java/org/squiddev/cobalt/lib/Bit32Lib.java b/src/main/java/org/squiddev/cobalt/lib/Bit32Lib.java index 1af773fa..cb4953cf 100644 --- a/src/main/java/org/squiddev/cobalt/lib/Bit32Lib.java +++ b/src/main/java/org/squiddev/cobalt/lib/Bit32Lib.java @@ -25,6 +25,7 @@ package org.squiddev.cobalt.lib; import org.squiddev.cobalt.*; +import org.squiddev.cobalt.function.LibFunction; import org.squiddev.cobalt.function.RegisteredFunction; import static org.squiddev.cobalt.ErrorFactory.argError; @@ -33,11 +34,9 @@ /** * Subclass of LibFunction that implements the Lua standard {@code bit32} library. */ -public class Bit32Lib implements LuaLibrary { - @Override - public LuaValue add(LuaState state, LuaTable env) { - LuaTable t = new LuaTable(); - RegisteredFunction.bind(t, new RegisteredFunction[]{ +public class Bit32Lib { + public static void add(LuaState state, LuaTable env) { + LibFunction.setGlobalLibrary(state, env, "bit32", RegisteredFunction.bind(new RegisteredFunction[]{ RegisteredFunction.ofV("band", Bit32Lib::band), RegisteredFunction.of("bnot", Bit32Lib::bnot), RegisteredFunction.ofV("bor", Bit32Lib::bor), @@ -50,11 +49,7 @@ public LuaValue add(LuaState state, LuaTable env) { RegisteredFunction.of("lshift", Bit32Lib::lshift), RegisteredFunction.of("rrotate", Bit32Lib::rrotate), RegisteredFunction.of("rshift", Bit32Lib::rshift), - }); - - env.rawset("bit32", t); - state.loadedPackages.rawset("bit32", t); - return t; + })); } private static Varargs band(LuaState state, Varargs args) throws LuaError { diff --git a/src/main/java/org/squiddev/cobalt/lib/LuaLibrary.java b/src/main/java/org/squiddev/cobalt/lib/CoreLibraries.java similarity index 51% rename from src/main/java/org/squiddev/cobalt/lib/LuaLibrary.java rename to src/main/java/org/squiddev/cobalt/lib/CoreLibraries.java index ed210080..404a0599 100644 --- a/src/main/java/org/squiddev/cobalt/lib/LuaLibrary.java +++ b/src/main/java/org/squiddev/cobalt/lib/CoreLibraries.java @@ -26,18 +26,47 @@ import org.squiddev.cobalt.LuaState; import org.squiddev.cobalt.LuaTable; -import org.squiddev.cobalt.LuaValue; +import org.squiddev.cobalt.lib.system.SystemLibraries; /** - * A library for the environment + * The {@link CoreLibraries} class is a convenience class to standardize install "core" (i.e. + * non-{@linkplain SystemLibraries system}) into the global state. */ -public interface LuaLibrary { +public final class CoreLibraries { + private CoreLibraries() { + } + + /** + * Create a standard set of globals and setup a thread + * + * @param state The current lua state + * @return Table of globals initialized with the standard JSE libraries + * @see #debugGlobals(LuaState) + * @see CoreLibraries + */ + public static LuaTable standardGlobals(LuaState state) { + LuaTable globals = state.getMainThread().getfenv(); + new BaseLib().add(globals); + TableLib.add(state, globals); + StringLib.add(state, globals); + CoroutineLib.add(state, globals); + new MathLib().add(state, globals); + new Utf8Lib().add(state, globals); + return globals; + } + /** - * Add this library into an environment + * Create standard globals including the {@link DebugLib} library. * - * @param state The current Lua state - * @param environment The environment to add to - * @return The sub-table that was added + * @param state The current lua state + * @return Table of globals initialized with the standard JSE and debug libraries + * @see #standardGlobals(LuaState) + * @see CoreLibraries + * @see DebugLib */ - LuaValue add(LuaState state, LuaTable environment); + public static LuaTable debugGlobals(LuaState state) { + LuaTable _G = standardGlobals(state); + DebugLib.add(state, _G); + return _G; + } } diff --git a/src/main/java/org/squiddev/cobalt/lib/CoroutineLib.java b/src/main/java/org/squiddev/cobalt/lib/CoroutineLib.java index 366830b3..c5a12eae 100644 --- a/src/main/java/org/squiddev/cobalt/lib/CoroutineLib.java +++ b/src/main/java/org/squiddev/cobalt/lib/CoroutineLib.java @@ -29,8 +29,8 @@ import org.squiddev.cobalt.debug.DebugFrame; import org.squiddev.cobalt.function.LibFunction; import org.squiddev.cobalt.function.LuaFunction; +import org.squiddev.cobalt.function.RegisteredFunction; import org.squiddev.cobalt.function.ResumableVarArgFunction; -import org.squiddev.cobalt.lib.jse.JsePlatform; import static org.squiddev.cobalt.ValueFactory.valueOf; import static org.squiddev.cobalt.ValueFactory.varargsOf; @@ -39,7 +39,7 @@ /** * 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, @@ -48,101 +48,97 @@ * may not be collected by the garbage collector. * * @see LibFunction - * @see JsePlatform + * @see CoreLibraries * @see http://www.lua.org/manual/5.1/manual.html#5.2 */ -public class CoroutineLib extends ResumableVarArgFunction implements LuaLibrary { - private static final int CREATE = 0; - private static final int RESUME = 1; - private static final int RUNNING = 2; - private static final int STATUS = 3; - private static final int YIELD = 4; - private static final int WRAP = 5; - private static final int WRAPPED = 6; - - private final LuaThread thread; - - public CoroutineLib() { - thread = null; +public final class CoroutineLib { + private CoroutineLib() { } - private CoroutineLib(LuaThread thread) { - this.thread = thread; + public static void add(LuaState state, LuaTable env) { + LibFunction.setGlobalLibrary(state, env, "coroutine", RegisteredFunction.bind(new RegisteredFunction[]{ + RegisteredFunction.of("create", CoroutineLib::create), + RegisteredFunction.of("running", CoroutineLib::running), + RegisteredFunction.of("status", CoroutineLib::status), + RegisteredFunction.of("wrap", CoroutineLib::wrap), + RegisteredFunction.ofFactory("resume", Resume::new), + RegisteredFunction.ofFactory("yield", Yield::new), + })); } - @Override - public LuaValue add(LuaState state, LuaTable env) { - LuaTable t = new LuaTable(); - bind(t, CoroutineLib::new, new String[]{"create", "resume", "running", "status", "yield", "wrap"}); - env.rawset("coroutine", t); - state.loadedPackages.rawset("coroutine", t); - return t; + private static LuaValue create(LuaState state, LuaValue arg) throws LuaError { + final LuaFunction func = arg.checkFunction(); + return new LuaThread(state, func, state.getCurrentThread().getfenv()); } - @Override - public Varargs invoke(LuaState state, DebugFrame di, Varargs args) throws LuaError, UnwindThrowable { - switch (opcode) { - case CREATE: { - final LuaFunction func = args.arg(1).checkFunction(); - return new LuaThread(state, func, state.getCurrentThread().getfenv()); - } - case RESUME: { - di.flags |= FLAG_YPCALL; - LuaThread thread = args.arg(1).checkThread(); - try { - Varargs result = LuaThread.resume(state, thread, args.subargs(2)); - return varargsOf(Constants.TRUE, result); - } catch (LuaError le) { - return varargsOf(Constants.FALSE, le.value); - } - } - case RUNNING: { - final LuaThread r = state.getCurrentThread(); - return r.isMainThread() ? Constants.NIL : r; - } - case STATUS: { - return valueOf(args.arg(1).checkThread().getStatus()); - } - case YIELD: - return LuaThread.yield(state, args); - case WRAP: { - final LuaFunction func = args.arg(1).checkFunction(); - final LuaTable env = func.getfenv(); - final LuaThread thread = new LuaThread(state, func, env); - CoroutineLib cl = new CoroutineLib(thread); - cl.setfenv(env); - cl.name = "wrapped"; - cl.opcode = WRAPPED; - return cl; - } - case WRAPPED: { - return LuaThread.resume(state, thread, args); + private static LuaValue running(LuaState state) { + final LuaThread r = state.getCurrentThread(); + return r.isMainThread() ? Constants.NIL : r; + } + + private static LuaValue status(LuaState state, LuaValue arg) throws LuaError { + return valueOf(arg.checkThread().getStatus()); + } + + private static LuaValue wrap(LuaState state, LuaValue arg) throws LuaError { + final LuaFunction func = arg.checkFunction(); + final LuaTable env = func.getfenv(); + final LuaThread thread = new LuaThread(state, func, env); + + return new Wrapped(thread); + } + + private static class Resume extends ResumableVarArgFunction { + @Override + protected Varargs invoke(LuaState state, DebugFrame di, Varargs args) throws LuaError, UnwindThrowable { + di.flags |= FLAG_YPCALL; + LuaThread thread = args.arg(1).checkThread(); + try { + Varargs result = LuaThread.resume(state, thread, args.subargs(2)); + return varargsOf(Constants.TRUE, result); + } catch (LuaError le) { + return varargsOf(Constants.FALSE, le.value); } - default: - return Constants.NONE; + } + + @Override + protected Varargs resumeThis(LuaState state, Void object, Varargs value) { + return varargsOf(Constants.TRUE, value); + } + + @Override + protected Varargs resumeErrorThis(LuaState state, Void object, LuaError error) { + return varargsOf(Constants.FALSE, error.value); } } - @Override - public Varargs resumeThis(LuaState state, Object object, Varargs value) { - switch (opcode) { - case YIELD: - case WRAPPED: - return value; - case RESUME: - return varargsOf(Constants.TRUE, value); - default: - throw new NonResumableException("Cannot resume " + debugName()); + private static class Yield extends ResumableVarArgFunction { + @Override + protected Varargs invoke(LuaState state, DebugFrame di, Varargs args) throws LuaError, UnwindThrowable { + return LuaThread.yield(state, args); + } + + @Override + protected Varargs resumeThis(LuaState state, Void object, Varargs value) { + return value; } } - @Override - public Varargs resumeErrorThis(LuaState state, Object object, LuaError error) { - switch (opcode) { - case RESUME: - return varargsOf(Constants.FALSE, error.value); - default: - throw new NonResumableException("Cannot resume " + debugName()); + private static class Wrapped extends ResumableVarArgFunction { + private final LuaThread thread; + + private Wrapped(LuaThread thread) { + this.thread = thread; + } + + @Override + protected Varargs invoke(LuaState state, DebugFrame di, Varargs args) throws LuaError, UnwindThrowable { + return LuaThread.resume(state, thread, args); + } + + @Override + protected Varargs resumeThis(LuaState state, Void object, Varargs value) { + return value; } } } diff --git a/src/main/java/org/squiddev/cobalt/lib/DebugLib.java b/src/main/java/org/squiddev/cobalt/lib/DebugLib.java index da7da916..6a5ba23c 100644 --- a/src/main/java/org/squiddev/cobalt/lib/DebugLib.java +++ b/src/main/java/org/squiddev/cobalt/lib/DebugLib.java @@ -30,7 +30,6 @@ import org.squiddev.cobalt.debug.DebugHelpers; import org.squiddev.cobalt.debug.DebugState; import org.squiddev.cobalt.function.*; -import org.squiddev.cobalt.lib.jse.JsePlatform; import static org.squiddev.cobalt.Constants.*; import static org.squiddev.cobalt.ValueFactory.valueOf; @@ -45,10 +44,10 @@ * instances. * * @see LibFunction - * @see JsePlatform + * @see CoreLibraries * @see http://www.lua.org/manual/5.1/manual.html#5.9 */ -public class DebugLib implements LuaLibrary { +public final class DebugLib { private static final LuaString MAIN = valueOf("main"); private static final LuaString LUA = valueOf("Lua"); private static final LuaString C = valueOf("C"); @@ -72,24 +71,23 @@ public class DebugLib implements LuaLibrary { private static final LuaString ISVARARG = valueOf("isvararg"); private static final LuaString ISTAILCALL = valueOf("istailcall"); - private final LuaTable registry = new LuaTable(); + private DebugLib() { + } - @Override - public LuaTable add(LuaState state, LuaTable env) { - LuaTable t = new LuaTable(); - RegisteredFunction.bind(t, new RegisteredFunction[]{ + public static void add(LuaState state, LuaTable env) { + LibFunction.setGlobalLibrary(state, env, "debug", RegisteredFunction.bind(new RegisteredFunction[]{ RegisteredFunction.ofV("debug", DebugLib::debug), RegisteredFunction.ofV("getfenv", DebugLib::getfenv), RegisteredFunction.ofV("gethook", DebugLib::gethook), RegisteredFunction.ofFactory("getinfo", () -> new VarArgFunction() { @Override - public Varargs invoke(LuaState state, Varargs args) throws LuaError { - return DebugLib.getinfo(state, args, this); + public Varargs invoke(LuaState state1, Varargs args) throws LuaError { + return DebugLib.getinfo(state1, args, this); } }), RegisteredFunction.ofV("getlocal", DebugLib::getlocal), RegisteredFunction.ofV("getmetatable", DebugLib::getmetatable), - RegisteredFunction.ofV("getregistry", this::getregistry), + RegisteredFunction.ofV("getregistry", DebugLib::getregistry), RegisteredFunction.ofV("getupvalue", DebugLib::getupvalue), RegisteredFunction.ofV("setfenv", DebugLib::setfenv), RegisteredFunction.ofV("sethook", DebugLib::sethook), @@ -99,16 +97,11 @@ public Varargs invoke(LuaState state, Varargs args) throws LuaError { RegisteredFunction.ofV("traceback", DebugLib::traceback), RegisteredFunction.ofV("upvalueid", DebugLib::upvalueId), RegisteredFunction.ofV("upvaluejoin", DebugLib::upvalueJoin), - }); - env.rawset("debug", t); - state.loadedPackages.rawset("debug", t); - return t; + })); } // ------------------- library function implementations ----------------- - // j2se subclass may wish to override and provide actual console here. - // j2me platform has not System.in to provide console. private static Varargs debug(LuaState state, Varargs args) { return NONE; } @@ -358,8 +351,8 @@ private static Varargs setmetatable(LuaState state, Varargs args) { } } - private Varargs getregistry(LuaState state, Varargs args) { - return registry; + private static Varargs getregistry(LuaState state, Varargs args) { + return state.registry().get(); } private static LuaString findupvalue(LuaClosure c, int up) { diff --git a/src/main/java/org/squiddev/cobalt/lib/MathLib.java b/src/main/java/org/squiddev/cobalt/lib/MathLib.java index dc541632..710539ff 100644 --- a/src/main/java/org/squiddev/cobalt/lib/MathLib.java +++ b/src/main/java/org/squiddev/cobalt/lib/MathLib.java @@ -28,7 +28,6 @@ import org.squiddev.cobalt.*; import org.squiddev.cobalt.function.LibFunction; import org.squiddev.cobalt.function.RegisteredFunction; -import org.squiddev.cobalt.lib.jse.JsePlatform; import java.util.Random; @@ -41,14 +40,13 @@ * This has been implemented to match as closely as possible the behavior in the corresponding library in C. * * @see LibFunction - * @see JsePlatform + * @see CoreLibraries * @see http://www.lua.org/manual/5.1/manual.html#5.6 */ -public class MathLib implements LuaLibrary { +public class MathLib { private Random random; - @Override - public LuaValue add(LuaState state, LuaTable env) { + public void add(LuaState state, LuaTable env) { final RegisteredFunction[] functions = new RegisteredFunction[]{ RegisteredFunction.of("abs", (s, arg) -> valueOf(Math.abs(arg.checkDouble()))), RegisteredFunction.of("ceil", (s, arg) -> valueOf(Math.ceil(arg.checkDouble()))), @@ -87,9 +85,7 @@ public LuaValue add(LuaState state, LuaTable env) { t.rawset("huge", LuaDouble.POSINF); t.rawset("mod", t.rawget("fmod")); - env.rawset("math", t); - state.loadedPackages.rawset("math", t); - return t; + LibFunction.setGlobalLibrary(state, env, "math", t); } private static LuaValue fmod(LuaState state, LuaValue arg1, LuaValue arg2) throws LuaError { diff --git a/src/main/java/org/squiddev/cobalt/lib/StringFormat.java b/src/main/java/org/squiddev/cobalt/lib/StringFormat.java index bf57213e..65ca2033 100644 --- a/src/main/java/org/squiddev/cobalt/lib/StringFormat.java +++ b/src/main/java/org/squiddev/cobalt/lib/StringFormat.java @@ -25,7 +25,7 @@ static class FormatState { /** * 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. @@ -35,14 +35,14 @@ static class FormatState { * 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. * @@ -134,7 +134,8 @@ private static void addQuoted(Buffer buf, int arg, LuaValue s) throws LuaError { } break; } - case TBOOLEAN: case TNIL: + case TBOOLEAN: + case TNIL: buf.append(s.toString()); break; default: diff --git a/src/main/java/org/squiddev/cobalt/lib/StringLib.java b/src/main/java/org/squiddev/cobalt/lib/StringLib.java index e7e8763d..e7cc70b4 100644 --- a/src/main/java/org/squiddev/cobalt/lib/StringLib.java +++ b/src/main/java/org/squiddev/cobalt/lib/StringLib.java @@ -25,12 +25,11 @@ package org.squiddev.cobalt.lib; import org.squiddev.cobalt.*; -import org.squiddev.cobalt.compiler.DumpState; +import org.squiddev.cobalt.compiler.BytecodeDumper; import org.squiddev.cobalt.debug.DebugFrame; import org.squiddev.cobalt.function.*; import org.squiddev.cobalt.lib.StringFormat.FormatState; import org.squiddev.cobalt.lib.StringMatch.GSubState; -import org.squiddev.cobalt.lib.jse.JsePlatform; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -45,17 +44,18 @@ * This is a direct port of the corresponding library in C. * * @see LibFunction - * @see JsePlatform + * @see CoreLibraries * @see http://www.lua.org/manual/5.1/manual.html#5.4 */ -public class StringLib implements LuaLibrary { +public final class StringLib { static final int L_ESC = '%'; private static final int MAX_LEN = Integer.MAX_VALUE; - @Override - public LuaValue add(LuaState state, LuaTable env) { - LuaTable t = new LuaTable(); - RegisteredFunction.bind(t, new RegisteredFunction[]{ + private StringLib() { + } + + public static void add(LuaState state, LuaTable env) { + LuaTable t = RegisteredFunction.bind(new RegisteredFunction[]{ RegisteredFunction.of("len", StringLib::len), RegisteredFunction.of("lower", StringLib::lower), RegisteredFunction.of("reverse", StringLib::reverse), @@ -76,11 +76,9 @@ public LuaValue add(LuaState state, LuaTable env) { }); t.rawset("gfind", t.rawget("gmatch")); - env.rawset("string", t); + LibFunction.setGlobalLibrary(state, env, "string", t); state.stringMetatable = tableOf(INDEX, t); - state.loadedPackages.rawset("string", t); - return t; } private static LuaValue len(LuaState state, LuaValue arg) throws LuaError { @@ -236,7 +234,7 @@ static LuaValue dump(LuaState state, Varargs args) throws LuaError { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { - DumpState.dump(((LuaClosure) f).getPrototype(), baos, strip); + BytecodeDumper.dump(((LuaClosure) f).getPrototype(), baos, strip); } catch (IOException e) { throw new LuaError(e.getMessage()); } diff --git a/src/main/java/org/squiddev/cobalt/lib/TableLib.java b/src/main/java/org/squiddev/cobalt/lib/TableLib.java index 939e1590..040c4799 100644 --- a/src/main/java/org/squiddev/cobalt/lib/TableLib.java +++ b/src/main/java/org/squiddev/cobalt/lib/TableLib.java @@ -26,8 +26,10 @@ import org.squiddev.cobalt.*; import org.squiddev.cobalt.debug.DebugFrame; -import org.squiddev.cobalt.function.*; -import org.squiddev.cobalt.lib.jse.JsePlatform; +import org.squiddev.cobalt.function.LibFunction; +import org.squiddev.cobalt.function.LuaFunction; +import org.squiddev.cobalt.function.RegisteredFunction; +import org.squiddev.cobalt.function.ResumableVarArgFunction; import static org.squiddev.cobalt.Constants.*; import static org.squiddev.cobalt.ValueFactory.valueOf; @@ -40,24 +42,32 @@ * This has been implemented to match as closely as possible the behavior in the corresponding library in C. * * @see LibFunction - * @see JsePlatform + * @see CoreLibraries * @see http://www.lua.org/manual/5.1/manual.html#5.5 */ -public class TableLib implements LuaLibrary { +public final class TableLib { private static final LuaValue N = LuaString.valueOf("n"); - @Override - public LuaTable add(LuaState state, LuaTable env) { - LuaTable t = new LuaTable(); - RegisteredFunction.bind(t, new RegisteredFunction[]{ + private TableLib() { + } + + public static void add(LuaState state, LuaTable env) { + LuaTable t = RegisteredFunction.bind(new RegisteredFunction[]{ RegisteredFunction.of("getn", TableLib::getn), - RegisteredFunction.of("maxn", TableLib::maxn) + RegisteredFunction.of("maxn", TableLib::maxn), + RegisteredFunction.ofV("remove", TableLib::remove), + RegisteredFunction.ofV("concat", TableLib::concat), + RegisteredFunction.ofV("insert", TableLib::insert), + RegisteredFunction.ofV("pack", TableLib::pack), + RegisteredFunction.ofFactory("sort", Sort::new), + RegisteredFunction.ofFactory("foreach", ForEach::new), + RegisteredFunction.ofFactory("foreachi", ForEachI::new), + RegisteredFunction.ofFactory("unpack", Unpack::new), }); - LibFunction.bind(t, TableLibV::new, new String[]{"remove", "concat", "insert", "pack"}); - LibFunction.bind(t, TableLibR::new, new String[]{"sort", "foreach", "foreachi", "unpack"}); - env.rawset("table", t); - state.loadedPackages.rawset("table", t); - return t; + + env.rawset("unpack", t.rawget("unpack")); + + LibFunction.setGlobalLibrary(state, env, "table", t); } private static LuaValue getn(LuaState state, LuaValue arg) throws LuaError { @@ -70,232 +80,221 @@ private static LuaValue maxn(LuaState state, LuaValue arg) throws LuaError { return valueOf(arg.checkTable().maxn()); } - private static final class TableLibV extends VarArgFunction { - @Override - public Varargs invoke(LuaState state, Varargs args) throws LuaError { - switch (opcode) { - case 0: { // "remove" (table [, pos]) -> removed-ele - LuaTable table = args.arg(1).checkTable(); - int pos = args.count() > 1 ? args.arg(2).checkInteger() : 0; - return table.remove(pos); - } - case 1: { // "concat" (table [, sep [, i [, j]]]) -> string - LuaTable table = args.arg(1).checkTable(); - return table.concat( - args.arg(2).optLuaString(EMPTYSTRING), - args.arg(3).optInteger(1), - args.exists(4) ? args.arg(4).checkInteger() : table.length()); - } - case 2: { // "insert" (table, [pos,] value) -> prev-ele - final LuaTable table = args.arg(1).checkTable(); - final int pos = args.count() > 2 ? args.arg(2).checkInteger() : 0; - final LuaValue value = args.arg(args.count() > 2 ? 3 : 2); - table.insert(pos, value); - return NONE; - } - case 3: { // pack(...) - int count = args.count(); - LuaTable table = new LuaTable(count, 1); - for (int i = 1; i <= count; i++) table.rawset(i, args.arg(i)); - table.rawset(N, valueOf(count)); - return table; - } - default: - return NONE; - } - } + private static Varargs remove(LuaState state, Varargs args) throws LuaError { + // remove (table [, pos]) -> removed-ele + LuaTable table = args.arg(1).checkTable(); + int pos = args.count() > 1 ? args.arg(2).checkInteger() : 0; + return table.remove(pos); } - private static final class TableLibR extends ResumableVarArgFunction { - @Override - protected Varargs invoke(LuaState state, DebugFrame di, Varargs args) throws LuaError, UnwindThrowable { - switch (opcode) { - case 0: { // "sort" (table [, comp]) -> void - LuaTable table = args.arg(1).checkTable(); - LuaValue compare = args.isNoneOrNil(2) ? NIL : args.arg(2).checkFunction(); - int n = table.length(); - if (n > 1) { - SortState res = new SortState(table, n, compare); - di.state = res; - heapSort(state, table, n, compare, res, 0, n / 2 - 1); - } - return NONE; - } - case 1: { // "foreach" (table, func) -> void - LuaTable table = args.arg(1).checkTable(); - LuaFunction function = args.arg(2).checkFunction(); + private static Varargs concat(LuaState state, Varargs args) throws LuaError { + LuaTable table = args.arg(1).checkTable(); + return table.concat( + args.arg(2).optLuaString(EMPTYSTRING), + args.arg(3).optInteger(1), + args.exists(4) ? args.arg(4).checkInteger() : table.length()); + } - ForEachState res = new ForEachState(table, function); - di.state = res; - return foreach(state, table, function, res); - } - case 2: { // "foreachi" (table, func) -> void - LuaTable table = args.arg(1).checkTable(); - LuaFunction function = args.arg(2).checkFunction(); + private static Varargs insert(LuaState state, Varargs args) throws LuaError { + final LuaTable table = args.arg(1).checkTable(); + final int pos = args.count() > 2 ? args.arg(2).checkInteger() : 0; + final LuaValue value = args.arg(args.count() > 2 ? 3 : 2); + table.insert(pos, value); + return NONE; + } - ForEachIState res = new ForEachIState(table, function); - di.state = res; - return foreachi(state, table, function, res); - } - case 3: { // unpack(table[, start[, stop]]) - LuaValue table = args.arg(1); - int start = args.arg(2).optInteger(1); - UnpackState res = new UnpackState(table, start); - di.state = res; - - LuaValue endValue = args.arg(3); - int end = res.end = (endValue.isNil() ? OperationHelper.length(state, table) : endValue).checkInteger(); - if (start > end) return NONE; - LuaValue[] values = res.values = new LuaValue[end - start + 1]; - - for (int i = start; i <= end; i++) { - res.index = i; - values[i - start] = OperationHelper.getTable(state, table, valueOf(i)); - } + private static Varargs pack(LuaState state, Varargs args) throws LuaError { + int count = args.count(); + LuaTable table = new LuaTable(count, 1); + for (int i = 1; i <= count; i++) table.rawset(i, args.arg(i)); + table.rawset(N, valueOf(count)); + return table; + } - return varargsOf(values); - } - default: - return NONE; + // "sort" (table [, comp]) -> void + private static class Sort extends ResumableVarArgFunction { + @Override + protected Varargs invoke(LuaState state, DebugFrame di, Varargs args) throws LuaError, UnwindThrowable { + LuaTable table = args.arg(1).checkTable(); + LuaValue compare = args.isNoneOrNil(2) ? NIL : args.arg(2).checkFunction(); + int n = table.length(); + if (n > 1) { + SortState res = new SortState(table, n, compare); + di.state = res; + heapSort(state, table, n, compare, res, 0, n / 2 - 1); } + return NONE; } @Override - protected Varargs resumeThis(LuaState state, Object object, Varargs value) throws LuaError, UnwindThrowable { - switch (opcode) { - case 0: { // "sort" (table [, comp]) -> void - SortState res = (SortState) object; - LuaTable table = res.table; - LuaValue compare = res.compare; - int count = res.count; - - // We attempt to recover from sifting the state - if (res.siftState != -1) { - int root = stateSiftDown(state, table, compare, res, value.first().toBoolean()); - res.siftState = -1; - - // Continue sifting here - if (root != -1) normalSiftDown(state, table, root, res.end, compare, res); - } - - // And continue sorting - heapSort(state, table, count, compare, res, res.sortState, res.counter); - return NONE; - } + protected Varargs resumeThis(LuaState state, SortState res, Varargs value) throws LuaError, UnwindThrowable { + LuaTable table = res.table; + LuaValue compare = res.compare; + int count = res.count; + + // We attempt to recover from sifting the state + if (res.siftState != -1) { + int root = stateSiftDown(state, table, compare, res, value.first().toBoolean()); + res.siftState = -1; + + // Continue sifting here + if (root != -1) normalSiftDown(state, table, root, res.end, compare, res); + } - case 1: { // "foreach" (table, func) -> void - ForEachState res = (ForEachState) object; - return foreach(state, res.table, res.func, res); - } + // And continue sorting + heapSort(state, table, count, compare, res, res.sortState, res.counter); + return NONE; + } + } - case 2: { // "foreachi" (table, func) -> void - ForEachIState res = (ForEachIState) object; - return foreachi(state, res.table, res.func, res); - } + /** + * {@code foreach(table, func) -> void}: Call the supplied function once for each key-value pair + */ + private static class ForEach extends ResumableVarArgFunction { + private static final class State { + LuaValue k = NIL; + final LuaTable table; + final LuaValue func; + + State(LuaTable table, LuaValue func) { + this.table = table; + this.func = func; + } + } - case 3: { // unpack(table[, start[, stop]]) - UnpackState res = (UnpackState) object; - int start = res.start; - LuaValue table = res.table; - int end = res.end; - LuaValue[] values = res.values; - - // If values is null, then we've yielded from fetching the length. - if (values == null) { - end = res.end = value.first().checkInteger(); - if (start > end) return NONE; - values = res.values = new LuaValue[end - start + 1]; - res.index = start; - } else { - values[res.index - start] = value.first(); - res.index++; - } + @Override + protected Varargs invoke(LuaState state, DebugFrame di, Varargs args) throws LuaError, UnwindThrowable { + LuaTable table = args.arg(1).checkTable(); + LuaFunction function = args.arg(2).checkFunction(); - for (int i = res.index; i <= end; i++) { - res.index = i; - values[i - start] = OperationHelper.getTable(state, table, valueOf(i)); - } + State res = new State(table, function); + di.state = res; + return run(state, res); + } - return varargsOf(values); - } + @Override + protected Varargs resumeThis(LuaState state, State res, Varargs value) throws LuaError, UnwindThrowable { + return run(state, res); + } - default: - throw new NonResumableException("Cannot resume " + debugName()); + private static LuaValue run(LuaState state, State res) throws LuaError, UnwindThrowable { + Varargs n; + LuaValue k = res.k; + while (!(res.k = k = ((n = res.table.next(k)).first())).isNil()) { + LuaValue r = OperationHelper.call(state, res.func, k, n.arg(2)); + if (!r.isNil()) return r; } + return NIL; } } - static final class ForEachState { - public LuaValue k = NIL; - public final LuaTable table; - public final LuaValue func; - - ForEachState(LuaTable table, LuaValue func) { - this.table = table; - this.func = func; + /** + * {@code foreachi(table, func) -> void}: Call the supplied function once for each key-value pair in the contiguous + * array part + */ + private static class ForEachI extends ResumableVarArgFunction { + private static final class State { + int k = 0; + final LuaTable table; + final LuaValue func; + + State(LuaTable table, LuaValue func) { + this.table = table; + this.func = func; + } } - } - static final class UnpackState { - private final LuaValue table; - private final int start; - private int index; + @Override + protected Varargs invoke(LuaState state, DebugFrame di, Varargs args) throws LuaError, UnwindThrowable { + LuaTable table = args.arg(1).checkTable(); + LuaFunction function = args.arg(2).checkFunction(); - private int end; - private LuaValue[] values; + State res = new State(table, function); + di.state = res; + return run(state, res); + } - UnpackState(LuaValue table, int start) { - this.table = table; - this.start = start; + @Override + protected Varargs resumeThis(LuaState state, State res, Varargs value) throws LuaError, UnwindThrowable { + return run(state, res); + } + + private static LuaValue run(LuaState state, State res) throws LuaError, UnwindThrowable { + LuaValue v; + int k = res.k; + while (!(v = res.table.rawget(res.k = ++k)).isNil()) { + LuaValue r = OperationHelper.call(state, res.func, valueOf(k), v); + if (!r.isNil()) return r; + } + return NIL; } } /** - * Call the supplied function once for each key-value pair - * - * @param state The current lua state - * @param func The function to call - * @return {@link Constants#NIL} + * {@code unpack(table[, start[, stop]])} */ - private static LuaValue foreach(LuaState state, LuaTable table, LuaValue func, ForEachState res) throws LuaError, UnwindThrowable { - Varargs n; - LuaValue k = res.k; - while (!(res.k = k = ((n = table.next(k)).first())).isNil()) { - LuaValue r = OperationHelper.call(state, func, k, n.arg(2)); - if (!r.isNil()) return r; + private static final class Unpack extends ResumableVarArgFunction { + static final class State { + final LuaValue table; + final int start; + + int index; + + int end; + LuaValue[] values; + + State(LuaValue table, int start) { + this.table = table; + this.start = start; + } } - return NIL; - } - static final class ForEachIState { - int k = 0; - final LuaTable table; - final LuaValue func; + @Override + protected Varargs invoke(LuaState state, DebugFrame di, Varargs args) throws LuaError, UnwindThrowable { + LuaValue table = args.arg(1); + int start = args.arg(2).optInteger(1); + State res = new State(table, start); + di.state = res; + + LuaValue endValue = args.arg(3); + int end = res.end = (endValue.isNil() ? OperationHelper.length(state, table) : endValue).checkInteger(); + if (start > end) return NONE; + LuaValue[] values = res.values = new LuaValue[end - start + 1]; + + for (int i = start; i <= end; res.index = ++i) { + values[i - start] = OperationHelper.getTable(state, table, valueOf(i)); + } - ForEachIState(LuaTable table, LuaValue func) { - this.table = table; - this.func = func; + return varargsOf(values); } - } - /** - * Call the supplied function once for each key-value pair - * in the contiguous array part - * - * @param state The current lua state - * @param func The function to call - * @return {@link Constants#NIL} - */ - private static LuaValue foreachi(LuaState state, LuaTable table, LuaValue func, ForEachIState res) throws LuaError, UnwindThrowable { - LuaValue v; - int k = res.k; - while (!(v = table.rawget(res.k = ++k)).isNil()) { - LuaValue r = OperationHelper.call(state, func, valueOf(k), v); - if (!r.isNil()) return r; + @Override + protected Varargs resumeThis(LuaState state, State res, Varargs value) throws LuaError, UnwindThrowable { + int start = res.start; + LuaValue table = res.table; + int end = res.end; + LuaValue[] values = res.values; + + // If values is null, then we've yielded from fetching the length. + if (values == null) { + end = res.end = value.first().checkInteger(); + if (start > end) return NONE; + values = res.values = new LuaValue[end - start + 1]; + res.index = start; + } else { + values[res.index - start] = value.first(); + res.index++; + } + + for (int i = res.index; i <= end; res.index = ++i) { + values[i - start] = OperationHelper.getTable(state, table, valueOf(i)); + } + + return varargsOf(values); } - return NIL; } + private static final class SortState { final LuaTable table; final int count; diff --git a/src/main/java/org/squiddev/cobalt/lib/UncheckedLuaError.java b/src/main/java/org/squiddev/cobalt/lib/UncheckedLuaError.java index e5bc91a9..8b74aee2 100644 --- a/src/main/java/org/squiddev/cobalt/lib/UncheckedLuaError.java +++ b/src/main/java/org/squiddev/cobalt/lib/UncheckedLuaError.java @@ -28,11 +28,11 @@ /** * An unchecked version of {@link org.squiddev.cobalt.LuaError}. - * + *

* This should only be used when you need to propagate across a Java call boundary (say within * an {@link java.io.InputStream}. * - * @see org.squiddev.cobalt.LuaError#wrap(Exception) + * @see org.squiddev.cobalt.LuaError#wrap(Throwable) */ public final class UncheckedLuaError extends RuntimeException { private static final long serialVersionUID = -2431451026200110553L; diff --git a/src/main/java/org/squiddev/cobalt/lib/Utf8Lib.java b/src/main/java/org/squiddev/cobalt/lib/Utf8Lib.java index 30114c24..86d23071 100644 --- a/src/main/java/org/squiddev/cobalt/lib/Utf8Lib.java +++ b/src/main/java/org/squiddev/cobalt/lib/Utf8Lib.java @@ -9,8 +9,7 @@ import static org.squiddev.cobalt.ValueFactory.valueOf; import static org.squiddev.cobalt.ValueFactory.varargsOf; -public class Utf8Lib implements LuaLibrary { - +public class Utf8Lib { private static final int[] LIMITS = {0xFF, 0x7F, 0x7FF, 0xFFFF}; public static final long MAX_UNICODE = 0x10FFFFL; @@ -27,8 +26,7 @@ public class Utf8Lib implements LuaLibrary { */ private LibFunction codesIter; - @Override - public LuaValue add(LuaState state, LuaTable env) { + public void add(LuaState state, LuaTable env) { codesIter = RegisteredFunction.ofV("utf8.codesIter", Utf8Lib::codesIter).create(); LuaTable t = new LuaTable(0, 6); @@ -41,10 +39,7 @@ public LuaValue add(LuaState state, LuaTable env) { }); t.rawset("charpattern", PATTERN); - env.rawset("utf8", t); - state.loadedPackages.rawset("utf8", t); - - return t; + LibFunction.setGlobalLibrary(state, env, "utf8", t); } public static int buildCharacter(byte[] buffer, long codepoint) { diff --git a/src/main/java/org/squiddev/cobalt/lib/doubles/Assert.java b/src/main/java/org/squiddev/cobalt/lib/doubles/Assert.java index 569d4295..c15384a7 100644 --- a/src/main/java/org/squiddev/cobalt/lib/doubles/Assert.java +++ b/src/main/java/org/squiddev/cobalt/lib/doubles/Assert.java @@ -30,23 +30,7 @@ package org.squiddev.cobalt.lib.doubles; -final public class Assert { - private static boolean enabled = false; - - public static boolean assertEnabled() { - return enabled; - } - - public static void assertThat(boolean expected) { - if (enabled && !expected) { - throw new DoubleConversionAssertionException("Assertion failed"); - } - } - - public static void setEnabled(boolean enabled) { - Assert.enabled = enabled; - } - +final class Assert { public static void requireState(boolean condition, String msg) { if (!condition) throw new IllegalStateException(msg); } @@ -55,25 +39,6 @@ public static void requireArg(boolean condition, String msg) { if (!condition) throw new IllegalArgumentException(msg); } - /** - * Error thrown on Double conversion assertion failures. - * Used in tests to help isolate which test data item failed. - */ - public static class DoubleConversionAssertionException extends IllegalStateException { - private static final long serialVersionUID = 1L; - - public DoubleConversionAssertionException() { - } - - public DoubleConversionAssertionException(String message) { - super(message, null); - } - - public DoubleConversionAssertionException(String message, Throwable cause) { - super(message, cause); - } - } - private Assert() { } } diff --git a/src/main/java/org/squiddev/cobalt/lib/doubles/BigNumDtoa.java b/src/main/java/org/squiddev/cobalt/lib/doubles/BigNumDtoa.java index d35ae034..26586908 100644 --- a/src/main/java/org/squiddev/cobalt/lib/doubles/BigNumDtoa.java +++ b/src/main/java/org/squiddev/cobalt/lib/doubles/BigNumDtoa.java @@ -33,10 +33,7 @@ import org.checkerframework.checker.signedness.qual.Unsigned; -import static org.squiddev.cobalt.lib.doubles.Assert.assertEnabled; -import static org.squiddev.cobalt.lib.doubles.Assert.assertThat; - -public class BigNumDtoa { +final class BigNumDtoa { @SuppressWarnings("ImplicitNumericConversion") private static final int ASCII_ZERO = '0'; @@ -57,7 +54,7 @@ public enum BignumDtoaMode { } private static int normalizedExponent(@Unsigned long significand, int exponent) { - if (assertEnabled()) assertThat(significand != 0L); + assert significand != 0L; while ((significand & Ieee.Double.HIDDEN_BIT) == 0L) { significand = significand << 1; exponent = exponent - 1; @@ -68,9 +65,9 @@ private static int normalizedExponent(@Unsigned long significand, int exponent) /** * Converts the given double 'v' to ascii. * The result should be interpreted as buffer * 10^(point-length). - * + *

* The input v must be > 0 and different from NaN, and Infinity. - * + *

* The output depends on the given mode: * - FIXED: produces digits necessary to print a given number with * 'requestedDigits' digits after the decimal point. The produced digits @@ -89,10 +86,8 @@ private static int normalizedExponent(@Unsigned long significand, int exponent) * 'bignumDtoa' expects the given buffer to be big enough to hold all digits. */ public static void bignumDtoa(double v, BignumDtoaMode mode, int requestedDigits, DecimalRepBuf buf) { - if (assertEnabled()) { - assertThat(v > 0.0); - assertThat(!new Ieee.Double(v).isSpecial()); - } + assert v > 0.0; + assert !new Ieee.Double(v).isSpecial(); @Unsigned long significand; int exponent; significand = new Ieee.Double(v).significand(); @@ -154,7 +149,7 @@ public static void bignumDtoa(double v, BignumDtoaMode mode, int requestedDigits * Once 'count' digits have been produced rounds the result depending on the * remainder (remainders of exactly .5 round upwards). Might update the * decimalPoint when rounding up (for example for 0.9999). - * + *

* Let v = numerator / denominator < 10. * Then we generate 'count' digits of d = x.xxxxx... (without the decimal point) * from left to right. Once 'count' digits have been produced we decide wether @@ -163,7 +158,7 @@ public static void bignumDtoa(double v, BignumDtoaMode mode, int requestedDigits * exponent (decimalPoint), when rounding upwards. */ private static void generateCountedDigits(int count, Bignum numerator, Bignum denominator, DecimalRepBuf buf) { - if (assertEnabled()) assertThat(count >= 0); + assert count >= 0; for (int i = 0; i < count - 1; ++i) { @Unsigned int digit = numerator.divideModuloIntBignum(denominator); // digit = numerator / denominator (integer division). @@ -187,7 +182,7 @@ private static void generateCountedDigits(int count, Bignum numerator, Bignum de * Generates 'requestedDigits' after the decimal point. It might omit * trailing '0's. If the input number is too small then no digits at all are * generated (ex.: 2 fixed digits for 0.00001). - * + *

* Input verifies: 1 <= (numerator + delta) / denominator < 10. */ private static void bignumToFixed(int requestedDigits, Bignum numerator, Bignum denominator, DecimalRepBuf buf) { @@ -207,7 +202,7 @@ private static void bignumToFixed(int requestedDigits, Bignum numerator, Bignum } else if (-decimalPoint == requestedDigits) { // We only need to verify if the number rounds down or up. // Ex: 0.04 and 0.06 with requestedDigits == 1. - if (assertEnabled()) assertThat(decimalPoint == -requestedDigits); + assert decimalPoint == -requestedDigits; // Initially the fraction lies in range (1, 10]. Multiply the denominator // by 10 so that we can compare more easily. denominator.times10(); @@ -238,16 +233,16 @@ private static void bignumToFixed(int requestedDigits, Bignum numerator, Bignum * v = f * 2^exponent and 2^52 <= f < 2^53. * v is hence a normalized double with the given exponent. The output is an * approximation for the exponent of the decimal approximation .digits * 10^k. - * + *

* The result might undershoot by 1 in which case 10^k <= v < 10^k+1. * Note: this property holds for v's upper boundary m+ too. * 10^k <= m+ < 10^k+1. * (see explanation below). - * + *

* Examples: * estimatePower(0) => 16 * estimatePower(-52) => 0 - * + *

* Note: e >= 0 => EstimatedPower(e) > 0. No similar claim can be made for e<0. */ private static int estimatePower(int exponent) { @@ -289,7 +284,7 @@ private static int estimatePower(int exponent) { * Then d * 10^estimatedPower is the representation of v. * (Note: the fraction and the estimatedPower might get adjusted before * generating the decimal representation.) - * + *

* The initial start values consist of: * - a scaled numerator: s.t. numerator/denominator == v / 10^estimatedPower. * - a scaled (common) denominator. @@ -297,9 +292,9 @@ private static int estimatePower(int exponent) { * decimal converting back to v): * - v - m-: the distance to the lower boundary. * - m+ - v: the distance to the upper boundary. - * + *

* v, m+, m-, and therefore v - m- and m+ - v all share the same denominator. - * + *

* Let ep == estimatedPower, then the returned values will satisfy: * v / 10^ep = numerator / denominator. * v's boundaries m- and m+: @@ -308,14 +303,14 @@ private static int estimatePower(int exponent) { * Or in other words: * m- == v - deltaMinus * 10^ep / denominator; * m+ == v + deltaPlus * 10^ep / denominator; - * + *

* Since 10^(k-1) <= v < 10^k (with k == estimatedPower) * or 10^k <= v < 10^(k+1) * we then have 0.1 <= numerator/denominator < 1 * or 1 <= numerator/denominator < 10 - * + *

* It is then easy to kickstart the digit-generation routine. - * + *

* The boundary-deltas are only filled if the mode equals BIGNUM_DTOA_SHORTEST. */ private static void initialScaledStartValuesPositiveExponent( @@ -323,7 +318,7 @@ private static void initialScaledStartValuesPositiveExponent( int estimatedPower, Bignum numerator, Bignum denominator) { // A positive exponent implies a positive power. - assertThat(estimatedPower >= 0); + assert estimatedPower >= 0; // Since the estimatedPower is positive we simply multiply the denominator // by 10^estimatedPower. @@ -344,7 +339,7 @@ private static void initialScaledStartValuesPositiveExponent( * Then d * 10^estimatedPower is the representation of v. * (Note: the fraction and the estimatedPower might get adjusted before * generating the decimal representation.) - * + *

* The initial start values consist of: * - a scaled numerator: s.t. numerator/denominator == v / 10^estimatedPower. * - a scaled (common) denominator. @@ -352,9 +347,9 @@ private static void initialScaledStartValuesPositiveExponent( * decimal converting back to v): * - v - m-: the distance to the lower boundary. * - m+ - v: the distance to the upper boundary. - * + *

* v, m+, m-, and therefore v - m- and m+ - v all share the same denominator. - * + *

* Let ep == estimatedPower, then the returned values will satisfy: * v / 10^ep = numerator / denominator. * v's boundaries m- and m+: @@ -363,14 +358,14 @@ private static void initialScaledStartValuesPositiveExponent( * Or in other words: * m- == v - deltaMinus * 10^ep / denominator; * m+ == v + deltaPlus * 10^ep / denominator; - * + *

* Since 10^(k-1) <= v < 10^k (with k == estimatedPower) * or 10^k <= v < 10^(k+1) * we then have 0.1 <= numerator/denominator < 1 * or 1 <= numerator/denominator < 10 - * + *

* It is then easy to kickstart the digit-generation routine. - * + *

* The boundary-deltas are only filled if the mode equals BIGNUM_DTOA_SHORTEST. */ private static void initialScaledStartValuesNegativeExponentPositivePower( @@ -401,7 +396,7 @@ private static void initialScaledStartValuesNegativeExponentPositivePower( * Then d * 10^estimatedPower is the representation of v. * (Note: the fraction and the estimatedPower might get adjusted before * generating the decimal representation.) - * + *

* The initial start values consist of: * - a scaled numerator: s.t. numerator/denominator == v / 10^estimatedPower. * - a scaled (common) denominator. @@ -409,9 +404,9 @@ private static void initialScaledStartValuesNegativeExponentPositivePower( * decimal converting back to v): * - v - m-: the distance to the lower boundary. * - m+ - v: the distance to the upper boundary. - * + *

* v, m+, m-, and therefore v - m- and m+ - v all share the same denominator. - * + *

* Let ep == estimatedPower, then the returned values will satisfy: * v / 10^ep = numerator / denominator. * v's boundaries m- and m+: @@ -420,14 +415,14 @@ private static void initialScaledStartValuesNegativeExponentPositivePower( * Or in other words: * m- == v - deltaMinus * 10^ep / denominator; * m+ == v + deltaPlus * 10^ep / denominator; - * + *

* Since 10^(k-1) <= v < 10^k (with k == estimatedPower) * or 10^k <= v < 10^(k+1) * we then have 0.1 <= numerator/denominator < 1 * or 1 <= numerator/denominator < 10 - * + *

* It is then easy to kickstart the digit-generation routine. - * + *

* The boundary-deltas are only filled if the mode equals BIGNUM_DTOA_SHORTEST. */ private static void initialScaledStartValuesNegativeExponentNegativePower( @@ -446,7 +441,6 @@ private static void initialScaledStartValuesNegativeExponentNegativePower( // numerator = v * 10^-estimatedPower * 2 * 2^-exponent. // Remember: numerator has been abused as powerTen. So no need to assign it // to itself. - if (assertEnabled()) assertThat(numerator.equals(powerTen)); numerator.multiplyByUInt64(ulSignificand); // denominator = 2 * 2^-exponent with exponent < 0. @@ -463,7 +457,7 @@ private static void initialScaledStartValuesNegativeExponentNegativePower( * Then d * 10^estimatedPower is the representation of v. * (Note: the fraction and the estimatedPower might get adjusted before * generating the decimal representation.) - * + *

* The initial start values consist of: * - a scaled numerator: s.t. numerator/denominator == v / 10^estimatedPower. * - a scaled (common) denominator. @@ -471,9 +465,9 @@ private static void initialScaledStartValuesNegativeExponentNegativePower( * decimal converting back to v): * - v - m-: the distance to the lower boundary. * - m+ - v: the distance to the upper boundary. - * + *

* v, m+, m-, and therefore v - m- and m+ - v all share the same denominator. - * + *

* Let ep == estimatedPower, then the returned values will satisfy: * v / 10^ep = numerator / denominator. * v's boundaries m- and m+: @@ -482,14 +476,14 @@ private static void initialScaledStartValuesNegativeExponentNegativePower( * Or in other words: * m- == v - deltaMinus * 10^ep / denominator; * m+ == v + deltaPlus * 10^ep / denominator; - * + *

* Since 10^(k-1) <= v < 10^k (with k == estimatedPower) * or 10^k <= v < 10^(k+1) * we then have 0.1 <= numerator/denominator < 1 * or 1 <= numerator/denominator < 10 - * + *

* It is then easy to kickstart the digit-generation routine. - * + *

* The boundary-deltas are only filled if the mode equals BIGNUM_DTOA_SHORTEST. */ private static void initialScaledStartValues( @@ -518,7 +512,7 @@ private static void initialScaledStartValues( * v = numerator'/denominator' * 10^(decimalPoint-1) * where numerator' and denominator' are the values of numerator and * denominator after the call to this function. - * + *

* This routine multiplies numerator/denominator so that its values lies in the * range 1-10. That is after a call to this function we have: * 1 <= (numerator + deltaPlus) /denominator < 10. diff --git a/src/main/java/org/squiddev/cobalt/lib/doubles/Bignum.java b/src/main/java/org/squiddev/cobalt/lib/doubles/Bignum.java index 577f5d50..2ff1fb40 100644 --- a/src/main/java/org/squiddev/cobalt/lib/doubles/Bignum.java +++ b/src/main/java/org/squiddev/cobalt/lib/doubles/Bignum.java @@ -43,7 +43,7 @@ * A mutable proxy object to java BigDecimal. Probably can be * optimized away at some point, but for now it will get the things into a functional state. */ -public class Bignum { +final class Bignum { private static final long LONG_SIGN_BIT = 0x8000_0000_0000_0000L; private static final long LONG_UNSIGNED_BITS = 0x7fff_ffff_ffff_ffffL; private static final BigInteger INT_MASK = BigInteger.valueOf(0xffff_ffffL); diff --git a/src/main/java/org/squiddev/cobalt/lib/doubles/DecimalRepBuf.java b/src/main/java/org/squiddev/cobalt/lib/doubles/DecimalRepBuf.java index d0469105..6ef0da7c 100644 --- a/src/main/java/org/squiddev/cobalt/lib/doubles/DecimalRepBuf.java +++ b/src/main/java/org/squiddev/cobalt/lib/doubles/DecimalRepBuf.java @@ -45,7 +45,7 @@ * of the decimal point, and the sign of the number. * Also includes point position {@code pointPosition} */ -public class DecimalRepBuf { +final class DecimalRepBuf { @SuppressWarnings("ImplicitNumericConversion") private static final int ASCII_ZERO = '0'; @SuppressWarnings("ImplicitNumericConversion") diff --git a/src/main/java/org/squiddev/cobalt/lib/doubles/DiyFp.java b/src/main/java/org/squiddev/cobalt/lib/doubles/DiyFp.java index 9e564651..3355703a 100644 --- a/src/main/java/org/squiddev/cobalt/lib/doubles/DiyFp.java +++ b/src/main/java/org/squiddev/cobalt/lib/doubles/DiyFp.java @@ -33,7 +33,7 @@ import org.checkerframework.checker.signedness.qual.Unsigned; -import static org.squiddev.cobalt.lib.doubles.Assert.*; +import static org.squiddev.cobalt.lib.doubles.Assert.requireArg; import static org.squiddev.cobalt.lib.doubles.UnsignedValues.ulongGE; /** @@ -44,7 +44,7 @@ * DiyFp store only non-negative numbers and are not designed to contain special * doubles (NaN and Infinity). */ -public class DiyFp { +final class DiyFp { public static final int SIGNIFICAND_SIZE = 64; private static final long UINT_64_MSB = 0x80000000_00000000L; @@ -126,7 +126,7 @@ public static DiyFp times(DiyFp a, DiyFp b) { } public void normalize() { - if (assertEnabled()) assertThat(f != 0L); + assert f != 0L; @Unsigned long significand = f; int exponent = e; diff --git a/src/main/java/org/squiddev/cobalt/lib/doubles/DoubleToStringConverter.java b/src/main/java/org/squiddev/cobalt/lib/doubles/DoubleToStringConverter.java index 2c86770f..12876c61 100644 --- a/src/main/java/org/squiddev/cobalt/lib/doubles/DoubleToStringConverter.java +++ b/src/main/java/org/squiddev/cobalt/lib/doubles/DoubleToStringConverter.java @@ -35,9 +35,9 @@ import org.squiddev.cobalt.Buffer; import static java.util.Objects.requireNonNull; -import static org.squiddev.cobalt.lib.doubles.Assert.*; +import static org.squiddev.cobalt.lib.doubles.Assert.requireArg; -public class DoubleToStringConverter { +public final class DoubleToStringConverter { public static final Symbols ECMA_SCRIPT_SYMBOLS = new Symbols("Infinity", "NaN", 'e'); /** * When calling toFixed with a double > 10^MAX_FIXED_DIGITS_BEFORE_POINT @@ -289,7 +289,7 @@ private void createExponentialRepresentation( requireArg(decimalDigits.length() != 0, "decimalDigits must not be empty"); requireArg(length <= decimalDigits.length(), "length must be smaller than decimalDigits"); - if (assertEnabled()) assertThat((double) exponent < 1e4); + assert (double) exponent < 1e4; ExponentPart exponentPart = createExponentPart(exponent); boolean emitTrailingPoint = fo.isAlternateForm() || (flags & Flags.EMIT_TRAILING_DECIMAL_POINT) != 0; @@ -412,7 +412,7 @@ private void createDecimalRepresentation( resultBuilder.append('.'); addPadding(resultBuilder, '0', -decimalPoint); - if (assertEnabled()) assertThat(digitsLength <= digitsAfterPoint - (-decimalPoint)); + assert digitsLength <= digitsAfterPoint - (-decimalPoint); resultBuilder.append(decimalDigits.getBuffer(), 0, decimalDigits.length()); int remainingDigits = digitsAfterPoint - (-decimalPoint) - digitsLength; addPadding(resultBuilder, '0', remainingDigits); @@ -427,10 +427,10 @@ private void createDecimalRepresentation( } } else { // "decima.l_rep000". - if (assertEnabled()) assertThat(digitsAfterPoint > 0); + assert digitsAfterPoint > 0; resultBuilder.append(decimalDigits.getBuffer(), 0, decimalPoint); resultBuilder.append('.'); - if (assertEnabled()) assertThat(digitsLength - decimalPoint <= digitsAfterPoint); + assert digitsLength - decimalPoint <= digitsAfterPoint; resultBuilder.append(decimalDigits.getBuffer(), decimalPoint, digitsLength - decimalPoint); int remainingDigits = digitsAfterPoint - (digitsLength - decimalPoint); addPadding(resultBuilder, '0', remainingDigits); @@ -619,7 +619,7 @@ public void toExponential(double value, int requestedDigits, FormatOptions forma doubleToAscii(value, DtoaMode.PRECISION, requestedDigits + 1, decimalRep); - if (assertEnabled()) assertThat(decimalRep.length() <= requestedDigits + 1); + assert decimalRep.length() <= requestedDigits + 1; decimalRep.zeroExtend(requestedDigits + 1); @@ -690,7 +690,7 @@ public void toPrecision(double value, int precision, FormatOptions formatOptions // Add one for the terminating null character. DecimalRepBuf decimalRep = new DecimalRepBuf(PRECISION_REP_CAPACITY); doubleToAscii(value, DtoaMode.PRECISION, precision, decimalRep); - if (assertEnabled()) assertThat(decimalRep.length() <= precision); + assert decimalRep.length() <= precision; // The exponent if we print the number as x.xxeyyy. That is with the // decimal point after the first digit. @@ -758,8 +758,10 @@ public enum DtoaMode { private static BigNumDtoa.BignumDtoaMode dtoaToBignumDtoaMode(DoubleToStringConverter.DtoaMode dtoaMode) { switch (dtoaMode) { - case FIXED: return BigNumDtoa.BignumDtoaMode.FIXED; - case PRECISION: return BigNumDtoa.BignumDtoaMode.PRECISION; + case FIXED: + return BigNumDtoa.BignumDtoaMode.FIXED; + case PRECISION: + return BigNumDtoa.BignumDtoaMode.PRECISION; default: throw new IllegalStateException("Unreachable"); } @@ -782,22 +784,22 @@ private static BigNumDtoa.BignumDtoaMode dtoaToBignumDtoaMode(DoubleToStringConv * 'requestedDigits' digits after the decimal outPoint. The produced digits * might be too short in which case the caller has to fill the remainder * with '0's. - *

+ *

* Example: toFixed(0.001, 5) is allowed to return buffer="1", outPoint=-2. - *

+ *

* Halfway cases are rounded towards +/-Infinity (away from 0). The call * toFixed(0.15, 2) thus returns buffer="2", outPoint=0. - *

+ *

* The returned buffer may contain digits that would be truncated from the * shortest representation of the input. - *

+ *

* *

  • * {@link DtoaMode#PRECISION PRECISION}: produces 'requestedDigits' where the first digit is not '0'. * Even though the outLength of produced digits usually equals * 'requestedDigits', the function is allowed to return fewer digits, in * which case the caller has to fill the missing digits with '0's. - *

    + *

    * Halfway cases are again rounded away from 0. *

  • * @@ -820,7 +822,7 @@ private static BigNumDtoa.BignumDtoaMode dtoaToBignumDtoaMode(DoubleToStringConv * and the {@link DecimalRepBuf#getSign() sign} of the number. */ public static void doubleToAscii(double v, DtoaMode mode, int requestedDigits, DecimalRepBuf buffer) { - if (assertEnabled()) requireArg(!new Ieee.Double(v).isSpecial(), "value can't be a special value"); + assert !new Ieee.Double(v).isSpecial() : "value can't be a special value"; requireArg(requestedDigits >= 0, "requestedDigits must be >= 0"); // begin with an empty buffer diff --git a/src/main/java/org/squiddev/cobalt/lib/doubles/FastDtoa.java b/src/main/java/org/squiddev/cobalt/lib/doubles/FastDtoa.java index af68181e..075e1be7 100644 --- a/src/main/java/org/squiddev/cobalt/lib/doubles/FastDtoa.java +++ b/src/main/java/org/squiddev/cobalt/lib/doubles/FastDtoa.java @@ -32,10 +32,10 @@ import org.checkerframework.checker.signedness.qual.Unsigned; -import static org.squiddev.cobalt.lib.doubles.Assert.*; +import static org.squiddev.cobalt.lib.doubles.Assert.requireArg; import static org.squiddev.cobalt.lib.doubles.UnsignedValues.*; -public class FastDtoa { +final class FastDtoa { @SuppressWarnings("ImplicitNumericConversion") private static final int ASCII_ZERO = '0'; @@ -49,7 +49,7 @@ public class FastDtoa { * The minimal and maximal target exponent define the range of w's binary * exponent, where 'w' is the result of multiplying the input by a cached power * of ten. - * + *

    * A different range might be chosen on a different platform, to optimize digit * generation, but a smaller range requires more powers of ten to be cached. */ @@ -58,7 +58,7 @@ public class FastDtoa { * The minimal and maximal target exponent define the range of w's binary * exponent, where 'w' is the result of multiplying the input by a cached power * of ten. - * + *

    * A different range might be chosen on a different platform, to optimize digit * generation, but a smaller range requires more powers of ten to be cached. */ @@ -70,12 +70,12 @@ public class FastDtoa { * round correctly, return false. * The rounding might shift the whole buffer in which case the kappa is * adjusted. For example "99", kappa = 3 might become "10", kappa = 4. - * + *

    * If 2*rest > tenKappa then the buffer needs to be round up. * rest can have an error of +/- 1 unit. This function accounts for the * imprecision and returns false, if the rounding direction cannot be * unambiguously determined. - * + *

    * Precondition: rest < tenKappa. */ private static boolean roundWeedCounted( @@ -85,7 +85,7 @@ private static boolean roundWeedCounted( @Unsigned long unit, int[] kappa ) { - if (assertEnabled()) assertThat(ulongLT(rest, tenKappa)); + assert ulongLT(rest, tenKappa); // The following tests are done in a specific order to avoid overflows. They // will work correctly with any uint64 values of rest < tenKappa and unit. // @@ -120,7 +120,7 @@ private static boolean roundWeedCounted( /** * Returns the biggest power of ten that is less than or equal to the given * number. We furthermore receive the maximum number of bits 'number' has. - * + *

    * Returns power == 10^(exponentPlusOne-1) such that * power <= number < power * 10. * If numberBits == 0 then 0^(0-1) is returned. @@ -150,14 +150,14 @@ static void biggestPowerTen(@Unsigned int number, int numberBits, @Unsigned int[ * exponent. Its exponent is bounded by MAXIMAL_TARGET_EXPONENT and * MAXIMAL_TARGET_EXPONENT. * Hence -60 <= w.e() <= -32. - * + *

    * Returns false if it fails, in which case the generated digits in the buffer * should not be used. * Preconditions: * * w is correct up to 1 ulp (unit in the last place). That * is, its error must be strictly less than a unit of its last digit. * * MINIMAL_TARGET_EXPONENT <= w.e() <= MAXIMAL_TARGET_EXPONENT - * + *

    * Postconditions: returns false if procedure fails. * otherwise: * * length contains the number of digits. @@ -167,16 +167,14 @@ static void biggestPowerTen(@Unsigned int number, int numberBits, @Unsigned int[ * than requestedDigits digits then some trailing '0's have been removed. * * kappa is such that * w = buffer * 10^kappa + eps with |eps| < 10^kappa / 2. - * + *

    * Remark: This procedure takes into account the imprecision of its input * numbers. If the precision is not enough to guarantee all the postconditions * then false is returned. This usually happens rarely, but the failure-rate * increases with higher requestedDigits. */ private static boolean digitGenCounted(DiyFp w, int requestedDigits, DecimalRepBuf buf, int[] kappa) { - if (assertEnabled()) assertThat(MINIMAL_TARGET_EXPONENT <= w.e() && w.e() <= MAXIMAL_TARGET_EXPONENT); - if (assertEnabled()) assertThat(MINIMAL_TARGET_EXPONENT >= -60); - if (assertEnabled()) assertThat(MAXIMAL_TARGET_EXPONENT <= -32); + assert MINIMAL_TARGET_EXPONENT <= w.e() && w.e() <= MAXIMAL_TARGET_EXPONENT; // w is assumed to have an error less than 1 unit. Whenever w is scaled we // also scale its error. @Unsigned long wError = 1L; @@ -231,11 +229,9 @@ private static boolean digitGenCounted(DiyFp w, int requestedDigits, DecimalRepB // data (the 'unit'), too. // Note that the multiplication by 10 does not overflow, because w.e >= -60 // and thus one.e >= -60. - if (assertEnabled()) { - assertThat(one.e() >= -60); - assertThat(ulongLT(fractionals, one.f())); - assertThat(ulongGE(uDivide(0xFFFF_FFFF_FFFF_FFFFL, 10L), one.f())); - } + assert one.e() >= -60; + assert ulongLT(fractionals, one.f()); + assert ulongGE(uDivide(0xFFFF_FFFF_FFFF_FFFFL, 10L), one.f()); while (requestedDigits > 0 && ulongGT(fractionals, wError)) { fractionals *= 10L; wError *= 10L; @@ -276,12 +272,8 @@ private static boolean grisu3Counted(double v, int requestedDigits, DecimalRepBu ten_mk = inTenMk[0]; mk = inMk[0]; } - if (assertEnabled()) { - assertThat((MINIMAL_TARGET_EXPONENT <= w.e() + ten_mk.e() + - DiyFp.SIGNIFICAND_SIZE) && - (MAXIMAL_TARGET_EXPONENT >= w.e() + ten_mk.e() + - DiyFp.SIGNIFICAND_SIZE)); - } + assert MINIMAL_TARGET_EXPONENT <= w.e() + ten_mk.e() + DiyFp.SIGNIFICAND_SIZE; + assert MAXIMAL_TARGET_EXPONENT >= w.e() + ten_mk.e() + DiyFp.SIGNIFICAND_SIZE; // Note that ten_mk is only an approximation of 10^-k. A DiyFp only contains a // 64 bit significand and ten_mk is thus only precise up to 64 bits. @@ -308,10 +300,10 @@ private static boolean grisu3Counted(double v, int requestedDigits, DecimalRepBu /** * Provides a decimal representation of v. * The result should be interpreted as buffer * 10^(point - outLength). - * + *

    * Precondition: * * v must be a strictly positive finite double. - * + *

    * Returns true if it succeeds, otherwise the result can not be trusted. * If the function returns true and mode equals * - FAST_DTOA_PRECISION, then @@ -323,10 +315,8 @@ private static boolean grisu3Counted(double v, int requestedDigits, DecimalRepBu * For both modes the buffer must be large enough to hold the result. */ public static boolean fastDtoa(double v, int requestedDigits, DecimalRepBuf buf) { - if (assertEnabled()) { - assertThat(v > 0.0); - assertThat(!new Ieee.Double(v).isSpecial()); - } + assert v > 0.0; + assert !new Ieee.Double(v).isSpecial(); boolean result; int[] decimalExponent = new int[1]; // initialized to 0 diff --git a/src/main/java/org/squiddev/cobalt/lib/doubles/FixedDtoa.java b/src/main/java/org/squiddev/cobalt/lib/doubles/FixedDtoa.java index 09cc17eb..2c00c283 100644 --- a/src/main/java/org/squiddev/cobalt/lib/doubles/FixedDtoa.java +++ b/src/main/java/org/squiddev/cobalt/lib/doubles/FixedDtoa.java @@ -34,11 +34,9 @@ import org.checkerframework.checker.signedness.qual.SignedPositive; import org.checkerframework.checker.signedness.qual.Unsigned; -import static org.squiddev.cobalt.lib.doubles.Assert.assertEnabled; -import static org.squiddev.cobalt.lib.doubles.Assert.assertThat; import static org.squiddev.cobalt.lib.doubles.UnsignedValues.*; -public class FixedDtoa { +final class FixedDtoa { private static final int DOUBLE_SIGNIFICAND_SIZE = Ieee.Double.SIGNIFICAND_SIZE; // Includes the hidden bit. private static final @Unsigned long TEN_POW_OF_7 = 10000000L; @@ -85,13 +83,13 @@ public UInt128 times(@Unsigned long multiplicand) { accumulator >>>= 32L; accumulator = accumulator + (high >>> 32) * multiplicand; long newHighBits = (accumulator << 32) + part; - if (assertEnabled()) assertThat((accumulator >>> 32) == 0L); + assert (accumulator >>> 32) == 0L; return new UInt128(newHighBits, newLowBits); } public UInt128 shift(int shift_amount) { - if (assertEnabled()) assertThat(-64 <= shift_amount && shift_amount <= 64); + assert -64 <= shift_amount && shift_amount <= 64; long nHigh, nLow; if (shift_amount == 0) { return this; @@ -225,13 +223,13 @@ private static void fillDigits64(@Unsigned long number, DecimalRepBuf buf) { // rounding-up will change the contents of the buffer to "20000". private static void fillFractionals(@Unsigned long fractionals, int exponent, int fractionalCount, DecimalRepBuf buf) { - if (assertEnabled()) assertThat(-128 <= exponent && exponent <= 0); + assert -128 <= exponent && exponent <= 0; // 'fractionals' is a fixed-point number, with binary point at bit // (-exponent). Inside the function the non-converted remainder of fractionals // is a fixed-point number, with binary point at bit 'point'. if (-exponent <= 64) { // One 64 bit number is sufficient. - if (assertEnabled()) assertThat(fractionals >>> 56 == 0L); + assert fractionals >>> 56 == 0L; int point = -exponent; for (int i = 0; i < fractionalCount; ++i) { if (fractionals == 0L) break; @@ -252,12 +250,12 @@ private static void fillFractionals(@Unsigned long fractionals, int exponent, fractionals = fractionals - (toUlong(digit) << point); } // If the first bit after the point is set we have to round up. - if (assertEnabled()) assertThat(fractionals == 0L || point - 1 >= 0); + assert fractionals == 0L || point - 1 >= 0; if (fractionals != 0L && ((fractionals >>> (point - 1)) & 1L) == 1L) { buf.roundUp(); } } else { // We need 128 bits. - if (assertEnabled()) assertThat(64 < -exponent && -exponent <= 128); + assert 64 < -exponent && -exponent <= 128; UInt128 fractionals128 = new UInt128(fractionals, 0L); fractionals128 = fractionals128.shift(-exponent - 64); int point = 128; @@ -370,7 +368,7 @@ public static boolean fastFixedDtoa(double v, int fractionalCount, DecimalRepBuf } else if (exponent < -128) { // This configuration (with at most 20 digits) means that all digits must be // 0. - if (assertEnabled()) assertThat(fractionalCount <= 20); + assert fractionalCount <= 20; buf.clearBuf(); buf.setPointPosition(-fractionalCount); } else { diff --git a/src/main/java/org/squiddev/cobalt/lib/doubles/Ieee.java b/src/main/java/org/squiddev/cobalt/lib/doubles/Ieee.java index 596c730f..f6d5e3b4 100644 --- a/src/main/java/org/squiddev/cobalt/lib/doubles/Ieee.java +++ b/src/main/java/org/squiddev/cobalt/lib/doubles/Ieee.java @@ -38,8 +38,7 @@ import static org.squiddev.cobalt.lib.doubles.UnsignedValues.toUlong; import static org.squiddev.cobalt.lib.doubles.UnsignedValues.ulongGT; -public final class Ieee { - +final class Ieee { public static class Double { public static final @Unsigned long SIGN_MASK = 0x8000000000000000L; public static final @Unsigned long EXPONENT_MASK = 0x7FF0000000000000L; @@ -83,7 +82,7 @@ public DiyFp asDiyFp() { requireState(sign() > 0, "instance must be positive"); requireState(!isSpecial(), "must not be special"); return new DiyFp(significand(), - exponent()); + exponent()); } // The value encoded by this Double must be strictly greater than 0. @@ -176,7 +175,7 @@ public boolean isSpecial() { public boolean isNan() { long d64 = asUint64(); return ((d64 & EXPONENT_MASK) == EXPONENT_MASK) && - ((d64 & SIGNIFICAND_MASK) != 0L); + ((d64 & SIGNIFICAND_MASK) != 0L); } public boolean isQuietNan() { @@ -191,7 +190,7 @@ public boolean isSignalingNan() { public boolean isInfinite() { long d64 = asUint64(); return ((d64 & EXPONENT_MASK) == EXPONENT_MASK) && - ((d64 & SIGNIFICAND_MASK) == 0L); + ((d64 & SIGNIFICAND_MASK) == 0L); } public int sign() { @@ -206,7 +205,7 @@ public int sign() { public DiyFp upperBoundary() { requireState(sign() > 0, "instance must be positive"); return new DiyFp((significand() * 2L) + 1L, - exponent() - 1); + exponent() - 1); } /** @@ -297,7 +296,7 @@ public static double nan() { biasedExponent = toUlong(exponent + EXPONENT_BIAS); } return (significand & SIGNIFICAND_MASK) | - (biasedExponent << PHYSICAL_SIGNIFICAND_SIZE); + (biasedExponent << PHYSICAL_SIGNIFICAND_SIZE); } } diff --git a/src/main/java/org/squiddev/cobalt/lib/doubles/PowersOfTenCache.java b/src/main/java/org/squiddev/cobalt/lib/doubles/PowersOfTenCache.java index 5807f6ab..67623dc2 100644 --- a/src/main/java/org/squiddev/cobalt/lib/doubles/PowersOfTenCache.java +++ b/src/main/java/org/squiddev/cobalt/lib/doubles/PowersOfTenCache.java @@ -32,9 +32,6 @@ import org.checkerframework.checker.signedness.qual.Unsigned; -import static org.squiddev.cobalt.lib.doubles.Assert.assertEnabled; -import static org.squiddev.cobalt.lib.doubles.Assert.assertThat; - public class PowersOfTenCache { /** * Not all powers of ten are cached. The decimal exponent of two neighboring @@ -68,11 +65,11 @@ public static void getCachedPowerForBinaryExponentRange( int foo = CACHED_POWERS_OFFSET; int index = (foo + ((int) k) - 1) / DECIMAL_EXPONENT_DISTANCE + 1; - if (assertEnabled()) assertThat(0 <= index && index < CACHED_POWERS.length); + assert 0 <= index && index < CACHED_POWERS.length; CachedPower cachedPower = CACHED_POWERS[index]; - if (assertEnabled()) assertThat(minExponent <= cachedPower.binaryExponent); + assert minExponent <= cachedPower.binaryExponent; //maxExponent; // Mark variable as used. - if (assertEnabled()) assertThat(cachedPower.binaryExponent <= maxExponent); + assert cachedPower.binaryExponent <= maxExponent; decimalExponent[0] = cachedPower.decimalExponent; power[0] = new DiyFp(cachedPower.significand, cachedPower.binaryExponent); } @@ -87,19 +84,15 @@ public static void getCachedPowerForBinaryExponentRange( void getCachedPowerForDecimalExponent(int requestedExponent, DiyFp[] power, int[] foundExponent) { - if (assertEnabled()) { - assertThat(MIN_DECIMAL_EXPONENT <= requestedExponent); - assertThat(requestedExponent < MAX_DECIMAL_EXPONENT + DECIMAL_EXPONENT_DISTANCE); - } + assert MIN_DECIMAL_EXPONENT <= requestedExponent; + assert requestedExponent < MAX_DECIMAL_EXPONENT + DECIMAL_EXPONENT_DISTANCE; int index = (requestedExponent + CACHED_POWERS_OFFSET) / DECIMAL_EXPONENT_DISTANCE; CachedPower cachedPower = CACHED_POWERS[index]; power[0] = new DiyFp(cachedPower.significand, cachedPower.binaryExponent); foundExponent[0] = cachedPower.decimalExponent; - if (assertEnabled()) { - assertThat(foundExponent[0] <= requestedExponent); - assertThat(requestedExponent < foundExponent[0] + DECIMAL_EXPONENT_DISTANCE); - } + assert foundExponent[0] <= requestedExponent; + assert requestedExponent < foundExponent[0] + DECIMAL_EXPONENT_DISTANCE; } static class CachedPower { diff --git a/src/main/java/org/squiddev/cobalt/lib/doubles/UnsignedValues.java b/src/main/java/org/squiddev/cobalt/lib/doubles/UnsignedValues.java index 7a4849dd..47c3d6d6 100644 --- a/src/main/java/org/squiddev/cobalt/lib/doubles/UnsignedValues.java +++ b/src/main/java/org/squiddev/cobalt/lib/doubles/UnsignedValues.java @@ -36,7 +36,7 @@ import java.math.BigInteger; -public final class UnsignedValues { +final class UnsignedValues { @SuppressWarnings("ImplicitNumericConversion") private static final int ASCII_ZERO = '0'; diff --git a/src/main/java/org/squiddev/cobalt/lib/jse/JseIoLib.java b/src/main/java/org/squiddev/cobalt/lib/jse/JseIoLib.java deleted file mode 100644 index 1fcbf5f4..00000000 --- a/src/main/java/org/squiddev/cobalt/lib/jse/JseIoLib.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * The MIT License (MIT) - * - * Original Source: Copyright (c) 2009-2011 Luaj.org. All rights reserved. - * Modifications: Copyright (c) 2015-2020 SquidDev - * - * 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.squiddev.cobalt.lib.jse; - -import org.squiddev.cobalt.LuaString; -import org.squiddev.cobalt.function.LibFunction; -import org.squiddev.cobalt.lib.IoLib; - -import java.io.*; - -/** - * 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. - * - * This has been implemented to match as closely as possible the behavior in the corresponding library in C. - * - * @see LibFunction - * @see JsePlatform - * @see IoLib - * @see http://www.lua.org/manual/5.1/manual.html#5.7 - */ -public class JseIoLib extends IoLib { - - public JseIoLib() { - super(); - } - - @Override - protected File wrapStandardStream(InputStream stream) throws IOException { - return new FileImpl(stream, true); - } - - @Override - protected File wrapStandardStream(OutputStream stream) throws IOException { - return new FileImpl(stream, true); - } - - @Override - 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); - } - - @Override - protected File openProgram(String prog, String mode) throws IOException { - final Process p = Runtime.getRuntime().exec(prog); - return "w".equals(mode) ? - new FileImpl(p.getOutputStream(), false) : - new FileImpl(p.getInputStream(), false); - } - - @Override - 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 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 final boolean isStandard; - - private FileImpl(RandomAccessFile file, InputStream is, OutputStream os, boolean isStandard) { - this.file = file; - this.is = is != null ? is.markSupported() ? is : new BufferedInputStream(is) : null; - this.os = os; - this.isStandard = isStandard; - } - - private FileImpl(RandomAccessFile f) { - this(f, null, null, false); - } - - private FileImpl(InputStream i, boolean isStandard) { - this(null, i, null, isStandard); - } - - private FileImpl(OutputStream o, boolean isStandard) { - this(null, null, o, isStandard); - } - - @Override - public String toString() { - return "file (" + (isClosed() ? "closed" : hashCode()) + ")"; - } - - @Override - public boolean isStandardFile() { - return isStandard; - } - - @Override - public void close() throws IOException { - closed = true; - if (file != null) { - file.close(); - } - } - - @Override - public void flush() throws IOException { - if (os != null) { - os.flush(); - } - } - - @Override - public void write(LuaString s) throws IOException { - if (os != null) { - os.write(s.bytes, s.offset, s.length); - } else if (file != null) { - file.write(s.bytes, s.offset, s.length); - } else { - throw new IOException("not implemented"); - } - if (nobuffer) { - flush(); - } - } - - @Override - public boolean isClosed() { - return closed; - } - - @Override - 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(); - } - throw new IOException("not implemented"); - } - - @Override - public void setvbuf(String mode, int size) { - nobuffer = "no".equals(mode); - } - - // get length remaining to read - @Override - public int remaining() throws IOException { - return file != null ? (int) (file.length() - file.getFilePointer()) : -1; - } - - // peek ahead one character - @Override - 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; - } - throw new IOException("not implemented"); - } - - // return char if read, -1 if eof, throw IOException on other exception - @Override - public int read() throws IOException { - if (is != null) { - return is.read(); - } else if (file != null) { - return file.read(); - } - throw new IOException("not implemented"); - } - - // return number of bytes read if positive, -1 if eof, throws IOException - @Override - 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 { - throw new IOException("not implemented"); - } - } - } -} diff --git a/src/main/java/org/squiddev/cobalt/lib/jse/JsePlatform.java b/src/main/java/org/squiddev/cobalt/lib/jse/JsePlatform.java deleted file mode 100644 index 990fdffc..00000000 --- a/src/main/java/org/squiddev/cobalt/lib/jse/JsePlatform.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * The MIT License (MIT) - * - * Original Source: Copyright (c) 2009-2011 Luaj.org. All rights reserved. - * Modifications: Copyright (c) 2015-2020 SquidDev - * - * 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.squiddev.cobalt.lib.jse; - -import org.squiddev.cobalt.LuaState; -import org.squiddev.cobalt.LuaTable; -import org.squiddev.cobalt.compiler.LuaC; -import org.squiddev.cobalt.lib.*; -import org.squiddev.cobalt.lib.platform.ResourceManipulator; - -/** - * 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(LuaState)} or debug globals using {@link #debugGlobals(LuaState)} - *

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

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

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

     {@code
    - * LoadState.load(new FileInputStream("main.lua"), "main.lua", _G ).call();
    - * } 
    - *

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

     {@code
    - * _G.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 BaseLib} for details on finding scripts using {@link ResourceManipulator}. - *

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

      - *
    • {@link BaseLib}
    • - *
    • {@link PackageLib}
    • - *
    • {@link TableLib}
    • - *
    • {@link StringLib}
    • - *
    • {@link CoroutineLib}
    • - *
    • {@link MathLib}
    • - *
    • {@link JseIoLib}
    • - *
    • {@link OsLib}
    • - *
    - * 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}. - */ -public class JsePlatform { - - /** - * Create a standard set of globals and setup a thread - * - * @param state The current lua state - * @return Table of globals initialized with the standard JSE libraries - * @see #debugGlobals(LuaState) - * @see JsePlatform - */ - public static LuaTable standardGlobals(LuaState state) { - LuaTable _G = state.getMainThread().getfenv(); - _G.load(state, new BaseLib()); - _G.load(state, new PackageLib()); - _G.load(state, new TableLib()); - _G.load(state, new StringLib()); - _G.load(state, new CoroutineLib()); - _G.load(state, new MathLib()); - _G.load(state, new JseIoLib()); - _G.load(state, new OsLib()); - _G.load(state, new Utf8Lib()); - return _G; - } - - /** - * Create standard globals including the {@link DebugLib} library. - * - * @param state The current lua state - * @return Table of globals initialized with the standard JSE and debug libraries - * @see #standardGlobals(LuaState) - * @see JsePlatform - * @see DebugLib - */ - public static LuaTable debugGlobals(LuaState state) { - LuaTable _G = standardGlobals(state); - _G.load(state, new DebugLib()); - return _G; - } -} diff --git a/src/main/java/org/squiddev/cobalt/lib/jse/JseProcess.java b/src/main/java/org/squiddev/cobalt/lib/jse/JseProcess.java deleted file mode 100644 index 36830079..00000000 --- a/src/main/java/org/squiddev/cobalt/lib/jse/JseProcess.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * The MIT License (MIT) - * - * Original Source: Copyright (c) 2009-2011 Luaj.org. All rights reserved. - * Modifications: Copyright (c) 2015-2020 SquidDev - * - * 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.squiddev.cobalt.lib.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 { - private final Process process; - private 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. - * - * @return The process' exit code - */ - public int exitValue() { - return process.exitValue(); - } - - /** - * Wait for the process to complete, and all pending output to finish. - * - * @return The exit status. - * @throws InterruptedException If the thread is interrupted - */ - 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 Thread() { - @Override - 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(); - } - } - }); - t.start(); - return t; - } -} diff --git a/src/main/java/org/squiddev/cobalt/lib/platform/AbstractResourceManipulator.java b/src/main/java/org/squiddev/cobalt/lib/platform/AbstractResourceManipulator.java deleted file mode 100644 index eaa7d665..00000000 --- a/src/main/java/org/squiddev/cobalt/lib/platform/AbstractResourceManipulator.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * The MIT License (MIT) - * - * Original Source: Copyright (c) 2009-2011 Luaj.org. All rights reserved. - * Modifications: Copyright (c) 2015-2020 SquidDev - * - * 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.squiddev.cobalt.lib.platform; - -import java.io.IOException; - -/** - * A resource manipulator where nothing is implemented - */ -public abstract class AbstractResourceManipulator implements ResourceManipulator { - public int latest; - - @Override - public int execute(String command) { - return 0; - } - - @Override - public void rename(String from, String to) throws IOException { - throw new IOException("not implemented"); - } - - @Override - public void remove(String file) throws IOException { - throw new IOException("not implemented"); - } - - @Override - public String tmpName() throws IOException { - return TMP_PREFIX + (latest++) + TMP_SUFFIX; - } -} diff --git a/src/main/java/org/squiddev/cobalt/lib/platform/FileResourceManipulator.java b/src/main/java/org/squiddev/cobalt/lib/platform/FileResourceManipulator.java deleted file mode 100644 index 2afa7984..00000000 --- a/src/main/java/org/squiddev/cobalt/lib/platform/FileResourceManipulator.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * The MIT License (MIT) - * - * Original Source: Copyright (c) 2009-2011 Luaj.org. All rights reserved. - * Modifications: Copyright (c) 2015-2020 SquidDev - * - * 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.squiddev.cobalt.lib.platform; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * A resource manipulator that accesses the file system - */ -public class FileResourceManipulator implements ResourceManipulator { - @Override - public InputStream findResource(String filename) { - File f = new File(filename); - if (!f.exists()) { - Class c = getClass(); - return c.getResourceAsStream(filename.startsWith("/") ? filename : "/" + filename); - } - - try { - return new FileInputStream(f); - } catch (IOException ioe) { - return null; - } - } - - @Override - public int execute(String command) { - Runtime r = Runtime.getRuntime(); - try { - final Process p = r.exec(command); - try { - p.waitFor(); - return p.exitValue(); - } finally { - p.destroy(); - } - } catch (IOException ioe) { - return EXEC_IOEXCEPTION; - } catch (InterruptedException e) { - return EXEC_INTERRUPTED; - } catch (Throwable t) { - return EXEC_ERROR; - } - } - - @Override - public void rename(String from, String to) throws IOException { - File f = new File(from); - if (!f.exists()) { - throw new IOException("No such file or directory"); - } - if (!f.renameTo(new File(to))) { - throw new IOException("Failed to rename"); - } - } - - @Override - public void remove(String file) throws IOException { - File f = new File(file); - if (!f.exists()) { - throw new IOException("No such file or directory"); - } - if (!f.delete()) { - throw new IOException("Failed to delete"); - } - } - - @Override - public String tmpName() throws IOException { - File f = File.createTempFile(TMP_PREFIX, TMP_SUFFIX); - f.deleteOnExit(); - return f.getName(); - } -} diff --git a/src/main/java/org/squiddev/cobalt/lib/platform/ResourceManipulator.java b/src/main/java/org/squiddev/cobalt/lib/platform/ResourceManipulator.java deleted file mode 100644 index 2c312972..00000000 --- a/src/main/java/org/squiddev/cobalt/lib/platform/ResourceManipulator.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * The MIT License (MIT) - * - * Original Source: Copyright (c) 2009-2011 Luaj.org. All rights reserved. - * Modifications: Copyright (c) 2015-2020 SquidDev - * - * 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.squiddev.cobalt.lib.platform; - -import org.squiddev.cobalt.lib.BaseLib; - -import java.io.IOException; -import java.io.InputStream; - -/** - * Interface for manipulating files and resources - */ -public interface ResourceManipulator { - /** - * Prefix for temporary file names - */ - String TMP_PREFIX = ".luaj"; - - /** - * Suffix for temporary file names - */ - String TMP_SUFFIX = "tmp"; - - /** - * return code indicating the execute() threw an I/O exception - */ - int EXEC_IOEXCEPTION = 1; - - /** - * return code indicating the execute() was interrupted - */ - int EXEC_INTERRUPTED = -2; - - /** - * return code indicating the execute() threw an unknown exception - */ - int EXEC_ERROR = -3; - - /** - * Try to open a file, or return null if not found. - * - * @param filename Filename to open - * @return InputStream, or null if not found. - * @see BaseLib - */ - InputStream findResource(String filename); - - /** - * 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 - * @return The command's exit code - */ - int execute(String command); - - /** - * Renames file or directory named oldname to newname. - * If this function fails,it throws and IOException - * - * @param from old file name - * @param to new file name - * @throws IOException if it fails - */ - void rename(String from, String to) throws IOException; - - /** - * 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 file The filename to delete - * @throws IOException if it fails - */ - void remove(String file) throws IOException; - - /** - * 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 - * @throws IOException If the file name cannot be generated - */ - String tmpName() throws IOException; -} diff --git a/src/main/java/org/squiddev/cobalt/lib/IoLib.java b/src/main/java/org/squiddev/cobalt/lib/system/IoLib.java similarity index 63% rename from src/main/java/org/squiddev/cobalt/lib/IoLib.java rename to src/main/java/org/squiddev/cobalt/lib/system/IoLib.java index 3aa422de..502cc906 100644 --- a/src/main/java/org/squiddev/cobalt/lib/IoLib.java +++ b/src/main/java/org/squiddev/cobalt/lib/system/IoLib.java @@ -22,77 +22,156 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package org.squiddev.cobalt.lib; +package org.squiddev.cobalt.lib.system; import org.squiddev.cobalt.*; import org.squiddev.cobalt.function.LibFunction; import org.squiddev.cobalt.function.RegisteredFunction; import org.squiddev.cobalt.function.VarArgFunction; -import org.squiddev.cobalt.lib.jse.JseIoLib; -import org.squiddev.cobalt.lib.jse.JsePlatform; +import org.squiddev.cobalt.lib.CoreLibraries; import java.io.*; +import java.nio.file.Files; import static org.squiddev.cobalt.Constants.*; import static org.squiddev.cobalt.ValueFactory.valueOf; import static org.squiddev.cobalt.ValueFactory.varargsOf; /** - * Abstract base class extending {@link LibFunction} which implements the - * core of the lua standard {@code io} library. + * 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 JseIoLib} for the JSE 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. - *

    - * This has been implemented to match as closely as possible the behavior in the corresponding library in C. + * While this has been implemented to match as closely as possible the behavior in the corresponding library in C, it + * is mostly intended * * @see LibFunction - * @see JsePlatform - * @see JseIoLib + * @see CoreLibraries * @see http://www.lua.org/manual/5.1/manual.html#5.7 */ -public abstract class IoLib implements LuaLibrary { +public class IoLib { + 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"); - protected abstract class File extends LuaValue { + private class LuaFile extends LuaValue { + protected final RandomAccessFile file; + protected final InputStream is; + protected final OutputStream os; + protected final boolean isStandard; private LuaTable metatable = fileMethods; + private boolean closed = false; + private boolean flush = false; - protected File() { + private LuaFile(RandomAccessFile file, InputStream is, OutputStream os, boolean isStandard) { super(TUSERDATA); + this.file = file; + this.is = is != null ? is.markSupported() ? is : new BufferedInputStream(is) : null; + this.os = os; + this.isStandard = isStandard; } - public abstract void write(LuaString string) throws IOException; + LuaFile(RandomAccessFile f) { + this(f, null, null, false); + } - public abstract void flush() throws IOException; + LuaFile(InputStream i, boolean isStandard) { + this(null, i, null, isStandard); + } - public abstract boolean isStandardFile(); + LuaFile(OutputStream o, boolean isStandard) { + this(null, null, o, isStandard); + } - public abstract void close() throws IOException; + public void write(LuaString s) throws IOException { + if (os != null) { + os.write(s.bytes, s.offset, s.length); + } else if (file != null) { + file.write(s.bytes, s.offset, s.length); + } else { + throw new IOException("not implemented"); + } - public abstract boolean isClosed(); + if (flush) flush(); + } - // returns new position - public abstract int seek(String option, int bytecount) throws IOException; + public void flush() throws IOException { + if (os != null) os.flush(); + } - public abstract void setvbuf(String mode, int size); + public boolean isStandardFile() { + return isStandard; + } + + public void close() throws IOException { + closed = true; + if (file != null) file.close(); + } + + 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(); + } + throw new IOException("not implemented"); + } + + public void setvbuf(String mode, int size) { + flush = "no".equals(mode); + } // get length remaining to read - public abstract int remaining() throws IOException; + public int remaining() throws IOException { + return file != null ? (int) (file.length() - file.getFilePointer()) : -1; + } // peek ahead one character - public abstract int peek() throws IOException; + 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; + } + throw new IOException("not implemented"); + } // return char if read, -1 if eof, throw IOException on other exception - public abstract int read() throws IOException; + public int read() throws IOException { + if (is != null) { + return is.read(); + } else if (file != null) { + return file.read(); + } + throw new IOException("not implemented"); + } - // return number of bytes read if positive, false if eof, throw IOException on other exception - public abstract int read(byte[] bytes, int offset, int length) throws IOException; + // 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 { + throw new IOException("not implemented"); + } + } @Override public LuaTable getMetatable(LuaState state) { @@ -111,74 +190,27 @@ public String toString() { } } + protected LuaFile openProgram(String prog, String mode) throws IOException { + final Process p = Runtime.getRuntime().exec(prog); + return "w".equals(mode) ? + new LuaFile(p.getOutputStream(), false) : + new LuaFile(p.getInputStream(), false); + } - /** - * Wrap the standard input. - * - * @param stream The stream to wrap - * @return File - * @throws IOException On stream exception - */ - protected abstract File wrapStandardStream(InputStream stream) throws IOException; - - /** - * Wrap the standard output. - * - * @param stream The stream to wrap - * @return File - * @throws IOException On stream exception - */ - protected abstract File wrapStandardStream(OutputStream stream) throws IOException; - - /** - * Open a file in a particular mode. - * - * @param filename Filename to open - * @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 - */ - protected abstract 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 - */ - protected abstract 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 - */ - protected abstract 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 final InputStream stdin; + private final PrintStream stdout; + private LuaFile inFile = null; + private LuaFile outFile = null; + private LuaFile errFile = null; private LuaTable fileMethods; - public IoLib() { + public IoLib(InputStream stdin, PrintStream stdout) { + this.stdin = stdin; + this.stdout = stdout; } - @Override - public LuaValue add(LuaState state, LuaTable env) { - + public void add(LuaState state, LuaTable env) { // io lib functions LuaTable t = RegisteredFunction.bind(new RegisteredFunction[]{ RegisteredFunction.ofV("close", this::close), @@ -196,9 +228,9 @@ public LuaValue add(LuaState state, LuaTable env) { // Setup streams try { - t.rawset(STDIN, getCurrentInput(state)); - t.rawset(STDOUT, getCurrentIn(state)); - t.rawset(STDERR, getCurrentErr(state)); + t.rawset(STDIN, getCurrentInput()); + t.rawset(STDOUT, getCurrentIn()); + t.rawset(STDERR, getCurrentErr()); } catch (LuaError e) { throw new IllegalStateException(e); } @@ -215,28 +247,25 @@ public LuaValue add(LuaState state, LuaTable env) { }); fileMethods.rawset("__index", fileMethods); - // return the table - env.rawset("io", t); - state.loadedPackages.rawset("io", t); - return t; + LibFunction.setGlobalLibrary(state, env, "io", t); } - private File getCurrentInput(LuaState state) throws LuaError { - return inFile != null ? inFile : (inFile = doOpenFile(state, "-", "r")); + private LuaFile getCurrentInput() throws LuaError { + return inFile != null ? inFile : (inFile = doOpenFile("-", "r")); } - private File getCurrentIn(LuaState state) throws LuaError { - return outFile != null ? outFile : (outFile = doOpenFile(state, "-", "w")); + private LuaFile getCurrentIn() throws LuaError { + return outFile != null ? outFile : (outFile = doOpenFile("-", "w")); } - private File getCurrentErr(LuaState state) throws LuaError { - return errFile != null ? errFile : (errFile = doOpenFile(state, "-", "w")); + private LuaFile getCurrentErr() throws LuaError { + return errFile != null ? errFile : (errFile = doOpenFile("-", "w")); } // io.flush() -> bool private Varargs flush(LuaState state, Varargs varargs) throws LuaError { try { - checkOpen(getCurrentIn(state)); + checkOpen(getCurrentIn()); outFile.flush(); return successResult(); } catch (IOException e) { @@ -245,9 +274,11 @@ private Varargs flush(LuaState state, Varargs varargs) throws LuaError { } // io.tmpfile() -> file - private Varargs tmpfile(LuaState state, Varargs varargs) throws LuaError { + private Varargs tmpfile(LuaState state, Varargs varargs) { try { - return tmpFile(); + File file = Files.createTempFile(null, "cobalt").toFile(); + file.deleteOnExit(); + return new LuaFile(new RandomAccessFile(file, "rw")); } catch (IOException e) { return errorResult(e); } @@ -257,7 +288,7 @@ private Varargs tmpfile(LuaState state, Varargs varargs) throws LuaError { private Varargs close(LuaState state, Varargs args) throws LuaError { try { LuaValue file = args.first(); - File f = file.isNil() ? getCurrentIn(state) : checkFile(file); + LuaFile f = file.isNil() ? getCurrentIn() : checkFile(file); checkOpen(f); return doClose(f); } catch (IOException e) { @@ -269,9 +300,9 @@ private Varargs close(LuaState state, Varargs args) throws LuaError { private Varargs input(LuaState state, Varargs args) throws LuaError { LuaValue file = args.first(); if (file.isNil()) { - return getCurrentInput(state); + return getCurrentInput(); } else { - return inFile = file.isString() ? doOpenFile(state, file.checkString(), "r") : checkFile(file); + return inFile = file.isString() ? doOpenFile(file.checkString(), "r") : checkFile(file); } } @@ -279,15 +310,15 @@ private Varargs input(LuaState state, Varargs args) throws LuaError { private Varargs output(LuaState state, Varargs args) throws LuaError { LuaValue filename = args.first(); if (filename.isNil()) { - return getCurrentIn(state); + return getCurrentIn(); } else { - return outFile = filename.isString() ? doOpenFile(state, filename.checkString(), "w") : checkFile(filename); + return outFile = filename.isString() ? doOpenFile(filename.checkString(), "w") : checkFile(filename); } } // io.type(obj) -> "file" | "closed file" | nil private static LuaValue type(LuaState state, LuaValue obj) { - File f = optfile(obj); + LuaFile f = optfile(obj); return f != null ? f.isClosed() ? CLOSED_FILE : FILE : NIL; @@ -309,7 +340,7 @@ private Varargs open(LuaState state, Varargs args) throws LuaError { String filename = args.arg(1).checkString(); String mode = args.arg(2).optString("r"); try { - return rawOpenFile(state, filename, mode); + return rawOpenFile(filename, mode); } catch (IOException e) { return errorResult(e); } @@ -319,11 +350,11 @@ private Varargs open(LuaState state, Varargs args) throws LuaError { private Varargs lines(LuaState state, Varargs args) throws LuaError { String filename = args.arg(1).optString(null); if (filename == null) { - File file = getCurrentInput(state); + LuaFile file = getCurrentInput(); checkOpen(file); return doLines(file, false); } else { - File file = doOpenFile(state, filename, "r"); + LuaFile file = doOpenFile(filename, "r"); checkOpen(file); return doLines(file, true); } @@ -331,7 +362,7 @@ private Varargs lines(LuaState state, Varargs args) throws LuaError { // io.read(...) -> (...) private Varargs read(LuaState state, Varargs args) throws LuaError { - checkOpen(getCurrentInput(state)); + checkOpen(getCurrentInput()); try { return doRead(inFile, args); } catch (IOException e) { @@ -341,7 +372,7 @@ private Varargs read(LuaState state, Varargs args) throws LuaError { // io.write(...) -> void private Varargs write(LuaState state, Varargs args) throws LuaError { - checkOpen(getCurrentIn(state)); + checkOpen(getCurrentIn()); try { return doWrite(outFile, args); } catch (IOException e) { @@ -370,7 +401,7 @@ private static Varargs fileFlush(LuaState state, Varargs args) throws LuaError { // file:setvbuf(mode,[size]) -> void private static Varargs fileSetvbuf(LuaState state, Varargs args) throws LuaError { - File file = checkFile(args.first()); + LuaFile file = checkFile(args.first()); String mode = args.arg(2).checkString(); int size = args.arg(3).optInteger(1024); file.setvbuf(mode, size); @@ -393,7 +424,7 @@ private static Varargs fileRead(LuaState state, Varargs args) throws LuaError { // file:seek([whence][,offset]) -> pos | nil,error private static Varargs fileSeek(LuaState state, Varargs args) throws LuaError { - File file = checkFile(args.first()); + LuaFile file = checkFile(args.first()); String whence = args.arg(2).optString("cur"); int offset = args.arg(3).optInteger(0); try { @@ -412,15 +443,15 @@ private static Varargs fileWrite(LuaState state, Varargs args) throws LuaError { } } - private File doOpenFile(LuaState state, String filename, String mode) throws LuaError { + private LuaFile doOpenFile(String filename, String mode) throws LuaError { try { - return rawOpenFile(state, filename, mode); + return rawOpenFile(filename, mode); } catch (IOException e) { throw new LuaError("io error: " + e.getMessage()); } } - private static Varargs doClose(File f) throws IOException { + private static Varargs doClose(LuaFile f) throws IOException { if (f.isStandardFile()) { return errorResult("cannot close standard file"); } else { @@ -442,7 +473,7 @@ private static Varargs errorResult(String message) { return varargsOf(NIL, valueOf(message), ZERO); } - private static Varargs doLines(final File f, final boolean autoClose) { + private static Varargs doLines(final LuaFile f, final boolean autoClose) { return new VarArgFunction() { @Override public Varargs invoke(LuaState state, Varargs args) throws LuaError { @@ -460,14 +491,14 @@ public Varargs invoke(LuaState state, Varargs args) throws LuaError { }; } - private static Varargs doWrite(File f, Varargs args) throws IOException, LuaError { + private static Varargs doWrite(LuaFile f, Varargs args) throws IOException, LuaError { for (int i = 1, n = args.count(); i <= n; i++) { f.write(args.arg(i).checkLuaString()); } return TRUE; } - private static Varargs doRead(File f, Varargs args) throws IOException, LuaError { + private static Varargs doRead(LuaFile f, Varargs args) throws IOException, LuaError { int i, n = args.count(); if (n == 0) { return readLine(f); @@ -508,38 +539,42 @@ private static Varargs doRead(File f, Varargs args) throws IOException, LuaError return i == 0 ? NIL : varargsOf(v, 0, i); } - private static File checkFile(LuaValue val) throws LuaError { - File f = optfile(val); + private static LuaFile checkFile(LuaValue val) throws LuaError { + LuaFile f = optfile(val); if (f == null) throw ErrorFactory.argError(1, "file"); checkOpen(f); return f; } - private static File optfile(LuaValue val) { - return (val instanceof File) ? (File) val : null; + private static LuaFile optfile(LuaValue val) { + return (val instanceof LuaFile) ? (LuaFile) val : null; } - private static void checkOpen(File file) throws LuaError { + private static void checkOpen(LuaFile file) throws LuaError { if (file.isClosed()) throw new LuaError("attempt to use a closed file"); } - private File rawOpenFile(LuaState state, String filename, String mode) throws IOException { + private LuaFile rawOpenFile(String filename, String mode) throws IOException { boolean isStdFile = "-".equals(filename); boolean isRead = mode.startsWith("r"); - if (isStdFile) { - return isRead ? - wrapStandardStream(state.stdin) : - wrapStandardStream(state.stdout); - } + if (isStdFile) return isRead ? new LuaFile(stdin, true) : new LuaFile(stdout, true); + boolean isAppend = mode.startsWith("a"); boolean isUpdate = mode.indexOf("+") > 0; - boolean isBinary = mode.endsWith("b"); - return openFile(filename, isRead, isAppend, isUpdate, isBinary); + + RandomAccessFile f = new RandomAccessFile(filename, isRead ? "r" : "rw"); + if (isAppend) { + f.seek(f.length()); + } else { + if (!isRead && !isUpdate) f.setLength(0); + } + + return new LuaFile(f); } // ------------- file reading utilitied ------------------ - public static LuaValue readBytes(File f, int count) throws IOException { + private static LuaValue readBytes(LuaFile f, int count) throws IOException { byte[] b = new byte[count]; int r; if ((r = f.read(b, 0, b.length)) < 0) { @@ -548,7 +583,7 @@ public static LuaValue readBytes(File f, int count) throws IOException { return LuaString.valueOf(b, 0, r); } - public static LuaValue readUntil(File f, boolean lineonly) throws IOException { + private static LuaValue readUntil(LuaFile f, boolean lineonly) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); int c; try { @@ -573,21 +608,19 @@ public static LuaValue readUntil(File f, boolean lineonly) throws IOException { } catch (EOFException e) { c = -1; } - return (c < 0 && baos.size() == 0) ? - NIL : - LuaString.valueOf(baos.toByteArray()); + return c < 0 && baos.size() == 0 ? NIL : LuaString.valueOf(baos.toByteArray()); } - public static LuaValue readLine(File f) throws IOException { + public static LuaValue readLine(LuaFile f) throws IOException { return readUntil(f, true); } - public static LuaValue readAll(File f) throws IOException { + public static LuaValue readAll(LuaFile f) throws IOException { int n = f.remaining(); return n >= 0 ? readBytes(f, n) : readUntil(f, false); } - public static LuaValue readNumber(File f) throws IOException { + public static LuaValue readNumber(LuaFile f) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); readChars(f, " \t\r\n", null); readChars(f, "-+", baos); @@ -603,19 +636,13 @@ public static LuaValue readNumber(File f) throws IOException { return s.length() > 0 ? valueOf(Double.parseDouble(s)) : NIL; } - private static void readChars(File f, String chars, ByteArrayOutputStream baos) throws IOException { - int c; + private static void readChars(LuaFile f, String chars, ByteArrayOutputStream baos) throws IOException { while (true) { - c = f.peek(); - if (chars.indexOf(c) < 0) { - return; - } + int c = f.peek(); + if (chars.indexOf(c) < 0) return; + f.read(); - if (baos != null) { - baos.write(c); - } + if (baos != null) baos.write(c); } } - - } diff --git a/src/main/java/org/squiddev/cobalt/lib/OsLib.java b/src/main/java/org/squiddev/cobalt/lib/system/OsLib.java similarity index 58% rename from src/main/java/org/squiddev/cobalt/lib/OsLib.java rename to src/main/java/org/squiddev/cobalt/lib/system/OsLib.java index 7737aec6..b7fd125a 100644 --- a/src/main/java/org/squiddev/cobalt/lib/OsLib.java +++ b/src/main/java/org/squiddev/cobalt/lib/system/OsLib.java @@ -22,152 +22,196 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package org.squiddev.cobalt.lib; +package org.squiddev.cobalt.lib.system; import org.squiddev.cobalt.*; import org.squiddev.cobalt.function.LibFunction; -import org.squiddev.cobalt.function.VarArgFunction; -import org.squiddev.cobalt.lib.jse.JsePlatform; +import org.squiddev.cobalt.function.RegisteredFunction; +import org.squiddev.cobalt.lib.CoreLibraries; +import org.squiddev.cobalt.lib.FormatDesc; +import java.io.FileNotFoundException; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.Calendar; import java.util.Date; import java.util.Locale; +import static org.squiddev.cobalt.Constants.NIL; +import static org.squiddev.cobalt.Constants.ZERO; import static org.squiddev.cobalt.ErrorFactory.argError; import static org.squiddev.cobalt.ValueFactory.*; /** - * Subclass of {@link LibFunction} which implements the standard lua {@code os} library. - * - * 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. + * A Lua library which implements the standard lua {@code os} library. + *

    + * Because the nature of the {@code os} library is to encapsulate os-specific features, the behavior of these functions + * may vary from their counterparts in the C platform. * * @see LibFunction - * @see JsePlatform + * @see CoreLibraries * @see http://www.lua.org/manual/5.1/manual.html#5.8 */ -public class OsLib extends VarArgFunction implements LuaLibrary { - 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; - +public class OsLib { private static final LuaString DATE_FORMAT = valueOf("%c"); private static final LuaString DATE_TABLE = valueOf("*t"); - private static final String[] NAMES = { - "clock", - "date", - "difftime", - "execute", - "exit", - "getenv", - "remove", - "rename", - "setlocale", - "time", - "tmpname", - }; - - private final long t0 = System.currentTimeMillis(); - - @Override - public LuaValue add(LuaState state, LuaTable env) { - LuaTable t = new LuaTable(); - LibFunction.bind(t, OsLib::new, NAMES); - env.rawset("os", t); - state.loadedPackages.rawset("os", t); - return t; + private final long startTime = System.nanoTime(); + + public void add(LuaState state, LuaTable env) { + LuaTable t = RegisteredFunction.bind(new RegisteredFunction[]{ + RegisteredFunction.of("clock", this::clock), + RegisteredFunction.of("date", OsLib::date), + RegisteredFunction.of("difftime", OsLib::difftime), + RegisteredFunction.ofV("execute", OsLib::execute), + RegisteredFunction.of("exit", OsLib::exit), + RegisteredFunction.of("getenv", OsLib::getenv), + RegisteredFunction.ofV("remove", OsLib::remove), + RegisteredFunction.ofV("rename", OsLib::rename), + RegisteredFunction.of("setlocale", OsLib::setlocale), + RegisteredFunction.ofV("time", OsLib::time), + RegisteredFunction.ofV("tmpname", OsLib::tmpname), + }); + + LibFunction.setGlobalLibrary(state, env, "os", t); } - @Override - public Varargs invoke(LuaState state, Varargs args) throws LuaError { - try { - switch (opcode) { - case CLOCK: - return valueOf((System.currentTimeMillis() - t0) / 1000); - case DATE: { - LuaString format = args.arg(1).optLuaString(DATE_FORMAT); - long time = args.arg(2).optLong(time(state, null)); - - Calendar d = Calendar.getInstance(state.timezone, Locale.ROOT); - d.setTime(new Date(time * 1000)); - if (format.startsWith('!')) { - time -= timeZoneOffset(d); - d.setTime(new Date(time * 1000)); - format = format.substring(1); - } + private LuaValue clock(LuaState state) { + return valueOf((long) ((System.nanoTime() - startTime) * 1e-9)); + } - if (format.equals(DATE_TABLE)) { - LuaTable tbl = tableOf(); - tbl.rawset("year", valueOf(d.get(Calendar.YEAR))); - tbl.rawset("month", valueOf(d.get(Calendar.MONTH) + 1)); - tbl.rawset("day", valueOf(d.get(Calendar.DAY_OF_MONTH))); - tbl.rawset("hour", valueOf(d.get(Calendar.HOUR_OF_DAY))); - tbl.rawset("min", valueOf(d.get(Calendar.MINUTE))); - tbl.rawset("sec", valueOf(d.get(Calendar.SECOND))); - tbl.rawset("wday", valueOf(d.get(Calendar.DAY_OF_WEEK))); - tbl.rawset("yday", valueOf(d.get(Calendar.DAY_OF_YEAR))); - tbl.rawset("isdst", valueOf(isDaylightSavingsTime(d))); - return tbl; - } + private static LuaValue date(LuaState state, LuaValue arg1, LuaValue arg2) throws LuaError { + LuaString format = arg1.optLuaString(DATE_FORMAT); + long time = arg2.optLong(formatTime(null)); + + Calendar d = Calendar.getInstance(Locale.ROOT); + d.setTime(new Date(time * 1000)); + if (format.startsWith('!')) { + time -= timeZoneOffset(d); + d.setTime(new Date(time * 1000)); + format = format.substring(1); + } + + if (format.equals(DATE_TABLE)) { + LuaTable tbl = tableOf(); + tbl.rawset("year", valueOf(d.get(Calendar.YEAR))); + tbl.rawset("month", valueOf(d.get(Calendar.MONTH) + 1)); + tbl.rawset("day", valueOf(d.get(Calendar.DAY_OF_MONTH))); + tbl.rawset("hour", valueOf(d.get(Calendar.HOUR_OF_DAY))); + tbl.rawset("min", valueOf(d.get(Calendar.MINUTE))); + tbl.rawset("sec", valueOf(d.get(Calendar.SECOND))); + tbl.rawset("wday", valueOf(d.get(Calendar.DAY_OF_WEEK))); + tbl.rawset("yday", valueOf(d.get(Calendar.DAY_OF_YEAR))); + tbl.rawset("isdst", valueOf(isDaylightSavingsTime(d))); + return tbl; + } + + Buffer buffer = new Buffer(format.length); + formatDate(buffer, d, format); + return buffer.toLuaString(); + } + + private static LuaValue difftime(LuaState state, LuaValue arg1, LuaValue arg2) throws LuaError { + return valueOf(arg1.checkLong() - arg2.checkLong()); + } + + private static Varargs execute(LuaState state, Varargs args) throws LuaError { + return valueOf(execute(args.arg(1).optString(null))); + } - Buffer buffer = new Buffer(format.length); - date(state, buffer, d, format); - return buffer.toLuaString(); - } - case DIFFTIME: - return valueOf(args.arg(1).checkLong() - args.arg(2).checkLong()); - case EXECUTE: - return valueOf(state.resourceManipulator.execute(args.arg(1).optString(null))); - case EXIT: - System.exit(args.arg(1).optInteger(0)); - return Constants.NONE; - case GETENV: { - final String val = System.getenv(args.arg(1).checkString()); - return val != null ? valueOf(val) : Constants.NIL; - } - case REMOVE: - state.resourceManipulator.remove(args.arg(1).checkString()); - return Constants.TRUE; - case RENAME: - state.resourceManipulator.rename(args.arg(1).checkString(), args.arg(2).checkString()); - return Constants.TRUE; - case SETLOCALE: { - String locale = args.arg(1).optString(null); - args.arg(2).optString("all"); - return locale == null || locale.equals("C") ? valueOf("C") : Constants.NIL; - } - case TIME: - return valueOf(time(state, args.first().isNil() ? null : args.arg(1).checkTable())); - case TMPNAME: - return valueOf(state.resourceManipulator.tmpName()); + private static int execute(String command) { + Runtime r = Runtime.getRuntime(); + try { + final Process p = r.exec(command); + try { + p.waitFor(); + return p.exitValue(); + } finally { + p.destroy(); } - return Constants.NONE; + } catch (IOException ioe) { + return -1; + } catch (InterruptedException e) { + return -2; + } catch (Throwable t) { + return -3; + } + } + + private static LuaValue exit(LuaState state, LuaValue arg) throws LuaError { + System.exit(arg.optInteger(0)); + return Constants.NIL; + } + + private static LuaValue getenv(LuaState state, LuaValue arg) throws LuaError { + final String val = System.getenv(arg.checkString()); + return val != null ? valueOf(val) : Constants.NIL; + } + + private static Varargs remove(LuaState state, Varargs args) throws LuaError { + Path file = Paths.get(args.first().checkString()); + + try { + Files.delete(file); + return Constants.TRUE; + } catch (FileNotFoundException e) { + return errorResult("file not found"); + } catch (IOException e) { + return errorResult(e); + } + } + + private static Varargs rename(LuaState state, Varargs args) throws LuaError { + String from = args.arg(1).checkString(); + String to = args.arg(2).checkString(); + + try { + Files.move(Paths.get(from), Paths.get(to), StandardCopyOption.REPLACE_EXISTING); + return Constants.TRUE; + } catch (IOException e) { + return errorResult(e); + } + } + + private static LuaValue setlocale(LuaState state, LuaValue arg1, LuaValue arg2) throws LuaError { + String locale = arg1.optString(null); + arg2.optString("all"); + return locale == null || locale.equals("C") ? valueOf("C") : Constants.NIL; + } + + private static Varargs time(LuaState state, Varargs args) throws LuaError { + return valueOf(formatTime(args.first().isNil() ? null : args.arg(1).checkTable())); + } + + private static Varargs tmpname(LuaState state, Varargs args) { + try { + Path path = Files.createTempFile(null, "cobalt"); + path.toFile().deleteOnExit(); + return valueOf(path.toString()); } catch (IOException e) { - return varargsOf(Constants.NIL, valueOf(e.getMessage())); + return errorResult(e); } } + private static Varargs errorResult(Exception ioe) { + String s = ioe.getMessage(); + return errorResult("io error: " + (s != null ? s : ioe.toString())); + } + + private static Varargs errorResult(String message) { + return varargsOf(NIL, valueOf(message), ZERO); + } + private static final String[] WEEKDAY_NAME_ABBREV = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; private static final String[] WEEKDAY_NAME = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; private static final String[] MONTH_NAME_ABBREV = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; private static final String[] MONTH_NAME = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; - private Calendar beginningOfYear(LuaState state, Calendar d) { - Calendar y0 = Calendar.getInstance(state.timezone, Locale.ROOT); + private static Calendar beginningOfYear(Calendar d) { + Calendar y0 = Calendar.getInstance(Locale.ROOT); y0.setTime(d.getTime()); y0.set(Calendar.MONTH, 0); y0.set(Calendar.DAY_OF_MONTH, 1); @@ -178,8 +222,8 @@ private Calendar beginningOfYear(LuaState state, Calendar d) { return y0; } - private int weekNumber(LuaState state, Calendar d, int startDay) { - Calendar y0 = beginningOfYear(state, d); + private static 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); @@ -189,7 +233,7 @@ private int weekNumber(LuaState state, Calendar d, int startDay) { return 1 + (int) (dt / (7L * 24L * 3600L * 1000L)); } - private int timeZoneOffset(Calendar d) { + private static int timeZoneOffset(Calendar d) { int localStandardTimeMillis = 1000 * (d.get(Calendar.HOUR_OF_DAY) * 3600 + d.get(Calendar.MINUTE) * 60 + d.get(Calendar.SECOND)); @@ -203,7 +247,7 @@ private int timeZoneOffset(Calendar d) { ) / 1000; } - private boolean isDaylightSavingsTime(Calendar d) { + private static boolean isDaylightSavingsTime(Calendar d) { return timeZoneOffset(d) != d.getTimeZone().getRawOffset() / 1000; } @@ -218,7 +262,7 @@ private boolean isDaylightSavingsTime(Calendar d) { private static final FormatDesc ZERO_THREE = FormatDesc.ofUnsafe("03d"); private static final FormatDesc SPACE_TWO = FormatDesc.ofUnsafe("2d"); - private void date(LuaState state, Buffer result, Calendar date, LuaString format) throws LuaError { + private static void formatDate(Buffer result, Calendar date, LuaString format) throws LuaError { byte[] fmt = format.bytes; int n = format.length + format.offset; for (int i = format.offset; i < n; ) { @@ -253,7 +297,7 @@ private void date(LuaState state, Buffer result, Calendar date, LuaString format result.append(MONTH_NAME[date.get(Calendar.MONTH)]); break; case 'c': - date(state, result, date, FORMAT_C); + formatDate(result, date, FORMAT_C); break; case 'C': ZERO_TWO.format(result, (date.get(Calendar.YEAR) / 100) % 100); @@ -263,13 +307,13 @@ private void date(LuaState state, Buffer result, Calendar date, LuaString format break; case 'D': case 'x': - date(state, result, date, FORMAT_DATE_D); + formatDate(result, date, FORMAT_DATE_D); break; case 'e': SPACE_TWO.format(result, date.get(Calendar.DAY_OF_MONTH)); break; case 'F': - date(state, result, date, FORMAT_DATE_F); + formatDate(result, date, FORMAT_DATE_F); break; case 'g': ZERO_TWO.format(result, date.isWeekDateSupported() ? date.getWeekYear() % 100 : 0); @@ -299,10 +343,10 @@ private void date(LuaState state, Buffer result, Calendar date, LuaString format result.append(date.get(Calendar.HOUR_OF_DAY) < 12 ? "AM" : "PM"); break; case 'r': - date(state, result, date, FORMAT_TIME_UPPER_R); + formatDate(result, date, FORMAT_TIME_UPPER_R); break; case 'R': - date(state, result, date, FORMAT_TIME_LOWER_R); + formatDate(result, date, FORMAT_TIME_LOWER_R); break; case 'S': ZERO_TWO.format(result, date.get(Calendar.SECOND)); @@ -312,7 +356,7 @@ private void date(LuaState state, Buffer result, Calendar date, LuaString format break; case 'T': case 'X': - date(state, result, date, FORMAT_TIME_T); + formatDate(result, date, FORMAT_TIME_T); break; case 'u': { int day = date.get(Calendar.DAY_OF_WEEK); @@ -320,16 +364,16 @@ private void date(LuaState state, Buffer result, Calendar date, LuaString format break; } case 'U': - ZERO_TWO.format(result, weekNumber(state, date, 0)); + ZERO_TWO.format(result, weekNumber(date, 0)); break; case 'V': - ZERO_TWO.format(result, weekNumber(state, date, 1)); + ZERO_TWO.format(result, weekNumber(date, 1)); break; case 'w': result.append(Integer.toString((date.get(Calendar.DAY_OF_WEEK) + 6) % 7)); break; case 'W': - ZERO_TWO.format(result, weekNumber(state, date, 1)); + ZERO_TWO.format(result, weekNumber(date, 1)); break; case 'y': ZERO_TWO.format(result, date.get(Calendar.YEAR) % 100); @@ -363,10 +407,10 @@ private void date(LuaState state, Buffer result, Calendar date, LuaString format * @param table Table to use * @return long value for the time */ - private long time(LuaState state, LuaTable table) throws LuaError { + private static long formatTime(LuaTable table) throws LuaError { if (table == null) return System.currentTimeMillis() / 1000; - Calendar c = Calendar.getInstance(state.timezone, Locale.ROOT); + Calendar c = Calendar.getInstance(Locale.ROOT); c.set(Calendar.YEAR, getField(table, "year", -1)); c.set(Calendar.MONTH, getField(table, "month", -1) - 1); c.set(Calendar.DAY_OF_MONTH, getField(table, "day", -1)); diff --git a/src/main/java/org/squiddev/cobalt/lib/PackageLib.java b/src/main/java/org/squiddev/cobalt/lib/system/PackageLib.java similarity index 71% rename from src/main/java/org/squiddev/cobalt/lib/PackageLib.java rename to src/main/java/org/squiddev/cobalt/lib/system/PackageLib.java index 47916ab8..41645411 100644 --- a/src/main/java/org/squiddev/cobalt/lib/PackageLib.java +++ b/src/main/java/org/squiddev/cobalt/lib/system/PackageLib.java @@ -22,14 +22,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package org.squiddev.cobalt.lib; +package org.squiddev.cobalt.lib.system; import org.squiddev.cobalt.*; import org.squiddev.cobalt.function.LibFunction; import org.squiddev.cobalt.function.LuaFunction; -import org.squiddev.cobalt.function.OneArgFunction; -import org.squiddev.cobalt.function.VarArgFunction; +import org.squiddev.cobalt.function.RegisteredFunction; +import org.squiddev.cobalt.lib.BaseLib; import static org.squiddev.cobalt.OperationHelper.noUnwind; import static org.squiddev.cobalt.ValueFactory.*; @@ -46,7 +46,7 @@ * @see BaseLib * @see http://www.lua.org/manual/5.1/manual.html#5.3 */ -public class PackageLib implements LuaLibrary { +public class PackageLib { private static final LuaString _M = valueOf("_M"); private static final LuaString _NAME = valueOf("_NAME"); private static final LuaString _PACKAGE = valueOf("_PACKAGE"); @@ -61,111 +61,48 @@ public class PackageLib implements LuaLibrary { private static final LuaString _CPATH_DEFAULT = Constants.EMPTYSTRING; private static final LuaString _SEEALL = valueOf("seeall"); - private static final int OP_MODULE = 0; - private static final int OP_REQUIRE = 1; - private static final int OP_LOADLIB = 2; - private static final int OP_SEEALL = 3; - private static final int OP_PRELOAD_LOADER = 4; - private static final int OP_LUA_LOADER = 5; - private static final int OP_JAVA_LOADER = 6; + private static final LuaString REGISTRY_PRELOAD = valueOf("_PRELOAD"); - private LuaTable packageTbl; + private final ResourceLoader loader; private final LuaValue sentinel = userdataOf(new Object()); + private LuaTable packageTbl; - @Override - public LuaValue add(LuaState state, LuaTable env) { - env.rawset("require", new PkgLib1(env, "require", OP_REQUIRE, this)); - env.rawset("module", new PkgLibV(env, "module", OP_MODULE, this)); - env.rawset("package", packageTbl = tableOf(_LOADED, state.loadedPackages, - _PRELOAD, tableOf(), + public PackageLib(ResourceLoader loader) { + this.loader = loader; + } + + public void add(LuaState state, LuaTable env) { + env.rawset("require", RegisteredFunction.of("require", (s, a) -> OperationHelper.noUnwind(s, () -> require(s, a))).create()); + env.rawset("module", RegisteredFunction.ofV("require", (s, a) -> OperationHelper.noUnwind(s, () -> module(s, a))).create()); + + LibFunction.setGlobalLibrary(state, env, "package", packageTbl = tableOf( + _LOADED, loaded(state), + _PRELOAD, state.registry().getSubTable(REGISTRY_PRELOAD), _PATH, _PATH_DEFAULT, - _LOADLIB, new PkgLibV(env, "loadlib", OP_LOADLIB, this), - _SEEALL, new PkgLibV(env, "seeall", OP_SEEALL, this), + _LOADLIB, RegisteredFunction.ofV("loadlib", PackageLib::loadlib).create(), + _SEEALL, RegisteredFunction.ofV("seeall", PackageLib::seeall).create(), _CPATH, _CPATH_DEFAULT, _LOADERS, listOf( - new PkgLibV(env, "preload_loader", OP_PRELOAD_LOADER, this), - new PkgLibV(env, "lua_loader", OP_LUA_LOADER, this), - new PkgLibV(env, "java_loader", OP_JAVA_LOADER, this) + RegisteredFunction.ofV("preload_loader", (s, a) -> OperationHelper.noUnwind(s, () -> loader_preload(s, a))).create(), + RegisteredFunction.ofV("lua_loader", (s, a) -> OperationHelper.noUnwind(s, () -> loader_Lua(s, a))).create(), + RegisteredFunction.ofV("java_loader", (s, a) -> OperationHelper.noUnwind(s, () -> loader_Java(a))).create() ) )); - state.loadedPackages.rawset("package", packageTbl); - return env; } - static final class PkgLib1 extends OneArgFunction { - PackageLib lib; - - public PkgLib1(LuaTable env, String name, int opcode, PackageLib lib) { - setfenv(env); - this.name = name; - this.opcode = opcode; - this.lib = lib; - } - - @Override - public LuaValue call(LuaState state, LuaValue arg) throws LuaError { - switch (opcode) { - case OP_REQUIRE: - return OperationHelper.noUnwind(state, () -> lib.require(state, arg)); - } - return Constants.NIL; - } + private static LuaTable loaded(LuaState state) { + return state.registry().getSubTable(Constants.LOADED); } - static final class PkgLibV extends VarArgFunction { - PackageLib lib; - - public PkgLibV(LuaTable env, String name, int opcode, PackageLib lib) { - setfenv(env); - this.name = name; - this.opcode = opcode; - this.lib = lib; - } - - @Override - public Varargs invoke(LuaState state, Varargs args) throws LuaError { - switch (opcode) { - case OP_MODULE: - return OperationHelper.noUnwind(state, () -> lib.module(state, args)); - case OP_LOADLIB: - return loadlib(args); - case OP_SEEALL: { - LuaTable t = args.first().checkTable(); - LuaTable m = t.getMetatable(state); - if (m == null) { - t.setMetatable(state, m = ValueFactory.tableOf()); - } - LuaTable mt = m; - noUnwind(state, () -> OperationHelper.setTable(state, mt, Constants.INDEX, state.getCurrentThread().getfenv())); - return Constants.NONE; - } - case OP_PRELOAD_LOADER: { - return OperationHelper.noUnwind(state, () -> lib.loader_preload(state, args)); - } - case OP_LUA_LOADER: { - return OperationHelper.noUnwind(state, () -> lib.loader_Lua(state, args)); - } - case OP_JAVA_LOADER: { - return lib.loader_Java(args, getfenv()); - } - } - return Constants.NONE; + private static Varargs seeall(LuaState state, Varargs args) throws LuaError { + LuaTable t = args.first().checkTable(); + LuaTable m = t.getMetatable(state); + if (m == null) { + t.setMetatable(state, m = ValueFactory.tableOf()); } - } - - /** - * Allow packages to mark themselves as loaded - * - * @param state The current lua state - * @param name Name of package - * @param value Value of package - */ - public static void setIsLoaded(LuaState state, String name, LuaTable value) { - state.loadedPackages.rawset(name, value); - } - - public void setLuaPath(LuaState state, String newLuaPath) { - packageTbl.rawset(_PATH, valueOf(newLuaPath)); + LuaTable mt = m; + noUnwind(state, () -> OperationHelper.setTable(state, mt, Constants.INDEX, state.getCurrentThread().getfenv())); + return Constants.NONE; } @Override @@ -203,23 +140,23 @@ public String toString() { * @throws LuaError If there is a name conflict. */ private Varargs module(LuaState state, Varargs args) throws LuaError, UnwindThrowable { + LuaTable loaded = loaded(state); LuaString modname = args.arg(1).checkLuaString(); int n = args.count(); - LuaValue value = OperationHelper.getTable(state, state.loadedPackages, modname); + LuaValue value = loaded.rawget(modname); LuaTable module; if (!value.isTable()) { /* not found? */ /* try global variable (and create one if it does not exist) */ LuaTable globals = state.getCurrentThread().getfenv(); - module = findtable(state, globals, modname); + module = findtable(globals, modname); if (module == null) { throw new LuaError("name conflict for module '" + modname + "'"); } - OperationHelper.setTable(state, state.loadedPackages, modname, module); + loaded.rawset(modname, module); } else { module = (LuaTable) value; } - /* check whether table already has a _NAME field */ LuaValue name = OperationHelper.getTable(state, module, _NAME); if (name.isNil()) { @@ -246,12 +183,11 @@ private Varargs module(LuaState state, Varargs args) throws LuaError, UnwindThro } /** - * @param state The current lua state * @param table the table at which to start the search * @param fname the name to look up or create, such as "abc.def.ghi" * @return the table for that name, possible a new one, or null if a non-table has that name already. */ - private static LuaTable findtable(LuaState state, LuaTable table, LuaString fname) throws LuaError, UnwindThrowable { + private static LuaTable findtable(LuaTable table, LuaString fname) { int b, e = (-1); do { e = fname.indexOf(_DOT, b = e + 1); @@ -262,7 +198,7 @@ private static LuaTable findtable(LuaState state, LuaTable table, LuaString fnam LuaValue val = table.rawget(key); if (val.isNil()) { /* no such field? */ LuaTable field = new LuaTable(); /* new table for field */ - OperationHelper.setTable(state, table, key, field); + table.rawset(key, field); table = field; } else if (!val.isTable()) { /* field has a non-table value? */ return null; @@ -315,12 +251,13 @@ private static void modinit(LuaState state, LuaValue module, LuaString modname) */ LuaValue require(LuaState state, LuaValue arg) throws LuaError, UnwindThrowable { LuaString name = arg.checkLuaString(); - LuaValue loaded = OperationHelper.getTable(state, state.loadedPackages, name); - if (loaded.toBoolean()) { - if (loaded == sentinel) { + LuaTable loaded = loaded(state); + LuaValue existing = OperationHelper.getTable(state, loaded, name); + if (existing.toBoolean()) { + if (existing == sentinel) { throw new LuaError("loop or previous error loading module '" + name + "'"); } - return loaded; + return existing; } /* else must load it; iterate over available loaders */ @@ -344,32 +281,32 @@ LuaValue require(LuaState state, LuaValue arg) throws LuaError, UnwindThrowable } // load the module using the loader - OperationHelper.setTable(state, state.loadedPackages, name, sentinel); + OperationHelper.setTable(state, loaded, name, sentinel); LuaValue result = OperationHelper.call(state, chunk, name); if (!result.isNil()) { - OperationHelper.setTable(state, state.loadedPackages, name, result); - } else if ((result = OperationHelper.getTable(state, state.loadedPackages, name)) == sentinel) { + OperationHelper.setTable(state, loaded, name, result); + } else if ((result = OperationHelper.getTable(state, loaded, name)) == sentinel) { LuaValue value = result = Constants.TRUE; - OperationHelper.setTable(state, state.loadedPackages, name, value); + OperationHelper.setTable(state, loaded, name, value); } return result; } - public static Varargs loadlib(Varargs args) throws LuaError { + public static Varargs loadlib(LuaState state, Varargs args) throws LuaError { args.arg(1).checkLuaString(); return varargsOf(Constants.NIL, valueOf("dynamic libraries not enabled"), valueOf("absent")); } - LuaValue loader_preload(LuaState state, Varargs args) throws LuaError, UnwindThrowable { + private LuaValue loader_preload(LuaState state, Varargs args) throws LuaError, UnwindThrowable { LuaString name = args.arg(1).checkLuaString(); - LuaValue preload = OperationHelper.getTable(state, packageTbl, _PRELOAD).checkTable(); + LuaValue preload = state.registry().getSubTable(REGISTRY_PRELOAD); LuaValue val = OperationHelper.getTable(state, preload, name); return val.isNil() ? valueOf("\n\tno field package.preload['" + name + "']") : val; } - LuaValue loader_Lua(LuaState state, Varargs args) throws LuaError, UnwindThrowable { + private LuaValue loader_Lua(LuaState state, Varargs args) throws LuaError, UnwindThrowable { String name = args.arg(1).checkString(); // get package path @@ -382,7 +319,7 @@ LuaValue loader_Lua(LuaState state, Varargs args) throws LuaError, UnwindThrowab // check the path elements int e = -1; int n = path.length(); - StringBuffer sb = null; + StringBuilder sb = null; name = name.replace('.', '/'); while (e < n) { @@ -398,28 +335,26 @@ LuaValue loader_Lua(LuaState state, Varargs args) throws LuaError, UnwindThrowab String filename = template.replace("?", name); // try loading the file - Varargs v = BaseLib.loadFile(state, filename); + Varargs v = SystemBaseLib.loadFile(state, loader, filename); if (v.first().isFunction()) { return v.first(); } // report error if (sb == null) { - sb = new StringBuffer(); + sb = new StringBuilder(); } sb.append("\n\t'").append(filename).append("': ").append(v.arg(2)); } return valueOf(sb.toString()); } - private LuaValue loader_Java(Varargs args, LuaTable env) throws LuaError { + private LuaValue loader_Java(Varargs args) throws LuaError { String name = args.arg(1).checkString(); String classname = toClassname(name); try { Class c = Class.forName(classname); - LuaValue v = (LuaValue) c.newInstance(); - v.setfenv(env); - return v; + return (LuaValue) c.newInstance(); } catch (ClassNotFoundException cnfe) { return valueOf("\n\tno class '" + classname + "'"); } catch (Exception e) { diff --git a/src/main/java/org/squiddev/cobalt/lib/platform/VoidResourceManipulator.java b/src/main/java/org/squiddev/cobalt/lib/system/ResourceLoader.java similarity index 65% rename from src/main/java/org/squiddev/cobalt/lib/platform/VoidResourceManipulator.java rename to src/main/java/org/squiddev/cobalt/lib/system/ResourceLoader.java index e71574b8..ceceba28 100644 --- a/src/main/java/org/squiddev/cobalt/lib/platform/VoidResourceManipulator.java +++ b/src/main/java/org/squiddev/cobalt/lib/system/ResourceLoader.java @@ -22,37 +22,38 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package org.squiddev.cobalt.lib.platform; +package org.squiddev.cobalt.lib.system; +import org.squiddev.cobalt.lib.BaseLib; + +import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; /** - * A resource manipulator which errors on any action. + * Interface for manipulating files and resources */ -public class VoidResourceManipulator implements ResourceManipulator { - @Override - public InputStream findResource(String filename) { - return null; - } - - @Override - public int execute(String command) { - return 1; - } - - @Override - public void rename(String from, String to) throws IOException { - throw new IOException("file could not be renamed"); - } +public interface ResourceLoader { + /** + * Try to open a file, or return null if not found. + * + * @param filename Filename to open + * @return InputStream, or null if not found. + * @see BaseLib + */ + InputStream load(String filename); - @Override - public void remove(String file) throws IOException { - throw new IOException("file could not be removed"); - } + /** + * A resource loader that reads from the filesystem. + */ + ResourceLoader FILES = filename -> { + File f = new File(filename); - @Override - public String tmpName() throws IOException { - throw new IOException("cannot create temporary file"); - } + try { + return Files.newInputStream(f.toPath()); + } catch (IOException ioe) { + return null; + } + }; } diff --git a/src/main/java/org/squiddev/cobalt/lib/system/SystemBaseLib.java b/src/main/java/org/squiddev/cobalt/lib/system/SystemBaseLib.java new file mode 100644 index 00000000..24f8f3df --- /dev/null +++ b/src/main/java/org/squiddev/cobalt/lib/system/SystemBaseLib.java @@ -0,0 +1,115 @@ +package org.squiddev.cobalt.lib.system; + +import org.squiddev.cobalt.*; +import org.squiddev.cobalt.function.RegisteredFunction; +import org.squiddev.cobalt.lib.BaseLib; + +import java.io.InputStream; +import java.io.PrintStream; + +import static org.squiddev.cobalt.OperationHelper.noUnwind; +import static org.squiddev.cobalt.ValueFactory.valueOf; +import static org.squiddev.cobalt.ValueFactory.varargsOf; + +/** + * Adds additional globals to the base library that interact with the running system, and so may not be safe to use in + * a sandboxed environment. + */ +public class SystemBaseLib { + private static final LuaString STDIN_STR = valueOf("=stdin"); + + private final ResourceLoader resources; + private final InputStream in; + private final PrintStream out; + + public SystemBaseLib(ResourceLoader resources, InputStream in, PrintStream out) { + this.resources = resources; + this.in = in; + this.out = out; + } + + public void add(LuaTable env) { + RegisteredFunction.bind(env, new RegisteredFunction[]{ + RegisteredFunction.of("collectgarbage", SystemBaseLib::collectgarbage), + RegisteredFunction.ofV("loadfile", this::loadfile), + RegisteredFunction.ofV("dofile", this::dofile), + RegisteredFunction.ofV("print", this::print), + }); + } + + private static LuaValue collectgarbage(LuaState state, LuaValue arg1, LuaValue arg2) throws LuaError { + // collectgarbage( opt [,arg] ) -> value + String s = arg1.optString("collect"); + switch (s) { + case "collect": + System.gc(); + return Constants.ZERO; + case "count": + Runtime rt = Runtime.getRuntime(); + long used = rt.totalMemory() - rt.freeMemory(); + return valueOf(used / 1024.); + case "step": + System.gc(); + return Constants.TRUE; + default: + throw ErrorFactory.argError(1, "invalid option"); + } + } + + private Varargs loadfile(LuaState state, Varargs args) throws LuaError { + // loadfile( [filename] ) -> chunk | nil, msg + return args.isNil(1) ? + BaseLib.loadStream(state, in, STDIN_STR) : + SystemBaseLib.loadFile(state, resources, args.arg(1).checkString()); + } + + private Varargs dofile(LuaState state, Varargs args) throws LuaError, UnwindThrowable { + // dofile( filename ) -> result1, ... + Varargs v = args.isNil(1) ? + BaseLib.loadStream(state, in, STDIN_STR) : + SystemBaseLib.loadFile(state, resources, args.arg(1).checkString()); + if (v.isNil(1)) { + throw new LuaError(v.arg(2).toString()); + } else { + return OperationHelper.invoke(state, v.first(), Constants.NONE); + } + } + + private Varargs print(LuaState state, Varargs args) throws LuaError { + // print(...) -> void + return noUnwind(state, () -> { + LuaValue tostring = OperationHelper.getTable(state, state.getCurrentThread().getfenv(), valueOf("tostring")); + for (int i = 1, n = args.count(); i <= n; i++) { + if (i > 1) out.write('\t'); + LuaString s = OperationHelper.call(state, tostring, args.arg(i)).strvalue(); + int z = s.indexOf((byte) 0, 0); + out.write(s.bytes, s.offset, z >= 0 ? z : s.length); + } + out.println(); + return Constants.NONE; + }); + } + + /** + * Load from a named file, returning the chunk or nil,error of can't load + * + * @param state The current lua state + * @param filename Name of the file + * @return Varargs containing chunk, or NIL,error-text on error + */ + public static Varargs loadFile(LuaState state, ResourceLoader resources, String filename) { + InputStream is = resources.load(filename); + if (is == null) { + return varargsOf(Constants.NIL, valueOf("cannot open " + filename + ": No such file or directory")); + } + try { + return BaseLib.loadStream(state, is, valueOf("@" + filename)); + } finally { + try { + is.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } +} diff --git a/src/main/java/org/squiddev/cobalt/lib/system/SystemLibraries.java b/src/main/java/org/squiddev/cobalt/lib/system/SystemLibraries.java new file mode 100644 index 00000000..586bc57c --- /dev/null +++ b/src/main/java/org/squiddev/cobalt/lib/system/SystemLibraries.java @@ -0,0 +1,62 @@ +package org.squiddev.cobalt.lib.system; + +import org.squiddev.cobalt.LuaState; +import org.squiddev.cobalt.LuaTable; +import org.squiddev.cobalt.lib.CoreLibraries; +import org.squiddev.cobalt.lib.DebugLib; + +import java.io.InputStream; +import java.io.PrintStream; + +public final class SystemLibraries { + private SystemLibraries() { + } + + /** + * Create a standard set of globals. + * + * @param state The current lua state + * @return Table of globals initialized with the standard JSE libraries + */ + public static LuaTable standardGlobals(LuaState state) { + return standardGlobals(state, ResourceLoader.FILES, System.in, System.out); + } + + /** + * Create a standard set of globals. + * + * @param state The current lua state + * @return Table of globals initialized with the standard JSE libraries + */ + public static LuaTable standardGlobals(LuaState state, ResourceLoader resources, InputStream stdin, PrintStream stdout) { + LuaTable globals = CoreLibraries.standardGlobals(state); + new SystemBaseLib(resources, stdin, stdout).add(globals); + new PackageLib(resources).add(state, globals); + new IoLib(stdin, stdout).add(state, globals); + new OsLib().add(state, globals); + return globals; + } + + /** + * Create a standard set of globals. + * + * @param state The current lua state + * @return Table of globals initialized with the standard JSE libraries + */ + public static LuaTable debugGlobals(LuaState state) { + return debugGlobals(state, ResourceLoader.FILES, System.in, System.out); + } + + /** + * Create standard globals including the {@link DebugLib} library. + * + * @param state The current lua state + * @return Table of globals initialized with the standard JSE and debug libraries + * @see DebugLib + */ + public static LuaTable debugGlobals(LuaState state, ResourceLoader loader, InputStream stdin, PrintStream stdout) { + LuaTable globals = standardGlobals(state, loader, stdin, stdout); + DebugLib.add(state, globals); + return globals; + } +} diff --git a/src/test/java/org/squiddev/cobalt/AssertTests.java b/src/test/java/org/squiddev/cobalt/AssertTests.java index 6e2f4880..f7b957ed 100644 --- a/src/test/java/org/squiddev/cobalt/AssertTests.java +++ b/src/test/java/org/squiddev/cobalt/AssertTests.java @@ -136,7 +136,7 @@ public LuaValue call(LuaState state) { public void lua52(String name) throws Exception { ScriptHelper helpers = new ScriptHelper("/assert/lua5.2/"); helpers.setup(); - helpers.globals.load(helpers.state, new Bit32Lib()); + Bit32Lib.add(helpers.state, helpers.globals); helpers.runWithDump(name); } @@ -151,8 +151,8 @@ public void lua52(String name) throws Exception { public void lua53(String name) throws Exception { ScriptHelper helpers = new ScriptHelper("/assert/lua5.3/"); helpers.setup(); - helpers.globals.load(helpers.state, new Bit32Lib()); - helpers.globals.load(helpers.state, new Utf8Lib()); + Bit32Lib.add(helpers.state, helpers.globals); + new Utf8Lib().add(helpers.state, helpers.globals); helpers.runWithDump(name); } diff --git a/src/test/java/org/squiddev/cobalt/CompareTest.java b/src/test/java/org/squiddev/cobalt/CompareTest.java index 7dc2ae46..ae8f1037 100644 --- a/src/test/java/org/squiddev/cobalt/CompareTest.java +++ b/src/test/java/org/squiddev/cobalt/CompareTest.java @@ -24,7 +24,6 @@ */ package org.squiddev.cobalt; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -32,7 +31,7 @@ public class CompareTest { /** * Test argument type check errors - * + *

    * Results are compared for exact match with * the installed C-based lua environment. */ @@ -48,7 +47,7 @@ public void errors(String name) throws Exception { /** * Compatibility tests for the LuaJ VM - * + *

    * Results are compared for exact match with * the installed C-based lua environment. */ diff --git a/src/test/java/org/squiddev/cobalt/CoroutineTest.java b/src/test/java/org/squiddev/cobalt/CoroutineTest.java index e848faf8..44f36d52 100644 --- a/src/test/java/org/squiddev/cobalt/CoroutineTest.java +++ b/src/test/java/org/squiddev/cobalt/CoroutineTest.java @@ -34,9 +34,9 @@ import org.squiddev.cobalt.debug.DebugHelpers; import org.squiddev.cobalt.debug.DebugState; import org.squiddev.cobalt.function.LuaFunction; +import org.squiddev.cobalt.function.RegisteredFunction; import org.squiddev.cobalt.function.ResumableVarArgFunction; import org.squiddev.cobalt.function.VarArgFunction; -import org.squiddev.cobalt.lib.LuaLibrary; import java.io.IOException; @@ -63,7 +63,14 @@ public static String[] getTests() { public void setup() { helpers = new ScriptHelper("/coroutine/"); helpers.setup(); - helpers.globals.load(helpers.state, new Functions()); + RegisteredFunction.bind(helpers.globals, new RegisteredFunction[]{ + RegisteredFunction.ofFactory("suspend", Suspend::new), + RegisteredFunction.ofFactory("run", Run::new), + RegisteredFunction.ofV("assertEquals", CoroutineTest::assertEquals$), + RegisteredFunction.ofV("fail", CoroutineTest::fail$), + RegisteredFunction.ofV("id", CoroutineTest::id), + RegisteredFunction.ofV("noUnwind", CoroutineTest::noUnwind$), + }); } private void setBlockingYield() { @@ -127,59 +134,56 @@ public void runSuspendBlocking(String name) throws IOException, CompileException assertEquals("dead", helpers.state.getMainThread().getStatus()); } - private static class Functions extends ResumableVarArgFunction implements LuaLibrary { + private static class Suspend extends ResumableVarArgFunction { @Override - public LuaValue add(LuaState state, LuaTable environment) { - bind(environment, Functions::new, new String[]{"suspend", "run", "assertEquals", "fail", "id", "noUnwind"}); - return environment; + protected Varargs invoke(LuaState state, DebugFrame di, Varargs args) throws LuaError, UnwindThrowable { + LuaThread.suspend(state); + return Constants.NONE; } @Override - public Varargs invoke(LuaState state, DebugFrame di, Varargs args) throws LuaError, UnwindThrowable { - switch (opcode) { - case 0: // suspend - LuaThread.suspend(state); - return Constants.NONE; - case 1: { // run - LuaThread thread = new LuaThread(state, args.first().checkFunction(), state.getCurrentThread().getfenv()); - di.state = thread; - Varargs value = Constants.NONE; - while (thread.isAlive()) value = LuaThread.resume(state, thread, value); - return value; - } - case 2: { // assertEquals - String traceback = DebugHelpers.traceback(state.getCurrentThread(), 0); - assertEquals(args.arg(1), args.arg(2), traceback); - return Constants.NONE; - } - case 3: { // fail - String traceback = DebugHelpers.traceback(state.getCurrentThread(), 0); - fail(args.first().toString() + ":\n" + traceback); - return Constants.NONE; - } - case 4: // id - return args; - case 5: // noYield - return noUnwind(state, () -> args.first().checkFunction().call(state)); - default: - return Constants.NONE; - } + protected Varargs resumeThis(LuaState state, Void object, Varargs value) { + return Constants.NONE; } + } + private static class Run extends ResumableVarArgFunction { @Override - public Varargs resumeThis(LuaState state, LuaThread thread, Varargs value) throws LuaError, UnwindThrowable { - switch (opcode) { - case 0: - return Constants.NONE; - case 1: // run - while (thread.isAlive()) value = LuaThread.resume(state, thread, value); - return value; - default: - throw new NonResumableException("Cannot resume " + debugName()); - } + protected Varargs invoke(LuaState state, DebugFrame di, Varargs args) throws LuaError, UnwindThrowable { + LuaThread thread = new LuaThread(state, args.first().checkFunction(), state.getCurrentThread().getfenv()); + di.state = thread; + Varargs value = Constants.NONE; + while (thread.isAlive()) value = LuaThread.resume(state, thread, value); + return value; + } + + @Override + protected Varargs resumeThis(LuaState state, LuaThread thread, Varargs value) throws LuaError, UnwindThrowable { + while (thread.isAlive()) value = LuaThread.resume(state, thread, value); + return value; } } + private static Varargs assertEquals$(LuaState state, Varargs args) { + String traceback = DebugHelpers.traceback(state.getCurrentThread(), 0); + assertEquals(args.arg(1), args.arg(2), traceback); + return Constants.NONE; + } + + private static Varargs fail$(LuaState state, Varargs args) { + String traceback = DebugHelpers.traceback(state.getCurrentThread(), 0); + fail(args.first().toString() + ":\n" + traceback); + return Constants.NONE; + } + + private static Varargs id(LuaState state, Varargs args) { + return args; + } + + private static Varargs noUnwind$(LuaState state, Varargs args) throws LuaError { + return noUnwind(state, () -> args.first().checkFunction().call(state)); + } + private static class SuspendingDebug extends DebugHandler { private boolean suspend = true; diff --git a/src/test/java/org/squiddev/cobalt/OrphanedThreadTest.java b/src/test/java/org/squiddev/cobalt/OrphanedThreadTest.java index 9befa69e..ad3a7409 100644 --- a/src/test/java/org/squiddev/cobalt/OrphanedThreadTest.java +++ b/src/test/java/org/squiddev/cobalt/OrphanedThreadTest.java @@ -32,7 +32,7 @@ import org.squiddev.cobalt.function.LuaFunction; import org.squiddev.cobalt.function.OneArgFunction; import org.squiddev.cobalt.function.VarArgFunction; -import org.squiddev.cobalt.lib.jse.JsePlatform; +import org.squiddev.cobalt.lib.system.SystemLibraries; import java.io.ByteArrayInputStream; import java.lang.ref.WeakReference; @@ -76,7 +76,7 @@ public void setup() { .build(); // And force coroutine.yield to actually be a blocking one. - env = JsePlatform.standardGlobals(state); + env = SystemLibraries.standardGlobals(state); ((LuaTable) env.rawget("coroutine")).rawset("yield", new VarArgFunction() { @Override public Varargs invoke(LuaState state, Varargs args) throws LuaError { diff --git a/src/test/java/org/squiddev/cobalt/RequireClassTest.java b/src/test/java/org/squiddev/cobalt/RequireClassTest.java index bc225e51..eb2e1982 100644 --- a/src/test/java/org/squiddev/cobalt/RequireClassTest.java +++ b/src/test/java/org/squiddev/cobalt/RequireClassTest.java @@ -27,7 +27,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.squiddev.cobalt.function.LuaFunction; -import org.squiddev.cobalt.lib.jse.JsePlatform; +import org.squiddev.cobalt.lib.system.SystemLibraries; import org.squiddev.cobalt.require.RequireSampleClassCastExcep; import org.squiddev.cobalt.require.RequireSampleLoadLuaError; import org.squiddev.cobalt.require.RequireSampleLoadRuntimeExcep; @@ -43,7 +43,7 @@ public class RequireClassTest { @BeforeEach public void setup() throws LuaError, UnwindThrowable { state = new LuaState(); - LuaTable globals = JsePlatform.standardGlobals(state); + LuaTable globals = SystemLibraries.standardGlobals(state); require = (LuaFunction) OperationHelper.getTable(state, globals, ValueFactory.valueOf("require")); } diff --git a/src/test/java/org/squiddev/cobalt/ScriptHelper.java b/src/test/java/org/squiddev/cobalt/ScriptHelper.java index a69b56e4..9ead492c 100644 --- a/src/test/java/org/squiddev/cobalt/ScriptHelper.java +++ b/src/test/java/org/squiddev/cobalt/ScriptHelper.java @@ -30,9 +30,8 @@ import org.squiddev.cobalt.debug.DebugState; import org.squiddev.cobalt.function.LuaFunction; import org.squiddev.cobalt.function.VarArgFunction; -import org.squiddev.cobalt.lib.jse.JseIoLib; -import org.squiddev.cobalt.lib.jse.JsePlatform; -import org.squiddev.cobalt.lib.platform.FileResourceManipulator; +import org.squiddev.cobalt.lib.system.ResourceLoader; +import org.squiddev.cobalt.lib.system.SystemLibraries; import java.io.*; import java.time.ZoneOffset; @@ -42,11 +41,14 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; -public class ScriptHelper extends FileResourceManipulator { +public class ScriptHelper { private final String subdir; public LuaState state; public LuaTable globals; + private final DelegatingOutputStream stdout = new DelegatingOutputStream(System.out); + private final PrintStream stdoutStream = new PrintStream(stdout); + public ScriptHelper(String subdir) { this.subdir = subdir; } @@ -57,36 +59,19 @@ public void setup() { } public void setup(Consumer extend) { - LuaState.Builder builder = LuaState.builder() - .resourceManipulator(this) - .stdin(new InputStream() { - @Override - public int read() { - return -1; - } - }); + LuaState.Builder builder = LuaState.builder(); extend.accept(builder); setupCommon(builder.build()); } public void setupQuiet() { - setupCommon(LuaState.builder() - .resourceManipulator(this) - .stdout(new PrintStream(new OutputStream() { - @Override - public void write(int b) { - } - - @Override - public void write(byte[] b, int off, int len) { - } - })) - .build()); + setupCommon(new LuaState()); + stdout.setOut(new VoidOutputStream()); } private void setupCommon(LuaState state) { this.state = state; - globals = JsePlatform.debugGlobals(state); + globals = SystemLibraries.debugGlobals(state, this::load, new VoidInputStream(), stdoutStream); globals.rawset("id_", new VarArgFunction() { @Override public Varargs invoke(LuaState state, Varargs args) { @@ -96,10 +81,17 @@ public Varargs invoke(LuaState state, Varargs args) { TimeZone.setDefault(TimeZone.getTimeZone(ZoneOffset.UTC)); } - @Override - public InputStream findResource(String filename) { - InputStream stream = getClass().getResourceAsStream(subdir + filename); - return stream == null ? super.findResource(filename) : stream; + private InputStream load(String filename) { + { + InputStream stream = getClass().getResourceAsStream(subdir + filename); + if (stream != null) return stream; + } + { + InputStream stream = getClass().getResourceAsStream("/" + filename); + if (stream != null) return stream; + } + + return ResourceLoader.FILES.load(filename); } /** @@ -108,30 +100,27 @@ public InputStream findResource(String filename) { * @param testName The name of the test file to run */ public void runComparisonTest(String testName) throws Exception { - // Override print() + // Redirect our stdout! final ByteArrayOutputStream output = new ByteArrayOutputStream(); - final PrintStream oldps = state.stdout; - final PrintStream ps = new PrintStream(output); - state.stdout = ps; - globals.load(state, new JseIoLib()); + final OutputStream oldOutput = stdout.getOut(); + stdout.setOut(output); // Run the script try { LuaThread.runMain(state, loadScript(testName)); - ps.flush(); - String actualOutput = new String(output.toByteArray()); + stdoutStream.flush(); + String actualOutput = output.toString(); String expectedOutput = getExpectedOutput(testName); actualOutput = actualOutput.replaceAll("\r\n", "\n"); expectedOutput = expectedOutput.replaceAll("\r\n", "\n"); assertEquals(expectedOutput, actualOutput); } catch (LuaError e) { - System.out.println(new String(output.toByteArray())); + System.out.println(output); throw e; } finally { - state.stdout = oldps; - ps.close(); + stdout.setOut(oldOutput); } } @@ -143,7 +132,7 @@ public void runComparisonTest(String testName) throws Exception { * @throws IOException */ public LuaFunction loadScript(String name) throws IOException, CompileException { - InputStream script = findResource(name + ".lua"); + InputStream script = load(name + ".lua"); if (script == null) fail("Could not load script for test case: " + name); try { return LoadState.load(state, script, "@" + name + ".lua", globals); @@ -152,13 +141,13 @@ public LuaFunction loadScript(String name) throws IOException, CompileException } } - public Varargs runWithDump(String script) throws InterruptedException, LuaError, IOException, CompileException { - return runWithDump(loadScript(script)); + public void runWithDump(String script) throws InterruptedException, LuaError, IOException, CompileException { + runWithDump(loadScript(script)); } - public Varargs runWithDump(LuaFunction function) throws InterruptedException, LuaError { + public void runWithDump(LuaFunction function) throws InterruptedException, LuaError { try { - return LuaThread.runMain(state, function); + LuaThread.runMain(state, function); } catch (LuaError e) { DebugState debug = state.getCurrentThread().getDebugState(); int level = 0; @@ -180,7 +169,7 @@ public Varargs runWithDump(LuaFunction function) throws InterruptedException, Lu } private String getExpectedOutput(final String name) throws IOException { - InputStream output = this.findResource(name + ".out"); + InputStream output = load(name + ".out"); if (output == null) fail("Failed to get comparison output for " + name); try { return readString(output); @@ -196,7 +185,37 @@ private String readString(InputStream is) throws IOException { while ((r = is.read(buf)) >= 0) { outputStream.write(buf, 0, r); } - return new String(outputStream.toByteArray()); + return outputStream.toString(); + } + + private static class VoidOutputStream extends OutputStream { + @Override + public void write(int b) { + } + + @Override + public void write(byte[] bytes, int off, int len) { + } + } + + private static class VoidInputStream extends InputStream { + @Override + public int read() { + return -1; + } } + private static class DelegatingOutputStream extends FilterOutputStream { + public DelegatingOutputStream(OutputStream output) { + super(output); + } + + public OutputStream getOut() { + return out; + } + + public void setOut(OutputStream out) { + this.out = out; + } + } } diff --git a/src/test/java/org/squiddev/cobalt/compiler/CompilerUnitTests.java b/src/test/java/org/squiddev/cobalt/compiler/CompilerUnitTests.java index e0d87e04..72ac57bb 100644 --- a/src/test/java/org/squiddev/cobalt/compiler/CompilerUnitTests.java +++ b/src/test/java/org/squiddev/cobalt/compiler/CompilerUnitTests.java @@ -24,17 +24,13 @@ */ package org.squiddev.cobalt.compiler; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import org.squiddev.cobalt.LuaState; import org.squiddev.cobalt.Print; import org.squiddev.cobalt.Prototype; -import org.squiddev.cobalt.lib.jse.JsePlatform; import java.io.*; import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.Paths; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -43,12 +39,6 @@ * Compiles Lua's test files to bytecode and asserts that it is equal to a golden file produced by luac. */ public class CompilerUnitTests { - @BeforeEach - public void setup() { - LuaState state = new LuaState(); - JsePlatform.standardGlobals(state); - } - @ParameterizedTest(name = ParameterizedTest.ARGUMENTS_WITH_NAMES_PLACEHOLDER) @ValueSource(strings = { "all", "api", "attrib", "big", "calls", "checktable", "closure", "code", "constructs", "db", "errors", @@ -75,9 +65,9 @@ private static void compareResults(String dir, String file) throws IOException, Prototype expectedPrototype = LuaC.compile(CompilerUnitTests.class.getResourceAsStream(dir + file + ".lc"), file + ".lua"); String expectedBytecode = dumpState(expectedPrototype); - if(!expectedBytecode.equals(sourceBytecode)) { - try(OutputStream output = Files.newOutputStream(Paths.get("src/test/resources" + dir + file + ".lc"))) { - DumpState.dump(sourcePrototype, output, false); + if (!expectedBytecode.equals(sourceBytecode)) { + try (OutputStream output = Files.newOutputStream(Paths.get("src/test/resources" + dir + file + ".lc"))) { + BytecodeDumper.dump(sourcePrototype, output, false); } } @@ -85,7 +75,7 @@ private static void compareResults(String dir, String file) throws IOException, // Round-trip the bytecode ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - DumpState.dump(expectedPrototype, outputStream, false); + BytecodeDumper.dump(expectedPrototype, outputStream, false); String redumpBytecode = dumpState(LuaC.compile(new ByteArrayInputStream(outputStream.toByteArray()), file + ".lua")); // compare again diff --git a/src/test/java/org/squiddev/cobalt/compiler/DumpLoadEndianIntTest.java b/src/test/java/org/squiddev/cobalt/compiler/DumpLoadEndianIntTest.java index 9ad40629..ff1a4370 100644 --- a/src/test/java/org/squiddev/cobalt/compiler/DumpLoadEndianIntTest.java +++ b/src/test/java/org/squiddev/cobalt/compiler/DumpLoadEndianIntTest.java @@ -29,7 +29,7 @@ import org.squiddev.cobalt.*; import org.squiddev.cobalt.function.LuaFunction; import org.squiddev.cobalt.function.LuaInterpretedFunction; -import org.squiddev.cobalt.lib.jse.JsePlatform; +import org.squiddev.cobalt.lib.CoreLibraries; import java.io.*; @@ -53,56 +53,56 @@ public class DumpLoadEndianIntTest { @BeforeEach public void setup() { state = new LuaState(); - _G = JsePlatform.standardGlobals(state); - DumpState.ALLOW_INTEGER_CASTING = false; + _G = CoreLibraries.standardGlobals(state); + BytecodeDumper.ALLOW_INTEGER_CASTING = false; } @Test public void testBigDoubleCompile() throws LuaError, CompileException, UnwindThrowable { - doTest(false, DumpState.NUMBER_FORMAT_FLOATS_OR_DOUBLES, false, mixedscript, withdoubles, withdoubles, SHOULDPASS); - doTest(false, DumpState.NUMBER_FORMAT_FLOATS_OR_DOUBLES, true, mixedscript, withdoubles, withdoubles, SHOULDPASS); + doTest(false, BytecodeDumper.NUMBER_FORMAT_FLOATS_OR_DOUBLES, false, mixedscript, withdoubles, withdoubles, SHOULDPASS); + doTest(false, BytecodeDumper.NUMBER_FORMAT_FLOATS_OR_DOUBLES, true, mixedscript, withdoubles, withdoubles, SHOULDPASS); } @Test public void testLittleDoubleCompile() throws LuaError, CompileException, UnwindThrowable { - doTest(true, DumpState.NUMBER_FORMAT_FLOATS_OR_DOUBLES, false, mixedscript, withdoubles, withdoubles, SHOULDPASS); - doTest(true, DumpState.NUMBER_FORMAT_FLOATS_OR_DOUBLES, true, mixedscript, withdoubles, withdoubles, SHOULDPASS); + doTest(true, BytecodeDumper.NUMBER_FORMAT_FLOATS_OR_DOUBLES, false, mixedscript, withdoubles, withdoubles, SHOULDPASS); + doTest(true, BytecodeDumper.NUMBER_FORMAT_FLOATS_OR_DOUBLES, true, mixedscript, withdoubles, withdoubles, SHOULDPASS); } @Test public void testBigIntCompile() throws LuaError, CompileException, UnwindThrowable { - DumpState.ALLOW_INTEGER_CASTING = true; - doTest(false, DumpState.NUMBER_FORMAT_INTS_ONLY, false, mixedscript, withdoubles, withints, SHOULDPASS); - doTest(false, DumpState.NUMBER_FORMAT_INTS_ONLY, true, mixedscript, withdoubles, withints, SHOULDPASS); - DumpState.ALLOW_INTEGER_CASTING = false; - doTest(false, DumpState.NUMBER_FORMAT_INTS_ONLY, false, mixedscript, withdoubles, withints, SHOULDFAIL); - doTest(false, DumpState.NUMBER_FORMAT_INTS_ONLY, true, mixedscript, withdoubles, withints, SHOULDFAIL); - doTest(false, DumpState.NUMBER_FORMAT_INTS_ONLY, false, intscript, withints, withints, SHOULDPASS); - doTest(false, DumpState.NUMBER_FORMAT_INTS_ONLY, true, intscript, withints, withints, SHOULDPASS); + BytecodeDumper.ALLOW_INTEGER_CASTING = true; + doTest(false, BytecodeDumper.NUMBER_FORMAT_INTS_ONLY, false, mixedscript, withdoubles, withints, SHOULDPASS); + doTest(false, BytecodeDumper.NUMBER_FORMAT_INTS_ONLY, true, mixedscript, withdoubles, withints, SHOULDPASS); + BytecodeDumper.ALLOW_INTEGER_CASTING = false; + doTest(false, BytecodeDumper.NUMBER_FORMAT_INTS_ONLY, false, mixedscript, withdoubles, withints, SHOULDFAIL); + doTest(false, BytecodeDumper.NUMBER_FORMAT_INTS_ONLY, true, mixedscript, withdoubles, withints, SHOULDFAIL); + doTest(false, BytecodeDumper.NUMBER_FORMAT_INTS_ONLY, false, intscript, withints, withints, SHOULDPASS); + doTest(false, BytecodeDumper.NUMBER_FORMAT_INTS_ONLY, true, intscript, withints, withints, SHOULDPASS); } @Test public void testLittleIntCompile() throws LuaError, CompileException, UnwindThrowable { - DumpState.ALLOW_INTEGER_CASTING = true; - doTest(true, DumpState.NUMBER_FORMAT_INTS_ONLY, false, mixedscript, withdoubles, withints, SHOULDPASS); - doTest(true, DumpState.NUMBER_FORMAT_INTS_ONLY, true, mixedscript, withdoubles, withints, SHOULDPASS); - DumpState.ALLOW_INTEGER_CASTING = false; - doTest(true, DumpState.NUMBER_FORMAT_INTS_ONLY, false, mixedscript, withdoubles, withints, SHOULDFAIL); - doTest(true, DumpState.NUMBER_FORMAT_INTS_ONLY, true, mixedscript, withdoubles, withints, SHOULDFAIL); - doTest(true, DumpState.NUMBER_FORMAT_INTS_ONLY, false, intscript, withints, withints, SHOULDPASS); - doTest(true, DumpState.NUMBER_FORMAT_INTS_ONLY, true, intscript, withints, withints, SHOULDPASS); + BytecodeDumper.ALLOW_INTEGER_CASTING = true; + doTest(true, BytecodeDumper.NUMBER_FORMAT_INTS_ONLY, false, mixedscript, withdoubles, withints, SHOULDPASS); + doTest(true, BytecodeDumper.NUMBER_FORMAT_INTS_ONLY, true, mixedscript, withdoubles, withints, SHOULDPASS); + BytecodeDumper.ALLOW_INTEGER_CASTING = false; + doTest(true, BytecodeDumper.NUMBER_FORMAT_INTS_ONLY, false, mixedscript, withdoubles, withints, SHOULDFAIL); + doTest(true, BytecodeDumper.NUMBER_FORMAT_INTS_ONLY, true, mixedscript, withdoubles, withints, SHOULDFAIL); + doTest(true, BytecodeDumper.NUMBER_FORMAT_INTS_ONLY, false, intscript, withints, withints, SHOULDPASS); + doTest(true, BytecodeDumper.NUMBER_FORMAT_INTS_ONLY, true, intscript, withints, withints, SHOULDPASS); } @Test public void testBigNumpatchCompile() throws LuaError, CompileException, UnwindThrowable { - doTest(false, DumpState.NUMBER_FORMAT_NUM_PATCH_INT32, false, mixedscript, withdoubles, withdoubles, SHOULDPASS); - doTest(false, DumpState.NUMBER_FORMAT_NUM_PATCH_INT32, true, mixedscript, withdoubles, withdoubles, SHOULDPASS); + doTest(false, BytecodeDumper.NUMBER_FORMAT_NUM_PATCH_INT32, false, mixedscript, withdoubles, withdoubles, SHOULDPASS); + doTest(false, BytecodeDumper.NUMBER_FORMAT_NUM_PATCH_INT32, true, mixedscript, withdoubles, withdoubles, SHOULDPASS); } @Test public void testLittleNumpatchCompile() throws LuaError, CompileException, UnwindThrowable { - doTest(true, DumpState.NUMBER_FORMAT_NUM_PATCH_INT32, false, mixedscript, withdoubles, withdoubles, SHOULDPASS); - doTest(true, DumpState.NUMBER_FORMAT_NUM_PATCH_INT32, true, mixedscript, withdoubles, withdoubles, SHOULDPASS); + doTest(true, BytecodeDumper.NUMBER_FORMAT_NUM_PATCH_INT32, false, mixedscript, withdoubles, withdoubles, SHOULDPASS); + doTest(true, BytecodeDumper.NUMBER_FORMAT_NUM_PATCH_INT32, true, mixedscript, withdoubles, withdoubles, SHOULDPASS); } public void doTest(boolean littleEndian, int numberFormat, boolean stripDebug, @@ -122,7 +122,7 @@ public void doTest(boolean littleEndian, int numberFormat, boolean stripDebug, // dump into bytes ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { - DumpState.dump(p, baos, stripDebug, numberFormat, littleEndian); + BytecodeDumper.dump(p, baos, stripDebug, numberFormat, littleEndian); if (!shouldPass) { fail("dump should not have succeeded"); } @@ -147,9 +147,9 @@ public void doTest(boolean littleEndian, int numberFormat, boolean stripDebug, new File("build").mkdirs(); String filename = "build/test-" + (littleEndian ? "little-" : "big-") - + (numberFormat == DumpState.NUMBER_FORMAT_FLOATS_OR_DOUBLES ? "double-" : - numberFormat == DumpState.NUMBER_FORMAT_INTS_ONLY ? "int-" : - numberFormat == DumpState.NUMBER_FORMAT_NUM_PATCH_INT32 ? "numpatch4-" : "???-") + + (numberFormat == BytecodeDumper.NUMBER_FORMAT_FLOATS_OR_DOUBLES ? "double-" : + numberFormat == BytecodeDumper.NUMBER_FORMAT_INTS_ONLY ? "int-" : + numberFormat == BytecodeDumper.NUMBER_FORMAT_NUM_PATCH_INT32 ? "numpatch4-" : "???-") + (stripDebug ? "nodebug-" : "debug-") + "bin.lua"; FileOutputStream fos = new FileOutputStream(filename); diff --git a/src/test/java/org/squiddev/cobalt/compiler/SimpleTests.java b/src/test/java/org/squiddev/cobalt/compiler/SimpleTests.java index 754cd5ad..423bf7d8 100644 --- a/src/test/java/org/squiddev/cobalt/compiler/SimpleTests.java +++ b/src/test/java/org/squiddev/cobalt/compiler/SimpleTests.java @@ -28,7 +28,7 @@ import org.junit.jupiter.api.Test; import org.squiddev.cobalt.*; import org.squiddev.cobalt.function.LuaFunction; -import org.squiddev.cobalt.lib.jse.JsePlatform; +import org.squiddev.cobalt.lib.system.SystemLibraries; import java.io.ByteArrayInputStream; import java.io.InputStream; @@ -44,13 +44,13 @@ public class SimpleTests { @BeforeEach public void setup() { state = new LuaState(); - _G = JsePlatform.standardGlobals(state); + _G = SystemLibraries.standardGlobals(state); } private void doTest(String script) { try { InputStream is = new ByteArrayInputStream(script.getBytes(StandardCharsets.UTF_8)); - LuaFunction c = LuaC.INSTANCE.load(is, valueOf("script"), null, _G); + LuaFunction c = LoadState.interpretedFunction(LuaC.compile(is, valueOf("script"), null), _G); LuaThread.runMain(state, c); } catch (Exception e) { fail("i/o exception: " + e); @@ -117,7 +117,7 @@ public void testZap() { String s = "print('\\z"; assertThrows(CompileException.class, () -> { InputStream is = new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8)); - LuaC.INSTANCE.load(is, valueOf("script"), null, _G); + LoadState.interpretedFunction(LuaC.compile(is, valueOf("script"), null), _G); }); } diff --git a/src/test/java/org/squiddev/cobalt/lib/doubles/BignumTest.java b/src/test/java/org/squiddev/cobalt/lib/doubles/BignumTest.java index ea2c1673..2a005c4c 100644 --- a/src/test/java/org/squiddev/cobalt/lib/doubles/BignumTest.java +++ b/src/test/java/org/squiddev/cobalt/lib/doubles/BignumTest.java @@ -31,7 +31,6 @@ package org.squiddev.cobalt.lib.doubles; import org.checkerframework.checker.signedness.qual.Unsigned; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import static org.squiddev.cobalt.lib.doubles.DoubleTestHelper.CHECK; @@ -49,11 +48,6 @@ static void assignDecimalString(Bignum bignum, String str) { bignum.assignDecimalString(str); } - @BeforeAll - static void initAll() { - Assert.setEnabled(true); - } - @Test public void assign() { char[] buffer = new char[kBufferSize]; diff --git a/src/test/java/org/squiddev/cobalt/lib/doubles/DiyFpTest.java b/src/test/java/org/squiddev/cobalt/lib/doubles/DiyFpTest.java index 0b1ef4af..0b1a15dd 100644 --- a/src/test/java/org/squiddev/cobalt/lib/doubles/DiyFpTest.java +++ b/src/test/java/org/squiddev/cobalt/lib/doubles/DiyFpTest.java @@ -31,19 +31,12 @@ package org.squiddev.cobalt.lib.doubles; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.squiddev.cobalt.lib.doubles.DoubleTestHelper.CHECK_EQ; public class DiyFpTest { - - @BeforeAll - static void initAll() { - Assert.setEnabled(true); - } - @Test public void subtract() { DiyFp diy_fp1 = new DiyFp(3L, 0); diff --git a/src/test/java/org/squiddev/cobalt/lib/doubles/DoubleToStringConverterTest.java b/src/test/java/org/squiddev/cobalt/lib/doubles/DoubleToStringConverterTest.java index dddd8d80..7f9df472 100644 --- a/src/test/java/org/squiddev/cobalt/lib/doubles/DoubleToStringConverterTest.java +++ b/src/test/java/org/squiddev/cobalt/lib/doubles/DoubleToStringConverterTest.java @@ -30,7 +30,6 @@ package org.squiddev.cobalt.lib.doubles; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.squiddev.cobalt.Buffer; import org.squiddev.cobalt.lib.doubles.DoubleToStringConverter.Flags; @@ -46,11 +45,6 @@ class DoubleToStringConverterTest { new FormatOptions(SYMBOLS, false, false, false, -1, false, false); private DoubleToStringConverter conv; - @BeforeAll - static void initAll() { - Assert.setEnabled(true); - } - @Test void toFixed() { conv = DoubleToStringConverter.ecmaScriptConverter(); diff --git a/src/test/java/org/squiddev/cobalt/lib/doubles/DtoaTest.java b/src/test/java/org/squiddev/cobalt/lib/doubles/DtoaTest.java index bf94f067..3ccdfcd4 100644 --- a/src/test/java/org/squiddev/cobalt/lib/doubles/DtoaTest.java +++ b/src/test/java/org/squiddev/cobalt/lib/doubles/DtoaTest.java @@ -30,23 +30,15 @@ */ package org.squiddev.cobalt.lib.doubles; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.squiddev.cobalt.lib.doubles.DoubleToStringConverter.DtoaMode; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -import static org.junit.jupiter.api.Assertions.fail; import static org.squiddev.cobalt.lib.doubles.DoubleTestHelper.*; @SuppressWarnings("type.argument.type.incompatible") public class DtoaTest { - - @BeforeAll - static void initAll() { - Assert.setEnabled(true); - } - static void doubleToAscii(double v, DtoaMode test_mode, int requested_digits, DecimalRepBuf buffer) { DoubleToStringConverter.DtoaMode mode = DtoaMode.FIXED; @@ -273,37 +265,29 @@ static class DataTestState { @Test public void dtoaGayFixed() throws Exception { DataTestState state = new DataTestState(); - try { - DoubleTestHelper.eachFixed(state, (st, v, numberDigits, representation, decimalPoint) -> { - st.underTest = String.format("Using {%g, \"%s\", %d}", v, representation, decimalPoint); - doubleToAscii(v, DtoaMode.FIXED, numberDigits, st.buffer); - assertThat(st.underTest, st.buffer.getSign(), is(false)); // All precomputed numbers are positive. - assertThat(st.underTest, st.buffer.getPointPosition(), is(equalTo(decimalPoint))); - assertThat(st.underTest, (st.buffer.length() - st.buffer.getPointPosition()), is(lessThanOrEqualTo(numberDigits))); - st.buffer.truncateAllZeros(); - assertThat(st.underTest, stringOf(st.buffer), is(equalTo(representation))); - }); - } catch (Assert.DoubleConversionAssertionException e) { - fail("Assertion failed for test " + state.underTest, e); - } + DoubleTestHelper.eachFixed(state, (st, v, numberDigits, representation, decimalPoint) -> { + st.underTest = String.format("Using {%g, \"%s\", %d}", v, representation, decimalPoint); + doubleToAscii(v, DtoaMode.FIXED, numberDigits, st.buffer); + assertThat(st.underTest, st.buffer.getSign(), is(false)); // All precomputed numbers are positive. + assertThat(st.underTest, st.buffer.getPointPosition(), is(equalTo(decimalPoint))); + assertThat(st.underTest, (st.buffer.length() - st.buffer.getPointPosition()), is(lessThanOrEqualTo(numberDigits))); + st.buffer.truncateAllZeros(); + assertThat(st.underTest, stringOf(st.buffer), is(equalTo(representation))); + }); } @Test public void dtoaGayPrecision() throws Exception { DataTestState state = new DataTestState(); - try { - DoubleTestHelper.eachPrecision(state, (st, v, numberDigits, representation, decimalPoint) -> { - st.underTest = String.format("Using {%g, \"%s\", %d}", v, representation, decimalPoint); - doubleToAscii(v, DtoaMode.PRECISION, numberDigits, - st.buffer); - assertThat(st.underTest, st.buffer.getSign(), is(false)); // All precomputed numbers are positive. - assertThat(st.underTest, st.buffer.getPointPosition(), is(equalTo(decimalPoint))); - assertThat(st.underTest, st.buffer.length(), is(greaterThanOrEqualTo(numberDigits))); - st.buffer.truncateAllZeros(); - assertThat(st.underTest, stringOf(st.buffer), is(equalTo(representation))); - }); - } catch (Assert.DoubleConversionAssertionException e) { - fail("Assertion failed for test " + state.underTest, e); - } + DoubleTestHelper.eachPrecision(state, (st, v, numberDigits, representation, decimalPoint) -> { + st.underTest = String.format("Using {%g, \"%s\", %d}", v, representation, decimalPoint); + doubleToAscii(v, DtoaMode.PRECISION, numberDigits, + st.buffer); + assertThat(st.underTest, st.buffer.getSign(), is(false)); // All precomputed numbers are positive. + assertThat(st.underTest, st.buffer.getPointPosition(), is(equalTo(decimalPoint))); + assertThat(st.underTest, st.buffer.length(), is(greaterThanOrEqualTo(numberDigits))); + st.buffer.truncateAllZeros(); + assertThat(st.underTest, stringOf(st.buffer), is(equalTo(representation))); + }); } } diff --git a/src/test/java/org/squiddev/cobalt/lib/doubles/FastDtoaTest.java b/src/test/java/org/squiddev/cobalt/lib/doubles/FastDtoaTest.java index 12d87be2..24e0258c 100644 --- a/src/test/java/org/squiddev/cobalt/lib/doubles/FastDtoaTest.java +++ b/src/test/java/org/squiddev/cobalt/lib/doubles/FastDtoaTest.java @@ -31,23 +31,15 @@ package org.squiddev.cobalt.lib.doubles; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -import static org.junit.jupiter.api.Assertions.fail; import static org.squiddev.cobalt.lib.doubles.DoubleTestHelper.*; public class FastDtoaTest { - private static final int BUFFER_SIZE = 100; - @BeforeAll - static void initAll() { - Assert.setEnabled(true); - } - @Test public void precisionVariousDoubles() { DecimalRepBuf buffer = new DecimalRepBuf(BUFFER_SIZE); @@ -169,30 +161,25 @@ static class PrecisionState { @Test public void gayPrecision() throws Exception { PrecisionState state = new PrecisionState(); - try { - DoubleTestHelper.eachPrecision(state, (st, v, numberDigits, representation, decimalPoint) -> { - int[] length = new int[1]; - int[] point = new int[1]; - boolean status; - - st.total++; - st.underTest = String.format("Using {%g, %d, \"%s\", %d}", v, numberDigits, representation, decimalPoint); - if (numberDigits <= 15) st.total15++; - - status = FastDtoa.fastDtoa(v, numberDigits, - st.buffer); - CHECK_GE(numberDigits, st.buffer.length()); - if (status) { - st.succeeded++; - if (numberDigits <= 15) st.succeeded15++; - st.buffer.truncateAllZeros(); - assertThat(st.underTest, st.buffer.getPointPosition(), is(equalTo(decimalPoint))); - assertThat(st.underTest, stringOf(st.buffer), is(equalTo(representation))); - } - }); - } catch (Assert.DoubleConversionAssertionException e) { - fail("Assertion failed for test " + state.underTest, e); - } + + DoubleTestHelper.eachPrecision(state, (st, v, numberDigits, representation, decimalPoint) -> { + boolean status; + + st.total++; + st.underTest = String.format("Using {%g, %d, \"%s\", %d}", v, numberDigits, representation, decimalPoint); + if (numberDigits <= 15) st.total15++; + + status = FastDtoa.fastDtoa(v, numberDigits, st.buffer); + CHECK_GE(numberDigits, st.buffer.length()); + if (status) { + st.succeeded++; + if (numberDigits <= 15) st.succeeded15++; + st.buffer.truncateAllZeros(); + assertThat(st.underTest, st.buffer.getPointPosition(), is(equalTo(decimalPoint))); + assertThat(st.underTest, stringOf(st.buffer), is(equalTo(representation))); + } + }); + // The precomputed numbers contain many entries with many requested // digits. These have a high failure rate and we therefore expect a lower // success rate than for the shortest representation. diff --git a/src/test/java/org/squiddev/cobalt/lib/doubles/FixedDtoaTest.java b/src/test/java/org/squiddev/cobalt/lib/doubles/FixedDtoaTest.java index b8784dad..dc69a759 100644 --- a/src/test/java/org/squiddev/cobalt/lib/doubles/FixedDtoaTest.java +++ b/src/test/java/org/squiddev/cobalt/lib/doubles/FixedDtoaTest.java @@ -31,22 +31,15 @@ package org.squiddev.cobalt.lib.doubles; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -import static org.junit.jupiter.api.Assertions.fail; import static org.squiddev.cobalt.lib.doubles.DoubleTestHelper.*; public class FixedDtoaTest { private static final int kBufferSize = 500; - @BeforeAll - static void initAll() { - Assert.setEnabled(true); - } - @Test public void variousDoubles() { DecimalRepBuf buffer = new DecimalRepBuf(kBufferSize); @@ -507,29 +500,19 @@ static class DataTestState { @Test public void gayFixed() throws Exception { DtoaTest.DataTestState state = new DtoaTest.DataTestState(); - try { - DoubleTestHelper.eachFixed(state, (st, v, numberDigits, representation, decimalPoint) -> { - int[] length = new int[1]; - int[] point = new int[1]; - boolean[] sign = new boolean[1]; - boolean status; - - st.total++; - - st.underTest = String.format("Using {%g, \"%s\", %d}", v, representation, decimalPoint); - status = FixedDtoa.fastFixedDtoa(v, numberDigits, - st.buffer); - - assertThat(st.underTest, status, is(true)); - assertThat(st.underTest, st.buffer.getPointPosition(), is(equalTo(decimalPoint))); - - assertThat(st.underTest, st.buffer.getPointPosition(), is(equalTo(decimalPoint))); - assertThat(st.underTest, (st.buffer.length() - st.buffer.getPointPosition()), is(lessThanOrEqualTo(numberDigits))); - assertThat(st.underTest, stringOf(st.buffer), is(equalTo(representation))); - }); - } catch (Assert.DoubleConversionAssertionException e) { - fail("Assertion failed for test " + state.underTest, e); - } + DoubleTestHelper.eachFixed(state, (st, v, numberDigits, representation, decimalPoint) -> { + st.total++; + + st.underTest = String.format("Using {%g, \"%s\", %d}", v, representation, decimalPoint); + boolean status = FixedDtoa.fastFixedDtoa(v, numberDigits, st.buffer); + + assertThat(st.underTest, status, is(true)); + assertThat(st.underTest, st.buffer.getPointPosition(), is(equalTo(decimalPoint))); + + assertThat(st.underTest, st.buffer.getPointPosition(), is(equalTo(decimalPoint))); + assertThat(st.underTest, (st.buffer.length() - st.buffer.getPointPosition()), is(lessThanOrEqualTo(numberDigits))); + assertThat(st.underTest, stringOf(st.buffer), is(equalTo(representation))); + }); System.out.println("day-precision tests run :" + Integer.toString(state.total)); } diff --git a/src/test/java/org/squiddev/cobalt/lib/doubles/IeeeTest.java b/src/test/java/org/squiddev/cobalt/lib/doubles/IeeeTest.java index 4e33d72d..d3dce88a 100644 --- a/src/test/java/org/squiddev/cobalt/lib/doubles/IeeeTest.java +++ b/src/test/java/org/squiddev/cobalt/lib/doubles/IeeeTest.java @@ -32,18 +32,11 @@ package org.squiddev.cobalt.lib.doubles; import org.checkerframework.checker.signedness.qual.Unsigned; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import static org.squiddev.cobalt.lib.doubles.DoubleTestHelper.*; public class IeeeTest { - - @BeforeAll - static void initAll() { - Assert.setEnabled(true); - } - @Test public void uint64Conversions() { // Start by checking the byte-order. diff --git a/src/test/java/org/squiddev/cobalt/lib/doubles/UInt128Test.java b/src/test/java/org/squiddev/cobalt/lib/doubles/UInt128Test.java index 49814ae7..bcdac52e 100644 --- a/src/test/java/org/squiddev/cobalt/lib/doubles/UInt128Test.java +++ b/src/test/java/org/squiddev/cobalt/lib/doubles/UInt128Test.java @@ -30,19 +30,12 @@ package org.squiddev.cobalt.lib.doubles; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.squiddev.cobalt.lib.doubles.FixedDtoa.UInt128; import static org.squiddev.cobalt.lib.doubles.DoubleTestHelper.CHECK_EQ; class UInt128Test { - - @BeforeAll - static void initAll() { - Assert.setEnabled(true); - } - @Test public void shiftOps() { UInt128 val = new UInt128(0L, 0xffffL); diff --git a/src/test/java/org/squiddev/cobalt/vm/LuaOperationsTest.java b/src/test/java/org/squiddev/cobalt/vm/LuaOperationsTest.java index e2a82baf..b4c7d2ae 100644 --- a/src/test/java/org/squiddev/cobalt/vm/LuaOperationsTest.java +++ b/src/test/java/org/squiddev/cobalt/vm/LuaOperationsTest.java @@ -31,7 +31,7 @@ import org.squiddev.cobalt.function.LuaFunction; import org.squiddev.cobalt.function.LuaInterpretedFunction; import org.squiddev.cobalt.function.ZeroArgFunction; -import org.squiddev.cobalt.lib.jse.JsePlatform; +import org.squiddev.cobalt.lib.CoreLibraries; import java.io.ByteArrayInputStream; import java.io.InputStream; @@ -219,7 +219,7 @@ public void testFunctionClosureThreadEnv() throws LuaError, UnwindThrowable, Int // set up suitable environments for execution LuaValue aaa = valueOf("aaa"); LuaValue eee = valueOf("eee"); - LuaTable _G = JsePlatform.standardGlobals(state); + LuaTable _G = CoreLibraries.standardGlobals(state); LuaTable newenv = ValueFactory.tableOf(valueOf("a"), valueOf("aaa"), valueOf("b"), valueOf("bbb")); LuaTable mt = ValueFactory.tableOf(Constants.INDEX, _G); diff --git a/src/test/java/org/squiddev/cobalt/vm/MetatableTest.java b/src/test/java/org/squiddev/cobalt/vm/MetatableTest.java index c45a0a06..9076cedb 100644 --- a/src/test/java/org/squiddev/cobalt/vm/MetatableTest.java +++ b/src/test/java/org/squiddev/cobalt/vm/MetatableTest.java @@ -25,11 +25,9 @@ package org.squiddev.cobalt.vm; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.squiddev.cobalt.*; import org.squiddev.cobalt.function.*; -import org.squiddev.cobalt.lib.StringLib; import static org.junit.jupiter.api.Assertions.*; import static org.squiddev.cobalt.ValueFactory.valueOf; @@ -54,17 +52,8 @@ public LuaValue call(LuaState state) { private final LuaUserdata userdata = ValueFactory.userdataOf(sampleobject); private final LuaUserdata userdatamt = ValueFactory.userdataOf(sampledata, table); - public MetatableTest() throws LuaError { - } - - @BeforeEach - public void setup() throws Exception { - // needed for metatable ops to work on strings - new StringLib(); - } - @AfterEach - public void tearDown() throws Exception { + public void tearDown() { state.booleanMetatable = null; state.functionMetatable = null; state.nilMetatable = null; diff --git a/src/test/java/org/squiddev/cobalt/vm/StringTest.java b/src/test/java/org/squiddev/cobalt/vm/StringTest.java index 5fd77b3d..8ff2e345 100644 --- a/src/test/java/org/squiddev/cobalt/vm/StringTest.java +++ b/src/test/java/org/squiddev/cobalt/vm/StringTest.java @@ -27,7 +27,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.squiddev.cobalt.*; -import org.squiddev.cobalt.lib.jse.JsePlatform; +import org.squiddev.cobalt.lib.CoreLibraries; import java.io.IOException; import java.io.InputStream; @@ -40,7 +40,7 @@ public class StringTest { @BeforeEach public void setup() { - JsePlatform.standardGlobals(state); + CoreLibraries.standardGlobals(state); } @Test diff --git a/src/test/resources/compare/baselib.lua b/src/test/resources/compare/baselib.lua index a395209d..dee9e5a3 100644 --- a/src/test/resources/compare/baselib.lua +++ b/src/test/resources/compare/baselib.lua @@ -250,11 +250,11 @@ print('tostring({"one","two",a="aa",b="bb"})', type(tostring({ "one", "two", a = -- unpack print('pcall(unpack)', pcall(unpack)); print('pcall(unpack,nil)', pcall(unpack, nil)); -print('pcall(unpack,"abc")', pcall(unpack, "abc")); print('pcall(unpack,1)', pcall(unpack, 1)); print('unpack({"aa"})', unpack({ "aa" })); print('unpack({"aa","bb"})', unpack({ "aa", "bb" })); print('unpack({"aa","bb","cc"})', unpack({ "aa", "bb", "cc" })); +print('unpack("abc")', unpack("abc")); local t = { "aa", "bb", "cc", "dd", "ee", "ff" } print('pcall(unpack,t)', pcall(unpack, t)); print('pcall(unpack,t,2)', pcall(unpack, t, 2)); diff --git a/src/test/resources/compare/baselib.out b/src/test/resources/compare/baselib.out index 8c07c2c8..ebbce4ad 100644 --- a/src/test/resources/compare/baselib.out +++ b/src/test/resources/compare/baselib.out @@ -221,11 +221,11 @@ tostring(function() end) string tostring({"one","two",a="aa",b="bb"}) string pcall(unpack) false string pcall(unpack,nil) false string -pcall(unpack,"abc") false string pcall(unpack,1) false string unpack({"aa"}) aa unpack({"aa","bb"}) aa bb unpack({"aa","bb","cc"}) aa bb cc +unpack("abc") nil nil nil pcall(unpack,t) aa pcall(unpack,t,2) bb pcall(unpack,t,2,5) bb diff --git a/src/test/resources/compare/errors/args.lua b/src/test/resources/compare/errors/args.lua index 3b6909e0..a9303239 100644 --- a/src/test/resources/compare/errors/args.lua +++ b/src/test/resources/compare/errors/args.lua @@ -37,6 +37,7 @@ notatable = { nil, astring, anumber, aboolean, afunction, athread } notafunction = { nil, astring, anumber, aboolean, atable, athread } notathread = { nil, astring, anumber, aboolean, atable, afunction } notanil = { astring, anumber, aboolean, atable, afunction, athread } +notlen = { nil, anumber, aboolean, afunction, athread } nonstring = { aboolean, atable, afunction, athread } nonnumber = { astring, aboolean, atable, afunction, athread } diff --git a/src/test/resources/compare/errors/baselibargs.lua b/src/test/resources/compare/errors/baselibargs.lua index dfb0531f..e1c9180d 100644 --- a/src/test/resources/compare/errors/baselibargs.lua +++ b/src/test/resources/compare/errors/baselibargs.lua @@ -157,7 +157,9 @@ banner('unpack') checkallpass('unpack', { sometable }) checkallpass('unpack', { sometable, { 3, '5' } }) checkallpass('unpack', { sometable, { 3, '5' }, { 1.25, '7' } }) -checkallerrors('unpack', { notatable, somenumber, somenumber }, 'bad argument') +checkallpass('unpack', { notatable, { 2 }, { 1 } }) +checkallerrors('unpack', { notlen }, 'attempt to get length of') +checkallerrors('unpack', { notatable, { 1 }, { 2 } }, 'attempt to index') checkallerrors('unpack', { sometable, nonnumber, somenumber }, 'bad argument') checkallerrors('unpack', { sometable, somenumber, nonnumber }, 'bad argument') diff --git a/src/test/resources/compare/errors/baselibargs.out b/src/test/resources/compare/errors/baselibargs.out index dd3b81c9..6612d1ed 100644 --- a/src/test/resources/compare/errors/baselibargs.out +++ b/src/test/resources/compare/errors/baselibargs.out @@ -559,31 +559,26 @@ true - unpack(,'5',1.25) - unpack(
    ,3,'7') nil,nil,nil,nil,nil - unpack(
    ,'5','7') nil,nil,nil ---- checkallerrors -- unpack(nil,1.25,1.25) ...bad argument... -- unpack('abc',1.25,1.25) ...bad argument... -- unpack(1.25,1.25,1.25) ...bad argument... -- unpack(true,1.25,1.25) ...bad argument... -- unpack(,1.25,1.25) ...bad argument... -- unpack(,1.25,1.25) ...bad argument... -- unpack(nil,'789',1.25) ...bad argument... -- unpack('abc','789',1.25) ...bad argument... -- unpack(1.25,'789',1.25) ...bad argument... -- unpack(true,'789',1.25) ...bad argument... -- unpack(,'789',1.25) ...bad argument... -- unpack(,'789',1.25) ...bad argument... -- unpack(nil,1.25,'789') ...bad argument... -- unpack('abc',1.25,'789') ...bad argument... -- unpack(1.25,1.25,'789') ...bad argument... -- unpack(true,1.25,'789') ...bad argument... -- unpack(,1.25,'789') ...bad argument... -- unpack(,1.25,'789') ...bad argument... -- unpack(nil,'789','789') ...bad argument... -- unpack('abc','789','789') ...bad argument... -- unpack(1.25,'789','789') ...bad argument... -- unpack(true,'789','789') ...bad argument... -- unpack(,'789','789') ...bad argument... -- unpack(,'789','789') ...bad argument... +--- checkallpass +- unpack(nil,2,1) +- unpack('abc',2,1) +- unpack(1.25,2,1) +- unpack(true,2,1) +- unpack(,2,1) +- unpack(,2,1) +--- checkallerrors +- unpack(nil) ...attempt to get length of... +- unpack(1.25) ...attempt to get length of... +- unpack(true) ...attempt to get length of... +- unpack() ...attempt to get length of... +- unpack() ...attempt to get length of... +--- checkallerrors +- unpack(nil,1,2) ...attempt to index... +needcheck unpack('abc',1,2) nil +- unpack(1.25,1,2) ...attempt to index... +- unpack(true,1,2) ...attempt to index... +- unpack(,1,2) ...attempt to index... +- unpack(,1,2) ...attempt to index... --- checkallerrors - unpack(
    ,'abc',1.25) ...bad argument... - unpack(
    ,true,1.25) ...bad argument... diff --git a/src/test/resources/coroutine/debug.lua b/src/test/resources/coroutine/debug.lua index 13c5a0e6..5629d6cd 100644 --- a/src/test/resources/coroutine/debug.lua +++ b/src/test/resources/coroutine/debug.lua @@ -18,8 +18,8 @@ run(function() end)) debug.sethook(nil) - assertEquals(5, counts.call) - assertEquals(5, counts['return']) + assertEquals(3, counts.call) + assertEquals(3, counts['return']) assertEquals(36, counts.count) assertEquals(13, counts.line) end)