From 89adc082f89ab3c41c19a8529f5dd367e027f463 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Sat, 25 Feb 2023 11:04:02 +0000 Subject: [PATCH] Finally remove LibFunction.opcode - Change unpack to be indentical to table.unpack. Lua 5.1's unpack doesn't __len/__index (and so only works on tables, rather than userdata), but I don't think will break any existing code. - Remove several of our platform abstractions, and always just use the underlying OS API calls. This code only exists for passing the Lua tests (it's not used in CC at all), so the additional flexibility is not really useful. - Similarly, split BaseLib into a safe subset (as used by CC) and the full version as needed for tests. - Move the compiler interface to only generate functions from prototypes, rather than starting from scratch. We could remove it (luaj.luajc is long dead), I do have plans to have another go at using it. - Some small bits of cleanup while I was in the area. --- .../java/org/squiddev/cobalt/Constants.java | 5 + .../org/squiddev/cobalt/GlobalRegistry.java | 36 ++ .../java/org/squiddev/cobalt/LuaState.java | 185 +------- .../java/org/squiddev/cobalt/LuaTable.java | 16 - .../java/org/squiddev/cobalt/LuaThread.java | 6 +- .../java/org/squiddev/cobalt/LuaValue.java | 10 +- .../java/org/squiddev/cobalt/cmd/lua.java | 229 ---------- .../java/org/squiddev/cobalt/cmd/luac.java | 188 -------- .../org/squiddev/cobalt/compiler/BinOpr.java | 72 ++-- .../{DumpState.java => BytecodeDumper.java} | 34 +- .../cobalt/compiler/BytecodeLoader.java | 5 +- .../cobalt/compiler/CompileException.java | 13 +- .../squiddev/cobalt/compiler/FuncState.java | 2 +- .../org/squiddev/cobalt/compiler/IntPtr.java | 2 +- .../org/squiddev/cobalt/compiler/Lex.java | 2 +- .../squiddev/cobalt/compiler/LoadState.java | 99 ++--- .../org/squiddev/cobalt/compiler/LuaC.java | 67 +-- .../org/squiddev/cobalt/compiler/Parser.java | 2 +- .../org/squiddev/cobalt/compiler/UnOpr.java | 24 +- .../squiddev/cobalt/function/LibFunction.java | 35 +- .../function/LuaInterpretedFunction.java | 8 +- .../java/org/squiddev/cobalt/lib/BaseLib.java | 315 +++++--------- .../org/squiddev/cobalt/lib/Bit32Lib.java | 15 +- .../{LuaLibrary.java => CoreLibraries.java} | 45 +- .../org/squiddev/cobalt/lib/CoroutineLib.java | 160 ++++--- .../org/squiddev/cobalt/lib/DebugLib.java | 31 +- .../java/org/squiddev/cobalt/lib/MathLib.java | 12 +- .../org/squiddev/cobalt/lib/StringFormat.java | 11 +- .../org/squiddev/cobalt/lib/StringLib.java | 22 +- .../org/squiddev/cobalt/lib/TableLib.java | 407 +++++++++--------- .../cobalt/lib/UncheckedLuaError.java | 4 +- .../java/org/squiddev/cobalt/lib/Utf8Lib.java | 11 +- .../squiddev/cobalt/lib/doubles/Assert.java | 37 +- .../cobalt/lib/doubles/BigNumDtoa.java | 84 ++-- .../squiddev/cobalt/lib/doubles/Bignum.java | 2 +- .../cobalt/lib/doubles/DecimalRepBuf.java | 2 +- .../squiddev/cobalt/lib/doubles/DiyFp.java | 6 +- .../lib/doubles/DoubleToStringConverter.java | 34 +- .../squiddev/cobalt/lib/doubles/FastDtoa.java | 52 +-- .../cobalt/lib/doubles/FixedDtoa.java | 18 +- .../org/squiddev/cobalt/lib/doubles/Ieee.java | 13 +- .../cobalt/lib/doubles/PowersOfTenCache.java | 21 +- .../cobalt/lib/doubles/UnsignedValues.java | 2 +- .../org/squiddev/cobalt/lib/jse/JseIoLib.java | 227 ---------- .../squiddev/cobalt/lib/jse/JsePlatform.java | 111 ----- .../squiddev/cobalt/lib/jse/JseProcess.java | 137 ------ .../platform/AbstractResourceManipulator.java | 54 --- .../lib/platform/FileResourceManipulator.java | 99 ----- .../lib/platform/ResourceManipulator.java | 117 ----- .../cobalt/lib/{ => system}/IoLib.java | 351 ++++++++------- .../cobalt/lib/{ => system}/OsLib.java | 300 +++++++------ .../cobalt/lib/{ => system}/PackageLib.java | 179 +++----- .../ResourceLoader.java} | 51 +-- .../cobalt/lib/system/SystemBaseLib.java | 115 +++++ .../cobalt/lib/system/SystemLibraries.java | 62 +++ .../java/org/squiddev/cobalt/AssertTests.java | 6 +- .../java/org/squiddev/cobalt/CompareTest.java | 5 +- .../org/squiddev/cobalt/CoroutineTest.java | 94 ++-- .../squiddev/cobalt/OrphanedThreadTest.java | 4 +- .../org/squiddev/cobalt/RequireClassTest.java | 4 +- .../org/squiddev/cobalt/ScriptHelper.java | 111 +++-- .../cobalt/compiler/CompilerUnitTests.java | 18 +- .../compiler/DumpLoadEndianIntTest.java | 62 +-- .../squiddev/cobalt/compiler/SimpleTests.java | 8 +- .../cobalt/lib/doubles/BignumTest.java | 6 - .../cobalt/lib/doubles/DiyFpTest.java | 7 - .../doubles/DoubleToStringConverterTest.java | 6 - .../squiddev/cobalt/lib/doubles/DtoaTest.java | 54 +-- .../cobalt/lib/doubles/FastDtoaTest.java | 51 +-- .../cobalt/lib/doubles/FixedDtoaTest.java | 43 +- .../squiddev/cobalt/lib/doubles/IeeeTest.java | 7 - .../cobalt/lib/doubles/UInt128Test.java | 7 - .../squiddev/cobalt/vm/LuaOperationsTest.java | 4 +- .../org/squiddev/cobalt/vm/MetatableTest.java | 13 +- .../org/squiddev/cobalt/vm/StringTest.java | 4 +- src/test/resources/compare/baselib.lua | 2 +- src/test/resources/compare/baselib.out | 2 +- src/test/resources/compare/errors/args.lua | 1 + .../resources/compare/errors/baselibargs.lua | 4 +- .../resources/compare/errors/baselibargs.out | 45 +- src/test/resources/coroutine/debug.lua | 4 +- 81 files changed, 1655 insertions(+), 3064 deletions(-) create mode 100644 src/main/java/org/squiddev/cobalt/GlobalRegistry.java delete mode 100644 src/main/java/org/squiddev/cobalt/cmd/lua.java delete mode 100644 src/main/java/org/squiddev/cobalt/cmd/luac.java rename src/main/java/org/squiddev/cobalt/compiler/{DumpState.java => BytecodeDumper.java} (90%) rename src/main/java/org/squiddev/cobalt/lib/{LuaLibrary.java => CoreLibraries.java} (51%) delete mode 100644 src/main/java/org/squiddev/cobalt/lib/jse/JseIoLib.java delete mode 100644 src/main/java/org/squiddev/cobalt/lib/jse/JsePlatform.java delete mode 100644 src/main/java/org/squiddev/cobalt/lib/jse/JseProcess.java delete mode 100644 src/main/java/org/squiddev/cobalt/lib/platform/AbstractResourceManipulator.java delete mode 100644 src/main/java/org/squiddev/cobalt/lib/platform/FileResourceManipulator.java delete mode 100644 src/main/java/org/squiddev/cobalt/lib/platform/ResourceManipulator.java rename src/main/java/org/squiddev/cobalt/lib/{ => system}/IoLib.java (63%) rename src/main/java/org/squiddev/cobalt/lib/{ => system}/OsLib.java (58%) rename src/main/java/org/squiddev/cobalt/lib/{ => system}/PackageLib.java (71%) rename src/main/java/org/squiddev/cobalt/lib/{platform/VoidResourceManipulator.java => system/ResourceLoader.java} (65%) create mode 100644 src/main/java/org/squiddev/cobalt/lib/system/SystemBaseLib.java create mode 100644 src/main/java/org/squiddev/cobalt/lib/system/SystemLibraries.java 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)