|  | /******************************************************************************* | 
|  | * Copyright (c) 2012 Luaj.org. All rights reserved. | 
|  | * | 
|  | * Permission is hereby granted, free of charge, to any person obtaining a copy | 
|  | * of this software and associated documentation files (the "Software"), to deal | 
|  | * in the Software without restriction, including without limitation the rights | 
|  | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | 
|  | * copies of the Software, and to permit persons to whom the Software is | 
|  | * furnished to do so, subject to the following conditions: | 
|  | * | 
|  | * The above copyright notice and this permission notice shall be included in | 
|  | * all copies or substantial portions of the Software. | 
|  | * | 
|  | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | 
|  | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | 
|  | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | 
|  | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | 
|  | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | 
|  | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | 
|  | * THE SOFTWARE. | 
|  | ******************************************************************************/ | 
|  | package org.luaj.vm2; | 
|  |  | 
|  | import java.io.IOException; | 
|  | import java.io.InputStream; | 
|  | import java.io.PrintStream; | 
|  | import java.io.Reader; | 
|  |  | 
|  | import org.luaj.vm2.lib.BaseLib; | 
|  | import org.luaj.vm2.lib.DebugLib; | 
|  | import org.luaj.vm2.lib.PackageLib; | 
|  | import org.luaj.vm2.lib.ResourceFinder; | 
|  |  | 
|  | /** | 
|  | * Global environment used by luaj.  Contains global variables referenced by executing lua. | 
|  | * <p> | 
|  | * | 
|  | * <h3>Constructing and Initializing Instances</h3> | 
|  | * Typically, this is constructed indirectly by a call to | 
|  | * {@link JsePlatform.standardGlobasl()} or {@link JmePlatform.standardGlobals()}, | 
|  | * and then used to load lua scripts for execution as in the following example. | 
|  | * <pre> {@code | 
|  | * Globals globals = JsePlatform.standardGlobals(); | 
|  | * globals.load( new StringReader("print 'hello'"), "main.lua" ).call(); | 
|  | * } </pre> | 
|  | * The creates a complete global environment with the standard libraries loaded. | 
|  | * <p> | 
|  | * For specialized circumstances, the Globals may be constructed directly and loaded | 
|  | * with only those libraries that are needed, for example. | 
|  | * <pre> {@code | 
|  | * Globals globals = new Globals(); | 
|  | * globals.load( new BaseLib() ); | 
|  | * } </pre> | 
|  | * | 
|  | * <h3>Loading and Executing Lua Code</h3> | 
|  | * Globals contains convenience functions to load and execute lua source code given a Reader. | 
|  | * A simple example is: | 
|  | * <pre> {@code | 
|  | * globals.load( new StringReader("print 'hello'"), "main.lua" ).call(); | 
|  | * } </pre> | 
|  | * | 
|  | * <h3>Fine-Grained Control of Compiling and Loading Lua</h3> | 
|  | * Executable LuaFunctions are created from lua code in several steps | 
|  | * <ul> | 
|  | * <li>find the resource using the platform's {@link ResourceFinder} | 
|  | * <li>compile lua to lua bytecode using {@link Compiler} | 
|  | * <li>load lua bytecode to a {@link LuaPrototpye} using {@link Undumper} | 
|  | * <li>construct {@link LuaClosure} from {@link Prototype} with {@link Globals} using {@link Loader} | 
|  | * </ul> | 
|  | * <p> | 
|  | * There are alternate flows when the direct lua-to-Java bytecode compiling {@link LuaJC} is used. | 
|  | * <ul> | 
|  | * <li>compile lua to lua bytecode using {@link Compiler} or load precompiled code using {@link Undumper} | 
|  | * <li>convert lua bytecode to equivalent Java bytecode using {@link LuaJC} that implements {@link Loader} directly | 
|  | * </ul> | 
|  | * | 
|  | * <h3>Java Field</h3> | 
|  | * Certain public fields are provided that contain the current values of important global state: | 
|  | * <ul> | 
|  | * <li>{@link STDIN} Current value for standard input in the laaded IoLib, if any. | 
|  | * <li>{@link STDOUT} Current value for standard output in the loaded IoLib, if any. | 
|  | * <li>{@link STDERR} Current value for standard error in the loaded IoLib, if any. | 
|  | * <li>{@link FINDER} Current loaded {@link ResourceFinder}, if any. | 
|  | * <li>{@link compiler} Current loaded {@link Compiler}, if any. | 
|  | * <li>{@link undumper} Current loaded {@link Undumper}, if any. | 
|  | * <li>{@link loader} Current loaded {@link Loader}, if any. | 
|  | * </ul> | 
|  | * | 
|  | * <h3>Lua Environment Variables</h3> | 
|  | * When using {@link JsePlatform} or {@link JmePlatform}, | 
|  | * these environment variables are created within the Globals. | 
|  | * <ul> | 
|  | * <li>"_G" Pointer to this Globals. | 
|  | * <li>"_VERSION" String containing the version of luaj. | 
|  | * </ul> | 
|  | * | 
|  | * <h3>Use in Multithreaded Environments</h3> | 
|  | * In a multi-threaded server environment, each server thread should create one Globals instance, | 
|  | * which will be logically distinct and not interfere with each other, but share certain | 
|  | * static immutable resources such as class data and string data. | 
|  | * <p> | 
|  | * | 
|  | * @see org.luaj.vm2.lib.jse.JsePlatform | 
|  | * @see org.luaj.vm2.lib.jme.JmePlatform | 
|  | * @see LuaValue | 
|  | * @see Compiler | 
|  | * @see Loader | 
|  | * @see Undumper | 
|  | * @see ResourceFinder | 
|  | * @see LuaC | 
|  | * @see LuaJC | 
|  | */ | 
|  | public class Globals extends LuaTable { | 
|  |  | 
|  | /** The current default input stream. */ | 
|  | public InputStream STDIN  = null; | 
|  |  | 
|  | /** The current default output stream. */ | 
|  | public PrintStream STDOUT = System.out; | 
|  |  | 
|  | /** The current default error stream. */ | 
|  | public PrintStream STDERR = System.err; | 
|  |  | 
|  | /** The installed ResourceFinder for looking files by name. */ | 
|  | public ResourceFinder FINDER; | 
|  |  | 
|  | /** The currently running thread.  Should not be changed by non-library code. */ | 
|  | public LuaThread running = new LuaThread(this); | 
|  |  | 
|  | /** The BaseLib instance loaded into this Globals */ | 
|  | public BaseLib baselib; | 
|  |  | 
|  | /** The PackageLib instance loaded into this Globals */ | 
|  | public PackageLib package_; | 
|  |  | 
|  | /** The DebugLib instance loaded into this Globals, or null if debugging is not enabled */ | 
|  | public DebugLib debuglib; | 
|  |  | 
|  | /** Interface for module that converts a Prototype into a LuaFunction with an environment. */ | 
|  | public interface Loader { | 
|  | /** Convert the prototype into a LuaFunction with the supplied environment. */ | 
|  | LuaFunction load(Prototype prototype, String chunkname, LuaValue env) throws IOException; | 
|  | } | 
|  |  | 
|  | /** Interface for module that converts lua source text into a prototype. */ | 
|  | public interface Compiler { | 
|  | /** Compile lua source into a Prototype. The InputStream is assumed to be in UTF-8. */ | 
|  | Prototype compile(InputStream stream, String chunkname) throws IOException; | 
|  | } | 
|  |  | 
|  | /** Interface for module that loads lua binary chunk into a prototype. */ | 
|  | public interface Undumper { | 
|  | /** Load the supplied input stream into a prototype. */ | 
|  | Prototype undump(InputStream stream, String chunkname) throws IOException; | 
|  | } | 
|  |  | 
|  | /** Check that this object is a Globals object, and return it, otherwise throw an error. */ | 
|  | public Globals checkglobals() { | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /** The installed loader. | 
|  | * @see Loader */ | 
|  | public Loader loader; | 
|  |  | 
|  | /** The installed compiler. | 
|  | * @see Compiler */ | 
|  | public Compiler compiler; | 
|  |  | 
|  | /** The installed undumper. | 
|  | * @see Undumper */ | 
|  | public Undumper undumper; | 
|  |  | 
|  | /** Convenience function for loading a file that is either binary lua or lua source. | 
|  | * @param filename Name of the file to load. | 
|  | * @return LuaValue that can be call()'ed or invoke()'ed. | 
|  | * @throws LuaError if the file could not be loaded. | 
|  | */ | 
|  | public LuaValue loadfile(String filename) { | 
|  | try { | 
|  | return load(FINDER.findResource(filename), "@"+filename, "bt", this); | 
|  | } catch (Exception e) { | 
|  | return error("load "+filename+": "+e); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** Convenience function to load a string value as a script.  Must be lua source. | 
|  | * @param script Contents of a lua script, such as "print 'hello, world.'" | 
|  | * @param chunkname Name that will be used within the chunk as the source. | 
|  | * @return LuaValue that may be executed via .call(), .invoke(), or .method() calls. | 
|  | * @throws LuaError if the script could not be compiled. | 
|  | */ | 
|  | public LuaValue load(String script, String chunkname) { | 
|  | return load(new StrReader(script), chunkname); | 
|  | } | 
|  |  | 
|  | /** Convenience function to load a string value as a script.  Must be lua source. | 
|  | * @param script Contents of a lua script, such as "print 'hello, world.'" | 
|  | * @return LuaValue that may be executed via .call(), .invoke(), or .method() calls. | 
|  | * @throws LuaError if the script could not be compiled. | 
|  | */ | 
|  | public LuaValue load(String script) { | 
|  | return load(new StrReader(script), script); | 
|  | } | 
|  |  | 
|  | /** Load the content form a reader as a text file.  Must be lua source. | 
|  | * The source is converted to UTF-8, so any characters appearing in quoted literals | 
|  | * above the range 128 will be converted into multiple bytes.  */ | 
|  | public LuaValue load(Reader reader, String chunkname) { | 
|  | return load(new UTF8Stream(reader), chunkname, "t", this); | 
|  | } | 
|  |  | 
|  | /** Load the content form an input stream as a binary chunk or text file. */ | 
|  | public LuaValue load(InputStream is, String chunkname, String mode, LuaValue env) { | 
|  | try { | 
|  | Prototype p = loadPrototype(is, chunkname, mode); | 
|  | return loader.load(p, chunkname, env); | 
|  | } catch (LuaError l) { | 
|  | throw l; | 
|  | } catch (Exception e) { | 
|  | return error("load "+chunkname+": "+e); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** Load lua source or lua binary from an input stream into a Prototype. | 
|  | * The InputStream is either a binary lua chunk starting with the lua binary chunk signature, | 
|  | * or a text input file.  If it is a text input file, it is interpreted as a UTF-8 byte sequence. | 
|  | */ | 
|  | public Prototype loadPrototype(InputStream is, String chunkname, String mode) throws IOException { | 
|  | if (mode.indexOf('b') >= 0) { | 
|  | if (undumper == null) | 
|  | error("No undumper."); | 
|  | if (!is.markSupported()) | 
|  | is = new MarkStream(is); | 
|  | is.mark(4); | 
|  | final Prototype p = undumper.undump(is, chunkname); | 
|  | if (p != null) | 
|  | return p; | 
|  | is.reset(); | 
|  | } | 
|  | if (mode.indexOf('t') >= 0) { | 
|  | return compilePrototype(is, chunkname); | 
|  | } | 
|  | error("Failed to load prototype "+chunkname+" using mode '"+mode+"'"); | 
|  | return null; | 
|  | } | 
|  |  | 
|  | /** Compile lua source from a Reader into a Prototype. The characters in the reader | 
|  | * are converted to bytes using the UTF-8 encoding, so a string literal containing | 
|  | * characters with codepoints 128 or above will be converted into multiple bytes. | 
|  | */ | 
|  | public Prototype compilePrototype(Reader reader, String chunkname) throws IOException { | 
|  | return compilePrototype(new UTF8Stream(reader), chunkname); | 
|  | } | 
|  |  | 
|  | /** Compile lua source from an InputStream into a Prototype. | 
|  | * The input is assumed to be UTf-8, but since bytes in the range 128-255 are passed along as | 
|  | * literal bytes, any ASCII-compatible encoding such as ISO 8859-1 may also be used. | 
|  | */ | 
|  | public Prototype compilePrototype(InputStream stream, String chunkname) throws IOException { | 
|  | if (compiler == null) | 
|  | error("No compiler."); | 
|  | return compiler.compile(stream, chunkname); | 
|  | } | 
|  |  | 
|  | /** Function which yields the current thread. | 
|  | * @param args  Arguments to supply as return values in the resume function of the resuming thread. | 
|  | * @return Values supplied as arguments to the resume() call that reactivates this thread. | 
|  | */ | 
|  | public Varargs yield(Varargs args) { | 
|  | if (running == null || running.isMainThread()) | 
|  | throw new LuaError("cannot yield main thread"); | 
|  | final LuaThread.State s = running.state; | 
|  | return s.lua_yield(args); | 
|  | } | 
|  |  | 
|  | /** Reader implementation to read chars from a String in JME or JSE. */ | 
|  | static class StrReader extends Reader { | 
|  | final String s; | 
|  | int i = 0, n; | 
|  | StrReader(String s) { | 
|  | this.s = s; | 
|  | n = s.length(); | 
|  | } | 
|  | public void close() throws IOException { | 
|  | i = n; | 
|  | } | 
|  | public int read(char[] cbuf, int off, int len) throws IOException { | 
|  | int j = 0; | 
|  | for (; j < len && i < n; ++j, ++i) | 
|  | cbuf[off+j] = s.charAt(i); | 
|  | return j > 0 || len == 0 ? j : -1; | 
|  | } | 
|  | } | 
|  |  | 
|  | /**  Simple converter from Reader to InputStream using UTF8 encoding that will work | 
|  | * on both JME and JSE. | 
|  | */ | 
|  | static class UTF8Stream extends InputStream { | 
|  | final char[] c = new char[32]; | 
|  | final byte[] b = new byte[96]; | 
|  | int i = 0, j = 0; | 
|  | final Reader r; | 
|  | UTF8Stream(Reader r) { | 
|  | this.r = r; | 
|  | } | 
|  | public int read() throws IOException { | 
|  | if (i < j) | 
|  | return c[i++]; | 
|  | int n = r.read(c); | 
|  | if (n < 0) | 
|  | return -1; | 
|  | j = LuaString.encodeToUtf8(c, n, b, i = 0); | 
|  | return b[i++]; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** Simple InputStream that supports mark. | 
|  | * Used to examine an InputStream for a 4-byte binary lua signature, | 
|  | * and fall back to text input when the signature is not found. | 
|  | */ | 
|  | static class MarkStream extends InputStream { | 
|  | private int[] b; | 
|  | private int i = 0, j = 0; | 
|  | private final InputStream s; | 
|  | MarkStream(InputStream s) { | 
|  | this.s = s; | 
|  | } | 
|  | public int read() throws IOException { | 
|  | if (i < j) | 
|  | return b[i++]; | 
|  | final int c = s.read(); | 
|  | if (c < 0) | 
|  | return -1; | 
|  | if (j < b.length) { | 
|  | b[j++] = c; | 
|  | i = j; | 
|  | } | 
|  | return c; | 
|  | } | 
|  | public synchronized void mark(int n) { | 
|  | b = new int[n]; | 
|  | i = j = 0; | 
|  | } | 
|  | public boolean markSupported() { | 
|  | return true; | 
|  | } | 
|  | public synchronized void reset() throws IOException { | 
|  | i = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | } |