Calls, Loops, and Conditionals mCallLevel is the macro level in a calling sequence and starts at 0 mLoopLevel is the level of loop and/or if block, starts at -1 outside blocks mLoopDepths[mCallLevel] is maintained in tandem with mLoopLevel mLoopLimit[mLoopLevel] is the loop count to do, or 0 for an unsatisfied IF or -1 for a satisfied IF mLoopStart[mLoopLevel] is the line index of a loop start mLoopCount[mLoopLevel] is current iteration # of the loop, starts at 1 mCallMacro[mCallLevel] is the macro number being called at a call level mCallIndex[mCallLevel] is the line index of the CALL statement to be returned to When a LOOP is encountered, it increases mLoopLevel, assigns mLoopLimit, mLoopStart, and mLoopCount, and sets an optional variable ENDLOOP increases the count and goes back to starting index if not above the limit, otherwise it clears variables and drops the loop level When an IF or an ELSEIF in an unsatisfied IF is found, it evaluates the conditional and increases mLoopLevel for an IF Then if the condition is NOT satisfied it skips to an ELSE or ENDIF (or ELSEIF?) and leaves mLoopLimit at 0 to indicate unsatisfied. If the condition is satisfied it set mLoopLimit to -1 If it skipped to an ELSE it goes 1 past it so that it will execute, all other skips are to the point in question Skipping is done to the requested kind of end by keeping track of if and loop levels and making sure it matches the respective starting level Thus an ELSE or ELSEIF is encountered only when the IF is satisfied, and it unconditionally skips to an ENDIF BREAK or CONTINUE pop any IFs off the stack by testing for nonpositive mLoopLimit and decrementing mLoopLevel and it skips to the ENDLOOP. BREAK is implemented just by setting the mLoopCount to mLoopLimit CALL just increases the mCallLevel after saving the current line index and saves the new macro number and sets the new loop depth to -1 RETURN pops off any ifs/loops by clearing index variable at each level and decrementing mLoopLevel until mLoopDepths is -1 Thus the mLoopLevel stack works across calls and returns and loop/if blocks are not kept track of per macro level. SKIPTO and labels work by finding the label, counting the net number of loop levels that it descends, then popping that many IFs/LOOPS. The checking for this is hairy because it has to prevent jumps into different block sections, which are referred to as subblocks. Every loop, and every component of IF - ELSEIF -ELSE -ENDIF, is counted as a different subblock at the given loop level. PYTHON TaskDone is visited twice; first it runs a command when waitingForCommand is still set, then it returns for it to loop back in, either directly or on the next OnIdle waitingForCommand is set by SEM: 1) when it starts running Python, 2) in SuspendMacro when it "signal function done after Abort" and sets Done event, and 3) In TaskDone when a command is done (not waitingForCommand), and it processes the last command result then sets Done event and "signal operations done after loop back in" it is cleared by SEM: 1) in TaskDone when it IS waitingForCommand, before starting to run the command 2) In RunOrResume 3) in PythonServer in TryToStartExternalControl before it sits and waits on it commandReady is set by module before running RegularCommand() It is set in PythonServer when running a regular commandn with "Command ready to execute, wait for done event" It is cleared in: 1) when starts running Python 2) TaskDone when it sets waitingForCommand 3) RunOrResume to initialize 4) Abort when it sets waitingForCommand errorOccurred contains an error code for an error or stop. Is set to 1 for bad function code SCRIPT_EXIT_NO_EXC or SCRIPT_EXIT_NO_EXC when EXIT command is given SCRIPT_NORMAL_EXIT at ScriptEnd or on RETURN from embedded python in SEM script SCRIPT_USER_STOP on a user stop in Stop 1 on "True error" (not exit or user stop) in Abort 0 by RunOrResume to initialize 0 in StartRunningScrpLang 0 by CBaseServer::ReportErrorAndClose when it is set to USER_STOP 0 in TaskDone AFTER return from command function if not EXIT (when waitingForCommand is set) 0 when command done after looping back in (when waitingForCommand is not set), before checking result of command then signaling done 0 in ProcessCommand when external control is already off and the next command comes in with USER_STOP set - to use up the stop Abort is generally visited twice, when either internal thread still running or under external control, except that if it is already marked as disconnected and it is not a user stop with commandReady still set. just turns off external control in latter case Otherwise it does some cleanup and starts a task when it is internal script or errorOccurred set, which makes it call Abort again in TaskDone The involvement of threadDone is still to be described. disconnected is a flag for the socket getting closed It is set when the client disconnects, i.e. close after error in ReportErrorAndClose It is cleared in StartRunningScrpLang It is cleared when a new connection is accepted if external control is still on, i.e., if MacroProcessor hasn't yet acted on the disconnect and turned off EC