-- 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];
}.