From d55ba84834ab70dd13212134a517357f8d471447 Mon Sep 17 00:00:00 2001 From: mstreeter10 Date: Thu, 28 Sep 2023 15:46:44 -0400 Subject: [PATCH] udap: debug adapter protocol implementation Attempt to start udb in adapter mode Debuggee communication reconfiguration Add debuggee error handling Add debug console handling Respond to stackTrace request Respond to scope and variables requests Respond to continue, next, stepIn, and stepOut Add runInTerminal features Add udb and socket error handling Signed-off-by: mstreeter10 --- .gitignore | 4 + uni/udb/Makefile | 7 +- uni/udb/udap/Makefile | 29 ++ uni/udb/udap/communicator.icn | 295 ++++++++++++++++++ uni/udb/udap/dapcom.icn | 23 ++ uni/udb/udap/launch-dap.icn | 40 +++ uni/udb/udap/server.icn | 564 ++++++++++++++++++++++++++++++++++ uni/ulsp/Makefile | 4 + uni/ulsp/database.icn | 1 + 9 files changed, 966 insertions(+), 1 deletion(-) create mode 100644 uni/udb/udap/Makefile create mode 100644 uni/udb/udap/communicator.icn create mode 100644 uni/udb/udap/dapcom.icn create mode 100644 uni/udb/udap/launch-dap.icn create mode 100644 uni/udb/udap/server.icn diff --git a/.gitignore b/.gitignore index 149718e99..718f924cd 100644 --- a/.gitignore +++ b/.gitignore @@ -67,6 +67,8 @@ uni/progs/uget uni/progs/umake uni/progs/uprof uni/udb/udb +uni/udb/udap/udap +uni/udb/udap/dapcom uni/unicon/unicon uni/unidep/unidep uni/unidoc/.lastbuild @@ -81,6 +83,8 @@ uni/xml/testinvalid uni/xml/testnotwf uni/xml/testvalid uni/ulsp/ulsp +uni/ulsp/udap/udap +uni/ulsp/udap/progcom # uflex uni/uflex/flexgram.icn diff --git a/uni/udb/Makefile b/uni/udb/Makefile index 22dfd109a..92dbe8a6c 100644 --- a/uni/udb/Makefile +++ b/uni/udb/Makefile @@ -7,6 +7,7 @@ CLEAN = clean cflags = $(UFLAGS) -c ldflags = -u +.PHONY: all udap # # Add dependency on system include "evdefs.icn" because it is really sad # if udb gets broken. @@ -39,7 +40,10 @@ DTA = dta DTA_UFILES = $(DTA)/atomic_agent.u $(DTA)/temporals.u -all: Libfiles Assertions $(EXEs) +all: Libfiles Assertions udap $(EXEs) + +udap: + cd udap; $(MAKE) Libfiles: cd $(LIB); $(MAKE) @@ -134,6 +138,7 @@ clean: cd $(DTA); $(MAKE) clean $(DEL) ../../bin/$(EXEs) cd lib; $(MAKE) clean + cd udap; $(MAKE) clean cleantools: cd $(LIB); $(MAKE) cleantools diff --git a/uni/udb/udap/Makefile b/uni/udb/udap/Makefile new file mode 100644 index 000000000..5ad0483ca --- /dev/null +++ b/uni/udb/udap/Makefile @@ -0,0 +1,29 @@ +BASE = ../../.. +include $(BASE)/Makedefs.uni + +UFLAGS=-s -u + +SRC=launch-dap.icn server.icn communicator.icn +OBJ=launch-dap.u server.u communicator.u + +.PHONY:all + +all: dapcom udap + +udap: $(OBJ) + $(UC) $(DASHG) -o udap $(OBJ) + $(CP) udap$(EXE) ../../../bin + +launch-dap.u:launch-dap.icn server.u +server.u:server.icn communicator.u +communicator.u:communicator.icn + +dapcom:dapcom.icn + $(UC) $(DASHG) -o dapcom dapcom.icn + $(CP) dapcom$(EXE) ../../../bin + +zip: + zip udap.zip Makefile *.icn + +clean: + $(RM) -f *.u $(prog)$(EXE) uniclass* dapcom$(EXE) \ No newline at end of file diff --git a/uni/udb/udap/communicator.icn b/uni/udb/udap/communicator.icn new file mode 100644 index 000000000..620c7c4d4 --- /dev/null +++ b/uni/udb/udap/communicator.icn @@ -0,0 +1,295 @@ +package udap + +link findre +import json + +class Communicator(udb, udbSock, tpsock, dapcomSock, filePath, tpArgs) + + # Attempt to start udb if not already active and connect to it. + # Returns "success" if successful and an appropriate error string if otherwise. + method start_debugger(port) + local udbPath, dir, result + + udbPath := find_debugger() + if /udbPath then return "udap could not find udb" + + udb := system(udbPath || " -adapter " || port, &null, &null, &null, "nowait") + + udbSock := open_sock(port) + if /udbSock then return "udap failed to open udbSock: " || port + + tpsock := open_sock(port + 10) + if /tpsock then return "udap failed to open tpsock: " || port + 10 + + return "success" + end + + # Send a termination signal to udb. + method end_debugger() + kill(\udb, 9) + end + + # Returns udb's absolute path. + method find_debugger() + return pathfind("udb") + end + + # Returns a list of tables containing stack trace information. + # A table of with key "type" set to "crash" is returned if udb experiences an error. + method stack_trace() + local udbResTable, udbResTableList, i, frames + + udbResTableList := list() + frames := list() + + every i := udb_input("bt", 1) do { + put(udbResTableList, \i) + } + if *udbResTableList ~= 0 then { + every udbResTable := !udbResTableList do { + if member(udbResTable, "type") then { + if udbResTable["type"] == "frame" then { + udbResTable["name"] := replace(udbResTable["name"], "\"", "\\\"") + udbResTable["consoleMsg"] := replace(udbResTable["consoleMsg"], "\"", "\\\"") + put(frames, udbResTable) + } + if udbResTable["type"] == "crash" then return udbResTable + } + } + } + + return frames + end + + # Returns a list of tables containing scope information. + # A table of with key "type" set to "crash" is returned if udb experiences an error. + method get_scopes(frame) + local udbResTableList, udbResTable, scopes, i, j, k, l, m + + udbResTableList := list() + scopes := list() + + every i := udb_input("frame " || frame, 1) do put(udbResTableList, \i) + every j := udb_input("print -g", 1) do put(udbResTableList, \j) + every k := udb_input("print -l", 1) do put(udbResTableList, \k) + every l := udb_input("print -s", 1) do put(udbResTableList, \l) + every m := udb_input("print -p", 1) do put(udbResTableList, \m) + + if *udbResTableList ~= 0 then { + every udbResTable := !udbResTableList do { + if member(udbResTable, "type") then { + if member(udbResTable, "variables") then { + if udbResTable["type"] == "globals" & *udbResTable["variables"] > 0 then { + put(scopes, table("name", "Globals", "variablesReference", 1)) + } + if udbResTable["type"] == "locals" & *udbResTable["variables"] > 0 then { + put(scopes, table("name", "Locals", "variablesReference", 2)) + } + if udbResTable["type"] == "statics" & *udbResTable["variables"] > 0 then { + put(scopes, table("name", "Statics", "variablesReference", 3)) + } + if udbResTable["type"] == "params" & *udbResTable["variables"] > 0 then { + put(scopes, table("name", "Parameters", "variablesReference", 4)) + } + } + if udbResTable["type"] == "crash" then return udbResTable + } + } + } + + return scopes + end + + # Returns a list of tables containing variable information. + # A table of with key "type" set to "crash" is returned if udb experiences an error. + method get_variables(variablesReference) + local udbResTable, udbResTableList, variables, cmd, i + + udbResTableList := list() + variables := list() + + if variablesReference = 1 then cmd := "print -g" + else if variablesReference = 2 then cmd := "print -l" + else if variablesReference = 3 then cmd := "print -s" + else if variablesReference = 4 then cmd := "print -p" + + every i := udb_input(cmd, 1) do put(udbResTableList, \i) + + if *udbResTableList ~= 0 then { + every udbResTable := !udbResTableList do { + if member(udbResTable, "type") then { + if member(udbResTable, "variables") then { + if udbResTable["type"] == "globals" | udbResTable["type"] == "locals" | udbResTable["type"] == "statics" | udbResTable["type"] == "params" then { + variables := udbResTable["variables"] + } + } + if udbResTable["type"] == "crash" then return udbResTable + } + } + } + + every variable := !variables do { + variable["value"] := replace(variable["value"], "\"", "\\\"") + variable["type"] := replace(variable["type"], "\"", "\\\"") + variable["variablesReference"] := 0 + } + + return variables + end + + # Generator that suspends all udb commands needed for loading debuggee. + method load_cmds() + local dir, procs + + filePath ? dir := tab(findre("\/[^\/]+$")) + suspend "dir args " || dir + + if procs := find_debugger() then { + procs ? procs := tab(findre("unicon") + 6) + procs ||:= "/ipl/procs" + suspend "dir args " || procs + } + + if \tpArgs then suspend "load " || filePath || " " || tpArgs + else suspend "load " || filePath + end + + # Sets the file path of the debuggee. + method set_filepath(fpath) + filePath := fpath + end + + # Attempts to open a specified port. Returns communication source if successful. + method open_sock(port) + local sock + if /port then return "udb communication port not declared" + + every 1 to 5 do { + if sock := open(":" || port, "na") then { + return sock + } + else { + write("Attempting to open sock on port " || port || " again.") + delay(1000) + } + } + + write("udap failed to open port: " || port) + end + + # Disconnects from udb. + method disconnect_udbsock() + close(\udbSock) + udbSock := &null + end + + # Attempts to read udb socket output. + # Returns what was read or fails if reading isn't possible. + # If 'wait' is not null, process will wait a maximum of 5 seconds for udb to respond. + # 'wait' should be not null if udap needs a response from udb. + method udb_output(wait) + local msg, i, seg, failsafe + + failsafe := 0 + msg := "" + if /wait then { + while *select(udbSock, 200) > 0 do { + if failsafe > 50 then fail + seg := ready(udbSock) + if /seg then fail + msg ||:= seg + failsafe +:= 1 + } + } + else { + if *select(udbSock, 5000) > 0 then { + seg := ready(udbSock) + if /seg then fail + msg ||:= seg + } + while *select(udbSock, 200) > 0 do { + if failsafe > 50 then fail + seg := ready(udbSock) + if /seg then fail + msg ||:= seg + failsafe +:= 1 + } + } + + write("udb -> udap: " || msg) + return msg + end + + # Sends udb a command as 'exp' and returns what was output. + # A table of with key "type" set to "crash" is returned if udb experiences an error or if reading isn't possible. + # If 'wait' is not null, process will wait a maximum of 5 seconds for udb to respond. + # 'wait' should be not null if udap needs a response from udb. + # Will always return a crash table if a crash has happened unless if a call with 'resetError' not null has been executed. + method udb_input(exp, wait, resetError) + local udbRes, resultTable + static errorCalled + initial errorCalled := &null + + if \resetError then { + errorCalled := &null + return + } + + if \errorCalled then return errorCalled + + write("udap -> udb: " || exp) + write(udbSock, exp) + udbRes := udb_output(wait) + + if /udbRes then { + resultTable := table("type", "crash", "errornumber", 1040) + resultTable["errortext"] := "socket error" + errorCalled := resultTable + return resultTable + } + + if udbRes ~== "" then { + every resultTable := jtou(udbRes) do { + if member(resultTable, "type") then + if resultTable["type"] == "crash" then + errorCalled := resultTable + suspend resultTable + } + } + end + + # Returns the communcation source used to communicate with udb. + method get_communication_source() + if \udbSock then return udbSock + end + + # Attempts to set breakpoints given DAP setBreakpoints request as 'arguments'. + # Gives a "verified" key to each breakpoint table and sets it based on if the breakpoint was successfully set or not. + # A table of with key "type" set to "crash" is returned if udb experiences an error. + method set_breakpoints(arguments) + local breakpoints, bp, line, cond, udbResTable, udbResTableList, i + + udbResTableList := list() + + udb_input("clear break", 1) + + breakpoints := arguments["breakpoints"] + if *breakpoints = 0 then fail + + every bp := 1 to *breakpoints do { + line := breakpoints[bp]["line"] + cond := \breakpoints[bp]["condition"] + + every i := udb_input("b " || arguments["source"]["name"] || ":" || line, 1) do put(udbResTableList, \i) + + if *udbResTableList ~= 0 then { + every udbResTable := !udbResTableList do { + if member(udbResTable, "type") then + if udbResTable["type"] == "crash" then return udbResTable + breakpoints[bp]["verified"] := udbResTable["success"] + } + } + else breakpoints[bp]["verified"] := "__false__" + } + end +end diff --git a/uni/udb/udap/dapcom.icn b/uni/udb/udap/dapcom.icn new file mode 100644 index 000000000..3cb71309f --- /dev/null +++ b/uni/udb/udap/dapcom.icn @@ -0,0 +1,23 @@ +# +# relay communication between the target program's std in/out, and udap socket. +# +global sock +procedure main(argv) + port := (if &features == "MacOS" then "127.0.0.1" else "") || ":" || pop(argv) + every trap("SIGINT" | "SIGHUP" | "SIGPIPE", onExit) + + every !5 do + if sock := open(port, "n") then + break + else + delay(1000) + + \sock | stop("failed to connect to ",port, " ", &errortext ) + repeat every s := !select([sock, &input]) do + writes(ready(s === sock)) | writes(sock, ready()) +end + +procedure onExit(non) + close(\sock) + stop() +end diff --git a/uni/udb/udap/launch-dap.icn b/uni/udb/udap/launch-dap.icn new file mode 100644 index 000000000..4e71ccd52 --- /dev/null +++ b/uni/udb/udap/launch-dap.icn @@ -0,0 +1,40 @@ +import udap +link options +link basename +link ximage + +procedure usage() + local prog + prog := basename(&progname) + write("Usage: ", prog, " [options]\n") + write(prog, " is an implementation of the debug adapter protocol.") + write("You are handling the socket parameters/options for your IDE incorrectly.") + write("Check your IDE for the correct DAP server invocation.") + write("\nOptions:") + write("\t --socket : set the DAP server port") + write("\t -h : show this help\n") + exit(-1) +end + +procedure validate_args(args) + local opts, port + opts := options(args, "--socket:") + if *opts = 0 then usage() + port := \opts["-socket"] | usage() + port := opts["-socket"] + return port +end + + +procedure main(args) + local port + port := validate_args(args) | stop("Error: invalid args/port number.") + + if &features == "MacOS" then + #port := "localhost:" || port + port := "127.0.0.1:" || port + else + port := ":" || port + + Server(port).run() +end \ No newline at end of file diff --git a/uni/udb/udap/server.icn b/uni/udb/udap/server.icn new file mode 100644 index 000000000..09269fcd3 --- /dev/null +++ b/uni/udb/udap/server.icn @@ -0,0 +1,564 @@ +package udap + +link ximage +link strings +import json +global seq, lastUdbCmd, waitingForTerminal, requestQueue + +class Server(port, sock, communicator, shellProcessId, clientDetails, currentRequestBody, udbError) + + # Main loop udap process. + method run() + local request_body, jsontable, request_seq, request_command, request_arguments, cmd + + repeat { + every request_body := get_request(sock) do { + if \waitingForTerminal then { + if jtou(request_body)["command"] ~== "runInTerminal" then { + push(requestQueue, request_body) + next + } + else { + waitingForTerminal := &null + process_request(request_body) + every cmd := communicator.load_cmds() do udb_input(cmd) + every request_body := pop(requestQueue) do process_request(request_body) + } + } + else { + process_request(request_body) + } + } + } + end + + # Given a DAP client request body, process that request. + method process_request(request_body) + local jsontable, request_seq, request_command, request_arguments, response_body + + jsontable := jtou(request_body) + + if jsontable["type"] == "request" then currentRequestBody := jsontable + + request_seq := jsontable["seq"] + request_command := jsontable["command"] + request_arguments := jsontable["arguments"] + response_body := jsontable["body"] + + write("client -> udap: " || request_body) + + case request_command of { + "initialize": { initialize(request_seq, request_command, request_arguments) } + "launch" : { launch(request_seq, request_command, request_arguments) } + "setBreakpoints" : { set_breakpoints(request_seq, request_command, request_arguments) } + "configurationDone" : { udb_input("run", 1); if /udbError then acknowledge(request_seq, request_command) } + "threads" : { threads(request_seq, request_command) } + "continue" : { udb_input("cont", 1); if /udbError then acknowledge(request_seq, request_command) } + "next" : { udb_input("next", 1); if /udbError then acknowledge(request_seq, request_command) } + "stepIn" : { udb_input("step", 1); if /udbError then acknowledge(request_seq, request_command) } + "stepOut" : { udb_input("return", 1); if /udbError then acknowledge(request_seq, request_command) } + "stackTrace" : { stackTrace(request_seq, request_command, request_arguments) } + "scopes" : { scopes(request_seq, request_command, request_arguments) } + "variables" : { variables(request_seq, request_command, request_arguments) } + "disconnect" : { acknowledge(request_seq, request_command); disconnect() } + "runInTerminal" : { if member(response_body, "shellProcessId") then shellProcessId := response_body["shellProcessId"] } + "evaluate" : { evaluate(request_seq, request_command, request_arguments) } + default: { write("Don't know what to do with: ", request_command) } + } + end + +################################################################################# +# Get Request # +################################################################################# + + # Attempt to read messages from client given a socket and returns each message as a generator. + method get_request(sock) + local request_body, msg, len + + while /request_body | request_body == "" do { + + # Even while waiting for request, listen to udb + if \communicator.udbSock then { + select([sock, communicator.udbSock]) + udb_listen() + } + else select(sock) + + msg := ready(sock) + + # Handling socket reading anomoly: header alone or header + request_body + while msg ~== "" do { + msg ? { + tab(find("Content-Length:") + 16) + len := integer(tab(many(&digits))) + tab(upto("\r\n\r\n") + 4) + if pos(0) then { + request_body := ready(sock, len) + } + else { + request_body := move(len) + msg := tab(0) + } + } + suspend request_body + } + } + end + +################################################################################# +# Build Response # +################################################################################# + + # Create and return a response for client in json format. + method build_response(request_seq, success, request_command, body, message) + local responseTable, responseBody, responseHeader + + responseTable := table() + responseTable["seq"] := seq + responseTable["type"] := "response" + responseTable["request_seq"] := request_seq + responseTable["success"] := success + responseTable["command"] := request_command + responseTable["message"] := \message + responseTable["body"] := \body + + responseBody := tojson(responseTable) + responseHeader := "Content-Length:" || *responseBody || "\r\n\r\n" + + write("udap -> client: " || responseBody) + + seq +:= 1 + + return responseHeader || responseBody + end + +################################################################################# +# Build Request # +################################################################################# + + # Create and return a request for client in json format. + method build_request(command, arguments) + local requestTable, requestBody, requestHeader + + requestTable := table() + requestTable["seq"] := seq + requestTable["type"] := "request" + requestTable["command"] := command + requestTable["arguments"] := \arguments + + requestBody := tojson(requestTable) + requestHeader := "Content-Length:" || *requestBody || "\r\n\r\n" + + write("udap -> client: " || requestBody) + + seq +:= 1 + + return requestHeader || requestBody + end + +################################################################################# +# Build Event # +################################################################################# + + # Create and return an event for client in json format. + method build_event(event, body) + local eventTable, eventBody, eventHeader + + eventTable := table() + eventTable["seq"] := seq + eventTable["type"] := "event" + eventTable["event"] := event + eventTable["body"] := \body + + eventBody := tojson(eventTable) + eventHeader := "Content-Length:" || *eventBody || "\r\n\r\n" + + write("udap -> client: " || eventBody) + + seq +:= 1 + + return eventHeader || eventBody + end + +######################################################## +# initialize # +######################################################## + + # Handles all the things required from a client "initialize" request. + method initialize(request_seq, request_command, request_arguments) + local capabilitiesTable, res, udbPort, startRes, event, req + + clientDetails := \request_arguments + + port ? { + move() + udbPort := tab(0) + } + udbPort := integer(udbPort) + udbPort +:= 10 + + startRes := communicator.start_debugger(udbPort) + if startRes ~== "success" then { + res := build_response(request_seq, "__false__", request_command, &null, startRes) + writes(sock, res) + disconnect(startRes) + } + + capabilitiesTable := table() + capabilitiesTable["supportsConfigurationDoneRequest"] := "__true__" + + res := build_response(request_seq, "__true__", request_command, capabilitiesTable) + writes(sock, res) + + event := build_event("initialized") + writes(sock, event) + end + +######################################################## +# launch # +######################################################## + + # Handles all the things required from a client "launch" request. + method launch(request_seq, request_command, request_arguments) + local res, initEvent, pth, event, dapPort + + pth := request_arguments["program"] + pth ? { + pth := tab(find(".icn")) + } + communicator.set_filepath(pth) + + if member(request_arguments, "args") then { + communicator.tpArgs := request_arguments["args"] + } + + port ? { + move() + dapPort := tab(0) + } + dapPort := integer(dapPort) + 30 + + event := build_request("runInTerminal", + table("kind", "integrated", "cwd", "", "title", "udbTerminal", "args",[ "dapcom " || dapPort ] , + "argsCanBeInterpretedByShell", "__true__")) + writes(sock, event) + waitingForTerminal := 1 + + communicator.dapcomSock := communicator.open_sock(dapPort) + if /communicator.dapcomSock then { + res := build_response(request_seq, "__false__", request_command, &null, "udap failed to open dapcomSock: " || port + 10) + writes(sock, res) + return + } + + res := build_response(request_seq, "__true__", request_command) + writes(sock, res) + end + +######################################################## +# set breakpoints # +######################################################## + + # Handles all the things required from a client "setBreakpoints" request. + method set_breakpoints(request_seq, request_command, request_arguments) + local res, breakpointTable, breakpoint, resTable + + breakpointTable := table() + breakpointTable["breakpoints"] := request_arguments["breakpoints"] + + resTable := communicator.set_breakpoints(request_arguments) + + if type(\resTable) == "table" then { + if member(resTable, "type") then + if resTable["type"] == "crash" then + handle_error(resTable) + } + else { + res := build_response(request_seq, "__true__", request_command, breakpointTable) + writes(sock, res) + } + end + +######################################################## +# threads # +######################################################## + + # Handles all the things required from a client "threads" request. + method threads(request_seq, request_command) + local res, threadsTable + + threadsTable := table() + threadsTable["threads"] := [table("id", 1, "name", "main")] + + res := build_response(request_seq, "__true__", request_command, threadsTable) + writes(sock, res) + end + +######################################################## +# stackTrace # +######################################################## + + # Handles all the things required from a client "stackTrace" request. + method stackTrace(request_seq, request_command, request_arguments) + local res, stackList + + stackList := communicator.stack_trace() + + if type(stackList) == "table" then { + if member(stackList, "type") then + if stackList["type"] == "crash" then + handle_error(stackList) + } + else { + res := build_response(request_seq, "__true__", request_command, table("stackFrames", stackList)) + writes(sock, res) + } + end + +######################################################## +# scopes # +######################################################## + + # Handles all the things required from a client "scopes" request. + method scopes(request_seq, request_command, request_arguments) + local res, scopes + + scopes := communicator.get_scopes(request_arguments["frameId"]) + + if type(scopes) == "table" then { + if member(scopes, "type") then + if scopes["type"] == "crash" then + handle_error(scopes) + } + else { + res := build_response(request_seq, "__true__", request_command, table("scopes", scopes)) + writes(sock, res) + } + end + +######################################################## +# variables # +######################################################## + + # Handles all the things required from a client "variables" request. + method variables(request_seq, request_command, request_arguments) + local res, variables + + variables := communicator.get_variables(request_arguments["variablesReference"]) + + if type(variables) == "table" then { + if member(variables, "type") then + if variables["type"] == "crash" then + handle_error(variables) + } + else { + res := build_response(request_seq, "__true__", request_command, table("variables", variables)) + writes(sock, res) + } + end + +######################################################## +# acknowledge # +######################################################## + + # Default response for request that only requires an acknowledgement. + method acknowledge(request_seq, request_command) + local res + + res := build_response(request_seq, "__true__", request_command) + writes(sock, res) + end + +######################################################## +# udb_listen # +######################################################## + + # Reads udb's communication socket and processes any output collected. + method udb_listen() + local outputTable, udbRes + + udbRes := communicator.udb_output() + if \udbRes ~== "" then { + every outputTable := jtou(udbRes) do { + process_udb_output_table(outputTable) + } + } + end + +######################################################## +# udb_input # +######################################################## + + # Sends udb a command as 'expression' and processes any output collected. + # If 'wait' is not null, process will wait a maximum of 5 seconds for udb to respond. + # 'wait' should be not null if udap needs a response from udb. + method udb_input(expression, wait) + local outputTable + every outputTable := communicator.udb_input(expression, wait) do { + process_udb_output_table(outputTable) + } + end + +######################################################## +# process_udb_output_table # +######################################################## + + # Given a udb output table as 'outputTable', process that table. + method process_udb_output_table(outputTable) + local udbRes := "", event + + udbRes := replacem(\outputTable["consoleMsg"], "\\\\\\\"", "\\\"", "\\\\\"", "\\\"", "\"", "\\\"") + + /outputTable["type"] := "console" + if not (outputTable["type"] == ("exited" | "stderr" | "crash")) then { + event := build_event("output", table("category", "console", "output", udbRes)) + writes(sock, event) + if outputTable["type"] == "breakpoint" then { + event := build_event("stopped", table( + "reason", "breakpoint", + "description", udbRes, + "hitBreakpointIds", [outputTable["id"]], + "threadId", 1)) + writes(sock, event) + } + else if outputTable["type"] == "step" then { + event := build_event("stopped", table( + "reason", "step", + "threadId", 1)) + writes(sock, event) + } + } + else if outputTable["type"] == "stderr" then { + event := build_event("output", table("category", "stderr", "output", udbRes)) + writes(sock, event) + event := build_event("stopped", table( + "reason", "exception", + "description", udbRes, + "text", udbRes, + "threadId", 1)) + writes(sock, event) + } + else if outputTable["type"] == "exited" then { + event := build_event("exited", table("exitCode", outputTable["exitCode"])) + writes(sock, event) + } + else if outputTable["type"] == "crash" then { + handle_error(outputTable) + return + } + if \outputTable["requireResponse"] then { + event := build_event("output", table("category", "console", "output", "[answered Y; input not from terminal]\n")) + writes(sock, event) + udb_input("Y") + } + end + +######################################################## +# evaluate # +######################################################## + + # Handles all the things required from a client "evaluate" request. + method evaluate(request_seq, request_command, request_arguments) + local res, expression, result := "", isEvalExp + + expression := request_arguments["expression"] + expression ? { + if tab(match("-exec ")) then { + expression := tab() + lastUdbCmd := expression + } + else if expression ~== "" then { + expression := "p "||expression + } + else if \lastUdbCmd then { + expression := lastUdbCmd + } + } + + udb_input(expression) + end + +######################################################## +# handle_error # +######################################################## + + # Handle a udb output table of "type" set to "crash". + method handle_error(outputTable) + local errorText, errorMessageTable, res, event + + udbError := "__true__" + + errorText := "Unicon Debugger experienced an error during " || currentRequestBody["command"] || " request. " + if \outputTable then { + errorText ||:= "Error number: " || outputTable["errornumber"] || ". " + if member(outputTable, "errortext") then errorText ||:= "Error text: " || outputTable["errortext"] || ". " + if member(outputTable, "errorvalue") then errorText ||:= "Error value: " || outputTable["errorvalue"] || ". " + } + errorText ||:= "Closing Unicon Debugger." + + errorMessageTable := table("format", errorText, "showUser", "__true__") + if \outputTable then errorMessageTable["id"] := outputTable["errornumber"] + else errorMessageTable["id"] := 0 + + res := build_response(currentRequestBody["seq"], "__false__", currentRequestBody["command"], table("error", errorMessageTable)) + writes(sock, res) + + event := build_event("terminated") + writes(sock, event) + end + + # Enable communication between dapcom and debugee. + method debuggee_dapcom_communication() + local resList, res + repeat { + if \communicator then { + resList := select([\communicator.tpSock, \communicator.dapcomSock]) | next + every res := !resList do { + case res of { + communicator.tpSock: writes(communicator.dapcomSock, ready(communicator.tpSock)) + communicator.dapcomSock: writes(communicator.tpSock, ready(communicator.dapcomSock)) + } + } + } + } + end + +######################################################## +# disconnect # +######################################################## + + # Disconnect from client and udb and startup as a fresh session. + method disconnect() + communicator.disconnect_udbsock() + communicator.end_debugger() + communicator.udb_input(&null, &null, 1) + kill(\shellProcessId, 9) + close(\sock) + startup() + end + + # Attempt to open communication port for client and set default class parameters. + method startup() + every 1 to 5 do + if sock := open(port, "na") then { + break + } + else { + write("open(",port,") ERROR: ", &errortext) + delay(1000) + } + + if /sock then stop("failed to connect to ",port) + + communicator := Communicator() + + seq := 1 + udbError := &null + lastUdbCmd := "" + requestQueue := [] + end + + initially + thread debuggee_dapcom_communication() + startup() +end \ No newline at end of file diff --git a/uni/ulsp/Makefile b/uni/ulsp/Makefile index f66bcc761..03df291d6 100644 --- a/uni/ulsp/Makefile +++ b/uni/ulsp/Makefile @@ -10,6 +10,10 @@ OBJ=launch-lsp.u workspace.u database.u server.u completion.u signature.u hover. export IPATH=$(UNI)/unidoc +.PHONY: all + +all: $(prog) + $(prog): $(OBJ) $(UC) $(DASHG) -o $(prog) $(OBJ) $(CP) $(prog)$(EXE) ../../bin diff --git a/uni/ulsp/database.icn b/uni/ulsp/database.icn index 1011333f9..3f9931939 100644 --- a/uni/ulsp/database.icn +++ b/uni/ulsp/database.icn @@ -117,6 +117,7 @@ class LSPDB( put(paths, dirPath || "lib") put(paths, dirPath || "unidoc") put(paths, dirPath || "ulsp") + put(paths, dirPath || "ulsp/udap") every path := !paths do build_by_path(path) end