How To
The Mops Programming Language—Part 1
A rich part of the personal computing experience is writing your own programs, useful or otherwise. There is a slight danger that doing so can be addictive, or at least habit forming. In any case, if you have any interest in doing some Mac programming, a suitable freeware programming system exists named Mops. It is designed exclusively for the Macintosh.
The Mops system has been developed over the past 13 or 14 years by Mike Hore, an indefatigable and gifted Australian programmer, as a part-time effort. Given the size of the system currently in distribution, Mops just might be the best Mac software value around. (Let’s see, 7.4 MB divided by $0—no, no, that won’t work.)
Mops, Forth, and OOP
Mops is unusual in that it is an intrinsically Macintosh programming language by design, much as AppleScript is an intrinsically Macintosh scripting language. This lends itself to (relative) ease of use in both cases, though programming is never an easy task. Furthermore, Mops is unique in that it seamlessly blends the best of the old and the new. Its language is based on Forth, the oldest programming language in common use. Mops adds to its Forth “backbone” a very robust and complete form of object-oriented programming (OOP). The Mops implementation of OOP includes just about everything that an OOP system is supposed to provide, up to and including multiple inheritance.
Since Mops is Macintosh-specific it doesn’t need a separate API or framework in order to provide necessary Mac Toolbox and system services. In a sense, these are built-in. (The Toolbox classes will be further described in Part 2 of this article.) Thus, you can implement any sort of Mac program and you can build the kind of shared libraries (with callback routines and such) that are rapidly taking over from system extensions. The AltiVec real-number coprocessor in G4 Macintosh models is supported for wizard-level vector mathematical processing. (Ever wonder why Photoshop runs so fast on a G4?)
For those not yet familiar with the Mac’s programming services, the Toolbox has been the traditional programming interface for the humongous number of services that the OS provides: windows, menus, and dialogs, to mention the obvious few. The Toolbox is invoked by any one of many defined “Syscalls” that are intercepted by the system when executed. In recent OS versions, the CarbonLib shared library intercepts most of those calls for “Carbonized” programs—those built to co-exist under Mac OS 9 and Mac OS X—and attempts to provide the same or similar services.
Forth: A History
The Forth language merits some historical background. Charles H. Moore is credited with inventing Forth single-handedly; his ideas began to take form in the late 50s at the Smithsonian Astrophysical Laboratory, where he employed a few key elements of the language-to-be. By 1969 Moore had developed a fully-fledged Forth language, so named, initially for the IBM 1130 mini-computer. Moore wrote computer graphics programs in Forth, among other things. With the advent of microprocessors, Forth was a natural for use in embedded control processors. Those devices had little space for the control software, let alone a sizable program development system. Forth lent itself to cross-compiling and produced small object programs.
Although there is an ANSI Forth standard, the Forth world is actually characterized by a host of variants that differ somewhat from each other and from the standard. So it is reasonable to describe the Mops coding backbone as Forthish, although Mops is certainly a member in good standing. Mops does define many additional Forthish words specifically for Macintosh programming, for convenience, and for object-oriented operations. (Mops can readily be converted to a standards-compliant ANSI Forth compiler, and just as readily back again.)
Mops can be described as a good Forthish language underneath higher-level OOP constructs. One might (but probably shouldn’t) say that Mops is Forth wearing an OOP overcoat. Coding, as in all Forth-based languages, is characterized by three major features: explicit stack operations, postfix notation, and language extensibility.
When Forth evolved, the memory available for programs was tiny by present standards. It follows that the curious nature of Forth’s notation was not adopted by its inventor arbitrarily or for its novelty value. Now that memory is cheap and plentiful, the compact code produced is equally valued for its speed of execution.
You can download Mops V.4 at The Mops Page. The Web site contains a number of other useful and tutorial items and has links to Forth documentation and resources, including the “Evolution of Forth” paper from which I quote later in this article.
A Mac OS X native version of Mops is currently available in beta form. The ‘old’ standard Mops produces programs for 68K processors, as far back as the Macintosh Plus. (A “legacy” compiler?) The newer PowerMops compiles native for PowerPC machines, and fat applications (for both 68K and PowerPC machines) can be built.
Naked Stacks and “Backward” Notation
Whereas the great majority of compilers make extensive use of stack operations internally, for efficiency Forth brings one stack out into the open, so to speak, so that it is directly under the programmer’s control. This is called the data, or parameter, stack. It is used to pass program data from one Forth word, or procedure, to another.
A second stack, the return stack, contains mostly program return addresses rather than data but is available for use by an experienced Forth programmer. Any unqualified reference to “the stack” always means the data stack, which by the nature of the language must be managed by the programmer. (Assembly language programmers also generally employ one or more stacks in their code, again for efficiency.)
Mops—and every other Forth-based language—uses the common push-down stack, also known as the Last In First Out (LIFO) list. The coding style and “grammar” of Mops, i.e. the rules for forming proper language expressions, are dictated by appropriateness in expressing LIFO list operations.
Forth expert Elizabeth Rather states the case concisely in her paper “The Evolution of Forth” that she presented to the Association for Computing Machinery:
Forth’s explicit use of stacks leads to a “postfix” notation in which operands precede operators. Since results of operations are left on the stack, operations may be strung together effortlessly, and there is little need to define variables to use for temporary storage.
(The paper is historically fascinating in that Ms. Rather was always “at the scene” with Moore, or else right next door. She herself had an influence on the language.)
I believe she is saying that stack programming naturally leads to use of a postfix or Reverse Polish Notation (RPN) as being right for the job, and that stack usage radically reduces the need for declared variables. Incidentally, Hewlett Packard handheld calculators also use RPN and—you guessed it—are also stack oriented.
Figure 1 shows three sequential stages or points in the life of one data stack.
Three Stages of a Stack. TOS labels the Top Of the Stack.
Next is a really simple-minded Mops word definition which we will call Add, for obvious reasons:
: ADD ( n1 n2 -- sum ) + ;
All that the Add word does is add two values that it expects to find on the stack when it is called. The n1 and n2 symbols in the word’s stack comment (within parentheses) indicate that. The “sum” symbol indicates that (by the nature of arithmetic operations) the word will leave the result on top of the stack when it returns to its caller. Note that the only code in the definition is the single add operator, or plus sign.
Now back to the stack diagram; the left-most drawing shows a condition of the stack just prior to a call to Add. The stack content might be values waiting to be used by nested (partly executed) words and information pushed on the stack by Toolbox routines. The middle drawing shows two integers pushed on the stack by a caller of the Add word. These are pushed specifically for the execution of the Add word.
The right hand side drawing shows the result of the operation placed on top of the stack. Note two things: One, that Mops operators uniformly consume or “use up” their operands and, two, the top of stack (TOS) pointer is automatically maintained by Mops. (In general, operands never remain on the stack but intermediate results or more complex procedures do.)
The following is a little more interesting sample of Mops coding.
Value TEMP ( at top level ) . . . : SUM-OF-SQUARES ( n1 n2 -- n3 ) DUP * -> Temp DUP * Temp + ;
The Value declaration creates a global variable named Temp. The ‘:’ (colon) is the Forth word for defining new words. The colon initiates the definition and is followed by the name of the new word, SUM-OF-SQUARES in this case. (A sum of squares operation produces the squares of two or more values and adds them together.) The comment, in parentheses, is called a stack-effect comment or sometimes stack notation. While stack comments are optional, purely for documentation, they are considered very important in Forth programming. Using a comment makes clear what the defined word, when executed, requires to be on the stack as input and what it will leave on the stack as output. A stack comment may become very complex, and that is when it is needed most.
In this example there must again be two integers on top of the stack when the word is called, symbolized by n1 and n2. Any mismatch between the caller and called word in this respect means big trouble in computer city. The stack will rapidly become incoherent if the called word “eats” some stack data that doesn’t belong to it. (The n2 or right-most symbol always represents the top of stack value but that is not significant in this case.) The n3 symbol indicates that the defined word will push one value on the stack as it terminates (the calculated sum-of-squares, naturally).
Note that the stack comment is in no way a map of the data stack. There may be many, many values on the stack below the ones relevant to a given word execution. The lower data items are the business of other words to consume.
The DUP word, a specialized stack operator, duplicates the top of stack value, symbolized by n2, creating a new top of stack cell. The multiply operator—‘*’—multiplies the top-of-stack and next-on-stack values, consuming those values and creating a new top of stack cell containing the product. The Mops-specific ‘->’ operator (called the “gazinita” for serendipitous reasons) is a store operator that moves the top of stack value into certain kinds of variables, such as Temp in this example. So the square of n2 is has been moved off the stack and n1 is now on top. (Of course, we mean the data values signified by n2 and n1.)
The second line of code produces the square of the value symbolized by n1 as before, but then the value of Temp is pushed on the stack, simply by naming it, and the top of stack (n2 squared) and the second on stack (n1 squared) are added together. Both addends are consumed by the add operation and the sum is pushed on the stack as the word’s return value. (The SUM-OF-SQUARES word would properly be considered a function in some other languages.) The stack comment’s usefulness has been demonstrated I think, even though it is literally just another program comment in the usual sense.
The ‘;’ (semicolon) word ends the word definition. A whole lot was going on in those two little lines of code, no? A somewhat more slick and proper version of the same routine, requiring no variable declaration, is as follows:
: SUM-OF-SQUARES ( n1 n2 -- n3 ) DUP * SWAP DUP * + ;
The very useful SWAP word, another specialized stack operator, swaps the values of the top two stack cells. It consumes nothing. Having explained that much I’ll leave the rest as an exercise for you, the student. Just concentrate on each word separately. Dead easy.
There are several ways, using Named Input Parameters and Local Variables, that explicit stack manipulation can be largely hidden or limited, at the discretion of the programmer. At times a required operation can be so complex that use of such magic variables may be required. (The Named Input Parameter and Local Variable are Mops-specific language features.)
Direct benefits of Mops’ stack orientation to the programmer include:
- The ability to produce fast and compact code. (Mops programs are sometimes competitive with assembler code.)
- Much-reduced need for global variables because both input and output parameters can be passed onto the stack and the stack can be used for holding intermediate results, thus reducing both the programmer’s error-prone housekeeping chores and the program’s memory requirements.
- Mops procedures are naturally re-entrant as long as external variables are not used, a decided gain in coding simplicity. (Re-entrancy is the ability of a procedure to call itself during its execution, which can be very useful and a necessity in certain applications.) While most languages provide for some re-entrancy, they do so by means of special, additional compiler code whereas Mops procedures are simply and inherently re-entrant.
- Mops programs that use only the normal high-level Mops words are inherently structured and modular (by the Dijkstra/Parnas definitions), excepting only the use of EXIT and EXECUTE (which take program control out of line). In programming theory, code that is structured and/or modular is more likely to be reliable, or in my interpretation less likely to contain hidden “gotchas.”
Words, Words, and More Words
Another face of Mops’ Forthish simplicity is its very simple lexical rules (the rules that tell you how to form valid basic elements of a language). In Forth the syntactic elements or identifiers are space-delimited character strings, period. Anything you want can be in that string, excepting only a white-space character. The string can be as long or as short as you like.
The Mops grammar, consists of a simple notation for defining new words, which you have seen above (: and ; space delimited) and several other equally simple notations. There are many different kinds of Mops words of course, but lexically they are all the same. The OOP constructs require a few additional rules but they are also few and very simple. Indeed, some say that Forth has no syntax because it doesn’t need any. The syntactic simplicity leads to the third major feature of the language: user extensibility.
Normal (high-level) Mops words fall into three categories: named data items, named procedures, and defining words. The defining words allow the user to declare named procedures, that is, new Mops words, and also what are called action words. Executable words, or “definitions,” are functionally analogous to routines, subroutines, and functions in other languages and sometimes are equivalent to commands in other languages. Much as algebra is the metaphor or model for Fortran, for example, Forth’s metaphor is natural-language prose. (Some would say German, with the “verbs” at the end.) Any arbitrary character string can represent a wide variety of functionally different elements. There are virtually no reserved words (: and ; are a few exceptions). The name of almost any word in the dictionary may be redefined.
The Forthish emphasis on the word “word” stems in part from its use of the dictionary construct. All pre-defined Mops words, such as ‘+’ for example, as well as—at some point—all the words that comprise the user’s program, are in a dictionary. The dictionary in large part is the user’s program. (The interpreter for the dictionary is a matter of a few fast machine instructions.) Conceptually below the dictionary there is a small nucleus that contains the compiler/interpreter’s primitives.
All these lexical considerations lead to a fully extensible language in which the user freely makes new words out of combinations of existing ones. This provides the ability to easily factor a program, a very important programming technique.
The veteran Phillip J. Koopman says:
Writing a Forth program is equivalent to extending the language to include all functions needed to implement an application. Therefore, programming in Forth may be thought of as creating an application-specific language extension. This paradigm, when coupled with a very quick edit/compile/test cycle, seems to significantly increase productivity. As each Forth word is written, it can be tested from the keyboard for immediate programmer feedback.
Extending the language is reflected in the code compiled for a program. The dictionary, which initially contains only the definitions of the Mops language, is literally extended to include all of the user-program definitions. Execution of the program is tantamount to interpretation of the dictionary, either before or after the program is installed as a standalone, i.e. double-clickable, program.
Interpreting Mops Programs
It might be well to reinforce the concept that when a Mops program is compiled for standalone execution, the program still executes in interpretive mode. This mode of execution is often associated with poor performance but that is not the case with Mops programs.
As a postfix, interpretable language, Mops has more in common with PostScript than with the other popular programming languages of today such as C, Java, and Pascal. (It does share Pascal’s structured code.) AppleScript on the other hand is an interpretive language but is designed more for ease of use than “blazing speed.”
One may test the definition of, for example, the SUM-OF-SQUARES word defined above, prior to incorporating it into any program source, by entering it into Mops’ data entry window and making calls to it, in interactive interpretive mode. Very confidence making. As shown in Figure 2 below we have entered the sum-of-squares definition into Mops’ data-entry (and editing) window, which is the portion below the horizontal line. Even though we have selected the definition and pressed the Enter (not Return) key, which enters what has been typed, the stack remains empty, as shown in the upper window.
That is because the SUM-OF-SQUARES definition has been compiled into the dictionary as a Mops word. (The colon word puts Mops into compile mode.) In Figure 3 we enter two values, 12 and 24, and call SUM-OF-SQUARES simply by naming it. (Followed by the Enter key.) Mops, in interpretation mode, places the numeric values on the stack (standard behavior) and searches for a match for the name in the dictionary—not far to look in this case—and executes the matching word when found. Finally we see the sum-of-squares result on the stack. (Maybe if we had some sort of alien high-speed vision we could have seen the two numbers on the stack before they were eaten by the SUM-OF-SQUARES word. We could have entered the stack values by themselves of course and seen them there before invoking SUM-OF-SQUARES.)
As can be seen, when a more complex word definition has been entered in the dictionary, you can sit at the keyboard and test it all day long with various values and combinations of inputs. The advantage of being able to test small pieces of a program independently of most other parts is obvious.
The Mops system includes a manual that runs to over 370 pages, in four parts. Except for the tutorial Part 1, it is not intended to cover the Forth language aspect of Mops in any comprehensive way. I found that I needed a good Forth reference manual to fill in the gaps, and I can recommend the Forth Programmer’s Handbook offered by FORTH Inc. as an excellent value.
• • •
Part 2 of this article, in next month’s issue of ATPM, will cover the object-oriented programming (OOP) aspect of Mops. OOP allows the user to build programs with clearly delineated and separate functional units, as runtime objects. It is also the mechanism by which Mops provides access to system services.
Also in This Series
- Give Alert Sounds a Little Personality · March 2012
- Create Your Own iPhone Ringtones · February 2012
- Create Your Own Homemade Audio Book · December 2011
- Upgrade to Lion Painlessly · August 2011
- Make the Most of TextEdit · July 2011
- Using the Free Disk Utility on Your Mac · May 2011
- Making Use of QuickTime X · March 2011
- Making the Most of What’s Already on Your Mac · February 2011
- Making the Most of What’s Already on Your Mac · January 2011
- Complete Archive
Reader Comments (7)
-CompSci Prof. (retired)
To write a program, you define the words, using the FORTH dictionary as a starting point, that do the basic tasks. Then you start defining other words that combine the first ones into higher level tasks and so on. Eventually you write the final word -- RUN.
I believe that anyone interested in programming should grab MOPS or some variant of FORTH and give it a whirl. It is cheap, enjoyable education and it changes the way you think about programming.
Please tell us where to get this.
Let me know if the domain is no longer correct. Mike has been moving around a bit lately.
Send comments, bug reports, etc., to [email protected]. Cheers.
Ed W.
Add A Comment