The new FuzzBall 6.00 server has made a lot of changes to the MUF language. These changes enable new ways of writing MUF programs that are faster, smaller, more powerful, and easier to read and maintain. It can all seem rather overwhelming at first, but each part is fairly simple. This document is designed to walk you through the new feature, building you up to the more serious high-end programming tasks.
Perhaps one of the most important, yet minor changes to MUF is the addition of "function-scoped" variables. You can now declare variables inside a function, that will only exist until that function is exited. You can have several functions in your program that use function-scoped variables of the same name, but MUF knows to treat them as different variables. Not only that, but MUF knows that if a function calls itself recursively, each call gets its own variables. Changes to the variable in one call level won't change the value of the same named variable in the caller.With these new scoped variables, it is no longer discouraged to use variables in MUF. In fact, the use of variables tends to lead to much more readable code, so it is highly encouraged that you should use them, within reason.
Declaring a function scoped variable is simple: Just declare it inside the function definition. For example:
Note that the example program here is extremely simplistic, for demonstration purposes. For something so simple as this, you normally wouldn't bother using variables, as they don't really help make the code any more readable.: tell ( str -- )
var mesg
mesg !
me @ mesg @ notify
;The sequence of declaring a variable, then setting its value turns out to be fairly common. There's a shortcut available that will both declare a variable, and set its value in one step. You simple declare the variable with '
var!' instead of 'var'. For example:: tell ( str -- )
var! mesg
me @ mesg @ notify
;
Function-scoped variables are all nice and all, but wouldn't it be nice if you could declare a function and have its arguments automaticly stored in variables? Well, you can. When defining a function, if the last character of the function name is a '[', MUF knows that the following words are part of a function declaration with variables. It looks like this:The ': tellto[ who mesg -- ]
who @ mesg @ notify
;[' at the end of the function name tells it that the next few words, until the '--', are the names of function scoped variables for it to declare and set the value of from the stack. The last declared variable in the list gets the topmost stack item stored in it. Everything from the '--' until the ending ']' is treated as a comment. This lets programmers show what the function will return on the stack. The '[' will not be declared as part of the function name, so you would use 'tell' and not 'tell[' to call the above listed function. The above code is effectively the same as:You can also put tiny comments at the beginning of each argument, as long as you separate them from the argument name with a colon (': tellto ( ref str -- )
var mesg mesg !
var who who !
who @ mesg @ notify
;:'). These are used to show what data type is expected for each argument. Note that these argument comments cannot contain spaces. For example:The convention used for what comment to put before variables of what types, can be seen in the following table:: tellto[ ref:who str:mesg -- ]
who @ mesg @ notify
;For example, if you had a function that took a dbref, an int, a string, and a list array, it might be declared like this:
Mnemonic Argument Type int Integer Number flt Floating Point Number ref DB Reference str String lok Boolexp Lock arr Array of any type list List Array dict Dictionary Array addr Function Address gvar Global Variable lvar Program Local Variable svar Function Scoped Variable mark Stack Marker any Argument may contain any data type. The above will declare the function named ': MakeStuff[ ref:dest int:count str:basename list:links -- list:createdObjs ]
(...)
;MakeStuff' and declares four function scoped variables for it nameddest,count,basename, andlinks. It will initialize them from the stack, withlinksgetting the topmost stack item. The comment after the '--' suggests that this function returns a list array of 'createdObjs'.Someday in the future, MUF may implement type checking based on the type comments for each argument. If you stick to the mnemonics listed in the table above, your code should be able to take advantage of this automatically.
In MUF, there are times that you need to work with a variable number of stack items. This is usually done by having the items on the stack, with the count of them pushed on top of them. These are often called stacklists or stackranges. For example, to store three strings on the stack,"One","two", and"three", you would push those strings onto the stack, then push '3' to indicate that there are three strings. You can then later add a fourth string, as long as you increment the counter '3' to '4'. TheEXPLODEprimitive returns string stacklists like this.Sometimes it would be more convenient if MUF could just count the items for you as you push them onto the stack. In fact, it can. Using the concept of Stack Markers, you can push a marker onto the stack, push an arbitrary number of items onto the stack above it, then get the count of how many items are between the top of the stack and the marker. (The marker is removed in the process.) For example, the earlier code to push three strings onto the stack to make a string stacklist, would be rewritten as:
The '{ "One" "two" "three" }{' command pushes a stack marker onto the stack. You then push whatever you want onto the stack. The '}' command then counts how many stack items are above the topmost stack marker, removes the marker, then pushes an integer that is the count of the items. The above code would result is the following stack:There are a few MUF primitives that also been added to help deal with stacklists. These are:"One" "two" "three" 3
LDUP- will duplicate a stacklist, pushing a second copy of it onto the stack.
LREVERSE- will reverse the order of the items in the stacklist.
POPN- will let you pop all the items in the stacklist off the stack, including their count.
Probably the biggest improvement in dealing with stacklists, though, is to hide them. :-) By that, I mean use list arrays. A list array is a single stack item that can contain zero or more other stack items of any type inside itself. List arrays are made from stacklists with theARRAY_MAKEcommand, like thus:What that will return is a single stack item, that is a list array containing"Alpha" "Beta" "Gamma" 3 array_make"Alpha","Beta", and"Gamma". What I mean by 'single stack item' is that it takes up only one space on the stack, the same as an integer, string, or other value. This lets you shuffle stack items about more easily, and also lets you store multiple items in a variable, simply by keeping them in a list array. For example:will end with the integer{ "Alpha" "Beta" "Gamma" } array_make 23
swap var! mylist23on top of the stack, and the list stored in the new variable named 'mylist'. You'll notice that we used the{and}stack marker commands to count the strings in our stacklist for us. Using{and}withARRAY_MAKEturns out to be very common. So common, there is a shortcut for creating list arrays, called '}list'. For example, the first sample code in this section can (and should!) be written as:To get the stacklist of values back out of the list array, you can use{ "Alpha" "Beta" "Gamma" }listARRAY_VALS. For example:will return the following on the stack:{ "Alpha" "Beta" "Gamma" }list array_valsIf you want just the second item of the list, you can get just that, using"Alpha" "Beta" "Gamma" 3ARRAY_GETITEM. For example:will return{ "Alpha" "Beta" "Gamma" }list 1 array_getitem"Beta". Note that list arrays are zero-indexed, meaning that the first item in a list array has an index of 0. That's why we used 1 to get the second item. The use ofARRAY_GETITEMis so common that there is a shortcut for it, '[]'. For example, you can rewrite the above example as:You can add new items to an array using{ "Alpha" "Beta" "Gamma" }list 1 []ARRAY_APPENDITEM. This will append a new item to the end of the list. For example:will end up with the list array stored in 'mylist' containing{ "Alpha" "Beta" "Gamma" }list var! mylist
"Delta" mylist @ array_appenditem mylist !"Alpha","Beta","Gamma", and"Delta"in order. It is important to note at this point that if youDUPan array, and modify one copy, it will not change the other copy of the array. Ie:will end up with two different list array items on the stack, with differing contents. The topmost list array will have four items, including{ "Alpha" "Beta" "Gamma" }list dup
"Delta" swap array_appenditem"Delta"while the other list array will only contain"Alpha","Beta"and"Gamma". Similarly if you fetch an array from a variable, then modify that array, the copy of the array that is in the variable will be unchanged. That is why you have to store the array back in the variable like we did earlier. Ie:To replace an existing list item, so it has a new value, you can use"Delta" mylist @ array_appenditem mylist !ARRAY_SETITEM. For example:Would change the third item in list array stored in"Third" mylist @ 2 array_setitem mylist !mylistto"Third", making the resulting list array contain:"Alpha","Beta","Third", and"Delta". If you useARRAY_SETITEMto set the value of the position just beyond the end of the list array, it will act just likeARRAY_APPENDITEM. TheARRAY_SETITEMprimitive is used commonly enough that a shortcut of '->[]' has been provided for it. Ie:To insert items into the middle of a list array, you can use"Third" mylist @ 2 ->[] mylist !ARRAY_INSERTITEM. For example:Would result in a list array containing,"Fee" mylist @ 2 array_insertitem mylist !"Alpha","Beta","Fee","Third", and"Delta".To delete or remove items from a list array, you can use ARRAY_DELITEM . For example:
Would result in a list array containingmylist @ 3 array_delitem mylist !"Alpha","Beta","Fee", and"Delta".
You can also set, insert, delete, and fetch ranges of data from an array using the array_*range group of primitives. For example, if you want to get the second through fourth items of a list, you can do the following:
This would return a list on the stack containing "Beta", "Fee", and "Delta".mylist @ 1 3 array_getrange mylist !
You can insert multiple items into a specific position in an array with ARRAY_INSERTRANGE. For example:
mylist @ 1 { "One" "Two" }list array_insertrange mylist !Would result in a list containing, "Beta", "One", "Two", "Fee", and "Delta".
To overwrite a set of items in the middle of a list, you use ARRAY_SETRANGE . For example:
mylist @ 1 { "Gamma" "Epsilon" "Phi" }list array_setrange mylist !Would result in a list containing "Beta", "Gamma", "Epsilon", "Phi", and "Delta".
Deletion of ranges of list items is also simple via ARRAY_DELRANGE . For example:
mylist @ 2 3 array_delrangeWould result in a list containing "Beta", "Gamma", and "Delta".
INCOMPLETE: discuss list specific prims.
In reality there are two types of arrays. What I've shown you so far are called list arrays. The other type of arrays are called dictionary arrays, or dictionaries. List arrays are really just a special case of dictionaries that have a few special properties:Dictionaries, on the other hand:
- List arrays have integer indexes.
- List array indexes are contiguous from 0 to one less than the number of list items. Ie: For a five item list array, the first list item will have an index of 0, and the last list item will have an index of 4.
- If you insert into the middle of a list array, the indexes of the items after the insertion point are renumbered so that the indexes are contiguous. Ie: If you insert an item at index 3 of a five item list array, then the original items at indexes 3 and 4 will be renumbered to be at 4 and 5, and the new list item will be put in at index 3. If you delete items from the middle of a list array, the items after ther removed items will be renumbered so that the indexes are contiguous. Ie: If you delete item 3 of a 6 item list, then the items at indexes 4 and 5 will be renumbered to 3 and 4 respectively.
- You can use
ARRAY_APPENDITEMto append items to a list array easily.- If you set an item in a list array, that has a string index, or a non-contiguous integer index, the list array is automatically converted into a dictionary.
For almost any MUF array primitive that takes dictionaries as arguments, you can use list arrays and dictionaries interchangably. For many, if you pass in a list array argument, the result will also be a list array, so long as the command wouldn't make the resulting array violate the above list array rules.
- Can have non-contiguous integer or string indexes.
- Do not renumber if you insert or delete items.
- Do not automatically revert to list arrays when you make the indexes all contiguous integers.
- Cannot be used with some MUF primitives that expect list arrays.
For MUF array primitives that expect list array arguments, you can sometimes pass in dictionaries. In these cases, the indexes will determine the order of the values, but will otherwise be dropped and ignored.
Dictionaries can be used to implement the equivalent of hash tables or associative arrays (to use perl terminology). Each item has a key (or index), and a value. You can create a dictionary in a similar way to how you created a list array, except you need to specify the key and value for each item, and you use '
}dict' instead of '}list'. For example:Would create a dictionary with five entries and store it in the variable '{
"color" "blue"
"age" 31
"height" 1.98
"object" #1234
42 "The answer."
}dict var! exdictexdict'. The entry indexed as"color"would contain the value"blue", the entry indexed as"age"would contain the integer31, the entry indexed as"height"would contain the floating point number1.98, the entry indexed as"object"would have the dbref value of#1234, and the entry indexed as42would have the string value of"The answer.".Using the above created dictionary in the variable '
exdict', you can retrieve various values using the sameARRAY_GETITEMor[]primitive we discussed earlier with list arrays. For example:Would return the integer valueexdict @ "age" []30. You may have noticed that while the keys (or indexes) of a dictionary can only be strings or integers, the values could also be floats or dbrefs. In fact, arrays can contain values of any stack item type. Including stack markers and arrays. Yes, this means that is is possible to nest arrays.INCOMPLETE: discuss insert, delete, set, ranges, etc.
TBW: discuss array_union, array_diff, array_intersect, array_extract, array_sort, array_reverse, array_matchkey, array_matchval, array_filter_prop
TBW
TBW
There's been a couple of additions to how loops work in MUF. The first addition is theFORloop. TheFORloop will iterate from one integer to a second, incrementing by a given step. It pushes the current counter onto the stack before executing the code inside the loop. You can useFORin the place you might normally use theBEGINkeyword, at the start of a loop. For example:Will count from 1 to 100 by steps of 2 and show the count to the user. It will start with 1, and keep incrementing by 2 until the number would be greater than 100. The code inside the loop will be run for each time the number is less than or equal to the ending number. Ie: for the example above, the last number displayed would be 99, not 101.1 100 2 for
intostr me @ swap notify
repeatThe other new addition to looping is the
FOREACHloop, which is similar to theFORloop, except it only takes an array as an argument, and runs the loop once for each item in the array.FOREACHwill push both the key and the value onto the stack before running the code inside the loop. LikeFOR,FOREACHis used in place ofBEGINat the beginning of a loop. For example:Would display three lines, reading: "{
"stop" "red"
"warn" "yellow"
"go" "green"
}dict
foreach
" = " swap strcat strcat
me @ swap notify
repeatgo = green", "stop = red", and "warn = yellow". These aren't in the same order as they were in the source code above, because dictionaries are internally sorted alphabetically by key. It should also be noted that integer keys are always sorted before string keys, and are sorted among themselves in numeric order.As
FORandFOREACHare simply additions to the original looping constructs, you can use them withWHILE,BREAK,CONTINUE,REPEAT, andUNTIL. For example, the following is a perfectly valid looping construct:1 100 3 for
dup 88 = not while
dup 92 = if pop continue then
dup 90 = if pop break then
31 =
until
There's three major new additions to MUF to make it easier to construct formatted strings. The first addition isFMTSTRING, which is very similar to theSPRINTF()function in C. This is an extremely powerful text formatting tool, but it is also rather complicated.
FMTSTRINGcan be used to create complicated strings based on a format string, and a number of other values on the stack. The topmost stack item will be the format string. The format string itself will determine how many other stack items will be used. The first format substitution in the format string will use the stack item under the format string. The next format substitution will use the next item down the stack, and so on.The start of a format substitution in the format string is marked by a '
%'. If a literal '%' is needed in the string, a '%%' may be used. The format of a substitution is as follows: '%[-,|][+,][0][width][.precision]type' where width and precision are optional integer values, and type is one of the following identifiers:A '
Identifier What it substitutes in iInteger number sString value dDBRef number, in the form #1234DGiven a dbref, substitutes the name of the object. If a bad object, an error is thrown. lGiven a lock, substitutes the human readable prettified form of the lock. fFloating point number, in xxx.yyy form. eFloating point number, in scientific notation. x .yyEzz gShorter of form eorf.~Default representation for any given stack type. ?Substituted the name of the stack item type. Ie: INTEGER,FLOAT,STRING, etc.-' at the start of a format substitution indicates the field will be left justified.
A '|' at the start indicated the field will be centered.
A '+' forces the + sign to appear for positive numbers.
A space forces a blank in front of positive numbers. (This is the default.)
A leading0will force the field to be padded on the left with0's instead of spaces.
If you use a '*' in place of either the width or precision format fields, then that integer number will be obtained from the stack.
The width field specifies the minimum width of the field into which the subsititution will be placed.
The precision field specifies the maximum number of characters to use for strings, the number of decimal places to use for floats, and the minumum number of decimal digits for integers.Well, that was a lot of info. Lets try explaining that with a few progressively more complex examples:
The code: Would return the string: "%%" fmtstring"%"42 "%i" fmtstring"42"42 "%4i" fmtstring" 42"42 "%-4i" fmtstring"42 "42 "%|4i" fmtstring" 42 "42 "%04i" fmtstring"0042"42 "%+04i" fmtstring"+042"#1 "%D" fmtstring"Wizard"#1 "%|10D" fmtstring" Wizard "#1 "%-10d" fmtstring"#1 ""foo" "%-10s" fmtstring"foo ""foo" 5 "%-*s" fmtstring"foo ""foobar" "%.5s" fmtstring"fooba""foobar" "%6.5s" fmtstring" fooba""foo" "%4s" fmtstring"foo ""foobar" "%4s" fmtstring"foobar""foobar" 4 8 "%-*.*s" fmtstring"foob "pi "pi = %.5f" fmtstring"pi = 3.14159"123456.7 "Num" "%-5s = %7.2e" fmtstring"Num = 1.23e5""foobar" "%7~" fmtstring" foobar"42 "%7~" fmtstring" 42"pi "%7~" fmtstring"3.14159"#1234 "%7~" fmtstring" #1234"
FMTSTRINGis unfortunately a bit ugly, even if powerful. The reversed argument order is also particularly confusing. Luckily something can be done about that. If you use the Stack Markers we discussed before, with theREVERSEcommand, you can make the arguments toFMTSTRINGappear in the same order they do in the format string:The above code would return the string{
"%s is a %s with a value of %.5f."
"Pi" "floating point number" pi
} reverse fmtstring"Pi is a floating point number with a value of 3.14159".
Here's a more complex example of using fmtstring:
The above code will display four lines to the user, each one with three columns, containing the left justified name of the object, the centered dbref of the object, and the right-justified location name of that object. It should look something like:{ #0 #1 #123 #23456 }list
foreach
swap pop
dup location name swap dup
"%-25.24D %|10d %30.29s" fmtstring
me @ swap notify
repeatNotice that the first format code 'Empty Nothingness #0 *NOTHING*
Wizard #1 Empty Nothingness
The Resturant at the End #123 Global Environment Room
East;e #23456 The Resturant at the End of th%-25.24D' specifies that the first column is left justified, with a minimum width of 25 columns, and the object name substituted inside is limited to 24 characters. The second column, with a format code of "%|10d" is centered in a field 10 characters wide, and will display the numeric dbref value with the '#' prepended. The third column, with a substitution code of "%30.29s" specifies a right-justified string no more than 29 characters long, in a field 30 characters wide.
There is a higher level primitive called ARRAY_FMTSTRINGS that is similar to FMTSTRING , except it is used to format several lines of information at a time. It takes a list of dictionaries and a format string. For each dictionary in the list, it formats the contents into a new string, based upon the given format string. Then ARRAY_FMTSTRINGS returns a list containing all of these strings, in order. The main differences in the formatting string from that used in FMTSTRING is that * options are not supported, and each field must specify the index it will use to find the data it needs in each dictionary. It uses '[ index]' to specify the index in each format field. For example, the format string "%-30.30[foo]s" would make a left formatted string (for each dictionary) that was exactly 30 characters long, that is filled with the text value of the "foo" entry in each dictionary.
The earlier complex FMTSTRING example could be rewritten (admittedly more complexly) as:
{
{ #0 #1 #123 #23456 }list
foreach swap pop
"room" swap "loc" over location
2 array_make_dict
repeat
}list
"%-25.24[room]D %|10[room]d %30.29[loc]s"
array_fmtstrings
{ me @ }list array_notify
For the majority of cases, you don't need the power of
FMTSTRINGor ARRAY_FMTSTRINGS. For those cases, it is often easier to understand and use theARRAY_JOINcommand.ARRAY_JOINwill simply concatenate a number of items in a list array, regardless of value type (using default formats), and returns the result as a string. For example:Would return the string"Fee" var! fooname
42 var! foopos
2.717 var! fooval
{ fooname @ " is " foopos @ " with a value of " fooval @ "." }list
"" array_join"Fee is 42 with a value of 2.717". The string argument that is passed to ARRAY_JOIN on top of the stack is the separator to put between the parts it joins. For example:Would result in the string{ "Tom" "Dick" "Harry }list ", " array_join"Tom, Dick, Harry".
TBW
TBW
Sometimes, when a MUF program would crash with an error, you want to catch that error instead, and just deal with it cleanly. This is what exception handling is for. Exception handling uses the keywords TRY, CATCH, and ENDCATCH. The TRY statement takes an integer argument to tell it how far down the stack you want to lock the stack. If you tell it 3, it will lock all except the top 3 stack items, and any attempt to use or pop the locked stack items will throw an error. (Which will be caught by the CATCH statement.) If an error is thrown, all unlocked items will be popped off the stack so that the stack will be in a known state, the stack is unlocked, the string error is pushed onto the stack, and execution resumes after the CATCH statement. If execution reaches the end of the TRY-CATCH block without any errors being thrown, then the stack is unlocked and execution will jump to after the ENDCATCH. It looks like this:
You can nest TRY-CATCH blocks, and the innermost will catch an error and handle it. When an inner TRY-CATCH block unlocks the stack, it will only unlock down to the item where the outer TRY-CATCH has the stack locked. You can pass errors to outer handlers using the ABORT primitive in the CATCH -ENDCATCH block. You can also use ABORT to throw arbitrary errors in the TRY-CATCH block itself.#123 #234
2 try ( lock all but top 2 stack items )
moveto
catch ( str:error )
"That move could not be performed because: " swap strcat
me @ swap notify
endcatch
TBW