[comp.sources.unix] v11i086: Little Smalltalk interpreter, Part01/03

rsalz@uunet.UU.NET (Rich Salz) (10/04/87)

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);
}
;
}