Subject: v13i056: New release of little smalltalk, Part04/05 Newsgroups: comp.sources.unix Sender: sources Approved: rsalz@uunet.UU.NET Submitted-by: Tim Budd Posting-number: Volume 13, Issue 56 Archive-name: little-st2/part04 #!/bin/sh # # # This is version 2.02 of Little Smalltalk, distributed in five parts. # # This version is dated 12/25/87 # # Several bugs and many features and improvements have been made since the # first posting to comp.src.unix. See the file ``todo'' for a partial list. # # Comments, bug reports, and the like should be submitted to: # Tim Budd # Smalltalk Distribution # Department of Computer Science # Oregon State University # Corvallis, Oregon # 97330 # # budd@cs.orst.edu # {hp-pcd, tektronix}!orstcs!budd # # echo 'Start of small.v2, part 04 of 05:' echo 'x - at.ms' sed 's/^X//' > at.ms << '/' X.LP X(note: this is the first of a series of essays descriging how various Xfeatures of the Little Smalltalk bytecodes work). X.SH XWhere It's At X.PP XThis short note explains how the messages \fBat:\fP, \fBat:put:\fP, and their Xrelatives are defined and used in collections. We start by discussing the Xsimplest form of collections, arrays and strings. X.PP XThe message \fBat:\fP is not defined anywhere in class \fBArray\fP or any of Xits subclasses. Instead, this message is inherited from Xclass \fBCollection\fP, which defines it using the following method: X.DS I X\fBat:\fP index X \(ua self at: index X ifAbsent: [ smalltalk error: 'index to at: illegal' ] X.DE X.PP XThe functioning of the message \fBerror:\fP is the topic of another essay; Xit is sufficient for our purposes to note only that this message prints out Xthe error string and returns nil. By redefining \fBat:\fP in this fashion, Xthe subclasses of \fBCollection\fP need not be concerned about how to deal Xwith errors in cases where no error recovery action has been specified. X.PP XFor an array, an index is out of bounds if it is either less than 1 or greater Xthan the size of the array. This is tested by a method in class \fBArray\fP: X.DS I X\fBincludesKey:\fP index X ^ index between: 1 and: self size X.DE X.PP XThe message \fBsize\fP is defined in class \fBArray\fP in terms of the Xmessage \fBbasicSize\fP X.DS I X\fBsize\fP X ^ self basicSize X.DE X.PP XThe message \fBbasicSize\fP (as well as \fBbasicAt:\fP, discussed below) Xis inherited from class X\fBObject\fP. It can be used on any object; on non-arrays it returns Xthe number of instance variables for the object. The messages \fBbasicSize\fP Xand \fBbasicAt:put:\fP can be used by system Xclasses, for example debuggers, to access instance variables in an object Xwithout having explicit access to the instance variables. One must be Xcareful, however, X\fBbasicAt:\fP produces a system error, and not a Smalltalk error message, Xif it is given an index value that is out of range. X.PP XUsing \fBincludesKey:\fP for a test, a value is only accessed if the index Xis legal. The following method appears in class \fBArray\fP: X.DS I X\fBat:\fP index \fBifAbsent:\fP exceptionBlock X ^ (self includesKey: index) X ifTrue: [ self basicAt: index ] X ifFalse: [ exceptionBlock value ] X.DE X.PP XA subclass of \fBArray\fP is the class \fBByteArray\fP. A byte array is a form Xof array in which the elements can only take on values from zero to 255, or Xto put it another way, values that can be stored in one byte. XOn most 16 bit machines, we can store two such bytes in the space it takes Xto store one object pointer. Thus, the message \fBsize\fP is redefined Xin class \fBByteArray\fP as follows: X.DS I X\fBsize\fP X \(ua self basicSize * 2 X.DE X.LP XNote that this implies that byte arrays always have an even number of Xelements. Next the message \fBbasicAt:\fP is redefined to use a byte, Xinstead of object, form of index. This is accomplished using a primitive Xmethod, (the message \fBbasicAt:\fP is handled in a similar fashion in Xclass \fBObject\fP, only using a different primitive). X.DS I X\fBbasicAt:\fP index X \(ua <26 self index> X.DE X.PP XLike a byte array, a string can also store two byte values in the space Xit takes to store a single object pointer. Unlike a byte array, however, Xa string can be any length, not just an even length. Therefore the message X\fBsize\fP is redefned in class \fBString\fP, a subclass of \fBByteArray\fP. X.DS I X\fBsize\fP X \(ua <14 self> X.DE X.PP XAnother difference between a string and a byte array is that the value Xreturned by a string must be a character, not an integer. Therefore X\fBbasicAt:\fP must also be redefined. By using the message \fBbasicAt:\fP Xdefined in \fBByteArray\fP, (the superclass of String, and therefore accessible Xvia the pseudo variable \fBsuper\fP) the method can obtain the integer value Xof the appropriate character. This value is then used to create a new Xinstance of class \fBChar\fP: X.DS I X\fBbasicAt:\fP index X \(ua Char new; value: (super basicAt: index) X.DE X.PP XA value is placed into an array using the message \fPat:put:\fP. As with X\fBat:\fP, a value should only be placed if the index represents a legal Xsubscript. This is checked in the following method: X.DS I X\fBat:\fP index \fBput:\fP value X (self includesKey: index) X ifTrue: [ self basicAt: index put: value ] X ifFalse: [ smalltalk error: X 'illegal index to at:put: for array' ] X.DE X.PP XAs was the case with \fBbasicAt:\fP, one version of \fBbasicAt:put:\fP, Xto be used by arrays of objects, is defined as part of class \fBObject\fP. XA different version is found in class \fBByteArray\fP. Finally a third Xversion, which first checks to see if the argument is a Character, is found Xin class \fBString\fP. X.DS I X\fBat:\fP index \fBput:\fP aValue X (aValue isMemberOf: Char) X ifTrue: [ super basicAt: index put: aValue asciiValue ] X ifFalse: [ smalltalk error: X 'cannot put non Char into string' ] X.DE X.SH XExercises X.IP 1. XDescribe the sequence of messages used to respond to the following: X.DS B Xx \(<- #(1 2 3) at: 2 X.DE X.IP 2. XDescribe how the execution of the above expression could be speeded up by Xadding new methods. Note if your methods are specific to arrays of objects, Xarrays of bytes, or strings. / echo 'x - general.ms' sed 's/^X//' > general.ms << '/' X.\" information on Little Smalltalk, version 2, beta release X.SH XGeneral Overview X.PP XFirst, the obvious facts. This is not Smalltalk-80, nor even Smalltalk-V. XThis is the second version of the Little Smalltalk system, the first version Xof which is described in the book recently published by Addison-Wesley*. X.FS X* \fIA Little Smalltalk\fP, by Timothy A. Budd. Published by Addison XWesley, 1987. In better bookshops everywhere. X.FE XVersion two is smaller and faster; does more in Smalltalk, less in C; and is Xdesigned to be more portable to a wider variety of machines (we are working Xon versions now for various PCs). X.PP XMy attitude towards the language has been Xrather cavalier; what I liked I kept and what I didn't like I tossed out. XThis is explained in more detail in my book and in the end of this note. XAs a consequence, individuals familiar with ST-80 or Smalltalk-V will be struck Xby how much they are missing, and I make no apologies for this. On the Xother hand, you don't find Smalltalk-V posted to comp.source.unix. XAmong the features Xyou won't find here are metaclasses, class methods, windows, graphics Xsupport, and more. X.PP XWhat you will find is a small language that does give you the flavor of Xobject oriented programming at very little cost. We are working to improve Xthe system, and hope to distribute new versions as we develop them, Xas well as porting it to a wide range of machines. XIf you find (and preferably, fix!) bugs let us know. XIf you make nice additions let us know. XIf you want to make complements let us know. XIf you want to make complaints let us know. XIf you want support you just might be out of luck. X.PP XThis software is entirely public domain. You are encouraged to give it Xto as many friends as you may have. As a courtesy, I would appreciate it Xif you left my name on the code as the author, but I make no other claims Xto it (I also, of course, disavow any liability for any bizarre things you Xmay choose to do with it). Enjoy. X.SH XBuilding the System X.PP XThe first step in building the system is to unpack the sources. XThe fact that you are reading this means you have probably already figured Xout how to do this. X.PP XThere are various different types of files sent with the distribution. XFiles ending in .c and .h are C sources, for both the parser and the bytecode Xinterpreter. Files ending in .ms are manuscripts, in troff format using Xthe ms macro package (this file is a good example). Files ending in .st Xare smalltalk sources, compiled by the parser to make the initial object Ximage. Finally, there are a few small files that don't fall into any Xcategory, BUGS for listing notable bugs, the make file, and so on. X.PP XThe next step is to tailor the system to the type of enviornment it will be Xrun in. XFor most users, this should mean only changing at most three lines in the Xfile env.h. These three lines are near the front of the file and are Xclearly marked. Two are hard paths; for the default initial object image Xand for a temporary file to be used when editing. The third line is a X``meta-define'' which indicates the type of machine and/or operating system Xto be used. You should examine the rest of the file to see the variety of Xsystems supported. If you are unable to find anything appropriate, you will Xhave to look in the document install.ms for further instructions. In this Xlatter case, if you are sucessful in porting the software to a new machine, XI would be pleased if you could let me know the nature of the changes Xrequired. X.PP XOnce you have tailored the system, there are then Xthree steps involving in building the system; making the parser X(the component used to generate the initial object image), making the Xbytecode interpreter, and making the object image. Typing \fImake\fP, with Xno arguments, will do all three. For more detailed instructions on making Xthe system consult install.ms. X.PP XOnce you have sucessfully created the parser, the bytecode compiler, and Xan object image, type X.DS I Xst X.DE X.LP Xto run the system. XNow would be a very good time to go read explore.ms, which would tell you Xmore how to find your way around. X.SH XChanges from Little Smalltalk version one X.PP XThe following changes have been made from version one to version two: X.IP \(bu XThe user interface is slightly different. This is most apparent in the way Xnew classes are added (see explore.ms), and in the fact that expressions will Xnot be printed unless you explicitly request printing, and in the fact that Xnew global variables cannot be created at the command level merely by Xassignment. X.IP \(bu XMuch (very much) more of the system is now written in Smalltalk, rather Xthan C. This allows the user to see, and modify it if they wish. XThis also means that the virtual machine is now much smaller. X.IP \(bu XThe pseudo variable selfProcess is no longer supported. XThe variables true, false and nil are now treated as global variables, not Xpseudo variables (see below). XThere are plans for adding processes to version two, but they have not Xbeen formalized yet. X.IP \(bu XGlobal variables are now supported; in fact classes are now simply global Xvariables, as are the variables true, false, smalltalk and nil. XThe global variable globalNames contains the dictionary of all currently Xknown global variables and their values. X(Pool variables are still not supported). X.IP \(bu XNumbers are a little more robust. If integer operations overflow, they are Xautomatically converted into floating point numbers. This is under control Xof Smalltalk code, so if I (or, preferably, somebody else) ever get around Xto implementing infinite precision numbers, they can easily be added in. X.IP \(bu XThe internal bytecodes are slightly different. In particular, the bytecode Xrepresenting ``send to super'' has been eliminated, and a bytecode representing X``do a primitive'' has been added. X.IP \(bu XThe internal representation of objects is different. Instead of the X``super-object'' chain, objects are now created big enough to hold all the Xinstance variables for all their superclasses. (This is the way it is done Xin Smalltalk-80, and, to the best of my knowledge, in Smalltalk-V). X.IP \(bu XThe Collection hierarchy has been rearranged. The rational for this change Xis explained in more detail in another essay. X(possibly not written yet). X.IP \(bu XSome methods, most notably the error message methods, have been moved out Xof class Object and into class Smalltalk. X.IP \(bu XThe syntax for primitives is different; the keyword \fBprimitive\fP has been Xeliminated, and named primitives are now gone as well. XFewer actions are performed by primitives, having been Xreplaced by Smalltalk methods. X.IP \(bu XCommand line options, such as the fast load feature, have been eliminated. XHowever, since version two reads in a binary object image, not a textual Xfile, loading should be considerably faster. X.SH XElectronic Communication X.PP XHere is my address, various net addresses: X.DS I XTim Budd XOregon State University XDepartment of Computer Science XCorvallis, Oregon 97331 USA X(503) 754-3273 X Xbudd@ cs.orst.edu X X{tektronix, hp-pcd} !orstcs!budd X.DE X.SH XChanges X.PP XI want to emphasize that this is not even a beta-test version (does that Xmake it an alpha or a gamma version?). I will be making a number of Xchanges, hopefully just additions to the initial image, in the next Xfew months. In addition, I hope to prepare versions for other machines, Xnotably the Macintosh and the IBM PC. I am also encouraging others to Xport the system to new machines. If you have done so, please let me Xknow. / echo 'x - install.ms' sed 's/^X//' > install.ms << '/' X.SH XInstalling Little Smalltalk X.PP XFor most users the following simple steps should suffice to build the Xsystem. X.IP \(bu XUnpack the sources. X.IP \(bu XEdit the file env.h, changing the metadefine to an already known system Xtype. X.IP \(bu XType \fImake\fP. X.PP XThere are a few systems for which more extensive work is required; Xand of course porting the system to new environments may entail Xconsiderable work. XThe remainder of this document is intended to provide more detailed Xinformation for the curious, and to help those individuals for whom the Xprevious instructions don't work. X.SH XIBM PC and Turbo C X.PP XFor these systems there are two small changes that are required, in addition Xto those noted above. In the method for saveImage, class Smalltalk, file Xunix.c, the mode for the open statement must be 'wb', not 'w'. XAnd in method delete, class File, file unix.c, the command used to delete Xfiles should be ``DEL'', not ``rm''. XIt may also be necessary to redefine the value of the global variable X\fIeditor\fP (set by the script found in file script.ini, used when making Xthe initial image). Also the name of a temporary file (found in method XscratchFile, class File, file unix.st) may have to be changed. X.SH XTektronix 4404 X.PP XThere are several changes that are required to get the system to run on the X4404. Start by defining the metadefine constant SYSV in env.h. XNext, change the Makefile in the following ways, X.IP \(bu XTranslate all the .o extensions to .r . X.IP \(bu XRemove any CFLAGS definitions. X.IP \(bu XChange the rules for parse and st to use +o= notation, which must appear at Xthe end. Also add a call on headset, setting the program size to at least X512K (more if you have it). X.DS I Xst: \fIfile names\fP X cc \fIfile names\fP +o=st X headset st +b=512K X.DE X.SH XPorting to new systems X.PP XThere are many ways in which compilers and operating systems differ Xfrom each other. XA fair amount of work has been expanded in making sure the software will Xoperate on most machines, which requires that different code fragements be Xused on different systems. In large part these are controlled by a single X``meta-define'' in the file env.h. Setting this one value then causes the Xexpansion of another code segment, which then defines many more options. X.PP XIn the event that you are attempting to port the software to a system that Xhas not previously been defined, you will need to decide which set of Xoptions to enable. The next two sections contain information you may need Xin making this determination. X.SH XDefine Options X.PP XMany options are specified merely by giving or not giving a DEFINE Xstatement in the file env.h. The following table presents the meaning for Xeach of these values: X.de Op X.IP \\fB\\$1\\fP X.br X.. X.Op ALLOC XDefined If there is an include file called alloc.h which defines calloc, Xmalloc, and the like. X.Op BINREADWRITE XDefined if the fopen specification for binary files must include the "b" Xmodifier. This is true on many MS-DOS inspired systems. X.Op NOENUMS XDefined if enumerated datatypes are not supported. If defined, these will Xbe replaced by #define constants. X.Op NOTYPEDEF XDefined if the typedef construct is not supported. If defined, these will Xbe replaced by #define constructs. X.Op NOVOID XDefined if the void keyword is not recognized. XIf defined, expect \fIlint\fP to complain a lot about functions returning Xvalues which are sometimes (or always) ignored. X.Op SIGNALS XUsed if \fIboth\fP the package and the package are Xavilable, and if the routine used to set signals is signal. XIncompatible with \fBSSIGNALS\fP. X.Op SSIGNALS XUsed if \fIboth\fP the package and the package are Xavailable, and if the routine used to set signals is ssignal. XIncompatible with \fBSIGNALS\fP. X.Op STRING XUsed if the string functions (strcpy, strcat and the like) are found in X. This switch is incompatible with \fBSTRINGS\fP. X.Op STRINGS XUsed if the string functions (strcpy, strcat and the like) are found in X. This switch is incompatible with \fBSTRING\fP. X.LP XIn addition, several routines can optionally be replaced by macros for Xgreater efficiency. See the file memory.h for more information. X.SH XObject Memory X.PP XThere are several datatypes, not directly supported by C, that are used in Xthe Little Smalltalk system. The first of these is the datatype byte. XA byte is an eight bit unsigned (hence positive) quantity. XOn many systems the appropriate datatype is unsigned char, however on other Xsystems this declaration is not recognized and other forms may be required. XTo aid in coverting to and from bytes the macro byteToInt() is used, which Xconverts a byte value into an integer. In addition, the routines byteAt Xand byteAtPut are used to get and put bytes from byte strings. X.PP XThe other datatype is that used to represent object points. On most Xmachines in which a short is 16 bits, the datatype short should suffice. XMuch more information on the memory module can be found in the file Xmemory.h. X.SH XOptions in Building the System X.PP XTo create the parser type X.DS I Xmake parse X.DE X.PP XThe resulting program, called parse, is used to generate the object image Xinitially loaded into the bytecode interpreter. X.PP XNext, make the interpreter itself by typing X.DS I Xmake st X.DE X.PP XNote that the interpreter and the parser share some files. X.PP XFinally, produce an initial object image. The image created when you type X.DS I Xmake sunix X.DE X.LP Xis the smallest and fastest. It is a single process version of smalltalk. XA slower multiprocess version can be created by typing ``make munix''*. X.FS X* Multi processing from munix is done entirely in Smalltalk. XWhile this is a good idea from the point of view of keeping the bytecode Xinterpreter small and giving one the greatest flexibility, there seems to Xbe a dramatic performance penalty. I'm considering the alternatives. X.FE XOf more interest, an image containing test cases Xcan be generated by typing ``make stest''. XThis command compiles several test cases, then runs st on a script which Xinvokes all the test cases. There is a similar command mtest for the Xmultiprocessing version. X.PP XOnce you have created an object image, type X.DS I Xst X.DE X.LP Xto run the system. XBy default the image file ``imageFile'' is read. You can optionally Xuse a different image file by giving the name on the command line following Xthe st command. X.SH XCompiler Bogosities X.PP XThis section will detail some of the unnatural actions you may have to Xperform to get Little Smalltalk to work with a few brain damaged compilers. XSince there are so few of these, it I see it as a problem more with the Xcompilers than with the system, the code make no accomodation for these. X.PP XOn some older Sequent Balance C compilers, incorrect code is produced for X.DS I Xhash %= (objectSize(symbols) / 2) X.DE X.LP XIn the file image.c. This should be replaced by X.DS I Xhash = hash % (objectSize(symbols) / 2) X.DE X.PP XOn many systems external names are restricted to six (or even five!) Xcharacters. This causes significant problems. I have heard of systems Xwhich automatically generate a sed script to take care of this, but have Xnot found one yet. If you know of such a thing let me know. X.SH XHelping Others X.PP XIf you succeed in getting Little Smalltalk ported to a machine or operating Xsystem not described in env.h, I would be most pleased to get a listing of Xthe changes you made. These can then be incorporated into the latest Xdistribution. / echo 'x - lex.c' sed 's/^X//' > lex.c << '/' X/* X Little Smalltalk, version 2 X Written by Tim Budd, Oregon State University, July 1987 X X lexical analysis routines for method parser X should be called only by parser X*/ X X# include X# include X# include "env.h" X# include "memory.h" X# include "lex.h" X Xextern double atof(); X X/* global variables returned by lexical analyser */ X Xtokentype token; /* token variety */ Xchar tokenString[80]; /* text of current token */ Xint tokenInteger; /* integer (or character) value of token */ Xdouble tokenFloat; /* floating point value of token */ X X/* local variables used only by lexical analyser */ X Xstatic char *cp; /* character pointer */ Xstatic char pushBuffer[10]; /* pushed back buffer */ Xstatic int pushindex; /* index of last pushed back char */ Xstatic char cc; /* current character */ Xstatic long longresult; /* value used when building int tokens */ X X/* lexinit - initialize the lexical analysis routines */ Xnoreturn lexinit(str) Xchar *str; X{ X pushindex = 0; X cp = str; X /* get first token */ X ignore nextToken(); X} X X/* pushBack - push one character back into the input */ Xstatic pushBack(c) Xchar c; X{ X pushBuffer[pushindex++] = c; X} X X/* nextChar - retrieve the next char, from buffer or input */ Xstatic char nextChar() X{ X if (pushindex > 0) cc = pushBuffer[--pushindex]; X else if (*cp) cc = *cp++; X else cc = '\0'; X return(cc); X} X X/* isClosing - characters which can close an expression */ Xstatic boolean isClosing(c) Xchar c; X{ X switch(c) { X case '.': case ']': case ')': case ';': X return(true); X } X return(false); X} X X/* isSymbolChar - characters which can be part of symbols */ Xstatic boolean isSymbolChar(c) Xchar c; X{ X if (isdigit(c) || isalpha(c)) return(true); X if (isspace(c) || isClosing(c)) return(false); X return(true); X} X X/* singleBinary - binary characters that cannot be continued */ Xstatic boolean singleBinary(c) Xchar c; X{ X switch(c) { X case '[': case '(': case ')': case ']': X return(true); X } X return(false); X} X X/* binarySecond - return true if char can be second char in binary symbol */ Xstatic boolean binarySecond(c) Xchar c; X{ X if (isalpha(c) || isdigit(c) || isspace(c) || isClosing(c) || X singleBinary(c)) X return(false); X return(true); X} X Xtokentype nextToken() X{ char *tp; X boolean sign; X X /* skip over blanks and comments */ X while(nextChar() && (isspace(cc) || (cc == '"'))) X if (cc == '"') { X /* read comment */ X while (nextChar() && (cc != '"')) ; X if (! cc) break; /* break if we run into eof */ X } X X tp = tokenString; X *tp++ = cc; X X if (! cc) /* end of input */ X token = inputend; X X else if (isalpha(cc)) { /* identifier */ X while (nextChar() && isalnum(cc)) X *tp++ = cc; X if (cc == ':') { X *tp++ = cc; X token = namecolon; X } X else { X pushBack(cc); X token = nameconst; X } X } X X else if (isdigit(cc)) { /* number */ X longresult = cc - '0'; X while (nextChar() && isdigit(cc)) { X *tp++ = cc; X longresult = (longresult * 10) + (cc - '0'); X } X if (longCanBeInt(longresult)) { X tokenInteger = longresult; X token = intconst; X } X else { X token = floatconst; X tokenFloat = (double) longresult; X } X if (cc == '.') { /* possible float */ X if (nextChar() && isdigit(cc)) { X *tp++ = '.'; X do X *tp++ = cc; X while (nextChar() && isdigit(cc)); X if (cc) pushBack(cc); X token = floatconst; X *tp = '\0'; X tokenFloat = atof(tokenString); X } X else { X /* nope, just an ordinary period */ X if (cc) pushBack(cc); X pushBack('.'); X } X } X else X pushBack(cc); X X if (nextChar() && cc == 'e') { /* possible float */ X if (nextChar() && cc == '-') { X sign = true; X ignore nextChar(); X } X else X sign = false; X if (cc && isdigit(cc)) { /* yep, its a float */ X *tp++ = 'e'; X if (sign) *tp++ = '-'; X while (cc && isdigit(cc)) { X *tp++ = cc; X ignore nextChar(); X } X if (cc) pushBack(cc); X *tp = '\0'; X token = floatconst; X tokenFloat = atof(tokenString); X } X else { /* nope, wrong again */ X if (cc) pushBack(cc); X if (sign) pushBack('-'); X pushBack('e'); X } X } X else X if (cc) pushBack(cc); X } X X else if (cc == '$') { /* character constant */ X tokenInteger = (int) nextChar(); X token = charconst; X } X X else if (cc == '#') { /* symbol */ X tp--; /* erase pound sign */ X if (nextChar() == '(') X token = arraybegin; X else { X pushBack(cc); X while (nextChar() && isSymbolChar(cc)) X *tp++ = cc; X pushBack(cc); X token = symconst; X } X } X X else if (cc == '\'') { /* string constant */ X tp--; /* erase pound sign */ X strloop: X while (nextChar() && (cc != '\'')) X *tp++ = cc; X /* check for nested quote marks */ X if (cc && nextChar() && (cc == '\'')) { X *tp++ = cc; X goto strloop; X } X pushBack(cc); X token = strconst; X } X X else if (isClosing(cc)) /* closing expressions */ X token = closing; X X else if (singleBinary(cc)) { /* single binary expressions */ X token = binary; X } X X else { /* anything else is binary */ X if (nextChar() && binarySecond(cc)) X *tp++ = cc; X else X pushBack(cc); X token = binary; X } X X *tp = '\0'; X return(token); X} / echo 'x - names.c' sed 's/^X//' > names.c << '/' X/* X Little Smalltalk, version 2 X Written by Tim Budd, Oregon State University, July 1987 X X Name Table module X X A name table is the term used for a Dictionary indexed by symbols. X There are two name tables used internally by the bytecode interpreter. X The first is the table, contained in the variable globalNames, X that contains the names and values of all globally accessible X identifiers. The second is the table of methods associated with X every class. Notice that in neither of these cases does the X system ever put anything INTO the tables, thus there are only X routines here for reading FROM tables. X X (putting things INTO the table is all done in Smalltalk code, X using the methods from class Dictionary) X X One complication of instances of class Symbol is that all X symbols must be unique, not only so that == will work as expected, X but so that memory does not get overly clogged up with symbols. X Thus all symbols are kept in a hash table, and when new symbols X are created (via newSymbol(), below) they are inserted into this X table, if not already there. X X This module also manages the definition of various symbols that are X given fixed values for efficiency sake. These include the objects X nil, true, false, and various classes. X*/ X X# include X# include "env.h" X# include "memory.h" X# include "names.h" X X/* global variables used to avoid repeated examinations of the global symbol table */ Xobject trueobj = nilobj; /* the pseudo variable true */ Xobject falseobj = nilobj; /* the pseudo variable false */ Xobject smallobj = nilobj; /* the pseudo variable smalltalk */ Xobject arrayclass = nilobj; /* the class ``Array'' */ Xobject blockclass = nilobj; /* the class ``Block'' */ Xobject contextclass = nilobj; /* the class ``Context'' */ Xobject intclass = nilobj; /* the class ``Integer'' */ Xobject intrclass = nilobj; /* the class ``Interpreter'' */ Xobject symbolclass = nilobj; /* the class ``Symbol'' */ Xobject stringclass = nilobj; /* the class ``String'' */ X X/* X some messages are encoded in concise bytecode format - Xto reduce the size of the compiled methods X(also, in some cases, to more efficiently detect special cases Xhandled in the interpreter, rather than by methods) X*/ X Xchar *binStrs[] = {"+", "-", "<", ">", "<=", ">=", "=", "~=", "*", X"quo:", "rem:", "bitAnd:", "bitXor:", X"==", ",", "at:", "basicAt:", "do:", "coerce:", "error:", "includesKey:", X"isMemberOf:", "new:", "to:", "value:", "whileTrue:", "addFirst:", "addLast:", X0}; X Xobject binSyms[28]; X Xchar *unStrs[] = {"isNil", "notNil", "new", "value", "class", "size", X"basicSize", "print", "printString", 0}; X Xobject unSyms[9]; X Xchar *keyStrs[] = {"at:ifAbsent:", "at:put:", "basicAt:put:", "between:and:", X0}; X Xobject keySyms[4]; X Xobject nameTableLookup(dict, symbol) Xobject dict, symbol; X{ int hash, tablesize; X object table, link; X X /* first get hash table */ X table = basicAt(dict, 1); X X /* now see if table is valid */ X if ((tablesize = objectSize(table)) < 3) X sysError("system error","lookup on null table"); X else { X hash = 3 * ( symbol % (tablesize / 3)); X if (basicAt(table, hash+1) == symbol) X return(basicAt(table, hash+2)); X X /* otherwise look along the chain of links */ X for (link=basicAt(table, hash+3); link != nilobj; X link=basicAt(link, 3)) X if (basicAt(link, 1) == symbol) X return(basicAt(link, 2)); X X } X return (nilobj); X} X Xobject getClass(obj) Xobject obj; X{ X if (isInteger(obj)) X return(intclass); X return (classField(obj)); X} X Xstatic object globalGet(name) Xchar *name; X{ object newobj; X X newobj = globalSymbol(name); X if (newobj == nilobj) X sysError("symbol not found in image", name); X return(newobj); X} X Xnoreturn initCommonSymbols() X{ int i; X X trueobj = globalGet("true"); X falseobj = globalGet("false"); X smallobj = globalGet("smalltalk"); X arrayclass = globalGet("Array"); X blockclass = globalGet("Block"); X contextclass = globalGet("Context"); X intclass = globalGet("Integer"); X symbolclass = globalGet("Symbol"); X stringclass = globalGet("String"); X /* interpreter may or may not be there */ X intrclass = globalSymbol("Interpreter"); X X for (i = 0; i < 28; i++) X binSyms[i] = newSymbol(binStrs[i]); X X for (i = 0; i < 9; i++) X unSyms[i] = newSymbol(unStrs[i]); X X for (i = 0; i < 4; i++) X keySyms[i] = newSymbol(keyStrs[i]); X} X Xobject newArray(size) Xint size; X{ object newobj; X X if (arrayclass == nilobj) { X arrayclass = globalSymbol("Array"); X if (arrayclass == nilobj) X sysError("can't find global symbol","Array"); X } X newobj = allocObject(size); X setClass(newobj, arrayclass); X return(newobj); X} X Xobject newSymbol(str) Xchar *str; X{ int hash; X object newSym, link; X char *p; X X /* first compute hash value of string text */ X /* this is duplicated in image.c - make sure any changes match there */ X hash = 0; X for (p = str; *p; p++) X hash += *p; X if (hash < 0) hash = - hash; X hash = 2 * ( hash % (objectSize(symbols) / 2)); X X /* next look to see if it is in symbols - note that this X text duplicates that found in nameTableLookup, only using X string comparison instead of symbol comparison */ X newSym = basicAt(symbols, hash+1); X if (newSym && streq(str, charPtr(newSym))) X return(newSym); X X /* not in table, look along links */ X for (link=basicAt(symbols, hash+2); link != nilobj; link=basicAt(link,2)) { X newSym = basicAt(link, 1); X if (streq(str, charPtr(newSym))) X return(newSym); X } X X /* not found, make a new symbol */ X newSym = allocSymbol(str); X setClass(newSym, symbolclass); X X /* now insert new symbol in table, so next time we will find it */ X if (basicAt(symbols, hash+1) == nilobj) X basicAtPut(symbols, hash+1, newSym); X else { /* insert along links */ X link = allocObject(2); X basicAtPut(link, 1, newSym); X basicAtPut(link, 2, basicAt(symbols, hash+2)); X basicAtPut(symbols, hash+2, link); X } X X return(newSym); X} X Xobject newStString(value) Xchar *value; X{ object newobj; X X newobj = allocSymbol(value); X setClass(newobj, stringclass); X return(newobj); X} X Xobject newFloat(d) Xdouble d; X{ object newobj; X X newobj = allocFloat(d); X setClass(newobj, globalSymbol("Float")); X return(newobj); X} / echo 'x - todo' sed 's/^X//' > todo << '/' XThings to do X XWrite infinite precision number package. X XAdd traceback (unfortunately, this must be done in the interpreter, not Xin Smalltalk code). X XAdd descriptions to classes (to be displayed by display) X Xflush message cache shouldn't flush everything, just one entry. X Xchange process manager to use alloca, if available (this would allow Xprocesses to run as deep as the process stack, instead of the artificial Xlimitation now imposed). X Xadd methods to class Class so that assigning variables: or changing Xsuperclass will automatically recompile all methods. X XMake files a subclass of collection (to be useful, requires adding more Xfunctionality to files). X Xmodify the parser so that it can add to an existing image. X XDone X XClass File now added, changes interface to use files. XInteger arith ops now trap overflows XAllowed non-alpha tokens (such as #+ ) XFixed doEdit so that it allows you to retry. XFixed parser so that optimized messages don't require [ ] around args. XFixed parser so that empty blocks work correctly XTraps interrups and restarts gracefully now. XCreated much better user interface XFixed bug in lexer, preventing reading past end of input X Xmessages added: X display (for classes and collections) X respondsTo (for symbols) X changed indexOf to take a block, rather than a value X class addSubClass X smalltalk inquire: / echo 'x - top.ms' sed 's/^X//' > top.ms << '/' X.SH XWho's On Top? X.PP XOne of the most important decisions to be made in designing a new user Xinterface (or front end) for the Little Smalltalk system is whether the user Xinterface management code should sit on top of the Smalltalk bytecode Xinterpreter, setting up commands and invoking the interpreter to execute them, Xor underneith the bytecode interpreter, being invoked by Smalltalk, via the Xmechanism of primitive methods. Both schemes have advantages and disadvantages Xwhich we will discuss in this essay. X.PP XIn a simple interface, placing Smalltalk on top is often easier. The main Xdriver need only set up one initial call to the Smalltalk bytecode interpreter, Xand thereafter everything is done in Smalltalk. For example, we might put Xinitialization code in a method in class \fBSmalltalk\fP, as follows: X.DS L XClass Smalltalk X getString X \(ua <1> X| X run | string | X [ '> ' printNoReturn. X string <- smalltalk getString. X string notNil ] X whileTrue: [ (string size > 0) X ifTrue: [ smalltalk doIt: string ] ] X] X.DE X.PP XOnce the bytecode interpreter is started on the method \fBrun\fP, it will Xloop continuously, reading commands from the user (via the method X\fBgetString\fP) and executing them (via the method \fBdoIt:\fP). XPresumably the user has some way of indicating end of input, such as the Xunix control-D convention, which causes \fBgetString\fP to return the Xvalue nil. The \fIif\fP statement inside the while loop Xinsures that if the user simply hits the return key execution will quickly Xloop back to the prompt. X.PP XBesides making the initialization for the Little Smalltalk system easy, Xthis approach also has the advantage of putting more code into Smalltalk Xitself, where the user can see it and (presumably) modify it if they wish. XA general guideline is that it is better to put as much into Smalltalk Xas possible, since Smalltalk is easier to write and the bytecode representation Xusually smaller than the equivalent code in C. XNever the less, there are valid reasons why an implementor might choose Xa different technique. X.PP XFor example, if there are many other activities which should command the Xattention of the controlling program (window updates, mouse motions) the XSmalltalk code may not be able to respond fast enough, or might become too Xlarge and complex to be workable. XIn this case the only alternative is to have the front end respond directly Xto events, and only invoke the Smalltalk interpreter as time permits. XIn basic terms, the front end would perform the loop written in the method X\fBinit\fP shown above (along with handling various other tasks), and then Xcall upon the method in class \fBSmalltalk\fP Xto execute the message \fBdoIt:\fP. X.SH XHow to Do It X.PP XIn either of the two schemes described above, an important message is X\fBdoIt:\fP, which takes a string (presumably representing a Smalltalk Xexpression) and performs it. An easy way to perform this message is to Xmake a method out of the expression, by appending a message pattern Xon front, and then pass the string to the method parser. If the method Xparser is successful, the method can then be executed. X.DS L XdoIt: aString | method | X method <- Method new. X method text: ( 'proceed ', aString ). X (method compileWithClass: Smalltalk) X ifTrue: [ method executeWith: #( 0 ) ] X.DE X.PP XThe message \fBcompileWithClass:\fP compiles the method as if it was Xappearing as part of class Smalltalk. If compilation is successful, Xthe message \fBexecuteWith:\fP executes the message, using as arguments Xthe array #(0). The array that accompanies this message must have at Xleast one element, as the first value is used as the receiver for Xthe method. XSimilar techniques can be used for the message \fBprintIt:\fP, if desired. X.SH XThe Other End X.PP XThe opposite extreme from the front end are those messages that originate Xwithin the bytecode interpreter and must be communicated to the user. XWe can divide these values into four categories: X.IP 1. XSystem errors. These are all funnelled through the routine sysError(), found Xin memory.c. System errors are caused by dramatically wrong conditions, Xand should generally cause the system to abort after printing the message Xpassed as argument to sysError(). X.IP 2. XCompiler errors. As we noted above, the method compiler is used to Xparse expressions typed directly at the keyboard, so these message can Xalso arise in that manner. These are all funnelled through the routine XcompilError(), found in parse.c. These should print their arguments X(two strings), in an appropriate location on the users screen. XExecution continues normally after call. X.IP 3. XVarious primitives, found in primitive.c, are also used to print strings Xon the users terminal. In particular, an appropriate meaning should be Xgiven to the message \fBprint\fP in class \fBString\fP. What appropriate Xmeans is undoubtedly implementation specific. X.IP 4. XFinally, and perhaps most importantly, there must be some means provided Xto allow users to enter and edit methods. The interface for this task Xis standard; instances of class \fBClass\fP must respond to the messages X\fBaddMethod\fP and \fBeditMethod:\fP, the latter taking as argument a Xsymbol representing the name of a method. How they achieve their two Xtasks is, however, implementation specific. XUnder Unix, a simple implementation adds a new primitive for Strings; Xthis primitive copies the string into a temporary file, starts up the Xeditor on the file, and returns the contents of the file when the user Xexits the editor. Having this capability, the method editing code Xcan be given as follows. In class \fBClass\fP: X.DS L X addMethod X self doEdit: '' X| X editMethod: name | theMethod | X theMethod <- methods at: name X ifAbsent: [ 'no such method ' print. \(ua nil ]. X self doEdit: theMethod text X| X doEdit: startingText | theMethod | X theMethod <- Method new; X text: startingText edit. X (theMethod compileWithClass: self) X ifTrue: [ methods at: theMethod name put: theMethod ] X.DE X.LP XAnd in class \fBString\fP: X.DS L X edit X \(ua <19 self> X.DE X.LP XHere primitive 19 performs all the tasks of creating the temporary file, Xstarting the editor, and creating the string representing the file Xcontents when the editor is exited. X.PP XAlternative techniques, for example using windowing, would undoubtedly Xbe more complicated. / echo 'x - unix.st' sed 's/^X//' > unix.st << '/' X* X* Little Smalltalk, version 2 X* Written by Tim Budd, Oregon State University, July 1987 X* X* methods for the unix front end - single process version X* X* (override previous declaration, adding new instance variable) XDeclare Smalltalk Object errorRecoveryBlock XDeclare File Object name pointer X* (better override global variable as well ) XInstance Smalltalk smalltalk X* make global variables for standard files XInstance File stdin XInstance File stdout XInstance File stderr X* XClass File X asString | text line | X text <- ''. X [line <- self getString. line notNil] X whileTrue: [ text <- text , line ]. X ^ text X| X name: string X name <- string X| X name X ^ name X| X scratchFile X name <- 'junk.tmp' X| X open: mode X pointer <- <120 name mode>. X pointer isNil X ifTrue: [ smalltalk error: 'open failed'] X| X close X (pointer notNil) X ifTrue: [<121 pointer>]. X pointer <- nil. X| X delete X ('rm ', name) unixCommand X| X fileIn | line | X [ line <- self getString. line notNil ] X whileTrue: [ line <- line words: X [:x | x isAlphabetic ] . X Switch new; key: (line at: 1); X ifMatch: 'Class' do: [self fileInClass: line ] ; X ifMatch: 'Method' do: X [ self fileInMethod: line ] ; X else: [ ^ smalltalk error: X 'invalid format for fileIn'] ] X| X fileInClass: commandLine | name | X name <- (commandLine at: 2 X ifAbsent: [^ smalltalk error: X 'missing class name in Class directive']) X asSymbol. X globalNames at: name put: ( Class new; X name: name; X superClass: (globalNames at: ( X commandLine at: 3 X ifAbsent: [ ^ smalltalk error: X 'missing superclass name']) X asSymbol X ifAbsent: [ ^ smalltalk error: X 'unknown class']); X variables: (commandLine copyFrom: 4 to: X commandLine size ) ) X| X fileInMethod: commandLine X (commandLine size ~= 2) X ifTrue: [ ^ smalltalk error: X 'invalid Method command line ']. X (globalNames at: (commandLine at: 2) asSymbol X ifAbsent: [ ^ smalltalk error: X 'unknown class in Method header']) X fileInMethod: self X| X getString X ^ (pointer notNil) X ifTrue: [<125 pointer>] X| X getPrompt: aString X stdout printNoReturn: aString. X ^ self getString X| X inquire: aString | response | X response <- self getPrompt: aString. X response isNil X ifTrue: [ ^ false ]. X ^ 'yY' includes: (response at: 1 ifAbsent: []) X| X print: aString X (pointer notNil) X ifTrue: [<129 pointer aString>] X ifFalse: [smalltalk error: 'file not open'] X| X printNoReturn: aString X (pointer notNil) X ifTrue: [<128 pointer aString>] X ifFalse: [smalltalk error: 'file not open'] X| X readUntil: conditionBlock doing: actionBlock | line | X [ line <- self getString. line notNil] X whileTrue: [ (conditionBlock value: line) X ifTrue: [ ^ line ]. X actionBlock value: line ]. X ^ nil X| X saveImage X (pointer notNil) X ifTrue: [<127 pointer>] X ifFalse: [smalltalk error: 'file not open'] X] XClass Method X executeWith: arguments X ^ ( Context new ; method: self ; X temporaries: ( Array new: temporarySize) ; X arguments: arguments ) X executeFrom: 0 creator: nil X] XClass Class X addMethod X self doEdit: '' X| X addSubClass | name | X name <- (stdin getPrompt: 'Class Name? ') asSymbol. X globalNames at: name put: X ( Class new; name: name ; superClass: self ; X readInstanceVariables; readMethods ) X| X addMethodText: text | theMethod | X theMethod <- Method new; text: text. X (theMethod compileWithClass: self) X ifTrue: [ methods at: theMethod name put: theMethod. X smalltalk flushMessageCache. X ^ true ]. X ^ false X| X doEdit: startingText | text | X text <- startingText. X [ text <- text edit. X (self addMethodText: text) X ifTrue: [ false ] X ifFalse: [ stdin inquire: 'edit again (yn) ? ' ] X ] whileTrue X| X display X ('Class name: ', name asString) print. X (superClass notNil) X ifTrue: [ ('Superclass: ', superClass ) print ]. X 'Instance Variables:' print. X variables isNil X ifTrue: [ 'no instance variables ' print ] X ifFalse: [ variables display ]. X 'Subclasses: ' print. X self subClasses display X| X editMethod: name X self doEdit: ( methods at: name X ifAbsent: [ 'no such method ' print. ^ nil ] ) text X| X fileInMethod: file | text line | X text <- ''. X line <- file readUntil: [:x | '|[' includes: X (x at: 1 ifAbsent: [] ) ] X doing: [:x | text <- text , x]. X self addMethodText: text. X ^ line X| X fileOut: file X file printNoReturn: 'Class ', name asString. X file printNoReturn: ' ', superClass name asString. X variables do: [:x | file printNoReturn: ' ', x ]. X file print: ''. X methods do: [:x | self fileOutMethod: x name to: file ] X| X fileOutMethod: method to: file X file print: 'Method ', name asString. X file print: (methods at: method X ifAbsent: [^ smalltalk error: X 'no such method' ]) text. X file print: '|' X| X readInstanceVariables X self variables: X ((stdin getPrompt: 'Instance Variables? ') X words: [:x | x isAlphabetic ]) X| X readMethods X [ stdin inquire: 'Add a method (yn) ? ' ] X whileTrue: [ self addMethod ] X| X viewMethod: name X (methods at: name X ifAbsent: [ 'no such method ' print. ^ nil ]) text print X] XClass Smalltalk X error: aString X stderr print: 'Error: ',aString. X errorRecoveryBlock value X| X openFiles X stdin name: 'stdin'. X stdin open: 'r'. X stdout name: 'stdout'. X stdout open: 'w'. X stderr name: 'stderr'. X stderr open: 'w'. X| X commandLoop | string | X self openFiles. X [ string <- stdin getPrompt: '> '. string notNil ] X whileTrue: [ (string size strictlyPositive) X ifTrue: [ self doIt: string ] ] X| X doIt: aString | method | X errorRecoveryBlock <- [ ^ nil ]. X method <- Method new. X method text: ( 'proceed ', aString ). X (method compileWithClass: Object) X ifTrue: [ method executeWith: #( 1 ) ] X| X saveImage | name | X name <- stdin getPrompt: 'type image name: '. X File new; X name: name; X open: 'w'; X saveImage; X close. X ('image ', name, ' created') print X] XClass String X edit | file text | X file <- File new; X scratchFile; X open: 'w'; X print: self; X close. X (editor, ' ', file name) unixCommand. X file open: 'r'. X text <- file asString. X file close; delete. X ^ text X| X print X ^ stdout print: self X| X unixCommand X ^ <88 self> X] / echo 'Part 04 of small.v2 complete.' exit