home *** CD-ROM | disk | FTP | other *** search
- Subject: v11i086: Little Smalltalk interpreter, Part01/03
- Newsgroups: comp.sources.unix
- Sender: sources
- Approved: rs@uunet.UU.NET
-
- Submitted-by: Tim Budd <budd@cs.orst.edu>
- Posting-number: Volume 11, Issue 86
- Archive-name: little-st/part01
-
- The following is version two of the Little Smalltalk system, distributed
- in three parts. Little Smalltalk is an interpreter for the language
- Smalltalk.
-
- Questions or comments should be sent to Tim Budd,
- budd@oregon-state.csnet
- budd@cs.orst.edu (128.193.32.1)
- {tektronix, hp-pcd}!orstcs!budd
-
- -----------cut here--------------------------------------------
- : To unbundle, sh this file
- echo unbundling READ_ME 1>&2
- cat >READ_ME <<'End'
- .\" information on Little Smalltalk, version 2, beta release
- .SH
- General Overview
- .PP
- First, the obvious facts. This is not Smalltalk-80, nor even Smalltalk-V.
- This is the second version of the Little Smalltalk system, the first version
- of which is described in the book recently published by Addison-Wesley*.
- .FS
- * \fIA Little Smalltalk\fP, by Timothy A. Budd. Published by Addison
- Wesley, 1987. In better bookshops everywhere.
- .FE
- Version two is smaller and faster; does more in Smalltalk, not in C; and is
- designed to be more portable to a wider variety of machines (we are working
- on versions now for various PCs).
- .PP
- My attitude towards the language has been
- rather cavalier; what I liked I kept and what I didn't like I tossed out.
- This is explained in more detail in my book and in the end of this note.
- As a consequence, individuals familiar with ST-80 or ST-V will be struck
- by how much they are missing, and I make no apologies for this. On the
- other hand, you don't find ST-V posted to net.sources. Among the features
- you won't find here are metaclasses, class methods, windows, graphics
- support, and more.
- .PP
- What you will find is a small language that does give you the flavor of
- object oriented programming at very little cost. We are working to improve
- the system, and hope to distribute new versions as we develop them,
- as well as porting it to a wide range of machines.
- If you find (and preferably, fix!) bugs let us know.
- If you make nice additions let us know.
- If you want to make complements let us know.
- If you want to make complaints let us know.
- If you want support you just might be out of luck.
- .PP
- This software is entirely public domain. You are encouraged to give it
- to as many friends as you may have. As a courtesy, I would appreciate it
- if you left my name on the code as the author, but I make no other claims
- to it (I also, of course, disavow any liability for any bizarre things you
- may choose to do with it). Enjoy.
- .SH
- Building the System
- .PP
- There are three steps involving in building the system; making the parser
- (the component used to generate the initial object image), making the
- bytecode interpreter, and making the object image.
- .PP
- After you have unbundled all the files, to create the parser type
- .DS I
- make parse
- .DE
- .PP
- The resulting program, called parse, is used to generate the object image
- initially loaded into the bytecode interpreter.
- .PP
- Next, make the interpreter itself by typing
- .DS I
- make st
- .DE
- .PP
- Note that the interpreter and the parser share some files.
- .PP
- Finally, produce an initial object image. The image created when you type
- .DS I
- make sunix
- .DE
- .LP
- is the smallest and fastest. It is a single process version of smalltalk.
- A buggy multiprocess version can be created by typing ``make munix''*.
- .FS
- * Multi processing from munix is done entirely in Smalltalk.
- While this is a good idea from the point of view of keeping the bytecode
- interpreter small and giving one the greatest flexibility, there seems to
- be a dramatic performance penalty. I'm considering the alternatives.
- .FE
- Of more interest, an image containing test cases (***currently only
- the 8 queens***) can be generated by typing ``make stest''.
- In the latter case, the command ``test all'', when given in response to the
- prompt (see below), runs all the test cases.
- .PP
- Once you have created an object image, type
- .DS I
- st
- .DE
- .LP
- to run the system.
- By default the image file ``imageFile'' is read. You can optionally
- use a different image file by giving the name on the command line following
- the st command.
- .SH
- Getting Started
- .PP
- When you start version two Little Smalltalk under Unix, you will be given a
- prompt.
- You can enter expressions in response to the prompt, and the system will
- evaluate them (although it will not print the result unless you request it).
- For example:
- .DS I
- > (4 + 5) print
- 7
- .DE
- .PP
- You can create a new global variable (a variable known every place, including
- the command line) by simply inserting a command into the dictionary that
- maintains the names of all global variables. You use as key the name of
- the new global variable (as a Symbol), and as value the initial value to
- be associated with the variable.
- .DS I
- > globalNames at: #i put: 17
- > i print
- 17
- .DE
- .PP
- Global variables cannot be modified by the assignment arrow. In particular,
- the following gives an error:
- .DS I
- > i <- 16
- Compiler error: unknown variable i
- .DE
- .PP
- Global variables can, however, be used in expressions:
- .DS I
- > (i + 3) print
- 20
- .DE
- .PP
- The most common use for global variables is creating a new Class. A Class
- is simply a global variable, by convention (but only convention) being given
- a name beginning with an uppercase letter. For example:
- .DS I
- > globalNames at: #Employee put: Class new
- .DE
- .PP
- This creates a new class called \fBEmployee\fP, an instance of
- class \fBClass\fP. Various messages, understood by instances of class
- \fBClass\fP, can be used to initialize various features of this new object.
- (This would be a good time to take a peek at the file ``basicclasses'', which
- contains a textual description of all the methods used in the standard
- classes. Note carefully the methods used in class Class).
- .DS I
- > globalNames superClass: Object
- > globalNames name: #Employee
- > globalNames variables: #(department salary)
- .DE
- .PP
- The most important initializing message is \fBaddMethod\fP, which
- drops you into an editor (currently only \fIvi\fP), in which you enter
- the body of a method. When you exit the editor the method is compiled,
- and either entered into the method dictionary for the class (if there
- are no errors) or a sequence of error messages are displayed on the output
- device.
- .PP
- To save an object image, type the command
- .DS I
- smalltalk saveImage
- .DE
- You will be prompted for the name of the image file.
- .SH
- Changes from Little Smalltalk version one
- .PP
- The following changes have been made from version one to version two:
- .IP \(bu
- The user interface is slightly different. This is most apparent in the way
- new classes are added (see above).
- .IP \(bu
- Much (very much) more of the system is now written in Smalltalk, rather
- than C. This allows the user to see, and modify it if they wish.
- This also means that the virtual machine is now much smaller.
- .IP \(bu
- The pseudo variable selfProcess is no longer supported.
- The variables true, false and nil are now treated as global variables, not
- pseudo variables (see below).
- There are plans for adding processes to version two, but they have not
- been formalized yet.
- .IP \(bu
- Global variables are now supported; in fact classes are now simply global
- variables, as are the variables true, false, smalltalk and nil.
- The global variable globalNames contains the dictionary of all currently
- known global variables and their values.
- (Pool variables are still not supported).
- .IP \(bu
- The internal bytecodes are slightly different. In particular, the bytecode
- representing ``send to super'' has been eliminated, and a bytecode representing
- ``do a primitive'' has been added.
- .IP \(bu
- The Collection hierarchy has been rearranged. The rational for this change
- is explained in more detail in another essay.
- (possibly not written yet).
- .IP \(bu
- Some methods, most notably the error message methods, have been moved out
- of class Object and into class Smalltalk.
- .IP \(bu
- The syntax for primitives is different; the keyword \fBprimitive\fP has been
- eliminated, and named primitives are now gone as well.
- Fewer actions are performed by primitives, having been
- replaced by Smalltalk methods.
- .IP \(bu
- Command line options, such as the fast load feature, have been eliminated.
- However, since version two reads in a binary object image, not a textual
- file, loading should be considerably faster.
- .SH
- Electronic Communication
- .PP
- Here is my address, various net addresses:
- .DS I
- Tim Budd
- Oregon State University
- Department of Computer Science
- Corvallis, Oregon 97331 USA
- (503) 754-3273
-
- budd@oregon-state.csnet
-
- {tektronix, hp-pcd} !orstcs!budd
- .DE
- .SH
- Changes
- .PP
- I want to emphasize that this is not even a beta-test version (does that
- make it an alpha or a gamma version?). I will be making a number of
- changes, hopefully just additions to the initial image, in the next
- few months. In addition, I hope to prepare versions for other machines,
- notably the Macintosh and the IBM PC. I am also encouraging others to
- port the system to new machines. If you have done so, please let me
- know.
- End
- echo unbundling Bugs 1>&2
- cat >Bugs <<'End'
- objects are limited to size 256
- this mostly limits the text (char) size of methods - to 512 chars.
- this could be fixed by changing memory.c.
-
- nested array literals don't seem to work properly
-
- radices other than 10 aren't implemented.
-
- parser should leave method text in method, so it can be edited dynamically
- (does this now, but it should be an option).
-
- The collection hierarchy has been completely reorginized (this isn't a bug)
- many of the more obscure messages are left unimplmented.
- many of the abstract classes are eliminated
- Bags have been eliminated (they can be replaced by lists)
- collections are now magnitudes (set subset relations)
-
- The basic classes are somewhat incomplete, in particular
- points aren't implemented
- radians are implemented (neither are trig functions)
-
- Bytearrays are a bit odd. In particular,
- converting to bytearrays gives something too big (by twice)
- converting bytearrays to strings can cause bugs if the last
- byte is not zero (causing non null terminated strings)
-
- Files aren't implemented;
- when they are addMethod and editMethod should be changed to use
- Smalltalk files.
-
- Semaphores and processes aren't implemented yet - even in the multiprocess
- version
- initial experiments aren't encouraging -
- they seem to be too slow.
-
- PROJECTS______________________________________________________________
- For those with time on their hands and nothing to do, here is a list
- of several projects that need doing.
-
- 1. Profiling indicates that about 45% of execution time is spent in the
- routine ``execute'', in interp.c. Rewrite this in your favorite assembly
- language to speed it up.
-
- 2. Rewrite the memory manager. Possible changes
- a. use garbage collection of some sort
- b. allow big objects (bigger than 256 words)
-
- 3. Rewrite the process manager in assembly language, permitting the
- Smalltalk process stack to exist intermixed with the C
- execution stack.
-
- 4. Port to your favorite machine, making the interface fit the machine.
- End
- echo unbundling Makefile 1>&2
- cat >Makefile <<'End'
- #
- # Makefile for Little Smalltalk, version 2
- #
- CFLAGS = -p -O
-
- COMMONc = memory.c names.c lex.c parser.c
- COMMONo = memory.o names.o lex.o parser.o
- PARSEc = comp.c $(COMMONc) image.c
- PARSEo = comp.o $(COMMONo) image.o
- STc = main.c $(COMMONc) process.c primitive.c interp.c
- STo = main.o $(COMMONo) process.o primitive.o interp.o
- classes = basicclasses unixclasses multclasses unix2classes testclasses
- B1F = READ_ME Bugs Makefile at top *.h comp.c image.c main.c process.c
- B2F = $(COMMONc) primitive.c interp.c
- B3F = $(classes) stest.out
-
- install: parse sunix st
- echo "created single process version, see docs for more info"
-
- #
- # parse - the object image parser.
- # used to build the initial object image
- #
- parse: $(PARSEo)
- cc -o parse $(CFLAGS) $(PARSEo)
-
- parseprint:
- pr *.h $(PARSEc) | lpr
-
- parselint:
- lint $(PARSEc)
-
- #
- # st - the actual bytecode interpreter
- # runs bytecodes from the initial image, or another image
- #
- st: $(STo)
- cc $(CFLAGS) -o st $(STo) -lm
-
- stlint:
- lint $(STc)
-
- stprint:
- pr *.h $(STc) | lpr
-
- report: memory.o report.o
- cc -o report memory.o report.o
-
- #
- # image - build the initial object image
- #
- classlpr:
- pr $(classes) | lpr
-
- sunix: parse
- parse basicclasses unixclasses
-
- munix: parse
- parse basicclasses multclasses unix2classes
-
- stest: parse
- parse basicclasses unixclasses testclasses
-
- mtest: parse
- parse basicclasses multclasses unix2classes testclasses
-
- #
- # distribution bundles
- #
-
- bundles:
- bundle $(B1F) >bundle.1
- bundle $(B2F) >bundle.2
- bundle $(B3F) >bundle.3
-
- tar:
- tar cvf ../smalltalk.v2.tar .
- compress -c ../smalltalk.v2.tar >../smalltalk.v2.tar.Z
- End
- echo unbundling at 1>&2
- cat >at <<'End'
- .LP
- (note: this is the first of a series of essays descriging how various
- features of the Little Smalltalk bytecodes work).
- .SH
- Where It's At
- .PP
- This short note explains how the messages \fBat:\fP, \fBat:put:\fP, and their
- relatives are defined and used in collections. We start by discussing the
- simplest form of collections, arrays and strings.
- .PP
- The message \fBat:\fP is not defined anywhere in class \fBArray\fP or any of
- its subclasses. Instead, this message is inherited from
- class \fBCollection\fP, which defines it using the following method:
- .DS I
- \fBat:\fP index
- \(ua self at: index
- ifAbsent: [ smalltalk error: 'index to at: illegal' ]
- .DE
- .PP
- The functioning of the message \fBerror:\fP is the topic of another essay;
- it is sufficient for our purposes to note only that this message prints out
- the error string and returns nil. By redefining \fBat:\fP in this fashion,
- the subclasses of \fBCollection\fP need not be concerned about how to deal
- with errors in cases where no error recovery action has been specified.
- .PP
- For an array, an index is out of bounds if it is either less than 1 or greater
- than the size of the array. This is tested by a method in class \fBArray\fP:
- .DS I
- \fBincludesKey:\fP index
- ^ index between: 1 and: self size
- .DE
- .PP
- The message \fBsize\fP is defined in class \fBArray\fP in terms of the
- message \fBbasicSize\fP
- .DS I
- \fBsize\fP
- ^ self basicSize
- .DE
- .PP
- The message \fBbasicSize\fP (as well as \fBbasicAt:\fP, discussed below)
- is inherited from class
- \fBObject\fP. It can be used on any object; on non-arrays it returns
- the number of instance variables for the object. The messages \fBbasicSize\fP
- and \fBbasicAt:put:\fP can be used by system
- classes, for example debuggers, to access instance variables in an object
- without having explicit access to the instance variables. One must be
- careful, however,
- \fBbasicAt:\fP produces a system error, and not a Smalltalk error message,
- if it is given an index value that is out of range.
- .PP
- Using \fBincludesKey:\fP for a test, a value is only accessed if the index
- is legal. The following method appears in class \fBArray\fP:
- .DS I
- \fBat:\fP index \fBifAbsent:\fP exceptionBlock
- ^ (self includesKey: index)
- ifTrue: [ self basicAt: index ]
- ifFalse: [ exceptionBlock value ]
- .DE
- .PP
- A subclass of \fBArray\fP is the class \fBByteArray\fP. A byte array is a form
- of array in which the elements can only take on values from zero to 255, or
- to put it another way, values that can be stored in one byte.
- On most 16 bit machines, we can store two such bytes in the space it takes
- to store one object pointer. Thus, the message \fBsize\fP is redefined
- in class \fBByteArray\fP as follows:
- .DS I
- \fBsize\fP
- \(ua self basicSize * 2
- .DE
- .LP
- Note that this implies that byte arrays always have an even number of
- elements. Next the message \fBbasicAt:\fP is redefined to use a byte,
- instead of object, form of index. This is accomplished using a primitive
- method, (the message \fBbasicAt:\fP is handled in a similar fashion in
- class \fBObject\fP, only using a different primitive).
- .DS I
- \fBbasicAt:\fP index
- \(ua <26 self index>
- .DE
- .PP
- Like a byte array, a string can also store two byte values in the space
- it takes to store a single object pointer. Unlike a byte array, however,
- a string can be any length, not just an even length. Therefore the message
- \fBsize\fP is redefned in class \fBString\fP, a subclass of \fBByteArray\fP.
- .DS I
- \fBsize\fP
- \(ua <14 self>
- .DE
- .PP
- Another difference between a string and a byte array is that the value
- returned by a string must be a character, not an integer. Therefore
- \fBbasicAt:\fP must also be redefined. By using the message \fBbasicAt:\fP
- defined in \fBByteArray\fP, (the superclass of String, and therefore accessible
- via the pseudo variable \fBsuper\fP) the method can obtain the integer value
- of the appropriate character. This value is then used to create a new
- instance of class \fBChar\fP:
- .DS I
- \fBbasicAt:\fP index
- \(ua Char new; value: (super basicAt: index)
- .DE
- .PP
- A value is placed into an array using the message \fPat:put:\fP. As with
- \fBat:\fP, a value should only be placed if the index represents a legal
- subscript. This is checked in the following method:
- .DS I
- \fBat:\fP index \fBput:\fP value
- (self includesKey: index)
- ifTrue: [ self basicAt: index put: value ]
- ifFalse: [ smalltalk error:
- 'illegal index to at:put: for array' ]
- .DE
- .PP
- As was the case with \fBbasicAt:\fP, one version of \fBbasicAt:put:\fP,
- to be used by arrays of objects, is defined as part of class \fBObject\fP.
- A different version is found in class \fBByteArray\fP. Finally a third
- version, which first checks to see if the argument is a Character, is found
- in class \fBString\fP.
- .DS I
- \fBat:\fP index \fBput:\fP aValue
- (aValue isMemberOf: Char)
- ifTrue: [ super basicAt: index put: aValue asciiValue ]
- ifFalse: [ smalltalk error:
- 'cannot put non Char into string' ]
- .DE
- .SH
- Exercises
- .IP 1.
- Describe the sequence of messages used to respond to the following:
- .DS B
- x \(<- #(1 2 3) at: 2
- .DE
- .IP 2.
- Describe how the execution of the above expression could be speeded up by
- adding new methods. Note if your methods are specific to arrays of objects,
- arrays of bytes, or strings.
- End
- echo unbundling top 1>&2
- cat >top <<'End'
- .SH
- Who's On Top?
- .PP
- One of the most important decisions to be made in designing a new user
- interface (or front end) for the Little Smalltalk system is whether the user
- interface management code should sit on top of the Smalltalk bytecode
- interpreter, setting up commands and invoking the interpreter to execute them,
- or underneith the bytecode interpreter, being invoked by Smalltalk, via the
- mechanism of primitive methods. Both schemes have advantages and disadvantages
- which we will discuss in this essay.
- .PP
- In a simple interface, placing Smalltalk on top is often easier. The main
- driver need only set up one initial call to the Smalltalk bytecode interpreter,
- and thereafter everything is done in Smalltalk. For example, we might put
- initialization code in a method in class \fBSmalltalk\fP, as follows:
- .DS L
- Class Smalltalk
- getString
- \(ua <1>
- |
- run | string |
- [ '> ' printNoReturn.
- string <- smalltalk getString.
- string notNil ]
- whileTrue: [ (string size > 0)
- ifTrue: [ smalltalk doIt: string ] ]
- ]
- .DE
- .PP
- Once the bytecode interpreter is started on the method \fBrun\fP, it will
- loop continuously, reading commands from the user (via the method
- \fBgetString\fP) and executing them (via the method \fBdoIt:\fP).
- Presumably the user has some way of indicating end of input, such as the
- unix control-D convention, which causes \fBgetString\fP to return the
- value nil. The \fIif\fP statement inside the while loop
- insures that if the user simply hits the return key execution will quickly
- loop back to the prompt.
- .PP
- Besides making the initialization for the Little Smalltalk system easy,
- this approach also has the advantage of putting more code into Smalltalk
- itself, where the user can see it and (presumably) modify it if they wish.
- A general guideline is that it is better to put as much into Smalltalk
- as possible, since Smalltalk is easier to write and the bytecode representation
- usually smaller than the equivalent code in C.
- Never the less, there are valid reasons why an implementor might choose
- a different technique.
- .PP
- For example, if there are many other activities which should command the
- attention of the controlling program (window updates, mouse motions) the
- Smalltalk code may not be able to respond fast enough, or might become too
- large and complex to be workable.
- In this case the only alternative is to have the front end respond directly
- to events, and only invoke the Smalltalk interpreter as time permits.
- In basic terms, the front end would perform the loop written in the method
- \fBinit\fP shown above (along with handling various other tasks), and then
- call upon the method in class \fBSmalltalk\fP
- to execute the message \fBdoIt:\fP.
- .SH
- How to Do It
- .PP
- In either of the two schemes described above, an important message is
- \fBdoIt:\fP, which takes a string (presumably representing a Smalltalk
- expression) and performs it. An easy way to perform this message is to
- make a method out of the expression, by appending a message pattern
- on front, and then pass the string to the method parser. If the method
- parser is successful, the method can then be executed.
- .DS L
- doIt: aString | method |
- method <- Method new.
- method text: ( 'proceed ', aString ).
- (method compileWithClass: Smalltalk)
- ifTrue: [ method executeWith: #( 0 ) ]
- .DE
- .PP
- The message \fBcompileWithClass:\fP compiles the method as if it was
- appearing as part of class Smalltalk. If compilation is successful,
- the message \fBexecuteWith:\fP executes the message, using as arguments
- the array #(0). The array that accompanies this message must have at
- least one element, as the first value is used as the receiver for
- the method.
- Similar techniques can be used for the message \fBprintIt:\fP, if desired.
- .SH
- The Other End
- .PP
- The opposite extreme from the front end are those messages that originate
- within the bytecode interpreter and must be communicated to the user.
- We can divide these values into four categories:
- .IP 1.
- System errors. These are all funnelled through the routine sysError(), found
- in memory.c. System errors are caused by dramatically wrong conditions,
- and should generally cause the system to abort after printing the message
- passed as argument to sysError().
- .IP 2.
- Compiler errors. As we noted above, the method compiler is used to
- parse expressions typed directly at the keyboard, so these message can
- also arise in that manner. These are all funnelled through the routine
- compilError(), found in parse.c. These should print their arguments
- (two strings), in an appropriate location on the users screen.
- Execution continues normally after call.
- .IP 3.
- Various primitives, found in primitive.c, are also used to print strings
- on the users terminal. In particular, an appropriate meaning should be
- given to the message \fBprint\fP in class \fBString\fP. What appropriate
- means is undoubtedly implementation specific.
- .IP 4.
- Finally, and perhaps most importantly, there must be some means provided
- to allow users to enter and edit methods. The interface for this task
- is standard; instances of class \fBClass\fP must respond to the messages
- \fBaddMethod\fP and \fBeditMethod:\fP, the latter taking as argument a
- symbol representing the name of a method. How they achieve their two
- tasks is, however, implementation specific.
- Under Unix, a simple implementation adds a new primitive for Strings;
- this primitive copies the string into a temporary file, starts up the
- editor on the file, and returns the contents of the file when the user
- exits the editor. Having this capability, the method editing code
- can be given as follows. In class \fBClass\fP:
- .DS L
- addMethod
- self doEdit: ''
- |
- editMethod: name | theMethod |
- theMethod <- methods at: name
- ifAbsent: [ 'no such method ' print. \(ua nil ].
- self doEdit: theMethod text
- |
- doEdit: startingText | theMethod |
- theMethod <- Method new;
- text: startingText edit.
- (theMethod compileWithClass: self)
- ifTrue: [ methods at: theMethod name put: theMethod ]
- .DE
- .LP
- And in class \fBString\fP:
- .DS L
- edit
- \(ua <19 self>
- .DE
- .LP
- Here primitive 19 performs all the tasks of creating the temporary file,
- starting the editor, and creating the string representing the file
- contents when the editor is exited.
- .PP
- Alternative techniques, for example using windowing, would undoubtedly
- be more complicated.
- End
- echo unbundling env.h 1>&2
- cat >env.h <<'End'
- /*
- Little Smalltalk, version two
- Written by Tim Budd, Oregon State University, July 1987
-
- environmental factors
-
- This include file gathers together environmental factors that
- are likely to change from one C compiler to another, or from
- one system to another. These include:
-
- 1. The type boolean. A typedef is used to define this;
- on some older systems typedefs may not work, and a
- # define statement should be used instead.
- The only other typedef appears in memory.h
-
- 2. The statement ignore - this appears on functions being used
- as procedures. It has no effect except as a lint
- silencer, and is also the only place where the type
- ``void'' appears. If your system doesn't support void,
- define ignore to be nothing.
-
- 3. The datatype byte - an 8-bit unsigned quantity.
- On some systems the datatype ``unsigned char'' does not
- work - for these some experimentation may be necessary.
- The macro byteToInt() converts a byte value into an integer.
- Again a typedef is used, which can be replaced by a
- define.
-
- 4. If your system does not have enumerated constants, the define
- NOENUM should be given, in which case enumerated constants
- are replaced by defines.
-
- 5. The define constant INITIALIMAGE should give the name (as a path)
- of the default standard object image file.
- */
-
- # define true 1
- # define false 0
-
- typedef int boolean;
-
- # define ignore (void)
-
- typedef unsigned char byte;
-
- # define byteToInt(b) (b)
-
- # define INITIALIMAGE "imageFile"
-
- # define TEMPFILENAME "/usr/tmp/lstXXXXXX"
- End
- echo unbundling interp.h 1>&2
- cat >interp.h <<'End'
- /*
- Little Smalltalk, version 2
- Written by Tim Budd, Oregon State University, July 1987
- */
- /*
- symbolic definitions for the bytecodes
- */
-
- # define Extended 0
- # define PushInstance 1
- # define PushArgument 2
- # define PushTemporary 3
- # define PushLiteral 4
- # define PushConstant 5
- # define PushGlobal 6
- # define PopInstance 7
- # define PopTemporary 8
- # define SendMessage 9
- # define SendUnary 10
- # define SendBinary 11
- # define SendKeyword 12
- # define DoPrimitive 13
- # define CreateBlock 14
- # define DoSpecial 15
-
- /* types of special instructions (opcode 15) */
-
- # define SelfReturn 1
- # define StackReturn 2
- # define BlockReturn 3
- # define Duplicate 4
- # define PopTop 5
- # define Branch 6
- # define BranchIfTrue 7
- # define BranchIfFalse 8
- # define AndBranch 9
- # define OrBranch 10
- # define SendToSuper 11
- End
- echo unbundling lex.h 1>&2
- cat >lex.h <<'End'
- /*
- Little Smalltalk, version 2
- Written by Tim Budd, Oregon State University, July 1987
- */
- /*
- values returned by the lexical analyzer
- */
-
- # ifndef NOENUMS
-
- typedef enum tokensyms { nothing, name, namecolon,
- intconst, floatconst, charconst, symconst,
- arraybegin, strconst, binary, closing, inputend} tokentype;
- # endif
-
- # ifdef NOENUMS
- # define tokentype int
- # define nothing 0
- # define name 1
- # define namecolon 2
- # define intconst 3
- # define floatconst 4
- # define charconst 5
- # define symconst 6
- # define arraybegin 7
- # define strconst 8
- # define binary 9
- # define closing 10
- # define inputend 11
-
- # endif
-
- extern tokentype nextToken();
-
- extern tokentype token; /* token variety */
- extern char tokenString[]; /* text of current token */
- extern int tokenInteger; /* integer (or character) value of token */
- extern double tokenFloat; /* floating point value of token */
- End
- echo unbundling memory.h 1>&2
- cat >memory.h <<'End'
- /*
- Little Smalltalk, version 2
- Written by Tim Budd, Oregon State University, July 1987
- */
-
- # define streq(a,b) (strcmp(a,b) == 0)
-
- /*
- The first major decision to be made in the memory manager is what
- an entity of type object really is. Two obvious choices are a pointer (to
- the actual object memory) or an index into an object table. We decided to
- use the latter, although either would work.
- Similarly, one can either define the token object using a typedef,
- or using a define statement. Either one will work (check this?)
- */
-
- typedef short object;
-
- /*
- The memory module itself is defined by over a dozen routines.
- All of these could be defined by procedures, and indeed this was originally
- done. However, for efficiency reasons, many of these procedures can be
- replaced by macros generating in-line code. For the latter approach
- to work, the structure of the object table must be known. For this reason,
- it is given here. Note, however, that ONLY the macros described in this
- file make use of this structure: therefore modifications or even complete
- replacement is possible as long as the interface remains consistent
-
- */
-
- struct objectStruct {
- object class;
- short referenceCount;
- byte size;
- byte type;
- object *memory;
- };
-
- extern struct objectStruct objectTable[];
-
- /* types of object memory */
- # define objectMemory 0
- # define byteMemory 1
- # define charMemory 2
- # define floatMemory 3
-
- # define isString(x) ((objectTable[x>>1].type == charMemory) || (objectTable[x>>1].type == byteMemory))
- # define isFloat(x) (objectTable[x>>1].type == floatMemory)
-
- /*
- The most basic routines to the memory manager are incr and decr,
- which increment and decrement reference counts in objects. By separating
- decrement from memory freeing, we could replace these as procedure calls
- by using the following macros:*/
- extern object incrobj;
- # define incr(x) if ((incrobj=(x))&&!isInteger(incrobj)) \
- objectTable[incrobj>>1].referenceCount++
- # define decr(x) if (((incrobj=x)&&!isInteger(incrobj))&&\
- (--objectTable[incrobj>>1].referenceCount<=0)) sysDecr(incrobj);
- /*
- notice that the argument x is first assigned to a global variable; this is
- in case evaluation of x results in side effects (such as assignment) which
- should not be repeated. */
-
- # ifndef incr
- extern void incr();
- # endif
- # ifndef decr
- extern void decr();
- # endif
-
- /*
- The next most basic routines in the memory module are those that
- allocate blocks of storage. There are three routines:
- allocObject(size) - allocate an array of objects
- allocByte(size) - allocate an array of bytes
- allocChar(size) - allocate an array of character values
- allocSymbol(value) - allocate a string value
- allocInt(value) - allocate an integer value
- allocFloat(value) - allocate a floating point object
- again, these may be macros, or they may be actual procedure calls
- */
-
- extern object alcObject(); /* the actual routine */
- # define allocObject(size) alcObject(size, objectMemory)
- # define allocByte(size) alcObject(size, byteMemory)
- # define allocChar(size) alcObject(size, charMemory)
- extern object allocSymbol();
- # define allocInt(value) ((value<0)?value:(value<<1)+1)
- extern object allocFloat();
-
- /*
- integer objects are (but need not be) treated specially.
- In this memory manager, negative integers are just left as is, but
- position integers are changed to x*2+1. Either a negative or an odd
- number is therefore an integer, while a nonzero even number is an
- object pointer (multiplied by two). Zero is reserved for the object ``nil''
- Since newInteger does not fill in the class field, it can be given here.
- If it was required to use the class field, it would have to be deferred
- until names.h
- */
-
- extern object intobj;
- # define isInteger(x) ((x) & 0x8001)
- # define newInteger(x) ( (intobj = x)<0 ? intobj : (intobj<<1)+1 )
- # define intValue(x) ( (intobj = x)<0 ? intobj : (intobj>>1) )
-
- /*
- in addition to alloc floating point routine given above,
- another routine must be provided to go the other way. Note that
- the routine newFloat, which fills in the class field as well, must
- wait until the global name table is known, in names.h
- */
- extern double floatValue();
-
- /*
- there are four routines used to access fields within an object.
- Again, some of these could be replaced by macros, for efficiency
- basicAt(x, i) - ith field (start at 1) of object x
- basicAtPut(x, i, v) - put value v in object x
- byteAt(x, i) - ith field (start at 0) of object x
- byteAtPut(x, i, v) - put value v in object x
- */
-
- # define basicAt(x,i) (sysMemPtr(x)[i-1])
-
- # ifndef basicAt
- extern object basicAt();
- # endif
- # ifndef basicAtPut
- extern void basicAtPut();
- # endif
- # ifndef byteAt
- extern int byteAt();
- # endif
- # ifndef byteAtPut
- extern void byteAtPut();
- # endif
-
- /*
- Finally, a few routines (or macros) are used to access or set
- class fields and size fields of objects
- */
-
- # define classField(x) objectTable[x>>1].class
- # define setClass(x,y) incr(classField(x)=y)
-
- # define objectSize(x) byteToInt(objectTable[x>>1].size)
-
- # define sysMemPtr(x) objectTable[x>>1].memory
- extern object sysobj;
- # define memoryPtr(x) (isInteger(sysobj = x)?(object *) 0:sysMemPtr(sysobj))
- # define bytePtr(x) ((byte *) memoryPtr(x))
- # define charPtr(x) ((char *) memoryPtr(x))
-
- # define nilobj (object) 0
-
- /*
- these two objects are the source of all objects in the system
- */
- extern object symbols;
- extern object globalNames;
- End
- echo unbundling names.h 1>&2
- cat >names.h <<'End'
- /*
- Little Smalltalk, version 2
- Written by Tim Budd, Oregon State University, July 1987
- */
- /*
- names and sizes of internally object used internally in the system
- */
-
- # define classSize 6
- # define nameInClass 1
- # define sizeInClass 2
- # define methodsInClass 3
- # define superClassInClass 4
- # define variablesInClass 5
-
- # define methodSize 6
- # define textInMethod 1
- # define messageInMethod 2
- # define bytecodesInMethod 3
- # define literalsInMethod 4
- # define stackSizeInMethod 5
- # define temporarySizeInMethod 6
-
- # define contextSize 6
- # define methodInContext 1
- # define methodClassInContext 2
- # define argumentsInContext 3
- # define temporariesInContext 4
-
- # define blockSize 6
- # define contextInBlock 1
- # define argumentCountInBlock 2
- # define argumentLocationInBlock 3
- # define bytecountPositionInBlock 4
- # define creatingInterpreterInBlock 5
-
- # define InterpreterSize 6
- # define contextInInterpreter 1
- # define previousInterpreterInInterpreter 2
- # define creatingInterpreterInInterpreter 3
- # define stackInInterpreter 4
- # define stackTopInInterpreter 5
- # define byteCodePointerInInterpreter 6
-
- extern object nameTableLookup();
-
- # define globalSymbol(s) nameTableLookup(globalNames, newSymbol(s))
-
- extern object trueobj; /* the pseudo variable true */
- extern object falseobj; /* the pseudo variable false */
- extern object smallobj; /* the pseudo variable smalltalk */
- extern object blockclass; /* the class ``Block'' */
- extern object contextclass; /* the class ``Context'' */
- extern object intclass; /* the class ``Integer'' */
- extern object intrclass; /* the class ``Interpreter'' */
- extern object symbolclass; /* the class ``Symbol'' */
- extern object stringclass; /* the class ``String'' */
-
- extern object newSymbol(); /* new smalltalk symbol */
- extern object newArray(); /* new array */
- extern object newStString(); /* new smalltalk string */
- extern object newFloat(); /* new floating point number */
- End
- echo unbundling process.h 1>&2
- cat >process.h <<'End'
- /*
- Little Smalltalk, version 2
- Written by Tim Budd, Oregon State University, July 1987
- */
- /*
- constants and types used by process manager.
- See process.c and interp.c for more details.
- */
- /*
- if there are no enumerated types, make tasks simply integer constants
- */
- # ifdef NOENUMS
- # define taskType int
-
- # define sendMessageTask 1
- # define sendSuperTask 2
- # define ReturnTask 3
- # define BlockReturnTask 4
- # define BlockCreateTask 5
- # define ContextExecuteTask 6
-
- #endif
-
- # ifndef NOENUMS
-
- typedef enum {sendMessageTask, sendSuperTask, ReturnTask, BlockReturnTask,
- BlockCreateTask, ContextExecuteTask} taskType;
-
- # endif
-
- extern int finalStackTop; /* stack top when finished with interpreter */
- extern int finalByteCounter; /* bytecode counter when finished with interpreter */
- extern int argumentsOnStack; /* position of arguments on stack for mess send */
- extern object messageToSend; /* message to send */
- extern object returnedObject; /* object returned from message */
- extern taskType finalTask; /* next task to do (see below) */
-
-
- End
- echo unbundling comp.c 1>&2
- cat >comp.c <<'End'
- /*
- Little Smalltalk, version 2
- Written by Tim Budd, Oregon State University, July 1987
-
- Unix specific front end for the initial object image maker
- */
-
- # include <stdio.h>
- # include "env.h"
- # include "memory.h"
- # include "names.h"
-
- main(argc, argv)
- int argc;
- char **argv;
- { FILE *fp;
- int i;
-
- initMemoryManager();
-
- buildInitialNameTables();
-
- if (argc == 1)
- readFile(stdin);
- else
- for (i = 1; i < argc; i++) {
- fp = fopen(argv[i], "r");
- if (fp == NULL)
- sysError("can't open file", argv[i]);
- else {
- readFile(fp);
- ignore fclose(fp);
- }
- }
-
- fp = fopen("imageFile", "w");
- if (fp == NULL) sysError("error during image file open","imageFile");
- imageWrite(fp);
- ignore fclose(fp);
-
- }
- End
- echo unbundling image.c 1>&2
- cat >image.c <<'End'
- /*
- Little Smalltalk, version 2
- Written by Tim Budd, Oregon State University, July 1987
-
- routines used in the making of the initial object image
- */
-
- # include <stdio.h>
- # include "env.h"
- # include "memory.h"
- # include "names.h"
- # include "lex.h"
-
- # define SymbolTableSize 71
- # define GlobalNameTableSize 53
- # define MethodTableSize 39
-
- # define globalNameSet(sym, value) nameTableInsert(globalNames, sym, value)
- /*
- the following classes are used repeately, so we put them in globals.
- */
- static object ObjectClass;
- static object ClassClass;
- static object LinkClass;
- static object DictionaryClass;
- static object ArrayClass;
-
- /*
- we read the input a line at a time, putting lines into the following
- buffer. In addition, all methods must also fit into this buffer.
- */
- # define TextBufferSize 1024
- static char textBuffer[TextBufferSize];
-
- /*
- nameTableInsert is used to insert a symbol into a name table.
- see names.c for futher information on name tables
- */
- nameTableInsert(table, symbol, value)
- object table, symbol, value;
- { object link, newLink, nextLink, entry;
- int hash;
-
- if (objectSize(table) < 3)
- sysError("attempt to insert into","too small name table");
- else {
- hash = 3 * ( symbol % (objectSize(table) / 3));
- entry = basicAt(table, hash+1);
- if ((entry == nilobj) || (entry == symbol)) {
- basicAtPut(table, hash+1, symbol);
- basicAtPut(table, hash+2, value);
- }
- else {
- newLink = allocObject(3);
- incr(newLink);
- setClass(newLink, globalSymbol("Link"));
- basicAtPut(newLink, 1, symbol);
- basicAtPut(newLink, 2, value);
- link = basicAt(table, hash+3);
- if (link == nilobj)
- basicAtPut(table, hash+3, newLink);
- else
- while(1)
- if (basicAt(link,1) == symbol) {
- basicAtPut(link, 2, value);
- break;
- }
- else if ((nextLink = basicAt(link, 3)) == nilobj) {
- basicAtPut(link, 3, newLink);
- break;
- }
- else
- link = nextLink;
- decr(newLink);
- }
- }
- }
-
- /*
- there is sort of a chicken and egg problem about building the
- first classes.
- in order to do it, you need symbols,
- but in order to make symbols, you need the class Symbol.
- the routines makeClass and buildInitialNameTable attempt to get
- carefully get around this initialization problem
- */
-
- static object makeClass(name)
- char *name;
- { object theClass, theSymbol;
-
- /* this can only be called once newSymbol works properly */
-
- theClass = allocObject(classSize);
- theSymbol = newSymbol(name);
- basicAtPut(theClass, nameInClass, theSymbol);
- globalNameSet(theSymbol, theClass);
- setClass(theClass, ClassClass);
-
- return(theClass);
- }
-
- buildInitialNameTables()
- { object symbolString, classString;
- int hash;
- char *p;
-
- /* build the table that contains all symbols */
- symbols = allocObject(2 * SymbolTableSize);
- incr(symbols);
- /* build the table that contains all global names */
- globalNames = allocObject(3 * GlobalNameTableSize);
- incr(globalNames);
-
- /* next create class Symbol, so we can call newSymbol */
- /* notice newSymbol uses the global variable symbolclass */
- symbolString = allocSymbol("Symbol");
- symbolclass = allocObject(classSize);
- setClass(symbolString, symbolclass);
- basicAtPut(symbolclass, nameInClass, symbolString);
- /* we recreate the hash computation used by newSymbol */
- hash = 0;
- for (p = "Symbol"; *p; p++)
- hash += *p;
- if (hash < 0) hash = - hash;
- hash %= (objectSize(symbols) / 2);
- basicAtPut(symbols, 2*hash + 1, symbolString);
- globalNameSet(symbolString, symbolclass);
- /* now the routine newSymbol should work properly */
-
- /* now go on to make class Class so we can call makeClass */
- ClassClass = allocObject(classSize);
- classString = newSymbol("Class");
- basicAtPut(ClassClass, nameInClass, classString);
- globalNameSet(classString, ClassClass);
- setClass(ClassClass, ClassClass);
- setClass(symbolclass, ClassClass);
-
- /* now create a few other important classes */
- ObjectClass = makeClass("Object");
- LinkClass = makeClass("Link");
- setClass(nilobj, makeClass("UndefinedObject"));
- DictionaryClass = makeClass("Dictionary");
- ArrayClass = makeClass("Array");
- setClass(symbols, DictionaryClass);
- setClass(globalNames, DictionaryClass);
-
- globalNameSet(newSymbol("globalNames"), globalNames);
- }
-
- /*
- findClass gets a class object,
- either by finding it already or making it
- in addition, it makes sure it has a size, by setting
- the size to zero if it is nil.
- */
- static object findClass(name)
- char *name;
- { object newobj;
-
- newobj = globalSymbol(name);
- if (newobj == nilobj)
- newobj = makeClass(name);
- if (basicAt(newobj, sizeInClass) == nilobj)
- basicAtPut(newobj, sizeInClass, newInteger(0));
- return(newobj);
- }
-
- /*
- readDeclaration reads a declaration of a class
- */
- static readDeclaration()
- { object classObj, super, vars;
- int i, size, instanceTop;
- object instanceVariables[15];
-
- if (nextToken() != name)
- sysError("bad file format","no name in declaration");
- classObj = findClass(tokenString);
- size = 0;
- if (nextToken() == name) { /* read superclass name */
- super = findClass(tokenString);
- basicAtPut(classObj, superClassInClass, super);
- size = intValue(basicAt(super, sizeInClass));
- ignore nextToken();
- }
- if (token == name) { /* read instance var names */
- instanceTop = 0;
- while (token == name) {
- instanceVariables[instanceTop++] = newSymbol(tokenString);
- size++;
- ignore nextToken();
- }
- vars = newArray(instanceTop);
- for (i = 0; i < instanceTop; i++)
- basicAtPut(vars, i+1, instanceVariables[i]);
- basicAtPut(classObj, variablesInClass, vars);
- }
- basicAtPut(classObj, sizeInClass, newInteger(size));
- }
-
- /*
- readInstance - read an instance directive
- */
- static readInstance()
- { object classObj, newObj;
- int size;
-
- if (nextToken() != name)
- sysError("no name","following instance command");
- classObj = globalSymbol(tokenString);
- if (nextToken() != name)
- sysError("no instance name","in instance command");
-
- /* now make a new instance of the class -
- note that we can't do any initialization */
- size = intValue(basicAt(classObj, sizeInClass));
- newObj = allocObject(size);
- setClass(newObj, classObj);
- globalNameSet(newSymbol(tokenString), newObj);
- }
-
- /*
- readClass reads a class method description
- */
- static readClass(fd)
- FILE *fd;
- { object classObj, methTable, theMethod, selector;
- char *eoftest, lineBuffer[80];
-
- /* if we haven't done it already, read symbols now */
- if (trueobj == nilobj)
- initCommonSymbols();
-
- if (nextToken() != name)
- sysError("missing name","following Class keyword");
- classObj = findClass(tokenString);
- setInstanceVariables(classObj);
- fprintf(stderr,"class %s\n", charPtr(basicAt(classObj, nameInClass)));
-
- /* find or create a methods table */
- methTable = basicAt(classObj, methodsInClass);
- if (methTable == nilobj) {
- methTable = allocObject(MethodTableSize);
- setClass(methTable, globalSymbol("Dictionary"));
- basicAtPut(classObj, methodsInClass, methTable);
- }
-
- /* now go read the methods */
- do {
- textBuffer[0] = '\0';
- while((eoftest = fgets(lineBuffer, 80, fd)) != NULL) {
- if ((lineBuffer[0] == '|') || (lineBuffer[0] == ']'))
- break;
- ignore strcat(textBuffer, lineBuffer);
- }
- if (eoftest == NULL) {
- sysError("unexpected end of file","while reading method");
- break;
- }
- /* now we have a method */
- theMethod = allocObject(methodSize);
- setClass(theMethod, globalSymbol("Method"));
- if (parse(theMethod, textBuffer)) {
- selector = basicAt(theMethod, messageInMethod);
- fprintf(stderr,"method %s\n", charPtr(selector));
- nameTableInsert(methTable, selector, theMethod);
- }
- else {
- /* get rid of unwanted method */
- incr(theMethod);
- decr(theMethod);
- fprintf(stderr,"push return to continue\n");
- gets(textBuffer);
- }
-
- } while (lineBuffer[0] != ']');
- }
-
- /*
- readFile reads a class descriptions file
- */
- readFile(fd)
- FILE *fd;
- {
- while(fgets(textBuffer, TextBufferSize, fd) != NULL) {
- lexinit(textBuffer);
- if (token == inputend)
- ; /* do nothing, get next line */
- else if ((token == binary) && streq(tokenString, "*"))
- ; /* do nothing, its a comment */
- else if ((token == name) && streq(tokenString, "Declare"))
- readDeclaration();
- else if ((token == name) && streq(tokenString,"Instance"))
- readInstance();
- else if ((token == name) && streq(tokenString,"Class"))
- readClass(fd);
- else
- fprintf("unknown line %s\n", textBuffer);
- }
- }
- End
- echo unbundling main.c 1>&2
- cat >main.c <<'End'
- /*
- Little Smalltalk, version 2
- Written by Tim Budd, Oregon State University, July 1987
-
- driver (front-end) for bytecode interpreter.
- */
- # include <stdio.h>
- # include "env.h"
- # include "memory.h"
- # include "names.h"
- # include "interp.h"
-
- extern int processStackTop;
- extern object processStack[];
- extern char tempfilename[];
-
-
- main(argc, argv)
- int argc;
- char **argv;
- {
- FILE *fp;
-
- initMemoryManager();
-
- if ((argc == 1) || ((argc > 1) && streq(argv[1],"-"))){
- fp = fopen(INITIALIMAGE,"r");
- if (fp == NULL)
- sysError("cannot read image file",INITIALIMAGE);
- }
- else {
- fp = fopen(argv[1], "r");
- if (fp == NULL)
- sysError("cannot read image file", argv[1]);
- }
- imageRead(fp);
- ignore fclose(fp);
-
- initCommonSymbols();
-
- ignore strcpy(tempfilename, TEMPFILENAME);
- ignore mktemp(tempfilename);
-
- fprintf(stderr,"initially %d objects\n", objcount());
-
- /* now we are ready to start */
- prpush(smallobj);
- sendMessage(newSymbol("init"), getClass(smallobj), 0);
- flushstack();
-
- fprintf(stderr,"finally %d objects\n", objcount());
-
- if (argc > 2) {
- fp = fopen(argv[2],"w");
- if (fp == NULL)
- sysError("cannot write image file",argv[2]);
- fprintf(stderr,"creating image file %s\n", argv[2]);
- imageWrite(fp);
- ignore fclose(fp);
- }
- }
- End
- echo unbundling process.c 1>&2
- cat >process.c <<'End'
- /*
- Little Smalltalk, version 2
- Written by Tim Budd, Oregon State University, July 1987
-
- Process Manager
-
- This module manages the stack of pending processes.
- SendMessage is called when it is desired to send a message to an
- object. It looks up the method associated with the class of
- the receiver, then executes it.
- A block context is created only when it is necessary, and when it
- is required the routine executeFromContext is called instead of
- sendMessage.
- DoInterp is called by a primitive method to execute an interpreter,
- it returns the interpreter to which execution should continue
- following execution.
- */
- # include <stdio.h>
- # include "env.h"
- # include "memory.h"
- # include "names.h"
- # include "process.h"
-
- # define ProcessStackMax 2000
-
- /* values set by interpreter when exiting */
- int finalStackTop; /* stack top when finished with interpreter */
- int finalByteCounter; /* bytecode counter when finished with interpreter */
- int argumentsOnStack; /* position of arguments on stack for mess send */
- object messageToSend; /* message to send */
- object returnedObject; /* object returned from message */
- taskType finalTask; /* next task to do (see below) */
-
- static object blockReturnContext;
-
- object processStack[ProcessStackMax];
- int processStackTop = 0;
-
- /*
- we cache recently used methods, in case we want them again
- */
-
- # define ProcessCacheSize 101 /* a suitable prime number */
-
- struct {
- object startClass, messageSymbol, methodClass, theMethod;
- } methodCache[ProcessCacheSize];
-
- prpush(newobj)
- object newobj;
- {
- incr(processStack[processStackTop++] = newobj);
- if (processStackTop >= ProcessStackMax)
- sysError("stack overflow","process stack");
- }
-
- /* flush out cache so new methods can be read in */
- flushMessageCache()
- { int i;
-
- for (i = 0; i < ProcessCacheSize; i++)
- methodCache[i].messageSymbol = nilobj;
- }
-
- static object findMethod(hash, message, startingClass)
- int hash;
- object message, startingClass;
- { object method, class, methodtable;
-
- /* first examine cache */
- if ((methodCache[hash].messageSymbol == message) &&
- (methodCache[hash].startClass == startingClass)) {
- /* found it in cache */
- method = methodCache[hash].theMethod;
- }
- else { /* must look in methods tables */
- method = nilobj;
- class = startingClass;
- while ( class != nilobj ) {
- methodtable = basicAt(class, methodsInClass);
- if (methodtable != nilobj)
- method = nameTableLookup(methodtable, message);
- if (method != nilobj) {
- /* fill in cache */
- methodCache[hash].messageSymbol = message;
- methodCache[hash].startClass = startingClass;
- methodCache[hash].methodClass = class;
- methodCache[hash].theMethod = method;
- class = nilobj;
- }
- else
- class = basicAt(class, superClassInClass);
- }
- }
-
- return(method);
- }
-
- /* newContext - create a new context. Note this returns three values,
- via side effects
- */
- static newContext(method, methodClass, contextobj, argobj, tempobj)
- object method, methodClass, *contextobj, argobj, *tempobj;
- { int temporarysize;
-
- *contextobj = allocObject(contextSize);
- incr(*contextobj);
- setClass(*contextobj, contextclass);
- basicAtPut(*contextobj, methodInContext, method);
- basicAtPut(*contextobj, methodClassInContext, methodClass);
- basicAtPut(*contextobj, argumentsInContext, argobj);
- temporarysize = intValue(basicAt(method, temporarySizeInMethod));
- *tempobj = newArray(temporarysize);
- basicAtPut(*contextobj, temporariesInContext, *tempobj);
- }
-
- sendMessage(message, startingClass, argumentPosition)
- object message, startingClass;
- int argumentPosition;
- { object method, methodClass, size;
- object contextobj, tempobj, argobj, errMessage;
- int i, hash, bytecounter, temporaryPosition, errloc;
- int argumentsize, temporarySize;
- boolean done;
-
- /* compute size of arguments part of stack */
- argumentsize = processStackTop - argumentPosition;
-
- hash = (message + startingClass) % ProcessCacheSize;
- method = findMethod(hash, message, startingClass);
- /*fprintf(stderr,"sending message %s class %s\n", charPtr(message), charPtr(basicAt(startingClass, nameInClass)));*/
-
- if (method == nilobj) { /* didn't find it */
- errMessage = newSymbol("class:doesNotRespond:");
- if (message == errMessage)
- /* better give up */
- sysError("didn't find method", charPtr(message));
- else {
- errloc = processStackTop;
- prpush(smallobj);
- prpush(startingClass);
- prpush(message);
- sendMessage(errMessage, getClass(smallobj), errloc);
- }
- }
- else { /* found it, start execution */
- /* initialize things for execution */
- bytecounter = 0;
- done = false;
-
- /* allocate temporaries */
- temporaryPosition = processStackTop;
- size = basicAt(method, temporarySizeInMethod);
- if (! isInteger(size))
- sysError("temp size not integer","in method");
- else
- for (i = temporarySize = intValue(size); i > 0; i--)
- prpush(nilobj);
- methodClass = methodCache[hash].methodClass;
-
- while( ! done ) {
- execute(method, bytecounter,
- processStack, processStackTop,
- &processStack[argumentPosition],
- &processStack[temporaryPosition]);
- bytecounter = finalByteCounter;
- processStackTop = finalStackTop;
-
- switch(finalTask) {
- case sendMessageTask:
- sendMessage(messageToSend,
- getClass(processStack[argumentsOnStack]),
- argumentsOnStack);
- if (finalTask == BlockReturnTask)
- done = true;
- break;
-
- case sendSuperTask:
- sendMessage(messageToSend,
- basicAt(methodCache[hash].methodClass, superClassInClass),
- argumentsOnStack);
- if (finalTask == BlockReturnTask)
- done = true;
- break;
-
-
- case ContextExecuteTask:
- contextobj = messageToSend;
- executeFromContext(contextobj,
- argumentsOnStack);
- decr(contextobj);
- if (finalTask == ReturnTask)
- processStack[processStackTop++] = returnedObject;
- else
- done = true;
- break;
-
- case BlockCreateTask:
- /* block is in returnedObject, we just add */
- /* context info but first we must */
- /* create the context */
- argobj = newArray(argumentsize);
- newContext(method, methodClass, &contextobj, argobj, &tempobj);
- for (i = 1; i <= argumentsize; i++) {
- basicAtPut(argobj, i, processStack[argumentPosition + i - 1]);
- }
- for (i = 1; i <= temporarySize; i++) {
- basicAtPut(tempobj, i, processStack[temporaryPosition + i - 1]);
- }
- basicAtPut(returnedObject, contextInBlock, contextobj);
- processStack[processStackTop++] = returnedObject;
- /* we now execute using context - */
- /* so that changes to temp will be */
- /* recorded properly */
- executeFromContext(contextobj, bytecounter);
- while (processStackTop > argumentPosition) {
- decr(processStack[--processStackTop]);
- processStack[processStackTop] = nilobj;
- }
-
- /* if it is a block return, */
- /* see if it is our context */
- /* if so, make into a simple return */
- /* otherwise pass back to caller */
- /* we can decr, since only nums are */
- /* important */
- decr(contextobj);
- if (finalTask == BlockReturnTask) {
- if (blockReturnContext != contextobj)
- return;
- }
- finalTask = ReturnTask;
- /* fall into return code */
-
- case ReturnTask:
- while (processStackTop > argumentPosition) {
- decr(processStack[--processStackTop]);
- processStack[processStackTop] = nilobj;
- }
- /* note that ref count is picked up */
- /* from the interpreter */
- processStack[processStackTop++] = returnedObject;
- done = true;
- break;
-
- default:
- sysError("unknown task","in sendMessage");
- }
- }
- }
- /*fprintf(stderr,"returning from message %s\n", charPtr(message));*/
- }
-
- /*
- execute from a context rather than from the process stack
- */
- executeFromContext(context, bytecounter)
- object context;
- int bytecounter;
- { object method, methodclass, arguments, temporaries;
- boolean done = false;
-
- method = basicAt(context, methodInContext);
- methodclass = basicAt(context, methodClassInContext);
- arguments = basicAt(context, argumentsInContext);
- temporaries = basicAt(context, temporariesInContext);
-
- while (! done) {
- execute(method, bytecounter, processStack, processStackTop,
- memoryPtr(arguments), memoryPtr(temporaries));
- bytecounter = finalByteCounter;
- processStackTop = finalStackTop;
-
- switch(finalTask) {
- case sendMessageTask:
- sendMessage(messageToSend,
- getClass(processStack[argumentsOnStack]),
- argumentsOnStack);
- if (finalTask == BlockReturnTask)
- done = true;
- break;
-
- case sendSuperTask:
- sendMessage(messageToSend,
- basicAt(methodclass, superClassInClass),
- argumentsOnStack);
- if (finalTask == BlockReturnTask)
- done = true;
- break;
-
- case BlockCreateTask:
- /* block is in returnedObject already */
- /* just add our context to it */
- basicAtPut(returnedObject, contextInBlock, context);
- processStack[processStackTop++] = returnedObject;
- break;
-
- case BlockReturnTask:
- blockReturnContext = context;
- /* fall into next case and return */
-
- case ReturnTask:
- /* exit and let caller handle it */
- done = true;
- break;
-
- default:
- sysError("unknown task","in context execute");
- }
- }
- }
-
- flushstack()
- {
- while (processStackTop > 0) {
- decr(processStack[--processStackTop]);
- processStack[processStackTop] = nilobj;
- }
- }
-
- static interpush(interp, value)
- object interp, value;
- {
- int stacktop;
- object stack;
-
- stacktop = 1 + intValue(basicAt(interp, stackTopInInterpreter));
- stack = basicAt(interp, stackInInterpreter);
- basicAtPut(stack, stacktop, value);
- basicAtPut(interp, stackTopInInterpreter, newInteger(stacktop));
- }
-
- object doInterp(interpreter)
- object interpreter;
- { object context, method, arguments, temporaries, stack;
- object prev, contextobj, obj, argobj, class, newinterp, tempobj;
- int i, hash, argumentSize, bytecounter, stacktop;
-
- context = basicAt(interpreter, contextInInterpreter);
- method = basicAt(context, methodInContext);
- arguments = basicAt(context, argumentsInContext);
- temporaries = basicAt(context, temporariesInContext);
- stack = basicAt(interpreter, stackInInterpreter);
- stacktop = intValue(basicAt(interpreter, stackTopInInterpreter));
- bytecounter = intValue(basicAt(interpreter, byteCodePointerInInterpreter));
-
- execute(method, bytecounter, memoryPtr(stack), stacktop,
- memoryPtr(arguments), memoryPtr(temporaries));
- basicAtPut(interpreter, stackTopInInterpreter, newInteger(finalStackTop));
- basicAtPut(interpreter, byteCodePointerInInterpreter, newInteger(finalByteCounter));
-
- switch(finalTask) {
- case sendMessageTask:
- case sendSuperTask:
- /* first gather up arguments */
- argumentSize = finalStackTop - argumentsOnStack;
- argobj = newArray(argumentSize);
- for (i = argumentSize; i >= 1; i--) {
- obj = basicAt(stack, finalStackTop);
- basicAtPut(argobj, i, obj);
- basicAtPut(stack, finalStackTop, nilobj);
- finalStackTop--;
- }
-
- /* now go look up method */
- if (finalTask == sendMessageTask)
- class = getClass(basicAt(argobj, 1));
- else
- class = basicAt(basicAt(context,
- methodClassInContext), superClassInClass);
- hash = (messageToSend + class) % ProcessCacheSize;
- method = findMethod(hash, messageToSend, class);
-
- if (method == nilobj) {
- /* didn't find it, change message */
- incr(argobj); /* get rid of old args */
- decr(argobj);
- argobj = newArray(3);
- basicAtPut(argobj, 1, smallobj);
- basicAtPut(argobj, 2, class);
- basicAtPut(argobj, 3, messageToSend);
- class = getClass(smallobj);
- messageToSend = newSymbol("class:doesNotRespond:");
- hash = (messageToSend + class) % ProcessCacheSize;
- method = findMethod(hash, messageToSend, class);
- if (method == nilobj) /* oh well */
- sysError("cant find method",charPtr(messageToSend));
- }
- newContext(method, methodCache[hash].methodClass, &contextobj, argobj, &tempobj);
- basicAtPut(interpreter, stackTopInInterpreter, newInteger(finalStackTop));
- argumentsOnStack = 0;
- /* fall into context execute */
-
- case ContextExecuteTask:
- if (finalTask == ContextExecuteTask) {
- contextobj = messageToSend;
- }
- newinterp = allocObject(InterpreterSize);
- setClass(newinterp, intrclass);
- basicAtPut(newinterp, contextInInterpreter, contextobj);
- basicAtPut(newinterp, previousInterpreterInInterpreter, interpreter);
- /* this shouldn't be 15, but what should it be?*/
- basicAtPut(newinterp, stackInInterpreter, newArray(15));
- basicAtPut(newinterp, stackTopInInterpreter, newInteger(0));
- basicAtPut(newinterp, byteCodePointerInInterpreter, newInteger(argumentsOnStack));
- decr(contextobj);
- return(newinterp);
- break;
-
- case BlockCreateTask:
- basicAtPut(returnedObject, contextInBlock, context);
- prev = basicAt(interpreter, creatingInterpreterInInterpreter);
- if (prev == nilobj)
- prev = interpreter;
- basicAtPut(returnedObject, creatingInterpreterInBlock, prev);
- interpush(interpreter, returnedObject);
- decr(returnedObject);
- return(interpreter);
-
- case BlockReturnTask:
- interpreter = basicAt(interpreter, creatingInterpreterInInterpreter);
- /* fall into return task */
-
- case ReturnTask:
- prev = basicAt(interpreter, previousInterpreterInInterpreter);
- if (prev != nilobj) {
- interpush(prev, returnedObject);
- }
- /* get rid of excess ref count */
- decr(returnedObject);
- return(prev);
-
- default:
- sysError("unknown final task","doInterp");
- }
- return(nilobj);
- }
- End
-