| 
   
  
 3.2 Overview: MUF (cont'd) Refining the Program (Last Page!): There are some weak points to this version of tinker.muf. If it is anM1 program, and someone tried to use it in a room she
doesn't own, the program would crash with a Permission
denied error. Also, there are no comments. And, it doesn't
include online documentation such as a #help function.
We'll rectify these weaknesses and add some new capabilities in our
final version, and in the process cover new aspects of MUF
programming.
Tinker.muf, final product... 
====================================
( tinker.muf, v1.0, by jessy @ genericmuck  5/96
  
  A builder's utility program that checks the exits in a room to make 
  sure they have @succ, @osucc, @odrop and @desc messages, and that
  none are unlinked.
  
  This program was written to accompany the MUF tutorial in the muck 
  manual. While it is slightly more convenient than examining all the 
  exits -- and possibly overlooking some -- its primary purpose is to 
  introduce different aspects of MUF. Besides, your MUCK probably 
  already has a @check command which does the same thing better.
  
  INSTALLATION:
  
  Port the program and set it Link_ok if it is to be public. Create 
  an action and link it to the program. If the program is set M2 or
  higher, edit the first line of code following the header comment
  to read `$def runningM2'. The program requires the standard libarary
  lib-match, which should be available on any established muck.
  
  USE:
  
  Typing the action name checks all exits in the room. Typing the action 
  name followed by an exit name checks just that exit.
  
  Clean up:
  
  Your MUCK really doesn't need multiple copies of tinker.muf lying 
  around. If you are using this program while learning muf with the 
  muck manual, it would be best to recycle it and the action when 
  finished or -- better yet -- put your own new program in it.
  
  Tinker.muf may be freely ported. Please comment any changes.
)
                               ( define owner's mucker level. if not m1, 
	   		            replace `runningM1' with `runningM2' )
$def runningM1
$define tell me @ swap notify $enddef
                           ( include lib-match for finding exits by name )
$include $lib/match
lvar ourExit                                         ( stores exit dbref )
lvar counter                ( store count of exits checked as an integer )
: help  (  --  )                                      ( show help screen )
    
    "tinker.muf v1.0" tell
    " " tell
    "a builder's utility that checks to make sure all exits "
    "in a room have a @succ, @osucc, @odrop and @desc set."
    strcat tell
    " " tell
    "to use, simply type \"" command @ strcat "\"." strcat tell
    " " tell
    "to check a specific exit, type \"" command @ strcat 
    " <exit name>\"." strcat tell   
;
  
: plural?  ( s -- s i )                    ( return true if s is not "1" )
                                            ( leave test string on stack )  
    dup "1" smatch if
        0
    else
        1
    then
;
  
: checkexits  (  --  )                                 ( report on exits )
               
    0 counter !                                           ( init counter )
          
    dup if                                 ( match specified exit name   )
        .noisy_match not if                ( couldn't find it;           )
            exit                           ( lib-match will notify; exit )
        else
            ourExit !             ( found it; store dbref; store 9999 in ) 
            9999 counter !        ( counter; will use for loop-exit test )      
        then
    else
        pop                                ( not doing a specific match; )
        loc @ exits ourExit !              (   init ourExit to first one )
    then
    
    begin                                     ( BEGIN EXIT-CHECKING LOOP )
    
        ourExit @ while                         ( break if no more exits )
        
        ourExit @                            ( put current exit on stack )
        
        dup getlink not if                                ( exit linked? )
            dup unparseobj 
            " is unlinked (not secure)." strcat
            me @ swap notify
        then                                             
     
        $ifdef runningM2       ( only check room-to-room exits; M2+ only )
        dup getlink room? not if 
            pop ourExit @ next ourExit !
            continue
        then  
        $endif
        
        dup "_/sc" getpropstr not if                      ( has a @succ? )
            dup unparseobj 
            " needs a success message." strcat 
            me @ swap notify
        then
                                 
        dup "_/osc" getpropstr not if                      ( ... @osucc? )
            dup unparseobj 
            " needs an osuccess message." strcat 
            me @ swap notify
        then
                                   
        dup "_/odr" getpropstr not if                      ( ... @odrop? )
            dup unparseobj 
            " needs an odrop message." strcat 
            me @ swap notify
        then 
                               
        dup "_/de" getpropstr not if                        ( ... @desc? )
            dup unparseobj 
            " needs a description." strcat 
            me @ swap notify
        then
        
        pop
        counter @ 9999 = if           ( break if we're only checking one )
             break
        then
        
        counter @ 1 + counter !                      ( increment counter )
        
        ourExit @ next ourExit !                     ( increment ourExit )
        
    repeat                                      ( END EXIT-CHECKING LOOP )
    
    counter @ intostr                   ( report how many exits checked. )
    Plural? if
       " exits"
    else
       " exit" 
    then
    " checked." strcat strcat Tell                           ( all done! )
;
  
: main
    
    strip
    "me" match me !
    
    dup if                                          ( check: wants help? )
        "#help" smatch if
            Help exit
        then
    then
    
    $ifdef runningM1          ( bail out if someone else's room; M1 only )
    loc @ owner me @ dbcmp not if    
        "Sorry, this program is only for rooms that you own."
        me @ swap notify exit
    then
    $endif
    
    CheckExits                                    ( go check those exits )
;
====================================
    
Comments: This version is properly commented. A header comment provides the
author of the program, its purpose, discusion of how to install and use
it, and a statement of conditions for porting the program to other
 Compiler Directives: This version makes use of several `compiler directives' (also called `preprocessor directives')... additional steps the compiler performs before compiling the program. (Compiler directives are discussed more fully in Section 3.2.4) 
 The first of these directives,  Because the term  
 begin depth while pop repeat Rather than include all this every time we want to clear the stack, and rather than putting in a separate function that the program would have to jump to each time we use it (a slight additional overhead), we could put... $define NukeStack begin depth while pop repeat $enddef...somewhere near the top of the program, and use NukeStack
whenever we want to clear the stack. (This technique is known as
`in-lining' or creating an `in-line' function. The effeciency gained by
not having to jump to a separate function can be significant in a tight
loop that can run many times.)
Libraries: This version makes uses of a  $include $lib/match (The server is able to find the right library because we're specifying
it by its registered name of  
 Error Checking: In this instance, we're doing a little bit of error checking: If the program is running at Mucker Level 1, it checks  toward the end of main  if the user owns the room. If not, it gracefully bails out, instead of crashing.
====================================
    $ifdef runningM1          ( bail out if someone else's room; M1 only )
    loc @ owner me @ dbcmp not if    
        "Sorry, this program is only for rooms that you own."
        me @ swap notify exit
    then
    $endif
====================================
Good error checking is one of the most difficult aspects of writing solid programs: As you're writing the code, you know how the program is supposed to be used, and may tend to make unconscious assumptions based on that. At such-and-such point in the program, it asks the user for  say  the amount of pennies to charge. Of course this should be a number... but rest assured, someday someone will enter not "100" or the like, but "All of them" or "I don't know". So, get in the habit of thinking `What could go wrong? What assumptions am I making that might not always be true?', and include code to check for and manuever around these conditions. Cultivate friends with wacko sick minds that never do what they're supposed to, and ask them to test your programs. Different programs will have different error-checking needs, but here are some common error conditions: 
 Of course, these aren't the only possible error conditions... but
these do tend to pop up repeatedly in  Formatting: This version reports how many exits were checked. Before doing so, it
looks at the number with the   |