From 43efe9bdb872a1af277fb18b71112c0d88a7c43d Mon Sep 17 00:00:00 2001 From: mstreeter10 Date: Sun, 29 Oct 2023 19:03:30 -0400 Subject: [PATCH 1/2] udb: prepare to add debug adapter protocol support Tableize breakpoint Tableize stackFrame Tableize checkstep Tableize run Add full reset command Alter required response handling Alter error handling Update version number Signed-off-by: mstreeter10 --- uni/udb/breakpoint.icn | 481 +++++++++++++++++++++++--------------- uni/udb/console.icn | 39 +++- uni/udb/data.icn | 98 ++++++-- uni/udb/defaults.icn | 2 +- uni/udb/dta/temporals.icn | 2 +- uni/udb/evaluator.icn | 6 +- uni/udb/session.icn | 72 ++++-- uni/udb/stack.icn | 25 +- uni/udb/state.icn | 120 ++++++++-- uni/udb/stepping.icn | 2 +- uni/udb/trace.icn | 16 +- uni/udb/udb.icn | 44 +++- uni/udb/watchpoint.icn | 10 +- 13 files changed, 643 insertions(+), 274 deletions(-) diff --git a/uni/udb/breakpoint.icn b/uni/udb/breakpoint.icn index 92dc9e060..dbd13f6fc 100644 --- a/uni/udb/breakpoint.icn +++ b/uni/udb/breakpoint.icn @@ -32,28 +32,27 @@ class BreakPoint( # Check the Monitored file for a breakpoint # method checkBreakpoint() - local cur_file, cur_line, L, bp, id - + local cur_file, cur_line, L, bp, id, bpTable cur_file := keyword("file", MONITORED) if member(breakPoints, cur_file) then { cur_line := keyword("line", MONITORED) L := breakPoints[cur_file] - every bp := !L do - if cur_line = bp.line & bp.state = ENABLED & - id := isBreakExist(cur_file,cur_line) & + every bp := !L do { + if cur_line = bp.line & bp.state = ENABLED & + id := isBreakExist(cur_file,cur_line) & (/bp.coexp | bp.coexp.id == DState.coState.curr.id) - then { - - DState.State := PAUSE - # this delete will help "next" and "step" operate well - # jut remove the the breakMask set from the valueMask - # table temporarly until the continue command comes on - delete(DState.valueMask,E_Line) - msg := constructBpMsg(bp, 1) # 1 -> shows source code. add a flag? - DState.Write(msg) - return - } + then { + DState.State := PAUSE + # this delete will help "next" and "step" operate well + # jut remove the the breakMask set from the valueMask + # table temporarly until the continue command comes on + delete(DState.valueMask,E_Line) + bpTable := constructBpMsg(bp, 1) # 1 -> shows source code. add a flag? + DState.Write(bpTable) + return + } } + } end # @@ -76,20 +75,31 @@ end # method constructBpMsg(bp, verbose) local breakpoint := "", coexp := "", src_code := "" + local bpTable := table() if /bp then return "" # null breakpoint? breakpoint ||:= "\n Breakpoint #"|| bp.id || " (" || getBreakState(bp.state) || ") at " || bp.fname || ":" || bp.line + bpTable["id"] := bp.id + bpTable["state"] := getBreakState(bp.state) + bpTable["source"] := bp.fname + bpTable["line"] := bp.line + bpTable["type"] := "breakpoint" - if \verbose then + if \verbose then { src_code ||:= "\n " || bp.line || ": " || bp.code + bpTable["code"] := bp.code + } - if \bp.coexp then - coexp ||:= " coexp " || bp.coexp.id + if \bp.coexp then { + coexp ||:= " coexp " || bp.coexp.id + bpTable["coexp"] := bp.coexp.id + } - return breakpoint || coexp || src_code + bpTable["consoleMsg"] := breakpoint || coexp || src_code + return bpTable end # @@ -97,47 +107,64 @@ end # method printBreakInfo(cmd) local x, L, bp, name + local resultTable := table("cmd", cmd, "breakpoints", list()) if type(cmd) == "list" then name := cmd[3] msg := "" if /name then { every x := key(breakPoints) do { - L := breakPoints[x] - every bp := !L do { - msg ||:= constructBpMsg(bp) - } - } + L := breakPoints[x] + every bp := !L do { + bpTable := constructBpMsg(bp) + msg ||:= bpTable["consoleMsg"] + push(resultTable["breakpoints"], bpTable) + } } + } else { if integer(name) then { - every x := key(breakPoints) do { - L := breakPoints[x] - every bp := !L do { - if name = bp.id then { - msg := constructBpMsg(bp) - break break - } - } - msg||:="\n Breakpoint #"||name||" is not available." - } - } + every x := key(breakPoints) do { + L := breakPoints[x] + every bp := !L do { + if name = bp.id then { + bpTable := constructBpMsg(bp) + msg ||:= bpTable["consoleMsg"] + push(resultTable["breakpoints"], bpTable) + break break + } + } + msg||:="\n Breakpoint #"||name||" is not available." + push(resultTable["breakpoints"], bpTable) + } + } else { - if not find(".icn",name) then name ||:= ".icn" - if DState.srcFile.isSrcFile(name) then { - if L:=breakPoints[name] & type(L) == "list" then { - every bp := !L do - msg ||:= constructBpMsg(bp) - } - else - msg := "\n No breakpoints available in "||name - } - else - msg := "\n Source file \""||name||"\" is not in the binary !!!" - } + if not find(".icn",name) then name ||:= ".icn" + if DState.srcFile.isSrcFile(name) then { + if L:=breakPoints[name] & type(L) == "list" then { + every bp := !L do { + bpTable := constructBpMsg(bp) + msg ||:= bpTable["consoleMsg"] + push(resultTable["breakpoints"], bpTable) + } + } + else { + msg := "\n No breakpoints available in "||name + setResultTable(resultTable, "No breakpoints available in " || name, "__true__") + } + } + else { + msg := "\n Source file \""||name||"\" is not in the binary !!!" + setResultTable(resultTable, "Source file " ||name|| " not in binary", "__false__") + resultTable["reason"] := "failed" + } } - if *msg = 0 then + } + setResultTable(resultTable, &null, &null, msg) + if *msg = 0 then { msg := "\n No breakpoints available." - DState.Write(msg) + setResultTable(resultTable, "No breakpoints available", "__true__", msg) + } + DState.Write(resultTable) end # @@ -145,6 +172,7 @@ end # method isBreakExist(fname, line, breakpoint) local L, bp, coexp + local resultTable := table("cmd", cmd) if member(breakPoints, fname) then { L := breakPoints[fname] @@ -159,10 +187,12 @@ method isBreakExist(fname, line, breakpoint) if (\coexp & (/bp.coexp | bp.coexp.id ~= coexp.id)) | (/coexp & \bp.coexp) then { # ask if breakpoint should be replaced - msg := constructBpMsg(bp) || " already exists. " + msg := constructBpMsg(bp)["consoleMsg"] || " already exists. " msg ||:= "\n Do you really want to replace it (Y/n)?: " - DState.Writes(msg) - if read() == (""|"y"|"Y"|"yes"|"YES") then { + setResultTable(resultTable, &null, &null, msg) + resultTable["requireResponse"] := "__true__" + DState.Writes(resultTable) + if map(DState.stateRead()) == ("y" | "yes" | "") then { # replace breakpoint id breakpoint.id := bp.id count -:= 1 @@ -217,6 +247,7 @@ end method parse_colon_arg(arg) local arg_list, num_colon, s + local resultTable := table() arg_list := [] num_colon := 0 @@ -234,14 +265,16 @@ method parse_colon_arg(arg) while c := (move(1) == ":") do { num_colon +:= 1 if num_colon > 2 then { - DState.State := ERROR - msg :="\n \""||DState.cmdHistory[1]|| - "\" is not a known command"|| - "\n Try \"help\" or \"help break\" for assistance." - DState.Write(msg) + DState.State := ERROR + msg :="\n \""||DState.cmdHistory[1]|| + "\" is not a known command"|| + "\n Try \"help\" or \"help break\" for assistance." + + setResultTable(resultTable, DState.cmdHistory[1]||" is not a known command", "__false__", msg) + DState.Write(resultTable) fail - } } + } # append word argument put(arg_list,s) s := "" @@ -267,13 +300,17 @@ end method cmdBreak(cmd) local code, line, fname, cname, pkgname, arg, break_point, i local coexp, num_colon, arg_list + local resultTable := table("cmd", cmd) msg := "" if not (DState.State = (LOAD | PAUSE)) then { DState.State := ERROR msg := "\n No Program is Loaded_ \n Type \"help\" for assistance" - DState.Write(msg) - return + setResultTable(resultTable, "No program loaded", &null, msg) + resultTable["verified"] := "__false__" + resultTable["reason"] := "failed" + + DState.Write(resultTable) } DState.State := SKIP @@ -289,7 +326,8 @@ method cmdBreak(cmd) DState.State := ERROR msg := "\n "||cmd[-2]||" "||cmd[-1]||" does not exist."|| "\n Use command \"coexp\" to view activated co-expressions." - DState.Write(msg) + setResultTable(resultTable, "coexp error??", "__false__", msg) + DState.Write(resultTable) fail } until pull(cmd) == "coexp" # pull [coexp N] off list @@ -316,7 +354,8 @@ method cmdBreak(cmd) DState.State := ERROR msg := "\n Could not find procedure \""||arg||"\"" "\n Please try again" - DState.Write(msg) + setResultTable(resultTable, "Could not find procedure " ||arg, "__false__", msg) + DState.Write(resultTable) fail } } @@ -337,7 +376,8 @@ method cmdBreak(cmd) if temp := integer(arg) then line := temp else { msg := "\n Expected \""||arg||"\" to be a line number." || - "\n Please try again." + "\n Please try again." + setResultTable(resultTable, "Expected " ||arg|| " to be a line number", "__false__", msg) } } # break class::method | break package::procedure @@ -354,7 +394,8 @@ method cmdBreak(cmd) else { msg := "\n Could not find \""||arg_list[1]||"::"||arg|| "\".\n Please try again." - DState.Write(msg) + setResultTable(resultTable, "Could not find "||arg_list[1]||"::"||arg, "__false__", msg) + DState.Write(resultTable) fail } } @@ -365,24 +406,25 @@ method cmdBreak(cmd) cname := arg_list[3] arg := arg_list[5] - if temp := DState.srcFile.findPkgClassMethod(pkgname, cname, arg) - then { + if temp := DState.srcFile.findPkgClassMethod(pkgname, cname, arg) then { fname := temp[1] line := temp[2] - } + } else { msg := "\n Could not find \""||pkgname||"::"||arg||"::"||arg|| "\".\n Please try again." - DState.Write(msg) + setResultTable(resultTable, "Could not find "||pkgname||"::"||arg||"::"||arg, "__false__", msg) + DState.Write(resultTable) fail - } } + } # invalid syntax else { DState.State := ERROR msg :="\n \""||DState.cmdHistory[1]||"\" is not a known command" msg ||:="\n Try \"help\" or \"help break\" for assistance." - DState.Write(msg) + setResultTable(resultTable, DState.cmdHistory[1]|| " is not a known command", "__false__", msg) + DState.Write(resultTable) return } } @@ -396,7 +438,8 @@ method cmdBreak(cmd) DState.State := ERROR msg :="\n \""||DState.cmdHistory[1]||"\" is not a known command" msg ||:="\n Try \"help\" or \"help break\" for assistance." - DState.Write(msg) + setResultTable(resultTable, DState.cmdHistory[1]|| " is not a known command", "__false__", msg) + DState.Write(resultTable) return } @@ -412,17 +455,25 @@ method cmdBreak(cmd) if \break_point & \break_point.line then { line := break_point.line if not isBreakExist(fname, line, break_point) then { - addBreakPoint(fname, break_point) - msg ||:= constructBpMsg(break_point) - } - DState.Write(msg) + addBreakPoint(fname, break_point) + bpTable := constructBpMsg(break_point) + msg ||:= bpTable["consoleMsg"] + resultTable["breakpoint"] := bpTable + setResultTable(resultTable, &null, "__true__") + } + else { + setResultTable(resultTable, "Breakpoint already exists", "__false__") + } + setResultTable(resultTable, &null, &null, msg) + DState.Write(resultTable) return } else { msg :="\n \""||DState.cmdHistory[1]||"\" is not a known command" msg ||:="\n Try \"help\" or \"help break\" for assistance." - DState.Write(msg) - } + setResultTable(resultTable, DState.cmdHistory[1]|| " is not a known command", "__false__", msg) + DState.Write(resultTable) + } end # @@ -430,6 +481,7 @@ end # method clearAllBreakpoints() local x, size := 0 + local resultTable := table() size := *breakPoints if size > 0 then { @@ -437,11 +489,17 @@ method clearAllBreakpoints() every x := key(breakPoints) do delete(breakPoints, x) msg := "\n All breakpoints are cleared; ("||size||" total)." + setResultTable(resultTable, &null, "__true__") + resultTable["size"] := size count := 0 } - else + else { msg := "\n No breakpoints are available to be cleared." - DState.Write(msg) + setResultTable(resultTable, "No breakpoints available", "__false__") + } + + setResultTable(resultTable, &null, &null, msg) + DState.Write(resultTable) return end @@ -450,6 +508,7 @@ end # method cmdClear(cmd) local line, fname, arg, id, i, size:=0 + local resultTable := table("cmd", cmd) # clears all of the preset breakpoints # 'clear break' @@ -471,7 +530,8 @@ method cmdClear(cmd) DState.State := ERROR msg := "\n Could not find procedure \""||arg||"\"" "\n Please try again" - DState.Write(msg) + setResultTable(resultTable, "Could not find procedure "||arg, "__false__", msg) + DState.Write(resultTable) fail } } @@ -494,9 +554,10 @@ method cmdClear(cmd) DState.State := ERROR msg :="\n \""||DState.cmdHistory[1]||"\" is not a known command" msg ||:="\n Try \"help\" or \"help break\" for assistance." - DState.Write(msg) + setResultTable(resultTable, DState.cmdHistory[1]|| " is not a known command", "__false__", msg) + DState.Write(resultTable) return - } + } if \fname then { if not find(".icn", fname) then @@ -508,12 +569,16 @@ method cmdClear(cmd) msg :="\n Deleted breakpoint #"||id||" at : file "||fname|| ", line "||line ||"." msg||:="\n Source code:"||DState.srcFile.getSrcLine(fname, line) + setResultTable(resultTable, "Deleted breakpoint #"||id|| " at : file "||fname||", line "||line, "__true__") } else { msg :="\n No breakpoint at : file "||fname||", line "||line ||"." msg||:="\n Source code: "||DState.srcFile.getSrcLine(fname, line) + setResultTable(resultTable, "No breakpoint at : file "||fname||", line "||line, "__false__") + resultTable["reason"] := "failed" } - DState.Write(msg) + setResultTable(resultTable, &null, &null, msg) + DState.Write(resultTable) end # @@ -521,52 +586,62 @@ end # method cmdDelete(cmd) local id, x, L, i, j:=0 + local resultTable := table("cmd", cmd) if not cmd[3] then { every x := key(breakPoints) do { - L := breakPoints[x] - every i := 1 to *L do { - if L[i].state ~= DELETED then { - L[i].state := DELETED - j +:= 1 - } - } - } - if j > 0 then - msg := "\n All breakpoints are deleted; ("||j||" in total)." - else - msg := "\n No breakpoints are available to be deleted." - DState.Write(msg) - return + L := breakPoints[x] + every bp := !L do { + if bp.state ~= DELETED then { + bp.state := DELETED + j +:= 1 + } + } + } + if j > 0 then { + msg := "\n All breakpoints are deleted; ("||j||" in total)." + setResultTable(resultTable, "All breakpoints are deleted; ("||j||" in total)", "__true__") + } + else { + msg := "\n No breakpoints are available to be deleted." + setResultTable(resultTable, "No breakpoint are available to be deleted", "__false__") } + setResultTable(resultTable, &null, &null, msg) + DState.Write(resultTable) + return + } else if id := integer(cmd[3]) then { every x := key(breakPoints) do { - L := breakPoints[x] - every i := 1 to *L do { - if L[i].id = id then - if L[i].state ~= DELETED then { - L[i].state := DELETED - msg := "\n Breakpoint #"||id||" is deleted." - DState.Write(msg) - return - } - else { - msg := "\n Breakpoint #"||id||" is already deleted." - DState.Write(msg) - fail - } - } - } - msg := "\n Breakpoint #"||id||" is not available." - DState.Write(msg) - fail + L := breakPoints[x] + every bp := !L do { + if bp.id = id then + if bp.state ~= DELETED then { + bp.state := DELETED + msg := "\n Breakpoint #"||id||" is deleted." + setResultTable(resultTable, "Breakpoint #"||id||" is deleted", "__true__", msg) + DState.Write(resultTable) + return + } + else { + msg := "\n Breakpoint #"||id||" is already deleted." + setResultTable(resultTable, "Breakpoint #"||id||" is already deleted", "__false__", msg) + DState.Write(resultTable) + fail + } + } } + msg := "\n Breakpoint #"||id||" is not available." + setResultTable(resultTable, "Breakpoint #"||id||" is not available", "__false__", msg) + DState.Write(resultTable) + fail + } else { msg:="\n Undefined Command: \""||DState.cmdHistory[1]||"\""|| "\n Try \"help\" for assistance." - DState.Write(msg) + setResultTable(resultTable, "Undefined Command: "||DState.cmdHistory[1], "__false__", msg) + DState.Write(resultTable) fail - } + } end # @@ -574,55 +649,69 @@ end # method cmdEnable(cmd) local id, x, L, i, j:=0 + local resultTable := table() # Enables all the preset breakpoints # enable break if not cmd[3] then { every x := key(breakPoints) do { - L := breakPoints[x] - every i := 1 to *L do - if L[i].state = DISABLED then { - L[i].state := ENABLED - j +:= 1 - } - } - if j > 0 then - msg := "\n All breakpoints are enabled; ("||j||" total)." - else - msg := "\n No disabled breakpoints are available to be enabled." - DState.Write(msg) - return + L := breakPoints[x] + every bp := !L do { + if bp.state = DISABLED then { + bp.state := ENABLED + j +:= 1 + } + } + } + if j > 0 then { + msg := "\n All breakpoints are enabled; ("||j||" total)." + setResultTable(resultTable, "All breakpoints are enabled; ("||j||" total)", "__true__") + } + else { + msg := "\n No disabled breakpoints are available to be enabled." + setResultTable(resultTable, "No disabled breakpoints are available to be enabled", "__false__") } + setResultTable(resultTable, &null, &null, msg) + DState.Write(resultTable) + return + } else if id := integer(cmd[3]) then { every x := key(breakPoints) do { - L := breakPoints[x] - every i := 1 to *L do { - if L[i].id = id then - if L[i].state = DISABLED then { - L[i].state := ENABLED - msg := "\n Breakpoint #"||id||" is enabled." - DState.Write(msg) - return - } - else { - if L[i].state = ENABLED then - msg:="\n Breakpoint #"||id||" is already enabled." - else - msg:="\n Breakpoint #"||id||" is deleted;_ - it cannot be enabled." - DState.Write(msg) - fail - } + L := breakPoints[x] + every bp := !L do { + if bp.id = id then + if bp.state = DISABLED then { + bp.state := ENABLED + msg := "\n Breakpoint #"||id||" is enabled." + setResultTable(resultTable, "Breakpoint #"||id||" is enabled", "__true__", msg) + DState.Write(resultTable) + return + } + else { + if bp.state = ENABLED then { + msg:="\n Breakpoint #"||id||" is already enabled." + setResultTable(resultTable, "Breakpoint #"||id||" is already enabled") + } + else { + msg:="\n Breakpoint #"||id||" is deleted; it cannot be enabled." + setResultTable(resultTable, "Breakpoint #"||id||" is deleted; it cannot be enabled") + } + setResultTable(resultTable, &null, "__false__", msg) + DState.Write(resultTable) + fail + } } } msg := "\n Breakpoint #"||id||" is not available." - DState.Write(msg) + setResultTable(resultTable, "Breakpoint #"||id||" is not available", "__false__", msg) + DState.Write(resultTable) fail } else { msg:="\n Undefined Command: \""||DState.cmdHistory[1]||"\""|| "\n Try \"help\" for assistance." - DState.Write(msg) + setResultTable(resultTable, "Undefined Command: "||DState.cmdHistory[1], "__false__", msg) + DState.Write(resultTable) fail } end @@ -636,52 +725,64 @@ method cmdDisable(cmd) if not cmd[3] then { every x := key(breakPoints) do { L := breakPoints[x] - every i := 1 to *L do - if L[i].state := ENABLED then { - L[i].state := DISABLED + every bp := !L do + if bp.state := ENABLED then { + bp.state := DISABLED j +:= 1 - } + } } - if j > 0 then - msg := "\n All breakpoints are disabled; ("||j||" total)." - else - msg := "\n No enabled breakpoints are available to be disabled." - DState.Write(msg) - return + if j > 0 then { + msg := "\n All breakpoints are disabled; ("||j||" total)." + setResultTable(resultTable, "All breakpoints are disabled; ("||j||" total)", "__true__") + } + else { + msg := "\n No enabled breakpoints are available to be disabled." + setResultTable(resultTable, "No enabled breakpoint are available to be disabled", "__false__") } + setResultTable(resultTable, &null, &null, msg) + DState.Write(resultTable) + return + } else if id := integer(cmd[3]) then { every x := key(breakPoints) do { - L := breakPoints[x] - every i := 1 to *L do { - if L[i].id = id then { - if L[i].state = ENABLED then { - L[i].state := DISABLED - msg := "\n Breakpoint #"||id||" is disabled." - DState.Write(msg) - return - } - else { - if L[i].state = DISABLED then - msg:="\n Breakpoint #"||id||" is already disabled." - else - msg:="\n Breakpoint #"||id||" is deleted;_ - it cannot be disabled." - DState.Write(msg) - fail - } - } - } - } + L := breakPoints[x] + every bp := !L do { + if bp.id = id then { + if bp.state = ENABLED then { + bp.state := DISABLED + msg := "\n Breakpoint #"||id||" is disabled." + setResultTable(resultTable, "Breakpoint #"||id||" is disabled", "__true__", msg) + DState.Write(resultTable) + return + } + else { + if bp.state = DISABLED then { + msg:="\n Breakpoint #"||id||" is already disabled." + setResultTable(resultTable, "Breakpoint #"||id||" is already disabled") + } + else { + msg:="\n Breakpoint #"||id||" is deleted; it cannot be disabled." + setResultTable(resultTable, "Breakpoint #"||id||" is deleted; it cannot be disabled") + } + setResultTable(resultTable, &null, "__false__", msg) + DState.Write(resultTable) + fail + } + } + } + } msg := "\n Breakpoint #"||id||" is not available." - DState.Write(msg) + setResultTable(resultTable, &null, "__false__", msg) + DState.Write(resultTable) fail - } + } else { msg:="\n Undefined Command: \""||DState.cmdHistory[1]||"\""|| "\n Try \"help\" for assistance." - DState.Write(msg) + setResultTable(resultTable, "Undefined Command: "||cmdHistory[1], "__false__", msg) + DState.Write(resultTable) fail - } + } end method resetBreakpoints() @@ -697,6 +798,12 @@ method resetBreakpoints() return end +method setResultTable(resTable, message, success, consoleMsg) + if \message then resTable["message"] := message + if \success then resTable["success"] := success + if \consoleMsg then resTable["consoleMsg"] := consoleMsg +end + # # Initialization # diff --git a/uni/udb/console.icn b/uni/udb/console.icn index bec6f2d17..09340c3bf 100644 --- a/uni/udb/console.icn +++ b/uni/udb/console.icn @@ -19,7 +19,7 @@ class Console : Session( ENTER, # The enter key BkSp, # The Back Space key badKeys, # A set of all other control keys - mode # Either &null (normal) or "-line"; test with \ or / + mode # Either &null (normal), "-line", or "-adapter" ) # @@ -61,8 +61,8 @@ method get_Line() writes(PROMPT) # if line mode, read a whole line the old fashioned way, skip this function - if \mode == "-line" then - return read() | "quit" + if \mode == "-line" | \mode == "-adapter" then + return DState.stateRead() | "quit" # normal mode, read the line interactively repeat { @@ -160,8 +160,8 @@ end # Here where we start a UDB Console Session and have # most of the user control debugging interface # -method startConsole(argv) - local ans, cmd, old_State := NONE, funame +method startConsole(argv, sock, progSock) + local ans, cmd, old_State := NONE, funame, port, errorTable # check whether we are on Solaris, and if so, default to cooked input if funame := open("uname","p") then { @@ -169,8 +169,19 @@ method startConsole(argv) close(funame) } if argv[1] == "-line" then { mode := pop(argv); udb_no_more_flag := 1 } + else if argv[1] == "-adapter" then { + mode := pop(argv) + + # Don't let DState try and initialize a port number + pop(argv) + + DState.sock := sock + DState.progSock := progSock + udb_no_more_flag := 1 + } else if argv[1] == "-noline" then { mode := &null; pop(argv) } + DState.mode := mode DState.initializeState(argv) # main interface loop @@ -212,6 +223,14 @@ $endif old_State := DState.State # user interaction loop during debug while DState.State ~= RUN do { + if \DState.mode == "-adapter" then { + if \&errornumber then { + errorTable := table("type", "crash", "errornumber", &errornumber) + errorTable["errortext"] := \&errortext + errorTable["errorvalue"] := \&errorvalue + DState.Write(errorTable) + } + } case DState.State of { RELOAD | END | @@ -268,7 +287,10 @@ $endif # update current symbol table before user input DState.srcFile.updateCurrSymtab(DState.coState) - if cmd := get_CommandLine() then parse_Command(cmd) + if cmd := get_CommandLine() then { + if cmd[1] == "reset" then return "reset" + else parse_Command(cmd) + } else DState.State := SKIP }# end of while }#end of every @@ -297,7 +319,10 @@ $endif } } # end case DState.State old_State := DState.State - if cmd := get_CommandLine() then parse_Command(cmd) + if cmd := get_CommandLine() then { + if cmd[1] == "reset" then return "reset" + else parse_Command(cmd) + } else DState.State := SKIP }# end while DState.State end diff --git a/uni/udb/data.icn b/uni/udb/data.icn index e82a1ffc3..e5f88d548 100644 --- a/uni/udb/data.icn +++ b/uni/udb/data.icn @@ -22,64 +22,128 @@ class Data( # prints all of Global Variables state in some level # method printGlobals(level) - local x, i, CMonitored := DState.coState.target.val, output := [] + local x, i, CMonitored := DState.coState.target.val, output := [], globalsTable, variableTable /level := DState.coState.target.curr_frame + globalsTable := table() + globalsTable["type"] := "globals" + + if /DState.mode | \DState.mode ~== "-adapter" then + put(output,"\n Global variables:") - put(output,"\n Global variables:") every x := !DState.srcFile.getCurrGlobals() do { i := variable(x, CMonitored, keyword("level",CMonitored)) + if /DState.mode | \DState.mode ~== "-adapter" then put(output," "||x||" = "||image(i)||" : "|| type(i)) + else { + variableTable := table("name", x, "value", image(i), "type", type(i)) + variableTable["value"] := replace(variableTable["value"], "\"", "\\\"") + variableTable["type"] := replace(variableTable["type"], "\"", "\\\"") + put(output, variableTable) } - more(output) + } + + globalsTable["variables"] := output + if /DState.mode | \DState.mode ~== "-adapter" then + more(output) + else + DState.Write(globalsTable) end # # prints all Local Variables for some Level # method printLocals(level) - local x, i, CMonitored := DState.coState.target.val, output := [] + local x, i, CMonitored := DState.coState.target.val, output := [], localsTable, variableTable - /level := DState.coState.target.curr_frame + /level := DState.coState.target.curr_frame + localsTable := table() + localsTable["type"] := "locals" + + if /DState.mode | \DState.mode ~== "-adapter" then + put(output,"\n Local variables:") - put(output,"\n Local variables:") every x := !DState.srcFile.getCurrLocals() do { i := variable(x, CMonitored, level) - put(output," "||x||" = "||image(i)||" : "|| type(i)) + if /DState.mode | \DState.mode ~== "-adapter" then + put(output," "||x||" = "||image(i)||" : "|| type(i)) + else { + variableTable := table("name", x, "value", image(i), "type", type(i)) + variableTable["value"] := replace(variableTable["value"], "\"", "\\\"") + variableTable["type"] := replace(variableTable["type"], "\"", "\\\"") + put(output, variableTable) } - more(output) + } + + localsTable["variables"] := output + if /DState.mode | \DState.mode ~== "-adapter" then + more(output) + else + DState.Write(localsTable) end # # prints all Local Variables for some Level # method printStatics(level) - local x, i, CMonitored := DState.coState.target.val, output := [] + local x, i, CMonitored := DState.coState.target.val, output := [], staticsTable, variableTable /level := DState.coState.target.curr_frame + staticsTable := table() + staticsTable["type"] := "statics" + + if /DState.mode | \DState.mode ~== "-adapter" then + put(output,"\n Static variables:") - put(output,"\n Static variables:") every x := !DState.srcFile.getCurrStatics() do { i := variable(x, CMonitored, level) - put(output," "||x||" = "||image(i)||" : "|| type(i)) + if /DState.mode | \DState.mode ~== "-adapter" then + put(output," "||x||" = "||image(i)||" : "|| type(i)) + else { + variableTable := table("name", x, "value", image(i), "type", type(i)) + variableTable["value"] := replace(variableTable["value"], "\"", "\\\"") + variableTable["type"] := replace(variableTable["type"], "\"", "\\\"") + put(output, variableTable) } - more(output) + } + + staticsTable["variables"] := output + if /DState.mode | \DState.mode ~== "-adapter" then + more(output) + else + DState.Write(staticsTable) end # # prints all Formal Parameters for some Level # method printParams(level) - local x, i, CMonitored := DState.coState.target.val, output := [] + local x, i, CMonitored := DState.coState.target.val, output := [], paramsTable, variableTable - /level := DState.coState.target.curr_frame + /level := DState.coState.target.curr_frame + paramsTable := table() + paramsTable["type"] := "params" + + if /DState.mode | \DState.mode ~== "-adapter" then + put(output,"\n Parameters:") - put(output,"\n Parameters:") every x := !DState.srcFile.getCurrParams() do { i := variable(x, CMonitored, level) - put(output," "||x||" = "||image(i)||" : "|| type(i)) + if /DState.mode | \DState.mode ~== "-adapter" then + put(output," "||x||" = "||image(i)||" : "|| type(i)) + else { + variableTable := table("name", x, "value", image(i), "type", type(i)) + variableTable["value"] := replace(variableTable["value"], "\"", "\\\"") + variableTable["type"] := replace(variableTable["type"], "\"", "\\\"") + put(output, variableTable) } - more(output) + } + + paramsTable["variables"] := output + if /DState.mode | \DState.mode ~== "-adapter" then + more(output) + else + DState.Write(paramsTable) end # diff --git a/uni/udb/defaults.icn b/uni/udb/defaults.icn index 5ce344b86..f6feb5c10 100644 --- a/uni/udb/defaults.icn +++ b/uni/udb/defaults.icn @@ -80,7 +80,7 @@ $define PROMPT "(udb) " # # The UDB Version # -$define VERSION " UDB Version 2.1, July 4, 2018." +$define VERSION " UDB Version 2.2, December 12, 2023." # # Set of all the letters and all digits diff --git a/uni/udb/dta/temporals.icn b/uni/udb/dta/temporals.icn index a042c4f59..e0b215a54 100644 --- a/uni/udb/dta/temporals.icn +++ b/uni/udb/dta/temporals.icn @@ -186,7 +186,7 @@ method cmdAssert(cmd) msg :="\n An assertion is found in virtual location" msg ||:="\n Do you want to replace it with the new one (Y|n):" DState.Writes(msg) - if (not (ans:=read())) | (*ans=0) | not(ans[1] == ("n"|"N")) then{ + if (not (ans:=DState.stateRead())) | (*ans=0) | not(ans[1] == ("n"|"N")) then{ assertTable[loc] := obj put(assertList,loc)#assertList[-1] := loc fname := line := code := &null diff --git a/uni/udb/evaluator.icn b/uni/udb/evaluator.icn index 312d86928..f2ec0fa6c 100644 --- a/uni/udb/evaluator.icn +++ b/uni/udb/evaluator.icn @@ -152,7 +152,7 @@ end # Handles UDB's E_Error Events and prints the RunTime error Messages # method handle_Error() - local cur_line, cur_file, cur_p, src + local cur_line, cur_file, cur_p, src, resultTable := table() DState.State := PAUSE #DState.State := END @@ -167,7 +167,9 @@ method handle_Error() Message ||:="\n"||DState.srcFile.getSrcLine(cur_file, cur_line) Message ||:="\n"||repl("-", keyword("column",MONITORED))||"^" Message ||:="\n"||keyword("errortext",MONITORED) - DState.Write(Message) + resultTable["consoleMsg"] := Message + resultTable["type"] := "stderr" + DState.Write(resultTable) end # diff --git a/uni/udb/session.icn b/uni/udb/session.icn index 68535b6e8..817492800 100644 --- a/uni/udb/session.icn +++ b/uni/udb/session.icn @@ -21,12 +21,23 @@ class Session( # Here where the udb Session starts on # method startSession() - local arg, t + local arg, t, failed + local resultTable := table() #-- it may need to be in EvInit() for the sake of re-run &eventsource := &null + + if \DState.mode == "-adapter" then + EvInit(DState.TP, DState.progSock, DState.progSock, DState.progSock) | { failed := "__true__" } + else + EvInit(DState.TP) | { failed := "__true__" } - EvInit(DState.TP) | stop("cant start evinit on " || DState.TP[1]) + if \failed then { + DState.State := END + declare_exit("cant start evinit on " || DState.TP[1], 1) + fail + } + Message :=" Starting program: " every arg := !DState.TP do Message ||:=" "||arg Message ||:="\n" @@ -44,7 +55,7 @@ method startSession() if DState.State = PAUSE then { if DState.RunCode = SIGNAL then { Message := "\n "||DState.TP[1]||" program received a signal "|| - &eventvalue||"." + &eventvalue||"." DState.Write(Message) } # suspends the loop and go back to udb_Console() for a new cmd. @@ -66,10 +77,16 @@ method startSession() #write("====================================") #--- Test - DState.State := END - Message := "\n "||DState.TP[1]||" program exited normally." - DState.Write(Message) - return + DState.State := END + declare_exit("\n "||DState.TP[1]||" program exited normally.", 0) +return +end + +method declare_exit(msg, code) + local resTable + Message := msg + resTable := ["consoleMsg": msg; "type": "exited"; "exitCode": code] + DState.Write(resTable) end # @@ -201,48 +218,53 @@ end # method cmdRun(cmd) local i, ans, args + local resultTable := table() if DState.State = (LOAD | END | PAUSE) then { # run - use args from 'load' if *cmd = 1 then { args := DState.TP - } + } # run args - overwrite args from 'load' else if *cmd >= 2 then { args := [DState.TP[1]] every i:=2 to *cmd do put(args, cmd[i]) - } + } DState.TP := args if DState.State = PAUSE then{ Message :="\n The program being debugged has been started already." Message||:="\n Start it from the beginning? (Y/n)?: " - DState.Writes(Message) + resultTable["consoleMsg"] := Message + resultTable["requireResponse"] := "__true__" + DState.Writes(resultTable) # rerun - if *(ans:=read())=0 | not(ans[1] == ("n"|"N")) then { + if *(ans:=DState.stateRead())=0 | not(ans[1] == ("n"|"N")) then { Debug.Watch.resetWatchInfo() Debug.Trace.resetTraceInfo() DState.Update(RERUN) - } + } else{ DState.State := SKIP return - } - } + } + } # run (like normal) - else{ + else { DState.Update(RUN) if DState.RunCount >= 1 then{ Debug.Watch.resetWatchInfo() Debug.Trace.resetTraceInfo() - } - } + } } - else{ + } + else { DState.State := ERROR Message := "\n No program to RUN, load program first_ \n Type \"help\" for assistance" - DState.Write(Message) + resultTable["consoleMsg"] := msg + resultTable["success"] := "__false__" + DState.Write(resultTable) } end @@ -250,15 +272,19 @@ end # Allows you to quit the program. # method cmdQuit() - local ans + local ans, msg + local resultTable := table() # check whether a program is running or not, Paused, etc?????? # do not exit directlly if the program is in the running state if DState.State = (RUN | PAUSE) then{ - DState.Writes("\n The program \""|| + msg := "\n The program \""|| DState.srcFile.exeName || - "\" is running. Exit anyhow (Y/n)?: ") - if (not (ans:=read())) | (*ans=0) | (map(ans[1]) == "y") then { + "\" is running. Exit anyhow (Y/n)?: " + resultTable["consoleMsg"] := msg + resultTable["requireResponse"] := "__true__" + DState.Writes(resultTable) + if (not (ans:=DState.stateRead())) | (*ans=0) | (map(ans[1]) == "y") then { DState.State := QUIT } else diff --git a/uni/udb/stack.icn b/uni/udb/stack.icn index d56f3c1b4..5c294c031 100644 --- a/uni/udb/stack.icn +++ b/uni/udb/stack.icn @@ -32,14 +32,18 @@ end # used by cmdPrint, cmdFrame, cmdUp, cmdDown # method printFrame(frame, detail:1) - local x, i, level, p:="(", pname + local x, i, level, p:="(", pname, frameTable local pfile:="", pline:="", CMonitored := DState.coState.target.val + frameTable := table() + frameTable["type"] := "frame" + # something funky is going on with &level I believe... level := keyword("level", CMonitored) - 1 if 0 <= frame <= level then { Message := " #"|| frame + frameTable["id"] := frame # check for proc() failure in lieu of &level being reliable if not (pname := image(proc(CMonitored, frame))) then fail @@ -57,6 +61,7 @@ method printFrame(frame, detail:1) p:= p[1:-2] Message ||:= p ||")" + frameTable["name"] := pname || p || ")" # work around that is valid when the source file of the debuggee is # loaded and the name of the procedure is not anywhere in another class @@ -73,16 +78,24 @@ method printFrame(frame, detail:1) #DState.srcFile.getProcLine(pname,pfile) | "?" } Message ||:= " at "|| pfile ||":"||pline + + frameTable["line"] := integer(pline) + frameTable["column"] := 0 + frameTable["source"] := table("name", pfile, "path", DState.srcFile.findDirectory(pfile) || "/" || pfile, "presentationHint", "normal") + frameTable["success"] := "__true__" + frameTable["consoleMsg"] := Message } else { Message := "\n The \""||frame ||"\" is not a valid frame number_ \n valid frames are (0 <= frame_num <= "||level||")." + frameTable["consoleMsg"] := Message + frameTable["success"] := "__false__" # in lieu of &level reliability - DState.Write(Message) + DState.Write(frameTable) fail } - DState.Write(Message) + DState.Write(frameTable) return end @@ -220,13 +233,13 @@ method cmdBacktrace(cmd) level := keyword("level", CMonitored) - 1 if *cmd = 1 then { BaseFrame := 0 - while BaseFrame < level do { + while BaseFrame <= level do { printFrames(BaseFrame, BaseFrame + MaxFrame) BaseFrame +:= MaxFrame + 1 if BaseFrame < level then { Message:= "---Type to continue, or q to quit---" DState.Writes(Message) - if *(ans:=read())~=0 & ans[1]==("q"|"Q") then break + if *(ans:=DState.stateRead())~=0 & ans[1]==("q"|"Q") then break } } } @@ -255,7 +268,7 @@ method cmdBacktrace(cmd) Message := "---Type to continue, _ or q to quit---" DState.Writes(Message) - if *(ans:=read())~=0 & ans[1]==("q"|"Q") then break + if *(ans:=DState.stateRead())~=0 & ans[1]==("q"|"Q") then break } } } diff --git a/uni/udb/state.icn b/uni/udb/state.icn index ec2589da8..13786b4bc 100644 --- a/uni/udb/state.icn +++ b/uni/udb/state.icn @@ -7,6 +7,9 @@ $include "evdefs.icn" $include "defaults.icn" +import json +link ximage + # # This class is used to handle the current debugging state of UDB # @@ -38,26 +41,83 @@ class State( srcFile, # an instance of the SourceFile class icode, # an instance of the Icode class coState, # an instance of CoexpState class - cmdHistory # list keeps all of the UDB's cmd Session History + cmdHistory, # list keeps all of the UDB's cmd Session History + mode, + sock, + progSock ) # # prints a message out into a console, message box, and so # method Write(Msg) + if /Msg then + Msg := Message + + if \mode == "-adapter" then { + if type(Msg) == "table" then { + if member(Msg, "code") then + Msg["code"] := replacem(\Msg["code"], "\"", "\\\"") + if member(Msg, "name") then + Msg["name"] := replacem(\Msg["name"], "\"", "\\\"") + if member(Msg, "consoleMsg") then + Msg["consoleMsg"] := replacem(\Msg["consoleMsg"], "\"", "\\\"") + Msg["state"] := State + write(sock, utoj(Msg)) + } + else { + output := ["consoleMsg": replacem(Msg, "\"", "\\\""); "state": State] + write(sock, utoj(output)) + } + } + else { + if type(Msg) == "table" then + Msg := Msg["consoleMsg"] + write(Msg) + } - /Msg := Message - write(Msg) Message := "" end +method stateRead() +local msg := "" + if \mode == "-adapter" then { + return read(sock) + } + else { + return read() + } +end + # # prints a message out into a console, message box, and so # method Writes(Msg) + if /Msg then + Msg := Message + + if \mode == "-adapter" then { + if type(Msg) == "table" then { + if member(Msg, "code") then + Msg["code"] := replacem(\Msg["code"], "\"", "\\\"") + if member(Msg, "name") then + Msg["name"] := replacem(\Msg["name"], "\"", "\\\"") + if member(Msg, "consoleMsg") then + Msg["consoleMsg"] := replacem(\Msg["consoleMsg"], "\"", "\\\"") + Msg["state"] := State + writes(sock, utoj(Msg)) + } + else { + output := ["consoleMsg": replacem(Msg, "\"", "\\\""); "state": State] + writes(sock, utoj(output)) + } + } + else { + if type(Msg) == "table" then + Msg := Msg["consoleMsg"] + writes(Msg) + } - /Msg := Message - writes(Msg) Message := "" end @@ -231,7 +291,7 @@ end # Returns if debugging data needs to be reinitialized, otherwise fails # method initializeState(tp) - local x, src, found, missing, plural:="", missing_files, reset := 0 + local x, src, found, missing, plural:="", missing_files, reset := 0, resultTable := table() # This is only when the TP is passed at the start of udb, i.e: ./udb test # in either case the ErrorCode will be initialized @@ -253,13 +313,18 @@ method initializeState(tp) Message :="\n The current program's debugging session is active." Message||:="\n The session will be terminated in order to load " Message||:="\n a program. Proceed (Y/n)?: " - Writes(Message) - - until (ans := read()) == (""|"y"|"n"|"Y"|"N"|"yes"|"no"|"Yes"|"No") do - write("\n Invalid input, please try again (Y/n): ") + resultTable["consoleMsg"] := Message + resultTable["requireResponse"] := "__true__" + Writes(resultTable) + + until isYesNo(ans := stateRead()) do { + resultTable["consoleMsg"] := "\n Invalid input, please try again (Y/n): " + resultTable["requireResponse"] := "__true__" + Writes(resultTable) + } # no, don't load - if ans == ("n"|"N"|"no"|"No") then { + if isNo(ans) then { State := SKIP fail } @@ -322,7 +387,9 @@ $endif else{ Message ||:= "\n Can not get the Source File Names_ \n Type \"help\" for assistance." - Write(Message) + resultTable["consoleMsg"] := Message + resultTable["type"] := "stderr" + Write(resultTable) State := NONE fail } @@ -330,12 +397,16 @@ $endif else { Message ||:= "\n Could not access \"" || TP[1] ||"\""|| "\n Type \"help\" for assistance." - Write(Message) + resultTable["consoleMsg"] := Message + resultTable["type"] := "stderr" + Write(resultTable) State := NONE fail } } - Write(Message) + resultTable := table() + resultTable["consoleMsg"] := Message + Write(resultTable) return } end @@ -402,10 +473,12 @@ method Fresh() end method runtimeErrorMsg() - local msg + local msg, resultTable := table() msg := "\n That action cannot be performed right now. " || "\n The program has experienced an error." - write(msg) + resultTable["consoleMsg"] := msg + resultTable["type"] := "stderr" + Write(resultTable) end # @@ -459,3 +532,18 @@ initially() E_CoCreate || E_Coact || E_Cofail || E_Coret) Fresh() end + +procedure isYes(val) + if match(map(val), ""|"yes") then return + else fail +end + +procedure isNo(val) + if match(map(val), "no") then return + else fail +end + +procedure isYesNo(val) + if match(map(val), ""|"yes"|"no") then return + else fail +end \ No newline at end of file diff --git a/uni/udb/stepping.icn b/uni/udb/stepping.icn index 23a8567f4..8a392913e 100644 --- a/uni/udb/stepping.icn +++ b/uni/udb/stepping.icn @@ -160,7 +160,7 @@ method checkStep() DState.srcFile.getSrcLine(cur_file, cur_line) msg||:="\n "||cur_line+1||":"|| DState.srcFile.getSrcLine(cur_file, cur_line+1) - DState.Write(msg) + DState.Write(["type": "step"; "line": cur_line; "source": cur_file; "consoleMsg": msg]) DState.State := PAUSE DState.RunCode := STEP diff --git a/uni/udb/trace.icn b/uni/udb/trace.icn index fac1f7315..05c45988e 100644 --- a/uni/udb/trace.icn +++ b/uni/udb/trace.icn @@ -259,7 +259,7 @@ end # it notfies the user that there is no more watching # method showEndTraceInfo(name) - local r, i + local r, i, resultTable := table() DState.State := PAUSE DState.RunCode := WATCH @@ -275,8 +275,10 @@ method showEndTraceInfo(name) else msg := "" msg ||:="\n Do you want to display old trace info (Y|n)? : " - DState.Writes(msg) - if read() == (""|"y"|"Y"|"yes"|"YES") then { + resultTable["consoleMsg"] := msg + resultTable["requireResponse"] := "__true__" + DState.Writes(resultTable) + if DState.stateRead() == (""|"y"|"Y"|"yes"|"YES") then { if traceBehavior[name].hitMax > 0 then { msg :="\n --------------------------------------------------" every i := 1 to *traceBehavior[name].traceList do { @@ -543,7 +545,7 @@ end # build Variable(s) and/or Behavior(s) tracer # method cmdTrace(cmd) - local name, coexp + local name, coexp, resultTable := table() if DState.isValidCommand(cmd) then { if *cmd >= 2 & resolveTraceInfo(cmd) then { @@ -566,8 +568,10 @@ method cmdTrace(cmd) " already exist: " ||name msg ||:="\n previous command is : "||traceBehavior[name].cmd msg ||:="\n Do you really want to replace it (Y/n)? : " - DState.Writes(msg) - if read() == (""|"y"|"Y"|"yes"|"YES") then { + resultTable["consoleMsg"] := msg + resultTable["requireResponse"] := "__true__" + DState.Writes(resultTable) + if DState.stateRead() == (""|"y"|"Y"|"yes"|"YES") then { msg :="\n previous command has been replaced." DState.Write(msg) addTracepoint(name, cmd, coexp) diff --git a/uni/udb/udb.icn b/uni/udb/udb.icn index 81b444118..7658033b1 100644 --- a/uni/udb/udb.icn +++ b/uni/udb/udb.icn @@ -7,22 +7,60 @@ # $include "defaults.icn" +import json procedure main(argv) - local udb + local udb, res := "reset", sock, progSock if not (&features == "multiple programs") then stop("udb requires multiple programs feature to be enabled.") - udb := Console() + if argv[1] == "-adapter" then { + trap("SIGTERM", sig_ignore) + &error := -1 + sock := connect_sock(argv[2]) + } + trap("SIGINT", sig_ignore) trap("SIGPIPE", sig_ignore) - udb.startConsole(argv) + while res == "reset" do { + res := "" + &eventsource := &null + tempargv := copy(argv) + + if argv[1] == "-adapter" then { + if \progSock then close(progSock) + progSock := connect_sock(argv[2] + 10) + } + + udb := Console() + res := udb.startConsole(tempargv, sock, progSock) + } write("\n Thank you for using UDB.\n Goodbye !!!") end +procedure connect_sock(port) + local sock + if /port then stop("No port declared") + port := (if &features == "MacOS" then "127.0.0.1" else "") || ":" || port + + every 1 to 5 do { + if sock := open(port, "n") then { + write("udb open(",port,") SUCCESS") + break + } + else { + write("udb open(",port,") ERROR: ", &errortext) + delay(1000) + } + } + if /sock then stop("udb failed to connect to port: ",port) + + return sock +end + procedure sig_ignore(non) writes("\r") write(PROMPT || "Quit") diff --git a/uni/udb/watchpoint.icn b/uni/udb/watchpoint.icn index 678db37fd..6b32496a9 100644 --- a/uni/udb/watchpoint.icn +++ b/uni/udb/watchpoint.icn @@ -433,7 +433,7 @@ method showEndWatchInfo(var) msg := "" msg ||:="\n Do you want to display previous info (Y|n)? : " DState.Writes(msg) - if *(ans := read()) = 0 | ans == ("y"|"Y"|"yes"|"YES") then { + if *(ans := DState.stateRead()) = 0 | ans == ("y"|"Y"|"yes"|"YES") then { if varInfo[var].hitMax > 0 then { msg :="\n --------------------------------------------------" every i := 1 to *varInfo[var].traceList do { @@ -659,7 +659,7 @@ end # used to set a general watchpoint on some program variable # method cmdWatch(cmd) - local ans, coexp + local ans, coexp, resultTable := table() if not isOKcmdWatch() then fail @@ -706,8 +706,10 @@ method cmdWatch(cmd) msg :="\n Watchpoint #"||varInfo[var].id||" already exist: "||var msg ||:="\n previous command is : "||varInfo[var].cmd msg ||:="\n Do you really want to replace it (Y/n)? : " - DState.Writes(msg) - if *(ans:=read())=0 | ans == ("y"|"Y"|"yes"|"YES") then { + resultTable["consoleMsg"] := msg + resultTable["requireResponse"] := "__true__" + DState.Writes(resultTable) + if *(ans:=DState.stateRead())=0 | ans == ("y"|"Y"|"yes"|"YES") then { delete(DState.watchReadMask, var) delete(DState.watchChangeMask, var) msg :="\n previous command has been replaced." From 439eb10598b6c9123794a9b2a67dfcc4c0124d93 Mon Sep 17 00:00:00 2001 From: mstreeter10 Date: Thu, 28 Sep 2023 15:46:44 -0400 Subject: [PATCH 2/2] 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 Simplify code in a few places Signed-off-by: Jafar Al-Gharaibeh --- .gitignore | 4 + uni/udb/Makefile | 7 +- uni/udb/udap/Makefile | 29 ++ uni/udb/udap/communicator.icn | 271 ++++++++++++++++ uni/udb/udap/dapcom.icn | 23 ++ uni/udb/udap/launch-dap.icn | 40 +++ uni/udb/udap/server.icn | 573 ++++++++++++++++++++++++++++++++++ uni/ulsp/Makefile | 4 + uni/ulsp/database.icn | 1 + 9 files changed, 951 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..c9b4fad85 --- /dev/null +++ b/uni/udb/udap/communicator.icn @@ -0,0 +1,271 @@ +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, frames := [], __1 + udbResTableList := [: udb_input("bt", 1) :] | [] + + 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, __1, __2 + scopes := [] + + udbResTableList := [: udb_input("frame " || frame, 1) :] ||| [: udb_input("print -" || !"glsp", 1) :] | [] + + 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, __1 + + variables := [] + + cmd := "print -" || "glsp"[variablesReference] + + udbResTableList := [: udb_input(cmd, 1) :] | [] + + every udbResTable := !udbResTableList do { + if member(udbResTable, "type") then { + if member(udbResTable, "variables") then { + if udbResTable["type"] == "globals" | "locals" | "statics" | "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 !5 do + if sock := open(":" || port, "na") then + return sock + else + 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 + \udbSock | fail + 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 + fail + } + + if \errorCalled then return errorCalled + + write("udap -> udb: " || exp) + write(udbSock, exp) + udbRes := udb_output(wait) + + if /udbRes then { + resultTable := ["type": "crash"; "errornumber": 1040; "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, __1 + + udb_input("clear break", 1) + + breakpoints := arguments["breakpoints"] + + every bp := !breakpoints do { + line := bp["line"] + cond := bp["condition"] + + udbResTableList := [: udb_input("b " || arguments["source"]["name"] || ":" || line, 1) :] | [] + + if *udbResTableList ~= 0 then { + every udbResTable := !udbResTableList do { + if member(udbResTable, "type") then + if udbResTable["type"] == "crash" then return udbResTable + bp["verified"] := udbResTable["success"] + } + } + else 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..ac66dbd3b --- /dev/null +++ b/uni/udb/udap/server.icn @@ -0,0 +1,573 @@ +package udap + +link ximage +link strings +import json +global seq, lastUdbCmd, waitingForTerminal, requestQueue, stop_debuggee_dapcom_communication + +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 := [ + "seq": seq; + "type": "response"; + "request_seq": request_seq; + "success": success; + "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 := [ + "seq": seq; + "type": "request"; + "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 := [ + "seq": seq; + "type": "event"; + "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 := [ + "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 := [ + "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 := [ + "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 + while /stop_debuggee_dapcom_communication do { + 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) + stop_debuggee_dapcom_communication := "__true__" + 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) + + stop_debuggee_dapcom_communication := &null + thread debuggee_dapcom_communication() + + communicator := Communicator() + + seq := 1 + udbError := &null + lastUdbCmd := "" + requestQueue := [] + end + + initially + 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..1cef6ae23 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 || "udb/udap") every path := !paths do build_by_path(path) end