added debugging support and integrated with Eclipse debugger
This commit is contained in:
@@ -1,311 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2007 LuaJ. 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 lua;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import lua.addon.luacompat.LuaCompat;
|
||||
import lua.addon.luajava.LuaJava;
|
||||
import lua.debug.DebugEvent;
|
||||
import lua.debug.DebugEventType;
|
||||
import lua.debug.DebugRequest;
|
||||
import lua.debug.DebugRequestListener;
|
||||
import lua.debug.DebugResponse;
|
||||
import lua.debug.DebugStackState;
|
||||
import lua.debug.DebugSupport;
|
||||
import lua.debug.DebugUtils;
|
||||
import lua.io.Closure;
|
||||
import lua.io.LoadState;
|
||||
import lua.io.Proto;
|
||||
import lua.value.LString;
|
||||
import lua.value.LValue;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.CommandLineParser;
|
||||
import org.apache.commons.cli.GnuParser;
|
||||
import org.apache.commons.cli.HelpFormatter;
|
||||
import org.apache.commons.cli.OptionBuilder;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
|
||||
/**
|
||||
* LuaJVM executes a lua program in normal run mode or debug mode.
|
||||
*
|
||||
* @author: Shu Lei
|
||||
* @version: 1.0
|
||||
*/
|
||||
public class LuaJVM implements DebugRequestListener {
|
||||
protected Options options = new Options();
|
||||
protected boolean isDebugMode = false;
|
||||
protected DebugSupport debugSupport;
|
||||
protected int requestPort;
|
||||
protected int eventPort;
|
||||
protected String script;
|
||||
protected String[] scriptArgs;
|
||||
protected StackState state;
|
||||
protected boolean isReady = false;
|
||||
protected boolean isTerminated = false;
|
||||
|
||||
@SuppressWarnings("static-access")
|
||||
public LuaJVM() {
|
||||
options.addOption(OptionBuilder.withArgName("requestPort eventPort").
|
||||
hasArgs(2).
|
||||
isRequired(false).
|
||||
withValueSeparator(' ').
|
||||
withDescription("run LuaJ VM in debug mode").
|
||||
create("debug"));
|
||||
options.addOption(OptionBuilder.withArgName("LuaJProgram").
|
||||
withDescription("lua program to be executed").
|
||||
isRequired().
|
||||
hasArgs().
|
||||
withValueSeparator(' ').
|
||||
create("file"));
|
||||
}
|
||||
|
||||
protected void printUsage() {
|
||||
HelpFormatter formatter = new HelpFormatter();
|
||||
formatter.printHelp("java LuaJVM", options);
|
||||
}
|
||||
|
||||
protected void parse(String[] args) throws ParseException {
|
||||
CommandLineParser parser = new GnuParser();
|
||||
try {
|
||||
CommandLine line = parser.parse(options, args);
|
||||
if (line.hasOption("debug")) {
|
||||
this.isDebugMode = true;
|
||||
String[] ports = line.getOptionValues("debug");
|
||||
|
||||
this.requestPort = Integer.parseInt(ports[0]);
|
||||
if (this.requestPort <= 0) {
|
||||
throw new ParseException("Invalid request port: it must be greater than zero.");
|
||||
}
|
||||
|
||||
this.eventPort = Integer.parseInt(ports[1]);
|
||||
if (this.eventPort <= 0) {
|
||||
throw new ParseException("Invalid event port: it must be greater than zero.");
|
||||
}
|
||||
|
||||
if (this.requestPort == this.eventPort) {
|
||||
throw new ParseException("Invalid ports: request port and event port must be different");
|
||||
}
|
||||
}
|
||||
|
||||
if (line.hasOption("file")) {
|
||||
String[] fileArgs = line.getOptionValues("file");
|
||||
this.script = URLDecoder.decode(fileArgs[0], "UTF-8");
|
||||
DebugUtils.println("Lua script to run: " + this.script);
|
||||
this.scriptArgs = new String[fileArgs.length - 1];
|
||||
for (int i = 1; i < fileArgs.length; i++) {
|
||||
this.scriptArgs[i-1] = URLDecoder.decode(fileArgs[i], "UTF-8");
|
||||
}
|
||||
}
|
||||
} catch(NumberFormatException e) {
|
||||
throw new ParseException("Invalid port number: " + e.getMessage());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new ParseException("Malformed program argument strings: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isDebug() {
|
||||
return this.isDebugMode;
|
||||
}
|
||||
|
||||
protected int getRequestPort() {
|
||||
return this.requestPort;
|
||||
}
|
||||
|
||||
protected int getEventPort() {
|
||||
return this.eventPort;
|
||||
}
|
||||
|
||||
protected String getScript() {
|
||||
return this.script;
|
||||
}
|
||||
|
||||
protected boolean hasScriptArgs() {
|
||||
return (this.scriptArgs != null && this.scriptArgs.length > 0);
|
||||
}
|
||||
|
||||
protected String[] getScriptArgs() {
|
||||
return this.scriptArgs;
|
||||
}
|
||||
|
||||
public void run() throws IOException {
|
||||
if (isDebug()) {
|
||||
doDebug();
|
||||
} else {
|
||||
doRun();
|
||||
}
|
||||
}
|
||||
|
||||
protected void init() {
|
||||
// reset global states
|
||||
GlobalState.resetGlobals();
|
||||
|
||||
// add LuaJava bindings
|
||||
LuaJava.install();
|
||||
|
||||
// add LuaCompat bindings
|
||||
LuaCompat.install();
|
||||
}
|
||||
|
||||
public void doRun() throws IOException {
|
||||
init();
|
||||
|
||||
// new lua state
|
||||
state = new StackState();
|
||||
|
||||
// convert args to lua
|
||||
int numOfScriptArgs = getScriptArgs().length;
|
||||
LValue[] vargs = new LValue[numOfScriptArgs];
|
||||
for (int i = 0; i < numOfScriptArgs; i++) {
|
||||
vargs[i] = new LString(getScriptArgs()[i]);
|
||||
}
|
||||
|
||||
// load the Lua file
|
||||
DebugUtils.println("loading Lua script '" + getScript() + "'");
|
||||
InputStream is = new FileInputStream(new File(getScript()));
|
||||
Proto p = LoadState.undump(state, is, getScript());
|
||||
|
||||
// create closure and execute
|
||||
Closure c = new Closure(state, p);
|
||||
state.doCall(c, vargs);
|
||||
}
|
||||
|
||||
private void doDebug() throws IOException {
|
||||
DebugUtils.println("start debugging...");
|
||||
this.debugSupport = new DebugSupport(this, getRequestPort(), getEventPort());
|
||||
DebugUtils.println("created client request socket connection...");
|
||||
debugSupport.start();
|
||||
|
||||
DebugUtils.println("setting up LuaJava and debug stack state...");
|
||||
|
||||
init();
|
||||
|
||||
// new lua state
|
||||
state = new DebugStackState();
|
||||
getDebugState().addDebugEventListener(debugSupport);
|
||||
|
||||
// load the Lua file
|
||||
DebugUtils.println("loading Lua script '" + getScript() + "'");
|
||||
InputStream is = new FileInputStream(new File(getScript()));
|
||||
Proto p = LoadState.undump(state, is, getScript());
|
||||
|
||||
// create closure and execute
|
||||
final Closure c = new Closure(state, p);
|
||||
getDebugState().suspend();
|
||||
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
int numOfScriptArgs = getScriptArgs().length;
|
||||
LValue[] vargs = new LValue[numOfScriptArgs];
|
||||
for (int i = 0; i < numOfScriptArgs; i++) {
|
||||
vargs[i] = new LString(getScriptArgs()[i]);
|
||||
}
|
||||
|
||||
getDebugState().doCall(c, vargs);
|
||||
stop();
|
||||
}
|
||||
}).start();
|
||||
|
||||
debugSupport.fireEvent(new DebugEvent(DebugEventType.started));
|
||||
}
|
||||
|
||||
private DebugStackState getDebugState() {
|
||||
return (DebugStackState)state;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see lua.debug.DebugRequestListener#handleRequest(java.lang.String)
|
||||
*/
|
||||
public DebugResponse handleRequest(DebugRequest request) {
|
||||
if (!isDebug()) {
|
||||
throw new UnsupportedOperationException("Must be in debug mode to handle the debug requests");
|
||||
}
|
||||
|
||||
DebugUtils.println("handling request: " + request.toString());
|
||||
switch (request.getType()) {
|
||||
case suspend:
|
||||
DebugResponse status = getDebugState().handleRequest(request);
|
||||
DebugEvent event = new DebugEvent(DebugEventType.suspendedByClient);
|
||||
debugSupport.fireEvent(event);
|
||||
return status;
|
||||
case resume:
|
||||
status = getDebugState().handleRequest(request);
|
||||
event = new DebugEvent(DebugEventType.resumedByClient);
|
||||
debugSupport.fireEvent(event);
|
||||
return status;
|
||||
case exit:
|
||||
stop();
|
||||
default:
|
||||
return getDebugState().handleRequest(request);
|
||||
}
|
||||
}
|
||||
|
||||
protected void stop() {
|
||||
DebugUtils.println("exit LuaJ VM...");
|
||||
if (this.debugSupport != null) {
|
||||
DebugEvent event = new DebugEvent(DebugEventType.terminated);
|
||||
debugSupport.fireEvent(event);
|
||||
Timer timer = new Timer("DebugServerDeathThread");
|
||||
timer.schedule(new TimerTask() {
|
||||
public void run() {
|
||||
debugSupport.stop();
|
||||
debugSupport = null;
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
getDebugState().exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the command line arguments and executes/debugs the lua program.
|
||||
* @param args -- command line arguments:
|
||||
* [-debug requestPort eventPort] -file luaProgram args
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
LuaJVM vm = new LuaJVM();
|
||||
|
||||
try {
|
||||
vm.parse(args);
|
||||
} catch (ParseException e) {
|
||||
DebugUtils.println(e.getMessage());
|
||||
vm.printUsage();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
vm.run();
|
||||
} catch (IOException e) {
|
||||
//TODO: handle the error
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/main/java/lua/value/LBoolean.java
Normal file
38
src/main/java/lua/value/LBoolean.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package lua.value;
|
||||
|
||||
public final class LBoolean extends LValue {
|
||||
|
||||
public static final LBoolean TRUE = new LBoolean("true",true);
|
||||
|
||||
public static final LBoolean FALSE = new LBoolean("false",false);
|
||||
|
||||
public static final LString TYPE_NAME = new LString(Type.bool.toString());
|
||||
|
||||
private final LString m_name;
|
||||
private final boolean m_value;
|
||||
|
||||
private LBoolean( String name, boolean value ) {
|
||||
this.m_name = new LString( name );
|
||||
this.m_value = value;
|
||||
}
|
||||
|
||||
public final LString luaAsString() {
|
||||
return m_name;
|
||||
}
|
||||
|
||||
public final boolean luaAsBoolean() {
|
||||
return m_value;
|
||||
}
|
||||
|
||||
public final int luaAsInt() {
|
||||
return m_value? 1: 0;
|
||||
}
|
||||
|
||||
public final static LBoolean valueOf(boolean value) {
|
||||
return value? TRUE: FALSE;
|
||||
}
|
||||
|
||||
public LString luaGetType() {
|
||||
return TYPE_NAME;
|
||||
}
|
||||
}
|
||||
119
src/main/java/lua/value/LDouble.java
Normal file
119
src/main/java/lua/value/LDouble.java
Normal file
@@ -0,0 +1,119 @@
|
||||
package lua.value;
|
||||
|
||||
import lua.Lua;
|
||||
|
||||
public class LDouble extends LNumber {
|
||||
|
||||
private final double m_value;
|
||||
|
||||
public LDouble(double value) {
|
||||
this.m_value = value;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return (int) m_value;
|
||||
}
|
||||
|
||||
public LString luaAsString() {
|
||||
long l = (long) m_value;
|
||||
if ( m_value == (double) l ) {
|
||||
// TODO: is this a good idea?
|
||||
return new LString( Long.toString( l ) );
|
||||
} else {
|
||||
return LString.valueOf( m_value );
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInteger() {
|
||||
// Cast to int and then back to double and see if the value
|
||||
// survives the round trip.
|
||||
return ( (double) ( (int) m_value ) ) == m_value;
|
||||
}
|
||||
|
||||
// binary operations on integers, first dispatch
|
||||
public LValue luaBinOpUnknown(int opcode, LValue lhs) {
|
||||
return lhs.luaBinOpDouble( opcode, this.m_value );
|
||||
}
|
||||
|
||||
// binary operations on mixtures of doubles and integers
|
||||
public LValue luaBinOpInteger(int opcode, int rhs) {
|
||||
return luaBinOpDoubleDouble( opcode, m_value, (double) rhs );
|
||||
}
|
||||
|
||||
// binary operations on doubles
|
||||
public LValue luaBinOpDouble(int opcode, double rhs) {
|
||||
return luaBinOpDoubleDouble( opcode, m_value, rhs );
|
||||
}
|
||||
|
||||
public static LValue luaBinOpDoubleDouble( int opcode, double lhs, double rhs ) {
|
||||
switch ( opcode ) {
|
||||
case Lua.OP_ADD: return new LDouble( lhs + rhs );
|
||||
case Lua.OP_SUB: return new LDouble( lhs - rhs );
|
||||
case Lua.OP_MUL: return new LDouble( lhs * rhs );
|
||||
case Lua.OP_DIV: return new LDouble( lhs / rhs );
|
||||
case Lua.OP_MOD: return new LDouble( lhs - Math.floor(lhs/rhs) * rhs );
|
||||
// case Lua.OP_POW: return new LDouble( dpow(lhs, rhs) );
|
||||
}
|
||||
return luaUnsupportedOperation();
|
||||
}
|
||||
|
||||
/*
|
||||
public static double dpow(double a, double b) {
|
||||
if ( b < 0 )
|
||||
return 1 / dpow( a, -b );
|
||||
int p = 1;
|
||||
int whole = (int) b;
|
||||
for ( double v=a; whole > 0; whole>>=1, v=v*v )
|
||||
if ( (whole & 1) != 0 )
|
||||
p *= v;
|
||||
int frac = (int) (0x10000 * b);
|
||||
for ( ; (frac&0xffff)!=0; frac<<=1 ) {
|
||||
a = Math.sqrt(a);
|
||||
if ( (frac & 0x8000) != 0 )
|
||||
p *= a;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
public int luaAsInt() {
|
||||
return (int) m_value;
|
||||
}
|
||||
|
||||
public double luaAsDouble() {
|
||||
return m_value;
|
||||
}
|
||||
|
||||
// binary compares on integers, first dispatch
|
||||
public boolean luaBinCmpUnknown(int opcode, LValue lhs) {
|
||||
return lhs.luaBinCmpDouble( opcode, this.m_value );
|
||||
}
|
||||
|
||||
// binary compares on mixtures of doubles and integers
|
||||
public boolean luaBinCmpInteger(int opcode, int rhs) {
|
||||
return luaBinCmpDoubleDouble( opcode, m_value, (double) rhs );
|
||||
}
|
||||
|
||||
// binary compares on doubles
|
||||
public boolean luaBinCmpDouble(int opcode, double rhs) {
|
||||
return luaBinCmpDoubleDouble( opcode, m_value, rhs );
|
||||
}
|
||||
|
||||
// compare two doubles
|
||||
public static boolean luaBinCmpDoubleDouble( int opcode, double lhs, double rhs ) {
|
||||
switch ( opcode ) {
|
||||
case Lua.OP_EQ: return lhs == rhs;
|
||||
case Lua.OP_LT: return lhs < rhs;
|
||||
case Lua.OP_LE: return lhs <= rhs;
|
||||
}
|
||||
luaUnsupportedOperation();
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Arithmetic negative */
|
||||
public LValue luaUnaryMinus() {
|
||||
return new LDouble( -m_value );
|
||||
}
|
||||
|
||||
}
|
||||
33
src/main/java/lua/value/LFunction.java
Normal file
33
src/main/java/lua/value/LFunction.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package lua.value;
|
||||
|
||||
import lua.VM;
|
||||
|
||||
|
||||
public class LFunction extends LValue {
|
||||
|
||||
public static final LString TYPE_NAME = new LString(Type.function.toString());
|
||||
|
||||
public LString luaAsString() {
|
||||
return new LString( "function: "+hashCode() );
|
||||
}
|
||||
|
||||
public void luaSetTable(VM vm, LValue table, LValue key, LValue val) {
|
||||
vm.push( this );
|
||||
vm.push( table );
|
||||
vm.push( key );
|
||||
vm.push( val );
|
||||
vm.lua_call( 3, 0 );
|
||||
}
|
||||
|
||||
public void luaGetTable(VM vm, LValue table, LValue key) {
|
||||
vm.push( this );
|
||||
vm.push( table );
|
||||
vm.push( key );
|
||||
vm.lua_call( 2, 1 );
|
||||
}
|
||||
|
||||
public LString luaGetType() {
|
||||
return TYPE_NAME;
|
||||
}
|
||||
|
||||
}
|
||||
90
src/main/java/lua/value/LInteger.java
Normal file
90
src/main/java/lua/value/LInteger.java
Normal file
@@ -0,0 +1,90 @@
|
||||
package lua.value;
|
||||
|
||||
import lua.Lua;
|
||||
|
||||
public class LInteger extends LNumber {
|
||||
private final int m_value;
|
||||
|
||||
public LInteger(int value) {
|
||||
this.m_value = value;
|
||||
}
|
||||
|
||||
public final int hashCode() {
|
||||
return hashCodeOf( m_value );
|
||||
}
|
||||
|
||||
public static int hashCodeOf( int v ) {
|
||||
return v;
|
||||
}
|
||||
|
||||
public int luaAsInt() {
|
||||
return m_value;
|
||||
}
|
||||
|
||||
public LString luaAsString() {
|
||||
return LString.valueOf(m_value);
|
||||
}
|
||||
|
||||
public boolean isInteger() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// binary operations on integers, first dispatch
|
||||
public LValue luaBinOpUnknown(int opcode, LValue lhs) {
|
||||
return lhs.luaBinOpInteger( opcode, this.m_value );
|
||||
}
|
||||
|
||||
// binary operations on integers
|
||||
public LValue luaBinOpInteger(int opcode, int rhs) {
|
||||
switch ( opcode ) {
|
||||
case Lua.OP_ADD: return new LInteger( m_value + rhs );
|
||||
case Lua.OP_SUB: return new LInteger( m_value - rhs );
|
||||
case Lua.OP_MUL: return new LInteger( m_value * rhs );
|
||||
case Lua.OP_DIV: return new LInteger( m_value / rhs );
|
||||
case Lua.OP_MOD: return new LInteger( m_value - ((int) Math.floor(m_value/(double)rhs)) * rhs );
|
||||
case Lua.OP_POW: return new LInteger( ipow(m_value, rhs) );
|
||||
}
|
||||
return luaUnsupportedOperation();
|
||||
}
|
||||
|
||||
private static int ipow(int v, int rhs) {
|
||||
int p = 1;
|
||||
for ( ; rhs > 0; rhs>>=1, v=v*v )
|
||||
if ( (rhs & 1) != 0 )
|
||||
p *= v;
|
||||
return p;
|
||||
}
|
||||
|
||||
// binary operations on mixed integer, double
|
||||
public LValue luaBinOpDouble(int opcode, double rhs) {
|
||||
return LDouble.luaBinOpDoubleDouble(opcode, (double) m_value, rhs );
|
||||
}
|
||||
|
||||
// binary compare for integers, first dispatch
|
||||
public boolean luaBinCmpUnknown(int opcode, LValue lhs) {
|
||||
return lhs.luaBinCmpInteger( opcode, this.m_value );
|
||||
}
|
||||
|
||||
// unsupported except for numbers
|
||||
public boolean luaBinCmpInteger(int opcode, int rhs) {
|
||||
switch ( opcode ) {
|
||||
case Lua.OP_EQ: return m_value == rhs;
|
||||
case Lua.OP_LT: return m_value < rhs;
|
||||
case Lua.OP_LE: return m_value <= rhs;
|
||||
}
|
||||
luaUnsupportedOperation();
|
||||
return false;
|
||||
}
|
||||
|
||||
// unsupported except for numbers
|
||||
public boolean luaBinCmpDouble(int opcode, double rhs) {
|
||||
return LDouble.luaBinCmpDoubleDouble(opcode, (double) m_value, rhs );
|
||||
}
|
||||
|
||||
/** Arithmetic negative */
|
||||
public LValue luaUnaryMinus() {
|
||||
return new LInteger( -m_value );
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
23
src/main/java/lua/value/LNil.java
Normal file
23
src/main/java/lua/value/LNil.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package lua.value;
|
||||
|
||||
public final class LNil extends LValue {
|
||||
public static final LNil NIL = new LNil();
|
||||
public static final LString TYPE_NAME = new LString(Type.nil.toString());
|
||||
|
||||
public final LString luaAsString() {
|
||||
return TYPE_NAME;
|
||||
}
|
||||
|
||||
public boolean luaAsBoolean() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public LString luaGetType() {
|
||||
return TYPE_NAME;
|
||||
}
|
||||
|
||||
public int luaAsInt() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
392
src/main/java/lua/value/LString.java
Normal file
392
src/main/java/lua/value/LString.java
Normal file
@@ -0,0 +1,392 @@
|
||||
package lua.value;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import lua.Lua;
|
||||
|
||||
/**
|
||||
* A String implementation for Lua using bytes instead of chars.
|
||||
*
|
||||
* This should have the following advantages:
|
||||
*
|
||||
* (1) We can use strings as byte buffers, as Lua does, and therefore avoid
|
||||
* questions about how to adapt Lua APIs that use strings with binary data.
|
||||
*
|
||||
* (2) Half the memory usage when strings are primarily ASCII
|
||||
*
|
||||
*
|
||||
* TODO: Decide if/when to copy the bytes to a new array to ensure memory does
|
||||
* not "leak" in the form of unused portions of byte arrays. Currently, for
|
||||
* efficiency, new LStrings and substrings never create copies.
|
||||
*/
|
||||
public class LString extends LValue {
|
||||
|
||||
public static final LString TYPE_NAME = new LString(Type.string.toString());
|
||||
|
||||
public final byte[] m_bytes;
|
||||
public final int m_offset;
|
||||
public final int m_length;
|
||||
public final int m_hash;
|
||||
|
||||
private static LTable s_stringMT;
|
||||
|
||||
/**
|
||||
* Construct a Lua string from the given Java string. Characters are encoded
|
||||
* using UTF-8.
|
||||
*/
|
||||
public LString(String string) {
|
||||
byte[] bytes;
|
||||
try {
|
||||
bytes = string.getBytes( "UTF-8" );
|
||||
} catch ( UnsupportedEncodingException exn ) {
|
||||
bytes = stringToUtf8Bytes( string );
|
||||
}
|
||||
this.m_bytes = bytes;
|
||||
this.m_offset = 0;
|
||||
this.m_length = m_bytes.length;
|
||||
this.m_hash = hashBytes( m_bytes, 0, m_length );
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a string from the given byte array.
|
||||
*
|
||||
* new LString(b) is identical to new LString(b, 0, b.length)
|
||||
*/
|
||||
public LString(byte[] bytes) {
|
||||
this( bytes, 0, bytes.length );
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a string from the given byte array and range. For efficiency,
|
||||
* the byte array is not copied. Lua strings are immutable so the bytes must
|
||||
* not be modified after the string is constructed.
|
||||
*/
|
||||
public LString(byte[] bytes, int off, int len) {
|
||||
if ( off < 0 || len < 0 || off+len > bytes.length )
|
||||
throw new IndexOutOfBoundsException();
|
||||
this.m_bytes = bytes;
|
||||
this.m_offset = off;
|
||||
this.m_length = len;
|
||||
this.m_hash = hashBytes( bytes, off, len );
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if ( o != null && o instanceof LString ) {
|
||||
LString s = (LString) o;
|
||||
return m_hash == s.m_hash &&
|
||||
m_length == s.m_length &&
|
||||
( ( m_bytes == s.m_bytes && m_offset == s.m_offset ) ||
|
||||
equals( m_bytes, m_offset, s.m_bytes, s.m_offset, m_length ) );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int compareTo( LString o ) {
|
||||
final byte[] a = this.m_bytes;
|
||||
final byte[] b = o.m_bytes;
|
||||
int i = this.m_offset;
|
||||
int j = o.m_offset;
|
||||
final int imax = i + m_length;
|
||||
final int jmax = j + o.m_length;
|
||||
|
||||
if ( a == b && i == j && imax == jmax )
|
||||
return 0;
|
||||
|
||||
while ( i < imax && j < jmax ) {
|
||||
if ( a[i] != b[i] ) {
|
||||
return ( ( (int)a[i] ) & 0x0FF ) - ( ( (int)b[j] ) & 0x0FF );
|
||||
}
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
|
||||
return m_length - o.m_length;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return m_hash;
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return m_length;
|
||||
}
|
||||
|
||||
public LString substring( int beginIndex, int endIndex ) {
|
||||
return new LString( m_bytes, m_offset + beginIndex, endIndex - beginIndex );
|
||||
}
|
||||
|
||||
public int charAt( int index ) {
|
||||
if ( index < 0 || index >= m_length )
|
||||
throw new IndexOutOfBoundsException();
|
||||
return luaByte( index );
|
||||
}
|
||||
|
||||
/** Java version of strpbrk, which is a terribly named C function. */
|
||||
public int indexOfAny( LString accept ) {
|
||||
final int ilimit = m_offset + m_length;
|
||||
final int jlimit = accept.m_offset + accept.m_length;
|
||||
for ( int i = m_offset; i < ilimit; ++i ) {
|
||||
for ( int j = accept.m_offset; j < jlimit; ++j ) {
|
||||
if ( m_bytes[i] == accept.m_bytes[j] ) {
|
||||
return i - m_offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int indexOf( LString s, int start ) {
|
||||
final int slen = s.length();
|
||||
final int limit = m_offset + m_length - slen;
|
||||
for ( int i = m_offset + start; i <= limit; ++i ) {
|
||||
if ( equals( m_bytes, i, s.m_bytes, s.m_offset, slen ) ) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static LString valueOf( double d ) {
|
||||
return new LString( String.valueOf( d ) );
|
||||
}
|
||||
|
||||
public static LString valueOf( int x ) {
|
||||
return new LString( String.valueOf( x ) );
|
||||
}
|
||||
|
||||
public static LString concat( final LString[] strings ) {
|
||||
int length = 0;
|
||||
for ( int i = 0; i < strings.length; ++i ) {
|
||||
length += strings[i].length();
|
||||
}
|
||||
byte[] bytes = new byte[length];
|
||||
|
||||
for ( int i = 0, offset = 0; i < strings.length; ++i ) {
|
||||
LString s = strings[i];
|
||||
final int len = s.length();
|
||||
System.arraycopy( s.m_bytes, s.m_offset, bytes, offset, len );
|
||||
offset += len;
|
||||
}
|
||||
|
||||
return new LString( bytes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the specified substring of this string to the given output stream.
|
||||
*/
|
||||
public void write( OutputStream os, int offset, int len ) throws IOException {
|
||||
if ( offset < 0 || len < 0 )
|
||||
throw new IndexOutOfBoundsException();
|
||||
if ( offset + len > m_length )
|
||||
throw new IndexOutOfBoundsException();
|
||||
|
||||
os.write( m_bytes, m_offset+offset, len );
|
||||
}
|
||||
|
||||
public void write(OutputStream os) throws IOException {
|
||||
write(os, 0, m_length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the bytes of the string into the given byte array.
|
||||
*/
|
||||
public void copyInto( int strOffset, byte[] bytes, int arrayOffset, int len ) {
|
||||
System.arraycopy( m_bytes, m_offset+strOffset, bytes, arrayOffset, len );
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce an InputStream instance from which the bytes of this LString can be read.
|
||||
* Underlying byte array is not copied.
|
||||
*/
|
||||
public ByteArrayInputStream toInputStream() {
|
||||
// Well, this is really something.
|
||||
// Javadoc for java versions 1.3 and earlier states that if reset() is
|
||||
// called on a ByteArrayInputStream constructed with the 3-argument
|
||||
// constructor, then bytes 0 .. offset will be returned by the next
|
||||
// calls to read(). In JDK 1.4, the behavior improved, so that the
|
||||
// initial mark is set to the initial offset. We still need to
|
||||
// override ByteArrayInputStream here just in case we run on a
|
||||
// JVM with the older behavior.
|
||||
return new ByteArrayInputStream( m_bytes, m_offset, m_length ) {
|
||||
public synchronized void reset() {
|
||||
pos = Math.max( m_offset, mark );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public boolean luaBinCmpUnknown(int opcode, LValue lhs) {
|
||||
return lhs.luaBinCmpString(opcode, this);
|
||||
}
|
||||
|
||||
public boolean luaBinCmpString(int opcode, LString rhs) {
|
||||
switch ( opcode ) {
|
||||
case Lua.OP_EQ: return equals(rhs);
|
||||
case Lua.OP_LT: return compareTo(rhs) < 0;
|
||||
case Lua.OP_LE: return compareTo(rhs) <= 0;
|
||||
}
|
||||
luaUnsupportedOperation();
|
||||
return false;
|
||||
}
|
||||
|
||||
public LValue luaBinOpDouble( int opcode, double m_value ) {
|
||||
return luaToNumber().luaBinOpDouble( opcode, m_value );
|
||||
}
|
||||
|
||||
public LValue luaBinOpInteger( int opcode, int m_value ) {
|
||||
return luaToNumber().luaBinOpInteger( opcode, m_value );
|
||||
}
|
||||
|
||||
public LValue luaBinOpUnknown( int opcode, LValue lhs ) {
|
||||
return luaToNumber().luaBinOpUnknown( opcode, lhs );
|
||||
}
|
||||
|
||||
public LValue luaUnaryMinus() {
|
||||
return luaToNumber().luaUnaryMinus();
|
||||
}
|
||||
|
||||
public LValue luaToNumber() {
|
||||
return luaToNumber( 10 );
|
||||
}
|
||||
|
||||
public LValue luaToNumber( int base ) {
|
||||
if ( base >= 2 && base <= 36 ) {
|
||||
String str = toJavaString().trim();
|
||||
try {
|
||||
return new LInteger( Integer.parseInt( str, base ) );
|
||||
} catch ( NumberFormatException nfe ) {
|
||||
if ( base == 10 ) {
|
||||
try {
|
||||
return new LDouble( Double.parseDouble( str ) );
|
||||
} catch ( NumberFormatException nfe2 ) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return LNil.NIL;
|
||||
}
|
||||
|
||||
public LString luaAsString() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public String toJavaString() {
|
||||
try {
|
||||
return new String( m_bytes, m_offset, m_length, "UTF-8" );
|
||||
} catch ( UnsupportedEncodingException uee ) {
|
||||
throw new RuntimeException("toJavaString: UTF-8 decoding not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
/** Built-in opcode LEN, for Strings and Tables */
|
||||
public LValue luaLength() {
|
||||
return new LInteger( length() );
|
||||
}
|
||||
|
||||
public LString luaGetType() {
|
||||
return TYPE_NAME;
|
||||
}
|
||||
|
||||
public LTable luaGetMetatable() {
|
||||
synchronized ( LString.class ) {
|
||||
return s_stringMT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the metatable for all string values. Creates the table if it does not
|
||||
* exist yet, and sets its __index entry to point to itself.
|
||||
*
|
||||
* @return metatable that will be used for all strings
|
||||
*/
|
||||
public static synchronized LTable getMetatable() {
|
||||
if ( s_stringMT == null ) {
|
||||
s_stringMT = new LTable();
|
||||
s_stringMT.put( TM_INDEX, s_stringMT );
|
||||
}
|
||||
return s_stringMT;
|
||||
}
|
||||
|
||||
public static boolean equals( LString a, int i, LString b, int j, int n ) {
|
||||
return equals( a.m_bytes, a.m_offset + i, b.m_bytes, b.m_offset + j, n );
|
||||
}
|
||||
|
||||
public static boolean equals( byte[] a, int i, byte[] b, int j, int n ) {
|
||||
if ( a.length < i + n || b.length < j + n )
|
||||
return false;
|
||||
final int imax = i + n;
|
||||
final int jmax = j + n;
|
||||
while ( i < imax && j < jmax ) {
|
||||
if ( a[i++] != b[j++] )
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int hashBytes( byte[] bytes, int offset, int length ) {
|
||||
// Compute the hash of the given bytes.
|
||||
// This code comes right out of Lua 5.1.2 (translated from C to Java)
|
||||
int h = length; /* seed */
|
||||
int step = (length>>5)+1; /* if string is too long, don't hash all its chars */
|
||||
for (int l1=length; l1>=step; l1-=step) /* compute hash */
|
||||
h = h ^ ((h<<5)+(h>>2)+(((int) bytes[offset+l1-1] ) & 0x0FF ));
|
||||
return h;
|
||||
}
|
||||
|
||||
private static byte[] stringToUtf8Bytes( final String string ) {
|
||||
final int strlen = string.length();
|
||||
byte[] bytes = new byte[ strlen ];
|
||||
byte b1 = 0, b2 = 0, b3 = 0;
|
||||
|
||||
int j = 0;
|
||||
for ( int i = 0; i < strlen; ++i ) {
|
||||
int c = string.charAt( i );
|
||||
// TODO: combine 2-character combinations
|
||||
int count;
|
||||
if ( c > 0x07FF ) {
|
||||
count = 3;
|
||||
b3 = (byte)( 0xE0 | ( c >> 12 ) );
|
||||
b2 = (byte)( 0x80 | ( ( c >> 6 ) & 0x03F ) );
|
||||
b1 = (byte)( 0x80 | ( ( c ) & 0x03F ) );
|
||||
} else if ( c > 0x07F ) {
|
||||
count = 2;
|
||||
b2 = (byte)( 0xC0 | ( c >> 6 ) );
|
||||
b1 = (byte)( 0x80 | ( c & 0x03F ) );
|
||||
} else {
|
||||
count = 1;
|
||||
b1 = (byte) c;
|
||||
}
|
||||
if ( j + count > bytes.length ) {
|
||||
bytes = realloc( bytes, ( j + count ) * 2 );
|
||||
}
|
||||
switch ( count ) {
|
||||
case 3:
|
||||
bytes[j++] = b3;
|
||||
case 2:
|
||||
bytes[j++] = b2;
|
||||
case 1:
|
||||
bytes[j++] = b1;
|
||||
}
|
||||
}
|
||||
|
||||
if ( j != bytes.length ) {
|
||||
bytes = realloc( bytes, j );
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static byte[] realloc( byte[] a, int newSize ) {
|
||||
final byte[] newbytes = new byte[ newSize ];
|
||||
System.arraycopy( a, 0, newbytes, 0, Math.min( newSize, a.length ) );
|
||||
return newbytes;
|
||||
}
|
||||
|
||||
public int luaByte(int index) {
|
||||
return m_bytes[m_offset + index] & 0x0FF;
|
||||
}
|
||||
|
||||
}
|
||||
540
src/main/java/lua/value/LTable.java
Normal file
540
src/main/java/lua/value/LTable.java
Normal file
@@ -0,0 +1,540 @@
|
||||
package lua.value;
|
||||
|
||||
import lua.Lua;
|
||||
import lua.VM;
|
||||
|
||||
/**
|
||||
* Simple implementation of table structure for Lua VM. Maintains both an array
|
||||
* part and a hash part. Does not attempt to achieve the same performance as the
|
||||
* C version.
|
||||
*
|
||||
* Java code can put values in the table or get values out (bypassing the
|
||||
* metatable, if there is one) using put() and get(). There are specializations
|
||||
* of put() and get() for integers and Strings to avoid allocating wrapper
|
||||
* objects when possible.
|
||||
*
|
||||
* remove() methods are private: setting a key's value to nil is the correct way
|
||||
* to remove an entry from the table.
|
||||
*
|
||||
* TODO: Support for weak tables has to be shoehorned in here somehow.
|
||||
*
|
||||
*/
|
||||
public class LTable extends LValue {
|
||||
|
||||
public static final LString TYPE_NAME = new LString(Type.table.toString());
|
||||
|
||||
/**
|
||||
* Zero-length array to use instead of null, so that we don't need to
|
||||
* check for null everywhere.
|
||||
*/
|
||||
private static final LValue[] EMPTY_ARRAY = new LValue[0];
|
||||
|
||||
/**
|
||||
* Minimum legal capacity for the hash portion. Note that the hash portion
|
||||
* must never be filled to capacity or findSlot() will run forever.
|
||||
*/
|
||||
private static final int MIN_HASH_CAPACITY = 2;
|
||||
|
||||
/**
|
||||
* Array of keys in the hash part. When there is no hash part this is null.
|
||||
* Elements of m_hashKeys are never LNil.NIL - they are null to indicate
|
||||
* the hash slot is empty and some non-null, non-nil value otherwise.
|
||||
*/
|
||||
private LValue[] m_hashKeys;
|
||||
|
||||
/**
|
||||
* Values in the hash part. Must be null when m_hashKeys is null and equal
|
||||
* in size otherwise.
|
||||
*/
|
||||
private LValue[] m_hashValues;
|
||||
|
||||
/**
|
||||
* m_hashEntries is the number of slots that are used. Must always be less
|
||||
* than m_hashKeys.length.
|
||||
*/
|
||||
private int m_hashEntries;
|
||||
|
||||
/**
|
||||
* Array of values to store the "array part" of the table, that is the
|
||||
* entries with positive integer keys. Elements must never be null: "empty"
|
||||
* slots are set to LNil.NIL.
|
||||
*/
|
||||
private LValue[] m_vector;
|
||||
|
||||
/**
|
||||
* Number of values in m_vector that non-nil.
|
||||
*/
|
||||
private int m_arrayEntries;
|
||||
|
||||
private LTable m_metatable;
|
||||
|
||||
|
||||
|
||||
/** Construct an empty LTable with no initial capacity. */
|
||||
public LTable() {
|
||||
m_vector = EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an empty LTable that is expected to contain entries with keys
|
||||
* in the range 1 .. narray and nhash non-integer keys.
|
||||
*/
|
||||
public LTable( int narray, int nhash ) {
|
||||
if ( nhash > 0 ) {
|
||||
// Allocate arrays 25% bigger than nhash to account for load factor.
|
||||
final int capacity = Math.max( nhash + ( nhash >> 2 ), nhash + 1 );
|
||||
m_hashKeys = new LValue[capacity];
|
||||
m_hashValues = new LValue[capacity];
|
||||
}
|
||||
m_vector = new LValue[narray];
|
||||
for ( int i = 0; i < narray; ++i ) {
|
||||
m_vector[i] = LNil.NIL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return total number of keys mapped to non-nil values. Not to be confused
|
||||
* with luaLength, which returns some number n such that the value at n+1 is
|
||||
* nil.
|
||||
*/
|
||||
public int size() {
|
||||
return m_hashEntries + m_arrayEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic put method for all types of keys, but does not use the metatable.
|
||||
*/
|
||||
public void put( LValue key, LValue val ) {
|
||||
if ( key.isInteger() ) {
|
||||
// call the integer-specific put method
|
||||
put( key.luaAsInt(), val );
|
||||
} else if ( val == null || val == LNil.NIL ) {
|
||||
// Remove the key if the value is nil. This comes after the check
|
||||
// for an integer key so that values are properly removed from
|
||||
// the array part.
|
||||
remove( key );
|
||||
} else {
|
||||
if ( checkLoadFactor() )
|
||||
rehash();
|
||||
int slot = findSlot( key );
|
||||
if ( fillHashSlot( slot, val ) )
|
||||
return;
|
||||
m_hashKeys[slot] = key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method for putting a string-keyed value directly, typically for
|
||||
* initializing a table. Bypasses the metatable, if any.
|
||||
*/
|
||||
public void put( String key, LValue value ) {
|
||||
put( new LString( key ), value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for putting an integer-keyed value. Bypasses the metatable, if
|
||||
* any.
|
||||
*/
|
||||
public void put( int key, LValue value ) {
|
||||
if (value == null || value == LNil.NIL) {
|
||||
remove( key );
|
||||
return;
|
||||
}
|
||||
if ( key > 0 ) {
|
||||
final int index = key - 1;
|
||||
for ( ;; ) {
|
||||
if ( index < m_vector.length ) {
|
||||
if ( m_vector[index] == LNil.NIL ) {
|
||||
++m_arrayEntries;
|
||||
}
|
||||
m_vector[index] = value;
|
||||
return;
|
||||
} else if ( index < ( m_arrayEntries + 1 ) * 2 ) {
|
||||
resize( ( m_arrayEntries + 1 ) * 2 );
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No room in array part, use hash part instead.
|
||||
if ( checkLoadFactor() )
|
||||
rehash();
|
||||
int slot = findSlot( key );
|
||||
if ( fillHashSlot( slot, value ) )
|
||||
return;
|
||||
m_hashKeys[ slot ] = new LInteger( key );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Utility method to directly get the value in a table, without metatable
|
||||
* calls. Must never return null, use LNil.NIL instead.
|
||||
*/
|
||||
public LValue get( LValue key ) {
|
||||
if ( m_vector.length > 0 && key.isInteger() ) {
|
||||
final int index = key.luaAsInt() - 1;
|
||||
if ( index >= 0 && index < m_vector.length ) {
|
||||
return m_vector[index];
|
||||
}
|
||||
}
|
||||
|
||||
if ( m_hashKeys == null )
|
||||
return LNil.NIL;
|
||||
|
||||
int slot = findSlot( key );
|
||||
return ( m_hashKeys[slot] != null ) ? m_hashValues[slot] : LNil.NIL;
|
||||
}
|
||||
|
||||
/** Utility method for retrieving an integer-keyed value */
|
||||
public LValue get( int key ) {
|
||||
if ( key > 0 && key <= m_vector.length ) {
|
||||
return m_vector[key - 1];
|
||||
}
|
||||
|
||||
int slot = findSlot( key );
|
||||
return ( m_hashKeys[slot] != null ) ? m_hashValues[slot] : LNil.NIL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if the table contains an entry with the given key, false if
|
||||
* not. Ignores the metatable.
|
||||
*/
|
||||
public boolean containsKey( LValue key ) {
|
||||
if ( m_vector.length > 0 && key.isInteger() ) {
|
||||
final int index = key.luaAsInt() - 1;
|
||||
if ( index >= 0 && index < m_vector.length ) {
|
||||
final LValue v = m_vector[index];
|
||||
return v != LNil.NIL;
|
||||
}
|
||||
}
|
||||
if ( m_hashKeys == null )
|
||||
return false;
|
||||
final int slot = findSlot( key );
|
||||
return m_hashKeys[ slot ] != null;
|
||||
}
|
||||
|
||||
public void luaGetTable(VM vm, LValue table, LValue key) {
|
||||
LValue v = get(key);
|
||||
if ( v == LNil.NIL && m_metatable != null ) {
|
||||
super.luaGetTable( vm, table, key );
|
||||
} else {
|
||||
vm.push(v);
|
||||
}
|
||||
}
|
||||
|
||||
public void luaSetTable(VM vm, LValue table, LValue key, LValue val) {
|
||||
if ( !containsKey( key ) && m_metatable != null ) {
|
||||
super.luaSetTable( vm, table, key, val );
|
||||
} else {
|
||||
put(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the "length" of this table. This will not return the same result
|
||||
* as the C version in all cases, but that's ok because the length operation
|
||||
* on a table where the integer keys are sparse is vaguely defined.
|
||||
*/
|
||||
public LValue luaLength() {
|
||||
for ( int i = Math.max( 0, m_arrayEntries-1 ); i < m_vector.length; ++i ) {
|
||||
if ( m_vector[i] != LNil.NIL &&
|
||||
( i+1 == m_vector.length || m_vector[i+1] == LNil.NIL ) ) {
|
||||
return new LInteger( i+1 );
|
||||
}
|
||||
}
|
||||
return new LInteger( 0 );
|
||||
}
|
||||
|
||||
/** Valid for tables */
|
||||
public LTable luaGetMetatable() {
|
||||
return this.m_metatable;
|
||||
}
|
||||
|
||||
/** Valid for tables */
|
||||
public void luaSetMetatable(LValue metatable) {
|
||||
this.m_metatable = ( metatable != null && metatable != LNil.NIL ) ?
|
||||
(LTable) metatable : null;
|
||||
}
|
||||
|
||||
public LString luaAsString() {
|
||||
return new LString("table: "+id());
|
||||
}
|
||||
|
||||
public LString luaGetType() {
|
||||
return TYPE_NAME;
|
||||
}
|
||||
|
||||
/** Valid for tables */
|
||||
public LValue luaPairs(boolean isPairs) {
|
||||
return new LTableIterator(isPairs);
|
||||
}
|
||||
|
||||
/** Iterator for tables */
|
||||
private final class LTableIterator extends LFunction {
|
||||
private int arrayIndex;
|
||||
private int hashIndex;
|
||||
private final boolean isPairs;
|
||||
|
||||
private LTableIterator(boolean isPairs) {
|
||||
this.arrayIndex = 0;
|
||||
this.hashIndex = 0;
|
||||
this.isPairs = isPairs;
|
||||
}
|
||||
|
||||
// perform a lua call
|
||||
public boolean luaStackCall(VM vm) {
|
||||
vm.setResult();
|
||||
int i;
|
||||
while ( ( i = arrayIndex++ ) < m_vector.length ) {
|
||||
if ( m_vector[i] != LNil.NIL ) {
|
||||
vm.push( new LInteger( arrayIndex ) );
|
||||
vm.push( m_vector[ i ] );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ( isPairs && (m_hashKeys != null) ) {
|
||||
while ( ( i = hashIndex++ ) < m_hashKeys.length ) {
|
||||
if ( m_hashKeys[i] != null ) {
|
||||
vm.push( m_hashKeys[i] );
|
||||
vm.push( m_hashValues[i] );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get all the keys in this table in an array. Meant to be
|
||||
* used instead of keys() (which returns an enumeration) when an array is
|
||||
* more convenient. Note that for a very large table, getting an Enumeration
|
||||
* instead would be more space efficient.
|
||||
*/
|
||||
public LValue[] getKeys() {
|
||||
LValue[] keys = new LValue[ m_arrayEntries + m_hashEntries ];
|
||||
int out = 0;
|
||||
|
||||
for ( int i = 0; i < m_vector.length; ++i ) {
|
||||
if ( m_vector[ i ] != LNil.NIL ) {
|
||||
keys[ out++ ] = new LInteger( i + 1 );
|
||||
}
|
||||
}
|
||||
|
||||
if ( m_hashKeys != null ) {
|
||||
for ( int i = 0; i < m_hashKeys.length; ++i ) {
|
||||
if ( m_hashKeys[ i ] != null )
|
||||
keys[ out++ ] = m_hashKeys[i];
|
||||
}
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
/** Remove the value in the table with the given integer key. */
|
||||
private void remove( int key ) {
|
||||
if ( key > 0 ) {
|
||||
final int index = key - 1;
|
||||
if ( index < m_vector.length ) {
|
||||
if ( m_vector[ index ] != LNil.NIL ) {
|
||||
--m_arrayEntries;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( m_hashKeys != null ) {
|
||||
int slot = findSlot( key );
|
||||
clearSlot( slot );
|
||||
}
|
||||
}
|
||||
|
||||
private void remove( LValue key ) {
|
||||
if ( m_hashKeys != null ) {
|
||||
int slot = findSlot( key );
|
||||
clearSlot( slot );
|
||||
}
|
||||
}
|
||||
|
||||
private void clearSlot( int i ) {
|
||||
if ( m_hashKeys[ i ] != null ) {
|
||||
|
||||
int j = i;
|
||||
while ( m_hashKeys[ j = ( ( j + 1 ) % m_hashKeys.length ) ] != null ) {
|
||||
final int k = hashToIndex( m_hashKeys[ j ].hashCode() );
|
||||
if ( ( j > i && ( k <= i || k > j ) ) ||
|
||||
( j < i && ( k <= i && k > j ) ) ) {
|
||||
m_hashKeys[ i ] = m_hashKeys[ j ];
|
||||
m_hashValues[ i ] = m_hashValues[ j ];
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
--m_hashEntries;
|
||||
m_hashKeys[ i ] = null;
|
||||
m_hashValues[ i ] = null;
|
||||
|
||||
if ( m_hashEntries == 0 ) {
|
||||
m_hashKeys = null;
|
||||
m_hashValues = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int findSlot( LValue key ) {
|
||||
int i = hashToIndex( key.hashCode() );
|
||||
|
||||
// This loop is guaranteed to terminate as long as we never allow the
|
||||
// table to get 100% full.
|
||||
LValue k;
|
||||
while ( ( k = m_hashKeys[i] ) != null &&
|
||||
!key.luaBinCmpUnknown( Lua.OP_EQ, k ) ) {
|
||||
i = ( i + 1 ) % m_hashKeys.length;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private int findSlot( int key ) {
|
||||
int i = hashToIndex( LInteger.hashCodeOf( key ) );
|
||||
|
||||
// This loop is guaranteed to terminate as long as we never allow the
|
||||
// table to get 100% full.
|
||||
LValue k;
|
||||
while ( ( k = m_hashKeys[i] ) != null &&
|
||||
!k.luaBinCmpInteger( Lua.OP_EQ, key ) ) {
|
||||
i = ( i + 1 ) % m_hashKeys.length;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the given slot was already occupied, false otherwise.
|
||||
*/
|
||||
private boolean fillHashSlot( int slot, LValue value ) {
|
||||
m_hashValues[ slot ] = value;
|
||||
if ( m_hashKeys[ slot ] != null ) {
|
||||
return true;
|
||||
} else {
|
||||
++m_hashEntries;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private int hashToIndex( int hash ) {
|
||||
return ( hash & 0x7FFFFFFF ) % m_hashKeys.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called before inserting a value into the hash.
|
||||
*
|
||||
* @return true if the hash portion of the LTable is at its capacity.
|
||||
*/
|
||||
private boolean checkLoadFactor() {
|
||||
if ( m_hashKeys == null )
|
||||
return true;
|
||||
// Using a load factor of 2/3 because that is easy to compute without
|
||||
// overflow or division.
|
||||
final int hashCapacity = m_hashKeys.length;
|
||||
return ( hashCapacity >> 1 ) >= ( hashCapacity - m_hashEntries );
|
||||
}
|
||||
|
||||
private void rehash() {
|
||||
final int oldCapacity = ( m_hashKeys != null ) ? m_hashKeys.length : 0;
|
||||
final int newCapacity = ( oldCapacity > 0 ) ? 2 * oldCapacity : MIN_HASH_CAPACITY;
|
||||
|
||||
final LValue[] oldKeys = m_hashKeys;
|
||||
final LValue[] oldValues = m_hashValues;
|
||||
|
||||
m_hashKeys = new LValue[ newCapacity ];
|
||||
m_hashValues = new LValue[ newCapacity ];
|
||||
|
||||
for ( int i = 0; i < oldCapacity; ++i ) {
|
||||
final LValue k = oldKeys[i];
|
||||
if ( k != null ) {
|
||||
final LValue v = oldValues[i];
|
||||
final int slot = findSlot( k );
|
||||
m_hashKeys[slot] = k;
|
||||
m_hashValues[slot] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resize( int newCapacity ) {
|
||||
final int oldCapacity = m_vector.length;
|
||||
LValue[] newVector = new LValue[ newCapacity ];
|
||||
System.arraycopy( m_vector, 0, newVector, 0, Math.min( oldCapacity, newCapacity ) );
|
||||
|
||||
// We need to move keys from hash part to array part if array part is
|
||||
// getting bigger, and from array part to hash part if array is getting
|
||||
// smaller.
|
||||
if ( newCapacity > oldCapacity ) {
|
||||
if ( m_hashKeys != null ) {
|
||||
for ( int i = oldCapacity; i < newCapacity; ++i ) {
|
||||
int slot = findSlot( i+1 );
|
||||
if ( m_hashKeys[ slot ] != null ) {
|
||||
newVector[ i ] = m_hashValues[ slot ];
|
||||
m_hashKeys[ i ] = null;
|
||||
--m_hashEntries;
|
||||
} else {
|
||||
// Make sure all array-part values are initialized to nil
|
||||
// so that we can just do one compare instead of two
|
||||
// whenever we need to check if a slot is full or not.
|
||||
newVector[ i ] = LNil.NIL;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for ( int i = oldCapacity; i < newCapacity; ++i ) {
|
||||
newVector[ i ] = LNil.NIL;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for ( int i = newCapacity; i < oldCapacity; ++i ) {
|
||||
LValue v = m_vector[i];
|
||||
if ( v != LNil.NIL ) {
|
||||
if (checkLoadFactor())
|
||||
rehash();
|
||||
final int slot = findSlot( i+1 );
|
||||
m_hashKeys[ slot ] = new LInteger( i+1 );
|
||||
m_hashValues[ slot ] = v;
|
||||
++m_hashEntries;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_vector = newVector;
|
||||
}
|
||||
|
||||
// hooks for junit
|
||||
|
||||
int getHashCapacity() {
|
||||
return ( m_hashKeys != null ) ? m_hashKeys.length : 0;
|
||||
}
|
||||
|
||||
int getArrayCapacity() {
|
||||
return m_vector.length;
|
||||
}
|
||||
|
||||
/*
|
||||
* @pos index to insert at, or 0 to insert at end.
|
||||
*/
|
||||
public void luaInsertPos(int pos, LValue value) {
|
||||
if ( pos != 0 )
|
||||
throw new RuntimeException("luaInsertPos() not implemented");
|
||||
put( m_arrayEntries + m_hashEntries + 1, value );
|
||||
}
|
||||
|
||||
public void luaSort() {
|
||||
throw new RuntimeException("luaSort() not implemented");
|
||||
}
|
||||
|
||||
public void luaRemovePos(int pos) {
|
||||
throw new RuntimeException("luaRemovePos() not implemented");
|
||||
}
|
||||
|
||||
public int luaMaxN() {
|
||||
throw new RuntimeException("luaMaxN() not implemented");
|
||||
}
|
||||
|
||||
}
|
||||
13
src/main/java/lua/value/LThread.java
Normal file
13
src/main/java/lua/value/LThread.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package lua.value;
|
||||
|
||||
public class LThread extends LValue {
|
||||
public static final LString TYPE_NAME = new LString(Type.thread.toString());
|
||||
|
||||
public LString luaGetType() {
|
||||
return TYPE_NAME;
|
||||
}
|
||||
|
||||
public LString luaAsString() {
|
||||
return new LString("thread: "+hashCode());
|
||||
}
|
||||
}
|
||||
33
src/main/java/lua/value/LUserData.java
Normal file
33
src/main/java/lua/value/LUserData.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package lua.value;
|
||||
|
||||
public class LUserData extends LValue {
|
||||
public static final LString TYPE_NAME = new LString(Type.userdata.toString());
|
||||
|
||||
public final Object m_instance;
|
||||
public LTable m_metatable;
|
||||
|
||||
public LUserData(Object obj) {
|
||||
m_instance = obj;
|
||||
}
|
||||
|
||||
public LString luaAsString() {
|
||||
return new LString(m_instance.toString());
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
return (this == obj) ||
|
||||
(obj instanceof LUserData && this.m_instance == ((LUserData) obj).m_instance);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return System.identityHashCode( m_instance );
|
||||
}
|
||||
|
||||
public LString luaGetType() {
|
||||
return TYPE_NAME;
|
||||
}
|
||||
|
||||
public LTable luaGetMetatable() {
|
||||
return m_metatable;
|
||||
}
|
||||
}
|
||||
56
src/test/java/lua/debug/DebugStackStateTest.java
Normal file
56
src/test/java/lua/debug/DebugStackStateTest.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package lua.debug;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import lua.io.Closure;
|
||||
import lua.io.LoadState;
|
||||
import lua.io.Proto;
|
||||
import lua.value.LValue;
|
||||
|
||||
public class DebugStackStateTest extends TestCase {
|
||||
|
||||
public void testDebugStackState() throws InterruptedException, IOException {
|
||||
String script = "/test6.luac";
|
||||
|
||||
// set up the vm
|
||||
final DebugStackState state = new DebugStackState();
|
||||
InputStream is = getClass().getResourceAsStream( script );
|
||||
Proto p = LoadState.undump(state, is, script);
|
||||
|
||||
// create closure and execute
|
||||
final Closure c = new Closure( state, p );
|
||||
|
||||
// suspend the vm right away
|
||||
state.suspend();
|
||||
state.setBreakpoint(script, 14);
|
||||
|
||||
// start the call processing in its own thread
|
||||
new Thread() {
|
||||
public void run() {
|
||||
try {
|
||||
state.doCall( c, new LValue[0] );
|
||||
} catch ( Exception e ) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
|
||||
// step for 5 steps
|
||||
for ( int i=0; i<5; i++ ) {
|
||||
state.step();
|
||||
Thread.sleep(500);
|
||||
System.out.println("--- callgraph="+state.getCallgraph() );
|
||||
System.out.println("--- stack="+state.getStack(0) );
|
||||
}
|
||||
|
||||
// resume the vm
|
||||
state.resume();
|
||||
Thread.sleep(500);
|
||||
System.out.println("--- callgraph="+state.getCallgraph() );
|
||||
state.resume();
|
||||
Thread.sleep(500);
|
||||
System.out.println("--- callgraph="+state.getCallgraph() );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user