diff --git a/.classpath b/.classpath
index 1ed5c4b3..009529c2 100644
--- a/.classpath
+++ b/.classpath
@@ -7,5 +7,6 @@
+
diff --git a/lib/commons-cli-1.1.jar b/lib/commons-cli-1.1.jar
new file mode 100644
index 00000000..e633afbe
Binary files /dev/null and b/lib/commons-cli-1.1.jar differ
diff --git a/src/main/java/lua/LuaJVM.java b/src/main/java/lua/LuaJVM.java
new file mode 100644
index 00000000..ec6f8eef
--- /dev/null
+++ b/src/main/java/lua/LuaJVM.java
@@ -0,0 +1,226 @@
+/*******************************************************************************
+* 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 lua.addon.luajava.LuaJava;
+import lua.debug.DebugRequestListener;
+import lua.debug.DebugServer;
+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 DebugServer debugServer;
+ protected int requestPort;
+ protected int eventPort;
+ protected String script;
+ protected String[] scriptArgs;
+
+ @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 = fileArgs[0];
+ this.scriptArgs = new String[fileArgs.length - 1];
+ for (int i = 1; i < fileArgs.length; i++) {
+ this.scriptArgs[i-1] = fileArgs[i];
+ }
+ }
+ } catch(NumberFormatException e) {
+ throw new ParseException("Invalid port number: " + 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()) {
+ setupDebugHooks(getRequestPort(), getEventPort());
+ }
+
+ // TODO: VM hook for debugging
+
+ // add LuaJava bindings
+ LuaJava.install();
+
+ // new lua state
+ StackState 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
+ System.out.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);
+ }
+
+ protected void setupDebugHooks(int requestPort, int eventPort)
+ throws IOException {
+ this.debugServer = new DebugServer(this, requestPort, eventPort);
+ this.debugServer.start();
+ }
+
+ /* (non-Javadoc)
+ * @see lua.debug.DebugRequestListener#handleRequest(java.lang.String)
+ */
+ public String handleRequest(String request) {
+ //TODO: handle the following requests:
+ // suspend -- suspend the execution and listen for debug requests
+ // resume -- resume the execution
+ // exit -- exit the VM
+ // set N -- set breakpoint at line N
+ // clear N -- clear breakpoint at line N
+ // callgraph -- return the current call graph (i.e. stack frames from
+ // old to new, include information about file, method, etc.)
+ // stack -- return the content of the current stack frame,
+ // listing the (variable, value) pairs
+ // step -- single step forward (go to next statement)
+ // variable N M
+ // -- return the value of variable M from the stack frame N
+ // (stack frames are indexed from 0)
+ return null;
+ }
+
+ public void stop() {
+ if (this.debugServer != null) {
+ this.debugServer.stop();
+ this.debugServer = null;
+ }
+ }
+
+ /**
+ * 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) throws IOException {
+ LuaJVM vm = new LuaJVM();
+
+ try {
+ vm.parse(args);
+ } catch (ParseException e) {
+ System.out.println(e.getMessage());
+ System.out.println();
+ vm.printUsage();
+ return;
+ }
+
+ vm.run();
+ }
+}
diff --git a/src/main/java/lua/debug/DebugRequestListener.java b/src/main/java/lua/debug/DebugRequestListener.java
new file mode 100644
index 00000000..3f695f1e
--- /dev/null
+++ b/src/main/java/lua/debug/DebugRequestListener.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+* 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.debug;
+
+/**
+ * DebugRequestListener handles debugging requests.
+ *
+ * @author: Shu Lei
+ * @version: 1.0
+ */
+public interface DebugRequestListener {
+
+ /**
+ * Debugging client can send the following requests to the server:
+ * suspend -- suspend the execution and listen for debug requests
+ * resume -- resume the execution
+ * exit -- terminate the execution
+ * set N -- set breakpoint at line N
+ * clear N -- clear breakpoint at line N
+ * callgraph -- return the current call graph (i.e. stack frames from
+ * old to new, include information about file, method, etc.)
+ * stack -- return the content of the current stack frame,
+ * listing the (variable, value) pairs
+ * step -- single step forward (go to next statement)
+ * variable N M
+ * -- return the value of variable M from the stack frame N
+ * (stack frames are indexed from 0)
+ */
+ public String handleRequest(String request);
+}
diff --git a/src/main/java/lua/debug/DebugServer.java b/src/main/java/lua/debug/DebugServer.java
new file mode 100644
index 00000000..b22c9914
--- /dev/null
+++ b/src/main/java/lua/debug/DebugServer.java
@@ -0,0 +1,184 @@
+/*******************************************************************************
+* 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.debug;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+/**
+ * DebugServer manages the communications between LuaJ VM and
+ * the debugging client.
+ *
+ * @author: Shu Lei
+ * @version:
+ */
+public class DebugServer {
+ public enum State {
+ UNKNOWN,
+ RUNNING,
+ STOPPED
+ }
+
+ protected DebugRequestListener listener;
+ protected int requestPort;
+ protected int eventPort;
+ protected Thread requestWatcherThread;
+ protected State state = State.UNKNOWN;
+
+ protected ServerSocket requestSocket;
+ protected Socket clientRequestSocket;
+ protected BufferedReader requestReader;
+ protected PrintWriter requestWriter;
+
+ protected ServerSocket eventSocket;
+ protected Socket clientEventSocket;
+ protected PrintWriter eventWriter;
+
+ public DebugServer(DebugRequestListener listener,
+ int requestPort,
+ int eventPort) {
+ this.listener = listener;
+ this.requestPort = requestPort;
+ this.eventPort = eventPort;
+ }
+
+ protected void destroy() {
+ if (requestReader != null) {
+ try {
+ requestReader.close();
+ } catch (IOException e) {}
+ }
+
+ if (requestWriter != null) {
+ requestWriter.close();
+ }
+
+ if (clientRequestSocket != null) {
+ try {
+ clientRequestSocket.close();
+ } catch (IOException e) {}
+ }
+
+ if (requestSocket != null) {
+ try {
+ requestSocket.close();
+ } catch (IOException e) {}
+ }
+
+ if (eventWriter != null) {
+ eventWriter.close();
+ }
+
+ if (clientEventSocket != null) {
+ try {
+ clientEventSocket.close();
+ } catch (IOException e) {}
+ }
+
+ if (eventSocket != null){
+ try {
+ eventSocket.close();
+ } catch (IOException e) {}
+ }
+ }
+
+ public synchronized void start() throws IOException {
+ this.requestSocket = new ServerSocket(requestPort);
+ this.clientRequestSocket = requestSocket.accept();
+ this.requestReader = new BufferedReader(
+ new InputStreamReader(clientRequestSocket.getInputStream()));
+ this.requestWriter = new PrintWriter(clientRequestSocket.getOutputStream());
+
+ this.eventSocket = new ServerSocket(eventPort);
+ this.clientEventSocket = eventSocket.accept();
+ this.eventWriter = new PrintWriter(clientEventSocket.getOutputStream());
+
+ this.requestWatcherThread = new Thread(new Runnable() {
+ public void run() {
+ if (getState() != State.STOPPED) {
+ handleRequest();
+ } else {
+ destroy();
+ }
+ }
+ });
+ this.requestWatcherThread.start();
+ this.state = State.RUNNING;
+ }
+
+ public synchronized State getState() {
+ return this.state;
+ }
+
+ public synchronized void stop() {
+ this.state = State.STOPPED;
+ }
+
+ public void handleRequest() {
+ synchronized (clientRequestSocket) {
+ String request = null;
+ try {
+ while (getState() != State.STOPPED &&
+ (request = requestReader.readLine()) != null) {
+ System.out.println("SERVER receives request: " + request);
+ String response = listener.handleRequest(request);
+ requestWriter.write(response);
+ requestWriter.flush();
+ }
+
+ if (getState() == State.STOPPED) {
+ destroy();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * This method provides the second communication channel with the debugging
+ * client. The server can send events via this channel to notify the client
+ * about debug events (see below) asynchonously.
+ *
+ * The following events can be fired:
+ * 1. started -- the vm is started and ready to receive debugging requests
+ * (guaranteed to be the first event sent)
+ * 2. terminated -- the vm is terminated (guaranteed to be the last event sent)
+ * 3. suspended client|step|breakpoint N
+ * -- the vm is suspended by client, due to a stepping request or
+ * the breakpoint at line N is hit
+ * 4. resumed client|step
+ * -- the vm resumes execution by client or step
+ *
+ * @param event
+ */
+ public void fireEvent(String event) {
+ synchronized (eventSocket) {
+ eventWriter.println(event);
+ eventWriter.flush();
+ }
+ }
+}