MiniBasic
How to write a BASIC Interpreter By Malcolm Mclean
© Copyright all rights reserved (except for permission to...
58 downloads
1693 Views
382KB Size
Report
This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Report copyright / DMCA form
MiniBasic
How to write a BASIC Interpreter By Malcolm Mclean
© Copyright all rights reserved (except for permission to use source code as described in text)
Introduction MiniBasic is designed as a simple programming language, based on BASIC. If you already know BASIC then you are well on your way to learning MiniBasic, if you don’t then MiniBasic is one of the simplest programming languages to learn. MiniBasic programs are written in ASCII script. They are then interpreted by the computer. This is in contrast to most “serious” languages, which are compiled, that is, translated into machine instructions and then run. Interpreted languages are slower than compiled languages, but they have several advantages. One major one is that they are portable – a MiniBasic script will run on any computer that has a MiniBasic interpreter installed. Another advantage, especially for beginners, is that errors are much easier to identify. Finally, MiniBasic is not really intended as a standalone program, except for teaching purposes. It is meant for incorporation into other products, where the user is expected to provide functions in a general-purpose programming language. An example might be a desk calculator which can be extended to provide user-defined functions like the Fibonnaci series, or an adventure game for which the user can design his own levels. For technical reasons, this is much easier to implement as an interpreted rather than a compiled language. One design goal of MiniBasic was that it should be easy to learn. Millions of people already know some BASIC from school or through having a microcomputer in the 1980s. The second design goal was that it should be easy to implement. The interpreter is written in portable ANSI C, and is freely available. It is in a single, reasonable-length source, and is available for incorporation into user programs. The final goal is that the interpreter should be what is technically known as “Turing equivalent”. This means that it is possible to implement any algorithm in MiniBasic. This required one major extension to common Basic – the ability to redimension arrays. It is impossible to implement graphics commands in portable ANSI C, so sound, graphics, and mice are not supported in MiniBasic. Interaction with the user in the standalone model is via the console. However, where MiniBasic is incorporated into another program, generally there will not be direct interaction with the user. The caller will create temporary files for input and output.
2
The first program You are now ready to write your first program in MiniBasic. Traditionally, this is “Hello World”. Firstly you need to install the MiniBasic interpreter. On a PC this is done by copying the executable MiniBasic.exe to your hard drive. Then you open a text editor and type 10 PRINT “Hello World”
Remember to terminate with a newline. Save as “Hello.mb” (the extension is optional). You then call the interpreter by typing “MiniBasic Hello.mb” in a command prompt. You should see the output Hello World All MiniBasic programs have line numbers. Execution begins with the first line and ends with the last line. Lines must be in order and every statement must have a number. The number must be the first character in the line. However, we can spread long strings (sequences of characters) over several lines. This second program 10 PRINT “In the beginning God created the heavens and the Earth” “and the Earth was without form and void “
Will output a string too long to easily fit on one line. Note that the second line must begin with a space character, to indicate that it is a continuation of the first line.
3
The second program There is very little point in a program that outputs something but has no input. So for our second program we will use the command INPUT. 10 20 30 40 50
PRINT INPUT PRINT INPUT PRINT
“Input first number “ x “Input second number” y “X + Y is”, x + y
INPUT will get two numbers that you type in the command prompt. It ignores any non-numeric characters, and translates the first number that you see. The comma separates items to print, and also tells the computer to insert a space. It is also possible to input strings of characters. To do this, we use what is called a “string variable”. A string variable always ends with the dollar character ($), and contains text rather than numbers. 10 PRINT “What is your name?” 20 INPUT n$ 30 PRINT “Hello”, n$
When inputting a string, INPUT reads up to the newline (which it discards). We can use the ‘+’ operator, but not any others, on string variables. 10 20 30 40 50
PRINT INPUT PRINT INPUT PRINT
“What is your first name?” fname$ “What is your second name?” sname$ “Hello”, fname$ + sname$
Notice that this program has a bug. The ‘+’ operator doesn’t insert a space, so unless you inadvertently added a space the program prints “FREDBLOGGS”. Try modifying the program by inserting a space between the two names.
4
The third program Now we need to get to the core of MiniBasic, the “LET” statement. MiniBasic will evaluate arbitrarily complicated arithmetical expressions. The operators allowed are the familiar ‘+’ ‘-‘ ‘*’ and ‘/’, and also MOD (modulus). Use parentheses ‘(‘ ‘)’ to disambiguate the order of evaluation. 10 20 30 40
PRINT “Enter temperature in Fahrenheit” INPUT f LET c = (f – 32) / 1.8 PRINT “=”, c, “Celsius”
As well as these, there are a large number of mathematical functions built into MiniBasic, for example POW(x,y), which does exponentiation, SQRT(x) (square root), SIN(x), COS(x) and TAN(x), sine, cosine and tangent. All the trigonometric functions take or return radians. The logarithm function, LN(x), takes a natural logarithm. There are also two mathematical constants, PI and e (Euler’s number). Be careful not to use these as variable names. To convert radians to degrees, divide by 2 * PI and multiply by 360. To convert a natural, base e log to log10, divide by LN(10). LET also works on string variables. String variables always end with the character ‘$’, as do string functions. 10 20 30 40
PRINT “What is your name?” INPUT name$ LET name$ = “SIR” + “ “ + name$ PRINT “Arise,”, name$
Note that expressions such as LET x = x + 1
are legal and are in fact very useful. Variable names must be shorter than 31 characters, and mustn’t duplicate any MiniBasic keywords.
5
The fourth program Programs often need to make branching decisions. In MiniBasic this is provided by the IF ... THEN statement. 10 20 30 40 50 60
REM Square root program. PRINT “Enter a number” INPUT x REM Square root of negative is not allowed IF x < 0 THEN 20 PRINT “Square root of”, x, “is”, SQRT(x)
We have also introduced the REM statement. This simply adds comments to make the program easier for a human to understand. REM statements are ignored by the interpreter. This program is actually not very effective. Really we should tell the user what is wrong. For this, we need the GOTO statement. A GOTO simply executes an unconditional jump. 10 20 30 40 50 60 70 80
REM Improved square root program PRINT “Enter a number” INPUT x REM Prompt user if negative IF x >= 0 THEN 80 PRINT “Number may not be negative” GOTO 30 PRINT “Square root of ”, x, “is”, SQRT(x)
An IF ... THEN statement always takes a line number as an argument. This can be of the form IF x < y THEN b, as long as b holds a valid line number. The operators recognised by the IF ... THEN statement are ‘=’, ‘’ (not equals), ‘>’, ‘=’, ‘= 18 AND age < 65 THEN x
Use parentheses to disambiguate lengthy tests. IF age >= 18 AND (age < 65 OR job$ = “Caretaker”)
The IF ... THEN operators can also be applied to string variables. In this case the strings are ordered alphabetically.
6
The fifth program To enable MiniBasic to compute complicated functions we need access to arbitrary amounts of memory. For this we have the DIM statement. It creates a special type of variable known as an array. 10 REM Calendar program. 20 DIM months$(12) = "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" 30 PRINT "Day of birth?" 40 INPUT day 50 PRINT "Month?" 60 INPUT month 70 REM Make sure day and month are legal 80 IF day >= 1 AND day = 1 AND month , >=, =, < and = 0.0) answer = sqrt(answer); else seterror(ERR_NEGSQRT); break; default: seterror(ERR_SYNTAX); break; } return answer; } 49
This function, which is much simplified from the actual code, is a bit different. A factor consists of a number, most simply. So if we have the token VALUE, we examine the digits to obtain the number, match it to move the lexical anlyser on, and return the result. However a factor can also be a opening bracket, an expression, followed by a closing bracket. Therefore we have to call expr() recursively. Another complication is unary minus. A factor can be a minus sign, followed by another factor. We are disallowing unary plus, but it could be added in the same way. Finally, I have allowed for another complication, a function call to SQRT(). All the other mathematical functions can be added to the factor() function in a similar way. With the expr() function, we have our basic logic for an expression parser. The high-level function, double expression(const char *str)
would clear the error state, and set up the lexical analyser with the first token. It then calls expr() to get the result, and match(EOF). It then checks the error state, and if everything is correct, returns the result. Otherwise, it reports the error. The expression parser is the skeleton round which MiniBasic is built. It is trivial enough to add the factorial and MOD operators, more functions, and a few bells and whistles like e and PI. The next major complication comes when we allow the user to add variables. We need to allow program of the form LET x = 1 + 2 LET y = x * x
To implement this, we need the concept of the lvalue. An lvalue is something which can be assigned. Since BASIC, unlike C, does not require declaration of variables, a lvalue can be either a pre-existing variable, or one we have not encountered before.
50
If the lexical analyser hits an alphanumerical string that is not a keyword, it reports it as a FLTID ( floating point identifier ). We maintain a list of all scalar variables in the system, in the array variables. For convenience, string variables share the same space. When control reaches a LET statement, we check the identifier to see if it is already in use. If not, we add it. Then we assign it the value on the right hand side of the equals sign. The structure of a LET statement is therefore LET lvalue = expression To allow for expansion into dimensioned variables, the LVALUE structure contains a pointer to the data item to assign. Once we have assigned the variable, it becomes available as a part of an expression. Therefore the factor() routine has to be expanded to accommodate a FLTID. The function variable() matches a float identifier. Failure to find in the context of an expression is an error. It will be noted that the interpreter searches the expression list in a linear fashion. This could easily be the focus for algorithmic improvements. String expressions are basically simpler than arithmetical expressions. They do not introduce any new concepts, except that the parser has to know whether it is parsing a string or an arithmetical expression. This is where the LALR model could break down. PRINT x could require us to parse x as a numerical expression, or as a string expression. MiniBasic, like normal BASIC, gets round this by requiring all string variables to end with the character ‘$’. We therefore know whether we are dealing with a string or a numerical expression. Strings are allocated using malloc(). This is slow, but it allows for arbitrary length strings without gobbling too much memory. The next problem is flow control. Flow control is what distinguishes a programming language from a calculator. The BASIC method is to use line numbers. When the function is called, we do an initial pass through the script, to index all the line numbers. This is easy because every line must begin with a line number and end with a newline character, except that 51
MiniBasic allows for continuation lines, which are blank. If the lines are not in ascending order, we reject the program. Indexing lines this way allow for reasonably efficient jumps – otherwise we would have to read through the whole script in order to find the destination. Execution starts at the first line. We store the line number, in internal consecutive numbering, in curline. We parse one line at a time, and return the destination line number, or zero in the normal case of control simply incrementing. An expression can be converted to an internal line number, by doing a binary search on the line list. Therefore the GOTO statement consists of GOTO expression And the expression is simply evaluated and returned. It is actually simpler to allow for arbitrary numerical expressions in jump destinations, including ones computed at run time, though it wouldn’t be if we were writing a compiler rather than an interpreter. IF is the slightly more complicated form of the GOTO statement. I chose to use the canonical BASIC form of IF … THEN linenumber, because it is familiar to microcomputer programmers, though in fact it is a pain to use and the more modern IF … ENDIF is a lot more intuitive. The IF statement requires the introduction of relational expressions. Similarly to numerical expressions, these have precedence of ANDs and ORs, together with relational operators like ‘>’ and ‘=’. They also have to allow nested parentheses. A relational expression parser can be built in exactly the same way as an expression parser. In fact it needs to call the numerical expression parser IF expression > expression is a perfectly legitimate and unexceptional form of use. The other essential for a programming language is the use of vector variables. A huge number of operations, such as calculations of the mean, or sorting, rely on lists. It is of course easy to emulate any multi-dimensional array with a onedimensional array. In fact in C most data which is inherently two dimensional, such as image rasters, has to be treated as one-dimensional 52
because of limitations in the language. However BASIC programmers expect to be able to use multi-dimensional arrays. The DIM statement, of course, just calls malloc() internally. To simplify coding I restrict arrays to at most five dimensions, which is about the maximum that can be written out by hand. Even a big computer will quickly run out of memory if arrays get much bigger than this anyway. Allowing arbitrary dimensions forces you to roll the indexing into a loop over the dimensions, which gets headachy. This does complicate the variable system, because we have both scalar and dimensioned variables. However they can be distinguished by requiring all dimensioned variables to end with an opening parenthesis. This is done at the level of the lexical analyser. The use of the LVALUE structure helps to keep things under control – it simply points into the array. The whole point of dimensioning arrays is to iterate over them. This can be done using IF statements and keeping a counter, but it is clumsy. Unfortunately FOR … NEXT loops introduce other problems into the interpreter. They can be nested, so a stack of control structures has to be maintained. Then the loop is exited at the terminating NEXT statement, but the control is in the FOR, which leads to other problems, particularly because the user may not enter a script with nicely nested loops. Finally, there is the issue of what to do if a user jumps out of a loop. The solution, which is simple but not the most elegant, is to allow the user to mess around with flow control, but keep the FOR stack relatively small, and insist on the matching NEXT being labelled with the control variable. So bad control will rapidly either overflow the stack or cause a mismatch error. The problem is that there then is no way to break out of the FOR loop legitimately. The FOR loop searches for a matching NEXT if the loop is null, the advantage is that it allows for cleaner user scripts, though the fiddliness is probably more effort than it is worth. The step size and terminating expression is evaluated once only, on loop entry. The cost of changing this and providing C-style fors is that you then need two FOR-evaluation routines, one for loop entry and one for each update. Finally, every program needs IO. Microcomputers didn’t have any sort of worthwhile backing store, so the canonical file-handling functions were not very good. In the UNIX world, it is quite common to take everything from standard input and direct 53
everything to standard output, redirecting by means of pipes. In the PC world, users expect graphical interfaces to their file system, which of course is way beyond our capacity to provide. The PRINT statement is built on top of ANSI fprintf(). The form is that the user can specify either a string or a numerical variable, so we need a function, isstring() to distinguish between the two. The INPUT statement suffers from the problem of what to do if input doesn’t match. The solution for inputting numbers is to ignore nonnumerical input until a number is found. It is implemented in terms of fscanf(). For string input, the string is defined as the line. This is then limited to 1024 characters to allow for the call to fgets(). In practise I suspect that most users of MiniBasic would want to provide their own IO extensions. For instance, if you want to control a plotter, you would provide instructions like PENUP, PENDOWN, PENMOVE and functions to query the pen position. However you could use the program as it is – printing the statements to stdout would be interpreted by a calling function and translated to pen commands, whilst changes to the pen state would go on standard input. MiniBasic is yours to use as you want. I would like to be acknowledged if you find the code useful, either as is or as the basis for redevelopment, but I don’t insist on this. If it makes your boss happy to think that the code was developed all by yourself in the five minutes it took to download this book, then that is fine by me. You can incorporate it in free or commercial products without charge. The only thing I insist on is that you do not try to restrict my rights in the code in any way, which means that I can make use of any enhancements, bug fixes, or derivative products as I see fit.
54
The design of MiniBasic MiniBasic is designed to allow non-programmers to add arbitrary functions to programs. Imagine we are attempting to SPAM-shield an email program. Different users have different ideas of what constitutes SPAM. By providing checkboxes we can go so far, but if a user want, say to, regard as SPAM everything with an attachment, unless it is under a certain size, unless it has come from a trusted list of addresses, then we are stuck. However by providing a MiniBasic interface, the user can input the relevant values and provide the logic. The calling program would do this by setting up the input stream with, say, the email address of the sender, the title of the email, the length of any attachment. The calling program then presents half of a MiniBasic program to the user, with the input set up eg 10 20 30 40 50 60 70 80
REM SPAM filter REM sender’s email INPUT address$ REM tile of email INPUT title$ REM length of attachment INPUT attachlen REM PRINT “Accept” to accept the email or “Reject” to reject
The user then provides the logic for his choice.
MiniBasic can of course also be used as a stand-alone console programming language. This is useful for teaching purposes, for testing MiniBasic programs, or if you simply want to write a “filter” program that accepts from standard input and writes to standard output. It was important that MiniBasic be simple to learn, and simple to implement. For this reason the syntax of the language has been kept as
55
close as possible to the type of BASIC used on microcomputers in the 1980s. Millions of people know a BASIC of this kind. Because of advances in computer power since the 1980s array initialisation was allowed. This allows us to eliminate the difficult to use READ and DATA statements. Re-dimensioning of arrays was also allowed, largely for theoretical reasons (it turns MiniBasic into a Turing machine). GOSUB was not included. It is not of much practical use without local variables and parameters, and a functional language isn’t very useful without some mechanism for passing and returning vectors. Adding these would have complicated the design of the interpreter considerably, and moved the language away from original BASIC. PEEK and POKE are obviously hardware-dependent, add potential security risks, and were also not included. The C-language source to MiniBasic is included. The interface is int basic(const char *script, FILE *in, FILE *out, FILE *err) In the standalone program these are called with stdin, stdout, stderr. In a component environment, these will usually be temporary memory files, and the input will be set up with the parameters to the function the user is to write. The function returns 0 on success or non-zero on failure. The source code is portable ANSI C. With the exception of the CHR$() and ASCII() functions, which rely on the execution character set being ASCII. The relational operators for strings also call the function strcmp() internally, which may have implications on non-ASCII systems. An interpreted language is obviously not a particularly efficient way of running functions. Variables are stored in linear lists, with an O(N) access time, so big programs are O(N*N). However because of the lack of support for subroutines MiniBasic is not very suitable for complex programs anyway. If you were to extend the scope of the program to run very large scripts, it would be necessary to replace the variable list with a hash table, binary tree, or other structure that supports fast searching. All MiniBasic keywords, with the exception of e, start with uppercase letters. This fact is exploited to allow faster recognition of identifiers starting 56
with lower case. Users can use this feature to gain some performance advantage. On a fast computer the efficiency of MiniBasic shouldn’t be a major problem unless users run very processor-intensive scripts, or if the function is in a time-critical portion of code. In these cases the answer would be to move to a pseudo-compiler system, where the MiniBasic scripts are translated into an intermediate bytecode that is similar to machine language. This is a project for a later date. Since MiniBasic is available as C source, it is possible to extend the language. Where possible extensions should be in the form of functions rather than new statements, to avoid changing the grammar of the language. To add a numerical function, foo, which takes a numerical and a string argument, write the function like this foo – check the first character of a string FOO(85, “Useless function”) double foo(void) { double answer; double x; char *str; int ch; match(FOO); /* match the token for the function */ match(OPAREN); /*opening parenthesis */ x = expr(); /* read the numerical argument */ match(COMMA); /* comma separates arguments */ str = stringexpr(); /* read the string argument */ match(CPAREN); /* match close */ f(str == NULL) /* computer can run out of memory */ return 0; /* stringexpr() will have signalled the error so no point in generating another */ ch = integer(x); /* signal error if x isn’t an integer*/ if( !isalpha(ch) ) seterror(ERR_BADVALUE); /* signal an error of your if ch isn’t valid */ if(str[0] == ch) answer = 1.0; else answer = 2.0; free(str); malloc(), so free */ return answer;
/* function logic */
/* str is allocated with
57
}
Once you have your function, add an identifier for it, FOO, to the token list. Then to the functions gettoken() and tokenlen() add the appropriate lines. Finally to the function factor() add the code for calling your function. For string functions, the procedure is similar, except that they must return an allocated string. The convention is that they end with a dollar sign, and the token id ends with the sequence “STRING”. Add the call to the function stringexpr(), and add your symbol to the function isstring() so that statements like PRINT know that it generates a string expression. To change the input and output model, you need only change the functions doprint() and doinput(). If you wish to change the error system then you need to look at the functions setup(), reporterror() and the toplevel function basic(). Currently the program takes FILE pointers, which should be flexible enough for most uses, but not if say you want to provide for interactive scripts.
58
Hello World 10 REM Hello World program 20 PRINT "Hello World"
59
Name Handling 10 20 30 40 50
REM String-handling program REM Inputs a name, tests for validity REM and breaks up into parts. PRINT "Enter your full name" INPUT name$
60 REM First check for non-English characters 70 LET flag = 0 80 FOR I = 1 TO LEN(name$) 90 LET ch$ = MID$(name$, I,1) 100 IF (ch$ >= "A" AND ch$ "z" THEN return LET word$ = word$ + ch$ LET name$ = MID$(name$, 2, -1) GOTO 2020
3000 REM END
61
ROT13 10 REM ROT13 CODE 15 LET CODE$ = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz" 20 INPUT A$ 30 FOR I = 1 TO LEN(A$) 40 LET B$ = MID$(A$,I, 1) 50 LET TAR = INSTR(CODE$, B$, 1) 60 IF TAR = 0 THEN 90 70 LET TAR = (TAR + 26) MOD 52 80 LET B$ = MID$(CODE$, TAR, 1) 90 PRINT B$; 100 NEXT I 110 PRINT "" 120 GOTO 20
62
Median 10 REM Median program. 20 LET N = 0 30 DIM array(N+1) 40 PRINT "Enter a number, q to quit" 50 INPUT line$ 60 IF line$ = "q" THEN 100 70 LET N = N + 1 80 LET array(N) = VAL(line$) 90 GOTO 30 100 PRINT N, "numbers entered" 105 IF N = 0 THEN 1000 106 IF N = 1 THEN 210 110 REM Bubble sort the numbers 120 LET flag = 0 130 LET i = 1 140 IF array(i) #include <stdlib.h> #include "basic.h" char *loadfile(char *path); /* here is a simple script to play with */ char *script = "10 REM Test Script\n" "20 REM Tests the Interpreter\n" "30 REM By Malcolm Mclean\n" "35 PRINT \"HERE\" \n" "40 PRINT INSTR(\"FRED\", \"ED\", 4)\n" "50 PRINT VALLEN(\"12a\"), VALLEN(\"xyz\")\n" "60 LET x = SQRT(3.0) * SQRT(3.0)\n" "65 LET x = INT(x + 0.5)\n" "70 PRINT MID$(\"1234567890\", x, -1)\n" ; void usage(void) { printf("MiniBasic: a BASIC interpreter\n"); printf("usage:\n"); printf("Basic <script>\n"); printf("See documentation for BASIC syntax.\n"); exit(EXIT_FAILURE); } /* call with the name of the Minibasic script file */ int main(int argc, char **argv) { char *scr; if(argc == 1) { /* comment out usage call to run test script */ usage(); basic(script, stdin, stdout, stderr); }
65
else { scr = loadfile(argv[1]); if(scr) { basic(scr, stdin, stdout, stderr); free(scr); } } return 0; } /* function to slurp in an ASCII file Params: path - path to file Returns: malloced string containing whole file */ char *loadfile(char *path) { FILE *fp; int ch; long i = 0; long size = 0; char *answer; fp = fopen(path, "r"); if(!fp) { printf("Can't open %s\n", path); return 0; } fseek(fp, 0, SEEK_END); size = ftell(fp); fseek(fp, 0, SEEK_SET); answer = malloc(size + 100); if(!answer) { printf("Out of memory\n"); fclose(fp); return 0; } while( (ch = fgetc(fp)) != EOF) answer[i++] = ch; answer[i++] = 0; fclose(fp); return answer; }
66
#ifndef basic_h #define basic_h /* Minibasic header file By Malcolm Mclean */ int basic(const char *script, FILE *in, FILE *out, FILE *err); #endif
67
/******************************************************** * Mini BASIC * * by Malcolm McLean * * version 1.0 * ********************************************************/ #include #include #include #include #include #include #include #include
<stdio.h> <stdlib.h> <string.h> <stdarg.h> <math.h>
/* tokens defined */ #define EOS 0 #define VALUE 1 #define PI 2 #define E 3 #define #define #define #define #define #define #define #define #define
DIV 10 MULT 11 OPAREN 12 CPAREN 13 PLUS 14 MINUS 15 SHRIEK 16 COMMA 17 MOD 200
#define #define #define #define #define #define #define #define #define #define #define
ERROR 20 EOL 21 EQUALS 22 STRID 23 FLTID 24 DIMFLTID 25 DIMSTRID 26 QUOTE 27 GREATER 28 LESS 29 SEMICOLON 30
#define #define #define #define #define
PRINT 100 LET 101 DIM 102 IF 103 THEN 104
68
#define #define #define #define #define #define #define #define #define
AND 105 OR 106 GOTO 107 INPUT 108 REM 109 FOR 110 TO 111 NEXT 112 STEP 113
#define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define
SIN 5 COS 6 TAN 7 LN 8 POW 9 SQRT 18 ABS 201 LEN 202 ASCII 203 ASIN 204 ACOS 205 ATAN 206 INT 207 RND 208 VAL 209 VALLEN 210 INSTR 211
#define #define #define #define #define #define
CHRSTRING 300 STRSTRING 301 LEFTSTRING 302 RIGHTSTRING 303 MIDSTRING 304 STRINGSTRING 305
/* relational operators defined */ #define #define #define #define #define #define
ROP_EQ 1 ROP_NEQ 2 ROP_LT 3 ROP_LTE 4 ROP_GT 5 ROP_GTE 6
/* /* /* /* /* /*
equals */ doesn't equal */ less than */ less than or equals */ greater than */ greater than or equals */
/* error codes (in BASIC script) defined */ #define ERR_CLEAR 0 #define ERR_SYNTAX 1 #define ERR_OUTOFMEMORY 2 #define ERR_IDTOOLONG 3 #define ERR_NOSUCHVARIABLE 4 #define ERR_BADSUBSCRIPT 5
69
#define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define
ERR_TOOMANYDIMS 6 ERR_TOOMANYINITS 7 ERR_BADTYPE 8 ERR_TOOMANYFORS 9 ERR_NONEXT 10 ERR_NOFOR 11 ERR_DIVIDEBYZERO 12 ERR_NEGLOG 13 ERR_NEGSQRT 14 ERR_BADSINCOS 15 ERR_EOF 16 ERR_ILLEGALOFFSET 17 ERR_TYPEMISMATCH 18 ERR_INPUTTOOLONG 19 ERR_BADVALUE 20 ERR_NOTINT 21
#define MAXFORS 32 typedef struct { int no; const char *str; }LINE; typedef struct { char id[32]; double dval; char *sval; (malloced) */ } VARIABLE; typedef struct { char id[32]; int type; int ndims; int dim[5]; char **str; double *dval; } DIMVAR; typedef struct { int type; or FLTID or ERROR) */ char **sval; double *dval; } LVALUE;
/* maximum number of nested fors */
/* line number */ /* points to start of line */
/* id of variable */ /* its value if a real */ /* its value if a string
/* /* /* /* /* /*
id of dimensioned variable */ its type, STRID or FLTID */ number of dimensions */ dimensions in x y order */ pointer to string data */ pointer to real data */
/* type of variable (STRID /* pointer to string data */ /* pointer to real data */
70
typedef struct { char id[32]; int nextline; control passes */ double toval; double step; } FORLOOP;
/* id of control variable */ /* line below FOR to which /* terminal value */ /* step size */
static FORLOOP forstack[MAXFORS]; control */ static int nfors; stack */
/* stack for for loop /* number of fors on
static VARIABLE *variables; variables */ static int nvariables; */
/* the script's
static DIMVAR *dimvariables; */ static int ndimvariables; dimensioned arrays */
/* dimensioned arrays
/* number of variables
/* number of
static LINE *lines; starts */ static int nlines; BASIC lines in program */
/* list of line /* number of
static FILE *fpin; static FILE *fpout; static FILE *fperr;
/* input stream */ /* output strem */ /* error stream */
static const char *string; parsing */ static int token; (lookahead) */ static int errorflag; input encountered */
/* string we are /* current token /* set when error in
static int setup(const char *script); static void cleanup(void); static void reporterror(int lineno); static int findline(int no); static static static static
int line(void); void doprint(void); void dolet(void); void dodim(void);
71
static static static static static static
int doif(void); int dogoto(void); void doinput(void); void dorem(void); int dofor(void); int donext(void);
static void lvalue(LVALUE *lv); static int boolexpr(void); static int boolfactor(void); static int relop(void);
static static static static static static
double double double double double double
expr(void); term(void); factor(void); instr(void); variable(void); dimvariable(void);
static static static static static static static
VARIABLE *findvariable(const char *id); DIMVAR *finddimvar(const char *id); DIMVAR *dimension(const char *id, int ndims, ...); void *getdimvar(DIMVAR *dv, ...); VARIABLE *addfloat(const char *id); VARIABLE *addstring(const char *id); DIMVAR *adddimvar(const char *id);
static static static static static static static static static static
char char char char char char char char char char
*stringexpr(void); *chrstring(void); *strstring(void); *leftstring(void); *rightstring(void); *midstring(void); *stringstring(void); *stringdimvar(void); *stringvar(void); *stringliteral(void);
static int integer(double x); static static static static static
void match(int tok); void seterror(int errorcode); int getnextline(const char *str); int gettoken(const char *str); int tokenlen(const char *str, int token);
static int isstring(int token); static double getvalue(const char *str, int *len);
72
static void getid(const char *str, char *out, int *len); static static static static static *cat); static
void mystrgrablit(char *dest, const char *src); char *mystrend(const char *str, char quote); int mystrcount(const char *str, char ch); char *mystrdup(const char *str); char *mystrconcat(const char *str, const char double factorial(double x);
/* Interpret a BASIC script Params: script - the script to run in - input stream out - output stream err - error stream Returns: 0 on success, 1 on error condition. */ int basic(const char *script, FILE *in, FILE *out, FILE *err) { int curline = 0; int nextline; int answer = 0; fpin = in; fpout = out; fperr = err; if( setup(script) == -1 ) return 1; while(curline != -1) { string = lines[curline].str; token = gettoken(string); errorflag = 0; nextline = line(); if(errorflag) { reporterror(lines[curline].no); answer = 1; break; } if(nextline == -1) break; if(nextline == 0)
73
{ curline++; if(curline == nlines) break; } else { curline = findline(nextline); if(curline == -1) { if(fperr) fprintf(fperr, "line %d not found\n", nextline); answer = 1; break; } } } cleanup(); return answer; } /* Sets up all our globals, including the list of lines. Params: script - the script passed by the user Returns: 0 on success, -1 on failure */ static int setup(const char *script) { int i; nlines = mystrcount(script, '\n'); lines = malloc(nlines * sizeof(LINE)); if(!lines) { if(fperr) fprintf(fperr, "Out of memory\n"); return -1; } for(i=0;i= dv->dim[i] || index[i] < 0) { seterror(ERR_BADSUBSCRIPT); return 0; } if(dv->type == FLTID) { switch(dv->ndims) { case 1: answer = &dv->dval[ index[0] ]; break; case 2:
106
answer = &dv->dval[ index[1] * dv->dim[0] + index[0] ]; break; case 3: answer = &dv->dval[ index[2] * (dv->dim[0] * dv>dim[1]) + index[1] * dv->dim[0] + index[0] ]; break; case 4: answer = &dv->dval[ index[3] * (dv->dim[0] + dv->dim[1] + dv->dim[2]) + index[2] * (dv->dim[0] * dv->dim[1]) + index[1] * dv->dim[0] + index[0] ]; case 5: answer = &dv->dval[ index[4] * (dv->dim[0] + dv->dim[1] + dv->dim[2] + dv->dim[3]) + index[3] * (dv->dim[0] + dv->dim[1] + dv>dim[2]) + index[2] * (dv->dim[0] + dv->dim[1]) + index[1] * dv->dim[0] + index[0] ]; break; } } else if(dv->type = STRID) { switch(dv->ndims) { case 1: answer = &dv->str[ index[0] ]; break; case 2: answer = &dv->str[ index[1] * dv->dim[0] + index[0] ]; break; case 3: answer = &dv->str[ index[2] * (dv->dim[0] * dv>dim[1]) + index[1] * dv->dim[0] + index[0] ]; break; case 4: answer = &dv->str[ index[3] * (dv->dim[0] + dv>dim[1] + dv->dim[2]) + index[2] * (dv->dim[0] * dv->dim[1]) + index[1] * dv->dim[0] + index[0] ]; case 5:
107
answer = &dv->str[ index[4] * (dv->dim[0] + dv>dim[1] + dv->dim[2] + dv->dim[3]) + index[3] * (dv->dim[0] + dv->dim[1] + dv>dim[2]) + index[2] * (dv->dim[0] + dv->dim[1]) + index[1] * dv->dim[0] + index[0] ]; break; } } return answer; } /* add a real varaible to our variable list Params: id - id of varaible to add. Returns: pointer to new entry in table */ static VARIABLE *addfloat(const char *id) { VARIABLE *vars; vars = realloc(variables, (nvariables + 1) * sizeof(VARIABLE)); if(vars) { variables = vars; strcpy(variables[nvariables].id, id); variables[nvariables].dval = 0; variables[nvariables].sval = 0; nvariables++; return &variables[nvariables-1]; } else seterror(ERR_OUTOFMEMORY); return 0; }
108
/* add a string variable to table. Params: id - id of variable to get (including trailing $) Retruns: pointer to new entry in table, 0 on fail. */ static VARIABLE *addstring(const char *id) { VARIABLE *vars; vars = realloc(variables, (nvariables + 1) * sizeof(VARIABLE)); if(vars) { variables = vars; strcpy(variables[nvariables].id, id); variables[nvariables].sval = 0; variables[nvariables].dval = 0; nvariables++; return &variables[nvariables-1]; } else seterror(ERR_OUTOFMEMORY); return 0; } /* add a new array to our symbol table. Params: id - id of array (include leading () Returns: pointer to new entry, 0 on fail. */ static DIMVAR *adddimvar(const char *id) { DIMVAR *vars; vars = realloc(dimvariables, (ndimvariables + 1) * sizeof(DIMVAR)); if(vars) { dimvariables = vars; strcpy(dimvariables[ndimvariables].id, id); dimvariables[ndimvariables].dval = 0; dimvariables[ndimvariables].str = 0; dimvariables[ndimvariables].ndims = 0; dimvariables[ndimvariables].type = strchr(id, '$') ? STRID : FLTID; ndimvariables++; return &dimvariables[ndimvariables-1];
109
} else seterror(ERR_OUTOFMEMORY); return 0; } /* high level string parsing function. Returns: a malloced pointer, or 0 on error condition. caller must free! */ static { char char char
char *stringexpr(void) *left; *right; *temp;
switch(token) { case DIMSTRID: left = mystrdup(stringdimvar()); break; case STRID: left = mystrdup(stringvar()); break; case QUOTE: left = stringliteral(); break; case CHRSTRING: left = chrstring(); break; case STRSTRING: left = strstring(); break; case LEFTSTRING: left = leftstring(); break; case RIGHTSTRING: left = rightstring(); break; case MIDSTRING: left = midstring(); break; case STRINGSTRING: left = stringstring(); break; default: if(!isstring(token)) seterror(ERR_TYPEMISMATCH); else
110
seterror(ERR_SYNTAX); return mystrdup(""); } if(!left) { seterror(ERR_OUTOFMEMORY); return 0; } switch(token) { case PLUS: match(PLUS); right = stringexpr(); if(right) { temp = mystrconcat(left, right); free(right); if(temp) { free(left); left = temp; } else seterror(ERR_OUTOFMEMORY); } else seterror(ERR_OUTOFMEMORY); break; default: return left; } return left; } /* parse the CHR$ token */ static char *chrstring(void) { double x; char buff[6]; char *answer; match(CHRSTRING); match(OPAREN); x = integer( expr() ); match(CPAREN);
111
buff[0] = (char) x; buff[1] = 0; answer = mystrdup(buff); if(!answer) seterror(ERR_OUTOFMEMORY); return answer; } /* parse the STR$ token */ static char *strstring(void) { double x; char buff[64]; char *answer; match(STRSTRING); match(OPAREN); x = expr(); match(CPAREN); sprintf(buff, "%g", x); answer = mystrdup(buff); if(!answer) seterror(ERR_OUTOFMEMORY); return answer; } /* parse the LEFT$ token */ static char *leftstring(void) { char *str; int x; char *answer; match(LEFTSTRING); match(OPAREN); str = stringexpr(); if(!str) return 0; match(COMMA); x = integer( expr() ); match(CPAREN); if(x > (int) strlen(str)) return str;
112
if(x < 0) { seterror(ERR_ILLEGALOFFSET); return str; } str[x] = 0; answer = mystrdup(str); free(str); if(!answer) seterror(ERR_OUTOFMEMORY); return answer; } /* parse the RIGHT$ token */ static char *rightstring(void) { int x; char *str; char *answer; match(RIGHTSTRING); match(OPAREN); str = stringexpr(); if(!str) return 0; match(COMMA); x = integer( expr() ); match(CPAREN); if( x > (int) strlen(str)) return str; if(x < 0) { seterror(ERR_ILLEGALOFFSET); return str; } answer = mystrdup( &str[strlen(str) - x] ); free(str); if(!answer) seterror(ERR_OUTOFMEMORY); return answer; }
113
/* parse the MID$ token */ static char *midstring(void) { char *str; int x; int len; char *answer; char *temp; match(MIDSTRING); match(OPAREN); str = stringexpr(); match(COMMA); x = integer( expr() ); match(COMMA); len = integer( expr() ); match(CPAREN); if(!str) return 0; if(len == -1) len = strlen(str) - x + 1; if( x > (int) strlen(str) || len < 1) { free(str); answer = mystrdup(""); if(!answer) seterror(ERR_OUTOFMEMORY); return answer; } if(x < 1.0) { seterror(ERR_ILLEGALOFFSET); return str; } temp = &str[x-1]; answer = malloc(len + 1); if(!answer) { seterror(ERR_OUTOFMEMORY); return str; } strncpy(answer, temp, len);
114
answer[len] = 0; free(str); return answer; } /* parse the string$ token */ static char *stringstring(void) { int x; char *str; char *answer; int len; int N; int i; match(STRINGSTRING); match(OPAREN); x = integer( expr() ); match(COMMA); str = stringexpr(); match(CPAREN); if(!str) return 0; N = x; if(N < 1) { free(str); answer = mystrdup(""); if(!answer) seterror(ERR_OUTOFMEMORY); return answer; } len = strlen(str); answer = malloc( N * len + 1 ); if(!answer) { free(str); seterror(ERR_OUTOFMEMORY); return 0; } for(i=0; i < N; i++) { strcpy(answer + len * i, str); }
115
free(str); return answer; } /* read a dimensioned string variable from input. Returns: pointer to string (not malloced) */ static char *stringdimvar(void) { char id[32]; int len; DIMVAR *dimvar; char **answer; int index[5]; getid(string, id, &len); match(DIMSTRID); dimvar = finddimvar(id); if(dimvar) { switch(dimvar->ndims) { case 1: index[0] = integer( expr() ); answer = getdimvar(dimvar, index[0]); break; case 2: index[0] = integer( expr() ); match(COMMA); index[1] = integer( expr() ); answer = getdimvar(dimvar, index[0], index[1]); break; case 3: index[0] = integer( expr() ); match(COMMA); index[1] = integer( expr() ); match(COMMA); index[2] = integer( expr() ); answer = getdimvar(dimvar, index[0], index[1], index[2]); break; case 4: index[0] = integer( expr() ); match(COMMA); index[1] = integer( expr() ); match(COMMA); index[2] = integer( expr() ); match(COMMA);
116
index[3] = integer( expr() answer = getdimvar(dimvar, index[2], index[3]); break; case 5: index[0] = integer( expr() match(COMMA); index[1] = integer( expr() match(COMMA); index[2] = integer( expr() match(COMMA); index[3] = integer( expr() match(COMMA); index[4] = integer( expr() answer = getdimvar(dimvar, index[2], index[3], index[4]); break;
); index[0], index[1],
); ); ); ); ); index[0], index[1],
} match(CPAREN); } else seterror(ERR_NOSUCHVARIABLE); if(!errorflag) if(*answer) return *answer; return ""; } /* parse a string variable. Returns: pointer to string (not malloced) */ static char *stringvar(void) { char id[32]; int len; VARIABLE *var; getid(string, id, &len); match(STRID); var = findvariable(id); if(var) { if(var->sval) return var->sval; return ""; }
117
seterror(ERR_NOSUCHVARIABLE); return ""; } /* parse a string literal Returns: malloced string literal Notes: newlines aren't allwed in literals, but blind concatenation across newlines is. */ static char *stringliteral(void) { int len = 1; char *answer = 0; char *temp; char *substr; char *end; while(token == QUOTE) { while(isspace(*string)) string++; end = mystrend(string, '"'); if(end) { len = end - string; substr = malloc(len); if(!substr) { seterror(ERR_OUTOFMEMORY); return answer; } mystrgrablit(substr, string); if(answer) { temp = mystrconcat(answer, substr); free(substr); free(answer); answer = temp; if(!answer) { seterror(ERR_OUTOFMEMORY); return answer; } } else answer = substr; string = end; } else
118
{ seterror(ERR_SYNTAX); return answer; } match(QUOTE); } return answer; } /* cast a double to an integer, triggering errors if out of range */ static int integer(double x) { if( x < INT_MIN || x > INT_MAX ) seterror( ERR_BADVALUE ); if( x != floor(x) ) seterror( ERR_NOTINT ); return (int) x; } /* check that we have a token of the passed type (if not set the errorflag) Move parser on to next token. Sets token and string. */ static void match(int tok) { if(token != tok) { seterror(ERR_SYNTAX); return; } while(isspace(*string)) string++; string += tokenlen(string, token); token = gettoken(string); if(token == ERROR) seterror(ERR_SYNTAX); }
119
/* set the errorflag. Params: errorcode - the error. Notes: ignores error cascades */ static void seterror(int errorcode) { if(errorflag == 0 || errorcode == 0) errorflag = errorcode; } /* get the next line number Params: str - pointer to parse string Returns: line no of next line, 0 if end Notes: goes to newline, then finds first line starting with a digit. */ static int getnextline(const char *str) { while(*str) { while(*str && *str != '\n') str++; if(*str == 0) return 0; str++; if(isdigit(*str)) return atoi(str); } return 0; } /* get a token from the string Params: str - string to read token from Notes: ignores white space between tokens */ static int gettoken(const char *str) { while(isspace(*str)) str++; if(isdigit(*str)) return VALUE; switch(*str) { case 0:
120
return EOS; case '\n': return EOL; case '/': return DIV; case '*': return MULT; case '(': return OPAREN; case ')': return CPAREN; case '+': return PLUS; case '-': return MINUS; case '!': return SHRIEK; case ',': return COMMA; case ';': return SEMICOLON; case '"': return QUOTE; case '=': return EQUALS; case '': return GREATER; default: if(!strncmp(str, "e", 1) && !isalnum(str[1])) return E; if(isupper(*str)) { if(!strncmp(str, "SIN", 3) && !isalnum(str[3])) return SIN; if(!strncmp(str, "COS", 3) && !isalnum(str[3])) return COS; if(!strncmp(str, "TAN", 3) && !isalnum(str[3])) return TAN; if(!strncmp(str, "LN", 2) && !isalnum(str[2])) return LN; if(!strncmp(str, "POW", 3) && !isalnum(str[3])) return POW; if(!strncmp(str, "PI", 2) && !isalnum(str[2])) return PI; if(!strncmp(str, "SQRT", 4) && !isalnum(str[4])) return SQRT; if(!strncmp(str, "PRINT", 5) && !isalnum(str[5])) return PRINT; if(!strncmp(str, "LET", 3) && !isalnum(str[3]))
121
return LET; if(!strncmp(str, return DIM; if(!strncmp(str, return IF; if(!strncmp(str, return THEN; if(!strncmp(str, return AND; if(!strncmp(str, return OR; if(!strncmp(str, return GOTO; if(!strncmp(str, return INPUT; if(!strncmp(str, return REM; if(!strncmp(str, return FOR; if(!strncmp(str, return TO; if(!strncmp(str, return NEXT; if(!strncmp(str, return STEP;
"DIM", 3) && !isalnum(str[3])) "IF", 2) && !isalnum(str[2])) "THEN", 4) && !isalnum(str[4])) "AND", 3) && !isalnum(str[3])) "OR", 2) && !isalnum(str[2])) "GOTO", 4) && !isalnum(str[4])) "INPUT", 5) && !isalnum(str[5])) "REM", 3) && !isalnum(str[3])) "FOR", 3) && !isalnum(str[3])) "TO", 2) && !isalnum(str[2])) "NEXT", 4) && !isalnum(str[4])) "STEP", 4) && !isalnum(str[4]))
if(!strncmp(str, "MOD", 3) && !isalnum(str[3])) return MOD; if(!strncmp(str, "ABS", 3) && !isalnum(str[3])) return ABS; if(!strncmp(str, "LEN", 3) && !isalnum(str[3])) return LEN; if(!strncmp(str, "ASCII", 5) && !isalnum(str[5])) return ASCII; if(!strncmp(str, "ASIN", 4) && !isalnum(str[4])) return ASIN; if(!strncmp(str, "ACOS", 4) && !isalnum(str[4])) return ACOS; if(!strncmp(str, "ATAN", 4) && !isalnum(str[4])) return ATAN; if(!strncmp(str, "INT", 3) && !isalnum(str[3])) return INT; if(!strncmp(str, "RND", 3) && !isalnum(str[3])) return RND; if(!strncmp(str, "VAL", 3) && !isalnum(str[3])) return VAL; if(!strncmp(str, "VALLEN", 6) && !isalnum(str[6])) return VALLEN; if(!strncmp(str, "INSTR", 5) && !isalnum(str[5])) return INSTR;
122
if(!strncmp(str, "CHR$", 4)) return CHRSTRING; if(!strncmp(str, "STR$", 4)) return STRSTRING; if(!strncmp(str, "LEFT$", 5)) return LEFTSTRING; if(!strncmp(str, "RIGHT$", 6)) return RIGHTSTRING; if(!strncmp(str, "MID$", 4)) return MIDSTRING; if(!strncmp(str, "STRING$", 7)) return STRINGSTRING; } /* end isupper() */ if(isalpha(*str)) { while(isalnum(*str)) str++; switch(*str) { case '$': return str[1] == '(' ? DIMSTRID : STRID; case '(': return DIMFLTID; default: return FLTID; } } return ERROR; } } /* get the length of a token. Params: str - pointer to the string containing the token token - the type of the token read Returns: length of the token, or 0 for EOL to prevent it being read past. */ static int tokenlen(const char *str, int token) { int len = 0; char buff[32]; switch(token) { case EOS: return 0;
123
case EOL: return 1; case VALUE: getvalue(str, &len); return len; case DIMSTRID: case DIMFLTID: case STRID: getid(str, buff, &len); return len; case FLTID: getid(str, buff, &len); return len; case PI: return 2; case E: return 1; case SIN: return 3; case COS: return 3; case TAN: return 3; case LN: return 2; case POW: return 3; case SQRT: return 4; case DIV: return 1; case MULT: return 1; case OPAREN: return 1; case CPAREN: return 1; case PLUS: return 1; case MINUS: return 1; case SHRIEK: return 1; case COMMA: return 1; case QUOTE: return 1; case EQUALS: return 1; case LESS: return 1;
124
case GREATER: return 1; case SEMICOLON: return 1; case ERROR: return 0; case PRINT: return 5; case LET: return 3; case DIM: return 3; case IF: return 2; case THEN: return 4; case AND: return 3; case OR: return 2; case GOTO: return 4; case INPUT: return 5; case REM: return 3; case FOR: return 3; case TO: return 2; case NEXT: return 4; case STEP: return 4; case MOD: return 3; case ABS: return 3; case LEN: return 3; case ASCII: return 5; case ASIN: return 4; case ACOS: return 4; case ATAN: return 4; case INT: return 3; case RND:
125
return 3; case VAL: return 3; case VALLEN: return 6; case INSTR: return 5; case CHRSTRING: return 4; case STRSTRING: return 4; case LEFTSTRING: return 5; case RIGHTSTRING: return 6; case MIDSTRING: return 4; case STRINGSTRING: return 7; default: assert(0); return 0; } } /* test if a token represents a string expression Params: token - token to test Returns: 1 if a string, else 0 */ static int isstring(int token) { if(token == STRID || token == QUOTE || token == DIMSTRID || token == CHRSTRING || token == STRSTRING || token == LEFTSTRING || token == RIGHTSTRING || token == MIDSTRING || token == STRINGSTRING) return 1; return 0; }
126
/* get a numerical value from the parse string Params: str - the string to search len - return pinter for no chars read Retuns: the value of the string. */ static double getvalue(const char *str, int *len) { double answer; char *end; answer = strtod(str, &end); assert(end != str); *len = end - str; return answer; } /* getid - get an id from the parse string: Params: str - string to search out - id output [32 chars max ] len - return pointer for id length Notes: triggers an error if id > 31 chars the id includes the $ and ( qualifiers. */ static void getid(const char *str, char *out, int *len) { int nread = 0; while(isspace(*str)) str++; assert(isalpha(*str)); while(isalnum(*str)) { if(nread < 31) out[nread++] = *str++; else { seterror(ERR_IDTOOLONG); break; } } if(*str == '$') { if(nread < 31) out[nread++] = *str++; else seterror(ERR_IDTOOLONG); } if(*str == '(') {
127
if(nread < 31) out[nread++] = *str++; else seterror(ERR_IDTOOLONG); } out[nread] = 0; *len = nread; }
/* grab a literal from the parse string. Params: dest - destination string src - source string Notes: strings are in quotes, double quotes the escape */ static void mystrgrablit(char *dest, const char *src) { assert(*src == '"'); src++; while(*src) { if(*src == '"') { if(src[1] == '"') { *dest++ = *src; src++; src++; } else break; } else *dest++ = *src++; } *dest++ = 0; }
128
/* find where a source string literal ends Params: src - string to check (must point to quote) quote - character to use for quotation Returns: pointer to quote which ends string Notes: quotes escape quotes */ static char *mystrend(const char *str, char quote) { assert(*str == quote); str++; while(*str) { while(*str != quote) { if(*str == '\n' || *str == 0) return 0; str++; } if(str[1] == quote) str += 2; else break; } return (char *) (*str? str : 0); } /* Count the instances of ch in str Params: str - string to check ch - character to count Returns: no time chs occurs in str. */ static int mystrcount(const char *str, char ch) { int answer = 0; while(*str) { if(*str++ == ch) answer++; } return answer; }
129
/* duplicate a string: Params: str - string to duplicate Returns: malloced duplicate. */ static char *mystrdup(const char *str) { char *answer; answer = malloc(strlen(str) + 1); if(answer) strcpy(answer, str); return answer; } /* concatenate two strings Params: str - firsts string cat - second string Returns: malloced string. */ static char *mystrconcat(const char *str, const char *cat) { int len; char *answer; len = strlen(str) + strlen(cat); answer = malloc(len + 1); if(answer) { strcpy(answer, str); strcat(answer, cat); } return answer; }
130
/* compute x! */ static double factorial(double x) { double answer = 1.0; double t; if( x > 1000.0) x = 1000.0; for(t=1;t