zMUD Programming Language
What is zMUD?
While zMUD is primarily a MUD client, it is also a programming language. Many examples of scripts, triggers, and aliases have been shown in previous chapters and all illustrate the power of this programming language. At its heart, zMUD is an interpreted, event-driven, un-typed, programming language, much like PERL or BASIC.
Interpreted zMUD executes statements and commands as they are entered, rather than compiling them to a separate file. When a zMUD program is loaded, it is executed one line at a time, just as if those lines had been entered directly into the command line. Interpreted languages are slower than compiled language, but can offer more flexibility and are usually easier for non-programmers to learn.
Event-driven Like Microsoft Windows itself, zMUD is event driven. This means that object methods are executed in response to external events. These events can include keystrokes or mouse clicks, but more commonly rely upon receiving text patterns from the MUD. Other events, such as alarms and timers are also provided.
Un-typed All variables in zMUD are stored as strings. Variables do not have a formal type associated with them. The type of a variable is determined by the context in which it is used.
Because zMUD is a general-purpose programming language with the attributes described above, you can do almost anything with it that you would normally use a programming language such as Pascal or C for. In fact, it is because of this language that zMUD is so powerful and that there are so many different ways to accomplish similar tasks.
In this chapter, zMUD will be compared and contrasted with other well-known languages, and the formal zMUD language syntax will be presented, along with several examples. While this chapter will be easier to follow those users with some programming experience, new users will also find the information useful in understanding how zMUD really works.
zMUD Command Line
The zMUD command line is used to execute zMUD program scripts, as well as to send text to the MUD. In order to determine whether you are sending text to the MUD or executing commands and scripts, zMUD relies on a mode setting, as well as special characters.
zMUD has 3 primary modes of operation:
Normal text entered into the command line is generally sent to the MUD. However, several special characters can be used to execute zMUD commands or scripts. This mode is selected when the Parse option in the Settings menu is enabled.
Verbatim all text entered into the command line is sent to the MUD. None of the special zMUD characters are recognized. This mode is selected when the Parse option in the Settings menu is disabled.
Script this mode is used when executing previously defined scripts. This mode cannot be selected from the command line, but is used internally by zMUD.
When working in Normal mode, the following special characters are used by zMUD:
;
Command Separator used to enter multiple commands on the same line. The text before the semi-colon is executed, followed by the text after the semi-colon.<>
Expansion text entered between the angle-brackets is expanded before it is executed. The brackets are then discarded. This syntax can be disabled by turning off the Allow <> for Expand option in the General Preferences dialog.[]
Evaluation text entered between the square brackets is evaluated as an expression before it is executed. The brackets are then discarded. This syntax can be disabled by turning off the Allow [] for Evaluation option in the General Preferences dialog.""
Double-quotes text entered between the quotes is sent to the MUD verbatim. Depending upon the Strip " Quotes option in the General Preferences dialog, the quotes are either discarded or sent to the MUD with the text. Surrounding the entire line with double-quotes discards the quotes and causes the entire line to be sent to the MUD in Verbatim mode.‘’
Single quotes Work the same as double quotes, but uses a separate Strip ‘ Quotes option. This allows you to set up one type of quotes that are discarded, and one type that are not depending upon the needs of your MUD.#
Command Used to prefix a built-in zMUD command. All of the following text (up to the next semi-colon) is parsed as part of that command and is not sent to the MUD:
Focus Used to specify the window focus for the command. This allows you to send commands to different MUD windows that you might have open at the same time. The Multiplaying chapter describes the use of the Focus character in detail. The text up to and including the colon is not sent to the MUD.@
User Variable Used to expand a user-defined variable or function. This character is only recognized when expanding text, such as when using the <> brackets described above. If the Expand Vars option in the General Preferences is enabled, variables are expanded without requiring the angle brackets.%
System Variable Used to expand a built-in variable or function. This character is only recognized when expanding text, such as when using the <> brackets described above. If the Expand Vars option in the General Preferences is enabled, variables are expanded without requiring the angle brackets.All of the single characters shown above (
; # : @ %) can be changed or disabled in the Characters tab of the General Preferences dialog. Note that if you change these characters, your scripts may not be portable or useable by other players. Also, you should make sure that when changing these characters, each character is unique. If you specify the same character for two different uses, zMUD can get very confused. If you disable one of these special character functions, the character will be sent to the MUD verbatim and you will loose the functionality that the character normally provides. For example, if you disable the # Command character, then you will not be able to issue any commands to zMUD.Also remember that in Verbatim mode, none of these characters are recognized; they are all sent to the MUD directly. Thus, rather than disabling characters, it might make more sense to just turn off the Parse option in the Settings menu to enable Verbatim mode. This can also be achieved by clicking on the Parse icon in the icon bar in the lower right corner of the zMUD window. The Parse icon is shown as a picture of a computer. When this icon has a red X drawn through it, then parsing is disabled and you are working in Verbatim mode.
Commands, Procedures, and Functions
Any line that begins with the Command character (
#) is executed as a zMUD command. zMUD has dozens of commands, described in the Appendix. Each command performs a particular action, such as defining language elements (aliases, triggers, macros, paths, etc). zMUD commands are analogous to statements in other programming languages.Aliases are analogous to procedures or subroutines in other programming languages. Aliases can take an unlimited number of replaceable parameters that are defined when the alias is actually executed.
Variables are just like variables in other languages. zMUD stores all variables as strings with a maximum length of 64k characters. Even numeric results are stored as strings, which, while slower, provides great flexibility and removes the issue of variable type that is present in most languages.
Functions are similar to functions in other languages. Functions can take an unlimited number of replaceable parameters that are defined when the function is actually executed. Functions return a value which is discarded if not stored somewhere or sent to the MUD. Functions cannot contain commands. Instead, functions are defined as combinations of other built-in and user-defined functions.
With the above definitions, lets look at some programming examples, comparing zMUD to the Pascal programming language (which is what zMUD is written in).
Whenever a new programming language is taught, the first program traditionally prints the text "Hello world" to the user. Here is such a program in zMUD and in Pascal
zMUD |
Pascal |
#SHOW Hello World |
Program Hello; |
In zMUD, the #SHOW command operates similar to the writeln procedure in Pascal (or the PRINT statement in BASIC). Because Pascal is a compiled language, the name of the program must be defined, along with other syntax that simply isn’t needed in zMUD.
The next example illustrates aliases (procedures) and functions. In this simplistic program, a range of numbers is specified, and the squares of each number in the range is displayed
zMUD |
Pascal |
#FUNC Square {%eval(%1*%1)} #ALIAS DoIt {#LOOP %1,%2 {#SHOW @Square(%I)}} DoIt 1 10 |
Program PrintSquares; Function Square( x: integer): integer; Procedure DoIt( MinX, MaxX: integer); Begin |
It is clear from the above example that programming in zMUD requires less typing than a language such as Pascal, but such convenience results in zMUD being less readable.
First, a function called Square is defined whose purpose is to return the square of whatever number is given to it. In Pascal, the argument given to the Square function must be explicitly defined. zMUD is type-less and does not require any formal parameter or variable definitions. The parameter
%1 simply represents the first value given to the function and corresponds to the argument x in the Pascal example. Since all zMUD variables and functions store results as strings, no formal function type is required as it is in Pascal. As described later in more detail, in Script mode, zMUD simply expands text, but does not evaluate the result. In order to get the zMUD Square function to multiply its arguments together, the %eval function must be explicitly called. %eval returns the results of evaluating its parameter, which in this case is %1*%1, corresponding to the x*x in the Pascal example.Next, an alias/procedure DoIt is defined. This procedure takes two arguments (
MinX and MaxX in the Pascal example, %1 and %2 in the zMUD example). This procedure then loops through the specified range and prints the square of each number by called the previously defined Square function. The #LOOP command in zMUD is equivalent to the FOR statement in Pascal. The first argument to #LOOP is the range to loop over, which is given by %1 and %2, the two arguments passes to the DoIt procedure. Then, the #SHOW command is used to print the result of the Square function to the screen. Note that in order to tell zMUD that Square is a function call and not just plain text to be printed, the variable/function character @ is used. Finally, note that in Pascal you must declare and specify the looping variable (I in this case), while in zMUD, the looping variable is automatically stored in the %I built-in variable.Finally, the DoIt alias is called and passed the range 1 through 10. In zMUD, arguments to aliases are given after the name of the alias and separated by spaces. In languages such as Pascal, arguments are separated with commas and enclosed in parenthesis.
Let’s summarize some important points comparing zMUD with another language such as Pascal:
Brackets, Braces, Parenthesis, and Quotes
Because zMUD uses such a "shorthand" syntax, the uses of various special characters is often confusing. When writing complex zMUD programs or scripts, it is very important to understand the meaning of various characters. The most important and confusing characters are the various brackets, quotes, and parenthesis used to delimit text. Each of these grouping characters have a very specific purpose.
<>
Angle Brackets text within the brackets is expanded[]
Square Brackets text within the brackets is evaluated{}
Braces used to group text into a single argument for aliases or functions()
Parenthesis used to group text into a single expression and to specify evaluation order within an expression""
Double quotes used to group verbatim text into a single argument. Text within quotes is not parsed‘’
Single quotes used to group verbatim text into a single argument. Text within quotes is not parsedThe curly braces
{} are the most commonly used delimiter characters. Since the arguments for zMUD commands and aliases are separated by spaces, braces are often needed when arguments contain spaces of their own. For example,test a b c
executes an alias called
test and passes a as the first argument %1, b as the second argument %2, and c as the third argument %3. Where astest {a b c}
executes an alias called
test and passes the text a b c as the first argument %1. In general it is recommended that you surround each argument to a command or function with braces whether it is needed or not. Braces are always stripped from the arguments before they are used.To contrast the operation of the other delimiters, let’s look at an example using the #VAR command to define a variable. For this example, assume the variable
@A has the value of 100 and the variable @B has the value of 2.
Definition command |
Alternative command |
Resulting value stored in @test |
Mode |
#VAR test "@A/@B" |
#FUNC test {@A/@B} |
@A/@B |
Verbatim |
#VAR test <@A/@B> |
#VAR test {@A/@B} |
100/2 |
Expansion |
#VAR test [@A/@B] |
#MATH test {@A/@B} |
50 |
Evaluation |
Each argument in zMUD has a default mode: verbatim, expansion, or evaluation. In the case of the #VAR command, the second argument has a default mode of expansion, so
#VAR test {@A/@B} is the same as #VAR test <@A/@B>.To determine the default mode for a particular command or function, consult the command or function wizard by typing the name of the command (preceded with a
#) or name of the function (preceded with a %) and then pressing f1. The syntax for the command or function is shown, with a particular type of each argument. Here is a list of the default mode for each argument type:
Argument Type |
Default mode |
Expression, Range, FileNumber |
Evaluate |
String, Color |
Expand (recursive) |
Name, Filename, WindowName, TriggerClass |
Expand (once) |
Pattern |
Expand with trigger pattern syntax |
All others |
Verbatim |
For example, the commands #VAR, #FUNC, and #MATH all perform the same operation (storing a value in a variable), but each has a different default argument mode. In each case, the first argument is a Name which means it is expanded once to determine the name of the variable to store a value in. Then, for #VAR, the second argument is String, which indicates it is expanded recursively and is equivalent to the
<> syntax. The second argument to #FUNC is a Literal which means it is passed verbatim and is equivalent to the "" syntax. The second argument to #MATH is an Expression which means it is evaluated and is equivalent to the [] syntax.So, to summarize the rules for delimiters
Flow control, Looping, and Recursion
zMUD is a structured language like C and Pascal and uses flow control commands to determine execution order, rather than using GOTO statements such as in BASIC. The primary reason for this restriction is that zMUD is also event-driven as described in more detail later in this chapter. In event-driven languages, event triggers are used in place of GOTO statements.
The basic flow control commands in zMUD are #IF, #CASE, #WHILE, and #UNTIL, corresponding to the if, case, while, and repeat statements in Pascal, or the if, select, while, and until statements in C.
If Statements
The #IF command is used to test the result of an expression and to perform one action is the expression is true, and optionally a different action if the expression is false. The syntax is
#IF (expression) {true-command} {false-command}
and is equivalent to the Pascal statement
if (expression) then true-command else false-command.
Because the curly braces
{} delimit the arguments, multiple true or false commands can be places within the braces separated by the semi-colon (;) command separator. To contrast, in Pascal you must place a begin…end block around multiple statements. In this regard, zMUD is more like C than Pascal.zMUD does not have any equivalent to the elseif construct in C. Instead, nesting #IF commands is done like this:
#IF (expression1) {true-command} {#IF (expression2) {true2-command} {false-command}}
which is equivalent to the Pascal statement
if (expression1) then true-command else if (expression2) then true2-command else false-command
Case Statements
The #CASE command is used to execute a different command depending upon the value of its first argument. Unlike the case statement in Pascal or the select statement in C, the zMUD #CASE command can only select based upon a numeric argument. If the argument is one, the first command is executed, if the argument is two, the second command is executed, and so on. The syntax is
#CASE expression {command1} {command2} {command3} …
and is equivalent to the Pascal statement:
case expression of
1: command1;
2: command2;
3: command3;
…
end;
If you need to select based upon a string then you must use nested #IF statements in zMUD. However, for a trick around this cumbersome syntax, you can use zMUD virtual arrays described later in this chapter.
Repeat statements
To repeat a command until a particular condition is reached, use the #UNTIL command. It executes the second argument until the first argument is true. The syntax is
#UNTIL (expression) {command}
and is equivalent to the Pascal statement:
repeat
command
until (expression);
While statements
To repeat a command as long as a particular condition is true, use the #WHILE command. The syntax is:
#WHILE (expression) {command}
and is equivalent to the Pascal statement:
while (expression) do command;
Looping statements
To loop through a range of values and execute a command, use the #LOOP command. The first argument specifies the range of the loop. It consists of two values, separated by commas. If only a single value is given, it specifies the number of times to execute the loop (making the minimum value of the range 1, and the maximum value of the range the specified number). The syntax is:
#LOOP min,max {command}
which is equivalent to the Pascal statement
for I := min to max do command
or
#LOOP num {command}
which is equivalent to the Pascal statement
for I := 1 to num do command
In zMUD, the current value of the looping variable is stored in the
%I built-in variable.If you just want to execute a command a certain number of times, there is a shortcut to the #LOOP command. Simply enter
#num command
where
num is the number of times you want to execute the command. Again, the current value of the looping variable is stored in %I.Because zMUD does not allow you to specify the looping variable, nested loops are somewhat tricky. Here is an example of two nested loops in zMUD and in Pascal:
zMUD |
Pascal |
#LOOP @MinJ,@MaxJ {#VAR J %I;#LOOP @MinI,@MaxI {#SHOW @J,%I}} |
For J := MinJ to MaxJ do |
In order to access the value of the outer loop from within the inner loop, the
%I from the outer loop is stored into the @J user-defined variable. Then, within the inner loop, %I is the index of the inner loop and @J is the index of the outer loop.Recursion
A recursive procedure or function is one that calls itself. In zMUD, recursion is accomplished both explicitly as in other programming languages, and also implicitly. Implicit recursion involves argument expansion and evaluation. Evaluation in zMUD is always recursive. Expansion is recursive unless using the default mode for Name, Filename, WindowName, TriggerClass arguments which only expand once. For example, with recursive expansion:
#ALIAS upper {%upper(%1)}
upper apple
returns
APPLE. In this example, upper apple is expanded once to %upper(apple), which is then expanded again to produce APPLE. Arguments and functions continue to expand or evaluate until there is nothing else to expand or evaluate. Here is another example#FUNC add {%1+%2}
[@add(1,2)*@add(3,4)]
returns
21. Remember that #FUNC has a default mode of verbatim, so %1+%2 is stored into the function/variable @add. The square brackets in the next line force evaluation, which is recursive. The first expansion results in (1+2)*(3+4). The second evaluation/expansion results in 3*7, and the third evaluation results in 21.Explicit recursion is performed the same as in other languages by specifying the name of the procedure or function within its own definition. To prevent infinite recursion, you normally test the argument before proceeding. For example, here is a recursive function to calculate the factorial of a number, shown in both zMUD and Pascal:
zMUD |
Pascal |
#FUNC fact {%if(%1<=1,1,%1*@fact(%eval(%1-1)))} |
Function fact( x: integer): integer; |
There are several important differences between the above programs that will help you better understand how zMUD works. In other languages such as Pascal, all expressions are automatically evaluated. Thus,
fact(4) returns 24. However, since zMUD is designed to process strings rather than numbers, the result of @fact(4) is 4*3*2*1. In order to get 24, you must evaluate the result. Also, functions in zMUD are inline expressions rather than a list of commands separated by semi-colons. Thus, we need to use the %if function which tests its first argument and returns either the second or third argument depending upon whether the first is true or false, rather than using the #IF command. Finally, note that the arguments to user-defined functions are only expanded by default, thus we need to explicitly call the %eval function to compute x-1 (or %1-1) for the next iteration of the recursive loop.More on Aliases and Functions
As shown in the preceding section, zMUD functions are inline expressions and cannot contain multiple statements separated by a semi-colon. By contract, functions in other languages are usually more like aliases that return a result. For example, look at the following Pascal function:
Function AddRange( MinX, MaxX: integer): integer;
var I, N: Integer;
begin
N := 0;
for I := MinX to MaxX do N := N + I;
Result := N;
end;
Since zMUD doesn’t have an inline function to mimic the #LOOP command, it seems almost impossible to program an equivalent zMUD function. However, we can create an alias the comes close to doing the job:
#ALIAS AddRange {#VAR N 0;#LOOP %1,%2 {#MATH N @N+%I};@N}
Now, when you type
AddRange 5 8
You get a result of
26 (which is 5+6+7+8). However, the trick is to make this alias into a function so that you can call @addrange(5,8). This is done using the %exec function which executes its arguments as commands and returns the result rather than sending it to the MUD. Thus,#FUNC AddRange {%exec(#VAR N 0;#LOOP %1,%2 {#MATH N @N+%I};@N)}
will do the trick.
@addrange(5,8) returns a result of 26. Keep in mind that any text normally sent to the MUD is captured as the result of the %exec function. If multiple lines are send to the MUD, they are returned from %exec separated by the vertical bar (|) character.Most of the time you will not need to use the %exec function since most zMUD commands have an inline function equivalent. For example %if corresponds to #IF, %case to #CASE, etc. The main commands that do not have functional equivalents are the looping constructs such as #LOOP, #WHILE, and #UNTIL.
Event Triggers
When you enter a command on the command line, you are actually triggering an event. For example, in the previous section when you enter
AddRange 5 8 on the command line, you are causing a trigger for the AddRange alias. If you assign the command AddRange 5 8 to the f8 key, then when you press f8 you are causing a keyboard trigger for the AddRange alias. If you define a button and give it an On Command of AddRange 5 8, then clicking on the button results in a mouse trigger of the AddRange alias.You should already be familiar with event triggers from the use of the #TRIGGER command. The #TRIGGER command specifies a pattern of text that executes a series of commands when the text pattern is received from the MUD. You can trigger an event under script control using the #SHOW command which mimics text received from the MUD. For example, consider the following triggers:
#TRIGGER {^--DoRecall--$} {#GAG;get recall @bag;recite recall}
#TRIGGER {You are BLEEDING} {#SHOW --DoRecall--}
#TRIGGER {That HURT} {#SHOW --DoRecall--}
The first trigger defines an event that causes your MUD character to get a recall scroll from a container (stored in the
@bag variable) and then recite the scroll. The #GAG command simply deletes the line that triggered the event (the --DoRecall-- text) from the screen. The other triggers look for text from the MUD and cause the DoRecall event to be generated. In this case we just needed to use text that is not normally received from the MUD. It is also important to use the ^ and $ in the event trigger to prevent it from triggering if someone just gossips this text on the MUD.Of course, this is a pretty strange example. Normally you would just use an alias like this:
#ALIAS DoRecall {get recall @bag;recite recall}
#TRIGGER {You are BLEEDING} {DoRecall}
#TRIGGER {That HURT} {DoRecall}
However, someone will probably find a good use for user-defined event triggers.
Other than the keyboard and text triggers, zMUD also has time-based triggers called alarms. Alarms cause events to be triggered based upon the time of day, the MUD connect time, or when a duration of time has elapsed. These alarms were described in the chapter on Triggers.