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