-- 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 <ESC> 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 <CR> | NonPromptedCommandList ;
		NonPromptedCommandList <EOF>
PromptedCommandList ::= PromptedCommand | Command | CommandList <SP> Command
		  | CommandList <SP> PromptedCommand
NonPromptedCommandList ::= Command | CommandList <SP> Command
Command ::= ID [ ParamList ]
PromptedCommand ::= ID <CR> PromptedParamList
ParamList ::= Param | ParamList , Param
PromptedParamList ::= Param | PromptedParamList <CR> Param
Param ::= " STRING " | ' CHARACTER | Expression | <empty>
Expression ::= Product | Expression + Product | Expression - Product
Product ::= Factor | Product * Factor | Product / Factor | Product MOD Factor
Factor ::= - Primary | Primary
Primary ::= NUM | ( Expression )