TMGIDE3 ;TMG/kst/A debugger/tracer for GT.M (Controller code) ;04/14/08 ;;1.0;TMG-LIB;**1**;03/23/09 ;" TMG IDE Debugger Controller ;" ;" K. Toppenberg ;" 4-14-2008 ;" License: GPL Applies ;" ;"------------------------------------------------------------ ;"------------------------------------------------------------ ;"Notes: HOW DOES IT ALL WORK? ;" ;"Here is how the system fits together: ;" ;"Below is what happens in the CONTROLLING job ;"================================================= ;" -- LaunchIntr^TMGIDE5(JobNum) sets up a signaling global and then ;" creates a 'mupip intrpt JobNum', then starts listening ;" in Controller^TMGIDE3 for communication from the interrupted job. ;" (See below about how this communication gets started) ;" -- Controller^TMGIDE3 polls a communicating global location and can talk back ;" and forth with the other job. When it gets a message to ;" DO TRAP %ZPOS, it then calls STEPTRAP^TMGIDE2, and returns ;" the result of that function back to the other job. ;" -- STEPTRAP^TMGIDE2 is the same interface as the prior debugger. It shows ;" the code, allows the user to move around, and interact ;" with the code. If the user wants to query variables ;" in the other process, then a message is sent out, and ;" a copy of that variable is passed back here for display. ;" If the user wants to modify the other environment, then ;" arbitrary M code can be entered by the user, and it is passed ;" to the other job for execution in that job process space. ;" When ready to execute the next line of code, then STEPTRAP ;" quits with a result signalling a zstep INTO or OVER. ;" ;"Below is what happens in the OTHER job ;"================================================= ;" -- mupip intrpt JobNum --> causes the specified job to execute the code ;" stored in $ZINTERRUPT. For VistA (or if setup in ;" in an environmental script during GT.M launch), this ;" code is to run JOBEXAM^ZU (slightly customized) ;" -- JOBEXAM^ZU --> looks for signaling global, and if found runs INTERUPT^TMGIDE5 ;" -- INTERUPT^TMGIDE5 --> sets up $ZSTEP and then calls ZSTEP INTO and ;" quits out of the $ZINTERRUPT code. ;" -- ZSTEP --> causes GT.M to execute the code in $ZSTEP before performing ;" the next line of mumps code for the program that was ;" running at the time the interrupt request was received. ;" -- $ZSTEP holds instruction to run $$STEPTRAP^TMGIDE4($ZPOS) ;" -- STEPTRAP^TMGIDE4 --> sends message to CONTROLLING JOB and waits for reply. ;" Reply will either be a request from the user for more ;" information from this job, or a final reply that allows ;" execution to continue, either by a step INTO, OVER, or ;" a plain ZCONTINUE (which will stop further code-stepping) Controller ;"Purpose: This code will wait for messages from the executing process, and ;" will display the code as it changes, and send messages back to ;" all the user to control the process remotely. ;"Notice: There are ;"A globally-scoped var that will be checked in STEPTRAP^TMGIDE2 if +$get(tmgDbgRemoteJob)'>0 set tmgDbgRemoteJob=1 ;"write # new i for i=1:1:12 write ! write "=== TMG IDE Controller (Job# "_$JOB_") ===",!,! write "Waiting for action from SENDING (Remote) process (ESC to abort)",! new msgRef set msgRef=$name(^TMG("TMGIDE","CONTROLLER")) kill @msgRef set @msgRef@("JOB")=$JOB new Msg,UsrInput,Cmd new hangDelay set hangDelay=0.2 new ideBlankLine set $piece(ideBlankLine," ",78)=" " ;"new HxSize set HxSize=8 ;"hard codes in history length of 8 new TMGdbgLine new TMGlastline set TMGlastLine="" new HxShowNum set HxShowNum=0 new HxLine,HxLineMax,HxLineCur do INITKB^XGF() ;"set up keyboard input escape code processing ; Init set @msgRef@("STATUS")="AVAIL" set @msgRef@("MSG-OUT")="" new TMGstartH set TMGstartH=$piece($H,",",2) new tempCh,% ; Loop set Msg=$get(@msgRef@("MSG-IN")) set Cmd=$piece(Msg," ",1) ; if Cmd="INQ" do HndlINQ(Msg) goto Loop if Cmd="LISTEN" do HndlListen(Msg) goto Loop if Cmd="DONE" do HndlDone(Msg) goto LstnDone ;"This is when SENDER signals a quit. if Cmd="WRITE" do HndlWrite(Msg) goto Loop if Cmd="DO" goto:($$HndlDo(Msg)'=0) Loop goto LstnDone ;"Leave if CONTROLLER signals a quit if Cmd="READ" do HndlRead(Msg) goto Loop if Cmd="NEED" do HndlNeed(Msg) goto Loop ; ;"Checking UserAborted grabs keystrokes, and prevents user from getting out of RUN mode ;"in ^TMGIDE2, so only check here after an X second delay. if $piece($H,",",2)-TMGstartH<2 goto Loop read *tempCh:0 if tempCh'=27 goto Loop write !,"Abort From Remote Debugging Controller" set TMGstartH=$piece($H,",",2) set %=2 do YN^DICN write ! if %'=1 goto Loop ; LstnDone ; write !,"Quitting.",! kill @msgRef kill tmgDbgRemoteJob quit ; ;"------------------------------- ;"------------------------------- ; ACK SET @msgRef@("MSG-OUT")="ACK "_$J SET @msgRef@("MSG-IN")="" QUIT HndlINQ(Msg) ;"Expects 'INQ ' ;"write "Msg=",Msg,! ;"temp!! set tmgDbgRemoteJob=+$piece(Msg," ",2) do ACK quit HndlListen(Msg) new JobToControl set JobToControl=+$piece(Msg," ",3) set @msgRef@("STATUS")="LISTENING TO "_JobToControl set @msgRef@("MSG-OUT")=@msgRef@("STATUS") DO ACK quit HndlWrite(Msg) write $piece(Msg," ",2,99),! DO ACK quit HndlDo(Msg) ;"Purpose: Handle message from interrupted application to DO something. ;"Result: 1 = OK to continue ;" 0 = should quit controller. new result set result=1 ;"default to continue new msgResult set msgResult="" if $piece(Msg," ",2)="PROMPT" do . set msgResult=$$Prompt() else if $piece(Msg," ",2)="TRAP" do . new idePos set idePos=$piece(Msg," ",3) . new TMGMsg set TMGMsg=$piece(Msg," ",4) . set msgResult=$$STEPTRAP^TMGIDE2(idePos,TMGMsg) . if msgResult=0 set result=0 ;"STEPTRAP result of 0 means to stop controller. set @msgRef@("MSG-OUT")=msgResult set @msgRef@("MSG-IN")="" set TMGstartH=$piece($H,",",2) ;"restart timer countdown before allowing user input. quit result HndlDone(Msg) DO ACK quit HndlRead(Msg) new result write $piece(Msg," ",2,99) read result:$get(DTIME,3600),! if result="" set result="" set @msgRef@("MSG-OUT")=result set @msgRef@("MSG-IN")="" quit HndlNeed(Msg) set SndJob=+$piece(Msg," ",3) set @MsgRef@("STATUS")="CONTROLLING "_SndJob set @MsgRef@("MSG-OUT")=@MsgCtrlRef@("STATUS") quit ;"------------------------------------------------------------------- Prompt() ;"Purpose: to interact with user and run through code. ;"new i write # for i=1:1:12 write ! write "=== TMG IDE Controller ===",!,! Ppt2 set HxShowNum=+$get(HxShowNum) set TMGStepMode="into" ;"kt added 5/3/06 set HxLine=$get(^TMG("TMGIDE","CMD HISTORY",$J,HxShowNum)) set HxLineMax=$get(^TMG("TMGIDE","CMD HISTORY",$J,"MAX"),0) write "Remote command (^ to quit): " if HxShowNum=0 write "^// " else write "// ",HxLine set TMGdbgLine=$$READ^TMGIDE() ;"$$READ^XGF ;"returns line terminator in tmgXGRT set tmgXGRT=$get(tmgXGRT) ;"ensure existence if TMGdbgLine="?" do goto Ppt2 . write !,"Here you should enter any valid M command, as would normally",! . write "entered at a GTM> prompt.",! . write " examples: WRITE ""HELLO"",! or DO ^TMGTEST",! if (TMGdbgLine="")&(HxShowNum>0) set TMGdbgLine=HxLine if (tmgXGRT="DOWN")!(tmgXGRT="RIGHT")!(TMGdbgLine="]") do goto Ppt2 . set HxShowNum=HxShowNum-1 . if HxShowNum<0 set HxShowNum=HxLineMax . ;"write "setting HxShowNum=",HxShowNum,! . do CHA^TMGTERM(1) write ideBlankLine do CHA^TMGTERM(1) if (tmgXGRT="UP")!(tmgXGRT="LEFT")!(TMGdbgLine="[") do goto Ppt2 . set HxShowNum=HxShowNum+1 . if HxShowNum>HxLineMax set HxShowNum=0 . ;"write "setting HxShowNum=",HxShowNum,! . do CHA^TMGTERM(1) write ideBlankLine do CHA^TMGTERM(1) if TMGdbgLine="" set TMGdbgLine="^" write ! ;"Save Cmd history set HxLineCur=$get(^TMG("TMGIDE","CMD HISTORY",$J,"CUR"),0) ;"<-- points to last used, not next avail set HxLineMax=$get(^TMG("TMGIDE","CMD HISTORY",$J,"MAX"),0) ;"equals buffer size AFTER it fills set HxLineCur=HxLineCur+1 ;"if HxLineCur>HxSize set HxLineCur=1 set ^TMG("TMGIDE","CMD HISTORY",$J,HxLineCur)=TMGdbgLine set ^TMG("TMGIDE","CMD HISTORY",$J,"CUR")=HxLineCur if HxLineCur>HxLineMax do . set HxLineMax=HxLineCur . set ^TMG("TMGIDE","CMD HISTORY",$J,"MAX")=HxLineMax ;"write "Saving line in #",HxLineCur," Max=",HxLineMax,! quit TMGdbgLine