blob: f10556888b32e71390ae5b51abad177259086893 [file] [log] [blame] [raw]
/*******************************************************************************
* 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.vm3;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.Reader;
import org.luaj.vm3.lib.BaseLib;
import org.luaj.vm3.lib.DebugLib;
import org.luaj.vm3.lib.PackageLib;
import org.luaj.vm3.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.vm3.lib.jse.JsePlatform
* @see org.luaj.vm3.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 BufferedStream(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;
final int n;
StrReader(String s) {
this.s = s;
n = s.length();
}
public void close() throws IOException {
i = n;
}
public int read() throws IOException {
return i < n ? s.charAt(i++) : -1;
}
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;
}
}
/* Abstract base class to provide basic buffered input storage and delivery.
* This class may be moved to its own package in the future.
*/
abstract static class AbstractBufferedStream extends InputStream {
protected byte[] b;
protected int i = 0, j = 0;
protected AbstractBufferedStream(int buflen) {
this.b = new byte[buflen];
}
abstract protected int avail() throws IOException;
public int read() throws IOException {
int a = avail();
return (a <= 0 ? -1 : 0xff & b[i++]);
}
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
public int read(byte[] b, int i0, int n) throws IOException {
int a = avail();
if (a <= 0) return -1;
final int n_read = Math.min(a, n);
System.arraycopy(this.b, i, b, i0, n_read);
i += n_read;
return n_read;
}
public long skip(long n) throws IOException {
final long k = Math.min(n, j - i);
i += k;
return k;
}
public int available() throws IOException {
return j - i;
}
}
/** Simple converter from Reader to InputStream using UTF8 encoding that will work
* on both JME and JSE.
* This class may be moved to its own package in the future.
*/
static class UTF8Stream extends AbstractBufferedStream {
private final char[] c = new char[32];
private final Reader r;
UTF8Stream(Reader r) {
super(96);
this.r = r;
}
protected int avail() throws IOException {
if (i < j) return j - i;
int n = r.read(c);
if (n < 0)
return -1;
if (n == 0) {
int u = r.read();
if (u < 0)
return -1;
c[0] = (char) u;
n = 1;
}
j = LuaString.encodeToUtf8(c, n, b, i = 0);
return j;
}
public void close() throws IOException {
r.close();
}
}
/** Simple buffered 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,
* as well as speed up normal compilation and reading of lua scripts.
* This class may be moved to its own package in the future.
*/
static class BufferedStream extends AbstractBufferedStream {
private final InputStream s;
public BufferedStream(InputStream s) {
this(128, s);
}
BufferedStream(int buflen, InputStream s) {
super(buflen);
this.s = s;
}
protected int avail() throws IOException {
if (i < j) return j - i;
if (j >= b.length) i = j = 0;
// leave previous bytes in place to implement mark()/reset().
int n = s.read(b, j, b.length - j);
if (n < 0)
return -1;
if (n == 0) {
int u = s.read();
if (u < 0)
return -1;
b[j] = (byte) u;
n = 1;
}
j += n;
return n;
}
public void close() throws IOException {
s.close();
}
public synchronized void mark(int n) {
if (i > 0 || n > b.length) {
byte[] dest = n > b.length ? new byte[n] : b;
System.arraycopy(b, i, dest, 0, j - i);
j -= i;
i = 0;
b = dest;
}
}
public boolean markSupported() {
return true;
}
public synchronized void reset() throws IOException {
i = 0;
}
}
}