|
|
1.1 ! root 1: .nr Cl 7 ! 2: .ND "October 29, 1979" ! 3: .TL "20239-7048" "40295-2" ! 4: Guide To The Internals Of ! 5: .I Bite ! 6: .AU "R. B. Drake" RBD WH 2425 4163 8C-005 ! 7: .TM 79-2425-5 ! 8: .AS 1 ! 9: .I Bite, ! 10: .B B asic ! 11: .B I nterpreter ! 12: for ! 13: .B T esting ! 14: and ! 15: .B E ngineering, ! 16: with very few exceptions, follows the syntax of the original Dartmouth ! 17: .I BASIC ! 18: It is unique because it is written in C, ! 19: it's source code is available, new commands to fit any desired purpose ! 20: can be written in C and easily added, ! 21: or for that matter, any existing routine such as those in section 3 of ! 22: the ! 23: .I "UNIX Users Manual" ! 24: can be linked in with ! 25: .I Bite . ! 26: The authors purpose was to ! 27: provide an interpretive language to control automated test sets where ! 28: the basic control flow operations, ! 29: .I Print ! 30: statements, ! 31: .I If ! 32: statements ! 33: etc. would be standard ! 34: .I BASIC ! 35: but where additional commands to control ! 36: test instruments would be added as needed. ! 37: With this approach, the language can be tailored to fit virtually any test ! 38: set configuration, or molded to perform any ! 39: kind of task where an interpretive language is suitable. ! 40: .P ! 41: Presented here is a tutorial which describes in detail how to add/remove ! 42: commands and functions to/from the language and how to use the vital internal ! 43: functions which store and retrieve variables, alter program flow, parse ! 44: expressions, ! 45: evaluate expressions, print diagnostic messages etc. ! 46: .AE ! 47: .OK "Basic" "Interpreters" "Bite" "Automated Testing" "Product Evaluation Project" ! 48: .MT 1 ! 49: .SA 0 ! 50: .H 1 INTRODUCTION ! 51: .H 2 Purpose ! 52: This memorandum is for the C language programmer who wishes to modify or ! 53: extend ! 54: .I bite ! 55: in order to tailor it to some special purpose. It is presumed that the ! 56: reader is already fluent in C and ! 57: .I UNIX* ! 58: .FS * ! 59: UNIX is a Trademark of Bell Laboratories. ! 60: .FE ! 61: and familiar with ! 62: .I bite .\*F ! 63: .FS ! 64: Hawkins J. P.,"BITE Users Guide",BTL,TM-79-2425-4 ! 65: .FE ! 66: We will follow the method of tutoring by example so that when the reader ! 67: is finished he should be able to: ! 68: .BL ! 69: .LI ! 70: Add or remove commands from the language. ! 71: .LI ! 72: Add or remove function subroutines. ! 73: .LI ! 74: Understand how to use certain important internal function subroutines. ! 75: .LI ! 76: Have a general understanding of how ! 77: .I bite ! 78: works internally. ! 79: .LI ! 80: Have a general understanding of why the authors made certain decisions ! 81: regarding the way ! 82: .I bite ! 83: is implemented. ! 84: .H 1 "BITE RELEASE TAPE" ! 85: .I Bite ! 86: is available on magnetic tape from the author or J. P. Hawkins of department ! 87: 2425. ! 88: The ! 89: .I Bite ! 90: release tape is divided into four major directories, ! 91: doc,include,lib and src. Doc of course contains all currently ! 92: available documentation. Include contains all of the header (.h) ! 93: files necessary to recompile. ! 94: Lib ! 95: contains archive files containing all of the object modules ! 96: which may be used to create a new load module with perhaps some ! 97: new or changed routines without recompiling everything. ! 98: Src contains five directories, ! 99: each of which contains source code for all of the files ! 100: contained in the Lib archives. In addition to source code each ! 101: directory under "src" contains one or more shell scripts intended ! 102: to build a new archive or recompile individual modules within ! 103: that particular source directory and link the new module with the ! 104: existing ones in the archives. In general the scripts to build ! 105: new archives are called "build.sh" and the ones to recompile individual ! 106: modules are called "compile.sh". In the "src/bite" ! 107: directory there are separate "build" and "compile" scripts for ! 108: .I bite , ! 109: .I bitex ! 110: and ! 111: .I bitem ! 112: Where ! 113: .I bite ! 114: is intended to run under PWB/UNIX version 2.0. ! 115: .I Bitex ! 116: runs stand alone on a PDP-11/03 micro computer and contains drivers for the ! 117: IBV11-A (IEEE 488 instrumentt bus) and certain test instruments utilized by ! 118: Dept. 2425 for automated testing. Due to space considerations, this version ! 119: does not have a complete math package (i.e. sin(),cos(),tan() etc.). ! 120: .I Bitem ! 121: also runs stand alone on a PDP 11/03 but contains no instrument bus drivers ! 122: and does contain a complete math package. ! 123: Both PDP 11/03 versions are compiled using a special floating point precompiler, ! 124: most of which was obtained from Ron Hardin of Columbus Laboratories and ! 125: are linked together with a set of subroutines referred to as the ! 126: file system interface (FSI). FSI interfaces ! 127: .I bite ! 128: with the file system and contains all necessary system utilities ! 129: such that, on the PDP-11/03, ! 130: .I bite ! 131: runs with no operating system in memory. Versions of FSI are available for ! 132: Digital Equipment Corp. RX-01 floppy disks and RL-01 hard disks. The "src" ! 133: directory contains the source code for both the special compiler and FSI. ! 134: The tape supplied is created by the "cpio" command. ! 135: To load the tape, create a directory where it is desired to ! 136: place the software, mount the tape and issue: ! 137: .DF 1 0 ! 138: cpio -id < /dev/mt0 ! 139: where 0 is mag tape unit zero. ! 140: .DE ! 141: .H 1 "ADDING COMMANDS" ! 142: .H 2 "General Format Of bite Instructions" ! 143: In ! 144: .I bite ! 145: the users ! 146: source code is operated on by a special purpose editor ! 147: .I bed.c ! 148: and stored as pseudo object code in large character array hereafter ! 149: referred to as the ! 150: .I "Program Buffer" . ! 151: Each instruction consists of four fields, which are arranged as follows: ! 152: .VL 8 5 ! 153: .LI 1) ! 154: The first two bytes of each instruction are interpreted as an integer ! 155: which is the line number of the instruction. ! 156: .LI 2) ! 157: The third byte is an excess 200 tab counter. One or more tabs may be inserted ! 158: between the line number and the command. Rather than physically storing ! 159: these tabs in the program buffer, all that is kept is a count of how many ! 160: tabs. A tab count of zero indicates that one blank is to be used. If the user ! 161: enters more than one blank, the additional ones will be stripped away. ! 162: Excess 200 means that 200 (base 8) is added to the actual tab count. This is ! 163: done so that byte three will either be zero or a number greater than 200 and ! 164: as such will never look like a valid ! 165: .I ascii ! 166: character. Since it is not a valid character, it can be and is used as a ! 167: beginning of line delimiter. Such a delimiter is needed whenever the buffer ! 168: pointer must be moved backwards to the beginning of the previous line as is ! 169: done by the "-" command. ! 170: .LI 3) ! 171: The fourth byte is interpreted as an integer hereafter referred to as the ! 172: .I opcode ! 173: which is an index into a table of function addresses hereafter called the ! 174: .I "Command Dispatch Table" ! 175: which is used to call the appropriate function via an indirect function call. ! 176: .LI 4) ! 177: The fourth field is variable in length beginning with the fifth byte ! 178: and continuing through a ! 179: .I NULL ! 180: byte. This ! 181: .I expression ! 182: field is pure ascii text, except that all blanks are ! 183: stripped out of it and certain ! 184: .I keywords ! 185: (See 3.6.2) ! 186: are encoded into a single ascii character in the ! 187: range of 0 to 37 base 8 i.e. the control characters. ! 188: The ! 189: .I expression ! 190: field ! 191: is used to pass variable information to the function called. ! 192: .LE ! 193: .SP ! 194: Thus the name of a command (if, print, for etc.) is never stored ! 195: in the program buffer as ! 196: .I ascii ! 197: text but rather is encoded into a single integer number. ! 198: Internally then the ! 199: .I bite ! 200: command is executed by the C language statement ! 201: .DF 2 0 ! 202: bascall(opcode) ! 203: .DE ! 204: where ! 205: .I bascall ! 206: is a macro defined within the ! 207: .I bite ! 208: header file ! 209: .I bas.h ! 210: and has the following form: ! 211: .DF 1 0 ! 212: #define bascall(opcode) ((*cmdtbl[opcode].function)()) ! 213: .DE ! 214: and ! 215: .I cmdtbl ! 216: is defined as follows: ! 217: .DF 1 0 ! 218: struct { ! 219: char *cmdtxt /*pointer to the command name */ ! 220: int (*function)(); /*pointer to the command function*/ ! 221: } cmdtbl[]; ! 222: .DE ! 223: The expression field of the instruction is decoded by the function called. ! 224: Conversion of the first four bytes of each instruction is handled by ! 225: copying them into a "union" defined in ! 226: .I bas.h ! 227: as follows: ! 228: .DF 1 0 ! 229: /* line number and command code structure aligned with 4 bytes for storage*/ ! 230: union bascmd ! 231: { ! 232: struct ! 233: { ! 234: int linno; ! 235: struct ! 236: { ! 237: char hibyte; ! 238: char lobyte; ! 239: } opcode; ! 240: }; ! 241: char byte[4]; ! 242: }; ! 243: .DE ! 244: Each time an instruction is fetched from the program buffer (See 3.3.1) ! 245: the first four bytes are copied into a golbally accessable ! 246: instance "inst" of this "union" defined ! 247: in "bed.c". This eliminates the need to be concerned with byte allignment ! 248: in the program buffer. Without this "union", each instruction would have to ! 249: begin on an even byte boundary so that the first two bytes could be referred ! 250: to as an integer. We pay a small execution time price, by requireing the ! 251: copy but save space in the program buffer by not requireing the byte allignment. ! 252: .H 2 Encode.c ! 253: To add an instruction to ! 254: .I bite , ! 255: first edit the ! 256: .I encode.c ! 257: module to define the name of the function and add its name and address ! 258: into the command dispatch table. ! 259: The order in which commands are defined or appear in the dispatch table ! 260: is arbitrary and totally unimportant to ! 261: .I bite . ! 262: The list of definitions begins with the comment: ! 263: .DF 1 0 ! 264: BASIC INTERPRETER COMMAND TABLE ! 265: .DE ! 266: .SP ! 267: simply follow the syntax of the other definitions and add in the new one. ! 268: The list entries in the dispatch table begins with the line: ! 269: .DF 1 0 ! 270: struct COMMAND cmdtbl[] ! 271: .DE ! 272: Each entry in the dispatch table consists of two items: ! 273: .VL 8 5 ! 274: .LI 1) ! 275: The name of the command as it will be written in the ! 276: .I bite ! 277: source statement. ! 278: .LI 2) ! 279: The name of the function to be called. These two names may or may not ! 280: be the same. Syntactically it's easier for human beings to read the program ! 281: if the names are the same but quite often other considerations such as ! 282: conflicts with ! 283: .I Unix ! 284: system names etc. force them to be different. ! 285: .LE ! 286: .SP ! 287: The next step is to write the function in C, then compile and link it ! 288: with ! 289: .I bite ! 290: using the appropriate shell script (See section 2). ! 291: .H 3 Example ! 292: We will take a look at the most simple command in ! 293: .I bite ! 294: "rem". ! 295: It's definition appears in the command table as: ! 296: .DF 2 0 ! 297: int no_op(); ! 298: .DE ! 299: It's entry in the dispatch table is: ! 300: .DF 2 0 ! 301: {"rem", no_op}, ! 302: .DE ! 303: and the C language function is as follows: ! 304: .DF 1 0 ! 305: no_op() ! 306: { ! 307: return(0); ! 308: } ! 309: .DE ! 310: Obviously, "rem" is a ! 311: .I bite ! 312: no-op, i.e. it does nothing. ! 313: The "return(0)" is absolutely necessary however. An unpublished and therefore ! 314: often unknown to the new C programmer (veterans have learned it, usually ! 315: the hard way) fact is that although C does not require a "return" statement ! 316: at the end of a function, the value returned by a function without a ! 317: "return" statement is undefined i.e. it could be anything. Since the run time ! 318: monitor of ! 319: .I bite ! 320: tests the value returned by all functions, all must have a specific ! 321: returned value. A value of -1 indicates that a fatal error was detected ! 322: during the execution of the function and the run is terminated. A value ! 323: of 0 indicates that no fatal errors were detected. ! 324: Every time a source statement such as: ! 325: .DF 2 0 ! 326: 10 rem this is a comment ! 327: .DE ! 328: is encountered, ! 329: the "no_op" function is called. ! 330: It is up to "no_op" to evaluate the expression "this is a comment", however, ! 331: comments don't require any action so "no_op" just returns without doing ! 332: anything. ! 333: .H 2 fetch.c ! 334: The "run" function of ! 335: .I bite ! 336: uses an internal function called ! 337: .I fetch ! 338: to retrieve instructions from the program buffer. The call to ! 339: fetch is as follows: ! 340: .DF 1 0 ! 341: fetch(n,&ptr); ! 342: .DE ! 343: where n is an integer with the following meaning: ! 344: .VL 15 5 ! 345: .LI "n= -1" ! 346: Get the first instruction in the program buffer and set "ptr" ("ptr" is ! 347: defined as "char *ptr") to point to the next sequential line, which in ! 348: this case, will be the second line in the program buffer. ! 349: .LI "n= 0" ! 350: Get the line pointed to by "ptr" and set "ptr" to point to the next line. ! 351: .LI "n= line #" ! 352: Get line number "n" and set "ptr" to point to the next line. ! 353: If line number "n" does not exist, get the line with the next ! 354: higher line number or the last line of the program if there ! 355: are no higher line numbers. ! 356: .LE ! 357: .SP ! 358: .I Fetch ! 359: takes the following action: ! 360: .VL 8 5 ! 361: .LI 1) ! 362: Copies the first four bytes into the instruction "union" (See 3.1). ! 363: .LI 2) ! 364: Sets a global character pointer "expr" to point to the buffer position where ! 365: the expression field of the fetched line begins. ! 366: .LI 3) ! 367: As noted above, updates the value of the argument "ptr". ! 368: .LI 4) ! 369: Sets a global character pointer "curptr" to point to the beginning ! 370: of the fetched line. ! 371: .LI 5) ! 372: Returns an integer with the following meaning: 0 if the operation was ! 373: 100% successful, -1 if the operation was a total failure, -2 if a line ! 374: was found but not the one asked for. ! 375: .LE ! 376: .SP ! 377: Thus, the line number, opcode, tab count, ! 378: and expression field are globally available ! 379: after a call to ! 380: .I fetch . ! 381: It should be noted here that if the function being called via ! 382: .I opcode ! 383: must for some reason modify the character string pointed to by "expr" ! 384: it ! 385: .B must ! 386: copy the expression to a local place otherwise it will be permanently ! 387: modifying the program buffer which can have disastrous side affects. ! 388: In most cases, however, it is not necessary for the function to modify ! 389: the expression field, rather it need only evaluate what's there. ! 390: .H 2 "Run Time Monitor" ! 391: Before continuing the discussion of ! 392: .I "Expression Evaluation" ! 393: it will be helpful for the reader to understand a little of how ! 394: program execution is controlled. ! 395: The "run" function in ! 396: .I bite ! 397: which is sometimes referred to as ! 398: .I "The Run Time Monitor" ! 399: is first called by typing "run" instead of a line number followed ! 400: by an operation and expression. The ! 401: .I bite ! 402: editor ! 403: .I bed ! 404: which is in control when ! 405: .I bite ! 406: is first invoked recognizes it as a command for immediate execution ! 407: because it is not preceded by a line number and calls it. ! 408: "run" first carries out a few housekeeping chores then fetches ! 409: the first instruction in the program buffer and executes it via ! 410: .I bascall. ! 411: Assuming that the first instruction does not return a -1 (fatal error) ! 412: "run" then begins sequentially fetching and calling each instruction in order. ! 413: This continues until ! 414: .I fetch ! 415: returns a non zero value (meaning end of file) or until a fatal error ! 416: is returned by one of the instruction functions. ! 417: In either case when "run" executes it's return instruction, ! 418: control is returned to ! 419: .I bed. ! 420: .H 3 Example ! 421: Another rather short ! 422: .I bite ! 423: function ! 424: .I goto ! 425: will be used to demonstrate the features discussed so far. The source ! 426: of ! 427: .I goto ! 428: is as follows: ! 429: .DF 1 0 ! 430: /* ///// goto routine ////// ! 431: /* part of Drake Hawkins Basic ! 432: /* R. B. Drake ///// ! 433: /* copyright Bell Telephone Laboratories /// ! 434: /* Whippany N. J. //////////// ! 435: */ ! 436: #include <bas.h> ! 437: __goto() ! 438: { ! 439: int savno,start; ! 440: extern char *ptr,*curptr; ! 441: savno = inst.linno; ! 442: start=atoi(expr); ! 443: fetch(start,&ptr); ! 444: if(inst.linno != start) ! 445: { ! 446: error(savno,0); ! 447: inst.linno = savno; ! 448: return(-1); ! 449: } ! 450: ptr = curptr; ! 451: return(0); ! 452: } ! 453: .DE ! 454: "savno" is used to save the current line number for possible use in ! 455: a call to the error printing routine (See 3.5). "start" is ! 456: used to hold the line number gleaned from the expression field. "*ptr" ! 457: is the global instruction pointer used by the run time monitor and ! 458: "*curptr" is set by ! 459: .I fetch ! 460: (see 3.3). ! 461: The statement ! 462: .DF 1 0 ! 463: start=atoi(expr); ! 464: .DE ! 465: .SP ! 466: Converts the line number which must be in the expression field i.e. ! 467: .DF 1 0 ! 468: 10 rem the 40 in line 30 is the ! 469: 20 rem line number in the expression field ! 470: 30 goto 40 ! 471: 40 end ! 472: .DE ! 473: The statement ! 474: .DF 2 0 ! 475: fetch(start,&ptr); ! 476: .DE ! 477: fetches the line asked for and the code which follows that checks to ! 478: see that the line fetched is indeed the one requested. If the line ! 479: fetched is not the one requested, then the "goto" refers to a ! 480: non-existent line in which case an error message is printed and a ! 481: -1 (function detected a fatal error) is returned to the run time ! 482: monitor. Otherwise, *ptr is set to *curptr and a zero is returned ! 483: to the run time monitor. Setting *ptr to *curptr primes ! 484: .I fetch ! 485: so that the next time it is called with a ! 486: .DF 1 0 ! 487: fetch(0,&ptr); ! 488: .DE ! 489: which is what the run time monitor will do when it gets control again, ! 490: the line indicated in the "goto" is the one that will be fetched. ! 491: .H 2 Error.c ! 492: All printing of error messages in ! 493: .I bite ! 494: is done by calling a special error printing routine as follows: ! 495: .DF 2 0 ! 496: error(linno,num); ! 497: .DE ! 498: .B Notes: ! 499: .VL 8 5 ! 500: .LI 1) ! 501: If ! 502: .I linno ! 503: is greater than zero, it prints: ! 504: .DF 1 0 ! 505: ERROR LINE ! 506: .I linno ! 507: MESSAGE INDICATED BY ! 508: .I num ! 509: .DE ! 510: .SP ! 511: where ! 512: .I num ! 513: is an index number into an array of pointers to character ! 514: strings. ! 515: The array of pointers to character strings is defined within ! 516: .I error.c ! 517: as follows: ! 518: .DF ! 519: static char *mesg[] ! 520: { ! 521: "REFERS TO A NON EXISTING LINE NUMBER", /* 0 */ ! 522: "UNRECOGNIZABLE OPERATION", /* 1 */ ! 523: . ! 524: . ! 525: . ! 526: . ! 527: "EXPRESSION YIELDS AN IMPOSSIBLE VALUE", /* 38 */ ! 528: }; ! 529: .DE ! 530: So that if the call to ! 531: .I error ! 532: was ! 533: .DF 2 0 ! 534: error(10,0); ! 535: .DE ! 536: The message printed would be: ! 537: .DF 1 0 ! 538: ERROR LINE 10 REFERS TO A NON EXISTING LINE NUMBER ! 539: .DE ! 540: The numbers inside the comment delimiters i.e. "/* 0 */" correspond ! 541: to the appropriate array index number to access that message. This is ! 542: indispensable to the programmer and of utmost importance that anyone ! 543: modifying ! 544: .I error.c ! 545: maintain that convention. ! 546: It should also be noted that since the messages are referred to by ! 547: their index number, the position of the current messages can not ! 548: be changed. for example if one message is deleted, all the messages ! 549: above it would be in the wrong position, and any calls to ! 550: .I error ! 551: with intent to print those messages would print the wrong one. ! 552: Therefore any new messages must be added to the end of the list. ! 553: .LI 2) ! 554: If ! 555: .I num ! 556: is zero, the printing of the line number is suppressed. This feature ! 557: is used in commands being executed in immediate mode where there is ! 558: no line number to reference. ! 559: .LI 3) ! 560: Error numbers less than 100 are reserved for basic ! 561: .I bite . ! 562: That is, that portion of ! 563: .I bite ! 564: which is common to all versions. While error numbers 100 and greater ! 565: are used for user added instructions. This is convenient for the use ! 566: of conditional compile instructions so that several versions may be ! 567: kept in a single source file and the appropriate version can be compiled ! 568: by simply changing one ! 569: .I define ! 570: statement ! 571: in a header file. ! 572: For example, the current source tape of ! 573: .I bite ! 574: contains a conditional variable "LSX". If "LSX" is defined in the header ! 575: file ! 576: .I bas.h , ! 577: then a version for the PDP-11/03 computer containing many test set ! 578: instructions is compiled. If "LSX" is not defined then a version ! 579: for the PDP-11/70 computer containing no test set instructions or ! 580: the error messages that go with them is compiled. ! 581: .LE ! 582: .SP ! 583: Printing error messages in this fashion as opposed to just printing them ! 584: via ! 585: .I printf ! 586: statements where they occur has a number of advantages. ! 587: .VL 8 5 ! 588: .LI 1) ! 589: It gathers all of the error messages in one place where they are easily ! 590: seen and therefore there is no temptation to have the same or similar ! 591: messages appearing in several different places within ! 592: .I bite ! 593: which would be an unnecessary waste of core space. ! 594: .LI 2) ! 595: It standardizes the format in which errors are printed. ! 596: .LI 3) ! 597: If core space should become tight as it is in the LSI version of ! 598: .I bite , ! 599: all error messages can be reduced to the printing of an error number ! 600: which the user would have to look up, thereby saving the space required ! 601: to store the messages. While this would be less convenient, it would be ! 602: less of a sacrifice than having to eliminate needed functions. Since ! 603: all the messages are in one place, only one routine ! 604: .I error.c ! 605: would have to be modified to accomplish this. ! 606: .LE ! 607: .SP ! 608: .H 2 "Parsing Expressions" ! 609: .H 3 prncpy ! 610: Many commands in ! 611: .I bite ! 612: are followed by an expression field consisting of a list of items ! 613: separated by commas such as: ! 614: .DF 1 0 ! 615: 10 input a,b,c ! 616: 20 on a goto 30,40,50 ! 617: 30 print a,b,c ! 618: 40 stop ! 619: 50 goto 10 ! 620: .DE ! 621: The comma is always the delimiter separating items except when it is ! 622: within parenthesis as in: ! 623: .DF 1 0 ! 624: 10 read a(1,1),a(1,2) ! 625: .DE ! 626: In the above case, the only comma which is an item delimiter is the ! 627: one before "a(1,2)". ! 628: .I Prncpy ! 629: is a function designed to parse this type of construct. The call to ! 630: .I prncpy ! 631: is as follows: ! 632: .DF 1 0 ! 633: char *prncpy(); ! 634: char *pt ! 635: extern char *expr; ! 636: char to[80]; ! 637: pt = prncpy(to,expr); ! 638: .DE ! 639: Assuming that "expr" is a pointer to the beginning of the expression ! 640: field (see 3.3), ! 641: .I prncpy ! 642: takes the following action: ! 643: .VL 8 5 ! 644: .LI 1) ! 645: Copies the characters beginning at "expr" to the array "to" up to ! 646: but not including the first comma ! 647: which is ! 648: .B not ! 649: enclosed in parentheses. ! 650: If the end of string (NULL byte) occurs before such a comma is found, ! 651: the copy stops. In either case the string copied into "to" is NULL ! 652: terminated. ! 653: .LI 2) ! 654: Returns a pointer to the comma or NULL byte. In this way the calling routine ! 655: can use the returned pointer to determine what the delimiter was and if ! 656: there are more items to be parsed, it has a pointer to the beginning of the ! 657: next item by bumping the returned pointer by one. ! 658: .LE ! 659: .SP ! 660: .H 3 evalx ! 661: The inner workings of ! 662: .I evalx ! 663: will be described in a separate memorandum by J. P. Hawkins who is ! 664: the author of that particular function. However, it is of utmost ! 665: importance to this discussion and therefore will be described here ! 666: in enough detail to allow readers of this document to use it. ! 667: .P ! 668: The call to ! 669: .I evalx ! 670: is ! 671: .DF 1 0 ! 672: char *pointer; ! 673: double evalx(); ! 674: evalx(pointer); ! 675: .DE ! 676: Here, "pointer" must point to the beginning of a NULL or ! 677: .I keyword ! 678: terminated expression (See 3.1 note 3). ! 679: Where in this case, ! 680: expression is one of the following: ! 681: .VL 8 5 ! 682: .LI 1) ! 683: A variable. Such as "a" or "b1" etc. ! 684: .LI 2) ! 685: A Subscripted variable. Such as "a(1,1)". ! 686: .LI 3) ! 687: Any numeric literal. Such as "10" or "551.325" etc. ! 688: .LI 4) ! 689: A function reference. Such as "sin(3.14159)" or "log(10)" etc. ! 690: .LI 5) ! 691: A mathematical expression consisting of any or all of the above ! 692: connected by mathematical operators. Such as ! 693: .DF 1 0 ! 694: a+a(1,1)*551.325/sin(3.14159) ! 695: .DE ! 696: The keywords and their associated ascii codes are: ! 697: .DF 2 0 ! 698: .B "KEYWORD ASCII CODE(octal)" ! 699: goto 01 ! 700: go to 02 ! 701: then 03 ! 702: to 04 ! 703: step 05 ! 704: <> 06 ! 705: <= 07 ! 706: =< 10 ! 707: < 11 ! 708: >= 12 ! 709: => 13 ! 710: > 14 ! 711: = 15 ! 712: gosub 16 ! 713: .DE ! 714: .SP ! 715: .I Evalx ! 716: takes the following action: ! 717: .VL 8 5 ! 718: .LI 1) ! 719: Evaluates the expression and returns its numeric value in double precision ! 720: floating point form. ! 721: .LI 2) ! 722: Sets a global character pointer "eoexpr" (mnemonic for "end of expression ! 723: pointer") to point to the character at the end of the expression. ! 724: .LI 3) ! 725: Unlike other ! 726: .I bite ! 727: functions, ! 728: "Evalx" can not return a "-1" if an error occurs during the expression ! 729: evaluation because "-1" is a perfectly valid value. So, if an error occurs ! 730: during expression evaluation,"evalx" prints an error message, sets the ! 731: stop flag "stpflg" and returns a value of zero. ! 732: This will cause program termination when control ! 733: is returned to the run time moniter. ! 734: .H 2 "Calling command functions as though they were internal functions". ! 735: Very often, one of the ! 736: .I bite ! 737: commands already does exactly whats needed in the way of expression ! 738: parsing. For example, consider the instruction ! 739: .DF 2 0 ! 740: 10 for i=1 to 100 ! 741: .DE ! 742: The first thing that the ! 743: .I for ! 744: function must be concerned with is setting the variable "i" equal to ! 745: 1. The ! 746: .I let ! 747: command already deals with expressions of this nature and the pointer ! 748: "expr" already points to the "i" at the beginning of the ! 749: expression field (See 3.3). So one of the first statements ! 750: in ! 751: .I for ! 752: is ! 753: .DF 2 0 ! 754: let(); ! 755: .DE ! 756: Since ! 757: .I let ! 758: uses ! 759: .I evalx ! 760: to evaluate that part of the expression to the right of the "=", and ! 761: since ! 762: .I evalx ! 763: terminates at ! 764: .I keywords ! 765: (in this case the "to"), on return from the call to ! 766: .I let ! 767: the variable "i" will have been set to 1 and the pointer ! 768: .I eoexpr ! 769: will be pointing to the ascii character "004" which is the ! 770: encoded "to". The ! 771: .I for ! 772: function then proceeds to deal with that part of the expression beyond ! 773: the "to" and of course uses ! 774: .I evalx ! 775: again to accomplish that. ! 776: .H 3 Example. ! 777: The ! 778: .I on ! 779: function is fairly short and provides a good sample of the features ! 780: discussed thus far. The source code is as follows: ! 781: .DF 1 0 ! 782: /* routine to handle on goto */ ! 783: /* R.B. Drake 4/26/79 */ ! 784: #include <bas.h> ! 785: extern int stpflg; ! 786: double evalx(); ! 787: char *prncpy(); ! 788: extern char *expr,*eoexpr; ! 789: on() ! 790: { ! 791: int i,j; ! 792: char buffer[80]; ! 793: char c; ! 794: if((j = evalx(expr)) <= 0) ! 795: { ! 796: error(inst.linno,38); ! 797: return(-1); ! 798: } ! 799: /* "stpflg" is set by the interrupt routine "stopl" ! 800: * when the user hits the "del" or "rubout" key. ! 801: * When "stpflg" becomes set, the "run" is to be ! 802: * terminated. It is necessary to test it here because ! 803: * this routine can remain active for an unlimited number ! 804: * of instructions and may be called recursively. ! 805: * i.e. "gosub" does not return until it encounters a ! 806: * "return" instruction in the program buffer and there ! 807: * is nothing which prevents the occurrence of other ! 808: * "gosub's" or other "on" statements within the range ! 809: * of "gosub". ! 810: */ ! 811: if(stpflg == 1) ! 812: return(-1); ! 813: if((c= *eoexpr++) > '\\003' && c != '\\016') ! 814: { ! 815: error(inst.linno,8); /* expression syntax */ ! 816: return(-1); ! 817: } ! 818: for(i=0;i<j;i++) ! 819: { ! 820: eoexpr = prncpy(buffer,eoexpr); ! 821: if(*eoexpr++ == '\\0' && i != (j-1)) ! 822: { ! 823: error(inst.linno,38); /* not enough references*/ ! 824: return(-1); ! 825: } ! 826: } ! 827: expr = buffer; ! 828: if(c != '\\016') ! 829: { ! 830: if(__goto() < 0) ! 831: return(-1); ! 832: return(0); ! 833: } ! 834: if(gosub() < 0) ! 835: return(-1); ! 836: return(0); ! 837: } ! 838: .DE ! 839: Recall that the format of the ! 840: .I on ! 841: statement in ! 842: .I bite ! 843: is ! 844: .DF 2 0 ! 845: line# on expr goto line#,line#......,line# ! 846: or ! 847: line# on expr gosub line#,line#......,line# ! 848: .DE ! 849: Where "expr" will be evaluated and truncated to an integer ! 850: indicating which of the "line#'s" to go to. ! 851: .P ! 852: Referring now to the above source code, note that the very first ! 853: executable instructions is ! 854: .DF 2 0 ! 855: if(j = evalx(expr) <= 0) ! 856: .DE ! 857: Whatever the form of "expr", (See 3.6.2) ! 858: .I evalx ! 859: will return its value as a double precision ! 860: floating point number. However, since "j" was declared integer, ! 861: C will automatically truncate it. Of course if it's value is negative, ! 862: there is an error because negative numbers have no place in this context. ! 863: .I Evalx ! 864: will return at the first ! 865: .I keyword ! 866: which in this case must be either "goto" or "gosub". This syntax ! 867: is tested by the statement ! 868: .DF 2 0 ! 869: if((c= *eoexpr++) > '\\003' && c != '\\016') ! 870: .DE ! 871: Where '\\003' is the ! 872: .I ascii ! 873: character for "goto" and '\\016' the "gosub" (See 3.6.2). ! 874: Note that the "++" in the above statement pushes the pointer ! 875: beyond the ! 876: .I keyword ! 877: and makes it point to the beginning of the list of ! 878: line numbers. Remember that the ! 879: .I bite ! 880: editor ! 881: .I bed ! 882: strips all blanks and tabs out of the expression field. ! 883: Therefore, there is no need for any of the command functions ! 884: to worry about white space. ! 885: The "for" loop beginning at ! 886: .DF 2 0 ! 887: for(i=0;i<j;i++) ! 888: .DE ! 889: takes care of copying the j'th line# in the list ! 890: into the array "buffer". ! 891: This is accomplished ! 892: by calling ! 893: .I prncpy ! 894: (See 3.6.1) "j" times, while making sure that the end of the ! 895: expression string is not reached before the j'th line number is ! 896: found. Now, "buffer" contains a character string representing ! 897: a line number which is to be the object of the "goto" or "gosub". ! 898: Since ! 899: .I bite ! 900: already has functions to handle "goto" and "gosub" all that is ! 901: necessary is to set the pointer "expr" to point to "buffer" ! 902: and call the appropriate "go" routine. ! 903: .I Bite ! 904: functions don't ! 905: care whether ! 906: "expr" points into the program buffer or somewhere else as ! 907: long as the string it points to has the appropriate syntax. ! 908: .H 2 "Dealing With Variables" . ! 909: In the discussion of the "for" command in section 3.7, the issue ! 910: of variables was avoided by allowing the ! 911: .I let ! 912: function to handle them. It is recommended that, where ever possible, ! 913: that procedure be followed. However, there may well be situations ! 914: where that is not practical. There are several internal functions ! 915: which are used for this purpose as follows: ! 916: .DF 1 0 ! 917: class(from,to) ! 918: getvar(varnam,value) ! 919: agetvar(varnam,value) ! 920: putvar(varnam,value) ! 921: aputvar(varnam,value) ! 922: .DE ! 923: A brief description of the calling protocol and the action of each ! 924: of these will be given here. The details on how they work will be ! 925: covered in another memorandum. ! 926: .H 3 class(from,to) ! 927: .SP ! 928: Calling protocol: ! 929: .SP ! 930: .DF 1 0 ! 931: char *from; /* pointer to an expression string */ ! 932: char *to; /* pointer to a character array large enough ! 933: * to hold one element of the expression */ ! 934: class(&from,to); ! 935: .DE ! 936: .I Action: ! 937: .VL 8 5 ! 938: .LI 1) ! 939: Copies one element of the expression into the array "to" and ! 940: NULL terminates the string. Where an expression element is ! 941: a mathematical operator, ! 942: a numeric literal, a variable name, an array name or a function name. ! 943: (See 3.6.2). ! 944: .LI 2) ! 945: Bumps the pointer "from", leaving it pointing to the first character ! 946: of the next expression element. ! 947: .LI 3) ! 948: Returns an integer, which is a code telling the calling routine ! 949: what kind of element was copied. These integers are defined as ! 950: manifest constants in the header file ! 951: .I bas.h ! 952: as follows: ! 953: .DF 1 0 ! 954: #define OPCLASS 1 /* operator field ^ * / + - ( */ ! 955: #define NMCLASS 2 /* numeric field */ ! 956: #define VRCLASS 3 /* variable name */ ! 957: #define VACLASS 5 /* variable array name */ ! 958: #define FNCLASS 6 /* function name */ ! 959: .DE ! 960: .H 3 "Getvar and Agetvar" ! 961: .SP ! 962: Calling protocol for getvar: ! 963: .SP ! 964: .nf ! 965: char *string; /*pointer to a string containing a variable name*/ ! 966: double value; /*place to store the variable's value*/ ! 967: getvar(string,&value); /* call */ ! 968: .SP ! 969: Calling protocol for agetvar: ! 970: .SP ! 971: Same as for getvar except "string" must point to the name of an ! 972: array variable name. ! 973: .SP ! 974: .fi ! 975: These routines find the address of the data associated with the variable ! 976: or array name, retrieve the data from the data buffer and store it in ! 977: the location specified by their second argument. ! 978: .H 3 "Putvar and aputvar" ! 979: .SP ! 980: Calling protocol for putvar: ! 981: .SP ! 982: .nf ! 983: char *string; /* pointer to the name of a variable*/ ! 984: double value; /*data to be stored*/ ! 985: putvar(string,value); ! 986: .SP ! 987: Calling protocol for aputvar: ! 988: .SP ! 989: Same as for putvar except "string" must point to the name of an ! 990: array variable. ! 991: .SP ! 992: .fi ! 993: These routines find the address of ! 994: the storage area in the data buffer where the data associated with the ! 995: variable name is to go. In the case of ! 996: .I putvar ! 997: if no such storage area exists, one will be allocated. In the case ! 998: of ! 999: .I aputvar , ! 1000: a storage area must have been reserved by a previous "dim" statement ! 1001: or a fatal error will occur with an appropriate error diagnostic ! 1002: message. ! 1003: .H 4 "Example." ! 1004: Perhaps the most complete example using all of these functions is in ! 1005: "evalx.c". However, "evalx" is long and complex therefore we will ! 1006: use the simpler "let" routine which hopefully will demonstrate the ! 1007: point without clouding it by complexity. ! 1008: .DF 1 0 ! 1009: ! 1010: #include <bas.h> ! 1011: /* ! 1012: #define skip00() {while(*lexptr == ' ' || *lexptr == '\t') *lexptr++;} ! 1013: */ ! 1014: #define skip00() {} /* does nothing */ ! 1015: ! 1016: let() ! 1017: { ! 1018: int type; /* field type */ ! 1019: char varnam[40]; ! 1020: float evalx(); ! 1021: float value; ! 1022: char field[40]; ! 1023: char *lexptr; /* string pointer for let */ ! 1024: ! 1025: lexptr = expr; /* set text pointer */ ! 1026: ! 1027: type = class(&lexptr,field); /* get field type */ ! 1028: if((type != VRCLASS) && (type != VACLASS)) /* if not var */ ! 1029: { ! 1030: error(inst.linno, 3); /* ill var name */ ! 1031: return(-1); ! 1032: } ! 1033: strcpy(varnam, field); /* copy variable name */ ! 1034: skip00(); ! 1035: if(*lexptr++ == '\15') /* if code for '=' */ ! 1036: { ! 1037: value = evalx(lexptr); /* evaluate right side */ ! 1038: switch(type) ! 1039: { ! 1040: case VRCLASS: /* regular variable name */ ! 1041: putvar(varnam, value); ! 1042: return(0); ! 1043: break; ! 1044: case VACLASS: /* subscripted var field */ ! 1045: aputvar(varnam, value); ! 1046: return(0); ! 1047: break; ! 1048: default: ! 1049: break; ! 1050: } ! 1051: } ! 1052: else ! 1053: { ! 1054: error(inst.linno, 8); /* missing = */ ! 1055: return(-1); ! 1056: } ! 1057: } ! 1058: .DE ! 1059: "Let" of course expects to see an expression field of the form ! 1060: "variable = mathematical expression" (See 3.6.2 for the definition ! 1061: of these terms). Therefore the first thing it does is make sure that ! 1062: the expression begins with a variable or array name. ! 1063: This is done by: ! 1064: .DF 1 0 ! 1065: type = class(&lexptr,field); /* get field type */ ! 1066: if((type != VRCLASS) && (type != VACLASS)) /* if not var */ ! 1067: { ! 1068: error(inst.linno, 3); /* ill var name */ ! 1069: return(-1); ! 1070: } ! 1071: .DE ! 1072: After the call to "class", "type" should be equal to either ! 1073: "VRCLASS" or "VACLASS", "lexptr" should be pointing to an "=" ! 1074: and "field" will contain the null terminated name of the variable ! 1075: or array. (in the case of an array, the name includes the parenthises ! 1076: and what's inside them i.e. "a(x,y)" ). The variable or array name ! 1077: will be needed later so is copied to a new temporary location "varnam" ! 1078: by: ! 1079: .DF 1 0 ! 1080: strcpy(varnam, field); /* copy variable name */ ! 1081: .DE ! 1082: Then a test is made to be sure that "lexptr" is pointing to an "=" ! 1083: and at the same time "lexptr' is bumped so that it will point to the ! 1084: first character of the mathematical expression by the statement: ! 1085: .DF 1 0 ! 1086: if(*lexptr++ == '\15') /* if code for '=' */ ! 1087: .DE ! 1088: If "lexptr" was not pointing to an "=" an error message is issued ! 1089: and a fatal error indicated by: ! 1090: .DF 1 0 ! 1091: else ! 1092: { ! 1093: error(inst.linno, 8); /* missing = */ ! 1094: return(-1); ! 1095: } ! 1096: .DE ! 1097: But assuming that everything is allright so far, the mathematical expression ! 1098: is evaluated by: ! 1099: .DF 1 0 ! 1100: value = evalx(lexptr); /* evaluate right side */ ! 1101: .DE ! 1102: Now all that remains is to store "value" in the appropriate variable or ! 1103: array element. This is handled by: ! 1104: .DF 1 0 ! 1105: switch(type) ! 1106: { ! 1107: case VRCLASS: /* regular variable name */ ! 1108: putvar(varnam, value); ! 1109: return(0); ! 1110: break; ! 1111: case VACLASS: /* subscripted var field */ ! 1112: aputvar(varnam, value); ! 1113: return(0); ! 1114: break; ! 1115: } ! 1116: .DE ! 1117: Admitedly this example has not used either "getvar" or "agetvar", ! 1118: but they are so similar to their "put" sisters that such an example ! 1119: does not seem necessary. ! 1120: .H 1 "ADDING FUNCTION SUBROUTINES" ! 1121: Function subroutines differ from commands in that they appear in the ! 1122: expression field and are replaced during expression evaluation by the ! 1123: value they return. The syntax is: ! 1124: .DF 1 0 ! 1125: anyname(arg) ! 1126: .DE ! 1127: There is no hard limit as to the number of characters that can be ! 1128: in the name but make sure it can't be mistaken for an array name ! 1129: (i.e. it must be at least two characters long and ! 1130: the second character must ! 1131: not be ! 1132: a digit). ! 1133: "Arg" may be nothing or any expression acceptable to "evalx" (see 3.6.2). ! 1134: Only one argument is permitted. Adding a function subroutine to ! 1135: .I bite ! 1136: is very similar to adding a command except that the function declaration ! 1137: and dispatch table are in "evalx.c", just preceeding the routine called ! 1138: "mathcall". "Mathcall" is the routine which actually handles the calling ! 1139: of function subroutines. To add a function subroutine, first write your ! 1140: function in "c" being sure to declare it as a "float" function and if ! 1141: there is to be an argument, the argument must also be declared as float. ! 1142: Then declare its name along with the others (i.e. float exp()) and add ! 1143: it's name into the dispatch table "mathtbl" (i.e. {"exp", exp},). ! 1144: .H 2 "Example". ! 1145: Perhaps the simplest example of a function subroutine, so simple in fact ! 1146: that it was included right in the "evllx.c" file instead of as a seperate ! 1147: file, is "int". "Int" simply truncates a floating point number to an integer ! 1148: value. Since there are no actual integers in ! 1149: .I bite ! 1150: the value is still in floating point form but the fractional part of the ! 1151: number will be zero. "Int" appears as follows: ! 1152: "Int" appears as follows: ! 1153: .DF 1 0 ! 1154: float _int(num) ! 1155: float num; ! 1156: { ! 1157: long trunc; ! 1158: trunc=num; ! 1159: num=trunc; ! 1160: return(num); ! 1161: } ! 1162: .DE ! 1163: This routine simply makes use of the automatic type conversions and truncation ! 1164: provided by the "c" compiler. That is, "num" is truncated and ! 1165: converted to a long integer ! 1166: by the statement "trunc=num" and then from that integer back to a floating ! 1167: value by the statement "num=trunc". It's use in bite is as follows: ! 1168: .DF 1 0 ! 1169: 20 b = (1+int(1.1234))/2 ! 1170: .DE ! 1171: After executing the above instruction the value of "b" will be 1. ! 1172: i.e. (1+1)/2. ! 1173: .H 1 "Conclusion" ! 1174: One of the reasons that UNIX is becoming very popular throughout the ! 1175: computing industry is that it's source code is aviailable and it is ! 1176: written predominantly in a semi high level language, "C". Users are ! 1177: no longer stuck with the features that the system designers decided ! 1178: that they should have. Rather the user is free to add or subtract ! 1179: whatever seems appropriate for his task. ! 1180: .I Bite ! 1181: is no exception, we feel that we have provided a strong foundation for ! 1182: a good interprative language but make no pretence that we have thought ! 1183: of everyting. ! 1184: We invite users to dig into it, improve it and add to it. ! 1185: The author hopes that this memorandum provides enough information to make ! 1186: that task easy so that no one will again feel the need to start from ! 1187: scratch as we did. ! 1188: .H 1 ACKNOWLEDGEMENTS ! 1189: .I Bite ! 1190: is entirely the work of the author and J. P. Hawkins. However, two people ! 1191: helped us immeasurably by being our "friendly users". They began using ! 1192: .I bite ! 1193: when it was only half finished and far from debugged. Many thanks to ! 1194: Nick Episcopo and Don Jackowski. ! 1195: Their patience is much appreciated and the evidence of their ! 1196: suggestions is everywhere. ! 1197: .CS ! 1198: .TC ! 1199: .SG UNIX
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.