! ! Preprocessor 17/4/79 PMM fixed 13/6 RWT ! ! re-ported to VAX 15/07/80 (LDS) ! %BEGIN %systemstring (255) %fnspec itos(%integer v,p) %systemstring (8) %fnspec date !%include "inc:util.imp" %EXTERNALINTEGERFNSPEC DEF STREAMS(%STRING(127) STREAMS, DEFAULTS) %INTEGER RETURN CODE %OWNSTRING(15) DEFAULTS=".PRE/%I1.SRC" !*********************************************** !* General Purpose PreProcessor * !*********************************************** ! Logical values %CONSTINTEGER FALSE = 0 %CONSTINTEGER TRUE = -1 ! The lexical analyser recognises reserved words ! and these may not be used as tags %CONSTINTEGER RESWORD=128 %CONSTINTEGER NUMTYPE=129 %CONSTINTEGER TAGTYPE=130 %CONSTINTEGER BOOLTYPE=131 %CONSTINTEGER STRINGTYPE=132 %CONSTINTEGER BUILTINFN=133 %CONSTINTEGER LENFN=1 %CONSTINTEGER UNDEF=2 %CONSTINTEGER MACTYPE=134 %CONSTINTEGER ESCAT=135 %CONSTINTEGER IF=-1 %CONSTINTEGER NOT=-2 %CONSTINTEGER OR=-3 %CONSTINTEGER AND=-4 %CONSTINTEGER THEN=-5 %CONSTINTEGER ELSE=-6 %CONSTINTEGER ELIF=-7 %CONSTINTEGER DEFINE=-8 %CONSTINTEGER REDEF=-9 %CONSTINTEGER FINISH=-10 %CONSTINTEGER CYCLE=-11 %CONSTINTEGER REPEAT=-12 %CONSTINTEGER END=-14 %CONSTINTEGER BEGIN=-15 %CONSTINTEGER INCLUDE=-16 %CONSTINTEGER WHILE=-17 %CONSTINTEGER ERR=-18 %CONSTINTEGER MACRO=-19 %CONSTINTEGER FOR=-20 %CONSTINTEGER TO=-21 %CONSTINTEGER STEP=-22 %CONSTINTEGER UNLESS=-23 %CONSTINTEGER UNTIL=-24 %CONSTINTEGER ELUNLESS=-25 %CONSTINTEGER MEND=-26 %CONSTINTEGER EVAL=-27 %CONSTINTEGER STOPPER=-100 ! The stored text is all saved in the text buffer ! BUFFER and is inserted and retrieved by STORE ! and RCH. %CONSTINTEGER BUFFERSIZE=8000 %BYTEINTEGERARRAY BUFFER(257:BUFFERSIZE) %OWNINTEGER BP=256 ! Any temporary text such as string constatns and ! macro arguments are stored in another text buffer ! TEMP, accessed by TSTORE and PERMANENT. %CONSTINTEGER TEMPSIZE=3000 %BYTEINTEGERARRAY TEMP(-TEMPSIZE:-2) %OWNINTEGER TBP=-TEMPSIZE-1 ! The current lexical level and macro evaluation number %OWNINTEGER LEVEL=0 %OWNINTEGER MACNO=0 %INTEGERNAME HMACNO ! Flag to inhibit expansion of meta-tags when reading ! the text of the tags themselves %OWNINTEGER EXPAND=0 ! To get out of cycles we have WFLAG which is set non-32767 ! when a `whi statemanet fails %OWNINTEGER WFLAG=32767 ! To prevent too many error messages we have ! a flag indicating that we have output one %INTEGER ERRMESS ! The escape character is normally '@' though ! it can be changed. This is the only character ! which is afforded any special significance by ! the preporocessor %OWNINTEGER ESCAPE = '@' ! The current input state and evaluation state are ! stored on a stack STACK accessed by TOS, PUSH and ! POP. The items on the stack are indicators of where ! to get the next input character from as follows. Zero: ! the next input character is to come from the real source ! file. 1-255: the stacked value is the next input ! character. >255: the stacked item is a pointer into ! the text buffer whence the next character is to be found. %CONSTINTEGER STACKSIZE=50 %INTEGERARRAY STACK(1:STACKSIZE) %INTEGERARRAY LSTACK(0:STACKSIZE) %OWNINTEGER SP=0 %OWNINTEGER LSP=0 %INTEGERNAME LINENO %OWNINTEGER INCLUDEFLAG=1 %OWNINTEGER TOS=0 ! Directves to RCH %CONSTINTEGER EOF=254 %CONSTINTEGER EOS=255 %CONSTINTEGER ENDINC=253 ! The current output state is held in OFLAG. ! If OFLAG is zero then the output is going ! to the real output destination file. If ! output is non-zero then it is being discarded. %OWNINTEGER OFLAG=0 ! The number of errors so far; when we get lost we ! get really lost so give up fast. %OWNINTEGER NERRS=0 %CONSTINTEGER MAXERRS=10 ! The current input character is held in CH while ! reading tokens %INTEGER CH ! When we are parsing stuff we know about, the current ! input token is held in TOKEN with any associated ! subvalues held in SUB and WORK. %INTEGER TOKEN, SUB, WORK %ROUTINESPEC IGNOREWORD %ROUTINESPEC PROCESS %ROUTINESPEC PWORD(%INTEGER W) %INTEGERFNSPEC TYPEOF(%INTEGER W,%INTEGERNAME V) %PREDICATESPEC LETTER(%INTEGERNAME T) %PREDICATESPEC DIGIT(%INTEGERNAME T) %ROUTINESPEC CLEAR(%INTEGER L) %ROUTINESPEC GENNUM(%INTEGER N) %ROUTINESPEC RWORD(%INTEGERNAME N, %INTEGER NN) %ROUTINESPEC RTOKEN %ROUTINESPEC RCH %ROUTINESPEC ERROR(%STRING(30) S, %INTEGER T) %ROUTINE MONITOR(%INTEGER CH) ! Debugging routine - outputs interleaved ! source and destination files to output ! stream 2 %OWNBYTEINTEGERARRAY B(1:60) %OWNINTEGER BP=0 %INTEGER I,F %IF BP>=60 %OR CH=0 %START %IF BP>0 %START SELECTOUTPUT(2) F = 0 %FOR I = 1,1,BP %CYCLE PRINTSYMBOL(B(I)&127) F = 1 %IF B(I)>=128 %REPEAT %IF F#0 %START PRINTSYMBOL(13) %FOR I = 1,1,BP %CYCLE %IF B(I)>=128 %THEN PRINTSYMBOL('_') %C %ELSE SPACE %REPEAT %FINISH NEWLINE SELECTOUTPUT(1) %FINISH BP = 0 %RETURN %IF CH=0 %FINISH I = CH&127; F = CH&128 CH = '^'+F %IF I=NL BP = BP+1; B(BP) = CH %END %ROUTINE PUSHLINE(%INTEGER L) ! Push a line number onto the line no stack LSP = LSP+1;! %IF LSPSTACKSIZE %START ERROR("Nesting too deep",-1) %STOP %FINISH STACK(SP) = TOS TOS = Q LINENO = LINENO-1 %IF Q=NL %END %ROUTINE BACKSPACE ! Push the current pending CH onto the stack PUSH(CH) %END %ROUTINE POP ! Pop the last value off the input state stack TOS = STACK(SP) SP = SP-1 %END %INTEGERFN SOURCE ! Return zero if the current source is the ! real input stream, else non-zero ! pointer to the source buffer %INTEGER I STACK(SP+1) = TOS %FOR I = SP+1,-1,1 %CYCLE %IF STACK(I)>255 %OR STACK(I)<=0 %THEN %RESULT = STACK(I) %REPEAT %RESULT = 0 %END %BYTEINTEGERFN TEXTCH(%INTEGER N) ! Return the value of stored character N. ! The indices of TEMP and BUFFER do not overlap so ! we can tell in which buffer it is held %IF N<0 %THEN %RESULT = TEMP(N) %RESULT = BUFFER(N) %END %INTEGERFN GETCH ! Read a character from the current input source ! as defined by the top item on the input ! state stack, and leave it in CH. End-of-file ! is indicated by zero. %INTEGER CH %on %event 3,9 %start ch=eos; pop %finish AGAIN: %IF TOS=0 %START ! Real input source stream READSYMBOL(CH) CH = CH&127 MONITOR(CH+128) %finishELSEIF 0<=TOS<256 %START ! Literal character to be popped CH = TOS; POP %finishELSEIF TOS=STOPPER %START ERROR("Unexpected end of input",-1) %STOP %finish %ELSE %start ! Buffer pointer, increment TOS = TOS+1 CH = TEXTCH(TOS-1) %FINISH %IF CH=EOS %START ! End of this input POP; ->AGAIN %FINISH %IF CH=ENDINC %START ! End of this input stream INCLUDEFLAG = INCLUDEFLAG-1 POPLINE SELECTINPUT(INCLUDEFLAG) -> AGAIN %FINISH LINENO = LINENO+1 %IF CH=NL %RESULT = CH %END %ROUTINE RCH %INTEGER W,V,T AGAIN: CH = GETCH %RETURN %UNLESS EXPAND=0 %AND CH=ESCAPE CH = GETCH %UNTIL CH#' ' -> AGAIN %IF CH=NL CH = ESCAT %AND %RETURN %IF CH='@' %RETURN %UNLESS 'A'<=CH<='Z' %OR 'a'<=CH<='z' BACKSPACE EXPAND = 1 RWORD(W,1) EXPAND = 0 T = TYPEOF(W,V) %IF T=NUMTYPE %START IGNOREWORD; GENNUM(V); -> AGAIN %finishELSEIF T=STRINGTYPE %START IGNOREWORD; PUSH(V); -> AGAIN %finishELSEIF T=BOOLTYPE %START IGNOREWORD %IF V=FALSE %THEN PUSH('0') %ELSE PUSH('1') -> AGAIN %finish %ELSE %start CH = ESCAPE %FINISH %END %ROUTINE IGNOREWORD ! Ignore the current tag in the input ! without expansion %INTEGER DUMMY EXPAND = 1 RCH DUMMY = DUMMY %WHILE LETTER(DUMMY) %OR DIGIT(DUMMY) RCH %IF CH='.' BACKSPACE EXPAND = 0 %END %INTEGERFN COMPARE(%INTEGER P1,P2) ! Compare two strings. The value returned ! is -1 for 1<2, 0 for equal and +1 for 1>2. %INTEGER C %CYCLE C = TEXTCH(P1) %EXIT %IF C#TEXTCH(P2) %RESULT = 0 %IF C=0 P1 = P1+1; P2 = P2+1 %REPEAT %RESULT = -1 %IF C=BUFFERSIZE %START ERROR("Too much stored text",-1) %STOP %FINISH BUFFER(BP) = CH; BUFFER(BP+1) = EOS %END %ROUTINE TSTORE(%INTEGER CH) ! Store CH at the current position in the ! temporary text buffer. TBP = TBP+1 %IF TBP>=-2 %START ERROR("Too much temptext",-1) %STOP %FINISH TEMP(TBP) = CH; TEMP(TBP+1) = EOS %END %INTEGERFN PERMANENT(%INTEGER P) ! Make string at P permanent by copying ! it into the text buffer if necessary %INTEGER Q,CH Q = BP+1 %RESULT = P %IF P>0; !already permanent %CYCLE CH = TEMP(P) P = P+1 STORE(CH) %REPEAT %UNTIL CH=EOS %RESULT = Q %END %ROUTINE PCH(%INTEGER CH) !Print the character CH if necessary %IF OFLAG=0 %START CH = ESCAPE %IF CH=ESCAT PRINTSYMBOL(CH) MONITOR(CH) %FINISH ! else discard CH %END ! The hashtable is organised as an array (of a power of two) ! frames for holding symbols. The frame contains the symbol ! name, its value and type, and the level at which it ! was created. The symbols mask each other in the usual block ! structured way if duplicates are declared at different ! levels. %CONSTINTEGER HTABSIZE=511 %RECORDFORMAT HTABF(%INTEGER NAME,VALUE, %BYTEINTEGER TYPE,LEVEL) %RECORD(HTABF)%ARRAY HTAB(0:HTABSIZE) %OWNRECORD(HTABF) BADTAG %INTEGERFN HASH(%INTEGER WHAT) ! Calculate the hash value for WHAT %RESULT = ((WHAT*16_E945)>>3)&HTABSIZE %END %RECORD(HTABF)%MAP LOOKUP(%INTEGER WHAT,EFLAG) ! Lookup the packed tag WHAT in the symbol ! table and return a pointer to its frame. ! If EFLAG is zero then it is an error for ! the symbol to be absent. %INTEGER PR,P,L %RECORD(HTABF)%NAME R; R == BADTAG PR = HASH(WHAT); P = PR; ! Initial probe into table L = -1 %CYCLE %IF HTAB(P)_NAME=WHAT %START ! Found an occurrence of the symbol WHAT %IF HTAB(P)_LEVEL>L %START ! at a higher level than before L = HTAB(P)_LEVEL; R == HTAB(P) %FINISH %FINISH %EXIT %IF HTAB(P)_NAME=0 %OR P=PR-1 P = (P+1)&HTABSIZE %REPEAT ! R now points to the innermost occurrence ! of WHAT unless there were none at all %IF R==BADTAG %AND EFLAG=0 %START ERROR("Undefined: ", WHAT) %FINISH %RESULT == R %END %ROUTINE ENTER(%INTEGER WHAT,LEVEL,TYPE,VALUE) ! Enter a new symbol WHAT into the symbol table at ! level LEVEL, with type TYPE and value VALUE. %INTEGER PR,P %RECORD(HTABF)%NAME H H == LOOKUP(WHAT,1); !No error if absent %UNLESS H==BADTAG %START; ! Check for duplication ERROR("Duplicate: ",WHAT) %IF H_LEVEL>=LEVEL %FINISH PR = HASH(WHAT) P = PR %CYCLE H == HTAB(P) %IF H_NAME=0 %OR H_NAME=-1 %START; ! Empty slot H_NAME = WHAT; H_LEVEL = LEVEL H_TYPE = TYPE; H_VALUE = VALUE %RETURN %FINISH P = (P+1)&HTABSIZE %REPEAT %UNTIL P=PR ERROR("Symbol table full",-1) %STOP %END %ROUTINE CLEAR(%INTEGER LEVEL) ! Remove all symbols declared at levels ! below LEVEL from the table. %INTEGER I %RECORD(HTABF)%NAME H %FOR I = 0,1,HTABSIZE %CYCLE H == HTAB(I) %IF H_LEVEL>=LEVEL %START H_NAME = -1; ! Free slot %FINISH %REPEAT %END %ROUTINE RESERVE ! Clear out the symbol table and enter the ! reserved words %RECORD(HTABF)%NAME H %INTEGER PACKED %ROUTINE R(%STRING(8) S, %INTEGER ITY,IVAL) ! Enter S as a reserved word %INTEGER I,T PUSH(NL) %FOR I = 3,-1,1 %CYCLE %IF I<=LENGTH(S) %THEN PUSH(CHARNO(S,I)) %REPEAT RCH RWORD(T,0) PACKED = T ENTER(T,0,ITY,IVAL) %END %INTEGER I LINENO == LSTACK(0); LINENO=0 %FOR I = 0,1,HTABSIZE %CYCLE HTAB(I)_NAME = 0 %REPEAT R("IF",RESWORD,IF) R("NOT",RESWORD,NOT) R("OR",RESWORD,OR) R("AND",RESWORD,AND) R("THE",RESWORD,THEN) R("ELS",RESWORD,ELSE) R("ELI",RESWORD,ELIF) R("DEF",RESWORD,DEFINE) R("RED",RESWORD,REDEF) R("FIN",RESWORD,FINISH) R("INC",RESWORD,INCLUDE) R("FOR",RESWORD,FOR) R("TO",RESWORD,TO) R("STE",RESWORD,STEP) R("UNL",RESWORD,UNLESS) R("UNT",RESWORD,UNTIL) R("ELU",RESWORD,ELUNLESS) R("TRU",BOOLTYPE,TRUE) R("FAL",BOOLTYPE,FALSE) R("NL",STRINGTYPE,257) R("CYC",RESWORD,CYCLE) R("REP",RESWORD,REPEAT) R("WHI",RESWORD,WHILE) R("BEG",RESWORD,BEGIN) R("END",RESWORD,END) R("ERR",RESWORD,ERR) R("LEN",BUILTINFN,LENFN) R("UND",BUILTINFN,UNDEF) R("MAC",RESWORD,MACRO) R("MEN",RESWORD,MEND) R("EVA",RESWORD,EVAL) R("MNO",NUMTYPE,MACNO);! must be last H == LOOKUP(PACKED,0) HMACNO == H_VALUE TOS = STOPPER; SP = 0 PUSH(EOF); PUSH(0); !initialise stack LEVEL = 1 STORE(NL); STORE(EOS) PUSHLINE(1) %END %PREDICATE LETTER(%INTEGERNAME C) ! Test CH for being a letter. If so, map to ! 0-25 in C, read the next character and true. CH = CH-'a'+'A' %IF 'a'<=CH<='z' %IF 'A'<=CH<='Z' %START C = CH-'A'; RCH; %TRUE %FINISH %FALSE %END %PREDICATE DIGIT(%INTEGERNAME C) ! Test CH for being a digit. If so, map to ! 0-9 in C, read the next character and true. %IF '0'<=CH<='9' %START C = CH-'0'; RCH; %TRUE %FINISH %FALSE %END %ROUTINE RWORD(%INTEGERNAME WORD, %INTEGER N) ! Read in a tag into WORD and pack ! up the first three characters. ! If N is non-zero then the characters ! are not read but only examined ! and it is assumed that CH does ! not contain the first char (nor is it ! left with the last) %INTEGER C,T,C1,C2,C3 T = 0; C1 = 0; C2 = 0; C3 = 0 RCH %IF N#0 -> RW1 %UNLESS LETTER(C) C1 = 'A'+C T = C*1073+1111 -> RW2 %UNLESS LETTER(C) C2 = 'A'+C T =T+C*37 +111 -> RW3 %UNLESS LETTER(C) C3 = 'A'+C T = T+C+11 -> OUT RW1: -> OUT %UNLESS DIGIT(C) C1 = '0'+C T = C*111+1 RW2: -> OUT %UNLESS DIGIT(C) C2 = '0'+C T = T+C*11+1 RW3: -> OUT %UNLESS DIGIT(C) C3 = '0'+C T = T+C+1 OUT: %IF N#0 %START ! Push the three characters back onto the stack BACKSPACE PUSH(C3) %IF C3#0 PUSH(C2) %IF C2#0 PUSH(C1) %IF C1#0 %FINISH WORD = T %END %ROUTINE PWORD(%INTEGER T) ! Print out the packed tag T as (up to) ! three characters %IF T=0 %THEN PRINTSYMBOL('?') %AND %RETURN T = T-1111 -> PW1 %IF T<0 PRINTSYMBOL('A'+T//1073); T = REM(T,1073) -> PW2 %IF T<111 PRINTSYMBOL('A'-3+T//37); T = REM(T,37) -> PW3 %IF T<11 PRINTSYMBOL(T+'A'-11) %RETURN PW1: T = T+1110 -> PW2 %IF T<0 PRINTSYMBOL('0'+T//111); T = REM(T,111) PW2: -> PW3 %IF T=0 T = T-1 PRINTSYMBOL('0'+T//11); T = REM(T,11) PW3: PRINTSYMBOL('0'-1+T) %IF T#0 %END %ROUTINE ERROR(%STRING(30) WHY, %INTEGER WORD) ! Send an error message to the report ! stream consisting of the string WHY. ! If TAG is positive, then append it ! to the end of the message ! using PWORD. %INTEGER S,K %RETURN %IF ERRMESS#0; ERRMESS = 1 MONITOR(0) %FOR S = 0,2,2 %CYCLE SELECTOUTPUT(S) %FOR K = 1,1,LSP %CYCLE PRINTSYMBOL('/') %IF K#1 WRITE(LSTACK(K),0) %REPEAT SPACE PRINTSTRING("ERROR: "); PRINTSTRING(WHY) PRINTSYMBOL(WORD&127) %IF WORD<-1 PWORD(WORD) %IF WORD#-1; NEWLINE %REPEAT SELECTOUTPUT(1) NERRS = NERRS+1 %IF NERRS>MAXERRS %START SELECTOUTPUT(0) PRINTSTRING("I give up"); NEWLINE %STOP %FINISH %END %ROUTINE RTAG(%INTEGERNAME TAG) ! Read in a tag into TAG, dealing with ! extra (above three) alphanumerics %INTEGER DUMMY RWORD(TAG,0) DUMMY = DUMMY %WHILE LETTER(DUMMY) %OR DIGIT(DUMMY) %END %ROUTINE GENNUM(%INTEGER N) ! Push the characters of N (as a text string) ! back onto the input to be read. N = -N %IF N<0; N = REM(N,1000) PUSH(REM(N,10)+'0') %IF N>=10 %START GENNUM(N//10) %FINISH %END %ROUTINE RNUM(%INTEGERNAME N) ! Read a decimal number into N %INTEGER C N = 0 N = N*10+C %WHILE DIGIT(C) %END %ROUTINE NEWVAL(%INTEGER TAG,TYPE,VAL) ! Assign a new value to an existing ! tag %RECORD(HTABF)%NAME H H == LOOKUP(TAG,0) %UNLESS H==BADTAG %START %IF H_TYPE=TYPE %THEN H_VALUE = VAL %C %ELSE ERROR("Type redefinition",-1) %FINISH %END %INTEGERFN TYPEOF(%INTEGER TAG, %INTEGERNAME V) ! Returns the type of tag TAG. If it is not ! in the symbol table then we return type TAG. ! The value is also returned in V. %RECORD(HTABF)%NAME H H == LOOKUP(TAG,1) V = 0 %RESULT = TAGTYPE %IF H==BADTAG %RESULT = H_VALUE %IF H_TYPE=RESWORD V = H_VALUE %RESULT = H_TYPE %END %ROUTINE SKIPTONL ! Skip to next newline in input (used ! for attempting error recovery) RCH; !Get a character RCH %WHILE CH#NL %AND CH#EOF BACKSPACE %END %ROUTINE IGNOREDOT ! Ignore the period following a substitution if ! necessary RCH BACKSPACE %UNLESS CH='.' %END %INTEGERFN NTOKEN ! Look at the next token but do not read it. ! The token must be either >128 or a tag following ! an escape character %INTEGER T,DUMMY RCH; !read the @ RWORD(T,1) %RESULT = 0 %IF T=0 T = TYPEOF(T,DUMMY) %RESULT = T %END %ROUTINE REVERT ! Revert to normal processing using ! RCH and not parsing into tokens. We ignore the ! first non-space character following the parsed section RCH RCH %WHILE CH=' ' BACKSPACE %UNLESS CH=';' %OR CH=NL %END %ROUTINE RTOKEN ! Read a token from the input into TOKEN. This ! routine should only be used in parts of the ! input that the preprocessor should know about ! or it may gobble up the user's text %INTEGER TAG RCH; !get the first character RCH %WHILE CH=' ';! Ignore spaces SUB = 0; WORK = 0 %IF 'A'<=CH<='Z' %OR 'a'<=CH<='z' %START ! It's a tag RTAG(TAG) BACKSPACE WORK = TAG TOKEN = TYPEOF(TAG,SUB) %finishELSEIF CH=ESCAPE %START ! It's a reserved word RCH; ! read escape RTAG(TAG) %IF TAG=0 %START ! nothing there TOKEN = CH %finish %ELSE %start BACKSPACE WORK = TAG TOKEN = TYPEOF(TAG,SUB) %FINISH %finishELSEIF '0'<=CH<='9' %START TOKEN = NUMTYPE RNUM(SUB) BACKSPACE %finishELSEIF CH='"' %START TOKEN = STRINGTYPE; SUB = TBP+1 RCH %CYCLE %IF CH='"' %START RCH; %EXIT %IF CH#'"' %FINISH TSTORE(CH); RCH %REPEAT TSTORE(EOS) BACKSPACE %finishELSEIF CH='<' %OR CH='>' %START ! Deal with < <= << > >= >> TOKEN = CH RCH %IF CH='=' %THEN TOKEN = TOKEN+1000 %C %ELSEIF CH=TOKEN %THEN TOKEN = TOKEN+2000 BACKSPACE %IF TOKEN<1000 %finish %ELSE %start TOKEN = CH %FINISH %END ! Expression are passed as two element records, the ! first element containing the type (string, predicate ! or numeric) %RECORDFORMAT EXPF(%INTEGER TYPE,VALUE) %ROUTINESPEC COERCE(%RECORD(EXPF)%NAME E,%INTEGER T) %ROUTINESPEC EVALUATE(%RECORD(EXPF)%NAME S) %INTEGERFN LENGTH(%INTEGER S) ! Return the length of the string at S %INTEGER L %FOR L = 0,1,10000 %CYCLE %EXIT %IF TEXTCH(S+L)=EOS %REPEAT %RESULT = L %END %INTEGERFN CONCAT(%INTEGER S1,S2) ! Concatenate two strings %INTEGER C,B; B = TBP+1 %CYCLE C = TEXTCH(S1) %EXIT %IF C=EOS TSTORE(C) S1 = S1+1 %REPEAT %CYCLE C = TEXTCH(S2) TSTORE(C) S2 = S2+1 %REPEAT %UNTIL C=EOS %RESULT = B %END %INTEGERFN SUBSTRING(%INTEGER S,L,R) ! Return the substring from the L'th to the R'th ! character of string S %INTEGER C,B,P B = TBP+1 L = 1 %IF L<=0 -> NULL %IF L>R P = 1 %CYCLE C = TEXTCH(S+P-1) -> NULL %IF C=EOS %EXIT %IF P>=L P = P+1 %REPEAT %CYCLE C = TEXTCH(S+P-1) -> NULL %IF C=EOS TSTORE(C) %EXIT %IF P>=R P = P+1 %REPEAT NULL: TSTORE(EOS) %RESULT = B %END %INTEGERFN STRINGEXP ! Get a string expression %RECORD(EXPF) E EVALUATE(E) COERCE(E,STRINGTYPE) %IF E_TYPE#STRINGTYPE %START ERROR("Not a string",-1) TSTORE(EOS) %RESULT = TBP %FINISH %RESULT = E_VALUE %END %INTEGERFN NUMEXP ! Get a numerical expression %RECORD(EXPF) E EVALUATE(E) COERCE(E,NUMTYPE) %IF E_TYPE#NUMTYPE %START ERROR("Not numeric",-1) %RESULT = 0 %FINISH %RESULT = E_VALUE %END %ROUTINE COERCE(%RECORD(EXPF)%NAME E, %INTEGER TYPE) ! Coerce expression E to be of type TYPE if ! possible. We currently only implement the ! coercion NUMTYPE -> STRINGTYPE ! and STRINGTYPE -> NUMTYPE %ROUTINE NTOS(%INTEGER N) N = -N %IF N<0; N = REM(N,1000) %IF N>=10 %THEN NTOS(N//10) TSTORE(REM(N,10)+'0') %END %INTEGER B,C,N %IF E_TYPE=NUMTYPE %AND TYPE=STRINGTYPE %START E_TYPE = STRINGTYPE; B = TBP+1 NTOS(E_VALUE); TSTORE(EOS) E_VALUE = B %finishELSEIF E_TYPE=STRINGTYPE %AND TYPE=NUMTYPE %START B = E_VALUE; N = 0 %CYCLE C = TEXTCH(B); %EXIT %UNLESS '0'<=C<='9' N = N*10+C-'0' B = B+1 %REPEAT %IF C=EOS %START E_TYPE = NUMTYPE; E_VALUE = N %FINISH %FINISH %END %ROUTINE EXPRESSION(%INTEGER PRECEDENCE, %RECORD(EXPF)%NAME RESULT) ! Read in and evaluate an arithmetic or logical ! expression. The precedence rules are: ! 10 - Constants or bracketed expressions ! 9 - Monadic oparators, -, + and \ ! 8 - Diadic operators *, / and \ ! 7 - Diadic operators + and - ! 6 - Diadic comparators <, <=, >, >=, = and # ! 5 - Shifts << and >> ! 4 - Boolean and & ! 3 - Boolean or ! ! 2 - Predicate and @AND ! 1 - Predicate or @OR ! Operators all associate to the right with equal ! precedence operators %INTEGER OPN,OP,R,T1,T2 %RECORD(EXPF) TERM1,TERM2 ! the allowable diadic operators %CONSTINTEGER NOPS=18 %CONSTINTEGERARRAY DOPS(1:NOPS) = OR, AND, '!', '&', '<'+2000, '>'+2000, '<', '>', '<'+1000, '>'+1000, '=', '#', '+', '-', '*', '/', '\', '.' ! the operator precedences %CONSTINTEGERARRAY DOPP(1:NOPS) = 1, 2, 3, 4, 5(2), 6(6), 7(2), 8(3), 7 ! the result type from each operator %CONSTINTEGERARRAY RES(1:NOPS) = BOOLTYPE(2), NUMTYPE(4), BOOLTYPE(6), NUMTYPE(5), STRINGTYPE ! the allowable arument types for each operator %CONSTINTEGERARRAY ARGS(1:NOPS) = BOOLTYPE(2), NUMTYPE(4), 0(6), NUMTYPE(5), STRINGTYPE RESULT_TYPE = NUMTYPE; RESULT_VALUE = 0 %IF PRECEDENCE<=8 %START ! Diadic operator EXPRESSION(PRECEDENCE+1,TERM1) OPN = 0 %CYCLE OPN = OPN+1 %IF OPN>NOPS %START ! No operator follows RESULT_TYPE = TERM1_TYPE RESULT_VALUE = TERM1_VALUE %RETURN %FINISH %EXIT %IF TOKEN=DOPS(OPN) %AND PRECEDENCE<=DOPP(OPN) %REPEAT OP = TOKEN RTOKEN EXPRESSION(PRECEDENCE,TERM2) %IF ARGS(OPN)#0 %START COERCE(TERM1,ARGS(OPN)) COERCE(TERM2,ARGS(OPN)) %IF TERM1_TYPE#TERM2_TYPE %THEN -> ERR %finish %ELSE %start COERCE(TERM1,TERM2_TYPE) %FINISH %IF ARGS(OPN)#0 %START -> ERR %IF ARGS(OPN)#TERM1_TYPE %FINISH RESULT_TYPE = RES(OPN) T1 = TERM1_VALUE; T2 = TERM2_VALUE %IF OP='+' %THEN R = T1+T2 %IF OP='-' %THEN R = T1-T2 %IF OP='/' %THEN R = T1//T2 %IF OP='*' %THEN R = T1*T2 %IF OP='\' %THEN R = REM(T1,T2) %IF OP='<'+2000 %THEN R = T1<>T2 %IF OP='!' %OR OP=OR %THEN R = T1!T2 %IF OP='&' %OR OP=AND %THEN R = T1&T2 %IF OP='.' %THEN R = CONCAT(T1,T2) %UNLESS 7<=OPN<=12 %START ! Not a comparator RESULT_VALUE = R; %RETURN %FINISH %IF TERM1_TYPE = STRINGTYPE %START ! Set up correct relation between t1 and t2 T1 = COMPARE(T1,T2); T2 = 0 %FINISH RESULT_VALUE = FALSE %IF T1=T2 %START RESULT_VALUE = TRUE %IF OP>1000 %OR OP='=' %RETURN %FINISH %IF OP='=' %OR OP='#' %START RESULT_VALUE = TRUE %IF OP='#'; %RETURN %FINISH OP = OP-1000 %IF OP>1000 %IF T1>T2 %START RESULT_VALUE = TRUE %IF OP='>'; %RETURN %FINISH RESULT_VALUE = TRUE %IF OP='<'; %RETURN %FINISH %IF PRECEDENCE<=9 %START ! Monadic operator %IF TOKEN='-' %OR TOKEN='+' %OR TOKEN='\' %OR TOKEN=NOT %START OP = TOKEN; RTOKEN EXPRESSION(10,TERM1) %IF OP=NOT %START -> ERR %IF TERM1_TYPE#BOOLTYPE RESULT_TYPE = BOOLTYPE %finish %ELSE %start COERCE(TERM1,NUMTYPE) -> ERR %IF TERM1_TYPE#NUMTYPE %FINISH T1 = TERM1_VALUE RESULT_VALUE = T1 %IF OP='-' %THEN RESULT_VALUE = -T1 %IF OP='\' %OR OP=NOT %THEN RESULT_VALUE = \T1 %RETURN %FINISH %FINISH ! Primary or bracketed expression %IF TOKEN='(' %START RTOKEN EXPRESSION(1,RESULT) %IF TOKEN#')' %THEN -> ERR RTOKEN; %RETURN %finishELSEIF TOKEN=NUMTYPE %START RESULT_VALUE = SUB; RTOKEN %RETURN %finishELSEIF TOKEN=STRINGTYPE %START RESULT_TYPE = STRINGTYPE RESULT_VALUE = SUB RTOKEN %IF TOKEN='(' %START ! substring RTOKEN T1 = NUMEXP -> ERR %IF TOKEN#',' RTOKEN T2 = NUMEXP -> ERR %IF TOKEN#')' RTOKEN RESULT_VALUE = SUBSTRING(RESULT_VALUE,T1,T2) %FINISH %RETURN %finishELSEIF TOKEN=BOOLTYPE %START RESULT_TYPE = BOOLTYPE RESULT_VALUE = SUB RTOKEN; %RETURN %finishELSEIF TOKEN=BUILTINFN %START %IF SUB=LENFN %START RTOKEN -> ERR %IF TOKEN#'(' T1 = STRINGEXP RESULT_VALUE = LENGTH(T1) %RETURN %finishELSEIF TOKEN=UNDEF %START RTOKEN -> ERR %IF TOKEN#'('; RTOKEN -> ERR %IF WORK=0; T1 = WORK; RTOKEN -> ERR %IF TOKEN#')'; RTOKEN T2 = TYPEOF(T1,T1) RESULT_TYPE = BOOLTYPE RESULT_VALUE = FALSE ! true if undefined or reserved word RESULT_VALUE = TRUE %IF T2=TAGTYPE %OR T2<0 %OR T2=BUILTINFN %RETURN %FINISH %FINISH ERR: ERROR("Malformed expression",-1) SKIPTONL %RETURN %END %ROUTINE EVALUATE(%RECORD(EXPF)%NAME P) ! Read and evaluate an expression of arbitrary ! type EXPRESSION(1,P) %END %INTEGERFN CONDITION ! Read and evaluate a condition %RECORD(EXPF) C EVALUATE(C) %IF C_TYPE#BOOLTYPE %START ERROR("Bad condition",-1) SKIPTONL; %RESULT = FALSE %FINISH %RESULT= TRUE %UNLESS C_VALUE=FALSE %RESULT = FALSE %END %ROUTINE SYNTAX ! Syntax error; read up to newline ERROR("Syntax",-1) SKIPTONL %END %ROUTINE SCOPY(%INTEGER FLAG,TFLAG) ! Store text not-containing meta-constructs ! The text is not stored if flag is non-zero. RCH %WHILE CH#ESCAPE %AND CH<128 %CYCLE %IF FLAG=0 %START %IF TFLAG=0 %THEN TSTORE(CH) %ELSE STORE(CH) %FINISH RCH %REPEAT BACKSPACE %END %ROUTINE COPY ! Copy text not containing meta-constructs RCH %WHILE CH#ESCAPE %AND CH<128 %CYCLE PCH(CH); RCH %REPEAT BACKSPACE ERRMESS = 0 %END %ROUTINE DOEVAL ! Replace an expression with its evaluated form %RECORD(EXPF) E RTOKEN %RETURN %IF TOKEN#'(' RTOKEN EVALUATE(E) %IF E_TYPE=NUMTYPE %START GENNUM(E_VALUE) %finishELSEIF E_TYPE=STRINGTYPE %START PUSH(E_VALUE) %finishELSEIF E_TYPE=BOOLTYPE %START %IF E_VALUE=FALSE %THEN PUSH('0') %ELSE PUSH('1') %FINISH SYNTAX %IF TOKEN#')' %END %INTEGERFN STORECYCLE(%INTEGERNAME L) ! Store a cycle body in the text buffer, delaing ! correctly with nested cycles. %INTEGER B,DEPTH,T,S B = TBP+1; DEPTH = 1; S = SOURCE EXPAND = 1; !no expansion as we read it in REVERT; !ignore ch after @cyc L = LINENO %CYCLE SCOPY(S,0) T = NTOKEN; !token found - could be cyc or rep %IF T=CYCLE %START DEPTH = DEPTH+1 %finishELSEIF T=REPEAT %START DEPTH = DEPTH-1 %FINISH TSTORE(ESCAPE) %IF S=0 %REPEAT %UNTIL DEPTH=0 PUSH(ESCAPE); RTOKEN; REVERT; !ignore last @REP EXPAND = 0 %IF S=0 %START TSTORE('R'); TSTORE('E'); TSTORE('P'); TSTORE(NL) TSTORE(EOS) %RESULT = B %FINISH %RESULT = S %END %ROUTINE DOCYCLE ! Process a cycle-repeat. If the source ! is not from core already then we must first ! load the cycle body into a core buffer %INTEGER OLDTBP,CHEAD,OLDWFLAG,DUMMY %INTEGER T,CONTROL,INITIAL,INCREMENT,FINAL %INTEGER EXISTED,OLDLINE %RECORD(HTABF)%NAME H OLDTBP = TBP OLDWFLAG = WFLAG CONTROL = -1 INITIAL = 1; INCREMENT = 1; FINAL = 1000 EXISTED = 0 %IF TOKEN=FOR %START RTOKEN -> SERR %IF WORK=0 CONTROL = WORK; RTOKEN %IF TOKEN='=' %START RTOKEN INITIAL = NUMEXP -> SERR %IF TOKEN#TO %FINISH %FINISH %IF TOKEN=TO %START RTOKEN FINAL = NUMEXP %IF TOKEN=STEP %START RTOKEN INCREMENT = NUMEXP %FINISH -> SERR %IF TOKEN#CYCLE %FINISH %IF CONTROL#-1 %START ! declare control variable H == LOOKUP(CONTROL,1) %IF H==BADTAG %START ! not in table - create it EXISTED = 1 ENTER(CONTROL,LEVEL,NUMTYPE,INITIAL) H == LOOKUP(CONTROL,0) %finish %ELSE %start ! use the old value %IF H_TYPE#NUMTYPE %START ERROR("Non numeric control variable",INITIAL) %finish %ELSE %start H_VALUE = INITIAL %FINISH %FINISH %FINISH CHEAD = STORECYCLE(OLDLINE); !scan cycle and store if necessary %IF OFLAG=0 %START ! actually outputting %CYCLE %IF INCREMENT<0 %START %EXIT %IF INITIALFINAL %FINISH pushline(oldline) PUSH(CHEAD); PROCESS T = TOKEN REVERT; POP %IF T#REPEAT %START ERROR("Nesting",-1) PROCESS; %RETURN %FINISH POPLINE INITIAL = INITIAL+INCREMENT H_VALUE = INITIAL %IF CONTROL#-1 %REPEAT %UNTIL WFLAG#32767 %FINISH %IF EXISTED=1 %START ! we created control v so remove it H_NAME = -1 %FINISH OFLAG = WFLAG %IF WFLAG#32767 WFLAG = OLDWFLAG %RETURN SERR: SYNTAX TBP = OLDTBP %END %ROUTINE STOREMACRO ! Store the text of a Macro, including any internal ! macro definitions %INTEGER B,DEPTH,S,T,NAME B = BP+1; DEPTH = 1; S = SOURCE RTOKEN -> ERR %IF WORK=0 STORE(LINENO>>8); STORE(LINENO&255) NAME = WORK RTOKEN %IF TOKEN='(' %START %CYCLE RTOKEN -> ERR %IF WORK=0 STORE(WORK>>8); STORE(WORK&255) RTOKEN %REPEAT %UNTIL TOKEN#',' -> ERR %IF TOKEN#')' RTOKEN %FINISH STORE(EOS) EXPAND = 1 %CYCLE SCOPY(S,1) T = NTOKEN %IF T=MACRO %THEN DEPTH = DEPTH+1 %C %ELSEIF T=MEND %THEN DEPTH = DEPTH-1 STORE(ESCAPE) %IF S=0 %REPEAT %UNTIL DEPTH=0 PUSH(ESCAPE); RTOKEN; REVERT EXPAND = 0 %IF S=0 %START STORE('M'); STORE('E'); STORE('N'); STORE(NL) STORE(EOS) %FINISH S = B %IF S=0 ENTER(NAME,LEVEL,MACTYPE,S) %RETURN ERR: ERROR("Error in macro definition",NAME) %END %ROUTINE READTOCOMMA ! Store text up to the next , or ) not contained ! within quotes. Strip off the outermost quotes ! if they occur %IF CH='"' %START %CYCLE RCH %IF CH='"' %START RCH; %EXIT %IF CH#'"' %FINISH STORE(CH) %REPEAT %IF CH#',' %AND CH#')' %START ERROR("Comma or bracket expected",-1) %FINISH %FINISH %WHILE CH#',' %AND CH#')' %CYCLE STORE(CH); RCH %REPEAT STORE(EOS) ! leave , or ) in CH %END %ROUTINE EXPANDMACRO ! Expand a macro body %INTEGER M,OLDBP,N,C,T %INTEGER OLDHMACNO,MNAME OLDHMACNO = HMACNO; MNAME = WORK; OLDBP = BP; M = SUB-1 %INTEGERFN FETCH M = M+1; %RESULT = BUFFER(M) %END PUSHLINE(FETCH<<8+FETCH) MACNO = MACNO+1 HMACNO = MACNO LEVEL = LEVEL+1 EXPAND = 1 RCH %IF CH#'(' %THEN BACKSPACE %AND PUSH(')') CH=',' %CYCLE RCH %IF CH=',' N = FETCH %EXIT %IF N=EOS; !end of arglist N = N<<8+FETCH ENTER(N,LEVEL,STRINGTYPE,0) T = BP+1 READTOCOMMA NEWVAL(N,STRINGTYPE,T) %REPEAT ERROR("Too many arguments: ",MNAME) %IF CH#')' IGNOREDOT EXPAND = 0 %IF OFLAG=0 %START ! actually outputting PUSH(M+1) PROCESS %IF TOKEN#MEND %START ERROR("End of macro expected: ",MNAME) SKIPTONL; PROCESS; %RETURN %FINISH POP %FINISH POPLINE BP = OLDBP HMACNO = OLDHMACNO CLEAR(LEVEL) LEVEL = LEVEL-1 %END %ROUTINE DOERROR ! Output an error message for the user %INTEGER E,OLDTBP,K,CH %BYTEINTEGERARRAY B(0:81) OLDTBP = TBP RTOKEN E = STRINGEXP %FOR K = 1,1,82 %CYCLE CH = TEXTCH(E+K-1) %EXIT %IF CH=EOS B(K) = CH %EXIT %IF K=81 %REPEAT B(0) = K-1 ERROR(STRING(ADDR(B(0))),-1) TBP = OLDTBP %END %ROUTINE DOBLOCK ! Process a begin-end block %INTEGER OLDBP; OLDBP = BP LEVEL = LEVEL+1 REVERT PROCESS %IF TOKEN#END %START ERROR("@END expected",-1) REVERT; PROCESS; %RETURN %FINISH REVERT CLEAR(LEVEL) LEVEL = LEVEL-1 BP = OLDBP %END %ROUTINE DOIF ! Process an @IF statement ! Syntax: @IF condition THEN ... {@ELIF condition ! THEN ... } @ELSE ... @FINISH %INTEGER OLDOFLAG, ELSEFLAG, UNL OLDOFLAG = OFLAG; ELSEFLAG = 0 %IF TOKEN=IF %THEN TOKEN=ELIF %ELSE TOKEN=ELUNLESS OFLAG = 1 %CYCLE %EXIT %IF TOKEN=ELSE %OR TOKEN=FINISH UNL = TRUE; UNL = FALSE %IF TOKEN=ELUNLESS -> NERR %IF TOKEN#ELIF %AND TOKEN#ELUNLESS RTOKEN %IF UNL=CONDITION %AND ELSEFLAG=0 %START ELSEFLAG = 1 OFLAG = OLDOFLAG %FINISH %IF TOKEN#THEN %START ERROR("@THEN expected",-1) %FINISH REVERT PROCESS OFLAG = 1 %REPEAT %IF TOKEN=ELSE %START %IF ELSEFLAG=0 %THEN OFLAG = OLDOFLAG REVERT PROCESS %FINISH -> NERR %IF TOKEN#FINISH REVERT OFLAG = OLDOFLAG %RETURN NERR: ERROR("Nesting: ",WORK) OFLAG = OLDOFLAG PROCESS %END %ROUTINE DOINCLUDE ! Include a named file ! Syntax: @INC string-expression %INTEGER OLDTBP,FILE %string(31) vaxfile OLDTBP = TBP RTOKEN; !read @INC FILE = STRINGEXP REVERT %IF WORK#0 %IF INCLUDEFLAG>2 %START ERROR("@INCLUDEs nested too deep",-1) %RETURN %FINISH INCLUDEFLAG = INCLUDEFLAG+1 vaxfile="" %cycle tbp=tbp-1 %exit %if tbp=oldtbp vaxfile=tostring(temp(tbp)).vaxfile %repeat OPENINPUT(INCLUDEFLAG,vaxFILE) PUSH(ENDINC) PUSHLINE(1) SELECTINPUT(INCLUDEFLAG) PUSH(0) TBP = OLDTBP; !throw away the filename %END %ROUTINE DODEFINE ! Process a @DEF or @RED instruction ! Syntax: @DEF tag = expression {, tag = expression} %INTEGER WHICH,NAME,OLDTBP %RECORD(EXPF) V OLDTBP = TBP WHICH = TOKEN %CYCLE RTOKEN; !read @DEF or "," %IF WORK=0 %THEN -> ERR; !not a tag NAME = WORK; RTOKEN %IF TOKEN#'=' %THEN -> ERR RTOKEN EVALUATE(V); !expression of any type %IF V_TYPE=STRINGTYPE %START ! string may be in temporary area V_VALUE = PERMANENT(V_VALUE) %FINISH !ignore if skipping %IF OFLAG=0 %START %IF WHICH=DEFINE %START ENTER(NAME,LEVEL,V_TYPE,V_VALUE) %finish %ELSE %start NEWVAL(NAME,V_TYPE,V_VALUE) %FINISH %FINISH TBP = OLDTBP; !clear out any string constants %IF TOKEN#',' %THEN %RETURN %REPEAT ERR: SYNTAX %END %ROUTINE PROCESS ! Process the input, dealing with any ! meta-constructs encountered %INTEGER W %CYCLE COPY; RTOKEN; !find something interesting ALREADY: ;!got something interesting by mistake %IF TOKEN=DEFINE %OR TOKEN=REDEF %START ! @DEF or @RED - perform the assignment DODEFINE -> ALREADY %IF WORK#0 %finishELSEIF TOKEN=ELSE %OR TOKEN=ELIF %OR TOKEN=FINISH %OR %C TOKEN=REPEAT %OR TOKEN=END %OR TOKEN=MEND %START %RETURN; !Not for us on this level %finishELSEIF TOKEN=IF %OR TOKEN=UNLESS %START ! @IF statement - process it DOIF %finishELSEIF TOKEN=INCLUDE %START ! @INC filename - push onto input stack DOINCLUDE -> ALREADY %IF WORK#0 %finishELSEIF TOKEN=CYCLE %OR TOKEN=FOR %OR TOKEN=TO %START ! @CYC-@REP loop - expand it DOCYCLE %finishELSEIF TOKEN=MACRO %START ! Macro definition STOREMACRO %finishELSEIF TOKEN=MACTYPE %START ! Macro evaluation EXPANDMACRO %finishELSEIF TOKEN=WHILE %OR TOKEN=UNTIL %START ! @WHI statement -evaluate and maybe go on W = TRUE; W = FALSE %IF TOKEN=UNTIL RTOKEN %UNLESS W=CONDITION %START WFLAG = OFLAG %IF WFLAG=32767 OFLAG = 1; !inhibit output %FINISH -> ALREADY %IF WORK#0 %finishELSEIF TOKEN=EOF %START ! End-of-input MONITOR(0) %RETURN %finishELSEIF TOKEN=BEGIN %START ! @BEGIN...@END block DOBLOCK %finishELSEIF TOKEN=EVAL %START DOEVAL %finishELSEIF TOKEN=ERR %START DOERROR -> ALREADY %IF WORK#0 %finish %ELSE %start W = TOKEN!16_8000; W = WORK %IF WORK#0 ERROR("Context: ",W) SKIPTONL %FINISH %REPEAT %END RETURN CODE=DEF STREAMS(CLIPARAM,DEFAULTS) ->DONE %UNLESS RETURN CODE=1 RESERVE SELECTINPUT(1); SELECTOUTPUT(1) PROCESS DONE: %ENDOFPROGRAM