gza@mentor.cc.purdue.edu (William R Burdick) (03/21/90)
I've had enough requests for info on mob that I figured I should post this. PLEASE note that mob isn't available, yet. Version 1.0 will be ready by May, or so. ------------------------------------------------------------------------------ Mob, the language Mob is an object oriented language that we (TEAM CTHULHU) have been developing at Purdue University for the last couple years. We have been working on it specfically for having a language to implement multi-user adventure games, but there is no reason it should be limited to such. After a dozens of revisions (and many base languages) mob has settled down to be a super-set of Smalltalk. The syntax is identical to Smalltalk except for changes made to support multiple inheritance, forwarders, and our primitive methods to interface with C. There are also a couple of new conventions and features we use. Mob does not stand for anything really, and is normally all lowercase, unless it's at the beginning of a sentence. This is the current grammar: program: header methods classMethods footer ; header: className 'is' superclasses iVars cVars cIVars; className: IDENT ; superclasses: idents ; idents: IDENT ; idents: IDENT idents ; footer: 'end' className ; /* this should be the same as the first token */ iVars: ; iVars: 'instVars' varNames ; varNames: idents ; cVars: ; cVars: 'classVars' varNames ; cIVars: ; cIVars: 'classInstVars' varNames ; locals: ; locals: 'localVars' varNames '.'; methods: ; methods: method methods ; method: permission 'method' msgHdr '.' locals optstatements ; method: 'method' '@' className msgHdr ; /* className must be a direct superclass */ method: 'protocol' stringList ; method: 'require' className ; method: 'globalVars' varNames ; method: 'primitive' msgHdr '.' cfuncName ; classMethods: ; classMethods: 'classMethods' methods ; permission: ; permission: 'private' ; permission: 'subclass' ; cfuncPart: '_' ; cfuncPart: UNARY ; cfuncName: cfuncPart ; cfuncName: cfuncPart cfuncName ; msgHdr: UNARY ; msgHdr: BINARY varName ; msgHdr: infixMsgHdr ; infixMsgHdr: INFIX varName ; infixMsgHdr: INFIX varName infixMsgHdr ; optstatements: ; optstatements: statements ; statements: statement ; statements: statement '.' statements; statement: expr ; statement: '^' expr ; expr: infixExpr messageList ; expr: IDENT '<-' expr ; messageList: ; messageList: ';' message messageList ; message: infixMessage ; message: binaryMessage ; message: unaryMessage ; infixExpr: binaryExpr ; infixExpr: binaryExpr infixMessage ; infixMessage: INFIX infixExpr ; binaryExpr: unaryexpr ; binaryExpr: unaryexpr binaryMessage ; binaryMessage: BINARY unaryExpr ; binaryMessage: BINARY unaryExpr binaryMessage; unaryExpr: primaryExpr ; unaryExpr: primaryExpr unaryMessage ; unaryMessage: IDENT ; unaryMessage: IDENT unaryMessage ; primaryExpr: constant ; primaryExpr: lambda ; primaryExpr: IDENT ; primaryExpr: 'self' ; primaryExpr: 'realSelf' ; primaryExpr: 'super' ; primaryExpr: 'super' '@' className ; /* className must be an immediate super class */ primaryExpr: '(' expr ')' ; primaryExpr: '#' constantList ; primaryExpr: '#' symbol ; constant: 'nil' ; constant: 'true' ; constant: 'false' ; constant: FLOAT ; /* [0-9]*.[0-9]* */ constant: INT ; /* [0-9]* */ constant: '$' CHAR ; /* '$$' would be a single '$'. No whitespace after the '$' unless you mean a whitespace character. */ constant: stringList ; stringList: STRING ; stringList: STRING stringList ; lambda: '[' varDecs statements ']' ; varDecs: ; varDecs: decVars '|'; decVars: ':' IDENT ; decVars: ':' IDENT decVars ; symbol: UNARY ; symbol: BINARY ; symbol: SYMBOL ; symbol: INFIX ; constantPart: constantList ; constantPart: constant ; constantPart: symbol ; constantList: '(' className '_' constantParts ')' ; constantList: '(' constantParts ')' ; constantParts: ; constantParts: constantPart constantParts ; ASSIGNMENT OPERATOR Smalltalk uses an underbar character for the assignment operator, (which appears as a left arrow with their font). In mob, we use two characters <- for this operator. "SAYING" OBJECTS As in Smalltalk, mob has a method of 'saying' arrays in a file, like this: #(a b c) yeilds an array of the symbols: #a, #b, #c. You cannot put variables in here, only constants and other 'sayings.' One modification to Smalltalk's representation is that you can say: #(LinkedList _ a b c) to make an instance of LinkedList. This really sends a message to LinkedList called given:, with an array of the elements as an argument. In order for this to work, LinkedList must already be defined, of course, so it is possible to 'say' ambiguous things this way. Note that using a class while you are 'saying' an object automatically 'requires' that class (see REQUIRE below). MULTIPLE INHERTANCE The concept of multiple inheritance seems easy enough at first, but it does throw a monkey wrench into things. The basic idea is that you inherit the instance variables and methods from all of your parents. Obviously, name conflicts may arise. If one or more of your parents define a method, you inherit one at RANDOM, unless you specify which one you want to inherit (via the @ declaration of a method). If you want to call the code defined in more than one, you must make a new method to do this. If there is a conflict in instance variables this is an error condition. If your parents have the same instance variable because they inherited it from the same class, this is NOT an error. The before and after methods are for multiple inheritance. Before will call the method as defined in your superclasses. It is guaranteed to call your ancestors' methods in a depth-first search, it will never execute a method more than once during a particular invokation. This guarantees that a method which overrides an ancestor's method (ie. has the same name) will be invoked BEFORE the overridden method. After is defined to call the supers first. The order of invokation is likewise guaranteed; a method which overrides an ancestor's method will be invoked AFTER the ancestor's method (the call looks like this: 'self before'). Before and after use an inheritance list which is generated by the compiler when the class is defined. Before and after can be crucial in defining methods where multiple unwanted invokations of an ancestor's method would arise from simple invocation of superclass methods due to multiple inheritance. For example, in the given class tree: Object / \ Human Gender | / \ | Male Female \ / Man Every class present has an initialize method. The method in Man will want to call initialize defined in Human, and in Male, but both of these (probably) call initialize in Object. For the sake of argument, assume Object has an instance variable called 'count'. Human, Gender, and Male will all inherit this in the same manner as single inheritance. Man will also inherit refCount. Although Human and Male both have the same variable, they inherited it from a common ancestor (Object) so there is no error. The class files for Object, Gender, Male, and Man are given below: (All methods other than initialize are deleted for brevity). Object is Root instVars count method initialize. self count: 1 "although calling this method more than once is harmless" "in some cases it might not be" end Object Gender is Object instVars sex. method initialize. super initialize. self sex: 'neuter' end Gender Male is Gender method initialize. self after. self sex: 'male' end Male Human is Object instVars name. method initialize. self after. self name: 'I have no name' end Human Man is Human Male instVars age. method initialize. "I call all my supers in this order:" "Human Male Gender Object" "The initialize in Object will be called ONCE, even though" "Human and Gender both make calls to it, afterwards I execute:" self after. self age: 0 end Man There are extensions to the before and after methods, to allow control over the business of method invokation. They are: beforeDo: [:class :returnVal | <stuff>] afterDo: [:class :returnVal | <stuff>] After each method executes, these execute their blocks with the return value of the method and the class where the method is defined. beforeIf: [:class | <conditional stuff>] do: [:returnVal | <stuff>] afterIf: [:class | <conditional stuff>] do: [:returnVal | <stuff>] These behave the same way, except that they execute the method only if the conditional stuff yields a true value. FORWARDERS Forwarders are a concept we decided to use a year or so ago. To really implement magic in an adventure game, you need to have a hook into virtually every single method, so spells can wedge themselves anywhere in the universe. Forwarders are our solution to this problem, you can add a hook to ANY method(s),for one specific object (called the target), and do this at RUN time. The overhead occured with the hook will only be incurred if the target gets a message, unlike in a totally compiled environment, where the hook may have to be checked for each function call. Forwarders are instances of a descendant of the Forwarder class. They work by setting an instance variable to point to the target object, and then 'become:'ing the target. Every other object in mob that used to point to the target now points to the forwarder instead. The mechanism is totally transparent. Now the forwarder has its doesNotUnderstand: method defined to pass the message to the target by default. Forwarders that want to do something interesting can check the message first and do anything it likes with it, ignore it, change the arguments, etc. The kernel actually keeps track of two 'self's for every object, called 'self' and 'realSelf'. When a forwarder is attached to an object, that object's self is changed to point to the forwarder. If you put another forwarder on top, you can form a chain. The 'self' keyword yeilds 'self,' and the 'realSelf' keyword yields 'realSelf'. RealSelf is only used internally by forwarders when they need to fiddle with themselves and also by a few methods defined in Object. Instances of primitive classes (like Integer) do not have mob selves, and hence may not have forwarders placed on them. The reason forwarders break in Smalltalk is that there is no redirectable self. This causes forwarders to be excluded from messages which an object sends to itself and results in inconsistent and unexpected behavior. The tricky thing you can do in Smalltalk to implement consistent forwarders is to define a variable to hold the virtual self in Object and replace all occurrences of self (except in a few methods) in all methods defined for 'complex objects' (ie. not symbols, numbers, (or other things which are copied when they are passed as arguments to methods) with the accessing method for that variable. So Smalltalk could conceivably do forwarders right, but you'd have to edit virtually every method already in the system to insure that they behave right. ACCESSING METHODS You should NEVER access an instance variable directly, NOT EVEN IN METHODS DEFINED IN THE SAME CLASS WHICH DEFINES THE VARIABLE. You should always pass a message to retrieve or change its value! The two exceptions are the methods for setting and getting the variables' values (You have to define the accessing method somehow...). Doing otherwise will break forwarders, since you will be circumventing the forwarder's 'hook'. It will probably break your code too. The evil thing about this is that the code will probably break in a subclass which you didn't think about while you were writing the original class, not in the class which defined the instance variable. This convention is not, however, currently enforced by the language, but it is so important, that it may well be. AUTO-GENERATED ACCESSORS For lazy people (like us), mob will automatically generate accessing methods for instance variables if the programmer doesn't provide the methods him/herself. This also removes any excuse for not passing messages to use variable. In the examples above for the Human class, the two methods age and age: would be automatically defined: method age. ^age method age: newAge. age <- newAge METHODS RETURN SELF BY DEFAULT As in Smalltalk, unless a method explicitly returns a value, self is returned. STRING CONCATENATION As apparent from the grammar, adjacent constant strings are automatically concatenated by the parser. Hence, 'hello ' 'world' is identical to 'hello world' Whitespace is entered in the same form as C, \n = newline, \r = return, \b = backspace, \t = tab, and \v = vertical tab (\\ = backslash). REQUIRE <CLASSNAME> Any time you wish to refer to a class by its name, you must have a require statement in the file at some point before the reference. This is an equivalent to extern or importing, and is used to help insure that your programs work. All of your super classes are automatically 'required'. If you include all the necessary require directives, you will only have to explicitly load one file into the mob environment. PROTOCOL <subject> Protocol statements are entirely optional, and are only used by a browser to group related methods. Their use is STRONGLY encouraged. PRIMITIVES Primitives are naturally tricky little beasts, and I will refrain from explaining how to write these. When the language is released, it should not be necessary for a normal user to mess with them. They will however, be explained in the release. METHOD SECURITY You can limit which objects may invoke a method by declaring methods to be 'private' or 'subclass.' An object only understands a private method if it itself is invoking the method and its class is the same as the one where the method was defined (ie. it isn't a subclass of that class). An object only understands a subclass methods if it itself is invoking the method. If another object sends a message for a private or subclass method to another object, that object will not understand it. If an object sends a message for a private method to itself, but that private method is defined in a superclass, it will not understand the method. Method security is used for explicitly designating which methods may be used and which are 'implementation dependent.' ---------------------------------------------------------------------- Team Cthulhu: (The mentor accounts are only until May '90) Mitch Adler g1w@mentor.cc.purdue.edu Bill Burdick burdick@carame.ecn.purdue.edu Roy Riggs gp5@mentor.cc.purdue.edu -- -- Bill Burdick burdick@cello.ecn.purdue.edu