Quinta © 1990 Eric W. Sink Introduction This document describes the language Quinta, an object oriented, stack based programming language. Quinta has been implemented on the Macintosh. Basic Definitions The relevant terms in Quinta are object, class, message, and response. These terms will be explained below with examples given later. Objects represent data. Each object is an instance of a class. Different forms of data are represented by different classes. An object my be pushed on the stack. An object may also be stored into a variable. An object may be destroyed. Messages manipulate data. A message is sent to the object(s) on top of the stack. The actual manipulations which take place depend entirely on the response of the receiving object(s) to the message which was sent. It is possible that the object(s) will not respond at all. For a given message and object on top of the stack, the response of the object to the given message is determined by the class of the object. If the class of the object is defined to respond to the message, then the object will respond in the defined manner. Classes have a relationship to one another which allows them to share responses. A class may have one or more subclasses. A subclass inherits all of the responses of its parent. Any object of the subclass may respond to any message to which objects of its parent class may respond as well. Subclasses may have subclasses themselves. Any inherited response has an original definition class. This class, the class in which the response is defined and not inherited, is referred to as the home of the response. Generally, the definition of a response to a given message for a given class is another stream of messages to be sent. In other words, an object responds to a message by sending more messages. A response to a message for a given class may be public or private. A private message may not be sent from outside of the class in which it is defined. In other words, a response may only send private messages successfully to objects of the same class as its home. A public response may be sent by the response of any object. If a private message is sent to an object by an entity outside of the class of that object, the object will not respond. Lexical Conventions Quinta source code is stored in text files. The code consists of tokens, delimited by white space. Delimiter characters are defined so that a token may contain white space. Quinta source code is a stream of tokens. A token can be either a message (a command) or the textual representation of an object (a data constant). A token is passed to the interpreter for processing by typing it in the Worksheet window and pressing ENTER. Quinta messages will appear in this text underlined. Capitalization is important -- the Quinta interpreter is case sensitive. Also, TOS is the abbreviation for Top Of Stack. Quinta Constructs The most important data structure of Quinta is the main stack, simply referred to as the stack. The stack is global and can hold objects, and only objects. The fundamental mechanism in execution of a Quinta program is the passing of messages to the stack. The object(s) on the stack respond appropriately to the messages, perhaps generating more messages and modifying the stack further. The stack is used as the intermediate storage for all calculations and manipulations of data. Therefore, all operations are postfix. This "Reverse-Polish" system gives Quinta the flavor of Forth, or an RPN calculator. Any token received by the interpreter which is not a message will be processed as the textual representation of an object. If the token does not match any standard pattern for conversion to an object, it is an error. If the token is converted to an object, it is pushed onto the stack. For example, by typing 487.9962 into the Quinta interpreter, a floating point object would be pushed onto the stack. Then by typing sqrt the floating point object on TOS would change to 22.0906360252. sqrt is a message. "487.9962" is the textual representation of an object. The class of this object is float. Objects of class float have a response defined for the message sqrt. The effect of this response is to push a new object containing the square root of the value of the receiver. The interpreter continually obtains tokens from the user and processes them. Any token is assumed to be a message and is passed to the object on top of the stack. If the class of that object or any of its super classes have a response defined for the message specified by the token, that response is interpreted. Otherwise, the interpreter attempts to convert the token to an object and push it onto the stack. Messages are always passed to the object or set of objects on TOS. The number of receivers of the message is determined by the order of the message. A message of order n requires n receivers. For example, sqrt is a message of order 1. Exactly 1 object is needed to respond to this message. An example of such an object is a float, which must be the object on TOS. However, swap is a message of order 2. For this message to be interpreted, there must be a response defined for the 2 objects in level 1 and 2 of the stack. This particular message is a trivial example, because swap is defined for ANY two objects on TOS. swap has a response defined for the set of classes "generic:generic". All object classes are subclasses of generic, so any two objects can be swapped. The effect of swap is to exchange the objects in level 1 and 2 of the stack. Note that swap is not defined for a single object; i.e. the depth of the stack must be at least 2. A more complicated example is the message *. This message is obviously used for multiplication. There are many different responses possible for this message. For example, there is a response to * defined for the classes "integer:float". In other words, if the object in level 1 of the stack is an integer and the object in level 2 is a float, then that response will be executed. The effect of this message is to multiply the two numbers a push the result onto the stack. (Incidentally, in Quinta, multiplication of an integer and a float produces a float.) There is also a separate response defined for the classes "float:integer". Clearly, * is a message of order 2. This mechanism, known in some other object oriented languages as a multimethod, is quite flexible. For example, there is a response to * defined for "matrix:integer" which implements the multiplication of a matrix by an integer constant, pushing the resulting matrix. However, there is no response to + defined for "matrix:integer". The addition of a matrix and an integer does not produce a defined result. Consider the following line of Quinta source: 45 98 + dup print drop 45 is an Integer, it is pushed onto the stack. 98 is handled the same way. The next token is +. This is a message. The message is passed to the top of the stack. + is a message of order 2. Both of the top two items on the stack are of class integer. The classes integer:integer have a response defined for message +. The effect of this response is to add the two integers and return the result to the top of the stack. dup is a message. At this time, the object on top of the stack is an integer (the sum of 45 and 98). The message dup has no response defined in class Integer. However, the class integer is indirectly a subclass of the class generic. Class generic has a response defined which duplicates the object on top of the stack. Because integer is a subclass of generic, it inherits all of the responses defined for generic. Consequently, objects of class integer may respond to dup, even though a special response to this message is not defined for that class. Notice, that since all classes are subclasses of generic, any object may be duplicated. print and drop are messages which output an object's textual representation and drop an object from the top of the stack, respectively. Note that Quinta messages are untyped. In other words, a given message does not always modify the stack in an identical known manner for all its responses. Multiplication of two integers yields an integer. Multiplication of two floats yields a float. The interpreter places no restrictions on user defined responses, except that all responses to a given message must be of the same order. However, a user is free to define a response to * for classes generic:string which exits the interpreter. It is good practice to keep the essential effect and meaning consistent in all responses to a given message. For example, drop should always drop the object from the top of stack. Quinta will happily allow you to define a response to drop which duplicates the receiver, but to do so would be poor practice. Response definitions The essence of Quinta programming is in defining new classes of objects and in defining new responses for the various classes. To define a response to a message, the following four items are needed on the stack, in order (the last item is the top of the stack): 4: A Block of tokens which will become the response 3: A String containing the name of the message 2: A Logical which should be TRUE if the response is to be Private. 1: A List object, containing the set of classes for which the message is to be defined. The number of elements in this list is the order of the message. If an attempt is made to define a response to a nonexistent message, the message is newly created. For convenience, the messages priv and pub have been predefined as true and false, respectively. An example response definition: Suppose we wished to define a public message to drop two objects from the stack and multiply the third by 4. We will call this message TRYME. [ drop drop 4 * ] "TRYME" pub generic 1 >list This set of four tokens prepares the stack to create the new response. To actually perform the operation, we must send the message respond. This will be received by the class object on top of the stack, and the response will be created. Therefore, consider the following fragment of source: [ drop drop 4 * ] "TRYME" pub generic 1 >list respond 7 4 9 TRYME print The output of this fragment is 28 Class Definitions To define a new class, the following objects are needed on top of the stack, in order: 4: A List of strings giving the names of the Public data members of the class 3: A List of strings giving the names of the Private data members of the class 2: A String containing the name of the new class 1: A class object which will be the parent class. The message which must be sent to perform the actual operation is subclass. For example, let us say we want to define a class to represent a window, such as is found in a Window, Icon, Mouse and Pointer operating system interface. The attributes of the window will be the location of the cursor, the size of the window, and whether or not it needs to be redrawn. These will correspond to 4 integers and a logical. The cursor coordinates will be public and the other data members will be private. "Height" "Width" "Dirty" 3 >list "X_cursor" "Y_cursor" 2 >list "Window" generic subclass There is now a class Window which has 5 member variables. In most cases, member variables may be treated exactly as "regular" variables which are described in the next section. Variables A variable is a place to store an object. To store an object into a variable, push the object, then push the variable and send the message sto. To recall the value stored, push the variable and send the message rcl. Once a value has been stored in a variable, simply sending the variable name as a message results in a rcl as well. A variable object may be removed by placing it (not its value) on the stack and sending the message purge. To clarify, a variable's contents may be pushed onto the stack by sending the name of the variable as a message. You may think of this as a message to CONTROL telling it to push the contents of a variable onto the stack. To place the variable itself on the stack, it must be surrounded by single quotes. For example, 48 'simple' sto this creates a variable called simple. After this, the message simple will push 48 onto the stack. However the message 'simple' will push the variable itself onto the stack. Then, sending the message rcl will push 48 onto the stack. The fragment 33 'plok' sto plok 21 'plok' sto plok * 'plok' purge will leave the value 693 on the stack. Also, after this fragment is executed, the variable 'plok' will not exist. A user defined class in Quinta may have a number of member variables. Member variables operate as other variables, generally. For a given member variable within an object, to retrieve its contents or push it onto the stack, the object in which it resides must be on top of the stack. After a sto into a member variable, the newly modified object is left on the stack. For example, consider the class Window described above and presuppose that an object of class Window is in variable 'pocket'. By sending the message: pocket a copy of the window object in 'pocket' will be pushed onto the stack. Then, by sending the message 'x_cursor' the x_cursor member variable of the window object will be pushed onto the stack (the actual window object will not be on the stack). Now send the message 78 sto After this operation, the window object will again be on the stack, including its new value for x_cursor. The reason for this, is that the contents of 'pocket' have not changed! Remember, whenever a variable contents are recalled to the stack, only a copy of the contents are ever obtained. To complete this sequence, and store the resulting changes back into pocket, we must do just that: 'pocket' sto Finally, observe that Quinta variables are untyped. Local variables A local variable is one which exists only for the lifetime of its enclosing and defining response. The variable may be initialized at any time during a response definition, and is destroyed upon exiting that response. Statically allocated local variables are not allowed. The message local is of order 2. In level one, a string containing the name of the local variable. And, in level two, the initial contents of the local variable, which may be any object. [ "x" local x x * ] "sq" pub integer 1 >list respond The above fragment of code is one way of implementing a message to square the receiver. Another way of doing the same thing would be: [ dup * ] "sq" pub integer 1 >list respond The second example uses no local variables, and is probably faster. In general, local variables make Quinta code easier to read, and less efficient. Local variables may be created at any time in a block of code but they always expect there to be at least one object on the stack to initialize the variable. Recursion is allowed, and local variables with the same name as other local or global variables (or messages !) previously in existence do not destroy the previous definition for that name. Try examining the response to fact for class integer. This is a factorial function, actually defined in Quinta, and is recursive. To do this, _fact_ responses print