//Mcmd.bcpl--command menu, error, and timeout procedures
//23 June 1983
get "streams.d"
get "mdecl.d"
get "mcommon.d"
external [
// OS
Dvec; AddToZone; Allocate; Free; sysZone; CallSwat; DoubleAdd; keys
Resets; Endofs; Gets; ReadBlock; WriteBlock; Closes; CreateDiskStream
Timer; GotoLabel; CallersFrame; GotoFrame
// MINIT0
DtachFP; RunProgFP; MStatus; FirstStatic; LastStatic; NStatics
Storage; EndStorage; StateBlock; StatePtr; StateEnd
// MIDAS
MidasSwat; StartTimer; TimerGoing; Initialized; BegCF; TopFrame
Resume; StateFileSize; @NestedCFiles
// MASM
Wss; @WssCSS; PutsCSS; ResetsCSS
DummyCall1; ErrorProtect; @AbortLabel; @AbortFrame
// MIOC
Wns
// MTXTBUF
ClearInText; InputStream; InputTextBuffer; TxtBNewChar
// MOVERLAY
KillOverlays; FlushOverlays; PeelOverlay
OverlayZone; OverlayFlushed
// MSYM
StreamFromTextName
// MDISP
MakeDispZoneAvail; SetDisplay; Blink; DisplayOff
// MRGN
DriverLoop; UpdateDisplay; RemoveFromEveryTimeList
ErrorFlag; AllowedRgn; PaintRgnLine
// MMENU
CreateAction; ForgetTemporaryActions; ExecuteTextCmdStream
FormMenu; DoTextAction; CommandCount; SkipCommandCount; CFileStream
@WsMarkA; WssMark; ThisAction
// MGO
@QuitF
// MCMDOV
TextCmdOutStream
// xxACTIONS
CmdDumpAct; CmdStartWCAct; CmdStopWCAct; CmdShowCmdAct
CmdConcealAct; CmdErrorContinueAct; CmdErrorAbortAct
CmdCFileAbortAct
LastAction; FirstCmdAction; LastCmdAction; TimeTimeOut
HWCFActions; NHWCFActions
// Machine dependent
FormHWMenu; DetachHardware
//Defined here
WnsCSS; WnsCSSD; WnsCS1; WnsCS1D; CmdDoRC; RunProgMenu; ReadCMenu
FormCmdMenu; ShowActions; DisplayError; ErrorExit; ErrorAbort
ConfirmAct; StartCmdOverlay; StartLargeOverlay; QuitCmdOverlay
SetAbort; SetAbortPure; CmdAbort
TEveryTime; TimeOutEveryTime
CmdCommentStream; CmdCS1; CFOutStream
SavedLoadText; Confirmed; LoadDone; ShowActionForm
CmdMDFS; CmdAltMenuP; @CmdAbortAct; PassiveOnly
// Defined here for init and xxActions.asm only
DoOverlay; EndError; CmdRunProg; CmdWriteState; RestoreState
ExecuteCFile; NPrograms; NReadCFiles
]
static[
CmdMDFS; CmdCommentStream; CmdCS1; CmdEString2; CmdAltMenuP
CmdQuitOverlayF; LoadDone = false; Confirmed = false
ShowActionForm = false; SavedLoadText; NPrograms; NReadCFiles
PassiveOnly = false; TEveryTime = 0; CFOutStream = 0; @CmdAbortAct
]
// Procedures made external
let WnsCSS(Num) be Wns(CmdCommentStream,Num,0,8)
and WnsCSSD(Num) be Wns(CmdCommentStream,Num,0,10)
and WnsCS1(N) be Wns(CmdCS1,N,0,8)
and WnsCS1D(N) be Wns(CmdCS1,N,0,10)
and FormCmdMenu() be FormMenu(CmdMDFS,FormCmdmenuText)
//Action subr is called with args
// (1) menu stream (always ItemStream)
// (2) MDFS (always CmdMDFS for command menu)
// (3) Mouse button union
// (4) arg from ActionBlock
and FormCmdmenuText(S,MDFS) be
[ //Initialize the ScreenTV stuff for this region to all blanks and
//set dirty
for L = 0 to MDFS>>Rgn.Height-1 do
PaintRgnLine(MDFS,L,table [ 0 ] )
test CmdAltMenuP ne 0
ifso CmdAltMenuP()
ifnot
[
//Kludge to allow partitioning of the main command menu actions according
//to flags in the Action structure. At the moment only two flags are used:
//ifPassive and ifActive. Machine-dependent code manipulates the
//PassiveOnly flag to control what’s displayed.
for I = FirstCmdAction to LastCmdAction by size Action/16 do
[ test PassiveOnly
ifso if I>>Action.ifPassive eq 0 then loop
ifnot if I>>Action.ifActive eq 0 then loop
WsMarkA(I)
]
if LoadDone & not PassiveOnly then WsMarkA(CmdDumpAct)
WsMarkA(ShowActionForm ? CmdConcealAct,CmdShowCmdAct)
WsMarkA(TextCmdOutStream eq 0 ? CmdStartWCAct,CmdStopWCAct)
FormHWMenu()
]
]
and ShowActions(Act1,FirstAct,NActs) be
[if Act1 ne -1 then WsMarkA(Act1)
for I = 1 to NActs do
[ WsMarkA(FirstAct); FirstAct = FirstAct+(size Action/16)
]
]
and SetAbort(lvProc,Arg) be CmdAbortAct =
CreateAction("Abort",lvProc,Arg,0,$C-100B)
//Like SetAbort but don’t clear comment lines at onset of action.
and SetAbortPure(lvProc,Arg) be
[SetAbort(lvProc,Arg)
CmdAbortAct>>Action.ifNoRes = 1
]
and CmdAbort(nil,nil,nil) be
[ResetsCSS(); WssCSS("XXX"); QuitCmdOverlay(0)
]
//Called both as an action (with Menu eq ReadCMenu) and during
//initialization (with Menu eq RunProgMenu).
and CmdDoRC(lvMenu,nil,nil) be
[if CmdAltMenuP ne 0 then
ErrorAbort("RdCmds ill. inside an action")
unless TimerGoing do StartTimer()
SkipCommandCount = 0
//Use menu when junk numbers are on input line
if (InputTextBuffer!1 le $9) & (InputTextBuffer!1 ge $0)
then Resets(InputStream)
if InputTextBuffer!0 eq 0 do//Get name from menu?
[ let DisplayState = SetDisplay(false)//Display on
SetAbort(lv CmdAbort,0)
SkipCommandCount = StartCmdOverlay(rv lvMenu)
SetDisplay(DisplayState)
]
//Flush stack and continue in MIDAS.BCPL
GotoLabel(TopFrame,BegCF,0)
]
//Execute actions in the RdCmds menu.
and ExecuteCFile(fname,MBunion,nil) = valof
[Wss(InputStream,fname)
//The MProc word in the Action table for some of the ReadCMenu actions
//contains the entry-skip-count or 0 if no skip; this feature is accessible
//only to SPECIAL.MIDAS at present because the actions must be predefined
//in d1actions.asm.
resultis QuitCmdOverlay(ThisAction>>Action.lvMProc)
]
and ReadCMenu() be
[ShowActions(CmdAbortAct,LastAction+(NPrograms*(size Action/16)),
NReadCFiles)
ShowActions(-1,HWCFActions,NHWCFActions)
]
and RunProgMenu() be ShowActions(CmdAbortAct,LastAction,NPrograms)
//At the moment, this procedure is pretty clean. However, if at some
//future time code is added to save the state of the current program
//prior to a Dtach, then will have to add the commented code below.
and CmdRunProg(Dtach,nil,nil) be
[if CmdAltMenuP ne 0 do
[ if not MStatus>>MStatus.MachRunning then
ErrorAbort("RunProg/Dtach ill. inside an action")
// RemoveFromEveryTimeList(QuitF)
// ForgetTemporaryActions()
// FlushOverlays()
//TimeOut will be on when Dtach’ing from a Go inside a command file.
// if TEveryTime ne 0 do
// [ RemoveFromEveryTimeList(TEveryTime); TEveryTime = 0
// ]
]
SetDisplay(true)
if Dtach do
[ if CFileStream ne 0 do
[ Closes(CFileStream); CFileStream = 0
]
if TextCmdOutStream ne 0 do
[ Closes(TextCmdOutStream); TextCmdOutStream = 0
]
NestedCFiles = 0
]
if CFileStream eq 0 then StartTimer()
MStatus>>MStatus.RunProg = Dtach ? 2,1
MStatus>>MStatus.CFileStream = CFileStream//For RdCmds
MStatus>>MStatus.CFOutStream = CFOutStream//For Open/AppendOutput
MStatus>>MStatus.TextCmdOutStream = TextCmdOutStream//For WrtCmds
MStatus>>MStatus.ShowActionForm = ShowActionForm//For ShowCmds
MakeDispZoneAvail()
//OverlayFlushed = true
while PeelOverlay() do [ ]
//Reset Midas and continue execution at "Resume" in MIDAS.BCPL
RestoreState((Dtach ? DtachFP,RunProgFP),true)
]
//EveryTimeList procedure called by DriverLoop during a TimeOut.
and TimeOutEveryTime(nil) be
[let Now = vec 1; Timer(Now); DoubleAdd(Now,TimeTimeOut)
//Timed out or completed?
if (Now!0 ge 0) % (CommandCount < 0) do
[ RemoveFromEveryTimeList(TEveryTime); TEveryTime = 0
if CommandCount eq 0 do//if timed out
[ SkipCommandCount = 1
//DoTextAction should not return because QuitCmdOverlay will be called
DoTextAction($C - 100B)
]
]
]
//Start a very large overlay: First call InitP to build the actions;
//InitP returns lvProc for a procedure that will return the menu-forming
//procedure. Then flush out OverlayZone, add large storage blocks from
//the stack and sysZone to OverlayZone, and call StartCmdOverlay(lvProc);
//overlays needed will swap in during execution. After QuitCmdOverlay,
//flush everything and clean up.
//**All of callers leading to StartLargeOverlay must be resident.
and StartLargeOverlay(lvInitP,A1,A2,A3,A4; numargs NA) be
[lvInitP = DummyCall1(lvInitP,NA-1,A1,A2,A3,A4)
FlushOverlays()
//sysZone block will be used for the D1SimIFU overlay.
let ExtraSysZone = Allocate(sysZone,#5100)
if ExtraSysZone eq 0 then MidasSwat(SmallsysZone)
AddToZone(OverlayZone,ExtraSysZone,#5100)
//Use Dvec so stack depth of DummyCall1(..) above won’t add to the #3100
//words here.
let ExtraStack = #3100
Dvec(StartLargeOverlay,lv ExtraStack)
//This block will be used for D1SimCon overlay.
AddToZone(OverlayZone,ExtraStack,#3100)
StartCmdOverlay(lvInitP)
KillOverlays()//Rebuilds zone if display is off
Free(sysZone,ExtraSysZone)
]
//Initiate an action which uses an alternate command menu. Action exits
//with QuitCmdOverlay(Result) and returns Result, or by ErrorExit or
//DisplayError, which return 0. StartCmdOverlay establishes a
//super-errorset that overrides the more local errorsets established by
//ErrorProtect. lvInitP may be either a menu-forming procedure or lvProc
//for a procedure that returns the menu-forming procedure as its result.
and StartCmdOverlay(lvInitP,A1,A2,A3,A4; numargs NA) = valof
[let DisplayState,Result = DisplayOff,nil
let oldCmdAltMenuP,oldCmdQuitOverlayF = CmdAltMenuP,CmdQuitOverlayF
CmdAltMenuP = lvInitP > LastStatic ? lvInitP,
DummyCall1(lvInitP,NA-1,A1,A2,A3,A4)
FormCmdMenu()
//Call DoOverlay by ErrorProtect so that AbortFrame and AbortLabel will be
//restored after QuitCmdOverlay before any other errors can occur.
let Result = ErrorProtect(lv DoOverlay)
CmdAltMenuP,CmdQuitOverlayF = oldCmdAltMenuP,oldCmdQuitOverlayF
FormCmdMenu()
//Restore display to its old on/off state
SetDisplay(DisplayState); resultis Result
]
//DoOverlay is called from StartCmdOverlay (via ErrorProtect) and from Init2.
//In cfiles ExecuteTextCmdStream is recursively called; it calls DriverLoop
//following the action after a Timeout. The only exits from DriverLoop and
//ExecuteTextCmdStream are by QuitCmdOverlay.
and DoOverlay() = valof
[CmdQuitOverlayF = CallersFrame()//ErrorProtect frame
test (CFileStream ne 0) & (not ErrorFlag)
ifso ExecuteTextCmdStream(CFileStream)
ifnot DriverLoop()
]
//Used by actions to return from command overlays; by DisplayError and
//ErrorExit to abort either the command overlay or last ErrorProtect call.
and QuitCmdOverlay(Result) be
[if CmdAltMenuP ne 0 do
[ ForgetTemporaryActions(); GotoFrame(CmdQuitOverlayF,Result)
]
GotoLabel(AbortFrame,AbortLabel,Result)
]
//If numargs is > 3 then CmdCommentStream is untouched
and DisplayError(S1,S2,S3,NoCSS; numargs NA) = valof
[Confirmed = false
if not Initialized then CallSwat(S1)
let DisplayOff = SetDisplay(false)
if NA < 4 do
[ ResetsCSS()
if NA > 2 then WssCSS(S3)
WssCSS(S1)
]
CmdEString2 = NA > 1? S2, 0
ErrorFlag,AllowedRgn = true,CmdMDFS
let R = StartCmdOverlay(ErrorMenu)
ErrorFlag = false
SetDisplay(DisplayOff)
if R then resultis true//Continue
//Return 0 from StartCmdOverlay or last ErrorProtect(..)
QuitCmdOverlay(0)
]
//EndError is the action procedure for "Continue" and "Abort" in
//DisplayError’s menu; it is also called by CmdAbortCFile.
//P is true to continue from error, false to abort command
and EndError(P,nil,nil) be GotoFrame(CmdQuitOverlayF,P)
//???Presently cannot allow abort of command within CFile because on
//OpenFile errors this crashes???
//CmdCFileAbortAct => CmdAbortCFile
//CmdErrorAbortAct => EndError
//CmdErrorContinueAct => EndError
and ErrorMenu() be
[if CmdEString2 ne 0 then WssMark(CmdEString2,CmdErrorContinueAct)
WsMarkA((CFileStream ne 0 ? CmdCFileAbortAct,CmdErrorAbortAct))
]
and ErrorPrin(S1,S2,S3; numargs NA) be
[if NA < 1 then S1 = "No good"
Confirmed = false
test Initialized
ifso WssCSS(S1)
ifnot CallSwat(S1)
if NA > 1 then WssCSS(S2)
if NA > 2 then WssCSS(S3)
test CFileStream ne 0
ifso DisplayError(0,"Continue",0,true)
ifnot
[ SetDisplay(false); Blink()
]
]
//Prints the three argument strings on CmdCommentStream (prints
//"No good" if none of these strings is given); then, if inside a
//command overlay calls DisplayError and exits from the command overlay
//if the user continues; if not inside a command overlay, exits from the
//last ErrorProtect call with a result of 0.
and ErrorExit(S1,S2,S3; numargs NA) be
[DummyCall1(lv ErrorPrin,NA,S1,S2,S3)
QuitCmdOverlay(0)
]
//Abort action not in command overlay (actions that are illegal during
//a command overlay, such as SetValue, cannot call ErrorExit because
//they crash Midas).
and ErrorAbort(S1,S2,S3; numargs NA) be
[DummyCall1(lv ErrorPrin,NA,S1,S2,S3)
GotoLabel(AbortFrame,AbortLabel,0)
]
and ConfirmAct(Str,TV) = valof
[if Confirmed then
[ Confirmed = false; resultis true
]
WssCSS(Str)
for I = 1 to TV!0 do PutsCSS(TV!I)
WssCSS(" [confirm]")
UpdateDisplay()
let DisplayOff = SetDisplay(false)
[ if not Endofs(keys) then switchon Gets(keys) into
[
case $n: case $N: case 177B: case $C-100:
ResetsCSS(); WssCSS("XXX")
SetDisplay(DisplayOff); resultis false
case $.: case $Y: case $y: case $*N:
ResetsCSS(); SetDisplay(DisplayOff); resultis true
default: Blink()
]
] repeat
]
//The state of the program consists of its statics (all of which are saved),
//some page zero items (explicitly saved by calls on SaveStatics), and the
//core between StateEnd and EndStorage when SaveState is called.
//The address of the first static (Layout!26) and last static (Layout!27)
//are obtained from the layout vector in the Executive’s call to Midas.
//It seems wasteful to save all procedure statics, but this is necessary
//if the Overlay package is used, and it is easier to save all than be
//selective. There is no provision for enumerating individual blocks of
//storage outside the region delimited by StateEnd and (final) EndStorage.
//SaveState can be called multiple times, allowing the program to
//fire up with different initial information.
and SaveState(S) = valof
[if S eq 0 then MidasSwat(BadSaveStateFP)
//Copy selected items (zrel statics) into StateBlock
for I = 0 to StatePtr-2 by 2 do
[ StateBlock!(I+1) = rv StateBlock!I
]
WriteBlock(S,FirstStatic,NStatics)
let BufSize = StateEnd-EndStorage
WriteBlock(S,EndStorage,BufSize)
Closes(S)
//Return file size for statistical purposes
resultis NStatics+BufSize
]
//Must be resident
and CmdWriteState(nil,nil,nil) be
[if InputTextBuffer!0 eq 0 then ErrorAbort("Missing state file name")
let S = StreamFromTextName(InputTextBuffer,
".STATE",ksTypeWriteOnly,wordItem)
TxtBNewChar(#177); KillOverlays()
let C = CFileStream; CFileStream = 0
StateFileSize = SaveState(S)
CFileStream = C
]
//S is a stream argument for the file created by SaveState. reinitFlag
//should be true when RestoreState is called during initialization.
//The program can reinitialize itself during operation by calling
//RestoreState with reinitFlag false.
and RestoreState(FP,reinitFlag) be
[let S = CreateDiskStream(FP,ksTypeReadOnly)
if S eq 0 then MidasSwat(BadRestoreStateFP)
let newEndStorage = EndStorage
ReadBlock(S,FirstStatic,NStatics)
if not reinitFlag then
if newEndStorage ne StateEnd then MidasSwat(MidasIncompatible)
let WC = StateEnd - EndStorage
let N = ReadBlock(S,EndStorage,WC)
if N ne WC then MidasSwat(StateFileClobbered)
for I = 0 to StatePtr-2 by 2 do
[ rv (StateBlock!I) = StateBlock!(I+1)
]
Closes(S); GotoLabel(TopFrame,Resume,0)
]