>From stinglai@bureau.World.GOV Tue Jun 5 08:50:55 1990
From: stinglai@bureau.World.GOV (Stinglai Ka'abi)
Newsgroups: alt.mud
Subject: Beginner's guide to MUF programming
Keywords: MUD Muck MUF forth Atlantis and a partridge in a pear tree
Date: 5 Jun 90 09:19:45 GMT
Reply-To: blojo@ocf.berkeley.edu
Distribution: alt
[ Updated August 10th, 1990, by ChupChup for the release of TinyMUCK 2.2. ]
Zen in the Art of the Towers of Hanoi
(or The Basics of MUF in 10 megs or less.)
This is an introduction to MUF, a dialect/subset of forth used to do really
really neat things with TinyMuck 2.2. This intro was designed to be read
before any of the other MUF information; it (hopefully) should supply you
with a fair idea of what MUF is all about. It's written at a non-programming-
stud level, all the better for understanding.
All MUF programs work by performing operations on a stack.
For all you non-programmer types, a stack is just a tool used to store
information. Information is manipulated by "pushing" things onto the
stack and "popping" things off. The last thing you've placed on a stack
is always the next thing you would take off if you tried; it's like
piling objects on top of each other, when the only thing you can remove
>from the pile is the thing on top. For example, if you were to push the
number 23, then push the number 42, and then pop a value off the stack,
you would get 42. If you were to pop another value off the stack after this,
you would get 23. If you were to try and pop another value, you would get
an error, since the stack would now be empty. (This is a "stack underflow.")
The basic procedural unit in MUF is called the word. A word is simply a
sequence of instructions. In program text, a word always starts with a
colon, then the word's name. A semicolon marks the end of a word.
For example:
: detonate_explosives
(text of word here)
;
would define a word called detonate_explosives.
Parentheses are used to delineate program comments; everything inside comments
is ignored by the computer. The detonate_explosives word above, if run
as shown, would do absolutely nothing.
Indentation in MUF is arbitrary and serves to make the program more readable
to people.
In MUF, there are three types of constant values: integers, strings, and
database references. Each of these types is stored and retrieved from the
stack as a single unit (The string "Hello, Sailor!", for example, would be
stored on the stack in its entirety: "Hello, Sailor!"; it would not be stored
byte-by-byte or word-by-word or in any other such silly way.)
To push a constant onto the stack, you only need to state its value. The
following is a completely legitimate procedure:
: pointless_word
"Old Man"
"I'm"
37
"What?"
37
"Not old!"
;
However, run by itself, it wouldn't do anything visible to the user. It would,
however, create a stack which looks like this:
("Old Man" "I'm" 37 "What?" 37 "Not old!")
In the above stack, "Old Man" is the value on the bottom. "Not old!" is the
value on top of the stack, and would be the next value retrieved.
Placement of values on the same line of a program is arbitrary. Since each
value represents something being put on a stack, the word
: example
"Semprini?"
"All right, wot's all this then!"
;
is the same as:
:example
"Semprini?" "All right, wot's all this then!"
;
Functions which are available in the standard MUF library take values from the
top of the stack, do things with them, and usually leave something new back
on top of the stack. The words +, -, swap, pop, and random provide good
examples of this and are discussed here.
The + routine takes the top two integers from the stack, adds them together,
and leaves the result on top of the stack. In order to easily describe
what functions like this do, a certain stack notation is used: for +,
this would be (i1 i2 -- z). What's inside those parenthesis is a sort of
"Before and After" synopsis; the things to the left of the double-dash are
the "before", and those to the right are the "after". (i1 i2 -- i) says that
the function in question takes two integers away and leaves one. The letters
used here to tell what kind of data a stack object can be are: i for integer,
d for database object, s for string, v for variable, and x or y to mean
something that can be more than one type.
Here are short descriptions of the procedures listed above so you can get
the hang of how they work:
+ (i1 i2 -- i)
Adds i1 and i2 together. The word
: add_some_stuff
2 3 +
;
will return 5. The word
: add_some_more_stuff
2 3 4
5
+ + +
;
will return 14. When add_some_more_stuff first reaches the "+ + +" line,
the stack looks like:
(2 3 4 5).
The first + changes the stack to look like:
(2 3 9).
The next causes:
(2 12).
The final plus returns:
(14).
- (i1 i2 -- i)
Subtracts i2 from i1.
: subtract_arbitrary_things
10 7 -
;
will return 3 on top of the stack.
swap (x y -- y x)
Switches the top two things on the stack. This is useful for when you want
to know the value of x but want to save y for later use.
: swap_stuff_around
1 5
2 swap
3
"Three, sir!"
swap
"Boom!"
;
will, before it gets to the first swap, create a stack of (1 5 2).
After the swap, the stack looks like (1 2 5). It then accumulates another
3 and a string constant, to look like (1 2 5 3 "Three, sir!") It swaps
the last two again and adds another string, so the stack looks like:
(1 2 5 "Three, sir!" 3 "Boom!").
pop (x --)
Throws away the value on top of the stack. As shown in the stack diagram, it
returns nothing but takes something, and so decreases the stack's total size.
Useful when you really really want to get to the next thing on the stack so bad
you don't care what's on top. The word:
: needless_popping_waste
"Immanuel Kant"
"Heideggar"
pop
"David Hume"
"Schoppenhauer"
"Hegel"
pop pop
;
would leave the stack looking like ("Immanuel Kant" "David Hume").
random (-- i)
Doesn't even look at the stack, but piles a really really random integer on
top. The word:
: feel_lucky_punkP
random
random
random
;
would return a stack of three random numbers.
Because of the way the stack works, variables aren't as necessary in MUF as
they are in other languages, but they can be used to simplify stack-handling
operations. To declare a variable, you simply add the line "var "
at the beginning of your program. Variables are of no specific type; a
variable which holds an integer can turn around the next second and hold
a string if it's feeling haughty enough.
The following words are important when dealing with variables:
! (x v --)
Set variable v to hold value x. The program:
var answer
: multiply-and-store
6 9 *
answer
!
;
will give the variable "answer" the value 42. This is the same as:
: multiply-and-store
6 9 * answer !
;
@ (v -- x)
This word (pronounced "fetch") retrieves the value of a variable and puts it
on the stack. You should remember this since a common mistake among beginning
MUF programmers is to forget to put fetch symbols in their programs.
The word
garply
by itself stands for the variable "garply", while the expression
garply @
stands for the value of that same variable. If you're familiar with Lisp, this
is analogous to the difference between garply and (garply).
The program:
var biggles
var fang
: more_silly_manipulation
10 biggles !
24 fang !
biggles @ fang @
+
;
will return the value 34 on top of the stack. The program:
var biggles
var fang
: more_silly_manipulation
10 biggles !
24 fang !
biggles fang +
;
is *wrong*. For reasons I won't go into now, since this guide was written
at the last moment and at great expense, the above word will return the
value 7 on top of the stack.
In MUF, there are two variables which are predefined and available for use at
all times. These variables are "me" and "loc", where "me" holds the
player's database reference, and loc holds the player's location's database
reference.
(Database references were mentioned before as the third type of constant, then
sort of ignored till now. For the sake of completeness, I will introduce the
word
dbref (i -- d)
Where i is an integer and d is a database reference, dbref converts between
the two types. The line
2032 dbref
will return item #2032 in the Muck database. This is useful since there
are lots of functions that operate on database references that won't work
on integers. [If you want to declare something in one of your programs as
being a dbref instead of an integer, you should just put a # in front. For
example, 69 means the integer 69, while #69 means object number 69. You
could say '69 dbref' instead of '#69', but it would be a little slower and
a little harder to read.]
Me @ will return the player's item reference, while loc @ will return the
room they are in. Trigger @ returns the item that triggered the current
program, whether it is a player, exit, room, whatever. A useful word to
know is:
name (d -- s)
Where d is a db reference and s is a string, name returns the name of item
x.
Now that you know about me @, another Muck function becomes useful. Its
synopsis is:
notify (d s --)
When d is a player, notify prints string s out to user d. The program
: warn
me @
"There is someone behind the next column waiting to jump you."
notify
;
would print said message on the user's screen.
Before you can really start writing neat stuff in Muck, there are two more
things you should know about. One is = and the other is the "if/then" setup.
= (i1 i2 -- i)
Returns 1 if integer i1 is equal to integer i2, otherwise returns 0.
: nonequals
2 3 =
;
returns 0.
If/then could be written up with a synopsis, but it would be sort of
complicated and probably a lie also. The way it works is this: If
pulls an item off the stack. If the item is 0, it skips over the
program after the IF and resumes execution at the THEN. If the item is not
0, the program will execute everything in between.
The naming of this construction as if/then can be somewhat confusing. It
certainly doesn't work quite like the if/then of normal languages, and the
THEN actually being called THEN is sort of confusing. As nearly as I can
tell, if/then is a sort of forth-creators' joke. It does not mean
"IF the previous is true THEN do this." like it does in most languages.
Rather, it means "IF the previous is true do this; THEN go on with the rest
of the program." Remarkably silly.
The word:
: word
2 3 =
if me @ "Your computer is broken!" notify
then me @ "Done executing this word." notify
;
will always print "Done executing this word." to the user, and will print
"Your computer is broken!" if something is really screwy with the math and
it actually thinks 2 = 3. Getting a bit more sophisticated, one can write
something like:
: word_up
"Your computer works fine."
2 3 =
if pop
"Your computer is broken. Sorry. Truth hurts."
then
me @ swap notify
;
When word_up is called, "Your computer works fine." gets put on the stack.
If your computer actually works, 2 is *not* equal to 3, so that right after
the = the stack looks like:
("Your computer works fine." 0)
The IF reads the 0 and skips all the way down to the THEN.
The SWAP in the last line is used since the NOTIFY word wants its
parameters in the opposite order of where they would be.
If your computer is broken, right after the =, the stack looks like:
("Your computer works fine." 1)
The IF reads this 1 and decides to keep executing. It then gets to the POP
which gets rid of the filthy lie about well-working computers and replaces
it with the painful truth.
*SAMPLE PROGRAM*
Ok, so you've been reading this whole thing so far, and you really want to
use this stuff to do something interesting. The following program does
something interesting, and uses the function
strcat (s1 s2 -- s)
Concatenate strings s1 and s2, returning the result.
it also uses
location (d -- d')
Takes db reference d and returns d', the db reference for its location.
and
dup (x -- x x)
Duplicate the top of the stack.
and
dbcmp (d1 d2 -- )
Works just like =, except operates on db references instead of integers.
: far_vision
#2032 (2032 is Celia's object number. )
dup (Make 2 copies; we're about to use 1. )
name (Celia might change her name in the future, so)
(instead of using "Celia" here we just look up)
(her name. )
" is currently in "
strcat (Attach name to sentence so far )
swap (Flip the sentence back so we can get at)
(Celia's dbref again. )
(Celia's dbref is now at top of stack. )
location (Where is Celia? )
name (What is the name of the place she is in?)
"." strcat strcat
me @ swap notify (Tell the player where Celia is.)
#2055 (Celia's hardsuit is #2055. )
location
#2032 (Celia again )
dbcmp (Has she got her hardsuit with her?)
if me @ "Watch out-- she's wearing her hardsuit!" notify then
;
Note that this program uses no variables (except for the universally
defined ME variable.)
In Muck, this program would be attached to, say, a homing device or a magic
staff. Now, if Boomer ever wants to find Celia, he can, and he'll even know
if she's defenseless or she's got her armor.
Without the comments and spaced out like you might see normally, this program
looks like:
: far_vision
#2032 dup
name " is currently in " strcat
swap location name
"." strcat strcat (Now we know where she is.)
me @ swap notify
#2055 location
#2032 dbcmp
if me @ "Watch out-- she's wearing her hardsuit!" notify
then
;
Words can also be called by other words; to do this, you treat your other
words just like library functions when you use them. When you have more
than one word in the same program, the word which is listed *last* is
the one executed, and all the ones listed before it are subroutines. The
above program could be rewritten:
: celia-identity
#2032
;
: far_vision
celia-identity dup
name " is currently in " strcat
swap location name
"." strcat strcat (Now we know where she is.)
me @ swap notify
#2055
location celia-identity dbcmp (Using celia-identity and spacing the )
(commands like this makes this bit a )
(little easier to understand. )
if me @ "Watch out-- she's wearing her hardsuit!" notify
then
;
Oodles and oodles of other neat MUF library routines are available, too
numerous to be detailed in an introduction such as this. A complete list,
as well as sample code, is available from such spiffy ftp sites as
prince.white.toronto.edu and belch.berkeley.edu.
If you're interested in seeing more sample code, write to me for
program listings for the Pan Galactic Gargle Blaster, walkie-talkies,
and several useful library routines.
Stinglai "Two Sheds" Ka'abi
Mail to: blojo@ocf.berkeley.edu
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
"We are too proud to fight." --Woodrow Wilson 1856-1924
"Violence never settles anything." --Genghis Khan 1162-1227
"The mice voted to bell the cat." --Aesop c. 620-c. 560 B.C.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Coming next week:
"Life, the Universe, and INTERCAL:"
A most excellent introduction to that programming language.