-- file Macro.mesa
-- last edited by Satterthwaite,  9-Mar-82 14:18:58

DIRECTORY
  Ascii: TYPE USING [CR, SP, TAB],
  DCSFileTypes: TYPE USING [tLeaderPage],
  Directory: TYPE USING [CreateFile, Error, Lookup, UpdateDates, ignore],
  Exec: TYPE USING [AddCommand, commandLine, w],
  File: TYPE USING [Capability, Permissions, delete, grow, read, shrink, write],
  FileStream: TYPE USING [Create, EndOf],
  Heap: TYPE USING [systemMDSZone],
  Stream: TYPE USING [Handle, Delete, GetChar, PutChar],
  TTY: TYPE USING [Handle, GetLine, LineOverflow, PutChar, Rubout];

Macro: PROGRAM
    IMPORTS Directory, Exec, FileStream, Heap, Stream, TTY = {

 -- characters and strings
 
  MAXCHAR: NAT = LAST[CHARACTER] - 0c;
  MAXSTR: NAT = 100;
  
  character: TYPE = [-1..MAXCHAR];
  string: TYPE = --PACKED-- ARRAY [1..MAXSTR] OF character;
  
  ENDFILE:   character = -1;
  ENDSTR:    character =  0;
  BACKSPACE: character =  9;
  TAB:       character =  9;
  NEWLINE:   character = 13;
  BLANK:     character = 32;
  ESCAPE:    character = ord['@];
  
 -- file descriptors and io-related stuff
 
  MAXOPEN: NAT = 10;
  
  filedesc: TYPE = [0..MAXOPEN];
  
  IOERROR: filedesc = 0;
  STDIN:   filedesc = 1;
  STDOUT:  filedesc = 2;
  STDERR:  filedesc = 3;
  
  IOREAD:  INTEGER = 1;
  IOWRITE: INTEGER = 2;
  IOBOTH:  INTEGER = 3;  
  

-- standard (Pascal) procedures
   
  ord: PROC [c: CHARACTER] RETURNS [character] = INLINE {RETURN [c - 0c]};
  chr: PROC [c: character] RETURNS [CHARACTER] = INLINE {RETURN [c + 0c]};
  
  
-- storage primitives

  zone: MDSZone;	-- for NEW
  
-- file primitives
   
  tty: TTY.Handle;
 
  kbdline: STRING ← [MAXSTR];
  kbdx: CARDINAL;
  kbdend: BOOLEAN;
  
  
  readkbd: PROC = {
    IF (kbdend) THEN
      kbdline.length ← 0
    ELSE {
      ENABLE {
	TTY.LineOverflow => {
	  putcf[NEWLINE, STDERR];
	  message["*** line too long"L];
	  putcf[NEWLINE, STDERR];
	  RETRY};
	TTY.Rubout => {
	  IF (kbdline.length) > 0 THEN
	    RESUME
	  ELSE {
	    kbdend ← TRUE;
	    CONTINUE}}};
      IF (kbdline.length > 0)
       AND (kbdline[kbdline.length-1] = Ascii.CR) THEN
        kbdline.length ← kbdline.length - 1;	-- for ESC
      tty.GetLine[kbdline];
      IF kbdline.length >= kbdline.maxlength THEN
        [] ← ERROR TTY.LineOverflow[kbdline];
      kbdline[kbdline.length] ← Ascii.CR;
      kbdline.length ← kbdline.length + 1};
    kbdx ← 0};
	   
    
    
  filerec: TYPE = RECORD [
    type: {none, stream, tty} ← none,
    handle: Stream.Handle ← NIL];
    
  filetab: ARRAY filedesc OF filerec;
  
  accessmap: PROC [mode: INTEGER] RETURNS [File.Permissions] = {
    RETURN [SELECT mode FROM
      IOREAD => File.read,
      IOWRITE => File.write + File.grow + File.shrink + File.delete,
      IOBOTH => File.read + File.write + File.grow + File.shrink + File.delete,
      ENDCASE => File.read]};
    
  fdalloc: PROC RETURNS [fd: filedesc] = {
    -- find a free slot in filetab
    FOR fd IN (STDERR .. MAXOPEN] DO
      IF (filetab[fd].type = none) THEN EXIT
      REPEAT
        FINISHED => fd ← IOERROR; 
      ENDLOOP;
    RETURN};
  
  fileerror: ERROR = CODE;
  
  findfile: PROC [name: STRING, access: File.Permissions]
      RETURNS [File.Capability] = {
    cap: File.Capability;
    old: BOOLEAN ← (access = File.read);
    IF ~old THEN { 
      cap ← Directory.CreateFile[name, DCSFileTypes.tLeaderPage, 0
	      ! Directory.Error => {
		 IF type = fileAlreadyExists THEN GOTO fileExists 
		 ELSE GO TO fileProblem}];
      EXITS
        fileExists => old ← TRUE};
    IF old THEN
       cap ← Directory.Lookup[fileName: name, permissions: Directory.ignore
	! Directory.Error => {GO TO fileProblem}];
    RETURN [Directory.UpdateDates[cap, access]]
    EXITS
      fileProblem => ERROR fileerror};
  
  makestream: PROC [name: POINTER TO string, mode: INTEGER, fd: filedesc] = {
    i: INTEGER ← 0;
    intname: STRING ← [MAXSTR];
    WHILE (name[i+1] # ENDSTR) DO
      intname[i] ← chr[name[i+1]];
      i ← i + 1;
      ENDLOOP;
    intname.length ← i;
    filetab[fd] ← [
	type: stream,
	handle: FileStream.Create[findfile[intname, accessmap[mode]]]]};
  

  open: PROC [name: POINTER TO string, mode: INTEGER] RETURNS [fd: filedesc] = {
    fd ← fdalloc[];
    IF fd # IOERROR THEN {
      makestream[name, mode, fd ! fileerror => {GOTO fail}];
      EXITS fail => {filetab[fd] ← [type: none]; fd ← IOERROR}};
    RETURN};

  create: PROC [name: POINTER TO string, mode: INTEGER] RETURNS [filedesc] = open;
    -- check positioning of files opened for output
    
  close: PROC [fd: filedesc] = {
    SELECT filetab[fd].type FROM
      stream => {
        h: Stream.Handle = filetab[fd].handle;
	Stream.Delete[h]};
      tty => NULL;
      ENDCASE => NULL;
    filetab[fd] ← [type: none]};

  remove: PROC [s: POINTER TO string] = {
    -- this version just prints a message
    message["If we had remove, we would be deleting "L];
    putcf[TAB, STDERR];
    putstr[s, STDERR];
    putcf[NEWLINE, STDERR]};

  getc: PROC [c: POINTER TO character] RETURNS [character] = INLINE {
    RETURN [getcf[c, STDIN]]};

  getcf: PROC [c: POINTER TO character, fd: filedesc] RETURNS [character] = {
    SELECT filetab[fd].type FROM
      stream => {
        h: Stream.Handle = filetab[fd].handle;
	c↑ ← IF FileStream.EndOf[h] THEN ENDFILE ELSE ord[h.GetChar[]]};
      tty =>
        IF (kbdx < kbdline.length) THEN {
	  c↑ ← ord[kbdline[kbdx]];
	  kbdx ← kbdx + 1}
	ELSE {
	  readkbd[];
	  IF (kbdline.length = 0) THEN	-- iff kbdend
	    c↑ ← ENDFILE
	  ELSE {
	    c↑ ← ord[kbdline[kbdx]];
	    kbdx ← kbdx + 1}};
      ENDCASE => error["bad file descriptor"L];
    RETURN [c↑]};

  getline: PROC [s: POINTER TO string, fd: filedesc, maxsize: INTEGER ← MAXSTR]
      RETURNS [BOOLEAN] = {
    i: INTEGER ← 1;
    c: character;
    DO
      s[i] ← getcf[@c, fd];
      i ← i + 1;
      IF (c = ENDFILE) OR (c = NEWLINE) OR (i >= maxsize) THEN EXIT
      ENDLOOP;
    IF (c = ENDFILE) THEN	-- went one too far
      i ← i - 1;
    s[i] ← ENDSTR;
    RETURN [c # ENDFILE]};


  putc: PROC [c: character] = INLINE {putcf[c, STDOUT]};

  putcf: PROC [c: character, fd: filedesc] = {
    SELECT filetab[fd].type FROM
      stream => {
        h: Stream.Handle = filetab[fd].handle;
	h.PutChar[chr[c]]};
      tty => tty.PutChar[chr[c]];
      ENDCASE => error["bad file descriptor"L]};

  putstr: PROC [s: POINTER TO string, fd: filedesc] = {
    i: INTEGER ← 1;
    WHILE (s[i] # ENDSTR) DO
      putcf[s[i], fd];
      i ← i + 1
      ENDLOOP};
      

-- string utilities

  setstring: PROC [s: POINTER TO string, text: STRING] = {
    FOR i: CARDINAL IN [1 .. text.length] DO
      s[i] ← ord[text[i-1]] ENDLOOP;
    s[text.length+1] ← ENDSTR};
  
  
-- errors and messages

  message: PROC [msg: STRING] = {
    FOR i: CARDINAL IN [0 .. msg.length) DO
      putcf[ord[msg[i]], STDERR] ENDLOOP};

  exit: ERROR = CODE;
  
  error: PROC [msg: STRING] = {
    message[msg];  ERROR exit};
   

-- command arg primitives

  MAXCMD: NAT = 20;

  cmdargs: [0 .. MAXCMD];
  cmd: ARRAY [1 .. MAXCMD] OF RECORD [idx, len: CARDINAL];
  
  getarg: PROC [n: INTEGER, str: POINTER TO string, maxsize: INTEGER]
      RETURNS [BOOLEAN] = {
    b: BOOLEAN ← ((n > 0) AND (n <= cmdargs));
    IF (b) THEN
      commandstr[cmd[n].idx, cmd[n].len, str, 1, maxsize];
    RETURN [b]};

  nargs: PROC RETURNS [INTEGER] = {RETURN [cmdargs]};
  
  commandstr: PROC [i, n: INTEGER,
      dest: POINTER TO string, j: INTEGER, maxsize: INTEGER] = {
    WHILE (n > 0) AND (j < maxsize) DO
      dest[j] ← ord[Exec.commandLine.s[i]];
      i ← i + 1;
      j ← j + 1;
      n ← n - 1;
      ENDLOOP;
    dest[j] ← ENDSTR}; 
  

-- utilities

 -- equal: test two strings for equality
  equal: PROC [str1, str2: POINTER TO string] RETURNS [BOOLEAN] = {
    i: INTEGER ← 1;
    WHILE (str1[i] = str2[i]) AND (str1[i] # ENDSTR) DO
      i ← i + 1 ENDLOOP;
    RETURN [str1[i] = str2[i]]};
    
 -- isalphanum: true if c is a letter or digit
  isalphanum: PROC [c: character] RETURNS [BOOLEAN] = {
    RETURN [
      (c IN [ord['a] .. ord['z]])
       OR (c IN [ord['A] .. ord['Z]])
       OR (c IN [ord['0] .. ord['9]])]};

 -- isletter: true if c is a letter of either case
  isletter: PROC [c: character] RETURNS [BOOLEAN] = INLINE {
    RETURN [
      (c IN [ord['a] .. ord['z]])
       OR (c IN [ord['A] .. ord['Z]])]};

 -- isdigit: true if c is a digit
  isdigit: PROC [c: character] RETURNS [BOOLEAN] = INLINE {
    RETURN [c IN [ord['0] .. ord['9]]]};

 -- itoc: convert integer n to char string in s[i] ...
  itoc: PROC [n: INTEGER, s: POINTER TO string, i: INTEGER] RETURNS [next: INTEGER] = {
    IF (n < 0) THEN {
      s[i] ← ord['-];
      next ← itoc[-n, s, i+1]}
    ELSE {
      IF (n >= 10) THEN
	i ← itoc[n/10, s, i];
      s[i] ← n MOD 10 + ord['0];
      s[i+1] ← ENDSTR;
      next ← i+1};
    RETURN};
   
 -- length: compute length of string
  length: PROC [s: POINTER TO string] RETURNS [INTEGER] = {
    n: INTEGER ← 1;
    WHILE (s[n] # ENDSTR) DO
      n ← n + 1 ENDLOOP;
    RETURN [n - 1]};
    
 -- ctoi: convert string at s[i] to integer, increment i
  ctoi: PROC [s: POINTER TO string, i: POINTER TO INTEGER] RETURNS [INTEGER] = {
    n, sign: INTEGER;
    WHILE (s[i↑] = BLANK) OR (s[i↑] = TAB) DO
      i↑ ← i↑ + 1 ENDLOOP;
    sign ← IF s[i↑] = ord['-] THEN -1 ELSE 1;
    IF (s[i↑] = ord['+]) OR (s[i↑] = ord['-]) THEN
      i↑ ← i↑ + 1;
    n ← 0;
    WHILE (isdigit[s[i↑]]) DO
      n ← 10*n + (s[i↑] - ord['0]);
      i↑ ← i↑ + 1;
      ENDLOOP;
    RETURN [sign * n]};
     

-- macro: expand macros with argmuments
  macro: PROC = {
    LPAREN: character = ord['(];
    COMMA: character = ord[',];
    RPAREN: character = ord[')];

    sttype: TYPE = {DEFTYPE, MACTYPE, IFTYPE, SUBTYPE,
      EXPRTYPE, LENTYPE, CHQTYPE};	-- symbol table types
    
    BUFSIZE: NAT = 1000;		-- size of pushback buffer
    MAXCHARS: NAT = 3000;		-- size of name-defn table
    MAXPOS: NAT = 500;			-- size of position arrays
    CALLSIZE: NAT = MAXPOS;
    ARGSIZE: NAT = CALLSIZE;
    EVALSIZE: NAT = MAXCHARS;
    MAXDEF: NAT = MAXSTR;		-- max chars in a defn
    MAXTOK: NAT = MAXSTR;		-- max chars in a token
    HASHSIZE: NAT = 53;			-- size of hash table
    ARGFLAG: character = ord['$];	-- macro invocation character

    buf: ARRAY [1..BUFSIZE] OF character;	-- for pushback
    bp: [0..BUFSIZE] ← 0;		-- next available character
    
    putback: PROC [c: character] = {
      -- putback: push character back onto input
      IF (bp >= BUFSIZE) THEN
        error["macro: too many characters pushed back"L];
      bp ← bp + 1;
      buf[bp] ← c};
      
    getpbc: PROC [c: POINTER TO character] RETURNS [character] = {
      -- getpbc: get a (possibly pushed back) character
      IF (bp > 0) THEN
        c↑ ← buf[bp]
      ELSE {
        bp ← 1;
	buf[bp] ← getc[c]};
      IF (c↑ # ENDFILE) THEN
        bp ← bp - 1;
      RETURN [c↑]};
      
    pbstr: PROC [s: POINTER TO string] = {
      -- pbstr: push string back onto input
      FOR i: INTEGER DECREASING IN [1 .. length[s]] DO
        putback[s[i]] ENDLOOP};

    gettok: PROC [token: POINTER TO string, toksize: INTEGER]
        RETURNS [character] = {
      -- gettok: get token for define
      i: INTEGER ← 1;
      done: BOOLEAN ← FALSE;
      WHILE (~done) AND (i < toksize) DO
        IF (isalphanum[getpbc[@token[i]]]) THEN
	  i ← i + 1
	ELSE
	  done ← TRUE
	ENDLOOP;
      IF (i >= toksize) THEN
	error["macro: token too long"L];
      IF (i > 1) THEN {	-- some alpha was seen
	putback[token[i]];
	i ← i - 1};
      -- else single non-alphanumeric
      token[i+1] ← ENDSTR;
      RETURN [token[1]]};
	
    charpos: TYPE = [1..MAXCHARS];
    charbuf: TYPE = ARRAY charpos OF character;

    cscopy: PROC [cb: POINTER TO charbuf, i: charpos,
        s: POINTER TO string] = {
      -- sccopy: copy cb[i] to string s
      j: INTEGER ← 1;
      WHILE (cb[i] # ENDSTR) DO
	s[j] ← cb[i];
	i ← i + 1;
	j ← j + 1
	ENDLOOP;
      s[j] ← ENDSTR};
	
    sccopy: PROC [s: POINTER TO string, cb: POINTER TO charbuf,
        i: charpos] = {
      -- sccopy: copy string s to cb[i]
      j: INTEGER ← 1;
      WHILE (s[j] # ENDSTR) DO
	cb[i] ← s[j];
	j ← j + 1;
	i ← i + 1
	ENDLOOP;
      cb[i] ← ENDSTR};
    
    ndptr: TYPE = POINTER TO ndblock;	-- pointer to name-defn block
    ndblock: TYPE = RECORD [	-- name-defn block
      name: charpos,
      defn: charpos,
      kind: sttype,
      nextptr: ndptr];

    hashtab: ARRAY [1..HASHSIZE] OF ndptr ← ALL[NIL];
    ndtable: POINTER TO charbuf;
    nexttab: charpos ← 1;	-- first free position in ndtable

    inithash: PROC = INLINE {
      -- initialize hash table
      ndtable ← zone.NEW[charbuf]};
      
    resethash: PROC = INLINE {
      -- reset hash table
      FOR i: INTEGER IN [1 .. HASHSIZE] DO
        p: ndptr ← hashtab[i];
	WHILE p # NIL DO
	  q: ndptr = p.nextptr;
	  zone.FREE[@p];
	  p ← q;
	  ENDLOOP;
	ENDLOOP;
      zone.FREE[@ndtable]};

    hash: PROC [name: POINTER TO string] RETURNS [INTEGER] = {
      h: INTEGER ← 0;
      FOR i: INTEGER IN [1 .. length[name]] DO
        h ← (3*h + name[i]) MOD HASHSIZE ENDLOOP;
      RETURN [h+1]};
    
    hashfind: PROC [name: POINTER TO string] RETURNS [p: ndptr] = {
      -- hashfind: find name in hash table
      found: BOOLEAN ← FALSE;
      p ← hashtab[hash[name]];
      WHILE (~found) AND (p # NIL) DO
        tempname: string;
	cscopy[ndtable, p↑.name, @tempname];
	IF (equal[name, @tempname]) THEN
	  found ← TRUE
	ELSE p ← p↑.nextptr
	ENDLOOP;
      RETURN};
      
    install: PROC [name, defn: POINTER TO string, t: sttype] = {
      -- install: add name, definition and type to table
      nlen: INTEGER = length[name] + 1;		-- 1 for ENDSTR
      dlen: INTEGER = length[defn] + 1;
      IF (nexttab + nlen + dlen > MAXCHARS) THEN {
        putstr[name, STDERR];
	error[": too many definitions"L]}
      ELSE {	-- put it at front of chain
        h: INTEGER = hash[name];
	p: ndptr = zone.NEW[ndblock];
	p↑.nextptr ← hashtab[h];
	hashtab[h] ← p;
	p↑.name ← nexttab;
	sccopy[name, ndtable, nexttab];
	nexttab ← nexttab + nlen;
	p↑.defn ← nexttab;
	sccopy[defn, ndtable, nexttab];
	nexttab ← nexttab + dlen;
	p↑.kind ← t}};
    
    lookup: PROC [name, defn: POINTER TO string, t: POINTER TO sttype]
        RETURNS [found: BOOLEAN] = {
      -- lookup: locate name, get defn and type from table
      p: ndptr = hashfind[name];
      IF (p = NIL) THEN
        found ← FALSE
      ELSE {
        found ← TRUE;
	cscopy[ndtable, p↑.defn, defn];
	t↑ ← p↑.kind};
      RETURN};
    
    posbuf: TYPE = ARRAY [1..MAXPOS] OF charpos;
    pos: TYPE = [0..MAXPOS];
    
    callstk: POINTER TO posbuf;		-- call stack
    cp: pos ← 0;			-- current call stack position
    typestk: ARRAY [1..CALLSIZE] OF sttype;	-- type
    plev: ARRAY [1..CALLSIZE] OF INTEGER;	-- paren level
    argstk: POINTER TO posbuf;		-- argument stack for this call
    ap: pos ← 1;			-- current argument position

    push: PROC [ep: INTEGER, argstk: POINTER TO posbuf,
        ap: INTEGER] RETURNS [INTEGER] = {
      -- push: push ep onto argstk, return new position ap
      IF (ap > ARGSIZE) THEN
	error["macro: argument stack overflow"L];
      argstk[ap] ← ep;
      RETURN [ap + 1]};
    
    evalstk: POINTER TO charbuf;	-- evaluation stack
    ep: charpos ← 1;			-- first character unused in evalstk
    
    puttok: PROC [s: POINTER TO string] = {
      -- put token on output or evaluation stack
      i: INTEGER ← 1;
      WHILE (s[i] # ENDSTR) DO
        putchr[s[i]];
	i ← i + 1
	ENDLOOP};
	
    putchr: PROC [c: character] = {
      -- putchr: put single char on output or evaluation stack
      IF (cp <= 0) THEN
        putc[c]
      ELSE {
        IF (ep > EVALSIZE) THEN
	  error["macro: evaluation stack overflow"L];
	evalstk[ep] ← c;
	ep ← ep + 1}};
	
    eval: PROC [argstk: POINTER TO posbuf, td: sttype,
        i, j: INTEGER] = {
      -- eval: expand args i..j; do built-in or push back defn
      t: INTEGER = argstk[i];
      IF (td = DEFTYPE) THEN
        dodef[argstk, i, j]
      ELSE IF (td = EXPRTYPE) THEN
        doexpr[argstk, i, j]
      ELSE IF (td = SUBTYPE) THEN
        dosub[argstk, i, j]
      ELSE IF (td = IFTYPE) THEN
        doif[argstk, i, j]
      ELSE IF (td = LENTYPE) THEN
        dolen[argstk, i, j]
      ELSE IF (td = CHQTYPE) THEN
        dochq[argstk, i, j]
      ELSE {	-- process normal macro
        k: INTEGER ← t;
	WHILE (evalstk[k] # ENDSTR) DO
	  k ← k + 1 ENDLOOP;
	k ← k - 1;	-- last character of defn
	WHILE (k > t) DO
	  IF (evalstk[k-1] # ARGFLAG) THEN
	    putback[evalstk[k]]
	  ELSE {
	    argno: INTEGER = evalstk[k] - ord['0];
	    IF (argno >= 0) AND (argno < j-i) THEN {
	      temp: string;
	      cscopy[evalstk, argstk[i+argno+1], @temp];
	      pbstr[@temp]};
	    k ← k - 1};	-- skip over $
	  k ← k - 1
	  ENDLOOP;
	IF (k = t) THEN	-- do last character
	  putback[evalstk[k]]}};
	 
    dodef: PROC [argstk: POINTER TO posbuf, i, j: INTEGER] = {
      -- dodef: install definition in table
      IF (j - i > 2) THEN {
        temp1, temp2: string;
	cscopy[evalstk, argstk[i+2], @temp1];
	cscopy[evalstk, argstk[i+3], @temp2];
	install[@temp1, @temp2, MACTYPE]}};
	
    doif: PROC [argstk: POINTER TO posbuf, i, j: INTEGER] = {
      -- doif: select one of two arguments
      IF (j - i >= 4) THEN {
        temp1, temp2, temp3: string;
	cscopy[evalstk, argstk[i+2], @temp1];
	cscopy[evalstk, argstk[i+3], @temp2];
	IF (equal[@temp1, @temp2]) THEN
	  cscopy[evalstk, argstk[i+4], @temp3]
	ELSE IF (j - i >= 5) THEN
	  cscopy[evalstk, argstk[i+5], @temp3]
	ELSE
	  temp3[1] ← ENDSTR;
	pbstr[@temp3]}};
	
    doexpr: PROC [argstk: POINTER TO posbuf, i, j: INTEGER] = {
      -- doexpr: evaluate arithmetic expressions
      temp: string;
      junk: INTEGER ← 1;
      cscopy[evalstk, argstk[i+2], @temp];
      pbnum[expr[@temp, @junk]]};

    pbnum: PROC [n: INTEGER] = {
      -- pbnum: convert number to string, push back on input
      temp: string;
      [] ← itoc[n, @temp, 1];
      pbstr[@temp]};

    expr: PROC [s: POINTER TO string, i: POINTER TO INTEGER]
        RETURNS [INTEGER] = {
      -- expr: recursive expression evaluation
      PLUS: character = ord['+];
      MINUS: character = ord['-];
      STAR: character = ord['*];
      SLASH: character = ord['/];
      PERCENT: character = ord['%];
      LPAREN: character = ord['(];
      RPAREN: character = ord[')];

      gnbchar: PROC [s: POINTER TO string, i: POINTER TO INTEGER]
	  RETURNS [character] = {
	-- gnbchar: get next non-blank character
	WHILE (s[i↑] = BLANK) OR (s[i↑] = TAB) OR (s[i↑] = NEWLINE) DO
	  i↑ ← i↑ + 1  ENDLOOP;
	RETURN [s[i↑]]};

      term: PROC [s: POINTER TO string, i: POINTER TO INTEGER]
	  RETURNS [INTEGER] = {
	-- term: evaluate term of arithmetic expression
	
	factor: PROC [s: POINTER TO string, i: POINTER TO INTEGER]
	    RETURNS [v: INTEGER] = {
	  -- factor: evaluate factor of arithmetic expression
	  IF (gnbchar[s, i] = LPAREN) THEN {
	    i↑ ← i↑ + 1;
	    v ← expr[s, i];
	    IF (gnbchar[s, i] = RPAREN) THEN
	      i↑ ← i↑ + 1
	    ELSE
	      message["macro: missing paren in expr"L]}
	  ELSE
	    v ← ctoi[s, i];
	  RETURN};

	v: INTEGER ← factor[s, i];
	t: character ← gnbchar[s, i];
	WHILE (t = STAR) OR (t = SLASH) OR (t = PERCENT) DO
	  i↑ ← i↑ + 1;
	  SELECT t FROM
	    STAR =>
	      v ← v * factor[s, i];
	    SLASH =>
	      v ← v / factor[s, i];
	    PERCENT =>
	      v ← v MOD factor[s, i];
	    ENDCASE;
	  t ← gnbchar[s, i]
	  ENDLOOP;
	RETURN [v]};

      v: INTEGER ← term[s, i];
      t: character ← gnbchar[s, i];
      WHILE (t = PLUS) OR (t = MINUS) DO
	i↑ ← i↑ + 1;
	IF (t = PLUS) THEN
	  v ← v + term[s, i]
	ELSE
	  v ← v - term[s, i];
	t ← gnbchar[s, i]
	ENDLOOP;
      RETURN [v]};

    dolen: PROC [argstk: POINTER TO posbuf, i, j: INTEGER] = {
      -- dolen: return length of argument
      IF (j - i > 1) THEN {
        temp: string;
	cscopy[evalstk, argstk[i+2], @temp];
	pbnum[length[@temp]]}
      ELSE
	pbnum[0]};

    dosub: PROC [argstk: POINTER TO posbuf, i, j: INTEGER] = {
      -- dosub: select substring
      IF (j - i >= 3) THEN {
        ap, fc, k, nc: INTEGER;
	temp1, temp2: string;
	IF (j - i < 4) THEN
	  nc ← MAXTOK
	ELSE {
	  cscopy[evalstk, argstk[i+4], @temp1];
	  k ← 1;
	  nc ← expr[@temp1, @k]};
	cscopy[evalstk, argstk[i+3], @temp1];	-- origin
	ap ← argstk[i+2];	-- target string
	k ← 1;
	fc ← ap + expr[@temp1, @k] - 1;	-- first char
	cscopy[evalstk, ap, @temp2];
	IF (fc >= ap) AND (fc < ap + length[@temp2]) THEN {
	  cscopy[evalstk, fc, @temp1];
	  FOR k: INTEGER DECREASING IN [fc .. fc + MIN[nc, length[@temp1]]) DO
	    putback[evalstk[k]] ENDLOOP}}};
	
    dochq: PROC [argstk: POINTER TO posbuf, i, j: INTEGER] = {
      -- dochq: change quote characters
      temp: string;
      n: INTEGER;
      cscopy[evalstk, argstk[i+2], @temp];
      n ← length[@temp];
      IF (n <= 0) THEN {
        lquote ← ord['<];
	rquote ← ord['>]}
      ELSE IF (n = 1) THEN
        lquote ← rquote ← temp[1]
      ELSE {
        lquote ← temp[1];
	rquote ← temp[2]}};
	
    null: string;		-- value is ''
    defname: string;		-- value is 'define'
    exprname: string;		-- value is 'expr'
    subname: string;		-- value is 'substr'
    ifname: string;		-- value is 'ifelse'
    lenname: string;		-- value is 'len'
    chqname: string;		-- value is 'changeq'

    lquote: character;		-- left quote character
    rquote: character;		-- right quote character
    
    defn: string;
    token: string;
    toktype: sttype;
    t: character;
    nlpar: INTEGER;

    initmacro: PROC = {
      -- initmacro: initialize variables for macro
      evalstk ← zone.NEW[charbuf];
      argstk ← zone.NEW[posbuf];
      callstk ← zone.NEW[posbuf];
      null[1] ← ENDSTR;
      setstring[@defname, "define"L];
      setstring[@subname, "substr"L];
      setstring[@exprname, "expr"L];
      setstring[@ifname, "ifelse"L];
      setstring[@lenname, "len"L];
      setstring[@chqname, "changeq"L];
      inithash[];
      lquote ← ord['<];
      rquote ← ord['>]};
      
    resetmacro: PROC = INLINE {
      -- resetmacro: reset macro storage
      resethash[];
      zone.FREE[@callstk];
      zone.FREE[@argstk];
      zone.FREE[@evalstk]};
      
    initmacro[];
    install[@defname, @null, DEFTYPE];
    install[@exprname, @null, EXPRTYPE];
    install[@subname, @null, SUBTYPE];
    install[@ifname, @null, IFTYPE];
    install[@lenname, @null, LENTYPE];
    install[@chqname, @null, CHQTYPE];
    
    WHILE (gettok[@token, MAXTOK] # ENDFILE) DO
      IF (isletter[token[1]]) THEN {
        IF (~lookup[@token, @defn, @toktype]) THEN
	  puttok[@token]
	ELSE {	-- defined; put it in eval stack
	  cp ← cp + 1;
	  IF (cp > CALLSIZE) THEN
	    error["macro: call stack overflow"L];
	  callstk[cp] ← ap;
	  typestk[cp] ← toktype;
	  ap ← push[ep, argstk, ap];
	  puttok[@defn];	-- push definition
	  putchr[ENDSTR];
	  ap ← push[ep, argstk, ap];
	  puttok[@token];	-- stack name
	  putchr[ENDSTR];
	  ap ← push[ep, argstk, ap];
	  t ← gettok[@token, MAXTOK];	-- peek at next
	  pbstr[@token];
	  IF (t # LPAREN) THEN {	-- add ()
	    putback[RPAREN];
	    putback[LPAREN]};
	  plev[cp] ← 0}}
      ELSE IF (token[1] = lquote) THEN {	-- strip quotes
	nlpar ← 1;
	DO
	  t ← gettok[@token, MAXTOK];
	  IF (t = rquote) THEN
	    nlpar ← nlpar - 1
	  ELSE IF (t = lquote) THEN
	    nlpar ← nlpar + 1
	  ELSE IF (t = ENDFILE) THEN
	    error["macro: missing right quote"L];
	  IF (nlpar > 0) THEN
	    puttok[@token];
	  IF (nlpar = 0) THEN EXIT
	  ENDLOOP}
      ELSE IF (cp = 0) THEN			-- not in macro at all
	puttok[@token]
      ELSE IF (token[1] = LPAREN) THEN {
	IF (plev[cp] > 0) THEN
	  puttok[@token];
	plev[cp] ← plev[cp] + 1}
      ELSE IF (token[1] = RPAREN) THEN {
	plev [cp] ← plev[cp] - 1;
	IF (plev[cp] > 0) THEN
	  puttok[@token]
	ELSE {	-- end of argument list
	  putchr[ENDSTR];
	  eval[argstk, typestk[cp], callstk[cp], ap-1];
	  ap ← callstk[cp];	-- pop eval stack
	  ep ← argstk[ap];
	  cp ← cp - 1}}
      ELSE IF (token[1] = COMMA) AND (plev[cp]=1) THEN {
	putchr[ENDSTR];	-- new argument
	ap ← push[ep, argstk, ap]}
      ELSE
	puttok[@token];	-- just stack it
      ENDLOOP;
    IF (cp # 0) THEN
      error["macro: unexpected end of input"L];
    resetmacro[]};
    

 -- here begins the shell
 
  initcmd: PROC = {
    idx: CARDINAL ← Exec.commandLine.i;
    limx: CARDINAL =
      IF Exec.commandLine.s = NIL THEN 0 ELSE Exec.commandLine.s.length-1;
    zone ← Heap.systemMDSZone;
    tty ← Exec.w;
    kbdx ← kbdline.length ← 0;
    kbdend ← FALSE;
    filetab[STDIN].type ← filetab[STDOUT].type ← filetab[STDERR].type ← tty;
    cmdargs ← 0;
    WHILE (idx < limx) DO
      WHILE (Exec.commandLine.s[idx] = Ascii.SP) AND (idx < limx) DO
        idx ← idx + 1 ENDLOOP;
      IF (idx < limx) THEN {
        startidx: CARDINAL = idx;
	WHILE (idx < limx)
	 AND (Exec.commandLine.s[idx] # Ascii.SP)
	 AND (Exec.commandLine.s[idx] # Ascii.TAB) DO
	  idx ← idx + 1 ENDLOOP;
	SELECT Exec.commandLine.s[startidx] FROM
	  '< => {
	    name: string;
	    commandstr[startidx+1, idx - (startidx+1), @name, 1, MAXSTR];
	    close[STDIN];
	    makestream[@name, IOREAD, STDIN]};
	  '> => {
	    name: string;
	    commandstr[startidx+1, idx - (startidx+1), @name, 1, MAXSTR];
	    close[STDOUT];
	    makestream[@name, IOWRITE, STDOUT]};
	  ENDCASE => {
	    cmdargs ← cmdargs + 1;
	    IF cmdargs > MAXCMD THEN {
	      putcf[NEWLINE, STDERR];
	      message["*** too many arguments"L];
	      putcf[NEWLINE, STDERR]}
	    ELSE
	      cmd[cmdargs] ← [idx: startidx, len: idx - startidx]}};
      ENDLOOP;
    putcf[NEWLINE, STDERR]};
      
   endcmd: PROC = {
     FOR fd: filedesc IN (IOERROR .. MAXOPEN] DO
       close[fd] ENDLOOP;
     IF Exec.commandLine.s # NIL THEN
       Exec.commandLine.i ← Exec.commandLine.s.length};
       
  main: PROC = {
    initcmd[];
    macro[ ! exit => {CONTINUE}];
    endcmd[]};
  
  Exec.AddCommand["Macro.~"L, main];
  
  }.