-- file CommandPack.Mesa
-- last modified by Satterthwaite, September 5, 1982 2:50 pm
-- last modified by Russ Atkinson, 24-Nov-80 14:40:48

DIRECTORY
  Ascii: TYPE USING [CR, SP],
  CharIO: TYPE USING [PutChar, PutString],
  CommandUtil: TYPE USING [CommandObject, CommandPtr],
  Heap: TYPE USING [systemZone],
  Stream: TYPE USING [Handle],
  Strings: TYPE USING [
    String, SubString, SubStringDescriptor,
    AppendChar, AppendString, EquivalentSubStrings];

CommandPack: PROGRAM
    IMPORTS CharIO, Heap, Strings
    EXPORTS CommandUtil = {
  OPEN CommandUtil;

  String: TYPE = Strings.String;

  Pair: TYPE = RECORD [key, val: String, next: PairList];
  PairList: PUBLIC TYPE = LONG POINTER TO Pair;

  TokenClass: TYPE = CHAR;
    id: TokenClass = 'I;
    eom: TokenClass = 03c;	-- ControlC

  State: TYPE = [0..17];
    --  0:
    --  1:    id
    --  2:    id ←
    --  3:    [ ?{id : id},
    --  4:    [ ?{id : id}, id
    --  5:    [ ?{id : id}, id :
    --  6:    [ ?{id : id}, id : id
    --  7:    [ ?{id : id}, ]
    --  8:    [ ?{id : id}, ] ←
    --  9:    LHS ← id
    -- 10:    ?(LHS ←) id [ ?{id : id},
    -- 11:    ?(LHS ←) id [ ?{id : id}, id
    -- 12:    ?(LHS ←) id [ ?{id : id}, id :
    -- 13:    ?(LHS ←) id [ ?{id : id}, id : id
    -- 14:    ?(LHS ←) id [ ?{id : id}, ]
    -- 15:    ?(LHS) id ?([ ?{id : id}, ])) /
    -- 16:    ?(?(LHS) id ?([ ?{id : id}, ])) / id
    -- 17:    ?(?(LHS) id ?([ ?{id : id}, ])) ?(/ ?id) (;|eom) | eom
    --            where LHS = id | [ ?{id : id}, ]


  Failed: PUBLIC ERROR = CODE;


 -- stream-like utilities

  GetChar: PROC [p: CommandPtr] RETURNS [c: CHAR] = {
    IF p.pos >= p.len THEN RETURN [eom];
    c ← p.data[p.pos];
    p.pos ← p.pos + 1};

  GetIndex: PROC [p: CommandPtr, delta: INTEGER←0] RETURNS [CARDINAL] = {
     pos: CARDINAL ← p.pos + delta;
     IF delta < 0 AND pos > p.pos THEN pos ← 0;
     RETURN [pos]};

  SetIndex: PROC [p: CommandPtr, index: CARDINAL] = {p.pos ← index};


  -- external parsing routine

  Parse: PUBLIC PROC [s: CommandPtr, opX, argX, resultX: CARDINAL]
      RETURNS [operator: String, argList, resultList: PairList, switches: String] = {
    token: TokenClass;
    state: State;

    idString: STRING ← [100];
    tIndex: CARDINAL;

    c: CHAR;

    NextToken: PROC = INLINE {
      WHILE c = Ascii.SP OR c = Ascii.CR DO c ← GetChar[s] ENDLOOP;
      tIndex ← GetIndex[s] - 1;
      SELECT c FROM
	IN ['a..'z], IN ['A..'Z], IN ['0..'9], '<, '., '+, '-, '~, '!, '$ => {
	  idString.length ← 0;
	  DO
	    SELECT c FROM
	      IN ['a..'z], IN ['A..'Z], IN ['0..'9],
	      '<, '>, '., '+, '-, '~, '!, '$ => {
		Strings.AppendChar[idString, c]; c ← GetChar[s]};
	      ENDCASE => EXIT;
	    ENDLOOP;
	  token ← id};
	'←, '[, '], ':, '/ => {token ← c; c ← GetChar[s]};
	',, ';, eom => token ← c;
	ENDCASE => BadCommand[]};

    CopyId: PROC [extra: CARDINAL] RETURNS [String] = INLINE {
      RETURN [CopyString[idString, extra]]};

    pair: ARRAY [0..1] OF String;

    PushArg: PROC = INLINE {argList ← AddPair[argList, pair[0], pair[1]]};
    PushResult: PROC = INLINE {resultList ← AddPair[resultList, pair[0], pair[1]]};

    RestoreToken: PROC = {SetIndex[s, tIndex]};

    BadCommand: PROC = {
      operator ← FreeString[operator];
      resultList ← FreePairList[resultList]; argList ← FreePairList[argList];
      switches ← FreeString[switches];
      ERROR Failed};

    c ← ' ;  state ← 0;  operator ← switches ← NIL;
    argList ← resultList ← NIL;

    UNTIL state = 17 DO
      NextToken[];
      SELECT token FROM
	id =>
	  SELECT state FROM
	    0 =>    {operator ← CopyId[MAX[resultX, opX]]; state ← 1};
	    2, 8 => {operator ← CopyId[opX]; state ← 9};
	    3 =>    {pair[0] ← CopyId[0]; state ← 4};
	    5 =>    {pair[1] ← CopyId[resultX]; PushResult[]; state ← 6};
	    10 =>   {pair[0] ← CopyId[0]; state ← 11};
	    12 =>   {pair[1] ← CopyId[argX]; PushArg[]; state ← 13};
	    15 =>   {switches ← CopyId[0]; state ← 16};
	    1, 9, 14, 16 => {RestoreToken[]; state ← 17};
	    ENDCASE =>  BadCommand[];
	'← =>
	  SELECT state FROM
	    1 => {
	      pair[0] ← NIL; pair[1] ← operator;  operator ← NIL;
	      PushResult[];  state ← 2};
	    7 => state ← 8;
	    ENDCASE => BadCommand[];
	'[ =>
	  SELECT state FROM
	    0 => state ← 3;
	    1, 9 => state ← 10;
	    14 => {RestoreToken[]; state ← 17};
	    ENDCASE => BadCommand[];
	'] =>
	  SELECT state FROM
	    3, 6 => state ← 7;
	    10, 13 => state ← 14;
	    ENDCASE => BadCommand[];
	': =>
	  SELECT state FROM
	    4 => state ← 5;
	    11 => state ← 12;
	    ENDCASE => BadCommand[];
	', =>
	  SELECT state FROM
	    6 => {state ← 3; c ← GetChar[s]};
	    13 => {state ← 10; c ← GetChar[s]};
	    1, 9, 14, 15, 16 => state ← 17;
	    ENDCASE => BadCommand[];
	'/ =>
	  SELECT state FROM
	    0, 1, 9, 14 => state ← 15;
	    ENDCASE => BadCommand[];
	'; =>
	  SELECT state FROM
	    1, 9, 14, 15, 16 => state ← 17;
	    ENDCASE => BadCommand[];
	eom =>
	  SELECT state FROM
	    0, 1, 9, 14, 15, 16 => state ← 17;
	    ENDCASE => BadCommand[];
	ENDCASE;
      ENDLOOP;

    RETURN};


  Echo: PUBLIC PROC [
      d: Stream.Handle,
      operator: String, argList, resultList: PairList, switches: String] = {
    OPEN CharIO;

    EchoList: PROC [list: PairList] = {
      PutChar[d, '[];
      FOR p: PairList ← list, p.next UNTIL p = NIL DO
	IF p.key # NIL THEN {PutString[d, p.key];  PutString[d, ": "L]};
	PutString[d, p.val];
	IF p.next # NIL THEN PutString[d, ", "L];
	ENDLOOP;
      PutChar[d, ']]};

    IF resultList # NIL THEN {
      IF resultList.next = NIL AND resultList.key = NIL THEN PutString[d, resultList.val]
      ELSE EchoList[resultList];
      PutString[d, " ← "L]};
    PutString[d, operator];
    IF argList # NIL THEN EchoList[argList];
    IF switches # NIL AND switches.length > 0 THEN {
      PutString[d, "/"L]; PutString[d, switches]}};


 -- string utilities

  CopyString: PUBLIC PROC [s: String, extra: CARDINAL←0] RETURNS [String] = {
    copy: String = (Heap.systemZone).NEW[StringBody[(IF s=NIL THEN 0 ELSE s.length) + extra]];
    IF s # NIL THEN Strings.AppendString[copy, s];  RETURN [copy]};

  FreeString: PUBLIC PROC [s: String] RETURNS [String] = {
    IF s # NIL THEN (Heap.systemZone).FREE[@s];  RETURN [NIL]};


 -- PairList utilities

  AddPair: PUBLIC PROC [list: PairList, key,val: String] RETURNS [PairList] = {
    new: PairList ← (Heap.systemZone).NEW[Pair];
    new↑ ← [key: key, val: val, next: list];
    RETURN [new]};

  GetNthPair: PUBLIC PROC [list: PairList, n: CARDINAL, delete: BOOL←FALSE]
      RETURNS [key, value: String←NIL] = {
    i: CARDINAL ← 0;
    FOR p: PairList ← list, p.next UNTIL p = NIL DO
      IF i = n THEN {key ← p.key; value ← p.val; IF delete THEN p.key ← p.val ← NIL; EXIT};
      i ← i+1;
      ENDLOOP;
    RETURN};

  ListLength: PUBLIC PROC [list: PairList] RETURNS [n: CARDINAL←0] = {
    FOR p: PairList ← list, p.next UNTIL p = NIL DO n ← n+1 ENDLOOP;
    RETURN};

  KeyValue: PUBLIC PROC [key: Strings.SubString, list: PairList, delete: BOOL←FALSE]
      RETURNS [s: String←NIL] = {
    FOR p: PairList ← list, p.next UNTIL p = NIL DO
      ss: Strings.SubStringDescriptor ← [base: p.key, offset: 0, length: p.key.length];
      IF Strings.EquivalentSubStrings[@ss, key] THEN {
	s ← p.val; IF delete THEN p.val ← NIL; EXIT};
      ENDLOOP;
    RETURN};

  FreePairList: PUBLIC PROC [list: PairList] RETURNS [PairList] = {
    next: PairList;
    p: PairList ← list;
    UNTIL p = NIL DO 
      next ← p.next;
      [] ← FreeString[p.key]; [] ← FreeString[p.val];
      (Heap.systemZone).FREE[@p];  p ← next;
      ENDLOOP;
    RETURN [NIL]};


 -- file naming utilities

  SetExtension: PUBLIC PROC [root, defaultExt: String] RETURNS [name: String] = {
    SELECT TRUE FROM
      ~Dotted[root] => {
	name ← AdjustString[root, defaultExt.length + 2];
	Strings.AppendChar[name, '.];
	Strings.AppendString[name, defaultExt]};
      ENDCASE => name ← root;
    RETURN};

  Dotted: PROC [s: String] RETURNS [BOOL] = INLINE {
    FOR i: CARDINAL IN [0..s.length) DO IF s[i] = '. THEN RETURN [TRUE] ENDLOOP;
    RETURN [FALSE]};


 -- storage allocation

  AdjustString: PROC [old: String, extra: CARDINAL] RETURNS [new: String] = {
    IF old.length + extra <= old.maxlength THEN new ← old
    ELSE {
      new ← (Heap.systemZone).NEW[StringBody[old.length + extra]];
      Strings.AppendString[new, old];
      (Heap.systemZone).FREE[@old]};
    RETURN};

  }.