| 1 | TMGIDE3 ;TMG/kst/A debugger/tracer for GT.M (Controller code) ;04/14/08
 | 
|---|
| 2 |          ;;1.0;TMG-LIB;**1**;03/23/09
 | 
|---|
| 3 | 
 | 
|---|
| 4 |  ;" TMG IDE Debugger Controller
 | 
|---|
| 5 |  ;"
 | 
|---|
| 6 |  ;" K. Toppenberg
 | 
|---|
| 7 |  ;" 4-14-2008
 | 
|---|
| 8 |  ;" License: GPL Applies
 | 
|---|
| 9 |  ;"
 | 
|---|
| 10 |  ;"------------------------------------------------------------
 | 
|---|
| 11 |  ;"------------------------------------------------------------
 | 
|---|
| 12 |  ;"Notes:    HOW DOES IT ALL WORK?
 | 
|---|
| 13 |  ;"
 | 
|---|
| 14 |  ;"Here is how the system fits together:
 | 
|---|
| 15 |  ;"
 | 
|---|
| 16 |  ;"Below is what happens in the CONTROLLING job
 | 
|---|
| 17 |  ;"=================================================
 | 
|---|
| 18 |  ;" -- LaunchIntr^TMGIDE5(JobNum) sets up a signaling global and then
 | 
|---|
| 19 |  ;"                     creates a 'mupip intrpt JobNum', then starts listening
 | 
|---|
| 20 |  ;"                     in Controller^TMGIDE3 for communication from the interrupted job.
 | 
|---|
| 21 |  ;"                     (See below about how this communication gets started)
 | 
|---|
| 22 |  ;" -- Controller^TMGIDE3 polls a communicating global location and can talk back
 | 
|---|
| 23 |  ;"                     and forth with the other job.  When it gets a message to
 | 
|---|
| 24 |  ;"                     DO TRAP %ZPOS, it then calls STEPTRAP^TMGIDE2, and returns
 | 
|---|
| 25 |  ;"                     the result of that function back to the other job.
 | 
|---|
| 26 |  ;" -- STEPTRAP^TMGIDE2 is the same interface as the prior debugger.  It shows
 | 
|---|
| 27 |  ;"                     the code, allows the user to move around, and interact
 | 
|---|
| 28 |  ;"                     with the code.  If the user wants to query variables
 | 
|---|
| 29 |  ;"                     in the other process, then a message is sent out, and
 | 
|---|
| 30 |  ;"                     a copy of that variable is passed back here for display.
 | 
|---|
| 31 |  ;"                     If the user wants to modify the other environment, then
 | 
|---|
| 32 |  ;"                     arbitrary M code can be entered by the user, and it is passed
 | 
|---|
| 33 |  ;"                     to the other job for execution in that job process space.
 | 
|---|
| 34 |  ;"                     When ready to execute the next line of code, then STEPTRAP
 | 
|---|
| 35 |  ;"                     quits with a result signalling a zstep INTO or OVER.
 | 
|---|
| 36 |  ;"
 | 
|---|
| 37 |  ;"Below is what happens in the OTHER job
 | 
|---|
| 38 |  ;"=================================================
 | 
|---|
| 39 |  ;" -- mupip intrpt JobNum --> causes the specified job to execute the code
 | 
|---|
| 40 |  ;"                     stored in $ZINTERRUPT.  For VistA (or if setup in
 | 
|---|
| 41 |  ;"                     in an environmental script during GT.M launch), this
 | 
|---|
| 42 |  ;"                     code is to run JOBEXAM^ZU (slightly customized)
 | 
|---|
| 43 |  ;" -- JOBEXAM^ZU --> looks for signaling global, and if found runs INTERUPT^TMGIDE5
 | 
|---|
| 44 |  ;" -- INTERUPT^TMGIDE5 --> sets up $ZSTEP and then calls ZSTEP INTO and
 | 
|---|
| 45 |  ;"                     quits out of the $ZINTERRUPT code.
 | 
|---|
| 46 |  ;" -- ZSTEP --> causes GT.M to execute the code in $ZSTEP before performing
 | 
|---|
| 47 |  ;"                     the next line of mumps code for the program that was
 | 
|---|
| 48 |  ;"                     running at the time the interrupt request was received.
 | 
|---|
| 49 |  ;" -- $ZSTEP holds instruction to run $$STEPTRAP^TMGIDE4($ZPOS)
 | 
|---|
| 50 |  ;" -- STEPTRAP^TMGIDE4 --> sends message to CONTROLLING JOB and waits for reply.
 | 
|---|
| 51 |  ;"                     Reply will either be a request from the user for more
 | 
|---|
| 52 |  ;"                     information from this job, or a final reply that allows
 | 
|---|
| 53 |  ;"                     execution to continue, either by a step INTO, OVER, or
 | 
|---|
| 54 |  ;"                     a plain ZCONTINUE (which will stop further code-stepping)
 | 
|---|
| 55 | 
 | 
|---|
| 56 | 
 | 
|---|
| 57 | Controller
 | 
|---|
| 58 |        ;"Purpose: This code will wait for messages from the executing process, and
 | 
|---|
| 59 |        ;"         will display the code as it changes, and send messages back to
 | 
|---|
| 60 |        ;"         all the user to control the process remotely.
 | 
|---|
| 61 | 
 | 
|---|
| 62 |        ;"Notice: There are
 | 
|---|
| 63 | 
 | 
|---|
| 64 |        ;"A globally-scoped var that will be checked in STEPTRAP^TMGIDE2
 | 
|---|
| 65 |        if +$get(tmgDbgRemoteJob)'>0 set tmgDbgRemoteJob=1
 | 
|---|
| 66 | 
 | 
|---|
| 67 |        ;"write #
 | 
|---|
| 68 |        new i for i=1:1:12 write !
 | 
|---|
| 69 |        write "=== TMG IDE Controller (Job# "_$JOB_") ===",!,!
 | 
|---|
| 70 |        write "Waiting for action from SENDING (Remote) process (ESC to abort)",!
 | 
|---|
| 71 |        new msgRef set msgRef=$name(^TMG("TMGIDE","CONTROLLER"))
 | 
|---|
| 72 |        kill @msgRef
 | 
|---|
| 73 |        set @msgRef@("JOB")=$JOB
 | 
|---|
| 74 | 
 | 
|---|
| 75 |        new Msg,UsrInput,Cmd
 | 
|---|
| 76 |        new hangDelay set hangDelay=0.2
 | 
|---|
| 77 | 
 | 
|---|
| 78 |        new ideBlankLine set $piece(ideBlankLine," ",78)=" "
 | 
|---|
| 79 |        ;"new HxSize set HxSize=8     ;"hard codes in history length of 8
 | 
|---|
| 80 |        new TMGdbgLine
 | 
|---|
| 81 |        new TMGlastline set TMGlastLine=""
 | 
|---|
| 82 |        new HxShowNum set HxShowNum=0
 | 
|---|
| 83 |        new HxLine,HxLineMax,HxLineCur
 | 
|---|
| 84 |        do INITKB^XGF()  ;"set up keyboard input escape code processing
 | 
|---|
| 85 |        ;
 | 
|---|
| 86 | Init   set @msgRef@("STATUS")="AVAIL"
 | 
|---|
| 87 |        set @msgRef@("MSG-OUT")=""
 | 
|---|
| 88 |        new TMGstartH set TMGstartH=$piece($H,",",2)
 | 
|---|
| 89 |        new tempCh,%
 | 
|---|
| 90 |        ;
 | 
|---|
| 91 | Loop   set Msg=$get(@msgRef@("MSG-IN"))
 | 
|---|
| 92 |        set Cmd=$piece(Msg," ",1)
 | 
|---|
| 93 |        ;
 | 
|---|
| 94 |        if Cmd="INQ" do HndlINQ(Msg) goto Loop
 | 
|---|
| 95 |        if Cmd="LISTEN" do HndlListen(Msg) goto Loop
 | 
|---|
| 96 |        if Cmd="DONE" do HndlDone(Msg) goto LstnDone  ;"This is when SENDER signals a quit.
 | 
|---|
| 97 |        if Cmd="WRITE" do HndlWrite(Msg) goto Loop
 | 
|---|
| 98 |        if Cmd="DO" goto:($$HndlDo(Msg)'=0) Loop goto LstnDone ;"Leave if CONTROLLER signals a quit
 | 
|---|
| 99 |        if Cmd="READ" do HndlRead(Msg) goto Loop
 | 
|---|
| 100 |        if Cmd="NEED" do HndlNeed(Msg) goto Loop
 | 
|---|
| 101 |        ;
 | 
|---|
| 102 |        ;"Checking UserAborted grabs keystrokes, and prevents user from getting out of RUN mode
 | 
|---|
| 103 |        ;"in ^TMGIDE2, so only check here after an X second delay.
 | 
|---|
| 104 |        if $piece($H,",",2)-TMGstartH<2 goto Loop
 | 
|---|
| 105 |        read *tempCh:0
 | 
|---|
| 106 |        if tempCh'=27 goto Loop
 | 
|---|
| 107 |        write !,"Abort From Remote Debugging Controller"
 | 
|---|
| 108 |        set TMGstartH=$piece($H,",",2)
 | 
|---|
| 109 |        set %=2 do YN^DICN write !
 | 
|---|
| 110 |        if %'=1 goto Loop
 | 
|---|
| 111 |        ;
 | 
|---|
| 112 | LstnDone ;
 | 
|---|
| 113 |        write !,"Quitting.",!
 | 
|---|
| 114 |        kill @msgRef
 | 
|---|
| 115 |        kill tmgDbgRemoteJob
 | 
|---|
| 116 |        quit
 | 
|---|
| 117 |        ;
 | 
|---|
| 118 | ;"-------------------------------
 | 
|---|
| 119 | ;"-------------------------------
 | 
|---|
| 120 |        ;
 | 
|---|
| 121 | ACK    SET @msgRef@("MSG-OUT")="ACK "_$J
 | 
|---|
| 122 |        SET @msgRef@("MSG-IN")=""
 | 
|---|
| 123 |        QUIT
 | 
|---|
| 124 | 
 | 
|---|
| 125 | HndlINQ(Msg)  ;"Expects 'INQ <Job#>'
 | 
|---|
| 126 |        ;"write "Msg=",Msg,!  ;"temp!!
 | 
|---|
| 127 |        set tmgDbgRemoteJob=+$piece(Msg," ",2)
 | 
|---|
| 128 |        do ACK
 | 
|---|
| 129 |        quit
 | 
|---|
| 130 | 
 | 
|---|
| 131 | HndlListen(Msg)
 | 
|---|
| 132 |        new JobToControl
 | 
|---|
| 133 |        set JobToControl=+$piece(Msg," ",3)
 | 
|---|
| 134 |        set @msgRef@("STATUS")="LISTENING TO "_JobToControl
 | 
|---|
| 135 |        set @msgRef@("MSG-OUT")=@msgRef@("STATUS")
 | 
|---|
| 136 |        DO ACK
 | 
|---|
| 137 |        quit
 | 
|---|
| 138 | 
 | 
|---|
| 139 | HndlWrite(Msg)
 | 
|---|
| 140 |        write $piece(Msg," ",2,99),!
 | 
|---|
| 141 |        DO ACK
 | 
|---|
| 142 |        quit
 | 
|---|
| 143 | 
 | 
|---|
| 144 | HndlDo(Msg)
 | 
|---|
| 145 |        ;"Purpose: Handle message from interrupted application to DO something.
 | 
|---|
| 146 |        ;"Result: 1 = OK to continue
 | 
|---|
| 147 |        ;"        0 = should quit controller.
 | 
|---|
| 148 |        new result set result=1  ;"default to continue
 | 
|---|
| 149 |        new msgResult set msgResult=""
 | 
|---|
| 150 |        if $piece(Msg," ",2)="PROMPT" do
 | 
|---|
| 151 |        . set msgResult=$$Prompt()
 | 
|---|
| 152 |        else  if $piece(Msg," ",2)="TRAP" do
 | 
|---|
| 153 |        . new idePos set idePos=$piece(Msg," ",3)
 | 
|---|
| 154 |        . new TMGMsg set TMGMsg=$piece(Msg," ",4)
 | 
|---|
| 155 |        . set msgResult=$$STEPTRAP^TMGIDE2(idePos,TMGMsg)
 | 
|---|
| 156 |        . if msgResult=0 set result=0  ;"STEPTRAP result of 0 means to stop controller.
 | 
|---|
| 157 |        set @msgRef@("MSG-OUT")=msgResult
 | 
|---|
| 158 |        set @msgRef@("MSG-IN")=""
 | 
|---|
| 159 |        set TMGstartH=$piece($H,",",2) ;"restart timer countdown before allowing user input.
 | 
|---|
| 160 |        quit result
 | 
|---|
| 161 | 
 | 
|---|
| 162 | HndlDone(Msg)
 | 
|---|
| 163 |        DO ACK
 | 
|---|
| 164 |        quit
 | 
|---|
| 165 | 
 | 
|---|
| 166 | HndlRead(Msg)
 | 
|---|
| 167 |        new result
 | 
|---|
| 168 |        write $piece(Msg," ",2,99)
 | 
|---|
| 169 |        read result:$get(DTIME,3600),!
 | 
|---|
| 170 |        if result="" set result="<null>"
 | 
|---|
| 171 |        set @msgRef@("MSG-OUT")=result
 | 
|---|
| 172 |        set @msgRef@("MSG-IN")=""
 | 
|---|
| 173 |        quit
 | 
|---|
| 174 | 
 | 
|---|
| 175 | HndlNeed(Msg)
 | 
|---|
| 176 |        set SndJob=+$piece(Msg," ",3)
 | 
|---|
| 177 |        set @MsgRef@("STATUS")="CONTROLLING "_SndJob
 | 
|---|
| 178 |        set @MsgRef@("MSG-OUT")=@MsgCtrlRef@("STATUS")
 | 
|---|
| 179 |        quit
 | 
|---|
| 180 | 
 | 
|---|
| 181 | ;"-------------------------------------------------------------------
 | 
|---|
| 182 | 
 | 
|---|
| 183 | Prompt()
 | 
|---|
| 184 |        ;"Purpose: to interact with user and run through code.
 | 
|---|
| 185 | 
 | 
|---|
| 186 |        ;"new i write # for i=1:1:12 write !
 | 
|---|
| 187 |        write "=== TMG IDE Controller ===",!,!
 | 
|---|
| 188 | 
 | 
|---|
| 189 | Ppt2
 | 
|---|
| 190 |        set HxShowNum=+$get(HxShowNum)
 | 
|---|
| 191 |        set TMGStepMode="into"  ;"kt added 5/3/06
 | 
|---|
| 192 |        set HxLine=$get(^TMG("TMGIDE","CMD HISTORY",$J,HxShowNum))
 | 
|---|
| 193 |        set HxLineMax=$get(^TMG("TMGIDE","CMD HISTORY",$J,"MAX"),0)
 | 
|---|
| 194 | 
 | 
|---|
| 195 |        write "Remote command (^ to quit): "
 | 
|---|
| 196 |        if HxShowNum=0 write "^// "
 | 
|---|
| 197 |        else  write "// ",HxLine
 | 
|---|
| 198 | 
 | 
|---|
| 199 |        set TMGdbgLine=$$READ^TMGIDE()  ;"$$READ^XGF  ;"returns line terminator in tmgXGRT
 | 
|---|
| 200 |        set tmgXGRT=$get(tmgXGRT) ;"ensure existence
 | 
|---|
| 201 |        if TMGdbgLine="?" do  goto Ppt2
 | 
|---|
| 202 |        . write !,"Here you should enter any valid M command, as would normally",!
 | 
|---|
| 203 |        . write "entered at a GTM> prompt.",!
 | 
|---|
| 204 |        . write "  examples:  WRITE ""HELLO"",!  or DO ^TMGTEST",!
 | 
|---|
| 205 | 
 | 
|---|
| 206 |        if (TMGdbgLine="")&(HxShowNum>0) set TMGdbgLine=HxLine
 | 
|---|
| 207 | 
 | 
|---|
| 208 |        if (tmgXGRT="DOWN")!(tmgXGRT="RIGHT")!(TMGdbgLine="]") do  goto Ppt2
 | 
|---|
| 209 |        . set HxShowNum=HxShowNum-1
 | 
|---|
| 210 |        . if HxShowNum<0 set HxShowNum=HxLineMax
 | 
|---|
| 211 |        . ;"write "setting HxShowNum=",HxShowNum,!
 | 
|---|
| 212 |        . do CHA^TMGTERM(1) write ideBlankLine do CHA^TMGTERM(1)
 | 
|---|
| 213 | 
 | 
|---|
| 214 |        if (tmgXGRT="UP")!(tmgXGRT="LEFT")!(TMGdbgLine="[") do  goto Ppt2
 | 
|---|
| 215 |        . set HxShowNum=HxShowNum+1
 | 
|---|
| 216 |        . if HxShowNum>HxLineMax set HxShowNum=0
 | 
|---|
| 217 |        . ;"write "setting HxShowNum=",HxShowNum,!
 | 
|---|
| 218 |        . do CHA^TMGTERM(1) write ideBlankLine do CHA^TMGTERM(1)
 | 
|---|
| 219 | 
 | 
|---|
| 220 |        if TMGdbgLine="" set TMGdbgLine="^"
 | 
|---|
| 221 |        write !
 | 
|---|
| 222 | 
 | 
|---|
| 223 |        ;"Save Cmd history
 | 
|---|
| 224 |        set HxLineCur=$get(^TMG("TMGIDE","CMD HISTORY",$J,"CUR"),0)  ;"<-- points to last used, not next avail
 | 
|---|
| 225 |        set HxLineMax=$get(^TMG("TMGIDE","CMD HISTORY",$J,"MAX"),0) ;"equals buffer size AFTER it fills
 | 
|---|
| 226 |        set HxLineCur=HxLineCur+1
 | 
|---|
| 227 |        ;"if HxLineCur>HxSize set HxLineCur=1
 | 
|---|
| 228 |        set ^TMG("TMGIDE","CMD HISTORY",$J,HxLineCur)=TMGdbgLine
 | 
|---|
| 229 |        set ^TMG("TMGIDE","CMD HISTORY",$J,"CUR")=HxLineCur
 | 
|---|
| 230 |        if HxLineCur>HxLineMax do
 | 
|---|
| 231 |        . set HxLineMax=HxLineCur
 | 
|---|
| 232 |        . set ^TMG("TMGIDE","CMD HISTORY",$J,"MAX")=HxLineMax
 | 
|---|
| 233 |        ;"write "Saving line in #",HxLineCur," Max=",HxLineMax,!
 | 
|---|
| 234 | 
 | 
|---|
| 235 |        quit TMGdbgLine
 | 
|---|