-- Commander.mesa; edited by Johnsson, 9-Sep-80 20:35:49 -- edited by Sweet, 20-Apr-81 14:44:22 -- edited by Bruce, 10-Jan-81 15:32:29 -- edited by Andrew Birrell August 9, 1982 1:13 pm (for Cedar 3.3!) -- Copyright Xerox Corporation 1979, 1980 DIRECTORY Ascii USING [CR, DEL, ESC, NUL, SP], CommanderDefs USING [CommandBlock, CommandBlockHandle, CommandParam, ParamType], Exec, Frame USING [MyLocalFrame], Inline USING [BITAND, BITOR], PrincOps USING [StateVector], Runtime USING [CallDebugger], Storage USING [Node, String, CopyString, Free], String USING [AppendChar, AppendString, EquivalentString, InvalidNumber,StringBoundsFault], Time USING [AppendCurrent], TTY, UserTerminal; Commander: PROGRAM IMPORTS Exec, Frame, Inline, Runtime, Storage, String, Time, TTY, UserTerminal EXPORTS CommanderDefs = BEGIN OPEN String, CommanderDefs, Ascii; CommandItem: TYPE = RECORD [ cb: CommandBlockHandle, link: POINTER TO CommandItem]; StringItem: TYPE = RECORD [link: POINTER TO StringItem, string: STRING]; commandHead: POINTER TO CommandItem _ NIL; stringHead: POINTER TO StringItem _ NIL; SyntaxError: ERROR = CODE; Help: SIGNAL = CODE; BadName: ERROR = CODE; BadParam: ERROR [type: ParamType] = CODE; GetDebugger: PROCEDURE = BEGIN Runtime.CallDebugger[NIL]; END; ExtensionIs: PROCEDURE [name, ext: STRING] RETURNS [BOOLEAN] = BEGIN t: STRING _ [40]; i: CARDINAL; IF name.length <= ext.length THEN RETURN[FALSE]; FOR i IN [name.length - ext.length..name.length) DO String.AppendChar[t, name[i]]; ENDLOOP; RETURN[String.EquivalentString[t, ext]] END; CheckForExtension: PROCEDURE [name, ext: STRING] = BEGIN i: CARDINAL; FOR i IN [0..name.length) DO IF name[i] = '. THEN RETURN; ENDLOOP; String.AppendString[name, ext]; RETURN END; AddCommand: PUBLIC PROCEDURE [name: STRING, proc: PROCEDURE, numargs: CARDINAL] RETURNS [CommandBlockHandle] = BEGIN OPEN Storage; c: POINTER TO CommandItem _ Node[SIZE[CommandItem]]; cb: CommandBlockHandle _ Node[ SIZE[CommandBlock] + numargs*SIZE[CommandParam]]; c^ _ CommandItem[cb: cb, link: commandHead]; commandHead _ c; cb.name _ name; cb.proc _ proc; cb.nparams _ numargs; RETURN[cb] END; NewString: PROCEDURE [s: STRING] RETURNS [ns: STRING] = BEGIN OPEN Storage; si: POINTER TO StringItem _ Node[SIZE[StringItem]]; si^ _ StringItem[link: stringHead, string: ns _ CopyString[s]]; stringHead _ si; RETURN END; FreeStrings: PROCEDURE = BEGIN OPEN Storage; next: POINTER TO StringItem; WHILE stringHead # NIL DO next _ stringHead.link; Free[stringHead.string]; Free[stringHead]; stringHead _ next; ENDLOOP; RETURN END; WriteEOL: PROCEDURE = {IF ~TTY.NewLine[Exec.w] THEN TTY.PutCR[Exec.w]}; firstCommand: BOOLEAN _ TRUE; interactive: BOOLEAN _ FALSE; SetupCom: PROCEDURE [c: POINTER TO Exec.CommandLine _ @Exec.commandLine] RETURNS [BOOLEAN] = {RETURN[~(c.s = NIL OR c.i >= c.s.length)]}; SkipToken: PROCEDURE [c: POINTER TO Exec.CommandLine _ @Exec.commandLine] = BEGIN foundToken: BOOLEAN _ FALSE; ch: CHARACTER; DO IF c.i >= c.s.length THEN EXIT; ch _ c.s[c.i]; c.i _ c.i + 1; SELECT ch FROM Ascii.SP, Ascii.CR => IF foundToken THEN EXIT; ENDCASE => foundToken _ TRUE; ENDLOOP; RETURN END; -- code to get a line from the user, handling ESC and ?; stuffs it in line line: STRING _ NIL; lineTerminator: CHARACTER; Lindex: CARDINAL; AppendStringToLine: PROCEDURE [s: STRING] = BEGIN UNTIL (s.length + line.length) <= line.maxlength DO AddToLine[]; ENDLOOP; AppendString[line, s]; RETURN END; AppendCharToLine: PROCEDURE [c: CHARACTER] = BEGIN IF line.length = line.maxlength THEN AddToLine[]; AppendChar[line, c]; RETURN END; ReadUserLine: PROCEDURE [newstring: BOOLEAN] = BEGIN -- read line from user; also handles and '? for input from user IF line = NIL THEN line _ Storage.String[80]; [] _ TTY.GetEditedString[Exec.w, line, LineMonitor, newstring ! resume => BEGIN newstring _ FALSE; RETRY END]; Lindex _ 0; RETURN END; resume: SIGNAL = CODE; LineMonitor: PROCEDURE [c: CHARACTER] RETURNS [BOOLEAN] = BEGIN SELECT c FROM CR => RETURN[TRUE]; '? => BEGIN WriteChar['?]; IF line.length = 0 THEN SIGNAL Help; PromptCompletions[]; SIGNAL resume; ERROR END; ESC => BEGIN ExtendLine[]; SIGNAL resume; ERROR END; ENDCASE => RETURN[FALSE] END; PromptCompletions: PROCEDURE = BEGIN id: STRING = [40]; atLeastOne: BOOLEAN _ FALSE; p: POINTER TO CommandItem; IF GetLastID[id] THEN FOR p _ commandHead, p.link UNTIL p = NIL DO IF PrefixString[prefix: id, of: p.cb.name] THEN BEGIN IF ~atLeastOne THEN WriteEOL[]; WriteChar[SP]; WriteChar[SP]; WriteString[p.cb.name]; atLeastOne _ TRUE; END; ENDLOOP; IF atLeastOne THEN ReTypeLine[] ELSE UserTerminal.BlinkDisplay[]; RETURN END; ExtendLine: PROCEDURE = BEGIN i: CARDINAL; id: STRING = [40]; match: STRING = [40]; moreThanOne, atLeastOne: BOOLEAN _ FALSE; p: POINTER TO CommandItem; IF GetLastID[id] THEN BEGIN FOR p _ commandHead, p.link UNTIL p = NIL DO IF PrefixString[prefix: id, of: p.cb.name] THEN IF ~atLeastOne THEN BEGIN AppendString[match, p.cb.name]; atLeastOne _ TRUE; END ELSE BEGIN AndString[match, p.cb.name]; moreThanOne _ TRUE; END; ENDLOOP; END; IF atLeastOne AND id.length # match.length THEN BEGIN FOR i IN [id.length..match.length) DO AppendCharToLine[match[i]]; WriteChar[match[i]]; ENDLOOP; IF moreThanOne THEN UserTerminal.BlinkDisplay[]; END ELSE UserTerminal.BlinkDisplay[]; RETURN END; PrefixString: PROCEDURE [prefix, of: STRING] RETURNS [BOOLEAN] = BEGIN i: CARDINAL; IF prefix.length > of.length THEN RETURN[FALSE]; FOR i IN [0..prefix.length) DO IF ~EquivalentChar[prefix[i], of[i]] THEN RETURN[FALSE]; ENDLOOP; RETURN[TRUE] END; AndString: PROCEDURE [accum, s: STRING] = BEGIN i: CARDINAL; FOR i IN [0..s.length) DO IF ~EquivalentChar[accum[i], s[i]] THEN BEGIN accum.length _ i; RETURN END; ENDLOOP; accum.length _ s.length; RETURN; END; GetLastID: PROCEDURE [id: STRING] RETURNS [BOOLEAN] = BEGIN i, start: CARDINAL; c: CHARACTER; IF line.length = 0 THEN RETURN[FALSE]; start _ line.length; FOR i DECREASING IN [0..line.length) DO IF AlphaNumeric[c _ line[i]] THEN start _ i ELSE IF c = '] OR c = SP THEN EXIT ELSE RETURN[FALSE]; ENDLOOP; FOR i IN [start..line.length) DO id[i - start] _ line[i] ENDLOOP; id.length _ line.length - start; RETURN[id.length # 0] END; AlphaNumeric: PROCEDURE [c: CHARACTER] RETURNS [BOOLEAN] = {RETURN[Alphabetic[c] OR Digit[c]]}; Alphabetic: PROCEDURE [c: CHARACTER] RETURNS [BOOLEAN] = BEGIN RETURN[Inline.BITAND[c, 337B] IN [100B..132B]] END; Digit: PROCEDURE [c: CHARACTER] RETURNS [BOOLEAN] = BEGIN RETURN[c IN ['0..'9]] END; EquivalentChar: PROCEDURE [c, d: CHARACTER] RETURNS [BOOLEAN] = BEGIN OPEN Inline; RETURN[BITOR[c, 40B] = BITOR[d, 40B]] END; AddToLine: PROCEDURE = INLINE {line _ Storage.CopyString[line, 80]}; ReTypeLine: PROCEDURE = BEGIN WriteEOL[]; WriteString[line]; RETURN END; -- code to handle characters command: STRING = [100]; executing: BOOLEAN _ FALSE; Cindex: CARDINAL; currentChar: CHARACTER; EndOfString: SIGNAL = CODE; GetChar: PROCEDURE RETURNS [CHARACTER] _ GetCommandChar; PutBackChar: PROCEDURE _ PutBackCommandChar; GetCommandChar: PROCEDURE RETURNS [CHARACTER] = BEGIN IF Cindex >= command.length THEN currentChar _ NUL ELSE BEGIN currentChar _ command[Cindex]; Cindex _ Cindex + 1; END; RETURN[currentChar] END; PutBackCommandChar: PROCEDURE = BEGIN IF currentChar = NUL THEN RETURN; IF Cindex = 0 THEN ERROR; Cindex _ Cindex - 1; RETURN END; CommandOverFlow: SIGNAL = CODE; SetUpCommand: PROCEDURE RETURNS [BOOLEAN] = BEGIN BEGIN ENABLE StringBoundsFault => SIGNAL CommandOverFlow; RETURN[IF interactive THEN CopyFromLine[] ELSE CopyFromExecCommand[]]; END END; CopyFromLine: PROCEDURE RETURNS [BOOLEAN] = BEGIN c: CHARACTER _ NUL; DO IF Lindex >= line.length THEN RETURN[FALSE]; c _ line[Lindex]; Lindex _ Lindex + 1; IF c # SP AND c # CR THEN EXIT; ENDLOOP; command.length _ 0; DO AppendChar[command, c]; IF c = '] OR Lindex >= line.length THEN EXIT; c _ line[Lindex]; Lindex _ Lindex + 1; ENDLOOP; Cindex _ 0; RETURN[TRUE] END; SkipExecBlanks: PROCEDURE [c: POINTER TO Exec.CommandLine _ @Exec.commandLine] RETURNS [ch: CHARACTER] = BEGIN UNTIL c.i >= c.s.length DO ch _ c.s[c.i]; c.i _ c.i + 1; IF ch # SP AND ch # CR THEN EXIT; ENDLOOP; END; WriteChar: PROC [ch: CHARACTER] = {TTY.PutChar[Exec.w, ch]}; WriteString: PROC [s: STRING] = {TTY.PutString[Exec.w, s]}; WriteLine: PROC [s: STRING] = {TTY.PutLine[Exec.w, s]}; EndOf: PROC [c: POINTER TO Exec.CommandLine _ @Exec.commandLine] RETURNS [BOOLEAN] = {RETURN[c.s = NIL OR c.i >= c.s.length]}; CopyFromExecCommand: PROCEDURE [c: POINTER TO Exec.CommandLine _ @Exec.commandLine] RETURNS [BOOLEAN] = BEGIN ch: CHARACTER; IF ~SetupCom[c] THEN BEGIN interactive _ TRUE; RETURN[FALSE] END; ch _ SkipExecBlanks[c]; IF EndOf[c] THEN IF firstCommand THEN BEGIN interactive _ TRUE; RETURN[FALSE] END ELSE SIGNAL CommandExit; command.length _ 0; WriteEOL[]; WriteChar['<]; WriteChar['>]; DO AppendChar[command, ch]; WriteChar[ch]; IF ch = '] OR EndOf[c] THEN EXIT; ch _ c.s[c.i]; c.i _ c.i + 1; ENDLOOP; WriteEOL[]; Cindex _ 0; RETURN[TRUE] END; GetName: PROCEDURE [n: STRING] = BEGIN n.length _ 0; DO IF AlphaNumeric[GetChar[]] THEN AppendChar[n, currentChar] ELSE EXIT; ENDLOOP; PutBackChar[]; SkipBlanks[]; IF GetChar[] # '[ THEN SE[]; RETURN END; SkipBlanks: PROCEDURE = BEGIN DO IF GetChar[] # SP THEN BEGIN PutBackChar[]; RETURN END; ENDLOOP END; -- code to parse user command ParseCommand: PROCEDURE [state: POINTER TO PrincOps.StateVector] = BEGIN proc: STRING = [40]; cb: CommandBlockHandle; i: CARDINAL; GetName[proc]; cb _ FindProc[proc].cb; FOR i IN [0..cb.nparams) DO state.stk[i] _ GetArg[cb, cb.params[i].type]; IF GetChar[] # (IF i = cb.nparams - 1 THEN '] ELSE ',) THEN SE[]; ENDLOOP; state.dest _ LOOPHOLE[cb.proc]; state.stkptr _ cb.nparams; RETURN END; FindProc: PROCEDURE [name: STRING] RETURNS [p: POINTER TO CommandItem] = BEGIN FOR p _ commandHead, p.link UNTIL p = NIL DO IF EquivalentString[name, p.cb.name] THEN RETURN; ENDLOOP; ERROR BadName; END; GetArg: PROCEDURE [cb: CommandBlockHandle, t: ParamType] RETURNS [a: UNSPECIFIED] = BEGIN s: STRING = [100]; SkipBlanks[]; SELECT GetChar[] FROM '" => BEGIN IF t # string THEN ERROR BadParam[t]; DO IF GetChar[] = '" AND GetChar[] # '" THEN {PutBackChar[]; EXIT}; IF executing THEN AppendChar[s, currentChar]; ENDLOOP; IF executing THEN a _ NewString[s]; END; '' => {IF t # character THEN ERROR BadParam[t]; a _ GetChar[]}; IN ['0..'9], '(, '- => BEGIN IF t # numeric THEN ERROR BadParam[t]; PutBackChar[]; a _ ExpressionToNumber[]; END; IN ['a..'z], IN ['A..'Z] => { SELECT t FROM boolean => IF currentChar = 'T THEN a _ GetTRUE[t] ELSE IF currentChar = 'F THEN a _ GetFALSE[t]; string => { DO SELECT currentChar FROM ',, '], CR, NUL => {PutBackChar[]; EXIT}; ENDCASE => IF executing THEN AppendChar[s, currentChar]; [] _ GetChar[]; ENDLOOP; IF executing THEN a _ NewString[s]}; ENDCASE => ERROR BadParam[t]}; ENDCASE => ERROR BadParam[t]; SkipBlanks[]; RETURN END; GetTRUE: PROCEDURE [t: ParamType] RETURNS [BOOLEAN] = BEGIN IF GetChar[] # 'R THEN ERROR BadParam[t]; IF GetChar[] # 'U THEN ERROR BadParam[t]; IF GetChar[] # 'E THEN ERROR BadParam[t]; RETURN[TRUE]; END; GetFALSE: PROCEDURE [t: ParamType] RETURNS [BOOLEAN] = BEGIN IF GetChar[] # 'A THEN ERROR BadParam[t]; IF GetChar[] # 'L THEN ERROR BadParam[t]; IF GetChar[] # 'S THEN ERROR BadParam[t]; IF GetChar[] # 'E THEN ERROR BadParam[t]; RETURN[FALSE]; END; -- code to parse user commands in interactive mode ParsePromptedCommand: PROCEDURE = BEGIN proc: STRING = [40]; cb: CommandBlockHandle; IF GetLastID[proc] THEN BEGIN cb _ FindProc[proc].cb; GetPromptedArgs[cb]; Confirm[]; RETURN END; lineTerminator _ CR; RETURN END; CRFound: PROCEDURE [c: CHARACTER] RETURNS [BOOLEAN] = BEGIN RETURN[c = CR] END; GetPromptedArgs: PROCEDURE [cb: CommandBlockHandle] = BEGIN i: CARDINAL; cindex: CARDINAL; cstring: STRING = [100]; GetArgChar: PROCEDURE RETURNS [c: CHARACTER] = BEGIN IF cindex >= cstring.length THEN currentChar _ NUL ELSE BEGIN currentChar _ cstring[cindex]; cindex _ cindex + 1; END; RETURN[currentChar] END; PutBackArgChar: PROCEDURE = BEGIN IF currentChar = NUL THEN RETURN; IF cindex = 0 THEN ERROR; cindex _ cindex - 1; RETURN END; GetChar _ GetArgChar; PutBackChar _ PutBackArgChar; AppendCharToLine['[]; FOR i IN [0..cb.nparams) DO WriteString[" "]; WriteString[cb.params[i].prompt]; WriteChar[':]; WriteChar[' ]; [] _ TTY.GetEditedString[Exec.w, cstring, CRFound, TRUE]; cindex _ 0; [] _ GetArg[cb, cb.params[i].type]; AppendStringToLine[cstring]; AppendCharToLine[',]; ENDLOOP; IF cb.nparams # 0 THEN line[line.length - 1] _ '] ELSE AppendCharToLine[']]; GetChar _ GetCommandChar; PutBackChar _ PutBackCommandChar; RETURN END; Confirm: PROCEDURE = BEGIN char: CHARACTER; WriteString[" [confirm]"]; DO char _ TTY.GetChar[Exec.w]; SELECT char FROM DEL => SIGNAL TTY.Rubout; CR => BEGIN WriteEOL[]; EXIT END; SP => BEGIN WriteString[" <>"]; EXIT END; ENDCASE => WriteChar['?]; ENDLOOP; lineTerminator _ char; RETURN END; -- parsing arithmetic expressions symbol: Symbol; Symbol: TYPE = RECORD [ body: SELECT tag: * FROM num => [val: INTEGER], delim => [char: CHARACTER], ENDCASE]; Num: TYPE = num Symbol; SE: PROCEDURE = BEGIN ERROR SyntaxError END; Scan: PROCEDURE = BEGIN v8, v10, radix, number: CARDINAL; digits: ARRAY CHARACTER ['0..'9] OF CARDINAL = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; firstchar: BOOLEAN _ TRUE; v8 _ v10 _ 0; SkipBlanks[]; DO SELECT GetChar[ ] FROM IN ['0..'9] => BEGIN v8 _ v8*8 + digits[currentChar]; v10 _ v10*10 + digits[currentChar]; END; 'M => BEGIN IF ~firstchar THEN SE[]; IF ~(GetChar[] = 'O AND GetChar[] = 'D) THEN SE[]; IF ~Alphabetic[GetChar[]] THEN PutBackChar[] ELSE SE[]; symbol _ [delim['!]]; RETURN END; 'b, 'B => BEGIN number _ v8; radix _ 8; GOTO exponent END; 'd, 'D => BEGIN number _ v10; radix _ 10; GOTO exponent END; SP => GOTO done; NUL => IF ~firstchar THEN GOTO done ELSE BEGIN symbol _ nul; RETURN END; '(, '/, '*, '+, '-, '), '], ', => IF firstchar THEN BEGIN symbol _ [delim[currentChar]]; RETURN END ELSE BEGIN PutBackChar[]; GOTO done END; ENDCASE => SIGNAL InvalidNumber; firstchar _ FALSE; REPEAT done => BEGIN symbol _ [num[v10]]; RETURN END; exponent => BEGIN IF firstchar THEN SE[]; v10 _ 0; WHILE Digit[GetChar[]] DO v10 _ v10*10 + digits[currentChar]; REPEAT FINISHED => PutBackChar[]; -- took one too many ENDLOOP; THROUGH [1..v10] DO number _ number*radix ENDLOOP; symbol _ [num[number]]; RETURN END; ENDLOOP; END; nul: Symbol = [delim[NUL]]; Primary: PROCEDURE RETURNS [n: Num] = BEGIN WITH s: symbol SELECT FROM delim => BEGIN IF s.char # '( THEN SE[]; Scan[]; n _ Exp[]; WITH symbol SELECT FROM delim => IF char = ') THEN BEGIN Scan[]; RETURN END; ENDCASE; SE[]; END; num => BEGIN n _ s; Scan[]; RETURN END; ENDCASE END; Factor: PROCEDURE RETURNS [n: Num] = BEGIN WITH symbol SELECT FROM delim => IF char = '- THEN BEGIN Scan[]; n _ Primary[]; n.val _ -n.val; RETURN END; ENDCASE; RETURN[Primary[]] END; Product: PROCEDURE RETURNS [n: Num] = BEGIN x: Num; n _ Factor[]; DO WITH symbol SELECT FROM delim => SELECT char FROM '* => BEGIN Scan[]; n.val _ Factor[].val*n.val; END; '/ => BEGIN Scan[]; x _ Factor[]; n.val _ n.val/x.val; END; '! => BEGIN Scan[]; x _ Factor[]; n.val _ n.val MOD x.val; END; ENDCASE => EXIT; ENDCASE => EXIT; ENDLOOP; RETURN END; Exp: PROCEDURE RETURNS [n: Num] = BEGIN n _ Product[]; DO WITH symbol SELECT FROM delim => SELECT char FROM '+ => BEGIN Scan[]; n.val _ Product[].val + n.val; END; '- => BEGIN Scan[]; n.val _ n.val - Product[].val; END; '], ', => BEGIN PutBackChar[]; EXIT END; NUL, ') => EXIT; ENDCASE => SE[]; ENDCASE => EXIT; ENDLOOP; RETURN END; ExpressionToNumber: PROCEDURE RETURNS [INTEGER] = BEGIN Scan[]; RETURN[Exp[].val] END; ShowSE: PROCEDURE = BEGIN IF ~executing THEN BEGIN WriteChar['?]; RETURN END; WriteEOL[]; IF interactive THEN WriteString[command]; WriteEOL[]; THROUGH [1..(Cindex + (IF interactive THEN 0 ELSE 2))) DO WriteChar['.]; ENDLOOP; WriteChar['^]; RETURN END; Driver: PROCEDURE = BEGIN state: PrincOps.StateVector; newline: BOOLEAN; ci: POINTER TO CommandItem; i: CARDINAL; BEGIN ENABLE BEGIN SyntaxError, InvalidNumber, StringBoundsFault => BEGIN ShowSE[]; GO TO abort END; CommandOverFlow => BEGIN WriteEOL[]; WriteString["Command too long!"]; GO TO abort END; BadName => BEGIN ShowSE[]; WriteString[" not found!"]; GO TO abort END; BadParam => BEGIN ShowSE[]; WriteString[" expected "]; SELECT type FROM string => WriteString["string"]; character => WriteString["character"]; numeric => WriteString["numerical"]; ENDCASE; WriteString[" parameter"]; GO TO abort END; TTY.Rubout => BEGIN WriteString[" XXX"]; GO TO abort END; Help => BEGIN WriteEOL[]; FOR ci _ commandHead, ci.link UNTIL ci = NIL DO WriteString[ci.cb.name]; WriteChar['[]; FOR i IN [0..ci.cb.nparams) DO IF i # 0 THEN WriteChar[',]; SELECT ci.cb.params[ i].type FROM string => WriteChar['"]; character => WriteChar['']; ENDCASE; WriteString[ci.cb.params[i].prompt]; SELECT ci.cb.params[i].type FROM string => WriteChar['"]; ENDCASE; ENDLOOP; WriteChar[']]; IF ci.link # NIL THEN BEGIN WriteChar[',]; WriteChar[' ]; END; ENDLOOP; GO TO abort END; UNWIND => FreeStrings[] END; newline _ TRUE; executing _ FALSE; IF interactive THEN BEGIN WriteEOL[]; WriteChar['<]; WriteChar['>]; DO ENABLE StringBoundsFault => BEGIN AddToLine[]; RESUME [line] END; ReadUserLine[newline]; newline _ FALSE; ParsePromptedCommand[]; IF lineTerminator = CR THEN EXIT; ENDLOOP; END; GetChar _ GetCommandChar; PutBackChar _ PutBackCommandChar; executing _ TRUE; WHILE SetUpCommand[] DO ParseCommand[@state]; state.instbyte _ 0; state.source _ LOOPHOLE[Frame.MyLocalFrame[]]; firstCommand _ FALSE; TRANSFER WITH state; state _ STATE; ENDLOOP; executing _ FALSE; EXITS abort => NULL; END; FreeStrings[]; RETURN END; Quit: PROCEDURE = BEGIN SIGNAL CommandExit; END; WriteHerald: PROCEDURE = BEGIN time: STRING _ [22]; WriteEOL[]; IF h # NIL THEN WriteLine[h]; Time.AppendCurrent[time]; time.length _ time.length - 3; WriteLine[time]; END; h: STRING _ NIL; InitCommander: PUBLIC PROCEDURE [herald: STRING] = BEGIN h _ herald; [] _ AddCommand["Quit", Quit, 0]; [] _ AddCommand["Debug", GetDebugger, 0]; END; CommandExit: SIGNAL = CODE; WaitCommands: PUBLIC PROCEDURE = BEGIN IF h = NIL THEN InitCommander[NIL]; executing _ interactive _ FALSE; firstCommand _ TRUE; WriteHerald[]; DO Driver[ ! CommandExit => EXIT]; ENDLOOP; END; END. -- Here is the grammar for the command line CommandLine ::= PromptedCommandList | NonPromptedCommandList ; NonPromptedCommandList PromptedCommandList ::= PromptedCommand | Command | CommandList Command | CommandList PromptedCommand NonPromptedCommandList ::= Command | CommandList Command Command ::= ID [ ParamList ] PromptedCommand ::= ID PromptedParamList ParamList ::= Param | ParamList , Param PromptedParamList ::= Param | PromptedParamList Param Param ::= " STRING " | ' CHARACTER | Expression | Expression ::= Product | Expression + Product | Expression - Product Product ::= Factor | Product * Factor | Product / Factor | Product MOD Factor Factor ::= - Primary | Primary Primary ::= NUM | ( Expression )