[comp.lang.smalltalk] mob, a FREE OOPL based on Smalltalk

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