Graphical Remote Lua Debugger, a debugger for the lua programming language.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

303 lines
8.2 KiB

-- see copyright notice in wxLdb.lua
local net = require( "grldc.net" )
local socket = require( "grldc.socket" )
local print = print
local assert = assert
local pairs = pairs
local ipairs = ipairs
local type = type
local debug = debug
local xpcall = xpcall
local setmetatable = setmetatable
local error = error
local table = table
local tonumber = tonumber
module( "grld.engine" )
local clients
local activeClient
local nextClient = 1
local listeners = {}
local nextListener = 1
local events =
{
onNewClient = {},
onClientBreak = {},
onClientLost = {},
}
local clientMeta = { __index = {} }
local function addClient( listenerIdx, connection, name )
print( "New client connected on listener "..listenerIdx.." : assigned client ID "..nextClient )
local client = { clientId = nextClient, listenerIdx = listenerIdx, connection = connection, status_ = "running", activeThread_ = "current", name_ = name, ip_ = connection:getpeername(), breakPoints_ = {} }
setmetatable( client, clientMeta )
clients[nextClient] = client
if activeClient == nil or clients[activeClient] == nil then
setactiveclient( nextClient )
end
runEvents_( "onNewClient", nextClient )
nextClient = nextClient + 1
end
function init()
print( "Initializing grld.engine..." )
assert( clients == nil, "Trying to initialize grld.engine twice" )
clients = {}
end
function initialized()
return clients ~= nil
end
function listen( address, port )
print( "Listening for new clients on "..address..":"..port..", listener ID "..nextListener )
local listener = net.bind( address, port )
listeners[nextListener] = listener
nextListener = nextListener + 1
end
function registerEvent( eventName, callback )
assert( events[eventName] ~= nil, "Unknown event name "..eventName )
table.insert( events[eventName], callback )
end
function runEvents_( eventName, ... )
for _, callback in pairs( events[eventName] ) do
callback( ... )
end
end
function update( timeout )
local active = false
for idx, listener in pairs( listeners ) do
local connection = listener:accept()
if connection ~= nil then
local name = connection:receive()
addClient( idx, connection, name )
active = true
end
end
local sockets = {}
for idx, client in pairs( clients ) do
table.insert( sockets, client.connection )
end
socket.select( sockets, timeout )
for idx, client in pairs( clients ) do
local ok, msg = xpcall( function() active = active or client:update() end, client.errorHandler )
if not ok then
active = true
if msg == "closed" then
print( "Connection with client "..idx.." closed" )
runEvents_( "onClientLost", idx )
clients[idx] = nil
else
error( msg )
end
end
end
return active
end
function clientMeta.__index:checkConnection()
self.connection:send( "", "ka" )
end
function clientMeta.__index:update()
local command = self.connection:tryReceive()
if command ~= nil then
self["cmd_"..command]( self )
return true
end
return false
end
function clientMeta.__index:setactivethread( thread )
self.activeThread_ = thread
end
function clientMeta.__index:getactivethread()
return self.activeThread_
end
function clientMeta.__index:getcurrentthread()
assert( self.status_ == "break", "Bad status: "..self.status_ )
self.connection:send( "currentthread" )
return self.connection:receive()
end
function clientMeta.__index:name()
return self.name_
end
function clientMeta.__index:ip()
return self.ip_
end
function clientMeta.__index:status()
return self.status_
end
function clientMeta.__index:source()
assert( self.status_ == "break", "Bad status: "..self.status_ )
return self.source_
end
function clientMeta.__index:line()
assert( self.status_ == "break", "Bad status: "..self.status_ )
return self.line_
end
function clientMeta.__index:setbreakpoint( source, line, value )
local setValue = value or nil
local sourceBp = self.breakPoints_[source]
if sourceBp == nil then
sourceBp = {}
self.breakPoints_[source] = sourceBp
end
sourceBp[line] = setValue
self.connection:send( "setbreakpoint", "running" )
self.connection:send( { source = source, line = line, value = value }, "running" )
end
function clientMeta.__index:callstack()
assert( self.status_ == "break", "Bad status: "..self.status_ )
self.connection:send( "callstack" )
self.connection:send( self.activeThread_ )
return self.connection:receive()
end
function clientMeta.__index:breakpoints()
--if self.status_ == "break" then
-- self.connection:send( "breakpoints" )
-- self.breakPoints_ = self.connection:receive()
--end
return self.breakPoints_
end
function clientMeta.__index:locals( level )
assert( self.status_ == "break", "Bad status: "..self.status_ )
self.connection:send( "locals" )
self.connection:send( self.activeThread_ )
self.connection:send( level )
return self.connection:receive()
end
function clientMeta.__index:upvalues( level )
assert( self.status_ == "break", "Bad status: "..self.status_ )
self.connection:send( "upvalues" )
self.connection:send( self.activeThread_ )
self.connection:send( level )
return self.connection:receive()
end
function clientMeta.__index:evaluate( expr, level )
assert( self.status_ == "break", "Bad status: "..self.status_ )
self.connection:send( "evaluate" )
self.connection:send( expr )
self.connection:send( self.activeThread_ )
self.connection:send( level )
return self.connection:receive()
end
function clientMeta.__index:releaseValue( id )
self.connection:send( "releaseValue", "running" )
self.connection:send( id, "running" )
end
function clientMeta.__index:getValue( id )
assert( self.status_ == "break", "Bad status: "..self.status_ )
self.connection:send( "getValue" )
self.connection:send( id )
return self.connection:receive()
end
function clientMeta.__index:coroutines()
assert( self.status_ == "break", "Bad status: "..self.status_ )
self.connection:send( "coroutines" )
return self.connection:receive()
end
for _, command in ipairs( { "run", "stepover", "stepin", "stepout" } ) do
clientMeta.__index[command] = function( self )
assert( self.status_ == "break", "Bad status: "..self.status_ )
self.connection:send( command )
--assert( self.connection:receive() == "ack_"..command )
self.status_ = "running"
end
end
function clientMeta.__index:breaknow()
assert( self.status_ == "running", "Bad status: "..self.status_ )
self.connection:send( "break", "running" )
end
function clientMeta.__index:cmd_synchronize()
self.connection:send( 0 )
self.connection:send( true )
end
function clientMeta.__index:cmd_break()
assert( self.status_ == "running" )
--self.connection:send( "ack_break" )
self.source_ = self.connection:receive()
self.line_ = self.connection:receive()
self.status_ = "break"
assert( type( self.source_ ) == "string" )
assert( type( self.line_ ) == "number" )
print( "Client "..self.clientId.." break at "..self.source_.."("..self.line_..")" )
runEvents_( "onClientBreak", self.clientId )
end
function clientMeta.__index.errorHandler( msg )
if msg == "closed" then return msg end
return debug.traceback( msg )
end
function listclients()
local res = {}
for idx, client in pairs( clients ) do
table.insert( res, { clientId = idx } )
end
return res
end
function getactiveclient()
return activeClient or 0
end
function setactiveclient( idx )
idx = assert( tonumber( idx ) )
print( "Setting active client: "..idx )
activeClient = idx
if clients[idx] == nil then
print( "Warning: no such client" )
end
end
function getClient( clientId )
return clients[clientId]
end
for _, func in ipairs( { "name", "ip", "status", "source", "line", "callstack", "locals", "coroutines", "run", "breaknow", "stepover", "stepin", "stepout", "setactivethread", "getactivethread", "getcurrentthread" } ) do
_M[func] = function( ... )
local idx = getactiveclient()
local client = clients[idx]
if client == nil then return "no such client" end
return client[func]( client, ... )
end
end
function shutdown()
print( "Shuting down grld.engine..." )
clients = nil
listeners = {}
end