|
|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T b
Length: 25016 (0x61b8)
Types: TextFile
Names: »bas1.c«
└─⟦a0efdde77⟧ Bits:30001252 EUUGD11 Tape, 1987 Spring Conference Helsinki
└─⟦this⟧ »EUUGD11/euug-87hel/sec1/basic/bas1.c«
/*
* BASIC by Phil Cockcroft
*/
/*
* This file contains the main routines of the interpreter.
*/
/*
* the core is arranged as follows: -
* ------------------------------------------------------------------- - - -
* | file | text | string | user | array | simple | for/ | unused
* | buffers | of | space | def | space | variables | gosub | memory
* | | program | | fns | | | stack |
* ------------------------------------------------------------------- - - -
* ^ ^ ^ ^ ^ ^ ^ ^
* filestart fendcore ecore estring edefns earray vend vvend
* ^eostring ^estarr
*/
#define PART1
#include "bas.h"
#undef PART1
/*
* The main program , it sets up all the files, signals,terminal
* and pointers and prints the start up message.
* It then calls setexit().
* IMPORTANT NOTE:-
* setexit() sets up a point of return for a function
* It saves the local environment of the calling routine
* and uses that environment for further use.
* The function reset() uses the information saved in
* setexit() to perform a non-local goto , e.g. poping the stack
* until it looks as though it is a return from setexit()
* The program then continues as if it has just executed setexit()
* This facility is used all over the program as a way of getting
* out of functions and returning to command mode.
* The one exception to this is during error trapping , The error
* routine must pop the stack so that there is not a recursive call
* on execute() but if it does then it looks like we are back in
* command mode. The flag ertrap is used to signal that we want to
* go straight on to execute() the error trapping code. The pointers
* must be set up before the execution of the reset() , (see error ).
* N.B. reset() NEVER returns , so error() NEVER returns.
*/
main(argc,argv)
char **argv;
{
register i;
catchsignal();
startfp(); /* start up the floating point hardware */
setupfiles(argc,argv);
setupterm(); /* set up files after processing files */
ecore = fendcore+sizeof(xlinnumb);
( (lpoint) fendcore )->linnumb=0;
clear(DEFAULTSTRING);
prints("Phil's Basic version v1.8\n");
setexit();
if(ertrap)
goto execut;
docont();
runmode=0; /* say we are in immeadiate mode */
if(cursor) /* put cursor on a blank line */
prints(nl);
prints("Ready\n");
do{
do{
trapped=0;
*line ='>';
edit(1,1,1);
}while( trapped || ( !(i=compile(1)) && !linenumber ));
if(linenumber)
insert(i);
}while(linenumber);
if(inserted){
inserted=0;
clear(DEFAULTSTRING);
closeall();
}
vvend=bstk; /* reset the gosub stack */
errortrap=0; /* disable error traps */
intrap=0; /* say we are not in the error trap */
trapped=0; /* say we haven't got a cntrl-c */
cursor=0; /* cursor is at start of line */
elsecount=0; /* disallow elses as terminators */
curline=0; /* current line is zero */
point=nline; /* start executing at start of input line */
stocurlin=0; /* start of current line is null- see 'next' */
execut: execute(); /* execute the line */
return(-1); /* see note below */
}
/*
* Execute will return by calling reset and so if execute returns then
* there is a catastrophic error and we should exit with -1 or something
*/
/*
* compile converts the input line (in line[]) into tokenised
* form for execution(in nline). If the line starts with a linenumber
* then that is converted to binary and is stored in 'linenumber' N.B.
* not curline (see evalu() ). A linenumber of zero is assumed to
* be non existant and so the line is executed immeadiately.
* The parameter to compile() is an index into line that is to be
* ignored, e.g. the prompt.
*/
compile(fl)
int fl;
{
register char *p,*q;
register struct tabl *l;
unsigned lin=0;
char charac;
char *eql(),*k;
p= &line[fl];
q=nline;
while(*p++ ==' ');
p--;
while(isnumber(*p)){ /* get line number */
if(lin >= 6553)
error(7);
lin = lin*10 + (*p++ -'0');
}
while(*p==' ')
*q++ = *p++;
if(!*p){
linenumber =lin;
return(0); /* no characters on the line */
}
while(*p){
if(*p=='"' || *p=='`'){ /* quoted strings */
charac= *p;
*q++ = *p++;
while(*p && *p != charac)
*q++ = *p++;
if(*p)
*q++= *p++;
continue;
}
if(*p < '<' && *p != '\''){ /* ignore all characters */
*q++ = *p++; /* that couldn't be used */
continue; /* in reserved words */
}
for(l=table ; l->string ; l++) /* search the table */
if(*p != *(l->string) ) /* for the right entry */
continue;
else if(k = eql(p,l->string)){ /* if found then */
#ifdef LKEYWORDS
if( isletter(*p) ){
if(p!= &line[fl] && isletter(*(p-1)) )
continue;
if( isletter(*k) && l->chval != FN)
continue;
}
#endif
*q++ = l->chval; /* replace by a token */
p = k;
if(l->chval== REM || l->chval== QUOTE ||
l->chval == DATA)
while(*p)
*q++ = *p++;
goto more; /* dont compile comments */
} /* or data */
*q++ = *p++;
more: ;
}
*q='\0';
linenumber=lin;
return(q-nline); /* return length of line */
}
/*
* eql() returns true if the strings are the same .
* this routine is only called if the first letters are the same.
* hence the increment of the pointers , we don't need to compare
* the characters they point to.
* To increase speed this routine could be put into machine code
* the overheads on the function call and return are excessive
* for what it accomplishes. (it fails most of the time , and
* it can take a long time to load a large program ).
*/
char *
eql(p,q)
register char *p,*q;
{
p++,q++;
while(*q)
if(*p++ != *q++){
#ifdef SCOMMS
if(*(p-1) == '.')
return(p);
#endif
return(0);
}
return(p);
}
/*
* Puts a line in the table of lines then sets a flag (inserted) so that
* the variables are cleared , since it is very likely to have moved
* 'ecore' and so the variables will all be corrupted. The clearing
* of the variables is not done in this routine since it is only needed
* to clear the variables once and that is best accomplished in main
* just before it executes the immeadiate mode line.
* If the line existed before this routine is called then it is deleted
* and then space is made available for the new line, which is then
* inserted.
* The structure of a line in memory has the following structure:-
* struct olin{
* unsigned linnumb;
* unsigned llen;
* char lin[1];
* }
* The linenumber of the line is stored in linnumb , If this is zero
* then this is the end of the program (all searches of the line table
* terminate if it finds the linenumber is zero.
* The variable 'llen' is used to store the length of the line (in
* characters including the above structure and any padding needed to
* make the line an even length.
* To search through the table of lines then:-
* start at 'fendcore'
* IF linnumb is zero THEN terminate search
* ELSE IF linnumb is the required line THEN
* found line , terminate
* ELSE
* goto next line ( add llen to the current pointer )
* repeat loop.
* The line is in fact stored in lin[] , To the C compiler this
* is a one character array but since the lines are more than one
* character long (usually) it is fooled into using it as a variable
* length array ( impossible in 'pure' C ).
* The pointers used by the program storage routines are:-
* fendcore = start of text storage segment
* ecore = end of text storage
* = start of data segment (string space ).
* strings are stored after the text but before the numeric variables
* only 512 bytes are allocated at the start of the program for strings
* but clear can be called to get more core for the strings.
*/
insert(lsize)
register int lsize;
{
register lpoint p;
register unsigned l;
inserted=1; /* say we want the variables cleared */
l= linenumber;
for(p= (lpoint) fendcore ; p->linnumb; p=(lpoint)((memp)p+lenv(p)))
if(p->linnumb >= l ){
if(p->linnumb != l )
break;
l=lenv(p); /* delete the old line */
bmov( (short *)p, (int)l);
ecore -= l;
break;
}
if(!lsize) /* line has no length */
return;
lsize += sizeof(struct olin);
#ifdef ALIGN4
lsize = (lsize + 03) & ~03;
#else
if(lsize&01)
lsize++; /* make length of line even */
#endif
mtest(ecore+lsize); /* get the core for it */
ecore += lsize;
bmovu( (short *)p,lsize); /* make space for the line */
strcpy(nline,p->lin); /* move the line into the space */
p->linnumb=linenumber; /* give it a linenumber */
p->llen=lsize; /* give it its offset */
}
/* This routine will move the core image down so deleteing a line */
bmov(a,b)
register short *a;
int b;
{
register short *c,*d;
c= (short *)ecore;
d= (short *)((char *)a + b );
do{
*a++ = *d++;
}while(d<c);
}
/* This will move the text image up so that a new line can be inserted */
bmovu(a,b)
register short *a;
int b;
{
register short *c,*d;
c= (short *) ecore;
d= (short *) (ecore-b);
do{
*--c = *--d;
}while(a<d);
}
/*
* The interpreter needs three variables to control the flow of the
* the program. These are:-
* stocurlin : This is the pointer to the start of the current
* line it is used to index the next line.
* If the program is in immeadiate mode then
* this variable is NULL (very important for 'next')
* point: This points to the current location that
* we are executing.
* curline: The current line number ( zero in immeadiate mode)
* this is not needed for program exection ,
* but is used in error etc. It could be made faster
* if this variable is not used....
*/
/*
* The main loop of the execution of a program.
* It does the following:-
* FOR(ever){
* save point so that resume will go to the right place
* IF cntrl-c THEN stop
* IF NOT a reserved word THEN do_assignment
* ELSE IF legal command THEN execute_command
* IF return is NORMAL THEN
* BEGIN
* IF terminator is ':' THEN continue
* ELSE IF terminator is '\0' THEN
* goto next line ; continue
* ELSE IF terminator is 'ELSE' AND
* 'ELSES' are enabled THEN
* goto next line ; continue
* END
* ELSE IF return is < NORMAL THEN continue
* ( used by goto etc. ).
* ELSE IF return is > NORMAL THEN
* ignore_rest_of_line ; goto next line ; continue
* }
* All commands return a value ( if they return ). This value is NORMAL
* if the command is standard and does not change the flow of the program.
* If the value is greater than zero then the command wants to miss the
* rest of the line ( comments and data ).
* If the value is less than zero then the program flow has changed
* and so we should go back and try to execute the new command ( we are
* now at the start of a command ).
*/
execute()
{
register int i,c;
register lpoint p;
ertrap=0; /* stop recursive error trapping */
again:
savepoint=point;
if(trapped)
dobreak();
if(!((c=getch())&0200)){
point--;
assign();
goto retn;
}
if(c>=MAXCOMMAND)
error(8);
if((i=(*commandf[c&0177])())==NORMAL){ /* execute the command */
retn: if((c=getch())==':')
goto again;
else if(!c){
elseret: if(!runmode) /* end of immeadiate line */
reset();
p = stocurlin;
p = (lpoint)((memp)p + lenv(p)); /* goto next line */
stocurlin=p;
point=p->lin;
if(!(curline=p->linnumb)) /* end of program */
reset();
elsecount=0; /* disable `else`s */
goto again;
}
else if(c==ELSE && elsecount) /* `else` is a terminator */
goto elseret;
error(SYNTAX);
}
if(i < NORMAL)
goto again; /* changed execution position */
else
goto elseret; /* ignore rest of line */
}
/*
* The error routine , this is called whenever there is any error
* it does some tidying up of file descriptors and sets the error line
* number and the error code. If there is error trapping ( errortrap is
* non-zero and in runmode ), then save the old pointers and set up the
* new pointers for the error trap routine.
* Otherwise print out the error message and the current line if in
* runmode.
* Finally call reset() ( which DOES NOT return ) to pop
* the stack and to return to the main routine.
*/
error(i)
int i; /* error code */
{
register lpoint p;
if(readfile){ /* close file descriptor */
close(readfile); /* from loading a file */
readfile=0;
}
if(pipes[0]){ /* close the pipe (from chain ) */
close(pipes[0]); /* if an error while chaining */
pipes[0]=0;
}
evallock=0; /* stop the recursive eval message */
ecode=i; /* set up the error code */
if(runmode)
elinnumb=curline; /* set up the error line number */
else
elinnumb=0;
if(runmode && errortrap && !inserted ){ /* we have error trapping */
estocurlin=stocurlin; /* save the various pointers */
epoint=savepoint;
eelsecount=elsecount;
p=errortrap;
stocurlin=p; /* set up to execute code */
point=p->lin;
curline=p->linnumb;
saveertrap=p; /* save errortrap pointer */
errortrap=0; /* disable further error traps */
intrap=1; /* say we are trapped */
ertrap=1; /* we want to go to execute */
}
else { /* no error trapping */
if(cursor){
prints(nl);
cursor=0;
}
prints(ermesg[i-1]); /* error message */
if(runmode){
prints(" on line ");
prints(printlin(curline));
}
prints(nl);
}
reset(); /* no return - goes to main */
}
/*
* This is executed by the ON ERROR construct it checks to see
* that we are not executing an error trap then set up the error
* trap pointer.
*/
errtrap()
{
register lpoint p;
p=getline();
check();
if(intrap)
error(8);
errortrap=p;
}
/*
* The 'resume' command , checks to see that we are actually
* executing an error trap. If there is an optional linenumber then
* we resume from there else we resume from where the error was.
*/
resume()
{
register lpoint p;
register unsigned i;
if(!intrap)
error(8);
i= getlin();
check();
if(i!= (unsigned)(-1) ){
for(p=(lpoint)fendcore;p->linnumb;p=(lpoint)((memp)p+lenv(p)))
if(p->linnumb==i)
goto got;
error(6); /* undefined line */
got: stocurlin= p; /* resume at that line */
curline= p->linnumb;
point= p->lin;
elsecount=0;
}
else {
stocurlin=estocurlin; /* resume where we left off */
curline=elinnumb;
point=epoint;
elsecount=eelsecount;
}
errortrap=saveertrap; /* restore error trapping */
intrap=0; /* get out of the trap */
return(-1); /* return to re-execute */
}
/*
* The 'error' command , this calls the error routine ( used in testing
* an error trapping routine.
*/
doerror()
{
register i;
i=evalint();
check();
if(i<1 || i >MAXERR)
error(22); /* illegal error code */
error(i);
}
/*
* This routine is used to clear space for strings and to reset all
* other pointers so that it effectively clears the variables.
*/
clear(stringsize)
int stringsize; /* size of string space */
{
#ifdef LNAMES
register struct entry **p;
register int *ip;
for(p = hshtab ; p < &hshtab[HSHTABSIZ];) /* clear the hash table*/
*p++ = 0;
for(ip = varshash ; ip < &varshash[HSHTABSIZ]; )
*ip++ = -1;
#endif
#ifdef ALIGN4
estring= &ecore[stringsize& ~03]; /* allocate string space */
#else
estring= &ecore[stringsize& ~01]; /* allocate string space */
#endif
mtest(estring); /* get the core */
shash=1; /* string array "counter" */
datapoint=0; /* reset the pointer to data */
contpos=0;
#ifdef LNAMES
chained = 0; /* reset chained flag */
estdt=enames=edefns=earray=vend=bstk=vvend=estarr=estring;
#else
estdt=edefns=earray=vend=bstk=vvend=estarr=estring;
#endif
/* reset variable pointers */
eostring=ecore; /* string pointer */
srand(0); /* reset the random number */
} /* generator */
/*
* mtest() is used to set the amount of core for the current program
* it uses brk() to ask the system for more core.
* The core is allocated in 1K chunks, this is so that the program does
* not spend most of is time asking the system for more core and at the
* same time does not hog more core than is neccasary ( be friendly to
* the system ).
* Any test that is less than 'ecore' is though of as an error and
* so is any test greater than the size that seven memory management
* registers can handle.
* If there is this error then a test is done to see if 'ecore' can
* be accomodated. If so then that size is allocated and error() is called
* otherwise print a message and exit the interpreter.
* If the value of the call is less than 'ecore' we have a problem
* with the interpreter and we should cry for help. (It doesn't ).
*/
mtest(l)
memp l;
{
register memp m;
static memp maxmem; /* pointer to top of memory */
#ifdef ALIGN4
if( (int)l & 03){
prints("Illegal allignment\n");
quit();
}
#endif
m = (memp)(((int)l+MEMINC)&~MEMINC); /* round the size up */
if(m==maxmem) /* if allocated then return */
return;
if(m < ecore || m > MAXMEM || brk(m) == -1){ /* problems*/
m= (memp) (((int)ecore +DEFAULTSTRING+MEMINC )&~MEMINC);
if(m <= MAXMEM && brk(m)!= -1){
maxmem= m; /* oh, safe */
clear(DEFAULTSTRING); /* zap all pointers */
error(24); /* call error */
}
prints("out of core\n"); /* print message */
quit(); /* exit flushing buffers */
}
maxmem=m; /* set new limit */
}
/*
* This routine is called to test to see if there is enough space
* for an array. The result is true if there is no space left.
*/
nospace(l)
long l;
{
#ifndef pdp11
if(l< 0 || vvend+l >= MAXMEM)
#else
if(l< 0 || l >65535L || (long)vvend+l >= 0160000L)
#endif
return(1);
return(0); /* we have space */
}
/*
* This routine is called by the routines that define variables
* to increase the amount of space that is allocated between the
* two end pointers of that 'type'. It uses the fact that all the
* variable pointers are in a certain order (see bas.h ). It
* increments the relevent pointers and then moves up the rest of
* the data to a new position. It also clears the area that it
* has just allocated and then returns a pointer to the space.
*/
memp xpand(start,size)
register memp *start;
int size;
{
register short *p,*q;
short *bottom;
bottom = (short *) (*start);
p= (short *)vvend;
do{
*start++ += size;
}while( start <= &vvend);
mtest(vvend);
start= (memp *)bottom;
q= (short *)vvend;
do{
*--q = *--p;
}while(p > (short *)start);
do{
*--q=0;
}while(q > (short *)start);
return( (memp) start);
}
/*
* This routine tries to set up the system to catch all the signals that
* can be produced. (except kill ). and do something sensible if it
* gets one. ( There is no way of producing a core image through the
* sending of signals).
*/
#ifdef V6
#define _exit exit
#endif
catchsignal()
{
extern _exit(),quit1(),catchfp();
#ifdef SIGTSTP
extern onstop();
#endif
register int i;
static int (*traps[NSIG])()={
quit, /* hang up */
trap, /* cntrl-c */
quit1, /* cntrl-\ */
_exit,
_exit,
_exit,
_exit,
catchfp, /* fp exception */
0, /* kill */
seger, /* seg err */
mcore, /* bus err */
0,
_exit,
_exit,
_exit,
_exit,
_exit,
};
for(i=1;i<NSIG;i++)
signal(i,traps[i-1]);
#ifdef SIGTSTP
signal(SIGTSTP,onstop); /* the stop signal */
#endif
}
/*
* this routine deals with floating exceptions via fpfunc
* this is a function pointer set up in fpstart so that trapping
* can be done for floating point exceptions.
*/
catchfp()
{
extern (*fpfunc)();
signal(SIGFPE,catchfp); /* restart catching */
if(fpfunc== 0) /* this is set up in fpstart() */
_exit(1);
(*fpfunc)();
}
/*
* we have a segmentation violation and so should print the message and
* exit. Either a kill() from another process or an interpreter bug.
*/
seger()
{
prints("segmentation violation\n");
_exit(-1);
}
/*
* This does the same for bus errors as seger() does for segmentation
* violations. The interpreter is pretty nieve about the execution
* of complex expressions and should really check the stack every time,
* to see if there is space left. This is an easy error to fix, but
* it was not though worthwhile at the moment. If it runs out of stack
* space then there is a vain attempt to call mcore() that fails and
* so which produces another bus error and a core image.
*/
mcore()
{
prints("bus error\n");
_exit(-1);
}
/*
* Called by the cntrl-c signal (number 2 ). It sets 'trapped' to
* signify that there has been a cntrl-c and then re-enables the trap.
* It also bleeps at you.
*/
trap()
{
signal(SIGINT, SIG_IGN);/* ignore signal for the bleep */
write(1, "\07", 1); /* bleep */
signal(SIGINT, trap); /* re-enable the trap */
trapped=1; /* say we have had a cntrl-c */
#ifdef BSD42
if(ecalling){
ecalling = 0;
longjmp(ecall, 1);
}
#endif
}
/*
* called by cntrl-\ trap , It prints the message and then exits
* via quit() so flushing the buffers, and getting the terminal back
* in a sensible mode.
*/
quit1()
{
signal(SIGQUIT,SIG_IGN);/* ignore any more */
if(cursor){ /* put cursor on a new line */
prints(nl);
cursor=0;
}
prints("quit\n\r"); /* print the message */
quit(); /* exit */
}
/*
* resets the terminal , flushes all files then exits
* this is the standard route exit from the interpreter. The seger()
* and mcore() traps should not go through these traps since it could
* be the access to the files that is causing the error and so this
* would produce a core image.
* From this it may be gleened that I don't like core images.
*/
quit()
{
flushall(); /* flush the files */
rset_term(1);
if(cursor)
prints(nl);
exit(0); /* goodbye */
}
docont()
{
if(runmode){
contpos=0;
if(cancont){
bstk= vvend;
contpos=cancont;
}
else
bstk= vend;
}
cancont=0;
}
#ifdef SIGTSTP
/*
* support added for job control
*/
onstop()
{
flushall(); /* flush the files */
if(cursor){
prints(nl);
cursor = 0;
}
#ifdef BSD42
sigsetmask(0); /* Urgh !!!!!! */
#endif
signal(SIGTSTP, SIG_DFL);
kill(0,SIGTSTP);
/* The PC stops here */
signal(SIGTSTP,onstop);
}
#endif