The code for generating the checklist is written in Factor. Factor is rather outside the mainstream, but it works beautifully for my purposes.

I store the checklist data in a text file that looks like the following:

    FAMILY: Wrens
        SPECIES: Carolina Wren
            STATUS: Fairly common year-round. Can be found almost anywhere. There's too many around to not be breeding, but I have yet to find a nest.

        SPECIES: House Wren
            STATUS: Regular in spring and summer. Possible almost anywhere. Young at Paine Estate imply breeding.

        SPECIES: Winter Wren
            STATUS: Probably a rare visitor, possible at all seasons?
            RECORD: M. Emmons, 5/14/1997, ? , 2
            RECORD: R. Haaseth/D. Finch, 2/6/2004, near Prospect Hill, 1
            RECORD: R. Haaseth/D. Finch, 10/2005, ?, 1
            PERSONAL: 11/25/2006, Met State, 1

        SPECIES: Marsh Wren
            STATUS: Likely a regular migrant and possibly a breeder at Waverly Oaks Marsh.
            RECORD: M. Rines, 4/20/2006, Waverly Oaks Marsh, 1

    HYPOTHETICAL: White-winged Crossbill | irruptive species
    HYPOTHETICAL: Hoary Redpoll | irruptive species

    HISTORICAL: Black Vulture | coming soon
    HISTORICAL: Boreal Chickadee | coming soon
    HISTORICAL: Louisiana Waterthrush | coming soon

This is a nice clean structure that is easy to parse. In fact, the code to parse it is "USE: checklistn" swap dup file-length swap <file-reader> stream-read append parse call ; Yep, the checklist is actually code that parses itself.

To do this, I defined a few tuples:

  • TUPLE: checklist confirmed hypotheticals historicals
  • TUPLE: family name species
  • TUPLE: species name status breeding records
  • TUPLE: record observer date place quantity
  • TUPLE: historical name details
  • TUPLE: hypothetical name reason

Each one simply contains the data and stores anything below it in a vector. I then defined a bunch of words that handle the parsing.

  • CHECKLIST: creates a new checklist
  • FAMILY: creates a new family and gives it the name of whatever is on the line
  • SPECIES: does the same for species
  • STATUS: stores the status
  • PB and CB store the breeding
  • RECORD: and PERSONAL: create and store records (PERSONAL: creates a record with observer “me”)
  • END is a generic word that adds the species or family to the family or checklist respectively (I’d like to define it for the checklist as well, but the stack effects don’t match so it doesn’t work)
  • HISTORICAL: and HYPOTHETICAL: do the obvious

For the most part, defining those was straightforward. The trickiest part was dealing with status, as I had to be able to call a word for the next object on the stack after reading the status in. I ended up with the ugly

: STATUS: rest-of-line swap ?push  swap swap ?push  set-species-status V{ } clone [ push ] keep >quotation swap ?push  keep swap ?push ; parsing

mess. I’m sure there’s a better way but that took a couple days to get and I haven’t fiddled with it since.

Getting from the checklist tuple to the output was pretty easy, just doing lots of formatting with make and working down.