20091023

finding the wrong bugs

Has it ever happened to you that when you are trying to investigate the cause of some particular symptom, you find a bunch of other bugs in the process? This sometimes happens to me. Usually I'm totally baffled by what's going on, and I search through all the code relating to it and end up saying things like, "wow, well that's never going to work," and, "oh, no wonder!" and stuff. And I'll fix these various things I found, but it still won't fix the problem.

and then it'll turn out it was just that i had my inputs wrong.or the bug was in totally some different place. and then i'm sometimes left wondering how that ever worked in the first place. usually I'm annoyed enough to figure out the why of that too.

i get the feeling this is one of those universal kinds of behaviors that all programmers run into. just curious if other people do indeed do this.

20091019

Tenuous Path Connectivity

I'm now taking an algorithms class for my masters program. I had to do a kind of neat homework problem this week, for which I think I actually managed to do a proper proof for. Assuming I did the problem right, this is kind of a big deal for me, because I've alwys been pretty awful at proofs. I thought I'd write up my solution, just for fun.

The Problem

You have an undirected graph that represents paths between various places. Intuitively, there is a concept that two nodes far away from each other have a more tenuous connection - that is, they are susceptible from being isolated from each other.

Given a graph of n nodes, and two nodes, s and t that have a distance greater than n/2, show that there is always some node v (different from s and t) that, if deleted, severs all paths between s and t.

My solution

We received a hint to use a breadth-first-search to try to solve this. A BFS basically starts at some node and makes multiple steps, at each point finding all of the nodes one step farther away from the starting point. Visually, it might look like rippling outwards from some point.

More usefully, when you run a BFS on a graph, one representation that is helpful to work with is that it turns the graph into several "layers", L0 through Ln. L0 is the starting node itself, L1 is all of the immediate neighbors of the starting node, and more generally, Ln is all the nodes n hops away from the start.

A direct application of a BFS doesn't seem likely to find any particular special node to delete. We're also given that the node always exists, so there must be some criterion for determining it. My first guesses were all along the lines of finding a node that minimizes or maximizes some aspect of the connectivity. For example, finding a node in a layer that has the most connections to the next layer. If such a node were deleted, perhaps it would knock out too many connections to support a second path to the finish.

None of these attempts worked. Finally, after drawing a bunch of graphs, I noticed an interesting property: they all had one layer in the BFS that had just one node - a single point of failure. I did several example graphs and wasn't able to construct one with both all layers including more than one node and the start and finish being at minimum distance n/2.

If this property were really true, then all we'd have to do is perform a BFS from the start and look for the first layer in which there is only one node.

Proof - by contradiction

n = number of nodes in the graph
d = distance from s to t = number of layers in the BFS from s to t = (n / 2) + 1

For the contradiction, assume there is such a graph that has two independent paths from s to t, so that if any node in one path is severed, the other still remains. In this graph, every layer L1 through Ld-1 (s is in L0, and t is in Ld) contains 2 nodes.

The total number of nodes in this graph is now:
1 +                            (L0)
2 * (d - 1) +                  (L1 through Ld-1)
1                              (Ld)
= 2 + 2d - 2
= 2d
= 2 ((n / 2) + 1)              (d must be (n / 2) + 1)
= n + 2

Contradiction: in the beginning we stated that there are n nodes in the graph, but in this minimal graph that includes two independent paths, there must be n+2 nodes. So this proves that you can't satisfy the minimum distance constraint, the number of nodes in the graph, and have two or more nodes in each level of the BFS. One constraint will always yield.

I hope this is the right answer, because I feel pretty cool for having come up with it.

Update, 11/5: I got a 10/10 on this problem. Woot.

20091014

Test First, Try Later

As I was implementing aliases, I started getting better about testing things. In a previous post, I mentioned how I found testing my code to be kind of tedious work that I was loath to do. Well, I think I've had a change of heart. Part of it is probably that writing tests for old code that's already been working at some level for ages is drudgery. At work, too, nobody particularly likes that. On the other hand, testing shiny new features can be pretty fun.

This time, for pretty much every checkin that I did, some test update went along with it, usually in the form of adding a test to verify the specific behavior that I changed with that checkin.

One particularly bad bug I fixed was a strange case of reentrancy. Every room has a set of exits, each of which points to another room. It's conventional for rooms to have reciprocal exits, that is, a bidirectional passage between two rooms. In some cases, rooms with these reciprocal exits wouldn't fully load each other, so they'd end up with one way passages. The nature of the fix is a little complicated to explain, but when I thought I had it working, I made tests with reciprocal exits and rooms in a unidirectional loop (room 1 -> room 2 -> room 3 -> room 1) to test those kinds of cycles.

Actually, the main difference in my methodology for this feature was writing automated tests before ad-hoc testing a change. In the past, I'd notice a bug by running the MUD itself, log on, do something and notice something wrong due to some symptom. Then I'd fix the bug and test that I'd fixed it by loading the MUD again and trying the same scenario, noticing whether the symptom was gone. This time instead, I'd write a new test that should catch the bug, then check that it fails beforehand and passes after. Afterwards, of course, I test it out in the real MUD to verify my assumption that the new test reflects the same scenario as the problematic symptom.

It's probably worth saying that I don't do this at work. If I find a bug in one of my areas, sometimes it might get translated into a regression test, but not always. I'm not even sure if that happens half of the time. There are a few factors that contribute to that, chief among them that my product is not nearly as testable. I'll show a few examples of how I tested some of my MUD bugs here, and it is self evident that it's easier to write tests like these, compared to writing automated tests that have to click buttons and load web pages in order to make a bug appear.

Alias processing

One set of tests I wrote were for processing the alias strings. That is, given the input command and arguments and the output alias template, I wrote tests to make sure that all the input arguments got filled into the right places. These are pretty close to unit tests in the traditional sense.

def process_test(strCmd, strArgs, strExpected)
    strActual = AliasCommand.processCommandString(strCmd, strArgs)
    assert_equal(strExpected, strActual, "output of processCommandString")
end # function process_test

def test_1arg_trailer()
    process_test("abc &1 d", "a b", "abc a d b")
end

With most of my tests I use this kind of pattern of having a helper function take care of the common work. In every one of my alias processing tests, the test merely consisted of some setup (strCmd), some input (strArgs), and an expected result.

AliasModCommand

AliasModCommand is the command used by a player to view and modify his aliases in-game. I decided to test these with a series of mock objects that simulate quite a bit of a character actually playing the MUD.

def createManager()
    char = XmlCharacterIO.loadCharacter($td.cd1.name)
    manager = ConnectionManager.new(MockConnection.new())
    manager.enterCharacter(char)
    manager.mainInteractionMode.aliases = []
    return manager
end # function createManager

I load a character from a well-known set of test data, since there must always be a character when dealing with these types of simulations. I create a ConnectionManager, because that gives controls to inject commands into the MUD. The MockConnection is a stubbed out connection object. Normally this has the functionality of performing the network I/O, but in my case all the methods do nothing, so it's basically just a dummy.

def showAliases_test(aliases, expectedText)
    manager = createManager()

    aliases.each() { |pr|
        manager.process("alias #{pr[0]} #{pr[1]}")
        manager.pulse()
    }

    filt = LastLineOutputFilter.new()

    manager.addOutputFilter(filt)

    manager.process("alias")
    manager.pulse()

    assert_equal("Aliases:" + expectedText + "\r\n", filt.prev(), "output of alias command")
end # function showAliases_test

There's a lot going on here in this helper function, but I think it's kind of interesting. After creating the manager, we actually instruct it to process lines of text that add the aliases the test requested. The pulse call causes the manager basically to process the next command.

Now it gets trickier. In an old post, I talked about filters and how they're used to get all of the text that some player sees. Well, I'm using one here, too. The LastLineOutputFilter sees all of the text that a given manager outputs, and stores the last output call - basically a short history of the last things the player would have seen on his screen. I enter a command (in this case "alias") and catch the output to see what it was. Note that here it's the job of the test to pass in the correct expected output of the alias command to verify against. Still, with the amount of work taken care of by the helper function, the tests themselves are pretty concise.

def test_showAliases_simple()
    aliases = [["a", "b"]]
    showAliases_test(aliases, "\r\n  a => b")
end

TC_XmlArray

Implementing XmlArray was a big part of getting aliases done, so of course I wrote some tests to make sure they were working themselves. I could have written these tests using actual XML files, but I thought it would be easier just to embed the XML right into the tests.

def test_load_XmlReadable()
    strXml =
<<HERE
<root>
<arr>
    <e>1</e>
    <e>2</e>
    <e>3</e>
    <e>4</e>
    <e>5</e>
</arr>
</root>
HERE

    doc = REXML::Document.new(strXml)

    classTextArray = XmlArray.newClassWithType('e', Multiplier_XmlReadable)

    things = XmlReader.loadThings([doc.elements['root']], "arr", classTextArray, true)

    assert_equal(1, things.length(), "number of arrays loaded")

    arr = things[0]
    assert_equal(5, arr.length(), "number of elements in array")

    arr.each_index() { |i|
        assert_equal(((i+1) * 3), arr[i].num(), "index #{i} in the array")
    }
end

This illustrates something more useful than just embedding XML in the test. Any old schmuck can do that. Notice that the array is of type Multiplier_XmlReadable. This is a mock class I created that implements XmlReadable about as simply as possible. Its XML representation is simply a number. When it is loaded into an object, it simply multiplies the number that was read by some constant. To the loading code, this looks similar to a bunch of other objects throughout the MUD that are persisted.

Complicated loading tests - delayed load

I used these test objects pretty heavily for some other tests. For example, there is code in loading that makes repeated passes over all of the things being loaded, trying to load them and all their properties over and over until all dependencies have been resolved. If all the things being loaded are simple (no outward dependencies) then none of that iterative code ever gets exercised. In order to write tests targeting those codepaths, I need control over the objects being loaded.

Similar to Multiplier_XmlReadable, I created Multiplier_PartiallyLoadable that gives me some hooks to help here. For the example above, I would like to make certain elements not fully load on the first try. I could do this by really recreating this network of dependencies, but at some point that becomes complicated itself, and I want to do as little debugging of tests as possible.

# in the test
    strXml =
<<HERE
<root>
    <c sets='1'><e>1</e></c>
    <c sets='2'><e>2</e></c>
</root>
HERE

# in Multiplier_PartiallyLoadable
def num=(other)
    if (other.kind_of?(Integer))
        if @setCounter < setThreshold()
            @num = PartiallyLoadable::NotLoaded
            @setCounter = @setCounter + 1
        else
            @num = other * Coefficient
        end
    elsif other == PartiallyLoadable::NotLoaded
        @num = other
    else
        raise "setting num to #{other} of wrong type"
    end
end # function num=

So instead I simulate it with a trick. Multiplier_PartiallyLoadable has a single property that points to num, so the loading system knows to call num= when setting the property to some value. What I do here is fail to actually set the property for the first n times, where n is given by the "sets" attribute in XML.

So in the example above, in the first iteration in loading, the first element would get loaded completely, but the second would need one more pass.

Complicated loading tests - linked load

There are two times at which a thing can be pronounced fully loaded. In the top-level loop, if a thing has all of its properties loaded, then its status is changed to loaded. Or, if loading is in the middle of loading the properties of something, each of those properties that becomes loaded has its status likewise changed -- not in the top-level loop but down in the recursive property loading code.

In and of themselves, neither of these are too difficult, and are already covered above. What is a little harder is the case where one of the properties of some thing (which would be loaded in the property loading code, not the top-level iterations) is itself also in the top-level list of things. This requires some property relationship. For example, when loading a list of rooms, this would arise: all the rooms are in the top-level list and are also in properties of other rooms as exits. I alluded to a bug in this scenario earlier in this post.

# in the test
    strXml =
<<HERE
<root>
    <c sets='1' linked_load='2'><e>1</e></c>
    <c sets='1'><e>2</e></c>
</root>
HERE

# in Multiplier_PartiallyLoadable
def loadingComplete()
    if linkedLoad() != nil
        # ll is set to the actual object associated with the linked_load
        # attribute
        if ll
            ll.loadingComplete()
            ll.isLoadingComplete = true
        end
    end
end # function loadingComplete

This snippet of the code is hopefully fairly concise. When one of these objects becomes loaded (loadingComplete is called exactly once per object), if its XML has indicated the number of another object "linked" to this one, also load that guy.

Error condition tests

I throw (sorry, "raise") exceptions from a few places, so I need tests for those places. Although Test::Unit provides some assertions for raising exceptions, their focus is more on the exception being raised at the expense of the result in the case where an exception (incorrectly) isn't raised. Whenever an exception test fails, the actual outcome involves some data, and I want that as part of the failure message. So I wrote a helper.

def loadtest_bad(el)
    gotException = false

    text = nil
    begin
        al = XmlReader.constructFromElement(el, AliasCommand, true)
    rescue
        gotException = true
    end

    assert(gotException, "should have gotten exception for bad input, but got #{al}")
end # function loadtest_bad

I assert that the test got an exception, and if it doesn't, the data it did get is printed out.

Unit Testing doesn't scale

All this testing is fine, but there's a problem with it, and it's the reason why we don't write that many tests like this at work. Testing individual behaviors like this isn't the most efficient way in many cases, and time is always scarce.

If I were testing something like this at work, I would probably model the area. I'd break each aspect up into its separate pieces and create a matrix of possibilities for each scenario. For instance, when testing the command for manipulating aliases in-game, there are several parameters. Each of these parameters is either present, empty, or has some specific characteristics. Understanding the characteristics that are important is the essence of modeling.

The level of test coverage that I have so far is still in its infancy, so I'm not yet at the point where the remaining bugs to find are going to be found with larger models. I'm still covering the basics.

20091011

ramen riot 1: Unif Tung-I Chah Chiang

It's not really a secret that I like ramen. My usual brand of choice from regular stores is Maruchan, which I find to be noticably superior to top ramen. However, we have a few asian groceries around here, like the 99 Ranch Market (super weird name in my opinion). There we can find huge aisles full of weird types of ramen. I have gotten at least five different varieties before, and they're all a little weirder than the regular stuff from Safeway. I thought it would be amusing to write about them when I experience new ones.

Today's weird ramen is called Unif Tung-I Chah Chiang Flavor. What part of that is the brand? I dunno. Maybe "unif". I'm not sure what a Tung-I is either. Maybe it's korean or japanese for "weird ramen." Supposedly chah chiang is a flavor, but I'm not sure (even after eating it) what flavor it represents. Ah, I just tried to look it up, and it seems this is a chinese brand of ramen.

ramen_tung-i-chah-chiang

The main differentiating factor between normal ramen (e.g. maruchan) and weird ramen is the packets that come with it. While normal ramen usually only has a single flavor packet, the weird ones usually have at least two, and at least one of those is some kind of liquid.

Sure enough, this tung-i ramen came with two packets, one that looked like a traditional flavor packet and one containing an oily liquid that seemed to be the combination of something black and something orange that was viscous and might have had some kind of jellyish pellet thing going.

Another potentially interesting thing to note is that the noodles appeared to be pre-seasoned right out of the packet. I noticed red powder on them.

The directions kind of annoyed me on this one. They went a bit like this:

  1. cook noodles like usual (okay, I can handle that)
  2. be sure not to overcook them, stupid! (er, okay, i can handle that too. more on this later)
  3. Pour out 4/5 of the water into a bowl (sure, that's what I norma--hey what? into a bowl? I usually get rid of it)
  4. mix in the regular looking packet into the bowl (but... okay, whatever you say)
  5. now mix in the weird liquid packet into the noodles still in the pot
  6. hooray! now you have chah chiang noodles and a separate "tasty soup"!

I don't want a tasty soup. I want flavored noodles! Whatever. I tried it their way, found that the "tasty soup" was weak broth since either I had cooked the noodles in my usual amount of water or because they gave me too little flavoring, and dumped out the water.

I did mix the chah chiang stuff in with the noodles, and that yielded roughly what I was used to. Ah, but something else annoying was that I think the noodles were a bit weak. With the normal amount of cooking that I usually do, which is also monitored by me, they got wimpy and overcooked. Go figure.

Verdict

I'd eat them again in an pinch, but I think next time I will use less water, cook the noodles in the broth mix, and be careful to cook the noodles less. I've had a few other kinds of weird ramen, and on a scale of "yuck" to "woot", I give this a "meh".

20091009

XmlArray and Bags

In the previous post, I was talking about the aliases feature, which lets you use short commands to expand to longer ones. One useful feature here is that aliases that you enter are saved along with your preferences, so essentially they're remembered. It turns out that I probably spent about half of the time from this feature on getting loading and saving correctly. It's not that loading and saving things like aliases (basically pairs of strings with no outward dependencies) is difficult. It's more like I am vulnerable to feature creep.

The object that is directly responsible for loading the aliases is called CharacterPreferences. The ConnectionManager, which has the primary responsibility of entering and exiting the character from the world, holds the prefs object and tells it to start loading. So the preferences are stored alongside the character data in the file, but are managed separately since they're the player's settings, not the character's.

Just for example, the data in the file looks like this.

<preferences>
  <prompt>jointface&gt;</prompt>
  <aliases>
    <alias str='greet' cmd='t &1 hello sir, how are you doing?' order='0'/>
    <alias str='gre' cmd='greet' order='1'/>
  </aliases>
</preferences>

You can imagine other types of preferences being stored here eventually, such as color preferences and settings related to verbosity in messages.

The loading system on the MUD is nice at times and maybe too complicated at other times. I wanted alias loading to hook into the loading system in a fairly hands-off way, since these particular objects are pretty simple. Let me review loading briefly to frame the content. This is review from a more complete post from ages ago.

Things on the MUD can be XmlReadable and optionally also PartiallyLoadable. If something is XmlReadable, it means it implements fromXmlTree, whose contract is to take an XML element as input and produce an instance of the object as output. If the object is also PartiallyLoadable, then at this point it may not be fully loaded; certain members might be stubbed out due to unresolved dependencies.

In this partially loaded state, the loading system enumerates a set of "properties" in the member that are added in the ctor. These properties say the XPath needed to get to this property, given the element representing the parent, information about how to construct an instance of this object (in most cases, just its class name, upon which the ctor will be invoked), and instructions on what to do if the sub-element isn't found at all (usually just create a blank new instance of the object).

This partially loaded state is absolutely essential to the process of resolving circular dependencies. While it doesn't support true cycles, it will handle cycles that terminate at some depth by allowing repeated passes over all of the properties in all of the objects that aren't fully loaded.

The ideal case is to have a property on the CharacterPreferences object that points to the "aliases" element within the preferences element, and delegate the loading of multiple aliases to some other object, since there are plenty of situations where you need to load a bunch of objects with the same name and type. Additionally, the specifics of the type of the objects that should be loaded multiple times should be abstracted from the code that does the repeated loading.

Introducing XmlArray

This is where XmlArray comes in. Exactly what it sounds like, this represents an array of things in XML, knows how to load them into a real Ruby array, and doesn't know too much about the objects themselves. Using this, the preferences object can fully delegate the loading of aliases to an XmlArray by creating one that knows to enumerate all the "alias" elements and create an AliasCommand object for each one.

Need some class properties

One interesting limitation I came upon as I started to design this is that the property system is fairly heavily based on the class of the object that a property is reponsible for. For example, check out the property responsible for loading the prompt text from above.

addProp(PersistedProperty.new('prompt', ref(:promptText), String))

I declare that the name of the element needed is "prompt", that it is of type String, and in order to read or write its current value, I access the methods promptText() and promptText=(). String isn't a great example here, but it gives you a feel for the tie to the class.

Likewise, for the XmlArray, when the loading system creates it automatically in response to encountering the property, the class it creates needs to know some information. The information it needs to know is the class of the object it will create for each element and the element name in the array to look for.

The way I solve this is a little bit similar to C++ templates or C# generics. I put in functionality to make XmlArray not really a class itself, but a class generator, where the classes it generates have this extra information embedded.

    def self.newClassWithType(element_name, type)
        return newClassWithCreator(element_name) { |el|
            XmlReader.constructFromElement(el, type, true)
        }
    end # function newClassWithType

    def self.newClassWithCreator(element_name, &creator)
        tmp = Class.new(self)
        tmp.procCreator = creator
        tmp.element_name = element_name
        return tmp
    end # function newClassWithCreator

Notice that these functions aren't member functions; they're class methods (since they're declared on the class - see the self). The magic is the use of Class.new(self), which creates a new subclass of the current class, upon which I then set some members that apply for all objects created from that class. The other nice thing is that these "subclasses" are exactly that - new instances of them are still true for thing.kind_of?(XmlArray).

When I was trying to figure out how to get this class nonsense to work, I thought the way to do it was using metaclasses. I stll don't understand metaclasses well, but I get the feeling they aren't the right tool for the job.

And then here's how someone might consume the XmlArray. This creates a property that is an array that will load up all of the elements named alias under the element aliases, and each element will be loaded as type AliasCommand.

@xarrClass = XmlArray.newClassWithType('alias', AliasCommand)
addProp(PersistedProperty.new('aliases', ref(:aliasesForLoading), @xarrClass, PersistedProperty::UseNewTypeFallback))

Array properties

Not only is the XmlArray typically consumed as a property, it implements its own loading functionality using properties. It has to, in fact, in order to get automatic resolution of dependencies. It uses a cool trick to do this.

Recall from earlier in the post that the loading code first calls fromXmlTree to let the object instantiate itself from the element, then enumerates all the properties and loads them one by one. XmlArray takes advantage of this by using the fromXmlTree call to make an early pass over all the elements and add a property in place for each one. Once the placeholders are there, the loading system will automatically take care of loading them, since they're in the property list.

Put it all in a Bag

Midway through designing and coding this up, I noticed that another component of the MUD was doing almost the same thing: bags. Bags are containers represented in the MUD world that hold real MUD objects inside (like dealies, mobs, etc.). Functionally, they're basically sets. Bags have to load from XML (e.g. the nipics that spawn in a room) and save to XML (the contents of your inventory). The requirements are roughly the same, so it would be great to abstract away the loading and saving code into an XmlArray.

A lot of the work I went through was getting these to work with Bags. The idea and design is pretty straightforward, but like all things loading, there are gotchas.

@xarrClass = XmlArray.newClassWithCreator('item') { |el|
    XmlReader.constructFromElement(el, (@restrictClass || Object), false)
}

addProp(PersistedProperty.new('.', ref(:contentForLoading), @xarrClass, PersistedProperty::UseNewTypeFallback))

The somewhat clever part here is that the array loads from ".", i.e. the current node in XML, rather than some sub-element. I ended up having to fix some silly little bugs like not being able to load the XmlArray if the bag containing it isn't specified in the XML (duh! if the bag isn't there, of course any sub-elements aren't there too).

And since I didn't actually use the array as the backing store for the elements in the bag (since bags use sets as their underlying storage, not arrays), I had to make sure to sync content between the bag proper and the "loading/saving contents" at the right times. In contrast, the CharacterPreferences does use the loading array as the actual storage, so it is always up to date.

The other reason this work took so long is that I kept taking these detours to fix other loading bugs or add little enhancements here and there. For instance, I fixed the code that gives every object that is loaded one and only one call back to loadingComplete, which they can optionally implement. And it turns out to be a convenient way to clean up or finalize structures after loading is all done.

For next time I plan to talk about the testing I did to make sure things kept working.

My Fake Name (Alias)

Over the last several weeks (actually, over a month - I just checked svn log) I've been working on implementing aliases. Yes, yet another amenity rather than an actual MUD feature. Implementing actual MUD features are really hard, because they have all kinds of pesky considerations like game design and entertainment value. If I just stick to adding little dinkies like this, I won't have to bother with all that heavy stuff.

My other rationalization is that every feature I implement expands the flexibility of the codebase as a whole. As you'll see in either this post or another (depending on length), I added quite a bit of other functionality to make aliases work well. I tell myself that this extra flexbility will come in handy someday when I get to implementing the real MUD features.

What thems are

Aliases are a way to type a (usually short) command and have it turn into a different (usually longer) command. They're actually a pretty fundamental part of MUDding, and can be used for a lot of useful things. For instance, when I used to PK (nowadays called PvP in all the cool kids games), when I knew whom I was fighting, I'd make an alias mapping the single letter "j" to the command to initiate an attack on him. In this way, I wouldn't even have to move my hand off of the home row of the keyboard to start attacking. Aliases made that possible.

Client vs. server side aliases

Actually, with a sufficiently advanced client program, aliases are more or less obsolete on the MUD. I used to use MUSHclient, a very fully featured MUD client. It had enough customization that anything I could do alias-wise with the help of the MUD (on the server side) I could do at least as well on the client side. Still, sometimes I was away from home and had to use old fashioned telnet, so it's good that server side aliases existed anyway.

As a quick aside, MUSHclient's killer feature in my eyes was the ability to wire it up directly to a perl script that could essentially automate it for you. Sure, you could write perl to be a full telnet client for you, but MUSHclient let you relinquish control temporarily to the script by typing special commands. I'll admit, I wrote at least one script that after I'd left went on to be banned by that particular MUD's administration... but that's a story for another time.

Back to what thems are

The fundamental components of an alias are the command you type in and the string that it gets expanded to. I think many MUDs (stock ROM, for example) leave it at that. I went a little further, for fun. In my MUD, the expanded command can take arguments, too. Since non-programmers don't know or care what arguments are, I'll illustrate what this means with some MUD output.

> alias donate tell &2 Here you go, my good man, have &1 dollars.
Added alias donate => tell &2 Here you go, my good man, have &1 dollars..

> donate 5 man
You tell a man, 'Here you go, my good man, have 5 dollars.'

Here I've added an alias where I can tell someone about some money I'm giving them. The &1 and &2 are placeholders that correspond to the first and second things I type after donate, respectively. Those are called "arguments to donate". I have no idea how that word came about there. The &1 and &2 are inserted into the resultant text in the places I indicated.

Along with the ability to add aliases, it's expected to be able to overwrite them, remove them, and list them. Using the alias command like before, you can do exactly this.

> alias
Aliases:
  donate => tell &2 Here you go, my good man, have &1 dollars.

> alias donate
donate => tell &2 Here you go, my good man, have &1 dollars.

> alias donate ''
Removed alias donate.

And since they are often useful over the long term, they save to your playerfile in your preferences, so they're there automatically whenever you load up.

The rest of this post is more about how they were implemented and what happened along the way. As maybe you can tell, there are a few distinct parts involved in this work: the alias processing, the alias management command, and persistence.

Interpreter changes - lockout

The alias processing had a few interesting parts. For one, it required hooks in the interpreter to make it work. Normally, the interpreter has a single list of commands that it looks up in when you type in a command. Now, for a few reasons, I wanted to make it store aliases separately in another list.

  • I wanted alias lookup to follow different rules than normal command lookup
  • I wanted to be able to show a list of all the built-in commands separately, with the commands command
  • I wanted to be able to display the list of only aliases in the alias command
  • I wanted to be able to add and remove to the alias list without having to traverse or manipulate the list of built-in commands.

Most of those reasons are relatively self-explanatory, but the first one bears more detail. The fundamental difference between how aliases are looked up and how built-in commands are looked up is that aliases use an exact match and builtins use a prefix match. So you can type "al" and get the alias command above, but you can't type "don" and expect to get the donate alias from above.

The prime example I used in my rationale here is making aliases for the alias command itself. It's of utmost importance that users not be able to lock themselves out of getting to the alias command, because then they've have no way of removing a faulty alias.

Consider what would happen if I did prefix matching for aliases. What if the user made an alias named "alias"? Well, maybe the lookup could prefer the builtin there. What about one named "alia"? And then what if they type in "al"? The prefix matches both the alias "alia" and the built-in alias command. It's not obvious in the general case (for other built-in commands) which precedence should take place.

So the interpreter first tries an exact match against the alias list, then a prefix match of the builtins list. In the end I think this will yield the least confusing behavior.

Infinite recursion in the interpreter

Whenever you talk about aliases, the possibility of infinite recursion becomes very real. What happens when I map an alias back to itself?

> al
Aliases:
  abc => def
  def => abc

Oh noes! Danger lurks! Unchecked, this will keep expanding forever. Why would I even want to allow this? Well, it could be convenient. For instance, in the previous section, I talked about how we don't prefix match aliases. Well, with recursive alias mapping the user can do this.

> alias
Aliases:
  donate => tell &2 Here you go, my good man, have &1 dollars.
  don => donate

Now the player can type "don" to get the donate alias. I'd say that's useful enough to keep around. So how to combat against the recursion problem? My solution isn't terribly elegant, unfortunately. The interpreter just keeps track of how deep it is in lookup and bails out if it gets too deep.

def interpret(line)
    if interpretDepth() > MaxInterpretDepth
        DebugOutput.debugOut("possibly recursive alias or command", LWarning, LCommand)
        return nil
    end

    self.interpretDepth = interpretDepth() + 1

    # do lookup and recursive resolution of alias...

    self.interpretDepth = interpretDepth() - 1
    return ret
end # function interpret

Since the depth is stored as a member variable of the interpreter, there are obvious problems if the interpreter ever needs to be accessed, say, on two separate threads. I'll cross that bridge when I come to it.

Alias management

This is actually pretty straightforward. This class of commands is in line with other "management" commands like who, and so already has access to all the objects it needs to manage the list of aliases. Boooooring.

As I start to write the section about persistence, I'm getting the feeling it could use a post all to itself, so that's what I'm going to do. And I feel the urge to talk at least a bit about the way I did testing during this work, because I feel I did some neat things. So maybe that will get a post too.

I have a strange relationship with blogging. I like coding, but I also like blogging about what I did. Sometimes when I near the completion of a feature, I look forward to the blog post I'll write about it soon as a kind of reward for the work I did over the last weeks. One might ask why I don't just blog whenever I want. Well, for this sort of blog post I only feel like I want to when I have something concrete and complete to talk about. I don't really like presenting something that's obviously (to me) half baked or incomplete. As we call it at work, I like to be "code complete" before talking much about it.

20091008

standards mode

Huh, I just noticed that my blog wasn't being rendered in IE8 standards mode, but rather in IE7 compatibility mode. I pretty much wrote this blog to work in IE8 mode, so it's silly that it doesn't. Since the compatibility view button doesn't show up, it means that something has decided to put that site into compatibility mode automatically for me.

Since my user-controlled compat list is empty, the other likely culprit is the automatic list of domains pushed down by microsoft periodically. I bet that blogspot.com is in the compat list. I guess that makes sense, since there are probably a lot of blogs on here that were written for IE7.

Anyway, with the addition of the X-UA-Compatible meta tag, now it's actually in "edge" mode, since I will probably keep it up to date with IE.

potentially little known fact: you can actually specify multiple meta tags in your document. The correct usage is basically to declare what versions of IE you have tested your page with. IE will automatically use the latest mode that it supports. This is kind of an academic point until the day when there are multiple versions of IE out that support the x-ua-compatible meta tag.

links came from the compat view IE blog post.