MUF (programming language)
|Paradigm(s)||Procedural, stack-oriented, reflective|
|Designed by||Piaw Na|
|Typing discipline||strong dynamic|
|Major implementations||TinyMUCK, Fuzzball MUCK|
|Influenced||MOO, CoolMUD, ColdC|
MUF is the system programming language for TinyMUCK systems. Many fundamental MUCK commands, in fact, are implemented as MUF programs, and built-in commands of the MUCK server are often replaced with more sophisticated versions written in MUF.
Like Forth, MUF is a stack-based, compilable, structured language. MUF programs are created by entering source code in an interactive MUF editor (a feature of the MUCK server) and compiling it into a "program object" — a referenceable object in the MUCK's database providing redirection to the executable code. Because the editor is relatively crude, most MUF programmers write source code in a text editor on a client machine and upload it into the editor.
The language supports several primitive data types: strings, integers, floats, dictionaries, arrays, database references (usually referred to as dbrefs), and boolean locks. A string is a series of characters, used primarily to process input and output. Originally, MUCK supported only integer numbers, but support for floating point numbers — as well as arrays and dictionaries — was added around 1995. A dbref is a reference or index to a named object in the MUCK database. Boolean locks are a very MUCK specific data type often used to validate user's permissions to do various actions. MUF also stores references to variables on the stack, and allows these references to be manipulated with mathematical and boolean operators. For this reason, variables may be regarded as yet another datatype in MUF.
MUF is stack-based. Runtime data is stored in a LIFO stack, and MUF programs work primarily by manipulating the contents of the stack. Variables exist to make this easier, but they were deprecated as being dangerous to use until function-scoped variables were added around 1995.
MUF implements a core language of approximately 200 functions known as primitives. Each primitive performs a specific task. For example, the NOTIFY primitive outputs a string to a player. Other primitives perform tasks such as recalling or storing information, converting data types, utilizing data on the program stack to perform conditional logic, or manipulating the stack itself.
A complete list of primitives is output by the MAN PRIMITIVES command. Further information on a specific primitive is available with the command MAN <PRIMITIVE>. For example, typing "man notify" will provide information on the NOTIFY primitive. These documentation entries begin with the name of the primitive, followed by a "stack effect" statement. Stack effects are shown in the form of ( x1 x2 .. -- y1 y2 .. ), where text to the left of the double dash mark indicates data and the order it must be placed on the stack before that particular primitive can be used. Text to the right of the double dash indicates data elements that will be returned to the stack once the primitive has completed its operation.
Like Forth, MUF is easily extensible: programmers may combine primitives and data to form new named subroutines. As in Forth, user-created subroutines are properly known as words, but many MUF programmers frequently use the term function interchangeably. A MUF word declaration opens with a colon and the name of the word, followed by the primitives, data, and identifiers that make up the word's code. A word's declaration is closed with a semi-colon. As in most structured languages, whitespace is insignificant, with one exception: there must be no leading whitespace before the colon that opens a word declaration. The core language is supplemented by numerous program libraries containing user-defined words.
Program execution begins at the opening of the last word declared within a program. This is because all subroutine-words, define statements, and libraries need to be declared and defined before they can be used. This beginning word does not need to be named 'Main', as the only name restrictions imposed are that some reserved words and variables are not duplicated.
MUF programming on a MUCK server is governed by a system of permissions implemented through Mucker bits. By default, users (or "players") on a MUCK do not have permission to use the MUF editor, which means they cannot create or alter programs. At the site administrators' discretion, an individual player may be given a Mucker bit, which is a setting on the player's account that allows him or her use of the MUF editor, with varying degrees of freedom. An M1 bit (Apprentice) is the lowest setting: the player may create and alter programs, and may use a restricted set of the MUF primitives. An M2 bit (Journeyman) gives the user access to an extended set of primivitives. The M3 bit (Master) gives the user access to all but a very few primitives. The Mucker bit also controls the maximum number of instructions that may be executed in a given instance of the program.
Transforming a MUF program into a usable command or feature also requires creating a command or trigger to invoke the program. At the time of execution, any remaining text from the command line used to call the program is pushed onto the stack, this provides one method for the user to pass arguments into the code to be acted upon.
The MUF stack
MUF programs work by manipulating data stored in a LIFO stack. Some programming languages include a primitive for explicitly putting data on the stack, usually called PUSH. In MUF, this operation is implicit: data included in source code or returned from a word or program automatically goes on the stack. The top item on the stack is removed with the POP primitive.
The following code:
"foo" "bar" POP POP
...would produce the following stack trace (the text within the parentheses represents the stack; the left end is the "bottom" of the stack, the right is the "top"):
() "foo" ("foo") "bar" ("foo", "bar") POP ("foo") POP ()
This code begins with an empty stack. The statement "foo" (which is some data, of the type string) causes the string to be placed on the stack. The stack now consists of this one datum. The following statement "bar" (more data, another string) causes "bar" to be placed on the stack as well. "bar" is the top element. "foo" is below "bar". The statement POP causes the top element of the stack to be removed: "bar" is popped off the stack, into oblivion; the stack now once again consists of the single datum "foo". The second POP removes "foo" from the stack. The stack is now once again empty. A third POP statement at this point would cause a "stack underflow", and the program would crash.
An understanding of the stack is absolutely necessary for MUF programming, since virtually all primitives and user-defined words require data of a certain type to be on the stack, in the correct order, in order to perform their functions. For example, the stack effect note for the NOTIFY primitive is ( d s -- ). That is, the primitive requires a string to be on top of the stack, with a dbref immediately below that. If player Alice had a dbref of #99, then the code:
#99 "Hello there!" notify
...would output the string "Hello there!" to Alice's screen. After this happens, these two data would be removed from the stack: primitives and operators "use up" their input data. If the program did not have a dbref and a string on the top of the stack at runtime, in this order, the program would crash.
MUF includes a number of primitives that examine and manipulate the stack contents, including DUP, POP, SWAP, OVER, ROT, ROTATE, PICK, PUT, and DEPTH.
The standard "Hello World" program would look like this:
( a standard demo program ) : HelloWorld me @ "Hello, world!" notify ;
The first line is a comment. Text in parentheses is ignored by the compiler and during program execution. MUF is not a particularly readable language, and MUF programs are used, installed, and maintained in a hurly-burly environment by users and programmers of widely varying capabilities. Comments describing a program's authorship, terms of distribution, means of installation, and program flow are helpful and important.
The second line — a colon followed by a name, set flush left — opens the declaration of a word. This program contains one word; its name is HelloWorld.
The third line contains all the executable code in the program. "me" is a variable; it holds the dbref of the user running the program. "me" is a special variable that is declared and initialized by the server when the program is run. The programmer can also declare and initialize his or her own variables.
The @ at sign is the FETCH operator. It "fetches" the value stored in the variable "me" (the user's dbref), and puts it on the stack.
"Hello, world!" is a string. Simply including the string in the code does an implicit PUSH, putting the data on the stack. After this statement, the string will be put on the stack (above the user's dbref).
"notify" is a primitive. It instructs the server to output the string on the top of the stack to the player with the dbref that is currently stored as the second-from-top element on the stack. The user will see the string on his or her screen, and both data will be removed from the stack. If the stack did not contain a dbref and a string, in the proper order, the program would crash.
The semi-colon on the fourth line marks the end of the word.
The stack trace for this program would be:
2 ("") (HelloWorld) 3 ("") V0 3 ("", V0) @ 3 ("", #1) "Hello, world!" 3 ("", #1, "Hello, world!") NOTIFY Hello, world! 4 ("") EXIT
Data types can be differentiated by their appearance within a stack trace. Strings appear as characters surrounded by quotation marks ("Three Thousand" or "3000"). Dbrefs appear as an integer preceded by an octothorpe or pound sign (#3000). A number will appear as the number itself (3000). A variable appears as an integer prefixed with either "V" (for global variables that are shared between all programs and libraries in a call chain), "LV" (for variables local to the given program — module scope) (LV3), or "SV" (for function-scoped variables) (SV2).
(There is an empty string (also called a "null string") at the base of this stack because the program was called with no arguments. If the command that invoked the program were called "hello", and the user typed "hello", then the stack trace would be as above. If the user typed "hello foo", then "foo" would be an argument to the command. The stack trace would show "foo" in place of "", but since the program never references this element, it makes no practical difference.)
Note that "notify" appeared in lower case in the source code, and in all upper case in the stack trace. MUF is case insensitive, but some programmers follow a convention of typing primitives and server-defined variables in uppercase, and all other statements in Upper-lowercase or lowercase.
Turning HelloWorld into a functioning program on a MUCK would require the following steps:
- The programmer acquires a Mucker bit if he or she does not already have one.
- The programmer types "@program HelloWorld" to create the program object and edit its contents.
- The programmer types "i", to begin Inserting source code.
- The programmer types or uploads the source code.
- The programmer types a . period to exit the editor's Insert mode.
- The programmer types "c" to compile the source code into an executable program.
- The programmer types "q" to exit the editor.
- The programmer creates a command to invoke the program, by typing in MUCK commands such as:
- @action hello=here
- @link hello=HelloWorld
- The MUF editor is quite primitive and resembles a feature-deficient version of Unix's ED editor. Most MUF programmers write their code in a text editor on their local machine, and upload the code to the server.
- It is quite easy to write entirely unreadable code in MUF.
- MUF's implementation of variable scoping is rudimentary at best.
In addition, MUF includes no mechanism for examining data type at compile time though it may be examined at run time and easily typecast as needed. Given the design of the language that allows a function to legitimately accept many different types of arguments, compile-time typing would be difficult to implement and its usefulness would be questionable.
Despite these limitations, MUF has proven to be a very successful and appropriate tool for its intended purposes.
- Poirier, Joseph (1995). "15. MUCK and MUSH Programming". In Busey, Andrew. Secrets of the MUD Wizards. SAMS Publishing. p. 371. ISBN 0-672-30723-5. "MUCK [...] has its own programming language, TinyMUF. MUF stands for Multi-User FORTH."
- Shah, Rawn; Romine, James (1995). Playing MUDs on the Internet. John Wiley & Sons, Inc. p. 279. ISBN 0-471-11633-5. "TinyMUCK v2.* TinyMUCKv1.* with a programming language added. The language, MUF (multiple user forth), is accessible only to people with the MUCKER flag. [...] MUF is very powerful, and can do just about anything a Wizard can."
- Poirier, Joseph (1995). "15. MUCK and MUSH Programming". In Busey, Andrew. Secrets of the MUD Wizards. SAMS Publishing. pp. 371–433. ISBN 0-672-30723-5.