Roasting The Chestnut Again

In this section we are revisiting the program we examined in the last section and making it a little more flexible. Instead of sending "Hello World!" to only the person using the program, it will send "Hello World, <name>!" to whoever is specified by the user. In the process we'll learn a few more primitives and get our first introduction to libraries.

The first step in the new version of our program is taking the string that will be on top of the stack and determining if it is a valid player name and the database reference of that player.

We could use the primitive word match determine the player's database reference. Match compares the string on top of the stack to all database items 'nearby' to see if the name of those database items contains the string. If it only finds one item, it puts the database reference of that item on the stack.

But what if there is not a item that matches? Or worse, what if there are two items that do match? match uses two special database reference numbers to let you know if it encounters those situations. If it cannot find a match it will put #-1 on the top of the stack. If it finds more than one item it will put #-2 on the top of the stack.

For our program to work correctly we would have to check for these special database reference numbers and send the appropriate messages to the player. This is a process that is needed so frequently that a word to do the work is provided in a library. This word will do the work of matching a player, discovering if it is ambiguous, and informing the user of the result for you. Libraries are collections of words that provide commonly needed functions.

The MATCH library provides a variety of predefined words for matching players, things, etc. much the same way the primitive word 'match' does, but with more options. You cannot find information on library words via 'man', but you can usually find documentation by typing @view <library registered name> or @view #<database reference>. In this case @view $lib/match will reveal the documentation of the MATCH library.

The library word we're interested in is MATCH-noisy_pmatch. The noisy part of its name indicates that it talks to the user directly, rather than letting you determine what you want to tell the player yourself. Since that is what we want, it will be perfect for the task.

Looking at the documentation we see:

MATCH-noisy_pmatch: playername -- playerdbref

The documentation is saying that if you have a player's name on the stack, the noisy_pmatch will replace the name string with the player's database reference.

Further reading reveals that if noisy_pmatch can't find the player it will replace the name string with a #-1, which is a database reference used to represent invalid/not found database items.

At the beginning of our previous program we used 'pop' to remove the string at the top of the stack. This time, since the name that the user types after the command will be in that string, we will start our program by using MATCH-noisy_pmatch. So the program looks like this at the beginning:

  1. ( We tell the compiler we are using the lib-match library )
  2. $include $lib/match
  3. ( Begin our main word )
  4. : main ( s -- )
  5.   MATCH-noisy_pmatch

The first line: $include $lib/match tells the MUF compiler that we'll be using the match library. The next line should be familiar since it is the same as our first program in that begins the definition of the word 'main'. As the last word defined, it will be the first to be executed.

Now if the user of our program provides a name that doesn't exist, or is ambiguous, or otherwise invalid, MATCH-noisy_pmatch will put #-1 on top of the stack. When we use notify later in our program, if #-1 is the database reference it pulls off the stack, our program will fail because notify does not know how to send a message to #-1. Our program will need to check the result to make sure its not #-1 before proceeding.

We can do this using the ok? primitive. ok? pushes a one to the stack if the database reference on the top of the stack is in the valid range of database references (#0 to the highest in the database) and that the database reference does not refer to a garbage (recycled) database item. Otherwise, ok? push a zero on to the stack to indicate the database reference is not valid.

MUF, like many programming languages, uses a zero to represent 'false' and any non- zero value to be 'true'. This becomes important when you use the if primitive. When the value on top of the stack is false, the program will skip from the if to the first instruction after a then Otherwise, it will execute the portion of the program between the if and the then.

In addition, we can have the program execute a different set of instructions if the value on the top of the stack is false. We do this by adding an else. So the form of our if becomes: 'if [things to do when true] else [things to do when false] then'. In this situation, if the value is true, then all the instructions between the if and the else will be executed. If it is false, all the instructions between the else and the then will be executed. Either way, after it is done, execution will resume with the next instruction after the then.

The 'if then' technique allows you to have your program make either/or choices "if this is true, do this, if not, do this other thing.".

For our hello world program we need to skip sending the 'hello world' message when the database reference we have is incorrect. So we place the 'hello world' portion of our program between an if and else primitives.

That portion of our code will end up looking like this:

  1.   ok? ( d d -- d i )
  2.   ( If the dbref is valid... )
  3.   if ( d i -- d )
  4.     ( it is valid, so we go ahead and start building our message )
  5.     "Hello World, " ( d -- d s )
  6.     ( We'll need a copy of the player's dbref so we'll use over )
  7.     over ( d s -- d s d )
  8.     ( We'll use our copy of the dbref to find out the target player's name )
  9.     name ( d s d -- d s s )
  10.     ( We'll now combine the first part of our message with the target player's name )
  11.     strcat ( d s s -- d s )
  12.     ( We also need to add the tail end of our message )
  13.     "!" ( d s -- d s s ) strcat ( d s s -- d s )
  14.     ( And finally, send the string to the target player )
  15.     notify ( d s -- )
  16.   else
  17.     ( The dbref isn't valid, so we pop the dbref off the stack to clean up )
  18.     pop ( d -- )
  19.   then

In line 1, the database reference is checked whether it is a valid reference. If it is, the if on line 3 will allow execution to continue to the next instruction (on line 5). If the reference isn't valid, execution will skip to the instruction after the else on line 16.

Let's assume that the database reference is valid and examine the instructions between the if and the else. First, on line 5, we put "Hello World, " on to the stack. Next on line 7 we use the over primitive. over is similar to the dup primitive, but instead of duplicating the top item on the stack, it duplicates the item below the top item and the duplicate is put on top of the stack. You can think of the copy hopping over the top item in the stack to be on top.

We're using over on line 7 so that we can use the 'name' primitive next (line 9). Name removes a database reference from the stack pushes the name (as a string) of that database item on to the stack.

Since we want to have it say "Hello World, !" we need the target player's name. The next step is adding the string containing the player's name to our "Hello World" string. We use strcat to achieve the task. strcat is stands for "string concatenate" and operates by removing the two strings from the top of the stack and pushing a string on to the stack that is the two combined. The string on the top of the stack is appended to the end of the string right below it on the stack. To complete our string we also push a "!" on to the stack and use another strcat to append it to our message.

On line 15, we use the notify primitive to send the message to the player. Notify takes the message from the top of the stack and then in turn the database reference of the target player, and sends the message to the player. Then we reach the else. Since we're executing the code between if and else (because the database reference was "ok"), we're done. The program will skip down to the next thing after then. In the case of this program, there's nothing left to do, so the program just ends.

However, If for some reason the database reference of the target player is invalid (for instance, you type "Slartibartfast" and there's no player named Slartibartfast), then the code between the else and the then will be executed. In that situation,
the code between else and the then is executed. There, we simply pop the invalid database reference off the stack to clean up. Then we're done, the program will jump to the next instruction after the then, but there aren't any, so the program ends.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <h3> <h4> <h5> <h6> <blockquote> <iframe>
  • Lines and paragraphs break automatically.
  • Use [fn]...[/fn] (or <fn>...</fn>) to insert automatically numbered footnotes.
  • Use [# ...] to insert automatically numbered footnotes. Textile variant.
  • Web page addresses and e-mail addresses turn into links automatically. (Better URL filter.)
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <c>, <cpp>, <drupal5>, <drupal6>, <glow>, <muf>, <java>, <javascript>, <php>, <python>, <ruby>. The supported tag styles are: <foo>, [foo].
  • Images can be added to this post.

More information about formatting options